Editor preferences

This commit is contained in:
Kovid Goyal 2013-11-30 11:03:21 +05:30
parent 43770ce0f2
commit ea49fe8462
5 changed files with 163 additions and 12 deletions

View file

@ -13,7 +13,7 @@
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 @@ def keyPressEvent(self, e):
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 @@ def add_fonts(self):
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 @@ def add_fonts(self):
@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

View file

@ -66,7 +66,9 @@ def __call__(self, gui):
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

View file

@ -55,12 +55,12 @@ def __init__(self, parent=None):
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 @@ def apply_theme(self, theme):
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):

View file

@ -77,6 +77,9 @@ def replace_data(self, raw, only_if_different=True):
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)

View file

@ -6,12 +6,135 @@
__license__ = 'GPL v3'
__copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
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 @@ def __init__(self, gui, initial_panel=None):
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 @@ def __init__(self, gui, initial_panel=None):
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 @@ def __init__(self, gui, initial_panel=None):
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()):