class RevDetailsWidget(QWidget, qtlib.TaskWidget): showMessage = pyqtSignal(QString) linkActivated = pyqtSignal(unicode) grepRequested = pyqtSignal(unicode, dict) revisionSelected = pyqtSignal(int) updateToRevision = pyqtSignal(int) runCustomCommandRequested = pyqtSignal(str, list) def __init__(self, repoagent, parent, rev=None): QWidget.__init__(self, parent) self._repoagent = repoagent repo = repoagent.rawRepo() # TODO: replace by repoagent if setRepo(bundlerepo) can be removed self.repo = repo self.ctx = repo[rev] self.splitternames = [] self.setupUi() self.createActions() self.setupModels() self._deschtmlize = qtlib.descriptionhtmlizer(repo.ui) repoagent.configChanged.connect(self._updatedeschtmlizer) def setRepo(self, repo): self.repo = repo self.fileview.setRepo(repo) self.filelist.setRepo(repo) self._fileactions.setRepo(repo) def setupUi(self): SP = QSizePolicy sp = SP(SP.Preferred, SP.Expanding) sp.setHorizontalStretch(0) sp.setVerticalStretch(0) sp.setHeightForWidth(self.sizePolicy().hasHeightForWidth()) self.setSizePolicy(sp) # + basevbox -------------------------------------------------------+ # |+ filelistsplit ........ | # | + filelistframe (vbox) | + panelframe (vbox) | # | + filelisttbar | + revpanel | # +---------------------------+-------------------------------------+ # | + filelist | + messagesplitter | # | | :+ message | # | | :----------------------------------+ # | | + fileview | # +---------------------------+-------------------------------------+ basevbox = QVBoxLayout(self) basevbox.setSpacing(0) basevbox.setMargin(0) basevbox.setContentsMargins(2, 2, 2, 2) self.filelistsplit = QSplitter(self) basevbox.addWidget(self.filelistsplit) self.splitternames.append('filelistsplit') sp = SP(SP.Expanding, SP.Expanding) sp.setHorizontalStretch(0) sp.setVerticalStretch(0) sp.setHeightForWidth(self.filelistsplit.sizePolicy().hasHeightForWidth()) self.filelistsplit.setSizePolicy(sp) self.filelistsplit.setOrientation(Qt.Horizontal) self.filelistsplit.setChildrenCollapsible(False) self.filelisttbar = QToolBar(_('File List Toolbar')) self.filelisttbar.setIconSize(QSize(16,16)) self.filelist = HgFileListView(self.repo, self, True) self.filelist.setContextMenuPolicy(Qt.CustomContextMenu) self.filelist.customContextMenuRequested.connect(self.menuRequest) self.filelist.doubleClicked.connect(self.onDoubleClick) self.filelistframe = QFrame(self.filelistsplit) sp = SP(SP.Preferred, SP.Preferred) sp.setHorizontalStretch(3) sp.setVerticalStretch(0) sp.setHeightForWidth( self.filelistframe.sizePolicy().hasHeightForWidth()) self.filelistframe.setSizePolicy(sp) self.filelistframe.setFrameShape(QFrame.NoFrame) vbox = QVBoxLayout() vbox.setSpacing(0) vbox.setMargin(0) vbox.addWidget(self.filelisttbar) vbox.addWidget(self.filelist) self.filelistframe.setLayout(vbox) self.fileviewframe = QFrame(self.filelistsplit) sp = SP(SP.Preferred, SP.Preferred) sp.setHorizontalStretch(7) sp.setVerticalStretch(0) sp.setHeightForWidth( self.fileviewframe.sizePolicy().hasHeightForWidth()) self.fileviewframe.setSizePolicy(sp) self.fileviewframe.setFrameShape(QFrame.NoFrame) vbox = QVBoxLayout(self.fileviewframe) vbox.setSpacing(0) vbox.setSizeConstraint(QLayout.SetDefaultConstraint) vbox.setMargin(0) panelframevbox = vbox self.messagesplitter = QSplitter(self.fileviewframe) if os.name == 'nt': self.messagesplitter.setStyle(QStyleFactory.create('Plastique')) self.splitternames.append('messagesplitter') sp = SP(SP.Preferred, SP.Expanding) sp.setHorizontalStretch(0) sp.setVerticalStretch(0) sp.setHeightForWidth(self.messagesplitter.sizePolicy().hasHeightForWidth()) self.messagesplitter.setSizePolicy(sp) self.messagesplitter.setMinimumSize(QSize(50, 50)) self.messagesplitter.setFrameShape(QFrame.NoFrame) self.messagesplitter.setLineWidth(0) self.messagesplitter.setMidLineWidth(0) self.messagesplitter.setOrientation(Qt.Vertical) self.messagesplitter.setOpaqueResize(True) self.message = QTextBrowser(self.messagesplitter, lineWrapMode=QTextEdit.NoWrap, openLinks=False) self.message.minimumSizeHint = lambda: QSize(0, 25) self.message.anchorClicked.connect(self._forwardAnchorClicked) sp = SP(SP.Expanding, SP.Expanding) sp.setHorizontalStretch(0) sp.setVerticalStretch(2) sp.setHeightForWidth(self.message.sizePolicy().hasHeightForWidth()) self.message.setSizePolicy(sp) self.message.setMinimumSize(QSize(0, 0)) f = qtlib.getfont('fontcomment') self.message.setFont(f.font()) f.changed.connect(self.forwardFont) self.fileview = HgFileView(self._repoagent, self.messagesplitter) sp = SP(SP.Expanding, SP.Expanding) sp.setHorizontalStretch(0) sp.setVerticalStretch(5) sp.setHeightForWidth(self.fileview.sizePolicy().hasHeightForWidth()) self.fileview.setSizePolicy(sp) self.fileview.setMinimumSize(QSize(0, 0)) self.fileview.linkActivated.connect(self.linkActivated) self.fileview.setFont(qtlib.getfont('fontdiff').font()) self.fileview.showMessage.connect(self.showMessage) self.fileview.grepRequested.connect(self.grepRequested) self.fileview.revisionSelected.connect(self.revisionSelected) self.filelist.fileSelected.connect(self.fileview.displayFile) self.filelist.fileSelected.connect(self.updateItemFileActions) self.filelist.clearDisplay.connect(self.fileview.clearDisplay) self.revpanel = RevPanelWidget(self.repo) self.revpanel.linkActivated.connect(self.linkActivated) panelframevbox.addWidget(self.revpanel) panelframevbox.addSpacing(5) panelframevbox.addWidget(self.messagesplitter) def forwardFont(self, font): self.message.setFont(font) def setupModels(self): self.filelistmodel = model = HgFileListModel(self) self.filelistmodel.showMessage.connect(self.showMessage) self.filelist.setModel(model) self.actionShowAllMerge.toggled.connect(model.toggleFullFileList) # Because filelist.fileSelected, i.e. currentRowChanged, is emitted # *before* selection changed, we cannot rely only on fileSelected. # On fileSelected, getSelectedFiles() happens to return the previous # selection, even though currentFile() works as expected. self.filelist.selectionModel().selectionChanged.connect( self.updateItemFileActions) def createActions(self): self.actionUpdate = a = self.filelisttbar.addAction( qtlib.geticon('hg-update'), _('Update to this revision')) a.triggered.connect(self._emitUpdateToRevision) self.filelisttbar.addSeparator() self.actionShowAllMerge = QAction(_('Show All'), self) self.actionShowAllMerge.setToolTip( _('Toggle display of all files and the direction they were merged')) self.actionShowAllMerge.setCheckable(True) self.actionShowAllMerge.setChecked(False) self.actionShowAllMerge.setEnabled(False) self.filelisttbar.addAction(self.actionShowAllMerge) le = QLineEdit() if hasattr(le, 'setPlaceholderText'): # Qt >= 4.7 le.setPlaceholderText(_('### filter text ###')) self.filefilter = le self.filelisttbar.addWidget(self.filefilter) self.filefilter.textEdited.connect(self.setFilter) self.actionNextLine = QAction('Next line', self) self.actionNextLine.setShortcut(Qt.SHIFT + Qt.Key_Down) self.actionNextLine.triggered.connect(self.fileview.nextLine) self.addAction(self.actionNextLine) self.actionPrevLine = QAction('Prev line', self) self.actionPrevLine.setShortcut(Qt.SHIFT + Qt.Key_Up) self.actionPrevLine.triggered.connect(self.fileview.prevLine) self.addAction(self.actionPrevLine) self.actionNextCol = QAction('Next column', self) self.actionNextCol.setShortcut(Qt.SHIFT + Qt.Key_Right) self.actionNextCol.triggered.connect(self.fileview.nextCol) self.addAction(self.actionNextCol) self.actionPrevCol = QAction('Prev column', self) self.actionPrevCol.setShortcut(Qt.SHIFT + Qt.Key_Left) self.actionPrevCol.triggered.connect(self.fileview.prevCol) self.addAction(self.actionPrevCol) self._fileactions = filectxactions.FilectxActions(self.repo, self, rev=self.ctx.rev()) self._fileactions.linkActivated.connect(self.linkActivated) self._fileactions.runCustomCommandRequested.connect( self.runCustomCommandRequested) self.addActions(self._fileactions.actions()) def onRevisionSelected(self, rev): 'called by repowidget when repoview changes revisions' self.ctx = ctx = self.repo.changectx(rev) self.revpanel.set_revision(rev) self.revpanel.update(repo = self.repo) msg = ctx.description() inlinetags = self.repo.ui.configbool('tortoisehg', 'issue.inlinetags') if ctx.tags() and inlinetags: msg = ' '.join(['[%s]' % tag for tag in ctx.tags()]) + ' ' + msg # don't use <pre>...</pre>, which also changes font family self.message.setHtml('<div style="white-space: pre;">%s</div>' % self._deschtmlize(msg)) self._fileactions.setRev(rev) self.actionShowAllMerge.setEnabled(len(ctx.parents()) == 2) self.fileview.setContext(ctx) self.filelist.setContext(ctx) self.setFilter(self.filefilter.text()) @pyqtSlot() def _updatedeschtmlizer(self): self._deschtmlize = qtlib.descriptionhtmlizer(self.repo.ui) self.onRevisionSelected(self.ctx.rev()) # regenerate desc html def reload(self): 'Task tab is reloaded, or repowidget is refreshed' rev = self.ctx.rev() if (type(self.ctx.rev()) is int and len(self.repo) <= self.ctx.rev() or (rev is not None # wctxrev in repo raises TypeError and rev not in self.repo and rev not in self.repo.thgmqunappliedpatches)): rev = 'tip' self.onRevisionSelected(rev) @pyqtSlot(QUrl) def _forwardAnchorClicked(self, url): self.linkActivated.emit(url.toString()) @pyqtSlot() def _emitUpdateToRevision(self): self.updateToRevision.emit(self.ctx.rev()) #@pyqtSlot(QModelIndex) def onDoubleClick(self, index): model = self.filelist.model() itemstatus = model.dataFromIndex(index)['status'] itemissubrepo = (itemstatus == 'S') if itemissubrepo: self._fileactions.opensubrepo() elif itemstatus == 'C': self._fileactions.editfile() else: self._fileactions.vdiff() @pyqtSlot(QPoint) def menuRequest(self, point): index = self.filelist.currentIndex() if not index.isValid(): return model = self.filelist.model() data = model.dataFromIndex(index) if not data: return contextmenu = self._fileactions.menu() if contextmenu: contextmenu.exec_(self.filelist.viewport().mapToGlobal(point)) @pyqtSlot() def updateItemFileActions(self): index = self.filelist.currentIndex() model = self.filelist.model() data = model.dataFromIndex(index) if not data: return itemissubrepo = (data['status'] == 'S') self._fileactions.setPaths_(self.filelist.getSelectedFiles(), self.filelist.currentFile(), itemissubrepo) @pyqtSlot(QString) def setFilter(self, match): model = self.filelist.model() if model is not None: model.setFilter(match) self.filelist.enablefilterpalette(bool(match)) def saveSettings(self, s): wb = "RevDetailsWidget/" for n in self.splitternames: s.setValue(wb + n, getattr(self, n).saveState()) s.setValue(wb + 'revpanel.expanded', self.revpanel.is_expanded()) self.fileview.saveSettings(s, 'revpanel/fileview') def loadSettings(self, s): wb = "RevDetailsWidget/" for n in self.splitternames: getattr(self, n).restoreState(s.value(wb + n).toByteArray()) expanded = s.value(wb + 'revpanel.expanded', False).toBool() self.revpanel.set_expanded(expanded) self.fileview.loadSettings(s, 'revpanel/fileview')
class RevDetailsWidget(QWidget, qtlib.TaskWidget): showMessage = pyqtSignal(QString) linkActivated = pyqtSignal(unicode) grepRequested = pyqtSignal(unicode, dict) revisionSelected = pyqtSignal(int) updateToRevision = pyqtSignal(int) filecontextmenu = None subrepocontextmenu = None def __init__(self, repo, parent): QWidget.__init__(self, parent) self.repo = repo self.ctx = repo[None] self.splitternames = [] self._diff_dialogs = {} self._nav_dialogs = {} self.setupUi() self.createActions() self.setupModels() self._deschtmlize = qtlib.descriptionhtmlizer(repo.ui) repo.configChanged.connect(self._updatedeschtmlizer) def setRepo(self, repo): self.repo = repo self.fileview.setRepo(repo) self.filelist.setRepo(repo) def setupUi(self): SP = QSizePolicy sp = SP(SP.Preferred, SP.Expanding) sp.setHorizontalStretch(0) sp.setVerticalStretch(0) sp.setHeightForWidth(self.sizePolicy().hasHeightForWidth()) self.setSizePolicy(sp) # + basevbox -------------------------------------------------------+ # |+ filelistsplit ........ | # | + filelistframe (vbox) | + panelframe (vbox) | # | + filelisttbar | + revpanel | # +---------------------------+-------------------------------------+ # | + filelist | + messagesplitter | # | | :+ message | # | | :----------------------------------+ # | | + fileview | # +---------------------------+-------------------------------------+ basevbox = QVBoxLayout(self) basevbox.setSpacing(0) basevbox.setMargin(0) basevbox.setContentsMargins(2, 2, 2, 2) self.filelistsplit = QSplitter(self) basevbox.addWidget(self.filelistsplit) self.splitternames.append('filelistsplit') sp = SP(SP.Expanding, SP.Expanding) sp.setHorizontalStretch(0) sp.setVerticalStretch(0) sp.setHeightForWidth(self.filelistsplit.sizePolicy().hasHeightForWidth()) self.filelistsplit.setSizePolicy(sp) self.filelistsplit.setOrientation(Qt.Horizontal) self.filelistsplit.setChildrenCollapsible(False) self.filelisttbar = QToolBar(_('File List Toolbar')) self.filelisttbar.setIconSize(QSize(16,16)) self.filelist = HgFileListView(self.repo, self, True) self.filelist.linkActivated.connect(self.linkActivated) self.filelist.setContextMenuPolicy(Qt.CustomContextMenu) self.filelist.customContextMenuRequested.connect(self.menuRequest) self.filelist.doubleClicked.connect(self.onDoubleClick) self.filelistframe = QFrame(self.filelistsplit) sp = SP(SP.Preferred, SP.Preferred) sp.setHorizontalStretch(3) sp.setVerticalStretch(0) sp.setHeightForWidth( self.filelistframe.sizePolicy().hasHeightForWidth()) self.filelistframe.setSizePolicy(sp) self.filelistframe.setFrameShape(QFrame.NoFrame) vbox = QVBoxLayout() vbox.setSpacing(0) vbox.setMargin(0) vbox.addWidget(self.filelisttbar) vbox.addWidget(self.filelist) self.filelistframe.setLayout(vbox) self.fileviewframe = QFrame(self.filelistsplit) sp = SP(SP.Preferred, SP.Preferred) sp.setHorizontalStretch(7) sp.setVerticalStretch(0) sp.setHeightForWidth( self.fileviewframe.sizePolicy().hasHeightForWidth()) self.fileviewframe.setSizePolicy(sp) self.fileviewframe.setFrameShape(QFrame.NoFrame) vbox = QVBoxLayout(self.fileviewframe) vbox.setSpacing(0) vbox.setSizeConstraint(QLayout.SetDefaultConstraint) vbox.setMargin(0) panelframevbox = vbox self.messagesplitter = QSplitter(self.fileviewframe) self.splitternames.append('messagesplitter') sp = SP(SP.Preferred, SP.Expanding) sp.setHorizontalStretch(0) sp.setVerticalStretch(0) sp.setHeightForWidth(self.messagesplitter.sizePolicy().hasHeightForWidth()) self.messagesplitter.setSizePolicy(sp) self.messagesplitter.setMinimumSize(QSize(50, 50)) self.messagesplitter.setFrameShape(QFrame.NoFrame) self.messagesplitter.setLineWidth(0) self.messagesplitter.setMidLineWidth(0) self.messagesplitter.setOrientation(Qt.Vertical) self.messagesplitter.setOpaqueResize(True) self.message = QTextBrowser(self.messagesplitter, lineWrapMode=QTextEdit.NoWrap, openLinks=False) self.message.minimumSizeHint = lambda: QSize(0, 25) self.message.anchorClicked.connect( lambda url: self.linkActivated.emit(url.toString())) sp = SP(SP.Expanding, SP.Expanding) sp.setHorizontalStretch(0) sp.setVerticalStretch(2) sp.setHeightForWidth(self.message.sizePolicy().hasHeightForWidth()) self.message.setSizePolicy(sp) self.message.setMinimumSize(QSize(0, 0)) f = qtlib.getfont('fontcomment') self.message.setFont(f.font()) f.changed.connect(self.forwardFont) self.fileview = HgFileView(self.repo, self.messagesplitter) sp = SP(SP.Expanding, SP.Expanding) sp.setHorizontalStretch(0) sp.setVerticalStretch(5) sp.setHeightForWidth(self.fileview.sizePolicy().hasHeightForWidth()) self.fileview.setSizePolicy(sp) self.fileview.setMinimumSize(QSize(0, 0)) self.fileview.linkActivated.connect(self.linkActivated) self.fileview.setFont(qtlib.getfont('fontdiff').font()) self.fileview.showMessage.connect(self.showMessage) self.fileview.grepRequested.connect(self.grepRequested) self.fileview.revisionSelected.connect(self.revisionSelected) self.filelist.fileSelected.connect(self.fileview.displayFile) self.filelist.clearDisplay.connect(self.fileview.clearDisplay) self.revpanel = RevPanelWidget(self.repo) self.revpanel.linkActivated.connect(self.linkActivated) panelframevbox.addWidget(self.revpanel) panelframevbox.addSpacing(5) panelframevbox.addWidget(self.messagesplitter) def forwardFont(self, font): self.message.setFont(font) def setupModels(self): self.filelistmodel = model = HgFileListModel(self) self.filelistmodel.showMessage.connect(self.showMessage) self.filelist.setModel(model) self.actionShowAllMerge.toggled.connect(model.toggleFullFileList) def createActions(self): self.actionUpdate = a = self.filelisttbar.addAction( qtlib.geticon('hg-update'), _('Update to this revision')) a.triggered.connect(lambda: self.updateToRevision.emit(self.ctx.rev())) self.filelisttbar.addSeparator() self.actionShowAllMerge = QAction(_('Show All'), self) self.actionShowAllMerge.setToolTip( _('Toggle display of all files and the direction they were merged')) self.actionShowAllMerge.setCheckable(True) self.actionShowAllMerge.setChecked(False) self.actionShowAllMerge.setEnabled(False) self.filelisttbar.addAction(self.actionShowAllMerge) self.actionNextLine = QAction('Next line', self) self.actionNextLine.setShortcut(Qt.SHIFT + Qt.Key_Down) self.actionNextLine.triggered.connect(self.fileview.nextLine) self.addAction(self.actionNextLine) self.actionPrevLine = QAction('Prev line', self) self.actionPrevLine.setShortcut(Qt.SHIFT + Qt.Key_Up) self.actionPrevLine.triggered.connect(self.fileview.prevLine) self.addAction(self.actionPrevLine) self.actionNextCol = QAction('Next column', self) self.actionNextCol.setShortcut(Qt.SHIFT + Qt.Key_Right) self.actionNextCol.triggered.connect(self.fileview.nextCol) self.addAction(self.actionNextCol) self.actionPrevCol = QAction('Prev column', self) self.actionPrevCol.setShortcut(Qt.SHIFT + Qt.Key_Left) self.actionPrevCol.triggered.connect(self.fileview.prevCol) self.addAction(self.actionPrevCol) self._actions = {} for name, desc, icon, key, tip, cb in [ ('navigate', _('File history'), 'hg-log', 'Shift+Return', _('Show the history of the selected file'), self.navigate), ('diffnavigate', _('Compare file revisions'), 'compare-files', None, _('Compare revisions of the selected file'), self.diffNavigate), ('diff', _('Visual Diff'), 'visualdiff', 'Ctrl+D', _('View file changes in external diff tool'), self.vdiff), ('ldiff', _('Visual Diff to Local'), 'ldiff', 'Shift+Ctrl+D', _('View changes to current in external diff tool'), self.vdifflocal), ('edit', _('View at Revision'), 'view-at-revision', 'Alt+Ctrl+E', _('View file as it appeared at this revision'), self.editfile), ('save', _('Save at Revision'), None, 'Alt+Ctrl+S', _('Save file as it appeared at this revision'), self.savefile), ('ledit', _('Edit Local'), 'edit-file', 'Shift+Ctrl+E', _('Edit current file in working copy'), self.editlocal), ('revert', _('Revert to Revision'), 'hg-revert', 'Alt+Ctrl+T', _('Revert file(s) to contents at this revision'), self.revertfile), ('opensubrepo', _('Open subrepository'), 'thg-repository-open', 'Alt+Ctrl+O', _('Open the selected subrepository'), self.opensubrepo), ('explore', _('Explore subrepository'), 'system-file-manager', 'Alt+Ctrl+E', _('Open the selected subrepository'), self.explore), ('terminal', _('Open terminal in subrepository'), 'utilities-terminal', 'Alt+Ctrl+T', _('Open a shell terminal in the selected subrepository root'), self.terminal), ]: act = QAction(desc, self) if icon: act.setIcon(qtlib.getmenuicon(icon)) if key: act.setShortcut(key) if tip: act.setStatusTip(tip) if cb: act.triggered.connect(cb) self._actions[name] = act self.addAction(act) def onRevisionSelected(self, rev): 'called by repowidget when repoview changes revisions' self.ctx = ctx = self.repo.changectx(rev) self.revpanel.set_revision(rev) self.revpanel.update(repo = self.repo) self.message.setHtml('<pre>%s</pre>' % self._deschtmlize(ctx.description())) real = type(rev) is int wd = rev is None for act in ['navigate', 'diffnavigate', 'ldiff', 'edit', 'save']: self._actions[act].setEnabled(real) for act in ['diff', 'revert']: self._actions[act].setEnabled(real or wd) self.actionShowAllMerge.setEnabled(len(ctx.parents()) == 2) self.fileview.setContext(ctx) self.filelist.setContext(ctx) @pyqtSlot() def _updatedeschtmlizer(self): self._deschtmlize = qtlib.descriptionhtmlizer(self.repo.ui) self.onRevisionSelected(self.ctx.rev()) # regenerate desc html def reload(self): 'Task tab is reloaded, or repowidget is refreshed' rev = self.ctx.rev() if type(self.ctx.rev()) is int and len(self.repo) <= self.ctx.rev(): rev = 'tip' self.onRevisionSelected(rev) def navigate(self, filename=None): self._navigate(filename, FileLogDialog, self._nav_dialogs) def diffNavigate(self, filename=None): self._navigate(filename, FileDiffDialog, self._diff_dialogs) def vdiff(self): filenames = self.filelist.getSelectedFiles() if not filenames: return opts = {'change':self.ctx.rev()} dlg = visdiff.visualdiff(self.repo.ui, self.repo, filenames, opts) if dlg: dlg.exec_() def vdifflocal(self): filenames = self.filelist.getSelectedFiles() if not filenames: return assert type(self.ctx.rev()) is int opts = {'rev':['rev(%d)' % (self.ctx.rev())]} dlg = visdiff.visualdiff(self.repo.ui, self.repo, filenames, opts) if dlg: dlg.exec_() def editfile(self): filenames = self.filelist.getSelectedFiles() if not filenames: return rev = self.ctx.rev() if rev is None: qtlib.editfiles(self.repo, filenames, parent=self) else: base, _ = visdiff.snapshot(self.repo, filenames, self.ctx) files = [os.path.join(base, filename) for filename in filenames] qtlib.editfiles(self.repo, files, parent=self) def savefile(self): filenames = self.filelist.getSelectedFiles() if not filenames: return rev = self.ctx.rev() for curfile in filenames: wfile = util.localpath(curfile) wfile, ext = os.path.splitext(os.path.basename(wfile)) if wfile: filename = "%s@%d%s" % (wfile, rev, ext) else: filename = "%s@%d" % (ext, rev) result = QFileDialog.getSaveFileName(parent=self, caption=_("Save file to"), directory=filename) if not result: continue cwd = os.getcwd() try: os.chdir(self.repo.root) try: commands.cat(self.repo.ui, self.repo, curfile, rev = rev, output = hglib.fromunicode(result)) except (util.Abort, IOError), e: QMessageBox.critical(self, _('Unable to save file'), hglib.tounicode(str(e))) finally: os.chdir(cwd) def editlocal(self): filenames = self.filelist.getSelectedFiles() if not filenames: return qtlib.editfiles(self.repo, filenames, parent=self) def revertfile(self): fileSelection = self.filelist.getSelectedFiles() if len(fileSelection) == 0: return rev = self.ctx.rev() if rev is None: rev = self.ctx.p1().rev() dlg = revert.RevertDialog(self.repo, fileSelection, rev, self) dlg.exec_() def _navigate(self, filename, dlgclass, dlgdict): if not filename: filename = self.filelist.getSelectedFiles()[0] if filename is not None and len(self.repo.file(filename))>0: if filename not in dlgdict: dlg = dlgclass(self.repo, filename, repoviewer=self.window()) dlgdict[filename] = dlg ufname = hglib.tounicode(filename) dlg.setWindowTitle(_('Hg file log viewer - %s') % ufname) dlg.setWindowIcon(qtlib.geticon('hg-log')) dlg = dlgdict[filename] dlg.goto(self.ctx.rev()) dlg.show() dlg.raise_() dlg.activateWindow() def opensubrepo(self): path = os.path.join(self.repo.root, self.filelist.currentFile()) if os.path.isdir(path): self.linkActivated.emit(u'subrepo:'+hglib.tounicode(path)) else: QMessageBox.warning(self, _("Cannot open subrepository"), _("The selected subrepository does not exist on the working directory")) def explore(self): root = self.repo.wjoin(self.filelist.currentFile()) if os.path.isdir(root): qtlib.openlocalurl(root) def terminal(self): root = self.repo.wjoin(self.filelist.currentFile()) if os.path.isdir(root): qtlib.openshell(root, self.filelist.currentFile()) #@pyqtSlot(QModelIndex) def onDoubleClick(self, index): model = self.filelist.model() itemissubrepo = (model.dataFromIndex(index)['status'] == 'S') if itemissubrepo: self.opensubrepo() else: self.vdiff() @pyqtSlot(QPoint) def menuRequest(self, point): index = self.filelist.currentIndex() if not index.isValid(): return model = self.filelist.model() data = model.dataFromIndex(index) if not data: return itemissubrepo = (data['status'] == 'S') # Subrepos and regular items have different context menus if itemissubrepo: contextmenu = self.subrepocontextmenu actionlist = ['opensubrepo', 'explore', 'terminal'] else: contextmenu = self.filecontextmenu actionlist = ['diff', 'ldiff', 'edit', 'save', 'ledit', 'revert', 'navigate', 'diffnavigate'] if not contextmenu: contextmenu = QMenu(self) for act in actionlist: if act: contextmenu.addAction(self._actions[act]) else: contextmenu.addSeparator() if itemissubrepo: self.subrepocontextmenu = contextmenu else: self.filecontextmenu = contextmenu if len(self.filelist.getSelectedFiles()) > 1 and not itemissubrepo: singlefileactions = False else: singlefileactions = True self._actions['navigate'].setEnabled(singlefileactions) self._actions['diffnavigate'].setEnabled(singlefileactions) if actionlist: contextmenu.exec_(self.filelist.viewport().mapToGlobal(point)) def saveSettings(self, s): wb = "RevDetailsWidget/" for n in self.splitternames: s.setValue(wb + n, getattr(self, n).saveState()) s.setValue(wb + 'revpanel.expanded', self.revpanel.is_expanded()) self.fileview.saveSettings(s, 'revpanel/fileview') def loadSettings(self, s): wb = "RevDetailsWidget/" for n in self.splitternames: getattr(self, n).restoreState(s.value(wb + n).toByteArray()) expanded = s.value(wb + 'revpanel.expanded', False).toBool() self.revpanel.set_expanded(expanded) self.fileview.loadSettings(s, 'revpanel/fileview')
class RevDetailsWidget(QWidget, qtlib.TaskWidget): showMessage = pyqtSignal(QString) linkActivated = pyqtSignal(unicode) grepRequested = pyqtSignal(unicode, dict) revisionSelected = pyqtSignal(int) updateToRevision = pyqtSignal(int) def __init__(self, repo, parent): QWidget.__init__(self, parent) self.repo = repo self.ctx = repo[None] self.splitternames = [] self.setupUi() self.createActions() self.setupModels() self._deschtmlize = qtlib.descriptionhtmlizer(repo.ui) repo.configChanged.connect(self._updatedeschtmlizer) def setRepo(self, repo): self.repo = repo self.fileview.setRepo(repo) self.filelist.setRepo(repo) self._fileactions.setRepo(repo) def setupUi(self): SP = QSizePolicy sp = SP(SP.Preferred, SP.Expanding) sp.setHorizontalStretch(0) sp.setVerticalStretch(0) sp.setHeightForWidth(self.sizePolicy().hasHeightForWidth()) self.setSizePolicy(sp) # + basevbox -------------------------------------------------------+ # |+ filelistsplit ........ | # | + filelistframe (vbox) | + panelframe (vbox) | # | + filelisttbar | + revpanel | # +---------------------------+-------------------------------------+ # | + filelist | + messagesplitter | # | | :+ message | # | | :----------------------------------+ # | | + fileview | # +---------------------------+-------------------------------------+ basevbox = QVBoxLayout(self) basevbox.setSpacing(0) basevbox.setMargin(0) basevbox.setContentsMargins(2, 2, 2, 2) self.filelistsplit = QSplitter(self) basevbox.addWidget(self.filelistsplit) self.splitternames.append('filelistsplit') sp = SP(SP.Expanding, SP.Expanding) sp.setHorizontalStretch(0) sp.setVerticalStretch(0) sp.setHeightForWidth( self.filelistsplit.sizePolicy().hasHeightForWidth()) self.filelistsplit.setSizePolicy(sp) self.filelistsplit.setOrientation(Qt.Horizontal) self.filelistsplit.setChildrenCollapsible(False) self.filelisttbar = QToolBar(_('File List Toolbar')) self.filelisttbar.setIconSize(QSize(16, 16)) self.filelist = HgFileListView(self.repo, self, True) self.filelist.linkActivated.connect(self.linkActivated) self.filelist.setContextMenuPolicy(Qt.CustomContextMenu) self.filelist.customContextMenuRequested.connect(self.menuRequest) self.filelist.doubleClicked.connect(self.onDoubleClick) self.filelistframe = QFrame(self.filelistsplit) sp = SP(SP.Preferred, SP.Preferred) sp.setHorizontalStretch(3) sp.setVerticalStretch(0) sp.setHeightForWidth( self.filelistframe.sizePolicy().hasHeightForWidth()) self.filelistframe.setSizePolicy(sp) self.filelistframe.setFrameShape(QFrame.NoFrame) vbox = QVBoxLayout() vbox.setSpacing(0) vbox.setMargin(0) vbox.addWidget(self.filelisttbar) vbox.addWidget(self.filelist) self.filelistframe.setLayout(vbox) self.fileviewframe = QFrame(self.filelistsplit) sp = SP(SP.Preferred, SP.Preferred) sp.setHorizontalStretch(7) sp.setVerticalStretch(0) sp.setHeightForWidth( self.fileviewframe.sizePolicy().hasHeightForWidth()) self.fileviewframe.setSizePolicy(sp) self.fileviewframe.setFrameShape(QFrame.NoFrame) vbox = QVBoxLayout(self.fileviewframe) vbox.setSpacing(0) vbox.setSizeConstraint(QLayout.SetDefaultConstraint) vbox.setMargin(0) panelframevbox = vbox self.messagesplitter = QSplitter(self.fileviewframe) self.splitternames.append('messagesplitter') sp = SP(SP.Preferred, SP.Expanding) sp.setHorizontalStretch(0) sp.setVerticalStretch(0) sp.setHeightForWidth( self.messagesplitter.sizePolicy().hasHeightForWidth()) self.messagesplitter.setSizePolicy(sp) self.messagesplitter.setMinimumSize(QSize(50, 50)) self.messagesplitter.setFrameShape(QFrame.NoFrame) self.messagesplitter.setLineWidth(0) self.messagesplitter.setMidLineWidth(0) self.messagesplitter.setOrientation(Qt.Vertical) self.messagesplitter.setOpaqueResize(True) self.message = QTextBrowser(self.messagesplitter, lineWrapMode=QTextEdit.NoWrap, openLinks=False) self.message.minimumSizeHint = lambda: QSize(0, 25) self.message.anchorClicked.connect( lambda url: self.linkActivated.emit(url.toString())) sp = SP(SP.Expanding, SP.Expanding) sp.setHorizontalStretch(0) sp.setVerticalStretch(2) sp.setHeightForWidth(self.message.sizePolicy().hasHeightForWidth()) self.message.setSizePolicy(sp) self.message.setMinimumSize(QSize(0, 0)) f = qtlib.getfont('fontcomment') self.message.setFont(f.font()) f.changed.connect(self.forwardFont) self.fileview = HgFileView(self.repo, self.messagesplitter) sp = SP(SP.Expanding, SP.Expanding) sp.setHorizontalStretch(0) sp.setVerticalStretch(5) sp.setHeightForWidth(self.fileview.sizePolicy().hasHeightForWidth()) self.fileview.setSizePolicy(sp) self.fileview.setMinimumSize(QSize(0, 0)) self.fileview.linkActivated.connect(self.linkActivated) self.fileview.setFont(qtlib.getfont('fontdiff').font()) self.fileview.showMessage.connect(self.showMessage) self.fileview.grepRequested.connect(self.grepRequested) self.fileview.revisionSelected.connect(self.revisionSelected) self.filelist.fileSelected.connect(self.fileview.displayFile) self.filelist.fileSelected.connect(self.updateItemFileActions) self.filelist.clearDisplay.connect(self.fileview.clearDisplay) self.revpanel = RevPanelWidget(self.repo) self.revpanel.linkActivated.connect(self.linkActivated) panelframevbox.addWidget(self.revpanel) panelframevbox.addSpacing(5) panelframevbox.addWidget(self.messagesplitter) def forwardFont(self, font): self.message.setFont(font) def setupModels(self): self.filelistmodel = model = HgFileListModel(self) self.filelistmodel.showMessage.connect(self.showMessage) self.filelist.setModel(model) self.actionShowAllMerge.toggled.connect(model.toggleFullFileList) # Because filelist.fileSelected, i.e. currentRowChanged, is emitted # *before* selection changed, we cannot rely only on fileSelected. # On fileSelected, getSelectedFiles() happens to return the previous # selection, even though currentFile() works as expected. self.filelist.selectionModel().selectionChanged.connect( self.updateItemFileActions) def createActions(self): self.actionUpdate = a = self.filelisttbar.addAction( qtlib.geticon('hg-update'), _('Update to this revision')) a.triggered.connect(lambda: self.updateToRevision.emit(self.ctx.rev())) self.filelisttbar.addSeparator() self.actionShowAllMerge = QAction(_('Show All'), self) self.actionShowAllMerge.setToolTip( _('Toggle display of all files and the direction they were merged') ) self.actionShowAllMerge.setCheckable(True) self.actionShowAllMerge.setChecked(False) self.actionShowAllMerge.setEnabled(False) self.filelisttbar.addAction(self.actionShowAllMerge) self.actionNextLine = QAction('Next line', self) self.actionNextLine.setShortcut(Qt.SHIFT + Qt.Key_Down) self.actionNextLine.triggered.connect(self.fileview.nextLine) self.addAction(self.actionNextLine) self.actionPrevLine = QAction('Prev line', self) self.actionPrevLine.setShortcut(Qt.SHIFT + Qt.Key_Up) self.actionPrevLine.triggered.connect(self.fileview.prevLine) self.addAction(self.actionPrevLine) self.actionNextCol = QAction('Next column', self) self.actionNextCol.setShortcut(Qt.SHIFT + Qt.Key_Right) self.actionNextCol.triggered.connect(self.fileview.nextCol) self.addAction(self.actionNextCol) self.actionPrevCol = QAction('Prev column', self) self.actionPrevCol.setShortcut(Qt.SHIFT + Qt.Key_Left) self.actionPrevCol.triggered.connect(self.fileview.prevCol) self.addAction(self.actionPrevCol) self._fileactions = filectxactions.FilectxActions(self.repo, self, rev=self.ctx.rev()) self._fileactions.linkActivated.connect(self.linkActivated) self.addActions(self._fileactions.actions()) def onRevisionSelected(self, rev): 'called by repowidget when repoview changes revisions' self.ctx = ctx = self.repo.changectx(rev) self.revpanel.set_revision(rev) self.revpanel.update(repo=self.repo) msg = ctx.description() inlinetags = self.repo.ui.configbool('tortoisehg', 'issue.inlinetags') if ctx.tags() and inlinetags: msg = ' '.join(['[%s]' % tag for tag in ctx.tags()]) + ' ' + msg self.message.setHtml('<pre>%s</pre>' % self._deschtmlize(msg)) self._fileactions.setRev(rev) self.actionShowAllMerge.setEnabled(len(ctx.parents()) == 2) self.fileview.setContext(ctx) self.filelist.setContext(ctx) @pyqtSlot() def _updatedeschtmlizer(self): self._deschtmlize = qtlib.descriptionhtmlizer(self.repo.ui) self.onRevisionSelected(self.ctx.rev()) # regenerate desc html def reload(self): 'Task tab is reloaded, or repowidget is refreshed' rev = self.ctx.rev() if (type(self.ctx.rev()) is int and len(self.repo) <= self.ctx.rev() or (rev is not None # wctxrev in repo raises TypeError and rev not in self.repo and rev not in self.repo.thgmqunappliedpatches)): rev = 'tip' self.onRevisionSelected(rev) #@pyqtSlot(QModelIndex) def onDoubleClick(self, index): model = self.filelist.model() itemstatus = model.dataFromIndex(index)['status'] itemissubrepo = (itemstatus == 'S') if itemissubrepo: self._fileactions.opensubrepo() elif itemstatus == 'C': self._fileactions.editfile() else: self._fileactions.vdiff() @pyqtSlot(QPoint) def menuRequest(self, point): index = self.filelist.currentIndex() if not index.isValid(): return model = self.filelist.model() data = model.dataFromIndex(index) if not data: return contextmenu = self._fileactions.menu() if contextmenu: contextmenu.exec_(self.filelist.viewport().mapToGlobal(point)) @pyqtSlot() def updateItemFileActions(self): index = self.filelist.currentIndex() model = self.filelist.model() data = model.dataFromIndex(index) if not data: return itemissubrepo = (data['status'] == 'S') self._fileactions.setPaths_(self.filelist.getSelectedFiles(), self.filelist.currentFile(), itemissubrepo) def saveSettings(self, s): wb = "RevDetailsWidget/" for n in self.splitternames: s.setValue(wb + n, getattr(self, n).saveState()) s.setValue(wb + 'revpanel.expanded', self.revpanel.is_expanded()) self.fileview.saveSettings(s, 'revpanel/fileview') def loadSettings(self, s): wb = "RevDetailsWidget/" for n in self.splitternames: getattr(self, n).restoreState(s.value(wb + n).toByteArray()) expanded = s.value(wb + 'revpanel.expanded', False).toBool() self.revpanel.set_expanded(expanded) self.fileview.loadSettings(s, 'revpanel/fileview')