diff -r 898b0dda21e1 -r 06423d65a2b8 Plugins/VcsPlugins/vcsMercurial/HisteditExtension/HgHisteditPlanEditor.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/VcsPlugins/vcsMercurial/HisteditExtension/HgHisteditPlanEditor.py Sat Oct 29 19:28:04 2016 +0200 @@ -0,0 +1,283 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2016 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing a dialog to edit the history modification plan. +""" + +from PyQt5.QtCore import pyqtSlot, QCoreApplication +from PyQt5.QtWidgets import QDialog, QTreeWidgetItem, QComboBox + +from E5Gui import E5MessageBox + +from Ui_HgHisteditPlanEditor import Ui_HgHisteditPlanEditor + +import UI.PixmapCache + + +class HgHisteditPlanActionComboBox(QComboBox): + """ + Class implementing a combo box to select the action in the plan tree. + """ + def __init__(self, item, column): + """ + Constructor + + @param item reference to the item + @type QTreeWidgetItem + @param column column number inside the tree widget item + @type int + """ + super(HgHisteditPlanActionComboBox, self).__init__() + + self.__item = item + self.__column = column + + self.addItems(["pick", "drop", "mess", "fold", "roll", "edit"]) + txt = self.__item.text(self.__column) + index = self.findText(txt) + if index > -1: + self.setCurrentIndex(index) + + self.currentIndexChanged.connect(self.__changeItem) + + @pyqtSlot(int) + def __changeItem(self, index): + """ + Private slot to handle the selection of a plan action. + + This method sets the text of the associated item for the specified + cell in order to be able to retrieve it with a call of the text() + method of the item. + + @param index index of the selected action + @type int + """ + self.__item.setText(self.__column, self.currentText()) + self.__item.treeWidget().setCurrentItem(self.__item) + + def showPopup(self): + """ + Public method to show the list of items of the combo box. + + This is reimplemented in order to set the associated item as the + current item of the tree widget. + """ + self.__item.treeWidget().setCurrentItem(self.__item) + super(HgHisteditPlanActionComboBox, self).showPopup() + + +class HgHisteditPlanEditor(QDialog, Ui_HgHisteditPlanEditor): + """ + Class implementing a dialog to edit the history modification plan. + """ + def __init__(self, fileName, parent=None): + """ + Constructor + + @param fileName name of the file containing the history edit plan + to be edited + @type str + @param parent reference to the parent widget + @type QWidget + """ + super(HgHisteditPlanEditor, self).__init__(parent) + self.setupUi(self) + + self.upButton.setIcon(UI.PixmapCache.getIcon("1uparrow.png")) + self.downButton.setIcon(UI.PixmapCache.getIcon("1downarrow.png")) + + self.planTreeWidget.headerItem().setText( + self.planTreeWidget.columnCount(), "") + + self.__fileName = fileName + self.__readFile() + + self.__updateButtons() + + def __readFile(self): + """ + Private method to read the file containing the edit plan and + populate the dialog. + """ + try: + f = open(self.__fileName, "r") + txt = f.read() + f.close() + except (IOError, OSError) as err: + E5MessageBox.critical( + self, + self.tr("Edit Plan"), + self.tr("""<p>The file <b>{0}</b> could not be read.</p>""" + """<p>Reason: {1}</p>""").format( + self.__fileName, str(err))) + self.on_buttonBox_rejected() + return + + infoLines = [] + for line in txt.splitlines(): + if line.startswith("#"): + infoLines.append(line[1:].lstrip()) + else: + self.__createPlanItem(line) + self.infoEdit.setPlainText("\n".join(infoLines)) + + self.__resizeSections() + + def __addActionCombo(self, item): + """ + Private method to add an edit action combo to an item. + + @param item reference to the tree widget item + @type QTreeWidgetItem + """ + actionCombo = HgHisteditPlanActionComboBox(item, 0) + self.planTreeWidget.setItemWidget(item, 0, actionCombo) + item.setSizeHint(0, actionCombo.sizeHint()) + + def __createPlanItem(self, text): + """ + Private method to create an edit plan tree item. + """ + if not text.lstrip(): + return + + parts = text.split(" ", 3) + action = parts[0] + try: + rev = int(parts[2]) + if len(parts) > 3: + summary = parts[3] + else: + summary = "" + except ValueError: + rev = -1 + summary = " ".join(parts[2:]) + if rev > -1: + revision = "{0:>7}:{1}".format(rev, parts[1]) + else: + revision = parts[1] + + itm = QTreeWidgetItem(self.planTreeWidget, [ + action, + revision, + summary, + ]) + self.__addActionCombo(itm) + + def __resizeSections(self): + """ + Private method to resize the tree widget sections. + """ + for column in range(self.planTreeWidget.columnCount()): + self.planTreeWidget.resizeColumnToContents(column) + self.planTreeWidget.header().setStretchLastSection(True) + + def __updateButtons(self): + """ + Private method to set the enabled state of the up and down buttons. + """ + if self.planTreeWidget.currentItem() is None: + self.upButton.setEnabled(False) + self.downButton.setEnabled(False) + else: + row = self.planTreeWidget.indexOfTopLevelItem( + self.planTreeWidget.currentItem()) + self.upButton.setEnabled(row > 0) + self.downButton.setEnabled( + row < self.planTreeWidget.topLevelItemCount() - 1) + + @pyqtSlot(QTreeWidgetItem, QTreeWidgetItem) + def on_planTreeWidget_currentItemChanged(self, current, previous): + """ + Private slot handling the change of the current edit plan item. + + @param current reference to the current edit plan item + @type QTreeWidgetItem + @param previous reference to the previous current edit plan item + @type QTreeWidgetItem + """ + self.__updateButtons() + + @pyqtSlot() + def on_upButton_clicked(self): + """ + Private slot to move the current entry up one line. + """ + row = self.planTreeWidget.indexOfTopLevelItem( + self.planTreeWidget.currentItem()) + if row > 0: + targetRow = row - 1 + itm = self.planTreeWidget.takeTopLevelItem(row) + self.planTreeWidget.insertTopLevelItem(targetRow, itm) + self.__addActionCombo(itm) + self.planTreeWidget.setCurrentItem(itm) + + @pyqtSlot() + def on_downButton_clicked(self): + """ + Private slot to move the current entry down one line. + """ + row = self.planTreeWidget.indexOfTopLevelItem( + self.planTreeWidget.currentItem()) + if row < self.planTreeWidget.topLevelItemCount() - 1: + targetRow = row + 1 + itm = self.planTreeWidget.takeTopLevelItem(row) + self.planTreeWidget.insertTopLevelItem(targetRow, itm) + self.__addActionCombo(itm) + self.planTreeWidget.setCurrentItem(itm) + + @pyqtSlot() + def on_buttonBox_accepted(self): + """ + Private slot called by the buttonBox accepted signal. + """ + text = self.__assembleEditPlan() + try: + f = open(self.__fileName, "w") + f.write(text) + f.close() + except (IOError, OSError) as err: + E5MessageBox.critical( + self, + self.tr("Edit Plan"), + self.tr("""<p>The file <b>{0}</b> could not be read.</p>""" + """<p>Reason: {1}</p>""").format( + self.__fileName, str(err))) + self.on_buttonBox_rejected() + return + + self.close() + QCoreApplication.exit(0) + + @pyqtSlot() + def on_buttonBox_rejected(self): + """ + Private slot called by the buttonBox rejected signal. + """ + self.close() + QCoreApplication.exit(1) + + def __assembleEditPlan(self): + """ + Private method to assemble the edit plan into text suitable for the + histedit file. + + @return assembled edit plan text + @rtype str + """ + lines = [] + for row in range(self.planTreeWidget.topLevelItemCount()): + itm = self.planTreeWidget.topLevelItem(row) + if ":" in itm.text(1): + rev, changeset = itm.text(1).split(":", 1) + rev = "{0} {1}".format(changeset.strip(), rev.strip()) + else: + rev = itm.text(1).strip() + + lines.append("{0} {1} {2}".format(itm.text(0).strip(), rev, + itm.text(2))) + + return "\n".join(lines)