PageRenderTime 79ms CodeModel.GetById 17ms app.highlight 54ms RepoModel.GetById 1ms app.codeStats 0ms

/tortoisehg/hgqt/mq.py

https://bitbucket.org/tortoisehg/hgtk/
Python | 817 lines | 765 code | 40 blank | 12 comment | 21 complexity | 8516c24938c5e7832d745ed0aa419a77 MD5 | raw file
  1# mq.py - TortoiseHg MQ widget
  2#
  3# Copyright 2011 Steve Borho <steve@borho.org>
  4#
  5# This software may be used and distributed according to the terms of the
  6# GNU General Public License version 2 or any later version.
  7
  8import os
  9import re
 10
 11from PyQt4.QtCore import *
 12from PyQt4.QtGui import *
 13
 14from mercurial import hg, ui, url, util, error
 15from mercurial import merge as mergemod
 16from hgext import mq as mqmod
 17
 18from tortoisehg.util import hglib, patchctx
 19from tortoisehg.hgqt.i18n import _
 20from tortoisehg.hgqt import qtlib, cmdui, rejects, commit, shelve, qscilib
 21from tortoisehg.hgqt import qqueue, fileview
 22
 23# TODO: Disable MQ toolbar while cmdui.Runner is busy
 24
 25class MQWidget(QWidget):
 26    showMessage = pyqtSignal(unicode)
 27    output = pyqtSignal(QString, QString)
 28    progress = pyqtSignal(QString, object, QString, QString, object)
 29    makeLogVisible = pyqtSignal(bool)
 30
 31    def __init__(self, repo, parent, **opts):
 32        QWidget.__init__(self, parent)
 33
 34        self.repo = repo
 35        self.opts = opts
 36        self.refreshing = False
 37
 38        layout = QVBoxLayout()
 39        layout.setSpacing(0)
 40        self.setLayout(layout)
 41
 42        # top toolbar
 43        tbarhbox = QHBoxLayout()
 44        tbarhbox.setSpacing(5)
 45        self.layout().addLayout(tbarhbox, 0)
 46        self.queueCombo = QComboBox()
 47        self.optionsBtn = QPushButton(_('Options'))
 48        self.msgSelectCombo = PatchMessageCombo(self)
 49        tbarhbox.addWidget(self.queueCombo)
 50        tbarhbox.addWidget(self.optionsBtn)
 51        tbarhbox.addWidget(self.msgSelectCombo, 1)
 52
 53        # main area consists of a three-way horizontal splitter
 54        self.splitter = splitter = QSplitter()
 55        self.layout().addWidget(splitter, 1)
 56        splitter.setOrientation(Qt.Horizontal)
 57        splitter.setChildrenCollapsible(True)
 58        splitter.setObjectName('splitter')
 59
 60        self.queueFrame = QFrame(splitter)
 61        self.messageFrame = QFrame(splitter)
 62        self.fileview = fileview.HgFileView(repo, splitter)
 63
 64        self.fileview.showMessage.connect(self.showMessage)
 65        self.fileview.setContext(repo[None])
 66
 67        # Patch Queue Frame
 68        layout = QVBoxLayout()
 69        layout.setSpacing(5)
 70        layout.setContentsMargins(0, 0, 0, 0)
 71        self.queueFrame.setLayout(layout)
 72
 73        qtbarhbox = QHBoxLayout()
 74        qtbarhbox.setSpacing(5)
 75        layout.addLayout(qtbarhbox, 0)
 76        qtbarhbox.setContentsMargins(0, 0, 0, 0)
 77        self.qpushAllBtn = tb = QToolButton()
 78        tb.setIcon(qtlib.geticon('qpushall'))
 79        tb.setToolTip(_('Apply all patches'))
 80        self.qpushBtn = tb = QToolButton()
 81        tb.setIcon(qtlib.geticon('qpush'))
 82        tb.setToolTip(_('Apply one patch'))
 83        self.setGuardsBtn = tb = QToolButton()
 84        tb.setIcon(qtlib.geticon('qguards'))
 85        tb.setToolTip(_('Configure guards for selected patch'))
 86        self.qpushMoveBtn = tb = QToolButton()
 87        tb.setIcon(qtlib.geticon('qreorder'))
 88        tb.setToolTip(_('Apply selected patch next (change queue order)'))
 89        self.qdeleteBtn = tb = QToolButton()
 90        tb.setIcon(qtlib.geticon('filedelete'))
 91        tb.setToolTip(_('Delete selected patches'))
 92        self.qpopBtn = tb = QToolButton()
 93        tb.setIcon(qtlib.geticon('qpop'))
 94        tb.setToolTip(_('Unapply one patch'))
 95        self.qpopAllBtn = tb = QToolButton()
 96        tb.setIcon(qtlib.geticon('qpopall'))
 97        tb.setToolTip(_('Unapply all patches'))
 98        qtbarhbox.addWidget(self.qpushAllBtn)
 99        qtbarhbox.addWidget(self.qpushBtn)
100        qtbarhbox.addStretch(1)
101        qtbarhbox.addWidget(self.setGuardsBtn)
102        qtbarhbox.addWidget(self.qpushMoveBtn)
103        qtbarhbox.addWidget(self.qdeleteBtn)
104        qtbarhbox.addStretch(1)
105        qtbarhbox.addWidget(self.qpopBtn)
106        qtbarhbox.addWidget(self.qpopAllBtn)
107
108        self.queueListWidget = QListWidget(self)
109        layout.addWidget(self.queueListWidget, 1)
110
111        self.guardSelBtn = QPushButton()
112        layout.addWidget(self.guardSelBtn, 0)
113
114        self.revisionOrCommitBtn = QPushButton()
115        layout.addWidget(self.revisionOrCommitBtn, 0)
116
117        # Message Frame
118        layout = QVBoxLayout()
119        layout.setSpacing(5)
120        layout.setContentsMargins(0, 0, 0, 0)
121        self.messageFrame.setLayout(layout)
122
123        mtbarhbox = QHBoxLayout()
124        mtbarhbox.setSpacing(8)
125        layout.addLayout(mtbarhbox, 0)
126        mtbarhbox.setContentsMargins(0, 0, 0, 0)
127        self.newCheckBox = QCheckBox(_('New Patch'))
128        self.patchNameLE = QLineEdit()
129        mtbarhbox.addWidget(self.newCheckBox)
130        mtbarhbox.addWidget(self.patchNameLE, 1)
131
132        self.messageEditor = commit.MessageEntry(self)
133        self.messageEditor.installEventFilter(qscilib.KeyPressInterceptor(self))
134        self.messageEditor.setContextMenuPolicy(Qt.CustomContextMenu)
135        self.messageEditor.customContextMenuRequested.connect(self.menuRequested)
136        self.messageEditor.refresh(repo)
137        layout.addWidget(self.messageEditor, 1)
138
139        self.fileListWidget = QListWidget(self)
140        self.fileListWidget.currentRowChanged.connect(self.onFileSelected)
141        layout.addWidget(self.fileListWidget, 2)
142
143        qrefhbox = QHBoxLayout()
144        layout.addLayout(qrefhbox, 0)
145        qrefhbox.setContentsMargins(0, 0, 0, 0)
146        self.qqueueBtn = QPushButton(_('Manage queues'))
147        self.qqueueBtn.setMinimumWidth(150)
148        self.shelveBtn = QPushButton(_('Shelve'))
149        self.qnewOrRefreshBtn = QPushButton(_('QRefresh'))
150        qrefhbox.addWidget(self.qqueueBtn)
151        qrefhbox.addStretch(1)
152        qrefhbox.addWidget(self.shelveBtn)
153        qrefhbox.addWidget(self.qnewOrRefreshBtn)
154
155        # Command runner and connections...
156        self.cmd = cmdui.Runner(_('Patch Queue'), parent == None, self)
157        self.cmd.output.connect(self.output)
158        self.cmd.makeLogVisible.connect(self.makeLogVisible)
159        self.cmd.progress.connect(self.progress)
160        self.cmd.commandFinished.connect(self.onCommandFinished)
161
162        self.qqueueBtn.clicked.connect(self.launchQQueueTool)
163        self.shelveBtn.clicked.connect(self.launchShelveTool)
164        self.optionsBtn.clicked.connect(self.launchOptionsDialog)
165        self.revisionOrCommitBtn.clicked.connect(self.qinitOrCommit)
166        self.msgSelectCombo.activated.connect(self.onMessageSelected)
167        self.queueListWidget.currentRowChanged.connect(self.onPatchSelected)
168        self.queueListWidget.itemActivated.connect(self.onGotoPatch)
169        self.queueListWidget.itemChanged.connect(self.onRenamePatch)
170        self.qpushAllBtn.clicked.connect(self.onPushAll)
171        self.qpushBtn.clicked.connect(self.onPush)
172        self.qpushMoveBtn.clicked.connect(self.onPushMove)
173        self.qpopAllBtn.clicked.connect(self.onPopAll)
174        self.qpopBtn.clicked.connect(self.onPop)
175        self.setGuardsBtn.clicked.connect(self.onGuardConfigure)
176        self.qdeleteBtn.clicked.connect(self.onDelete)
177        self.newCheckBox.toggled.connect(self.onNewModeToggled)
178        self.qnewOrRefreshBtn.clicked.connect(self.onQNewOrQRefresh)
179
180        self.repo.configChanged.connect(self.onConfigChanged)
181        self.repo.repositoryChanged.connect(self.onRepositoryChanged)
182        self.setAcceptDrops(True)
183
184        if hasattr(self.patchNameLE, 'setPlaceholderText'): # Qt >= 4.7
185            self.patchNameLE.setPlaceholderText('### patch name ###')
186
187        if parent:
188            self.layout().setContentsMargins(2, 2, 2, 2)
189        else:
190            self.layout().setContentsMargins(0, 0, 0, 0)
191            self.setWindowTitle(_('TortoiseHg Patch Queue'))
192            self.statusbar = cmdui.ThgStatusBar(self)
193            self.layout().addWidget(self.statusbar)
194            self.progress.connect(self.statusbar.progress)
195            self.showMessage.connect(self.statusbar.showMessage)
196            QShortcut(QKeySequence.Refresh, self, self.reload)
197            self.resize(850, 550)
198
199        self.loadConfigs()
200        QTimer.singleShot(0, self.reload)
201
202    def menuRequested(self, point):
203        point = self.messageEditor.mapToGlobal(point)
204        return self.messageEditor.createStandardContextMenu().exec_(point)
205
206    def getUserOptions(self, *optionlist):
207        out = []
208        for opt in optionlist:
209            if opt not in self.opts:
210                continue
211            val = self.opts[opt]
212            if val is False:
213                continue
214            elif val is True:
215                out.append('--' + opt)
216            else:
217                out.append('--' + opt)
218                out.append(val)
219        return out
220
221    @pyqtSlot()
222    def onConfigChanged(self):
223        'Repository is reporting its config files have changed'
224        self.messageEditor.refresh(self.repo)
225        self.fileview.setContext(self.repo[None])
226
227    @pyqtSlot()
228    def onRepositoryChanged(self):
229        'Repository is reporting its changelog has changed'
230        self.reload()
231
232    @pyqtSlot(int)
233    def onCommandFinished(self, ret):
234        self.repo.decrementBusyCount()
235        if ret is not 0:
236            pass # TODO: look for reject notifications
237
238    @pyqtSlot()
239    def onPushAll(self):
240        if self.cmd.running():
241            return
242        self.repo.incrementBusyCount()
243        cmdline = ['qpush', '-R', self.repo.root, '--all']
244        cmdline += self.getUserOptions('force', 'exact')
245        self.cmd.run(cmdline)
246
247    @pyqtSlot()
248    def onPush(self):
249        if self.cmd.running():
250            return
251        self.repo.incrementBusyCount()
252        cmdline = ['qpush', '-R', self.repo.root]
253        cmdline += self.getUserOptions('force', 'exact')
254        self.cmd.run(cmdline)
255
256    @pyqtSlot()
257    def onPopAll(self):
258        if self.cmd.running():
259            return
260        self.repo.incrementBusyCount()
261        cmdline = ['qpop', '-R', self.repo.root, '--all']
262        cmdline += self.getUserOptions('force')
263        self.cmd.run(cmdline)
264
265    @pyqtSlot()
266    def onPop(self):
267        if self.cmd.running():
268            return
269        self.repo.incrementBusyCount()
270        cmdline = ['qpop', '-R', self.repo.root]
271        cmdline += self.getUserOptions('force')
272        self.cmd.run(cmdline)
273
274    @pyqtSlot()
275    def onPushMove(self):
276        if self.cmd.running():
277            return
278        patch = self.queueListWidget.currentItem()._thgpatch
279        cmdline = ['qpush', '-R', self.repo.root]
280        cmdline += self.getUserOptions('force')
281        cmdline += ['--move', '--', patch]
282        self.repo.incrementBusyCount()
283        self.cmd.run(cmdline)
284
285    @pyqtSlot()
286    def onGuardConfigure(self):
287        item = self.queueListWidget.currentItem()
288        patch = item._thgpatch
289        if item._thgguards:
290            uguards = hglib.tounicode(' '.join(item._thgguards))
291        else:
292            uguards = ''
293        new, ok = QInputDialog.getText(self,
294                      _('Configure guards'),
295                      _('Input new guards for %s:') % hglib.tounicode(patch),
296                      text=uguards, flags=Qt.Sheet)
297        if not ok or new == uguards:
298            return
299        guards = []
300        for guard in hglib.fromunicode(new).split(' '):
301            guard = guard.strip()
302            if not guard:
303                continue
304            if not (guard[0] == '+' or guard[0] == '-'):
305                self.showMessage.emit(_('Guards must begin with "+" or "-"'))
306                continue
307            guards.append(guard)
308        cmdline = ['qguard', '-R', self.repo.root, '--', patch]
309        if guards:
310            cmdline += guards
311        else:
312            cmdline.insert(3, '--none')
313        if self.cmd.running():
314            return
315        self.repo.incrementBusyCount()
316        self.cmd.run(cmdline)
317
318    @pyqtSlot()
319    def onDelete(self):
320        from tortoisehg.hgqt import qdelete
321        patch = self.queueListWidget.currentItem()._thgpatch
322        dlg = qdelete.QDeleteDialog(self.repo, [patch], self)
323        dlg.finished.connect(dlg.deleteLater)
324        if dlg.exec_() == QDialog.Accepted:
325            self.reload()
326
327    #@pyqtSlot(QListWidgetItem)
328    def onGotoPatch(self, item):
329        'Patch has been activated (return), issue qgoto'
330        if self.cmd.running():
331            return
332        cmdline = ['qgoto', '-R', self.repo.root]
333        cmdline += self.getUserOptions('force')
334        cmdline += ['--', item._thgpatch]
335        self.repo.incrementBusyCount()
336        self.cmd.run(cmdline)
337
338    #@pyqtSlot(QListWidgetItem)
339    def onRenamePatch(self, item):
340        'Patch has been renamed, issue qrename'
341        if self.cmd.running():
342            return
343        self.repo.incrementBusyCount()
344        self.cmd.run(['qrename', '-R', self.repo.root, '--',
345                      item._thgpatch, hglib.fromunicode(item.text())])
346
347    @pyqtSlot(int)
348    def onPatchSelected(self, row):
349        'Patch has been selected, update buttons'
350        if self.refreshing:
351            return
352        if row >= 0:
353            patch = self.queueListWidget.item(row)._thgpatch
354            applied = set([p.name for p in self.repo.mq.applied])
355            self.qdeleteBtn.setEnabled(patch not in applied)
356            self.qpushMoveBtn.setEnabled(patch not in applied)
357            self.setGuardsBtn.setEnabled(True)
358        else:
359            self.qdeleteBtn.setEnabled(False)
360            self.qpushMoveBtn.setEnabled(False)
361            self.setGuardsBtn.setEnabled(False)
362
363    @pyqtSlot(int)
364    def onFileSelected(self, row):
365        if self.refreshing or row == -1:
366            return
367        text = hglib.fromunicode(self.fileListWidget.item(row).text())
368        status = text[0]
369        filename = text[2:]
370        if self.newCheckBox.isChecked():
371            rev = self.repo['.'].rev()
372        else:
373            rev = self.repo['qtip'].p1().rev()
374        self.fileview.displayFile(filename, rev, status)
375
376    @pyqtSlot(int)
377    def onMessageSelected(self, row):
378        if self.messageEditor.text() and self.messageEditor.isModified():
379            d = QMessageBox.question(self, _('Confirm Discard Message'),
380                        _('Discard current commit message?'),
381                        QMessageBox.Ok | QMessageBox.Cancel)
382            if d != QMessageBox.Ok:
383                return
384        self.setMessage(self.messages[row][1])
385        self.messageEditor.setFocus()
386
387    def setMessage(self, message):
388        self.messageEditor.setText(message)
389        lines = self.messageEditor.lines()
390        if lines:
391            lines -= 1
392            pos = self.messageEditor.lineLength(lines)
393            self.messageEditor.setCursorPosition(lines, pos)
394            self.messageEditor.ensureLineVisible(lines)
395            hs = self.messageEditor.horizontalScrollBar()
396            hs.setSliderPosition(0)
397        self.messageEditor.setModified(False)
398
399    @pyqtSlot()
400    def onQNewOrQRefresh(self):
401        if self.newCheckBox.isChecked():
402            name = hglib.fromunicode(self.patchNameLE.text())
403            if not name:
404                qtlib.ErrorMsgBox(_('Patch Name Required'),
405                                  _('You must enter a patch name'))
406                self.patchNameLE.setFocus()
407                return
408            cmdline = ['qnew', '--repository', self.repo.root, name]
409        else:
410            cmdline = ['qrefresh', '--repository', self.repo.root]
411        message = self.messageEditor.text()
412        if message:
413            cmdline += ['--message=' + hglib.fromunicode(message)]
414        cmdline += self.getUserOptions('user', 'currentuser', 'git',
415                                       'date', 'currentdate')
416        files = ['--']
417        for row in xrange(self.fileListWidget.count()):
418            item = self.fileListWidget.item(row)
419            if item.checkState() == Qt.Checked:
420                wfile = hglib.fromunicode(item.text()[2:])
421                files.append(self.repo.wjoin(wfile))
422        if len(files) > 1:
423            cmdline += files
424        else:
425            cmdline += ['--exclude', self.repo.root]
426        self.repo.incrementBusyCount()
427        self.cmd.run(cmdline)
428
429    @pyqtSlot()
430    def qinitOrCommit(self):
431        if os.path.isdir(self.repo.mq.join('.hg')):
432            dlg = commit.CommitDialog([], dict(root=self.repo.mq.path), self)
433            dlg.finished.connect(dlg.deleteLater)
434            dlg.exec_()
435            self.reload()
436        else:
437            self.repo.incrementBusyCount()
438            self.cmd.run(['qinit', '-c', '-R', self.repo.root])
439
440    @pyqtSlot()
441    def launchQQueueTool(self):
442        dlg = qqueue.QQueueDialog(self.repo, self)
443        dlg.finished.connect(dlg.deleteLater)
444        dlg.exec_()
445        self.reload()
446
447    @pyqtSlot()
448    def launchShelveTool(self):
449        dlg = shelve.ShelveDialog(self.repo, self)
450        dlg.finished.connect(dlg.deleteLater)
451        dlg.exec_()
452        self.reload()
453
454    @pyqtSlot()
455    def launchOptionsDialog(self):
456        dlg = OptionsDialog(self)
457        dlg.finished.connect(dlg.deleteLater)
458        dlg.setWindowFlags(Qt.Sheet)
459        dlg.setWindowModality(Qt.WindowModal)
460        if dlg.exec_() == QDialog.Accepted:
461            self.opts.update(dlg.outopts)
462
463    def reload(self):
464        self.refreshing = True
465        try:
466            try:
467                self._reload()
468            except Exception, e:
469                self.showMessage.emit(hglib.tounicode(str(e)))
470                if 'THGDEBUG' in os.environ:
471                    import traceback
472                    traceback.print_exc()
473        finally:
474            self.refreshing = False
475
476    def _reload(self):
477        ui, repo = self.repo.ui, self.repo
478
479        self.queueCombo.clear()
480        self.queueListWidget.clear()
481
482        ui.pushbuffer()
483        mqmod.qqueue(ui, repo, list=True)
484        out = ui.popbuffer()
485        activestr = ' (active)' # TODO: not locale safe
486        for i, qname in enumerate(out.splitlines()):
487            if qname.endswith(activestr):
488                current = i
489                qname = qname[:-len(activestr)]
490            self.queueCombo.addItem(hglib.tounicode(qname))
491        self.queueCombo.setCurrentIndex(current)
492        self.queueCombo.setEnabled(self.queueCombo.count() > 1)
493
494        # TODO: maintain current selection
495        applied = set([p.name for p in repo.mq.applied])
496        self.allguards = set()
497        items = []
498        for idx, patch in enumerate(repo.mq.series):
499            item = QListWidgetItem(hglib.tounicode(patch))
500            if patch in applied: # applied
501                f = item.font()
502                f.setBold(True)
503                item.setFont(f)
504            elif not repo.mq.pushable(idx)[0]: # guarded
505                f = item.font()
506                f.setItalic(True)
507                item.setFont(f)
508            patchguards = repo.mq.series_guards[idx]
509            if patchguards:
510                for guard in patchguards:
511                    self.allguards.add(guard[1:])
512                uguards = hglib.tounicode(', '.join(patchguards))
513            else:
514                uguards = _('no guards')
515            uname = hglib.tounicode(patch)
516            item._thgpatch = patch
517            item._thgguards = patchguards
518            item.setToolTip(u'%s: %s' % (uname, uguards))
519            item.setFlags(Qt.ItemIsSelectable |
520                          Qt.ItemIsEditable |
521                          Qt.ItemIsEnabled)
522            items.append(item)
523        for item in reversed(items):
524            self.queueListWidget.addItem(item)
525
526        for guard in repo.mq.active():
527            self.allguards.add(guard)
528        self.refreshSelectedGuards()
529
530        self.messages = []
531        for patch in repo.mq.series:
532            ctx = repo.changectx(patch)
533            msg = ctx.description()
534            if msg:
535                self.messages.append((patch, msg))
536        self.msgSelectCombo.reset(self.messages)
537
538        if os.path.isdir(repo.mq.join('.hg')):
539            self.revisionOrCommitBtn.setText(_('QCommit'))
540        else:
541            self.revisionOrCommitBtn.setText(_('Revision Queue'))
542
543        self.qpushAllBtn.setEnabled(bool(repo.thgmqunappliedpatches))
544        self.qpushBtn.setEnabled(bool(repo.thgmqunappliedpatches))
545        self.qpushMoveBtn.setEnabled(False)
546        self.qdeleteBtn.setEnabled(False)
547        self.setGuardsBtn.setEnabled(False)
548        self.qpopBtn.setEnabled(bool(applied))
549        self.qpopAllBtn.setEnabled(bool(applied))
550
551        pctx = repo.changectx('.')
552        newmode = self.newCheckBox.isChecked()
553        if 'qtip' in pctx.tags():
554            self.fileListWidget.setEnabled(True)
555            self.messageEditor.setEnabled(True)
556            self.msgSelectCombo.setEnabled(True)
557            self.qnewOrRefreshBtn.setEnabled(True)
558            if not newmode:
559                self.setMessage(pctx.description())
560                name = repo.mq.applied[-1].name
561                self.patchNameLE.setText(hglib.tounicode(name))
562        else:
563            self.fileListWidget.setEnabled(newmode)
564            self.messageEditor.setEnabled(newmode)
565            self.msgSelectCombo.setEnabled(newmode)
566            self.qnewOrRefreshBtn.setEnabled(newmode)
567            if not newmode:
568                self.setMessage('')
569                self.patchNameLE.setText('')
570        self.patchNameLE.setEnabled(newmode)
571        self.refreshFileListWidget()
572
573    def onNewModeToggled(self, isChecked):
574        if isChecked:
575            self.fileListWidget.setEnabled(True)
576            self.qnewOrRefreshBtn.setText(_('QNew'))
577            self.qnewOrRefreshBtn.setEnabled(True)
578            self.messageEditor.setEnabled(True)
579            self.patchNameLE.setEnabled(True)
580            self.patchNameLE.selectAll()
581            self.patchNameLE.setFocus()
582            self.setMessage('')
583        else:
584            self.qnewOrRefreshBtn.setText(_('QRefresh'))
585            pctx = self.repo.changectx('.')
586            if 'qtip' in pctx.tags():
587                self.messageEditor.setEnabled(True)
588                self.setMessage(pctx.description())
589                name = self.repo.mq.applied[-1].name
590                self.patchNameLE.setText(hglib.tounicode(name))
591                self.qnewOrRefreshBtn.setEnabled(True)
592                self.fileListWidget.setEnabled(True)
593            else:
594                self.messageEditor.setEnabled(False)
595                self.qnewOrRefreshBtn.setEnabled(False)
596                self.fileListWidget.setEnabled(False)
597                self.patchNameLE.setText('')
598                self.setMessage('')
599            self.patchNameLE.setEnabled(False)
600        self.refreshing = True
601        try:
602            try:
603                self.refreshFileListWidget()
604            except Exception, e:
605                self.showMessage.emit(hglib.tounicode(str(e)))
606                import traceback
607                traceback.print_exc()
608        finally:
609            self.refreshing = False
610
611    def refreshFileListWidget(self):
612        # TODO: maintain selection, check state
613        def addfiles(mode, files):
614            for file in files:
615                item = QListWidgetItem(u'%s %s' % (mode, hglib.tounicode(file)))
616                item.setFlags(flags)
617                item.setCheckState(Qt.Checked)
618                self.fileListWidget.addItem(item)
619        self.fileListWidget.clear()
620        self.fileview.clearDisplay()
621        pctx = self.repo.changectx('.')
622        newmode = self.newCheckBox.isChecked()
623        if not newmode and 'qtip' in pctx.tags():
624            # Show qrefresh (qdiff) diffs
625            M, A, R = self.repo.status(pctx.p1().node(), None)[:3]
626        elif newmode:
627            # Show qnew (working) diffs
628            M, A, R = self.repo[None].status()[:3]
629        else:
630            return
631        flags = Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled
632        addfiles(u'A', A)
633        addfiles(u'M', M)
634        addfiles(u'R', R)
635
636    def refreshSelectedGuards(self):
637        total = len(self.allguards)
638        count = len(self.repo.mq.active())
639        oldmenu = self.guardSelBtn.menu()
640        if oldmenu:
641            oldmenu.setParent(None)
642        menu = QMenu(self)
643        for guard in self.allguards:
644            a = menu.addAction(hglib.tounicode(guard))
645            a.setCheckable(True)
646            a.setChecked(guard in self.repo.mq.active())
647            a.triggered.connect(self.onGuardSelectionChange)
648        self.guardSelBtn.setMenu(menu)
649        self.guardSelBtn.setText(_('Guards: %d/%d') % (count, total))
650
651    def onGuardSelectionChange(self, isChecked):
652        guard = hglib.fromunicode(self.sender().text())
653        newguards = self.repo.mq.active()[:]
654        if isChecked:
655            newguards.append(guard)
656        elif guard in newguards:
657            newguards.remove(guard)
658        cmdline = ['qselect', '-R', self.repo.root]
659        cmdline += newguards or ['--none']
660        self.repo.incrementBusyCount()
661        self.cmd.run(cmdline)
662
663    # Capture drop events, try to import into current patch queue
664
665    def dragEnterEvent(self, event):
666        event.acceptProposedAction()
667
668    def dragMoveEvent(self, event):
669        event.acceptProposedAction()
670
671    def dropEvent(self, event):
672        paths = [unicode(u.toLocalFile()) for u in event.mimeData().urls()]
673        filepaths = [p for p in paths if os.path.isfile(p)]
674        if filepaths:
675            event.setDropAction(Qt.CopyAction)
676            event.accept()
677        else:
678            super(MQWidget, self).dropEvent(event)
679            return
680        dlg = thgimport.ImportDialog(repo=self.repo, parent=self)
681        # TODO: send flag to dialog indicating this is a qimport (alias?)
682        dlg.finished.connect(dlg.deleteLater)
683        dlg.setfilepaths(filepaths)
684        dlg.exec_()
685
686    # End drop events
687
688    def loadConfigs(self):
689        'Load history, etc, from QSettings instance'
690        s = QSettings()
691        self.splitter.restoreState(s.value('mq/splitter').toByteArray())
692        userhist = s.value('commit/userhist').toStringList()
693        self.opts['userhist'] = [hglib.fromunicode(u) for u in userhist if u]
694        self.messageEditor.loadSettings(s, 'mq/editor')
695        self.fileview.loadSettings(s, 'mq/fileview')
696        if not self.parent():
697            self.restoreGeometry(s.value('mq/geom').toByteArray())
698
699    def storeConfigs(self):
700        'Save history, etc, in QSettings instance'
701        s = QSettings()
702        s.setValue('mq/splitter', self.splitter.saveState())
703        self.messageEditor.saveSettings(s, 'mq/editor')
704        self.fileview.saveSettings(s, 'mq/fileview')
705        if not self.parent():
706            s.setValue('mq/geom', self.saveGeometry())
707
708    def canExit(self):
709        self.storeConfigs()
710        return not self.cmd.core.running()
711
712    def keyPressEvent(self, event):
713        if event.key() == Qt.Key_Escape:
714            if self.cmd.core.running():
715                self.cmd.cancel()
716            elif not self.parent() and self.canExit():
717                self.close()
718        else:
719            return super(MQWidget, self).keyPressEvent(event)
720
721
722
723class PatchMessageCombo(QComboBox):
724    def __init__(self, parent):
725        super(PatchMessageCombo, self).__init__(parent)
726        self.reset([])
727
728    def reset(self, msglist):
729        self.clear()
730        self.addItem(_('Patch commit messages...'))
731        self.loaded = False
732        self.msglist = msglist
733
734    def showPopup(self):
735        if not self.loaded and self.msglist:
736            self.clear()
737            for patch, message in self.msglist:
738                sum = message.split('\n', 1)[0][:70]
739                self.addItem(hglib.tounicode('%s: %s' % (patch, sum)))
740            self.loaded = True
741        if self.loaded:
742            super(PatchMessageCombo, self).showPopup()
743
744
745
746class OptionsDialog(QDialog):
747    'Utility dialog for configuring uncommon options'
748    def __init__(self, parent):
749        QDialog.__init__(self, parent)
750        self.setWindowTitle('MQ options')
751
752        layout = QFormLayout()
753        self.setLayout(layout)
754
755        self.gitcb = QCheckBox(_('Force use of git extended diff format'))
756        layout.addRow(self.gitcb, None)
757
758        self.forcecb = QCheckBox(_('Force push or pop'))
759        layout.addRow(self.forcecb, None)
760
761        self.exactcb = QCheckBox(_('Apply patch to its recorded parent'))
762        layout.addRow(self.exactcb, None)
763
764        self.currentdatecb = QCheckBox(_('Update date field with current date'))
765        layout.addRow(self.currentdatecb, None)
766
767        self.datele = QLineEdit()
768        layout.addRow(QLabel(_('Specify an explicit date:')), self.datele)
769
770        self.currentusercb = QCheckBox(_('Update author field with current user'))
771        layout.addRow(self.currentusercb, None)
772
773        self.userle = QLineEdit()
774        layout.addRow(QLabel(_('Specify an explicit author:')), self.userle)
775
776        self.currentdatecb.toggled.connect(self.datele.setDisabled)
777        self.currentusercb.toggled.connect(self.userle.setDisabled)
778
779        self.gitcb.setChecked(parent.opts.get('git', False))
780        self.forcecb.setChecked(parent.opts.get('force', False))
781        self.exactcb.setChecked(parent.opts.get('exact', False))
782        self.currentdatecb.setChecked(parent.opts.get('currentdate', False))
783        self.currentusercb.setChecked(parent.opts.get('currentuser', False))
784        self.datele.setText(hglib.tounicode(parent.opts.get('date', '')))
785        self.userle.setText(hglib.tounicode(parent.opts.get('user', '')))
786
787        BB = QDialogButtonBox
788        bb = QDialogButtonBox(BB.Ok|BB.Cancel)
789        bb.accepted.connect(self.accept)
790        bb.rejected.connect(self.reject)
791        self.bb = bb
792        layout.addWidget(bb)
793
794    def accept(self):
795        outopts = {}
796        outopts['git'] = self.gitcb.isChecked()
797        outopts['force'] = self.forcecb.isChecked()
798        outopts['exact'] = self.exactcb.isChecked()
799        outopts['currentdate'] = self.currentdatecb.isChecked()
800        outopts['currentuser'] = self.currentusercb.isChecked()
801        if self.currentdatecb.isChecked():
802            outopts['date'] = ''
803        else:
804            outopts['date'] = hglib.fromunicode(self.datele.text())
805        if self.currentusercb.isChecked():
806            outopts['user'] = ''
807        else:
808            outopts['user'] = hglib.fromunicode(self.userle.text())
809
810        self.outopts = outopts
811        QDialog.accept(self)
812
813def run(ui, *pats, **opts):
814    from tortoisehg.util import paths
815    from tortoisehg.hgqt import thgrepo
816    repo = thgrepo.repository(ui, path=paths.find_root())
817    return MQWidget(repo, None, **opts)