Merge default with branch.

This commit is contained in:
Jim Miller
2014-12-28 17:28:12 -06:00
4 changed files with 485 additions and 59 deletions
+67 -48
View File
@@ -13,6 +13,8 @@ logger = logging.getLogger(__name__)
import traceback, copy, threading
from collections import OrderedDict
from ConfigParser import ParsingError
try:
from PyQt5.Qt import (QDialog, QWidget, QVBoxLayout, QHBoxLayout, QLabel,
QLineEdit, QFont, QWidget, QTextEdit, QComboBox,
@@ -40,7 +42,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
@@ -73,7 +75,7 @@ no_trans = { 'pini':'personal.ini',
from calibre_plugins.fanfictiondownloader_plugin.prefs import prefs, PREFS_NAMESPACE
from calibre_plugins.fanfictiondownloader_plugin.dialogs \
import (UPDATE, UPDATEALWAYS, collision_order, save_collisions, RejectListDialog,
EditTextDialog, RejectUrlEntry)
EditTextDialog, RejectUrlEntry, errors_dialog)
from calibre_plugins.fanfictiondownloader_plugin.fanficdownloader.adapters \
import getConfigSections
@@ -81,6 +83,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 +259,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 +545,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 +565,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 +580,8 @@ class BasicTab(QWidget):
label=_("Add Reject URLs. Use: <b>http://...,note</b> or <b>http://...,title by author - note</b><br>Invalid story URLs will be ignored."),
tooltip=_("One URL per line:\n<b>http://...,note</b>\n<b>http://...,title by author - note</b>"),
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 +604,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 +632,45 @@ 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)
try:
configini = get_ffdl_config("test1.com?sid=555",
personalini=self.personalini)
errors = configini.test_config()
except ParsingError as pe:
errors = pe.errors
if errors:
error = not errors_dialog(self.plugin_action.gui,
_('Go back to fix errors?'),
'<p>'+'</p><p>'.join([ '%s %s'%e for e in errors ])+'</p>')
class ReadingListTab(QWidget):
def __init__(self, parent_dialog, plugin_action):
+284 -9
View File
@@ -4,7 +4,7 @@ from __future__ import (unicode_literals, division,
print_function)
__license__ = 'GPL v3'
__copyright__ = '2011, Jim Miller'
__copyright__ = '2014, Jim Miller'
__docformat__ = 'restructuredtext en'
import logging
@@ -23,18 +23,22 @@ from datetime import datetime
try:
from PyQt5 import QtWidgets as QtGui
from PyQt5 import QtCore
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)
QGroupBox, QFrame, QTextBrowser, QSize, QAction,
QSyntaxHighlighter, QTextCharFormat, QBrush )
except ImportError as e:
from PyQt4 import QtGui
from PyQt4 import QtCore
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)
QGroupBox, QFrame, QTextBrowser, QSize, QAction, QtCore,
QSyntaxHighlighter, QTextCharFormat, QBrush )
try:
from calibre.gui2 import QVariant
@@ -1111,14 +1115,20 @@ 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.keys=dict()
#self.resize(600, 500)
self.l = QVBoxLayout()
self.setLayout(self.l)
self.label = QLabel(label)
@@ -1129,10 +1139,56 @@ class EditTextDialog(QDialog):
self.l.addWidget(self.label)
self.textedit = QTextEdit(self)
highlighter = IniHighlighter(self.textedit, "Classic")
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.setFocus)
# 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)
self.addCtrlKeyPress(QtCore.Qt.Key_F,self.findFocus)
self.addCtrlKeyPress(QtCore.Qt.Key_G,self.find)
if tooltip:
self.label.setToolTip(tooltip)
self.textedit.setToolTip(tooltip)
@@ -1159,11 +1215,230 @@ 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 addCtrlKeyPress(self,key,func):
# print("addKeyPress: key(0x%x)"%key)
# print("control: 0x%x"%QtCore.Qt.ControlModifier)
self.keys[key]=func
def keyPressEvent(self, event):
# print("event: key(0x%x) modifiers(0x%x)"%(event.key(),event.modifiers()))
if (event.modifiers() & QtCore.Qt.ControlModifier) and event.key() in self.keys:
func = self.keys[event.key()]
return func()
else:
return SizePersistedDialog.keyPressEvent(self, event)
def get_plain_text(self):
return unicode(self.textedit.toPlainText())
def get_reason_text(self):
return unicode(self.reason_edit.currentText()).strip()
def findFocus(self):
# print("findFocus called")
self.findField.setFocus()
self.findField.selectAll()
def find(self):
print("find self.lastStart:%s"%self.lastStart)
# 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().Start)
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.setFont(QFont("Courier",
parent.font().pointSize()+1))
self.tb.setHtml(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(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)
class IniHighlighter(QSyntaxHighlighter):
def __init__( self, parent, theme ):
QSyntaxHighlighter.__init__( self, parent )
self.parent = parent
keyword = QTextCharFormat()
reservedClasses = QTextCharFormat()
assignmentOperator = QTextCharFormat()
delimiter = QTextCharFormat()
specialConstant = QTextCharFormat()
boolean = QTextCharFormat()
number = QTextCharFormat()
comment = QTextCharFormat()
string = QTextCharFormat()
singleQuotedString = QTextCharFormat()
self.highlightingRules = []
# # keyword
# brush = QBrush( Qt.darkBlue, Qt.SolidPattern )
# keyword.setForeground( brush )
# keyword.setFontWeight( QFont.Bold )
# keywords = [ "break", "else", "for", "if", "in",
# "next", "repeat", "return", "switch",
# "try", "while" ]
# for word in keywords:
# pattern = "\\b" + word + "\\b"
# rule = HighlightingRule( pattern, keyword )
# self.highlightingRules.append( rule )
# # reservedClasses
# reservedClasses.setForeground( brush )
# reservedClasses.setFontWeight( QFont.Bold )
# keywords = [ "array", "character", "complex",
# "data.frame", "double", "factor",
# "function", "integer", "list",
# "logical", "matrix", "numeric",
# "vector" ]
# for word in keywords:
# pattern = "\\b" + word + "\\b"
# rule = HighlightingRule( pattern, reservedClasses )
# self.highlightingRules.append( rule )
# # assignmentOperator
# brush = QBrush( Qt.yellow, Qt.SolidPattern )
# pattern = "(<){1,2}-"
# assignmentOperator.setForeground( brush )
# assignmentOperator.setFontWeight( QFont.Bold )
# rule = HighlightingRule( pattern, assignmentOperator )
# self.highlightingRules.append( rule )
# section
pattern = r"^\[[^\]]+\]"
brush = QBrush( Qt.darkBlue, Qt.SolidPattern )
delimiter.setForeground( brush )
delimiter.setFontWeight( QFont.Bold )
rule = HighlightingRule( pattern, delimiter )
self.highlightingRules.append( rule )
# # specialConstant
# brush = QBrush( Qt.green, Qt.SolidPattern )
# specialConstant.setForeground( brush )
# keywords = [ "Inf", "NA", "NaN", "NULL" ]
# for word in keywords:
# pattern = "\\b" + word + "\\b"
# rule = HighlightingRule( pattern, specialConstant )
# self.highlightingRules.append( rule )
# boolean, case insensitive
boolean.setForeground( brush )
pattern = r"\b(true|false)\b"
rule = HighlightingRule( pattern, boolean )
self.highlightingRules.append( rule )
# # number
# pattern = "[-+]?[0-9]*\.?[0-9]+?([eE][-+]?[0-9]+?)?"
# number.setForeground( brush )
# rule = HighlightingRule( pattern, number )
# self.highlightingRules.append( rule )
# comment
brush = QBrush( Qt.darkGray, Qt.SolidPattern )
pattern = "#[^\n]*"
comment.setForeground( brush )
rule = HighlightingRule( pattern, comment )
self.highlightingRules.append( rule )
# string
brush = QBrush( Qt.red, Qt.SolidPattern )
pattern = "\".*?\""
string.setForeground( brush )
rule = HighlightingRule( pattern, string )
self.highlightingRules.append( rule )
# singleQuotedString
pattern = "\'.*?\'"
singleQuotedString.setForeground( brush )
rule = HighlightingRule( pattern, singleQuotedString )
self.highlightingRules.append( rule )
def highlightBlock( self, text ):
for rule in self.highlightingRules:
for match in rule.pattern.finditer(text):
self.setFormat( match.start(), match.end()-match.start(), rule.format )
self.setCurrentBlockState( 0 )
class HighlightingRule():
def __init__( self, pattern, format ):
if isinstance(pattern,basestring):
self.pattern = re.compile(pattern)
else:
self.pattern=pattern
self.format = format
+1 -1
View File
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
# Copyright 2011 Fanficdownloader team
# Copyright 2014 Fanficdownloader team
#
# Licensed under the Apache License, Version 2.0 (the 'License');
# you may not use this file except in compliance with the License.
+133 -1
View File
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
# Copyright 2011 Fanficdownloader team
# Copyright 2014 Fanficdownloader team
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -16,6 +16,7 @@
#
import ConfigParser, re
from ConfigParser import DEFAULTSECT, MissingSectionHeaderError, ParsingError
# All of the writers(epub,html,txt) and adapters(ffnet,twlt,etc)
# inherit from Configurable. The config file(s) uses ini format:
@@ -32,10 +33,15 @@ import ConfigParser, re
# [overrides]
# titlepage_entries: category
import adapters
class Configuration(ConfigParser.SafeConfigParser):
def __init__(self, site, fileform):
ConfigParser.SafeConfigParser.__init__(self)
self.linenos=dict() # key by section or section,key -> lineno
self.sectionslist = ['defaults']
if site.startswith("www."):
@@ -162,6 +168,132 @@ 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((self.get_lineno(section),"Bad Section Name: %s"%section))
return errors
def get_lineno(self,section,key=None):
if key:
return self.linenos.get(section+','+key,None)
else:
return self.linenos.get(section,None)
## Copied from Python library so as to make it save linenos too.
#
# Regular expressions for parsing section headers and options.
#
def _read(self, fp, fpname):
"""Parse a sectioned setup file.
The sections in setup file contains a title line at the top,
indicated by a name in square brackets (`[]'), plus key/value
options lines, indicated by `name: value' format lines.
Continuations are represented by an embedded newline then
leading whitespace. Blank lines, lines beginning with a '#',
and just about everything else are ignored.
"""
cursect = None # None, or a dictionary
optname = None
lineno = 0
e = None # None, or an exception
while True:
line = fp.readline()
if not line:
break
lineno = lineno + 1
# comment or blank line?
if line.strip() == '' or line[0] in '#;':
continue
if line.split(None, 1)[0].lower() == 'rem' and line[0] in "rR":
# no leading whitespace
continue
# continuation line?
if line[0].isspace() and cursect is not None and optname:
value = line.strip()
if value:
cursect[optname] = "%s\n%s" % (cursect[optname], value)
# a section header or option header?
else:
# is it a section header?
mo = self.SECTCRE.match(line)
if mo:
sectname = mo.group('header')
if sectname in self._sections:
cursect = self._sections[sectname]
elif sectname == DEFAULTSECT:
cursect = self._defaults
else:
cursect = self._dict()
cursect['__name__'] = sectname
self._sections[sectname] = cursect
self.linenos[sectname]=lineno
# So sections can't start with a continuation line
optname = None
# no section header in the file?
elif cursect is None:
if not e:
e = ParsingError(fpname)
e.append(lineno, u'(Line outside section) '+line)
#raise MissingSectionHeaderError(fpname, lineno, line)
# an option line?
else:
mo = self._optcre.match(line)
if mo:
optname, vi, optval = mo.group('option', 'vi', 'value')
# This check is fine because the OPTCRE cannot
# match if it would set optval to None
if optval is not None:
if vi in ('=', ':') and ';' in optval:
# ';' is a comment delimiter only if it follows
# a spacing character
pos = optval.find(';')
if pos != -1 and optval[pos-1].isspace():
optval = optval[:pos]
optval = optval.strip()
# allow empty values
if optval == '""':
optval = ''
optname = self.optionxform(optname.rstrip())
cursect[optname] = optval
self.linenos[cursect['__name__']+','+optname]=lineno
else:
# a non-fatal parsing error occurred. set up the
# exception but keep going. the exception will be
# raised at the end of the file and will contain a
# list of all bogus lines
if not e:
e = ParsingError(fpname)
e.append(lineno, line)
# if any parsing errors occurred, raise an exception
if e:
raise e
# extended by adapter, writer and story for ease of calling configuration.
class Configurable(object):