Changeset 11291:c5fb32b6d8a4 in orange


Ignore:
Timestamp:
01/29/13 13:27:19 (15 months ago)
Author:
Ales Erjavec <ales.erjavec@…>
Branch:
default
Message:

Fixed/changed OWReport to work in the new interface.

Location:
Orange/OrangeWidgets
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • Orange/OrangeWidgets/OWReport.py

    r10966 r11291  
    1  # Widgets cannot be reset to the settings they had at the time of reporting. 
    2  # The reason lies in the OWGUI callback mechanism: callbacks are triggered only 
    3  # when the controls are changed by the user. If the related widget's attribute 
    4  # is changed programmatically, the control is updated but the callback is not 
    5  # called. This is done intentionally and with a very solid reason: it enables us 
    6  # to do multiple changes without, for instance, the widget being redrawn every time. 
    7  # Besides, it would probably lead to cycles or at least a great number of redundant calls.  
    8  # However, since setting attributes does not trigger callbacks, setting the attributes 
    9  # here would have not other effect than changing the widget's controls and leaving it 
    10  # in undefined (possibly invalid) state. The reason why we do not have these problems 
    11  # in "normal" use of settings is that the context independent settings are loaded only 
    12  # when the widget is initialized and the context dependent settings are retrieved when 
    13  # the new data is sent and the widget "knows" it has to reconfigure. 
    14  # The only solution would be to require all the widgets have a method for updating 
    15  # everything from scratch according to settings. This would require a lot of work, which 
    16  # could even not be feasible. For instance, there are widget which get the data, compute 
    17  # something and discard the data. This is good since it is memory efficient, but it 
    18  # may prohibit the widget from implementing the update-from-the-scratch method.   
    19   
    20   
     1# Widgets cannot be reset to the settings they had at the time of reporting. 
     2# The reason lies in the OWGUI callback mechanism: callbacks are triggered only 
     3# when the controls are changed by the user. If the related widget's attribute 
     4# is changed programmatically, the control is updated but the callback is not 
     5# called. This is done intentionally and with a very solid reason: it enables 
     6# us to do multiple changes without, for instance, the widget being redrawn 
     7# every time. Besides, it would probably lead to cycles or at least a great 
     8# number of redundant calls. However, since setting attributes does not trigger 
     9# callbacks, setting the attributes here would have not other effect than 
     10# changing the widget's controls and leaving it in undefined (possibly 
     11# invalid) state. The reason why we do not have these problems in "normal" use 
     12# of settings is that the context independent settings are loaded only when 
     13# the widget is initialized and the context dependent settings are retrieved 
     14# when the new data is sent and the widget "knows" it has to reconfigure. 
     15# The only solution would be to require all the widgets have a method for 
     16# updating everything from scratch according to settings. This would require 
     17# a lot of work, which 
     18# could even not be feasible. For instance, there are widget which get the 
     19# data, compute something and discard the data. This is good since it is 
     20# memory efficient, but it may prohibit the widget from implementing the 
     21# update-from-the-scratch method. 
     22 
     23import os 
     24import time 
     25import tempfile 
     26import shutil 
     27import re 
     28import pickle 
     29import binascii 
     30import xml.dom.minidom 
     31 
    2132from OWWidget import * 
    22 from OWWidget import * 
     33 
    2334from PyQt4.QtWebKit import * 
    2435 
    2536from Orange.utils import environ 
    2637 
    27 import os, time, tempfile, shutil, re, shutil, pickle, binascii 
    28 import xml.dom.minidom 
    29  
    30 report = None 
    31  
    3238 
    3339def get_instance(): 
    34     """Return the global ReportWindow instance or None if it was not yet 
    35     initialized. 
    36  
    3740    """ 
    38     if hasattr(qApp, "canvasDlg") and hasattr(qApp.canvasDlg, "reportWindow"): 
    39         return qApp.canvasDlg.reportWindow 
    40     else: 
    41         return report 
     41    Return the global `ReportWindow` instance. 
     42    """ 
     43    app = QApplication.instance() 
     44    if not hasattr(app, "_reportWindow"): 
     45        report = ReportWindow() 
     46        app._reportWindow = report 
     47        app.sendPostedEvents(report, 0) 
     48 
     49        app.aboutToQuit.connect(report.removeTemp) 
     50        # event loop will still process deferred delete events 
     51        # after aboutToQuit is emitted 
     52        app.aboutToQuit.connect(report.deleteLater) 
     53 
     54    return app._reportWindow 
    4255 
    4356 
     
    5063        QListWidget.__init__(self, parent) 
    5164        self.widget = widget 
    52          
     65 
    5366    def dropEvent(self, ev): 
    5467        QListWidget.dropEvent(self, ev) 
     
    5770    def mousePressEvent(self, ev): 
    5871        QListWidget.mousePressEvent(self, ev) 
    59         node = self.currentItem()  
     72        node = self.currentItem() 
    6073        if ev.button() == Qt.RightButton and node: 
    6174            self.widget.nodePopup.popup(ev.globalPos()) 
    6275 
    63      
     76 
    6477class ReportWindow(OWWidget): 
    65     indexfile = os.path.join(environ.widget_install_dir, "report", "index.html") 
    66      
     78    indexfile = os.path.join(environ.widget_install_dir, 
     79                             "report", "index.html") 
     80 
    6781    def __init__(self): 
    6882        OWWidget.__init__(self, None, None, "Report") 
    6983        self.dontScroll = False 
    70         global report 
    71         report = self 
     84        self.widgets = [] 
    7285        self.counter = 0 
    73          
     86 
    7487        self.tempdir = tempfile.mkdtemp("", "orange-report-") 
    7588 
     
    87100 
    88101        self.reportBrowser = QWebView(self.mainArea) 
    89         self.reportBrowser.setUrl(QUrl.fromLocalFile(self.indexfile)) 
    90         self.reportBrowser.page().mainFrame().addToJavaScriptWindowObject("myself", self) 
     102#        self.reportBrowser.setUrl(QUrl.fromLocalFile(self.indexfile)) 
     103        self.reportBrowser.setHtml(open(self.indexfile, "rb").read()) 
    91104        frame = self.reportBrowser.page().mainFrame() 
     105        frame.addToJavaScriptWindowObject("myself", self) 
     106        frame.setScrollBarPolicy(Qt.Vertical, Qt.ScrollBarAsNeeded) 
    92107        self.javascript = frame.evaluateJavaScript 
    93         frame.setScrollBarPolicy(Qt.Vertical, Qt.ScrollBarAsNeeded) 
     108 
    94109        self.mainArea.layout().addWidget(self.reportBrowser) 
    95110 
    96111        box = OWGUI.widgetBox(self.controlArea) 
    97 #        exportButton = OWGUI.button(box, self, "&Export", self.saveXML) 
    98 #        OWGUI.button(box, self, "&Import", self.loadXML) 
    99112        saveButton = OWGUI.button(box, self, "&Save", self.saveReport) 
    100113        printButton = OWGUI.button(box, self, "&Print", self.printReport) 
    101114        saveButton.setAutoDefault(0) 
    102          
     115 
    103116        self.nodePopup = QMenu("Widget") 
    104         self.showWidgetAction = self.nodePopup.addAction( "Show widget", self.showActiveNodeWidget) 
     117        self.showWidgetAction = self.nodePopup.addAction("Show widget", self.showActiveNodeWidget) 
    105118        self.nodePopup.addSeparator() 
    106         #self.renameAction = self.nodePopup.addAction( "&Rename", self.renameActiveNode, Qt.Key_F2) 
    107119        self.deleteAction = self.nodePopup.addAction("Remove", self.removeActiveNode, Qt.Key_Delete) 
    108120        self.deleteAllAction = self.nodePopup.addAction("Remove All", self.clearReport) 
     
    110122 
    111123        self.resize(900, 850) 
    112         
    113     # this should have been __del__, but it doesn't get called! 
     124 
    114125    def removeTemp(self): 
    115126        try: 
     
    118129            pass 
    119130 
    120  
    121131    def __call__(self, name, data, widgetId, icon, wtime=None): 
    122132        if not self.isVisible(): 
     
    124134        else: 
    125135            self.raise_() 
     136 
    126137        self.counter += 1 
    127138        elid = "N%03i" % self.counter 
     
    136147        self.tree.addItem(widnode) 
    137148        self.treeItems[elid] = widnode 
     149 
    138150        self.addEntry(widnode) 
    139          
    140         
     151 
     152    def appendReport(self, name, report, sender=None): 
     153        """ 
     154        Append a report section titled `name` and with html contents 
     155        `report`. `sender` if specified can be a OWBaseWidget instance 
     156        that is sending this report. 
     157 
     158        """ 
     159        if not self.isVisible(): 
     160            self.show() 
     161        else: 
     162            self.raise_() 
     163 
     164        if sender is not None: 
     165            icon = sender.windowIcon() 
     166            widgetId = sender.widgetId 
     167        else: 
     168            icon = QIcon() 
     169            widgetId = -1 
     170 
     171        self.counter += 1 
     172        elid = "N%03i" % self.counter 
     173 
     174        widnode = QListWidgetItem(icon, name, self.tree) 
     175        widnode.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable | \ 
     176                         Qt.ItemIsDragEnabled | Qt.ItemIsEditable) 
     177        widnode.elementId = elid 
     178        widnode.widgetId = widgetId 
     179        widnode.time = time.strftime("%a %b %d %y, %H:%M:%S") 
     180        widnode.data = report 
     181        widnode.name = name 
     182        self.tree.addItem(widnode) 
     183 
     184        self.treeItems[elid] = widnode 
     185 
     186        self.addEntry(widnode) 
     187 
    141188    def addEntry(self, widnode, scrollIntoView=True): 
    142         newEntry = """ 
    143         <div id="%s" onClick="myself.changeItem(this.id);"> 
    144             <a name="%s" /> 
    145             <h1>%s<span class="timestamp">%s</span></h1> 
    146             <div class="insideh1"> 
    147                 %s 
    148             </div> 
    149         </div> 
    150         """ % (widnode.elementId, widnode.elementId, widnode.name, widnode.time, widnode.data) 
     189        newEntry = ( 
     190            '<div id="%s" onClick="myself.changeItem(this.id);">' 
     191            '    <a name="%s">' 
     192            '    <h1>%s<span class="timestamp">%s</span></h1>' 
     193            '    <div class="insideh1">' 
     194            '        %s' 
     195            '    </div>' 
     196            '</div>' 
     197        ) % (widnode.elementId, widnode.elementId, widnode.name, 
     198             widnode.time, widnode.data) 
     199 
    151200        widnode.content = newEntry 
    152         self.javascript("document.body.innerHTML += '%s'" % escape(newEntry)) 
     201 
     202        body = self.bodyFrame() 
     203 
     204        body.appendInside(newEntry) 
     205 
    153206        if scrollIntoView: 
    154207            self.javascript("document.getElementById('%s').scrollIntoView();" % widnode.elementId) 
    155208 
     209    def bodyFrame(self): 
     210        main = self.reportBrowser.page().mainFrame() 
     211        return main.findFirstElement("body") 
    156212 
    157213    def selectionChanged(self, current, previous): 
     
    164220                    newsel.className = 'selected'; 
    165221                    newsel.scrollIntoView();""" % current.elementId) 
    166 #            if not self.dontScroll: 
    167 #                self.javascript("newsel.scrollIntoView(document.getElementById('%s'));" % current.elementId) 
    168222            self.showWidgetAction.setEnabled(current.widgetId >= 0) 
    169223        if previous: 
    170224            self.javascript("document.getElementById('%s').className = '';" % previous.elementId) 
    171          
    172          
     225 
    173226    def rebuildHtml(self): 
    174         self.javascript("document.body.innerHTML = ''") 
     227        self.bodyFrame().setInnerXml("") 
    175228        for i in range(self.tree.count()): 
    176229            self.addEntry(self.tree.item(i)) 
     230 
    177231        selected = self.tree.selectedItems() 
    178232        if selected: 
    179233            self.selectionChanged(selected[0], None) 
    180          
    181          
    182     @pyqtSignature("QString")  
     234 
     235    @pyqtSignature("QString") 
    183236    def changeItem(self, elid): 
    184237        self.dontScroll = True 
     
    187240        self.tree.scrollToItem(item) 
    188241        self.dontScroll = False 
    189   
     242 
    190243    def raiseWidget(self, node): 
    191244        for widget in self.widgets: 
     
    195248            return 
    196249        widget.instance.reshow() 
    197          
     250 
    198251    def showActiveNodeWidget(self): 
    199252        node = self.tree.currentItem() 
    200253        if node: 
    201254            self.raiseWidget(node) 
    202              
     255 
    203256    re_h1 = re.compile(r'<h1>(?P<name>.*?)<span class="timestamp">') 
    204257    def itemChanged(self, node): 
     
    217270        self.tree.clear() 
    218271        self.rebuildHtml() 
    219          
     272 
    220273    def printReport(self): 
    221274        printer = QPrinter() 
    222275        printDialog = QPrintDialog(printer, self) 
    223276        printDialog.setWindowTitle("Print report") 
     277 
    224278        if (printDialog.exec_() != QDialog.Accepted): 
    225279            return 
    226         getattr(self.reportBrowser, "print")(printer) 
    227          
    228          
    229     def createDirectory(self): 
    230         tmpPathName = os.tempnam(orange-report) 
    231         os.mkdir(tmpPathName) 
    232         return tmpPathName 
    233      
     280 
     281        self.reportBrowser.print_(printer) 
     282 
    234283    def getUniqueFileName(self, patt): 
    235284        for i in xrange(1000000): 
    236285            fn = os.path.join(self.tempdir, patt % i) 
    237286            if not os.path.exists(fn): 
    238                 return "file:///"+fn, fn 
    239  
    240     img_re = re.compile(r'<IMG.*?\ssrc="(?P<imgname>[^"]*)"', re.DOTALL+re.IGNORECASE) 
     287                return "file:///" + fn, fn 
     288 
     289    img_re = re.compile(r'<IMG.*?\ssrc="(?P<imgname>[^"]*)"', 
     290                        re.DOTALL + re.IGNORECASE) 
    241291    browser_re = re.compile(r'<!--browsercode(.*?)-->') 
     292 
     293    def getSaveDir(self): 
     294        """ 
     295        Return the initial file system path for the 'Save' dialog. 
     296        """ 
     297        if hasattr(self, "saveDir"): 
     298            # set by orngCanvas.OrangeCanvasDlg 
     299            return self.saveDir 
     300        else: 
     301            settings = QSettings() 
     302            savedir = QDesktopServices.storageLocation( 
     303                QDesktopServices.DocumentsLocation 
     304            ) 
     305 
     306            if PYQT_VERSION < 0x40803: 
     307                savedir = settings.value("OWReport/save-directory", 
     308                                         defaultValue=savedir).toString() 
     309            else: 
     310                savedir = settings.value("OWReport/save-directory", 
     311                                         defaultValue=savedir, 
     312                                         type=unicode) 
     313            return savedir 
     314 
     315    def storeSaveDir(self, savedir): 
     316        """ 
     317        Store the chosen folder path for subsequent save dialog 
     318        initialization. 
     319 
     320        """ 
     321        if hasattr(self, "saveDir"): 
     322            self.saveDir = savedir 
     323        else: 
     324            settings = QSettings() 
     325            settings.setValue("OWReport/save-directory", savedir) 
     326 
    242327    def saveReport(self): 
    243         filename = QFileDialog.getSaveFileName(self, "Save Report", self.saveDir, "Web page (*.html *.htm)") 
     328        """ 
     329        Save the report to a html file chosen by the user. 
     330        """ 
     331        savedir = self.getSaveDir() 
     332        filename = QFileDialog.getSaveFileName(self, "Save Report", savedir, 
     333                                               "Web page (*.html *.htm)") 
    244334        filename = unicode(filename) 
    245          
     335 
    246336        if not filename: 
    247337            return 
    248          
     338 
    249339        path, fname = os.path.split(filename) 
    250         self.saveDir = path 
     340 
     341        self.storeSaveDir(path) 
     342 
    251343        if not os.path.exists(path): 
    252344            try: 
    253345                os.makedirs(path) 
    254346            except: 
    255                 QMessageBox.error(None, "Error", "Cannot create directory "+path) 
     347                QMessageBox.error(None, "Error", 
     348                                  "Cannot create directory " + path) 
    256349 
    257350        tt = file(self.indexfile, "rt").read() 
    258          
     351 
    259352        index = "<br/>".join('<a href="#%s">%s</a>' % (self.tree.item(i).elementId, self.re_h1.search(self.tree.item(i).content).group("name")) 
    260353                             for i in range(self.tree.count())) 
     
    297390            tt = tt.replace(filepref, subdir+"/") 
    298391        file(filename, "wb").write(tt.encode("utf8")) 
    299   
    300  
    301     def saveXML(self): 
    302         filename = QFileDialog.getSaveFileName(self, "Export Report", self.saveDir, "XML file (*.xml)") 
    303         filename = unicode(filename) 
    304         if not filename: 
    305             return 
    306  
    307         outf = file(filename, "wt") 
    308         outf.write('<?xml version="1.0" encoding="ascii"?>\n<report version="1.0">\n') 
    309          
    310         for i in range(self.tree.count()): 
    311             item = self.tree.item(i) 
    312             outf.write('<entry name="%s" time="%s">\n' % (item.name, item.time)) 
    313  
    314             filepref = "file:///"+self.tempdir 
    315             if filepref[-1] != os.sep: 
    316                 filepref += os.sep 
    317             lfilepref = len(filepref) 
    318             imspos = -1 
    319             data = item.data 
    320             while True: 
    321                 imspos = data.find(filepref, imspos+1) 
    322                 if imspos == -1: 
    323                     break 
    324                 imname = data[imspos+lfilepref:data.find('"', imspos)] 
    325                 fname = os.path.join(filepref[8:], imname) 
    326                 outf.write('    <binary name="%s"><![CDATA[%s]]></binary>\n' % (imname, binascii.b2a_base64(file(fname, "rb").read()))) 
    327                 
    328             data = data.replace(filepref, "binary:///") 
    329             outf.write("<content><![CDATA[%s]]></content>\n\n\n" % data) 
    330             outf.write('</entry>\n') 
    331         outf.write('</report>') 
    332          
    333  
    334     def loadXML(self): 
    335         filename = QFileDialog.getOpenFileName(self, "Import Report", self.saveDir, "XML file (*.xml)") 
    336         filename = unicode(filename) 
    337          
    338         if not filename: 
    339             return 
    340  
    341         filepref = "file:///"+self.tempdir 
    342         if 1:#try: 
    343             x = xml.dom.minidom.parse(file(str(filename))) 
    344             x.normalize() 
    345             entries = [] 
    346             files = [] 
    347             for entry in x.getElementsByTagName("entry"): 
    348                 data = entry.getElementsByTagName("content")[0].firstChild.data 
    349                 for fle in entry.getElementsByTagName("binary"): 
    350                     name = oname = fle.getAttribute("name") 
    351                     base, ext = os.path.splitext(name) 
    352                     i = 0 
    353                     while os.path.exists(os.path.join(self.tempdir, name)): 
    354                         i += 1 
    355                         name = "%s%04i%s" % (base, i, ext) 
    356                     data = data.replace("binary:///"+oname, filepref+"/"+name) 
    357                     filedata = binascii.a2b_base64(fle.firstChild.data) 
    358                     files.append((name, filedata))  
    359                 name = entry.getAttribute("name") 
    360                 time = entry.getAttribute("time") 
    361                 entries.append((name, data, None, QIcon(), time)) 
    362                  
    363             for fname, fdata in files: 
    364                 print fname, len(fdata) 
    365                 file(os.path.join(self.tempdir, fname), "wb").write(fdata) 
    366             for entry in entries: 
    367                 print entry[1] 
    368                 self(*entry) 
    369         #except: 
    370             pass 
    371             # !!!!!!!!!!! 
    372          
     392 
     393 
    373394def getDepth(item, expanded=True): 
    374395    ccount = item.childCount() 
  • Orange/OrangeWidgets/OWWidget.py

    r10966 r11291  
    77from OWBaseWidget import * 
    88 
     9 
    910class OWWidget(OWBaseWidget): 
    10     def __init__(self, parent=None, signalManager=None, title="Orange Widget", wantGraph=False, wantStatusBar=False, savePosition=True, wantMainArea=1, noReport=False, showSaveGraph=1, resizingEnabled=1, wantStateInfoWidget=None, **args): 
     11    def __init__(self, parent=None, signalManager=None, title="Orange Widget", 
     12                 wantGraph=False, wantStatusBar=False, savePosition=True, 
     13                 wantMainArea=1, noReport=False, showSaveGraph=1, 
     14                 resizingEnabled=1, wantStateInfoWidget=None, 
     15                 **args): 
    1116        """ 
    1217        Initialization 
     
    5863 
    5964        self.__reportData = None 
    60         if OWReport.get_instance() and not noReport and hasattr(self, "sendReport"): 
     65        if not noReport and hasattr(self, "sendReport"): 
    6166            self.buttonBackground.show() 
    6267            self.reportButton = OWGUI.button(self.buttonBackground, self, "&Report", self.reportAndFinish, debuggingEnabled=0) 
     
    169174        self.sendReport() 
    170175        self.finishReport() 
    171          
    172     def startReport(self, name=None, needDirectory=False): 
     176 
     177    def startReport(self, name=None): 
    173178        if self.__reportData is not None: 
    174179            print "Cannot open a new report when an old report is still active" 
     
    176181        self.reportName = name or self.windowTitle() 
    177182        self.__reportData = "" 
    178         if needDirectory: 
    179             return OWReport.get_instance().createDirectory() 
    180         else: 
    181             return True 
     183        return True 
    182184 
    183185    def reportSection(self, title): 
     
    208210        if self.__reportData is None: 
    209211            self.startReport() 
    210              
     212 
    211213        if type(filenameOrFunc) in [str, unicode]: 
    212214            self.__reportData += '    <IMG src="%s"/>\n' % filenameOrFunc 
     
    283285        elif ifNone is not None: 
    284286            self.reportRaw(ifNone) 
    285          
    286  
    287         
     287 
    288288    def finishReport(self): 
    289289        if self.__reportData is not None: 
    290             OWReport.get_instance()(self.reportName, self.__reportData or "", self.widgetId, self.windowIcon())#, self.getSettings(False)) 
     290            report = OWReport.get_instance() 
     291#            report(self.reportName, self.__reportData or "", 
     292#                   self.widgetId, self.windowIcon()) 
     293            report.appendReport(self.reportName, self.__reportData, 
     294                                sender=self) 
    291295            self.__reportData = None 
    292296 
Note: See TracChangeset for help on using the changeset viewer.