diff --git a/src/calibre/gui2/__init__.py b/src/calibre/gui2/__init__.py index 8bebb67f45..9371ba8be4 100644 --- a/src/calibre/gui2/__init__.py +++ b/src/calibre/gui2/__init__.py @@ -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 diff --git a/src/calibre/gui2/dialogs/book_info.py b/src/calibre/gui2/dialogs/book_info.py index 5f982f53c4..f2cee736b3 100644 --- a/src/calibre/gui2/dialogs/book_info.py +++ b/src/calibre/gui2/dialogs/book_info.py @@ -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') diff --git a/src/calibre/gui2/preferences/__init__.py b/src/calibre/gui2/preferences/__init__.py index 40a6d1a1a8..17c95573b8 100644 --- a/src/calibre/gui2/preferences/__init__.py +++ b/src/calibre/gui2/preferences/__init__.py @@ -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) diff --git a/src/calibre/gui2/preferences/look_feel.py b/src/calibre/gui2/preferences/look_feel.py index 39f61db12e..c7e327eaa5 100644 --- a/src/calibre/gui2/preferences/look_feel.py +++ b/src/calibre/gui2/preferences/look_feel.py @@ -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() diff --git a/src/calibre/gui2/preferences/look_feel.ui b/src/calibre/gui2/preferences/look_feel.ui index 4efa91ac8e..3a88668f44 100644 --- a/src/calibre/gui2/preferences/look_feel.ui +++ b/src/calibre/gui2/preferences/look_feel.ui @@ -1789,201 +1789,15 @@ structure and you want to use the same for each one.</p> - + &Hierarchy and searching - - - - - <p>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.</p> -<p>The categories 'authors', 'publisher', 'news', 'formats', and 'rating' -cannot be hierarchical.</p> -<p>User categories are always hierarchical and so do not appear in this list.</p> - - - Select categories with &hierarchical items: - - - tb_hierarchical_cats - - - - - - - - 0 - 1 - - - - - 0 - 200 - - - - true - - - - - - - - - Click this button to reset the list to its default order. - - - Reset list - - - - - - - <p>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.</p> - - - Import list - - - - - - - <p>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.</p> - - - Export list - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - Qt::Vertical - - - - 0 - 20 - - - - - - - - <p>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.</p> - - - Set the &order of searches when clicking on items - - - tb_search_order - - - - - - - QAbstractScrollArea::AdjustToContents - - - - - - - Move up. Keyboard shortcut: Ctrl-Up arrow - - - - :/images/arrow-up.png:/images/arrow-up.png - - - - - - - Qt::Vertical - - - - 1 - 1 - - - - - - - - Move down. Keyboard shortcut: Ctrl-Down arrow - - - - :/images/arrow-down.png:/images/arrow-down.png - - - - - - - - - Click this button to reset the list to its default order. - - - Reset list - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - + + + + Val&ue icon rules viewer + @@ -1994,164 +1808,9 @@ that don't have children.</p> Cover &browser - - - - - When showing in a separate window, show it &fullscreen - - - - - - - Show in a &separate window (needs restart) - - - - - - - - - - &Number of covers to show in browse mode (needs restart): - - - opt_cover_flow_queue_length - - - - - - - Template &editor - - - - - - - Qt::Vertical - - - - 690 - 20 - - - - - - - - <p>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.</p> - - - - - - - Show &reflections - - - - - - - Show covers in their original aspect ratio instead of resizing -them to all have the same width and height - - - Preserve &aspect ratio of covers - - - - - - - Cover browser &position in the narrow layout: - - - opt_cover_browser_narrow_view_position - - - - - - - - - - - - - &Template for caption: - - - opt_cover_browser_title_template - - - - - - - Fie&ld for sub-title: - - - opt_cover_browser_subtitle_field - - - - - - - margin-left: 1.5em - - - You can press the %s key to toggle full screen mode. - - - true - - - - - - - Show ne&xt cover during auto scroll after: - - - opt_books_autoscroll_time - - - - - - - seconds - - - 1 - - - 1.000000000000000 - - - 100000.000000000000000 - - - - - - - &Double click to view the central book, instead of single click - - + + + @@ -2297,6 +1956,26 @@ column being examined (the left-hand panel)</p>
calibre/gui2/widgets2.h
1 + + ConfigWidget + QWidget +
calibre/gui2/preferences/look_feel.h
+
+ + TbIconRulesTab + ConfigWidget +
calibre/gui2/preferences/look_feel_tabs.tb_icon_rules.h
+
+ + TbHierarchyTab + ConfigWidget +
calibre/gui2/preferences/look_feel_tabs.tb_hierarchy.h
+
+ + CoverView + ConfigWidget +
calibre/gui2/preferences/look_feel_tabs.cover_view.h
+
diff --git a/src/calibre/gui2/preferences/look_feel_tabs/__init__.py b/src/calibre/gui2/preferences/look_feel_tabs/__init__.py new file mode 100644 index 0000000000..c9d1b4f730 --- /dev/null +++ b/src/calibre/gui2/preferences/look_feel_tabs/__init__.py @@ -0,0 +1,122 @@ +#!/usr/bin/env python + + +__license__ = 'GPL v3' +__copyright__ = '2011, Kovid Goyal ' +__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) + +# }}} \ No newline at end of file diff --git a/src/calibre/gui2/preferences/look_feel_tabs/cover_view.py b/src/calibre/gui2/preferences/look_feel_tabs/cover_view.py new file mode 100644 index 0000000000..57f4936d24 --- /dev/null +++ b/src/calibre/gui2/preferences/look_feel_tabs/cover_view.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python + + +__license__ = 'GPL v3' +__copyright__ = '2010, Kovid Goyal ' +__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 + diff --git a/src/calibre/gui2/preferences/look_feel_tabs/cover_view.ui b/src/calibre/gui2/preferences/look_feel_tabs/cover_view.ui new file mode 100644 index 0000000000..545ccc4954 --- /dev/null +++ b/src/calibre/gui2/preferences/look_feel_tabs/cover_view.ui @@ -0,0 +1,185 @@ + + + Form + + + + :/images/cover_flow.png:/images/cover_flow.png + + + Cover &browser + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + When showing in a separate window, show it &fullscreen + + + + + + + Show in a &separate window (needs restart) + + + + + + + + + + &Number of covers to show in browse mode (needs restart): + + + opt_cover_flow_queue_length + + + + + + + Template &editor + + + + + + + Qt::Vertical + + + + 690 + 20 + + + + + + + + <p>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.</p> + + + + + + + Show &reflections + + + + + + + Show covers in their original aspect ratio instead of resizing +them to all have the same width and height + + + Preserve &aspect ratio of covers + + + + + + + Cover browser &position in the narrow layout: + + + opt_cover_browser_narrow_view_position + + + + + + + + + + + + + &Template for caption: + + + opt_cover_browser_title_template + + + + + + + Fie&ld for sub-title: + + + opt_cover_browser_subtitle_field + + + + + + + margin-left: 1.5em + + + You can press the %s key to toggle full screen mode. + + + true + + + + + + + Show ne&xt cover during auto scroll after: + + + opt_books_autoscroll_time + + + + + + + seconds + + + 1 + + + 1.000000000000000 + + + 100000.000000000000000 + + + + + + + &Double click to view the central book, instead of single click + + + + + + \ No newline at end of file diff --git a/src/calibre/gui2/preferences/look_feel_tabs/tb_hierarchy.py b/src/calibre/gui2/preferences/look_feel_tabs/tb_hierarchy.py new file mode 100644 index 0000000000..aaa49e07f1 --- /dev/null +++ b/src/calibre/gui2/preferences/look_feel_tabs/tb_hierarchy.py @@ -0,0 +1,173 @@ +#!/usr/bin/env python + + +__license__ = 'GPL v3' +__copyright__ = '2010, Kovid Goyal ' +__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'), + _('

Could not write field list. Error:
%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'), + _('

Could not read field list. Error:
%s')%err, show=True) + + def commit(self): + self.tb_search_order_commit() + self.tb_hierarchical_cats_model.commit() \ No newline at end of file diff --git a/src/calibre/gui2/preferences/look_feel_tabs/tb_hierarchy.ui b/src/calibre/gui2/preferences/look_feel_tabs/tb_hierarchy.ui new file mode 100644 index 0000000000..eaec795640 --- /dev/null +++ b/src/calibre/gui2/preferences/look_feel_tabs/tb_hierarchy.ui @@ -0,0 +1,215 @@ + + + Form + + + + 0 + 0 + 1035 + 547 + + + + Form + + + + + + <p>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.</p> +<p>The categories 'authors', 'publisher', 'news', 'formats', and 'rating' +cannot be hierarchical.</p> +<p>User categories are always hierarchical and so do not appear in this list.</p> + + + Select categories with &hierarchical items: + + + tb_hierarchical_cats + + + + + + + + 0 + 1 + + + + + 0 + 200 + + + + true + + + + + + + + + Click this button to reset the list to its default order. + + + Reset list + + + + + + + <p>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.</p> + + + Import list + + + + + + + <p>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.</p> + + + Export list + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Qt::Vertical + + + + 0 + 20 + + + + + + + + <p>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.</p> + + + Set the &order of searches when clicking on items + + + tb_search_order + + + + + + + QAbstractScrollArea::AdjustToContents + + + + + + + Move up. Keyboard shortcut: Ctrl-Up arrow + + + + :/images/arrow-up.png:/images/arrow-up.png + + + + + + + Qt::Vertical + + + + 1 + 1 + + + + + + + + Move down. Keyboard shortcut: Ctrl-Down arrow + + + + :/images/arrow-down.png:/images/arrow-down.png + + + + + + + + + Click this button to reset the list to its default order. + + + Reset list + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + ListWidgetWithMoveByKeyPress + QListWidget +

calibre/gui2/preferences.h
+ + + \ No newline at end of file diff --git a/src/calibre/gui2/preferences/look_feel_tabs/tb_icon_rules.py b/src/calibre/gui2/preferences/look_feel_tabs/tb_icon_rules.py new file mode 100644 index 0000000000..86ef1e83bb --- /dev/null +++ b/src/calibre/gui2/preferences/look_feel_tabs/tb_icon_rules.py @@ -0,0 +1,170 @@ +#!/usr/bin/env python + + +__license__ = 'GPL v3' +__copyright__ = '2010, Kovid Goyal ' +__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 diff --git a/src/calibre/gui2/preferences/look_feel_tabs/tb_icon_rules.ui b/src/calibre/gui2/preferences/look_feel_tabs/tb_icon_rules.ui new file mode 100644 index 0000000000..1bc9e6210d --- /dev/null +++ b/src/calibre/gui2/preferences/look_feel_tabs/tb_icon_rules.ui @@ -0,0 +1,61 @@ + + + Form + + + + 0 + 0 + 1035 + 547 + + + + Form + + + + + + + + Show icons on &categories in the Tag browser + + + + + + + Show icons on &values in the Tag browser + + + + + + + + + Icon value rules + + + + + + <p>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.</p> + + + true + + + + + + + + + + + + \ No newline at end of file diff --git a/src/calibre/gui2/preferences/main.py b/src/calibre/gui2/preferences/main.py index 51a5913c46..e75e48350a 100644 --- a/src/calibre/gui2/preferences/main.py +++ b/src/calibre/gui2/preferences/main.py @@ -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 diff --git a/src/calibre/gui2/tag_browser/model.py b/src/calibre/gui2/tag_browser/model.py index 6919800ca4..fe16253e01 100644 --- a/src/calibre/gui2/tag_browser/model.py +++ b/src/calibre/gui2/tag_browser/model.py @@ -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