#!/usr/bin/env python
__license__ = 'GPL v3'
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
import textwrap
from qt.core import (QWidget, pyqtSignal, QCheckBox, QAbstractSpinBox, QApplication,
QLineEdit, QComboBox, Qt, QIcon, QDialog, QVBoxLayout,
QDialogButtonBox, QListView, QEvent, QListWidget, QTableWidget)
from calibre.customize.ui import preferences_plugins
from calibre.utils.config import ConfigProxy
from calibre.gui2.complete2 import EditWithComplete
from calibre.gui2.widgets import HistoryLineEdit
from polyglot.builtins import string_or_bytes
class AbortCommit(Exception):
pass
class AbortInitialize(Exception):
pass
class Setting:
CHOICES_SEARCH_FLAGS = Qt.MatchFlag.MatchExactly | Qt.MatchFlag.MatchCaseSensitive
def __init__(self, name, config_obj, widget, gui_name=None,
empty_string_is_None=True, choices=None, restart_required=False):
self.name, self.gui_name = name, gui_name
self.empty_string_is_None = empty_string_is_None
self.restart_required = restart_required
self.choices = choices
if gui_name is None:
self.gui_name = 'opt_'+name
self.config_obj = config_obj
self.gui_obj = getattr(widget, self.gui_name)
self.widget = widget
if isinstance(self.gui_obj, QCheckBox):
self.datatype = 'bool'
self.gui_obj.stateChanged.connect(self.changed)
elif isinstance(self.gui_obj, QAbstractSpinBox):
self.datatype = 'number'
self.gui_obj.valueChanged.connect(self.changed)
elif isinstance(self.gui_obj, (QLineEdit, HistoryLineEdit)):
self.datatype = 'string'
self.gui_obj.textChanged.connect(self.changed)
if isinstance(self.gui_obj, HistoryLineEdit):
self.gui_obj.initialize('preferences_setting_' + self.name)
elif isinstance(self.gui_obj, QComboBox):
self.datatype = 'choice'
self.gui_obj.editTextChanged.connect(self.changed)
self.gui_obj.currentIndexChanged.connect(self.changed)
else:
raise ValueError('Unknown data type %s' % self.gui_obj.__class__)
if isinstance(self.config_obj, ConfigProxy) and \
not str(self.gui_obj.toolTip()):
h = self.config_obj.help(self.name)
if h:
self.gui_obj.setToolTip(h)
tt = str(self.gui_obj.toolTip())
if tt:
if not str(self.gui_obj.whatsThis()):
self.gui_obj.setWhatsThis(tt)
if not str(self.gui_obj.statusTip()):
self.gui_obj.setStatusTip(tt)
tt = '\n'.join(textwrap.wrap(tt, 70))
self.gui_obj.setToolTip(tt)
def changed(self, *args):
self.widget.changed_signal.emit()
def initialize(self):
self.gui_obj.blockSignals(True)
if self.datatype == 'choice':
choices = self.choices or []
if isinstance(self.gui_obj, EditWithComplete):
self.gui_obj.all_items = choices
else:
self.gui_obj.clear()
for x in choices:
if isinstance(x, string_or_bytes):
x = (x, x)
self.gui_obj.addItem(x[0], (x[1]))
self.set_gui_val(self.get_config_val(default=False))
self.gui_obj.blockSignals(False)
self.initial_value = self.get_gui_val()
def commit(self):
val = self.get_gui_val()
oldval = self.get_config_val()
changed = val != oldval
if changed:
self.set_config_val(self.get_gui_val())
return changed and self.restart_required
def restore_defaults(self):
self.set_gui_val(self.get_config_val(default=True))
def get_config_val(self, default=False):
if default:
val = self.config_obj.defaults[self.name]
else:
val = self.config_obj[self.name]
return val
def set_config_val(self, val):
self.config_obj[self.name] = val
def set_gui_val(self, val):
if self.datatype == 'bool':
self.gui_obj.setChecked(bool(val))
elif self.datatype == 'number':
self.gui_obj.setValue(val)
elif self.datatype == 'string':
self.gui_obj.setText(val if val else '')
elif self.datatype == 'choice':
if isinstance(self.gui_obj, EditWithComplete):
self.gui_obj.setText(val)
else:
idx = self.gui_obj.findData((val), role=Qt.ItemDataRole.UserRole,
flags=self.CHOICES_SEARCH_FLAGS)
if idx == -1:
idx = 0
self.gui_obj.setCurrentIndex(idx)
def get_gui_val(self):
if self.datatype == 'bool':
val = bool(self.gui_obj.isChecked())
elif self.datatype == 'number':
val = self.gui_obj.value()
elif self.datatype == 'string':
val = str(self.gui_obj.text()).strip()
if self.empty_string_is_None and not val:
val = None
elif self.datatype == 'choice':
if isinstance(self.gui_obj, EditWithComplete):
val = str(self.gui_obj.text())
else:
idx = self.gui_obj.currentIndex()
if idx < 0:
idx = 0
val = str(self.gui_obj.itemData(idx) or '')
return val
class CommaSeparatedList(Setting):
def set_gui_val(self, val):
x = ''
if val:
x = ', '.join(val)
self.gui_obj.setText(x)
def get_gui_val(self):
val = str(self.gui_obj.text()).strip()
ans = []
if val:
ans = [x.strip() for x in val.split(',')]
ans = [x for x in ans if x]
return ans
def get_plugin(category, name):
for plugin in preferences_plugins():
if plugin.category == category and plugin.name == name:
return plugin
raise ValueError(
'No Preferences Plugin with category: %s and name: %s found' %
(category, name))
class ConfigDialog(QDialog):
def set_widget(self, w):
self.w = w
def accept(self):
try:
self.restart_required = self.w.commit()
except AbortCommit:
return
QDialog.accept(self)
def init_gui():
from calibre.gui2.ui import Main
from calibre.gui2.main import option_parser
from calibre.library import db
parser = option_parser()
opts, args = parser.parse_args([])
actions = tuple(Main.create_application_menubar())
db = db()
gui = Main(opts)
gui.initialize(db.library_path, db, actions, show_gui=False)
return gui
def show_config_widget(category, name, gui=None, show_restart_msg=False,
parent=None, never_shutdown=False):
'''
Show the preferences plugin identified by category and name
:param gui: gui instance, if None a hidden gui is created
:param show_restart_msg: If True and the preferences plugin indicates a
restart is required, show a message box telling the user to restart
:param parent: The parent of the displayed dialog
:return: True iff a restart is required for the changes made by the user to
take effect
'''
from calibre.gui2 import gprefs
pl = get_plugin(category, name)
d = ConfigDialog(parent)
d.resize(750, 550)
conf_name = 'config_widget_dialog_geometry_%s_%s'%(category, name)
d.setWindowTitle(_('Configure ') + pl.gui_name)
d.setWindowIcon(QIcon.ic('config.png'))
bb = QDialogButtonBox(d)
bb.setStandardButtons(QDialogButtonBox.StandardButton.Apply|QDialogButtonBox.StandardButton.Cancel|QDialogButtonBox.StandardButton.RestoreDefaults)
bb.accepted.connect(d.accept)
bb.rejected.connect(d.reject)
w = pl.create_widget(d)
d.set_widget(w)
bb.button(QDialogButtonBox.StandardButton.RestoreDefaults).clicked.connect(w.restore_defaults)
bb.button(QDialogButtonBox.StandardButton.RestoreDefaults).setEnabled(w.supports_restoring_to_defaults)
bb.button(QDialogButtonBox.StandardButton.Apply).setEnabled(False)
bb.button(QDialogButtonBox.StandardButton.Apply).clicked.connect(d.accept)
def onchange():
b = bb.button(QDialogButtonBox.StandardButton.Apply)
b.setEnabled(True)
b.setDefault(True)
b.setAutoDefault(True)
w.changed_signal.connect(onchange)
bb.button(QDialogButtonBox.StandardButton.Cancel).setFocus(Qt.FocusReason.OtherFocusReason)
l = QVBoxLayout()
d.setLayout(l)
l.addWidget(w)
l.addWidget(bb)
mygui = gui is None
if gui is None:
gui = init_gui()
mygui = True
w.genesis(gui)
w.initialize()
d.restore_geometry(gprefs, conf_name)
d.exec()
d.save_geometry(gprefs, conf_name)
rr = getattr(d, 'restart_required', False)
if show_restart_msg and rr:
from calibre.gui2 import warning_dialog
warning_dialog(gui, 'Restart required', 'Restart required', show=True)
if mygui and not never_shutdown:
gui.shutdown()
return rr
class ListViewWithMoveByKeyPress(QListView):
def set_movement_functions(self, up_function, down_function):
self.up_function = up_function
self.down_function = down_function
def event(self, event):
if (event.type() == QEvent.KeyPress and
QApplication.keyboardModifiers() == Qt.KeyboardModifier.ControlModifier):
if event.key() == Qt.Key.Key_Up:
self.up_function()
elif event.key() == Qt.Key.Key_Down:
self.down_function()
return True
return QListView.event(self, event)
class ListWidgetWithMoveByKeyPress(QListWidget):
def set_movement_functions(self, up_function, down_function):
self.up_function = up_function
self.down_function = down_function
def event(self, event):
if (event.type() == QEvent.KeyPress and
QApplication.keyboardModifiers() == Qt.KeyboardModifier.ControlModifier):
if event.key() == Qt.Key.Key_Up:
self.up_function()
elif event.key() == Qt.Key.Key_Down:
self.down_function()
return True
return QListWidget.event(self, event)
class TableWidgetWithMoveByKeyPress(QTableWidget):
def set_movement_functions(self, up_function, down_function):
self.up_function = up_function
self.down_function = down_function
def event(self, event):
if (event.type() == QEvent.KeyPress and
QApplication.keyboardModifiers() == Qt.KeyboardModifier.ControlModifier):
if event.key() == Qt.Key.Key_Up:
self.up_function()
elif event.key() == Qt.Key.Key_Down:
self.down_function()
return True
return QTableWidget.event(self, event)
# Testing {{{
def test_widget(category, name, gui=None):
show_config_widget(category, name, gui=gui, show_restart_msg=True)
def test_all():
from qt.core import QApplication
app = QApplication([])
app
gui = init_gui()
for plugin in preferences_plugins():
test_widget(plugin.category, plugin.name, gui=gui)
gui.shutdown()
if __name__ == '__main__':
test_all()
# }}}