PageRenderTime 37ms CodeModel.GetById 12ms app.highlight 21ms RepoModel.GetById 2ms app.codeStats 0ms

/tortoisehg/hgqt/filelistview.py

https://bitbucket.org/tortoisehg/hgtk/
Python | 299 lines | 246 code | 32 blank | 21 comment | 55 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
 17import os
 18
 19from tortoisehg.util import hglib
 20from tortoisehg.hgqt.i18n import _
 21from tortoisehg.hgqt.qtlib import geticon
 22from tortoisehg.hgqt.filedialogs import FileLogDialog, FileDiffDialog 
 23from tortoisehg.hgqt import visdiff, wctxactions, revert
 24
 25from PyQt4.QtCore import *
 26from PyQt4.QtGui import *
 27
 28class HgFileListView(QTableView):
 29    """
 30    A QTableView for displaying a HgFileListModel
 31    """
 32
 33    fileRevSelected = pyqtSignal(object, object, object)
 34    clearDisplay = pyqtSignal()
 35    contextmenu = None
 36
 37    def __init__(self, parent=None):
 38        QTableView.__init__(self, parent)
 39        self.setShowGrid(False)
 40        self.horizontalHeader().hide()
 41        self.verticalHeader().hide()
 42        self.verticalHeader().setDefaultSectionSize(20)
 43        self.setSelectionMode(QAbstractItemView.SingleSelection)
 44        self.setSelectionBehavior(QAbstractItemView.SelectRows)
 45        self.setTextElideMode(Qt.ElideLeft)
 46
 47        self.createActions()
 48
 49        self.doubleClicked.connect(self.fileActivated)
 50        self._diff_dialogs = {}
 51        self._nav_dialogs = {}
 52
 53    def setModel(self, model):
 54        QTableView.setModel(self, model)
 55        model.layoutChanged.connect(self.layoutChanged)
 56        model.contextChanged.connect(self.contextChanged)
 57        self.selectionModel().currentRowChanged.connect(self.fileSelected)
 58        self.horizontalHeader().setResizeMode(1, QHeaderView.Stretch)
 59        self.actionShowAllMerge.setChecked(False)
 60        self.actionShowAllMerge.toggled.connect(model.toggleFullFileList)
 61        if model._ctx is not None:
 62            self.contextChanged(model._ctx)
 63
 64    def contextChanged(self, ctx):
 65        real = type(ctx.rev()) is int
 66        wd = ctx.rev() is None
 67        for act in ['ldiff', 'edit']:
 68            self._actions[act].setEnabled(real)
 69        for act in ['diff', 'revert']:
 70            self._actions[act].setEnabled(real or wd)
 71        if len(ctx.parents()) == 2:
 72            self.actionShowAllMerge.setVisible(True)
 73        else:
 74            self.actionShowAllMerge.setVisible(False)
 75            self.actionShowAllMerge.setChecked(False)
 76
 77    def currentFile(self):
 78        index = self.currentIndex()
 79        return self.model().fileFromIndex(index)
 80
 81    def layoutChanged(self):
 82        'file model has new contents'
 83        index = self.currentIndex()
 84        if index.row() >= len(self.model()):
 85            self.selectRow(0)
 86        else:
 87            self.selectRow(index.row())
 88        self.fileSelected()
 89
 90    def fileSelected(self, index=None, *args):
 91        if index is None:
 92            index = self.currentIndex()
 93        sel_file = self.model().fileFromIndex(index)
 94        from_rev = self.model().revFromIndex(index)
 95        status = self.model().flagFromIndex(index)
 96        if sel_file:
 97            self.fileRevSelected.emit(sel_file, from_rev, status)
 98        else:
 99            self.clearDisplay.emit()
100
101    def selectFile(self, filename):
102        index = self.model().indexFromFile(filename)
103        self.setCurrentIndex(index)
104        self.fileSelected(index)
105
106    def fileActivated(self, index, alternate=False):
107        sel_file = self.model().fileFromIndex(index)
108        if alternate:
109            self.navigate(sel_file)
110        else:
111            self.diffNavigate(sel_file)
112
113    def navigate(self, filename=None):
114        self._navigate(filename, FileLogDialog, self._nav_dialogs)
115
116    def diffNavigate(self, filename=None):
117        self._navigate(filename, FileDiffDialog, self._diff_dialogs)
118
119    def vdiff(self):
120        filename = self.currentFile()
121        if filename is None:
122            return
123        model = self.model()
124        pats = [filename]
125        rev = model._ctx.rev()
126        opts = {'change':model._ctx.rev()}
127        dlg = visdiff.visualdiff(model.repo.ui, model.repo, pats, opts)
128        if dlg:
129            dlg.exec_()
130
131    def vdifflocal(self):
132        filename = self.currentFile()
133        if filename is None:
134            return
135        model = self.model()
136        pats = [filename]
137        assert type(model._ctx.rev()) is int
138        opts = {'rev':['rev(%d)' % (model._ctx.rev())]}
139        dlg = visdiff.visualdiff(model.repo.ui, model.repo, pats, opts)
140        if dlg:
141            dlg.exec_()
142
143    def editfile(self):
144        filename = self.currentFile()
145        if filename is None:
146            return
147        model = self.model()
148        repo = model.repo
149        rev = model._ctx.rev()
150        if rev is None:
151            files = [repo.wjoin(filename)]
152            wctxactions.edit(self, repo.ui, repo, files)
153        else:
154            base, _ = visdiff.snapshot(repo, [filename], repo[rev])
155            files = [os.path.join(base, filename)]
156            wctxactions.edit(self, repo.ui, repo, files)
157
158    def editlocal(self):
159        filename = self.currentFile()
160        if filename is None:
161            return
162        model = self.model()
163        repo = model.repo
164        path = repo.wjoin(filename)
165        wctxactions.edit(self, repo.ui, repo, [path])
166
167    def revertfile(self):
168        filename = self.currentFile()
169        if filename is None:
170            return
171        model = self.model()
172        repo = model.repo
173        rev = model._ctx.rev()
174        if rev is None:
175            rev = model._ctx.p1().rev()
176        dlg = revert.RevertDialog(repo, filename, rev, self)
177        dlg.exec_()
178
179    def _navigate(self, filename, dlgclass, dlgdict):
180        if not filename:
181            filename = self.currentFile()
182        model = self.model()
183        if filename is not None and len(model.repo.file(filename))>0:
184            if filename not in dlgdict:
185                dlg = dlgclass(model.repo, filename,
186                               repoviewer=self.window())
187                dlgdict[filename] = dlg
188                ufname = hglib.tounicode(filename)
189                dlg.setWindowTitle(_('Hg file log viewer - %s') % ufname)
190            dlg = dlgdict[filename]
191            dlg.goto(model._ctx.rev())
192            dlg.show()
193            dlg.raise_()
194            dlg.activateWindow()
195
196    def createActions(self):
197        self.actionShowAllMerge = QAction('Show All', self)
198        self.actionShowAllMerge.setCheckable(True)
199        self.actionShowAllMerge.setChecked(False)
200        self.actionShowAllMerge.setVisible(False)
201
202        self._actions = {}
203        for name, desc, icon, key, tip, cb in [
204            ('navigate', _('File history'), None, 'Shift+Return',
205              _('Show the history of the selected file'), self.navigate),
206            ('diffnavigate', _('Compare file revisions'), None, None,
207              _('Compare revisions of the selected file'), self.diffNavigate),
208            ('diff', _('Visual Diff'), None, 'Ctrl+D',
209              _('View file changes in external diff tool'), self.vdiff),
210            ('ldiff', _('Visual Diff to Local'), None, 'Shift+Ctrl+D',
211              _('View changes to current in external diff tool'),
212              self.vdifflocal),
213            ('edit', _('View at Revision'), None, 'Alt+Ctrl+E',
214              _('View file as it appeared at this revision'), self.editfile),
215            ('ledit', _('Edit Local'), None, 'Shift+Ctrl+E',
216              _('Edit current file in working copy'), self.editlocal),
217            ('revert', _('Revert to Revision'), None, 'Alt+Ctrl+T',
218              _('Revert file(s) to contents at this revision'),
219              self.revertfile),
220            ]:
221            act = QAction(desc, self)
222            if icon:
223                act.setIcon(geticon(icon))
224            if key:
225                act.setShortcut(key)
226            if tip:
227                act.setStatusTip(tip)
228            if cb:
229                act.triggered.connect(cb)
230            self._actions[name] = act
231            self.addAction(act)
232
233    def contextMenuEvent(self, event):
234        if not self.contextmenu:
235            self.contextmenu = QMenu(self)
236            for act in ['diff', 'ldiff', 'edit', 'ledit', 'revert',
237                        'navigate', 'diffnavigate']:
238                if act:
239                    self.contextmenu.addAction(self._actions[act])
240                else:
241                    self.contextmenu.addSeparator()
242        self.contextmenu.exec_(event.globalPos())
243
244    def resizeEvent(self, event):
245        if self.model() is not None:
246            vp_width = self.viewport().width()
247            col_widths = [self.columnWidth(i) \
248                        for i in range(1, self.model().columnCount())]
249            col_width = vp_width - sum(col_widths)
250            col_width = max(col_width, 50)
251            self.setColumnWidth(0, col_width)
252        QTableView.resizeEvent(self, event)
253
254    #
255    ## Mouse drag
256    #
257
258    def selectedRows(self):
259        return self.selectionModel().selectedRows()
260
261    def dragObject(self):
262        ctx = self.model()._ctx
263        if type(ctx.rev()) == str:
264            return
265        paths = []
266        for index in self.selectedRows():
267            paths.append(self.model().fileFromIndex(index))
268        if not paths:
269            return
270        if ctx.rev() is None:
271            base = ctx._repo.root
272        else:
273            base, _ = visdiff.snapshot(ctx._repo, paths, ctx)
274        urls = []
275        for path in paths:
276            u = QUrl()
277            u.setPath('file://' + os.path.join(base, path))
278            urls.append(u)
279        if urls:
280            d = QDrag(self)
281            m = QMimeData()
282            m.setUrls(urls)
283            d.setMimeData(m)
284            d.start(Qt.CopyAction)
285
286    def mousePressEvent(self, event):
287        self.pressPos = event.pos()
288        self.pressTime = QTime.currentTime()
289        return QTableView.mousePressEvent(self, event)
290
291    def mouseMoveEvent(self, event):
292        d = event.pos() - self.pressPos
293        if d.manhattanLength() < QApplication.startDragDistance():
294            return QTableView.mouseMoveEvent(self, event)
295        elapsed = self.pressTime.msecsTo(QTime.currentTime())
296        if elapsed < QApplication.startDragTime():
297            return QTableView.mouseMoveEvent(self, event)
298        self.dragObject()
299        return QTableView.mouseMoveEvent(self, event)