WIP - ini edit, error check and syntax highlighting.

This commit is contained in:
Jim Miller 2014-12-28 17:27:53 -06:00
parent 6b3961b280
commit 5cff9401e3
4 changed files with 278 additions and 64 deletions

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,
@ -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
@ -656,14 +658,18 @@ class PersonalIniTab(QWidget):
if d.result() == d.Accepted:
self.personalini = unicode(d.get_plain_text())
configini = get_ffdl_config("test1.com?sid=555",
personalini=self.personalini)
try:
configini = get_ffdl_config("test1.com?sid=555",
personalini=self.personalini)
errors = configini.test_config()
errors = configini.test_config()
except ParsingError as pe:
errors = pe.errors
if errors:
error = not question_dialog(self.plugin_action.gui, _('Go back to fix errors?'), '<p>'+'</p><p>'.join(errors)+'</p>',
show_copy_button=False)
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):

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, 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, 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
@ -1121,6 +1125,9 @@ class EditTextDialog(SizePersistedDialog):
save_size_name='ffdl:edit text dialog',
):
SizePersistedDialog.__init__(self, parent, save_size_name)
self.keys=dict()
#self.resize(600, 500)
self.l = QVBoxLayout()
self.setLayout(self.l)
@ -1132,6 +1139,9 @@ class EditTextDialog(SizePersistedDialog):
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",
@ -1141,7 +1151,6 @@ class EditTextDialog(SizePersistedDialog):
self.textedit.setReadOnly(read_only)
self.textedit.setText(text)
self.l.addWidget(self.textedit)
@ -1164,7 +1173,7 @@ class EditTextDialog(SizePersistedDialog):
# The field into which to type the query
self.findField = QLineEdit(self)
self.findField.setToolTip(findtooltip)
self.findField.returnPressed.connect(self.findButton.click)
self.findField.returnPressed.connect(self.findButton.setFocus)
# Case Sensitivity option
self.caseSens = QtGui.QCheckBox(_('Case sensitive'),self)
@ -1177,6 +1186,9 @@ class EditTextDialog(SizePersistedDialog):
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)
@ -1210,16 +1222,33 @@ class EditTextDialog(SizePersistedDialog):
# 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):
## for findField.returnPressed
self.findButton.setFocus()
print("find self.lastStart:%s"%self.lastStart)
# Grab the parent's text
text = self.textedit.toPlainText()
@ -1242,46 +1271,8 @@ class EditTextDialog(SizePersistedDialog):
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)
self.textedit.moveCursor(self.textedit.textCursor().Start)
def moveCursor(self,start,end):
# We retrieve the QTextCursor object from the parent's QTextEdit
@ -1314,23 +1305,140 @@ class ViewLog(QDialog):
self.setLayout(l)
self.tb = QTextBrowser(self)
self.tb.setHtml('<pre style="font-family: monospace">%s</pre>' % html)
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)
# 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.resize(700, 500)
self.setWindowTitle(title)
self.setWindowIcon(QIcon(I('debug.png')))
self.show()
#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

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.

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:
@ -38,6 +39,9 @@ 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."):
@ -190,9 +194,105 @@ class Configuration(ConfigParser.SafeConfigParser):
for section in self.sections():
if section not in allowedsections and 'teststory:' not in section:
errors.append(_("BAD section name: [%s]")%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):