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 env.shelf.flushCategoryCache()
165 for category in self.__sortCategories(env.shelf.getRootCategories()):
166 self.__loadCategorySubTree(None, category)
167 if self.__objectCollection is not None:
168 self.objectSelectionChanged()
170 def setCollection(self, objectCollection):
171 if self.__objectCollection is not None:
172 self.__objectCollection.getObjectSelection().removeChangedCallback(self.objectSelectionChanged)
173 self.__objectCollection = objectCollection
174 self.__objectCollection.getObjectSelection().addChangedCallback(self.objectSelectionChanged)
175 self.objectSelectionChanged()
177 def objectSelectionChanged(self, objectSelection=None):
178 self.__updateToggleColumn()
179 self.__updateQSToggleColumn()
180 self.__updateContextMenu()
181 self.__expandAndCollapseRows(env.widgets["autoExpand"].get_active(),
182 env.widgets["autoCollapse"].get_active())
185 ###############################################################################
186 ### Callback functions registered by this class but invoked from other classes.
188 def _executeQuery(self, *foo):
189 query = self.__buildQueryFromSelection()
191 self.__mainWindow.loadQuery(query)
193 def _categoryViewFocusInEvent(self, widget, event):
194 self._menubarOids = []
195 for widgetName, function in [
196 ("menubarCut", lambda *x: self._cutCategory(None, None)),
197 ("menubarCopy", lambda *x: self._copyCategory(None, None)),
198 ("menubarPaste", lambda *x: self._pasteCategory(None, None)),
199 ("menubarDestroy", lambda *x: self._deleteCategories(None, None)),
200 ("menubarClear", lambda *x: widget.get_selection().unselect_all()),
201 ("menubarSelectAll", lambda *x: widget.get_selection().select_all()),
202 ("menubarProperties", lambda *x: self._editProperties(None, None)),
204 w = env.widgets[widgetName]
205 oid = w.connect("activate", function)
206 self._menubarOids.append((w, oid))
207 self.__updateContextMenu()
209 def _categoryViewFocusOutEvent(self, widget, event):
210 for (widget, oid) in self._menubarOids:
211 widget.disconnect(oid)
213 def _categorySelectionChanged(self, selection):
214 selectedCategoryRows = []
215 selection = self.__categoryView.get_selection()
216 # TODO replace with "get_selected_rows()" when it is introduced in Pygtk 2.2 API
217 selection.selected_foreach(lambda model,
220 selectedCategoryRows.append(model[path]))
221 self.__selectedCategoriesIds = {}
223 for categoryRow in selectedCategoryRows:
224 cid = categoryRow[self.__COLUMN_CATEGORY_ID]
225 # row.parent method gives assertion failed, dont know why. Using workaround instead.
226 parentPath = categoryRow.path[:-1]
228 parentId = categoryRow.model[parentPath][self.__COLUMN_CATEGORY_ID]
232 self.__selectedCategoriesIds[cid].append(parentId)
234 self.__selectedCategoriesIds[cid] = [parentId]
235 self.__updateContextMenu()
236 env.widgets["categorySearchButton"].set_sensitive(
237 len(selectedCategoryRows) > 0)
239 def _connectionToggled(self, renderer, path):
240 categoryRow = self.__categoryModel[path]
241 category = env.shelf.getCategory(categoryRow[self.__COLUMN_CATEGORY_ID])
242 if categoryRow[self.__COLUMN_INCONSISTENT] \
243 or not categoryRow[self.__COLUMN_CONNECTED]:
244 for obj in self.__objectCollection.getObjectSelection().getSelectedObjects():
246 obj.addCategory(category)
247 except CategoryPresentError:
248 # The object was already connected to the category
250 categoryRow[self.__COLUMN_INCONSISTENT] = False
251 categoryRow[self.__COLUMN_CONNECTED] = True
253 for obj in self.__objectCollection.getObjectSelection().getSelectedObjects():
254 obj.removeCategory(category)
255 categoryRow[self.__COLUMN_CONNECTED] = False
256 categoryRow[self.__COLUMN_INCONSISTENT] = False
257 self.__updateToggleColumn()
258 self.__updateQSToggleColumn()
260 def _button_pressed(self, treeView, event):
261 if event.button == 3:
262 self._contextMenu.popup(None,None,None,event.button,event.time)
264 rec = self.__categoryView.get_cell_area(0, self.__toggleColumn)
265 if event.x <= (rec.x + rec.width):
266 # Ignore selection event since the user clicked on the toggleColumn.
267 self.__ignoreSelectEvent = True
270 def _button_released(self, treeView, event):
271 self.__ignoreSelectEvent = False
274 def _rowActivated(self, a, b, c):
275 # TODO What should happen if the user dubble-click on a category?
278 def _copyCategory(self, item, data):
279 cc = ClipboardCategories()
281 cc.categories = self.__selectedCategoriesIds
282 env.clipboard.setCategories(cc)
284 def _cutCategory(self, item, data):
285 cc = ClipboardCategories()
287 cc.categories = self.__selectedCategoriesIds
288 env.clipboard.setCategories(cc)
290 def _pasteCategory(self, item, data):
291 assert env.clipboard.hasCategories()
292 clipboardCategories = env.clipboard[0]
293 env.clipboard.clear()
295 for (categoryId, previousParentIds) in clipboardCategories.categories.items():
296 for newParentId in self.__selectedCategoriesIds:
297 if clipboardCategories.type == ClipboardCategories.COPY:
298 self.__connectChildToCategory(categoryId, newParentId)
299 for parentId in previousParentIds:
301 self.__disconnectChildHelper(categoryId, None,
302 None, self.__categoryModel)
304 if newParentId in previousParentIds:
305 previousParentIds.remove(newParentId)
307 self.__connectChildToCategory(categoryId, newParentId)
308 for parentId in previousParentIds:
310 self.__disconnectChildHelper(categoryId, None,
311 None, self.__categoryModel)
313 self.__disconnectChild(categoryId, parentId)
314 except CategoryLoopError:
315 dialog = gtk.MessageDialog(
316 type=gtk.MESSAGE_ERROR,
317 buttons=gtk.BUTTONS_OK,
318 message_format="Category loop detected.")
321 self.__updateToggleColumn()
322 self.__expandAndCollapseRows(False, False)
324 def _createRootCategory(self, item, data):
325 dialog = CategoryDialog("Create top-level category")
326 dialog.run(self._createRootCategoryHelper)
328 def _createRootCategoryHelper(self, tag, desc):
329 category = env.shelf.createCategory(tag, desc)
330 self.__loadCategorySubTree(None, category)
332 def _createChildCategory(self, item, data):
333 dialog = CategoryDialog("Create subcategory")
334 dialog.run(self._createChildCategoryHelper)
336 def _createChildCategoryHelper(self, tag, desc):
337 newCategory = env.shelf.createCategory(tag, desc)
338 for selectedCategoryId in self.__selectedCategoriesIds:
339 self.__connectChildToCategory(newCategory.getId(), selectedCategoryId)
340 self.__expandAndCollapseRows(False, False)
342 def _deleteCategories(self, item, data):
343 dialogId = "destroyCategoriesDialog"
344 widgets = gtk.glade.XML(env.gladeFile, dialogId)
345 dialog = widgets.get_widget(dialogId)
346 result = dialog.run()
347 if result == gtk.RESPONSE_OK:
348 for categoryId in self.__selectedCategoriesIds:
349 category = env.shelf.getCategory(categoryId)
350 for child in list(category.getChildren()):
351 # The backend automatically disconnects childs
352 # when a category is deleted, but we do it ourself
353 # to make sure that the treeview widget is
355 self.__disconnectChild(child.getId(), categoryId)
356 env.shelf.deleteCategory(categoryId)
357 env.shelf.flushCategoryCache()
358 self.__forEachCategoryRow(
359 self.__deleteCategoriesHelper, categoryId)
362 def __deleteCategoriesHelper(self, categoryRow, categoryIdToDelete):
363 if categoryRow[self.__COLUMN_CATEGORY_ID] == categoryIdToDelete:
364 self.__categoryModel.remove(categoryRow.iter)
366 def _disconnectCategory(self, item, data):
367 for (categoryId, parentIds) in self.__selectedCategoriesIds.items():
368 for parentId in parentIds:
369 if not parentId == None: # Not possible to disconnect root categories
370 self.__disconnectChild(categoryId, parentId)
372 def _editProperties(self, item, data):
373 for categoryId in self.__selectedCategoriesIds:
374 dialog = CategoryDialog("Change properties", categoryId)
375 dialog.run(self._editPropertiesHelper, data=categoryId)
377 def _editPropertiesHelper(self, tag, desc, categoryId):
378 category = env.shelf.getCategory(categoryId)
380 category.setDescription(desc)
381 env.shelf.flushCategoryCache()
382 self.__forEachCategoryRow(self.__updatePropertiesFromShelf, categoryId)
384 def _selectionFunction(self, path, b):
385 return not self.__ignoreSelectEvent
387 def _categoryQSViewFocusInEvent(self, widget, event):
388 self.__categoryQSEntry.grab_focus()
390 def _categoryQSEntryActivateEvent(self, entry):
391 if not self.__qsSelectedPath:
393 self._qsConnectionToggled(None, self.__qsSelectedPath)
394 self.__categoryQSFreeze = True
395 self.__categoryQSEntry.set_text("")
396 self.__categoryQSFreeze = False
397 self.__qsSelectedPath = None
399 def _categoryQSEntryChangedEvent(self, entry):
400 if self.__categoryQSFreeze:
402 self.__categoryQSModel.clear()
403 self.__qsSelectedPath = None
404 self.__categoryQSButton.set_sensitive(False)
405 text = entry.get_text().decode("utf-8")
409 regexp = re.compile(".*%s.*" % re.escape(text.lower()))
410 categories = list(env.shelf.getMatchingCategories(regexp))
411 categories.sort(self.__compareCategories)
413 for category in categories:
414 iterator = self.__categoryQSModel.append()
415 if (category.getTag().lower() == text.lower() or
416 category.getDescription().lower() == text.lower()):
417 exactMatches.append(self.__categoryQSModel.get_path(iterator))
418 self.__categoryQSModel.set_value(
419 iterator, self.__COLUMN_CATEGORY_ID, category.getId())
420 self.__categoryQSModel.set_value(
422 self.__COLUMN_DESCRIPTION,
423 "%s [%s]" % (category.getDescription(), category.getTag()))
424 if len(categories) == 1:
425 self.__qsSelectedPath = (0,)
426 self.__categoryQSButton.set_sensitive(True)
427 elif len(exactMatches) == 1:
428 self.__qsSelectedPath = exactMatches[0]
429 self.__categoryQSButton.set_sensitive(True)
430 self.__updateQSToggleColumn()
432 def _qsConnectionToggled(self, renderer, path):
433 categoryRow = self.__categoryQSModel[path]
434 category = env.shelf.getCategory(
435 categoryRow[self.__COLUMN_CATEGORY_ID])
436 if categoryRow[self.__COLUMN_INCONSISTENT] \
437 or not categoryRow[self.__COLUMN_CONNECTED]:
438 for obj in self.__objectCollection.getObjectSelection().getSelectedObjects():
440 obj.addCategory(category)
441 except CategoryPresentError:
442 # The object was already connected to the category
444 categoryRow[self.__COLUMN_INCONSISTENT] = False
445 categoryRow[self.__COLUMN_CONNECTED] = True
447 for obj in self.__objectCollection.getObjectSelection().getSelectedObjects():
448 obj.removeCategory(category)
449 categoryRow[self.__COLUMN_CONNECTED] = False
450 categoryRow[self.__COLUMN_INCONSISTENT] = False
451 self.__updateToggleColumn()
452 self.__expandAndCollapseRows(
453 env.widgets["autoExpand"].get_active(),
454 env.widgets["autoCollapse"].get_active())
456 ######################################################################
459 __cutCategoryLabel = "Cut"
460 __copyCategoryLabel = "Copy"
461 __pasteCategoryLabel = "Paste as child(ren)"
462 __destroyCategoryLabel = "Destroy..."
463 __disconnectCategoryLabel = "Disconnect from parent"
464 __createChildCategoryLabel = "Create subcategory..."
465 __createRootCategoryLabel = "Create top-level category..."
466 __propertiesLabel = "Properties"
468 __COLUMN_CATEGORY_ID = 0
469 __COLUMN_DESCRIPTION = 1
470 __COLUMN_CONNECTED = 2
471 __COLUMN_INCONSISTENT = 3
473 def __loadCategorySubTree(self, parent, category):
474 # TODO Do we have to use iterators here or can we use pygtks simplified syntax?
475 iterator = self.__categoryModel.iter_children(parent)
476 while (iterator != None and
477 self.__categoryModel.get_value(iterator, self.__COLUMN_DESCRIPTION) <
478 category.getDescription()):
479 iterator = self.__categoryModel.iter_next(iterator)
480 iterator = self.__categoryModel.insert_before(parent, iterator)
481 self.__categoryModel.set_value(iterator, self.__COLUMN_CATEGORY_ID, category.getId())
482 self.__categoryModel.set_value(iterator, self.__COLUMN_DESCRIPTION, category.getDescription())
483 self.__categoryModel.set_value(iterator, self.__COLUMN_CONNECTED, False)
484 self.__categoryModel.set_value(iterator, self.__COLUMN_INCONSISTENT, False)
485 for child in self.__sortCategories(category.getChildren()):
486 self.__loadCategorySubTree(iterator, child)
488 def __buildQueryFromSelection(self):
489 if env.widgets["categoriesOr"].get_active():
493 return operator.join([env.shelf.getCategory(x).getTag()
494 for x in self.__selectedCategoriesIds])
496 def __updateContextMenu(self):
497 # TODO Create helper functions to use from this method
498 menubarWidgetNames = [
504 "menubarDisconnectFromParent",
505 "menubarCreateChild",
508 if len(self.__selectedCategoriesIds) == 0:
509 self._contextMenuGroup.disable()
510 for widgetName in menubarWidgetNames:
511 env.widgets[widgetName].set_sensitive(False)
512 self._contextMenuGroup[
513 self.__createRootCategoryLabel].set_sensitive(True)
514 env.widgets["menubarCreateRoot"].set_sensitive(True)
516 self._contextMenuGroup.enable()
517 for widgetName in menubarWidgetNames:
518 env.widgets[widgetName].set_sensitive(True)
519 if not env.clipboard.hasCategories():
520 self._contextMenuGroup[
521 self.__pasteCategoryLabel].set_sensitive(False)
522 env.widgets["menubarPaste"].set_sensitive(False)
523 propertiesItem = self._contextMenuGroup[self.__propertiesLabel]
524 propertiesItemSensitive = len(self.__selectedCategoriesIds) == 1
525 propertiesItem.set_sensitive(propertiesItemSensitive)
526 env.widgets["menubarProperties"].set_sensitive(propertiesItemSensitive)
528 def __updateToggleColumn(self):
529 # find out which categories are connected, not connected or
530 # partitionally connected to selected objects
531 nrSelectedObjectsInCategory = {}
532 nrSelectedObjects = 0
533 for obj in self.__objectCollection.getObjectSelection().getSelectedObjects():
534 nrSelectedObjects += 1
535 for category in obj.getCategories():
536 categoryId = category.getId()
538 nrSelectedObjectsInCategory[categoryId] += 1
540 nrSelectedObjectsInCategory[categoryId] = 1
541 self.__forEachCategoryRow(self.__updateToggleColumnHelper,
542 (nrSelectedObjects, nrSelectedObjectsInCategory))
544 def __updateQSToggleColumn(self):
546 self.__objectCollection.getObjectSelection().getSelectedObjects()
547 nrSelectedObjectsInCategory = {}
548 nrSelectedObjects = 0
549 for obj in selectedObjects:
550 nrSelectedObjects += 1
551 for category in obj.getCategories():
552 catid = category.getId()
553 nrSelectedObjectsInCategory.setdefault(catid, 0)
554 nrSelectedObjectsInCategory[catid] += 1
555 self.__forEachCategoryRow(
556 self.__updateToggleColumnHelper,
557 (nrSelectedObjects, nrSelectedObjectsInCategory),
558 self.__categoryQSModel)
560 def __updateToggleColumnHelper(self,
562 (nrSelectedObjects, nrSelectedObjectsInCategory)):
563 categoryId = categoryRow[self.__COLUMN_CATEGORY_ID]
564 if categoryId in nrSelectedObjectsInCategory:
565 if nrSelectedObjectsInCategory[categoryId] < nrSelectedObjects:
566 # Some of the selected objects are connected to the category
567 categoryRow[self.__COLUMN_CONNECTED] = False
568 categoryRow[self.__COLUMN_INCONSISTENT] = True
570 # All of the selected objects are connected to the category
571 categoryRow[self.__COLUMN_CONNECTED] = True
572 categoryRow[self.__COLUMN_INCONSISTENT] = False
574 # None of the selected objects are connected to the category
575 categoryRow[self.__COLUMN_CONNECTED] = False
576 categoryRow[self.__COLUMN_INCONSISTENT] = False
578 def __forEachCategoryRow(self, function, data=None, categoryRows=None):
579 # We can't use gtk.TreeModel.foreach() since it does not pass a row
580 # to the callback function.
582 categoryRows=self.__categoryModel
583 for categoryRow in categoryRows:
584 function(categoryRow, data)
585 self.__forEachCategoryRow(function, data, categoryRow.iterchildren())
587 def __expandAndCollapseRows(self, autoExpand, autoCollapse, categoryRows=None):
588 if categoryRows is None:
589 categoryRows=self.__categoryModel
590 someRowsExpanded = False
591 for categoryRow in categoryRows:
592 expandThisRow = False
593 # Expand all rows that are selected or has expanded childs
594 childRowsExpanded = self.__expandAndCollapseRows(autoExpand,
596 categoryRow.iterchildren())
597 if (childRowsExpanded
598 or self.__categoryView.get_selection().path_is_selected(categoryRow.path)):
600 # Auto expand all rows that has a checked toggle
602 if (categoryRow[self.__COLUMN_CONNECTED]
603 or categoryRow[self.__COLUMN_INCONSISTENT]):
606 for a in range(len(categoryRow.path)):
607 self.__categoryView.expand_row(categoryRow.path[:a+1], False)
608 someRowsExpanded = True
611 self.__categoryView.collapse_row(categoryRow.path)
612 return someRowsExpanded
614 def __connectChildToCategory(self, childId, parentId):
617 childCategory = env.shelf.getCategory(childId)
618 parentCategory = env.shelf.getCategory(parentId)
619 parentCategory.connectChild(childCategory)
620 env.shelf.flushCategoryCache()
621 # Update widget modell
622 # If we reload the whole category tree from the shelf, we would lose
623 # the widgets information about current selected categories,
624 # expanded categories and the widget's scroll position. Hence,
625 # we update our previously loaded model instead.
626 self.__connectChildToCategoryHelper(parentId,
628 self.__categoryModel)
629 except CategoriesAlreadyConnectedError:
633 def __connectChildToCategoryHelper(self, parentId, childCategory, categoryRows):
634 for categoryRow in categoryRows:
635 if categoryRow[self.__COLUMN_CATEGORY_ID] == parentId:
636 self.__loadCategorySubTree(categoryRow.iter, childCategory)
638 self.__connectChildToCategoryHelper(parentId, childCategory, categoryRow.iterchildren())
640 def __disconnectChild(self, childId, parentId):
642 childCategory = env.shelf.getCategory(childId)
643 parentCategory = env.shelf.getCategory(parentId)
644 if childCategory in env.shelf.getRootCategories():
645 alreadyWasRootCategory = True
647 alreadyWasRootCategory = False
648 parentCategory.disconnectChild(childCategory)
649 env.shelf.flushCategoryCache()
650 # Update widget modell.
651 # If we reload the whole category tree from the shelf, we would lose
652 # the widgets information about current selected categories,
653 # expanded categories and the widget's scroll position. Hence,
654 # we update our previously loaded model instead.
655 self.__disconnectChildHelper(childId,
658 self.__categoryModel)
659 if not alreadyWasRootCategory:
660 for c in env.shelf.getRootCategories():
661 if c.getId() == childCategory.getId():
662 self.__loadCategorySubTree(None, childCategory)
665 def __disconnectChildHelper(self, wantedChildId, wantedParentId,
666 parentId, categoryRows):
667 for categoryRow in categoryRows:
668 cid = categoryRow[self.__COLUMN_CATEGORY_ID]
669 if cid == wantedChildId and parentId == wantedParentId:
670 self.__categoryModel.remove(categoryRow.iter)
671 self.__disconnectChildHelper(wantedChildId, wantedParentId, cid, categoryRow.iterchildren())
673 def __updatePropertiesFromShelf(self, categoryRow, categoryId):
674 if categoryRow[self.__COLUMN_CATEGORY_ID] == categoryId:
675 category = env.shelf.getCategory(categoryId)
676 categoryRow[self.__COLUMN_DESCRIPTION] = category.getDescription()
678 def __sortCategories(self, categoryIter):
679 categories = list(categoryIter)
680 categories.sort(self.__compareCategories)
683 def __compareCategories(self, x, y):
685 (x.getDescription(), x.getTag()),
686 (y.getDescription(), y.getTag()))
688 class ClipboardCategories: