source: sasview/src/sas/qtgui/DataExplorer.py @ 0979dfb

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

QModel items conversion into SasModel? parameters + data display SASVIEW-535

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