/tortoisehg/hgqt/qqueue.py

https://bitbucket.org/tortoisehg/hgtk/ · Python · 356 lines · 282 code · 43 blank · 31 comment · 33 complexity · cd1fff9e7c671bb88069d413202e1711 MD5 · raw file

  1. # qqueue.py - TortoiseHg dialog for managing multiple MQ patch queues
  2. #
  3. # Copyright 2011 Johan Samyn <johan.samyn@gmail.com>
  4. #
  5. # This software may be used and distributed according to the terms of the
  6. # GNU General Public License version 2, incorporated herein by reference.
  7. import os
  8. from mercurial import ui as uimod
  9. from mercurial import util
  10. from hgext import mq
  11. from tortoisehg.hgqt.i18n import _
  12. from tortoisehg.hgqt import thgrepo, qtlib, cmdui
  13. from tortoisehg.util import paths, hglib
  14. from PyQt4.QtCore import *
  15. from PyQt4.QtGui import *
  16. # TODO:
  17. # - Renaming a non-active queue ? (Why is it hg doesn't allow this ?)
  18. class QQueueDialog(QDialog):
  19. """Dialog for managing multiple MQ patch queues"""
  20. output = pyqtSignal(QString, QString)
  21. makeLogVisible = pyqtSignal(bool)
  22. def __init__(self, repo, parent=None):
  23. super(QQueueDialog, self).__init__(parent)
  24. self.setWindowIcon(qtlib.geticon('thg_logo'))
  25. self.setWindowTitle(_('Manage MQ patch queues'))
  26. self.setWindowFlags(self.windowFlags()
  27. & ~Qt.WindowContextHelpButtonHint)
  28. self.activequeue = ''
  29. self.repo = repo
  30. repo.repositoryChanged.connect(self.reload)
  31. layout = QVBoxLayout()
  32. layout.setMargin(4)
  33. self.setLayout(layout)
  34. hbr = QHBoxLayout()
  35. hbr.setMargin(2)
  36. layout.addLayout(hbr)
  37. rlbl = QLabel(_('Repository:'))
  38. hbr.addWidget(rlbl)
  39. rle = QLineEdit()
  40. hbr.addWidget(rle)
  41. rle.setFont(qtlib.getfont('fontlist').font())
  42. rle.setText(repo.displayname)
  43. rle.setReadOnly(True)
  44. rle.setFocusPolicy(Qt.NoFocus)
  45. topsep = qtlib.LabeledSeparator('')
  46. layout.addWidget(topsep)
  47. hbl = QHBoxLayout()
  48. hbl.setMargin(2)
  49. layout.addLayout(hbl)
  50. qvb = QVBoxLayout()
  51. hbl.addLayout(qvb)
  52. qlbl = QLabel(_('Patch queues:'))
  53. qvb.addWidget(qlbl)
  54. ql = QListWidget(self)
  55. qvb.addWidget(ql)
  56. ql.currentRowChanged.connect(self.updateUI)
  57. vbb = QVBoxLayout()
  58. vbb.setMargin(2)
  59. qvb.addLayout(vbb)
  60. hqbtntop = QHBoxLayout()
  61. vbb.addLayout(hqbtntop)
  62. hqbtnmid = QHBoxLayout()
  63. vbb.addLayout(hqbtnmid)
  64. hqbtnbot = QHBoxLayout()
  65. vbb.addLayout(hqbtnbot)
  66. btrel = QPushButton(_('Reload'))
  67. btrel.clicked.connect(self.reload)
  68. hqbtntop.addWidget(btrel)
  69. btact = QPushButton(_('Activate'))
  70. btact.clicked.connect(self.qqueueActivate)
  71. hqbtntop.addWidget(btact)
  72. btadd = QPushButton(_('Add'))
  73. btadd.clicked.connect(self.qqueueAdd)
  74. hqbtnmid.addWidget(btadd)
  75. btren = QPushButton(_('Rename'))
  76. btren.clicked.connect(self.qqueueRename)
  77. hqbtnmid.addWidget(btren)
  78. btdel = QPushButton(_('Delete'))
  79. btdel.clicked.connect(self.qqueueDelete)
  80. hqbtnbot.addWidget(btdel)
  81. btpur = QPushButton(_('Purge'))
  82. btpur.clicked.connect(self.qqueuePurge)
  83. hqbtnbot.addWidget(btpur)
  84. pvb = QVBoxLayout()
  85. hbl.addLayout(pvb)
  86. plbl = QLabel(_('Patches:'))
  87. pvb.addWidget(plbl)
  88. pl = QListWidget(self)
  89. pvb.addWidget(pl)
  90. botsep = qtlib.LabeledSeparator('')
  91. layout.addWidget(botsep)
  92. cmdlist = cmdui.Runner()
  93. cmdlist.output.connect(self.output)
  94. cmdlist.makeLogVisible.connect(self.makeLogVisible)
  95. cmd = cmdui.Runner()
  96. cmd.output.connect(self.output)
  97. cmd.makeLogVisible.connect(self.makeLogVisible)
  98. BB = QDialogButtonBox
  99. bb = QDialogButtonBox(BB.Close)
  100. bb.button(BB.Close).clicked.connect(self.close)
  101. layout.addWidget(bb)
  102. self.setLayout(layout)
  103. self.ql = ql
  104. self.pl = pl
  105. self.btrel = btrel
  106. self.btact = btact
  107. self.btadd = btadd
  108. self.btren = btren
  109. self.btdel = btdel
  110. self.btpur = btpur
  111. self.bb = bb
  112. self.cmdlist = cmdlist
  113. self.cmd = cmd
  114. self.itemfont = None
  115. self.itemfontbold = None
  116. self._readsettings()
  117. self.reload()
  118. self.ql.setFocus()
  119. def setButtonState(self, state):
  120. if state:
  121. if self.ql.currentRow() != -1:
  122. q = hglib.fromunicode(self.ql.item(self.ql.currentRow()).text())
  123. self.btact.setEnabled(q != self.activequeue)
  124. self.btren.setEnabled(q == self.activequeue and q != 'patches')
  125. self.btdel.setEnabled(q != 'patches')
  126. self.btpur.setEnabled(q != 'patches')
  127. else:
  128. self.btact.setEnabled(False)
  129. self.btren.setEnabled(False)
  130. self.btdel.setEnabled(False)
  131. self.btpur.setEnabled(False)
  132. self.btrel.setEnabled(True)
  133. self.btadd.setEnabled(True)
  134. self.bb.setEnabled(True)
  135. else:
  136. self.btrel.setEnabled(False)
  137. self.btact.setEnabled(False)
  138. self.btadd.setEnabled(False)
  139. self.btren.setEnabled(False)
  140. self.btdel.setEnabled(False)
  141. self.btpur.setEnabled(False)
  142. self.bb.setEnabled(False)
  143. @pyqtSlot()
  144. def updateUI(self):
  145. if self.ql.currentRow() != -1:
  146. self.showPatchesForQueue()
  147. self.setButtonState(True)
  148. self.bb.setEnabled(True)
  149. @pyqtSlot()
  150. def reload(self):
  151. def reloadFinished():
  152. self.repo.decrementBusyCount()
  153. output = self.cmdlist.core.rawoutput()
  154. self.showQueues(output)
  155. self.updateUI()
  156. cmdline = ['qqueue', '--repository', self.repo.root, '--list']
  157. self.cmdlist.commandFinished.connect(reloadFinished)
  158. self.repo.incrementBusyCount()
  159. self.cmdlist.run(cmdline)
  160. # This seems to return the cached data as it was just before the last
  161. # issued command. So I used the threaded method again.
  162. # def reload(self):
  163. # _ui = uimod.ui()
  164. # _ui.pushbuffer()
  165. # try:
  166. # opts = {'list': True}
  167. # mq.qqueue(_ui, self.repo, None, **opts)
  168. # except (util.Abort, EnvironmentError), e:
  169. # print e
  170. # output = _ui.popbuffer()
  171. # qtlib.InfoMsgBox('test', '<p>reload - output = %s</p>' % output)
  172. # self.showQueues(output)
  173. def showQueues(self, output):
  174. queues = output.rstrip('\n').split('\n')
  175. self.ql.clear()
  176. self.pl.clear()
  177. row_activeq = 0
  178. for i, q in enumerate(queues):
  179. item = QListWidgetItem(q)
  180. item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled
  181. | Qt.ItemIsDragEnabled)
  182. self.ql.addItem(item)
  183. if self.itemfont == None:
  184. self.itemfont = item.font()
  185. self.itemfontbold = self.itemfont
  186. self.itemfontbold.setBold(True)
  187. if 'active' in q:
  188. row_activeq = i
  189. self.activequeue = q[:-9]
  190. item.setText(self.activequeue)
  191. item.setFont(self.itemfontbold)
  192. self.ql.setCurrentRow(row_activeq)
  193. def showPatchesForQueue(self):
  194. currow = self.ql.currentRow()
  195. if currow == -1:
  196. return
  197. while currow > self.ql.count() - 1:
  198. currow -= 1
  199. q = hglib.fromunicode(self.ql.item(currow).text())
  200. self.pl.clear()
  201. patches = []
  202. if q == self.activequeue:
  203. patches = self.repo.mq.full_series
  204. else:
  205. if q == 'patches':
  206. sf = '/.hg/patches/series'
  207. else:
  208. sf = '/.hg/patches-%s/series' % q
  209. sf = self.repo.root + sf
  210. if os.path.exists(sf):
  211. f = open(sf, 'r')
  212. try:
  213. patches = f.read().splitlines()
  214. finally:
  215. f.close()
  216. for p in patches:
  217. item = QListWidgetItem(hglib.tounicode(p))
  218. item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
  219. self.pl.addItem(item)
  220. self.ql.setFocus()
  221. @pyqtSlot()
  222. def qqueueActivate(self):
  223. q = hglib.fromunicode(self.ql.item(self.ql.currentRow()).text())
  224. if q == self.activequeue:
  225. return
  226. if qtlib.QuestionMsgBox(_('Confirm patch queue switch'),
  227. _('Do you really want to activate patch queue \'%s\' ?' % q),
  228. parent=self, defaultbutton=QMessageBox.No):
  229. opts = [q]
  230. self.qqueueCommand(opts)
  231. @pyqtSlot()
  232. def qqueueAdd(self):
  233. title = _('TortoiseHg Prompt')
  234. # this is the only way I found to make that dialog wide enough :(
  235. label = QString(_('New patch queue name') + (' ' * 30))
  236. # WindowContextHelpButton still there :( after this ?
  237. dlg = QInputDialog(self, Qt.WindowFlags()
  238. & ~Qt.WindowContextHelpButtonHint)
  239. qname, ok = dlg.getText(self, title, label)
  240. if qname and ok:
  241. opts = ['--create', hglib.fromunicode(qname)]
  242. self.qqueueCommand(opts)
  243. @pyqtSlot()
  244. def qqueueRename(self):
  245. q = hglib.fromunicode(self.ql.item(self.ql.currentRow()).text())
  246. if q == 'patches':
  247. return
  248. title = _('TortoiseHg Prompt')
  249. # this is the only way I found to make that dialog wide enough :(
  250. label = QString(_('Rename patch queue \'%s\' to' % q) + (' ' * 30))
  251. # WindowContextHelpButton still there :( after this ?
  252. dlg = QInputDialog(self, Qt.WindowFlags()
  253. & ~Qt.WindowContextHelpButtonHint)
  254. newqname, ok = dlg.getText(self, title, label)
  255. if newqname:
  256. newqname = hglib.fromunicode(newqname)
  257. if newqname and ok:
  258. opts = ['--rename', newqname]
  259. self.qqueueCommand(opts)
  260. @pyqtSlot()
  261. def qqueueDelete(self):
  262. q = hglib.fromunicode(self.ql.item(self.ql.currentRow()).text())
  263. if q == 'patches':
  264. return
  265. if qtlib.QuestionMsgBox(_('Confirm patch queue delete'),
  266. _('Do you really want to delete patch queue \'%s\' ?'
  267. % q), parent=self, defaultbutton=QMessageBox.No):
  268. opts = ['--delete', q]
  269. self.qqueueCommand(opts)
  270. @pyqtSlot()
  271. def qqueuePurge(self):
  272. q = hglib.fromunicode(self.ql.item(self.ql.currentRow()).text())
  273. if q == 'patches':
  274. return
  275. if qtlib.QuestionMsgBox(_('Confirm patch queue purge'),
  276. _('<p>This will also erase de patchfiles on disk!</p>'
  277. '<p>Do you really want to purge patch queue \'%s\' ?</p>'
  278. % q), parent=self, defaultbutton=QMessageBox.No):
  279. opts = ['--purge', q]
  280. self.qqueueCommand(opts)
  281. def qqueueCommand(self, opts):
  282. self.setButtonState(False)
  283. def qqcmdFinished():
  284. self.repo.decrementBusyCount()
  285. # This seems to cause excessive refreshes ?!
  286. # See when using 'thgdbg qqueue' from the commandline.
  287. # But when not used, the data are not reshown after a command.
  288. # Is it ok to have 2 cmd threads in the same dialog ?
  289. self.reload()
  290. # self.updateUI()
  291. self.cmd.commandFinished.connect(qqcmdFinished)
  292. cmdline = ['qqueue', '--repository', self.repo.root] + opts
  293. self.repo.incrementBusyCount()
  294. self.cmd.run(cmdline)
  295. def accept(self):
  296. self._writesettings()
  297. QDialog.accept(self)
  298. def close(self, event):
  299. self._writesettings()
  300. QDialog.close(self)
  301. def _readsettings(self):
  302. s = QSettings()
  303. self.restoreGeometry(s.value('qqueue/geom').toByteArray())
  304. def _writesettings(self):
  305. s = QSettings()
  306. s.setValue('qqueue/geom', self.saveGeometry())
  307. def run(ui, *pats, **opts):
  308. repo = thgrepo.repository(None, paths.find_root())
  309. if hasattr(repo, 'mq'):
  310. return QQueueDialog(repo)
  311. else:
  312. qtlib.ErrorMsgBox(_('TortoiseHg Error'),
  313. _('Please enable the MQ extension first.'))