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')
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)
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')
def setupUi(self): self.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Expanding) # + 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') self.filelistsplit.setOrientation(Qt.Horizontal) self.filelistsplit.setChildrenCollapsible(False) self.filelisttbar = QToolBar(_('File List Toolbar')) self.filelisttbar.setIconSize(qtlib.smallIconSize()) self.filelist = HgFileListView(self) self.filelist.setContextMenuPolicy(Qt.CustomContextMenu) self.filelist.customContextMenuRequested.connect(self.menuRequest) self.filelist.doubleClicked.connect(self.onDoubleClick) self._filelistpaletteswitcher = qtlib.PaletteSwitcher(self.filelist) self.filelistframe = QWidget(self.filelistsplit) self.filelistsplit.setStretchFactor(0, 3) vbox = QVBoxLayout() vbox.setSpacing(0) vbox.setMargin(0) vbox.addWidget(self.filelisttbar) vbox.addWidget(self.filelist) self.filelistframe.setLayout(vbox) self.fileviewframe = QWidget(self.filelistsplit) self.filelistsplit.setStretchFactor(1, 7) 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') self.messagesplitter.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Expanding) 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) self.message.setMinimumSize(QSize(0, 0)) self.message.sizeHint = lambda: QSize(0, 100) f = qtlib.getfont('fontcomment') self.message.setFont(f.font()) f.changed.connect(self.forwardFont) self.fileview = HgFileView(self._repoagent, self.messagesplitter) self.messagesplitter.setStretchFactor(1, 1) self.fileview.setMinimumSize(QSize(0, 0)) self.fileview.linkActivated.connect(self.linkActivated) self.fileview.showMessage.connect(self.showMessage) self.fileview.grepRequested.connect(self.grepRequested) self.fileview.revisionSelected.connect(self.revisionSelected) self.filelist.fileSelected.connect(self._onFileSelected) self.filelist.clearDisplay.connect(self._onFileSelected) self.revpanel = RevPanelWidget(self.repo) self.revpanel.linkActivated.connect(self.linkActivated) panelframevbox.addWidget(self.revpanel) panelframevbox.addSpacing(5) panelframevbox.addWidget(self.messagesplitter)
class RevDetailsWidget(QWidget, qtlib.TaskWidget): showMessage = pyqtSignal(str) linkActivated = pyqtSignal(str) grepRequested = pyqtSignal(str, dict) revisionSelected = pyqtSignal(int) revsetFilterRequested = pyqtSignal(str) runCustomCommandRequested = pyqtSignal(str, list) def __init__(self, repoagent, parent, rev=None): QWidget.__init__(self, parent) self._repoagent = repoagent repo = repoagent.rawRepo() self.ctx = repo[rev] self.splitternames = [] self.setupUi() self.createActions() self.setupModels() self.filelist.installEventFilter(self) self.filefilter.installEventFilter(self) self._deschtmlize = qtlib.descriptionhtmlizer(repo.ui) repoagent.configChanged.connect(self._updatedeschtmlizer) @property def repo(self): return self._repoagent.rawRepo() def setupUi(self): self.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Expanding) # + 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') self.filelistsplit.setOrientation(Qt.Horizontal) self.filelistsplit.setChildrenCollapsible(False) self.filelisttbar = QToolBar(_('File List Toolbar')) self.filelisttbar.setIconSize(qtlib.smallIconSize()) self.filelist = HgFileListView(self) self.filelist.setContextMenuPolicy(Qt.CustomContextMenu) self.filelist.customContextMenuRequested.connect(self.menuRequest) self.filelist.doubleClicked.connect(self.onDoubleClick) self._filelistpaletteswitcher = qtlib.PaletteSwitcher(self.filelist) self.filelistframe = QWidget(self.filelistsplit) self.filelistsplit.setStretchFactor(0, 3) vbox = QVBoxLayout() vbox.setSpacing(0) vbox.setMargin(0) vbox.addWidget(self.filelisttbar) vbox.addWidget(self.filelist) self.filelistframe.setLayout(vbox) self.fileviewframe = QWidget(self.filelistsplit) self.filelistsplit.setStretchFactor(1, 7) 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') self.messagesplitter.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Expanding) 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) self.message.setMinimumSize(QSize(0, 0)) self.message.sizeHint = lambda: QSize(0, 100) f = qtlib.getfont('fontcomment') self.message.setFont(f.font()) f.changed.connect(self.forwardFont) self.fileview = HgFileView(self._repoagent, self.messagesplitter) self.messagesplitter.setStretchFactor(1, 1) self.fileview.setMinimumSize(QSize(0, 0)) self.fileview.linkActivated.connect(self.linkActivated) self.fileview.showMessage.connect(self.showMessage) self.fileview.grepRequested.connect(self.grepRequested) self.fileview.revisionSelected.connect(self.revisionSelected) self.filelist.fileSelected.connect(self._onFileSelected) self.filelist.clearDisplay.connect(self._onFileSelected) 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): model = manifestmodel.ManifestModel(self._repoagent, self) model.setFlat(not self.isManifestMode() and self.isFlatFileList()) model.setStatusFilter(self.fileStatusFilter()) model.revLoaded.connect(self._expandShortFileList) self.filelist.setModel(model) # fileSelected is actually the wrapper of currentChanged, which is # unrelated to the selection self.filelist.selectionModel().selectionChanged.connect( self.updateItemFileActions) def createActions(self): self._createFileListActions() self._parentToggleGroup.actions()[0].setChecked(True) self._fileactions = filectxactions.FilectxActions(self._repoagent, self) self._fileactions.setupCustomToolsMenu('workbench.filelist.custom-menu') self._fileactions.linkActivated.connect(self.linkActivated) self._fileactions.filterRequested.connect(self.revsetFilterRequested) self._fileactions.runCustomCommandRequested.connect( self.runCustomCommandRequested) self.addActions(self._fileactions.actions()) def _createFileListActions(self): tbar = self.filelisttbar self._actionManifestMode = a = tbar.addAction(_('Ma&nifest Mode')) a.setCheckable(True) a.setIcon(qtlib.geticon('hg-annotate')) a.setToolTip(_('Show all version-controlled files in tree view')) a.triggered.connect(self._applyManifestMode) self._actionFlatFileList = a = QAction(_('&Flat List'), self) a.setCheckable(True) a.setChecked(True) a.triggered.connect(self._applyFlatFileList) le = QLineEdit() if hasattr(le, 'setPlaceholderText'): # Qt >= 4.7 le.setPlaceholderText(_('### filter text ###')) self.filefilter = le tbar.addWidget(self.filefilter) t = QTimer(self, interval=200, singleShot=True) t.timeout.connect(self._applyFileNameFilter) le.textEdited.connect(t.start) le.returnPressed.connect(self.filelist.expandAll) w = status.StatusFilterActionGroup('MARS', 'MARCS', self) self._fileStatusFilter = w w.statusChanged.connect(self._applyFileStatusFilter) # TODO: p1/p2 toggle should be merged with fileview's self._parentToggleGroup = QActionGroup(self) self._parentToggleGroup.triggered.connect(self._selectParentRevision) for i, (icon, text, tip) in enumerate([ ('hg-merged-both', _('Changed by &This Commit'), _('Show files changed by this commit')), ('hg-merged-p1', _('Compare to &1st Parent'), _('Show changes from first parent')), ('hg-merged-p2', _('Compare to &2nd Parent'), _('Show changes from second parent'))]): a = self._parentToggleGroup.addAction(qtlib.geticon(icon), text) a.setCheckable(True) a.setData(i) a.setStatusTip(tip) w = QToolButton(self) m = QMenu(w) m.addActions(self._parentToggleGroup.actions()) w.setMenu(m) w.setPopupMode(QToolButton.MenuButtonPopup) self._actionParentToggle = a = tbar.addWidget(w) a.setIcon(qtlib.geticon('hg-merged-both')) a.setToolTip(_('Toggle parent to be used as the base revision')) a.triggered.connect(self._toggleParentRevision) w.setDefaultAction(a) def canswitch(self): # assumes a user wants to browse changesets in manifest mode. commit # widget isn't suitable for such usage. return not self.isManifestMode() def eventFilter(self, watched, event): # switch between filter and list seamlessly if watched is self.filefilter: if event.type() == QEvent.KeyPress and event.key() == Qt.Key_Down: self.filelist.setFocus() return True return False elif watched is self.filelist: if event.type() == QEvent.KeyPress and event.key() == Qt.Key_Up: index = self.filelist.currentIndex() if index.row() == 0 and not index.parent().isValid(): self.filefilter.setFocus() return True return False return super(RevDetailsWidget, self).eventFilter(watched, event) 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._setContextToFileList(ctx) def _setContextToFileList(self, ctx): # useless to toggle manifest mode in patchctx self._actionManifestMode.setEnabled(not ctx.thgmqunappliedpatch()) self._parentToggleGroup.setVisible(len(ctx.parents()) == 2) self._actionParentToggle.setVisible(self._parentToggleGroup.isVisible()) m = self.filelist.model() if len(ctx.parents()) != 2: m.setRawContext(ctx) m.setChangedFilesOnly(False) self.updateItemFileActions() return parentmode = self._parentToggleGroup.checkedAction().data().toInt()[0] pnum, changedonly = [(0, True), (0, False), (1, False)][parentmode] m.setRev(ctx.rev(), ctx.parents()[pnum].rev()) m.setChangedFilesOnly(changedonly) self.updateItemFileActions() @pyqtSlot(QAction) def _selectParentRevision(self, action): self._actionParentToggle.setIcon(action.icon()) self._setContextToFileList(self.ctx) @pyqtSlot() def _toggleParentRevision(self): parentactions = [a for a in self._parentToggleGroup.actions() if a.isEnabled()] i = parentactions.index(self._parentToggleGroup.checkedAction()) parentactions[(i + 1) % len(parentactions)].trigger() @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(QModelIndex) def onDoubleClick(self, index): model = self.filelist.model() if model.subrepoType(index): self._fileactions.openSubrepo() elif model.isDir(index): # expand/collapse tree by default pass elif model.fileStatus(index) == 'C': self._fileactions.editFile() else: self._fileactions.visualDiffFile() def filePath(self): return hglib.tounicode(self.filelist.currentFile()) def setFilePath(self, path): self.filelist.setCurrentFile(hglib.fromunicode(path)) def showLine(self, line): self.fileview.showLine(line - 1) # fileview should do -1 instead? def setSearchPattern(self, text): self.fileview.searchbar.setPattern(text) @pyqtSlot() def _onFileSelected(self): index = self.filelist.currentIndex() model = self.filelist.model() self.fileview.display(model.fileData(index)) @pyqtSlot(QPoint) def menuRequest(self, point): contextmenu = QMenu(self) if self.filelist.selectionModel().hasSelection(): self._setupFileMenu(contextmenu) contextmenu.addSeparator() m = contextmenu.addMenu(_('List Optio&ns')) else: m = contextmenu m.addAction(self._actionManifestMode) m.addSeparator() m.addActions(self._fileStatusFilter.actions()) m.addSeparator() m.addActions(self._parentToggleGroup.actions()) m.addSeparator() m.addAction(self._actionFlatFileList) contextmenu.setAttribute(Qt.WA_DeleteOnClose) contextmenu.popup(self.filelist.viewport().mapToGlobal(point)) def _setupFileMenu(self, contextmenu): index = self.filelist.currentIndex() model = self.filelist.model() # Subrepos and regular items have different context menus if model.subrepoType(index): actnames = _fileactionsbytype['subrepo'] elif model.isDir(index): actnames = _fileactionsbytype['dir'] else: actnames = _fileactionsbytype['file'] for act in actnames + [None, 'customToolsMenu']: if act: contextmenu.addAction(self._fileactions.action(act)) else: contextmenu.addSeparator() @pyqtSlot() def updateItemFileActions(self): model = self.filelist.model() selmodel = self.filelist.selectionModel() selfds = map(model.fileData, selmodel.selectedIndexes()) self._fileactions.setFileDataList(selfds) @pyqtSlot() def _applyFileNameFilter(self): model = self.filelist.model() match = self.filefilter.text() if model is not None: model.setNameFilter(match) self._filelistpaletteswitcher.enablefilterpalette(bool(match)) self._expandShortFileList() def isManifestMode(self): """In manifest mode, clean files are listed and removed are hidden by default. Also, the view is forcibly switched to the tree mode.""" return self._actionManifestMode.isChecked() def setManifestMode(self, manifestmode): self._actionManifestMode.setChecked(manifestmode) self._applyManifestMode(manifestmode) @pyqtSlot(bool) def _applyManifestMode(self, manifestmode): self._fileStatusFilter.setChecked('C', manifestmode) self._fileStatusFilter.setChecked('R', not manifestmode) self._actionFlatFileList.setVisible(not manifestmode) self._applyFlatFileList(not manifestmode and self.isFlatFileList()) # manifest should show clean files, so only p1/p2 toggles are valid parentactions = self._parentToggleGroup.actions() parentactions[0].setEnabled(not manifestmode) parentactions[int(manifestmode)].trigger() def isFlatFileList(self): return self._actionFlatFileList.isChecked() def setFlatFileList(self, flat): self._actionFlatFileList.setChecked(flat) if not self.isManifestMode(): self._applyFlatFileList(flat) @pyqtSlot(bool) def _applyFlatFileList(self, flat): view = self.filelist model = view.model() model.setFlat(flat) view.setRootIsDecorated(not flat) if flat: view.setTextElideMode(Qt.ElideLeft) else: view.setTextElideMode(Qt.ElideRight) self._expandShortFileList() def fileStatusFilter(self): return self._fileStatusFilter.status() def setFileStatusFilter(self, statustext): self._fileStatusFilter.setStatus(statustext) @pyqtSlot(str) def _applyFileStatusFilter(self, statustext): model = self.filelist.model() model.setStatusFilter(statustext) self._expandShortFileList() @pyqtSlot() def _expandShortFileList(self): if self.isManifestMode(): # because manifest will contain large tree of files return self.filelist.expandAll() def saveSettings(self, s): wb = "RevDetailsWidget/" for n in self.splitternames: s.setValue(wb + n, getattr(self, n).saveState()) s.setValue(wb + 'flatfilelist', self.isFlatFileList()) 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()) self.setFlatFileList(s.value(wb + 'flatfilelist', True).toBool()) expanded = s.value(wb + 'revpanel.expanded', False).toBool() self.revpanel.set_expanded(expanded) self.fileview.loadSettings(s, 'revpanel/fileview')
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)
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')