class SVNPluginPropsDialog(QDialog): """SVN plugin properties dialog""" def __init__(self, plugin, client, path, parent=None): QDialog.__init__(self, parent) self.__plugin = plugin self.__client = client self.__path = path self.__createLayout() self.setWindowTitle("SVN Properties of " + path) self.__populate() self.__propsView.setFocus() def __populate(self): """Populate the properties list""" # Get the currently selected name selectedName = None selected = list(self.__propsView.selectedItems()) if selected: selectedName = str(selected[0].text(0)) self.__propsView.clear() properties = readProperties(self.__client, self.__path) if properties: for itemPath, itemProps in properties: if self.__path == itemPath or \ self.__path == itemPath + os.path.sep: for name, value in itemProps.iteritems(): name = str(name).strip() value = str(value).strip() newItem = QTreeWidgetItem([name, value]) self.__propsView.addTopLevelItem(newItem) self.__resizePropsView() self.__sortPropsView() if selectedName: index = 0 for index in range(0, self.__propsView.topLevelItemCount()): item = self.__propsView.topLevelItem(index) if selectedName == item.text(0): item.setSelected(True) def __resizePropsView(self): """Resizes the properties table""" self.__propsView.header().setStretchLastSection(True) self.__propsView.header().resizeSections(QHeaderView.ResizeToContents) def __sortPropsView(self): """Sorts the properties table""" self.__propsView.sortItems( self.__propsView.sortColumn(), self.__propsView.header().sortIndicatorOrder()) def __createLayout(self): """Creates the dialog layout""" self.resize(640, 480) self.setSizeGripEnabled(True) vboxLayout = QVBoxLayout(self) hLayout = QHBoxLayout() self.__propsView = QTreeWidget() self.__propsView.setAlternatingRowColors(True) self.__propsView.setRootIsDecorated(False) self.__propsView.setItemsExpandable(False) self.__propsView.setSortingEnabled(True) self.__propsView.setItemDelegate(NoOutlineHeightDelegate(4)) self.__propsView.itemSelectionChanged.connect( self.__propsSelectionChanged) propsViewHeader = QTreeWidgetItem(["Property Name", "Property Value"]) self.__propsView.setHeaderItem(propsViewHeader) self.__propsView.header().setSortIndicator(0, Qt.DescendingOrder) hLayout.addWidget(self.__propsView) self.__delButton = QToolButton() self.__delButton.setText("Delete") self.__delButton.setFocusPolicy(Qt.NoFocus) self.__delButton.setEnabled(False) self.__delButton.clicked.connect(self.__onDel) hLayout.addWidget(self.__delButton, 0, Qt.AlignBottom) vboxLayout.addLayout(hLayout) # Set property part setGroupbox = QGroupBox(self) setGroupbox.setTitle("Set Property") setLayout = QGridLayout(setGroupbox) setLayout.addWidget(QLabel("Name"), 0, 0, Qt.AlignTop | Qt.AlignRight) setLayout.addWidget(QLabel("Value"), 1, 0, Qt.AlignTop | Qt.AlignRight) self.__nameEdit = QLineEdit() self.__nameEdit.textChanged.connect(self.__nameChanged) setLayout.addWidget(self.__nameEdit, 0, 1) self.__valueEdit = QTextEdit() self.__valueEdit.setAcceptRichText(False) self.__valueEdit.textChanged.connect(self.__valueChanged) metrics = QFontMetrics(self.__valueEdit.font()) rect = metrics.boundingRect("X") self.__valueEdit.setFixedHeight(rect.height() * 4 + 5) setLayout.addWidget(self.__valueEdit, 1, 1) self.__setButton = QToolButton() self.__setButton.setText("Set") self.__setButton.setFocusPolicy(Qt.NoFocus) self.__setButton.setEnabled(False) self.__setButton.clicked.connect(self.__onSet) setLayout.addWidget(self.__setButton, 1, 2, Qt.AlignBottom | Qt.AlignHCenter) sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Maximum) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( setGroupbox.sizePolicy().hasHeightForWidth()) setGroupbox.setSizePolicy(sizePolicy) vboxLayout.addWidget(setGroupbox) # Buttons at the bottom buttonBox = QDialogButtonBox(self) buttonBox.setOrientation(Qt.Horizontal) buttonBox.setStandardButtons(QDialogButtonBox.Ok) buttonBox.button(QDialogButtonBox.Ok).setDefault(True) buttonBox.accepted.connect(self.close) vboxLayout.addWidget(buttonBox) def __onSet(self): """Triggered when propery set is clicked""" name = self.__nameEdit.text().strip() value = self.__valueEdit.toPlainText().strip() try: commitInfo = self.__client.propset(name, value, self.__path) if commitInfo: logging.info(str(commitInfo)) self.__populate() self.__plugin.notifyPathChanged(self.__path) self.__nameEdit.clear() self.__valueEdit.clear() self.__propsView.setFocus() except pysvn.ClientError as exc: message = exc.args[0] logging.error(message) except Exception as exc: logging.error(str(exc)) except: logging.error("Unknown property setting error") def __propsSelectionChanged(self): """Selection of a property has changed""" selected = list(self.__propsView.selectedItems()) self.__delButton.setEnabled(len(selected) > 0) def __onDel(self): """Triggered when a property del is clicked""" selected = list(self.__propsView.selectedItems()) if len(selected) == 0: self.__delButton.setEnabled(False) return name = str(selected[0].text(0)) res = QMessageBox.warning( self, "Deleting Property", "You are about to delete <b>" + name + "</b> SVN property from " + self.__path + ".\nAre you sure?", QMessageBox.StandardButtons(QMessageBox.Cancel | QMessageBox.Yes), QMessageBox.Cancel) if res != QMessageBox.Yes: return try: self.__client.propdel(name, self.__path) self.__populate() self.__plugin.notifyPathChanged(self.__path) self.__propsView.setFocus() except pysvn.ClientError as exc: message = exc.args[0] logging.error(message) except Exception as exc: logging.error(str(exc)) except: logging.error("Unknown property deleting error") def __nameChanged(self, text): """Triggered when a property name to set is changed""" self.__updateSetButton() def __valueChanged(self): """Triggered when a property value to set is changed""" self.__updateSetButton() def __updateSetButton(self): """Updates the 'Set' button state""" name = self.__nameEdit.text().strip() value = self.__valueEdit.toPlainText().strip() self.__setButton.setEnabled(name != "" and value != "")
class SVNPluginLogDialog(QDialog): """SVN plugin log dialog""" NODIFF = '<html><body bgcolor="#ffffe6"></body></html>' def __init__(self, plugin, client, path, logInfo, parent=None): QDialog.__init__(self, parent) self.__plugin = plugin self.__client = client self.__path = path self.__logInfo = logInfo self.__lhsSelected = None self.__rhsSelected = None self.__createLayout() self.setWindowTitle("SVN Log") lastIndex = len(self.__logInfo) - 1 index = 0 for log in self.__logInfo: newItem = LogItem(log) self.__logView.addTopLevelItem(newItem) if index != lastIndex: rev = log.revision.number nextRev = self.__logInfo[index + 1].revision.number diffButton = self.__createDiffButton( log.revision, self.__logInfo[index + 1].revision) if rev is not None and nextRev is not None: diffButton.setToolTip( "Click to see diff to the older revision (r." + str(rev) + " to r." + str(nextRev) + ")") else: diffButton.setEnabled(False) diffButton.setToolTip( "Could not determine current or previous revision") else: diffButton = self.__createDiffButton(None, None) diffButton.setEnabled(False) diffButton.setToolTip( "Diff to previous revision is not avalable for the first revision" ) self.__logView.setItemWidget(newItem, DIFFTONEXT_COL, diffButton) index += 1 self.__resizeLogView() self.__sortLogView() self.__logView.setFocus() def __createDiffButton(self, rev, prevRev): """Creates a diff button for a path""" button = DiffButton() button.rev = rev button.prevRev = prevRev self.connect(button, SIGNAL('CustomClick'), self.onDiffBetween) return button def __resizeLogView(self): """Resizes the plugins table""" self.__logView.header().setStretchLastSection(True) self.__logView.header().resizeSections(QHeaderView.ResizeToContents) self.__logView.header().resizeSection(SELECT_COL, 28) self.__logView.header().setResizeMode(SELECT_COL, QHeaderView.Fixed) self.__logView.header().resizeSection(DIFFTONEXT_COL, 24) self.__logView.header().setResizeMode(DIFFTONEXT_COL, QHeaderView.Fixed) def __sortLogView(self): """Sorts the log table""" self.__logView.sortItems(self.__logView.sortColumn(), self.__logView.header().sortIndicatorOrder()) def __createLayout(self): """Creates the dialog layout""" self.resize(640, 480) self.setSizeGripEnabled(True) vboxLayout = QVBoxLayout(self) # Revisions to compare compareGroupbox = QGroupBox(self) compareGroupbox.setTitle("Revisions to compare") sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( compareGroupbox.sizePolicy().hasHeightForWidth()) compareGroupbox.setSizePolicy(sizePolicy) revisionLayout = QHBoxLayout(compareGroupbox) self.__lhsRevisionLabel = QLabel() self.__lhsRevisionLabel.setFrameStyle(QFrame.StyledPanel) self.__lhsResetButton = QToolButton() self.__lhsResetButton.setIcon( getIcon(pluginHomeDir + 'svnclearrev.png')) self.__lhsResetButton.setFocusPolicy(Qt.NoFocus) self.__lhsResetButton.setEnabled(False) self.__lhsResetButton.setToolTip("Reset revision to compare") self.__lhsResetButton.clicked.connect(self.__onLHSReset) self.__rhsRevisionLabel = QLabel() self.__rhsRevisionLabel.setFrameStyle(QFrame.StyledPanel) self.__rhsResetButton = QToolButton() self.__rhsResetButton.setIcon( getIcon(pluginHomeDir + 'svnclearrev.png')) self.__rhsResetButton.setFocusPolicy(Qt.NoFocus) self.__rhsResetButton.setEnabled(False) self.__rhsResetButton.setToolTip("Reset revision to compare") self.__rhsResetButton.clicked.connect(self.__onRHSReset) lhsLayout = QHBoxLayout() lhsLayout.addWidget(self.__lhsRevisionLabel) lhsLayout.addWidget(self.__lhsResetButton) rhsLayout = QHBoxLayout() rhsLayout.addWidget(self.__rhsRevisionLabel) rhsLayout.addWidget(self.__rhsResetButton) bothLayout = QVBoxLayout() bothLayout.addLayout(lhsLayout) bothLayout.addLayout(rhsLayout) revisionLayout.addLayout(bothLayout) self.__diffButton = QToolButton() self.__diffButton.setText("Diff") self.__diffButton.setFocusPolicy(Qt.NoFocus) self.__diffButton.setEnabled(False) self.__diffButton.clicked.connect(self.__onDiff) revisionLayout.addWidget(self.__diffButton) vboxLayout.addWidget(compareGroupbox) # Log table logHeaderFrame = QFrame() logHeaderFrame.setFrameStyle(QFrame.StyledPanel) logHeaderFrame.setAutoFillBackground(True) self.__setLightPalette(logHeaderFrame) logHeaderFrame.setFixedHeight(24) logHeaderLayout = QHBoxLayout() logHeaderLayout.setContentsMargins(3, 0, 0, 0) logHeaderLayout.addWidget(QLabel("Subversion log of " + self.__path)) logHeaderFrame.setLayout(logHeaderLayout) vboxLayout.addWidget(logHeaderFrame) self.__logView = QTreeWidget() self.__logView.setAlternatingRowColors(True) self.__logView.setRootIsDecorated(False) self.__logView.setItemsExpandable(False) self.__logView.setSortingEnabled(True) self.__logView.setItemDelegate(NoOutlineHeightDelegate(4)) self.__logViewHeader = QTreeWidgetItem( ["", "", "Revision", "Date", "Author", "Message"]) self.__logView.setHeaderItem(self.__logViewHeader) self.__logView.header().setSortIndicator(REVISION_COL, Qt.AscendingOrder) self.__logView.itemChanged.connect(self.__onLogViewChanged) vboxLayout.addWidget(self.__logView) # Diff part diffHeaderFrame = QFrame() diffHeaderFrame.setFrameStyle(QFrame.StyledPanel) diffHeaderFrame.setAutoFillBackground(True) self.__setLightPalette(diffHeaderFrame) diffHeaderFrame.setFixedHeight(24) diffLabel = QLabel("Diff") diffExpandingSpacer = QSpacerItem(10, 10, QSizePolicy.Expanding) self.__showHideDiffButton = QToolButton() self.__showHideDiffButton.setAutoRaise(True) self.__showHideDiffButton.setIcon(getIcon('less.png')) self.__showHideDiffButton.setFixedSize(20, 20) self.__showHideDiffButton.setToolTip("Show diff") self.__showHideDiffButton.setFocusPolicy(Qt.NoFocus) self.__showHideDiffButton.clicked.connect(self.__onShowHideDiff) diffLayout = QHBoxLayout() diffLayout.setContentsMargins(3, 0, 0, 0) diffLayout.addWidget(diffLabel) diffLayout.addSpacerItem(diffExpandingSpacer) diffLayout.addWidget(self.__showHideDiffButton) diffHeaderFrame.setLayout(diffLayout) self.__diffViewer = DiffTabWidget() self.__diffViewer.setHTML(self.NODIFF) self.__diffViewer.setVisible(False) vboxLayout.addWidget(diffHeaderFrame) vboxLayout.addWidget(self.__diffViewer) # Buttons at the bottom buttonBox = QDialogButtonBox(self) buttonBox.setOrientation(Qt.Horizontal) buttonBox.setStandardButtons(QDialogButtonBox.Ok) buttonBox.button(QDialogButtonBox.Ok).setDefault(True) buttonBox.accepted.connect(self.close) vboxLayout.addWidget(buttonBox) @staticmethod def __setLightPalette(frame): """Creates a lighter palette for the widget background""" palette = frame.palette() background = palette.color(QPalette.Background) background.setRgb(min(background.red() + 30, 255), min(background.green() + 30, 255), min(background.blue() + 30, 255)) palette.setColor(QPalette.Background, background) frame.setPalette(palette) def __onShowHideDiff(self): """On/off the diff section""" if self.__diffViewer.isVisible(): self.__diffViewer.setVisible(False) self.__showHideDiffButton.setIcon(getIcon('less.png')) self.__showHideDiffButton.setToolTip("Show diff") else: self.__diffViewer.setVisible(True) self.__showHideDiffButton.setIcon(getIcon('more.png')) self.__showHideDiffButton.setToolTip("Hide diff") def onDiffBetween(self, rev, prevRev): """Called when diff is requested between revisions""" if not rev or not prevRev: return QApplication.setOverrideCursor(QCursor(Qt.WaitCursor)) try: lhsContent = self.__client.cat(self.__path, prevRev) rhsContent = self.__client.cat(self.__path, rev) except Exception as exc: QApplication.restoreOverrideCursor() logging.error(str(exc)) return except: QApplication.restoreOverrideCursor() logging.error("Unknown error while retrieving " + self.__path + " content from the repository.") return QApplication.restoreOverrideCursor() diff = difflib.unified_diff(lhsContent.splitlines(), rhsContent.splitlines()) nodiffMessage = self.__path + " has no difference from revision " + \ str(prevRev.number) + " to revision " + str(rev.number) if diff is None: logging.info(nodiffMessage) return # There are changes, so replace the text and tell about the changes diffAsText = '\n'.join(list(diff)) if diffAsText.strip() == '': logging.info(nodiffMessage) return lhs = "--- revision " + str(prevRev.number) diffAsText = diffAsText.replace("--- ", lhs, 1) rhs = "+++ revision " + str(rev.number) diffAsText = diffAsText.replace("+++ ", rhs, 1) self.__diffViewer.setHTML(parse_from_memory(diffAsText, False, True)) if not self.__diffViewer.isVisible(): self.__onShowHideDiff() def __onDiff(self): """Show diff between revisions""" self.onDiffBetween(self.__rhsSelected.revision, self.__lhsSelected.revision) def __onLogViewChanged(self, item, column): """Revision selected for diff""" if item.checkState(SELECT_COL) == Qt.Checked: # An item has been selected if self.__lhsSelected is None: self.__lhsSelected = item.logInfo self.__normalizeSelected() return if self.__rhsSelected is None: self.__rhsSelected = item.logInfo self.__normalizeSelected() return # Both of the places have been occupied. Pick the one to update. if item.logInfo.date > self.__rhsSelected.date: self.__rhsSelected = item.logInfo else: self.__lhsSelected = item.logInfo self.__normalizeSelected() else: # An item has been de-selected if self.__lhsSelected is not None: if self.__lhsSelected.revision.number == item.logInfo.revision.number: self.__lhsSelected = None elif self.__rhsSelected is not None: if self.__rhsSelected.revision.number == item.logInfo.revision.number: self.__rhsSelected = None self.__normalizeSelected() return def __onLHSReset(self): """Revision removed from diff""" if self.__lhsSelected is not None: self.__deselectRevision(self.__lhsSelected.revision.number) self.__lhsSelected = None self.__lhsRevisionLabel.setText("") self.__diffButton.setEnabled(False) self.__lhsResetButton.setEnabled(False) def __onRHSReset(self): """Revision removed from diff""" if self.__rhsSelected is not None: self.__deselectRevision(self.__rhsSelected.revision.number) self.__rhsSelected = None self.__rhsRevisionLabel.setText("") self.__diffButton.setEnabled(False) self.__rhsResetButton.setEnabled(False) def __deselectRevision(self, revNumber): """Deselects a revision in the list""" index = 0 while index < self.__logView.topLevelItemCount(): item = self.__logView.topLevelItem(index) if item.logInfo.revision.number == revNumber: item.setCheckState(SELECT_COL, Qt.Unchecked) break index += 1 def __normalizeSelected(self): """Puts the earliest revision first""" if self.__lhsSelected is not None and self.__rhsSelected is not None: # It might be necessary to exchange the versions if self.__rhsSelected.date < self.__lhsSelected.date: temp = self.__rhsSelected self.__rhsSelected = self.__lhsSelected self.__lhsSelected = temp self.__diffButton.setEnabled(True) else: self.__diffButton.setEnabled(False) if self.__lhsSelected is None: self.__lhsRevisionLabel.setText("") self.__lhsRevisionLabel.setToolTip("") self.__lhsResetButton.setEnabled(False) else: self.__lhsRevisionLabel.setText( str(self.__lhsSelected.revision.number) + " (" + timestampToString(self.__lhsSelected.date) + ")") self.__lhsRevisionLabel.setToolTip(str(self.__lhsSelected.message)) self.__lhsResetButton.setEnabled(True) if self.__rhsSelected is None: self.__rhsRevisionLabel.setText("") self.__rhsRevisionLabel.setToolTip("") self.__rhsResetButton.setEnabled(False) else: self.__rhsRevisionLabel.setText( str(self.__rhsSelected.revision.number) + " (" + timestampToString(self.__rhsSelected.date) + ")") self.__rhsRevisionLabel.setToolTip(str(self.__rhsSelected.message)) self.__rhsResetButton.setEnabled(True)