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

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 5236449 was 5236449, checked in by Piotr Rozyczko <rozyczko@…>, 7 years ago

Default datasets for fitting SASVIEW-498

  • Property mode set to 100644
File size: 33.8 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
15from sas.sascalc.dataloader.loader import Loader
16from sas.sasgui.guiframe.data_manager import DataManager
[e540cd2]17from sas.sasgui.guiframe.dataFitting import Data1D
18from sas.sasgui.guiframe.dataFitting import Data2D
[f721030]19
[31c5b58]20import sas.qtgui.GuiUtils as GuiUtils
21import sas.qtgui.PlotHelper as PlotHelper
22from sas.qtgui.Plotter import Plotter
23from sas.qtgui.Plotter2D import Plotter2D
24from sas.qtgui.DroppableDataLoadWidget import DroppableDataLoadWidget
[416fa8f]25from sas.qtgui.MaskEditor import MaskEditor
[f721030]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
[630155bd]31    def __init__(self, parent=None, guimanager=None, manager=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()
[630155bd]44        self.manager = manager if manager is not None else DataManager()
[4b71e91]45        self.txt_widget = QtGui.QTextEdit(None)
[f721030]46
[481ff26]47        # Be careful with twisted threads.
48        self.mutex = QMutex()
49
[8cb6cd6]50        # Active plots
51        self.active_plots = []
52
[f721030]53        # Connect the buttons
54        self.cmdLoad.clicked.connect(self.loadFile)
[f82ab8c]55        self.cmdDeleteData.clicked.connect(self.deleteFile)
56        self.cmdDeleteTheory.clicked.connect(self.deleteTheory)
57        self.cmdFreeze.clicked.connect(self.freezeTheory)
[f721030]58        self.cmdSendTo.clicked.connect(self.sendData)
[1042dba]59        self.cmdNew.clicked.connect(self.newPlot)
[8cb6cd6]60        self.cmdAppend.clicked.connect(self.appendPlot)
[481ff26]61        self.cmdHelp.clicked.connect(self.displayHelp)
62        self.cmdHelp_2.clicked.connect(self.displayHelp)
63
64        # Display HTML content
65        self._helpView = QtWebKit.QWebView()
[f721030]66
[83d6249]67        # Fill in the perspectives combo
68        self.initPerspectives()
69
[e540cd2]70        # Custom context menu
71        self.treeView.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
72        self.treeView.customContextMenuRequested.connect(self.onCustomContextMenu)
[4b71e91]73        self.contextMenu()
[e540cd2]74
[488c49d]75        # Connect the comboboxes
76        self.cbSelect.currentIndexChanged.connect(self.selectData)
77
[f82ab8c]78        #self.closeEvent.connect(self.closeEvent)
79        # self.aboutToQuit.connect(self.closeEvent)
[e540cd2]80        self.communicator = self.parent.communicator()
[f82ab8c]81        self.communicator.fileReadSignal.connect(self.loadFromURL)
[8cb6cd6]82        self.communicator.activeGraphsSignal.connect(self.updateGraphCombo)
[27313b7]83        self.communicator.activeGraphName.connect(self.updatePlotName)
[5236449]84        #self.communicator.updateTheoryFromPerspectiveSignal.connect(self.updateTheoryFromPerspective)
[8cb6cd6]85        self.cbgraph.editTextChanged.connect(self.enableGraphCombo)
86        self.cbgraph.currentIndexChanged.connect(self.enableGraphCombo)
[f721030]87
[5236449]88        self._perspective = self.parent.perspective()
89        self._perspective.updateTheoryFromPerspectiveSignal.connect(self.updateTheoryFromPerspective)
90
[f721030]91        # Proxy model for showing a subset of Data1D/Data2D content
[481ff26]92        self.data_proxy = QtGui.QSortFilterProxyModel(self)
93        self.data_proxy.setSourceModel(self.model)
94
95        # Don't show "empty" rows with data objects
96        self.data_proxy.setFilterRegExp(r"[^()]")
[f721030]97
98        # The Data viewer is QTreeView showing the proxy model
[481ff26]99        self.treeView.setModel(self.data_proxy)
100
101        # Proxy model for showing a subset of Theory content
102        self.theory_proxy = QtGui.QSortFilterProxyModel(self)
103        self.theory_proxy.setSourceModel(self.theory_model)
104
105        # Don't show "empty" rows with data objects
106        self.theory_proxy.setFilterRegExp(r"[^()]")
[f721030]107
[f82ab8c]108        # Theory model view
[481ff26]109        self.freezeView.setModel(self.theory_proxy)
[f82ab8c]110
[8cb6cd6]111        self.enableGraphCombo(None)
112
[f82ab8c]113    def closeEvent(self, event):
114        """
115        Overwrite the close event - no close!
116        """
117        event.ignore()
[1042dba]118
[481ff26]119    def displayHelp(self):
120        """
121        Show the "Loading data" section of help
122        """
[31c5b58]123        tree_location = self.parent.HELP_DIRECTORY_LOCATION +\
124            "/user/sasgui/guiframe/data_explorer_help.html"
125        self._helpView.load(QtCore.QUrl(tree_location))
[481ff26]126        self._helpView.show()
127
[8cb6cd6]128    def enableGraphCombo(self, combo_text):
129        """
130        Enables/disables "Assign Plot" elements
131        """
132        self.cbgraph.setEnabled(len(PlotHelper.currentPlots()) > 0)
133        self.cmdAppend.setEnabled(len(PlotHelper.currentPlots()) > 0)
134
[83d6249]135    def initPerspectives(self):
136        """
137        Populate the Perspective combobox and define callbacks
138        """
139        self.cbFitting.currentIndexChanged.connect(self.updatePerspectiveCombo)
140        # Set the index so we see the default (Fitting)
141        self.updatePerspectiveCombo(0)
142
[f82ab8c]143    def loadFromURL(self, url):
144        """
145        Threaded file load
146        """
147        load_thread = threads.deferToThread(self.readData, url)
148        load_thread.addCallback(self.loadComplete)
[5032ea68]149
150    def loadFile(self, event=None):
[f721030]151        """
152        Called when the "Load" button pressed.
[481ff26]153        Opens the Qt "Open File..." dialog
[f721030]154        """
155        path_str = self.chooseFiles()
156        if not path_str:
157            return
[f82ab8c]158        self.loadFromURL(path_str)
[f721030]159
[5032ea68]160    def loadFolder(self, event=None):
[f721030]161        """
[5032ea68]162        Called when the "File/Load Folder" menu item chosen.
[481ff26]163        Opens the Qt "Open Folder..." dialog
[f721030]164        """
[481ff26]165        folder = QtGui.QFileDialog.getExistingDirectory(self, "Choose a directory", "",
[9e426c1]166              QtGui.QFileDialog.ShowDirsOnly | QtGui.QFileDialog.DontUseNativeDialog)
[481ff26]167        if folder is None:
[5032ea68]168            return
169
[481ff26]170        folder = str(folder)
[f721030]171
[481ff26]172        if not os.path.isdir(folder):
[5032ea68]173            return
[f721030]174
[5032ea68]175        # get content of dir into a list
[481ff26]176        path_str = [os.path.join(os.path.abspath(folder), filename)
[e540cd2]177                    for filename in os.listdir(folder)]
[f721030]178
[f82ab8c]179        self.loadFromURL(path_str)
[5032ea68]180
[630155bd]181    def loadProject(self):
182        """
183        Called when the "Open Project" menu item chosen.
184        """
185        kwargs = {
186            'parent'    : self,
187            'caption'   : 'Open Project',
188            'filter'    : 'Project (*.json);;All files (*.*)',
189            'options'   : QtGui.QFileDialog.DontUseNativeDialog
190        }
191        filename = str(QtGui.QFileDialog.getOpenFileName(**kwargs))
192        if filename:
193            load_thread = threads.deferToThread(self.readProject, filename)
194            load_thread.addCallback(self.readProjectComplete)
195
196    def readProject(self, filename):
197        self.communicator.statusBarUpdateSignal.emit("Loading Project... %s" % os.path.basename(filename))
198        try:
199            manager = DataManager()
200            with open(filename, 'r') as infile:
201                manager.load_from_readable(infile)
202
203            self.communicator.statusBarUpdateSignal.emit("Loaded Project: %s" % os.path.basename(filename))
204            return manager
205
206        except:
207            self.communicator.statusBarUpdateSignal.emit("Failed: %s" % os.path.basename(filename))
208            raise
209
210    def readProjectComplete(self, manager):
211        self.model.clear()
212
213        self.manager.assign(manager)
214        for id, item in self.manager.get_all_data().iteritems():
215            self.updateModel(item.data, item.path)
216
217        self.model.reset()
218
219    def saveProject(self):
220        """
221        Called when the "Save Project" menu item chosen.
222        """
223        kwargs = {
224            'parent'    : self,
225            'caption'   : 'Save Project',
226            'filter'    : 'Project (*.json)',
227            'options'   : QtGui.QFileDialog.DontUseNativeDialog
228        }
229        filename = str(QtGui.QFileDialog.getSaveFileName(**kwargs))
230        if filename:
231            self.communicator.statusBarUpdateSignal.emit("Saving Project... %s\n" % os.path.basename(filename))
232            with open(filename, 'w') as outfile:
233                self.manager.save_to_writable(outfile)
234
[5032ea68]235    def deleteFile(self, event):
236        """
237        Delete selected rows from the model
238        """
239        # Assure this is indeed wanted
240        delete_msg = "This operation will delete the checked data sets and all the dependents." +\
241                     "\nDo you want to continue?"
[e540cd2]242        reply = QtGui.QMessageBox.question(self,
243                                           'Warning',
244                                           delete_msg,
245                                           QtGui.QMessageBox.Yes,
246                                           QtGui.QMessageBox.No)
[5032ea68]247
248        if reply == QtGui.QMessageBox.No:
249            return
250
251        # Figure out which rows are checked
252        ind = -1
253        # Use 'while' so the row count is forced at every iteration
254        while ind < self.model.rowCount():
255            ind += 1
256            item = self.model.item(ind)
257            if item and item.isCheckable() and item.checkState() == QtCore.Qt.Checked:
258                # Delete these rows from the model
259                self.model.removeRow(ind)
260                # Decrement index since we just deleted it
261                ind -= 1
262
263        # pass temporarily kept as a breakpoint anchor
[f721030]264        pass
265
[f82ab8c]266    def deleteTheory(self, event):
267        """
268        Delete selected rows from the theory model
269        """
270        # Assure this is indeed wanted
271        delete_msg = "This operation will delete the checked data sets and all the dependents." +\
272                     "\nDo you want to continue?"
[8cb6cd6]273        reply = QtGui.QMessageBox.question(self,
274                                           'Warning',
275                                           delete_msg,
276                                           QtGui.QMessageBox.Yes,
277                                           QtGui.QMessageBox.No)
[f82ab8c]278
279        if reply == QtGui.QMessageBox.No:
280            return
281
282        # Figure out which rows are checked
283        ind = -1
284        # Use 'while' so the row count is forced at every iteration
285        while ind < self.theory_model.rowCount():
286            ind += 1
287            item = self.theory_model.item(ind)
288            if item and item.isCheckable() and item.checkState() == QtCore.Qt.Checked:
289                # Delete these rows from the model
290                self.theory_model.removeRow(ind)
291                # Decrement index since we just deleted it
292                ind -= 1
293
294        # pass temporarily kept as a breakpoint anchor
295        pass
296
[f721030]297    def sendData(self, event):
298        """
[5032ea68]299        Send selected item data to the current perspective and set the relevant notifiers
[f721030]300        """
[5032ea68]301        # should this reside on GuiManager or here?
302        self._perspective = self.parent.perspective()
[f721030]303
[5032ea68]304        # Set the signal handlers
305        self.communicator.updateModelFromPerspectiveSignal.connect(self.updateModelFromPerspective)
[f721030]306
[adf81b8]307        def isItemReady(index):
[5032ea68]308            item = self.model.item(index)
[adf81b8]309            return item.isCheckable() and item.checkState() == QtCore.Qt.Checked
310
311        # Figure out which rows are checked
312        selected_items = [self.model.item(index)
313                          for index in xrange(self.model.rowCount())
314                          if isItemReady(index)]
[f721030]315
[f82ab8c]316        if len(selected_items) < 1:
317            return
318
[f721030]319        # Which perspective has been selected?
[5032ea68]320        if len(selected_items) > 1 and not self._perspective.allowBatch():
321            msg = self._perspective.title() + " does not allow multiple data."
322            msgbox = QtGui.QMessageBox()
323            msgbox.setIcon(QtGui.QMessageBox.Critical)
324            msgbox.setText(msg)
325            msgbox.setStandardButtons(QtGui.QMessageBox.Ok)
326            retval = msgbox.exec_()
327            return
[a281ab8]328
[f721030]329        # Notify the GuiManager about the send request
[469b4622]330        self._perspective.setData(data_item=selected_items)
[5032ea68]331
[f82ab8c]332    def freezeTheory(self, event):
333        """
334        Freeze selected theory rows.
335
[ca8b853]336        "Freezing" means taking the plottable data from the Theory item
337        and copying it to a separate top-level item in Data.
[f82ab8c]338        """
[ca8b853]339        # Figure out which rows are checked
[f82ab8c]340        # Use 'while' so the row count is forced at every iteration
341        outer_index = -1
[481ff26]342        theories_copied = 0
[ca8b853]343        while outer_index < self.theory_model.rowCount():
[f82ab8c]344            outer_index += 1
[ca8b853]345            outer_item = self.theory_model.item(outer_index)
[f82ab8c]346            if not outer_item:
347                continue
[ca8b853]348            if outer_item.isCheckable() and \
349                   outer_item.checkState() == QtCore.Qt.Checked:
350                theories_copied += 1
351                new_item = self.recursivelyCloneItem(outer_item)
352                # Append a "unique" descriptor to the name
353                time_bit = str(time.time())[7:-1].replace('.', '')
354                new_name = new_item.text() + '_@' + time_bit
355                new_item.setText(new_name)
356                self.model.appendRow(new_item)
357            self.model.reset()
[f82ab8c]358
[481ff26]359        freeze_msg = ""
360        if theories_copied == 0:
361            return
362        elif theories_copied == 1:
[ca8b853]363            freeze_msg = "1 theory copied from the Theory tab as a data set"
[481ff26]364        elif theories_copied > 1:
[ca8b853]365            freeze_msg = "%i theories copied from the Theory tab as data sets" % theories_copied
[481ff26]366        else:
367            freeze_msg = "Unexpected number of theories copied: %i" % theories_copied
368            raise AttributeError, freeze_msg
369        self.communicator.statusBarUpdateSignal.emit(freeze_msg)
370        # Actively switch tabs
371        self.setCurrentIndex(1)
372
373    def recursivelyCloneItem(self, item):
374        """
375        Clone QStandardItem() object
376        """
377        new_item = item.clone()
378        # clone doesn't do deepcopy :(
379        for child_index in xrange(item.rowCount()):
380            child_item = self.recursivelyCloneItem(item.child(child_index))
381            new_item.setChild(child_index, child_item)
382        return new_item
[f82ab8c]383
[27313b7]384    def updatePlotName(self, name_tuple):
385        """
386        Modify the name of the current plot
387        """
388        old_name, current_name = name_tuple
389        ind = self.cbgraph.findText(old_name)
390        self.cbgraph.setCurrentIndex(ind)
391        self.cbgraph.setItemText(ind, current_name)
392
[8cb6cd6]393    def updateGraphCombo(self, graph_list):
394        """
395        Modify Graph combo box on graph add/delete
396        """
397        orig_text = self.cbgraph.currentText()
398        self.cbgraph.clear()
[adf81b8]399        graph_titles= ["Graph"+str(graph) for graph in graph_list]
400
[8cb6cd6]401        self.cbgraph.insertItems(0, graph_titles)
402        ind = self.cbgraph.findText(orig_text)
403        if ind > 0:
404            self.cbgraph.setCurrentIndex(ind)
405
[83d6249]406    def updatePerspectiveCombo(self, index):
407        """
408        Notify the gui manager about the new perspective chosen.
409        """
410        self.communicator.perspectiveChangedSignal.emit(self.cbFitting.currentText())
411
[1042dba]412    def newPlot(self):
413        """
414        Create a new matplotlib chart from selected data
415        """
[0cd8612]416        plots = GuiUtils.plotsFromCheckedItems(self.model)
[1042dba]417
418        # Call show on requested plots
[31c5b58]419        # All same-type charts in one plot
420        new_plot = Plotter(self)
[adf81b8]421
[3bdbfcc]422        def addDataPlot2D(plot_set, item):
[adf81b8]423            plot2D = Plotter2D(self)
[3bdbfcc]424            plot2D.item = item
[9290b1a]425            plot2D.plot(plot_set)
[adf81b8]426            self.plotAdd(plot2D)
427
[3bdbfcc]428        for item, plot_set in plots:
[49e124c]429            if isinstance(plot_set, Data1D):
[9290b1a]430                new_plot.plot(plot_set)
[49e124c]431            elif isinstance(plot_set, Data2D):
[3bdbfcc]432                addDataPlot2D(plot_set, item)
[49e124c]433            else:
434                msg = "Incorrect data type passed to Plotting"
435                raise AttributeError, msg
436
[31c5b58]437        if plots and \
[b4b8589]438            hasattr(new_plot, 'data') and \
439            isinstance(new_plot.data, Data1D):
440                self.plotAdd(new_plot)
[1042dba]441
[31c5b58]442    def plotAdd(self, new_plot):
443        """
444        Helper method for plot bookkeeping
445        """
446        # Update the global plot counter
447        title = "Graph"+str(PlotHelper.idOfPlot(new_plot))
448        new_plot.setWindowTitle(title)
[8cb6cd6]449
[31c5b58]450        # Add the plot to the workspace
451        self.parent.workspace().addWindow(new_plot)
[8cb6cd6]452
[31c5b58]453        # Show the plot
454        new_plot.show()
[f721030]455
[31c5b58]456        # Update the active chart list
457        self.active_plots.append(title)
[8cb6cd6]458
459    def appendPlot(self):
460        """
461        Add data set(s) to the existing matplotlib chart
462        """
463        # new plot data
464        new_plots = GuiUtils.plotsFromCheckedItems(self.model)
465
466        # old plot data
[27313b7]467        plot_id = self.cbgraph.currentIndex() + 1
[8cb6cd6]468
469        assert plot_id in PlotHelper.currentPlots(), "No such plot: Graph%s"%str(plot_id)
470
471        old_plot = PlotHelper.plotById(plot_id)
472
[14d9c7b]473        # Add new data to the old plot, if data type is the same.
[3bdbfcc]474        for _, plot_set in new_plots:
[14d9c7b]475            if type(plot_set) is type(old_plot._data):
[31c5b58]476                old_plot.data = plot_set
[14d9c7b]477                old_plot.plot()
[8cb6cd6]478
[f721030]479    def chooseFiles(self):
480        """
[5032ea68]481        Shows the Open file dialog and returns the chosen path(s)
[f721030]482        """
483        # List of known extensions
484        wlist = self.getWlist()
485
486        # Location is automatically saved - no need to keep track of the last dir
[5032ea68]487        # But only with Qt built-in dialog (non-platform native)
[9e426c1]488        paths = QtGui.QFileDialog.getOpenFileNames(self, "Choose a file", "",
[5032ea68]489                wlist, None, QtGui.QFileDialog.DontUseNativeDialog)
[f721030]490        if paths is None:
491            return
492
[481ff26]493        if isinstance(paths, QtCore.QStringList):
[9e426c1]494            paths = [str(f) for f in paths]
495
[0cd8612]496        if not isinstance(paths, list):
[f721030]497            paths = [paths]
498
[9e426c1]499        return paths
[f721030]500
501    def readData(self, path):
502        """
[481ff26]503        verbatim copy-paste from
504           sasgui.guiframe.local_perspectives.data_loader.data_loader.py
[f721030]505        slightly modified for clarity
506        """
507        message = ""
508        log_msg = ''
509        output = {}
510        any_error = False
511        data_error = False
512        error_message = ""
[e540cd2]513        number_of_files = len(path)
514        self.communicator.progressBarUpdateSignal.emit(0.0)
515
516        for index, p_file in enumerate(path):
[f721030]517            basename = os.path.basename(p_file)
518            _, extension = os.path.splitext(basename)
[0cd8612]519            if extension.lower() in GuiUtils.EXTENSIONS:
[f721030]520                any_error = True
521                log_msg = "Data Loader cannot "
522                log_msg += "load: %s\n" % str(p_file)
523                log_msg += """Please try to open that file from "open project" """
524                log_msg += """or "open analysis" menu\n"""
525                error_message = log_msg + "\n"
526                logging.info(log_msg)
527                continue
528
529            try:
[5032ea68]530                message = "Loading Data... " + str(basename) + "\n"
[f721030]531
532                # change this to signal notification in GuiManager
[f82ab8c]533                self.communicator.statusBarUpdateSignal.emit(message)
[f721030]534
535                output_objects = self.loader.load(p_file)
536
537                # Some loaders return a list and some just a single Data1D object.
538                # Standardize.
539                if not isinstance(output_objects, list):
540                    output_objects = [output_objects]
541
542                for item in output_objects:
[481ff26]543                    # cast sascalc.dataloader.data_info.Data1D into
544                    # sasgui.guiframe.dataFitting.Data1D
[f721030]545                    # TODO : Fix it
546                    new_data = self.manager.create_gui_data(item, p_file)
547                    output[new_data.id] = new_data
[481ff26]548
549                    # Model update should be protected
550                    self.mutex.lock()
[f721030]551                    self.updateModel(new_data, p_file)
[5032ea68]552                    self.model.reset()
553                    QtGui.qApp.processEvents()
[481ff26]554                    self.mutex.unlock()
[f721030]555
556                    if hasattr(item, 'errors'):
557                        for error_data in item.errors:
558                            data_error = True
559                            message += "\tError: {0}\n".format(error_data)
560                    else:
[5032ea68]561
[f721030]562                        logging.error("Loader returned an invalid object:\n %s" % str(item))
563                        data_error = True
564
[5032ea68]565            except Exception as ex:
[f721030]566                logging.error(sys.exc_value)
[5032ea68]567
[f721030]568                any_error = True
569            if any_error or error_message != "":
570                if error_message == "":
571                    error = "Error: " + str(sys.exc_info()[1]) + "\n"
572                    error += "while loading Data: \n%s\n" % str(basename)
573                    error_message += "The data file you selected could not be loaded.\n"
574                    error_message += "Make sure the content of your file"
575                    error_message += " is properly formatted.\n\n"
576                    error_message += "When contacting the SasView team, mention the"
577                    error_message += " following:\n%s" % str(error)
578                elif data_error:
579                    base_message = "Errors occurred while loading "
580                    base_message += "{0}\n".format(basename)
581                    base_message += "The data file loaded but with errors.\n"
582                    error_message = base_message + error_message
583                else:
584                    error_message += "%s\n" % str(p_file)
[481ff26]585
[e540cd2]586            current_percentage = int(100.0* index/number_of_files)
587            self.communicator.progressBarUpdateSignal.emit(current_percentage)
588
[f721030]589        if any_error or error_message:
[0cd8612]590            logging.error(error_message)
591            status_bar_message = "Errors occurred while loading %s" % format(basename)
592            self.communicator.statusBarUpdateSignal.emit(status_bar_message)
[f721030]593
594        else:
595            message = "Loading Data Complete! "
596        message += log_msg
[0cd8612]597        # Notify the progress bar that the updates are over.
[e540cd2]598        self.communicator.progressBarUpdateSignal.emit(-1)
[481ff26]599
[a281ab8]600        return output, message
[f721030]601
602    def getWlist(self):
603        """
[f82ab8c]604        Wildcards of files we know the format of.
[f721030]605        """
606        # Display the Qt Load File module
607        cards = self.loader.get_wildcards()
608
609        # get rid of the wx remnant in wildcards
610        # TODO: modify sasview loader get_wildcards method, after merge,
611        # so this kludge can be avoided
612        new_cards = []
613        for item in cards:
614            new_cards.append(item[:item.find("|")])
615        wlist = ';;'.join(new_cards)
616
617        return wlist
[488c49d]618
619    def selectData(self, index):
620        """
621        Callback method for modifying the TreeView on Selection Options change
622        """
623        if not isinstance(index, int):
624            msg = "Incorrect type passed to DataExplorer.selectData()"
625            raise AttributeError, msg
626
627        # Respond appropriately
628        if index == 0:
629            # Select All
[5032ea68]630            for index in range(self.model.rowCount()):
631                item = self.model.item(index)
632                if item.isCheckable() and item.checkState() == QtCore.Qt.Unchecked:
[488c49d]633                    item.setCheckState(QtCore.Qt.Checked)
634        elif index == 1:
635            # De-select All
[5032ea68]636            for index in range(self.model.rowCount()):
637                item = self.model.item(index)
638                if item.isCheckable() and item.checkState() == QtCore.Qt.Checked:
[488c49d]639                    item.setCheckState(QtCore.Qt.Unchecked)
640
641        elif index == 2:
642            # Select All 1-D
[5032ea68]643            for index in range(self.model.rowCount()):
644                item = self.model.item(index)
[488c49d]645                item.setCheckState(QtCore.Qt.Unchecked)
[5032ea68]646
647                try:
[8548d739]648                    is1D = isinstance(GuiUtils.dataFromItem(item), Data1D)
[5032ea68]649                except AttributeError:
650                    msg = "Bad structure of the data model."
651                    raise RuntimeError, msg
652
653                if is1D:
[488c49d]654                    item.setCheckState(QtCore.Qt.Checked)
655
656        elif index == 3:
657            # Unselect All 1-D
[5032ea68]658            for index in range(self.model.rowCount()):
659                item = self.model.item(index)
660
661                try:
[8548d739]662                    is1D = isinstance(GuiUtils.dataFromItem(item), Data1D)
[5032ea68]663                except AttributeError:
664                    msg = "Bad structure of the data model."
665                    raise RuntimeError, msg
666
667                if item.isCheckable() and item.checkState() == QtCore.Qt.Checked and is1D:
[488c49d]668                    item.setCheckState(QtCore.Qt.Unchecked)
669
670        elif index == 4:
671            # Select All 2-D
[5032ea68]672            for index in range(self.model.rowCount()):
673                item = self.model.item(index)
674                item.setCheckState(QtCore.Qt.Unchecked)
675                try:
[8548d739]676                    is2D = isinstance(GuiUtils.dataFromItem(item), Data2D)
[5032ea68]677                except AttributeError:
678                    msg = "Bad structure of the data model."
679                    raise RuntimeError, msg
680
681                if is2D:
[488c49d]682                    item.setCheckState(QtCore.Qt.Checked)
683
684        elif index == 5:
685            # Unselect All 2-D
[5032ea68]686            for index in range(self.model.rowCount()):
687                item = self.model.item(index)
688
689                try:
[8548d739]690                    is2D = isinstance(GuiUtils.dataFromItem(item), Data2D)
[5032ea68]691                except AttributeError:
692                    msg = "Bad structure of the data model."
693                    raise RuntimeError, msg
694
695                if item.isCheckable() and item.checkState() == QtCore.Qt.Checked and is2D:
[488c49d]696                    item.setCheckState(QtCore.Qt.Unchecked)
697
698        else:
699            msg = "Incorrect value in the Selection Option"
700            # Change this to a proper logging action
701            raise Exception, msg
702
[4b71e91]703    def contextMenu(self):
[e540cd2]704        """
[4b71e91]705        Define actions and layout of the right click context menu
[e540cd2]706        """
[4b71e91]707        # Create a custom menu based on actions defined in the UI file
708        self.context_menu = QtGui.QMenu(self)
709        self.context_menu.addAction(self.actionDataInfo)
710        self.context_menu.addAction(self.actionSaveAs)
711        self.context_menu.addAction(self.actionQuickPlot)
712        self.context_menu.addSeparator()
713        self.context_menu.addAction(self.actionQuick3DPlot)
714        self.context_menu.addAction(self.actionEditMask)
715
716        # Define the callbacks
717        self.actionDataInfo.triggered.connect(self.showDataInfo)
718        self.actionSaveAs.triggered.connect(self.saveDataAs)
719        self.actionQuickPlot.triggered.connect(self.quickDataPlot)
720        self.actionQuick3DPlot.triggered.connect(self.quickData3DPlot)
721        self.actionEditMask.triggered.connect(self.showEditDataMask)
[e540cd2]722
723    def onCustomContextMenu(self, position):
724        """
[4b71e91]725        Show the right-click context menu in the data treeview
[e540cd2]726        """
727        index = self.treeView.indexAt(position)
728        if index.isValid():
[4b71e91]729            model_item = self.model.itemFromIndex(self.data_proxy.mapToSource(index))
730            # Find the mapped index
731            orig_index = model_item.isCheckable()
732            if orig_index:
733                # Check the data to enable/disable actions
[8548d739]734                is_2D = isinstance(GuiUtils.dataFromItem(model_item), Data2D)
[4b71e91]735                self.actionQuick3DPlot.setEnabled(is_2D)
736                self.actionEditMask.setEnabled(is_2D)
737                # Fire up the menu
738                self.context_menu.exec_(self.treeView.mapToGlobal(position))
739
740    def showDataInfo(self):
741        """
742        Show a simple read-only text edit with data information.
743        """
744        index = self.treeView.selectedIndexes()[0]
745        model_item = self.model.itemFromIndex(self.data_proxy.mapToSource(index))
[8548d739]746        data = GuiUtils.dataFromItem(model_item)
[28a84e9]747        if isinstance(data, Data1D):
[4b71e91]748            text_to_show = GuiUtils.retrieveData1d(data)
[28a84e9]749            # Hardcoded sizes to enable full width rendering with default font
[4b71e91]750            self.txt_widget.resize(420,600)
751        else:
752            text_to_show = GuiUtils.retrieveData2d(data)
[28a84e9]753            # Hardcoded sizes to enable full width rendering with default font
[4b71e91]754            self.txt_widget.resize(700,600)
755
756        self.txt_widget.setReadOnly(True)
757        self.txt_widget.setWindowFlags(QtCore.Qt.Window)
758        self.txt_widget.setWindowIcon(QtGui.QIcon(":/res/ball.ico"))
759        self.txt_widget.setWindowTitle("Data Info: %s" % data.filename)
760        self.txt_widget.insertPlainText(text_to_show)
761
762        self.txt_widget.show()
[28a84e9]763        # Move the slider all the way up, if present
[4b71e91]764        vertical_scroll_bar = self.txt_widget.verticalScrollBar()
765        vertical_scroll_bar.triggerAction(QtGui.QScrollBar.SliderToMinimum)
766
767    def saveDataAs(self):
768        """
[28a84e9]769        Save the data points as either txt or xml
[4b71e91]770        """
[28a84e9]771        index = self.treeView.selectedIndexes()[0]
772        model_item = self.model.itemFromIndex(self.data_proxy.mapToSource(index))
[8548d739]773        data = GuiUtils.dataFromItem(model_item)
[28a84e9]774        if isinstance(data, Data1D):
775            GuiUtils.saveData1D(data)
776        else:
777            GuiUtils.saveData2D(data)
[4b71e91]778
779    def quickDataPlot(self):
[1af348e]780        """
781        Frozen plot - display an image of the plot
782        """
[39551a68]783        index = self.treeView.selectedIndexes()[0]
784        model_item = self.model.itemFromIndex(self.data_proxy.mapToSource(index))
[8548d739]785        data = GuiUtils.dataFromItem(model_item)
[39551a68]786
[ef01be4]787        method_name = 'Plotter'
788        if isinstance(data, Data2D):
789            method_name='Plotter2D'
[1af348e]790
[ef01be4]791        new_plot = globals()[method_name](self, quickplot=True)
792        new_plot.data = data
[5236449]793        #new_plot.plot(marker='o')
794        new_plot.plot()
[39551a68]795
796        # Update the global plot counter
797        title = "Plot " + data.name
798        new_plot.setWindowTitle(title)
799
800        # Show the plot
801        new_plot.show()
[4b71e91]802
803    def quickData3DPlot(self):
804        """
[55d89f8]805        Slowish 3D plot
[4b71e91]806        """
[55d89f8]807        index = self.treeView.selectedIndexes()[0]
808        model_item = self.model.itemFromIndex(self.data_proxy.mapToSource(index))
809        data = GuiUtils.dataFromItem(model_item)
810
811        new_plot = Plotter2D(self, quickplot=True, dimension=3)
812        new_plot.data = data
[965fbd8]813        new_plot.plot()
[55d89f8]814
815        # Update the global plot counter
816        title = "Plot " + data.name
817        new_plot.setWindowTitle(title)
818
819        # Show the plot
820        new_plot.show()
[4b71e91]821
822    def showEditDataMask(self):
823        """
[cad617b]824        Mask Editor for 2D plots
[4b71e91]825        """
[416fa8f]826        index = self.treeView.selectedIndexes()[0]
827        model_item = self.model.itemFromIndex(self.data_proxy.mapToSource(index))
828        data = GuiUtils.dataFromItem(model_item)
829
830        mask_editor = MaskEditor(self, data)
[cad617b]831        # Modal dialog here.
[416fa8f]832        mask_editor.exec_()
[f721030]833
[a281ab8]834    def loadComplete(self, output):
[f721030]835        """
836        Post message to status bar and update the data manager
837        """
[8cb6cd6]838        assert isinstance(output, tuple)
[e540cd2]839
[9e426c1]840        # Reset the model so the view gets updated.
[5032ea68]841        self.model.reset()
[e540cd2]842        self.communicator.progressBarUpdateSignal.emit(-1)
[a281ab8]843
844        output_data = output[0]
845        message = output[1]
[f721030]846        # Notify the manager of the new data available
[f82ab8c]847        self.communicator.statusBarUpdateSignal.emit(message)
848        self.communicator.fileDataReceivedSignal.emit(output_data)
[a281ab8]849        self.manager.add_data(data_list=output_data)
[f721030]850
851    def updateModel(self, data, p_file):
852        """
[481ff26]853        Add data and Info fields to the model item
[f721030]854        """
855        # Structure of the model
856        # checkbox + basename
[481ff26]857        #     |-------> Data.D object
[f721030]858        #     |-------> Info
859        #                 |----> Title:
860        #                 |----> Run:
861        #                 |----> Type:
862        #                 |----> Path:
863        #                 |----> Process
864        #                          |-----> process[0].name
[28a84e9]865        #     |-------> THEORIES
[f721030]866
867        # Top-level item: checkbox with label
868        checkbox_item = QtGui.QStandardItem(True)
869        checkbox_item.setCheckable(True)
870        checkbox_item.setCheckState(QtCore.Qt.Checked)
871        checkbox_item.setText(os.path.basename(p_file))
872
873        # Add the actual Data1D/Data2D object
874        object_item = QtGui.QStandardItem()
875        object_item.setData(QtCore.QVariant(data))
876
[488c49d]877        checkbox_item.setChild(0, object_item)
878
[f721030]879        # Add rows for display in the view
[0cd8612]880        info_item = GuiUtils.infoFromData(data)
[f721030]881
[28a84e9]882        # Set info_item as the first child
[488c49d]883        checkbox_item.setChild(1, info_item)
[f721030]884
[28a84e9]885        # Caption for the theories
886        checkbox_item.setChild(2, QtGui.QStandardItem("THEORIES"))
887
[f721030]888        # New row in the model
889        self.model.appendRow(checkbox_item)
[481ff26]890
[28a84e9]891
[5032ea68]892    def updateModelFromPerspective(self, model_item):
893        """
[a281ab8]894        Receive an update model item from a perspective
895        Make sure it is valid and if so, replace it in the model
[5032ea68]896        """
[a281ab8]897        # Assert the correct type
[0cd8612]898        if not isinstance(model_item, QtGui.QStandardItem):
[5032ea68]899            msg = "Wrong data type returned from calculations."
900            raise AttributeError, msg
[a281ab8]901
[1042dba]902        # TODO: Assert other properties
[a281ab8]903
[5032ea68]904        # Reset the view
905        self.model.reset()
[1042dba]906
[5032ea68]907        # Pass acting as a debugger anchor
908        pass
[481ff26]909
[5236449]910    def updateTheoryFromPerspective(self, model_item):
911        """
912        Receive an update theory item from a perspective
913        Make sure it is valid and if so, replace/add in the model
914        """
915        # Assert the correct type
916        if not isinstance(model_item, QtGui.QStandardItem):
917            msg = "Wrong data type returned from calculations."
918            raise AttributeError, msg
919
920        # Check if there are any other items for this tab
921        # If so, delete them
922        current_tab_name = model_item.text()[:2]
923        for current_index in xrange(self.theory_model.rowCount()):
924            if current_tab_name in self.theory_model.item(current_index).text():
925                self.theory_model.removeRow(current_index)
926                break
927
928        # Reset the view
929        self.model.reset()
930
931        # Reset the view
932        self.theory_model.appendRow(model_item)
933
934        # Pass acting as a debugger anchor
935        pass
936
[f721030]937
938if __name__ == "__main__":
939    app = QtGui.QApplication([])
940    dlg = DataExplorerWindow()
941    dlg.show()
[481ff26]942    sys.exit(app.exec_())
Note: See TracBrowser for help on using the repository browser.