From 2548babf59fda9b3615f74dabeca0c367e2abdfb Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 21 Aug 2019 13:31:30 +0530 Subject: [PATCH] UI to manage lookup sources --- src/calibre/gui2/viewer/lookup.py | 149 ++++++++++++++++++++++++++++-- 1 file changed, 143 insertions(+), 6 deletions(-) diff --git a/src/calibre/gui2/viewer/lookup.py b/src/calibre/gui2/viewer/lookup.py index 40d5db57e6..fd341f1aa4 100644 --- a/src/calibre/gui2/viewer/lookup.py +++ b/src/calibre/gui2/viewer/lookup.py @@ -6,10 +6,12 @@ from __future__ import absolute_import, division, print_function, unicode_litera import os import sys +import textwrap from PyQt5.Qt import ( - QApplication, QComboBox, QDialog, QHBoxLayout, QIcon, QLabel, QPushButton, Qt, - QTimer, QUrl, QVBoxLayout, QWidget + QApplication, QComboBox, QDialog, QFormLayout, QHBoxLayout, QIcon, QLabel, + QLineEdit, QListWidget, QListWidgetItem, QPushButton, Qt, QTimer, QUrl, + QVBoxLayout, QWidget ) from PyQt5.QtWebEngineWidgets import ( QWebEnginePage, QWebEngineProfile, QWebEngineView @@ -17,6 +19,7 @@ from PyQt5.QtWebEngineWidgets import ( from calibre import prints, random_user_agent from calibre.constants import cache_dir +from calibre.gui2 import error_dialog from calibre.gui2.viewer.web_view import vprefs from calibre.gui2.webengine import create_script, insert_scripts, secure_webengine from calibre.gui2.widgets2 import Dialog @@ -43,13 +46,144 @@ vprefs.defaults['lookup_locations'] = [ vprefs.defaults['lookup_location'] = 'Google dictionary' +class SourceEditor(Dialog): + + def __init__(self, parent, source_to_edit=None): + self.all_names = {x['name'] for x in parent.all_entries} + self.initial_name = self.initial_url = None + self.langs = [] + if source_to_edit is not None: + self.langs = source_to_edit['langs'] + self.initial_name = source_to_edit['name'] + self.initial_url = source_to_edit['url'] + Dialog.__init__(self, _('Edit lookup source'), 'viewer-edit-lookup-location', parent=parent) + + def setup_ui(self): + self.l = l = QFormLayout(self) + self.name_edit = n = QLineEdit(self) + n.setPlaceholderText(_('The name of the source')) + n.setMinimumWidth(450) + l.addRow(_('&Name:'), n) + if self.initial_name: + n.setText(self.initial_name) + n.setReadOnly(True) + self.url_edit = u = QLineEdit(self) + u.setPlaceholderText(_('The URL template of the source')) + u.setMinimumWidth(n.minimumWidth()) + u.setToolTip(textwrap.fill(_( + 'The URL template must starts with https:// and have {word} in it which will be replaced by the actual query'))) + l.addRow(_('&URL:'), u) + if self.initial_url: + u.setText(self.initial_url) + l.addRow(self.bb) + if self.initial_name: + u.setFocus(Qt.OtherFocusReason) + + @property + def source_name(self): + return self.name_edit.text().strip() + + @property + def url(self): + return self.url_edit.text().strip() + + def accept(self): + q = self.source_name + if not q: + return error_dialog(self, _('No name'), _( + 'You must specify a name'), show=True) + if not self.initial_name and q in self.all_names: + return error_dialog(self, _('Name already exists'), _( + 'A look up source with the name {} already exists').format(q), show=True) + if not self.url: + return error_dialog(self, _('No name'), _( + 'You must specify a URL'), show=True) + if not self.url.startswith('http://') and not self.url.startswith('https://'): + return error_dialog(self, _('Invalid URL'), _( + 'The URL must start with https://'), show=True) + if '{word}' not in self.url: + return error_dialog(self, _('Invalid URL'), _( + 'The URL must contain the placeholder {word}'), show=True) + return Dialog.accept(self) + + @property + def entry(self): + return {'name': self.source_name, 'url': self.url, 'langs': self.langs} + + class SourcesEditor(Dialog): def __init__(self, parent): - Dialog.__init__(self, _('Edit lookup sources'), 'viewer-edit-lookup-sources', parent=parent) + Dialog.__init__(self, _('Edit lookup sources'), 'viewer-edit-lookup-locations', parent=parent) def setup_ui(self): - pass + self.l = l = QVBoxLayout(self) + self.la = la = QLabel(_('Double-click to edit an entry')) + la.setWordWrap(True) + l.addWidget(la) + self.entries = e = QListWidget(self) + e.setDragEnabled(True) + e.itemDoubleClicked.connect(self.edit_source) + e.viewport().setAcceptDrops(True) + e.setDropIndicatorShown(True) + e.setDragDropMode(e.InternalMove) + e.setDefaultDropAction(Qt.MoveAction) + l.addWidget(e) + l.addWidget(self.bb) + self.build_entries(vprefs['lookup_locations']) + + self.add_button = b = self.bb.addButton(_('Add'), self.bb.ActionRole) + b.setIcon(QIcon(I('plus.png'))) + b.clicked.connect(self.add_source) + self.remove_button = b = self.bb.addButton(_('Remove'), self.bb.ActionRole) + b.setIcon(QIcon(I('minus.png'))) + b.clicked.connect(self.remove_source) + self.restore_defaults_button = b = self.bb.addButton(_('Restore defaults'), self.bb.ActionRole) + b.clicked.connect(self.restore_defaults) + + def add_entry(self, entry, prepend=False): + i = QListWidgetItem(entry['name']) + i.setData(Qt.UserRole, entry.copy()) + self.entries.insertItem(0, i) if prepend else self.entries.addItem(i) + + def build_entries(self, entries): + self.entries.clear() + for entry in entries: + self.add_entry(entry) + + def restore_defaults(self): + self.build_entries(vprefs.defaults['lookup_locations']) + + def add_source(self): + d = SourceEditor(self) + if d.exec_() == QDialog.Accepted: + self.add_entry(d.entry, prepend=True) + + def remove_source(self): + idx = self.entries.currentRow() + if idx > -1: + self.entries.takeItem(idx) + + def edit_source(self, source_item): + d = SourceEditor(self, source_item.data(Qt.UserRole)) + if d.exec_() == QDialog.Accepted: + source_item.setData(Qt.UserRole, d.entry) + source_item.setData(Qt.DisplayRole, d.name) + + @property + def all_entries(self): + return [self.entries.item(r).data(Qt.UserRole) for r in range(self.entries.count())] + + def accept(self): + entries = self.all_entries + if not entries: + return error_dialog(self, _('No sources'), _( + 'You ust specify at least one lookup source'), show=True) + if entries == vprefs.defaults['lookup_locations']: + del vprefs['lookup_locations'] + else: + vprefs['lookup_locations'] = entries + return Dialog.accept(self) def create_profile(): @@ -108,11 +242,14 @@ class Lookup(QWidget): def add_sources(self): if SourcesEditor(self).exec_() == QDialog.Accepted: self.populate_sources() + self.source_box.setCurrentIndex(0) self.update_query() def source_changed(self): - vprefs['lookup_location'] = self.source['name'] - self.update_query() + s = self.source + if s is not None: + vprefs['lookup_location'] = s['name'] + self.update_query() def populate_sources(self): sb = self.source_box