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 @@ from PyQt4.Qt import (QFontInfo, QFontMetrics, Qt, QFont, QFontDatabase, QPen,
QStyledItemDelegate, QSize, QStyle, QStringListModel, pyqtSignal, QStyledItemDelegate, QSize, QStyle, QStringListModel, pyqtSignal,
QDialog, QVBoxLayout, QApplication, QFontComboBox, QPushButton, QDialog, QVBoxLayout, QApplication, QFontComboBox, QPushButton,
QToolButton, QGridLayout, QListView, QWidget, QDialogButtonBox, QIcon, QToolButton, QGridLayout, QListView, QWidget, QDialogButtonBox, QIcon,
QHBoxLayout, QLabel, QModelIndex, QLineEdit) QHBoxLayout, QLabel, QModelIndex, QLineEdit, QSizePolicy)
from calibre.constants import config_dir from calibre.constants import config_dir
from calibre.gui2 import choose_files, error_dialog, info_dialog from calibre.gui2 import choose_files, error_dialog, info_dialog
@ -230,9 +230,11 @@ class FontFamilyDialog(QDialog):
def find(self, backwards=False): def find(self, backwards=False):
i = self.view.currentIndex().row() i = self.view.currentIndex().row()
if i < 0: i = 0 if i < 0:
i = 0
q = icu_lower(unicode(self.search.text())).strip() 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, r = (xrange(i-1, -1, -1) if backwards else xrange(i+1,
len(self.families))) len(self.families)))
for j in r: for j in r:
@ -263,7 +265,8 @@ class FontFamilyDialog(QDialog):
files = choose_files(self, 'add fonts to calibre', files = choose_files(self, 'add fonts to calibre',
_('Select font files'), filters=[(_('TrueType/OpenType Fonts'), _('Select font files'), filters=[(_('TrueType/OpenType Fonts'),
['ttf', 'otf'])], all_files=False) ['ttf', 'otf'])], all_files=False)
if not files: return if not files:
return
families = set() families = set()
for f in files: for f in files:
try: try:
@ -298,7 +301,8 @@ class FontFamilyDialog(QDialog):
@property @property
def font_family(self): def font_family(self):
idx = self.view.currentIndex().row() idx = self.view.currentIndex().row()
if idx == 0: return None if idx == 0:
return None
return self.families[idx] return self.families[idx]
def current_changed(self): def current_changed(self):
@ -313,9 +317,11 @@ class FontFamilyChooser(QWidget):
def __init__(self, parent=None): def __init__(self, parent=None):
QWidget.__init__(self, parent) QWidget.__init__(self, parent)
self.l = l = QHBoxLayout() self.l = l = QHBoxLayout()
l.setContentsMargins(0, 0, 0, 0)
self.setLayout(l) self.setLayout(l)
self.button = QPushButton(self) self.button = QPushButton(self)
self.button.setIcon(QIcon(I('font.png'))) self.button.setIcon(QIcon(I('font.png')))
self.button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
l.addWidget(self.button) l.addWidget(self.button)
self.default_text = _('Choose &font family') self.default_text = _('Choose &font family')
self.font_family = None self.font_family = None

View File

@ -66,7 +66,9 @@ class Boss(QObject):
def preferences(self): def preferences(self):
p = Preferences(self.gui) p = Preferences(self.gui)
p.exec_() if p.exec_() == p.Accepted:
for ed in editors.itervalues():
ed.apply_settings()
def mkdtemp(self, prefix=''): def mkdtemp(self, prefix=''):
self.container_count += 1 self.container_count += 1

View File

@ -55,12 +55,12 @@ class TextEdit(QPlainTextEdit):
self.current_cursor_line = None self.current_cursor_line = None
self.current_search_mark = None self.current_search_mark = None
self.highlighter = SyntaxHighlighter(self) self.highlighter = SyntaxHighlighter(self)
self.line_number_area = LineNumbers(self)
self.apply_settings() self.apply_settings()
self.setMouseTracking(True) self.setMouseTracking(True)
self.cursorPositionChanged.connect(self.highlight_cursor_line) self.cursorPositionChanged.connect(self.highlight_cursor_line)
self.blockCountChanged[int].connect(self.update_line_number_area_width) self.blockCountChanged[int].connect(self.update_line_number_area_width)
self.updateRequest.connect(self.update_line_number_area) self.updateRequest.connect(self.update_line_number_area)
self.line_number_area = LineNumbers(self)
self.syntax = None self.syntax = None
@dynamic_property @dynamic_property
@ -118,6 +118,7 @@ class TextEdit(QPlainTextEdit):
self.number_width = max(map(lambda x:w.width(str(x)), xrange(10))) self.number_width = max(map(lambda x:w.width(str(x)), xrange(10)))
self.size_hint = QSize(100 * w.averageCharWidth(), 50 * w.height()) self.size_hint = QSize(100 * w.averageCharWidth(), 50 * w.height())
self.highlight_color = theme_color(theme, 'HighlightRegion', 'bg') self.highlight_color = theme_color(theme, 'HighlightRegion', 'bg')
self.highlight_cursor_line()
# }}} # }}}
def load_text(self, text, syntax='html', process_template=False): def load_text(self, text, syntax='html', process_template=False):

View File

@ -77,6 +77,9 @@ class Editor(QMainWindow):
if current != raw: if current != raw:
self.editor.replace_text(raw) self.editor.replace_text(raw)
def apply_settings(self, prefs=None):
self.editor.apply_settings(prefs=None)
def set_focus(self): def set_focus(self):
self.editor.setFocus(Qt.OtherFocusReason) self.editor.setFocus(Qt.OtherFocusReason)

View File

@ -6,12 +6,135 @@ from __future__ import (unicode_literals, division, absolute_import,
__license__ = 'GPL v3' __license__ = 'GPL v3'
__copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>' __copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
from operator import attrgetter, methodcaller
from collections import namedtuple
from PyQt4.Qt import ( from PyQt4.Qt import (
QDialog, QGridLayout, QStackedWidget, QDialogButtonBox, QListWidget, 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.keyboard import ShortcutConfig
from calibre.gui2.tweak_book import tprefs 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): class Preferences(QDialog):
@ -32,7 +155,7 @@ class Preferences(QDialog):
cl.setFlow(cl.TopToBottom) cl.setFlow(cl.TopToBottom)
cl.setMovement(cl.Static) cl.setMovement(cl.Static)
cl.setWrapping(False) cl.setWrapping(False)
cl.setSpacing(10) cl.setSpacing(15)
cl.setWordWrap(True) cl.setWordWrap(True)
l.addWidget(cl, 0, 0, 1, 1) l.addWidget(cl, 0, 0, 1, 1)
@ -40,7 +163,11 @@ class Preferences(QDialog):
bb.accepted.connect(self.accept) bb.accepted.connect(self.accept)
bb.rejected.connect(self.reject) bb.rejected.connect(self.reject)
self.rdb = b = bb.addButton(_('Restore all defaults'), bb.ResetRole) self.rdb = b = bb.addButton(_('Restore all defaults'), bb.ResetRole)
b.setToolTip(_('Restore defaults for all preferences'))
b.clicked.connect(self.restore_all_defaults) 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) l.addWidget(bb, 1, 0, 1, 2)
self.resize(800, 600) self.resize(800, 600)
@ -50,22 +177,34 @@ class Preferences(QDialog):
self.keyboard_panel = ShortcutConfig(self) self.keyboard_panel = ShortcutConfig(self)
self.keyboard_panel.initialize(gui.keyboard) 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) i = QListWidgetItem(QIcon(I(icon)), name, cl)
cl.addItem(i) cl.addItem(i)
self.stacks.addWidget(getattr(self, panel + '_panel')) self.stacks.addWidget(getattr(self, panel + '_panel'))
cl.setCurrentRow(0) cl.setCurrentRow(0)
cl.item(0).setSelected(True) cl.item(0).setSelected(True)
cl.setMaximumWidth(cl.sizeHintForColumn(0) + 30) w, h = cl.sizeHintForColumn(0), 0
l.setColumnStretch(1, 10) 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): def restore_all_defaults(self):
for i in xrange(self.stacks.count()): for i in xrange(self.stacks.count()):
w = self.stacks.widget(i) w = self.stacks.widget(i)
w.restore_defaults() w.restore_defaults()
def restore_current_defaults(self):
self.stacks.currentWidget().restore_defaults()
def accept(self): def accept(self):
tprefs.set('preferences_geom', bytearray(self.saveGeometry())) tprefs.set('preferences_geom', bytearray(self.saveGeometry()))
for i in xrange(self.stacks.count()): for i in xrange(self.stacks.count()):