From 2a3e855c482a8fc618384850d55d39e2c0afbe6a Mon Sep 17 00:00:00 2001 From: calvin Date: Wed, 11 Jun 2008 13:33:28 +0000 Subject: [PATCH] Added simple GUI client. git-svn-id: https://linkchecker.svn.sourceforge.net/svnroot/linkchecker/trunk/linkchecker@3813 e7d03fd6-7b0d-0410-9947-9c21f3af8025 --- TODO.txt | 5 +- gui/linkchecker-gui | 29 +++++ gui/linkchecker_main.py | 175 +++++++++++++++++++++++++++ gui/linkchecker_ui.py | 163 ++++++++++++++++++++++++++ gui/ui/mainwindow.ui | 254 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 623 insertions(+), 3 deletions(-) create mode 100755 gui/linkchecker-gui create mode 100644 gui/linkchecker_main.py create mode 100644 gui/linkchecker_ui.py create mode 100644 gui/ui/mainwindow.ui diff --git a/TODO.txt b/TODO.txt index a8925764..7f51a411 100644 --- a/TODO.txt +++ b/TODO.txt @@ -1,5 +1,4 @@ -- [INTERFACE] make a nice GUI for linkchecker - + QT GUI - http://www.diotavelli.net/PyQtWiki/Threading,_Signals_and_Slots +- [INTERFACE] GUI for linkchecker + add debian .menu file + test on Windows + + more configuration options diff --git a/gui/linkchecker-gui b/gui/linkchecker-gui new file mode 100755 index 00000000..f5cb9ae2 --- /dev/null +++ b/gui/linkchecker-gui @@ -0,0 +1,29 @@ +#!/usr/bin/python +# -*- coding: iso-8859-1 -*- +# Copyright (C) 2008 Bastian Kleineidam +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +""" +Check HTML pages for broken links. This is the GUI client. +""" +import sys +from PyQt4.QtGui import QApplication +from linkchecker_main import LinkCheckerMain + +if __name__ == "__main__": + app = QApplication(sys.argv) + window = LinkCheckerMain() + window.show() + sys.exit(app.exec_()) diff --git a/gui/linkchecker_main.py b/gui/linkchecker_main.py new file mode 100644 index 00000000..2022884e --- /dev/null +++ b/gui/linkchecker_main.py @@ -0,0 +1,175 @@ +# -*- coding: iso-8859-1 -*- +# Copyright (C) 2008 Bastian Kleineidam +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +import os +from PyQt4 import QtCore, QtGui +from linkchecker_ui import Ui_MainWindow +import linkcheck +from linkcheck import configuration, checker, director, add_intern_pattern, \ + strformat + + +class LinkCheckerMain (QtGui.QMainWindow, Ui_MainWindow): + def __init__(self, parent=None): + super(LinkCheckerMain, self).__init__(parent) + self.setupUi(self) + self.setWindowTitle(configuration.App) + self.textEdit.setFontFamily("mono") + self.checker = Checker() + self.connect(self.checker, QtCore.SIGNAL("finished()"), self.updateUi) + self.connect(self.checker, QtCore.SIGNAL("terminated()"), self.updateUi) + self.connect(self.checker, QtCore.SIGNAL("addMessage(QString)"), self.addMessage) + self.connect(self.checker, QtCore.SIGNAL("setStatus(QString)"), self.setStatus) + self.connect(self.pushButton, QtCore.SIGNAL("clicked()"), self.check_or_cancel) + self.connect(self.actionQuit, QtCore.SIGNAL("triggered()"), QtGui.qApp, QtCore.SLOT("quit()")) + self.connect(self.actionAbout, QtCore.SIGNAL("triggered()"), self.about) + self.updateUi() + + def about (self): + d = { + "app": configuration.App, + "appname": configuration.AppName, + "copyright": configuration.HtmlCopyright, + } + QtGui.QMessageBox.about(self, _(u"About %(appname)s") % d, + _(u"""

%(appname)s checks HTML documents and websites +for broken links.

+

%(copyright)s

+

%(app)s is licensed under the +GPL +Version 2 or later.

+
""") % d) + + def check_or_cancel (self): + if self.aggregate is None: + self.check() + else: + self.cancel() + + def check (self): + self.pushButton.setEnabled(False) + self.textEdit.setText("") + config = self.get_config() + aggregate = director.get_aggregate(config) + url = unicode(self.lineEdit.text()).strip() + if not url: + self.textEdit.setText(_("Error, empty URL")) + self.updateUi() + return + if url.startswith(u"www."): + url = u"http://%s" % url + elif url.startswith(u"ftp."): + url = u"ftp://%s" % url + self.setStatus(_("Checking '%s'.") % strformat.limit(url, 40)) + url_data = checker.get_url_from(url, 0, aggregate) + try: + add_intern_pattern(url_data, config) + except UnicodeError: + self.textEdit.setText(_("Error, invalid URL '%s'.") % + strformat.limit(url, 40)) + self.updateUi() + return + aggregate.urlqueue.put(url_data) + self.pushButton.setText(_("Cancel")) + self.aggregate = aggregate + self.pushButton.setEnabled(True) + # check in background + self.checker.check(self.aggregate) + + def cancel (self): + self.setStatus(_("Aborting.")) + director.abort(self.aggregate) + + def get_config (self): + config = configuration.Configuration() + config["recursionlevel"] = self.spinBox.value() + config.logger_add("gui", GuiLogger) + config["logger"] = config.logger_new('gui', widget=self.checker) + config["verbose"] = self.checkBox.isChecked() + config.init_logging(StatusLogger(self.checker)) + config["status"] = True + return config + + def addMessage (self, msg): + text = self.textEdit.toPlainText() + self.textEdit.setText(text+msg) + self.textEdit.moveCursor(QtGui.QTextCursor.End) + + def setStatus (self, msg): + self.statusBar.showMessage(msg) + + def updateUi (self): + self.pushButton.setText(_("Check")) + self.aggregate = None + self.pushButton.setEnabled(True) + self.setStatus(_("Ready.")) + + +class Checker (QtCore.QThread): + + def __init__ (self, parent=None): + super(Checker, self).__init__(parent) + self.exiting = False + self.aggregate = None + + def __del__(self): + self.exiting = True + self.wait() + + def check (self, aggregate): + self.aggregate = aggregate + # setup the thread and call run() + self.start() + + def run (self): + # start checking + director.check_urls(self.aggregate) + + +from linkcheck.logger.text import TextLogger + +class GuiLogger (TextLogger): + + def __init__ (self, **args): + super(GuiLogger, self).__init__(**args) + self.widget = args["widget"] + + def write (self, s, **args): + self.widget.emit(QtCore.SIGNAL("addMessage(QString)"), s) + + def start_fileoutput (self): + pass + + def close_fileoutput (self): + pass + + +class StatusLogger (object): + + def __init__ (self, widget): + self.widget = widget + self.buf = [] + + def write (self, msg): + self.buf.append(msg) + + def writeln (self, msg): + self.buf.extend([msg, unicode(os.linesep)]) + + def flush (self): + self.widget.emit(QtCore.SIGNAL("setStatus(QString)"), u"".join(self.buf)) + self.buf = [] diff --git a/gui/linkchecker_ui.py b/gui/linkchecker_ui.py new file mode 100644 index 00000000..cd3883e5 --- /dev/null +++ b/gui/linkchecker_ui.py @@ -0,0 +1,163 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'ui/mainwindow.ui' +# +# Created: Wed Jun 11 15:18:13 2008 +# by: PyQt4 UI code generator 4.3.3 +# +# WARNING! All changes made in this file will be lost! + +from PyQt4 import QtCore, QtGui + +class Ui_MainWindow(object): + def setupUi(self, MainWindow): + MainWindow.setObjectName("MainWindow") + MainWindow.resize(QtCore.QSize(QtCore.QRect(0,0,400,500).size()).expandedTo(MainWindow.minimumSizeHint())) + + sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred,QtGui.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(MainWindow.sizePolicy().hasHeightForWidth()) + MainWindow.setSizePolicy(sizePolicy) + + self.centralwidget = QtGui.QWidget(MainWindow) + self.centralwidget.setGeometry(QtCore.QRect(0,26,400,455)) + self.centralwidget.setObjectName("centralwidget") + + self.layoutWidget = QtGui.QWidget(self.centralwidget) + self.layoutWidget.setGeometry(QtCore.QRect(20,20,351,411)) + self.layoutWidget.setObjectName("layoutWidget") + + self.vboxlayout = QtGui.QVBoxLayout(self.layoutWidget) + self.vboxlayout.setObjectName("vboxlayout") + + self.gridlayout = QtGui.QGridLayout() + self.gridlayout.setObjectName("gridlayout") + + self.label = QtGui.QLabel(self.layoutWidget) + self.label.setObjectName("label") + self.gridlayout.addWidget(self.label,0,0,1,1) + + self.hboxlayout = QtGui.QHBoxLayout() + self.hboxlayout.setObjectName("hboxlayout") + + self.lineEdit = QtGui.QLineEdit(self.layoutWidget) + + sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding,QtGui.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(1) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.lineEdit.sizePolicy().hasHeightForWidth()) + self.lineEdit.setSizePolicy(sizePolicy) + self.lineEdit.setObjectName("lineEdit") + self.hboxlayout.addWidget(self.lineEdit) + + spacerItem = QtGui.QSpacerItem(40,20,QtGui.QSizePolicy.Expanding,QtGui.QSizePolicy.Minimum) + self.hboxlayout.addItem(spacerItem) + self.gridlayout.addLayout(self.hboxlayout,0,1,1,1) + + self.label_2 = QtGui.QLabel(self.layoutWidget) + self.label_2.setObjectName("label_2") + self.gridlayout.addWidget(self.label_2,1,0,1,1) + + self.hboxlayout1 = QtGui.QHBoxLayout() + self.hboxlayout1.setObjectName("hboxlayout1") + + self.spinBox = QtGui.QSpinBox(self.layoutWidget) + + sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Minimum,QtGui.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.spinBox.sizePolicy().hasHeightForWidth()) + self.spinBox.setSizePolicy(sizePolicy) + self.spinBox.setMinimumSize(QtCore.QSize(0,0)) + self.spinBox.setObjectName("spinBox") + self.hboxlayout1.addWidget(self.spinBox) + + spacerItem1 = QtGui.QSpacerItem(40,20,QtGui.QSizePolicy.Expanding,QtGui.QSizePolicy.Minimum) + self.hboxlayout1.addItem(spacerItem1) + self.gridlayout.addLayout(self.hboxlayout1,1,1,1,1) + + self.label_3 = QtGui.QLabel(self.layoutWidget) + self.label_3.setObjectName("label_3") + self.gridlayout.addWidget(self.label_3,2,0,1,1) + + self.hboxlayout2 = QtGui.QHBoxLayout() + self.hboxlayout2.setObjectName("hboxlayout2") + + self.checkBox = QtGui.QCheckBox(self.layoutWidget) + self.checkBox.setObjectName("checkBox") + self.hboxlayout2.addWidget(self.checkBox) + + spacerItem2 = QtGui.QSpacerItem(40,20,QtGui.QSizePolicy.Expanding,QtGui.QSizePolicy.Minimum) + self.hboxlayout2.addItem(spacerItem2) + self.gridlayout.addLayout(self.hboxlayout2,2,1,1,1) + self.vboxlayout.addLayout(self.gridlayout) + + self.hboxlayout3 = QtGui.QHBoxLayout() + self.hboxlayout3.setObjectName("hboxlayout3") + + spacerItem3 = QtGui.QSpacerItem(40,20,QtGui.QSizePolicy.Expanding,QtGui.QSizePolicy.Minimum) + self.hboxlayout3.addItem(spacerItem3) + + self.pushButton = QtGui.QPushButton(self.layoutWidget) + + sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Minimum,QtGui.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(1) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.pushButton.sizePolicy().hasHeightForWidth()) + self.pushButton.setSizePolicy(sizePolicy) + self.pushButton.setObjectName("pushButton") + self.hboxlayout3.addWidget(self.pushButton) + self.vboxlayout.addLayout(self.hboxlayout3) + + self.textEdit = QtGui.QTextEdit(self.layoutWidget) + self.textEdit.setUndoRedoEnabled(False) + self.textEdit.setLineWrapMode(QtGui.QTextEdit.NoWrap) + self.textEdit.setReadOnly(True) + self.textEdit.setObjectName("textEdit") + self.vboxlayout.addWidget(self.textEdit) + MainWindow.setCentralWidget(self.centralwidget) + + self.menubar = QtGui.QMenuBar(MainWindow) + self.menubar.setGeometry(QtCore.QRect(0,0,400,26)) + self.menubar.setObjectName("menubar") + + self.menuLinkChecka = QtGui.QMenu(self.menubar) + self.menuLinkChecka.setObjectName("menuLinkChecka") + + self.menuHelp = QtGui.QMenu(self.menubar) + self.menuHelp.setObjectName("menuHelp") + MainWindow.setMenuBar(self.menubar) + + self.statusBar = QtGui.QStatusBar(MainWindow) + self.statusBar.setGeometry(QtCore.QRect(0,481,400,19)) + self.statusBar.setObjectName("statusBar") + MainWindow.setStatusBar(self.statusBar) + + self.actionQuit = QtGui.QAction(MainWindow) + self.actionQuit.setObjectName("actionQuit") + + self.actionAbout = QtGui.QAction(MainWindow) + self.actionAbout.setObjectName("actionAbout") + self.menuLinkChecka.addAction(self.actionQuit) + self.menuHelp.addAction(self.actionAbout) + self.menubar.addAction(self.menuLinkChecka.menuAction()) + self.menubar.addAction(self.menuHelp.menuAction()) + self.label.setBuddy(self.lineEdit) + self.label_2.setBuddy(self.spinBox) + self.label_3.setBuddy(self.checkBox) + + self.retranslateUi(MainWindow) + QtCore.QMetaObject.connectSlotsByName(MainWindow) + + def retranslateUi(self, MainWindow): + MainWindow.setWindowTitle(QtGui.QApplication.translate("MainWindow", "LinkChecker", None, QtGui.QApplication.UnicodeUTF8)) + self.label.setText(QtGui.QApplication.translate("MainWindow", "URL", None, QtGui.QApplication.UnicodeUTF8)) + self.label_2.setText(QtGui.QApplication.translate("MainWindow", "Recursion", None, QtGui.QApplication.UnicodeUTF8)) + self.label_3.setText(QtGui.QApplication.translate("MainWindow", "Show all", None, QtGui.QApplication.UnicodeUTF8)) + self.pushButton.setText(QtGui.QApplication.translate("MainWindow", "Check", None, QtGui.QApplication.UnicodeUTF8)) + self.menuLinkChecka.setTitle(QtGui.QApplication.translate("MainWindow", "File", None, QtGui.QApplication.UnicodeUTF8)) + self.menuHelp.setTitle(QtGui.QApplication.translate("MainWindow", "Help", None, QtGui.QApplication.UnicodeUTF8)) + self.actionQuit.setText(QtGui.QApplication.translate("MainWindow", "Quit", None, QtGui.QApplication.UnicodeUTF8)) + self.actionAbout.setText(QtGui.QApplication.translate("MainWindow", "About", None, QtGui.QApplication.UnicodeUTF8)) + diff --git a/gui/ui/mainwindow.ui b/gui/ui/mainwindow.ui new file mode 100644 index 00000000..5b7a966c --- /dev/null +++ b/gui/ui/mainwindow.ui @@ -0,0 +1,254 @@ + + MainWindow + + + + 0 + 0 + 400 + 500 + + + + + 0 + 0 + + + + LinkChecker + + + + + + + + 0 + 26 + 400 + 455 + + + + + + 20 + 20 + 351 + 411 + + + + + + + + + URL + + + lineEdit + + + + + + + + + + 1 + 0 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Recursion + + + spinBox + + + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Show all + + + checkBox + + + + + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 1 + 0 + + + + Check + + + + + + + + + false + + + QTextEdit::NoWrap + + + true + + + + + + + + + + 0 + 0 + 400 + 26 + + + + + File + + + + + + Help + + + + + + + + + + 0 + 481 + 400 + 19 + + + + + + Quit + + + + + About + + + + + +