diff --git a/src/calibre/gui2/tag_browser/model.py b/src/calibre/gui2/tag_browser/model.py index 05417f518b..8c6cb7d0eb 100644 --- a/src/calibre/gui2/tag_browser/model.py +++ b/src/calibre/gui2/tag_browser/model.py @@ -335,7 +335,6 @@ class TagsModel(QAbstractItemModel): # {{{ node.is_gst = is_gst if not is_gst: node.tag.is_hierarchical = '5state' - if not is_gst: tree_root[p] = {} tree_root = tree_root[p] else: @@ -519,7 +518,7 @@ class TagsModel(QAbstractItemModel): # {{{ # category display order is important here. The following works # only if all the non-user categories are displayed before the # user categories - if category_is_hierarchical: + if category_is_hierarchical or tag.is_hierarchical: components = get_name_components(tag.original_name) else: components = [tag.original_name] @@ -581,6 +580,14 @@ class TagsModel(QAbstractItemModel): # {{{ return [(t.tag.id, t.tag.original_name, t.tag.count) for t in cat.child_tags() if t.tag.count > 0] + def is_in_user_category(self, index): + if not index.isValid(): + return False + p = self.get_node(index) + while p.type != TagTreeItem.CATEGORY: + p = p.parent + return p.tag.category.startswith('@') + # Drag'n Drop {{{ def mimeTypes(self): return ["application/calibre+from_library", @@ -646,13 +653,13 @@ class TagsModel(QAbstractItemModel): # {{{ action is Qt.CopyAction or Qt.MoveAction ''' def process_source_node(user_cats, src_parent, src_parent_is_gst, - is_uc, dest_key, node): + is_uc, dest_key, idx): ''' Copy/move an item and all its children to the destination ''' copied = False - src_name = node.tag.original_name - src_cat = node.tag.category + src_name = idx.tag.original_name + src_cat = idx.tag.category # delete the item if the source is a user category and action is move if is_uc and not src_parent_is_gst and src_parent in user_cats and \ action == Qt.MoveAction: @@ -675,7 +682,7 @@ class TagsModel(QAbstractItemModel): # {{{ if add_it: user_cats[dest_key].append([src_name, src_cat, 0]) - for c in node.children: + for c in idx.children: copied = process_source_node(user_cats, src_parent, src_parent_is_gst, is_uc, dest_key, c) return copied @@ -696,11 +703,11 @@ class TagsModel(QAbstractItemModel): # {{{ if dest_key not in user_cats: continue - node = self.index_for_path(path) - if node: + idx = self.index_for_path(path) + if idx.isValid(): process_source_node(user_cats, src_parent, src_parent_is_gst, is_uc, dest_key, - self.get_node(node)) + self.get_node(idx)) self.db.prefs.set('user_categories', user_cats) self.refresh_required.emit() @@ -1139,6 +1146,8 @@ class TagsModel(QAbstractItemModel): # {{{ return QModelIndex() ans = self.createIndex(parent_item.row(), 0, parent_item) + if not ans.isValid(): + return QModelIndex() return ans def rowCount(self, parent): diff --git a/src/calibre/gui2/tag_browser/view.py b/src/calibre/gui2/tag_browser/view.py index d405294b01..090631025a 100644 --- a/src/calibre/gui2/tag_browser/view.py +++ b/src/calibre/gui2/tag_browser/view.py @@ -12,7 +12,8 @@ from functools import partial from itertools import izip from PyQt4.Qt import (QStyledItemDelegate, Qt, QTreeView, pyqtSignal, QSize, - QIcon, QApplication, QMenu, QPoint, QModelIndex, QToolTip, QCursor) + QIcon, QApplication, QMenu, QPoint, QModelIndex, QToolTip, QCursor, + QDrag) from calibre.gui2.tag_browser.model import (TagTreeItem, TAG_SEARCH_STATES, TagsModel) @@ -101,6 +102,7 @@ class TagsView(QTreeView): # {{{ self.setDragEnabled(True) self.setDragDropMode(self.DragDrop) self.setDropIndicatorShown(True) + self.in_drag_drop = False self.setAutoExpandDelay(500) self.pane_is_visible = False self.search_icon = QIcon(I('search.png')) @@ -232,10 +234,35 @@ class TagsView(QTreeView): # {{{ s = s if s else None self._model.set_search_restriction(s) + def mouseMoveEvent(self, event): + dex = self.indexAt(event.pos()) + if self.in_drag_drop or not dex.isValid(): + QTreeView.mouseMoveEvent(self, event) + return + # Must deal with odd case where the node being dragged is 'virtual', + # created to form a hierarchy. We can't really drag this node, but in + # addition we can't allow drag recognition to notice going over some + # other node and grabbing that one. So we set in_drag_drop to prevent + # this from happening, turning it off when the user lifts the button. + self.in_drag_drop = True + if not self._model.flags(dex) & Qt.ItemIsDragEnabled: + QTreeView.mouseMoveEvent(self, event) + return + md = self._model.mimeData([dex]) + pixmap = dex.data(Qt.DecorationRole).toPyObject().pixmap(25, 25) + drag = QDrag(self) + drag.setPixmap(pixmap) + drag.setMimeData(md) + if self._model.is_in_user_category(dex): + drag.exec_(Qt.CopyAction|Qt.MoveAction, Qt.CopyAction) + else: + drag.exec_(Qt.CopyAction) + def mouseReleaseEvent(self, event): # Swallow everything except leftButton so context menus work correctly - if event.button() == Qt.LeftButton: + if event.button() == Qt.LeftButton or self.in_drag_drop: QTreeView.mouseReleaseEvent(self, event) + self.in_drag_drop = False def mouseDoubleClickEvent(self, event): # swallow these to avoid toggling and editing at the same time @@ -639,12 +666,19 @@ class TagsView(QTreeView): # {{{ self.show_item_at_index(self._model.index_for_path(path), box=box, position=position) + def expand_parent(self, idx, depth=0): + p = self._model.parent(idx) + d = 0 + if p.isValid(): + d = self.expand_parent(p, depth+1) + if d == 0: + self.expand(idx) + return d+1 + def show_item_at_index(self, idx, box=False, position=QTreeView.PositionAtCenter): if idx.isValid() and idx.data(Qt.UserRole).toPyObject() is not self._model.root_item: - self.expand(self._model.parent(idx)) # Needed otherwise Qt sometimes segfaults if the - # node is buried in a collapsed, off - # screen hierarchy + self.expand_parent(idx) self.setCurrentIndex(idx) self.scrollTo(idx, position) if box: