@@ -0,0 +1,134 @@
#!/usr/bin/env python
#
# Copyright (C) 2012 Vinay Sajip. Licensed under the MIT license.
#
# Based on Roberto Alsina's 128-line web browser, see
#
# http://lateral.netmanagers.com.ar/weblog/posts/BB948.html
#
import json
import os
import subprocess
import sys
import tempfile
from urllib import pathname2url
import sip
sip .setapi ("QString" , 2 )
sip .setapi ("QVariant" , 2 )
from PyQt4 import QtGui ,QtCore ,QtWebKit , QtNetwork
settings = QtCore .QSettings ("Vinay Sajip" , "DocWatch" )
class Watcher (QtCore .QThread ):
"""
A watcher which looks for source file changes, builds the documentation,
and notifies the browser to refresh its contents
"""
def run (self ):
self ._stop = False
watch_command = 'inotifywait -rq -e close_write --exclude \' "*.html"\' .' .split ()
make_command = 'make html' .split ()
while not self ._stop :
# Perhaps should put notifier access in a mutex - not bothering yet
self .notifier = subprocess .Popen (watch_command )
self .notifier .wait ()
if self ._stop :
break
subprocess .call (make_command )
# Refresh the UI ...
self .parent ().changed .emit ()
def stop (self ):
self ._stop = True
# Perhaps should put notifier access in a mutex - not bothering for now
if self .notifier .poll () is None : # not yet terminated ...
self .notifier .terminate ()
class MainWindow (QtGui .QMainWindow ):
"""
A browser intended for viewing HTML documentation generated by Sphinx.
"""
changed = QtCore .pyqtSignal ()
def __init__ (self , url ):
QtGui .QMainWindow .__init__ (self )
self .sb = self .statusBar ()
self .pbar = QtGui .QProgressBar ()
self .pbar .setMaximumWidth (120 )
self .wb = QtWebKit .QWebView (loadProgress = self .pbar .setValue , loadFinished = self .pbar .hide , loadStarted = self .pbar .show , titleChanged = self .setWindowTitle )
self .setCentralWidget (self .wb )
self .tb = self .addToolBar ("Main Toolbar" )
for a in (QtWebKit .QWebPage .Back , QtWebKit .QWebPage .Forward , QtWebKit .QWebPage .Reload ):
self .tb .addAction (self .wb .pageAction (a ))
self .url = QtGui .QLineEdit (returnPressed = lambda :self .wb .setUrl (QtCore .QUrl .fromUserInput (self .url .text ())))
self .tb .addWidget (self .url )
self .wb .urlChanged .connect (lambda u : self .url .setText (u .toString ()))
self .wb .urlChanged .connect (lambda : self .url .setCompleter (QtGui .QCompleter (QtCore .QStringList ([QtCore .QString (i .url ().toString ()) for i in self .wb .history ().items ()]), caseSensitivity = QtCore .Qt .CaseInsensitive )))
self .wb .statusBarMessage .connect (self .sb .showMessage )
self .wb .page ().linkHovered .connect (lambda l : self .sb .showMessage (l , 3000 ))
self .search = QtGui .QLineEdit (returnPressed = lambda : self .wb .findText (self .search .text ()))
self .search .hide ()
self .showSearch = QtGui .QShortcut ("Ctrl+F" , self , activated = lambda : (self .search .show () , self .search .setFocus ()))
self .hideSearch = QtGui .QShortcut ("Esc" , self , activated = lambda : (self .search .hide (), self .wb .setFocus ()))
self .quit = QtGui .QShortcut ("Ctrl+Q" , self , activated = self .close )
self .zoomIn = QtGui .QShortcut ("Ctrl++" , self , activated = lambda : self .wb .setZoomFactor (self .wb .zoomFactor ()+ .2 ))
self .zoomOut = QtGui .QShortcut ("Ctrl+-" , self , activated = lambda : self .wb .setZoomFactor (self .wb .zoomFactor ()- .2 ))
self .zoomOne = QtGui .QShortcut ("Ctrl+=" , self , activated = lambda : self .wb .setZoomFactor (1 ))
self .wb .settings ().setAttribute (QtWebKit .QWebSettings .PluginsEnabled , True )
self .sb .addPermanentWidget (self .search )
self .sb .addPermanentWidget (self .pbar )
self .load_settings ()
self .wb .load (url )
self .watcher = Watcher (self )
self .changed .connect (self .wb .reload )
self .watcher .start ()
def load_settings (self ):
settings .beginGroup ('mainwindow' )
pos = settings .value ('pos' )
size = settings .value ('size' )
if isinstance (pos , QtCore .QPoint ):
self .move (pos )
if isinstance (size , QtCore .QSize ):
self .resize (size )
settings .endGroup ()
def save_settings (self ):
settings .beginGroup ('mainwindow' )
settings .setValue ('pos' , self .pos ())
settings .setValue ('size' , self .size ())
settings .endGroup ()
def closeEvent (self , event ):
self .save_settings ()
self .watcher .stop ()
if __name__ == "__main__" :
if not os .path .isdir ('_build' ):
# very simplistic sanity check. Works for me, as I generally use
# sphinx-quickstart defaults
print ('You must run this application from a Sphinx directory containing _build' )
rc = 1
else :
app = QtGui .QApplication (sys .argv )
path = os .path .join ('_build' , 'html' , 'index.html' )
url = 'file:///' + pathname2url (os .path .abspath (path ))
url = QtCore .QUrl (url )
wb = MainWindow (url )
wb .show ()
rc = app .exec_ ()
sys .exit (rc )