From 5036b68d8a0980d44969e3aade8db8b9db633ffb Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 20 Jun 2014 18:24:28 +0530 Subject: [PATCH] Edit Book: Allow customization of toolbars --- src/calibre/gui2/tweak_book/__init__.py | 2 +- src/calibre/gui2/tweak_book/boss.py | 5 + src/calibre/gui2/tweak_book/editor/widget.py | 4 +- src/calibre/gui2/tweak_book/preferences.py | 204 ++++++++++++++++++- src/calibre/gui2/tweak_book/ui.py | 27 ++- 5 files changed, 221 insertions(+), 21 deletions(-) diff --git a/src/calibre/gui2/tweak_book/__init__.py b/src/calibre/gui2/tweak_book/__init__.py index a78ee0a7b5..83afbbda4b 100644 --- a/src/calibre/gui2/tweak_book/__init__.py +++ b/src/calibre/gui2/tweak_book/__init__.py @@ -84,7 +84,7 @@ actions = NonReplaceDict() editors = NonReplaceDict() toolbar_actions = NonReplaceDict() editor_toolbar_actions = { - 'html':NonReplaceDict(), 'xml':NonReplaceDict(), 'css':NonReplaceDict()} + 'format':NonReplaceDict(), 'html':NonReplaceDict(), 'xml':NonReplaceDict(), 'css':NonReplaceDict()} TOP = object() dictionaries = Dictionaries() diff --git a/src/calibre/gui2/tweak_book/boss.py b/src/calibre/gui2/tweak_book/boss.py index efed019554..b71ba9f4ce 100644 --- a/src/calibre/gui2/tweak_book/boss.py +++ b/src/calibre/gui2/tweak_book/boss.py @@ -129,6 +129,11 @@ class Boss(QObject): if p.dictionaries_changed: dictionaries.clear_caches() dictionaries.initialize(force=True) # Reread user dictionaries + if p.toolbars_changed: + self.gui.populate_toolbars() + for ed in editors.itervalues(): + if hasattr(ed, 'populate_toolbars'): + ed.populate_toolbars() if ret == p.Accepted: setup_cssutils_serialization() self.gui.apply_settings() diff --git a/src/calibre/gui2/tweak_book/editor/widget.py b/src/calibre/gui2/tweak_book/editor/widget.py index db67205527..ee47252769 100644 --- a/src/calibre/gui2/tweak_book/editor/widget.py +++ b/src/calibre/gui2/tweak_book/editor/widget.py @@ -39,7 +39,7 @@ def create_icon(text, palette=None, sz=32, divider=2): def register_text_editor_actions(_reg, palette): def reg(*args, **kw): ac = _reg(*args) - for s in kw.get('syntaxes', ('html',)): + for s in kw.get('syntaxes', ('format',)): editor_toolbar_actions[s][args[3]] = ac return ac @@ -67,7 +67,7 @@ def register_text_editor_actions(_reg, palette): ac = reg('view-image', _('&Insert image'), ('insert_resource', 'image'), 'insert-image', (), _('Insert an image into the text'), syntaxes=('html', 'css')) ac.setToolTip(_('

Insert image

Insert an image into the text')) - ac = reg('insert-link', _('Insert &hyperlink'), ('insert_hyperlink',), 'insert-hyperlink', (), _('Insert hyperlink'), syntaxes=('html', 'css')) + ac = reg('insert-link', _('Insert &hyperlink'), ('insert_hyperlink',), 'insert-hyperlink', (), _('Insert hyperlink'), syntaxes=('html',)) ac.setToolTip(_('

Insert hyperlink

Insert a hyperlink into the text')) for i, name in enumerate(('h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p')): diff --git a/src/calibre/gui2/tweak_book/preferences.py b/src/calibre/gui2/tweak_book/preferences.py index aec7cce127..e043fc989d 100644 --- a/src/calibre/gui2/tweak_book/preferences.py +++ b/src/calibre/gui2/tweak_book/preferences.py @@ -10,15 +10,18 @@ from operator import attrgetter, methodcaller from collections import namedtuple from future_builtins import map from itertools import product +from functools import partial +from copy import copy, deepcopy from PyQt4.Qt import ( QDialog, QGridLayout, QStackedWidget, QDialogButtonBox, QListWidget, QListWidgetItem, QIcon, QWidget, QSize, QFormLayout, Qt, QSpinBox, QCheckBox, pyqtSignal, QDoubleSpinBox, QComboBox, QLabel, QFont, - QFontComboBox, QPushButton, QSizePolicy, QHBoxLayout) + QFontComboBox, QPushButton, QSizePolicy, QHBoxLayout, QGroupBox, + QToolButton, QVBoxLayout, QSpacerItem) from calibre.gui2.keyboard import ShortcutConfig -from calibre.gui2.tweak_book import tprefs +from calibre.gui2.tweak_book import tprefs, toolbar_actions, editor_toolbar_actions from calibre.gui2.tweak_book.editor.themes import default_theme, all_theme_names, ThemeEditor from calibre.gui2.tweak_book.spell import ManageDictionaries from calibre.gui2.font_family_chooser import FontFamilyChooser @@ -64,7 +67,7 @@ class BasicSettings(QWidget): # {{{ prefs = prefs or tprefs widget = QComboBox(self) widget.currentIndexChanged[int].connect(self.emit_changed) - for key, human in sorted(choices.iteritems(), key=lambda (key, human): human or key): + for key, human in sorted(choices.iteritems(), key=lambda key_human: key_human[1] or key_human[0]): widget.addItem(human or key, key) def getter(w): @@ -226,7 +229,7 @@ class EditorSettings(BasicSettings): s = self.settings['editor_theme'] current_val = s.getter(s.widget) s.widget.clear() - for key, human in sorted(choices.iteritems(), key=lambda (key, human): human or key): + for key, human in sorted(choices.iteritems(), key=lambda key_human1: key_human1[1] or key_human1[0]): s.widget.addItem(human or key, key) s.setter(s.widget, current_val) if d.theme_name: @@ -312,6 +315,193 @@ class PreviewSettings(BasicSettings): w.setMinimum(4), w.setMaximum(100), w.setSuffix(' px') l.addRow(_('Mi&nimum font size:'), w) +# ToolbarSettings {{{ + +class ToolbarList(QListWidget): + + def __init__(self, parent=None): + QListWidget.__init__(self, parent) + self.setSelectionMode(self.ExtendedSelection) + +class ToolbarSettings(QWidget): + + changed_signal = pyqtSignal() + + def __init__(self, parent=None): + QWidget.__init__(self, parent) + self.l = gl = QGridLayout(self) + self.setLayout(gl) + self.changed = False + + self.bars = b = QComboBox(self) + b.addItem(_('Choose which toolbar you want to customize')) + ft = _('Tools for %s editors') + for name, text in ( + ('global_book_toolbar', _('Book wide actions'),), + ('global_tools_toolbar', _('Book wide tools'),), + ('editor_html_toolbar', ft % 'HTML',), + ('editor_css_toolbar', ft % 'CSS',), + ('editor_xml_toolbar', ft % 'XML',), + ('editor_format_toolbar', _('Text formatting actions'),), + ): + b.addItem(text, name) + self.la = la = QLabel(_('&Toolbar to customize:')) + la.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) + la.setBuddy(b) + gl.addWidget(la), gl.addWidget(b, 0, 1) + self.sl = l = QGridLayout() + gl.addLayout(l, 1, 0, 1, -1) + + self.gb1 = gb1 = QGroupBox(_('A&vailable actions'), self) + self.gb2 = gb2 = QGroupBox(_('&Current actions'), self) + gb1.setFlat(True), gb2.setFlat(True) + gb1.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) + gb2.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) + l.addWidget(gb1, 0, 0, -1, 1), l.addWidget(gb2, 0, 2, -1, 1) + self.available, self.current = ToolbarList(self), ToolbarList(self) + self.ub = b = QToolButton(self) + b.clicked.connect(partial(self.move, up=True)) + b.setToolTip(_('Move selected action up')), b.setIcon(QIcon(I('arrow-up.png'))) + self.db = b = QToolButton(self) + b.clicked.connect(partial(self.move, up=False)) + b.setToolTip(_('Move selected action down')), b.setIcon(QIcon(I('arrow-down.png'))) + self.gl1 = gl1 = QVBoxLayout() + gl1.addWidget(self.available), gb1.setLayout(gl1) + self.gl2 = gl2 = QGridLayout() + gl2.addWidget(self.current, 0, 0, -1, 1) + gl2.addWidget(self.ub, 0, 1), gl2.addWidget(self.db, 2, 1) + gb2.setLayout(gl2) + self.lb = b = QToolButton(self) + b.setToolTip(_('Add selected actions to toolbar')), b.setIcon(QIcon(I('forward.png'))) + l.addWidget(b, 1, 1), b.clicked.connect(self.add_action) + self.rb = b = QToolButton(self) + b.setToolTip(_('Remove selected actions from toolbar')), b.setIcon(QIcon(I('back.png'))) + l.addWidget(b, 3, 1), b.clicked.connect(self.remove_action) + self.si = QSpacerItem(20, 10, hPolicy=QSizePolicy.Preferred, vPolicy=QSizePolicy.Expanding) + l.setRowStretch(0, 10), l.setRowStretch(2, 10), l.setRowStretch(4, 10) + l.addItem(self.si, 4, 1) + + self.read_settings() + self.toggle_visibility(False) + self.bars.currentIndexChanged.connect(self.bar_changed) + + def read_settings(self, prefs=None): + prefs = prefs or tprefs + val = self.original_settings = {} + for i in xrange(1, self.bars.count()): + name = unicode(self.bars.itemData(i).toString()) + val[name] = copy(prefs[name]) + self.current_settings = deepcopy(val) + + @property + def current_name(self): + return unicode(self.bars.itemData(self.bars.currentIndex()).toString()) + + def build_lists(self): + self.available.clear(), self.current.clear() + name = self.current_name + if not name: + return + items = self.current_settings[name] + applied = set(items) + all_items = toolbar_actions if name.startswith('global_') else editor_toolbar_actions[name.split('_')[1]] + blank = QIcon(I('blank.png')) + + def to_item(key, ac, parent): + ic = ac.icon() + if not ic or ic.isNull(): + ic = blank + ans = QListWidgetItem(ic, unicode(ac.text()).replace('&', ''), parent) + ans.setData(Qt.UserRole, key) + ans.setToolTip(ac.toolTip()) + return ans + + for key, ac in sorted(all_items.iteritems(), key=lambda k_ac: unicode(k_ac[1].text())): + if key not in applied: + to_item(key, ac, self.available) + if name == 'global_book_toolbar' and 'donate' not in applied: + QListWidgetItem(QIcon(I('donate.png')), _('Donate'), self.available).setData(Qt.UserRole, 'donate') + + QListWidgetItem(blank, '--- %s ---' % _('Separator'), self.available) + for key in items: + if key is None: + QListWidgetItem(blank, '--- %s ---' % _('Separator'), self.current) + else: + if key == 'donate': + QListWidgetItem(QIcon(I('donate.png')), _('Donate'), self.current).setData(Qt.UserRole, 'donate') + else: + try: + ac = all_items[key] + except KeyError: + pass + to_item(key, ac, self.current) + + def bar_changed(self): + name = self.current_name + self.toggle_visibility(bool(name)) + self.build_lists() + + def toggle_visibility(self, visible): + for x in ('gb1', 'gb2', 'lb', 'rb'): + getattr(self, x).setVisible(visible) + + def move(self, up=True): + r = self.current.currentRow() + v = self.current + if r < 0 or (r < 1 and up) or (r > v.count() - 2 and not up): + return + try: + s = self.current_settings[self.current_name] + except KeyError: + return + item = v.takeItem(r) + nr = r + (-1 if up else 1) + v.insertItem(nr, item) + v.setCurrentItem(item) + s[r], s[nr] = s[nr], s[r] + self.changed_signal.emit() + + def add_action(self): + try: + s = self.current_settings[self.current_name] + except KeyError: + return + names = [unicode(i.data(Qt.UserRole).toString()) for i in self.available.selectedItems()] + if not names: + return + for n in names: + s.append(n or None) + self.build_lists() + self.changed_signal.emit() + + def remove_action(self): + try: + s = self.current_settings[self.current_name] + except KeyError: + return + rows = sorted({self.current.row(i) for i in self.current.selectedItems()}, reverse=True) + if not rows: + return + for r in rows: + s.pop(r) + self.build_lists() + self.changed_signal.emit() + + def restore_defaults(self): + o = self.original_settings + self.read_settings(tprefs.defaults) + self.original_settings = o + self.build_lists() + self.changed_signal.emit() + + def commit(self): + if self.original_settings != self.current_settings: + self.changed = True + with tprefs: + tprefs.update(self.current_settings) + +# }}} + class Preferences(QDialog): def __init__(self, gui, initial_panel=None): @@ -357,12 +547,14 @@ class Preferences(QDialog): self.integration_panel = IntegrationSettings(self) self.main_window_panel = MainWindowSettings(self) self.preview_panel = PreviewSettings(self) + self.toolbars_panel = ToolbarSettings(self) for name, icon, panel in [ (_('Main window'), 'page.png', 'main_window'), (_('Editor settings'), 'modified.png', 'editor'), (_('Preview settings'), 'viewer.png', 'preview'), (_('Keyboard shortcuts'), 'keyboard-prefs.png', 'keyboard'), + (_('Toolbars'), 'wizard.png', 'toolbars'), (_('Integration with calibre'), 'lt.png', 'integration'), ]: i = QListWidgetItem(QIcon(I(icon)), name, cl) @@ -383,6 +575,10 @@ class Preferences(QDialog): def dictionaries_changed(self): return self.editor_panel.dictionaries_changed + @property + def toolbars_changed(self): + return self.toolbars_panel.changed + def restore_all_defaults(self): for i in xrange(self.stacks.count()): w = self.stacks.widget(i) diff --git a/src/calibre/gui2/tweak_book/ui.py b/src/calibre/gui2/tweak_book/ui.py index 98b6ab3989..0bfeda5906 100644 --- a/src/calibre/gui2/tweak_book/ui.py +++ b/src/calibre/gui2/tweak_book/ui.py @@ -310,7 +310,7 @@ class Main(MainWindow): self.action_save = treg('save.png', _('&Save'), self.boss.save_book, 'save-book', 'Ctrl+S', _('Save book')) self.action_save.setEnabled(False) self.action_save_copy = treg('save.png', _('Save a ©'), self.boss.save_copy, 'save-copy', 'Ctrl+Alt+S', _('Save a copy of the book')) - self.action_quit = treg('quit.png', _('&Quit'), self.boss.quit, 'quit', 'Ctrl+Q', _('Quit')) + self.action_quit = treg('window-close.png', _('&Quit'), self.boss.quit, 'quit', 'Ctrl+Q', _('Quit')) self.action_preferences = treg('config.png', _('&Preferences'), self.boss.preferences, 'preferences', 'Ctrl+P', _('Preferences')) self.action_new_book = treg('book.png', _('Create &new, empty book'), self.boss.new_book, 'new-book', (), _('Create a new, empty book')) self.action_import_book = treg('book.png', _('&Import an HTML or DOCX file as a new book'), @@ -564,19 +564,18 @@ class Main(MainWindow): if ac is None: bar.addSeparator() elif ac == 'donate': - if not hasattr(self, 'donate_button'): - self.donate_button = b = ThrobbingButton(self) - b.clicked.connect(open_donate) - b.setAutoRaise(True) - self.donate_widget = w = create_donate_widget(b) - if hasattr(w, 'filler'): - w.filler.setVisible(False) - b.set_normal_icon_size(self.global_bar.iconSize().width(), self.global_bar.iconSize().height()) - b.setIcon(QIcon(I('donate.png'))) - b.setToolTip(_('Donate to support calibre development')) - if animate: - QTimer.singleShot(10, b.start_animation) - bar.addWidget(w) + self.donate_button = b = ThrobbingButton(self) + b.clicked.connect(open_donate) + b.setAutoRaise(True) + self.donate_widget = w = create_donate_widget(b) + if hasattr(w, 'filler'): + w.filler.setVisible(False) + b.set_normal_icon_size(self.global_bar.iconSize().width(), self.global_bar.iconSize().height()) + b.setIcon(QIcon(I('donate.png'))) + b.setToolTip(_('Donate to support calibre development')) + if animate: + QTimer.singleShot(10, b.start_animation) + bar.addWidget(w) else: try: bar.addAction(actions[ac])