mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-08 18:54:09 -04:00
Merge branch 'master' of https://github.com/cbhaley/calibre
This commit is contained in:
commit
a26eba374d
@ -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
|
||||
|
@ -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')
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
|
@ -1789,201 +1789,15 @@ structure and you want to use the same for each one.</p></string>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="tb_tab3">
|
||||
<widget class="TbHierarchyTab" name="tb_hierarchy_tab">
|
||||
<attribute name="title">
|
||||
<string>&Hierarchy and searching</string>
|
||||
</attribute>
|
||||
<layout class="QGridLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="toolTip">
|
||||
<string><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></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Select categories with &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><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></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><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></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><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></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Set the &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>
|
||||
<widget class="TbIconRulesTab" name="tb_icon_browser_tab">
|
||||
<attribute name="title">
|
||||
<string>Val&ue icon rules viewer</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</widget>
|
||||
<widget class="QWidget" name="cover_browser_tab">
|
||||
@ -1994,164 +1808,9 @@ that don't have children.</p></string>
|
||||
<attribute name="title">
|
||||
<string>Cover &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 &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 &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>&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 &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><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></string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QCheckBox" name="opt_cover_browser_reflections">
|
||||
<property name="text">
|
||||
<string>Show &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 &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 &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>&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&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&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>&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)</p></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"/>
|
||||
|
122
src/calibre/gui2/preferences/look_feel_tabs/__init__.py
Normal file
122
src/calibre/gui2/preferences/look_feel_tabs/__init__.py
Normal 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)
|
||||
|
||||
# }}}
|
66
src/calibre/gui2/preferences/look_feel_tabs/cover_view.py
Normal file
66
src/calibre/gui2/preferences/look_feel_tabs/cover_view.py
Normal 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
|
||||
|
185
src/calibre/gui2/preferences/look_feel_tabs/cover_view.ui
Normal file
185
src/calibre/gui2/preferences/look_feel_tabs/cover_view.ui
Normal 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 &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 &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 &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>&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 &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><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></string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QCheckBox" name="opt_cover_browser_reflections">
|
||||
<property name="text">
|
||||
<string>Show &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 &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 &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>&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&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&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>&Double click to view the central book, instead of single click</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</ui>
|
173
src/calibre/gui2/preferences/look_feel_tabs/tb_hierarchy.py
Normal file
173
src/calibre/gui2/preferences/look_feel_tabs/tb_hierarchy.py
Normal 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()
|
215
src/calibre/gui2/preferences/look_feel_tabs/tb_hierarchy.ui
Normal file
215
src/calibre/gui2/preferences/look_feel_tabs/tb_hierarchy.ui
Normal 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><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></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Select categories with &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><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></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><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></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><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></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Set the &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>
|
170
src/calibre/gui2/preferences/look_feel_tabs/tb_icon_rules.py
Normal file
170
src/calibre/gui2/preferences/look_feel_tabs/tb_icon_rules.py
Normal 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
|
61
src/calibre/gui2/preferences/look_feel_tabs/tb_icon_rules.ui
Normal file
61
src/calibre/gui2/preferences/look_feel_tabs/tb_icon_rules.ui
Normal 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 &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 &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><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></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>
|
@ -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
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user