5 from environment import env
6 from categorydialog import CategoryDialog
7 from menuhandler import *
8 from kofoto.search import *
9 from kofoto.shelf import *
13 ######################################################################
16 def __init__(self, mainWindow):
17 self.__mainWindow = mainWindow
18 self.__toggleColumn = None
19 self.__objectCollection = None
20 self.__ignoreSelectEvent = False
21 self.__selectedCategoriesIds = {}
22 self.__categoryModel = gtk.TreeStore(gobject.TYPE_INT, # CATEGORY_ID
23 gobject.TYPE_STRING, # DESCRIPTION
24 gobject.TYPE_BOOLEAN, # CONNECTED
25 gobject.TYPE_BOOLEAN) # INCONSISTENT
30 self.__categoryView = env.widgets["categoryView"]
31 self.__categoryView.realize()
32 self.__categoryView.set_model(self.__categoryModel)
33 self.__categoryView.connect("focus-in-event", self._categoryViewFocusInEvent)
34 self.__categoryView.connect("focus-out-event", self._categoryViewFocusOutEvent)
36 # Create toggle column
37 toggleRenderer = gtk.CellRendererToggle()
38 toggleRenderer.connect("toggled", self._connectionToggled)
39 self.__toggleColumn = gtk.TreeViewColumn("",
41 active=self.__COLUMN_CONNECTED,
42 inconsistent=self.__COLUMN_INCONSISTENT)
43 self.__categoryView.append_column(self.__toggleColumn)
46 textRenderer = gtk.CellRendererText()
47 textColumn = gtk.TreeViewColumn("Category", textRenderer, text=self.__COLUMN_DESCRIPTION)
48 self.__categoryView.append_column(textColumn)
49 self.__categoryView.set_expander_column(textColumn)
52 # Category quick select view
54 self.__categoryQSModel = gtk.ListStore(
55 gobject.TYPE_INT, # CATEGORY_ID
56 gobject.TYPE_STRING, # DESCRIPTION
57 gobject.TYPE_BOOLEAN, # CONNECTED
58 gobject.TYPE_BOOLEAN) # INCONSISTENT
59 self.__categoryQSView = env.widgets["categoryQuickSelectView"]
60 self.__categoryQSView.connect(
61 "focus-in-event", self._categoryQSViewFocusInEvent)
62 self.__categoryQSEntry = env.widgets["categoryQuickSelectEntry"]
63 self.__categoryQSEntry.connect(
64 "activate", self._categoryQSEntryActivateEvent)
65 self.__categoryQSEntry.connect(
66 "changed", self._categoryQSEntryChangedEvent)
67 self.__categoryQSButton = env.widgets["categoryQuickSelectButton"]
68 self.__categoryQSButton.connect(
69 "clicked", self._categoryQSEntryActivateEvent)
70 self.__categoryQSView.realize()
71 self.__categoryQSView.set_model(self.__categoryQSModel)
72 self.__categoryQSFreeze = False
74 # Create toggle column
75 toggleRenderer = gtk.CellRendererToggle()
76 toggleRenderer.connect("toggled", self._qsConnectionToggled)
77 self.__toggleQSColumn = gtk.TreeViewColumn(
80 active=self.__COLUMN_CONNECTED,
81 inconsistent=self.__COLUMN_INCONSISTENT)
82 self.__qsToggleColumn = gtk.TreeViewColumn(
85 active=self.__COLUMN_CONNECTED,
86 inconsistent=self.__COLUMN_INCONSISTENT)
87 self.__categoryQSView.append_column(self.__qsToggleColumn)
90 textRenderer = gtk.CellRendererText()
91 textColumn = gtk.TreeViewColumn(
92 "Category", textRenderer, text=self.__COLUMN_DESCRIPTION)
93 self.__categoryQSView.append_column(textColumn)
94 self.__categoryQSView.set_expander_column(textColumn)
97 # TODO Is it possible to load a menu from a glade file instead?
98 # If not, create some helper functions to construct the menu...
99 self._contextMenu = gtk.Menu()
101 self._contextMenuGroup = MenuGroup()
102 self._contextMenuGroup.addStockImageMenuItem(
103 self.__cutCategoryLabel,
106 self._contextMenuGroup.addStockImageMenuItem(
107 self.__copyCategoryLabel,
110 self._contextMenuGroup.addStockImageMenuItem(
111 self.__pasteCategoryLabel,
114 self._contextMenuGroup.addStockImageMenuItem(
115 self.__destroyCategoryLabel,
117 self._deleteCategories)
118 self._contextMenuGroup.addMenuItem(
119 self.__disconnectCategoryLabel,
120 self._disconnectCategory)
121 self._contextMenuGroup.addMenuItem(
122 self.__createChildCategoryLabel,
123 self._createChildCategory)
124 self._contextMenuGroup.addMenuItem(
125 self.__createRootCategoryLabel,
126 self._createRootCategory)
127 self._contextMenuGroup.addStockImageMenuItem(
128 self.__propertiesLabel,
129 gtk.STOCK_PROPERTIES,
130 self._editProperties)
132 for item in self._contextMenuGroup:
133 self._contextMenu.append(item)
135 env.widgets["categorySearchButton"].set_sensitive(False)
137 # Init menubar items.
138 env.widgets["menubarDisconnectFromParent"].connect(
139 "activate", self._disconnectCategory, None)
140 env.widgets["menubarCreateChild"].connect(
141 "activate", self._createChildCategory, None)
142 env.widgets["menubarCreateRoot"].connect(
143 "activate", self._createRootCategory, None)
145 # Init selection functions
146 categorySelection = self.__categoryView.get_selection()
147 categorySelection.set_mode(gtk.SELECTION_MULTIPLE)
148 categorySelection.set_select_function(self._selectionFunction, None)
149 categorySelection.connect("changed", self._categorySelectionChanged)
150 categoryQSSelection = self.__categoryQSView.get_selection()
151 categoryQSSelection.set_mode(gtk.SELECTION_NONE)
153 # Connect the rest of the UI events
154 self.__categoryView.connect("button_press_event", self._button_pressed)
155 self.__categoryView.connect("button_release_event", self._button_released)
156 self.__categoryView.connect("row-activated", self._rowActivated)
157 env.widgets["categorySearchButton"].connect('clicked', self._executeQuery)
159 self.loadCategoryTree()
162 def loadCategoryTree(self):
163 self.__categoryModel.clear()
164 self.__categoryList = []
165 env.shelf.flushCategoryCache()
166 for category in self.__sortCategories(env.shelf.getRootCategories()):
167 self.__loadCategorySubTree(None, category)
168 if self.__objectCollection is not None:
169 self.objectSelectionChanged()
171 def setCollection(self, objectCollection):
172 if self.__objectCollection is not None:
173 self.__objectCollection.getObjectSelection().removeChangedCallback(self.objectSelectionChanged)
174 self.__objectCollection = objectCollection
175 self.__objectCollection.getObjectSelection().addChangedCallback(self.objectSelectionChanged)
176 self.objectSelectionChanged()
178 def objectSelectionChanged(self, objectSelection=None):
179 self.__updateToggleColumn()
180 self.__updateQSToggleColumn()
181 self.__updateContextMenu()
182 self.__expandAndCollapseRows(env.widgets["autoExpand"].get_active(),
183 env.widgets["autoCollapse"].get_active())
186 ###############################################################################
187 ### Callback functions registered by this class but invoked from other classes.
189 def _executeQuery(self, *foo):
190 query = self.__buildQueryFromSelection()
192 self.__mainWindow.loadQuery(query)
194 def _categoryViewFocusInEvent(self, widget, event):
195 self._menubarOids = []
196 for widgetName, function in [
197 ("menubarCut", lambda *x: self._cutCategory(None, None)),
198 ("menubarCopy", lambda *x: self._copyCategory(None, None)),
199 ("menubarPaste", lambda *x: self._pasteCategory(None, None)),
200 ("menubarDestroy", lambda *x: self._deleteCategories(None, None)),
201 ("menubarClear", lambda *x: widget.get_selection().unselect_all()),
202 ("menubarSelectAll", lambda *x: widget.get_selection().select_all()),
203 ("menubarProperties", lambda *x: self._editProperties(None, None)),
205 w = env.widgets[widgetName]
206 oid = w.connect("activate", function)
207 self._menubarOids.append((w, oid))
208 self.__updateContextMenu()
210 def _categoryViewFocusOutEvent(self, widget, event):
211 for (widget, oid) in self._menubarOids:
212 widget.disconnect(oid)
214 def _categorySelectionChanged(self, selection):
215 selectedCategoryRows = []
216 selection = self.__categoryView.get_selection()
217 # TODO replace with "get_selected_rows()" when it is introduced in Pygtk 2.2 API
218 selection.selected_foreach(lambda model,
221 selectedCategoryRows.append(model[path]))
222 self.__selectedCategoriesIds = {}
224 for categoryRow in selectedCategoryRows:
225 cid = categoryRow[self.__COLUMN_CATEGORY_ID]
226 # row.parent method gives assertion failed, dont know why. Using workaround instead.
227 parentPath = categoryRow.path[:-1]
229 parentId = categoryRow.model[parentPath][self.__COLUMN_CATEGORY_ID]
233 self.__selectedCategoriesIds[cid].append(parentId)
235 self.__selectedCategoriesIds[cid] = [parentId]
236 self.__updateContextMenu()
237 env.widgets["categorySearchButton"].set_sensitive(
238 len(selectedCategoryRows) > 0)
240 def _connectionToggled(self, renderer, path):
241 categoryRow = self.__categoryModel[path]
242 category = env.shelf.getCategory(categoryRow[self.__COLUMN_CATEGORY_ID])
243 if categoryRow[self.__COLUMN_INCONSISTENT] \
244 or not categoryRow[self.__COLUMN_CONNECTED]:
245 for obj in self.__objectCollection.getObjectSelection().getSelectedObjects():
247 obj.addCategory(category)
248 except CategoryPresentError:
249 # The object was already connected to the category
251 categoryRow[self.__COLUMN_INCONSISTENT] = False
252 categoryRow[self.__COLUMN_CONNECTED] = True
254 for obj in self.__objectCollection.getObjectSelection().getSelectedObjects():
255 obj.removeCategory(category)
256 categoryRow[self.__COLUMN_CONNECTED] = False
257 categoryRow[self.__COLUMN_INCONSISTENT] = False
258 self.__updateToggleColumn()
259 self.__updateQSToggleColumn()
261 def _button_pressed(self, treeView, event):
262 if event.button == 3:
263 self._contextMenu.popup(None,None,None,event.button,event.time)
265 rec = self.__categoryView.get_cell_area(0, self.__toggleColumn)
266 if event.x <= (rec.x + rec.width):
267 # Ignore selection event since the user clicked on the toggleColumn.
268 self.__ignoreSelectEvent = True
271 def _button_released(self, treeView, event):
272 self.__ignoreSelectEvent = False
275 def _rowActivated(self, a, b, c):
276 # TODO What should happen if the user dubble-click on a category?
279 def _copyCategory(self, item, data):
280 cc = ClipboardCategories()
282 cc.categories = self.__selectedCategoriesIds
283 env.clipboard.setCategories(cc)
285 def _cutCategory(self, item, data):
286 cc = ClipboardCategories()
288 cc.categories = self.__selectedCategoriesIds
289 env.clipboard.setCategories(cc)
291 def _pasteCategory(self, item, data):
292 assert env.clipboard.hasCategories()
293 clipboardCategories = env.clipboard[0]
294 env.clipboard.clear()
296 for (categoryId, previousParentIds) in clipboardCategories.categories.items():
297 for newParentId in self.__selectedCategoriesIds:
298 if clipboardCategories.type == ClipboardCategories.COPY:
299 self.__connectChildToCategory(categoryId, newParentId)
300 for parentId in previousParentIds:
302 self.__disconnectChildHelper(categoryId, None,
303 None, self.__categoryModel)
305 if newParentId in previousParentIds:
306 previousParentIds.remove(newParentId)
308 self.__connectChildToCategory(categoryId, newParentId)
309 for parentId in previousParentIds:
311 self.__disconnectChildHelper(categoryId, None,
312 None, self.__categoryModel)
314 self.__disconnectChild(categoryId, parentId)
315 except CategoryLoopError:
316 dialog = gtk.MessageDialog(
317 type=gtk.MESSAGE_ERROR,
318 buttons=gtk.BUTTONS_OK,
319 message_format="Category loop detected.")
322 self.__updateToggleColumn()
323 self.__expandAndCollapseRows(False, False)
325 def _createRootCategory(self, item, data):
326 dialog = CategoryDialog("Create top-level category")
327 dialog.run(self._createRootCategoryHelper)
329 def _createRootCategoryHelper(self, tag, desc):
330 category = env.shelf.createCategory(tag, desc)
331 self.__loadCategorySubTree(None, category)
333 def _createChildCategory(self, item, data):
334 dialog = CategoryDialog("Create subcategory")
335 dialog.run(self._createChildCategoryHelper)
337 def _createChildCategoryHelper(self, tag, desc):
338 newCategory = env.shelf.createCategory(tag, desc)
339 for selectedCategoryId in self.__selectedCategoriesIds:
340 self.__connectChildToCategory(newCategory.getId(), selectedCategoryId)
341 self.__expandAndCollapseRows(False, False)
343 def _deleteCategories(self, item, data):
344 dialogId = "destroyCategoriesDialog"
345 widgets = gtk.glade.XML(env.gladeFile, dialogId)
346 dialog = widgets.get_widget(dialogId)
347 result = dialog.run()
348 if result == gtk.RESPONSE_OK:
349 for categoryId in self.__selectedCategoriesIds:
350 category = env.shelf.getCategory(categoryId)
351 for child in list(category.getChildren()):
352 # The backend automatically disconnects childs
353 # when a category is deleted, but we do it ourself
354 # to make sure that the treeview widget is
356 self.__disconnectChild(child.getId(), categoryId)
357 env.shelf.deleteCategory(categoryId)
358 env.shelf.flushCategoryCache()
359 self.__forEachCategoryRow(
360 self.__deleteCategoriesHelper, categoryId)
363 def __deleteCategoriesHelper(self, categoryRow, categoryIdToDelete):
364 if categoryRow[self.__COLUMN_CATEGORY_ID] == categoryIdToDelete:
365 self.__categoryModel.remove(categoryRow.iter)
367 def _disconnectCategory(self, item, data):
368 for (categoryId, parentIds) in self.__selectedCategoriesIds.items():
369 for parentId in parentIds:
370 if not parentId == None: # Not possible to disconnect root categories
371 self.__disconnectChild(categoryId, parentId)
373 def _editProperties(self, item, data):
374 for categoryId in self.__selectedCategoriesIds:
375 dialog = CategoryDialog("Change properties", categoryId)
376 dialog.run(self._editPropertiesHelper, data=categoryId)
378 def _editPropertiesHelper(self, tag, desc, categoryId):
379 category = env.shelf.getCategory(categoryId)
381 category.setDescription(desc)
382 env.shelf.flushCategoryCache()
383 self.__forEachCategoryRow(self.__updatePropertiesFromShelf, categoryId)
385 def _selectionFunction(self, path, b):
386 return not self.__ignoreSelectEvent
388 def _categoryQSViewFocusInEvent(self, widget, event):
389 self.__categoryQSEntry.grab_focus()
391 def _categoryQSEntryActivateEvent(self, entry):
392 if not self.__qsSelectedPath:
394 self._qsConnectionToggled(None, self.__qsSelectedPath)
395 self.__categoryQSFreeze = True
396 self.__categoryQSEntry.set_text("")
397 self.__categoryQSFreeze = False
398 self.__qsSelectedPath = None
400 def _categoryQSEntryChangedEvent(self, entry):
401 if self.__categoryQSFreeze:
403 self.__categoryQSModel.clear()
404 self.__qsSelectedPath = None
405 self.__categoryQSButton.set_sensitive(False)
406 text = entry.get_text().decode("utf-8")
410 regexp = re.compile(".*%s.*" % re.escape(text.lower()))
411 categories = list(env.shelf.getMatchingCategories(regexp))
412 categories.sort(self.__compareCategories)
414 for category in categories:
415 iterator = self.__categoryQSModel.append()
416 if (category.getTag().lower() == text.lower() or
417 category.getDescription().lower() == text.lower()):
418 exactMatches.append(self.__categoryQSModel.get_path(iterator))
419 self.__categoryQSModel.set_value(
420 iterator, self.__COLUMN_CATEGORY_ID, category.getId())
421 self.__categoryQSModel.set_value(
423 self.__COLUMN_DESCRIPTION,
424 "%s [%s]" % (category.getDescription(), category.getTag()))
425 if len(categories) == 1:
426 self.__qsSelectedPath = (0,)
427 self.__categoryQSButton.set_sensitive(True)
428 elif len(exactMatches) == 1:
429 self.__qsSelectedPath = exactMatches[0]
430 self.__categoryQSButton.set_sensitive(True)
431 self.__updateQSToggleColumn()
433 def _qsConnectionToggled(self, renderer, path):
434 categoryRow = self.__categoryQSModel[path]
435 category = env.shelf.getCategory(
436 categoryRow[self.__COLUMN_CATEGORY_ID])
437 if categoryRow[self.__COLUMN_INCONSISTENT] \
438 or not categoryRow[self.__COLUMN_CONNECTED]:
439 for obj in self.__objectCollection.getObjectSelection().getSelectedObjects():
441 obj.addCategory(category)
442 except CategoryPresentError:
443 # The object was already connected to the category
445 categoryRow[self.__COLUMN_INCONSISTENT] = False
446 categoryRow[self.__COLUMN_CONNECTED] = True
448 for obj in self.__objectCollection.getObjectSelection().getSelectedObjects():
449 obj.removeCategory(category)
450 categoryRow[self.__COLUMN_CONNECTED] = False
451 categoryRow[self.__COLUMN_INCONSISTENT] = False
452 self.__updateToggleColumn()
453 self.__expandAndCollapseRows(
454 env.widgets["autoExpand"].get_active(),
455 env.widgets["autoCollapse"].get_active())
457 ######################################################################
460 __cutCategoryLabel = "Cut"
461 __copyCategoryLabel = "Copy"
462 __pasteCategoryLabel = "Paste as child(ren)"
463 __destroyCategoryLabel = "Destroy..."
464 __disconnectCategoryLabel = "Disconnect from parent"
465 __createChildCategoryLabel = "Create subcategory..."
466 __createRootCategoryLabel = "Create top-level category..."
467 __propertiesLabel = "Properties"
469 __COLUMN_CATEGORY_ID = 0
470 __COLUMN_DESCRIPTION = 1
471 __COLUMN_CONNECTED = 2
472 __COLUMN_INCONSISTENT = 3
474 def __loadCategorySubTree(self, parent, category):
475 # TODO Do we have to use iterators here or can we use pygtks simplified syntax?
476 iterator = self.__categoryModel.iter_children(parent)
477 while (iterator != None and
478 self.__categoryModel.get_value(iterator, self.__COLUMN_DESCRIPTION) <
479 category.getDescription()):
480 iterator = self.__categoryModel.iter_next(iterator)
481 iterator = self.__categoryModel.insert_before(parent, iterator)
482 self.__categoryModel.set_value(iterator, self.__COLUMN_CATEGORY_ID, category.getId())
483 self.__categoryModel.set_value(iterator, self.__COLUMN_DESCRIPTION, category.getDescription())
484 self.__categoryModel.set_value(iterator, self.__COLUMN_CONNECTED, False)
485 self.__categoryModel.set_value(iterator, self.__COLUMN_INCONSISTENT, False)
486 for child in self.__sortCategories(category.getChildren()):
487 self.__loadCategorySubTree(iterator, child)
489 def __buildQueryFromSelection(self):
490 if env.widgets["categoriesOr"].get_active():
494 return operator.join([env.shelf.getCategory(x).getTag()
495 for x in self.__selectedCategoriesIds])
497 def __updateContextMenu(self):
498 # TODO Create helper functions to use from this method
499 menubarWidgetNames = [
505 "menubarDisconnectFromParent",
506 "menubarCreateChild",
509 if len(self.__selectedCategoriesIds) == 0:
510 self._contextMenuGroup.disable()
511 for widgetName in menubarWidgetNames:
512 env.widgets[widgetName].set_sensitive(False)
513 self._contextMenuGroup[
514 self.__createRootCategoryLabel].set_sensitive(True)
515 env.widgets["menubarCreateRoot"].set_sensitive(True)
517 self._contextMenuGroup.enable()
518 for widgetName in menubarWidgetNames:
519 env.widgets[widgetName].set_sensitive(True)
520 if not env.clipboard.hasCategories():
521 self._contextMenuGroup[
522 self.__pasteCategoryLabel].set_sensitive(False)
523 env.widgets["menubarPaste"].set_sensitive(False)
524 propertiesItem = self._contextMenuGroup[self.__propertiesLabel]
525 propertiesItemSensitive = len(self.__selectedCategoriesIds) == 1
526 propertiesItem.set_sensitive(propertiesItemSensitive)
527 env.widgets["menubarProperties"].set_sensitive(propertiesItemSensitive)
529 def __updateToggleColumn(self):
530 # find out which categories are connected, not connected or
531 # partitionally connected to selected objects
532 nrSelectedObjectsInCategory = {}
533 nrSelectedObjects = 0
534 for obj in self.__objectCollection.getObjectSelection().getSelectedObjects():
535 nrSelectedObjects += 1
536 for category in obj.getCategories():
537 categoryId = category.getId()
539 nrSelectedObjectsInCategory[categoryId] += 1
541 nrSelectedObjectsInCategory[categoryId] = 1
542 self.__forEachCategoryRow(self.__updateToggleColumnHelper,
543 (nrSelectedObjects, nrSelectedObjectsInCategory))
545 def __updateQSToggleColumn(self):
547 self.__objectCollection.getObjectSelection().getSelectedObjects()
548 nrSelectedObjectsInCategory = {}
549 nrSelectedObjects = 0
550 for obj in selectedObjects:
551 nrSelectedObjects += 1
552 for category in obj.getCategories():
553 catid = category.getId()
554 nrSelectedObjectsInCategory.setdefault(catid, 0)
555 nrSelectedObjectsInCategory[catid] += 1
556 self.__forEachCategoryRow(
557 self.__updateToggleColumnHelper,
558 (nrSelectedObjects, nrSelectedObjectsInCategory),
559 self.__categoryQSModel)
561 def __updateToggleColumnHelper(self,
563 (nrSelectedObjects, nrSelectedObjectsInCategory)):
564 categoryId = categoryRow[self.__COLUMN_CATEGORY_ID]
565 if categoryId in nrSelectedObjectsInCategory:
566 if nrSelectedObjectsInCategory[categoryId] < nrSelectedObjects:
567 # Some of the selected objects are connected to the category
568 categoryRow[self.__COLUMN_CONNECTED] = False
569 categoryRow[self.__COLUMN_INCONSISTENT] = True
571 # All of the selected objects are connected to the category
572 categoryRow[self.__COLUMN_CONNECTED] = True
573 categoryRow[self.__COLUMN_INCONSISTENT] = False
575 # None of the selected objects are connected to the category
576 categoryRow[self.__COLUMN_CONNECTED] = False
577 categoryRow[self.__COLUMN_INCONSISTENT] = False
579 def __forEachCategoryRow(self, function, data=None, categoryRows=None):
580 # We can't use gtk.TreeModel.foreach() since it does not pass a row
581 # to the callback function.
583 categoryRows=self.__categoryModel
584 for categoryRow in categoryRows:
585 function(categoryRow, data)
586 self.__forEachCategoryRow(function, data, categoryRow.iterchildren())
588 def __expandAndCollapseRows(self, autoExpand, autoCollapse, categoryRows=None):
589 if categoryRows is None:
590 categoryRows=self.__categoryModel
591 someRowsExpanded = False
592 for categoryRow in categoryRows:
593 expandThisRow = False
594 # Expand all rows that are selected or has expanded childs
595 childRowsExpanded = self.__expandAndCollapseRows(autoExpand,
597 categoryRow.iterchildren())
598 if (childRowsExpanded
599 or self.__categoryView.get_selection().path_is_selected(categoryRow.path)):
601 # Auto expand all rows that has a checked toggle
603 if (categoryRow[self.__COLUMN_CONNECTED]
604 or categoryRow[self.__COLUMN_INCONSISTENT]):
607 for a in range(len(categoryRow.path)):
608 self.__categoryView.expand_row(categoryRow.path[:a+1], False)
609 someRowsExpanded = True
612 self.__categoryView.collapse_row(categoryRow.path)
613 return someRowsExpanded
615 def __connectChildToCategory(self, childId, parentId):
618 childCategory = env.shelf.getCategory(childId)
619 parentCategory = env.shelf.getCategory(parentId)
620 parentCategory.connectChild(childCategory)
621 env.shelf.flushCategoryCache()
622 # Update widget modell
623 # If we reload the whole category tree from the shelf, we would lose
624 # the widgets information about current selected categories,
625 # expanded categories and the widget's scroll position. Hence,
626 # we update our previously loaded model instead.
627 self.__connectChildToCategoryHelper(parentId,
629 self.__categoryModel)
630 except CategoriesAlreadyConnectedError:
634 def __connectChildToCategoryHelper(self, parentId, childCategory, categoryRows):
635 for categoryRow in categoryRows:
636 if categoryRow[self.__COLUMN_CATEGORY_ID] == parentId:
637 self.__loadCategorySubTree(categoryRow.iter, childCategory)
639 self.__connectChildToCategoryHelper(parentId, childCategory, categoryRow.iterchildren())
641 def __disconnectChild(self, childId, parentId):
643 childCategory = env.shelf.getCategory(childId)
644 parentCategory = env.shelf.getCategory(parentId)
645 if childCategory in env.shelf.getRootCategories():
646 alreadyWasRootCategory = True
648 alreadyWasRootCategory = False
649 parentCategory.disconnectChild(childCategory)
650 env.shelf.flushCategoryCache()
651 # Update widget modell.
652 # If we reload the whole category tree from the shelf, we would lose
653 # the widgets information about current selected categories,
654 # expanded categories and the widget's scroll position. Hence,
655 # we update our previously loaded model instead.
656 self.__disconnectChildHelper(childId,
659 self.__categoryModel)
660 if not alreadyWasRootCategory:
661 for c in env.shelf.getRootCategories():
662 if c.getId() == childCategory.getId():
663 self.__loadCategorySubTree(None, childCategory)
666 def __disconnectChildHelper(self, wantedChildId, wantedParentId,
667 parentId, categoryRows):
668 for categoryRow in categoryRows:
669 cid = categoryRow[self.__COLUMN_CATEGORY_ID]
670 if cid == wantedChildId and parentId == wantedParentId:
671 self.__categoryModel.remove(categoryRow.iter)
672 self.__disconnectChildHelper(wantedChildId, wantedParentId, cid, categoryRow.iterchildren())
674 def __updatePropertiesFromShelf(self, categoryRow, categoryId):
675 if categoryRow[self.__COLUMN_CATEGORY_ID] == categoryId:
676 category = env.shelf.getCategory(categoryId)
677 categoryRow[self.__COLUMN_DESCRIPTION] = category.getDescription()
679 def __sortCategories(self, categoryIter):
680 categories = list(categoryIter)
681 categories.sort(self.__compareCategories)
684 def __compareCategories(self, x, y):
686 (x.getDescription(), x.getTag()),
687 (y.getDescription(), y.getTag()))
689 class ClipboardCategories: