mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
1. added context menu on tags pane.
2. added saved search editor 3l added tags editor (a cut-down version of the original) 4. added access to editors from context menu.
This commit is contained in:
parent
c80e12c5a2
commit
26f5642e02
@ -97,7 +97,8 @@ def _config():
|
||||
help=_('Overwrite author and title with new metadata'))
|
||||
c.add_opt('enforce_cpu_limit', default=True,
|
||||
help=_('Limit max simultaneous jobs to number of CPUs'))
|
||||
|
||||
c.add_opt('tag_browser_hidden_categories', default=set(),
|
||||
help=_('tag browser categories not to display'))
|
||||
return ConfigProxy(c)
|
||||
|
||||
config = _config()
|
||||
|
86
src/calibre/gui2/dialogs/saved_search_editor.py
Normal file
86
src/calibre/gui2/dialogs/saved_search_editor.py
Normal file
@ -0,0 +1,86 @@
|
||||
__license__ = 'GPL v3'
|
||||
|
||||
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
|
||||
|
||||
from PyQt4.QtCore import SIGNAL, Qt
|
||||
from PyQt4.QtGui import QDialog, QIcon, QListWidgetItem
|
||||
|
||||
from calibre.gui2.dialogs.saved_search_editor_ui import Ui_SavedSearchEditor
|
||||
from calibre.utils.config import prefs
|
||||
from calibre.utils.search_query_parser import saved_searches
|
||||
from calibre.gui2.dialogs.confirm_delete import confirm
|
||||
from calibre.constants import islinux
|
||||
|
||||
class SavedSearchEditor(QDialog, Ui_SavedSearchEditor):
|
||||
|
||||
def __init__(self, window, initial_search=None):
|
||||
QDialog.__init__(self, window)
|
||||
Ui_SavedSearchEditor.__init__(self)
|
||||
self.setupUi(self)
|
||||
|
||||
self.connect(self.add_search_button, SIGNAL('clicked()'), self.add_search)
|
||||
self.connect(self.search_name_box, SIGNAL('currentIndexChanged(int)'),
|
||||
self.current_index_changed)
|
||||
self.connect(self.delete_search_button, SIGNAL('clicked()'), self.del_search)
|
||||
|
||||
self.current_search_name = None
|
||||
self.searches = {}
|
||||
self.searches_to_delete = []
|
||||
for name in saved_searches.names():
|
||||
self.searches[name] = saved_searches.lookup(name)
|
||||
|
||||
self.populate_search_list()
|
||||
if initial_search is not None and initial_search in self.searches:
|
||||
self.select_search(initial_search)
|
||||
|
||||
def populate_search_list(self):
|
||||
self.search_name_box.clear()
|
||||
for name in sorted(self.searches.keys()):
|
||||
self.search_name_box.addItem(name)
|
||||
|
||||
def add_search(self):
|
||||
search_name = unicode(self.input_box.text()).strip()
|
||||
if search_name == '':
|
||||
return False
|
||||
if search_name not in self.searches:
|
||||
self.searches[search_name] = ''
|
||||
self.populate_search_list()
|
||||
self.select_search(search_name)
|
||||
else:
|
||||
self.select_search(search_name)
|
||||
return True
|
||||
|
||||
def del_search(self):
|
||||
if self.current_search_name is not None:
|
||||
if not confirm('<p>'+_('The current saved search will be '
|
||||
'<b>permanently deleted</b>. Are you sure?')
|
||||
+'</p>', 'saved_search_editor_delete', self):
|
||||
return
|
||||
del self.searches[self.current_search_name]
|
||||
self.searches_to_delete.append(self.current_search_name)
|
||||
self.current_search_name = None
|
||||
self.search_name_box.removeItem(self.search_name_box.currentIndex())
|
||||
|
||||
def select_search(self, name):
|
||||
self.search_name_box.setCurrentIndex(self.search_name_box.findText(name))
|
||||
|
||||
def current_index_changed(self, idx):
|
||||
if self.current_search_name:
|
||||
self.searches[self.current_search_name] = unicode(self.search_text.toPlainText())
|
||||
name = unicode(self.search_name_box.itemText(idx))
|
||||
if name:
|
||||
self.current_search_name = name
|
||||
self.search_text.setPlainText(self.searches[name])
|
||||
else:
|
||||
self.current_search_name = None
|
||||
self.search_text.setPlainText('')
|
||||
|
||||
def accept(self):
|
||||
if self.current_search_name:
|
||||
self.searches[self.current_search_name] = unicode(self.search_text.toPlainText())
|
||||
for name in self.searches_to_delete:
|
||||
saved_searches.delete(name)
|
||||
for name in self.searches:
|
||||
saved_searches.add(name, self.searches[name])
|
||||
QDialog.accept(self)
|
181
src/calibre/gui2/dialogs/saved_search_editor.ui
Normal file
181
src/calibre/gui2/dialogs/saved_search_editor.ui
Normal file
@ -0,0 +1,181 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>SavedSearchEditor</class>
|
||||
<widget class="QDialog" name="SavedSearchEditor">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>548</width>
|
||||
<height>148</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Tag Editor</string>
|
||||
</property>
|
||||
<property name="windowIcon">
|
||||
<iconset>
|
||||
<normaloff>:/images/chapters.svg</normaloff>:/images/chapters.svg</iconset>
|
||||
</property>
|
||||
<layout class="QGridLayout">
|
||||
<item row="2" column="0" colspan="2">
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
<property name="centerButtons">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0" colspan="2">
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>100</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Saved Search: </string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>search_name_box</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="search_name_box">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>160</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>145</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Select a saved search to edit</string>
|
||||
</property>
|
||||
<property name="editable">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="2">
|
||||
<widget class="QToolButton" name="delete_search_button">
|
||||
<property name="toolTip">
|
||||
<string>Delete this selected saved search</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset>
|
||||
<normaloff>:/images/minus.svg</normaloff>:/images/minus.svg</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="3">
|
||||
<spacer>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="0" column="4">
|
||||
<widget class="QLineEdit" name="input_box">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>60</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Enter a new saved search name.</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="5">
|
||||
<widget class="QToolButton" name="add_search_button">
|
||||
<property name="toolTip">
|
||||
<string>Add the new saved search</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset>
|
||||
<normaloff>:/images/plus.svg</normaloff>:/images/plus.svg</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QPlainTextEdit" name="search_text"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources>
|
||||
<include location="../../../../../calibre_datesearch/resources/images"/>
|
||||
</resources>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>SavedSearchEditor</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>248</x>
|
||||
<y>254</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>157</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>SavedSearchEditor</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>316</x>
|
||||
<y>260</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>286</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
@ -24,13 +24,12 @@ class Item:
|
||||
class TagCategories(QDialog, Ui_TagCategories):
|
||||
category_labels_orig = ['', 'authors', 'series', 'publishers', 'tags']
|
||||
|
||||
def __init__(self, window, db, index=None):
|
||||
def __init__(self, window, db, on_category=None):
|
||||
QDialog.__init__(self, window)
|
||||
Ui_TagCategories.__init__(self)
|
||||
self.setupUi(self)
|
||||
|
||||
self.db = db
|
||||
self.index = index
|
||||
self.applied_items = []
|
||||
|
||||
cc_icon = QIcon(I('column.svg'))
|
||||
@ -102,8 +101,10 @@ class TagCategories(QDialog, Ui_TagCategories):
|
||||
self.connect(self.applied_items_box, SIGNAL('itemActivated(QListWidgetItem*)'), self.unapply_tags)
|
||||
|
||||
self.populate_category_list()
|
||||
return
|
||||
self.select_category(0)
|
||||
if on_category is not None:
|
||||
l = self.category_box.findText(on_category)
|
||||
if l >= 0:
|
||||
self.category_box.setCurrentIndex(l)
|
||||
|
||||
def make_list_widget(self, item):
|
||||
n = item.name if item.exists else item.name + _(' (not on any book)')
|
||||
|
55
src/calibre/gui2/dialogs/tag_list_editor.py
Normal file
55
src/calibre/gui2/dialogs/tag_list_editor.py
Normal file
@ -0,0 +1,55 @@
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
from PyQt4.QtCore import SIGNAL
|
||||
from PyQt4.QtGui import QDialog
|
||||
|
||||
from calibre.gui2.dialogs.tag_list_editor_ui import Ui_TagListEditor
|
||||
from calibre.gui2 import question_dialog, error_dialog
|
||||
|
||||
class TagListEditor(QDialog, Ui_TagListEditor):
|
||||
|
||||
def tag_cmp(self, x, y):
|
||||
return cmp(x.lower(), y.lower())
|
||||
|
||||
def __init__(self, window, db):
|
||||
QDialog.__init__(self, window)
|
||||
Ui_TagListEditor.__init__(self)
|
||||
self.setupUi(self)
|
||||
|
||||
self.to_delete = []
|
||||
self.db = db
|
||||
all_tags = [tag for tag in self.db.all_tags()]
|
||||
all_tags = list(set(all_tags))
|
||||
all_tags.sort(cmp=self.tag_cmp)
|
||||
for tag in all_tags:
|
||||
self.available_tags.addItem(tag)
|
||||
|
||||
self.connect(self.delete_button, SIGNAL('clicked()'), self.delete_tags)
|
||||
|
||||
def delete_tags(self, item=None):
|
||||
confirms, deletes = [], []
|
||||
items = self.available_tags.selectedItems() if item is None else [item]
|
||||
if not items:
|
||||
error_dialog(self, 'No tags selected', 'You must select at least one tag from the list of Available tags.').exec_()
|
||||
return
|
||||
for item in items:
|
||||
if self.db.is_tag_used(unicode(item.text())):
|
||||
confirms.append(item)
|
||||
else:
|
||||
deletes.append(item)
|
||||
if confirms:
|
||||
ct = ', '.join([unicode(item.text()) for item in confirms])
|
||||
if question_dialog(self, _('Are your sure?'),
|
||||
'<p>'+_('The following tags are used by one or more books. '
|
||||
'Are you certain you want to delete them?')+'<br>'+ct):
|
||||
deletes += confirms
|
||||
|
||||
for item in deletes:
|
||||
self.to_delete.append(item)
|
||||
self.available_tags.takeItem(self.available_tags.row(item))
|
||||
|
||||
def accept(self):
|
||||
for item in self.to_delete:
|
||||
self.db.delete_tag(unicode(item.text()))
|
||||
QDialog.accept(self)
|
||||
|
132
src/calibre/gui2/dialogs/tag_list_editor.ui
Normal file
132
src/calibre/gui2/dialogs/tag_list_editor.ui
Normal file
@ -0,0 +1,132 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>TagListEditor</class>
|
||||
<widget class="QDialog" name="TagListEditor">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>397</width>
|
||||
<height>335</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Tag Editor</string>
|
||||
</property>
|
||||
<property name="windowIcon">
|
||||
<iconset>
|
||||
<normaloff>:/images/chapters.svg</normaloff>:/images/chapters.svg</iconset>
|
||||
</property>
|
||||
<layout class="QGridLayout">
|
||||
<item row="0" column="0">
|
||||
<layout class="QVBoxLayout">
|
||||
<item>
|
||||
<layout class="QHBoxLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Tags in use</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>available_tags</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout">
|
||||
<item>
|
||||
<widget class="QToolButton" name="delete_button">
|
||||
<property name="toolTip">
|
||||
<string>Delete tag from database. This will unapply the tag from all books and then remove it from the database.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset>
|
||||
<normaloff>:/images/trash.svg</normaloff>:/images/trash.svg</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QListWidget" name="available_tags">
|
||||
<property name="alternatingRowColors">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="selectionMode">
|
||||
<enum>QAbstractItemView::MultiSelection</enum>
|
||||
</property>
|
||||
<property name="selectionBehavior">
|
||||
<enum>QAbstractItemView::SelectRows</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="1" column="0" colspan="2">
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources>
|
||||
<include location="../../../../resources/images.qrc"/>
|
||||
</resources>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>TagListEditor</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>248</x>
|
||||
<y>254</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>157</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>TagListEditor</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>316</x>
|
||||
<y>260</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>286</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
@ -8,10 +8,11 @@ Browsing book collection by tags.
|
||||
'''
|
||||
|
||||
from itertools import izip
|
||||
from functools import partial
|
||||
|
||||
from PyQt4.Qt import Qt, QTreeView, QApplication, pyqtSignal, \
|
||||
QFont, QSize, QIcon, QPoint, \
|
||||
QAbstractItemModel, QVariant, QModelIndex
|
||||
QAbstractItemModel, QVariant, QModelIndex, QMenu
|
||||
from calibre.gui2 import config, NONE
|
||||
from calibre.utils.config import prefs
|
||||
from calibre.library.field_metadata import TagsIcons
|
||||
@ -19,9 +20,12 @@ from calibre.utils.search_query_parser import saved_searches
|
||||
|
||||
class TagsView(QTreeView): # {{{
|
||||
|
||||
need_refresh = pyqtSignal()
|
||||
restriction_set = pyqtSignal(object)
|
||||
tags_marked = pyqtSignal(object, object)
|
||||
need_refresh = pyqtSignal()
|
||||
restriction_set = pyqtSignal(object)
|
||||
tags_marked = pyqtSignal(object, object)
|
||||
user_category_edit = pyqtSignal(object)
|
||||
tag_list_edit = pyqtSignal()
|
||||
saved_search_edit = pyqtSignal(object)
|
||||
|
||||
def __init__(self, *args):
|
||||
QTreeView.__init__(self, *args)
|
||||
@ -31,13 +35,16 @@ class TagsView(QTreeView): # {{{
|
||||
self.tag_match = None
|
||||
|
||||
def set_database(self, db, tag_match, popularity, restriction):
|
||||
self._model = TagsModel(db, parent=self)
|
||||
self.hidden_categories = config['tag_browser_hidden_categories']
|
||||
self._model = TagsModel(db, parent=self, hidden_columns=self.hidden_categories)
|
||||
self.popularity = popularity
|
||||
self.restriction = restriction
|
||||
self.tag_match = tag_match
|
||||
self.db = db
|
||||
self.setModel(self._model)
|
||||
self.setContextMenuPolicy(Qt.CustomContextMenu)
|
||||
self.clicked.connect(self.toggle)
|
||||
self.customContextMenuRequested.connect(self.show_context_menu)
|
||||
self.popularity.setChecked(config['sort_by_popularity'])
|
||||
self.popularity.stateChanged.connect(self.sort_changed)
|
||||
self.restriction.activated[str].connect(self.search_restriction_set)
|
||||
@ -45,10 +52,6 @@ class TagsView(QTreeView): # {{{
|
||||
db.add_listener(self.database_changed)
|
||||
self.saved_searches_changed(recount=False)
|
||||
|
||||
def create_tag_category(self, name, tag_list):
|
||||
self._model.create_tag_category(name, tag_list)
|
||||
self.recount()
|
||||
|
||||
def database_changed(self, event, ids):
|
||||
self.need_refresh.emit()
|
||||
|
||||
@ -72,12 +75,91 @@ class TagsView(QTreeView): # {{{
|
||||
self.recount() # Must happen after the emission of the restriction_set signal
|
||||
self.tags_marked.emit(self._model.tokens(), self.match_all)
|
||||
|
||||
def mouseReleaseEvent(self, event):
|
||||
# Swallow everything except leftButton so context menus work correctly
|
||||
if event.button() == Qt.LeftButton:
|
||||
QTreeView.mouseReleaseEvent(self, event)
|
||||
|
||||
def toggle(self, index):
|
||||
modifiers = int(QApplication.keyboardModifiers())
|
||||
exclusive = modifiers not in (Qt.CTRL, Qt.SHIFT)
|
||||
if self._model.toggle(index, exclusive):
|
||||
self.tags_marked.emit(self._model.tokens(), self.match_all)
|
||||
|
||||
def context_menu_handler(self, action=None, category=None):
|
||||
if not action:
|
||||
return
|
||||
try:
|
||||
if action == 'manage_tags':
|
||||
self.tag_list_edit.emit();
|
||||
return
|
||||
if action == 'manage_categories':
|
||||
self.user_category_edit.emit(category)
|
||||
return
|
||||
if action == 'manage_searches':
|
||||
self.saved_search_edit.emit(category)
|
||||
return
|
||||
if action == 'hide':
|
||||
self.hidden_categories.add(category)
|
||||
elif action == 'show':
|
||||
self.hidden_categories.discard(category)
|
||||
elif action == 'defaults':
|
||||
self.hidden_categories.clear()
|
||||
config.set('tag_browser_hidden_categories', self.hidden_categories)
|
||||
self.set_new_model()
|
||||
except:
|
||||
return
|
||||
|
||||
def show_context_menu(self, point):
|
||||
index = self.indexAt(point)
|
||||
if not index.isValid():
|
||||
return False
|
||||
item = index.internalPointer()
|
||||
tag_name = ''
|
||||
if item.type == TagTreeItem.TAG:
|
||||
tag_name = item.tag.name
|
||||
item = item.parent
|
||||
if item.type == TagTreeItem.CATEGORY:
|
||||
category = unicode(item.name.toString())
|
||||
self.context_menu = QMenu(self)
|
||||
self.context_menu.addAction(_('Hide column %s') % category,
|
||||
partial(self.context_menu_handler, action='hide', category=category))
|
||||
|
||||
if self.hidden_categories:
|
||||
self.context_menu.addSeparator()
|
||||
m = self.context_menu.addMenu(_('Show column'))
|
||||
for col in self.hidden_categories:
|
||||
m.addAction(col,
|
||||
partial(self.context_menu_handler, action='show', category=col))
|
||||
self.context_menu.addSeparator()
|
||||
self.context_menu.addAction(_('Restore defaults'),
|
||||
partial(self.context_menu_handler, action='defaults'))
|
||||
|
||||
self.context_menu.addSeparator()
|
||||
self.context_menu.addAction(_('Manage Tags'),
|
||||
partial(self.context_menu_handler, action='manage_tags'))
|
||||
|
||||
if category in prefs['user_categories'].keys():
|
||||
self.context_menu.addAction(_('Manage User Categories'),
|
||||
partial(self.context_menu_handler, action='manage_categories',
|
||||
category=category))
|
||||
else:
|
||||
self.context_menu.addAction(_('Manage User Categories'),
|
||||
partial(self.context_menu_handler, action='manage_categories',
|
||||
category=None))
|
||||
|
||||
if tag_name in saved_searches.names():
|
||||
self.context_menu.addAction(_('Manage Saved Searches'),
|
||||
partial(self.context_menu_handler, action='manage_searches',
|
||||
category=tag_name))
|
||||
else:
|
||||
self.context_menu.addAction(_('Manage Saved Searches'),
|
||||
partial(self.context_menu_handler, action='manage_searches',
|
||||
category=None))
|
||||
|
||||
self.context_menu.popup(self.mapToGlobal(point))
|
||||
return True;
|
||||
|
||||
def clear(self):
|
||||
self.model().clear_state()
|
||||
|
||||
@ -110,13 +192,12 @@ class TagsView(QTreeView): # {{{
|
||||
self.setCurrentIndex(idx)
|
||||
self.scrollTo(idx, QTreeView.PositionAtCenter)
|
||||
|
||||
'''
|
||||
If the number of user categories changed, or if custom columns have come or gone,
|
||||
we must rebuild the model. Reason: it is much easier to do that than to reconstruct
|
||||
the browser tree.
|
||||
'''
|
||||
# If the number of user categories changed, if custom columns have come or
|
||||
# gone, or if columns have been hidden or restored, we must rebuild the
|
||||
# model. Reason: it is much easier than reconstructing the browser tree.
|
||||
def set_new_model(self):
|
||||
self._model = TagsModel(self.db, parent=self)
|
||||
self._model = TagsModel(self.db, parent=self,
|
||||
hidden_categories=self.hidden_categories)
|
||||
self.setModel(self._model)
|
||||
# }}}
|
||||
|
||||
@ -200,7 +281,7 @@ class TagTreeItem(object): # {{{
|
||||
|
||||
class TagsModel(QAbstractItemModel): # {{{
|
||||
|
||||
def __init__(self, db, parent=None):
|
||||
def __init__(self, db, parent=None, hidden_categories=None):
|
||||
QAbstractItemModel.__init__(self, parent)
|
||||
|
||||
# must do this here because 'QPixmap: Must construct a QApplication
|
||||
@ -220,6 +301,7 @@ class TagsModel(QAbstractItemModel): # {{{
|
||||
|
||||
self.icon_state_map = [None, QIcon(I('plus.svg')), QIcon(I('minus.svg'))]
|
||||
self.db = db
|
||||
self.hidden_categories = hidden_categories
|
||||
self.search_restriction = ''
|
||||
self.ignore_next_search = 0
|
||||
|
||||
@ -237,6 +319,8 @@ class TagsModel(QAbstractItemModel): # {{{
|
||||
data = self.get_node_tree(config['sort_by_popularity'])
|
||||
self.root_item = TagTreeItem()
|
||||
for i, r in enumerate(self.row_map):
|
||||
if self.categories[i]in self.hidden_categories:
|
||||
continue
|
||||
if self.db.field_metadata[r]['kind'] != 'user':
|
||||
tt = _('The lookup/search name is "{0}"').format(r)
|
||||
else:
|
||||
@ -271,12 +355,16 @@ class TagsModel(QAbstractItemModel): # {{{
|
||||
|
||||
def refresh(self):
|
||||
data = self.get_node_tree(config['sort_by_popularity']) # get category data
|
||||
row_index = -1;
|
||||
for i, r in enumerate(self.row_map):
|
||||
category = self.root_item.children[i]
|
||||
if self.categories[i] in self.hidden_categories:
|
||||
continue
|
||||
row_index += 1
|
||||
category = self.root_item.children[row_index]
|
||||
names = [t.tag.name for t in category.children]
|
||||
states = [t.tag.state for t in category.children]
|
||||
state_map = dict(izip(names, states))
|
||||
category_index = self.index(i, 0, QModelIndex())
|
||||
category_index = self.index(row_index, 0, QModelIndex())
|
||||
if len(category.children) > 0:
|
||||
self.beginRemoveRows(category_index, 0,
|
||||
len(category.children)-1)
|
||||
@ -401,10 +489,14 @@ class TagsModel(QAbstractItemModel): # {{{
|
||||
def tokens(self):
|
||||
ans = []
|
||||
tags_seen = set()
|
||||
row_index = -1;
|
||||
for i, key in enumerate(self.row_map):
|
||||
if self.categories[i] in self.hidden_categories:
|
||||
continue
|
||||
row_index += 1
|
||||
if key.endswith(':'): # User category, so skip it. The tag will be marked in its real category
|
||||
continue
|
||||
category_item = self.root_item.children[i]
|
||||
category_item = self.root_item.children[row_index]
|
||||
for tag_item in category_item.children:
|
||||
tag = tag_item.tag
|
||||
if tag.state > 0:
|
||||
|
@ -61,6 +61,8 @@ from calibre.library.database2 import LibraryDatabase2
|
||||
from calibre.library.caches import CoverCache
|
||||
from calibre.gui2.dialogs.confirm_delete import confirm
|
||||
from calibre.gui2.dialogs.tag_categories import TagCategories
|
||||
from calibre.gui2.dialogs.tag_list_editor import TagListEditor
|
||||
from calibre.gui2.dialogs.saved_search_editor import SavedSearchEditor
|
||||
|
||||
class SaveMenu(QMenu):
|
||||
|
||||
@ -537,19 +539,23 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
||||
self.cover_cache = CoverCache(self.library_path)
|
||||
self.cover_cache.start()
|
||||
self.library_view.model().cover_cache = self.cover_cache
|
||||
self.connect(self.edit_categories, SIGNAL('clicked()'), self.do_edit_categories)
|
||||
self.connect(self.edit_categories, SIGNAL('clicked()'), self.do_user_categories_edit)
|
||||
self.tags_view.set_database(db, self.tag_match, self.popularity, self.search_restriction)
|
||||
self.tags_view.tags_marked.connect(self.search.search_from_tags)
|
||||
for x in (self.saved_search.clear_to_help, self.mark_restriction_set):
|
||||
self.tags_view.restriction_set.connect(x)
|
||||
self.tags_view.tags_marked.connect(self.saved_search.clear_to_help)
|
||||
self.tags_view.tag_list_edit.connect(self.do_tags_list_edit)
|
||||
self.tags_view.user_category_edit.connect(self.do_user_categories_edit)
|
||||
self.tags_view.saved_search_edit.connect(self.do_saved_search_edit)
|
||||
self.search.search.connect(self.tags_view.model().reinit)
|
||||
for x in (self.location_view.count_changed, self.tags_view.recount,
|
||||
self.restriction_count_changed):
|
||||
self.library_view.model().count_changed_signal.connect(x)
|
||||
|
||||
self.connect(self.search, SIGNAL('cleared()'), self.search_box_cleared)
|
||||
self.connect(self.saved_search, SIGNAL('changed()'), self.tags_view.saved_searches_changed, Qt.QueuedConnection)
|
||||
self.connect(self.saved_search, SIGNAL('changed()'),
|
||||
self.tags_view.saved_searches_changed, Qt.QueuedConnection)
|
||||
if not gprefs.get('quick_start_guide_added', False):
|
||||
from calibre.ebooks.metadata import MetaInformation
|
||||
mi = MetaInformation(_('Calibre Quick Start Guide'), ['John Schember'])
|
||||
@ -642,13 +648,28 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
||||
self._add_filesystem_book = Dispatcher(self.__add_filesystem_book)
|
||||
self.keyboard_interrupt.connect(self.quit, type=Qt.QueuedConnection)
|
||||
|
||||
def do_edit_categories(self):
|
||||
d = TagCategories(self, self.library_view.model().db)
|
||||
def do_user_categories_edit(self, on_category=None):
|
||||
d = TagCategories(self, self.library_view.model().db, on_category)
|
||||
d.exec_()
|
||||
if d.result() == d.Accepted:
|
||||
self.tags_view.set_new_model()
|
||||
self.tags_view.recount()
|
||||
|
||||
def do_tags_list_edit(self):
|
||||
d = TagListEditor(self, self.library_view.model().db)
|
||||
d.exec_()
|
||||
if d.result() == d.Accepted:
|
||||
self.tags_view.set_new_model()
|
||||
self.tags_view.recount()
|
||||
self.library_view.model().refresh()
|
||||
|
||||
def do_saved_search_edit(self, search):
|
||||
d = SavedSearchEditor(self, search)
|
||||
d.exec_()
|
||||
if d.result() == d.Accepted:
|
||||
self.tags_view.saved_searches_changed(recount=True)
|
||||
self.saved_search.clear_to_help()
|
||||
|
||||
def resizeEvent(self, ev):
|
||||
MainWindow.resizeEvent(self, ev)
|
||||
self.search.setMaximumWidth(self.width()-150)
|
||||
|
Loading…
x
Reference in New Issue
Block a user