Changeset 0686d6774b76…
Parent 1786f41873f1…
by
Changes to 5 files · Browse files at 0686d6774b76 Showing diff from parent 1786f41873f1 Diff from another changeset...
|
@@ -0,0 +1,1 @@ + #placeholder
|
|
|
@@ -0,0 +1,165 @@ + # cmdui.py - A widget to execute Mercurial command for TortoiseHg
+#
+# Copyright 2010 Yuki KODAMA <endflow.net@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 PyQt4.QtCore import Qt, QString
+from PyQt4.QtGui import QDialog, QDialogButtonBox, QLabel, QProgressBar
+from PyQt4.QtGui import QTextEdit, QHBoxLayout, QGridLayout, QMessageBox
+
+from tortoisehg.hgqt.i18n import _, localgettext
+from tortoisehg.hgqt import qtlib, thread
+
+local = localgettext()
+
+class Dialog(QDialog):
+ def __init__(self, cmdline, parent=None):
+ super(Dialog, self).__init__(parent, Qt.WindowTitleHint or \
+ Qt.WindowSystemMenuHint)
+
+ # main layout grid
+ grid = QGridLayout()
+ grid.setSpacing(6)
+ grid.setContentsMargins(*(7,)*4)
+
+ ## hbox for status and progress labels
+ hbox = QHBoxLayout()
+ grid.addLayout(hbox, 0, 0)
+
+ self.status_label = QLabel('')
+ hbox.addWidget(self.status_label, 1)
+
+ self.prog_label = QLabel('')
+ hbox.addWidget(self.prog_label, 0, Qt.AlignRight)
+
+ self.pbar = QProgressBar()
+ self.pbar.setTextVisible(False)
+ self.pbar.setMinimum(0)
+ grid.addWidget(self.pbar, 1, 0)
+
+ # command output area
+ self.log_text = QTextEdit()
+ self.log_text.setReadOnly(True)
+ grid.addWidget(self.log_text, 2, 0, 5, 0)
+ grid.setRowStretch(2, 1)
+
+ # bottom buttons
+ buttons = QDialogButtonBox()
+ self.cancel_btn = buttons.addButton(QDialogButtonBox.Cancel)
+ self.cancel_btn.clicked.connect(self.cancel_clicked)
+ self.close_btn = buttons.addButton(QDialogButtonBox.Close)
+ self.close_btn.setHidden(True)
+ self.close_btn.clicked.connect(self.reject)
+ grid.addWidget(buttons, 7, 0)
+
+ self.setLayout(grid)
+ self.setWindowTitle(_('TortoiseHg Command Dialog'))
+ self.resize(540, 420)
+
+ # setup and start command thread
+ self.cmd = thread.CmdThread(cmdline)
+ self.cmd.outputReceived.connect(self.output_received)
+ self.cmd.errorReceived.connect(self.error_received)
+ self.cmd.progressReceived.connect(self.progress_received)
+ self.cmd.started.connect(self.command_started)
+ self.cmd.commandFinished.connect(self.command_finished)
+ self.cmd.start()
+
+ def reject(self):
+ if self.cmd.isRunning():
+ ret = QMessageBox.question(self, _('Confirm Exit'), _('Mercurial'
+ ' command is still running.\nAre you sure you want'
+ ' to terminate?'), QMessageBox.Yes | QMessageBox.No,
+ QMessageBox.No)
+ if ret == QMessageBox.Yes:
+ self.cancel_clicked()
+
+ # don't close dialog
+ return
+
+ # close dialog
+ QDialog.reject(self)
+
+ def cancel_clicked(self):
+ # trigger KeyboardInterrupt
+ self.cmd.abort()
+
+ # until thread is terminated
+ self.cancel_btn.setDisabled(True)
+ self.pbar.setMaximum(0)
+
+ def command_started(self):
+ # use indeterminate mode
+ self.pbar.setMaximum(0)
+ self.status_label.setText(_('Running...'))
+
+ def command_finished(self, wrapper):
+ ret = wrapper.data
+ if ret is None or self.pbar.maximum() == 0:
+ self.clear_progress()
+ if ret is None:
+ if self.cmd.abortbyuser:
+ status = _('Terminated by user')
+ else:
+ status = _('Terminated')
+ else:
+ status = _('Finished')
+ self.status_label.setText(status)
+ self.cancel_btn.setHidden(True)
+ self.close_btn.setShown(True)
+ self.close_btn.setFocus()
+
+ def append_output(self, msg, style=''):
+ if isinstance(msg, str):
+ msg = unicode(msg, 'mbcs')
+ msg = msg.replace('\n', '<br />')
+ self.log_text.insertHtml('<pre style="%s">%s</pre>' % (style, msg))
+
+ def output_received(self, wrapper):
+ msg, label = wrapper.data
+ style = qtlib.LabelStyles.get(label, '')
+ style += 'font-size: 9pt;'
+ self.append_output(msg, style)
+
+ def error_received(self, wrapper):
+ self.append_output(wrapper.data, qtlib.LabelStyles['error'])
+
+ def clear_progress(self):
+ self.pbar.reset()
+ self.pbar.setMaximum(100)
+ self.status_label.setText('')
+ self.prog_label.setText('')
+ self.inprogress = False
+
+ def progress_received(self, wrapper):
+ if self.cmd.isFinished():
+ self.clear_progress()
+ return
+
+ counting = False
+ topic, item, pos, total, unit = wrapper.data
+ if pos is None:
+ self.clear_progress()
+ return
+ if total is None:
+ count = '%d' % pos
+ counting = True
+ else:
+ self.pbar.setMaximum(total)
+ self.pbar.setValue(pos)
+ count = '%d / %d' % (pos, total)
+ if unit:
+ count += ' ' + unit
+ self.prog_label.setText(unicode(count, 'mbcs'))
+ if item:
+ status = '%s: %s' % (topic, item)
+ else:
+ status = local._('Status: %s') % topic
+ self.status_label.setText(unicode(status, 'mbcs'))
+ self.inprogress = True
+
+ if not self.inprogress or counting:
+ # use indeterminate mode
+ self.pbar.setMinimum(0)
|
|
@@ -0,0 +1,16 @@ + # i18n.py - internationalization support for TortoiseHg
+#
+# Copyright 2010 Yuki KODAMA <endflow.net@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.util.i18n import _ as _gettext
+from tortoisehg.util.i18n import agettext, keepgettext
+
+def _(message):
+ return unicode(_gettext(message), 'utf-8')
+
+class localgettext(object):
+ def _(self, message):
+ return agettext(message)
|
|
@@ -0,0 +1,88 @@ + # common colors
+
+DRED = '#900000'
+DGREEN = '#006400'
+DBLUE = '#000090'
+DYELLOW = '#6A6A00'
+DORANGE = '#AA5000'
+DGRAY = '#404040'
+
+PRED = '#ffcccc'
+PGREEN = '#aaffaa'
+PBLUE = '#aaddff'
+PYELLOW = '#ffffaa'
+PORANGE = '#ffddaa'
+
+RED = 'red'
+GREEN = 'green'
+BLUE = 'blue'
+YELLOW = 'yellow'
+BLACK = 'black'
+WHITE = 'white'
+GRAY = 'gray'
+
+NORMAL = BLACK
+NEW_REV_COLOR = DGREEN
+CHANGE_HEADER = GRAY
+UP_ARROW_COLOR = '#feaf3e'
+DOWN_ARROW_COLOR = '#8ae234'
+STAR_COLOR = '#fce94f'
+CELL_GRAY = '#2e3436'
+STATUS_HEADER = '#DDDDDD'
+STATUS_REJECT_BACKGROUND = '#EEEEEE'
+STATUS_REJECT_FOREGROUND = '#888888'
+
+LabelStyles = {
+ 'error': 'font-weight: bold; color: %s;' % DRED,
+ 'control': 'font-weight: bold; color: %s;' % BLACK,
+ 'ui.debug': 'font-weight: lighter; color: %s;' % BLACK,
+ 'ui.status': 'color: %s;' % DGRAY,
+ 'ui.note': 'color: %s;' % BLACK,
+ 'ui.warning': 'font-weight: bold; color: %s;' % RED,
+ 'log.summary': 'color: %s;' % BLACK,
+ 'log.description': 'color: %s;' % DGRAY,
+ 'log.changeset': 'color: %s;' % GRAY,
+ 'log.tag': 'color: %s;' % RED,
+ 'log.user': 'color: %s;' % BLUE,
+ 'log.date': 'color: %s;' % BLACK,
+ 'log.files': 'color: %s;' % BLACK,
+ 'log.copies': 'font-weight: bold; color: %s;' % BLACK,
+ 'log.node': 'color: %s;' % BLACK,
+ 'log.branch': 'color: %s;' % BLACK,
+ 'log.parent': 'color: %s;' % BLACK,
+ 'log.manifest': 'color: %s;' % BLACK,
+ 'log.extra': 'color: %s;' % BLACK,
+ 'diff.diffline': 'color: %s;' % BLACK,
+ 'diff.inserted': 'color: %s;' % DGREEN,
+ 'diff.deleted': 'color: %s;' % RED,
+ 'diff.hunk': 'color: %s;' % BLUE,
+ 'diff.file_a': 'font-weight: bold; color: %s;' % BLACK,
+ 'diff.file_b': 'font-weight: bold; color: %s;' % BLACK,
+}
+
+# These labels are unreachable by TortoiseHg consoles, so we leave them
+# out for efficiency
+unusedLabelStyles = {
+ 'qseries.applied': 'color: %s;' % BLACK,
+ 'qseries.unapplied':'color: %s;' % DGRAY,
+ 'qseries.guarded': 'color: %s;' % BLUE,
+ 'qseries.missing': 'color: %s;' % DRED,
+ 'qguard.patch': 'color: %s;' % BLACK,
+ 'qguard.positive': 'color: %s;' % DGREEN,
+ 'qguard.negagive': 'color: %s;' % BLUE,
+ 'qguard.unguarded': 'color: %s;' % DGRAY,
+ 'diffstat.inserted':'color: %s;' % DGREEN,
+ 'diffstat.deleted': 'color: %s;' % RED,
+ 'bookmarks.current':'font-weight: bold; color: %s;' % BLACK,
+ 'resolve.resolved': 'color: %s;' % DGREEN,
+ 'resolve.unresolved':'color: %s;' % RED,
+ 'grep.match': 'font-weight: bold; color: %s;' % BLACK,
+ 'status.modified': 'color: %s;' % BLACK,
+ 'status.added': 'color: %s;' % BLACK,
+ 'status.removed': 'color: %s;' % BLACK,
+ 'status.missing': 'color: %s;' % BLACK,
+ 'status.unknown': 'color: %s;' % BLACK,
+ 'status.ignored': 'color: %s;' % BLACK,
+ 'status.clean': 'color: %s;' % BLACK,
+ 'status.copied': 'color: %s;' % BLACK,
+}
|
|
|
@@ -0,0 +1,226 @@ + # thread.py - A seprated thread to run Mercurial command
+#
+# Copyright 2009 Steve Borho <steve@borho.org>
+# Copyright 2010 Yuki KODAMA <endflow.net@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.
+
+import Queue
+import time
+import urllib2
+
+from PyQt4.QtCore import SIGNAL, pyqtSignal, QObject, QThread
+from PyQt4.QtGui import QMessageBox, QInputDialog, QLineEdit
+
+from mercurial import ui, util, error, dispatch
+
+from tortoisehg.util import thread2, hglib
+from tortoisehg.hgqt.i18n import _, localgettext
+
+local = localgettext()
+
+SIG_OUTPUT = SIGNAL('output(PyQt_PyObject)')
+SIG_ERROR = SIGNAL('error(PyQt_PyObject)')
+SIG_INTERACT = SIGNAL('interact(PyQt_PyObject)')
+SIG_PROGRESS = SIGNAL('progress(PyQt_PyObject)')
+
+class QtUi(ui.ui):
+ def __init__(self, src=None, responseq=None):
+ super(QtUi, self).__init__(src)
+
+ if src:
+ self.sig = src.sig
+ self.responseq = src.responseq
+ else:
+ self.sig = QObject() # dummy object to emit signals
+ self.responseq = responseq
+
+ self.setconfig('ui', 'interactive', 'on')
+ self.setconfig('progress', 'disable', 'True')
+
+ def write(self, *args, **opts):
+ if self._buffers:
+ self._buffers[-1].extend([str(a) for a in args])
+ else:
+ for a in args:
+ data = DataWrapper((str(a), opts.get('label', '')))
+ self.sig.emit(SIG_OUTPUT, data)
+
+ def write_err(self, *args, **opts):
+ for a in args:
+ data = DataWrapper(str(a))
+ self.sig.emit(SIG_ERROR, data)
+
+ def label(self, msg, label):
+ return msg
+
+ def flush(self):
+ pass
+
+ def prompt(self, msg, choices=None, default='y'):
+ if not self.interactive(): return default
+ try:
+ # emit SIG_INTERACT signal
+ data = DataWrapper((msg, False, choices, None))
+ self.sig.emit(SIG_INTERACT, data)
+ # await response
+ r = self.responseq.get(True)
+ if r is None:
+ raise EOFError
+ if not r:
+ return default
+ if choices:
+ # return char for Mercurial 1.3
+ choice = choices[r]
+ return choice[choice.index('&')+1].lower()
+ return r
+ except EOFError:
+ raise util.Abort(local._('response expected'))
+
+ def promptchoice(self, msg, choices, default=0):
+ if not self.interactive(): return default
+ try:
+ # emit SIG_INTERACT signal
+ data = DataWrapper((msg, False, choices, default))
+ self.sig.emit(SIG_INTERACT, data)
+ # await response
+ r = self.responseq.get(True)
+ if r is None:
+ raise EOFError
+ return r
+ except EOFError:
+ raise util.Abort(local._('response expected'))
+
+ def getpass(self, prompt=_('password: '), default=None):
+ # emit SIG_INTERACT signal
+ data = DataWrapper((prompt, True, None, default))
+ self.sig.emit(SIG_INTERACT, data)
+ # await response
+ r = self.responseq.get(True)
+ if r is None:
+ raise util.Abort(local._('response expected'))
+ return r
+
+ def progress(self, topic, pos, item='', unit='', total=None):
+ data = DataWrapper((topic, item, pos, total, unit))
+ self.sig.emit(SIG_PROGRESS, data)
+
+class DataWrapper(QObject):
+ def __init__(self, data):
+ super(DataWrapper, self).__init__(None)
+ self.data = data
+
+class CmdThread(QThread):
+ """Run an Mercurial command in a background thread, implies output
+ is being sent to a rendered text buffer interactively and requests
+ for feedback from Mercurial can be handled by the user via dialog
+ windows.
+ """
+ # (msg=str, label=str) [wrapped]
+ outputReceived = pyqtSignal(DataWrapper)
+
+ # msg=str [wrapped]
+ errorReceived = pyqtSignal(DataWrapper)
+
+ # (msg=str, password=bool, choices=tuple, default=str) [wrapped]
+ # password: whether should be masked by asterisk chars
+ # choices: tuple of choice strings
+ interactReceived = pyqtSignal(DataWrapper)
+
+ # (topic=str, item=str, pos=int, total=int, unit=str) [wrapped]
+ progressReceived = pyqtSignal(DataWrapper)
+
+ # result=int or None [wrapped]
+ # result: None - command is incomplete, possibly exited with exception
+ # 0 - command is finished successfully
+ # others - return code of command
+ commandFinished = pyqtSignal(DataWrapper)
+
+ def __init__(self, cmdline, parent=None):
+ super(QThread, self).__init__(None)
+
+ self.cmdline = cmdline
+ self.parent = parent
+ self.ret = None
+ self.abortbyuser = False
+ self.responseq = Queue.Queue()
+ self.ui = QtUi(responseq=self.responseq)
+
+ # Re-emit all ui.sig's signals to CmdThread (self).
+ # QSignalMapper doesn't help for this since our SIGNAL
+ # parameters contain 'PyQt_PyObject' types.
+ for name, sig in ((SIG_OUTPUT, self.outputReceived),
+ (SIG_ERROR, self.errorReceived),
+ (SIG_INTERACT, self.interactReceived),
+ (SIG_PROGRESS, self.progressReceived)):
+ def repeater(sig): # hide 'sig' local variable name
+ return lambda data: sig.emit(data)
+ self.connect(self.ui.sig, name, repeater(sig))
+
+ self.finished.connect(self.thread_finished)
+ self.interactReceived.connect(self.interact_handler)
+
+ def abort(self):
+ if self.isRunning() and hasattr(self, 'thread_id'):
+ self.abortbyuser = True
+ thread2._async_raise(self.thread_id, KeyboardInterrupt)
+
+ def thread_finished(self):
+ self.commandFinished.emit(DataWrapper(self.ret))
+
+ def interact_handler(self, wrapper):
+ prompt, password, choices, default = wrapper.data
+ if isinstance(prompt, str):
+ prompt = unicode(prompt, 'mbcs')
+ if choices:
+ dlg = QMessageBox(QMessageBox.Question,
+ _('TortoiseHg Prompt'), prompt,
+ QMessageBox.Yes | QMessageBox.Cancel, self.parent)
+ dlg.setDefaultButton(QMessageBox.Cancel)
+ rmap = {}
+ for index, choice in enumerate(choices):
+ button = dlg.addButton(unicode(choice, 'mbcs'),
+ QMessageBox.ActionRole)
+ rmap[id(button)] = index
+ dlg.exec_()
+ button = dlg.clickedButton()
+ if button is 0:
+ result = default
+ else:
+ result = rmap[id(button)]
+ self.responseq.put(result)
+ else:
+ mode = password and QLineEdit.Password \
+ or QLineEdit.Normal
+ text, ok = QInputDialog().getText(self.parent,
+ _('TortoiseHg Prompt'), prompt, mode)
+ self.responseq.put(ok and text or None)
+
+ def run(self):
+ # save thread id in order to terminate by KeyboardInterrupt
+ self.thread_id = int(QThread.currentThreadId())
+
+ try:
+ for k, v in self.ui.configitems('defaults'):
+ self.ui.setconfig('defaults', k, '')
+ self.ret = dispatch._dispatch(self.ui, self.cmdline) or 0
+ except util.Abort, e:
+ self.ui.write_err(local._('abort: ') + str(e) + '\n')
+ except (error.RepoError, urllib2.HTTPError), e:
+ self.ui.write_err(str(e) + '\n')
+ except (Exception, OSError, IOError), e:
+ self.ui.write_err(str(e) + '\n')
+ except KeyboardInterrupt:
+ pass
+
+ if self.ret is None:
+ if self.abortbyuser:
+ msg = _('[command terminated by user %s]')
+ else:
+ msg = _('[command interrupted %s]')
+ elif self.ret:
+ msg = _('[command returned code %d %%s]') % int(ret)
+ else:
+ msg = _('[command completed successfully %s]')
+ self.ui.write(msg % time.asctime() + '\n', label='control')
|
Loading...