/tortoisehg/hgqt/webconf.py
Python | 336 lines | 318 code | 10 blank | 8 comment | 6 complexity | 758a9b615e7c02d7fc241cd2ad95a6b8 MD5 | raw file
Possible License(s): GPL-2.0
- # webconf.py - Widget to show/edit hgweb config
- #
- # Copyright 2010 Yuya Nishihara <yuya@tcha.org>
- #
- # This software may be used and distributed according to the terms of the
- # GNU General Public License version 2, incorporated herein by reference.
- import os
- from PyQt4.QtCore import *
- from PyQt4.QtGui import *
- from tortoisehg.util import hglib, wconfig
- from tortoisehg.hgqt import qtlib
- from tortoisehg.hgqt.i18n import _
- from tortoisehg.hgqt.webconf_ui import Ui_WebconfForm
- _FILE_FILTER = _('Config files (*.conf *.config *.ini);;Any files (*)')
- class WebconfForm(QWidget):
- """Widget to show/edit webconf"""
- def __init__(self, parent=None, webconf=None):
- super(WebconfForm, self).__init__(parent, acceptDrops=True)
- self._qui = Ui_WebconfForm()
- self._qui.setupUi(self)
- self._initicons()
- self._qui.path_edit.currentIndexChanged.connect(self._updateview)
- self._qui.path_edit.currentIndexChanged.connect(self._updateform)
- self._qui.add_button.clicked.connect(self._addpathmap)
- self.setwebconf(webconf or wconfig.config())
- self._updateform()
- def _initicons(self):
- def setstdicon(w, name):
- w.setIcon(self.style().standardIcon(name))
- setstdicon(self._qui.open_button, QStyle.SP_DialogOpenButton)
- setstdicon(self._qui.save_button, QStyle.SP_DialogSaveButton)
- self._qui.add_button.setIcon(qtlib.geticon('fileadd'))
- self._qui.edit_button.setIcon(qtlib.geticon('fallback')) # TODO
- self._qui.remove_button.setIcon(qtlib.geticon('filedelete'))
- def dragEnterEvent(self, event):
- if self._getlocalpath_from_dropevent(event):
- event.setDropAction(Qt.LinkAction)
- event.accept()
- def dropEvent(self, event):
- localpath = self._getlocalpath_from_dropevent(event)
- if localpath:
- event.setDropAction(Qt.LinkAction)
- event.accept()
- self._addpathmap(localpath=localpath)
- @staticmethod
- def _getlocalpath_from_dropevent(event):
- m = event.mimeData()
- if m.hasFormat('text/uri-list') and len(m.urls()) == 1:
- return unicode(m.urls()[0].toLocalFile())
- def setwebconf(self, webconf):
- """set current webconf object"""
- path = hglib.tounicode(getattr(webconf, 'path', None) or '')
- i = self._qui.path_edit.findText(path)
- if i < 0:
- i = 0
- self._qui.path_edit.insertItem(i, path, webconf)
- self._qui.path_edit.setCurrentIndex(i)
- @property
- def webconf(self):
- """current webconf object"""
- def curconf(w):
- i = w.currentIndex()
- _path, conf = unicode(w.itemText(i)), w.itemData(i).toPyObject()
- return conf
- return curconf(self._qui.path_edit)
- @property
- def _webconfmodel(self):
- """current model object of webconf"""
- return self._qui.repos_view.model()
- @pyqtSlot()
- def _updateview(self):
- m = WebconfModel(config=self.webconf, parent=self)
- self._qui.repos_view.setModel(m)
- self._qui.repos_view.selectionModel().currentChanged.connect(
- self._updateform)
- def _updateform(self):
- """Update availability of each widget"""
- self._qui.repos_view.setEnabled(hasattr(self.webconf, 'write'))
- self._qui.add_button.setEnabled(hasattr(self.webconf, 'write'))
- self._qui.edit_button.setEnabled(
- hasattr(self.webconf, 'write')
- and self._qui.repos_view.currentIndex().isValid())
- self._qui.remove_button.setEnabled(
- hasattr(self.webconf, 'write')
- and self._qui.repos_view.currentIndex().isValid())
- @pyqtSlot()
- def on_open_button_clicked(self):
- path = QFileDialog.getOpenFileName(
- self, _('Open hgweb config'),
- getattr(self.webconf, 'path', None) or '', _FILE_FILTER)
- if path:
- self.openwebconf(path)
- def openwebconf(self, path):
- """load the specified webconf file"""
- path = hglib.fromunicode(path)
- c = wconfig.readfile(path)
- c.path = os.path.abspath(path)
- self.setwebconf(c)
- @pyqtSlot()
- def on_save_button_clicked(self):
- path = QFileDialog.getSaveFileName(
- self, _('Save hgweb config'),
- getattr(self.webconf, 'path', None) or '', _FILE_FILTER)
- if path:
- self.savewebconf(path)
- def savewebconf(self, path):
- """save current webconf to the specified file"""
- path = hglib.fromunicode(path)
- wconfig.writefile(self.webconf, path)
- self.openwebconf(path) # reopen in case file path changed
- @pyqtSlot()
- def _addpathmap(self, path=None, localpath=None):
- path, localpath = _PathDialog.getaddpathmap(
- self, path=path, localpath=localpath,
- invalidpaths=self._webconfmodel.paths)
- if path:
- self._webconfmodel.addpathmap(path, localpath)
- @pyqtSlot()
- def on_edit_button_clicked(self):
- self.on_repos_view_doubleClicked(self._qui.repos_view.currentIndex())
- @pyqtSlot(QModelIndex)
- def on_repos_view_doubleClicked(self, index):
- assert index.isValid()
- origpath, origlocalpath = self._webconfmodel.getpathmapat(index.row())
- path, localpath = _PathDialog.geteditpathmap(
- self, path=origpath, localpath=origlocalpath,
- invalidpaths=set(self._webconfmodel.paths) - set([origpath]))
- if not path:
- return
- if path != origpath:
- # we cannot change config key without reordering
- self._webconfmodel.removepathmap(origpath)
- self._webconfmodel.addpathmap(path, localpath)
- else:
- self._webconfmodel.setpathmap(path, localpath)
- @pyqtSlot()
- def on_remove_button_clicked(self):
- index = self._qui.repos_view.currentIndex()
- assert index.isValid()
- path, _localpath = self._webconfmodel.getpathmapat(index.row())
- self._webconfmodel.removepathmap(path)
- class _PathDialog(QDialog):
- """Dialog to add/edit path mapping"""
- def __init__(self, title, acceptlabel, path=None, localpath=None,
- invalidpaths=None, parent=None):
- super(_PathDialog, self).__init__(parent)
- self.setWindowFlags((self.windowFlags() | Qt.WindowMinimizeButtonHint)
- & ~Qt.WindowContextHelpButtonHint)
- self.resize(QFontMetrics(self.font()).width('M') * 50, self.height())
- self.setWindowTitle(title)
- self._invalidpaths = set(invalidpaths or [])
- self.setLayout(QFormLayout(fieldGrowthPolicy=QFormLayout.ExpandingFieldsGrow))
- self._initfields()
- self._initbuttons(acceptlabel)
- self._path_edit.setText(path or os.path.basename(localpath or ''))
- self._localpath_edit.setText(localpath or '')
- self._updateform()
- def _initfields(self):
- """initialize input fields"""
- def addfield(key, label, *extras):
- edit = QLineEdit(self)
- edit.textChanged.connect(self._updateform)
- if extras:
- field = QHBoxLayout()
- field.addWidget(edit)
- for e in extras:
- field.addWidget(e)
- else:
- field = edit
- self.layout().addRow(label, field)
- setattr(self, '_%s_edit' % key, edit)
- addfield('path', _('Path:'))
- self._localpath_browse_button = QToolButton(
- icon=self.style().standardIcon(QStyle.SP_DialogOpenButton))
- addfield('localpath', _('Local Path:'), self._localpath_browse_button)
- self._localpath_browse_button.clicked.connect(self._browse_localpath)
- def _initbuttons(self, acceptlabel):
- """initialize dialog buttons"""
- self._buttons = QDialogButtonBox(self)
- self._accept_button = self._buttons.addButton(QDialogButtonBox.Ok)
- self._reject_button = self._buttons.addButton(QDialogButtonBox.Cancel)
- self._accept_button.setText(acceptlabel)
- self._buttons.accepted.connect(self.accept)
- self._buttons.rejected.connect(self.reject)
- self.layout().addRow(self._buttons)
- @property
- def path(self):
- """value of path field"""
- return unicode(self._path_edit.text())
- @property
- def localpath(self):
- """value of localpath field"""
- return unicode(self._localpath_edit.text())
- @pyqtSlot()
- def _browse_localpath(self):
- path = QFileDialog.getExistingDirectory(self, _('Select Repository'),
- self.localpath)
- if not path:
- return
- self._localpath_edit.setText(path)
- if not self.path:
- self._path_edit.setText(os.path.basename(unicode(path)))
- @pyqtSlot()
- def _updateform(self):
- """update availability of form elements"""
- self._accept_button.setEnabled(self._isacceptable())
- def _isacceptable(self):
- return bool(self.path and self.localpath
- and self.path not in self._invalidpaths)
- @classmethod
- def getaddpathmap(cls, parent, path=None, localpath=None, invalidpaths=None):
- d = cls(title=_('Add Path to Serve'), acceptlabel=_('Add'),
- path=path, localpath=localpath,
- invalidpaths=invalidpaths, parent=parent)
- if d.exec_():
- return d.path, d.localpath
- else:
- return None, None
- @classmethod
- def geteditpathmap(cls, parent, path=None, localpath=None, invalidpaths=None):
- d = cls(title=_('Edit Path to Serve'), acceptlabel=_('Edit'),
- path=path, localpath=localpath,
- invalidpaths=invalidpaths, parent=parent)
- if d.exec_():
- return d.path, d.localpath
- else:
- return None, None
- class WebconfModel(QAbstractTableModel):
- """Wrapper for webconf object to be a Qt's model object"""
- _COLUMNS = [(_('Path'),),
- (_('Local Path'),)]
- def __init__(self, config, parent=None):
- super(WebconfModel, self).__init__(parent)
- self._config = config
- def data(self, index, role):
- if not index.isValid():
- return None
- if role == Qt.DisplayRole:
- v = self._config.items('paths')[index.row()][index.column()]
- return hglib.tounicode(v)
- return None
- def rowCount(self, parent=QModelIndex()):
- if parent.isValid():
- return 0 # no child
- return len(self._config['paths'])
- def columnCount(self, parent=QModelIndex()):
- if parent.isValid():
- return 0 # no child
- return len(self._COLUMNS)
- def headerData(self, section, orientation, role):
- if role != Qt.DisplayRole or orientation != Qt.Horizontal:
- return None
- return self._COLUMNS[section][0]
- @property
- def paths(self):
- """return list of known paths"""
- return [hglib.tounicode(e) for e in self._config['paths']]
- def getpathmapat(self, row):
- """return pair of (path, localpath) at the specified index"""
- assert 0 <= row and row < self.rowCount()
- return tuple(hglib.tounicode(e) for e in self._config.items('paths')[row])
- def addpathmap(self, path, localpath):
- """add path mapping to serve"""
- assert path not in self.paths
- self.beginInsertRows(QModelIndex(), self.rowCount(), self.rowCount())
- try:
- self._config.set('paths', hglib.fromunicode(path),
- hglib.fromunicode(localpath))
- finally:
- self.endInsertRows()
- def setpathmap(self, path, localpath):
- """change path mapping at the specified index"""
- self._config.set('paths', hglib.fromunicode(path),
- hglib.fromunicode(localpath))
- row = self._indexofpath(path)
- self.dataChanged.emit(self.index(row, 0),
- self.index(row, self.columnCount()))
- def removepathmap(self, path):
- """remove path from mapping"""
- row = self._indexofpath(path)
- self.beginRemoveRows(QModelIndex(), row, row)
- try:
- del self._config['paths'][hglib.fromunicode(path)]
- finally:
- self.endRemoveRows()
- def _indexofpath(self, path):
- path = hglib.fromunicode(path)
- assert path in self._config['paths']
- return list(self._config['paths']).index(path)