linkchecker/linkcheck/gui/__init__.py

337 lines
12 KiB
Python
Raw Normal View History

# -*- coding: iso-8859-1 -*-
2010-09-29 06:25:00 +00:00
# Copyright (C) 2008-2010 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.
#
2009-07-24 21:58:20 +00:00
# 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.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
import os
import sys
import webbrowser
from PyQt4 import QtCore, QtGui
from .linkchecker_ui_main import Ui_MainWindow
2010-11-06 10:34:44 +00:00
from .properties import PropertiesDialog
from .progress import LinkCheckerProgress, StatusLogger
2010-10-14 19:13:28 +00:00
from .debug import LinkCheckerDebug
from .logger import GuiLogger, GuiLogHandler
from .help import HelpWindow
from .options import LinkCheckerOptions
from .checker import CheckerThread
2009-03-07 13:40:13 +00:00
from .contextmenu import ContextMenu
2010-10-03 10:12:57 +00:00
from .editor import EditorWindow
from .urlmodel import UrlItem, UrlItemModel
2010-11-17 19:28:43 +00:00
from .settings import Settings
from .. import configuration, checker, director, add_intern_pattern, \
strformat, fileutil
from ..containers import enum
2010-10-03 10:12:57 +00:00
from .. import url as urlutil
from ..checker import httpheaders
2009-07-29 19:53:52 +00:00
DocBaseUrl = "qthelp://bfk.app.linkchecker/doc/"
RegistryBase = "Bastian"
Status = enum('idle', 'checking')
class LinkCheckerMain (QtGui.QMainWindow, Ui_MainWindow):
2010-11-18 22:26:34 +00:00
log_url_signal = QtCore.pyqtSignal(object)
def __init__(self, parent=None, url=None):
"""Initialize UI."""
super(LinkCheckerMain, self).__init__(parent)
self.setupUi(self)
if url:
self.urlinput.setText(url)
self.setWindowFlags(self.windowFlags() | QtCore.Qt.WindowContextHelpButtonHint)
self.setWindowTitle(configuration.App)
2010-11-17 19:28:43 +00:00
# app settings
self.settings = Settings(RegistryBase, configuration.AppName)
# init subdialogs
self.options = LinkCheckerOptions(parent=self)
self.progress = LinkCheckerProgress(parent=self)
2010-10-14 19:13:28 +00:00
self.debug = LinkCheckerDebug(parent=self)
self.checker = CheckerThread()
2009-03-07 13:40:13 +00:00
self.contextmenu = ContextMenu(parent=self)
2010-10-03 10:12:57 +00:00
self.editor = EditorWindow(parent=self)
2010-11-05 14:21:19 +00:00
self.properties = PropertiesDialog(parent=self)
2010-11-17 19:28:43 +00:00
# Note: do not use QT assistant here because of the .exe packaging
self.assistant = HelpWindow(self, self.get_qhcpath())
# init the rest
self.init_treeview()
self.connect_widgets()
self.init_config()
2010-11-17 19:28:43 +00:00
self.init_app()
def init_app (self):
2010-11-17 20:25:13 +00:00
data = self.settings.read_geometry()
if data["size"] is not None:
self.resize(data["size"])
if data["pos"] is not None:
self.move(data["pos"])
self.options.set_options(self.settings.read_options())
self.status = Status.idle
2010-11-06 19:03:26 +00:00
self.set_statusbar(_("Ready."))
def get_qhcpath (self):
2010-11-17 19:28:43 +00:00
"""Helper function to search for the QHC help file in different
locations."""
paths = [
# when developing
os.path.join(configuration.configdata.install_data, "doc", "html"),
# when running under py2exe
os.path.join(os.path.dirname(os.path.abspath(sys.executable)), "share", "linkchecker"),
# after installing as a package
configuration.configdata.config_dir,
]
for path in paths:
qhcfile = os.path.join(path, "lccollection.qhc")
if os.path.isfile(qhcfile):
break
return qhcfile
def connect_widgets (self):
2009-03-07 12:46:19 +00:00
"""Connect widget signals. Some signals use the AutoConnect feature.
Autoconnected methods have the form on_<objectname>_<signal>.
"""
2010-11-18 22:26:34 +00:00
def set_idle ():
self.status = Status.idle
self.checker.finished.connect(set_idle)
self.checker.terminated.connect(set_idle)
self.log_url_signal.connect(self.log_url)
def init_treeview (self):
self.model = UrlItemModel()
self.treeView.setModel(self.model)
self.treeView.setColumnHidden(0, True)
data = self.settings.read_treeviewcols()
self.treeView.setColumnWidth(1, data["col1"])
self.treeView.setColumnWidth(2, data["col2"])
self.treeView.setColumnWidth(3, data["col3"])
self.treeView.setSortingEnabled(True)
self.treeView.sortByColumn(0, QtCore.Qt.AscendingOrder)
def get_treeviewcols (self):
return dict(
col1=self.treeView.columnWidth(1),
col2=self.treeView.columnWidth(2),
col3=self.treeView.columnWidth(3),
)
def init_config (self):
"""Create a configuration object."""
self.config = configuration.Configuration()
self.config.logger_add("gui", GuiLogger)
self.config["logger"] = self.config.logger_new('gui',
signal=self.log_url_signal)
self.config["status"] = True
self.config["status_wait_seconds"] = 2
self.handler = GuiLogHandler(self.debug.log_msg_signal)
status = StatusLogger(self.progress.log_status_signal)
self.config.init_logging(status, handler=self.handler)
def set_config (self):
"""Set configuration."""
data = self.options.get_options()
self.config["recursionlevel"] = data["recursionlevel"]
self.config["verbose"] = data["verbose"]
if data["debug"]:
self.config.set_debug(["all"])
# make sure at least one thread is used
self.config["threads"] = 1
else:
self.config.reset_loglevel()
def get_status (self):
return self._status
def set_status (self, status):
self._status = status
if status == Status.idle:
self.progress.hide()
self.aggregate = None
self.controlButton.setEnabled(True)
elif status == Status.checking:
2009-02-24 21:36:35 +00:00
self.num = 0
2010-10-14 19:13:28 +00:00
self.debug.reset()
self.progress.reset()
self.progress.show()
self.controlButton.setEnabled(False)
status = property(get_status, set_status)
2010-11-18 22:26:34 +00:00
@QtCore.pyqtSlot()
2009-03-07 12:46:19 +00:00
def on_actionHelp_triggered (self):
"""Show help page."""
url = QtCore.QUrl("%sindex.html" % DocBaseUrl)
self.assistant.showDocumentation(url)
2010-11-18 22:26:34 +00:00
@QtCore.pyqtSlot()
2009-03-09 22:44:49 +00:00
def on_actionOptions_triggered (self):
"""Show option dialog."""
self.options.exec_()
2010-11-18 22:26:34 +00:00
@QtCore.pyqtSlot()
2009-03-09 22:44:49 +00:00
def on_actionQuit_triggered (self):
"""Quit application."""
self.close()
def closeEvent (self, e=None):
2010-11-17 19:28:43 +00:00
"""Save settings and remove registered logging handler"""
2010-11-17 20:25:13 +00:00
self.settings.save_geometry(dict(size=self.size(), pos=self.pos()))
self.settings.save_treeviewcols(self.get_treeviewcols())
2010-11-17 20:25:13 +00:00
self.settings.save_options(self.options.get_options())
2010-11-17 19:28:43 +00:00
self.settings.sync()
self.config.remove_loghandler(self.handler)
if e is not None:
e.accept()
2010-11-18 22:26:34 +00:00
@QtCore.pyqtSlot()
2009-03-07 12:46:19 +00:00
def on_actionAbout_triggered (self):
"""Display about dialog."""
d = {
"app": configuration.App,
"appname": configuration.AppName,
"copyright": configuration.HtmlCopyright,
}
QtGui.QMessageBox.about(self, _(u"About %(appname)s") % d,
_(u"""<qt><p>%(appname)s checks HTML documents and websites
for broken links.</p>
<p>%(copyright)s</p>
<p>%(app)s is licensed under the
<a href="http://www.gnu.org/licenses/gpl.html">GPL</a>
Version 2 or later.</p>
</qt>""") % d)
2010-11-18 22:26:34 +00:00
@QtCore.pyqtSlot()
2010-10-14 19:13:28 +00:00
def on_actionDebug_triggered (self):
"""Display debug dialog."""
self.debug.show()
def start (self):
"""Start a new check."""
if self.status == Status.idle:
self.check()
on_controlButton_clicked = on_urlinput_returnPressed = start
def check (self):
"""Check given URL."""
self.controlButton.setEnabled(False)
self.model.clear()
self.set_config()
aggregate = director.get_aggregate(self.config)
url = unicode(self.urlinput.text()).strip()
if not url:
self.set_statusbar(_("Error, empty URL"))
self.status = Status.idle
return
if url.startswith(u"www."):
url = u"http://%s" % url
elif url.startswith(u"ftp."):
url = u"ftp://%s" % url
self.set_statusbar(_("Checking '%s'.") % strformat.limit(url, 40))
url_data = checker.get_url_from(url, 0, aggregate)
try:
add_intern_pattern(url_data, self.config)
except UnicodeError:
self.set_statusbar(_("Error, invalid URL `%s'.") %
strformat.limit(url, 40))
self.status = Status.idle
return
aggregate.urlqueue.put(url_data)
self.aggregate = aggregate
# check in background
self.checker.check(self.aggregate, self.progress)
self.status = Status.checking
2009-03-07 12:46:19 +00:00
def log_url (self, url_data):
"""Add URL data to tree widget."""
self.model.addUrlItem(UrlItem(url_data, self.num))
2009-02-24 21:36:35 +00:00
self.num += 1
2010-11-05 14:21:19 +00:00
def view_item_properties (self, item):
self.properties.set_item(item)
self.properties.show()
2010-11-06 10:34:44 +00:00
def on_treeView_doubleClicked (self, index):
2009-03-07 13:40:13 +00:00
"""View property page."""
2010-11-06 10:34:44 +00:00
urlitem = self.model.getUrlItem(index)
if urlitem is not None:
self.view_item_properties(urlitem)
2009-03-07 13:40:13 +00:00
def on_treeView_customContextMenuRequested (self, point):
2009-03-07 13:40:13 +00:00
"""Show item context menu."""
2010-11-06 07:40:46 +00:00
urlitem = self.model.getUrlItem(self.treeView.currentIndex())
if urlitem is not None:
self.contextmenu.enableFromItem(urlitem)
2009-03-07 13:40:13 +00:00
self.contextmenu.popup(QtGui.QCursor.pos())
2010-11-18 22:26:34 +00:00
@QtCore.pyqtSlot()
2010-11-05 14:21:19 +00:00
def on_actionViewProperties_triggered (self):
"""View URL data properties in a separate window."""
2010-11-06 10:34:44 +00:00
urlitem = self.model.getUrlItem(self.treeView.currentIndex())
if urlitem is not None:
self.view_item_properties(urlitem)
2010-11-05 14:21:19 +00:00
2010-11-18 22:26:34 +00:00
@QtCore.pyqtSlot()
2009-03-07 13:40:13 +00:00
def on_actionViewOnline_triggered (self):
"""View item URL online."""
2010-11-06 07:40:46 +00:00
urlitem = self.model.getUrlItem(self.treeView.currentIndex())
if urlitem is not None:
webbrowser.open(urlitem.url_data.url)
2010-11-18 22:26:34 +00:00
@QtCore.pyqtSlot()
2010-10-03 10:12:57 +00:00
def on_actionViewParentOnline_triggered (self):
"""View item parent URL online."""
2010-11-06 07:40:46 +00:00
urlitem = self.model.getUrlItem(self.treeView.currentIndex())
if urlitem is not None:
webbrowser.open(urlitem.url_data.parent_url)
2010-09-30 05:32:39 +00:00
2010-11-18 22:26:34 +00:00
@QtCore.pyqtSlot()
2010-10-03 10:12:57 +00:00
def on_actionViewParentSource_triggered (self):
"""View item parent URL source in local text editor (read-only)."""
2010-11-06 07:40:46 +00:00
urlitem = self.model.getUrlItem(self.treeView.currentIndex())
if urlitem is not None:
self.view_source(urlitem.url_data.parent_url,
urlitem.url_data.line, urlitem.url_data.column)
2010-10-03 10:12:57 +00:00
def view_source (self, url, line, col):
self.editor.setWindowTitle(u"View %s" % url)
info, data = urlutil.get_content(url, proxy=self.config["proxy"])
if (info, data) == (None, None):
self.editor.setText(u"An error occurred retreiving URL `%s'." % url)
else:
content_type = httpheaders.get_content_type(info)
if not content_type:
# read function for content type guessing
read = lambda: data
content_type = fileutil.guess_mimetype(url, read=read)
2010-10-03 10:12:57 +00:00
self.editor.setContentType(content_type)
self.editor.setText(data, line=line, col=col)
self.editor.show()
2010-09-30 05:32:39 +00:00
2010-11-18 22:26:34 +00:00
@QtCore.pyqtSlot()
def on_actionCopyToClipboard_triggered (self):
2010-09-30 05:32:39 +00:00
"""Copy item URL to clipboard."""
2010-11-06 15:44:49 +00:00
urlitem = self.model.getUrlItem(self.treeView.currentIndex())
2010-11-06 07:40:46 +00:00
if urlitem:
clipboard = QtGui.QApplication.clipboard()
2010-11-06 07:40:46 +00:00
clipboard.setText(urlitem.url_data.url)
event = QtCore.QEvent(QtCore.QEvent.Clipboard)
QtGui.QApplication.sendEvent(clipboard, event)
def set_statusbar (self, msg):
"""Show status message in status bar."""
self.statusBar.showMessage(msg)