source: sasview/src/sas/qtgui/DataExplorer.py @ a95260d

ESS_GUIESS_GUI_DocsESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_iss879ESS_GUI_iss959ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalc
Last change on this file since a95260d was 0cd8612, checked in by Piotr Rozyczko <piotr.rozyczko@…>, 8 years ago

output console + logging

  • Property mode set to 100755
File size: 22.6 KB
RevLine 
[f721030]1# global
2import sys
3import os
[e540cd2]4import time
[f721030]5import logging
6
7from PyQt4 import QtCore
8from PyQt4 import QtGui
9from PyQt4 import QtWebKit
[481ff26]10from PyQt4.Qt import QMutex
11
[f721030]12from twisted.internet import threads
13
14# SAS
[0cd8612]15import GuiUtils
[1042dba]16from Plotter import Plotter
[f721030]17from sas.sascalc.dataloader.loader import Loader
18from sas.sasgui.guiframe.data_manager import DataManager
[e540cd2]19from sas.sasgui.guiframe.dataFitting import Data1D
20from sas.sasgui.guiframe.dataFitting import Data2D
[f721030]21
[f82ab8c]22from DroppableDataLoadWidget import DroppableDataLoadWidget
[f721030]23
[a281ab8]24# This is how to get data1/2D from the model item
25# data = [selected_items[0].child(0).data().toPyObject()]
26
[f82ab8c]27class DataExplorerWindow(DroppableDataLoadWidget):
[f721030]28    # The controller which is responsible for managing signal slots connections
29    # for the gui and providing an interface to the data model.
30
31    def __init__(self, parent=None, guimanager=None):
[f82ab8c]32        super(DataExplorerWindow, self).__init__(parent, guimanager)
[f721030]33
34        # Main model for keeping loaded data
35        self.model = QtGui.QStandardItemModel(self)
[f82ab8c]36
37        # Secondary model for keeping frozen data sets
38        self.theory_model = QtGui.QStandardItemModel(self)
[f721030]39
40        # GuiManager is the actual parent, but we needed to also pass the QMainWindow
41        # in order to set the widget parentage properly.
42        self.parent = guimanager
43        self.loader = Loader()
44        self.manager = DataManager()
45
[481ff26]46        # Be careful with twisted threads.
47        self.mutex = QMutex()
48
[f721030]49        # Connect the buttons
50        self.cmdLoad.clicked.connect(self.loadFile)
[f82ab8c]51        self.cmdDeleteData.clicked.connect(self.deleteFile)
52        self.cmdDeleteTheory.clicked.connect(self.deleteTheory)
53        self.cmdFreeze.clicked.connect(self.freezeTheory)
[f721030]54        self.cmdSendTo.clicked.connect(self.sendData)
[1042dba]55        self.cmdNew.clicked.connect(self.newPlot)
[481ff26]56        self.cmdHelp.clicked.connect(self.displayHelp)
57        self.cmdHelp_2.clicked.connect(self.displayHelp)
58
59        # Display HTML content
60        self._helpView = QtWebKit.QWebView()
[f721030]61
[e540cd2]62        # Context menu in the treeview
63        #self.treeView.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu)
64        #self.actionDataInfo.triggered.connect(self.contextDataInfo)
65        #self.treeView.addAction(self.actionDataInfo)
66
67        # Custom context menu
68        self.treeView.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
69        self.treeView.customContextMenuRequested.connect(self.onCustomContextMenu)
70
[488c49d]71        # Connect the comboboxes
72        self.cbSelect.currentIndexChanged.connect(self.selectData)
73
[f82ab8c]74        #self.closeEvent.connect(self.closeEvent)
75        # self.aboutToQuit.connect(self.closeEvent)
[e540cd2]76        self.communicator = self.parent.communicator()
[f82ab8c]77        self.communicator.fileReadSignal.connect(self.loadFromURL)
[f721030]78
79        # Proxy model for showing a subset of Data1D/Data2D content
[481ff26]80        self.data_proxy = QtGui.QSortFilterProxyModel(self)
81        self.data_proxy.setSourceModel(self.model)
82
83        # Don't show "empty" rows with data objects
84        self.data_proxy.setFilterRegExp(r"[^()]")
[f721030]85
86        # The Data viewer is QTreeView showing the proxy model
[481ff26]87        self.treeView.setModel(self.data_proxy)
88
89        # Proxy model for showing a subset of Theory content
90        self.theory_proxy = QtGui.QSortFilterProxyModel(self)
91        self.theory_proxy.setSourceModel(self.theory_model)
92
93        # Don't show "empty" rows with data objects
94        self.theory_proxy.setFilterRegExp(r"[^()]")
[f721030]95
[f82ab8c]96        # Theory model view
[481ff26]97        self.freezeView.setModel(self.theory_proxy)
[f82ab8c]98
99    def closeEvent(self, event):
100        """
101        Overwrite the close event - no close!
102        """
103        event.ignore()
[1042dba]104
[481ff26]105    def displayHelp(self):
106        """
107        Show the "Loading data" section of help
108        """
109        _TreeLocation = "html/user/sasgui/guiframe/data_explorer_help.html"
110        self._helpView.load(QtCore.QUrl(_TreeLocation))
111        self._helpView.show()
112
[f82ab8c]113    def loadFromURL(self, url):
114        """
115        Threaded file load
116        """
117        load_thread = threads.deferToThread(self.readData, url)
118        load_thread.addCallback(self.loadComplete)
[5032ea68]119
120    def loadFile(self, event=None):
[f721030]121        """
122        Called when the "Load" button pressed.
[481ff26]123        Opens the Qt "Open File..." dialog
[f721030]124        """
125        path_str = self.chooseFiles()
126        if not path_str:
127            return
[f82ab8c]128        self.loadFromURL(path_str)
[f721030]129
[5032ea68]130    def loadFolder(self, event=None):
[f721030]131        """
[5032ea68]132        Called when the "File/Load Folder" menu item chosen.
[481ff26]133        Opens the Qt "Open Folder..." dialog
[f721030]134        """
[481ff26]135        folder = QtGui.QFileDialog.getExistingDirectory(self, "Choose a directory", "",
[9e426c1]136              QtGui.QFileDialog.ShowDirsOnly | QtGui.QFileDialog.DontUseNativeDialog)
[481ff26]137        if folder is None:
[5032ea68]138            return
139
[481ff26]140        folder = str(folder)
[f721030]141
[481ff26]142        if not os.path.isdir(folder):
[5032ea68]143            return
[f721030]144
[5032ea68]145        # get content of dir into a list
[481ff26]146        path_str = [os.path.join(os.path.abspath(folder), filename)
[e540cd2]147                    for filename in os.listdir(folder)]
[f721030]148
[f82ab8c]149        self.loadFromURL(path_str)
[5032ea68]150
151    def deleteFile(self, event):
152        """
153        Delete selected rows from the model
154        """
155        # Assure this is indeed wanted
156        delete_msg = "This operation will delete the checked data sets and all the dependents." +\
157                     "\nDo you want to continue?"
[e540cd2]158        reply = QtGui.QMessageBox.question(self,
159                                           'Warning',
160                                           delete_msg,
161                                           QtGui.QMessageBox.Yes,
162                                           QtGui.QMessageBox.No)
[5032ea68]163
164        if reply == QtGui.QMessageBox.No:
165            return
166
167        # Figure out which rows are checked
168        ind = -1
169        # Use 'while' so the row count is forced at every iteration
170        while ind < self.model.rowCount():
171            ind += 1
172            item = self.model.item(ind)
173            if item and item.isCheckable() and item.checkState() == QtCore.Qt.Checked:
174                # Delete these rows from the model
175                self.model.removeRow(ind)
176                # Decrement index since we just deleted it
177                ind -= 1
178
179        # pass temporarily kept as a breakpoint anchor
[f721030]180        pass
181
[f82ab8c]182    def deleteTheory(self, event):
183        """
184        Delete selected rows from the theory model
185        """
186        # Assure this is indeed wanted
187        delete_msg = "This operation will delete the checked data sets and all the dependents." +\
188                     "\nDo you want to continue?"
189        reply = QtGui.QMessageBox.question(self, 'Warning', delete_msg,
190                QtGui.QMessageBox.Yes, QtGui.QMessageBox.No)
191
192        if reply == QtGui.QMessageBox.No:
193            return
194
195        # Figure out which rows are checked
196        ind = -1
197        # Use 'while' so the row count is forced at every iteration
198        while ind < self.theory_model.rowCount():
199            ind += 1
200            item = self.theory_model.item(ind)
201            if item and item.isCheckable() and item.checkState() == QtCore.Qt.Checked:
202                # Delete these rows from the model
203                self.theory_model.removeRow(ind)
204                # Decrement index since we just deleted it
205                ind -= 1
206
207        # pass temporarily kept as a breakpoint anchor
208        pass
209
[f721030]210    def sendData(self, event):
211        """
[5032ea68]212        Send selected item data to the current perspective and set the relevant notifiers
[f721030]213        """
[5032ea68]214        # should this reside on GuiManager or here?
215        self._perspective = self.parent.perspective()
[f721030]216
[5032ea68]217        # Set the signal handlers
218        self.communicator.updateModelFromPerspectiveSignal.connect(self.updateModelFromPerspective)
[f721030]219
[5032ea68]220        # Figure out which rows are checked
221        selected_items = []
222        for index in range(self.model.rowCount()):
223            item = self.model.item(index)
224            if item.isCheckable() and item.checkState() == QtCore.Qt.Checked:
225                selected_items.append(item)
[f721030]226
[f82ab8c]227        if len(selected_items) < 1:
228            return
229
[f721030]230        # Which perspective has been selected?
[5032ea68]231        if len(selected_items) > 1 and not self._perspective.allowBatch():
232            msg = self._perspective.title() + " does not allow multiple data."
233            msgbox = QtGui.QMessageBox()
234            msgbox.setIcon(QtGui.QMessageBox.Critical)
235            msgbox.setText(msg)
236            msgbox.setStandardButtons(QtGui.QMessageBox.Ok)
237            retval = msgbox.exec_()
238            return
[a281ab8]239
240        # Dig up the item
241        data = selected_items
[f721030]242
[5032ea68]243        # TODO
[f721030]244        # New plot or appended?
245
246        # Notify the GuiManager about the send request
[a281ab8]247        self._perspective.setData(data_item=data)
[5032ea68]248
[f82ab8c]249    def freezeTheory(self, event):
250        """
251        Freeze selected theory rows.
252
253        "Freezing" means taking the plottable data from the filename item
254        and copying it to a separate top-level item.
255        """
256        # Figure out which _inner_ rows are checked
257        # Use 'while' so the row count is forced at every iteration
258        outer_index = -1
[481ff26]259        theories_copied = 0
[f82ab8c]260        while outer_index < self.model.rowCount():
261            outer_index += 1
262            outer_item = self.model.item(outer_index)
263            if not outer_item:
264                continue
[481ff26]265            for inner_index in xrange(outer_item.rowCount()): # Should be just two rows: data and Info
[f82ab8c]266                subitem = outer_item.child(inner_index)
267                if subitem and subitem.isCheckable() and subitem.checkState() == QtCore.Qt.Checked:
[481ff26]268                    theories_copied += 1
269                    new_item = self.recursivelyCloneItem(subitem)
[e540cd2]270                    # Append a "unique" descriptor to the name
271                    time_bit = str(time.time())[7:-1].replace('.','')
272                    new_name = new_item.text() + '_@' + time_bit
273                    new_item.setText(new_name)
[481ff26]274                    self.theory_model.appendRow(new_item)
275            self.theory_model.reset()
[f82ab8c]276
[481ff26]277        freeze_msg = ""
278        if theories_copied == 0:
279            return
280        elif theories_copied == 1:
[e540cd2]281            freeze_msg = "1 theory copied to the Theory tab as a data set"
[481ff26]282        elif theories_copied > 1:
[e540cd2]283            freeze_msg = "%i theories copied to the Theory tab as data sets" % theories_copied
[481ff26]284        else:
285            freeze_msg = "Unexpected number of theories copied: %i" % theories_copied
286            raise AttributeError, freeze_msg
287        self.communicator.statusBarUpdateSignal.emit(freeze_msg)
288        # Actively switch tabs
289        self.setCurrentIndex(1)
290
291    def recursivelyCloneItem(self, item):
292        """
293        Clone QStandardItem() object
294        """
295        new_item = item.clone()
296        # clone doesn't do deepcopy :(
297        for child_index in xrange(item.rowCount()):
298            child_item = self.recursivelyCloneItem(item.child(child_index))
299            new_item.setChild(child_index, child_item)
300        return new_item
[f82ab8c]301
[1042dba]302    def newPlot(self):
303        """
304        Create a new matplotlib chart from selected data
[9e426c1]305
306        TODO: Add 2D-functionality
[1042dba]307        """
308
[0cd8612]309        plots = GuiUtils.plotsFromCheckedItems(self.model)
[1042dba]310
311        # Call show on requested plots
312        new_plot = Plotter()
313        for plot_set in plots:
314            new_plot.data(plot_set)
315            new_plot.plot()
316
317        new_plot.show()
[f721030]318
319    def chooseFiles(self):
320        """
[5032ea68]321        Shows the Open file dialog and returns the chosen path(s)
[f721030]322        """
323        # List of known extensions
324        wlist = self.getWlist()
325
326        # Location is automatically saved - no need to keep track of the last dir
[5032ea68]327        # But only with Qt built-in dialog (non-platform native)
[9e426c1]328        paths = QtGui.QFileDialog.getOpenFileNames(self, "Choose a file", "",
[5032ea68]329                wlist, None, QtGui.QFileDialog.DontUseNativeDialog)
[f721030]330        if paths is None:
331            return
332
[481ff26]333        if isinstance(paths, QtCore.QStringList):
[9e426c1]334            paths = [str(f) for f in paths]
335
[0cd8612]336        if not isinstance(paths, list):
[f721030]337            paths = [paths]
338
[9e426c1]339        return paths
[f721030]340
341    def readData(self, path):
342        """
[481ff26]343        verbatim copy-paste from
344           sasgui.guiframe.local_perspectives.data_loader.data_loader.py
[f721030]345        slightly modified for clarity
346        """
347        message = ""
348        log_msg = ''
349        output = {}
350        any_error = False
351        data_error = False
352        error_message = ""
[481ff26]353
[e540cd2]354        number_of_files = len(path)
355        self.communicator.progressBarUpdateSignal.emit(0.0)
356
357        for index, p_file in enumerate(path):
[f721030]358            basename = os.path.basename(p_file)
359            _, extension = os.path.splitext(basename)
[0cd8612]360            if extension.lower() in GuiUtils.EXTENSIONS:
[f721030]361                any_error = True
362                log_msg = "Data Loader cannot "
363                log_msg += "load: %s\n" % str(p_file)
364                log_msg += """Please try to open that file from "open project" """
365                log_msg += """or "open analysis" menu\n"""
366                error_message = log_msg + "\n"
367                logging.info(log_msg)
368                continue
369
370            try:
[5032ea68]371                message = "Loading Data... " + str(basename) + "\n"
[f721030]372
373                # change this to signal notification in GuiManager
[f82ab8c]374                self.communicator.statusBarUpdateSignal.emit(message)
[f721030]375
376                output_objects = self.loader.load(p_file)
377
378                # Some loaders return a list and some just a single Data1D object.
379                # Standardize.
380                if not isinstance(output_objects, list):
381                    output_objects = [output_objects]
382
383                for item in output_objects:
[481ff26]384                    # cast sascalc.dataloader.data_info.Data1D into
385                    # sasgui.guiframe.dataFitting.Data1D
[f721030]386                    # TODO : Fix it
387                    new_data = self.manager.create_gui_data(item, p_file)
388                    output[new_data.id] = new_data
[481ff26]389
390                    # Model update should be protected
391                    self.mutex.lock()
[f721030]392                    self.updateModel(new_data, p_file)
[5032ea68]393                    self.model.reset()
394                    QtGui.qApp.processEvents()
[481ff26]395                    self.mutex.unlock()
[f721030]396
397                    if hasattr(item, 'errors'):
398                        for error_data in item.errors:
399                            data_error = True
400                            message += "\tError: {0}\n".format(error_data)
401                    else:
[5032ea68]402
[f721030]403                        logging.error("Loader returned an invalid object:\n %s" % str(item))
404                        data_error = True
405
[5032ea68]406            except Exception as ex:
[f721030]407                logging.error(sys.exc_value)
[5032ea68]408
[f721030]409                any_error = True
410            if any_error or error_message != "":
411                if error_message == "":
412                    error = "Error: " + str(sys.exc_info()[1]) + "\n"
413                    error += "while loading Data: \n%s\n" % str(basename)
414                    error_message += "The data file you selected could not be loaded.\n"
415                    error_message += "Make sure the content of your file"
416                    error_message += " is properly formatted.\n\n"
417                    error_message += "When contacting the SasView team, mention the"
418                    error_message += " following:\n%s" % str(error)
419                elif data_error:
420                    base_message = "Errors occurred while loading "
421                    base_message += "{0}\n".format(basename)
422                    base_message += "The data file loaded but with errors.\n"
423                    error_message = base_message + error_message
424                else:
425                    error_message += "%s\n" % str(p_file)
426                info = "error"
[481ff26]427
[e540cd2]428            current_percentage = int(100.0* index/number_of_files)
429            self.communicator.progressBarUpdateSignal.emit(current_percentage)
430
[f721030]431        if any_error or error_message:
[0cd8612]432            logging.error(error_message)
433            status_bar_message = "Errors occurred while loading %s" % format(basename)
434            self.communicator.statusBarUpdateSignal.emit(status_bar_message)
[f721030]435
436        else:
437            message = "Loading Data Complete! "
438        message += log_msg
[0cd8612]439        # Notify the progress bar that the updates are over.
[e540cd2]440        self.communicator.progressBarUpdateSignal.emit(-1)
[481ff26]441
[a281ab8]442        return output, message
[f721030]443
444    def getWlist(self):
445        """
[f82ab8c]446        Wildcards of files we know the format of.
[f721030]447        """
448        # Display the Qt Load File module
449        cards = self.loader.get_wildcards()
450
451        # get rid of the wx remnant in wildcards
452        # TODO: modify sasview loader get_wildcards method, after merge,
453        # so this kludge can be avoided
454        new_cards = []
455        for item in cards:
456            new_cards.append(item[:item.find("|")])
457        wlist = ';;'.join(new_cards)
458
459        return wlist
[488c49d]460
461    def selectData(self, index):
462        """
463        Callback method for modifying the TreeView on Selection Options change
464        """
465        if not isinstance(index, int):
466            msg = "Incorrect type passed to DataExplorer.selectData()"
467            raise AttributeError, msg
468
469        # Respond appropriately
470        if index == 0:
471            # Select All
[5032ea68]472            for index in range(self.model.rowCount()):
473                item = self.model.item(index)
474                if item.isCheckable() and item.checkState() == QtCore.Qt.Unchecked:
[488c49d]475                    item.setCheckState(QtCore.Qt.Checked)
476        elif index == 1:
477            # De-select All
[5032ea68]478            for index in range(self.model.rowCount()):
479                item = self.model.item(index)
480                if item.isCheckable() and item.checkState() == QtCore.Qt.Checked:
[488c49d]481                    item.setCheckState(QtCore.Qt.Unchecked)
482
483        elif index == 2:
484            # Select All 1-D
[5032ea68]485            for index in range(self.model.rowCount()):
486                item = self.model.item(index)
[488c49d]487                item.setCheckState(QtCore.Qt.Unchecked)
[5032ea68]488
489                try:
[0cd8612]490                    is1D = isinstance(item.child(0).data().toPyObject(), Data1D)
[5032ea68]491                except AttributeError:
492                    msg = "Bad structure of the data model."
493                    raise RuntimeError, msg
494
495                if is1D:
[488c49d]496                    item.setCheckState(QtCore.Qt.Checked)
497
498        elif index == 3:
499            # Unselect All 1-D
[5032ea68]500            for index in range(self.model.rowCount()):
501                item = self.model.item(index)
502
503                try:
[0cd8612]504                    is1D = isinstance(item.child(0).data().toPyObject(), Data1D)
[5032ea68]505                except AttributeError:
506                    msg = "Bad structure of the data model."
507                    raise RuntimeError, msg
508
509                if item.isCheckable() and item.checkState() == QtCore.Qt.Checked and is1D:
[488c49d]510                    item.setCheckState(QtCore.Qt.Unchecked)
511
512        elif index == 4:
513            # Select All 2-D
[5032ea68]514            for index in range(self.model.rowCount()):
515                item = self.model.item(index)
516                item.setCheckState(QtCore.Qt.Unchecked)
517                try:
[0cd8612]518                    is2D = isinstance(item.child(0).data().toPyObject(), Data2D)
[5032ea68]519                except AttributeError:
520                    msg = "Bad structure of the data model."
521                    raise RuntimeError, msg
522
523                if is2D:
[488c49d]524                    item.setCheckState(QtCore.Qt.Checked)
525
526        elif index == 5:
527            # Unselect All 2-D
[5032ea68]528            for index in range(self.model.rowCount()):
529                item = self.model.item(index)
530
531                try:
[0cd8612]532                    is2D = isinstance(item.child(0).data().toPyObject(), Data2D)
[5032ea68]533                except AttributeError:
534                    msg = "Bad structure of the data model."
535                    raise RuntimeError, msg
536
537                if item.isCheckable() and item.checkState() == QtCore.Qt.Checked and is2D:
[488c49d]538                    item.setCheckState(QtCore.Qt.Unchecked)
539
540        else:
541            msg = "Incorrect value in the Selection Option"
542            # Change this to a proper logging action
543            raise Exception, msg
544
[e540cd2]545    def contextDataInfo(self):
546        """
547        """
548        print("contextDataInfo TRIGGERED")
549        pass
550
551    def onCustomContextMenu(self, position):
552        """
553        """
554        print "onCustomContextMenu triggered at point ", position.x(), position.y()
555        index = self.treeView.indexAt(position)
556        if index.isValid():
557            print "VALID CONTEXT MENU"
[0cd8612]558    #    self.context_menu.exec(self.treeView.mapToGlobal(position))
[e540cd2]559        pass
[f721030]560
[a281ab8]561    def loadComplete(self, output):
[f721030]562        """
563        Post message to status bar and update the data manager
564        """
[e540cd2]565        assert type(output) == tuple
566
[9e426c1]567        # Reset the model so the view gets updated.
[5032ea68]568        self.model.reset()
[e540cd2]569        self.communicator.progressBarUpdateSignal.emit(-1)
[a281ab8]570
571        output_data = output[0]
572        message = output[1]
[f721030]573        # Notify the manager of the new data available
[f82ab8c]574        self.communicator.statusBarUpdateSignal.emit(message)
575        self.communicator.fileDataReceivedSignal.emit(output_data)
[a281ab8]576        self.manager.add_data(data_list=output_data)
[f721030]577
578    def updateModel(self, data, p_file):
579        """
[481ff26]580        Add data and Info fields to the model item
[f721030]581        """
582        # Structure of the model
583        # checkbox + basename
[481ff26]584        #     |-------> Data.D object
[f721030]585        #     |-------> Info
586        #                 |----> Title:
587        #                 |----> Run:
588        #                 |----> Type:
589        #                 |----> Path:
590        #                 |----> Process
591        #                          |-----> process[0].name
592        #
593
594        # Top-level item: checkbox with label
595        checkbox_item = QtGui.QStandardItem(True)
596        checkbox_item.setCheckable(True)
597        checkbox_item.setCheckState(QtCore.Qt.Checked)
598        checkbox_item.setText(os.path.basename(p_file))
599
600        # Add the actual Data1D/Data2D object
601        object_item = QtGui.QStandardItem()
602        object_item.setData(QtCore.QVariant(data))
603
[488c49d]604        checkbox_item.setChild(0, object_item)
605
[f721030]606        # Add rows for display in the view
[0cd8612]607        info_item = GuiUtils.infoFromData(data)
[f721030]608
609        # Set info_item as the only child
[488c49d]610        checkbox_item.setChild(1, info_item)
[f721030]611
612        # New row in the model
613        self.model.appendRow(checkbox_item)
[481ff26]614
[5032ea68]615    def updateModelFromPerspective(self, model_item):
616        """
[a281ab8]617        Receive an update model item from a perspective
618        Make sure it is valid and if so, replace it in the model
[5032ea68]619        """
[a281ab8]620        # Assert the correct type
[0cd8612]621        if not isinstance(model_item, QtGui.QStandardItem):
[5032ea68]622            msg = "Wrong data type returned from calculations."
623            raise AttributeError, msg
[a281ab8]624
[1042dba]625        # TODO: Assert other properties
[a281ab8]626
[5032ea68]627        # Reset the view
628        self.model.reset()
[1042dba]629
[5032ea68]630        # Pass acting as a debugger anchor
631        pass
[481ff26]632
[f721030]633
634if __name__ == "__main__":
635    app = QtGui.QApplication([])
636    dlg = DataExplorerWindow()
637    dlg.show()
[481ff26]638    sys.exit(app.exec_())
Note: See TracBrowser for help on using the repository browser.