/tortoisehg/hgtk/quickop.py

https://bitbucket.org/tortoisehg/hgtk/ · Python · 289 lines · 216 code · 51 blank · 22 comment · 53 complexity · 109081a130fc9c480406263dab0d8eca MD5 · raw file

  1. # quickop.py - TortoiseHg's dialog for quick dirstate operations
  2. #
  3. # Copyright 2009 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, incorporated herein by reference.
  7. import os
  8. import gtk
  9. import pango
  10. from mercurial import cmdutil, util
  11. from tortoisehg.util.i18n import _
  12. from tortoisehg.util import hglib, shlib
  13. from tortoisehg.hgtk import gtklib, gdialog
  14. LABELS = { 'add': (_('Select files to add'), _('Add')),
  15. 'forget': (_('Select files to forget'), _('Forget')),
  16. 'revert': (_('Select files to revert'), _('Revert')),
  17. 'remove': (_('Select files to remove'), _('Remove')),}
  18. DEFAULT_SIZE = (450, 300)
  19. DEFAULT_POS = (0, 0)
  20. class QuickOpDialog(gdialog.GDialog):
  21. """ Dialog for performing quick dirstate operations """
  22. def __init__(self, command, pats):
  23. gdialog.GDialog.__init__(self, resizable=True)
  24. self.pats = pats
  25. # Handle rm alias
  26. if command == 'rm':
  27. command = 'remove'
  28. self.command = command
  29. # show minimize/maximize buttons
  30. self.realize()
  31. if self.window:
  32. self.window.set_decorations(gtk.gdk.DECOR_ALL)
  33. ### Start of Overriding Section ###
  34. def get_title(self, reponame):
  35. return reponame + ' - hg ' + self.command
  36. def get_icon(self):
  37. return 'hg.ico'
  38. def get_defsize(self):
  39. return self.defsize
  40. def get_setting_name(self):
  41. return 'quickop'
  42. def get_body(self, vbox):
  43. os.chdir(self.repo.root)
  44. # wrap box
  45. wrapbox = gtk.VBox()
  46. wrapbox.set_border_width(5)
  47. vbox.pack_start(wrapbox, True, True)
  48. self.wrapbox = wrapbox
  49. lbl = gtk.Label(LABELS[self.command][0])
  50. lbl.set_alignment(0, 0)
  51. wrapbox.pack_start(lbl, False, False)
  52. def keypressed(tree, event):
  53. 'Make spacebar toggle selected rows'
  54. if event.keyval != 32:
  55. return False
  56. def toggler(model, path, bufiter):
  57. model[path][0] = not model[path][0]
  58. selection = tree.get_selection()
  59. selection.selected_foreach(toggler)
  60. return True
  61. # add file list treeview
  62. fm = gtk.ListStore(bool, # Checked
  63. str, # Path
  64. str, # Path-UTF8
  65. str) # Status
  66. self.filetree = gtk.TreeView(fm)
  67. self.filetree.connect('key-press-event', keypressed)
  68. self.filetree.set_headers_clickable(True)
  69. self.filetree.set_reorderable(False)
  70. if hasattr(self.filetree, 'set_rubber_banding'):
  71. self.filetree.set_rubber_banding(True)
  72. fontlist = hglib.getfontconfig()['fontlist']
  73. self.filetree.modify_font(pango.FontDescription(fontlist))
  74. def select_toggle(cell, path):
  75. fm[path][0] = not fm[path][0]
  76. # file selection checkboxes
  77. toggle_cell = gtk.CellRendererToggle()
  78. toggle_cell.connect('toggled', select_toggle)
  79. toggle_cell.set_property('activatable', True)
  80. col = gtk.TreeViewColumn('', toggle_cell, active=0)
  81. col.set_resizable(False)
  82. self.filetree.append_column(col)
  83. col = gtk.TreeViewColumn(_('status'), gtk.CellRendererText(), text=3)
  84. self.filetree.append_column(col)
  85. col = gtk.TreeViewColumn(_('path'), gtk.CellRendererText(), text=2)
  86. self.filetree.append_column(col)
  87. scroller = gtk.ScrolledWindow()
  88. scroller.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
  89. scroller.set_shadow_type(gtk.SHADOW_ETCHED_IN)
  90. scroller.add(self.filetree)
  91. wrapbox.pack_start(scroller, True, True, 6)
  92. def toggleall(button):
  93. for row in self.filetree.get_model():
  94. row[0] = not row[0]
  95. # extra box
  96. self.extrabox = hbox = gtk.HBox()
  97. wrapbox.pack_start(hbox, False, False)
  98. ## toggle button
  99. tb = gtk.Button(_('Toggle all selections'))
  100. tb.connect('pressed', toggleall)
  101. hbox.pack_start(tb, False, False)
  102. if self.command == 'revert':
  103. ## no backup checkbox
  104. chk = gtk.CheckButton(_('Do not save backup files (*.orig)'))
  105. hbox.pack_start(chk, False, False, 6)
  106. self.nobackup = chk
  107. ## padding
  108. hbox.pack_start(gtk.Label())
  109. types = { 'add' : 'I?',
  110. 'forget' : 'MAR!C',
  111. 'revert' : 'MAR!',
  112. 'remove' : 'MAR!CI?',
  113. }
  114. filetypes = types[self.command]
  115. try:
  116. matcher = cmdutil.match(self.repo, self.pats)
  117. status = self.repo.status(match=matcher,
  118. clean='C' in filetypes,
  119. ignored='I' in filetypes,
  120. unknown='?' in filetypes)
  121. except (EnvironmentError, util.Abort), e:
  122. gdialog.Prompt(_('Unable to determine repository status'),
  123. str(e), self).run()
  124. self.earlyout=True
  125. self.hide()
  126. return
  127. (modified, added, removed, deleted, unknown, ignored, clean) = status
  128. if 'M' in filetypes:
  129. for f in modified:
  130. fm.append([True, f, hglib.toutf(f), _('modified')])
  131. if 'A' in filetypes:
  132. for f in added:
  133. fm.append([True, f, hglib.toutf(f), _('added')])
  134. if 'R' in filetypes:
  135. for f in removed:
  136. fm.append([True, f, hglib.toutf(f), _('removed')])
  137. if '!' in filetypes:
  138. for f in deleted:
  139. fm.append([True, f, hglib.toutf(f), _('missing')])
  140. if '?' in filetypes:
  141. for f in unknown:
  142. fm.append([True, f, hglib.toutf(f), _('unknown')])
  143. if 'I' in filetypes:
  144. for f in ignored:
  145. if self.command == 'remove' or f in self.pats:
  146. fm.append([True, f, hglib.toutf(f), _('ignored')])
  147. if 'C' in filetypes:
  148. for f in clean:
  149. if self.command == 'remove' or f in self.pats:
  150. fm.append([True, f, hglib.toutf(f), _('clean')])
  151. if not len(fm):
  152. gdialog.Prompt(_('No appropriate files'),
  153. _('No files found for this operation'), self).run()
  154. self.earlyout=True
  155. self.hide()
  156. def get_buttons(self):
  157. return [('go', LABELS[self.command][1], gtk.RESPONSE_OK),
  158. ('cancel', gtk.STOCK_CANCEL, gtk.RESPONSE_CLOSE)]
  159. def get_default_button(self):
  160. return 'go'
  161. def get_action_map(self):
  162. return {gtk.RESPONSE_OK: self.operation}
  163. def switch_to(self, normal, working, cmd):
  164. self.wrapbox.set_sensitive(normal)
  165. self.buttons['go'].set_property('visible', normal)
  166. self.buttons['cancel'].set_property('visible', normal)
  167. if normal:
  168. self.buttons['cancel'].grab_focus()
  169. def command_done(self, returncode, useraborted, list):
  170. if returncode == 0:
  171. shlib.shell_notify(list)
  172. self.cmd.set_result(_('Successfully'), style='ok')
  173. elif useraborted:
  174. self.cmd.set_result(_('Canceled'), style='error')
  175. else:
  176. self.cmd.set_result(_('Failed'), style='error')
  177. def before_show(self):
  178. # restore dialog state
  179. if self.defmax:
  180. self.maximize()
  181. # restore dialog position
  182. screen = self.get_screen()
  183. w, h = screen.get_width(), screen.get_height()
  184. x, y = self.defpos
  185. if x >= 0 and x < w and y >= 0 and y < h:
  186. self.move(x, y)
  187. def load_settings(self):
  188. self.defsize = self.settings.get_value('size', DEFAULT_SIZE)
  189. self.defpos = self.settings.get_value('pos', DEFAULT_POS)
  190. self.defmax = self.settings.get_value('maximize', False)
  191. def store_settings(self):
  192. state = self.window.get_state()
  193. ismaximized = bool(state & gtk.gdk.WINDOW_STATE_MAXIMIZED)
  194. if ismaximized or state & gtk.gdk.WINDOW_STATE_ICONIFIED:
  195. self.settings.set_value('size', DEFAULT_SIZE)
  196. self.settings.set_value('pos', DEFAULT_POS)
  197. else:
  198. rect = self.get_allocation()
  199. self.settings.set_value('size', (rect.width, rect.height))
  200. self.settings.set_value('pos', self.get_position())
  201. self.settings.set_value('maximize', ismaximized)
  202. self.settings.write()
  203. ### End of Overriding Section ###
  204. def operation(self):
  205. fm = self.filetree.get_model()
  206. deleting = self.command == 'remove'
  207. list, dellist = [], []
  208. for row in fm:
  209. if not row[0]: continue
  210. if deleting and row[3] in (_('unknown'), _('ignored')):
  211. dellist.append(row[1])
  212. else:
  213. list.append(row[1])
  214. if not (list or dellist):
  215. gdialog.Prompt(_('No files selected'),
  216. _('No operation to perform'), self).run()
  217. return
  218. for file in dellist:
  219. try:
  220. os.unlink(file)
  221. except EnvironmentError:
  222. pass
  223. if not list:
  224. gtklib.idle_add_single_call(self.response, gtk.RESPONSE_CLOSE)
  225. return
  226. # prepare command line
  227. cmdline = ['hg', self.command, '--verbose']
  228. if hasattr(self, 'nobackup') and self.nobackup.get_active():
  229. cmdline.append('--no-backup')
  230. cmdline.append('--')
  231. cmdline += list
  232. # execute command
  233. self.execute_command(cmdline, list)
  234. def run(ui, *pats, **opts):
  235. pats = hglib.canonpaths(pats)
  236. if opts.get('canonpats'):
  237. pats = list(pats) + opts['canonpats']
  238. return QuickOpDialog(opts.get('alias'), pats)