mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Refactor Tag Browser to separate model and view. WARNING: All advanced functionality in the Tag Browser is currently broken
This commit is contained in:
parent
7c5c0aaf68
commit
97b723de07
File diff suppressed because it is too large
Load Diff
@ -7,11 +7,12 @@ __license__ = 'GPL v3'
|
|||||||
__copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>'
|
__copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||||
__docformat__ = 'restructuredtext en'
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
import cPickle, traceback
|
import cPickle
|
||||||
from functools import partial
|
from functools import partial
|
||||||
|
from itertools import izip
|
||||||
|
|
||||||
from PyQt4.Qt import (QItemDelegate, Qt, QTreeView, pyqtSignal, QSize, QIcon,
|
from PyQt4.Qt import (QItemDelegate, Qt, QTreeView, pyqtSignal, QSize, QIcon,
|
||||||
QApplication, QMenu, QPoint)
|
QApplication, QMenu, QPoint, QModelIndex)
|
||||||
|
|
||||||
from calibre.gui2.tag_browser.model import (TagTreeItem, TAG_SEARCH_STATES,
|
from calibre.gui2.tag_browser.model import (TagTreeItem, TAG_SEARCH_STATES,
|
||||||
TagsModel)
|
TagsModel)
|
||||||
@ -70,6 +71,7 @@ class TagsView(QTreeView): # {{{
|
|||||||
search_item_renamed = pyqtSignal()
|
search_item_renamed = pyqtSignal()
|
||||||
drag_drop_finished = pyqtSignal(object)
|
drag_drop_finished = pyqtSignal(object)
|
||||||
restriction_error = pyqtSignal()
|
restriction_error = pyqtSignal()
|
||||||
|
show_at_path = pyqtSignal()
|
||||||
|
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
QTreeView.__init__(self, parent=None)
|
QTreeView.__init__(self, parent=None)
|
||||||
@ -90,14 +92,30 @@ class TagsView(QTreeView): # {{{
|
|||||||
self.setDropIndicatorShown(True)
|
self.setDropIndicatorShown(True)
|
||||||
self.setAutoExpandDelay(500)
|
self.setAutoExpandDelay(500)
|
||||||
self.pane_is_visible = False
|
self.pane_is_visible = False
|
||||||
if gprefs['tags_browser_collapse_at'] == 0:
|
|
||||||
self.collapse_model = 'disable'
|
|
||||||
else:
|
|
||||||
self.collapse_model = gprefs['tags_browser_partition_method']
|
|
||||||
self.search_icon = QIcon(I('search.png'))
|
self.search_icon = QIcon(I('search.png'))
|
||||||
self.user_category_icon = QIcon(I('tb_folder.png'))
|
self.user_category_icon = QIcon(I('tb_folder.png'))
|
||||||
self.delete_icon = QIcon(I('list_remove.png'))
|
self.delete_icon = QIcon(I('list_remove.png'))
|
||||||
self.rename_icon = QIcon(I('edit-undo.png'))
|
self.rename_icon = QIcon(I('edit-undo.png'))
|
||||||
|
self.show_at_path.connect(self.show_item_at_path,
|
||||||
|
type=Qt.QueuedConnection)
|
||||||
|
|
||||||
|
self._model = TagsModel(self)
|
||||||
|
self._model.search_item_renamed.connect(self.search_item_renamed)
|
||||||
|
self._model.refresh_required.connect(self.refresh_required,
|
||||||
|
type=Qt.QueuedConnection)
|
||||||
|
self._model.tag_item_renamed.connect(self.tag_item_renamed)
|
||||||
|
self._model.restriction_error.connect(self.restriction_error)
|
||||||
|
self._model.user_categories_edited.connect(self.user_categories_edited,
|
||||||
|
type=Qt.QueuedConnection)
|
||||||
|
self._model.drag_drop_finished.connect(self.drag_drop_finished)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def hidden_categories(self):
|
||||||
|
return self._model.hidden_categories
|
||||||
|
|
||||||
|
@property
|
||||||
|
def db(self):
|
||||||
|
return self._model.db
|
||||||
|
|
||||||
def set_pane_is_visible(self, to_what):
|
def set_pane_is_visible(self, to_what):
|
||||||
pv = self.pane_is_visible
|
pv = self.pane_is_visible
|
||||||
@ -105,40 +123,26 @@ class TagsView(QTreeView): # {{{
|
|||||||
if to_what and not pv:
|
if to_what and not pv:
|
||||||
self.recount()
|
self.recount()
|
||||||
|
|
||||||
|
def get_state(self):
|
||||||
|
state_map = {}
|
||||||
|
expanded_categories = []
|
||||||
|
for row, category in enumerate(self._model.category_nodes):
|
||||||
|
if self.isExpanded(self._model.index(row, 0, QModelIndex())):
|
||||||
|
expanded_categories.append(category.py_name)
|
||||||
|
states = [c.tag.state for c in category.child_tags()]
|
||||||
|
names = [(c.tag.name, c.tag.category) for c in category.child_tags()]
|
||||||
|
state_map[category.py_name] = dict(izip(names, states))
|
||||||
|
return expanded_categories, state_map
|
||||||
|
|
||||||
def reread_collapse_parameters(self):
|
def reread_collapse_parameters(self):
|
||||||
if gprefs['tags_browser_collapse_at'] == 0:
|
self._model.reread_collapse_parameters(self.get_state()[1])
|
||||||
self.collapse_model = 'disable'
|
|
||||||
else:
|
|
||||||
self.collapse_model = gprefs['tags_browser_partition_method']
|
|
||||||
self.set_new_model(self._model.get_filter_categories_by())
|
|
||||||
|
|
||||||
def set_database(self, db, tag_match, sort_by):
|
def set_database(self, db, tag_match, sort_by):
|
||||||
hidden_cats = db.prefs.get('tag_browser_hidden_categories', None)
|
self._model.set_database(db)
|
||||||
self.hidden_categories = []
|
|
||||||
# migrate from config to db prefs
|
|
||||||
if hidden_cats is None:
|
|
||||||
hidden_cats = config['tag_browser_hidden_categories']
|
|
||||||
# strip out any non-existence field keys
|
|
||||||
for cat in hidden_cats:
|
|
||||||
if cat in db.field_metadata:
|
|
||||||
self.hidden_categories.append(cat)
|
|
||||||
db.prefs.set('tag_browser_hidden_categories', list(self.hidden_categories))
|
|
||||||
self.hidden_categories = set(self.hidden_categories)
|
|
||||||
|
|
||||||
old = getattr(self, '_model', None)
|
self.pane_is_visible = True # because TagsModel.set_database did a recount
|
||||||
if old is not None:
|
|
||||||
old.break_cycles()
|
|
||||||
self._model = TagsModel(db, parent=self,
|
|
||||||
hidden_categories=self.hidden_categories,
|
|
||||||
search_restriction=None,
|
|
||||||
drag_drop_finished=self.drag_drop_finished,
|
|
||||||
collapse_model=self.collapse_model,
|
|
||||||
state_map={})
|
|
||||||
self.pane_is_visible = True # because TagsModel.init did a recount
|
|
||||||
self.sort_by = sort_by
|
self.sort_by = sort_by
|
||||||
self.tag_match = tag_match
|
self.tag_match = tag_match
|
||||||
self.db = db
|
|
||||||
self.search_restriction = None
|
|
||||||
self.setModel(self._model)
|
self.setModel(self._model)
|
||||||
self.setContextMenuPolicy(Qt.CustomContextMenu)
|
self.setContextMenuPolicy(Qt.CustomContextMenu)
|
||||||
pop = config['sort_tags_by']
|
pop = config['sort_tags_by']
|
||||||
@ -164,6 +168,12 @@ class TagsView(QTreeView): # {{{
|
|||||||
self.refresh_signal_processed = False
|
self.refresh_signal_processed = False
|
||||||
self.refresh_required.emit()
|
self.refresh_required.emit()
|
||||||
|
|
||||||
|
def user_categories_edited(self, user_cats, nkey):
|
||||||
|
state_map = self.get_state()[1]
|
||||||
|
self.db.prefs.set('user_categories', user_cats)
|
||||||
|
self._model.rebuild_node_tree(state_map=state_map)
|
||||||
|
self.show_at_path.emit('@'+nkey)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def match_all(self):
|
def match_all(self):
|
||||||
return self.tag_match and self.tag_match.currentIndex() > 0
|
return self.tag_match and self.tag_match.currentIndex() > 0
|
||||||
@ -179,11 +189,8 @@ class TagsView(QTreeView): # {{{
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
def set_search_restriction(self, s):
|
def set_search_restriction(self, s):
|
||||||
if s:
|
s = s if s else None
|
||||||
self.search_restriction = s
|
self._model.set_search_restriction(s)
|
||||||
else:
|
|
||||||
self.search_restriction = None
|
|
||||||
self.set_new_model()
|
|
||||||
|
|
||||||
def mouseReleaseEvent(self, event):
|
def mouseReleaseEvent(self, event):
|
||||||
# Swallow everything except leftButton so context menus work correctly
|
# Swallow everything except leftButton so context menus work correctly
|
||||||
@ -271,6 +278,7 @@ class TagsView(QTreeView): # {{{
|
|||||||
self.author_sort_edit.emit(self, index)
|
self.author_sort_edit.emit(self, index)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
reset_filter_categories = True
|
||||||
if action == 'hide':
|
if action == 'hide':
|
||||||
self.hidden_categories.add(category)
|
self.hidden_categories.add(category)
|
||||||
elif action == 'show':
|
elif action == 'show':
|
||||||
@ -279,12 +287,14 @@ class TagsView(QTreeView): # {{{
|
|||||||
changed = self.collapse_model != category
|
changed = self.collapse_model != category
|
||||||
self.collapse_model = category
|
self.collapse_model = category
|
||||||
if changed:
|
if changed:
|
||||||
self.set_new_model(self._model.get_filter_categories_by())
|
reset_filter_categories = False
|
||||||
gprefs['tags_browser_partition_method'] = category
|
gprefs['tags_browser_partition_method'] = category
|
||||||
elif action == 'defaults':
|
elif action == 'defaults':
|
||||||
self.hidden_categories.clear()
|
self.hidden_categories.clear()
|
||||||
self.db.prefs.set('tag_browser_hidden_categories', list(self.hidden_categories))
|
self.db.prefs.set('tag_browser_hidden_categories', list(self.hidden_categories))
|
||||||
self.set_new_model()
|
if reset_filter_categories:
|
||||||
|
self._model.filter_categories_by = None
|
||||||
|
self._model.rebuild_node_tree()
|
||||||
except:
|
except:
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -537,11 +547,31 @@ class TagsView(QTreeView): # {{{
|
|||||||
if not ci.isValid():
|
if not ci.isValid():
|
||||||
ci = self.indexAt(QPoint(10, 10))
|
ci = self.indexAt(QPoint(10, 10))
|
||||||
path = self.model().path_for_index(ci) if self.is_visible(ci) else None
|
path = self.model().path_for_index(ci) if self.is_visible(ci) else None
|
||||||
expanded_categories, state_map = self.model().get_state()
|
expanded_categories, state_map = self.get_state()
|
||||||
self.set_new_model(state_map=state_map)
|
self._model.rebuild_node_tree(state_map=state_map)
|
||||||
for category in expanded_categories:
|
for category in expanded_categories:
|
||||||
self.expand(self.model().index_for_category(category))
|
self.expand(self._model.index_for_category(category))
|
||||||
self._model.show_item_at_path(path)
|
self.show_item_at_path(path)
|
||||||
|
|
||||||
|
def show_item_at_path(self, path, box=False,
|
||||||
|
position=QTreeView.PositionAtCenter):
|
||||||
|
'''
|
||||||
|
Scroll the browser and open categories to show the item referenced by
|
||||||
|
path. If possible, the item is placed in the center. If box=True, a
|
||||||
|
box is drawn around the item.
|
||||||
|
'''
|
||||||
|
if path:
|
||||||
|
self.show_item_at_index(self._model.index_for_path(path), box=box,
|
||||||
|
position=position)
|
||||||
|
|
||||||
|
def show_item_at_index(self, idx, box=False,
|
||||||
|
position=QTreeView.PositionAtCenter):
|
||||||
|
if idx.isValid():
|
||||||
|
self.setCurrentIndex(idx)
|
||||||
|
self.scrollTo(idx, position)
|
||||||
|
self.setCurrentIndex(idx)
|
||||||
|
if box:
|
||||||
|
self._model.set_boxed(idx)
|
||||||
|
|
||||||
def item_expanded(self, idx):
|
def item_expanded(self, idx):
|
||||||
'''
|
'''
|
||||||
@ -549,30 +579,6 @@ class TagsView(QTreeView): # {{{
|
|||||||
'''
|
'''
|
||||||
self.setCurrentIndex(idx)
|
self.setCurrentIndex(idx)
|
||||||
|
|
||||||
def set_new_model(self, filter_categories_by=None, state_map={}):
|
|
||||||
'''
|
|
||||||
There are cases where we need to rebuild the category tree without
|
|
||||||
attempting to reposition the current node.
|
|
||||||
'''
|
|
||||||
try:
|
|
||||||
old = getattr(self, '_model', None)
|
|
||||||
if old is not None:
|
|
||||||
old.break_cycles()
|
|
||||||
self._model = TagsModel(self.db, parent=self,
|
|
||||||
hidden_categories=self.hidden_categories,
|
|
||||||
search_restriction=self.search_restriction,
|
|
||||||
drag_drop_finished=self.drag_drop_finished,
|
|
||||||
filter_categories_by=filter_categories_by,
|
|
||||||
collapse_model=self.collapse_model,
|
|
||||||
state_map=state_map)
|
|
||||||
self.setModel(self._model)
|
|
||||||
except:
|
|
||||||
# The DB must be gone. Set the model to None and hope that someone
|
|
||||||
# will call set_database later. I don't know if this in fact works.
|
|
||||||
# But perhaps a Bad Thing Happened, so print the exception
|
|
||||||
traceback.print_exc()
|
|
||||||
self._model = None
|
|
||||||
self.setModel(None)
|
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user