/tortoisehg/hgqt/mq.py

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