diff --git a/src/calibre/gui2/font_family_chooser.py b/src/calibre/gui2/font_family_chooser.py index e3a2662ac4..512b187985 100644 --- a/src/calibre/gui2/font_family_chooser.py +++ b/src/calibre/gui2/font_family_chooser.py @@ -13,7 +13,7 @@ from PyQt4.Qt import (QFontInfo, QFontMetrics, Qt, QFont, QFontDatabase, QPen, QStyledItemDelegate, QSize, QStyle, QStringListModel, pyqtSignal, QDialog, QVBoxLayout, QApplication, QFontComboBox, QPushButton, QToolButton, QGridLayout, QListView, QWidget, QDialogButtonBox, QIcon, - QHBoxLayout, QLabel, QModelIndex, QLineEdit) + QHBoxLayout, QLabel, QModelIndex, QLineEdit, QSizePolicy) from calibre.constants import config_dir from calibre.gui2 import choose_files, error_dialog, info_dialog @@ -230,9 +230,11 @@ class FontFamilyDialog(QDialog): def find(self, backwards=False): i = self.view.currentIndex().row() - if i < 0: i = 0 + if i < 0: + i = 0 q = icu_lower(unicode(self.search.text())).strip() - if not q: return + if not q: + return r = (xrange(i-1, -1, -1) if backwards else xrange(i+1, len(self.families))) for j in r: @@ -263,7 +265,8 @@ class FontFamilyDialog(QDialog): files = choose_files(self, 'add fonts to calibre', _('Select font files'), filters=[(_('TrueType/OpenType Fonts'), ['ttf', 'otf'])], all_files=False) - if not files: return + if not files: + return families = set() for f in files: try: @@ -298,7 +301,8 @@ class FontFamilyDialog(QDialog): @property def font_family(self): idx = self.view.currentIndex().row() - if idx == 0: return None + if idx == 0: + return None return self.families[idx] def current_changed(self): @@ -313,9 +317,11 @@ class FontFamilyChooser(QWidget): def __init__(self, parent=None): QWidget.__init__(self, parent) self.l = l = QHBoxLayout() + l.setContentsMargins(0, 0, 0, 0) self.setLayout(l) self.button = QPushButton(self) self.button.setIcon(QIcon(I('font.png'))) + self.button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) l.addWidget(self.button) self.default_text = _('Choose &font family') self.font_family = None diff --git a/src/calibre/gui2/tweak_book/boss.py b/src/calibre/gui2/tweak_book/boss.py index 8c483ca134..fd63d998b5 100644 --- a/src/calibre/gui2/tweak_book/boss.py +++ b/src/calibre/gui2/tweak_book/boss.py @@ -66,7 +66,9 @@ class Boss(QObject): def preferences(self): p = Preferences(self.gui) - p.exec_() + if p.exec_() == p.Accepted: + for ed in editors.itervalues(): + ed.apply_settings() def mkdtemp(self, prefix=''): self.container_count += 1 diff --git a/src/calibre/gui2/tweak_book/editor/text.py b/src/calibre/gui2/tweak_book/editor/text.py index cc8d6b6b53..f3edd3da2a 100644 --- a/src/calibre/gui2/tweak_book/editor/text.py +++ b/src/calibre/gui2/tweak_book/editor/text.py @@ -55,12 +55,12 @@ class TextEdit(QPlainTextEdit): self.current_cursor_line = None self.current_search_mark = None self.highlighter = SyntaxHighlighter(self) + self.line_number_area = LineNumbers(self) self.apply_settings() self.setMouseTracking(True) self.cursorPositionChanged.connect(self.highlight_cursor_line) self.blockCountChanged[int].connect(self.update_line_number_area_width) self.updateRequest.connect(self.update_line_number_area) - self.line_number_area = LineNumbers(self) self.syntax = None @dynamic_property @@ -118,6 +118,7 @@ class TextEdit(QPlainTextEdit): self.number_width = max(map(lambda x:w.width(str(x)), xrange(10))) self.size_hint = QSize(100 * w.averageCharWidth(), 50 * w.height()) self.highlight_color = theme_color(theme, 'HighlightRegion', 'bg') + self.highlight_cursor_line() # }}} def load_text(self, text, syntax='html', process_template=False): diff --git a/src/calibre/gui2/tweak_book/editor/widget.py b/src/calibre/gui2/tweak_book/editor/widget.py index e5427f9b7f..9ed724007c 100644 --- a/src/calibre/gui2/tweak_book/editor/widget.py +++ b/src/calibre/gui2/tweak_book/editor/widget.py @@ -77,6 +77,9 @@ class Editor(QMainWindow): if current != raw: self.editor.replace_text(raw) + def apply_settings(self, prefs=None): + self.editor.apply_settings(prefs=None) + def set_focus(self): self.editor.setFocus(Qt.OtherFocusReason) diff --git a/src/calibre/gui2/tweak_book/preferences.py b/src/calibre/gui2/tweak_book/preferences.py index def01c51a3..481bf7621d 100644 --- a/src/calibre/gui2/tweak_book/preferences.py +++ b/src/calibre/gui2/tweak_book/preferences.py @@ -6,12 +6,135 @@ from __future__ import (unicode_literals, division, absolute_import, __license__ = 'GPL v3' __copyright__ = '2013, Kovid Goyal ' +from operator import attrgetter, methodcaller +from collections import namedtuple + from PyQt4.Qt import ( QDialog, QGridLayout, QStackedWidget, QDialogButtonBox, QListWidget, - QListWidgetItem, QIcon) + QListWidgetItem, QIcon, QWidget, QSize, QFormLayout, Qt, QSpinBox, + QCheckBox, pyqtSignal, QDoubleSpinBox, QComboBox) from calibre.gui2.keyboard import ShortcutConfig from calibre.gui2.tweak_book import tprefs +from calibre.gui2.tweak_book.editor.themes import default_theme, THEMES +from calibre.gui2.font_family_chooser import FontFamilyChooser + +class BasicSettings(QWidget): # {{{ + + changed_signal = pyqtSignal() + + def __init__(self, parent=None): + QWidget.__init__(self, parent) + self.settings = {} + self._prevent_changed = False + self.Setting = namedtuple('Setting', 'name prefs widget getter setter initial_value') + + def __call__(self, name, widget=None, getter=None, setter=None, prefs=None): + prefs = prefs or tprefs + defval = prefs.defaults[name] + inval = prefs[name] + if widget is None: + if isinstance(defval, bool): + widget = QCheckBox(self) + getter = getter or methodcaller('isChecked') + setter = setter or (lambda x, v: x.setChecked(v)) + widget.toggled.connect(self.emit_changed) + elif isinstance(defval, (int, float)): + widget = (QSpinBox if isinstance(defval, int) else QDoubleSpinBox)(self) + getter = getter or methodcaller('value') + setter = setter or (lambda x, v:x.setValue(v)) + widget.valueChanged.connect(self.emit_changed) + else: + raise TypeError('Unknown setting type for setting: %s' % name) + else: + if getter is None or setter is None: + raise ValueError("getter or setter not provided for: %s" % name) + self._prevent_changed = True + setter(widget, inval) + self._prevent_changed = False + + self.settings[name] = self.Setting(name, prefs, widget, getter, setter, inval) + return widget + + def choices_widget(self, name, choices, fallback_val, none_val, prefs=None): + prefs = prefs or tprefs + widget = QComboBox(self) + widget.currentIndexChanged[int].connect(self.emit_changed) + for key, human in choices.iteritems(): + widget.addItem(human or key, key) + + def getter(w): + ans = unicode(w.itemData(w.currentIndex()).toString()) + return {none_val:None}.get(ans, ans) + + def setter(w, val): + val = {None:none_val}.get(val, val) + idx = w.findData(val, flags=Qt.MatchFixedString|Qt.MatchCaseSensitive) + if idx == -1: + idx = w.findData(fallback_val, flags=Qt.MatchFixedString|Qt.MatchCaseSensitive) + w.setCurrentIndex(idx) + + return self(name, widget=widget, getter=getter, setter=setter, prefs=prefs) + + def emit_changed(self, *args): + if not self._prevent_changed: + self.changed_signal.emit() + + def commit(self): + with tprefs: + for name in self.settings: + cv = self.current_value(name) + if self.initial_value(name) != cv: + prefs = self.settings[name].prefs + if cv == self.default_value(name): + del prefs[name] + else: + prefs[name] = cv + + def restore_defaults(self): + for setting in self.settings.itervalues(): + setting.setter(setting.widget, self.default_value(setting.name)) + + def initial_value(self, name): + return self.settings[name].initial_value + + def current_value(self, name): + s = self.settings[name] + return s.getter(s.widget) + + def default_value(self, name): + s = self.settings[name] + return s.prefs.defaults[name] + + def setting_changed(self, name): + return self.current_value(name) != self.initial_value(name) +# }}} + +class EditorSettings(BasicSettings): + + def __init__(self, parent=None): + BasicSettings.__init__(self, parent) + self.l = l = QFormLayout(self) + self.setLayout(l) + + fc = FontFamilyChooser(self) + self('editor_font_family', widget=fc, getter=attrgetter('font_family'), setter=lambda x, val: setattr(x, 'font_family', val)) + fc.family_changed.connect(self.emit_changed) + l.addRow(_('Editor font &family:'), fc) + + fs = self('editor_font_size') + fs.setMinimum(8), fs.setSuffix(' pt'), fs.setMaximum(50) + l.addRow(_('Editor font &size:'), fs) + + auto_theme = _('Automatic (%s)') % default_theme() + choices = {k:k for k in THEMES} + choices['auto'] = auto_theme + theme = self.choices_widget('editor_theme', choices, 'auto', 'auto') + l.addRow(_('&Color scheme:'), theme) + + lw = self('editor_line_wrap') + lw.setText(_('&Wrap long lines in the editor')) + l.addRow(lw) class Preferences(QDialog): @@ -32,7 +155,7 @@ class Preferences(QDialog): cl.setFlow(cl.TopToBottom) cl.setMovement(cl.Static) cl.setWrapping(False) - cl.setSpacing(10) + cl.setSpacing(15) cl.setWordWrap(True) l.addWidget(cl, 0, 0, 1, 1) @@ -40,7 +163,11 @@ class Preferences(QDialog): bb.accepted.connect(self.accept) bb.rejected.connect(self.reject) self.rdb = b = bb.addButton(_('Restore all defaults'), bb.ResetRole) + b.setToolTip(_('Restore defaults for all preferences')) b.clicked.connect(self.restore_all_defaults) + self.rcdb = b = bb.addButton(_('Restore current defaults'), bb.ResetRole) + b.setToolTip(_('Restore defaults for currently displayed preferences')) + b.clicked.connect(self.restore_current_defaults) l.addWidget(bb, 1, 0, 1, 2) self.resize(800, 600) @@ -50,22 +177,34 @@ class Preferences(QDialog): self.keyboard_panel = ShortcutConfig(self) self.keyboard_panel.initialize(gui.keyboard) + self.editor_panel = EditorSettings(self) - for name, icon, panel in [(_('Keyboard Shortcuts'), 'keyboard-prefs.png', 'keyboard')]: + for name, icon, panel in [ + (_('Editor settings'), 'modified.png', 'editor'), + (_('Keyboard Shortcuts'), 'keyboard-prefs.png', 'keyboard') + ]: i = QListWidgetItem(QIcon(I(icon)), name, cl) cl.addItem(i) self.stacks.addWidget(getattr(self, panel + '_panel')) cl.setCurrentRow(0) cl.item(0).setSelected(True) - cl.setMaximumWidth(cl.sizeHintForColumn(0) + 30) - l.setColumnStretch(1, 10) + w, h = cl.sizeHintForColumn(0), 0 + for i in xrange(cl.count()): + h = max(h, cl.sizeHintForRow(i)) + cl.item(i).setSizeHint(QSize(w, h)) + + cl.setMaximumWidth(cl.sizeHintForColumn(0) + 35) + cl.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) def restore_all_defaults(self): for i in xrange(self.stacks.count()): w = self.stacks.widget(i) w.restore_defaults() + def restore_current_defaults(self): + self.stacks.currentWidget().restore_defaults() + def accept(self): tprefs.set('preferences_geom', bytearray(self.saveGeometry())) for i in xrange(self.stacks.count()):