mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-07 18:24:30 -04:00
First implementation of a search box for the tags browser
This commit is contained in:
parent
a79d75bd5a
commit
7cdfdb27c2
@ -10,20 +10,20 @@ Browsing book collection by tags.
|
|||||||
from itertools import izip
|
from itertools import izip
|
||||||
from functools import partial
|
from functools import partial
|
||||||
|
|
||||||
from PyQt4.Qt import Qt, QTreeView, QApplication, pyqtSignal, \
|
from PyQt4.Qt import Qt, QTreeView, QApplication, pyqtSignal, QFont, QSize, \
|
||||||
QFont, QSize, QIcon, QPoint, QVBoxLayout, QComboBox, \
|
QIcon, QPoint, QVBoxLayout, QHBoxLayout, QComboBox,\
|
||||||
QAbstractItemModel, QVariant, QModelIndex, QMenu, \
|
QAbstractItemModel, QVariant, QModelIndex, QMenu, \
|
||||||
QPushButton, QWidget, QItemDelegate
|
QPushButton, QWidget, QItemDelegate, QLineEdit
|
||||||
|
|
||||||
from calibre.ebooks.metadata import title_sort
|
from calibre.ebooks.metadata import title_sort
|
||||||
from calibre.gui2 import config, NONE
|
from calibre.gui2 import config, NONE
|
||||||
from calibre.library.field_metadata import TagsIcons, category_icon_map
|
from calibre.library.field_metadata import TagsIcons, category_icon_map
|
||||||
from calibre.library.database2 import Tag
|
from calibre.library.database2 import Tag
|
||||||
from calibre.utils.config import tweaks
|
from calibre.utils.config import tweaks
|
||||||
from calibre.utils.icu import sort_key, upper
|
from calibre.utils.icu import sort_key, upper, lower
|
||||||
from calibre.utils.search_query_parser import saved_searches
|
from calibre.utils.search_query_parser import saved_searches
|
||||||
from calibre.utils.formatter import eval_formatter
|
from calibre.utils.formatter import eval_formatter
|
||||||
from calibre.gui2 import error_dialog
|
from calibre.gui2 import error_dialog, warning_dialog
|
||||||
from calibre.gui2.dialogs.confirm_delete import confirm
|
from calibre.gui2.dialogs.confirm_delete import confirm
|
||||||
from calibre.gui2.dialogs.tag_categories import TagCategories
|
from calibre.gui2.dialogs.tag_categories import TagCategories
|
||||||
from calibre.gui2.dialogs.tag_list_editor import TagListEditor
|
from calibre.gui2.dialogs.tag_list_editor import TagListEditor
|
||||||
@ -54,6 +54,8 @@ class TagDelegate(QItemDelegate): # {{{
|
|||||||
painter.setClipRect(r)
|
painter.setClipRect(r)
|
||||||
|
|
||||||
# Paint the text
|
# Paint the text
|
||||||
|
if item.boxed:
|
||||||
|
painter.drawRoundedRect(r, 5, 5)
|
||||||
r.setLeft(r.left()+r.height()+3)
|
r.setLeft(r.left()+r.height()+3)
|
||||||
painter.drawText(r, Qt.AlignLeft|Qt.AlignVCenter,
|
painter.drawText(r, Qt.AlignLeft|Qt.AlignVCenter,
|
||||||
model.data(index, Qt.DisplayRole).toString())
|
model.data(index, Qt.DisplayRole).toString())
|
||||||
@ -357,6 +359,7 @@ class TagTreeItem(object): # {{{
|
|||||||
parent=None, tooltip=None, category_key=None):
|
parent=None, tooltip=None, category_key=None):
|
||||||
self.parent = parent
|
self.parent = parent
|
||||||
self.children = []
|
self.children = []
|
||||||
|
self.boxed = False
|
||||||
if self.parent is not None:
|
if self.parent is not None:
|
||||||
self.parent.append(self)
|
self.parent.append(self)
|
||||||
if data is None:
|
if data is None:
|
||||||
@ -940,6 +943,79 @@ class TagsModel(QAbstractItemModel): # {{{
|
|||||||
ans.append('%s%s:"=%s"'%(prefix, category, tag.name))
|
ans.append('%s%s:"=%s"'%(prefix, category, tag.name))
|
||||||
return ans
|
return ans
|
||||||
|
|
||||||
|
def find_node(self, txt, start_index):
|
||||||
|
if not txt:
|
||||||
|
return None
|
||||||
|
txt = lower(txt)
|
||||||
|
if start_index is None:
|
||||||
|
start_index = QModelIndex()
|
||||||
|
self.node_found = None
|
||||||
|
|
||||||
|
def process_tag(depth, tag_index, tag_item, start_path):
|
||||||
|
path = self.path_for_index(tag_index)
|
||||||
|
if depth < len(start_path) and path[depth] <= start_path[depth]:
|
||||||
|
return False
|
||||||
|
tag = tag_item.tag
|
||||||
|
if tag is None:
|
||||||
|
return False
|
||||||
|
if lower(tag.name).find(txt) >= 0:
|
||||||
|
self.node_found = tag_index
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def process_level(depth, category_index, start_path):
|
||||||
|
path = self.path_for_index(category_index)
|
||||||
|
if depth < len(start_path):
|
||||||
|
if path[depth] < start_path[depth]:
|
||||||
|
return False
|
||||||
|
if path[depth] > start_path[depth]:
|
||||||
|
start_path = path
|
||||||
|
for j in xrange(self.rowCount(category_index)):
|
||||||
|
tag_index = self.index(j, 0, category_index)
|
||||||
|
tag_item = tag_index.internalPointer()
|
||||||
|
if tag_item.type == TagTreeItem.CATEGORY:
|
||||||
|
if process_level(depth+1, tag_index, start_path):
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
if process_tag(depth+1, tag_index, tag_item, start_path):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
for i in xrange(self.rowCount(QModelIndex())):
|
||||||
|
if process_level(0, self.index(i, 0, QModelIndex()),
|
||||||
|
self.path_for_index(start_index)):
|
||||||
|
break
|
||||||
|
return self.node_found
|
||||||
|
|
||||||
|
def show_item_at_index(self, idx, box=False):
|
||||||
|
if idx.isValid():
|
||||||
|
tag_item = idx.internalPointer()
|
||||||
|
self.tags_view.setCurrentIndex(idx)
|
||||||
|
self.tags_view.scrollTo(idx, QTreeView.PositionAtCenter)
|
||||||
|
if box:
|
||||||
|
tag_item.boxed = True
|
||||||
|
self.dataChanged.emit(idx, idx)
|
||||||
|
|
||||||
|
def clear_boxed(self):
|
||||||
|
def process_tag(tag_index, tag_item):
|
||||||
|
if tag_item.boxed:
|
||||||
|
tag_item.boxed = False
|
||||||
|
self.dataChanged.emit(tag_index, tag_index)
|
||||||
|
|
||||||
|
def process_level(category_index):
|
||||||
|
for j in xrange(self.rowCount(category_index)):
|
||||||
|
tag_index = self.index(j, 0, category_index)
|
||||||
|
tag_item = tag_index.internalPointer()
|
||||||
|
if tag_item.type == TagTreeItem.CATEGORY:
|
||||||
|
process_level(tag_index)
|
||||||
|
else:
|
||||||
|
process_tag(tag_index, tag_item)
|
||||||
|
|
||||||
|
for i in xrange(self.rowCount(QModelIndex())):
|
||||||
|
process_level(self.index(i, 0, QModelIndex()))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
class TagBrowserMixin(object): # {{{
|
class TagBrowserMixin(object): # {{{
|
||||||
@ -1059,6 +1135,24 @@ class TagBrowserWidget(QWidget): # {{{
|
|||||||
self.setLayout(self._layout)
|
self.setLayout(self._layout)
|
||||||
self._layout.setContentsMargins(0,0,0,0)
|
self._layout.setContentsMargins(0,0,0,0)
|
||||||
|
|
||||||
|
search_layout = QHBoxLayout()
|
||||||
|
self._layout.addLayout(search_layout)
|
||||||
|
self.item_search = QLineEdit(parent)
|
||||||
|
try:
|
||||||
|
self.item_search.setPlaceholderText(_('Find item in tag browser'))
|
||||||
|
except:
|
||||||
|
# Using Qt < 4.7
|
||||||
|
pass
|
||||||
|
search_layout.addWidget(self.item_search)
|
||||||
|
self.search_button = QPushButton()
|
||||||
|
self.search_button.setText(_('Find!'))
|
||||||
|
self.search_button.setFixedWidth(40)
|
||||||
|
search_layout.addWidget(self.search_button)
|
||||||
|
self.current_position = None
|
||||||
|
self.search_button.clicked.connect(self.find)
|
||||||
|
self.item_search.editingFinished.connect(self.find)
|
||||||
|
self.item_search.textChanged.connect(self.find_text_changed)
|
||||||
|
|
||||||
parent.tags_view = TagsView(parent)
|
parent.tags_view = TagsView(parent)
|
||||||
self.tags_view = parent.tags_view
|
self.tags_view = parent.tags_view
|
||||||
self._layout.addWidget(parent.tags_view)
|
self._layout.addWidget(parent.tags_view)
|
||||||
@ -1093,6 +1187,22 @@ class TagBrowserWidget(QWidget): # {{{
|
|||||||
def set_pane_is_visible(self, to_what):
|
def set_pane_is_visible(self, to_what):
|
||||||
self.tags_view.set_pane_is_visible(to_what)
|
self.tags_view.set_pane_is_visible(to_what)
|
||||||
|
|
||||||
|
def find_text_changed(self, str):
|
||||||
|
self.current_position = None
|
||||||
|
|
||||||
|
def find(self):
|
||||||
|
self.search_button.setFocus(True)
|
||||||
|
model = self.tags_view.model()
|
||||||
|
model.clear_boxed()
|
||||||
|
self.current_position =\
|
||||||
|
model.find_node(unicode(self.item_search.text()), self.current_position)
|
||||||
|
if self.current_position:
|
||||||
|
model.show_item_at_index(self.current_position, box=True)
|
||||||
|
elif self.item_search.text():
|
||||||
|
warning_dialog(self.tags_view, _('No item found'),
|
||||||
|
_('No (more) matches for that search')).exec_()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user