Refactor Tag Browser to separate model and view. WARNING: All advanced functionality in the Tag Browser is currently broken

This commit is contained in:
Kovid Goyal 2011-06-25 19:38:12 -06:00
parent 7c5c0aaf68
commit 97b723de07
2 changed files with 510 additions and 531 deletions

File diff suppressed because it is too large Load Diff

View File

@ -7,11 +7,12 @@ __license__ = 'GPL v3'
__copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
import cPickle, traceback
import cPickle
from functools import partial
from itertools import izip
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,
TagsModel)
@ -70,6 +71,7 @@ class TagsView(QTreeView): # {{{
search_item_renamed = pyqtSignal()
drag_drop_finished = pyqtSignal(object)
restriction_error = pyqtSignal()
show_at_path = pyqtSignal()
def __init__(self, parent=None):
QTreeView.__init__(self, parent=None)
@ -90,14 +92,30 @@ class TagsView(QTreeView): # {{{
self.setDropIndicatorShown(True)
self.setAutoExpandDelay(500)
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.user_category_icon = QIcon(I('tb_folder.png'))
self.delete_icon = QIcon(I('list_remove.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):
pv = self.pane_is_visible
@ -105,40 +123,26 @@ class TagsView(QTreeView): # {{{
if to_what and not pv:
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):
if gprefs['tags_browser_collapse_at'] == 0:
self.collapse_model = 'disable'
else:
self.collapse_model = gprefs['tags_browser_partition_method']
self.set_new_model(self._model.get_filter_categories_by())
self._model.reread_collapse_parameters(self.get_state()[1])
def set_database(self, db, tag_match, sort_by):
hidden_cats = db.prefs.get('tag_browser_hidden_categories', None)
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)
self._model.set_database(db)
old = getattr(self, '_model', None)
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.pane_is_visible = True # because TagsModel.set_database did a recount
self.sort_by = sort_by
self.tag_match = tag_match
self.db = db
self.search_restriction = None
self.setModel(self._model)
self.setContextMenuPolicy(Qt.CustomContextMenu)
pop = config['sort_tags_by']
@ -164,6 +168,12 @@ class TagsView(QTreeView): # {{{
self.refresh_signal_processed = False
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
def match_all(self):
return self.tag_match and self.tag_match.currentIndex() > 0
@ -179,11 +189,8 @@ class TagsView(QTreeView): # {{{
pass
def set_search_restriction(self, s):
if s:
self.search_restriction = s
else:
self.search_restriction = None
self.set_new_model()
s = s if s else None
self._model.set_search_restriction(s)
def mouseReleaseEvent(self, event):
# Swallow everything except leftButton so context menus work correctly
@ -271,6 +278,7 @@ class TagsView(QTreeView): # {{{
self.author_sort_edit.emit(self, index)
return
reset_filter_categories = True
if action == 'hide':
self.hidden_categories.add(category)
elif action == 'show':
@ -279,12 +287,14 @@ class TagsView(QTreeView): # {{{
changed = self.collapse_model != category
self.collapse_model = category
if changed:
self.set_new_model(self._model.get_filter_categories_by())
reset_filter_categories = False
gprefs['tags_browser_partition_method'] = category
elif action == 'defaults':
self.hidden_categories.clear()
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:
return
@ -537,11 +547,31 @@ class TagsView(QTreeView): # {{{
if not ci.isValid():
ci = self.indexAt(QPoint(10, 10))
path = self.model().path_for_index(ci) if self.is_visible(ci) else None
expanded_categories, state_map = self.model().get_state()
self.set_new_model(state_map=state_map)
expanded_categories, state_map = self.get_state()
self._model.rebuild_node_tree(state_map=state_map)
for category in expanded_categories:
self.expand(self.model().index_for_category(category))
self._model.show_item_at_path(path)
self.expand(self._model.index_for_category(category))
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):
'''
@ -549,30 +579,6 @@ class TagsView(QTreeView): # {{{
'''
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)
# }}}