diff --git a/calibre-plugin/config.py b/calibre-plugin/config.py index 792e66a5..7de11219 100644 --- a/calibre-plugin/config.py +++ b/calibre-plugin/config.py @@ -40,7 +40,7 @@ else: return x.toPyObject() from calibre.gui2.ui import get_gui -from calibre.gui2 import dynamic, info_dialog +from calibre.gui2 import dynamic, info_dialog, question_dialog from calibre.constants import numeric_version as calibre_version # pulls in translation files for _() strings @@ -81,6 +81,8 @@ from calibre_plugins.fanfictiondownloader_plugin.fanficdownloader.adapters \ from calibre_plugins.fanfictiondownloader_plugin.common_utils \ import ( KeyboardConfigDialog, PrefsViewerDialog ) +from calibre_plugins.fanfictiondownloader_plugin.ffdl_util import (get_ffdl_config) + from calibre.gui2.complete2 import EditWithComplete #MultiCompleteLineEdit class RejectURLList: @@ -255,7 +257,7 @@ class ConfigWidget(QWidget): prefs['addtolistsonread'] = self.readinglist_tab.addtolistsonread.isChecked() # personal.ini - ini = unicode(self.personalini_tab.ini.toPlainText()) + ini = self.personalini_tab.personalini if ini: prefs['personal.ini'] = ini else: @@ -541,10 +543,6 @@ class BasicTab(QWidget): if i > -1: self.collision.setCurrentIndex(i) - def show_defaults(self): - text = get_resources('plugin-defaults.ini') - ShowDefaultsIniDialog(self.windowIcon(),text,self).exec_() - def show_rejectlist(self): d = RejectListDialog(self, rejecturllist.get_list(), @@ -565,7 +563,9 @@ class BasicTab(QWidget): icon=self.windowIcon(), title=_("Reject Reasons"), label=_("Customize Reject List Reasons"), - tooltip=_("Customize the Reasons presented when Rejecting URLs")) + tooltip=_("Customize the Reasons presented when Rejecting URLs"), + save_size_name='ffdl:Reject List Reasons', + use_find=True) d.exec_() if d.result() == d.Accepted: prefs['rejectreasons'] = d.get_plain_text() @@ -578,7 +578,8 @@ class BasicTab(QWidget): label=_("Add Reject URLs. Use: http://...,note or http://...,title by author - note
Invalid story URLs will be ignored."), tooltip=_("One URL per line:\nhttp://...,note\nhttp://...,title by author - note"), rejectreasons=rejecturllist.get_reject_reasons(), - reasonslabel=_('Add this reason to all URLs added:')) + reasonslabel=_('Add this reason to all URLs added:'), + save_size_name='ffdl:Add Reject List') d.exec_() if d.result() == d.Accepted: rejecturllist.add_text(d.get_plain_text(),d.get_reason_text()) @@ -601,16 +602,25 @@ class PersonalIniTab(QWidget): self.label = QLabel('personal.ini:') self.l.addWidget(self.label) - self.ini = QTextEdit(self) - try: - self.ini.setFont(QFont("Courier", - self.plugin_action.gui.font().pointSize()+1)) - except Exception as e: - logger.error("Couldn't get font: %s"%e) - self.ini.setLineWrapMode(QTextEdit.NoWrap) - self.ini.setText(prefs['personal.ini']) - self.l.addWidget(self.ini) + # self.ini = QTextEdit(self) + # try: + # self.ini.setFont(QFont("Courier", + # self.plugin_action.gui.font().pointSize()+1)) + # except Exception as e: + # logger.error("Couldn't get font: %s"%e) + # self.ini.setLineWrapMode(QTextEdit.NoWrap) + # self.ini.setText(prefs['personal.ini']) + # self.l.addWidget(self.ini) + self.personalini = prefs['personal.ini'] + + self.ini_button = QPushButton(_('Edit personal.ini'), self) + self.ini_button.setToolTip(_("Edit personal.ini file.")) + self.ini_button.clicked.connect(self.add_ini_button) + self.l.addWidget(self.ini_button) + + + self.defaults = QPushButton(_('View Defaults')+' (plugin-defaults.ini)', self) self.defaults.setToolTip(_("View all of the plugin's configurable settings\nand their default settings.")) self.defaults.clicked.connect(self.show_defaults) @@ -620,38 +630,41 @@ class PersonalIniTab(QWidget): # let edit box fill the space. def show_defaults(self): - text = get_resources('plugin-defaults.ini') - ShowDefaultsIniDialog(self.windowIcon(),text,self).exec_() + EditTextDialog(self, + get_resources('plugin-defaults.ini'), + icon=self.windowIcon(), + title=_('Plugin Defaults'), + label=_("Plugin Defaults (%s) (Read-Only)")%'plugin-defaults.ini', + tooltip=_("These are all of the plugin's configurable options\nand their default settings."), + use_find=True, + read_only=True, + save_size_name='ffdl:defaults.ini').exec_() -class ShowDefaultsIniDialog(QDialog): + def add_ini_button(self): + d = EditTextDialog(self, + self.personalini, + icon=self.windowIcon(), + title=_("Edit personal.ini"), + label=_("Edit personal.ini"), + tooltip=_("Edit personal.ini"), + use_find=True, + save_size_name='ffdl:personal.ini') + error=False + while not error: + error=True + d.exec_() + if d.result() == d.Accepted: + self.personalini = unicode(d.get_plain_text()) - def __init__(self, icon, text, parent=None): - QDialog.__init__(self, parent) - self.resize(600, 500) - self.l = QVBoxLayout() - self.setLayout(self.l) - self.label = QLabel(_("Plugin Defaults (%s) (Read-Only)")%'plugin-defaults.ini') - self.label.setToolTip(_("These are all of the plugin's configurable options\nand their default settings.")) - self.setWindowTitle(_('Plugin Defaults')) - self.setWindowIcon(icon) - self.l.addWidget(self.label) - - self.ini = QTextEdit(self) - self.ini.setToolTip(_("These are all of the plugin's configurable options\nand their default settings.")) - try: - self.ini.setFont(QFont("Courier", - get_gui().font().pointSize()+1)) - except Exception as e: - logger.error("Couldn't get font: %s"%e) - self.ini.setLineWrapMode(QTextEdit.NoWrap) - self.ini.setText(text) - self.ini.setReadOnly(True) - self.l.addWidget(self.ini) - - self.ok_button = QPushButton(_('OK'), self) - self.ok_button.clicked.connect(self.hide) - self.l.addWidget(self.ok_button) - + configini = get_ffdl_config("test1.com?sid=555", + personalini=self.personalini) + + errors = configini.test_config() + + if errors: + error = not question_dialog(self.plugin_action.gui, _('Go back to fix errors?'), '

'+'

'.join(errors)+'

', + show_copy_button=False) + class ReadingListTab(QWidget): def __init__(self, parent_dialog, plugin_action): diff --git a/calibre-plugin/dialogs.py b/calibre-plugin/dialogs.py index 218a780e..0035a472 100644 --- a/calibre-plugin/dialogs.py +++ b/calibre-plugin/dialogs.py @@ -24,14 +24,14 @@ from datetime import datetime try: from PyQt5 import QtWidgets as QtGui from PyQt5.Qt import (QDialog, QTableWidget, QVBoxLayout, QHBoxLayout, QGridLayout, - QPushButton, QLabel, QCheckBox, QIcon, QLineEdit, + QPushButton, QFont, QLabel, QCheckBox, QIcon, QLineEdit, QComboBox, QProgressDialog, QTimer, QDialogButtonBox, QPixmap, Qt, QAbstractItemView, QTextEdit, pyqtSignal, QGroupBox, QFrame) except ImportError as e: from PyQt4 import QtGui from PyQt4.Qt import (QDialog, QTableWidget, QVBoxLayout, QHBoxLayout, QGridLayout, - QPushButton, QLabel, QCheckBox, QIcon, QLineEdit, + QPushButton, QFont, QLabel, QCheckBox, QIcon, QLineEdit, QComboBox, QProgressDialog, QTimer, QDialogButtonBox, QPixmap, Qt, QAbstractItemView, QTextEdit, pyqtSignal, QGroupBox, QFrame) @@ -1111,14 +1111,17 @@ class RejectListDialog(SizePersistedDialog): def get_deletebooks(self): return self.deletebooks.isChecked() -class EditTextDialog(QDialog): +class EditTextDialog(SizePersistedDialog): def __init__(self, parent, text, icon=None, title=None, label=None, tooltip=None, - rejectreasons=[],reasonslabel=None + rejectreasons=[],reasonslabel=None, + use_find=False, + read_only=False, + save_size_name='ffdl:edit text dialog', ): - QDialog.__init__(self, parent) - self.resize(600, 500) + SizePersistedDialog.__init__(self, parent, save_size_name) + #self.resize(600, 500) self.l = QVBoxLayout() self.setLayout(self.l) self.label = QLabel(label) @@ -1130,9 +1133,50 @@ class EditTextDialog(QDialog): self.textedit = QTextEdit(self) self.textedit.setLineWrapMode(QTextEdit.NoWrap) + try: + self.textedit.setFont(QFont("Courier", + parent.font().pointSize()+1)) + except Exception as e: + logger.error("Couldn't get font: %s"%e) + + self.textedit.setReadOnly(read_only) + + self.textedit.setText(text) self.l.addWidget(self.textedit) + self.lastStart = 0 + + if use_find: + + findtooltip=_('Search for string in edit box.') + + horz = QHBoxLayout() + label = QLabel(_('Find:')) + + label.setToolTip(findtooltip) + + # Button to search the document for something + self.findButton = QtGui.QPushButton(_('Find'),self) + self.findButton.clicked.connect(self.find) + self.findButton.setToolTip(findtooltip) + + # The field into which to type the query + self.findField = QLineEdit(self) + self.findField.setToolTip(findtooltip) + self.findField.returnPressed.connect(self.findButton.click) + + # Case Sensitivity option + self.caseSens = QtGui.QCheckBox(_('Case sensitive'),self) + self.caseSens.setToolTip(_("Search for case sensitive string; don't treat Harry, HARRY and harry all the same.")) + + horz.addWidget(label) + horz.addWidget(self.findField) + horz.addWidget(self.findButton) + horz.addWidget(self.caseSens) + + self.l.addLayout(horz) + if tooltip: self.label.setToolTip(tooltip) self.textedit.setToolTip(tooltip) @@ -1159,7 +1203,12 @@ class EditTextDialog(QDialog): button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) button_box.accepted.connect(self.accept) button_box.rejected.connect(self.reject) + # button_box.button(QDialogButtonBox.Ok).setDefault(False) + # button_box.button(QDialogButtonBox.Cancel).setDefault(False) self.l.addWidget(button_box) + + # Cause our dialog size to be restored from prefs or created on first usage + self.resize_dialog() def get_plain_text(self): return unicode(self.textedit.toPlainText()) @@ -1167,3 +1216,121 @@ class EditTextDialog(QDialog): def get_reason_text(self): return unicode(self.reason_edit.currentText()).strip() + def find(self): + + ## for findField.returnPressed + self.findButton.setFocus() + + # Grab the parent's text + text = self.textedit.toPlainText() + + # And the text to find + query = self.findField.text() + + if not self.caseSens.isChecked(): + text = text.lower() + query = query.lower() + + # Use normal string search to find the query from the + # last starting position + self.lastStart = text.find(query,self.lastStart + 1) + # If the find() method didn't return -1 (not found) + + if self.lastStart >= 0: + end = self.lastStart + len(query) + self.moveCursor(self.lastStart,end) + else: + # Make the next search start from the begining again + self.lastStart = 0 + self.textedit.moveCursor(self.textedit.textCursor().End) + + # # If the 'Whole Words' checkbox is checked, we need to append + # # and prepend a non-alphanumeric character + # # if self.wholeWords.isChecked(): + # # query = r'\W' + query + r'\W' + + # # By default regexes are case sensitive but usually a search isn't + # # case sensitive by default, so we need to switch this around here + # flags = 0 if self.caseSens.isChecked() else re.I + + # # Compile the pattern + # pattern = re.compile(query,flags) + + # # If the last match was successful, start at position after the last + # # match's start, else at 0 + # start = self.lastMatch.start() + 1 if self.lastMatch else 0 + + # # The actual search + # self.lastMatch = pattern.search(text,start) + + # if self.lastMatch: + + # start = self.lastMatch.start() + # end = self.lastMatch.end() + + # If 'Whole words' is checked, the selection would include the two + # non-alphanumeric characters we included in the search, which need + # to be removed before marking them. + # if self.wholeWords.isChecked(): + # start += 1 + # end -= 1 + + # self.moveCursor(start,end) + + # else: + + # # We set the cursor to the end if the search was unsuccessful + # self.textedit.moveCursor(self.textedit.textCursor().End) + + def moveCursor(self,start,end): + + # We retrieve the QTextCursor object from the parent's QTextEdit + cursor = self.textedit.textCursor() + + # Then we set the position to the beginning of the last match + cursor.setPosition(start) + + # Next we move the Cursor by over the match and pass the KeepAnchor parameter + # which will make the cursor select the the match's text + cursor.movePosition(cursor.Right,cursor.KeepAnchor,end - start) + + # And finally we set this new cursor as the parent's + self.textedit.setTextCursor(cursor) + +def errors_dialog(parent, + title, + html): + + d = ViewLog(title,html,parent) + + return d.exec_() == d.Accepted + + +class ViewLog(QDialog): + + def __init__(self, title, html, parent=None): + QDialog.__init__(self, parent) + self.l = l = QVBoxLayout() + self.setLayout(l) + + self.tb = QTextBrowser(self) + self.tb.setHtml('
%s
' % html) + l.addWidget(self.tb) + + self.bb = QDialogButtonBox(QDialogButtonBox.Yes | QDialogButtonBox.No) + self.bb.accepted.connect(self.accept) + self.bb.rejected.connect(self.reject) + self.copy_button = self.bb.addButton(_('Copy to clipboard'), + self.bb.ActionRole) + self.copy_button.setIcon(QIcon(I('edit-copy.png'))) + self.copy_button.clicked.connect(self.copy_to_clipboard) + l.addWidget(self.bb) + self.setModal(False) + self.resize(QSize(700, 500)) + self.setWindowTitle(title) + self.setWindowIcon(QIcon(I('debug.png'))) + self.show() + + def copy_to_clipboard(self): + txt = self.tb.toPlainText() + QApplication.clipboard().setText(txt) diff --git a/fanficdownloader/configurable.py b/fanficdownloader/configurable.py index 9b6d35f4..4a095536 100644 --- a/fanficdownloader/configurable.py +++ b/fanficdownloader/configurable.py @@ -32,6 +32,8 @@ import ConfigParser, re # [overrides] # titlepage_entries: category +import adapters + class Configuration(ConfigParser.SafeConfigParser): def __init__(self, site, fileform): @@ -162,6 +164,36 @@ class Configuration(ConfigParser.SafeConfigParser): def getConfigList(self, key): return self.get_config_list(self.sectionslist, key) + + def test_config(self): + errors=[] + + sites = adapters.getConfigSections() + sitesections = ['defaults','overrides'] + for section in sites: + sitesections.append(section) + if section.startswith('www.'): + # add w/o www if has www + sitesections.append(section[4:]) + else: + # add w/ www if doesn't www + sitesections.append('www.%s'%section) + + allowedsections = [] + forms=['html','txt','epub','mobi'] + allowedsections.extend(forms) + + for section in sitesections: + allowedsections.append(section) + for f in forms: + allowedsections.append('%s:%s'%(section,f)) + + for section in self.sections(): + if section not in allowedsections and 'teststory:' not in section: + errors.append(_("BAD section name: [%s]")%section) + + return errors + # extended by adapter, writer and story for ease of calling configuration. class Configurable(object):