Read about our upcoming Code of Conduct on this issue

mq.py 29.7 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# mq.py - TortoiseHg MQ widget
#
# Copyright 2011 Steve Borho <steve@borho.org>
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.

import os
import re

from PyQt4.QtCore import *
from PyQt4.QtGui import *

from mercurial import hg, ui, url, util, error
from mercurial import merge as mergemod
16
from hgext import mq as mqmod
17
18
19

from tortoisehg.util import hglib, patchctx
from tortoisehg.hgqt.i18n import _
20
from tortoisehg.hgqt import qtlib, cmdui, rejects, commit, shelve, qscilib
21
from tortoisehg.hgqt import qqueue, fileview
22

23
24
# TODO: Disable MQ toolbar while cmdui.Runner is busy

25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
class MQWidget(QWidget):
    showMessage = pyqtSignal(unicode)
    output = pyqtSignal(QString, QString)
    progress = pyqtSignal(QString, object, QString, QString, object)
    makeLogVisible = pyqtSignal(bool)

    def __init__(self, repo, parent, **opts):
        QWidget.__init__(self, parent)

        self.repo = repo
        self.opts = opts
        self.refreshing = False

        layout = QVBoxLayout()
        layout.setSpacing(0)
        self.setLayout(layout)

        # top toolbar
        tbarhbox = QHBoxLayout()
        tbarhbox.setSpacing(5)
        self.layout().addLayout(tbarhbox, 0)
        self.queueCombo = QComboBox()
        self.optionsBtn = QPushButton(_('Options'))
48
        self.msgSelectCombo = PatchMessageCombo(self)
49
50
        tbarhbox.addWidget(self.queueCombo)
        tbarhbox.addWidget(self.optionsBtn)
51
        tbarhbox.addWidget(self.msgSelectCombo, 1)
52
53

        # main area consists of a three-way horizontal splitter
Steve Borho's avatar
Steve Borho committed
54
        self.splitter = splitter = QSplitter()
55
56
57
58
59
60
61
        self.layout().addWidget(splitter, 1)
        splitter.setOrientation(Qt.Horizontal)
        splitter.setChildrenCollapsible(True)
        splitter.setObjectName('splitter')

        self.queueFrame = QFrame(splitter)
        self.messageFrame = QFrame(splitter)
62
        self.fileview = fileview.HgFileView(repo, splitter)
63
64
65

        self.fileview.showMessage.connect(self.showMessage)
        self.fileview.setContext(repo[None])
66
67
68

        # Patch Queue Frame
        layout = QVBoxLayout()
Steve Borho's avatar
Steve Borho committed
69
        layout.setSpacing(5)
70
71
72
73
        layout.setContentsMargins(0, 0, 0, 0)
        self.queueFrame.setLayout(layout)

        qtbarhbox = QHBoxLayout()
Steve Borho's avatar
Steve Borho committed
74
        qtbarhbox.setSpacing(5)
75
76
        layout.addLayout(qtbarhbox, 0)
        qtbarhbox.setContentsMargins(0, 0, 0, 0)
77
        self.qpushAllBtn = tb = QToolButton()
78
        tb.setIcon(qtlib.geticon('qpushall'))
79
80
81
82
        tb.setToolTip(_('Apply all patches'))
        self.qpushBtn = tb = QToolButton()
        tb.setIcon(qtlib.geticon('qpush'))
        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
89
90
91
92
93
94
95
        tb.setToolTip(_('Apply selected patch next (change queue order)'))
        self.qdeleteBtn = tb = QToolButton()
        tb.setIcon(qtlib.geticon('filedelete'))
        tb.setToolTip(_('Delete selected patches'))
        self.qpopBtn = tb = QToolButton()
        tb.setIcon(qtlib.geticon('qpop'))
        tb.setToolTip(_('Unapply one patch'))
        self.qpopAllBtn = tb = QToolButton()
96
        tb.setIcon(qtlib.geticon('qpopall'))
97
        tb.setToolTip(_('Unapply all patches'))
98
99
100
        qtbarhbox.addWidget(self.qpushAllBtn)
        qtbarhbox.addWidget(self.qpushBtn)
        qtbarhbox.addStretch(1)
101
        qtbarhbox.addWidget(self.setGuardsBtn)
102
103
104
105
106
107
108
109
110
        qtbarhbox.addWidget(self.qpushMoveBtn)
        qtbarhbox.addWidget(self.qdeleteBtn)
        qtbarhbox.addStretch(1)
        qtbarhbox.addWidget(self.qpopBtn)
        qtbarhbox.addWidget(self.qpopAllBtn)

        self.queueListWidget = QListWidget(self)
        layout.addWidget(self.queueListWidget, 1)

111
        self.guardSelBtn = QPushButton()
112
113
        layout.addWidget(self.guardSelBtn, 0)

114
        self.revisionOrCommitBtn = QPushButton()
115
116
117
118
        layout.addWidget(self.revisionOrCommitBtn, 0)

        # Message Frame
        layout = QVBoxLayout()
Steve Borho's avatar
Steve Borho committed
119
        layout.setSpacing(5)
120
121
122
123
        layout.setContentsMargins(0, 0, 0, 0)
        self.messageFrame.setLayout(layout)

        mtbarhbox = QHBoxLayout()
Steve Borho's avatar
Steve Borho committed
124
        mtbarhbox.setSpacing(8)
125
126
127
128
129
130
131
132
        layout.addLayout(mtbarhbox, 0)
        mtbarhbox.setContentsMargins(0, 0, 0, 0)
        self.newCheckBox = QCheckBox(_('New Patch'))
        self.patchNameLE = QLineEdit()
        mtbarhbox.addWidget(self.newCheckBox)
        mtbarhbox.addWidget(self.patchNameLE, 1)

        self.messageEditor = commit.MessageEntry(self)
133
        self.messageEditor.installEventFilter(qscilib.KeyPressInterceptor(self))
134
135
        self.messageEditor.setContextMenuPolicy(Qt.CustomContextMenu)
        self.messageEditor.customContextMenuRequested.connect(self.menuRequested)
136
        self.messageEditor.refresh(repo)
137
138
        layout.addWidget(self.messageEditor, 1)

Steve Borho's avatar
Steve Borho committed
139
        self.fileListWidget = QListWidget(self)
140
        self.fileListWidget.currentRowChanged.connect(self.onFileSelected)
Steve Borho's avatar
Steve Borho committed
141
142
        layout.addWidget(self.fileListWidget, 2)

143
144
145
        qrefhbox = QHBoxLayout()
        layout.addLayout(qrefhbox, 0)
        qrefhbox.setContentsMargins(0, 0, 0, 0)
Johan Samyn's avatar
Johan Samyn committed
146
147
        self.qqueueBtn = QPushButton(_('Manage queues'))
        self.qqueueBtn.setMinimumWidth(150)
148
149
        self.shelveBtn = QPushButton(_('Shelve'))
        self.qnewOrRefreshBtn = QPushButton(_('QRefresh'))
Johan Samyn's avatar
Johan Samyn committed
150
        qrefhbox.addWidget(self.qqueueBtn)
151
152
153
154
        qrefhbox.addStretch(1)
        qrefhbox.addWidget(self.shelveBtn)
        qrefhbox.addWidget(self.qnewOrRefreshBtn)

155
        # Command runner and connections...
156
        self.cmd = cmdui.Runner(_('Patch Queue'), parent == None, self)
157
158
159
        self.cmd.output.connect(self.output)
        self.cmd.makeLogVisible.connect(self.makeLogVisible)
        self.cmd.progress.connect(self.progress)
160
        self.cmd.commandFinished.connect(self.onCommandFinished)
161

Johan Samyn's avatar
Johan Samyn committed
162
        self.qqueueBtn.clicked.connect(self.launchQQueueTool)
Steve Borho's avatar
Steve Borho committed
163
164
165
        self.shelveBtn.clicked.connect(self.launchShelveTool)
        self.optionsBtn.clicked.connect(self.launchOptionsDialog)
        self.revisionOrCommitBtn.clicked.connect(self.qinitOrCommit)
166
        self.msgSelectCombo.activated.connect(self.onMessageSelected)
Steve Borho's avatar
Steve Borho committed
167
168
169
        self.queueListWidget.currentRowChanged.connect(self.onPatchSelected)
        self.queueListWidget.itemActivated.connect(self.onGotoPatch)
        self.queueListWidget.itemChanged.connect(self.onRenamePatch)
Steve Borho's avatar
Steve Borho committed
170
171
        self.qpushAllBtn.clicked.connect(self.onPushAll)
        self.qpushBtn.clicked.connect(self.onPush)
Steve Borho's avatar
Steve Borho committed
172
        self.qpushMoveBtn.clicked.connect(self.onPushMove)
Steve Borho's avatar
Steve Borho committed
173
174
        self.qpopAllBtn.clicked.connect(self.onPopAll)
        self.qpopBtn.clicked.connect(self.onPop)
175
        self.setGuardsBtn.clicked.connect(self.onGuardConfigure)
Steve Borho's avatar
Steve Borho committed
176
        self.qdeleteBtn.clicked.connect(self.onDelete)
177
        self.newCheckBox.toggled.connect(self.onNewModeToggled)
178
        self.qnewOrRefreshBtn.clicked.connect(self.onQNewOrQRefresh)
179
180
181
182
183

        self.repo.configChanged.connect(self.onConfigChanged)
        self.repo.repositoryChanged.connect(self.onRepositoryChanged)
        self.setAcceptDrops(True)

Johan Samyn's avatar
Johan Samyn committed
184
        if hasattr(self.patchNameLE, 'setPlaceholderText'): # Qt >= 4.7
185
186
187
            self.patchNameLE.setPlaceholderText('### patch name ###')

        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
193
194
195
            self.statusbar = cmdui.ThgStatusBar(self)
            self.layout().addWidget(self.statusbar)
            self.progress.connect(self.statusbar.progress)
            self.showMessage.connect(self.statusbar.showMessage)
196
            QShortcut(QKeySequence.Refresh, self, self.reload)
197
198
            self.resize(850, 550)

Steve Borho's avatar
Steve Borho committed
199
        self.loadConfigs()
200
201
        QTimer.singleShot(0, self.reload)

202
203
204
205
    def menuRequested(self, point):
        point = self.messageEditor.mapToGlobal(point)
        return self.messageEditor.createStandardContextMenu().exec_(point)

206
207
208
209
210
    def getUserOptions(self, *optionlist):
        out = []
        for opt in optionlist:
            if opt not in self.opts:
                continue
Steve Borho's avatar
Steve Borho committed
211
            val = self.opts[opt]
212
213
214
215
216
217
218
219
220
            if val is False:
                continue
            elif val is True:
                out.append('--' + opt)
            else:
                out.append('--' + opt)
                out.append(val)
        return out

221
    @pyqtSlot()
222
223
    def onConfigChanged(self):
        '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
229
230
231
    def onRepositoryChanged(self):
        'Repository is reporting its changelog has changed'
        self.reload()

232
233
234
235
236
237
    @pyqtSlot(int)
    def onCommandFinished(self, ret):
        self.repo.decrementBusyCount()
        if ret is not 0:
            pass # TODO: look for reject notifications

Steve Borho's avatar
Steve Borho committed
238
239
    @pyqtSlot()
    def onPushAll(self):
240
241
        if self.cmd.running():
            return
Steve Borho's avatar
Steve Borho committed
242
        self.repo.incrementBusyCount()
243
244
245
        cmdline = ['qpush', '-R', self.repo.root, '--all']
        cmdline += self.getUserOptions('force', 'exact')
        self.cmd.run(cmdline)
Steve Borho's avatar
Steve Borho committed
246
247
248

    @pyqtSlot()
    def onPush(self):
249
250
        if self.cmd.running():
            return
Steve Borho's avatar
Steve Borho committed
251
        self.repo.incrementBusyCount()
252
253
254
        cmdline = ['qpush', '-R', self.repo.root]
        cmdline += self.getUserOptions('force', 'exact')
        self.cmd.run(cmdline)
Steve Borho's avatar
Steve Borho committed
255
256
257

    @pyqtSlot()
    def onPopAll(self):
258
259
        if self.cmd.running():
            return
Steve Borho's avatar
Steve Borho committed
260
        self.repo.incrementBusyCount()
261
262
263
        cmdline = ['qpop', '-R', self.repo.root, '--all']
        cmdline += self.getUserOptions('force')
        self.cmd.run(cmdline)
Steve Borho's avatar
Steve Borho committed
264
265
266

    @pyqtSlot()
    def onPop(self):
267
268
        if self.cmd.running():
            return
Steve Borho's avatar
Steve Borho committed
269
        self.repo.incrementBusyCount()
270
271
272
        cmdline = ['qpop', '-R', self.repo.root]
        cmdline += self.getUserOptions('force')
        self.cmd.run(cmdline)
Steve Borho's avatar
Steve Borho committed
273

Steve Borho's avatar
Steve Borho committed
274
275
    @pyqtSlot()
    def onPushMove(self):
276
277
        if self.cmd.running():
            return
Steve Borho's avatar
Steve Borho committed
278
        patch = self.queueListWidget.currentItem()._thgpatch
Steve Borho's avatar
Steve Borho committed
279
        cmdline = ['qpush', '-R', self.repo.root]
280
281
        cmdline += self.getUserOptions('force')
        cmdline += ['--move', '--', patch]
Steve Borho's avatar
Steve Borho committed
282
        self.repo.incrementBusyCount()
283
        self.cmd.run(cmdline)
Steve Borho's avatar
Steve Borho committed
284

285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
    @pyqtSlot()
    def onGuardConfigure(self):
        item = self.queueListWidget.currentItem()
        patch = item._thgpatch
        if item._thgguards:
            uguards = hglib.tounicode(' '.join(item._thgguards))
        else:
            uguards = ''
        new, ok = QInputDialog.getText(self,
                      _('Configure guards'),
                      _('Input new guards for %s:') % hglib.tounicode(patch),
                      text=uguards, flags=Qt.Sheet)
        if not ok or new == uguards:
            return
        guards = []
        for guard in hglib.fromunicode(new).split(' '):
            guard = guard.strip()
            if not guard:
                continue
            if not (guard[0] == '+' or guard[0] == '-'):
                self.showMessage.emit(_('Guards must begin with "+" or "-"'))
                continue
            guards.append(guard)
        cmdline = ['qguard', '-R', self.repo.root, '--', patch]
        if guards:
            cmdline += guards
        else:
            cmdline.insert(3, '--none')
313
314
        if self.cmd.running():
            return
315
316
317
        self.repo.incrementBusyCount()
        self.cmd.run(cmdline)

Steve Borho's avatar
Steve Borho committed
318
319
320
321
322
323
324
325
326
    @pyqtSlot()
    def onDelete(self):
        from tortoisehg.hgqt import qdelete
        patch = self.queueListWidget.currentItem()._thgpatch
        dlg = qdelete.QDeleteDialog(self.repo, [patch], self)
        dlg.finished.connect(dlg.deleteLater)
        if dlg.exec_() == QDialog.Accepted:
            self.reload()

327
    #@pyqtSlot(QListWidgetItem)
Steve Borho's avatar
Steve Borho committed
328
329
    def onGotoPatch(self, item):
        'Patch has been activated (return), issue qgoto'
330
331
        if self.cmd.running():
            return
332
333
334
        cmdline = ['qgoto', '-R', self.repo.root]
        cmdline += self.getUserOptions('force')
        cmdline += ['--', item._thgpatch]
335
        self.repo.incrementBusyCount()
336
        self.cmd.run(cmdline)
Steve Borho's avatar
Steve Borho committed
337

338
    #@pyqtSlot(QListWidgetItem)
Steve Borho's avatar
Steve Borho committed
339
340
    def onRenamePatch(self, item):
        'Patch has been renamed, issue qrename'
341
342
        if self.cmd.running():
            return
343
        self.repo.incrementBusyCount()
344
345
        self.cmd.run(['qrename', '-R', self.repo.root, '--',
                      item._thgpatch, hglib.fromunicode(item.text())])
Steve Borho's avatar
Steve Borho committed
346
347
348
349
350
351
352
353
354

    @pyqtSlot(int)
    def onPatchSelected(self, row):
        'Patch has been selected, update buttons'
        if self.refreshing:
            return
        if row >= 0:
            patch = self.queueListWidget.item(row)._thgpatch
            applied = set([p.name for p in self.repo.mq.applied])
Steve Borho's avatar
Steve Borho committed
355
            self.qdeleteBtn.setEnabled(patch not in applied)
Steve Borho's avatar
Steve Borho committed
356
357
358
359
360
361
362
            self.qpushMoveBtn.setEnabled(patch not in applied)
            self.setGuardsBtn.setEnabled(True)
        else:
            self.qdeleteBtn.setEnabled(False)
            self.qpushMoveBtn.setEnabled(False)
            self.setGuardsBtn.setEnabled(False)

363
364
365
366
367
368
369
370
371
372
373
374
375
    @pyqtSlot(int)
    def onFileSelected(self, row):
        if self.refreshing or row == -1:
            return
        text = hglib.fromunicode(self.fileListWidget.item(row).text())
        status = text[0]
        filename = text[2:]
        if self.newCheckBox.isChecked():
            rev = self.repo['.'].rev()
        else:
            rev = self.repo['qtip'].p1().rev()
        self.fileview.displayFile(filename, rev, status)

376
377
378
379
380
381
382
383
    @pyqtSlot(int)
    def onMessageSelected(self, row):
        if self.messageEditor.text() and self.messageEditor.isModified():
            d = QMessageBox.question(self, _('Confirm Discard Message'),
                        _('Discard current commit message?'),
                        QMessageBox.Ok | QMessageBox.Cancel)
            if d != QMessageBox.Ok:
                return
384
385
386
387
388
        self.setMessage(self.messages[row][1])
        self.messageEditor.setFocus()

    def setMessage(self, message):
        self.messageEditor.setText(message)
389
390
391
392
393
394
395
396
397
398
        lines = self.messageEditor.lines()
        if lines:
            lines -= 1
            pos = self.messageEditor.lineLength(lines)
            self.messageEditor.setCursorPosition(lines, pos)
            self.messageEditor.ensureLineVisible(lines)
            hs = self.messageEditor.horizontalScrollBar()
            hs.setSliderPosition(0)
        self.messageEditor.setModified(False)

399
400
401
402
403
404
405
406
407
408
409
410
411
412
    @pyqtSlot()
    def onQNewOrQRefresh(self):
        if self.newCheckBox.isChecked():
            name = hglib.fromunicode(self.patchNameLE.text())
            if not name:
                qtlib.ErrorMsgBox(_('Patch Name Required'),
                                  _('You must enter a patch name'))
                self.patchNameLE.setFocus()
                return
            cmdline = ['qnew', '--repository', self.repo.root, name]
        else:
            cmdline = ['qrefresh', '--repository', self.repo.root]
        message = self.messageEditor.text()
        if message:
413
            cmdline += ['--message=' + hglib.fromunicode(message)]
414
415
        cmdline += self.getUserOptions('user', 'currentuser', 'git',
                                       'date', 'currentdate')
416
417
418
419
        files = ['--']
        for row in xrange(self.fileListWidget.count()):
            item = self.fileListWidget.item(row)
            if item.checkState() == Qt.Checked:
420
421
                wfile = hglib.fromunicode(item.text()[2:])
                files.append(self.repo.wjoin(wfile))
422
423
424
425
426
427
428
        if len(files) > 1:
            cmdline += files
        else:
            cmdline += ['--exclude', self.repo.root]
        self.repo.incrementBusyCount()
        self.cmd.run(cmdline)

429
430
431
432
433
434
435
436
437
438
439
    @pyqtSlot()
    def qinitOrCommit(self):
        if os.path.isdir(self.repo.mq.join('.hg')):
            dlg = commit.CommitDialog([], dict(root=self.repo.mq.path), self)
            dlg.finished.connect(dlg.deleteLater)
            dlg.exec_()
            self.reload()
        else:
            self.repo.incrementBusyCount()
            self.cmd.run(['qinit', '-c', '-R', self.repo.root])

Johan Samyn's avatar
Johan Samyn committed
440
441
442
443
444
445
446
    @pyqtSlot()
    def launchQQueueTool(self):
        dlg = qqueue.QQueueDialog(self.repo, self)
        dlg.finished.connect(dlg.deleteLater)
        dlg.exec_()
        self.reload()

447
    @pyqtSlot()
448
449
450
451
452
453
    def launchShelveTool(self):
        dlg = shelve.ShelveDialog(self.repo, self)
        dlg.finished.connect(dlg.deleteLater)
        dlg.exec_()
        self.reload()

454
    @pyqtSlot()
Steve Borho's avatar
Steve Borho committed
455
456
457
458
459
460
461
462
    def launchOptionsDialog(self):
        dlg = OptionsDialog(self)
        dlg.finished.connect(dlg.deleteLater)
        dlg.setWindowFlags(Qt.Sheet)
        dlg.setWindowModality(Qt.WindowModal)
        if dlg.exec_() == QDialog.Accepted:
            self.opts.update(dlg.outopts)

463
464
    def reload(self):
        self.refreshing = True
465
        try:
466
467
468
469
            try:
                self._reload()
            except Exception, e:
                self.showMessage.emit(hglib.tounicode(str(e)))
470
471
472
                if 'THGDEBUG' in os.environ:
                    import traceback
                    traceback.print_exc()
473
474
475
476
        finally:
            self.refreshing = False

    def _reload(self):
477
478
        ui, repo = self.repo.ui, self.repo

479
480
        self.queueCombo.clear()
        self.queueListWidget.clear()
481
482
483
484
485
486
487
488
489
490
491

        ui.pushbuffer()
        mqmod.qqueue(ui, repo, list=True)
        out = ui.popbuffer()
        activestr = ' (active)' # TODO: not locale safe
        for i, qname in enumerate(out.splitlines()):
            if qname.endswith(activestr):
                current = i
                qname = qname[:-len(activestr)]
            self.queueCombo.addItem(hglib.tounicode(qname))
        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])
Steve Borho's avatar
Steve Borho committed
496
        self.allguards = set()
497
498
        items = []
        for idx, patch in enumerate(repo.mq.series):
499
            item = QListWidgetItem(hglib.tounicode(patch))
Steve Borho's avatar
Steve Borho committed
500
            if patch in applied: # applied
501
                f = item.font()
502
                f.setBold(True)
503
                item.setFont(f)
Steve Borho's avatar
Steve Borho committed
504
505
            elif not repo.mq.pushable(idx)[0]: # guarded
                f = item.font()
506
                f.setItalic(True)
Steve Borho's avatar
Steve Borho committed
507
                item.setFont(f)
508
509
            patchguards = repo.mq.series_guards[idx]
            if patchguards:
Steve Borho's avatar
Steve Borho committed
510
511
512
                for guard in patchguards:
                    self.allguards.add(guard[1:])
                uguards = hglib.tounicode(', '.join(patchguards))
513
514
515
            else:
                uguards = _('no guards')
            uname = hglib.tounicode(patch)
Steve Borho's avatar
Steve Borho committed
516
            item._thgpatch = patch
517
            item._thgguards = patchguards
518
            item.setToolTip(u'%s: %s' % (uname, uguards))
Steve Borho's avatar
Steve Borho committed
519
520
521
            item.setFlags(Qt.ItemIsSelectable |
                          Qt.ItemIsEditable |
                          Qt.ItemIsEnabled)
522
523
            items.append(item)
        for item in reversed(items):
524
525
            self.queueListWidget.addItem(item)

526
        for guard in repo.mq.active():
527
528
529
            self.allguards.add(guard)
        self.refreshSelectedGuards()

530
531
532
533
534
535
        self.messages = []
        for patch in repo.mq.series:
            ctx = repo.changectx(patch)
            msg = ctx.description()
            if msg:
                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
541
        else:
            self.revisionOrCommitBtn.setText(_('Revision Queue'))
Steve Borho's avatar
Steve Borho committed
542
543
544
545
546
547
548
549

        self.qpushAllBtn.setEnabled(bool(repo.thgmqunappliedpatches))
        self.qpushBtn.setEnabled(bool(repo.thgmqunappliedpatches))
        self.qpushMoveBtn.setEnabled(False)
        self.qdeleteBtn.setEnabled(False)
        self.setGuardsBtn.setEnabled(False)
        self.qpopBtn.setEnabled(bool(applied))
        self.qpopAllBtn.setEnabled(bool(applied))
550

551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
        pctx = repo.changectx('.')
        newmode = self.newCheckBox.isChecked()
        if 'qtip' in pctx.tags():
            self.fileListWidget.setEnabled(True)
            self.messageEditor.setEnabled(True)
            self.msgSelectCombo.setEnabled(True)
            self.qnewOrRefreshBtn.setEnabled(True)
            if not newmode:
                self.setMessage(pctx.description())
                name = repo.mq.applied[-1].name
                self.patchNameLE.setText(hglib.tounicode(name))
        else:
            self.fileListWidget.setEnabled(newmode)
            self.messageEditor.setEnabled(newmode)
            self.msgSelectCombo.setEnabled(newmode)
            self.qnewOrRefreshBtn.setEnabled(newmode)
            if not newmode:
                self.setMessage('')
                self.patchNameLE.setText('')
        self.patchNameLE.setEnabled(newmode)
        self.refreshFileListWidget()

    def onNewModeToggled(self, isChecked):
        if isChecked:
            self.fileListWidget.setEnabled(True)
            self.qnewOrRefreshBtn.setText(_('QNew'))
            self.qnewOrRefreshBtn.setEnabled(True)
            self.messageEditor.setEnabled(True)
            self.patchNameLE.setEnabled(True)
            self.patchNameLE.selectAll()
            self.patchNameLE.setFocus()
            self.setMessage('')
        else:
            self.qnewOrRefreshBtn.setText(_('QRefresh'))
            pctx = self.repo.changectx('.')
            if 'qtip' in pctx.tags():
                self.messageEditor.setEnabled(True)
                self.setMessage(pctx.description())
                name = self.repo.mq.applied[-1].name
                self.patchNameLE.setText(hglib.tounicode(name))
                self.qnewOrRefreshBtn.setEnabled(True)
                self.fileListWidget.setEnabled(True)
            else:
                self.messageEditor.setEnabled(False)
                self.qnewOrRefreshBtn.setEnabled(False)
                self.fileListWidget.setEnabled(False)
                self.patchNameLE.setText('')
                self.setMessage('')
            self.patchNameLE.setEnabled(False)
        self.refreshing = True
        try:
            try:
                self.refreshFileListWidget()
            except Exception, e:
                self.showMessage.emit(hglib.tounicode(str(e)))
                import traceback
                traceback.print_exc()
        finally:
            self.refreshing = False

    def refreshFileListWidget(self):
612
        # TODO: maintain selection, check state
613
614
615
616
617
618
        def addfiles(mode, files):
            for file in files:
                item = QListWidgetItem(u'%s %s' % (mode, hglib.tounicode(file)))
                item.setFlags(flags)
                item.setCheckState(Qt.Checked)
                self.fileListWidget.addItem(item)
619
        self.fileListWidget.clear()
620
        self.fileview.clearDisplay()
621
        pctx = self.repo.changectx('.')
Steve Borho's avatar
Steve Borho committed
622
623
        newmode = self.newCheckBox.isChecked()
        if not newmode and 'qtip' in pctx.tags():
624
625
            # Show qrefresh (qdiff) diffs
            M, A, R = self.repo.status(pctx.p1().node(), None)[:3]
626
        elif newmode:
627
628
            # Show qnew (working) diffs
            M, A, R = self.repo[None].status()[:3]
629
        else:
630
631
            return
        flags = Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled
632
633
634
        addfiles(u'A', A)
        addfiles(u'M', M)
        addfiles(u'R', R)
635

636
    def refreshSelectedGuards(self):
637
        total = len(self.allguards)
638
        count = len(self.repo.mq.active())
Steve Borho's avatar
Steve Borho committed
639
640
641
642
643
644
645
        oldmenu = self.guardSelBtn.menu()
        if oldmenu:
            oldmenu.setParent(None)
        menu = QMenu(self)
        for guard in self.allguards:
            a = menu.addAction(hglib.tounicode(guard))
            a.setCheckable(True)
646
            a.setChecked(guard in self.repo.mq.active())
Steve Borho's avatar
Steve Borho committed
647
648
            a.triggered.connect(self.onGuardSelectionChange)
        self.guardSelBtn.setMenu(menu)
649
        self.guardSelBtn.setText(_('Guards: %d/%d') % (count, total))
650

Steve Borho's avatar
Steve Borho committed
651
652
    def onGuardSelectionChange(self, isChecked):
        guard = hglib.fromunicode(self.sender().text())
653
        newguards = self.repo.mq.active()[:]
Steve Borho's avatar
Steve Borho committed
654
655
656
657
658
659
        if isChecked:
            newguards.append(guard)
        elif guard in newguards:
            newguards.remove(guard)
        cmdline = ['qselect', '-R', self.repo.root]
        cmdline += newguards or ['--none']
Steve Borho's avatar
Steve Borho committed
660
        self.repo.incrementBusyCount()
Steve Borho's avatar
Steve Borho committed
661
662
        self.cmd.run(cmdline)

663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
    # Capture drop events, try to import into current patch queue

    def dragEnterEvent(self, event):
        event.acceptProposedAction()

    def dragMoveEvent(self, event):
        event.acceptProposedAction()

    def dropEvent(self, event):
        paths = [unicode(u.toLocalFile()) for u in event.mimeData().urls()]
        filepaths = [p for p in paths if os.path.isfile(p)]
        if filepaths:
            event.setDropAction(Qt.CopyAction)
            event.accept()
        else:
            super(MQWidget, self).dropEvent(event)
            return
        dlg = thgimport.ImportDialog(repo=self.repo, parent=self)
        # TODO: send flag to dialog indicating this is a qimport (alias?)
        dlg.finished.connect(dlg.deleteLater)
        dlg.setfilepaths(filepaths)
        dlg.exec_()

    # End drop events

Steve Borho's avatar
Steve Borho committed
688
689
690
691
692
693
    def loadConfigs(self):
        'Load history, etc, from QSettings instance'
        s = QSettings()
        self.splitter.restoreState(s.value('mq/splitter').toByteArray())
        userhist = s.value('commit/userhist').toStringList()
        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')
Steve Borho's avatar
Steve Borho committed
696
697
698
699
700
701
702
        if not self.parent():
            self.restoreGeometry(s.value('mq/geom').toByteArray())

    def storeConfigs(self):
        'Save history, etc, in QSettings instance'
        s = QSettings()
        s.setValue('mq/splitter', self.splitter.saveState())
703
        self.messageEditor.saveSettings(s, 'mq/editor')
704
        self.fileview.saveSettings(s, 'mq/fileview')
Steve Borho's avatar
Steve Borho committed
705
706
707
        if not self.parent():
            s.setValue('mq/geom', self.saveGeometry())

708
    def canExit(self):
Steve Borho's avatar
Steve Borho committed
709
        self.storeConfigs()
710
711
712
        return not self.cmd.core.running()

    def keyPressEvent(self, event):
Steve Borho's avatar
Steve Borho committed
713
        if event.key() == Qt.Key_Escape:
714
715
            if self.cmd.core.running():
                self.cmd.cancel()
Steve Borho's avatar
Steve Borho committed
716
            elif not self.parent() and self.canExit():
717
718
719
720
                self.close()
        else:
            return super(MQWidget, self).keyPressEvent(event)

721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745


class PatchMessageCombo(QComboBox):
    def __init__(self, parent):
        super(PatchMessageCombo, self).__init__(parent)
        self.reset([])

    def reset(self, msglist):
        self.clear()
        self.addItem(_('Patch commit messages...'))
        self.loaded = False
        self.msglist = msglist

    def showPopup(self):
        if not self.loaded and self.msglist:
            self.clear()
            for patch, message in self.msglist:
                sum = message.split('\n', 1)[0][:70]
                self.addItem(hglib.tounicode('%s: %s' % (patch, sum)))
            self.loaded = True
        if self.loaded:
            super(PatchMessageCombo, self).showPopup()



746
747
748
749
750
751
class OptionsDialog(QDialog):
    'Utility dialog for configuring uncommon options'
    def __init__(self, parent):
        QDialog.__init__(self, parent)
        self.setWindowTitle('MQ options')

Steve Borho's avatar
Steve Borho committed
752
        layout = QFormLayout()
753
754
        self.setLayout(layout)

755
        self.gitcb = QCheckBox(_('Force use of git extended diff format'))
756
757
758
759
760
761
762
763
764
765
766
        layout.addRow(self.gitcb, None)

        self.forcecb = QCheckBox(_('Force push or pop'))
        layout.addRow(self.forcecb, None)

        self.exactcb = QCheckBox(_('Apply patch to its recorded parent'))
        layout.addRow(self.exactcb, None)

        self.currentdatecb = QCheckBox(_('Update date field with current date'))
        layout.addRow(self.currentdatecb, None)

Steve Borho's avatar
Steve Borho committed
767
768
769
        self.datele = QLineEdit()
        layout.addRow(QLabel(_('Specify an explicit date:')), self.datele)

770
771
772
        self.currentusercb = QCheckBox(_('Update author field with current user'))
        layout.addRow(self.currentusercb, None)

Steve Borho's avatar
Steve Borho committed
773
774
775
776
777
778
779
780
781
782
783
784
785
786
        self.userle = QLineEdit()
        layout.addRow(QLabel(_('Specify an explicit author:')), self.userle)

        self.currentdatecb.toggled.connect(self.datele.setDisabled)
        self.currentusercb.toggled.connect(self.userle.setDisabled)

        self.gitcb.setChecked(parent.opts.get('git', False))
        self.forcecb.setChecked(parent.opts.get('force', False))
        self.exactcb.setChecked(parent.opts.get('exact', False))
        self.currentdatecb.setChecked(parent.opts.get('currentdate', False))
        self.currentusercb.setChecked(parent.opts.get('currentuser', False))
        self.datele.setText(hglib.tounicode(parent.opts.get('date', '')))
        self.userle.setText(hglib.tounicode(parent.opts.get('user', '')))

787
788
789
790
        BB = QDialogButtonBox
        bb = QDialogButtonBox(BB.Ok|BB.Cancel)
        bb.accepted.connect(self.accept)
        bb.rejected.connect(self.reject)
Steve Borho's avatar
Steve Borho committed
791
        self.bb = bb
792
793
794
795
796
797
798
799
800
        layout.addWidget(bb)

    def accept(self):
        outopts = {}
        outopts['git'] = self.gitcb.isChecked()
        outopts['force'] = self.forcecb.isChecked()
        outopts['exact'] = self.exactcb.isChecked()
        outopts['currentdate'] = self.currentdatecb.isChecked()
        outopts['currentuser'] = self.currentusercb.isChecked()
Steve Borho's avatar
Steve Borho committed
801
802
803
804
805
806
807
808
809
        if self.currentdatecb.isChecked():
            outopts['date'] = ''
        else:
            outopts['date'] = hglib.fromunicode(self.datele.text())
        if self.currentusercb.isChecked():
            outopts['user'] = ''
        else:
            outopts['user'] = hglib.fromunicode(self.userle.text())

810
811
812
813
814
815
816
817
        self.outopts = outopts
        QDialog.accept(self)

def run(ui, *pats, **opts):
    from tortoisehg.util import paths
    from tortoisehg.hgqt import thgrepo
    repo = thgrepo.repository(ui, path=paths.find_root())
    return MQWidget(repo, None, **opts)