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])