Merge lp:~hid-iwata/tortoisebzr/cmenu_customize into lp:~tortoisebzr-developers/tortoisebzr/trunk-2a

Proposed by IWATA Hidetaka
Status: Superseded
Proposed branch: lp:~hid-iwata/tortoisebzr/cmenu_customize
Merge into: lp:~tortoisebzr-developers/tortoisebzr/trunk-2a
Diff against target: 1316 lines (+873/-225)
9 files modified
tbzrcommands/cmd_settings.py (+10/-154)
tbzrcommands/contextmenuprefs.py (+499/-0)
tbzrcommands/prefwidgets.py (+159/-0)
tbzrcommands/ui/settings.ui (+6/-0)
tbzrlib/actions.py (+142/-50)
tbzrlib/app.py (+8/-0)
tbzrlib/config.py (+28/-0)
tbzrlib/dispatcher.py (+19/-18)
tbzrlib/ui/taskbar.py (+2/-3)
To merge this branch: bzr merge lp:~hid-iwata/tortoisebzr/cmenu_customize
Reviewer Review Type Date Requested Status
TortoiseBZR Developers Pending
Review via email: mp+42715@code.launchpad.net

This proposal has been superseded by a proposal from 2010-12-09.

Description of the change

Context menu customizing feature
---------------------------------
Make it possible to reorder context menus or hide unused menus as users like.
Related bug : #591774

Drive type filter and path filter for context menu
-----------------------------------------------------------
As same as icon overlays, make it possible to define folders on which tbzr avoids showing context menus, with drive type filters and path wildcards.

.. note::
Some of menus (e.g. ``settings``) are always shown regardless of these settings.

A little improvement for context menu filtering
------------------------------------------------
Hide obviously useless menus (e.g. ``annotate`` when no file selected) even when status of files/folders are unavailable.

To post a comment you must log in.
Revision history for this message
Jelmer Vernooij (jelmer) wrote :

Hi!

On Sat, 2010-12-04 at 03:59 +0000, iwata wrote:
> iwata has proposed merging lp:~hid-iwata/tortoisebzr/cmenu_customize into lp:tortoisebzr.
>
> Requested reviews:
> TortoiseBZR Developers (tortoisebzr-developers)
>
>
> Context menu customizing feature
> ---------------------------------
> Make it possible to reorder context menus or hide unused menus as users like.
> Related bug : #591774
>
> Drive type filter and path filter for context menu
> -----------------------------------------------------------
> As same as icon overlays, make it possible to define folders on which tbzr avoids showing context menus, with drive type filters and path wildcards.
>
> .. note::
> Some of menus (e.g. ``settings``) are always shown regardless of these settings.
>
> A little improvement for context menu filtering
> ------------------------------------------------
> Hide obviously useless menus (e.g. ``annotate`` when no file selected) even when status of files/folders are unavailable.
Thanks for the patch, this seems like a useful addition. Is there any
chance you can split this branch up a bit ? That might make it a bit
easier for reviewer(s) to review and merge it.

Cheers,

Jelmer

Revision history for this message
IWATA Hidetaka (hid-iwata) wrote :

Hi, Jelmer.
Thank you for your advice.

I've split this changes into these 3 new branches.

lp:~hid-iwata/tortoisebzr/cmenu_customize2 for "Context menu customizing".
lp:~hid-iwata/tortoisebzr/pathfilter_for_cmenu for "Drive type & path filter"
lp:~hid-iwata/tortoisebzr/cmenu_filter_improve for "Improvement for menu filtering"

1st one is ready to be reviewed.

2nd & 3rd are not ready (I've not reviewed them by myself yet).
I'll make other merge-proposal for 2nd & 3rd in a few days (maybe this weekend).

Cheers.

> Hi!
>
> On Sat, 2010-12-04 at 03:59 +0000, iwata wrote:
> > iwata has proposed merging lp:~hid-iwata/tortoisebzr/cmenu_customize into
> lp:tortoisebzr.
> >
> > Requested reviews:
> > TortoiseBZR Developers (tortoisebzr-developers)
> >
> >
> > Context menu customizing feature
> > ---------------------------------
> > Make it possible to reorder context menus or hide unused menus as users
> like.
> > Related bug : #591774
> >
> > Drive type filter and path filter for context menu
> > -----------------------------------------------------------
> > As same as icon overlays, make it possible to define folders on which tbzr
> avoids showing context menus, with drive type filters and path wildcards.
> >
> > .. note::
> > Some of menus (e.g. ``settings``) are always shown regardless of these
> settings.
> >
> > A little improvement for context menu filtering
> > ------------------------------------------------
> > Hide obviously useless menus (e.g. ``annotate`` when no file selected) even
> when status of files/folders are unavailable.
> Thanks for the patch, this seems like a useful addition. Is there any
> chance you can split this branch up a bit ? That might make it a bit
> easier for reviewer(s) to review and merge it.
>
> Cheers,
>
> Jelmer

Unmerged revisions

366. By IWATA Hidetaka

trivial fix.

365. By IWATA Hidetaka

Add drive type filter, whitelist and blacklist for context menu.

364. By IWATA Hidetaka

Enable contextmenu filtering by selected count, by item type(file or folder), even when sleeping, in progress, or unoverlayed.

363. By IWATA Hidetaka

context menu preferences : disable collapsing treeview.

362. By IWATA Hidetaka

Change button text etc.

361. By IWATA Hidetaka

Imprements contextmenu customize feature

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'tbzrcommands/cmd_settings.py'
2--- tbzrcommands/cmd_settings.py 2010-10-13 14:20:40 +0000
3+++ tbzrcommands/cmd_settings.py 2010-12-04 03:59:48 +0000
4@@ -6,161 +6,13 @@
5 )
6
7 from tbzrcommands.ui.settings import Ui_SettingsForm
8+from tbzrcommands.prefwidgets import *
9 from tbzrlib.i18n import (gettext, N_)
10 from tbzrlib.app import get_app
11
12-class PrefItem:
13- def __init__(self, pref_name):
14- self.pref_name = pref_name
15- self.config = get_app().config
16- self.pref_ob = self.config.get_opt(pref_name)
17-
18- # Add widget to specified QGridLayout. QGridLayout has 2 columns.
19- # 1st is for label, 2nd is for input box.
20- def add_to(self, grid, rowno):
21- pass
22-
23- # Get new value from widget.
24- def get_value(self):
25- pass
26-
27- def apply(self):
28- self.config.set(self.pref_name, self.get_value())
29-
30-class CheckBoxPref(PrefItem):
31-
32- def add_to(self, parent, grid, rowno):
33- cb = QtGui.QCheckBox(parent)
34-
35- val = self.config.get(self.pref_name)
36- if val:
37- cb.setCheckState(QtCore.Qt.Checked)
38- else:
39- cb.setCheckState(QtCore.Qt.Unchecked)
40-
41- cb.setText(gettext(self.pref_ob.doc))
42- help = self.pref_ob.help_text
43- if help:
44- cb.setToolTip(gettext(help))
45-
46- grid.addWidget(cb, rowno, 0, 1, -1)
47-
48- self.cb = cb
49-
50- def get_value(self):
51- return (self.cb.checkState() == QtCore.Qt.Checked)
52-
53-class TextPref(PrefItem):
54-
55- def __init__(self, pref_name, multiline = False, height = 48):
56- PrefItem.__init__(self, pref_name)
57- self.multiline = multiline
58- self.height = height
59-
60- def add_to(self, parent, grid, rowno):
61- if self.multiline:
62- edt = QtGui.QTextEdit(parent)
63- edt.setLineWrapMode(QtGui.QTextEdit.NoWrap)
64- edt.setAcceptRichText(False)
65- edt.setMaximumHeight(self.height)
66- edt.setTabChangesFocus(True)
67- else:
68- edt = QtGui.QLineEdit(parent)
69-
70- lbl = QtGui.QLabel(parent)
71- val = self.config.get(self.pref_name)
72- if val:
73- edt.setText(val)
74- lbl.setText(gettext(self.pref_ob.doc))
75-
76- help = self.pref_ob.help_text
77- if help:
78- edt.setToolTip(gettext(help))
79- lbl.setToolTip(gettext(help))
80-
81- grid.addWidget(lbl, rowno, 0, 1, 1)
82- grid.addWidget(edt, rowno, 1, 1, 1)
83-
84- self.edt = edt
85-
86- def get_value(self):
87- if self.multiline:
88- return self.edt.toPlainText()
89- else:
90- return self.edt.text()
91-
92-# Generic 'panel' tied to the tree widget.
93-class PrefPanel(QtGui.QWidget):
94- def __init__(self, parent):
95- QtGui.QWidget.__init__(self, parent)
96- self.centralwidget = QtGui.QVBoxLayout(self)
97- self.setup()
98-
99- def addGroupBoxAndLayout(self, text, klass = QtGui.QVBoxLayout):
100- gb = QtGui.QGroupBox(gettext(text), self)
101- self.centralwidget.addWidget(gb)
102- layout = klass(gb)
103- return layout
104-
105- def setup(self):
106- pass
107-
108- def apply(self):
109- pass
110-
111-# A panel that knows about our config object.
112-class ConfigPanel(PrefPanel):
113- # 'connect' a bool preference with a checkbox. Keep the 'connection' with
114- # the checkbox separate from the creation of the checkbox, incase we use
115- # QtDesigner based forms.
116-
117- def __init__(self, parent):
118- self.apply_handlers = []
119- PrefPanel.__init__(self, parent)
120-
121- def apply(self):
122- for handler in self.apply_handlers:
123- handler()
124-
125- # XXX Obsolete
126- def connect_checkbox_to_pref(self, cb, pref_name):
127- config = get_app().config
128- pref_ob = config.get_opt(pref_name)
129- val = config.get(pref_name)
130- if val:
131- cb.setCheckState(QtCore.Qt.Checked)
132- else:
133- cb.setCheckState(QtCore.Qt.Unchecked)
134-
135- cb.setText(gettext(pref_ob.doc))
136- help = pref_ob.help_text
137- if help:
138- cb.setToolTip(gettext(help))
139- # the event handler.
140- def cb_changed(state, cb=cb, pref_name=pref_name, config=config):
141- new_val = state == QtCore.Qt.Checked
142- config.set(pref_name, new_val)
143-
144- self.parentWidget().connect(cb, QtCore.SIGNAL("stateChanged(int)"),
145- cb_changed)
146-
147- # XXX Obsolete
148- # Create a groupbox filled with boolean checkboxes.
149- def create_groupbox_of_bool_prefs(self, gb_title, *pref_names):
150- gb = self.addGroupBoxAndLayout(gb_title)
151- for name in pref_names:
152- cb = QtGui.QCheckBox(self)
153- self.connect_checkbox_to_pref(cb, name)
154- gb.addWidget(cb)
155- return gb
156-
157- def create_groupbox_of_prefs(self, gb_title, *pref_items):
158- gb = self.addGroupBoxAndLayout(gb_title, QtGui.QGridLayout)
159- gb.setColumnStretch(0, 0)
160- for idx, item in enumerate(pref_items):
161- gb.setRowStretch(idx, 0)
162- item.add_to(self, gb, idx)
163- self.apply_handlers.append(item.apply)
164+from tbzrcommands.contextmenuprefs import (
165+ ContextMenuGeneralPrefPanel,
166+ ContextMenuPrefPanel)
167
168 # Implementation classes for specific options...
169 class GeneralPrefPanel(ConfigPanel):
170@@ -180,7 +32,7 @@
171
172 # Add the button to the form - must put it in a VBox to prevent it
173 # stretching to the full width of the window.
174- layout = QtGui.QHBoxLayout(self)
175+ layout = QtGui.QHBoxLayout()
176 layout.addWidget(button)
177 layout.addStretch(1)
178
179@@ -216,7 +68,11 @@
180 (N_("Other Options"), PrefPanel, []),
181 ]
182 ),
183- (N_("Icon Overlays"), OverlaysPrefPanel, [],)
184+ (N_("Icon Overlays"), OverlaysPrefPanel, [],),
185+ (N_("Context Menu"), ContextMenuGeneralPrefPanel, [
186+ (N_("Order of Menus"), ContextMenuPrefPanel, [],),
187+ ],
188+ ),
189 ]
190
191 # The main, top-level settings window. Holds the tree and the 'stacked'
192
193=== added file 'tbzrcommands/contextmenuprefs.py'
194--- tbzrcommands/contextmenuprefs.py 1970-01-01 00:00:00 +0000
195+++ tbzrcommands/contextmenuprefs.py 2010-12-04 03:59:48 +0000
196@@ -0,0 +1,499 @@
197+import sys
198+from PyQt4 import QtCore, QtGui
199+
200+if sys.getwindowsversion() >= (5,1):
201+ import winxpgui as win32gui
202+else:
203+ import win32gui
204+import win32con
205+
206+from tbzrcommands.prefwidgets import ConfigPanel, CheckBoxPref, TextPref
207+from tbzrlib.i18n import (gettext, N_)
208+from tbzrlib.app import get_app
209+
210+from tbzrlib.actions import get_default_actions, separate_actions
211+from tbzrlib.ui.taskbar import load_icon
212+
213+_blank_icon = QtGui.QColor(255, 255, 255, 0)
214+
215+class MenuData(object):
216+ def __init__(self, id, text, ico_file):
217+ self.id = id
218+ self.text = gettext(text).replace('&', '')
219+ self.ico = _blank_icon
220+ if ico_file:
221+ hico = load_icon(ico_file, cx = 16, cy = 16)
222+ try:
223+ if hico:
224+ self.ico = QtGui.QPixmap.fromWinHICON(hico)
225+ finally:
226+ win32gui.DestroyIcon(hico)
227+
228+ self.is_active = False
229+ self.in_folder = False
230+
231+ @classmethod
232+ def from_action(cls, action):
233+ return cls(action.id, action.text, action.ico_file)
234+
235+ @classmethod
236+ def separator(cls):
237+ # separator instance must be created new instance for each call.
238+ return cls('--', N_('--Separator--'), None)
239+
240+ @classmethod
241+ def folder(cls):
242+ return cls('__FOLDER__', N_('Tortoise Bazaar'), 'bzr.ico')
243+
244+
245+# Implementation classes for specific options...
246+
247+class ContextMenuGeneralPrefPanel(ConfigPanel):
248+ def setup(self):
249+ self.create_groupbox_of_prefs(N_("Drive Types"),
250+ CheckBoxPref('queried_drives_cmenu.removable'),
251+ CheckBoxPref('queried_drives_cmenu.floppy'),
252+ CheckBoxPref('queried_drives_cmenu.cdrom'),
253+ CheckBoxPref('queried_drives_cmenu.fixed'),
254+ CheckBoxPref('queried_drives_cmenu.remote'),
255+ CheckBoxPref('queried_drives_cmenu.unknown'))
256+
257+ self.create_groupbox_of_prefs(N_("Whitelist / Blacklist"),
258+ TextPref('context_menu.include_path', multiline = True),
259+ TextPref('context_menu.exclude_path', multiline = True))
260+
261+ self.centralwidget.addStretch(1)
262+
263+class ContextMenuPrefPanel(ConfigPanel):
264+ def setup(self):
265+ self.config = get_app().config
266+ self.layout()
267+ self.set_model()
268+
269+ def layout(self):
270+ layout = self.addGroupBoxAndLayout(N_("Order of Menus"), QtGui.QGridLayout)
271+ layout.setMargin(10)
272+ for i, x in enumerate((1, 0)):
273+ layout.setRowStretch(i, x)
274+ for i, x in enumerate((1, 0, 1)):
275+ layout.setColumnStretch(i, x)
276+
277+ self.left_view= QtGui.QTreeView()
278+ layout.addWidget(self.left_view, 0, 0)
279+
280+ button_panel = QtGui.QVBoxLayout()
281+ layout.addLayout(button_panel, 0, 1)
282+
283+ add_button = QtGui.QPushButton('>>')
284+ remove_button = QtGui.QPushButton('<<')
285+ up_button = QtGui.QPushButton(gettext('Up'))
286+ down_button = QtGui.QPushButton(gettext('Down'))
287+
288+ button_panel.addStretch(3)
289+ for b in (add_button, remove_button, up_button, down_button):
290+ b.setMaximumSize(50, b.height())
291+ button_panel.addWidget(b)
292+ button_panel.insertStretch(3, 1)
293+ button_panel.addStretch(3)
294+
295+ self.right_view= QtGui.QTreeView()
296+ layout.addWidget(self.right_view, 0, 2)
297+
298+ reset_button = QtGui.QPushButton(gettext("Reset"))
299+ layout.addWidget(reset_button, 1, 2, QtCore.Qt.AlignRight)
300+
301+ self.connect(add_button, QtCore.SIGNAL("clicked()"), self.add_clicked)
302+ self.connect(remove_button, QtCore.SIGNAL("clicked()"), self.remove_clicked)
303+ self.connect(up_button, QtCore.SIGNAL("clicked()"), self.up_clicked)
304+ self.connect(down_button, QtCore.SIGNAL("clicked()"), self.down_clicked)
305+ self.connect(reset_button, QtCore.SIGNAL("clicked()"), self.reset_clicked)
306+
307+ # disable collapsing
308+ def right_collapsed(index):
309+ self.right_view.expand(index)
310+ self.connect(self.right_view, QtCore.SIGNAL("collapsed(QModelIndex)"), right_collapsed)
311+
312+ def set_model(self):
313+ default_actions = get_default_actions()
314+ all_menus = [MenuData.from_action(a) for a in default_actions if a]
315+ menu_dict = {}
316+ for m in all_menus:
317+ menu_dict[m.id] = m
318+
319+ actions, sub_actions = separate_actions(default_actions)
320+ def get_id(action):
321+ return action.id if action else '--'
322+
323+ def get_menu(id):
324+ if id == '--':
325+ return MenuData.separator()
326+ else:
327+ return menu_dict[id]
328+
329+ default_menu_ids = [get_id(a) for a in actions]
330+ default_submenu_ids = [get_id(a) for a in sub_actions]
331+ self.default_menus = [get_menu(x) for x in default_menu_ids]
332+ self.default_submenus = [get_menu(x) for x in default_submenu_ids]
333+
334+ left_model = UnusedMenuFilter(
335+ AllMenusModel(all_menus, self.left_view))
336+ self.left_view.setModel(left_model)
337+ self.left_view.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection)
338+
339+ menu_ids = self.config.get("context_menu.actions").split(",")
340+ submenu_ids = self.config.get("context_menu.sub_actions").split(",")
341+ if not menu_ids[0] and not submenu_ids[0]:
342+ menu_ids = list(default_menu_ids)
343+ submenu_ids = list(default_submenu_ids)
344+
345+ if menu_ids[0] or submenu_ids[0]:
346+ try:
347+ menus = [get_menu(x) for x in menu_ids if x]
348+ submenus = [get_menu(x) for x in submenu_ids if x]
349+ except:
350+ menus = self.default_menus
351+ submenus = self.default_submenus
352+ else:
353+ menus = self.default_menus
354+ submenus = self.default_submenus
355+
356+ right_model = ContextMenuModel(menus, submenus, self.right_view)
357+ self.right_view.setModel(right_model)
358+ self.right_view.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection)
359+ self.right_view.expand(right_model._create_folder_index())
360+
361+ left_model.invalidateFilter()
362+
363+ def add_clicked(self):
364+ indices = self.left_view.selectedIndexes()
365+ if not indices:
366+ return
367+
368+ left_model, right_model = self.left_view.model(), self.right_view.model()
369+
370+ actions = [left_model.mapToSource(x).internalPointer() for x in indices]
371+
372+ indices = self.right_view.selectedIndexes()
373+ if len(indices) > 0:
374+ insert_index = right_model.sort_indices(indices, reverse=True)[0]
375+ else:
376+ insert_index = None
377+
378+ right_model.add(actions, insert_index)
379+ left_model.invalidateFilter()
380+
381+ def remove_clicked(self):
382+ indices = self.right_view.selectedIndexes()
383+ if len(indices) > 0:
384+ self.right_view.model().remove(indices)
385+ self.left_view.model().invalidateFilter()
386+
387+
388+ def up_clicked(self):
389+ indices = self.right_view.selectedIndexes()
390+ self.right_view.model().up(indices)
391+
392+ def down_clicked(self):
393+ indices = self.right_view.selectedIndexes()
394+ self.right_view.model().down(indices)
395+
396+ def reset_clicked(self):
397+ left_model = self.left_view.model()
398+ right_model = self.right_view.model()
399+ right_model.beginResetModel()
400+ right_model.reset_menus(
401+ self.default_menus, self.default_submenus)
402+ right_model.endResetModel()
403+ self.right_view.expand(right_model._create_folder_index())
404+ left_model.invalidateFilter()
405+
406+ def apply(self):
407+ # XXX: Should we show alert when `settings` menu will be hidden ?
408+ menu_ids = ",".join(m.id for m in self.right_view.model().menus)
409+ submenu_ids = ",".join(m.id for m in self.right_view.model().submenus)
410+
411+ default_menu_ids = ",".join(m.id for m in self.default_menus)
412+ default_submenu_ids = ",".join(m.id for m in self.default_submenus)
413+
414+ if menu_ids == default_menu_ids and submenu_ids == default_submenu_ids:
415+ self.config.set("context_menu.actions", "")
416+ self.config.set("context_menu.sub_actions", "")
417+ else:
418+ self.config.set("context_menu.actions", menu_ids)
419+ self.config.set("context_menu.sub_actions", submenu_ids)
420+
421+class UnusedMenuFilter(QtGui.QSortFilterProxyModel):
422+ def __init__(self, source, parent = None):
423+ QtGui.QSortFilterProxyModel.__init__(self, parent)
424+ self.setSourceModel(source)
425+
426+ def filterAcceptsRow(self, row, parent):
427+ return not self.sourceModel().menus[row].is_active
428+
429+class AllMenusModel(QtCore.QAbstractItemModel):
430+ def __init__(self, menus, parent = None):
431+ QtCore.QAbstractItemModel.__init__(self, parent)
432+ self.menus = list(menus)
433+ self.menus.append(MenuData.separator())
434+
435+ def rowCount(self, parent=QtCore.QModelIndex()):
436+ if parent.column() > 0:
437+ return 0
438+ if parent.isValid():
439+ return 0
440+ else:
441+ return len(self.menus)
442+
443+ def columnCount(self, parent=QtCore.QModelIndex()):
444+ return 1
445+
446+ def flags(self, index):
447+ if index.isValid():
448+ if index.internalPointer().is_active:
449+ return QtCore.Qt.NoItemFlags
450+ else:
451+ return QtCore.Qt.ItemIsEnabled|QtCore.Qt.ItemIsSelectable
452+ else:
453+ return QtCore.Qt.NoItemFlags
454+
455+ def headerData(self, section, orientation, role):
456+ if orientation == QtCore.Qt.Horizontal and role == QtCore.Qt.DisplayRole:
457+ if section == 0:
458+ return QtCore.QVariant(gettext("Available Commands"))
459+ return None
460+
461+ def index(self, row, column, parent):
462+ if not self.hasIndex(row, column, parent):
463+ return QtCore.QModelIndex()
464+ if row < len(self.menus):
465+ return self.createIndex(row, column, self.menus[row])
466+ else:
467+ return QtCore.QModelIndex()
468+
469+ def parent(self, index):
470+ return QtCore.QModelIndex()
471+
472+ def data(self, index, role):
473+ if not index.isValid():
474+ return QtCore.QVariant()
475+ if role == QtCore.Qt.DisplayRole:
476+ return index.internalPointer().text
477+ elif role == QtCore.Qt.DecorationRole:
478+ return index.internalPointer().ico
479+ return QtCore.QVariant()
480+
481+class ContextMenuModel(QtCore.QAbstractItemModel):
482+ def __init__(self, menus, submenus, parent = None):
483+ QtCore.QAbstractItemModel.__init__(self, parent)
484+ self.menus = []
485+ self.submenus = []
486+ self.reset_menus(menus, submenus)
487+ self.folder = MenuData.folder()
488+
489+ def rowCount(self, parent=QtCore.QModelIndex()):
490+ if parent.column() > 0:
491+ return 0
492+ if parent.isValid():
493+ if parent.internalPointer() == self.folder:
494+ return len(self.submenus)
495+ else:
496+ return 0
497+ else:
498+ return len(self.menus) + 1
499+
500+ def columnCount(self, parent=QtCore.QModelIndex()):
501+ return 1
502+
503+ def flags(self, index):
504+ if index.isValid():
505+ if index.internalPointer() == self.folder:
506+ return QtCore.Qt.ItemIsEnabled
507+ else:
508+ return QtCore.Qt.ItemIsEnabled|QtCore.Qt.ItemIsSelectable
509+ else:
510+ return QtCore.Qt.NoItemFlags
511+
512+ def headerData(self, section, orientation, role):
513+ if orientation == QtCore.Qt.Horizontal and role == QtCore.Qt.DisplayRole:
514+ if section == 0:
515+ return QtCore.QVariant(gettext("Context Menu"))
516+ return None
517+
518+ def index(self, row, column, parent):
519+ if not self.hasIndex(row, column, parent):
520+ return QtCore.QModelIndex()
521+ if parent.isValid():
522+ if row < len(self.submenus):
523+ return self.createIndex(row, column, self.submenus[row])
524+ else:
525+ if row < len(self.menus):
526+ return self.createIndex(row, column, self.menus[row])
527+ else:
528+ return self.createIndex(row, column, self.folder)
529+ return QtCore.QModelIndex()
530+
531+ def parent(self, index):
532+ if not index.isValid():
533+ return QtCore.QModelIndex()
534+ item = index.internalPointer()
535+ if item.in_folder:
536+ return self._create_folder_index()
537+ else:
538+ return QtCore.QModelIndex()
539+
540+ def data(self, index, role):
541+ if not index.isValid():
542+ return QtCore.QVariant()
543+ if role == QtCore.Qt.DisplayRole:
544+ return index.internalPointer().text
545+ elif role == QtCore.Qt.DecorationRole:
546+ return index.internalPointer().ico
547+ return QtCore.QVariant()
548+
549+ def reset_menus(self, menus, submenus):
550+ def set_flag(menus, is_active, in_folder):
551+ for m in menus:
552+ m.is_active = is_active
553+ m.in_folder = in_folder
554+
555+ set_flag(self.menus, False, False)
556+ set_flag(self.submenus, False, False)
557+
558+ self.menus[:] = menus
559+ self.submenus[:] = submenus
560+
561+ set_flag(self.menus, True, False)
562+ set_flag(self.submenus, True, True)
563+
564+ def sort_indices(self, indices, reverse = False, split = False):
565+ temp = sorted(((x.parent().isValid(), x.row(), x) for x in indices),
566+ reverse=reverse)
567+ sorted_indexes = [x[2] for x in temp]
568+ if split:
569+ return [x[2] for x in temp if not x[0]], \
570+ [x[2] for x in temp if x[0]]
571+ else:
572+ return [x[2] for x in temp]
573+
574+ def add(self, actions, insert_index):
575+ if insert_index is None:
576+ parent = QtCore.QModelIndex()
577+ row = -1
578+ in_folder = False
579+ else:
580+ parent = insert_index.parent()
581+ row = insert_index.row()
582+ in_folder = parent.isValid()
583+
584+ for action in actions:
585+ if action.is_active:
586+ continue
587+ if action.id == '--':
588+ action = MenuData.separator()
589+
590+ action.in_folder = in_folder
591+ action.is_active = True
592+
593+ row += 1
594+ self.beginInsertRows(parent, row, row)
595+ if in_folder:
596+ self.submenus.insert(row, action)
597+ else:
598+ self.menus.insert(row, action)
599+ self.endInsertRows()
600+
601+ def remove(self, indices):
602+ for index in self.sort_indices(indices, reverse=True):
603+ row = index.row()
604+ self.beginRemoveRows(index.parent(), row, row)
605+ action = index.internalPointer()
606+ if action.in_folder:
607+ del(self.submenus[row])
608+ else:
609+ del(self.menus[row])
610+ action.in_folder = False
611+ action.is_active = False
612+ self.endRemoveRows()
613+
614+ def _up(self, menus, row):
615+ if menus[row].in_folder:
616+ parent = self._create_folder_index()
617+ else:
618+ parent = QtCore.QModelIndex()
619+ self.beginMoveRows(parent, row, row, parent, row - 1)
620+ menus[row], menus[row - 1] = menus[row - 1], menus[row]
621+ self.endMoveRows()
622+
623+ def _down(self, menus, row):
624+ self._up(menus, row + 1)
625+
626+ def _move_to_folder(self, num):
627+ folder_idx = self._create_folder_index()
628+ end = len(self.menus) - 1
629+ start = end - num + 1
630+ self.beginMoveRows(QtCore.QModelIndex(), start, end, folder_idx, 0)
631+ while num > 0:
632+ action = self.menus.pop(-1)
633+ action.in_folder = True
634+ self.submenus.insert(0, action)
635+ num -= 1
636+ self.endMoveRows()
637+
638+ def _move_from_folder(self, num):
639+ folder_idx = self._create_folder_index()
640+ self.beginMoveRows(folder_idx, 0, num - 1, QtCore.QModelIndex(), folder_idx.row())
641+ while num > 0:
642+ action = self.submenus.pop(0)
643+ action.in_folder = False
644+ self.menus.append(action)
645+ num -= 1
646+ self.endMoveRows()
647+
648+ def up(self, indices):
649+ indices, sub_indices =self.sort_indices(indices, split=True)
650+
651+ def move_in_same_level(indices, menus):
652+ minimum_row = 0
653+ for index in indices:
654+ action = index.internalPointer()
655+ row = index.row()
656+ if row > minimum_row:
657+ self._up(menus, row)
658+ minimum_row = row
659+ else:
660+ minimum_row = row + 1
661+
662+ move_in_same_level(indices, self.menus)
663+
664+ if sub_indices:
665+ if sub_indices[-1].row() == len(sub_indices) - 1:
666+ self._move_from_folder(len(sub_indices))
667+ else:
668+ move_in_same_level(sub_indices, self.submenus)
669+
670+ def down(self, indices):
671+ def move_in_same_level(indices, menus):
672+ maximum_row = len(menus) - 1
673+ for index in indices:
674+ action = index.internalPointer()
675+ row = index.row()
676+ if row < maximum_row:
677+ self._down(menus, row)
678+ maxmum_row = row
679+ else:
680+ maximum_row = row - 1
681+
682+ indices, sub_indices = self.sort_indices(indices, reverse=True, split=True)
683+ move_in_same_level(sub_indices, self.submenus)
684+
685+ if (indices):
686+ if indices[-1].row() == len(self.menus) - len(indices):
687+ self._move_to_folder(len(indices))
688+ else:
689+ move_in_same_level(indices, self.menus)
690+
691+ def _create_folder_index(self):
692+ return self.createIndex(len(self.menus), 0, self.folder)
693+
694+
695+
696
697=== added file 'tbzrcommands/prefwidgets.py'
698--- tbzrcommands/prefwidgets.py 1970-01-01 00:00:00 +0000
699+++ tbzrcommands/prefwidgets.py 2010-12-04 03:59:48 +0000
700@@ -0,0 +1,159 @@
701+import sys
702+from PyQt4 import QtCore, QtGui
703+
704+from tbzrlib.i18n import (gettext, N_)
705+from tbzrlib.app import get_app
706+
707+class PrefItem:
708+ def __init__(self, pref_name):
709+ self.pref_name = pref_name
710+ self.config = get_app().config
711+ self.pref_ob = self.config.get_opt(pref_name)
712+
713+ # Add widget to specified QGridLayout. QGridLayout has 2 columns.
714+ # 1st is for label, 2nd is for input box.
715+ def add_to(self, grid, rowno):
716+ pass
717+
718+ # Get new value from widget.
719+ def get_value(self):
720+ pass
721+
722+ def apply(self):
723+ self.config.set(self.pref_name, self.get_value())
724+
725+class CheckBoxPref(PrefItem):
726+
727+ def add_to(self, parent, grid, rowno):
728+ cb = QtGui.QCheckBox(parent)
729+
730+ val = self.config.get(self.pref_name)
731+ if val:
732+ cb.setCheckState(QtCore.Qt.Checked)
733+ else:
734+ cb.setCheckState(QtCore.Qt.Unchecked)
735+
736+ cb.setText(gettext(self.pref_ob.doc))
737+ help = self.pref_ob.help_text
738+ if help:
739+ cb.setToolTip(gettext(help))
740+
741+ grid.addWidget(cb, rowno, 0, 1, -1)
742+
743+ self.cb = cb
744+
745+ def get_value(self):
746+ return (self.cb.checkState() == QtCore.Qt.Checked)
747+
748+class TextPref(PrefItem):
749+
750+ def __init__(self, pref_name, multiline = False, height = 48):
751+ PrefItem.__init__(self, pref_name)
752+ self.multiline = multiline
753+ self.height = height
754+
755+ def add_to(self, parent, grid, rowno):
756+ if self.multiline:
757+ edt = QtGui.QTextEdit(parent)
758+ edt.setLineWrapMode(QtGui.QTextEdit.NoWrap)
759+ edt.setAcceptRichText(False)
760+ edt.setMaximumHeight(self.height)
761+ edt.setTabChangesFocus(True)
762+ else:
763+ edt = QtGui.QLineEdit(parent)
764+
765+ lbl = QtGui.QLabel(parent)
766+ val = self.config.get(self.pref_name)
767+ if val:
768+ edt.setText(val)
769+ lbl.setText(gettext(self.pref_ob.doc))
770+
771+ help = self.pref_ob.help_text
772+ if help:
773+ edt.setToolTip(gettext(help))
774+ lbl.setToolTip(gettext(help))
775+
776+ grid.addWidget(lbl, rowno, 0, 1, 1)
777+ grid.addWidget(edt, rowno, 1, 1, 1)
778+
779+ self.edt = edt
780+
781+ def get_value(self):
782+ if self.multiline:
783+ return self.edt.toPlainText()
784+ else:
785+ return self.edt.text()
786+
787+# Generic 'panel' tied to the tree widget.
788+class PrefPanel(QtGui.QWidget):
789+ def __init__(self, parent):
790+ QtGui.QWidget.__init__(self, parent)
791+ self.centralwidget = QtGui.QVBoxLayout(self)
792+ self.setup()
793+
794+ def addGroupBoxAndLayout(self, text, klass = QtGui.QVBoxLayout):
795+ gb = QtGui.QGroupBox(gettext(text), self)
796+ self.centralwidget.addWidget(gb)
797+ layout = klass(gb)
798+ return layout
799+
800+ def setup(self):
801+ pass
802+
803+ def apply(self):
804+ pass
805+
806+# A panel that knows about our config object.
807+class ConfigPanel(PrefPanel):
808+ # 'connect' a bool preference with a checkbox. Keep the 'connection' with
809+ # the checkbox separate from the creation of the checkbox, incase we use
810+ # QtDesigner based forms.
811+
812+ def __init__(self, parent):
813+ self.apply_handlers = []
814+ PrefPanel.__init__(self, parent)
815+
816+ def apply(self):
817+ for handler in self.apply_handlers:
818+ handler()
819+
820+ # XXX Obsolete
821+ def connect_checkbox_to_pref(self, cb, pref_name):
822+ config = get_app().config
823+ pref_ob = config.get_opt(pref_name)
824+ val = config.get(pref_name)
825+ if val:
826+ cb.setCheckState(QtCore.Qt.Checked)
827+ else:
828+ cb.setCheckState(QtCore.Qt.Unchecked)
829+
830+ cb.setText(gettext(pref_ob.doc))
831+ help = pref_ob.help_text
832+ if help:
833+ cb.setToolTip(gettext(help))
834+ # the event handler.
835+ def cb_changed(state, cb=cb, pref_name=pref_name, config=config):
836+ new_val = state == QtCore.Qt.Checked
837+ config.set(pref_name, new_val)
838+
839+ self.parentWidget().connect(cb, QtCore.SIGNAL("stateChanged(int)"),
840+ cb_changed)
841+
842+ # XXX Obsolete
843+ # Create a groupbox filled with boolean checkboxes.
844+ def create_groupbox_of_bool_prefs(self, gb_title, *pref_names):
845+ gb = self.addGroupBoxAndLayout(gb_title)
846+ for name in pref_names:
847+ cb = QtGui.QCheckBox(self)
848+ self.connect_checkbox_to_pref(cb, name)
849+ gb.addWidget(cb)
850+ return gb
851+
852+ def create_groupbox_of_prefs(self, gb_title, *pref_items):
853+ gb = self.addGroupBoxAndLayout(gb_title, QtGui.QGridLayout)
854+ gb.setColumnStretch(0, 0)
855+ for idx, item in enumerate(pref_items):
856+ gb.setRowStretch(idx, 0)
857+ item.add_to(self, gb, idx)
858+ self.apply_handlers.append(item.apply)
859+
860
861=== modified file 'tbzrcommands/ui/settings.ui'
862--- tbzrcommands/ui/settings.ui 2008-09-13 03:35:48 +0000
863+++ tbzrcommands/ui/settings.ui 2010-12-04 03:59:48 +0000
864@@ -17,6 +17,12 @@
865 <layout class="QHBoxLayout" name="horizontalLayout" >
866 <item>
867 <widget class="QTreeWidget" name="tree" >
868+ <property name="maximumSize" >
869+ <size>
870+ <width>200</width>
871+ <height>16777215</height>
872+ </size>
873+ </property>
874 <property name="rootIsDecorated" >
875 <bool>true</bool>
876 </property>
877
878=== modified file 'tbzrlib/actions.py'
879--- tbzrlib/actions.py 2010-08-05 18:12:10 +0000
880+++ tbzrlib/actions.py 2010-12-04 03:59:48 +0000
881@@ -28,6 +28,12 @@
882 ITEMIS_EXTENDED = 1<<13
883 ITEMIS_UNVERSIONED = 1<<14
884
885+ITEMIS_UNKNOWN = (
886+ ITEMIS_VERSIONED | ITEMIS_UNCHANGED | ITEMIS_IGNORED |
887+ ITEMIS_ADDED | ITEMIS_DELETED | ITEMIS_CONFLICT |
888+ ITEMIS_UNVERSIONED | ITEMIS_INVERSIONEDFOLDER)
889+
890+ITEMIS_UNKNOWNFOLDER = ITEMIS_UNKNOWN | ITEMIS_FOLDER | ITEMIS_VERSIONEDFOLDER
891
892 # A couple of containers for action definitions and their commands.
893 class ActionExec(object):
894@@ -62,6 +68,10 @@
895 def __repr__(self):
896 return "Action(id=%r)" % self.id
897
898+class TbzrMenu(object):
899+ def __nonzero__(self):
900+ return 0
901+
902 def get_tbzr_exec(command, want_sel=True):
903 # fist workout how to execute tbzrcommand itself.
904 bindir = os.path.dirname(os.path.abspath(sys.executable.decode("mbcs")))
905@@ -87,6 +97,10 @@
906 # default order for actions
907 _action_order = []
908
909+def on_config_change():
910+ del(_action_order[:])
911+get_app().register_notify_config_change(on_config_change)
912+
913 def get_default_actions():
914 return [
915 Action("tbzr.newtree", get_tbzr_exec("getnew"),
916@@ -114,7 +128,7 @@
917 [(ITEMIS_FOLDER|ITEMIS_ONLYONE, 0),
918 ]),
919
920- None, # a sep
921+ TbzrMenu(), # `Tortoise Bazaar` menu (placeholder of sub menus)
922
923 Action("tbzr.browse", get_tbzr_exec("browse"),
924 N_("Bro&wse inventory"),
925@@ -324,38 +338,71 @@
926 ]
927
928 def _build_actions():
929- assert not _actions and not _action_order
930- for a in get_default_actions():
931- if a is not None:
932- _actions[a.id] = a
933- _action_order.append(a) # even Nones get in here!
934+ all_actions = get_default_actions()
935+ if not _actions:
936+ for a in all_actions:
937+ if a:
938+ _actions[a.id] = a
939+
940+ if not _action_order:
941+ config = get_app().config
942+ action_ids = config.get("context_menu.actions")
943+ subaction_ids = config.get("context_menu.sub_actions")
944+
945+ if action_ids or subaction_ids:
946+ def append_action_order(action_ids):
947+ if not action_ids:
948+ return
949+ for id in action_ids.split(","):
950+ if id == "--":
951+ _action_order.append(None)
952+ else:
953+ _action_order.append(_actions[id])
954+ try:
955+ append_action_order(action_ids)
956+ if subaction_ids:
957+ _action_order.append(TbzrMenu())
958+ append_action_order(subaction_ids)
959+ return
960+ except:
961+ pass
962+
963+ for a in all_actions:
964+ _action_order.append(a) # even Nones get in here!
965+
966
967 def get_action(action_id):
968- if not _actions:
969+ if not _actions or not _action_order:
970 _build_actions()
971 try:
972 return _actions[action_id]
973 except KeyError:
974 raise ValueError(repr(action_id))
975
976-def _filtered_actions(flags):
977+def _filtered_actions(maybe_flags, assured_flags):
978+ """
979+ Filter executable actions by flags.
980+
981+ :param maybe_flags: what files maybe. (includes all possibilities)
982+ :param assured_flags: what files surely are.
983+ """
984 ret = []
985 for act in _action_order:
986- if act is None: # a sep
987+ if not act: # a sep or "Tortoise Bazaar" menu
988 result = True
989 elif act.conditions:
990 result = False
991 for yes, no in act.conditions:
992 if yes and no:
993- if yes & flags == yes and (no & ~flags) == no:
994+ if yes & maybe_flags == yes and (no & ~assured_flags) == no:
995 result = True
996 break
997 elif yes:
998- if yes & flags == yes:
999+ if yes & maybe_flags == yes:
1000 result = True
1001 break
1002 elif no:
1003- if (no & ~flags) == no:
1004+ if (no & ~assured_flags) == no:
1005 result = True
1006 break
1007 else:
1008@@ -364,49 +411,93 @@
1009 result = True
1010 if result:
1011 ret.append(act)
1012+
1013+ return ret
1014+
1015+def _remove_duplicate_separator(actions):
1016+ ret = []
1017+ ends_with_action = False
1018+ for act in actions:
1019+ if act:
1020+ ends_with_action = True
1021+ ret.append(act)
1022+ elif ends_with_action or act is not None:
1023+ ends_with_action = False
1024+ ret.append(act)
1025+
1026+ if not ends_with_action:
1027+ del(ret[-1])
1028+
1029 return ret
1030
1031 def _get_flag(filenames):
1032+ """
1033+ This method return tuple (maybe_flags, assured_flags)
1034+ maybe_flags represents what files maybe. (includes all possibilities)
1035+ assured_flags represents what files surely are.
1036+ """
1037 app = get_app()
1038 dir_cache = app.dir_cache
1039-
1040- flags = 0
1041+ sleeping = app.sleeping
1042+
1043+ flags = [0, 0] # [maybe_flags, assured_flags]
1044+
1045+ def set_flag(flag):
1046+ flags[0] |= flag
1047+ flags[1] |= flag
1048+
1049+ def set_unknown(path):
1050+ if path.is_directory:
1051+ flags[0] |= ITEMIS_UNKNOWNFOLDER
1052+ flags[1] |= ITEMIS_FOLDER
1053+ else:
1054+ flags[0] |= ITEMIS_UNKNOWN
1055+
1056 if len(filenames)==1:
1057- flags |= ITEMIS_ONLYONE
1058+ set_flag(ITEMIS_ONLYONE)
1059 elif len(filenames)==2:
1060- flags |= ITEMIS_TWO
1061+ set_flag(ITEMIS_TWO)
1062 for raw_path in filenames:
1063 path = VCSPath(raw_path)
1064+ if not app.is_path_allowed_for_cmenu(path):
1065+ return 0, 0
1066+
1067+ if sleeping:
1068+ set_unknown(path)
1069+ continue
1070+
1071 if not app.is_path_allowed(path):
1072- return -1 # show all flags
1073+ set_unknown(path)
1074+ continue
1075
1076 status = dir_cache.get_status_for_path(path, 1)
1077 if status.is_progress():
1078- return -1 # show all flags
1079+ set_unknown(path)
1080+ continue
1081
1082 is_versioned = status.is_versioned()
1083 if not status.is_nothing():
1084- flags |= ITEMIS_INVERSIONEDFOLDER
1085+ set_flag(ITEMIS_INVERSIONEDFOLDER)
1086 if is_versioned:
1087- flags |= ITEMIS_VERSIONED
1088+ set_flag(ITEMIS_VERSIONED)
1089 # must usual case us unchanged - test that first.
1090 if status.is_changed():
1091 if status.is_modified():
1092 pass # hrm - no flag for this??
1093 elif status.is_added():
1094- flags |= ITEMIS_ADDED
1095+ set_flag(ITEMIS_ADDED)
1096 elif status.is_deleted():
1097- flags |= ITEMIS_DELETED
1098+ set_flag(ITEMIS_DELETED)
1099 elif status.is_conflicted():
1100- flags |= ITEMIS_CONFLICT
1101+ set_flag(ITEMIS_CONFLICT)
1102 else:
1103 assert False, status # missed a state!
1104 else:
1105- flags |= ITEMIS_UNCHANGED
1106+ set_flag(ITEMIS_UNCHANGED)
1107 elif status.is_unversioned():
1108- flags |= ITEMIS_UNVERSIONED
1109+ set_flag(ITEMIS_UNVERSIONED)
1110 elif status.is_ignored():
1111- flags |= ITEMIS_IGNORED
1112+ set_flag(ITEMIS_IGNORED)
1113 else:
1114 # not in a versioned folder.
1115 pass
1116@@ -414,44 +505,45 @@
1117 # ack - the below is wrong in some cases? Are we sure its a
1118 # VERSIONEDFOLDER? What about ITEMIS_INVERSIONEDFOLDER?
1119 if path.is_directory:
1120- flags |= ITEMIS_FOLDER
1121- if flags & is_versioned:
1122- flags |= ITEMIS_VERSIONEDFOLDER
1123- return flags
1124+ set_flag(ITEMIS_FOLDER)
1125+ for i in (0, 1):
1126+ if flags[i] & is_versioned:
1127+ flags[i] |= ITEMIS_VERSIONEDFOLDER
1128+ return flags[0], flags[1]
1129
1130 def get_actions_for_files(filenames):
1131- if not _actions:
1132+ if not _actions or not _action_order:
1133 _build_actions()
1134
1135- if get_app().sleeping:
1136- ret = list(_action_order)
1137- else:
1138- # flags that define the specified items
1139- flags = _get_flag(filenames)
1140+ # flags that define the specified items
1141+ maybe_flags, assured_flags = _get_flag(filenames)
1142
1143- # if success to get flag, filter actions by the flag.
1144- if flags < 0:
1145- ret = list(_action_order)
1146- else:
1147- ret = _filtered_actions(flags)
1148+ # if success to get flag, filter actions by the flag.
1149+ ret = _filtered_actions(maybe_flags, assured_flags)
1150+ ret = _remove_duplicate_separator(ret)
1151
1152 # We still need to implement a user-preference for which commands go
1153 # on which menu, but for now we stick with TSVN's defaults, which
1154 # boil down to all items before the first sep go on the top-level.
1155 # We also probably don't need arbitrary depths - so just return "toplevel,
1156 # sub" for now.
1157- actions = [None] # always start with a sep
1158- sub_actions = []
1159- filling = actions
1160- for action in ret:
1161- # if first sep, just swap
1162- if action is None and filling is actions: # first sep!
1163- filling = sub_actions
1164- else:
1165- filling.append(action)
1166+ actions, sub_actions = separate_actions(ret)
1167+ actions.insert(0, None) # always start with a sep
1168 if logger.isEnabledFor(logging.DEBUG):
1169 logger.debug("actions for %s are %s/%s",
1170 filenames,
1171 [a.id for a in actions if a],
1172 [a.id for a in sub_actions if a])
1173 return actions, sub_actions
1174+
1175+def separate_actions(actions):
1176+ toplevel_actions = []
1177+ sub_actions = []
1178+ filling = toplevel_actions
1179+ for a in actions:
1180+ if type(a) is TbzrMenu:
1181+ filling = sub_actions
1182+ else:
1183+ filling.append(a)
1184+ return toplevel_actions, sub_actions
1185+
1186
1187=== modified file 'tbzrlib/app.py'
1188--- tbzrlib/app.py 2010-08-05 18:04:39 +0000
1189+++ tbzrlib/app.py 2010-12-04 03:59:48 +0000
1190@@ -171,6 +171,14 @@
1191 return (self.config.get(config_name) or
1192 path.is_matched(self.config.get("icon_overlays.include_path")))
1193
1194+ def is_path_allowed_for_cmenu(self, path):
1195+ if path.is_matched(self.config.get("context_menu.exclude_path")):
1196+ return False
1197+
1198+ config_name = "queried_drives_cmenu." + path.get_device_type()
1199+ return (self.config.get(config_name) or
1200+ path.is_matched(self.config.get("context_menu.include_path")))
1201+
1202 def initialize(self):
1203 i18n.install()
1204 self.on_activity()
1205
1206=== modified file 'tbzrlib/config.py'
1207--- tbzrlib/config.py 2010-04-26 12:35:10 +0000
1208+++ tbzrlib/config.py 2010-12-04 03:59:48 +0000
1209@@ -99,6 +99,34 @@
1210 BoolOpt(True, N_("Show an icon in the taskbar when tbzrcache is running"),
1211 N_("If set, then an icon will be created in the taskbar, providing a menu for shutting down the application, displaying preferences, etc.")),
1212 },
1213+ 'queried_drives_cmenu' : {
1214+ 'removable' :
1215+ BoolOpt(True, N_("Removable devices")),
1216+ 'floppy' :
1217+ BoolOpt(True, N_("Floppy Drives")),
1218+ 'cdrom' :
1219+ BoolOpt(True, N_("CD and DVD Drives")),
1220+ 'fixed' :
1221+ BoolOpt(True, N_("Fixed disks")),
1222+ 'remote' :
1223+ BoolOpt(True, N_("Network Drives")),
1224+ 'unknown' :
1225+ BoolOpt(False, N_("Unknown Drives")),
1226+ },
1227+ 'context_menu' : {
1228+ 'actions' :
1229+ StringOpt("", N_("Actions")),
1230+ 'sub_actions' :
1231+ StringOpt("", N_("Sub actions")),
1232+ 'include_path' :
1233+ StringOpt("", N_("Include path"),
1234+ N_("Specify folder paths (separated by newline) in which tbzr will show bazaar commands in context menu.\n"
1235+ "You can use wildcard here.(*, ?, [abc], [!abc])")),
1236+ 'exclude_path' :
1237+ StringOpt("", N_("Exclude path"),
1238+ N_("Specify folder paths (separated by newline) in which tbzr will not show bazaar commands in context menu.\n"
1239+ "You can use wildcard here.(*, ?, [abc], [!abc])")),
1240+ },
1241 }
1242
1243 class Config(object):
1244
1245=== modified file 'tbzrlib/dispatcher.py'
1246--- tbzrlib/dispatcher.py 2010-07-16 02:23:30 +0000
1247+++ tbzrlib/dispatcher.py 2010-12-04 03:59:48 +0000
1248@@ -164,25 +164,26 @@
1249 fixup_ico(a.ico_file), commands)
1250 items.append(item)
1251 # and children
1252- depth += 1
1253- last = None
1254- for a in subs:
1255- if a is None:
1256- if last is None:
1257- continue # avoid dupe seps
1258- item = (0, depth, u"", u"", u"", [])
1259- else:
1260- commands = [(c.keystate, c.exec_options, c.cmdline) for c in a.commands]
1261- item = (MENU_FLAG_NORMAL, depth, gettext(a.text), gettext(a.long_text),
1262- fixup_ico(a.ico_file), commands)
1263+ if subs:
1264+ depth += 1
1265+ last = None
1266+ for a in subs:
1267+ if a is None:
1268+ if last is None:
1269+ continue # avoid dupe seps
1270+ item = (0, depth, u"", u"", u"", [])
1271+ else:
1272+ commands = [(c.keystate, c.exec_options, c.cmdline) for c in a.commands]
1273+ item = (MENU_FLAG_NORMAL, depth, gettext(a.text), gettext(a.long_text),
1274+ fixup_ico(a.ico_file), commands)
1275+ items.append(item)
1276+ last = a
1277+ # and finally the "parent" of the children, which goes last
1278+ depth -= 1
1279+ item = (MENU_FLAG_NORMAL, depth, u"&Tortoise Bazaar",
1280+ gettext("More Tortoise Bazaar options"),
1281+ fixup_ico("bzr.ico"), [])
1282 items.append(item)
1283- last = a
1284- # and finally the "parent" of the children, which goes last
1285- depth -= 1
1286- item = (MENU_FLAG_NORMAL, depth, u"&Tortoise Bazaar",
1287- gettext("More Tortoise Bazaar options"),
1288- fixup_ico("bzr.ico"), [])
1289- items.append(item)
1290 return items
1291
1292 class CommandDispatcher:
1293
1294=== modified file 'tbzrlib/ui/taskbar.py'
1295--- tbzrlib/ui/taskbar.py 2010-07-11 12:17:53 +0000
1296+++ tbzrlib/ui/taskbar.py 2010-12-04 03:59:48 +0000
1297@@ -33,9 +33,8 @@
1298
1299 # Loads an icon given a string in one of the formats we support.
1300 # NOTE: you must call win32gui.DestroyIcon on the result if not None
1301-def load_icon(ico_name):
1302+def load_icon(ico_name, ico_flags = win32con.LR_DEFAULTSIZE, cx = 0, cy = 0):
1303 ico = fixup_ico(ico_name)
1304- ico_flags = win32con.LR_DEFAULTSIZE
1305 try:
1306 if '#' in ico:
1307 mod_name, res = ico.split('#', 1)
1308@@ -49,7 +48,7 @@
1309 ico_flags |= win32con.LR_LOADFROMFILE
1310 logger.debug("Loading icon file %r", ico)
1311
1312- hico = win32gui.LoadImage(mod, res, win32con.IMAGE_ICON, 0, 0,
1313+ hico = win32gui.LoadImage(mod, res, win32con.IMAGE_ICON, cx, cy,
1314 ico_flags)
1315 except win32gui.error, exc:
1316 logger.error("Failed to load icon %r: %s", ico, exc)

Subscribers

People subscribed via source and target branches