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'))
|
help=_('Overwrite author and title with new metadata'))
|
||||||
c.add_opt('enforce_cpu_limit', default=True,
|
c.add_opt('enforce_cpu_limit', default=True,
|
||||||
help=_('Limit max simultaneous jobs to number of CPUs'))
|
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)
|
return ConfigProxy(c)
|
||||||
|
|
||||||
config = _config()
|
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):
|
class TagCategories(QDialog, Ui_TagCategories):
|
||||||
category_labels_orig = ['', 'authors', 'series', 'publishers', 'tags']
|
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)
|
QDialog.__init__(self, window)
|
||||||
Ui_TagCategories.__init__(self)
|
Ui_TagCategories.__init__(self)
|
||||||
self.setupUi(self)
|
self.setupUi(self)
|
||||||
|
|
||||||
self.db = db
|
self.db = db
|
||||||
self.index = index
|
|
||||||
self.applied_items = []
|
self.applied_items = []
|
||||||
|
|
||||||
cc_icon = QIcon(I('column.svg'))
|
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.connect(self.applied_items_box, SIGNAL('itemActivated(QListWidgetItem*)'), self.unapply_tags)
|
||||||
|
|
||||||
self.populate_category_list()
|
self.populate_category_list()
|
||||||
return
|
if on_category is not None:
|
||||||
self.select_category(0)
|
l = self.category_box.findText(on_category)
|
||||||
|
if l >= 0:
|
||||||
|
self.category_box.setCurrentIndex(l)
|
||||||
|
|
||||||
def make_list_widget(self, item):
|
def make_list_widget(self, item):
|
||||||
n = item.name if item.exists else item.name + _(' (not on any book)')
|
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 itertools import izip
|
||||||
|
from functools import partial
|
||||||
|
|
||||||
from PyQt4.Qt import Qt, QTreeView, QApplication, pyqtSignal, \
|
from PyQt4.Qt import Qt, QTreeView, QApplication, pyqtSignal, \
|
||||||
QFont, QSize, QIcon, QPoint, \
|
QFont, QSize, QIcon, QPoint, \
|
||||||
QAbstractItemModel, QVariant, QModelIndex
|
QAbstractItemModel, QVariant, QModelIndex, QMenu
|
||||||
from calibre.gui2 import config, NONE
|
from calibre.gui2 import config, NONE
|
||||||
from calibre.utils.config import prefs
|
from calibre.utils.config import prefs
|
||||||
from calibre.library.field_metadata import TagsIcons
|
from calibre.library.field_metadata import TagsIcons
|
||||||
@ -19,9 +20,12 @@ from calibre.utils.search_query_parser import saved_searches
|
|||||||
|
|
||||||
class TagsView(QTreeView): # {{{
|
class TagsView(QTreeView): # {{{
|
||||||
|
|
||||||
need_refresh = pyqtSignal()
|
need_refresh = pyqtSignal()
|
||||||
restriction_set = pyqtSignal(object)
|
restriction_set = pyqtSignal(object)
|
||||||
tags_marked = pyqtSignal(object, object)
|
tags_marked = pyqtSignal(object, object)
|
||||||
|
user_category_edit = pyqtSignal(object)
|
||||||
|
tag_list_edit = pyqtSignal()
|
||||||
|
saved_search_edit = pyqtSignal(object)
|
||||||
|
|
||||||
def __init__(self, *args):
|
def __init__(self, *args):
|
||||||
QTreeView.__init__(self, *args)
|
QTreeView.__init__(self, *args)
|
||||||
@ -31,13 +35,16 @@ class TagsView(QTreeView): # {{{
|
|||||||
self.tag_match = None
|
self.tag_match = None
|
||||||
|
|
||||||
def set_database(self, db, tag_match, popularity, restriction):
|
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.popularity = popularity
|
||||||
self.restriction = restriction
|
self.restriction = restriction
|
||||||
self.tag_match = tag_match
|
self.tag_match = tag_match
|
||||||
self.db = db
|
self.db = db
|
||||||
self.setModel(self._model)
|
self.setModel(self._model)
|
||||||
|
self.setContextMenuPolicy(Qt.CustomContextMenu)
|
||||||
self.clicked.connect(self.toggle)
|
self.clicked.connect(self.toggle)
|
||||||
|
self.customContextMenuRequested.connect(self.show_context_menu)
|
||||||
self.popularity.setChecked(config['sort_by_popularity'])
|
self.popularity.setChecked(config['sort_by_popularity'])
|
||||||
self.popularity.stateChanged.connect(self.sort_changed)
|
self.popularity.stateChanged.connect(self.sort_changed)
|
||||||
self.restriction.activated[str].connect(self.search_restriction_set)
|
self.restriction.activated[str].connect(self.search_restriction_set)
|
||||||
@ -45,10 +52,6 @@ class TagsView(QTreeView): # {{{
|
|||||||
db.add_listener(self.database_changed)
|
db.add_listener(self.database_changed)
|
||||||
self.saved_searches_changed(recount=False)
|
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):
|
def database_changed(self, event, ids):
|
||||||
self.need_refresh.emit()
|
self.need_refresh.emit()
|
||||||
|
|
||||||
@ -72,12 +75,91 @@ class TagsView(QTreeView): # {{{
|
|||||||
self.recount() # Must happen after the emission of the restriction_set signal
|
self.recount() # Must happen after the emission of the restriction_set signal
|
||||||
self.tags_marked.emit(self._model.tokens(), self.match_all)
|
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):
|
def toggle(self, index):
|
||||||
modifiers = int(QApplication.keyboardModifiers())
|
modifiers = int(QApplication.keyboardModifiers())
|
||||||
exclusive = modifiers not in (Qt.CTRL, Qt.SHIFT)
|
exclusive = modifiers not in (Qt.CTRL, Qt.SHIFT)
|
||||||
if self._model.toggle(index, exclusive):
|
if self._model.toggle(index, exclusive):
|
||||||
self.tags_marked.emit(self._model.tokens(), self.match_all)
|
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):
|
def clear(self):
|
||||||
self.model().clear_state()
|
self.model().clear_state()
|
||||||
|
|
||||||
@ -110,13 +192,12 @@ class TagsView(QTreeView): # {{{
|
|||||||
self.setCurrentIndex(idx)
|
self.setCurrentIndex(idx)
|
||||||
self.scrollTo(idx, QTreeView.PositionAtCenter)
|
self.scrollTo(idx, QTreeView.PositionAtCenter)
|
||||||
|
|
||||||
'''
|
# If the number of user categories changed, if custom columns have come or
|
||||||
If the number of user categories changed, or if custom columns have come or gone,
|
# gone, or if columns have been hidden or restored, we must rebuild the
|
||||||
we must rebuild the model. Reason: it is much easier to do that than to reconstruct
|
# model. Reason: it is much easier than reconstructing the browser tree.
|
||||||
the browser tree.
|
|
||||||
'''
|
|
||||||
def set_new_model(self):
|
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)
|
self.setModel(self._model)
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
@ -200,7 +281,7 @@ class TagTreeItem(object): # {{{
|
|||||||
|
|
||||||
class TagsModel(QAbstractItemModel): # {{{
|
class TagsModel(QAbstractItemModel): # {{{
|
||||||
|
|
||||||
def __init__(self, db, parent=None):
|
def __init__(self, db, parent=None, hidden_categories=None):
|
||||||
QAbstractItemModel.__init__(self, parent)
|
QAbstractItemModel.__init__(self, parent)
|
||||||
|
|
||||||
# must do this here because 'QPixmap: Must construct a QApplication
|
# 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.icon_state_map = [None, QIcon(I('plus.svg')), QIcon(I('minus.svg'))]
|
||||||
self.db = db
|
self.db = db
|
||||||
|
self.hidden_categories = hidden_categories
|
||||||
self.search_restriction = ''
|
self.search_restriction = ''
|
||||||
self.ignore_next_search = 0
|
self.ignore_next_search = 0
|
||||||
|
|
||||||
@ -237,6 +319,8 @@ class TagsModel(QAbstractItemModel): # {{{
|
|||||||
data = self.get_node_tree(config['sort_by_popularity'])
|
data = self.get_node_tree(config['sort_by_popularity'])
|
||||||
self.root_item = TagTreeItem()
|
self.root_item = TagTreeItem()
|
||||||
for i, r in enumerate(self.row_map):
|
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':
|
if self.db.field_metadata[r]['kind'] != 'user':
|
||||||
tt = _('The lookup/search name is "{0}"').format(r)
|
tt = _('The lookup/search name is "{0}"').format(r)
|
||||||
else:
|
else:
|
||||||
@ -271,12 +355,16 @@ class TagsModel(QAbstractItemModel): # {{{
|
|||||||
|
|
||||||
def refresh(self):
|
def refresh(self):
|
||||||
data = self.get_node_tree(config['sort_by_popularity']) # get category data
|
data = self.get_node_tree(config['sort_by_popularity']) # get category data
|
||||||
|
row_index = -1;
|
||||||
for i, r in enumerate(self.row_map):
|
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]
|
names = [t.tag.name for t in category.children]
|
||||||
states = [t.tag.state for t in category.children]
|
states = [t.tag.state for t in category.children]
|
||||||
state_map = dict(izip(names, states))
|
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:
|
if len(category.children) > 0:
|
||||||
self.beginRemoveRows(category_index, 0,
|
self.beginRemoveRows(category_index, 0,
|
||||||
len(category.children)-1)
|
len(category.children)-1)
|
||||||
@ -401,10 +489,14 @@ class TagsModel(QAbstractItemModel): # {{{
|
|||||||
def tokens(self):
|
def tokens(self):
|
||||||
ans = []
|
ans = []
|
||||||
tags_seen = set()
|
tags_seen = set()
|
||||||
|
row_index = -1;
|
||||||
for i, key in enumerate(self.row_map):
|
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
|
if key.endswith(':'): # User category, so skip it. The tag will be marked in its real category
|
||||||
continue
|
continue
|
||||||
category_item = self.root_item.children[i]
|
category_item = self.root_item.children[row_index]
|
||||||
for tag_item in category_item.children:
|
for tag_item in category_item.children:
|
||||||
tag = tag_item.tag
|
tag = tag_item.tag
|
||||||
if tag.state > 0:
|
if tag.state > 0:
|
||||||
|
@ -61,6 +61,8 @@ from calibre.library.database2 import LibraryDatabase2
|
|||||||
from calibre.library.caches import CoverCache
|
from calibre.library.caches import CoverCache
|
||||||
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.saved_search_editor import SavedSearchEditor
|
||||||
|
|
||||||
class SaveMenu(QMenu):
|
class SaveMenu(QMenu):
|
||||||
|
|
||||||
@ -537,19 +539,23 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
|||||||
self.cover_cache = CoverCache(self.library_path)
|
self.cover_cache = CoverCache(self.library_path)
|
||||||
self.cover_cache.start()
|
self.cover_cache.start()
|
||||||
self.library_view.model().cover_cache = self.cover_cache
|
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.set_database(db, self.tag_match, self.popularity, self.search_restriction)
|
||||||
self.tags_view.tags_marked.connect(self.search.search_from_tags)
|
self.tags_view.tags_marked.connect(self.search.search_from_tags)
|
||||||
for x in (self.saved_search.clear_to_help, self.mark_restriction_set):
|
for x in (self.saved_search.clear_to_help, self.mark_restriction_set):
|
||||||
self.tags_view.restriction_set.connect(x)
|
self.tags_view.restriction_set.connect(x)
|
||||||
self.tags_view.tags_marked.connect(self.saved_search.clear_to_help)
|
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)
|
self.search.search.connect(self.tags_view.model().reinit)
|
||||||
for x in (self.location_view.count_changed, self.tags_view.recount,
|
for x in (self.location_view.count_changed, self.tags_view.recount,
|
||||||
self.restriction_count_changed):
|
self.restriction_count_changed):
|
||||||
self.library_view.model().count_changed_signal.connect(x)
|
self.library_view.model().count_changed_signal.connect(x)
|
||||||
|
|
||||||
self.connect(self.search, SIGNAL('cleared()'), self.search_box_cleared)
|
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):
|
if not gprefs.get('quick_start_guide_added', False):
|
||||||
from calibre.ebooks.metadata import MetaInformation
|
from calibre.ebooks.metadata import MetaInformation
|
||||||
mi = MetaInformation(_('Calibre Quick Start Guide'), ['John Schember'])
|
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._add_filesystem_book = Dispatcher(self.__add_filesystem_book)
|
||||||
self.keyboard_interrupt.connect(self.quit, type=Qt.QueuedConnection)
|
self.keyboard_interrupt.connect(self.quit, type=Qt.QueuedConnection)
|
||||||
|
|
||||||
def do_edit_categories(self):
|
def do_user_categories_edit(self, on_category=None):
|
||||||
d = TagCategories(self, self.library_view.model().db)
|
d = TagCategories(self, self.library_view.model().db, on_category)
|
||||||
d.exec_()
|
d.exec_()
|
||||||
if d.result() == d.Accepted:
|
if d.result() == d.Accepted:
|
||||||
self.tags_view.set_new_model()
|
self.tags_view.set_new_model()
|
||||||
self.tags_view.recount()
|
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):
|
def resizeEvent(self, ev):
|
||||||
MainWindow.resizeEvent(self, ev)
|
MainWindow.resizeEvent(self, ev)
|
||||||
self.search.setMaximumWidth(self.width()-150)
|
self.search.setMaximumWidth(self.width()-150)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user