# HG changeset patch
# User Angel Ezquerra <
angel.e...@gmail.com>
# Date 1333456876 -7200
# Branch stable
# Node ID 0dd296db8c936a761206415b4c1c948e3a102d73
# Parent cb6330ef213ae7671e806692f3dead760d1fb2a3
settings: add 'Tools' panel
Add a new panel to the settings dialog which allows configuring (adding,
editing, deleting and reordering) TortoiseHg custom tools.
diff --git a/tortoisehg/hgqt/customtoolconfig.py b/tortoisehg/hgqt/customtoolconfig.py
new file mode 100644
--- /dev/null
+++ b/tortoisehg/hgqt/customtoolconfig.py
@@ -0,0 +1,179 @@
+# customtoolconfig.py - Configuration dialog for TortoiseHg custom tools
+#
+# Copyright 2012 Angel Ezquerra <
angel.e...@gmail.com>
+#
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2, incorporated herein by reference.
+
+from tortoisehg.hgqt.i18n import _
+from tortoisehg.hgqt import qtlib
+
+from PyQt4.QtCore import *
+from PyQt4.QtGui import *
+
+class CustomToolConfigDialog(QDialog):
+ 'Dialog for editing the a custom tool configuration'
+
+ _enablemappings = {'All items': 'istrue',
+ 'Working Directory': 'iswd',
+ 'All revisions': 'isrev',
+ 'All contexts': 'isctx',
+ 'Fixed revisions': 'fixed',
+ 'Applied patches': 'applied',
+ 'qgoto': 'qgoto'}
+
+ _locationmappings = {'Everywhere': 'workbench, repowidget',
+ 'Workbench Toolbar': 'workbench',
+ 'Selected Revision context menu': 'repowidget',}
+
+ def __init__(self, parent=None, toolname=None, toolconfig={}):
+ QDialog.__init__(self, parent)
+
+ self.setWindowIcon(qtlib.geticon('tools-spanner-hammer'))
+ self.setWindowTitle('Configure Custom Tool')
+ self.setWindowFlags(self.windowFlags() & ~Qt.WindowContextHelpButtonHint)
+
+ self.hbox = QHBoxLayout()
+ vbox = QVBoxLayout()
+
+ command = toolconfig.get('command', '')
+ label = toolconfig.get('label', '')
+ tooltip = toolconfig.get('tooltip', '')
+ ico = toolconfig.get('icon', '')
+ location = toolconfig.get('location', 'workbench repowidget')
+ enable = toolconfig.get('enable', 'all')
+ outputlog = toolconfig.get('outputlog', 'False')
+
+
self.name = self._addConfigItem(vbox, _('Tool name'),
+ QLineEdit(toolname), _('The tool name. It cannot contain spaces.'))
+ # Execute a mercurial command. These _MUST_ start with "hg"
+ self.command = self._addConfigItem(vbox, _('Command'),
+ QLineEdit(command), _('The command that will be executed.\n'
+ 'To execute a mercurial command use "hg" (rather than "hg.exe") '
+ 'as the executable command.\n'
+ 'You can use {ROOT} as an alias of the current repository root and\n'
+ '{REV} as an alias of the selected revision.'))
+ self.label = self._addConfigItem(vbox, _('Tool label'),
+ QLineEdit(label),
+ _('The tool label, which is what will be shown '
+ 'on the repowidget context menu.\n'
+ 'If no label is set, the tool name will be used as the tool label.\n'
+ 'If no tooltip is set, the label will be used as the tooltip as well.'))
+ self.tooltip = self._addConfigItem(vbox, _('Tooltip'),
+ QLineEdit(tooltip),
+ _('The tooltip that will be shown on the tool button.\n'
+ 'This is only shown when the tool button is shown on\n'
+ 'the workbench toolbar.'))
+ self.icon = self._addConfigItem(vbox, _('Icon'),
+ QLineEdit(ico),
+ _('The tool icon.\n'
+ 'You can use any built-in TortoiseHg icon\n'
+ 'by setting this value to a vaild TortoiseHg icon name\n'
+ '(e.g. clone, add, remove, sync, thg-logo, hg-update, etc).\n'
+ 'You can also set this value to the absoluate path to\n'
+ 'any icon on your file system.'))
+
+ combo = self._genCombo(self._locationmappings.keys(),
+ self._location2label(location), 'Everywhere')
+ self.location = self._addConfigItem(vbox, _('Show command in'), combo,
+ _('The location where you want to show'
+ 'the command icon or menu entry:\n'
+ '- Workbench: Show a button on the Workbench Custom toolbar.\n'
+ '- Repowidget: Show an entry on the selected revision context menu.'))
+
+ combo = self._genCombo(self._enablemappings.keys(),
+ self._enable2label(enable), 'All items')
+ self.enable = self._addConfigItem(vbox, _('On repowidget, show for'),
+ combo, _('For which kinds of revisions the tool will be enabled\n'
+ 'It is only taken into account when the tool is shown on the\n'
+ 'selected revision context menu.'))
+
+ combo = self._genCombo(('True', 'False'), outputlog)
+ self.showoutput = self._addConfigItem(vbox, _('Show Output Log'),
+ combo, _('When enabled, automatically show the Output Log when the'
+ 'command is run.\nDefault: False.'))
+
+ self.hbox.addLayout(vbox)
+ vbox = QVBoxLayout()
+ self.okbutton = QPushButton(_('OK'))
+ self.okbutton.clicked.connect(self.okClicked)
+ vbox.addWidget(self.okbutton)
+ self.cancelbutton = QPushButton(_('Cancel'))
+ self.cancelbutton.clicked.connect(self.reject)
+ vbox.addWidget(self.cancelbutton)
+ vbox.addStretch()
+ self.hbox.addLayout(vbox)
+ self.setLayout(self.hbox)
+
+ def value(self):
+ toolname = str(self.name.text()).strip()
+ toolconfig = {
+ 'label': str(self.label.text()),
+ 'command': str(self.command.text()),
+ 'tooltip': str(self.tooltip.text()),
+ 'icon': str(self.icon.text()),
+ 'location': self._locationmappings[str(self.location.currentText())],
+ 'enable': self._enablemappings[str(self.enable.currentText())],
+ 'showoutput': str(self.showoutput.currentText()),
+ }
+ return toolname, toolconfig
+
+ def _genCombo(self, items, selecteditem=None, defaultitem=None):
+ print items
+ index = 0
+ if selecteditem:
+ print 'selecteditem: %s' % selecteditem
+ try:
+ index = items.index(selecteditem)
+ print index
+ except:
+ if defaultitem:
+ try:
+ print 'defaultitem: %s' % defaultitem
+ index = items.index(defaultitem)
+ except:
+ print 'not found'
+ pass
+ combo = QComboBox()
+ combo.addItems(items)
+ if index:
+ combo.setCurrentIndex(index)
+ return combo
+
+ def _addConfigItem(self, parent, label, configwidget, tooltip=None):
+ if tooltip:
+ configwidget.setToolTip(tooltip)
+ hbox = QHBoxLayout()
+ hbox.addWidget(QLabel(label))
+ hbox.addWidget(configwidget)
+ parent.addLayout(hbox)
+ return configwidget
+
+ def _enable2label(self, label):
+ return self._dictvalue2key(self._enablemappings, label)
+
+ def _location2label(self, label):
+ return self._dictvalue2key(self._locationmappings, label)
+
+ def _dictvalue2key(self, dictionary, value):
+ for key in dictionary:
+ if value == dictionary[key]:
+ return key
+ return None
+
+ def okClicked(self):
+ errormsg = self.validateForm()
+ if errormsg:
+ qtlib.WarningMsgBox(_('Missing information'), errormsg)
+ return
+ return self.accept()
+
+ def validateForm(self):
+ name, config = self.value()
+ if not name:
+ return _('You must set a tool name.')
+ if name.find(' ') >= 0:
+ return _('The tool name cannot have any spaces in it.')
+ if not config['command']:
+ return _('You must set a command to run.')
+ return '' # No error
diff --git a/tortoisehg/hgqt/settings.py b/tortoisehg/hgqt/settings.py
--- a/tortoisehg/hgqt/settings.py
+++ b/tortoisehg/hgqt/settings.py
@@ -11,7 +11,7 @@
from tortoisehg.util import hglib, settings, paths, wconfig, i18n, bugtraq
from tortoisehg.hgqt.i18n import _
-from tortoisehg.hgqt import qtlib, qscilib, thgrepo
+from tortoisehg.hgqt import qtlib, qscilib, thgrepo, customtoolconfig
from PyQt4.QtCore import *
from PyQt4.QtGui import *
@@ -180,6 +180,7 @@
self.curvalue = None
self.setEchoMode(QLineEdit.Password)
self.setMinimumWidth(ENTRY_WIDTH)
+
class TextEntry(QTextEdit):
def __init__(self, parent=None, **opts):
QTextEdit.__init__(self, parent, toolTip=opts['tooltip'])
@@ -307,6 +308,103 @@
return self.value() != self.curvalue
+class ListBox(QListWidget):
+ def __init__(self, parent=None, **opts):
+ QListWidget.__init__(self, parent, **opts)
+ self.opts = opts
+ self.curvalue = None
+ self.setMinimumWidth(ENTRY_WIDTH)
+
+ def values(self):
+ out = []
+ for row in range(self.count()):
+ out.append(self.item(row).text())
+ return out
+
+ ## common APIs for all edit widgets
+ def setValue(self, curvalue):
+ self.curvalue = curvalue
+
+ def value(self):
+ row = self.currentIndex().row()
+ if row < 0:
+ return None
+ else:
+ return self.item(row).text()
+
+ def isDirty(self):
+ return self.value() != self.curvalue
+
+
+class ToolListBox(ListBox):
+ def __init__(self, parent=None, **opts):
+ ListBox.__init__(self, parent, **opts)
+
+ self.curvalue = (None, None)
+ self.tortoisehgtools, self.toolnames = hglib.tortoisehgtools(ui.ui())
+ self.addItems(self.toolnames)
+
+ self.doubleClicked.connect(self.editToolItem)
+
+ # Enable drag and drop to reorder the tools
+ self.setDragEnabled(True)
+ self.setDragDropMode(self.InternalMove)
+ self.setDefaultDropAction(Qt.MoveAction)
+
+ def takeItem(self, p_int):
+ item = self.item(p_int)
+ itemtext = str(item.text())
+ del self.tortoisehgtools[itemtext]
+ print self.tortoisehgtools
+ super(ToolListBox, self).takeItem(p_int)
+
+ def addTool(self):
+ td = customtoolconfig.CustomToolConfigDialog(self)
+ res = td.exec_()
+ if res:
+ row = self.currentIndex().row()
+ toolname, toolconfig = td.value()
+ if row < 0:
+ self.addItem(toolname)
+ self.setCurrentRow(self.count()-1)
+ else:
+ self.insertItem(row+1, toolname)
+ self.setCurrentRow(row+1)
+ self.tortoisehgtools[toolname] = toolconfig
+
+ def editTool(self, row=None):
+ if row is None:
+ row = self.currentIndex().row()
+ if row < 0:
+ return self.addTool()
+ else:
+ item = self.item(row)
+ toolname = item.text()
+ td = customtoolconfig.CustomToolConfigDialog(
+ self, toolname=toolname,
+ toolconfig=self.tortoisehgtools[str(toolname)])
+ res = td.exec_()
+ if res:
+ toolname, toolconfig = td.value()
+ self.takeItem(row)
+ self.insertItem(row, toolname)
+ self.setCurrentRow(row)
+ self.tortoisehgtools[toolname] = toolconfig
+
+ def editToolItem(self, item):
+ self.editTool(item.row())
+
+ def deleteTool(self, row=None):
+ if row is None:
+ row = self.currentIndex().row()
+ if row >= 0:
+ self.takeItem(row)
+
+ ## common APIs for all edit widgets
+ def value(self):
+ return (self.tortoisehgtools, self.values())
+
+
class BugTraqConfigureEntry(QPushButton):
def __init__(self, parent=None, **opts):
QPushButton.__init__(self, parent, toolTip=opts['tooltip'])
@@ -866,6 +964,9 @@
({'name': 'extensions', 'label': _('Extensions'), 'icon': 'hg-extensions'}, (
)),
+({'name': 'tools', 'label': _('Tools'), 'icon': 'tools-spanner-hammer'}, (
+ )),
+
({'name': 'issue', 'label': _('Issue Tracking'), 'icon': 'edit-file'}, (
_fi(_('Issue Regex'), 'tortoisehg.issue.regex', genEditCombo,
_('Defines the regex to match when picking up issue numbers.')),
@@ -1240,6 +1341,9 @@
# make sure widgets are shown properly,
# even when no extensions mentioned in the config file
self.validateextensions()
+ elif name == 'tools':
+ self.tortoisehgtools, self.toolnames = hglib.tortoisehgtools(ui.ui())
+ widgets[0].setValue((self.tortoisehgtools, self.toolnames))
else:
for row, e in enumerate(info):
curvalue = self.readCPath(e.cpath)
@@ -1333,6 +1437,32 @@
widgets.append(w)
return extsinfo, widgets
+ def fillToolsFrame(self):
+ widgets = []
+ frame = QFrame()
+ hbox = QHBoxLayout()
+ frame.setLayout(hbox)
+ self.stack.addWidget(frame)
+
+ toollist = ToolListBox()
+ hbox.addWidget(toollist)
+ vbox = QVBoxLayout()
+
+ addbutton = QPushButton(_('Add'), self)
+ addbutton.clicked.connect(toollist.addTool)
+ editbutton = QPushButton(_('Edit'), self)
+ editbutton.clicked.connect(lambda: toollist.editTool(None))
+ deletebutton = QPushButton(_('Delete'), self)
+ deletebutton.clicked.connect(lambda: toollist.deleteTool(None))
+ vbox.addWidget(addbutton)
+ vbox.addWidget(editbutton)
+ vbox.addWidget(deletebutton)
+ vbox.addStretch()
+ hbox.addLayout(vbox)
+ widgets.append(toollist)
+ toolsinfo = ()
+ return toolsinfo, widgets
+
def eventFilter(self, obj, event):
if event.type() in (QEvent.Enter, QEvent.FocusIn):
self.desctext.setHtml(obj.toolTip())
@@ -1350,6 +1480,9 @@
if name == 'extensions':
extsinfo, widgets = self.fillExtensionsFrame()
self.pages[name] = name, extsinfo, widgets
+ elif name == 'tools':
+ toolsinfo, widgets = self.fillToolsFrame()
+ self.pages[name] = name, toolsinfo, widgets
else:
widgets = self.fillFrame(info)
self.pages[name] = name, info, widgets
@@ -1407,6 +1540,8 @@
for name, info, widgets in self.pages.values():
if name == 'extensions':
self.applyChangesForExtensions()
+ elif name == 'tools':
+ self.applyChangesForTools()
else:
for row, e in enumerate(info):
newvalue = widgets[row].value()
@@ -1482,6 +1617,41 @@
invalmsg = invalmsg.decode('utf-8')
chk.setToolTip(invalmsg or hglib.tounicode(allexts[name]))
+ def applyChangesForTools(self):
+ emitChanged = False
+ toollist = self.pages['tools'][2][0]
+ if not toollist.isDirty():
+ return
+
+ tools, toolnames = toollist.value()
+ curtools, curtoolnames = toollist.curvalue
+
+ # In order to keep the tool order we must delete all existing
+ # custom tool configurations, and then set all the configuration
+ # settings anew:
+ section = 'tortoisehg-tools'
+
+ # 1. Remove the existing tool configurations
+ fieldnames = ('command', 'label', 'tooltip',
+ 'icon', 'location', 'enable', 'showoutput',)
+ for name in curtoolnames:
+ for field in fieldnames:
+ try:
+ keyname = '%s.%s' % (name, field)
+ del self.ini[section][keyname]
+ except KeyError:
+ pass
+
+ # 2. Configure the new configurations
+ for uname in toolnames:
+ name = hglib.fromunicode(uname)
+ for field in sorted(tools[name]):
+ keyname = '%s.%s' % (name, field)
+ value = tools[name][field]
+ if not value is '':
+ self.ini.set(section, keyname, value)
+
+ self.restartRequested.emit(_('Tools'))
def run(ui, *pats, **opts):
return SettingsDialog(opts.get('alias') == 'repoconfig',