This commit is contained in:
Kovid Goyal 2025-01-20 20:43:51 +05:30
commit a26eba374d
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
14 changed files with 1059 additions and 625 deletions

View File

@ -487,6 +487,8 @@ def create_defs():
defs['light_palettes'] = {}
defs['saved_layouts'] = {}
defs['book_details_note_link_icon_width'] = 1.0
defs['tag_browser_show_category_icons'] = True
defs['tag_browser_show_value_icons'] = True
def migrate_tweak(tweak_name, pref_name):
# If the tweak has been changed then leave the tweak in the file so

View File

@ -88,7 +88,7 @@ class Configure(Dialog):
Dialog.__init__(self, _('Configure the Book details window'), 'book-details-popup-conf', parent)
def setup_ui(self):
from calibre.gui2.preferences.look_feel import DisplayedFields, move_field_down, move_field_up
from calibre.gui2.preferences.look_feel_tabs import DisplayedFields, move_field_down, move_field_up
self.l = QVBoxLayout(self)
self.field_display_order = fdo = QListView(self)
self.model = DisplayedFields(self.db, fdo, pref_name='popup_book_display_fields')

View File

@ -20,6 +20,7 @@ from qt.core import (
QListView,
QListWidget,
Qt,
QTabWidget,
QTableWidget,
QVBoxLayout,
QWidget,
@ -112,6 +113,11 @@ class ConfigWidgetInterface:
'''
pass
def do_on_child_tabs(self, method, *args):
r = False
for t in self.child_tabs:
r = r | bool(getattr(t, method)(*args))
return r
def set_help_tips(gui_obj, tt):
if tt:
@ -285,6 +291,10 @@ class ConfigWidgetBase(QWidget, ConfigWidgetInterface):
if hasattr(self, 'setupUi'):
self.setupUi(self)
self.settings = {}
self.child_tabs = []
for v in self.__dict__.values():
if isinstance(v, ConfigTabWidget):
self.child_tabs.append(v)
def register(self, name, config_obj, gui_name=None, choices=None,
restart_required=False, empty_string_is_None=True, setting=Setting):
@ -328,6 +338,9 @@ class ConfigWidgetBase(QWidget, ConfigWidgetInterface):
for setting in self.settings.values():
setting.restore_defaults()
def register_child_tab(self, tab):
self.child_tabs.append(tab)
def get_plugin(category, name):
for plugin in preferences_plugins():
@ -338,6 +351,12 @@ def get_plugin(category, name):
(category, name))
class ConfigTabWidget(ConfigWidgetBase):
def set_changed_signal(self, changed_signal):
self.changed_signal.connect(changed_signal)
class ConfigDialog(QDialog):
def set_widget(self, w):
@ -411,7 +430,9 @@ def show_config_widget(category, name, gui=None, show_restart_msg=False,
gui = init_gui()
mygui = True
w.genesis(gui)
w.do_on_child_tabs('genesis', gui)
w.initialize()
w.do_on_child_tabs('initialize')
d.restore_geometry(gprefs, conf_name)
d.exec()
d.save_geometry(gprefs, conf_name)

View File

@ -11,7 +11,6 @@ from functools import partial
from threading import Thread
from qt.core import (
QAbstractListModel,
QApplication,
QBrush,
QColor,
@ -25,7 +24,6 @@ from qt.core import (
QFormLayout,
QHeaderView,
QIcon,
QItemSelectionModel,
QKeySequence,
QLabel,
QLineEdit,
@ -61,13 +59,12 @@ from calibre.gui2 import (
question_dialog,
)
from calibre.gui2.actions.show_quickview import get_quickview_action_plugin
from calibre.gui2.book_details import get_field_list
from calibre.gui2.custom_column_widgets import get_field_list as em_get_field_list
from calibre.gui2.dialogs.quickview import get_qv_field_list
from calibre.gui2.dialogs.template_dialog import TemplateDialog
from calibre.gui2.library.alternate_views import CM_TO_INCH, auto_height
from calibre.gui2.preferences import ConfigWidgetBase, Setting, set_help_tips, test_widget
from calibre.gui2.preferences.coloring import EditRules
from calibre.gui2.preferences.look_feel_tabs import DisplayedFields, move_field_down, move_field_up
from calibre.gui2.preferences.look_feel_ui import Ui_Form
from calibre.gui2.widgets import BusyCursor
from calibre.gui2.widgets2 import Dialog
@ -256,118 +253,6 @@ class IdLinksEditor(Dialog):
# }}}
class DisplayedFields(QAbstractListModel): # {{{
def __init__(self, db, parent=None, pref_name=None, category_icons=None):
self.pref_name = pref_name or 'book_display_fields'
QAbstractListModel.__init__(self, parent)
self.fields = []
self.db = db
self.changed = False
self.category_icons = category_icons
def get_field_list(self, use_defaults=False):
return get_field_list(self.db.field_metadata, use_defaults=use_defaults, pref_name=self.pref_name)
def initialize(self, use_defaults=False):
self.beginResetModel()
self.fields = [[x[0], x[1]] for x in self.get_field_list(use_defaults=use_defaults)]
self.endResetModel()
self.changed = True
def rowCount(self, *args):
return len(self.fields)
def data(self, index, role):
try:
field, visible = self.fields[index.row()]
except:
return None
if role == Qt.ItemDataRole.DisplayRole:
name = field
try:
name = self.db.field_metadata[field]['name']
except:
pass
if field == 'path':
name = _('Folders/path')
name = field.partition('.')[0][1:] if field.startswith('@') else name
if not name:
return field
return f'{name} ({field})'
if role == Qt.ItemDataRole.CheckStateRole:
return Qt.CheckState.Checked if visible else Qt.CheckState.Unchecked
if role == Qt.ItemDataRole.DecorationRole:
if self.category_icons:
icon = self.category_icons.get(field, None)
if icon is not None:
return icon
if field.startswith('#'):
return QIcon.ic('column.png')
return None
def toggle_all(self, show=True):
for i in range(self.rowCount()):
idx = self.index(i)
if idx.isValid():
self.setData(idx, Qt.CheckState.Checked if show else Qt.CheckState.Unchecked, Qt.ItemDataRole.CheckStateRole)
def flags(self, index):
ans = QAbstractListModel.flags(self, index)
return ans | Qt.ItemFlag.ItemIsUserCheckable
def setData(self, index, val, role):
ret = False
if role == Qt.ItemDataRole.CheckStateRole:
self.fields[index.row()][1] = val in (Qt.CheckState.Checked, Qt.CheckState.Checked.value)
self.changed = True
ret = True
self.dataChanged.emit(index, index)
return ret
def restore_defaults(self):
self.initialize(use_defaults=True)
def commit(self):
if self.changed:
self.db.new_api.set_pref(self.pref_name, self.fields)
def move(self, idx, delta):
row = idx.row() + delta
if row >= 0 and row < len(self.fields):
t = self.fields[row]
self.fields[row] = self.fields[row-delta]
self.fields[row-delta] = t
self.dataChanged.emit(idx, idx)
idx = self.index(row)
self.dataChanged.emit(idx, idx)
self.changed = True
return idx
def move_field_up(widget, model):
idx = widget.currentIndex()
if idx.isValid():
idx = model.move(idx, -1)
if idx is not None:
sm = widget.selectionModel()
sm.select(idx, QItemSelectionModel.SelectionFlag.ClearAndSelect)
widget.setCurrentIndex(idx)
def move_field_down(widget, model):
idx = widget.currentIndex()
if idx.isValid():
idx = model.move(idx, 1)
if idx is not None:
sm = widget.selectionModel()
sm.select(idx, QItemSelectionModel.SelectionFlag.ClearAndSelect)
widget.setCurrentIndex(idx)
# }}}
class EMDisplayedFields(DisplayedFields): # {{{
def __init__(self, db, parent=None):
DisplayedFields.__init__(self, db, parent)
@ -486,44 +371,6 @@ class TBPartitionedFields(DisplayedFields): # {{{
# }}}
class TBHierarchicalFields(DisplayedFields): # {{{
# The code in this class depends on the fact that the tag browser is
# initialized before this class is instantiated.
cant_make_hierarical = {'authors', 'publisher', 'formats', 'news',
'identifiers', 'languages', 'rating'}
def __init__(self, db, parent=None, category_icons=None):
DisplayedFields.__init__(self, db, parent, category_icons=category_icons)
from calibre.gui2.ui import get_gui
self.gui = get_gui()
def initialize(self, use_defaults=False, pref_data_override=None):
tv = self.gui.tags_view
cats = [k for k in tv.model().categories.keys() if (not k.startswith('@') and
k not in self.cant_make_hierarical)]
ans = []
if use_defaults:
ans = [[k, False] for k in cats]
self.changed = True
elif pref_data_override:
ph = {k:v for k,v in pref_data_override}
ans = [[k, ph.get(k, False)] for k in cats]
self.changed = True
else:
hier_cats = self.db.prefs.get('categories_using_hierarchy') or ()
for key in cats:
ans.append([key, key in hier_cats])
self.beginResetModel()
self.fields = ans
self.endResetModel()
def commit(self):
if self.changed:
self.db.prefs.set('categories_using_hierarchy', [k for k,v in self.fields if v])
# }}}
class BDVerticalCats(DisplayedFields): # {{{
def __init__(self, db, parent=None, category_icons=None):
@ -653,24 +500,12 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
r('cover_grid_show_title', gprefs)
r('tag_browser_show_counts', gprefs)
r('tag_browser_item_padding', gprefs)
r('books_autoscroll_time', gprefs)
r('qv_respects_vls', gprefs)
r('qv_dclick_changes_column', gprefs)
r('qv_retkey_changes_column', gprefs)
r('qv_follows_column', gprefs)
r('cover_flow_queue_length', config, restart_required=True)
r('cover_browser_reflections', gprefs)
r('cover_browser_narrow_view_position', gprefs,
choices=[(_('Automatic'), 'automatic'), # Automatic must be first
(_('On top'), 'on_top'),
(_('On right'), 'on_right')])
r('cover_browser_title_template', db.prefs)
fm = db.field_metadata
r('cover_browser_subtitle_field', db.prefs, choices=[(_('No subtitle'), 'none')] + sorted(
(fm[k].get('name'), k) for k in fm.all_field_keys() if fm[k].get('name')
))
r('emblem_size', gprefs)
r('emblem_position', gprefs, choices=[
(_('Left'), 'left'), (_('Top'), 'top'), (_('Right'), 'right'), (_('Bottom'), 'bottom')])
@ -679,7 +514,6 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
r('book_details_comments_heading_pos', gprefs, choices=[
(_('Never'), 'hide'), (_('Above text'), 'above'), (_('Beside text'), 'side')])
r('book_details_note_link_icon_width', gprefs)
self.cover_browser_title_template_button.clicked.connect(self.edit_cb_title_template)
self.id_links_button.clicked.connect(self.edit_id_link_rules)
def get_esc_lang(l):
@ -712,10 +546,6 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
r('show_splash_screen', gprefs)
r('disable_tray_notification', config)
r('use_roman_numerals_for_series_number', config)
r('separate_cover_flow', config, restart_required=True)
r('cb_fullscreen', gprefs)
r('cb_preserve_aspect_ratio', gprefs)
r('cb_double_click_to_activate', gprefs)
choices = [(_('Off'), 'off'), (_('Small'), 'small'),
(_('Medium-small'), 'mid-small'), (_('Medium'), 'medium'), (_('Large'), 'large')]
@ -807,27 +637,11 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
self.tb_partition_import_layout_button.clicked.connect(partial(self.import_layout,
model=self.tb_categories_to_part_model))
self.tb_hierarchical_cats_model = TBHierarchicalFields(self.gui.current_db, self.tb_hierarchical_cats,
category_icons=self.gui.tags_view.model().category_custom_icons)
self.tb_hierarchical_cats_model.dataChanged.connect(self.changed_signal)
self.tb_hierarchical_cats.setModel(self.tb_hierarchical_cats_model)
self.tb_hierarchy_reset_layout_button.clicked.connect(partial(self.reset_layout,
model=self.tb_hierarchical_cats_model))
self.tb_hierarchy_export_layout_button.clicked.connect(partial(self.export_layout,
model=self.tb_hierarchical_cats_model))
self.tb_hierarchy_import_layout_button.clicked.connect(partial(self.import_layout,
model=self.tb_hierarchical_cats_model))
self.bd_vertical_cats_model = BDVerticalCats(self.gui.current_db, self.tb_hierarchical_cats)
self.bd_vertical_cats_model = BDVerticalCats(self.gui.current_db, self.tb_hierarchy_tab.tb_hierarchical_cats)
self.bd_vertical_cats_model.dataChanged.connect(self.changed_signal)
self.bd_vertical_cats.setModel(self.bd_vertical_cats_model)
self.fill_tb_search_order_box()
self.tb_search_order_up_button.clicked.connect(self.move_tb_search_up)
self.tb_search_order_down_button.clicked.connect(self.move_tb_search_down)
self.tb_search_order.set_movement_functions(self.move_tb_search_up, self.move_tb_search_down)
self.tb_search_order_reset_button.clicked.connect(self.reset_tb_search_order)
self.edit_rules = EditRules(self.tabWidget)
self.edit_rules.changed.connect(self.changed_signal)
self.tabWidget.addTab(self.edit_rules, QIcon.ic('format-fill-color.png'), _('Column &coloring'))
@ -846,8 +660,6 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
keys = [QKeySequence('F11', QKeySequence.SequenceFormat.PortableText), QKeySequence(
'Ctrl+Shift+F', QKeySequence.SequenceFormat.PortableText)]
keys = [str(x.toString(QKeySequence.SequenceFormat.NativeText)) for x in keys]
self.fs_help_msg.setText(self.fs_help_msg.text()%(
QKeySequence(QKeySequence.StandardKey.FullScreen).toString(QKeySequence.SequenceFormat.NativeText)))
self.size_calculated.connect(self.update_cg_cache_size, type=Qt.ConnectionType.QueuedConnection)
self.tabWidget.currentChanged.connect(self.tab_changed)
@ -905,66 +717,6 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
def initial_tab_changed(self):
self.sections_view.setCurrentRow(self.tabWidget.currentIndex())
def fill_tb_search_order_box(self):
# The tb_search_order is a directed graph of nodes with an arc to the next
# node in the sequence. Node 0 (zero) is the start node with the last node
# arcing back to node 0. This code linearizes the graph
choices = [(1, _('Search for books containing the current item')),
(2, _('Search for books containing the current item or its children')),
(3, _('Search for books not containing the current item')),
(4, _('Search for books not containing the current item or its children'))]
icon_map = self.gui.tags_view.model().icon_state_map
order = gprefs.get('tb_search_order')
self.tb_search_order.clear()
node = 0
while True:
v = order[str(node)]
if v == 0:
break
item = QListWidgetItem(icon_map[v], choices[v-1][1])
item.setData(Qt.ItemDataRole.UserRole, choices[v-1][0])
self.tb_search_order.addItem(item)
node = v
def move_tb_search_up(self):
idx = self.tb_search_order.currentRow()
if idx <= 0:
return
item = self.tb_search_order.takeItem(idx)
self.tb_search_order.insertItem(idx-1, item)
self.tb_search_order.setCurrentRow(idx-1)
self.changed_signal.emit()
def move_tb_search_down(self):
idx = self.tb_search_order.currentRow()
if idx < 0 or idx == 3:
return
item = self.tb_search_order.takeItem(idx)
self.tb_search_order.insertItem(idx+1, item)
self.tb_search_order.setCurrentRow(idx+1)
self.changed_signal.emit()
def tb_search_order_commit(self):
t = {}
# Walk the items in the list box building the (node -> node) graph of
# the option order
node = 0
for i in range(0, 4):
v = self.tb_search_order.item(i).data(Qt.ItemDataRole.UserRole)
# JSON dumps converts integer keys to strings, so do it explicitly
t[str(node)] = v
node = v
# Add the arc from the last node back to node 0
t[str(node)] = 0
gprefs.set('tb_search_order', t)
def reset_tb_search_order(self):
gprefs.set('tb_search_order', gprefs.defaults['tb_search_order'])
self.fill_tb_search_order_box()
self.changed_signal.emit()
def update_color_palette_state(self):
if self.ui_style_available:
enabled = self.opt_ui_style.currentData() == 'calibre'
@ -1065,14 +817,9 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
self.opt_cover_grid_width.setValue(0)
self.opt_cover_grid_height.setValue(0)
def edit_cb_title_template(self):
t = TemplateDialog(self, self.opt_cover_browser_title_template.text(), fm=self.gui.current_db.field_metadata)
t.setWindowTitle(_('Edit template for caption'))
if t.exec():
self.opt_cover_browser_title_template.setText(t.rule[1])
def initialize(self):
ConfigWidgetBase.initialize(self)
self.default_author_link.value = default_author_link()
font = gprefs['font']
if font is not None:
@ -1085,7 +832,6 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
self.qv_display_model.initialize()
self.tb_display_model.initialize()
self.tb_categories_to_part_model.initialize()
self.tb_hierarchical_cats_model.initialize()
self.bd_vertical_cats_model.initialize()
db = self.gui.current_db
mi = []
@ -1108,12 +854,6 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
self.tb_focus_label.setVisible(self.opt_tag_browser_allow_keyboard_focus.isChecked())
self.update_color_palette_state()
self.opt_gui_layout.setCurrentIndex(0 if self.gui.layout_container.is_wide else 1)
set_help_tips(self.opt_cover_browser_narrow_view_position, _(
'This option controls the position of the cover browser when using the Narrow user '
'interface layout. "Automatic" will place the cover browser on top or on the right '
'of the book list depending on the aspect ratio of the calibre window. "On top" '
'places it over the book list, and "On right" places it to the right of the book '
'list. This option has no effect when using the Wide user interface layout.'))
def open_cg_cache(self):
open_local_file(self.gui.grid_view.thumbnail_cache.location)
@ -1233,9 +973,7 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
self.qv_display_model.commit()
self.tb_display_model.commit()
self.tb_categories_to_part_model.commit()
self.tb_hierarchical_cats_model.commit()
self.bd_vertical_cats_model.commit()
self.tb_search_order_commit()
self.edit_rules.commit(self.gui.current_db.prefs)
self.icon_rules.commit(self.gui.current_db.prefs)
self.grid_rules.commit(self.gui.current_db.prefs)
@ -1250,7 +988,6 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
bcss = None
set_data('templates/book_details.css', bcss)
self.gui.layout_container.change_layout(self.gui, self.opt_gui_layout.currentIndex() == 0)
return rr
def refresh_gui(self, gui):
@ -1267,11 +1004,6 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
gui.library_view.refresh_grid()
gui.library_view.refresh_composite_edit()
gui.library_view.set_row_header_visibility()
gui.cover_flow.setShowReflections(gprefs['cover_browser_reflections'])
gui.cover_flow.setPreserveAspectRatio(gprefs['cb_preserve_aspect_ratio'])
gui.cover_flow.setActivateOnDoubleClick(gprefs['cb_double_click_to_activate'])
gui.update_cover_flow_subtitle_font()
gui.cover_flow.template_inited = False
for view in 'library memory card_a card_b'.split():
getattr(gui, view + '_view').set_row_header_visibility()
gui.library_view.refresh_row_sizing()

View File

@ -1789,201 +1789,15 @@ structure and you want to use the same for each one.&lt;/p&gt;</string>
</item>
</layout>
</widget>
<widget class="QWidget" name="tb_tab3">
<widget class="TbHierarchyTab" name="tb_hierarchy_tab">
<attribute name="title">
<string>&amp;Hierarchy and searching</string>
</attribute>
<layout class="QGridLayout">
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="toolTip">
<string>&lt;p&gt;Check the box for an item if it is to be displayed as a
hierarchical tree in the Tag browser. For example, if you check
'tags' then tags of the form 'Mystery.English'
and 'Mystery.Thriller' will be displayed with English and Thriller
both under 'Mystery'. If 'tags' is not checked
then the tags will be displayed each on their own line.&lt;/p&gt;
&lt;p&gt;The categories 'authors', 'publisher', 'news', 'formats', and 'rating'
cannot be hierarchical.&lt;/p&gt;
&lt;p&gt;User categories are always hierarchical and so do not appear in this list.&lt;/p&gt;</string>
</property>
<property name="text">
<string>Select categories with &amp;hierarchical items:</string>
</property>
<property name="buddy">
<cstring>tb_hierarchical_cats</cstring>
</property>
</widget>
</item>
<item row="1" column="0" rowspan="3">
<widget class="QListView" name="tb_hierarchical_cats">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>1</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>200</height>
</size>
</property>
<property name="alternatingRowColors">
<bool>true</bool>
</property>
</widget>
</item>
<item row="4" column="0">
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QPushButton" name="tb_hierarchy_reset_layout_button">
<property name="toolTip">
<string>Click this button to reset the list to its default order.</string>
</property>
<property name="text">
<string>Reset list</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="tb_hierarchy_import_layout_button">
<property name="toolTip">
<string>&lt;p&gt;Click this button to set the list to one
previously exported. This could be useful if you have several libraries with
similar structure and you want to use the same for each one.&lt;/p&gt;</string>
</property>
<property name="text">
<string>Import list</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="tb_hierarchy_export_layout_button">
<property name="toolTip">
<string>&lt;p&gt;Click this button to write the current display
settings to a file. This could be useful if you have several libraries with similar
structure and you want to use the same for each one.&lt;/p&gt;</string>
</property>
<property name="text">
<string>Export list</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<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 row="10" column="0">
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="11" column="0">
<widget class="QLabel" name="label">
<property name="toolTip">
<string>&lt;p&gt;Set the order of the searches when clicking on an item in
the Tag browser. The 'or its children' options are ignored when clicking on
top-level categories, items that aren't in a hierarchical category, and items
that don't have children.&lt;/p&gt;</string>
</property>
<property name="text">
<string>Set the &amp;order of searches when clicking on items</string>
</property>
<property name="buddy">
<cstring>tb_search_order</cstring>
</property>
</widget>
</item>
<item row="12" column="0" rowspan="3">
<widget class="ListWidgetWithMoveByKeyPress" name="tb_search_order">
<property name="sizeAdjustPolicy">
<enum>QAbstractScrollArea::AdjustToContents</enum>
</property>
</widget>
</item>
<item row="12" column="1">
<widget class="QToolButton" name="tb_search_order_up_button">
<property name="toolTip">
<string>Move up. Keyboard shortcut: Ctrl-Up arrow</string>
</property>
<property name="icon">
<iconset resource="../../../../resources/images.qrc">
<normaloff>:/images/arrow-up.png</normaloff>:/images/arrow-up.png</iconset>
</property>
</widget>
</item>
<item row="13" column="1">
<spacer name="verticalSpacer_51">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>1</width>
<height>1</height>
</size>
</property>
</spacer>
</item>
<item row="14" column="1">
<widget class="QToolButton" name="tb_search_order_down_button">
<property name="toolTip">
<string>Move down. Keyboard shortcut: Ctrl-Down arrow</string>
</property>
<property name="icon">
<iconset resource="../../../../resources/images.qrc">
<normaloff>:/images/arrow-down.png</normaloff>:/images/arrow-down.png</iconset>
</property>
</widget>
</item>
<item row="15" column="0">
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QPushButton" name="tb_search_order_reset_button">
<property name="toolTip">
<string>Click this button to reset the list to its default order.</string>
</property>
<property name="text">
<string>Reset list</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<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>
</layout>
<widget class="TbIconRulesTab" name="tb_icon_browser_tab">
<attribute name="title">
<string>Val&amp;ue icon rules viewer</string>
</attribute>
</widget>
</widget>
<widget class="QWidget" name="cover_browser_tab">
@ -1994,164 +1808,9 @@ that don't have children.&lt;/p&gt;</string>
<attribute name="title">
<string>Cover &amp;browser</string>
</attribute>
<layout class="QGridLayout" name="gridLayout_11">
<item row="0" column="1" colspan="2">
<widget class="QCheckBox" name="opt_cb_fullscreen">
<property name="text">
<string>When showing in a separate window, show it &amp;fullscreen</string>
</property>
</widget>
</item>
<item row="0" column="0" colspan="3">
<widget class="QCheckBox" name="opt_separate_cover_flow">
<property name="text">
<string>Show in a &amp;separate window (needs restart)</string>
</property>
</widget>
</item>
<item row="7" column="1" colspan="2">
<widget class="QSpinBox" name="opt_cover_flow_queue_length"/>
</item>
<item row="7" column="0">
<widget class="QLabel" name="label_61">
<property name="text">
<string>&amp;Number of covers to show in browse mode (needs restart):</string>
</property>
<property name="buddy">
<cstring>opt_cover_flow_queue_length</cstring>
</property>
</widget>
</item>
<item row="8" column="2">
<widget class="QPushButton" name="cover_browser_title_template_button">
<property name="text">
<string>Template &amp;editor</string>
</property>
</widget>
</item>
<item row="11" column="0" colspan="2">
<spacer name="verticalSpacer_4">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>690</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="8" column="1">
<widget class="QLineEdit" name="opt_cover_browser_title_template">
<property name="toolTip">
<string>&lt;p&gt;The template used to generate the text below the covers. This template uses
the same syntax as save templates. Defaults to just the book title.
Note that this setting is per-library, which means that you have to
set it again for every different calibre library you use. Use an
empty template for no text.&lt;/p&gt;</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QCheckBox" name="opt_cover_browser_reflections">
<property name="text">
<string>Show &amp;reflections</string>
</property>
</widget>
</item>
<item row="4" column="1" colspan="2">
<widget class="QCheckBox" name="opt_cb_preserve_aspect_ratio">
<property name="toolTip">
<string>Show covers in their original aspect ratio instead of resizing
them to all have the same width and height</string>
</property>
<property name="text">
<string>Preserve &amp;aspect ratio of covers</string>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QLabel" name="label_987">
<property name="text">
<string>Cover browser &amp;position in the narrow layout:</string>
</property>
<property name="buddy">
<cstring>opt_cover_browser_narrow_view_position</cstring>
</property>
</widget>
</item>
<item row="6" column="1" colspan="2">
<widget class="QComboBox" name="opt_cover_browser_narrow_view_position"/>
</item>
<item row="9" column="1" colspan="2">
<widget class="QComboBox" name="opt_cover_browser_subtitle_field"/>
</item>
<item row="8" column="0">
<widget class="QLabel" name="label_23">
<property name="text">
<string>&amp;Template for caption:</string>
</property>
<property name="buddy">
<cstring>opt_cover_browser_title_template</cstring>
</property>
</widget>
</item>
<item row="9" column="0">
<widget class="QLabel" name="label_24">
<property name="text">
<string>Fie&amp;ld for sub-title:</string>
</property>
<property name="buddy">
<cstring>opt_cover_browser_subtitle_field</cstring>
</property>
</widget>
</item>
<item row="1" column="1" colspan="2">
<widget class="QLabel" name="fs_help_msg">
<property name="styleSheet">
<string notr="true">margin-left: 1.5em</string>
</property>
<property name="text">
<string>You can press the %s key to toggle full screen mode.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="10" column="0">
<widget class="QLabel" name="label_26">
<property name="text">
<string>Show ne&amp;xt cover during auto scroll after:</string>
</property>
<property name="buddy">
<cstring>opt_books_autoscroll_time</cstring>
</property>
</widget>
</item>
<item row="10" column="1">
<widget class="QDoubleSpinBox" name="opt_books_autoscroll_time">
<property name="suffix">
<string> seconds</string>
</property>
<property name="decimals">
<number>1</number>
</property>
<property name="minimum">
<double>1.000000000000000</double>
</property>
<property name="maximum">
<double>100000.000000000000000</double>
</property>
</widget>
</item>
<item row="5" column="0" colspan="2">
<widget class="QCheckBox" name="opt_cb_double_click_to_activate">
<property name="text">
<string>&amp;Double click to view the central book, instead of single click</string>
</property>
</widget>
<layout class="QHBoxLayout">
<item>
<widget class="CoverView" name="cover_view"/>
</item>
</layout>
</widget>
@ -2297,6 +1956,26 @@ column being examined (the left-hand panel)&lt;/p&gt;</string>
<header>calibre/gui2/widgets2.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>ConfigWidget</class>
<extends>QWidget</extends>
<header>calibre/gui2/preferences/look_feel.h</header>
</customwidget>
<customwidget>
<class>TbIconRulesTab</class>
<extends>ConfigWidget</extends>
<header>calibre/gui2/preferences/look_feel_tabs.tb_icon_rules.h</header>
</customwidget>
<customwidget>
<class>TbHierarchyTab</class>
<extends>ConfigWidget</extends>
<header>calibre/gui2/preferences/look_feel_tabs.tb_hierarchy.h</header>
</customwidget>
<customwidget>
<class>CoverView</class>
<extends>ConfigWidget</extends>
<header>calibre/gui2/preferences/look_feel_tabs.cover_view.h</header>
</customwidget>
</customwidgets>
<resources>
<include location="../../../../resources/images.qrc"/>

View File

@ -0,0 +1,122 @@
#!/usr/bin/env python
__license__ = 'GPL v3'
__copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
from qt.core import QAbstractListModel, QIcon, QItemSelectionModel, Qt
from calibre.gui2.book_details import get_field_list
class DisplayedFields(QAbstractListModel): # {{{
def __init__(self, db, parent=None, pref_name=None, category_icons=None):
self.pref_name = pref_name or 'book_display_fields'
QAbstractListModel.__init__(self, parent)
self.fields = []
self.db = db
self.changed = False
self.category_icons = category_icons
def get_field_list(self, use_defaults=False):
return get_field_list(self.db.field_metadata, use_defaults=use_defaults, pref_name=self.pref_name)
def initialize(self, use_defaults=False):
self.beginResetModel()
self.fields = [[x[0], x[1]] for x in self.get_field_list(use_defaults=use_defaults)]
self.endResetModel()
self.changed = True
def rowCount(self, *args):
return len(self.fields)
def data(self, index, role):
try:
field, visible = self.fields[index.row()]
except:
return None
if role == Qt.ItemDataRole.DisplayRole:
name = field
try:
name = self.db.field_metadata[field]['name']
except:
pass
if field == 'path':
name = _('Folders/path')
name = field.partition('.')[0][1:] if field.startswith('@') else name
if not name:
return field
return f'{name} ({field})'
if role == Qt.ItemDataRole.CheckStateRole:
return Qt.CheckState.Checked if visible else Qt.CheckState.Unchecked
if role == Qt.ItemDataRole.DecorationRole:
if self.category_icons:
icon = self.category_icons.get(field, None)
if icon is not None:
return icon
if field.startswith('#'):
return QIcon.ic('column.png')
return None
def toggle_all(self, show=True):
for i in range(self.rowCount()):
idx = self.index(i)
if idx.isValid():
self.setData(idx, Qt.CheckState.Checked if show else Qt.CheckState.Unchecked, Qt.ItemDataRole.CheckStateRole)
def flags(self, index):
ans = QAbstractListModel.flags(self, index)
return ans | Qt.ItemFlag.ItemIsUserCheckable
def setData(self, index, val, role):
ret = False
if role == Qt.ItemDataRole.CheckStateRole:
self.fields[index.row()][1] = val in (Qt.CheckState.Checked, Qt.CheckState.Checked.value)
self.changed = True
ret = True
self.dataChanged.emit(index, index)
return ret
def restore_defaults(self):
self.initialize(use_defaults=True)
def commit(self):
if self.changed:
self.db.new_api.set_pref(self.pref_name, self.fields)
def move(self, idx, delta):
row = idx.row() + delta
if row >= 0 and row < len(self.fields):
t = self.fields[row]
self.fields[row] = self.fields[row-delta]
self.fields[row-delta] = t
self.dataChanged.emit(idx, idx)
idx = self.index(row)
self.dataChanged.emit(idx, idx)
self.changed = True
return idx
def move_field_up(widget, model):
idx = widget.currentIndex()
if idx.isValid():
idx = model.move(idx, -1)
if idx is not None:
sm = widget.selectionModel()
sm.select(idx, QItemSelectionModel.SelectionFlag.ClearAndSelect)
widget.setCurrentIndex(idx)
def move_field_down(widget, model):
idx = widget.currentIndex()
if idx.isValid():
idx = model.move(idx, 1)
if idx is not None:
sm = widget.selectionModel()
sm.select(idx, QItemSelectionModel.SelectionFlag.ClearAndSelect)
widget.setCurrentIndex(idx)
# }}}

View File

@ -0,0 +1,66 @@
#!/usr/bin/env python
__license__ = 'GPL v3'
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
from qt.core import QKeySequence
from calibre.gui2 import config, gprefs
from calibre.gui2.dialogs.template_dialog import TemplateDialog
from calibre.gui2.preferences import ConfigTabWidget, ConfigWidgetBase, set_help_tips
from calibre.gui2.preferences.look_feel_tabs.cover_view_ui import Ui_Form
class CoverView(ConfigTabWidget, Ui_Form):
def genesis(self, gui):
self.gui = gui
db = gui.library_view.model().db
r = self.register
r('books_autoscroll_time', gprefs)
r('cover_flow_queue_length', config, restart_required=True)
r('cover_browser_reflections', gprefs)
r('cover_browser_narrow_view_position', gprefs,
choices=[(_('Automatic'), 'automatic'), # Automatic must be first
(_('On top'), 'on_top'),
(_('On right'), 'on_right')])
r('cover_browser_title_template', db.prefs)
fm = db.field_metadata
r('cover_browser_subtitle_field', db.prefs, choices=[(_('No subtitle'), 'none')] + sorted(
(fm[k].get('name'), k) for k in fm.all_field_keys() if fm[k].get('name')
))
self.cover_browser_title_template_button.clicked.connect(self.edit_cb_title_template)
r('separate_cover_flow', config, restart_required=True)
r('cb_fullscreen', gprefs)
r('cb_preserve_aspect_ratio', gprefs)
r('cb_double_click_to_activate', gprefs)
self.fs_help_msg.setText(self.fs_help_msg.text()%(
QKeySequence(QKeySequence.StandardKey.FullScreen).toString(QKeySequence.SequenceFormat.NativeText)))
def initialize(self):
ConfigWidgetBase.initialize(self)
set_help_tips(self.opt_cover_browser_narrow_view_position, _(
'This option controls the position of the cover browser when using the Narrow user '
'interface layout. "Automatic" will place the cover browser on top or on the right '
'of the book list depending on the aspect ratio of the calibre window. "On top" '
'places it over the book list, and "On right" places it to the right of the book '
'list. This option has no effect when using the Wide user interface layout.'))
def edit_cb_title_template(self):
t = TemplateDialog(self, self.opt_cover_browser_title_template.text(), fm=self.gui.current_db.field_metadata)
t.setWindowTitle(_('Edit template for caption'))
if t.exec():
self.opt_cover_browser_title_template.setText(t.rule[1])
def refresh_gui(self, gui):
gui.cover_flow.setShowReflections(gprefs['cover_browser_reflections'])
gui.cover_flow.setPreserveAspectRatio(gprefs['cb_preserve_aspect_ratio'])
gui.cover_flow.setActivateOnDoubleClick(gprefs['cb_double_click_to_activate'])
gui.update_cover_flow_subtitle_font()
gui.cover_flow.template_inited = False

View File

@ -0,0 +1,185 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Form</class>
<widget class="QWidget" name="cover_browser_tab">
<attribute name="icon">
<iconset resource="../../../../resources/images.qrc">
<normaloff>:/images/cover_flow.png</normaloff>:/images/cover_flow.png</iconset>
</attribute>
<attribute name="title">
<string>Cover &amp;browser</string>
</attribute>
<layout class="QGridLayout" name="gridLayout_cover_browser">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item row="0" column="1" colspan="2">
<widget class="QCheckBox" name="opt_cb_fullscreen">
<property name="text">
<string>When showing in a separate window, show it &amp;fullscreen</string>
</property>
</widget>
</item>
<item row="0" column="0" colspan="3">
<widget class="QCheckBox" name="opt_separate_cover_flow">
<property name="text">
<string>Show in a &amp;separate window (needs restart)</string>
</property>
</widget>
</item>
<item row="7" column="1" colspan="2">
<widget class="QSpinBox" name="opt_cover_flow_queue_length"/>
</item>
<item row="7" column="0">
<widget class="QLabel" name="label_61">
<property name="text">
<string>&amp;Number of covers to show in browse mode (needs restart):</string>
</property>
<property name="buddy">
<cstring>opt_cover_flow_queue_length</cstring>
</property>
</widget>
</item>
<item row="8" column="2">
<widget class="QPushButton" name="cover_browser_title_template_button">
<property name="text">
<string>Template &amp;editor</string>
</property>
</widget>
</item>
<item row="11" column="0" colspan="2">
<spacer name="verticalSpacer_4">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>690</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="8" column="1">
<widget class="QLineEdit" name="opt_cover_browser_title_template">
<property name="toolTip">
<string>&lt;p&gt;The template used to generate the text below the covers. This template uses
the same syntax as save templates. Defaults to just the book title.
Note that this setting is per-library, which means that you have to
set it again for every different calibre library you use. Use an
empty template for no text.&lt;/p&gt;</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QCheckBox" name="opt_cover_browser_reflections">
<property name="text">
<string>Show &amp;reflections</string>
</property>
</widget>
</item>
<item row="4" column="1" colspan="2">
<widget class="QCheckBox" name="opt_cb_preserve_aspect_ratio">
<property name="toolTip">
<string>Show covers in their original aspect ratio instead of resizing
them to all have the same width and height</string>
</property>
<property name="text">
<string>Preserve &amp;aspect ratio of covers</string>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QLabel" name="label_987">
<property name="text">
<string>Cover browser &amp;position in the narrow layout:</string>
</property>
<property name="buddy">
<cstring>opt_cover_browser_narrow_view_position</cstring>
</property>
</widget>
</item>
<item row="6" column="1" colspan="2">
<widget class="QComboBox" name="opt_cover_browser_narrow_view_position"/>
</item>
<item row="9" column="1" colspan="2">
<widget class="QComboBox" name="opt_cover_browser_subtitle_field"/>
</item>
<item row="8" column="0">
<widget class="QLabel" name="label_23">
<property name="text">
<string>&amp;Template for caption:</string>
</property>
<property name="buddy">
<cstring>opt_cover_browser_title_template</cstring>
</property>
</widget>
</item>
<item row="9" column="0">
<widget class="QLabel" name="label_24">
<property name="text">
<string>Fie&amp;ld for sub-title:</string>
</property>
<property name="buddy">
<cstring>opt_cover_browser_subtitle_field</cstring>
</property>
</widget>
</item>
<item row="1" column="1" colspan="2">
<widget class="QLabel" name="fs_help_msg">
<property name="styleSheet">
<string notr="true">margin-left: 1.5em</string>
</property>
<property name="text">
<string>You can press the %s key to toggle full screen mode.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="10" column="0">
<widget class="QLabel" name="label_26">
<property name="text">
<string>Show ne&amp;xt cover during auto scroll after:</string>
</property>
<property name="buddy">
<cstring>opt_books_autoscroll_time</cstring>
</property>
</widget>
</item>
<item row="10" column="1">
<widget class="QDoubleSpinBox" name="opt_books_autoscroll_time">
<property name="suffix">
<string> seconds</string>
</property>
<property name="decimals">
<number>1</number>
</property>
<property name="minimum">
<double>1.000000000000000</double>
</property>
<property name="maximum">
<double>100000.000000000000000</double>
</property>
</widget>
</item>
<item row="5" column="0" colspan="2">
<widget class="QCheckBox" name="opt_cb_double_click_to_activate">
<property name="text">
<string>&amp;Double click to view the central book, instead of single click</string>
</property>
</widget>
</item>
</layout>
</widget>
</ui>

View File

@ -0,0 +1,173 @@
#!/usr/bin/env python
__license__ = 'GPL v3'
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
from functools import partial
import json
from qt.core import QListWidgetItem, Qt
from calibre.gui2 import choose_files, choose_save_file, error_dialog, gprefs
from calibre.gui2.preferences import ConfigTabWidget
from calibre.gui2.preferences.look_feel_tabs import DisplayedFields
from calibre.gui2.preferences.look_feel_tabs.tb_hierarchy_ui import Ui_Form
class TBHierarchicalFields(DisplayedFields): # {{{
# The code in this class depends on the fact that the tag browser is
# initialized before this class is instantiated.
cant_make_hierarical = {'authors', 'publisher', 'formats', 'news',
'identifiers', 'languages', 'rating'}
def __init__(self, db, parent=None, category_icons=None):
DisplayedFields.__init__(self, db, parent, category_icons=category_icons)
from calibre.gui2.ui import get_gui
self.gui = get_gui()
def initialize(self, use_defaults=False, pref_data_override=None):
tv = self.gui.tags_view
cats = [k for k in tv.model().categories.keys() if (not k.startswith('@') and
k not in self.cant_make_hierarical)]
ans = []
if use_defaults:
ans = [[k, False] for k in cats]
self.changed = True
elif pref_data_override:
ph = {k:v for k,v in pref_data_override}
ans = [[k, ph.get(k, False)] for k in cats]
self.changed = True
else:
hier_cats = self.db.prefs.get('categories_using_hierarchy') or ()
for key in cats:
ans.append([key, key in hier_cats])
self.beginResetModel()
self.fields = ans
self.endResetModel()
def commit(self):
if self.changed:
self.db.prefs.set('categories_using_hierarchy', [k for k,v in self.fields if v])
# }}}
class TbHierarchyTab(ConfigTabWidget, Ui_Form):
def genesis(self, gui):
self.gui = gui
self.tb_hierarchical_cats_model = TBHierarchicalFields(gui.current_db, self.tb_hierarchical_cats,
category_icons=gui.tags_view.model().category_custom_icons)
self.tb_hierarchical_cats_model.dataChanged.connect(self.changed_signal)
self.tb_hierarchical_cats.setModel(self.tb_hierarchical_cats_model)
self.tb_hierarchy_reset_layout_button.clicked.connect(partial(self.reset_layout,
model=self.tb_hierarchical_cats_model))
self.tb_hierarchy_export_layout_button.clicked.connect(partial(self.export_layout,
model=self.tb_hierarchical_cats_model))
self.tb_hierarchy_import_layout_button.clicked.connect(partial(self.import_layout,
model=self.tb_hierarchical_cats_model))
self.fill_tb_search_order_box()
self.tb_search_order_up_button.clicked.connect(self.move_tb_search_up)
self.tb_search_order_down_button.clicked.connect(self.move_tb_search_down)
self.tb_search_order.set_movement_functions(self.move_tb_search_up, self.move_tb_search_down)
self.tb_search_order_reset_button.clicked.connect(self.reset_tb_search_order)
def initialize(self):
self.tb_hierarchical_cats_model.initialize()
def fill_tb_search_order_box(self):
# The tb_search_order is a directed graph of nodes with an arc to the next
# node in the sequence. Node 0 (zero) is the start node with the last node
# arcing back to node 0. This code linearizes the graph
choices = [(1, _('Search for books containing the current item')),
(2, _('Search for books containing the current item or its children')),
(3, _('Search for books not containing the current item')),
(4, _('Search for books not containing the current item or its children'))]
icon_map = self.gui.tags_view.model().icon_state_map
order = gprefs.get('tb_search_order')
self.tb_search_order.clear()
node = 0
while True:
v = order[str(node)]
if v == 0:
break
item = QListWidgetItem(icon_map[v], choices[v-1][1])
item.setData(Qt.ItemDataRole.UserRole, choices[v-1][0])
self.tb_search_order.addItem(item)
node = v
def move_tb_search_up(self):
idx = self.tb_search_order.currentRow()
if idx <= 0:
return
item = self.tb_search_order.takeItem(idx)
self.tb_search_order.insertItem(idx-1, item)
self.tb_search_order.setCurrentRow(idx-1)
self.changed_signal.emit()
def move_tb_search_down(self):
idx = self.tb_search_order.currentRow()
if idx < 0 or idx == 3:
return
item = self.tb_search_order.takeItem(idx)
self.tb_search_order.insertItem(idx+1, item)
self.tb_search_order.setCurrentRow(idx+1)
self.changed_signal.emit()
def tb_search_order_commit(self):
t = {}
# Walk the items in the list box building the (node -> node) graph of
# the option order
node = 0
for i in range(0, 4):
v = self.tb_search_order.item(i).data(Qt.ItemDataRole.UserRole)
# JSON dumps converts integer keys to strings, so do it explicitly
t[str(node)] = v
node = v
# Add the arc from the last node back to node 0
t[str(node)] = 0
gprefs.set('tb_search_order', t)
def reset_tb_search_order(self):
gprefs.set('tb_search_order', gprefs.defaults['tb_search_order'])
self.fill_tb_search_order_box()
self.changed_signal.emit()
def reset_layout(self, model=None):
model.initialize(use_defaults=True)
self.changed_signal.emit()
def export_layout(self, model=None):
filename = choose_save_file(self, 'em_import_export_field_list',
_('Save column list to file'),
filters=[(_('Column list'), ['json'])])
if filename:
try:
with open(filename, 'w') as f:
json.dump(model.fields, f, indent=1)
except Exception as err:
error_dialog(self, _('Export field layout'),
_('<p>Could not write field list. Error:<br>%s')%err, show=True)
def import_layout(self, model=None):
filename = choose_files(self, 'em_import_export_field_list',
_('Load column list from file'),
filters=[(_('Column list'), ['json'])])
if filename:
try:
with open(filename[0]) as f:
fields = json.load(f)
model.initialize(pref_data_override=fields)
self.changed_signal.emit()
except Exception as err:
error_dialog(self, _('Import layout'),
_('<p>Could not read field list. Error:<br>%s')%err, show=True)
def commit(self):
self.tb_search_order_commit()
self.tb_hierarchical_cats_model.commit()

View File

@ -0,0 +1,215 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Form</class>
<widget class="QWidget" name="Form">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1035</width>
<height>547</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QGridLayout">
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="toolTip">
<string>&lt;p&gt;Check the box for an item if it is to be displayed as a
hierarchical tree in the Tag browser. For example, if you check
'tags' then tags of the form 'Mystery.English'
and 'Mystery.Thriller' will be displayed with English and Thriller
both under 'Mystery'. If 'tags' is not checked
then the tags will be displayed each on their own line.&lt;/p&gt;
&lt;p&gt;The categories 'authors', 'publisher', 'news', 'formats', and 'rating'
cannot be hierarchical.&lt;/p&gt;
&lt;p&gt;User categories are always hierarchical and so do not appear in this list.&lt;/p&gt;</string>
</property>
<property name="text">
<string>Select categories with &amp;hierarchical items:</string>
</property>
<property name="buddy">
<cstring>tb_hierarchical_cats</cstring>
</property>
</widget>
</item>
<item row="1" column="0" rowspan="3">
<widget class="QListView" name="tb_hierarchical_cats">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>1</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>200</height>
</size>
</property>
<property name="alternatingRowColors">
<bool>true</bool>
</property>
</widget>
</item>
<item row="4" column="0">
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QPushButton" name="tb_hierarchy_reset_layout_button">
<property name="toolTip">
<string>Click this button to reset the list to its default order.</string>
</property>
<property name="text">
<string>Reset list</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="tb_hierarchy_import_layout_button">
<property name="toolTip">
<string>&lt;p&gt;Click this button to set the list to one
previously exported. This could be useful if you have several libraries with
similar structure and you want to use the same for each one.&lt;/p&gt;</string>
</property>
<property name="text">
<string>Import list</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="tb_hierarchy_export_layout_button">
<property name="toolTip">
<string>&lt;p&gt;Click this button to write the current display
settings to a file. This could be useful if you have several libraries with similar
structure and you want to use the same for each one.&lt;/p&gt;</string>
</property>
<property name="text">
<string>Export list</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<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 row="10" column="0">
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="11" column="0">
<widget class="QLabel" name="label">
<property name="toolTip">
<string>&lt;p&gt;Set the order of the searches when clicking on an item in
the Tag browser. The 'or its children' options are ignored when clicking on
top-level categories, items that aren't in a hierarchical category, and items
that don't have children.&lt;/p&gt;</string>
</property>
<property name="text">
<string>Set the &amp;order of searches when clicking on items</string>
</property>
<property name="buddy">
<cstring>tb_search_order</cstring>
</property>
</widget>
</item>
<item row="12" column="0" rowspan="3">
<widget class="ListWidgetWithMoveByKeyPress" name="tb_search_order">
<property name="sizeAdjustPolicy">
<enum>QAbstractScrollArea::AdjustToContents</enum>
</property>
</widget>
</item>
<item row="12" column="1">
<widget class="QToolButton" name="tb_search_order_up_button">
<property name="toolTip">
<string>Move up. Keyboard shortcut: Ctrl-Up arrow</string>
</property>
<property name="icon">
<iconset resource="../../../../resources/images.qrc">
<normaloff>:/images/arrow-up.png</normaloff>:/images/arrow-up.png</iconset>
</property>
</widget>
</item>
<item row="13" column="1">
<spacer name="verticalSpacer_51">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>1</width>
<height>1</height>
</size>
</property>
</spacer>
</item>
<item row="14" column="1">
<widget class="QToolButton" name="tb_search_order_down_button">
<property name="toolTip">
<string>Move down. Keyboard shortcut: Ctrl-Down arrow</string>
</property>
<property name="icon">
<iconset resource="../../../../resources/images.qrc">
<normaloff>:/images/arrow-down.png</normaloff>:/images/arrow-down.png</iconset>
</property>
</widget>
</item>
<item row="15" column="0">
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QPushButton" name="tb_search_order_reset_button">
<property name="toolTip">
<string>Click this button to reset the list to its default order.</string>
</property>
<property name="text">
<string>Reset list</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<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>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>ListWidgetWithMoveByKeyPress</class>
<extends>QListWidget</extends>
<header>calibre/gui2/preferences.h</header>
</customwidget>
</customwidgets>
</ui>

View File

@ -0,0 +1,170 @@
#!/usr/bin/env python
__license__ = 'GPL v3'
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
import copy
from functools import partial
import os
from qt.core import QAbstractItemView, QApplication, QIcon, QMenu, Qt, QTableWidgetItem
from calibre.constants import config_dir
from calibre.db.constants import TEMPLATE_ICON_INDICATOR
from calibre.gui2 import gprefs
from calibre.gui2.preferences import ConfigTabWidget, ConfigWidgetBase
from calibre.gui2.preferences.look_feel_tabs.tb_icon_rules_ui import Ui_Form
CATEGORY_COLUMN = 0
VALUE_COLUMN = 1
ICON_COLUMN = 2
FOR_CHILDREN_COLUMN = 3
DELECTED_COLUMN = 4
class CategoryTableWidgetItem(QTableWidgetItem):
def __init__(self, txt):
super().__init__(txt)
self._is_deleted = False
@property
def is_deleted(self):
return self._is_deleted
@is_deleted.setter
def is_deleted(self, to_what):
self._is_deleted = to_what
class TbIconRulesTab(ConfigTabWidget, Ui_Form):
def genesis(self, gui):
self.gui = gui
r = self.register
r('tag_browser_show_category_icons', gprefs)
r('tag_browser_show_value_icons', gprefs)
self.rules_table.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
self.rules_table.setEditTriggers(QAbstractItemView.EditTrigger.NoEditTriggers)
self.rules_table.setColumnCount(4)
self.rules_table.setHorizontalHeaderLabels((_('Category'), _('Value'), _('Icon file or template'),
_('Use for children')))
self.rules_table.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
self.rules_table.customContextMenuRequested.connect(self.show_context_menu)
# Capture clicks on the horizontal header to sort the table columns
hh = self.rules_table.horizontalHeader()
hh.sectionResized.connect(self.table_column_resized)
hh.setSectionsClickable(True)
hh.sectionClicked.connect(self.do_sort)
hh.setSortIndicatorShown(True)
v = gprefs['tags_browser_value_icons']
row = 0
for category,vdict in v.items():
for value in vdict:
self.rules_table.setRowCount(row + 1)
d = v[category][value]
self.rules_table.setItem(row, 0, CategoryTableWidgetItem(category))
self.rules_table.setItem(row, 1, QTableWidgetItem(value))
self.rules_table.setItem(row, 2, QTableWidgetItem(d[0]))
if value == TEMPLATE_ICON_INDICATOR:
txt = ''
else:
txt = _('Yes') if d[1] else _('No')
item = QTableWidgetItem(txt)
item.setTextAlignment(Qt.AlignmentFlag.AlignCenter|Qt.AlignmentFlag.AlignVCenter)
self.rules_table.setItem(row, 3, item)
row += 1
self.category_order = 1
self.value_order = 1
self.icon_order = 0
self.for_children_order = 0
self.do_sort(VALUE_COLUMN)
self.do_sort(CATEGORY_COLUMN)
try:
self.table_column_widths = gprefs.get('tag_browser_rules_dialog_table_widths', None)
except Exception:
pass
def show_context_menu(self, point):
clicked_item = self.rules_table.itemAt(point)
item = self.rules_table.item(clicked_item.row(), CATEGORY_COLUMN)
m = QMenu(self)
ac = m.addAction(_('Delete this rule'), partial(self.context_menu_handler, 'delete', item))
ac.setEnabled(not item.is_deleted)
ac = m.addAction(_('Undo delete'), partial(self.context_menu_handler, 'undelete', item))
ac.setEnabled(item.is_deleted)
m.addSeparator()
m.addAction(_('Copy'), partial(self.context_menu_handler, 'copy', clicked_item))
m.exec(self.rules_table.viewport().mapToGlobal(point))
def context_menu_handler(self, action, item):
if action == 'copy':
QApplication.clipboard().setText(item.text())
return
item.setIcon(QIcon.ic('trash.png') if action == 'delete' else QIcon())
item.is_deleted = action == 'delete'
self.changed_signal.emit()
def table_column_resized(self, col, old, new):
self.table_column_widths = []
for c in range(0, self.rules_table.columnCount()):
self.table_column_widths.append(self.rules_table.columnWidth(c))
gprefs['tag_browser_rules_dialog_table_widths'] = self.table_column_widths
def resizeEvent(self, *args):
super().resizeEvent(*args)
if self.table_column_widths is not None:
for c,w in enumerate(self.table_column_widths):
self.rules_table.setColumnWidth(c, w)
else:
# The vertical scroll bar might not be rendered, so might not yet
# have a width. Assume 25. Not a problem because user-changed column
# widths will be remembered.
w = self.tb_icon_rules_groupbox.width() - 25 - self.rules_table.verticalHeader().width()
w //= self.rules_table.columnCount()
for c in range(0, self.rules_table.columnCount()):
self.rules_table.setColumnWidth(c, w)
self.table_column_widths.append(self.rules_table.columnWidth(c))
gprefs['tag_browser_rules_dialog_table_widths'] = self.table_column_widths
def do_sort(self, section):
if section == CATEGORY_COLUMN:
self.category_order = 1 - self.category_order
self.rules_table.sortByColumn(CATEGORY_COLUMN, Qt.SortOrder(self.category_order))
elif section == VALUE_COLUMN:
self.value_order = 1 - self.value_order
self.rules_table.sortByColumn(VALUE_COLUMN, Qt.SortOrder(self.value_order))
elif section == ICON_COLUMN:
self.icon_order = 1 - self.icon_order
self.rules_table.sortByColumn(ICON_COLUMN, Qt.SortOrder(self.icon_order))
elif section == FOR_CHILDREN_COLUMN:
self.for_children_order = 1 - self.for_children_order
self.rules_table.sortByColumn(FOR_CHILDREN_COLUMN, Qt.SortOrder(self.for_children_order))
def commit(self):
rr = ConfigWidgetBase.commit(self)
v = copy.deepcopy(gprefs['tags_browser_value_icons'])
for r in range(0, self.rules_table.rowCount()):
cat_item = self.rules_table.item(r, CATEGORY_COLUMN)
if cat_item.is_deleted:
val = self.rules_table.item(r, VALUE_COLUMN).text()
if val != TEMPLATE_ICON_INDICATOR:
icon_file = self.rules_table.item(r, ICON_COLUMN).text()
path = os.path.join(config_dir, 'tb_icons', icon_file)
try:
os.remove(path)
except:
pass
v[cat_item.text()].pop(val, None)
# Remove categories with no rules
for category in list(v.keys()):
if len(v[category]) == 0:
v.pop(category, None)
gprefs['tags_browser_value_icons'] = v

View File

@ -0,0 +1,61 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Form</class>
<widget class="QWidget" name="Form">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1035</width>
<height>547</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout">
<item>
<layout class="QHBoxLayout">
<item>
<widget class="QCheckBox" name="opt_tag_browser_show_category_icons">
<property name="text">
<string>Show icons on &amp;categories in the Tag browser</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="opt_tag_browser_show_value_icons">
<property name="text">
<string>Show icons on &amp;values in the Tag browser</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QGroupBox" name="tb_icon_rules_groupbox">
<property name="title">
<string>Icon value rules</string>
</property>
<layout class="QVBoxLayout">
<item>
<widget class="QLabel" name="l12345">
<property name="text">
<string>&lt;p&gt;View all the defined value icon rules, including template rules.
Rules are defined and edited in the Tag browser context menus. Rules can be deleted in
this dialog using the context menu.&lt;/p&gt;</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QTableWidget" name="rules_table"/>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</ui>

View File

@ -329,8 +329,10 @@ class Preferences(QDialog):
def show_plugin(self, plugin):
self.showing_widget = plugin.create_widget(self.scroll_area)
self.showing_widget.genesis(self.gui)
self.showing_widget.do_on_child_tabs('genesis', self.gui)
try:
self.showing_widget.initialize()
self.showing_widget.do_on_child_tabs('initialize')
except AbortInitialize:
return
self.set_tooltips_for_labels()
@ -357,6 +359,7 @@ class Preferences(QDialog):
(_('Restoring to defaults not supported for') + ' ' + plugin.gui_name))
self.restore_defaults_button.setText(_('Restore &defaults'))
self.showing_widget.changed_signal.connect(self.changed_signal)
self.showing_widget.do_on_child_tabs('set_changed_signal', self.changed_signal)
def changed_signal(self):
b = self.bb.button(QDialogButtonBox.StandardButton.Apply)
@ -394,7 +397,8 @@ class Preferences(QDialog):
self.accept()
def commit(self, *args):
must_restart = self.showing_widget.commit()
# Commit the child widgets first in case the main widget uses the information
must_restart = bool(self.showing_widget.do_on_child_tabs('commit')) | self.showing_widget.commit()
rc = self.showing_widget.restart_critical
self.committed = True
do_restart = False
@ -407,6 +411,8 @@ class Preferences(QDialog):
' Please restart calibre as soon as possible.')
do_restart = show_restart_warning(msg, parent=self)
# Same with refresh -- do the child widgets first so the main widget has the info
self.showing_widget.do_on_child_tabs('refresh_gui', self.gui)
self.showing_widget.refresh_gui(self.gui)
if do_restart:
self.do_restart = True

View File

@ -115,7 +115,8 @@ class TagTreeItem: # {{{
def ensure_icon(self):
if self.icon_state_map[0] is not None:
return
if self.type == self.TAG:
cc = None
if self.type == self.TAG and gprefs['tag_browser_show_value_icons']:
if self.tag.category == 'formats':
fmt = self.tag.original_name.replace('ORIGINAL_', '')
cc = self.file_icon_provider(fmt)
@ -159,7 +160,7 @@ class TagTreeItem: # {{{
cc = self.category_custom_icons.get(self.tag.category, None)
else:
cc = self.icon
elif self.type == self.CATEGORY:
elif self.type == self.CATEGORY and gprefs['tag_browser_show_category_icons']:
cc = self.category_custom_icons.get(self.category_key, None)
self.icon_state_map[0] = cc or QIcon()
@ -521,6 +522,7 @@ class TagsModel(QAbstractItemModel): # {{{
def reset_tag_browser(self):
self.beginResetModel()
self.value_icons = self.prefs['tags_browser_value_icons']
hidden_cats = self.db.new_api.pref('tag_browser_hidden_categories', {})
self.hidden_categories = set()
# strip out any non-existent field keys