PageRenderTime 95ms CodeModel.GetById 13ms RepoModel.GetById 1ms app.codeStats 0ms

/tortoisehg/hgqt/webconf.py

https://bitbucket.org/tortoisehg/hgtk/
Python | 336 lines | 318 code | 10 blank | 8 comment | 6 complexity | 758a9b615e7c02d7fc241cd2ad95a6b8 MD5 | raw file
Possible License(s): GPL-2.0
  1. # webconf.py - Widget to show/edit hgweb config
  2. #
  3. # Copyright 2010 Yuya Nishihara <yuya@tcha.org>
  4. #
  5. # This software may be used and distributed according to the terms of the
  6. # GNU General Public License version 2, incorporated herein by reference.
  7. import os
  8. from PyQt4.QtCore import *
  9. from PyQt4.QtGui import *
  10. from tortoisehg.util import hglib, wconfig
  11. from tortoisehg.hgqt import qtlib
  12. from tortoisehg.hgqt.i18n import _
  13. from tortoisehg.hgqt.webconf_ui import Ui_WebconfForm
  14. _FILE_FILTER = _('Config files (*.conf *.config *.ini);;Any files (*)')
  15. class WebconfForm(QWidget):
  16. """Widget to show/edit webconf"""
  17. def __init__(self, parent=None, webconf=None):
  18. super(WebconfForm, self).__init__(parent, acceptDrops=True)
  19. self._qui = Ui_WebconfForm()
  20. self._qui.setupUi(self)
  21. self._initicons()
  22. self._qui.path_edit.currentIndexChanged.connect(self._updateview)
  23. self._qui.path_edit.currentIndexChanged.connect(self._updateform)
  24. self._qui.add_button.clicked.connect(self._addpathmap)
  25. self.setwebconf(webconf or wconfig.config())
  26. self._updateform()
  27. def _initicons(self):
  28. def setstdicon(w, name):
  29. w.setIcon(self.style().standardIcon(name))
  30. setstdicon(self._qui.open_button, QStyle.SP_DialogOpenButton)
  31. setstdicon(self._qui.save_button, QStyle.SP_DialogSaveButton)
  32. self._qui.add_button.setIcon(qtlib.geticon('fileadd'))
  33. self._qui.edit_button.setIcon(qtlib.geticon('fallback')) # TODO
  34. self._qui.remove_button.setIcon(qtlib.geticon('filedelete'))
  35. def dragEnterEvent(self, event):
  36. if self._getlocalpath_from_dropevent(event):
  37. event.setDropAction(Qt.LinkAction)
  38. event.accept()
  39. def dropEvent(self, event):
  40. localpath = self._getlocalpath_from_dropevent(event)
  41. if localpath:
  42. event.setDropAction(Qt.LinkAction)
  43. event.accept()
  44. self._addpathmap(localpath=localpath)
  45. @staticmethod
  46. def _getlocalpath_from_dropevent(event):
  47. m = event.mimeData()
  48. if m.hasFormat('text/uri-list') and len(m.urls()) == 1:
  49. return unicode(m.urls()[0].toLocalFile())
  50. def setwebconf(self, webconf):
  51. """set current webconf object"""
  52. path = hglib.tounicode(getattr(webconf, 'path', None) or '')
  53. i = self._qui.path_edit.findText(path)
  54. if i < 0:
  55. i = 0
  56. self._qui.path_edit.insertItem(i, path, webconf)
  57. self._qui.path_edit.setCurrentIndex(i)
  58. @property
  59. def webconf(self):
  60. """current webconf object"""
  61. def curconf(w):
  62. i = w.currentIndex()
  63. _path, conf = unicode(w.itemText(i)), w.itemData(i).toPyObject()
  64. return conf
  65. return curconf(self._qui.path_edit)
  66. @property
  67. def _webconfmodel(self):
  68. """current model object of webconf"""
  69. return self._qui.repos_view.model()
  70. @pyqtSlot()
  71. def _updateview(self):
  72. m = WebconfModel(config=self.webconf, parent=self)
  73. self._qui.repos_view.setModel(m)
  74. self._qui.repos_view.selectionModel().currentChanged.connect(
  75. self._updateform)
  76. def _updateform(self):
  77. """Update availability of each widget"""
  78. self._qui.repos_view.setEnabled(hasattr(self.webconf, 'write'))
  79. self._qui.add_button.setEnabled(hasattr(self.webconf, 'write'))
  80. self._qui.edit_button.setEnabled(
  81. hasattr(self.webconf, 'write')
  82. and self._qui.repos_view.currentIndex().isValid())
  83. self._qui.remove_button.setEnabled(
  84. hasattr(self.webconf, 'write')
  85. and self._qui.repos_view.currentIndex().isValid())
  86. @pyqtSlot()
  87. def on_open_button_clicked(self):
  88. path = QFileDialog.getOpenFileName(
  89. self, _('Open hgweb config'),
  90. getattr(self.webconf, 'path', None) or '', _FILE_FILTER)
  91. if path:
  92. self.openwebconf(path)
  93. def openwebconf(self, path):
  94. """load the specified webconf file"""
  95. path = hglib.fromunicode(path)
  96. c = wconfig.readfile(path)
  97. c.path = os.path.abspath(path)
  98. self.setwebconf(c)
  99. @pyqtSlot()
  100. def on_save_button_clicked(self):
  101. path = QFileDialog.getSaveFileName(
  102. self, _('Save hgweb config'),
  103. getattr(self.webconf, 'path', None) or '', _FILE_FILTER)
  104. if path:
  105. self.savewebconf(path)
  106. def savewebconf(self, path):
  107. """save current webconf to the specified file"""
  108. path = hglib.fromunicode(path)
  109. wconfig.writefile(self.webconf, path)
  110. self.openwebconf(path) # reopen in case file path changed
  111. @pyqtSlot()
  112. def _addpathmap(self, path=None, localpath=None):
  113. path, localpath = _PathDialog.getaddpathmap(
  114. self, path=path, localpath=localpath,
  115. invalidpaths=self._webconfmodel.paths)
  116. if path:
  117. self._webconfmodel.addpathmap(path, localpath)
  118. @pyqtSlot()
  119. def on_edit_button_clicked(self):
  120. self.on_repos_view_doubleClicked(self._qui.repos_view.currentIndex())
  121. @pyqtSlot(QModelIndex)
  122. def on_repos_view_doubleClicked(self, index):
  123. assert index.isValid()
  124. origpath, origlocalpath = self._webconfmodel.getpathmapat(index.row())
  125. path, localpath = _PathDialog.geteditpathmap(
  126. self, path=origpath, localpath=origlocalpath,
  127. invalidpaths=set(self._webconfmodel.paths) - set([origpath]))
  128. if not path:
  129. return
  130. if path != origpath:
  131. # we cannot change config key without reordering
  132. self._webconfmodel.removepathmap(origpath)
  133. self._webconfmodel.addpathmap(path, localpath)
  134. else:
  135. self._webconfmodel.setpathmap(path, localpath)
  136. @pyqtSlot()
  137. def on_remove_button_clicked(self):
  138. index = self._qui.repos_view.currentIndex()
  139. assert index.isValid()
  140. path, _localpath = self._webconfmodel.getpathmapat(index.row())
  141. self._webconfmodel.removepathmap(path)
  142. class _PathDialog(QDialog):
  143. """Dialog to add/edit path mapping"""
  144. def __init__(self, title, acceptlabel, path=None, localpath=None,
  145. invalidpaths=None, parent=None):
  146. super(_PathDialog, self).__init__(parent)
  147. self.setWindowFlags((self.windowFlags() | Qt.WindowMinimizeButtonHint)
  148. & ~Qt.WindowContextHelpButtonHint)
  149. self.resize(QFontMetrics(self.font()).width('M') * 50, self.height())
  150. self.setWindowTitle(title)
  151. self._invalidpaths = set(invalidpaths or [])
  152. self.setLayout(QFormLayout(fieldGrowthPolicy=QFormLayout.ExpandingFieldsGrow))
  153. self._initfields()
  154. self._initbuttons(acceptlabel)
  155. self._path_edit.setText(path or os.path.basename(localpath or ''))
  156. self._localpath_edit.setText(localpath or '')
  157. self._updateform()
  158. def _initfields(self):
  159. """initialize input fields"""
  160. def addfield(key, label, *extras):
  161. edit = QLineEdit(self)
  162. edit.textChanged.connect(self._updateform)
  163. if extras:
  164. field = QHBoxLayout()
  165. field.addWidget(edit)
  166. for e in extras:
  167. field.addWidget(e)
  168. else:
  169. field = edit
  170. self.layout().addRow(label, field)
  171. setattr(self, '_%s_edit' % key, edit)
  172. addfield('path', _('Path:'))
  173. self._localpath_browse_button = QToolButton(
  174. icon=self.style().standardIcon(QStyle.SP_DialogOpenButton))
  175. addfield('localpath', _('Local Path:'), self._localpath_browse_button)
  176. self._localpath_browse_button.clicked.connect(self._browse_localpath)
  177. def _initbuttons(self, acceptlabel):
  178. """initialize dialog buttons"""
  179. self._buttons = QDialogButtonBox(self)
  180. self._accept_button = self._buttons.addButton(QDialogButtonBox.Ok)
  181. self._reject_button = self._buttons.addButton(QDialogButtonBox.Cancel)
  182. self._accept_button.setText(acceptlabel)
  183. self._buttons.accepted.connect(self.accept)
  184. self._buttons.rejected.connect(self.reject)
  185. self.layout().addRow(self._buttons)
  186. @property
  187. def path(self):
  188. """value of path field"""
  189. return unicode(self._path_edit.text())
  190. @property
  191. def localpath(self):
  192. """value of localpath field"""
  193. return unicode(self._localpath_edit.text())
  194. @pyqtSlot()
  195. def _browse_localpath(self):
  196. path = QFileDialog.getExistingDirectory(self, _('Select Repository'),
  197. self.localpath)
  198. if not path:
  199. return
  200. self._localpath_edit.setText(path)
  201. if not self.path:
  202. self._path_edit.setText(os.path.basename(unicode(path)))
  203. @pyqtSlot()
  204. def _updateform(self):
  205. """update availability of form elements"""
  206. self._accept_button.setEnabled(self._isacceptable())
  207. def _isacceptable(self):
  208. return bool(self.path and self.localpath
  209. and self.path not in self._invalidpaths)
  210. @classmethod
  211. def getaddpathmap(cls, parent, path=None, localpath=None, invalidpaths=None):
  212. d = cls(title=_('Add Path to Serve'), acceptlabel=_('Add'),
  213. path=path, localpath=localpath,
  214. invalidpaths=invalidpaths, parent=parent)
  215. if d.exec_():
  216. return d.path, d.localpath
  217. else:
  218. return None, None
  219. @classmethod
  220. def geteditpathmap(cls, parent, path=None, localpath=None, invalidpaths=None):
  221. d = cls(title=_('Edit Path to Serve'), acceptlabel=_('Edit'),
  222. path=path, localpath=localpath,
  223. invalidpaths=invalidpaths, parent=parent)
  224. if d.exec_():
  225. return d.path, d.localpath
  226. else:
  227. return None, None
  228. class WebconfModel(QAbstractTableModel):
  229. """Wrapper for webconf object to be a Qt's model object"""
  230. _COLUMNS = [(_('Path'),),
  231. (_('Local Path'),)]
  232. def __init__(self, config, parent=None):
  233. super(WebconfModel, self).__init__(parent)
  234. self._config = config
  235. def data(self, index, role):
  236. if not index.isValid():
  237. return None
  238. if role == Qt.DisplayRole:
  239. v = self._config.items('paths')[index.row()][index.column()]
  240. return hglib.tounicode(v)
  241. return None
  242. def rowCount(self, parent=QModelIndex()):
  243. if parent.isValid():
  244. return 0 # no child
  245. return len(self._config['paths'])
  246. def columnCount(self, parent=QModelIndex()):
  247. if parent.isValid():
  248. return 0 # no child
  249. return len(self._COLUMNS)
  250. def headerData(self, section, orientation, role):
  251. if role != Qt.DisplayRole or orientation != Qt.Horizontal:
  252. return None
  253. return self._COLUMNS[section][0]
  254. @property
  255. def paths(self):
  256. """return list of known paths"""
  257. return [hglib.tounicode(e) for e in self._config['paths']]
  258. def getpathmapat(self, row):
  259. """return pair of (path, localpath) at the specified index"""
  260. assert 0 <= row and row < self.rowCount()
  261. return tuple(hglib.tounicode(e) for e in self._config.items('paths')[row])
  262. def addpathmap(self, path, localpath):
  263. """add path mapping to serve"""
  264. assert path not in self.paths
  265. self.beginInsertRows(QModelIndex(), self.rowCount(), self.rowCount())
  266. try:
  267. self._config.set('paths', hglib.fromunicode(path),
  268. hglib.fromunicode(localpath))
  269. finally:
  270. self.endInsertRows()
  271. def setpathmap(self, path, localpath):
  272. """change path mapping at the specified index"""
  273. self._config.set('paths', hglib.fromunicode(path),
  274. hglib.fromunicode(localpath))
  275. row = self._indexofpath(path)
  276. self.dataChanged.emit(self.index(row, 0),
  277. self.index(row, self.columnCount()))
  278. def removepathmap(self, path):
  279. """remove path from mapping"""
  280. row = self._indexofpath(path)
  281. self.beginRemoveRows(QModelIndex(), row, row)
  282. try:
  283. del self._config['paths'][hglib.fromunicode(path)]
  284. finally:
  285. self.endRemoveRows()
  286. def _indexofpath(self, path):
  287. path = hglib.fromunicode(path)
  288. assert path in self._config['paths']
  289. return list(self._config['paths']).index(path)