/tortoisehg/hgqt/filelistview.py

https://bitbucket.org/tortoisehg/hgtk/ · Python · 299 lines · 246 code · 32 blank · 21 comment · 47 complexity · fa62de7dc2297cc9dfa60b7219c4d4a7 MD5 · raw file

  1. # Copyright (c) 2009-2010 LOGILAB S.A. (Paris, FRANCE).
  2. # http://www.logilab.fr/ -- mailto:contact@logilab.fr
  3. #
  4. # This program is free software; you can redistribute it and/or modify it under
  5. # the terms of the GNU General Public License as published by the Free Software
  6. # Foundation; either version 2 of the License, or (at your option) any later
  7. # version.
  8. #
  9. # This program is distributed in the hope that it will be useful, but WITHOUT
  10. # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
  11. # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  12. #
  13. # You should have received a copy of the GNU General Public License along with
  14. # this program; if not, write to the Free Software Foundation, Inc.,
  15. # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
  16. import os
  17. from tortoisehg.util import hglib
  18. from tortoisehg.hgqt.i18n import _
  19. from tortoisehg.hgqt.qtlib import geticon
  20. from tortoisehg.hgqt.filedialogs import FileLogDialog, FileDiffDialog
  21. from tortoisehg.hgqt import visdiff, wctxactions, revert
  22. from PyQt4.QtCore import *
  23. from PyQt4.QtGui import *
  24. class HgFileListView(QTableView):
  25. """
  26. A QTableView for displaying a HgFileListModel
  27. """
  28. fileRevSelected = pyqtSignal(object, object, object)
  29. clearDisplay = pyqtSignal()
  30. contextmenu = None
  31. def __init__(self, parent=None):
  32. QTableView.__init__(self, parent)
  33. self.setShowGrid(False)
  34. self.horizontalHeader().hide()
  35. self.verticalHeader().hide()
  36. self.verticalHeader().setDefaultSectionSize(20)
  37. self.setSelectionMode(QAbstractItemView.SingleSelection)
  38. self.setSelectionBehavior(QAbstractItemView.SelectRows)
  39. self.setTextElideMode(Qt.ElideLeft)
  40. self.createActions()
  41. self.doubleClicked.connect(self.fileActivated)
  42. self._diff_dialogs = {}
  43. self._nav_dialogs = {}
  44. def setModel(self, model):
  45. QTableView.setModel(self, model)
  46. model.layoutChanged.connect(self.layoutChanged)
  47. model.contextChanged.connect(self.contextChanged)
  48. self.selectionModel().currentRowChanged.connect(self.fileSelected)
  49. self.horizontalHeader().setResizeMode(1, QHeaderView.Stretch)
  50. self.actionShowAllMerge.setChecked(False)
  51. self.actionShowAllMerge.toggled.connect(model.toggleFullFileList)
  52. if model._ctx is not None:
  53. self.contextChanged(model._ctx)
  54. def contextChanged(self, ctx):
  55. real = type(ctx.rev()) is int
  56. wd = ctx.rev() is None
  57. for act in ['ldiff', 'edit']:
  58. self._actions[act].setEnabled(real)
  59. for act in ['diff', 'revert']:
  60. self._actions[act].setEnabled(real or wd)
  61. if len(ctx.parents()) == 2:
  62. self.actionShowAllMerge.setVisible(True)
  63. else:
  64. self.actionShowAllMerge.setVisible(False)
  65. self.actionShowAllMerge.setChecked(False)
  66. def currentFile(self):
  67. index = self.currentIndex()
  68. return self.model().fileFromIndex(index)
  69. def layoutChanged(self):
  70. 'file model has new contents'
  71. index = self.currentIndex()
  72. if index.row() >= len(self.model()):
  73. self.selectRow(0)
  74. else:
  75. self.selectRow(index.row())
  76. self.fileSelected()
  77. def fileSelected(self, index=None, *args):
  78. if index is None:
  79. index = self.currentIndex()
  80. sel_file = self.model().fileFromIndex(index)
  81. from_rev = self.model().revFromIndex(index)
  82. status = self.model().flagFromIndex(index)
  83. if sel_file:
  84. self.fileRevSelected.emit(sel_file, from_rev, status)
  85. else:
  86. self.clearDisplay.emit()
  87. def selectFile(self, filename):
  88. index = self.model().indexFromFile(filename)
  89. self.setCurrentIndex(index)
  90. self.fileSelected(index)
  91. def fileActivated(self, index, alternate=False):
  92. sel_file = self.model().fileFromIndex(index)
  93. if alternate:
  94. self.navigate(sel_file)
  95. else:
  96. self.diffNavigate(sel_file)
  97. def navigate(self, filename=None):
  98. self._navigate(filename, FileLogDialog, self._nav_dialogs)
  99. def diffNavigate(self, filename=None):
  100. self._navigate(filename, FileDiffDialog, self._diff_dialogs)
  101. def vdiff(self):
  102. filename = self.currentFile()
  103. if filename is None:
  104. return
  105. model = self.model()
  106. pats = [filename]
  107. rev = model._ctx.rev()
  108. opts = {'change':model._ctx.rev()}
  109. dlg = visdiff.visualdiff(model.repo.ui, model.repo, pats, opts)
  110. if dlg:
  111. dlg.exec_()
  112. def vdifflocal(self):
  113. filename = self.currentFile()
  114. if filename is None:
  115. return
  116. model = self.model()
  117. pats = [filename]
  118. assert type(model._ctx.rev()) is int
  119. opts = {'rev':['rev(%d)' % (model._ctx.rev())]}
  120. dlg = visdiff.visualdiff(model.repo.ui, model.repo, pats, opts)
  121. if dlg:
  122. dlg.exec_()
  123. def editfile(self):
  124. filename = self.currentFile()
  125. if filename is None:
  126. return
  127. model = self.model()
  128. repo = model.repo
  129. rev = model._ctx.rev()
  130. if rev is None:
  131. files = [repo.wjoin(filename)]
  132. wctxactions.edit(self, repo.ui, repo, files)
  133. else:
  134. base, _ = visdiff.snapshot(repo, [filename], repo[rev])
  135. files = [os.path.join(base, filename)]
  136. wctxactions.edit(self, repo.ui, repo, files)
  137. def editlocal(self):
  138. filename = self.currentFile()
  139. if filename is None:
  140. return
  141. model = self.model()
  142. repo = model.repo
  143. path = repo.wjoin(filename)
  144. wctxactions.edit(self, repo.ui, repo, [path])
  145. def revertfile(self):
  146. filename = self.currentFile()
  147. if filename is None:
  148. return
  149. model = self.model()
  150. repo = model.repo
  151. rev = model._ctx.rev()
  152. if rev is None:
  153. rev = model._ctx.p1().rev()
  154. dlg = revert.RevertDialog(repo, filename, rev, self)
  155. dlg.exec_()
  156. def _navigate(self, filename, dlgclass, dlgdict):
  157. if not filename:
  158. filename = self.currentFile()
  159. model = self.model()
  160. if filename is not None and len(model.repo.file(filename))>0:
  161. if filename not in dlgdict:
  162. dlg = dlgclass(model.repo, filename,
  163. repoviewer=self.window())
  164. dlgdict[filename] = dlg
  165. ufname = hglib.tounicode(filename)
  166. dlg.setWindowTitle(_('Hg file log viewer - %s') % ufname)
  167. dlg = dlgdict[filename]
  168. dlg.goto(model._ctx.rev())
  169. dlg.show()
  170. dlg.raise_()
  171. dlg.activateWindow()
  172. def createActions(self):
  173. self.actionShowAllMerge = QAction('Show All', self)
  174. self.actionShowAllMerge.setCheckable(True)
  175. self.actionShowAllMerge.setChecked(False)
  176. self.actionShowAllMerge.setVisible(False)
  177. self._actions = {}
  178. for name, desc, icon, key, tip, cb in [
  179. ('navigate', _('File history'), None, 'Shift+Return',
  180. _('Show the history of the selected file'), self.navigate),
  181. ('diffnavigate', _('Compare file revisions'), None, None,
  182. _('Compare revisions of the selected file'), self.diffNavigate),
  183. ('diff', _('Visual Diff'), None, 'Ctrl+D',
  184. _('View file changes in external diff tool'), self.vdiff),
  185. ('ldiff', _('Visual Diff to Local'), None, 'Shift+Ctrl+D',
  186. _('View changes to current in external diff tool'),
  187. self.vdifflocal),
  188. ('edit', _('View at Revision'), None, 'Alt+Ctrl+E',
  189. _('View file as it appeared at this revision'), self.editfile),
  190. ('ledit', _('Edit Local'), None, 'Shift+Ctrl+E',
  191. _('Edit current file in working copy'), self.editlocal),
  192. ('revert', _('Revert to Revision'), None, 'Alt+Ctrl+T',
  193. _('Revert file(s) to contents at this revision'),
  194. self.revertfile),
  195. ]:
  196. act = QAction(desc, self)
  197. if icon:
  198. act.setIcon(geticon(icon))
  199. if key:
  200. act.setShortcut(key)
  201. if tip:
  202. act.setStatusTip(tip)
  203. if cb:
  204. act.triggered.connect(cb)
  205. self._actions[name] = act
  206. self.addAction(act)
  207. def contextMenuEvent(self, event):
  208. if not self.contextmenu:
  209. self.contextmenu = QMenu(self)
  210. for act in ['diff', 'ldiff', 'edit', 'ledit', 'revert',
  211. 'navigate', 'diffnavigate']:
  212. if act:
  213. self.contextmenu.addAction(self._actions[act])
  214. else:
  215. self.contextmenu.addSeparator()
  216. self.contextmenu.exec_(event.globalPos())
  217. def resizeEvent(self, event):
  218. if self.model() is not None:
  219. vp_width = self.viewport().width()
  220. col_widths = [self.columnWidth(i) \
  221. for i in range(1, self.model().columnCount())]
  222. col_width = vp_width - sum(col_widths)
  223. col_width = max(col_width, 50)
  224. self.setColumnWidth(0, col_width)
  225. QTableView.resizeEvent(self, event)
  226. #
  227. ## Mouse drag
  228. #
  229. def selectedRows(self):
  230. return self.selectionModel().selectedRows()
  231. def dragObject(self):
  232. ctx = self.model()._ctx
  233. if type(ctx.rev()) == str:
  234. return
  235. paths = []
  236. for index in self.selectedRows():
  237. paths.append(self.model().fileFromIndex(index))
  238. if not paths:
  239. return
  240. if ctx.rev() is None:
  241. base = ctx._repo.root
  242. else:
  243. base, _ = visdiff.snapshot(ctx._repo, paths, ctx)
  244. urls = []
  245. for path in paths:
  246. u = QUrl()
  247. u.setPath('file://' + os.path.join(base, path))
  248. urls.append(u)
  249. if urls:
  250. d = QDrag(self)
  251. m = QMimeData()
  252. m.setUrls(urls)
  253. d.setMimeData(m)
  254. d.start(Qt.CopyAction)
  255. def mousePressEvent(self, event):
  256. self.pressPos = event.pos()
  257. self.pressTime = QTime.currentTime()
  258. return QTableView.mousePressEvent(self, event)
  259. def mouseMoveEvent(self, event):
  260. d = event.pos() - self.pressPos
  261. if d.manhattanLength() < QApplication.startDragDistance():
  262. return QTableView.mouseMoveEvent(self, event)
  263. elapsed = self.pressTime.msecsTo(QTime.currentTime())
  264. if elapsed < QApplication.startDragTime():
  265. return QTableView.mouseMoveEvent(self, event)
  266. self.dragObject()
  267. return QTableView.mouseMoveEvent(self, event)