%PDF- %PDF-
| Direktori : /lib/calibre/calibre/gui2/viewer/ |
| Current File : //lib/calibre/calibre/gui2/viewer/toolbars.py |
#!/usr/bin/env python3
# License: GPL v3 Copyright: 2019, Kovid Goyal <kovid at kovidgoyal.net>
import os
from functools import partial
from qt.core import (
QAction, QGroupBox, QHBoxLayout, QIcon, QKeySequence, QLabel, QListWidget,
QListWidgetItem, QMenu, Qt, QToolBar, QToolButton, QVBoxLayout, pyqtSignal, QDialog,
QAbstractItemView, QDialogButtonBox
)
from qt.webengine import QWebEnginePage
from calibre.constants import ismacos
from calibre.gui2 import elided_text
from calibre.gui2.viewer.config import get_session_pref
from calibre.gui2.viewer.shortcuts import index_to_key_sequence
from calibre.gui2.viewer.web_view import set_book_path, vprefs
from calibre.gui2.widgets2 import Dialog
from calibre.utils.icu import primary_sort_key
class Action:
__slots__ = ('icon', 'text', 'shortcut_action')
def __init__(self, icon=None, text=None, shortcut_action=None):
self.icon, self.text, self.shortcut_action = QIcon(I(icon)), text, shortcut_action
class Actions:
def __init__(self, a):
self.__dict__.update(a)
self.all_action_names = frozenset(a)
def all_actions():
if not hasattr(all_actions, 'ans'):
amap = {
'color_scheme': Action('format-fill-color.png', _('Switch color scheme')),
'back': Action('back.png', _('Back')),
'forward': Action('forward.png', _('Forward')),
'open': Action('document_open.png', _('Open e-book')),
'copy': Action('edit-copy.png', _('Copy to clipboard'), 'copy_to_clipboard'),
'increase_font_size': Action('font_size_larger.png', _('Increase font size'), 'increase_font_size'),
'decrease_font_size': Action('font_size_smaller.png', _('Decrease font size'), 'decrease_font_size'),
'fullscreen': Action('page.png', _('Toggle full screen'), 'toggle_full_screen'),
'next': Action('next.png', _('Next page'), 'next'),
'previous': Action('previous.png', _('Previous page'), 'previous'),
'next_section': Action('arrow-down.png', _('Next section'), 'next_section'),
'previous_section': Action('arrow-up.png', _('Previous section'), 'previous_section'),
'toc': Action('toc.png', _('Table of Contents'), 'toggle_toc'),
'search': Action('search.png', _('Search'), 'toggle_search'),
'bookmarks': Action('bookmarks.png', _('Bookmarks'), 'toggle_bookmarks'),
'inspector': Action('debug.png', _('Inspector'), 'toggle_inspector'),
'reference': Action('reference.png', _('Toggle Reference mode'), 'toggle_reference_mode'),
'autoscroll': Action('auto-scroll.png', _('Toggle auto-scrolling'), 'toggle_autoscroll'),
'lookup': Action('generic-library.png', _('Lookup words'), 'toggle_lookup'),
'chrome': Action('tweaks.png', _('Show viewer controls'), 'show_chrome_force'),
'mode': Action('scroll.png', _('Toggle paged mode'), 'toggle_paged_mode'),
'print': Action('print.png', _('Print book'), 'print'),
'preferences': Action('config.png', _('Preferences'), 'preferences'),
'metadata': Action('metadata.png', _('Show book metadata'), 'metadata'),
'toggle_read_aloud': Action('bullhorn.png', _('Read aloud'), 'toggle_read_aloud'),
'toggle_highlights': Action('highlight_only_on.png', _('Browse highlights in book'), 'toggle_highlights'),
'select_all': Action('edit-select-all.png', _('Select all text in the current file')),
'edit_book': Action('edit_book.png', _('Edit this book'), 'edit_book'),
'reload_book': Action('reload.png', _('Reload this book'), 'reload_book'),
}
all_actions.ans = Actions(amap)
return all_actions.ans
DEFAULT_ACTIONS = (
'back', 'forward', None, 'open', 'copy', 'increase_font_size', 'decrease_font_size', 'fullscreen', 'color_scheme',
None, 'previous', 'next', None, 'toc', 'search', 'bookmarks', 'lookup', 'toggle_highlights', 'chrome', None,
'mode', 'print', 'preferences', 'metadata', 'inspector'
)
def current_actions():
ans = vprefs.get('actions-toolbar-actions')
if not ans:
ans = DEFAULT_ACTIONS
return tuple(ans)
class ToolBar(QToolBar):
def __init__(self, parent=None):
QToolBar.__init__(self, parent)
self.setWindowTitle(_('Toolbar'))
self.shortcut_actions = {}
self.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonIconOnly)
self.setVisible(False)
self.setAllowedAreas(Qt.ToolBarArea.AllToolBarAreas)
self.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
def create_shortcut_action(self, name):
a = getattr(all_actions(), name)
sc = a.shortcut_action
a = QAction(a.icon, a.text, self)
connect_lambda(a.triggered, self, lambda self: self.action_triggered.emit(sc))
self.shortcut_actions[sc] = a
return a
class ActionsToolBar(ToolBar):
action_triggered = pyqtSignal(object)
open_book_at_path = pyqtSignal(object)
def __init__(self, parent=None):
ToolBar.__init__(self, parent)
self.setObjectName('actions_toolbar')
self.prevent_sleep_cookie = None
self.customContextMenuRequested.connect(self.show_context_menu)
def update_action_state(self, book_open):
for ac in self.shortcut_actions.values():
ac.setEnabled(book_open)
self.search_action.setEnabled(book_open)
self.color_scheme_action.setEnabled(book_open)
def show_context_menu(self, pos):
m = QMenu(self)
a = m.addAction(_('Customize this toolbar'))
a.triggered.connect(self.customize)
a = m.addAction(_('Hide this toolbar'))
a.triggered.connect(self.hide_toolbar)
m.exec(self.mapToGlobal(pos))
def hide_toolbar(self):
self.web_view.trigger_shortcut('toggle_toolbar')
def initialize(self, web_view, toggle_search_action):
self.web_view = web_view
shortcut_action = self.create_shortcut_action
aa = all_actions()
self.action_triggered.connect(web_view.trigger_shortcut)
page = web_view.page()
web_view.paged_mode_changed.connect(self.update_mode_action)
web_view.reference_mode_changed.connect(self.update_reference_mode_action)
web_view.standalone_misc_settings_changed.connect(self.update_visibility)
web_view.autoscroll_state_changed.connect(self.update_autoscroll_action)
web_view.read_aloud_state_changed.connect(self.update_read_aloud_action)
web_view.customize_toolbar.connect(self.customize, type=Qt.ConnectionType.QueuedConnection)
web_view.view_created.connect(self.on_view_created)
self.back_action = page.action(QWebEnginePage.WebAction.Back)
self.back_action.setIcon(aa.back.icon)
self.back_action.setText(aa.back.text)
self.forward_action = page.action(QWebEnginePage.WebAction.Forward)
self.forward_action.setIcon(aa.forward.icon)
self.forward_action.setText(aa.forward.text)
self.select_all_action = a = page.action(QWebEnginePage.WebAction.SelectAll)
a.setIcon(aa.select_all.icon), a.setText(aa.select_all.text)
self.open_action = a = QAction(aa.open.icon, aa.open.text, self)
self.open_menu = m = QMenu(self)
a.setMenu(m)
m.aboutToShow.connect(self.populate_open_menu)
connect_lambda(a.triggered, self, lambda self: self.open_book_at_path.emit(None))
self.copy_action = shortcut_action('copy')
self.increase_font_size_action = shortcut_action('increase_font_size')
self.decrease_font_size_action = shortcut_action('decrease_font_size')
self.fullscreen_action = shortcut_action('fullscreen')
self.next_action = shortcut_action('next')
self.previous_action = shortcut_action('previous')
self.next_section_action = shortcut_action('next_section')
self.previous_section_action = shortcut_action('previous_section')
self.search_action = a = toggle_search_action
a.setText(aa.search.text), a.setIcon(aa.search.icon)
self.toc_action = a = shortcut_action('toc')
a.setCheckable(True)
self.bookmarks_action = a = shortcut_action('bookmarks')
a.setCheckable(True)
self.reference_action = a = shortcut_action('reference')
a.setCheckable(True)
self.toggle_highlights_action = self.highlights_action = a = shortcut_action('toggle_highlights')
a.setCheckable(True)
self.toggle_read_aloud_action = a = shortcut_action('toggle_read_aloud')
a.setCheckable(True)
self.lookup_action = a = shortcut_action('lookup')
a.setCheckable(True)
self.inspector_action = a = shortcut_action('inspector')
a.setCheckable(True)
self.autoscroll_action = a = shortcut_action('autoscroll')
a.setCheckable(True)
self.update_autoscroll_action(False)
self.update_read_aloud_action(False)
self.chrome_action = shortcut_action('chrome')
self.mode_action = a = shortcut_action('mode')
a.setCheckable(True)
self.print_action = shortcut_action('print')
self.preferences_action = shortcut_action('preferences')
self.metadata_action = shortcut_action('metadata')
self.edit_book_action = shortcut_action('edit_book')
self.reload_book_action = shortcut_action('reload_book')
self.update_mode_action()
self.color_scheme_action = a = QAction(aa.color_scheme.icon, aa.color_scheme.text, self)
self.color_scheme_menu = m = QMenu(self)
a.setMenu(m)
m.aboutToShow.connect(self.populate_color_scheme_menu)
self.add_actions()
def add_actions(self):
self.clear()
actions = current_actions()
for x in actions:
if x is None:
self.addSeparator()
else:
try:
self.addAction(getattr(self, f'{x}_action'))
except AttributeError:
pass
w = self.widgetForAction(self.color_scheme_action)
if w:
w.setPopupMode(QToolButton.ToolButtonPopupMode.InstantPopup)
def update_mode_action(self):
mode = get_session_pref('read_mode', default='paged', group=None)
a = self.mode_action
if mode == 'paged':
a.setChecked(False)
a.setToolTip(_('Switch to flow mode -- where the text is not broken into pages'))
else:
a.setChecked(True)
a.setToolTip(_('Switch to paged mode -- where the text is broken into pages'))
def change_sleep_permission(self, disallow_sleep=True):
from .control_sleep import prevent_sleep, allow_sleep
if disallow_sleep:
if self.prevent_sleep_cookie is None:
try:
self.prevent_sleep_cookie = prevent_sleep()
except Exception:
import traceback
traceback.print_exc()
else:
if self.prevent_sleep_cookie is not None:
try:
allow_sleep(self.prevent_sleep_cookie)
except Exception:
import traceback
traceback.print_exc()
self.prevent_sleep_cookie = None
def update_autoscroll_action(self, active):
self.autoscroll_action.setChecked(active)
self.autoscroll_action.setToolTip(
_('Turn off auto-scrolling') if active else _('Turn on auto-scrolling'))
self.change_sleep_permission(active)
def update_read_aloud_action(self, active):
self.toggle_read_aloud_action.setChecked(active)
self.toggle_read_aloud_action.setToolTip(
_('Stop reading') if active else _('Read the text of the book aloud'))
self.change_sleep_permission(active)
def update_reference_mode_action(self, enabled):
self.reference_action.setChecked(enabled)
def update_dock_actions(self, visibility_map):
for k in ('toc', 'bookmarks', 'lookup', 'inspector', 'highlights'):
ac = getattr(self, f'{k}_action')
ac.setChecked(visibility_map[k])
def set_tooltips(self, rmap):
aliases = {'show_chrome_force': 'show_chrome'}
def as_text(idx):
return index_to_key_sequence(idx).toString(QKeySequence.SequenceFormat.NativeText)
def set_it(a, sc):
x = rmap.get(sc)
if x is not None:
keys = sorted(filter(None, map(as_text, x)))
if keys:
a.setToolTip('{} [{}]'.format(a.text(), ', '.join(keys)))
for sc, a in self.shortcut_actions.items():
sc = aliases.get(sc, sc)
set_it(a, sc)
for a, sc in ((self.forward_action, 'forward'), (self.back_action, 'back'), (self.search_action, 'start_search')):
set_it(a, sc)
def populate_open_menu(self):
m = self.open_menu
m.clear()
recent = get_session_pref('standalone_recently_opened', group=None, default=())
if recent:
for entry in recent:
try:
path = os.path.abspath(entry['pathtoebook'])
except Exception:
continue
if hasattr(set_book_path, 'pathtoebook') and path == os.path.abspath(set_book_path.pathtoebook):
continue
if os.path.exists(path):
m.addAction('{}\t {}'.format(
elided_text(entry['title'], pos='right', width=250),
elided_text(os.path.basename(path), width=250))).triggered.connect(partial(
self.open_book_at_path.emit, path))
else:
self.web_view.remove_recently_opened(path)
def on_view_created(self, data):
self.default_color_schemes = data['default_color_schemes']
def populate_color_scheme_menu(self):
m = self.color_scheme_menu
m.clear()
ccs = get_session_pref('current_color_scheme', group=None) or ''
ucs = get_session_pref('user_color_schemes', group=None) or {}
def add_action(key, defns):
a = m.addAction(defns[key]['name'])
a.setCheckable(True)
a.setObjectName(f'color-switch-action:{key}')
a.triggered.connect(self.color_switch_triggerred)
if key == ccs:
a.setChecked(True)
for key in sorted(ucs, key=lambda x: primary_sort_key(ucs[x]['name'])):
add_action(key, ucs)
m.addSeparator()
for key in sorted(self.default_color_schemes, key=lambda x: primary_sort_key(self.default_color_schemes[x]['name'])):
add_action(key, self.default_color_schemes)
def color_switch_triggerred(self):
key = self.sender().objectName().partition(':')[-1]
self.action_triggered.emit('switch_color_scheme:' + key)
def update_visibility(self):
self.setVisible(bool(get_session_pref('show_actions_toolbar', default=False)))
@property
def visible_in_fullscreen(self):
return bool(get_session_pref('show_actions_toolbar_in_fullscreen', default=False))
def customize(self):
d = ConfigureToolBar(parent=self.parent())
if d.exec() == QDialog.DialogCode.Accepted:
self.add_actions()
class ActionsList(QListWidget):
def __init__(self, actions, parent=None, is_source=True):
QListWidget.__init__(self, parent)
self.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection)
self.setDragEnabled(True)
self.viewport().setAcceptDrops(True)
self.setDropIndicatorShown(True)
self.setDragDropMode(QAbstractItemView.DragDropMode.InternalMove)
self.setDefaultDropAction(Qt.DropAction.CopyAction if ismacos else Qt.DropAction.MoveAction)
self.setMinimumHeight(400)
self.is_source = is_source
if is_source:
actions = self.sort_actions_alphabetically(actions)
actions = [None] + actions
self.set_names(actions)
def sort_actions_alphabetically(self, actions):
aa = all_actions()
return sorted(actions, key=lambda name: primary_sort_key(getattr(aa, name).text) if name else primary_sort_key(''))
def add_item_from_name(self, action):
aa = all_actions()
if action is None:
i = QListWidgetItem('-- {} --'.format(_('Separator')), self)
else:
try:
a = getattr(aa, action)
except AttributeError:
return
i = QListWidgetItem(a.icon, a.text, self)
i.setData(Qt.ItemDataRole.UserRole, action)
return i
def set_names(self, names):
self.clear()
for name in names:
self.add_item_from_name(name)
def remove_item(self, item):
action = item.data(Qt.ItemDataRole.UserRole)
if action is not None or not self.is_source:
self.takeItem(self.row(item))
return action
def remove_selected(self):
ans = []
for item in tuple(self.selectedItems()):
ans.append(self.remove_item(item))
return ans
def add_names(self, names):
for action in names:
if action or not self.is_source:
self.add_item_from_name(action)
if self.is_source:
actions = self.sort_actions_alphabetically(self.names)
self.set_names(actions)
@property
def names(self):
for i in range(self.count()):
item = self.item(i)
yield item.data(Qt.ItemDataRole.UserRole)
class ConfigureToolBar(Dialog):
def __init__(self, parent=None):
Dialog.__init__(self, _('Configure the toolbar'), 'configure-viewer-toolbar', parent=parent, prefs=vprefs)
def setup_ui(self):
acnames = all_actions().all_action_names
self.available_actions = ActionsList(acnames - frozenset(current_actions()), parent=self)
self.available_actions.itemDoubleClicked.connect(self.add_item)
self.current_actions = ActionsList(current_actions(), parent=self, is_source=False)
self.current_actions.itemDoubleClicked.connect(self.remove_item)
self.l = l = QVBoxLayout(self)
self.la = la = QLabel(_('Choose the actions you want on the toolbar.'
' Drag and drop items in the right hand list to re-arrange the toolbar.'))
la.setWordWrap(True)
l.addWidget(la)
self.bv = bv = QVBoxLayout()
bv.addStretch(10)
self.add_button = b = QToolButton(self)
b.setIcon(QIcon(I('forward.png'))), b.setToolTip(_('Add selected actions to the toolbar'))
bv.addWidget(b), bv.addStretch(10)
b.clicked.connect(self.add_actions)
self.remove_button = b = QToolButton(self)
b.setIcon(QIcon(I('back.png'))), b.setToolTip(_('Remove selected actions from the toolbar'))
b.clicked.connect(self.remove_actions)
bv.addWidget(b), bv.addStretch(10)
self.h = h = QHBoxLayout()
l.addLayout(h)
self.lg = lg = QGroupBox(_('A&vailable actions'), self)
lg.v = v = QVBoxLayout(lg)
v.addWidget(self.available_actions)
h.addWidget(lg)
self.rg = rg = QGroupBox(_('&Current actions'), self)
rg.v = v = QVBoxLayout(rg)
v.addWidget(self.current_actions)
h.addLayout(bv), h.addWidget(rg)
l.addWidget(self.bb)
self.rdb = b = self.bb.addButton(_('Restore defaults'), QDialogButtonBox.ButtonRole.ActionRole)
b.clicked.connect(self.restore_defaults)
def remove_actions(self):
names = self.current_actions.remove_selected()
self.available_actions.add_names(names)
def remove_item(self, item):
names = self.current_actions.remove_item(item),
self.available_actions.add_names(names)
def add_actions(self):
names = self.available_actions.remove_selected()
self.current_actions.add_names(names)
def add_item(self, item):
names = self.available_actions.remove_item(item),
self.current_actions.add_names(names)
def restore_defaults(self):
self.current_actions.set_names(DEFAULT_ACTIONS)
acnames = all_actions().all_action_names
rest = acnames - frozenset(DEFAULT_ACTIONS)
rest = [None] + list(rest)
self.available_actions.set_names(rest)
def accept(self):
ans = tuple(self.current_actions.names)
if ans == DEFAULT_ACTIONS:
vprefs.__delitem__('actions-toolbar-actions')
else:
vprefs.set('actions-toolbar-actions', ans)
return Dialog.accept(self)
if __name__ == '__main__':
from calibre.gui2 import Application
app = Application([])
ConfigureToolBar().exec()