From 7a526fcb131bd67da3090073f2d6427281f8532c Mon Sep 17 00:00:00 2001 From: Justus Winter <4winter@informatik.uni-hamburg.de> Date: Thu, 9 Aug 2012 20:16:38 +0200 Subject: [PATCH 1/7] Use the github page for the kodos project page url --- kodos | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/kodos b/kodos index 8446047..5a433ae 100755 --- a/kodos +++ b/kodos @@ -983,7 +983,8 @@ class Kodos(KodosBA): def kodos_website(self): - self.launch_browser_wrapper("http://kodos.sourceforge.net") + self.launch_browser_wrapper('https://github.com/luksan/kodos', + message=self.tr('Launch web browser to go to the kodos project page?')) def check_for_update(self): From 0db86656064c8a9a530463b3866c2cd1ff4c3107 Mon Sep 17 00:00:00 2001 From: Justus Winter <4winter@informatik.uni-hamburg.de> Date: Thu, 9 Aug 2012 19:23:21 +0200 Subject: [PATCH 2/7] Remove the check_for_updates functionality --- kodos | 42 ------------------------------------------ modules/kodosBA.ui | 17 ----------------- 2 files changed, 59 deletions(-) diff --git a/kodos b/kodos index 5a433ae..f7797ed 100755 --- a/kodos +++ b/kodos @@ -987,48 +987,6 @@ class Kodos(KodosBA): message=self.tr('Launch web browser to go to the kodos project page?')) - def check_for_update(self): - url = "http://sourceforge.net/project/showfiles.php?group_id=43860" - try: - fp = urllib.urlopen(url) - except: - self.status_bar.set_message(self.tr("Failed to open url"), - 5, - TRUE) - return - - lines = fp.readlines() - html = string.join(lines) - - rawstr = r"""kodos-(?P.*?)\.\w{3,4}\<""" - match_obj = re.search(rawstr, html) - if match_obj: - latest_version = match_obj.group('version') - if latest_version == VERSION: - QMessageBox.information(None, - self.tr("No Update is Available"), - unicode(self.tr("You are currently using the latest version of Kodos")) + " (%s)" % VERSION) - else: - message = "%s\n\n%s: %s.\n%s: %s.\n\n%s\n" % \ - (unicode(self.tr("There is a newer version of Kodos available.")), - unicode(self.tr("You are using version:")), - VERSION, - unicode(self.tr("The latest version is:")), - latest_version, - unicode(self.tr("Press OK to launch browser"))) - - self.launch_browser_wrapper(url, - self.tr("Kodos Update Available"), - message) - else: - message = "%s.\n\n%s" % \ - (unicode(self.tr("Unable to get version info from Sourceforge")), - unicode(self.tr("Press OK to launch browser"))) - self.launch_browser_wrapper(url, - self.tr("Unknown version available"), - message) - - def launch_browser_wrapper(self, url, caption=None, message=None): if launch_browser(url, caption, message): self.status_bar.set_message(self.tr("Launching web browser"), diff --git a/modules/kodosBA.ui b/modules/kodosBA.ui index a600de2..14139b3 100644 --- a/modules/kodosBA.ui +++ b/modules/kodosBA.ui @@ -406,7 +406,6 @@ database. New in Python version 2.0. - @@ -1285,22 +1284,6 @@ database. New in Python version 2.0. - - helpCheckForUpdateAction - activated() - KodosBA - check_for_update() - - - -1 - -1 - - - 20 - 20 - - - replaceTextEdit textChanged() From 1aa7811ddf46b66c6b682d903a719e05bfc5b6ae Mon Sep 17 00:00:00 2001 From: Justus Winter <4winter@informatik.uni-hamburg.de> Date: Thu, 9 Aug 2012 19:53:21 +0200 Subject: [PATCH 3/7] Remove the reportbug functionality and send the user to github instead --- kodos | 4 +- modules/reportBug.py | 104 --------------- modules/reportBugBA.ui | 290 ----------------------------------------- 3 files changed, 2 insertions(+), 396 deletions(-) delete mode 100644 modules/reportBug.py delete mode 100644 modules/reportBugBA.ui diff --git a/kodos b/kodos index f7797ed..49ead1b 100755 --- a/kodos +++ b/kodos @@ -33,7 +33,6 @@ import modules.help as help from modules.status_bar import * from modules.reference import * from modules.prefs import * -from modules.reportBug import reportBugWindow from modules.version import VERSION from modules.recent_files import RecentFiles from modules.urlDialog import URLDialog @@ -1005,7 +1004,8 @@ class Kodos(KodosBA): def report_bug(self): - self.bug_report_win = reportBugWindow(self) + self.launch_browser_wrapper('https://github.com/luksan/kodos/issues', + message=self.tr("Launch web browser to report a bug in kodos?")) ############################################################################## diff --git a/modules/reportBug.py b/modules/reportBug.py deleted file mode 100644 index e4351e4..0000000 --- a/modules/reportBug.py +++ /dev/null @@ -1,104 +0,0 @@ -# -*- coding: utf-8 -*- -# reportBug.py: -*- Python -*- DESCRIPTIVE TEXT. - -from reportBugBA import reportBugBA -from util import * -from PyQt4.QtGui import * -from PyQt4.QtCore import * -from PyQt4 import * -import sys -import string -import smtplib -from version import VERSION - -AUTHOR_ADDR = "phil_schwartz@users.sourceforge.net" - -class reportBug(reportBugBA): - def __init__(self, parent=None, name=None): - reportBugBA.__init__(self, parent) - self.parent = parent - self.kodos_main = parent.kodos_main - self.populate() - - - def populate(self): - self.OSEdit.setText(sys.platform) - pyvers = string.replace(sys.version, "\n", " - ") - self.pythonVersionEdit.setText(pyvers) - self.PyQtVersionEdit.setText(QT_VERSION_STR) - self.regexMultiLineEdit.setPlainText(self.kodos_main.regexMultiLineEdit.toPlainText()) - self.stringMultiLineEdit.setPlainText(self.kodos_main.stringMultiLineEdit.toPlainText()) - - - def cancel_slot(self): - self.parent.close() - - def submit_slot(self): - addr = str(self.emailAddressEdit.text()) - if not addr: - msg = self.tr( - "An email address is necessary so that the author " - "can contact you. Your email address will not " - "be used for any other purposes.") - - QMessageBox.information(None, - self.tr("You must supply a valid email address"), - msg) - return - - msg = "Subject: Kodos bug report\n\n" - msg += "Kodos Version: %s\n" % VERSION - msg += "Operating System: %s\n" % unicode(self.OSEdit.text()) - msg += "Python Version: %s\n" % unicode(self.pythonVersionEdit.text()) - msg += "PyQt Version: %s\n" % unicode(self.PyQtVersionEdit.text()) - msg += "\n" + "=" * 70 + "\n" - msg += "Regex:\n%s\n" % unicode(self.regexMultiLineEdit.text()) - msg += "=" * 70 + "\n" - msg += "String:\n%s\n" % unicode(self.stringMultiLineEdit.text()) - msg += "=" * 70 + "\n" - msg += "Comments:\n%s\n" % unicode(self.commentsMultiLineEdit.text()) - email_server = unicode(self.kodos_main.prefs.emailServerEdit.text()) or "localhost" - try: - server = smtplib.SMTP(email_server) - server.sendmail(addr, AUTHOR_ADDR, msg) - server.quit() - QMessageBox.information(None, - self.tr("Bug report sent"), - self.tr("Your bug report has been sent.")) - self.parent.close() - except Exception, e: - QMessageBox.information(None, - self.tr("An exception occurred sending bug report"), - str(e)) - - -class reportBugWindow(QMainWindow): - def __init__(self, kodos_main): - self.kodos_main = kodos_main - QMainWindow.__init__(self, kodos_main)#, Qt.Window | Qt.WA_DeleteOnClose) - - self.setGeometry(100, 50, 800, 600) - self.setWindowTitle(self.tr("Report a Bug")) - self.setWindowIcon(QIcon(QPixmap(":images/kodos_icon.png"))) - - self.bug_report = reportBug(self) - self.setCentralWidget(self.bug_report) - - - self.createMenu() - self.createToolBar() - - self.show() - - - def createMenu(self): - self.menubar = self.menuBar() - self.filemenu = self.menubar.addMenu(self.tr("&File")) - self.filemenu.addAction(self.tr("&Close"), self, SLOT("close()")) - - - def createToolBar(self): - toolbar = QToolBar() - self.addToolBar(toolbar) - self.logolabel = kodos_toolbar_logo(toolbar) - diff --git a/modules/reportBugBA.ui b/modules/reportBugBA.ui deleted file mode 100644 index 74b2eec..0000000 --- a/modules/reportBugBA.ui +++ /dev/null @@ -1,290 +0,0 @@ - - - reportBugBA - - - - 0 - 0 - 750 - 653 - - - - Form1 - - - - 11 - - - 6 - - - - - 6 - - - 0 - - - - - Qt::Horizontal - - - QSizePolicy::Expanding - - - - 20 - 20 - - - - - - - - Submit Bug Report - - - - - - - Cancel - - - - - - - - - Kodos State Information - - - - 11 - - - 6 - - - - - 6 - - - 0 - - - - - Regular Expression: - - - false - - - - - - - Match String: - - - false - - - - - - - - - 6 - - - 0 - - - - - true - - - - - - - true - - - - - - - - - - - - System Information - - - - 11 - - - 6 - - - - - Operating System: - - - false - - - - - - - PyQt Version: - - - false - - - - - - - Python Version: - - - false - - - - - - - - - - - - - - - - - - - Comments - - - - 11 - - - 6 - - - - - 0 - - - 6 - - - - - Comments: - - - false - - - - - - - - - - - 0 - 0 - - - - Email address: - - - false - - - - - - - - - - - - - - - qPixmapFromMimeSource - - OSEdit - pythonVersionEdit - PyQtVersionEdit - emailAddressEdit - submitButton - cancelButton - - - - - submitButton - clicked() - reportBugBA - submit_slot() - - - 20 - 20 - - - 20 - 20 - - - - - cancelButton - clicked() - reportBugBA - cancel_slot() - - - 20 - 20 - - - 20 - 20 - - - - - From 888d127686628989ab7958a3a5f151b8f157488c Mon Sep 17 00:00:00 2001 From: Justus Winter <4winter@informatik.uni-hamburg.de> Date: Mon, 13 Aug 2012 12:18:26 +0200 Subject: [PATCH 4/7] Remove the email server config item and the associated code --- modules/prefs.py | 3 --- modules/prefsBA.ui | 26 +++----------------------- 2 files changed, 3 insertions(+), 26 deletions(-) diff --git a/modules/prefs.py b/modules/prefs.py index 56ea409..3112d49 100644 --- a/modules/prefs.py +++ b/modules/prefs.py @@ -27,8 +27,6 @@ def load(self): self.parent.setfont(setting.toPyObject()) if preference == 'Match Font': self.parent.setMatchFont(setting.toPyObject()) - if preference == 'Email Server': - self.emailServerEdit.setText(setting.toPyObject()) if preference == 'Recent Files Count': self.recentFilesSpinBox.setValue(int(setting.toPyObject())) except Exception, e: @@ -39,7 +37,6 @@ def load(self): def save(self): self.settings.setValue('Font', self.parent.getfont()) self.settings.setValue('Match Font', self.parent.getMatchFont()) - self.settings.setValue('Email Server', self.emailServerEdit.text()) self.settings.setValue('Recent Files Count', self.recentFilesSpinBox.text()) self.settings.sync() diff --git a/modules/prefsBA.ui b/modules/prefsBA.ui index c5c8586..6b3b40a 100644 --- a/modules/prefsBA.ui +++ b/modules/prefsBA.ui @@ -68,26 +68,7 @@ - - - - - - - - 0 - 0 - - - - Email Server: - - - false - - - - + @@ -103,7 +84,7 @@ - + Qt::Horizontal @@ -119,7 +100,7 @@ - + @@ -228,7 +209,6 @@ fontButton - emailServerEdit recentFilesSpinBox buttonHelp buttonApply From b2fb9528903ca32dcdb85d92e94aa411afe009fe Mon Sep 17 00:00:00 2001 From: Justus Winter <4winter@informatik.uni-hamburg.de> Date: Mon, 13 Aug 2012 12:29:48 +0200 Subject: [PATCH 5/7] Fix the layout of the preferences window --- modules/prefsBA.ui | 73 ++++++++++++++++++---------------------------- 1 file changed, 28 insertions(+), 45 deletions(-) diff --git a/modules/prefsBA.ui b/modules/prefsBA.ui index 6b3b40a..d2f2290 100644 --- a/modules/prefsBA.ui +++ b/modules/prefsBA.ui @@ -7,7 +7,7 @@ 0 0 540 - 268 + 145 @@ -22,7 +22,7 @@ 11 6 519 - 246 + 131 @@ -44,13 +44,6 @@ - - - - - - - @@ -61,14 +54,21 @@ - + - + + + + + + + + @@ -84,23 +84,7 @@ - - - - Qt::Horizontal - - - QSizePolicy::Expanding - - - - 380 - 20 - - - - - + @@ -116,24 +100,24 @@ + + + + Qt::Horizontal + + + QSizePolicy::Expanding + + + + 380 + 20 + + + + - - - - Qt::Vertical - - - QSizePolicy::Expanding - - - - 20 - 40 - - - - @@ -209,7 +193,6 @@ fontButton - recentFilesSpinBox buttonHelp buttonApply buttonOk From 881d9f3d6cbb568373d2e7a0c9eff27b51045f5d Mon Sep 17 00:00:00 2001 From: Justus Winter <4winter@informatik.uni-hamburg.de> Date: Mon, 13 Aug 2012 13:36:03 +0200 Subject: [PATCH 6/7] Remove the web browser and mail server paragraphs from the help --- help/prefs.html | 4 ---- 1 file changed, 4 deletions(-) diff --git a/help/prefs.html b/help/prefs.html index 06baaf2..793bfc3 100644 --- a/help/prefs.html +++ b/help/prefs.html @@ -77,12 +77,8 @@

Kodos - Help

Preferences

-Set your Web Browser path. You can use the ... button to use a file dialog to help locate your browser. The web browser is necessary to access external links within the Python Regex documentation.

- The Editor Font allows you to select a desirable font for use within the Kodos editor

-The Email Server is required for sending bug reports to the author. You can submit a bug report under the main Help menu

- You can control the number of Recent Files that are displayed at the bottom of the File menu. The default value is 5. To display more or less recent files, set this value accordingly. The recent file list is maintained independtly of this value such that applying a lower value and then a higher value will not purge entries. That is, if you have 10 files and then set this value to 3 and later set this value to 10, the 10 files will be immediately available for selection .

From 360b3442db3b70b2ccf2727b77a937affecfeb07 Mon Sep 17 00:00:00 2001 From: Justus Winter <4winter@informatik.uni-hamburg.de> Date: Mon, 13 Aug 2012 13:31:22 +0200 Subject: [PATCH 7/7] Split the kodos file into a small launcher and modules.main --- kodos | 1007 +---------------------------------------------- modules/main.py | 991 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 998 insertions(+), 1000 deletions(-) create mode 100644 modules/main.py diff --git a/kodos b/kodos index 49ead1b..b742888 100755 --- a/kodos +++ b/kodos @@ -4,1014 +4,21 @@ import sys import os -import string -import re -import cPickle -import types import getopt -import urllib -import signal try: - from PyQt4.QtGui import * - from PyQt4.QtCore import * + from PyQt4 import QtGui + from PyQt4 import QtCore except: sys.exit("""Could not locate the PyQt module. Please make sure that you have installed PyQt for the version of Python that you are running.""") ### make sure that this script can find kodos specific modules ### from distutils.sysconfig import get_python_lib - sys.path.append(os.path.join(get_python_lib(), "kodos")) -################################################################### - -from modules.kodosBA import * -from modules.util import * -from modules.about import * -import modules.help as help -from modules.status_bar import * -from modules.reference import * -from modules.prefs import * -from modules.version import VERSION -from modules.recent_files import RecentFiles -from modules.urlDialog import URLDialog -from modules.regexLibrary import RegexLibrary -from modules.newUserDialogBA import NewUserDialog -from modules.flags import reFlag, reFlagList - - -# match status -MATCH_NA = 0 -MATCH_OK = 1 -MATCH_FAIL = 2 -MATCH_PAUSED = 3 -MATCH_EXAMINED = 4 - -TRUE = 1 -FALSE = 0 - -TIMEOUT = 3 - -# regex to find special flags which must begin at beginning of line -# or after some spaces -EMBEDDED_FLAGS = r"^ *\(\?(?P[iLmsux]*)\)" - -RX_BACKREF = re.compile(r"""\\\d""") - -STATE_UNEDITED = 0 -STATE_EDITED = 1 - -GEO = "kodos_geometry" - -# colors for normal & examination mode -QCOLOR_WHITE = QColor(Qt.white) # normal -QCOLOR_YELLOW = QColor(255,255,127) # examine - -try: - signal.SIGALRM - HAS_ALARM = 1 -except: - HAS_ALARM = 0 - - -############################################################################## -# -# The Kodos class which defines the main functionality and user interaction -# -############################################################################## - -class Kodos(KodosBA): - def __init__(self, filename, debug): - KodosBA.__init__(self) - - self.debug = debug - self.regex = "" - self.matchstring = "" - self.replace = "" - self.is_paused = 0 - self.is_examined = 0 - self.filename = "" - self.match_num = 1 # matches are labeled 1..n - self.replace_num = 0 # replace all - self.url = None - self.group_tuples = None - self.editstate = STATE_UNEDITED - - self.ref_win = None - self.regexlibwin = None - - self.embedded_flags_obj = re.compile(EMBEDDED_FLAGS) - self.regex_embedded_flags_removed = "" - - self.createStatusBar() - - self.MSG_NA = self.tr("Enter a regular expression and a string to match against") - self.MSG_PAUSED = self.tr("Kodos regex processing is paused. Click the pause icon to unpause") - self.MSG_FAIL = self.tr("Pattern does not match") - - - self.statusPixmapsDict = { MATCH_NA: QPixmap(":images/yellow.png"), - MATCH_OK: QPixmap(":images/green.png"), - MATCH_FAIL: QPixmap(":images/red.png"), - MATCH_PAUSED: QPixmap(":images/pause.png"), - } - - - self.updateStatus(self.MSG_NA, MATCH_NA) - - self.reFlags = reFlagList([ - reFlag("re.IGNORECASE", "i", self.ignorecaseCheckBox), - reFlag("re.MULTILINE", "m", self.multilineCheckBox), - reFlag("re.DOTALL", "s", self.dotallCheckBox), - reFlag("re.VERBOSE", "x", self.verboseCheckBox), - reFlag("re.LOCALE", "L", self.localeCheckBox), - reFlag("re.UNICODE", "u", self.unicodeCheckBox), - ]) - self.reFlags.clearAll() - - restoreWindowSettings(self, GEO) - - self.show() - - self.prefs = Preferences(self, 1) - self.recent_files = RecentFiles(self, - self.prefs.recentFilesSpinBox.value(), - self.debug) - - if filename and self.openFile(filename): - qApp.processEvents() - - self.fileMenu.triggered.connect(self.fileMenuHandler) - - kodos_toolbar_logo(self.toolBar) - if self.replace: self.show_replace_widgets() - else: self.hide_replace_widgets() - - self.checkIfNewUser() - - - def checkIfNewUser(self): - s = QSettings() - if s.value('New User', "true").toPyObject() != "false": - self.newuserdialog = NewUserDialog() - self.newuserdialog.show() - s.setValue('New User', "false") - - - def createStatusBar(self): - self.status_bar = Status_Bar(self, FALSE, "") - - - def updateStatus(self, status_string, status_value, duration=0, replace=FALSE, tooltip=''): - pixmap = self.statusPixmapsDict.get(status_value) - - self.status_bar.set_message(status_string, duration, replace, tooltip, pixmap) - - - def fileMenuHandler(self, menuid): - if self.recent_files.isRecentFile(menuid): - fn = str(menuid.text()) - if self.openFile(fn): - self.recent_files.add(fn) - - def prefsSaved(self): - if self.debug: print "prefsSaved slot" - self.recent_files.setNumShown(self.prefs.recentFilesSpinBox.value()) - - - def kodos_edited_slot(self): - # invoked whenever the user has edited something - self.editstate = STATE_EDITED - - - def checkbox_slot(self): - self.process_regex() - - - def set_flags(self, flags): - # from the given integer value of flags, set the checkboxes - # this is used when loading a saved file - for f in self.reFlags: - f.checkBox.setChecked(flags & f.reFlag) - - - def get_flags_string(self): - flags_str = "" - - for f in self.reFlags: - if f.checkBox.isChecked(): - flags_str += "| " + f.flagName - - if flags_str: - flags_str = ", " + flags_str[1:] - return flags_str - - - def get_embedded_flags_string(self): - flags_str = flags = "" - - for f in self.reFlags: - if f.checkBox.isChecked(): - flags += f.shortFlag - - if flags: - flags_str = "(?" + flags + ")" - - return flags_str - - - def pause(self): - self.is_paused = not self.is_paused - if self.debug: print "is_paused:", self.is_paused - - if self.is_paused: - self.update_results(self.MSG_PAUSED, MATCH_PAUSED) - self.matchNumberSpinBox.setDisabled(1) - - else: - self.process_regex() - self.matchNumberSpinBox.setEnabled(1) - - - def examine(self): - self.is_examined = not self.is_examined - if self.debug: print "is_examined:", self.is_examined - - if self.is_examined: - color = QCOLOR_YELLOW - regex = self.regex - self.regex_saved = self.regex - length = len(regex) - self.regexMultiLineEdit.setReadOnly(1) - self.stringMultiLineEdit.setReadOnly(1) - self.replaceTextEdit.setReadOnly(1) - for i in range(length, 0, -1): - regex = regex[:i] - self.process_embedded_flags(self.regex) - try: - m = re.search(regex, self.matchstring, self.reFlags.allFlagsORed()) - if m: - if self.debug: print "examined regex:", regex - self.__refresh_regex_widget(color, regex) - return - except: - pass - - self.__refresh_regex_widget(color, "") - else: - regex = self.regex_saved - color = QCOLOR_WHITE - self.regexMultiLineEdit.setReadOnly(0) - self.stringMultiLineEdit.setReadOnly(0) - self.replaceTextEdit.setReadOnly(0) - self.__refresh_regex_widget(color, regex) - - - def __refresh_regex_widget(self, base_qcolor, regex): - pal = QPalette() - pal.setColor(pal.Base, base_qcolor) - self.regexMultiLineEdit.setPalette(pal) - - self.regexMultiLineEdit.blockSignals(1) - self.regexMultiLineEdit.clear() - self.regexMultiLineEdit.blockSignals(0) - self.regexMultiLineEdit.setPlainText(regex) - - - def match_num_slot(self, num): - self.match_num = num - self.process_regex() - - - def replace_num_slot(self, num): - self.replace_num = num - self.process_regex() - - - def regex_changed_slot(self): - self.regex = unicode(self.regexMultiLineEdit.toPlainText()) - self.process_regex() - - - def string_changed_slot(self): - self.matchstring = unicode(self.stringMultiLineEdit.toPlainText()) - self.process_regex() - - def helpContents(self, x = None): - pass #FIXME - def helpIndex(self, x = None): - pass #FIXME - - def hide_replace_widgets(self): - self.spacerLabel.hide() - self.replaceLabel.hide() - self.replaceNumberSpinBox.hide() - self.replaceTextBrowser.clear() - self.replaceTextBrowser.setDisabled(TRUE) - - def show_replace_widgets(self): - self.spacerLabel.show() - self.replaceLabel.show() - self.replaceNumberSpinBox.show() - self.replaceNumberSpinBox.setEnabled(TRUE) - self.replaceTextBrowser.setEnabled(TRUE) - - def replace_changed_slot(self): - self.replace = unicode(self.replaceTextEdit.toPlainText()) - self.process_regex() - if not self.replace: - self.hide_replace_widgets() - else: - self.show_replace_widgets() - - - def update_results(self, msg, val): - self.updateStatus(msg, val) - - - def populate_group_table(self, tuples): - rows = len(tuples) - # Remove old rows for groups that no longer exist - for i in range(rows, self.groupTable.rowCount()): - self.groupTable.removeRow(i) - - self.groupTable.setRowCount(rows) - row = 0 - for t in tuples: - self.groupTable.setItem(row, 0, QTableWidgetItem(t[1])) - self.groupTable.setItem(row, 1, QTableWidgetItem(t[2])) - row += 1 - - - def populate_code_textbrowser(self): - self.codeTextBrowser.clear() - - code = "import re\n\n" - code += "# common variables\n\n" - code += "rawstr = r\"\"\"" + self.regex_embedded_flags_removed + "\"\"\"\n" - code += "embedded_rawstr = r\"\"\"" + self.get_embedded_flags_string() + \ - self.regex_embedded_flags_removed + "\"\"\"\n" - code += 'matchstr = \"\"\"' + self.matchstring + '\"\"\"' - code += "\n\n" - code += "# method 1: using a compile object\n" - code += "compile_obj = re.compile(rawstr" - code += self.get_flags_string() - code += ")\n" - code += "match_obj = compile_obj.search(matchstr)\n\n" - - code += "# method 2: using search function (w/ external flags)\n" - code += "match_obj = re.search(rawstr, matchstr" - code += self.get_flags_string() - code += ")\n\n" - - code += "# method 3: using search function (w/ embedded flags)\n" - code += "match_obj = re.search(embedded_rawstr, matchstr)\n\n" - - - if self.group_tuples: - code += "# Retrieve group(s) from match_obj\n" - code += "all_groups = match_obj.groups()\n\n" - code += "# Retrieve group(s) by index\n" - i = 0 - named_grps = 0 - for grp in self.group_tuples: - i += 1 - code += "group_%d = match_obj.group(%d)\n" % (i, i) - if grp[1]: named_grps = 1 - - if named_grps: - code += "\n# Retrieve group(s) by name\n" - for grp in self.group_tuples: - if grp[1]: - code += "%s = match_obj.group('%s')\n" % (grp[1], grp[1]) - - code += "\n" - - if self.replace: - code += "# Replace string\n" - code += "newstr = compile_obj.subn('%s', %d)\n" % (self.replace, - self.replace_num) - - self.codeTextBrowser.setPlainText(code) - - - def colorize_strings(self, strings, widget, cursorOffset=0): - widget.clear() - - colors = (QBrush(QColor(Qt.black)), QBrush(QColor(Qt.blue)) ) - cur = widget.textCursor() - format = cur.charFormat() - - pos = cur.position() - i = 0 - for s in strings: - format.setForeground(colors[i%2]) - cur.insertText(s, format) - if i == cursorOffset: - pos = cur.position() - i += 1 - - cur.setPosition(pos) - widget.setTextCursor(cur) - widget.centerCursor() - - - def populate_match_textbrowser(self, startpos, endpos): - pre = post = match = "" - - match = self.matchstring[startpos:endpos] - - # prepend the beginning that didn't match - if startpos > 0: - pre = self.matchstring[0:startpos] - - # append the end that didn't match - if endpos < len(self.matchstring): - post = self.matchstring[endpos:] - - strings = [pre, match, post] - self.colorize_strings(strings, self.matchTextBrowser, 1) - - - def populate_replace_textbrowser(self, spans, nummatches, compile_obj): - self.replaceTextBrowser.clear() - if not spans: return - - num = self.replaceNumberSpinBox.value() - if num == 0: num = nummatches - text = self.matchstring - - replace_text = unicode(self.replaceTextEdit.toPlainText()) - if RX_BACKREF.search(replace_text): - # if the replace string contains a backref we just use the - # python regex methods for the substitution - replaced = compile_obj.subn(replace_text, text, num)[0] - self.replaceTextBrowser.setPlainText(replaced) - return - - numreplaced = idx = 0 - - strings = [] - - for span in spans: - if span[0] != 0: - s = text[idx:span[0]] - else: - s = "" - - idx = span[1] - numreplaced += 1 - - strings.append(s) - strings.append(self.replace) - - if numreplaced >= num: - strings.append(text[span[1]:]) - break - - self.colorize_strings(strings, self.replaceTextBrowser) - - - def populate_matchAll_textbrowser(self, spans): - self.matchAllTextBrowser.clear() - if not spans: return - - idx = 0 - text = self.matchstring - strings = [] - for span in spans: - if span[0] != 0: - s = text[idx:span[0]] - else: - s = "" - - idx = span[1] - strings.append(s) - strings.append(text[span[0]:span[1]]) - - if 0 <= idx <= len(text): - strings.append(text[span[1]:]) - - self.colorize_strings(strings, self.matchAllTextBrowser) - - - def clear_results(self): - # .clear() destroys the headers, and .clearContents() doesn't do - # anything at all, so remove the rows one by one - for i in range(self.groupTable.rowCount()): - self.groupTable.removeRow(i) - - self.codeTextBrowser.clear() - self.matchTextBrowser.clear() - self.matchNumberSpinBox.setEnabled(FALSE) - self.replaceNumberSpinBox.setEnabled(FALSE) - self.replaceTextBrowser.clear() - self.matchAllTextBrowser.clear() - - - def process_regex(self): - def timeout(signum, frame): - return - - if self.is_paused: - return - - self.process_embedded_flags(self.regex) - - if not self.regex or not self.matchstring: - self.update_results(self.MSG_NA, MATCH_NA) - self.clear_results() - return - - if HAS_ALARM: - signal.signal(signal.SIGALRM, timeout) - signal.alarm(TIMEOUT) - - try: - compile_obj = re.compile(self.regex, self.reFlags.allFlagsORed()) - allmatches = compile_obj.findall(self.matchstring) - - if allmatches and len(allmatches): - self.matchNumberSpinBox.setMaximum(len(allmatches)) - self.matchNumberSpinBox.setEnabled(TRUE) - self.replaceNumberSpinBox.setMaximum(len(allmatches)) - self.replaceNumberSpinBox.setEnabled(TRUE) - else: - self.matchNumberSpinBox.setEnabled(FALSE) - self.replaceNumberSpinBox.setEnabled(FALSE) - - match_obj = compile_obj.search(self.matchstring) - - except Exception, e: - self.update_results(unicode(e), MATCH_FAIL) - return - - if HAS_ALARM: - signal.alarm(0) - - if not match_obj: - self.update_results(self.MSG_FAIL, MATCH_FAIL) - - self.clear_results() - return - - # match_index is the list element for match_num. - # Therefor match_num is for ui display - # and match_index is for application logic. - match_index = self.match_num - 1 - - if match_index > 0: - for i in range(match_index): - match_obj = compile_obj.search(self.matchstring, - match_obj.end()) - - self.populate_match_textbrowser(match_obj.start(), match_obj.end()) - - self.group_tuples = [] - - if match_obj.groups(): - group_nums = {} - if compile_obj.groupindex: - keys = compile_obj.groupindex.keys() - for key in keys: - group_nums[compile_obj.groupindex[key]] = key - - if self.debug: - print "group_nums:", group_nums - print "grp index: ", compile_obj.groupindex - print "groups:", match_obj.groups() - print "span: ", match_obj.span() - - # create group_tuple in the form: (group #, group name, group matches) - g = allmatches[match_index] - if type(g) == types.TupleType: - for i in range(len(g)): - group_tuple = (i+1, group_nums.get(i+1, ""), g[i]) - self.group_tuples.append(group_tuple) - else: - self.group_tuples.append( (1, group_nums.get(1, ""), g) ) - - self.populate_group_table(self.group_tuples) - else: - # clear the group table - self.populate_group_table([]) - - str_pattern_matches = unicode(self.tr("Pattern matches")) - str_found = unicode(self.tr("found")) - str_match = unicode(self.tr("match")) - str_matches = unicode(self.tr("matches")) - - if len(allmatches) == 1: - status = "%s (%s 1 %s)" % (str_pattern_matches, - str_found, - str_match) - else: - status = "%s (%s %d %s)" % (str_pattern_matches, - str_found, - len(allmatches), - str_matches) - - self.update_results(status, MATCH_OK) - self.populate_code_textbrowser() - - spans = self.findAllSpans(compile_obj) - if self.replace: - self.populate_replace_textbrowser(spans, len(allmatches), compile_obj) - self.populate_matchAll_textbrowser(spans) - - - def findAllSpans(self, compile_obj): - spans = [] - - match_obj = compile_obj.search(self.matchstring) - - last_span = None - - while match_obj: - start = match_obj.start() - end = match_obj.end() - span = (start, end) - if last_span == span: break - - spans.append(span) - - last_span = span - match_obj = compile_obj.search(self.matchstring, end) - - return spans - - - def closeEvent(self, ev): - if not self.checkEditState(): - ev.ignore() - return - - saveWindowSettings(self, GEO) - - try: - self.regexlibwin.close() - except: - pass - - try: - self.ref_win.close() - except: - pass - ev.accept() - - - def fileNew(self): - if not self.checkEditState(): - return - self.filename = "" - - self.regexMultiLineEdit.setPlainText("") - self.stringMultiLineEdit.setPlainText("") - self.replaceTextEdit.setPlainText("") - self.set_flags(0) - self.editstate = STATE_UNEDITED - - - def importURL(self): - self.urldialog = URLDialog(self, self.url) - self.urldialog.urlImported.connect(self.urlImported) - - - def urlImported(self, html, url): - self.url = url - self.stringMultiLineEdit.setPlainText(html) - - - def importFile(self): - fn = QFileDialog.getOpenFileName(self, - self.tr("Import File"), - self.filename, - self.tr("All (*)")) - - if fn.isEmpty(): - self.updateStatus(self.tr("A file was not selected for import"), - -1, - 5, - TRUE) - return None - - filename = str(fn) - - try: - fp = open(filename, "r") - except: - msg = self.tr("Could not open file for reading: ") + filename - self.updateStatus(msg, -1, 5, TRUE) - return None - - data = fp.read() - fp.close() - self.stringMultiLineEdit.setPlainText(data) - - - def fileOpen(self): - filename = self.filename - if filename == None: - filename = "" - fn = QFileDialog.getOpenFileName(self, - self.tr("Open Kodos File"), - filename, - self.tr("Kodos file (*.kds);;All (*)")) - if not fn.isEmpty(): - filename = str(fn) - if self.openFile(filename): - self.recent_files.add(filename) - - - def openFile(self, filename): - if not self.checkEditState(): - return - - self.filename = None - - try: - fp = open(filename, "r") - except: - msg = self.tr("Could not open file for reading: ") + filename - self.updateStatus(msg, -1, 5, TRUE) - return None - - try: - u = cPickle.Unpickler(fp) - self.regex = u.load() - self.matchstring = u.load() - flags = u.load() - except Exception, e: #FIXME: don't catch everything - if self.debug: - print unicode(e) - msg = "%s %s" % (unicode(self.tr("Error reading from file:")), - filename) - self.updateStatus(msg, -1, 5, TRUE) - return 0 - - self.matchNumberSpinBox.setValue(1) - self.regexMultiLineEdit.setPlainText(self.regex) - self.stringMultiLineEdit.setPlainText(self.matchstring) - - self.set_flags(flags) - - try: - replace = u.load() - except: - # versions prior to 1.7 did not have replace functionality - # so kds files saved w/ these versions will throw exception - # here. - replace = "" - self.replaceTextEdit.setPlainText(replace) - - self.filename = filename - msg = "%s %s" % (filename, unicode(self.tr("loaded successfully"))) - self.updateStatus(msg, -1, 5, TRUE) - self.editstate = STATE_UNEDITED - return 1 - - - def fileSaveAs(self): - filename = self.filename - if filename == None: - filename = "" - filedialog = QFileDialog(self, - self.tr("Save Kodos File"), - filename, - "Kodos file (*.kds);;All (*)") - filedialog.setAcceptMode(QFileDialog.AcceptSave) - filedialog.setDefaultSuffix("kds") - ok = filedialog.exec_() - - if ok == QDialog.Rejected: - self.updateStatus(self.tr("No file selected to save"), -1, 5, TRUE) - return - - filename = os.path.normcase(unicode(filedialog.selectedFiles().first())) - - self.filename = filename - self.fileSave() - - - def fileSave(self): - if not self.filename: - self.fileSaveAs() - return - - try: - fp = open(self.filename, "w") - except: - msg = "%s: %s" % (unicode(self.tr("Could not open file for writing:")), - self.filename) - self.updateStatus(msg, -1, 5, TRUE) - return None - - self.editstate = STATE_UNEDITED - p = cPickle.Pickler(fp) - p.dump(self.regex) - p.dump(self.matchstring) - p.dump(self.reFlags.allFlagsORed()) - p.dump(self.replace) - - fp.close() - msg = "%s %s" % (unicode(self.filename), - unicode(self.tr("successfully saved"))) - self.updateStatus(msg, -1, 5, TRUE) - self.recent_files.add(self.filename) - - - def paste_symbol(self, symbol): - self.regexMultiLineEdit.insertPlainText(symbol) - - - def process_embedded_flags(self, regex): - # determine if the regex contains embedded regex flags. - # if it does, set the appropriate checkboxes on the UI to reflect the flags that are embedded - match = self.embedded_flags_obj.match(regex) - if not match: - embedded_flags = "" - self.regex_embedded_flags_removed = regex - else: - embedded_flags = match.group('flags') - self.regex_embedded_flags_removed = self.embedded_flags_obj.sub("", regex, 1) - - for f in self.reFlags: - if f.shortFlag in embedded_flags: - f.embed() - else: - f.deembed() - - - def checkEditState(self): - if self.editstate == STATE_EDITED: - message = self.tr("You have made changes. Would you like to save them before continuing?") - - prompt = QMessageBox.warning(None, - self.tr("Save changes?"), - message, - QMessageBox.Save | - QMessageBox.Cancel | - QMessageBox.Discard) - - if prompt == QMessageBox.Cancel: - return False - - if prompt == QMessageBox.Save: - self.fileSave() - if not self.filename: self.checkEditState() - - return True - - - def pasteFromRegexLib(self, d): - if not self.checkEditState(): - return - - self.filename = "" - - self.regexMultiLineEdit.setPlainText(d.get('regex', "")) - self.stringMultiLineEdit.setPlainText(d.get('text', "")) - self.replaceTextEdit.setPlainText(d.get('replace', "")) - - try: - # set the current page if applicable - self.resultTabWidget.setCurrentIndex(int(d['tab'])) - except KeyError: - pass - self.editstate = STATE_UNEDITED - - - def revert_file_slot(self): - if not self.filename: - self.updateStatus(self.tr("There is no filename to revert"), - -1, - 5, - TRUE) - return - - self.openFile(self.filename) - - - def getWidget(self): - widget = qApp.focusWidget() - if (widget == self.regexMultiLineEdit or - widget == self.stringMultiLineEdit or - widget == self.replaceTextEdit or - widget == self.codeTextBrowser): - return widget - else: - return None - - - def widgetMethod(self, methodstr, anywidget=0): - # execute the methodstr of widget only if widget - # is one of the editable widgets OR if the method - # may be applied to any widget. - widget = qApp.focusWidget() - if anywidget or ( - widget == self.regexMultiLineEdit or - widget == self.stringMultiLineEdit or - widget == self.replaceTextEdit or - widget == self.codeTextBrowser): - try: - eval("widget.%s" % methodstr) - except: - pass - - - def editUndo(self): - self.widgetMethod("undo()") - - def editRedo(self): - self.widgetMethod("redo()") - - def editCopy(self): - self.widgetMethod("copy()", 1) - - def editCut(self): - self.widgetMethod("cut()") - - def editPaste(self): - self.widgetMethod("paste()") - - - def preferences(self): - self.prefs.showPrefsDialog() - self.prefs.prefsSaved.connect(self.prefsSaved) - - def setfont(self, font): - self.regexMultiLineEdit.setFont(font) - self.stringMultiLineEdit.setFont(font) - self.replaceTextEdit.setFont(font) - - def setMatchFont(self, font): - self.groupTable.setFont(font) - self.matchTextBrowser.setFont(font) - self.matchAllTextBrowser.setFont(font) - self.replaceTextBrowser.setFont(font) - self.codeTextBrowser.setFont(font) - - - def getfont(self): - return self.regexMultiLineEdit.font() - - - def getMatchFont(self): - return self.groupTable.font() - - - def helpHelp(self): - self.helpWindow = help.Help(self, "kodos.html") - - - def helpPythonRegex(self): - self.helpWindow = help.Help(self, os.path.join("python", "module-re.html")) - - - def helpRegexLib(self): - f = os.path.join("help", "regex-lib.xml") - self.regexlibwin = RegexLibrary(f) - self.regexlibwin.pasteRegexLib.connect(self.pasteFromRegexLib) - self.regexlibwin.show() - - - def helpAbout(self): - self.aboutWindow = About() - self.aboutWindow.show() - - - def kodos_website(self): - self.launch_browser_wrapper('https://github.com/luksan/kodos', - message=self.tr('Launch web browser to go to the kodos project page?')) - - - def launch_browser_wrapper(self, url, caption=None, message=None): - if launch_browser(url, caption, message): - self.status_bar.set_message(self.tr("Launching web browser"), - 3, - TRUE) - else: - self.status_bar.set_message(self.tr("Cancelled web browser launch"), - 3, - TRUE) - - - def reference_guide(self): - self.ref_win = Reference(self) - self.ref_win.pasteSymbol.connect(self.paste_symbol) - self.ref_win.show() - - - def report_bug(self): - self.launch_browser_wrapper('https://github.com/luksan/kodos/issues', - message=self.tr("Launch web browser to report a bug in kodos?")) - - -############################################################################## -# -# -############################################################################## +from modules.main import Kodos +from modules.util import findFile def usage(): print "kodos.py [-f filename | --file=filename ] [ -d debug | --debug=debug ] [ -k kodos_dir ]" @@ -1056,20 +63,20 @@ def main(): os.environ['KODOS_DIR'] = kodos_dir - qApp = QApplication(sys.argv) + qApp = QtGui.QApplication(sys.argv) qApp.setOrganizationName("kodos") qApp.setApplicationName("kodos") qApp.setOrganizationDomain("kodos.sourceforge.net") if locale not in (None, 'en'): - localefile = "kodos_%s.qm" % (locale or QTextCodec.locale()) + localefile = "kodos_%s.qm" % (locale or QtCore.QTextCodec.locale()) localepath = findFile(os.path.join("translations", localefile)) if debug: print "locale changed to:", locale print localefile print localepath - translator = QTranslator(qApp) + translator = QtCore.QTranslator(qApp) translator.load(localepath) qApp.installTranslator(translator) diff --git a/modules/main.py b/modules/main.py new file mode 100644 index 0000000..e97f498 --- /dev/null +++ b/modules/main.py @@ -0,0 +1,991 @@ +# -*- coding: utf-8 -*- + +import re +import types +import signal +import string +import urllib +import cPickle + +from PyQt4.QtGui import * +from PyQt4.QtCore import * + +from kodosBA import * +from util import * +from about import * +from . import help +from status_bar import * +from reference import * +from prefs import * +from version import VERSION +from recent_files import RecentFiles +from urlDialog import URLDialog +from regexLibrary import RegexLibrary +from newUserDialogBA import NewUserDialog +from flags import reFlag, reFlagList + +# match status +MATCH_NA = 0 +MATCH_OK = 1 +MATCH_FAIL = 2 +MATCH_PAUSED = 3 +MATCH_EXAMINED = 4 + +TRUE = 1 +FALSE = 0 + +TIMEOUT = 3 + +# regex to find special flags which must begin at beginning of line +# or after some spaces +EMBEDDED_FLAGS = r"^ *\(\?(?P[iLmsux]*)\)" + +RX_BACKREF = re.compile(r"""\\\d""") + +STATE_UNEDITED = 0 +STATE_EDITED = 1 + +GEO = "kodos_geometry" + +# colors for normal & examination mode +QCOLOR_WHITE = QColor(Qt.white) # normal +QCOLOR_YELLOW = QColor(255,255,127) # examine + +try: + signal.SIGALRM + HAS_ALARM = 1 +except: + HAS_ALARM = 0 + + +############################################################################## +# +# The Kodos class which defines the main functionality and user interaction +# +############################################################################## + +class Kodos(KodosBA): + def __init__(self, filename, debug): + KodosBA.__init__(self) + + self.debug = debug + self.regex = "" + self.matchstring = "" + self.replace = "" + self.is_paused = 0 + self.is_examined = 0 + self.filename = "" + self.match_num = 1 # matches are labeled 1..n + self.replace_num = 0 # replace all + self.url = None + self.group_tuples = None + self.editstate = STATE_UNEDITED + + self.ref_win = None + self.regexlibwin = None + + self.embedded_flags_obj = re.compile(EMBEDDED_FLAGS) + self.regex_embedded_flags_removed = "" + + self.createStatusBar() + + self.MSG_NA = self.tr("Enter a regular expression and a string to match against") + self.MSG_PAUSED = self.tr("Kodos regex processing is paused. Click the pause icon to unpause") + self.MSG_FAIL = self.tr("Pattern does not match") + + + self.statusPixmapsDict = { MATCH_NA: QPixmap(":images/yellow.png"), + MATCH_OK: QPixmap(":images/green.png"), + MATCH_FAIL: QPixmap(":images/red.png"), + MATCH_PAUSED: QPixmap(":images/pause.png"), + } + + + self.updateStatus(self.MSG_NA, MATCH_NA) + + self.reFlags = reFlagList([ + reFlag("re.IGNORECASE", "i", self.ignorecaseCheckBox), + reFlag("re.MULTILINE", "m", self.multilineCheckBox), + reFlag("re.DOTALL", "s", self.dotallCheckBox), + reFlag("re.VERBOSE", "x", self.verboseCheckBox), + reFlag("re.LOCALE", "L", self.localeCheckBox), + reFlag("re.UNICODE", "u", self.unicodeCheckBox), + ]) + self.reFlags.clearAll() + + restoreWindowSettings(self, GEO) + + self.show() + + self.prefs = Preferences(self, 1) + self.recent_files = RecentFiles(self, + self.prefs.recentFilesSpinBox.value(), + self.debug) + + if filename and self.openFile(filename): + qApp.processEvents() + + self.fileMenu.triggered.connect(self.fileMenuHandler) + + kodos_toolbar_logo(self.toolBar) + if self.replace: self.show_replace_widgets() + else: self.hide_replace_widgets() + + self.checkIfNewUser() + + + def checkIfNewUser(self): + s = QSettings() + if s.value('New User', "true").toPyObject() != "false": + self.newuserdialog = NewUserDialog() + self.newuserdialog.show() + s.setValue('New User', "false") + + + def createStatusBar(self): + self.status_bar = Status_Bar(self, FALSE, "") + + + def updateStatus(self, status_string, status_value, duration=0, replace=FALSE, tooltip=''): + pixmap = self.statusPixmapsDict.get(status_value) + + self.status_bar.set_message(status_string, duration, replace, tooltip, pixmap) + + + def fileMenuHandler(self, menuid): + if self.recent_files.isRecentFile(menuid): + fn = str(menuid.text()) + if self.openFile(fn): + self.recent_files.add(fn) + + def prefsSaved(self): + if self.debug: print "prefsSaved slot" + self.recent_files.setNumShown(self.prefs.recentFilesSpinBox.value()) + + + def kodos_edited_slot(self): + # invoked whenever the user has edited something + self.editstate = STATE_EDITED + + + def checkbox_slot(self): + self.process_regex() + + + def set_flags(self, flags): + # from the given integer value of flags, set the checkboxes + # this is used when loading a saved file + for f in self.reFlags: + f.checkBox.setChecked(flags & f.reFlag) + + + def get_flags_string(self): + flags_str = "" + + for f in self.reFlags: + if f.checkBox.isChecked(): + flags_str += "| " + f.flagName + + if flags_str: + flags_str = ", " + flags_str[1:] + return flags_str + + + def get_embedded_flags_string(self): + flags_str = flags = "" + + for f in self.reFlags: + if f.checkBox.isChecked(): + flags += f.shortFlag + + if flags: + flags_str = "(?" + flags + ")" + + return flags_str + + + def pause(self): + self.is_paused = not self.is_paused + if self.debug: print "is_paused:", self.is_paused + + if self.is_paused: + self.update_results(self.MSG_PAUSED, MATCH_PAUSED) + self.matchNumberSpinBox.setDisabled(1) + + else: + self.process_regex() + self.matchNumberSpinBox.setEnabled(1) + + + def examine(self): + self.is_examined = not self.is_examined + if self.debug: print "is_examined:", self.is_examined + + if self.is_examined: + color = QCOLOR_YELLOW + regex = self.regex + self.regex_saved = self.regex + length = len(regex) + self.regexMultiLineEdit.setReadOnly(1) + self.stringMultiLineEdit.setReadOnly(1) + self.replaceTextEdit.setReadOnly(1) + for i in range(length, 0, -1): + regex = regex[:i] + self.process_embedded_flags(self.regex) + try: + m = re.search(regex, self.matchstring, self.reFlags.allFlagsORed()) + if m: + if self.debug: print "examined regex:", regex + self.__refresh_regex_widget(color, regex) + return + except: + pass + + self.__refresh_regex_widget(color, "") + else: + regex = self.regex_saved + color = QCOLOR_WHITE + self.regexMultiLineEdit.setReadOnly(0) + self.stringMultiLineEdit.setReadOnly(0) + self.replaceTextEdit.setReadOnly(0) + self.__refresh_regex_widget(color, regex) + + + def __refresh_regex_widget(self, base_qcolor, regex): + pal = QPalette() + pal.setColor(pal.Base, base_qcolor) + self.regexMultiLineEdit.setPalette(pal) + + self.regexMultiLineEdit.blockSignals(1) + self.regexMultiLineEdit.clear() + self.regexMultiLineEdit.blockSignals(0) + self.regexMultiLineEdit.setPlainText(regex) + + + def match_num_slot(self, num): + self.match_num = num + self.process_regex() + + + def replace_num_slot(self, num): + self.replace_num = num + self.process_regex() + + + def regex_changed_slot(self): + self.regex = unicode(self.regexMultiLineEdit.toPlainText()) + self.process_regex() + + + def string_changed_slot(self): + self.matchstring = unicode(self.stringMultiLineEdit.toPlainText()) + self.process_regex() + + def helpContents(self, x = None): + pass #FIXME + def helpIndex(self, x = None): + pass #FIXME + + def hide_replace_widgets(self): + self.spacerLabel.hide() + self.replaceLabel.hide() + self.replaceNumberSpinBox.hide() + self.replaceTextBrowser.clear() + self.replaceTextBrowser.setDisabled(TRUE) + + def show_replace_widgets(self): + self.spacerLabel.show() + self.replaceLabel.show() + self.replaceNumberSpinBox.show() + self.replaceNumberSpinBox.setEnabled(TRUE) + self.replaceTextBrowser.setEnabled(TRUE) + + def replace_changed_slot(self): + self.replace = unicode(self.replaceTextEdit.toPlainText()) + self.process_regex() + if not self.replace: + self.hide_replace_widgets() + else: + self.show_replace_widgets() + + + def update_results(self, msg, val): + self.updateStatus(msg, val) + + + def populate_group_table(self, tuples): + rows = len(tuples) + # Remove old rows for groups that no longer exist + for i in range(rows, self.groupTable.rowCount()): + self.groupTable.removeRow(i) + + self.groupTable.setRowCount(rows) + row = 0 + for t in tuples: + self.groupTable.setItem(row, 0, QTableWidgetItem(t[1])) + self.groupTable.setItem(row, 1, QTableWidgetItem(t[2])) + row += 1 + + + def populate_code_textbrowser(self): + self.codeTextBrowser.clear() + + code = "import re\n\n" + code += "# common variables\n\n" + code += "rawstr = r\"\"\"" + self.regex_embedded_flags_removed + "\"\"\"\n" + code += "embedded_rawstr = r\"\"\"" + self.get_embedded_flags_string() + \ + self.regex_embedded_flags_removed + "\"\"\"\n" + code += 'matchstr = \"\"\"' + self.matchstring + '\"\"\"' + code += "\n\n" + code += "# method 1: using a compile object\n" + code += "compile_obj = re.compile(rawstr" + code += self.get_flags_string() + code += ")\n" + code += "match_obj = compile_obj.search(matchstr)\n\n" + + code += "# method 2: using search function (w/ external flags)\n" + code += "match_obj = re.search(rawstr, matchstr" + code += self.get_flags_string() + code += ")\n\n" + + code += "# method 3: using search function (w/ embedded flags)\n" + code += "match_obj = re.search(embedded_rawstr, matchstr)\n\n" + + + if self.group_tuples: + code += "# Retrieve group(s) from match_obj\n" + code += "all_groups = match_obj.groups()\n\n" + code += "# Retrieve group(s) by index\n" + i = 0 + named_grps = 0 + for grp in self.group_tuples: + i += 1 + code += "group_%d = match_obj.group(%d)\n" % (i, i) + if grp[1]: named_grps = 1 + + if named_grps: + code += "\n# Retrieve group(s) by name\n" + for grp in self.group_tuples: + if grp[1]: + code += "%s = match_obj.group('%s')\n" % (grp[1], grp[1]) + + code += "\n" + + if self.replace: + code += "# Replace string\n" + code += "newstr = compile_obj.subn('%s', %d)\n" % (self.replace, + self.replace_num) + + self.codeTextBrowser.setPlainText(code) + + + def colorize_strings(self, strings, widget, cursorOffset=0): + widget.clear() + + colors = (QBrush(QColor(Qt.black)), QBrush(QColor(Qt.blue)) ) + cur = widget.textCursor() + format = cur.charFormat() + + pos = cur.position() + i = 0 + for s in strings: + format.setForeground(colors[i%2]) + cur.insertText(s, format) + if i == cursorOffset: + pos = cur.position() + i += 1 + + cur.setPosition(pos) + widget.setTextCursor(cur) + widget.centerCursor() + + + def populate_match_textbrowser(self, startpos, endpos): + pre = post = match = "" + + match = self.matchstring[startpos:endpos] + + # prepend the beginning that didn't match + if startpos > 0: + pre = self.matchstring[0:startpos] + + # append the end that didn't match + if endpos < len(self.matchstring): + post = self.matchstring[endpos:] + + strings = [pre, match, post] + self.colorize_strings(strings, self.matchTextBrowser, 1) + + + def populate_replace_textbrowser(self, spans, nummatches, compile_obj): + self.replaceTextBrowser.clear() + if not spans: return + + num = self.replaceNumberSpinBox.value() + if num == 0: num = nummatches + text = self.matchstring + + replace_text = unicode(self.replaceTextEdit.toPlainText()) + if RX_BACKREF.search(replace_text): + # if the replace string contains a backref we just use the + # python regex methods for the substitution + replaced = compile_obj.subn(replace_text, text, num)[0] + self.replaceTextBrowser.setPlainText(replaced) + return + + numreplaced = idx = 0 + + strings = [] + + for span in spans: + if span[0] != 0: + s = text[idx:span[0]] + else: + s = "" + + idx = span[1] + numreplaced += 1 + + strings.append(s) + strings.append(self.replace) + + if numreplaced >= num: + strings.append(text[span[1]:]) + break + + self.colorize_strings(strings, self.replaceTextBrowser) + + + def populate_matchAll_textbrowser(self, spans): + self.matchAllTextBrowser.clear() + if not spans: return + + idx = 0 + text = self.matchstring + strings = [] + for span in spans: + if span[0] != 0: + s = text[idx:span[0]] + else: + s = "" + + idx = span[1] + strings.append(s) + strings.append(text[span[0]:span[1]]) + + if 0 <= idx <= len(text): + strings.append(text[span[1]:]) + + self.colorize_strings(strings, self.matchAllTextBrowser) + + + def clear_results(self): + # .clear() destroys the headers, and .clearContents() doesn't do + # anything at all, so remove the rows one by one + for i in range(self.groupTable.rowCount()): + self.groupTable.removeRow(i) + + self.codeTextBrowser.clear() + self.matchTextBrowser.clear() + self.matchNumberSpinBox.setEnabled(FALSE) + self.replaceNumberSpinBox.setEnabled(FALSE) + self.replaceTextBrowser.clear() + self.matchAllTextBrowser.clear() + + + def process_regex(self): + def timeout(signum, frame): + return + + if self.is_paused: + return + + self.process_embedded_flags(self.regex) + + if not self.regex or not self.matchstring: + self.update_results(self.MSG_NA, MATCH_NA) + self.clear_results() + return + + if HAS_ALARM: + signal.signal(signal.SIGALRM, timeout) + signal.alarm(TIMEOUT) + + try: + compile_obj = re.compile(self.regex, self.reFlags.allFlagsORed()) + allmatches = compile_obj.findall(self.matchstring) + + if allmatches and len(allmatches): + self.matchNumberSpinBox.setMaximum(len(allmatches)) + self.matchNumberSpinBox.setEnabled(TRUE) + self.replaceNumberSpinBox.setMaximum(len(allmatches)) + self.replaceNumberSpinBox.setEnabled(TRUE) + else: + self.matchNumberSpinBox.setEnabled(FALSE) + self.replaceNumberSpinBox.setEnabled(FALSE) + + match_obj = compile_obj.search(self.matchstring) + + except Exception, e: + self.update_results(unicode(e), MATCH_FAIL) + return + + if HAS_ALARM: + signal.alarm(0) + + if not match_obj: + self.update_results(self.MSG_FAIL, MATCH_FAIL) + + self.clear_results() + return + + # match_index is the list element for match_num. + # Therefor match_num is for ui display + # and match_index is for application logic. + match_index = self.match_num - 1 + + if match_index > 0: + for i in range(match_index): + match_obj = compile_obj.search(self.matchstring, + match_obj.end()) + + self.populate_match_textbrowser(match_obj.start(), match_obj.end()) + + self.group_tuples = [] + + if match_obj.groups(): + group_nums = {} + if compile_obj.groupindex: + keys = compile_obj.groupindex.keys() + for key in keys: + group_nums[compile_obj.groupindex[key]] = key + + if self.debug: + print "group_nums:", group_nums + print "grp index: ", compile_obj.groupindex + print "groups:", match_obj.groups() + print "span: ", match_obj.span() + + # create group_tuple in the form: (group #, group name, group matches) + g = allmatches[match_index] + if type(g) == types.TupleType: + for i in range(len(g)): + group_tuple = (i+1, group_nums.get(i+1, ""), g[i]) + self.group_tuples.append(group_tuple) + else: + self.group_tuples.append( (1, group_nums.get(1, ""), g) ) + + self.populate_group_table(self.group_tuples) + else: + # clear the group table + self.populate_group_table([]) + + str_pattern_matches = unicode(self.tr("Pattern matches")) + str_found = unicode(self.tr("found")) + str_match = unicode(self.tr("match")) + str_matches = unicode(self.tr("matches")) + + if len(allmatches) == 1: + status = "%s (%s 1 %s)" % (str_pattern_matches, + str_found, + str_match) + else: + status = "%s (%s %d %s)" % (str_pattern_matches, + str_found, + len(allmatches), + str_matches) + + self.update_results(status, MATCH_OK) + self.populate_code_textbrowser() + + spans = self.findAllSpans(compile_obj) + if self.replace: + self.populate_replace_textbrowser(spans, len(allmatches), compile_obj) + self.populate_matchAll_textbrowser(spans) + + + def findAllSpans(self, compile_obj): + spans = [] + + match_obj = compile_obj.search(self.matchstring) + + last_span = None + + while match_obj: + start = match_obj.start() + end = match_obj.end() + span = (start, end) + if last_span == span: break + + spans.append(span) + + last_span = span + match_obj = compile_obj.search(self.matchstring, end) + + return spans + + + def closeEvent(self, ev): + if not self.checkEditState(): + ev.ignore() + return + + saveWindowSettings(self, GEO) + + try: + self.regexlibwin.close() + except: + pass + + try: + self.ref_win.close() + except: + pass + ev.accept() + + + def fileNew(self): + if not self.checkEditState(): + return + self.filename = "" + + self.regexMultiLineEdit.setPlainText("") + self.stringMultiLineEdit.setPlainText("") + self.replaceTextEdit.setPlainText("") + self.set_flags(0) + self.editstate = STATE_UNEDITED + + + def importURL(self): + self.urldialog = URLDialog(self, self.url) + self.urldialog.urlImported.connect(self.urlImported) + + + def urlImported(self, html, url): + self.url = url + self.stringMultiLineEdit.setPlainText(html) + + + def importFile(self): + fn = QFileDialog.getOpenFileName(self, + self.tr("Import File"), + self.filename, + self.tr("All (*)")) + + if fn.isEmpty(): + self.updateStatus(self.tr("A file was not selected for import"), + -1, + 5, + TRUE) + return None + + filename = str(fn) + + try: + fp = open(filename, "r") + except: + msg = self.tr("Could not open file for reading: ") + filename + self.updateStatus(msg, -1, 5, TRUE) + return None + + data = fp.read() + fp.close() + self.stringMultiLineEdit.setPlainText(data) + + + def fileOpen(self): + filename = self.filename + if filename == None: + filename = "" + fn = QFileDialog.getOpenFileName(self, + self.tr("Open Kodos File"), + filename, + self.tr("Kodos file (*.kds);;All (*)")) + if not fn.isEmpty(): + filename = str(fn) + if self.openFile(filename): + self.recent_files.add(filename) + + + def openFile(self, filename): + if not self.checkEditState(): + return + + self.filename = None + + try: + fp = open(filename, "r") + except: + msg = self.tr("Could not open file for reading: ") + filename + self.updateStatus(msg, -1, 5, TRUE) + return None + + try: + u = cPickle.Unpickler(fp) + self.regex = u.load() + self.matchstring = u.load() + flags = u.load() + except Exception, e: #FIXME: don't catch everything + if self.debug: + print unicode(e) + msg = "%s %s" % (unicode(self.tr("Error reading from file:")), + filename) + self.updateStatus(msg, -1, 5, TRUE) + return 0 + + self.matchNumberSpinBox.setValue(1) + self.regexMultiLineEdit.setPlainText(self.regex) + self.stringMultiLineEdit.setPlainText(self.matchstring) + + self.set_flags(flags) + + try: + replace = u.load() + except: + # versions prior to 1.7 did not have replace functionality + # so kds files saved w/ these versions will throw exception + # here. + replace = "" + self.replaceTextEdit.setPlainText(replace) + + self.filename = filename + msg = "%s %s" % (filename, unicode(self.tr("loaded successfully"))) + self.updateStatus(msg, -1, 5, TRUE) + self.editstate = STATE_UNEDITED + return 1 + + + def fileSaveAs(self): + filename = self.filename + if filename == None: + filename = "" + filedialog = QFileDialog(self, + self.tr("Save Kodos File"), + filename, + "Kodos file (*.kds);;All (*)") + filedialog.setAcceptMode(QFileDialog.AcceptSave) + filedialog.setDefaultSuffix("kds") + ok = filedialog.exec_() + + if ok == QDialog.Rejected: + self.updateStatus(self.tr("No file selected to save"), -1, 5, TRUE) + return + + filename = os.path.normcase(unicode(filedialog.selectedFiles().first())) + + self.filename = filename + self.fileSave() + + + def fileSave(self): + if not self.filename: + self.fileSaveAs() + return + + try: + fp = open(self.filename, "w") + except: + msg = "%s: %s" % (unicode(self.tr("Could not open file for writing:")), + self.filename) + self.updateStatus(msg, -1, 5, TRUE) + return None + + self.editstate = STATE_UNEDITED + p = cPickle.Pickler(fp) + p.dump(self.regex) + p.dump(self.matchstring) + p.dump(self.reFlags.allFlagsORed()) + p.dump(self.replace) + + fp.close() + msg = "%s %s" % (unicode(self.filename), + unicode(self.tr("successfully saved"))) + self.updateStatus(msg, -1, 5, TRUE) + self.recent_files.add(self.filename) + + + def paste_symbol(self, symbol): + self.regexMultiLineEdit.insertPlainText(symbol) + + + def process_embedded_flags(self, regex): + # determine if the regex contains embedded regex flags. + # if it does, set the appropriate checkboxes on the UI to reflect the flags that are embedded + match = self.embedded_flags_obj.match(regex) + if not match: + embedded_flags = "" + self.regex_embedded_flags_removed = regex + else: + embedded_flags = match.group('flags') + self.regex_embedded_flags_removed = self.embedded_flags_obj.sub("", regex, 1) + + for f in self.reFlags: + if f.shortFlag in embedded_flags: + f.embed() + else: + f.deembed() + + + def checkEditState(self): + if self.editstate == STATE_EDITED: + message = self.tr("You have made changes. Would you like to save them before continuing?") + + prompt = QMessageBox.warning(None, + self.tr("Save changes?"), + message, + QMessageBox.Save | + QMessageBox.Cancel | + QMessageBox.Discard) + + if prompt == QMessageBox.Cancel: + return False + + if prompt == QMessageBox.Save: + self.fileSave() + if not self.filename: self.checkEditState() + + return True + + + def pasteFromRegexLib(self, d): + if not self.checkEditState(): + return + + self.filename = "" + + self.regexMultiLineEdit.setPlainText(d.get('regex', "")) + self.stringMultiLineEdit.setPlainText(d.get('text', "")) + self.replaceTextEdit.setPlainText(d.get('replace', "")) + + try: + # set the current page if applicable + self.resultTabWidget.setCurrentIndex(int(d['tab'])) + except KeyError: + pass + self.editstate = STATE_UNEDITED + + + def revert_file_slot(self): + if not self.filename: + self.updateStatus(self.tr("There is no filename to revert"), + -1, + 5, + TRUE) + return + + self.openFile(self.filename) + + + def getWidget(self): + widget = qApp.focusWidget() + if (widget == self.regexMultiLineEdit or + widget == self.stringMultiLineEdit or + widget == self.replaceTextEdit or + widget == self.codeTextBrowser): + return widget + else: + return None + + + def widgetMethod(self, methodstr, anywidget=0): + # execute the methodstr of widget only if widget + # is one of the editable widgets OR if the method + # may be applied to any widget. + widget = qApp.focusWidget() + if anywidget or ( + widget == self.regexMultiLineEdit or + widget == self.stringMultiLineEdit or + widget == self.replaceTextEdit or + widget == self.codeTextBrowser): + try: + eval("widget.%s" % methodstr) + except: + pass + + + def editUndo(self): + self.widgetMethod("undo()") + + def editRedo(self): + self.widgetMethod("redo()") + + def editCopy(self): + self.widgetMethod("copy()", 1) + + def editCut(self): + self.widgetMethod("cut()") + + def editPaste(self): + self.widgetMethod("paste()") + + + def preferences(self): + self.prefs.showPrefsDialog() + self.prefs.prefsSaved.connect(self.prefsSaved) + + def setfont(self, font): + self.regexMultiLineEdit.setFont(font) + self.stringMultiLineEdit.setFont(font) + self.replaceTextEdit.setFont(font) + + def setMatchFont(self, font): + self.groupTable.setFont(font) + self.matchTextBrowser.setFont(font) + self.matchAllTextBrowser.setFont(font) + self.replaceTextBrowser.setFont(font) + self.codeTextBrowser.setFont(font) + + + def getfont(self): + return self.regexMultiLineEdit.font() + + + def getMatchFont(self): + return self.groupTable.font() + + + def helpHelp(self): + self.helpWindow = help.Help(self, "kodos.html") + + + def helpPythonRegex(self): + self.helpWindow = help.Help(self, os.path.join("python", "module-re.html")) + + + def helpRegexLib(self): + f = os.path.join("help", "regex-lib.xml") + self.regexlibwin = RegexLibrary(f) + self.regexlibwin.pasteRegexLib.connect(self.pasteFromRegexLib) + self.regexlibwin.show() + + + def helpAbout(self): + self.aboutWindow = About() + self.aboutWindow.show() + + + def kodos_website(self): + self.launch_browser_wrapper('https://github.com/luksan/kodos', + message=self.tr('Launch web browser to go to the kodos project page?')) + + + def launch_browser_wrapper(self, url, caption=None, message=None): + if launch_browser(url, caption, message): + self.status_bar.set_message(self.tr("Launching web browser"), + 3, + TRUE) + else: + self.status_bar.set_message(self.tr("Cancelled web browser launch"), + 3, + TRUE) + + + def reference_guide(self): + self.ref_win = Reference(self) + self.ref_win.pasteSymbol.connect(self.paste_symbol) + self.ref_win.show() + + + def report_bug(self): + self.launch_browser_wrapper('https://github.com/luksan/kodos/issues', + message=self.tr("Launch web browser to report a bug in kodos?"))