source: sasview/src/sas/qtgui/MainWindow/DataExplorer.py @ 4e255d1

ESS_GUIESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_iss879ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalc
Last change on this file since 4e255d1 was c7f259d, checked in by Torin Cooper-Bennun <torin.cooper-bennun@…>, 6 years ago

implement append-to-plot functionality for the Theory view

  • Property mode set to 100644
File size: 45.4 KB
RevLine 
[f721030]1# global
2import sys
3import os
[e540cd2]4import time
[f721030]5import logging
6
[4992ff2]7from PyQt5 import QtCore
8from PyQt5 import QtGui
9from PyQt5 import QtWidgets
[481ff26]10
[f721030]11from twisted.internet import threads
12
[dc5ef15]13# SASCALC
[f721030]14from sas.sascalc.dataloader.loader import Loader
15
[dc5ef15]16# QTGUI
[83eb5208]17import sas.qtgui.Utilities.GuiUtils as GuiUtils
18import sas.qtgui.Plotting.PlotHelper as PlotHelper
[dc5ef15]19
20from sas.qtgui.Plotting.PlotterData import Data1D
21from sas.qtgui.Plotting.PlotterData import Data2D
[83eb5208]22from sas.qtgui.Plotting.Plotter import Plotter
23from sas.qtgui.Plotting.Plotter2D import Plotter2D
24from sas.qtgui.Plotting.MaskEditor import MaskEditor
25
[dc5ef15]26from sas.qtgui.MainWindow.DataManager import DataManager
27from sas.qtgui.MainWindow.DroppableDataLoadWidget import DroppableDataLoadWidget
28
[83eb5208]29import sas.qtgui.Perspectives as Perspectives
[1970780]30
[d4881f6a]31DEFAULT_PERSPECTIVE = "Fitting"
32
[515c23df]33logger = logging.getLogger(__name__)
34
[f82ab8c]35class DataExplorerWindow(DroppableDataLoadWidget):
[f721030]36    # The controller which is responsible for managing signal slots connections
37    # for the gui and providing an interface to the data model.
38
[630155bd]39    def __init__(self, parent=None, guimanager=None, manager=None):
[f82ab8c]40        super(DataExplorerWindow, self).__init__(parent, guimanager)
[f721030]41
42        # Main model for keeping loaded data
43        self.model = QtGui.QStandardItemModel(self)
[f82ab8c]44
45        # Secondary model for keeping frozen data sets
46        self.theory_model = QtGui.QStandardItemModel(self)
[f721030]47
48        # GuiManager is the actual parent, but we needed to also pass the QMainWindow
49        # in order to set the widget parentage properly.
50        self.parent = guimanager
51        self.loader = Loader()
[630155bd]52        self.manager = manager if manager is not None else DataManager()
[4992ff2]53        self.txt_widget = QtWidgets.QTextEdit(None)
[f721030]54
[481ff26]55        # Be careful with twisted threads.
[4992ff2]56        self.mutex = QtCore.QMutex()
[481ff26]57
[d9150d8]58        # Plot widgets {name:widget}, required to keep track of plots shown as MDI subwindows
59        self.plot_widgets = {}
60
61        # Active plots {id:Plotter1D/2D}, required to keep track of currently displayed plots
[7d077d1]62        self.active_plots = {}
[8cb6cd6]63
[f721030]64        # Connect the buttons
65        self.cmdLoad.clicked.connect(self.loadFile)
[f82ab8c]66        self.cmdDeleteData.clicked.connect(self.deleteFile)
67        self.cmdDeleteTheory.clicked.connect(self.deleteTheory)
68        self.cmdFreeze.clicked.connect(self.freezeTheory)
[f721030]69        self.cmdSendTo.clicked.connect(self.sendData)
[1042dba]70        self.cmdNew.clicked.connect(self.newPlot)
[0268aed]71        self.cmdNew_2.clicked.connect(self.newPlot)
[8cb6cd6]72        self.cmdAppend.clicked.connect(self.appendPlot)
[c7f259d]73        self.cmdAppend_2.clicked.connect(self.appendPlot)
[481ff26]74        self.cmdHelp.clicked.connect(self.displayHelp)
75        self.cmdHelp_2.clicked.connect(self.displayHelp)
76
[83d6249]77        # Fill in the perspectives combo
78        self.initPerspectives()
79
[e540cd2]80        # Custom context menu
81        self.treeView.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
82        self.treeView.customContextMenuRequested.connect(self.onCustomContextMenu)
[4b71e91]83        self.contextMenu()
[e540cd2]84
[cbcdd2c]85        # Same menus for the theory view
86        self.freezeView.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
87        self.freezeView.customContextMenuRequested.connect(self.onCustomContextMenu)
88
[488c49d]89        # Connect the comboboxes
90        self.cbSelect.currentIndexChanged.connect(self.selectData)
91
[f82ab8c]92        #self.closeEvent.connect(self.closeEvent)
[cbcdd2c]93        self.currentChanged.connect(self.onTabSwitch)
[e540cd2]94        self.communicator = self.parent.communicator()
[f82ab8c]95        self.communicator.fileReadSignal.connect(self.loadFromURL)
[7d8bebf]96        self.communicator.activeGraphsSignal.connect(self.updateGraphCount)
[27313b7]97        self.communicator.activeGraphName.connect(self.updatePlotName)
[7d077d1]98        self.communicator.plotUpdateSignal.connect(self.updatePlot)
[e20870bc]99        self.communicator.maskEditorSignal.connect(self.showEditDataMask)
[d5c5d3d]100
[8cb6cd6]101        self.cbgraph.editTextChanged.connect(self.enableGraphCombo)
102        self.cbgraph.currentIndexChanged.connect(self.enableGraphCombo)
[f721030]103
104        # Proxy model for showing a subset of Data1D/Data2D content
[4992ff2]105        self.data_proxy = QtCore.QSortFilterProxyModel(self)
[481ff26]106        self.data_proxy.setSourceModel(self.model)
107
108        # Don't show "empty" rows with data objects
109        self.data_proxy.setFilterRegExp(r"[^()]")
[f721030]110
111        # The Data viewer is QTreeView showing the proxy model
[481ff26]112        self.treeView.setModel(self.data_proxy)
113
114        # Proxy model for showing a subset of Theory content
[4992ff2]115        self.theory_proxy = QtCore.QSortFilterProxyModel(self)
[481ff26]116        self.theory_proxy.setSourceModel(self.theory_model)
117
118        # Don't show "empty" rows with data objects
119        self.theory_proxy.setFilterRegExp(r"[^()]")
[f721030]120
[f82ab8c]121        # Theory model view
[481ff26]122        self.freezeView.setModel(self.theory_proxy)
[f82ab8c]123
[8cb6cd6]124        self.enableGraphCombo(None)
125
[cbcdd2c]126        # Current view on model
127        self.current_view = self.treeView
128
[f82ab8c]129    def closeEvent(self, event):
130        """
131        Overwrite the close event - no close!
132        """
133        event.ignore()
[1042dba]134
[cbcdd2c]135    def onTabSwitch(self, index):
136        """ Callback for tab switching signal """
137        if index == 0:
138            self.current_view = self.treeView
139        else:
140            self.current_view = self.freezeView
141
[481ff26]142    def displayHelp(self):
143        """
144        Show the "Loading data" section of help
145        """
[aed0532]146        tree_location = "/user/qtgui/MainWindow/data_explorer_help.html"
[e90988c]147        self.parent.showHelp(tree_location)
[481ff26]148
[8cb6cd6]149    def enableGraphCombo(self, combo_text):
150        """
151        Enables/disables "Assign Plot" elements
152        """
153        self.cbgraph.setEnabled(len(PlotHelper.currentPlots()) > 0)
154        self.cmdAppend.setEnabled(len(PlotHelper.currentPlots()) > 0)
155
[83d6249]156    def initPerspectives(self):
157        """
158        Populate the Perspective combobox and define callbacks
159        """
[b3e8629]160        available_perspectives = sorted([p for p in list(Perspectives.PERSPECTIVES.keys())])
[1970780]161        if available_perspectives:
162            self.cbFitting.clear()
163            self.cbFitting.addItems(available_perspectives)
[83d6249]164        self.cbFitting.currentIndexChanged.connect(self.updatePerspectiveCombo)
165        # Set the index so we see the default (Fitting)
[d4881f6a]166        self.cbFitting.setCurrentIndex(self.cbFitting.findText(DEFAULT_PERSPECTIVE))
[83d6249]167
[1970780]168    def _perspective(self):
169        """
170        Returns the current perspective
171        """
172        return self.parent.perspective()
173
[f82ab8c]174    def loadFromURL(self, url):
175        """
176        Threaded file load
177        """
178        load_thread = threads.deferToThread(self.readData, url)
179        load_thread.addCallback(self.loadComplete)
[7fb471d]180        load_thread.addErrback(self.loadFailed)
[5032ea68]181
182    def loadFile(self, event=None):
[f721030]183        """
184        Called when the "Load" button pressed.
[481ff26]185        Opens the Qt "Open File..." dialog
[f721030]186        """
187        path_str = self.chooseFiles()
188        if not path_str:
189            return
[f82ab8c]190        self.loadFromURL(path_str)
[f721030]191
[5032ea68]192    def loadFolder(self, event=None):
[f721030]193        """
[5032ea68]194        Called when the "File/Load Folder" menu item chosen.
[481ff26]195        Opens the Qt "Open Folder..." dialog
[f721030]196        """
[7969b9c]197        folder = QtWidgets.QFileDialog.getExistingDirectory(self, "Choose a directory", "",
[4992ff2]198              QtWidgets.QFileDialog.ShowDirsOnly | QtWidgets.QFileDialog.DontUseNativeDialog)
[481ff26]199        if folder is None:
[5032ea68]200            return
201
[481ff26]202        folder = str(folder)
[f721030]203
[481ff26]204        if not os.path.isdir(folder):
[5032ea68]205            return
[f721030]206
[5032ea68]207        # get content of dir into a list
[481ff26]208        path_str = [os.path.join(os.path.abspath(folder), filename)
[e540cd2]209                    for filename in os.listdir(folder)]
[f721030]210
[f82ab8c]211        self.loadFromURL(path_str)
[5032ea68]212
[630155bd]213    def loadProject(self):
214        """
215        Called when the "Open Project" menu item chosen.
216        """
217        kwargs = {
218            'parent'    : self,
219            'caption'   : 'Open Project',
220            'filter'    : 'Project (*.json);;All files (*.*)',
[4992ff2]221            'options'   : QtWidgets.QFileDialog.DontUseNativeDialog
[630155bd]222        }
[fbfc488]223        filename = QtWidgets.QFileDialog.getOpenFileName(**kwargs)[0]
[630155bd]224        if filename:
225            load_thread = threads.deferToThread(self.readProject, filename)
226            load_thread.addCallback(self.readProjectComplete)
[7fb471d]227            load_thread.addErrback(self.readProjectFailed)
228
[4992ff2]229    def loadFailed(self, reason):
230        """
231        """
232        print("file load FAILED: ", reason)
233        pass
234
[7fb471d]235    def readProjectFailed(self, reason):
236        """
237        """
238        print("readProjectFailed FAILED: ", reason)
239        pass
[630155bd]240
241    def readProject(self, filename):
242        self.communicator.statusBarUpdateSignal.emit("Loading Project... %s" % os.path.basename(filename))
243        try:
244            manager = DataManager()
245            with open(filename, 'r') as infile:
246                manager.load_from_readable(infile)
247
248            self.communicator.statusBarUpdateSignal.emit("Loaded Project: %s" % os.path.basename(filename))
249            return manager
250
251        except:
252            self.communicator.statusBarUpdateSignal.emit("Failed: %s" % os.path.basename(filename))
253            raise
254
255    def readProjectComplete(self, manager):
256        self.model.clear()
257
258        self.manager.assign(manager)
[4992ff2]259        self.model.beginResetModel()
[b3e8629]260        for id, item in self.manager.get_all_data().items():
[630155bd]261            self.updateModel(item.data, item.path)
262
[4992ff2]263        self.model.endResetModel()
[630155bd]264
265    def saveProject(self):
266        """
267        Called when the "Save Project" menu item chosen.
268        """
269        kwargs = {
270            'parent'    : self,
271            'caption'   : 'Save Project',
272            'filter'    : 'Project (*.json)',
[4992ff2]273            'options'   : QtWidgets.QFileDialog.DontUseNativeDialog
[630155bd]274        }
[d6b8a1d]275        name_tuple = QtWidgets.QFileDialog.getSaveFileName(**kwargs)
276        filename = name_tuple[0]
[630155bd]277        if filename:
[d6b8a1d]278            _, extension = os.path.splitext(filename)
279            if not extension:
280                filename = '.'.join((filename, 'json'))
[630155bd]281            self.communicator.statusBarUpdateSignal.emit("Saving Project... %s\n" % os.path.basename(filename))
282            with open(filename, 'w') as outfile:
283                self.manager.save_to_writable(outfile)
284
[5032ea68]285    def deleteFile(self, event):
286        """
287        Delete selected rows from the model
288        """
289        # Assure this is indeed wanted
290        delete_msg = "This operation will delete the checked data sets and all the dependents." +\
291                     "\nDo you want to continue?"
[53c771e]292        reply = QtWidgets.QMessageBox.question(self,
[e540cd2]293                                           'Warning',
294                                           delete_msg,
[53c771e]295                                           QtWidgets.QMessageBox.Yes,
296                                           QtWidgets.QMessageBox.No)
[5032ea68]297
[53c771e]298        if reply == QtWidgets.QMessageBox.No:
[5032ea68]299            return
300
301        # Figure out which rows are checked
302        ind = -1
303        # Use 'while' so the row count is forced at every iteration
[515c23df]304        deleted_items = []
[1420066]305        deleted_names = []
[5032ea68]306        while ind < self.model.rowCount():
307            ind += 1
308            item = self.model.item(ind)
[f0bb711]309
[5032ea68]310            if item and item.isCheckable() and item.checkState() == QtCore.Qt.Checked:
311                # Delete these rows from the model
[1420066]312                deleted_names.append(str(self.model.item(ind).text()))
[515c23df]313                deleted_items.append(item)
[f0bb711]314
[5032ea68]315                self.model.removeRow(ind)
316                # Decrement index since we just deleted it
317                ind -= 1
318
[38eb433]319        # Let others know we deleted data
[515c23df]320        self.communicator.dataDeletedSignal.emit(deleted_items)
[f721030]321
[f0bb711]322        # update stored_data
[1420066]323        self.manager.update_stored_data(deleted_names)
[f0bb711]324
[f82ab8c]325    def deleteTheory(self, event):
326        """
327        Delete selected rows from the theory model
328        """
329        # Assure this is indeed wanted
330        delete_msg = "This operation will delete the checked data sets and all the dependents." +\
331                     "\nDo you want to continue?"
[53c771e]332        reply = QtWidgets.QMessageBox.question(self,
[8cb6cd6]333                                           'Warning',
334                                           delete_msg,
[53c771e]335                                           QtWidgets.QMessageBox.Yes,
336                                           QtWidgets.QMessageBox.No)
[f82ab8c]337
[53c771e]338        if reply == QtWidgets.QMessageBox.No:
[f82ab8c]339            return
340
341        # Figure out which rows are checked
342        ind = -1
343        # Use 'while' so the row count is forced at every iteration
344        while ind < self.theory_model.rowCount():
345            ind += 1
346            item = self.theory_model.item(ind)
347            if item and item.isCheckable() and item.checkState() == QtCore.Qt.Checked:
348                # Delete these rows from the model
349                self.theory_model.removeRow(ind)
350                # Decrement index since we just deleted it
351                ind -= 1
352
353        # pass temporarily kept as a breakpoint anchor
354        pass
355
[f721030]356    def sendData(self, event):
357        """
[5032ea68]358        Send selected item data to the current perspective and set the relevant notifiers
[f721030]359        """
[5032ea68]360        # Set the signal handlers
361        self.communicator.updateModelFromPerspectiveSignal.connect(self.updateModelFromPerspective)
[f721030]362
[adf81b8]363        def isItemReady(index):
[5032ea68]364            item = self.model.item(index)
[adf81b8]365            return item.isCheckable() and item.checkState() == QtCore.Qt.Checked
366
367        # Figure out which rows are checked
368        selected_items = [self.model.item(index)
[b3e8629]369                          for index in range(self.model.rowCount())
[adf81b8]370                          if isItemReady(index)]
[f721030]371
[f82ab8c]372        if len(selected_items) < 1:
373            return
374
[f721030]375        # Which perspective has been selected?
[1970780]376        if len(selected_items) > 1 and not self._perspective().allowBatch():
377            msg = self._perspective().title() + " does not allow multiple data."
[53c771e]378            msgbox = QtWidgets.QMessageBox()
379            msgbox.setIcon(QtWidgets.QMessageBox.Critical)
[5032ea68]380            msgbox.setText(msg)
[53c771e]381            msgbox.setStandardButtons(QtWidgets.QMessageBox.Ok)
[5032ea68]382            retval = msgbox.exec_()
383            return
[a281ab8]384
[f721030]385        # Notify the GuiManager about the send request
[ee18d33]386        self._perspective().setData(data_item=selected_items, is_batch=self.chkBatch.isChecked())
[5032ea68]387
[f82ab8c]388    def freezeTheory(self, event):
389        """
390        Freeze selected theory rows.
391
[ca8b853]392        "Freezing" means taking the plottable data from the Theory item
393        and copying it to a separate top-level item in Data.
[f82ab8c]394        """
[ca8b853]395        # Figure out which rows are checked
[f82ab8c]396        # Use 'while' so the row count is forced at every iteration
397        outer_index = -1
[481ff26]398        theories_copied = 0
[ca8b853]399        while outer_index < self.theory_model.rowCount():
[f82ab8c]400            outer_index += 1
[ca8b853]401            outer_item = self.theory_model.item(outer_index)
[f82ab8c]402            if not outer_item:
403                continue
[ca8b853]404            if outer_item.isCheckable() and \
405                   outer_item.checkState() == QtCore.Qt.Checked:
[7969b9c]406                self.model.beginResetModel()
[ca8b853]407                theories_copied += 1
[685e0e3]408                new_item = self.cloneTheory(outer_item)
[ca8b853]409                self.model.appendRow(new_item)
[7969b9c]410                self.model.endResetModel()
[f82ab8c]411
[481ff26]412        freeze_msg = ""
413        if theories_copied == 0:
414            return
415        elif theories_copied == 1:
[ca8b853]416            freeze_msg = "1 theory copied from the Theory tab as a data set"
[481ff26]417        elif theories_copied > 1:
[ca8b853]418            freeze_msg = "%i theories copied from the Theory tab as data sets" % theories_copied
[481ff26]419        else:
420            freeze_msg = "Unexpected number of theories copied: %i" % theories_copied
[b3e8629]421            raise AttributeError(freeze_msg)
[481ff26]422        self.communicator.statusBarUpdateSignal.emit(freeze_msg)
423        # Actively switch tabs
424        self.setCurrentIndex(1)
425
[685e0e3]426    def cloneTheory(self, item_from):
427        """
428        Manually clone theory items into a new HashableItem
429        """
430        new_item = GuiUtils.HashableStandardItem()
431        new_item.setCheckable(True)
432        new_item.setCheckState(QtCore.Qt.Checked)
433        info_item = QtGui.QStandardItem("Info")
434        data_item = QtGui.QStandardItem()
435        data_item.setData(item_from.child(0).data())
436        new_item.setText(item_from.text())
437        new_item.setChild(0, data_item)
438        new_item.setChild(1, info_item)
439        # Append a "unique" descriptor to the name
440        time_bit = str(time.time())[7:-1].replace('.', '')
441        new_name = new_item.text() + '_@' + time_bit
442        new_item.setText(new_name)
443        # Change the underlying data so it is no longer a theory
444        try:
445            new_item.child(0).data().is_data = True
446        except AttributeError:
447            #no data here, pass
448            pass
449        return new_item
450
[481ff26]451    def recursivelyCloneItem(self, item):
452        """
453        Clone QStandardItem() object
454        """
455        new_item = item.clone()
456        # clone doesn't do deepcopy :(
[b3e8629]457        for child_index in range(item.rowCount()):
[481ff26]458            child_item = self.recursivelyCloneItem(item.child(child_index))
459            new_item.setChild(child_index, child_item)
460        return new_item
[f82ab8c]461
[27313b7]462    def updatePlotName(self, name_tuple):
463        """
464        Modify the name of the current plot
465        """
466        old_name, current_name = name_tuple
467        ind = self.cbgraph.findText(old_name)
468        self.cbgraph.setCurrentIndex(ind)
469        self.cbgraph.setItemText(ind, current_name)
470
[7d8bebf]471    def updateGraphCount(self, graph_list):
472        """
473        Modify the graph name combo and potentially remove
474        deleted graphs
475        """
476        self.updateGraphCombo(graph_list)
477
478        if not self.active_plots:
479            return
480        new_plots = [PlotHelper.plotById(plot) for plot in graph_list]
[b3e8629]481        active_plots_copy = list(self.active_plots.keys())
[7d8bebf]482        for plot in active_plots_copy:
483            if self.active_plots[plot] in new_plots:
484                continue
485            self.active_plots.pop(plot)
486
[8cb6cd6]487    def updateGraphCombo(self, graph_list):
488        """
489        Modify Graph combo box on graph add/delete
490        """
491        orig_text = self.cbgraph.currentText()
492        self.cbgraph.clear()
[0268aed]493        self.cbgraph.insertItems(0, graph_list)
[8cb6cd6]494        ind = self.cbgraph.findText(orig_text)
495        if ind > 0:
496            self.cbgraph.setCurrentIndex(ind)
497
[83d6249]498    def updatePerspectiveCombo(self, index):
499        """
500        Notify the gui manager about the new perspective chosen.
501        """
[8ac3551]502        self.communicator.perspectiveChangedSignal.emit(self.cbFitting.itemText(index))
[1970780]503        self.chkBatch.setEnabled(self.parent.perspective().allowBatch())
[83d6249]504
[d4dac80]505    def itemFromFilename(self, filename):
506        """
507        Retrieves model item corresponding to the given filename
508        """
509        item = GuiUtils.itemFromFilename(filename, self.model)
510        return item
511
[3b3b40b]512    def displayFile(self, filename=None, is_data=True):
[d48cc19]513        """
514        Forces display of charts for the given filename
515        """
[3b3b40b]516        model = self.model if is_data else self.theory_model
[88e1f57]517        # Now query the model item for available plots
[d48cc19]518        plots = GuiUtils.plotsFromFilename(filename, model)
[ebfe223]519        ids = [list(self.active_plots.values())[id].data.id for id in range(len(self.active_plots))]
[88e1f57]520
521        new_plots = []
[6ff103a]522        for item, plot in plots.items():
[fef38e8]523            plot_id = plot.id
[ebfe223]524            if plot_id in ids:
[7d8bebf]525                self.active_plots[plot_id].replacePlot(plot_id, plot)
[fef38e8]526            else:
[88e1f57]527                # 'sophisticated' test to generate standalone plot for residuals
528                if 'esiduals' in plot.title:
529                    self.plotData([(item, plot)])
530                else:
531                    new_plots.append((item, plot))
532
533        if new_plots:
534            self.plotData(new_plots)
[56b22f9]535
[3b3b40b]536    def displayData(self, data_list):
537        """
538        Forces display of charts for the given data set
539        """
540        plot_to_show = data_list[0]
541        # passed plot is used ONLY to figure out its title,
542        # so all the charts related by it can be pulled from
543        # the data explorer indices.
544        filename = plot_to_show.filename
545        self.displayFile(filename=filename, is_data=plot_to_show.is_data)
546
[56b22f9]547    def addDataPlot2D(self, plot_set, item):
[672b8ab]548        """
549        Create a new 2D plot and add it to the workspace
550        """
[56b22f9]551        plot2D = Plotter2D(self)
552        plot2D.item = item
553        plot2D.plot(plot_set)
554        self.addPlot(plot2D)
[88e1f57]555        self.active_plots[plot2D.data.id] = plot2D
[56b22f9]556        #============================================
[672b8ab]557        # Experimental hook for silx charts
558        #============================================
[56b22f9]559        ## Attach silx
560        #from silx.gui import qt
561        #from silx.gui.plot import StackView
562        #sv = StackView()
563        #sv.setColormap("jet", autoscale=True)
564        #sv.setStack(plot_set.data.reshape(1,100,100))
565        ##sv.setLabels(["x: -10 to 10 (200 samples)",
566        ##              "y: -10 to 5 (150 samples)"])
567        #sv.show()
568        #============================================
569
570    def plotData(self, plots):
571        """
572        Takes 1D/2D data and generates a single plot (1D) or multiple plots (2D)
[1042dba]573        """
574        # Call show on requested plots
[31c5b58]575        # All same-type charts in one plot
[3bdbfcc]576        for item, plot_set in plots:
[49e124c]577            if isinstance(plot_set, Data1D):
[7d8bebf]578                if not 'new_plot' in locals():
579                    new_plot = Plotter(self)
[d9150d8]580                    new_plot.item = item
[9290b1a]581                new_plot.plot(plot_set)
[88e1f57]582                # active_plots may contain multiple charts
583                self.active_plots[plot_set.id] = new_plot
[49e124c]584            elif isinstance(plot_set, Data2D):
[56b22f9]585                self.addDataPlot2D(plot_set, item)
[49e124c]586            else:
587                msg = "Incorrect data type passed to Plotting"
[b3e8629]588                raise AttributeError(msg)
[49e124c]589
[7d8bebf]590        if 'new_plot' in locals() and \
[b4b8589]591            hasattr(new_plot, 'data') and \
592            isinstance(new_plot.data, Data1D):
[56b22f9]593                self.addPlot(new_plot)
594
595    def newPlot(self):
596        """
597        Select checked data and plot it
598        """
599        # Check which tab is currently active
600        if self.current_view == self.treeView:
601            plots = GuiUtils.plotsFromCheckedItems(self.model)
602        else:
603            plots = GuiUtils.plotsFromCheckedItems(self.theory_model)
604
605        self.plotData(plots)
[1042dba]606
[56b22f9]607    def addPlot(self, new_plot):
[31c5b58]608        """
609        Helper method for plot bookkeeping
610        """
611        # Update the global plot counter
[0268aed]612        title = str(PlotHelper.idOfPlot(new_plot))
[31c5b58]613        new_plot.setWindowTitle(title)
[8cb6cd6]614
[7d8bebf]615        # Set the object name to satisfy the Squish object picker
616        new_plot.setObjectName(title)
617
[31c5b58]618        # Add the plot to the workspace
[d9150d8]619        plot_widget = self.parent.workspace().addSubWindow(new_plot)
[8cb6cd6]620
[31c5b58]621        # Show the plot
622        new_plot.show()
[fbfc488]623        new_plot.canvas.draw()
[f721030]624
[d9150d8]625        # Update the plot widgets dict
626        self.plot_widgets[title]=plot_widget
627
[31c5b58]628        # Update the active chart list
[88e1f57]629        #self.active_plots[new_plot.data.id] = new_plot
[8cb6cd6]630
631    def appendPlot(self):
632        """
633        Add data set(s) to the existing matplotlib chart
634        """
[c7f259d]635        # new plot data; check which tab is currently active
636        if self.current_view == self.treeView:
637            new_plots = GuiUtils.plotsFromCheckedItems(self.model)
638        else:
639            new_plots = GuiUtils.plotsFromCheckedItems(self.theory_model)
[8cb6cd6]640
641        # old plot data
[0268aed]642        plot_id = str(self.cbgraph.currentText())
[8cb6cd6]643
[0268aed]644        assert plot_id in PlotHelper.currentPlots(), "No such plot: %s"%(plot_id)
[8cb6cd6]645
646        old_plot = PlotHelper.plotById(plot_id)
647
[14d9c7b]648        # Add new data to the old plot, if data type is the same.
[3bdbfcc]649        for _, plot_set in new_plots:
[14d9c7b]650            if type(plot_set) is type(old_plot._data):
[31c5b58]651                old_plot.data = plot_set
[14d9c7b]652                old_plot.plot()
[8cb6cd6]653
[7d077d1]654    def updatePlot(self, new_data):
655        """
656        Modify existing plot for immediate response
657        """
658        data = new_data[0]
659        assert type(data).__name__ in ['Data1D', 'Data2D']
660
661        id = data.id
[b3e8629]662        if data.id in list(self.active_plots.keys()):
[7d077d1]663            self.active_plots[id].replacePlot(id, data)
664
[f721030]665    def chooseFiles(self):
666        """
[5032ea68]667        Shows the Open file dialog and returns the chosen path(s)
[f721030]668        """
669        # List of known extensions
670        wlist = self.getWlist()
671
672        # Location is automatically saved - no need to keep track of the last dir
[5032ea68]673        # But only with Qt built-in dialog (non-platform native)
[4992ff2]674        paths = QtWidgets.QFileDialog.getOpenFileNames(self, "Choose a file", "",
[7969b9c]675                wlist, None, QtWidgets.QFileDialog.DontUseNativeDialog)[0]
[fbfc488]676        if not paths:
[f721030]677            return
678
[0cd8612]679        if not isinstance(paths, list):
[f721030]680            paths = [paths]
681
[9e426c1]682        return paths
[f721030]683
684    def readData(self, path):
685        """
[481ff26]686        verbatim copy-paste from
687           sasgui.guiframe.local_perspectives.data_loader.data_loader.py
[f721030]688        slightly modified for clarity
689        """
690        message = ""
691        log_msg = ''
692        output = {}
693        any_error = False
694        data_error = False
695        error_message = ""
[e540cd2]696        number_of_files = len(path)
697        self.communicator.progressBarUpdateSignal.emit(0.0)
698
699        for index, p_file in enumerate(path):
[f721030]700            basename = os.path.basename(p_file)
701            _, extension = os.path.splitext(basename)
[0cd8612]702            if extension.lower() in GuiUtils.EXTENSIONS:
[f721030]703                any_error = True
704                log_msg = "Data Loader cannot "
705                log_msg += "load: %s\n" % str(p_file)
706                log_msg += """Please try to open that file from "open project" """
707                log_msg += """or "open analysis" menu\n"""
708                error_message = log_msg + "\n"
709                logging.info(log_msg)
710                continue
711
712            try:
[5032ea68]713                message = "Loading Data... " + str(basename) + "\n"
[f721030]714
715                # change this to signal notification in GuiManager
[f82ab8c]716                self.communicator.statusBarUpdateSignal.emit(message)
[f721030]717
718                output_objects = self.loader.load(p_file)
719
720                # Some loaders return a list and some just a single Data1D object.
721                # Standardize.
722                if not isinstance(output_objects, list):
723                    output_objects = [output_objects]
724
725                for item in output_objects:
[481ff26]726                    # cast sascalc.dataloader.data_info.Data1D into
727                    # sasgui.guiframe.dataFitting.Data1D
[f721030]728                    # TODO : Fix it
729                    new_data = self.manager.create_gui_data(item, p_file)
730                    output[new_data.id] = new_data
[481ff26]731
732                    # Model update should be protected
733                    self.mutex.lock()
[f721030]734                    self.updateModel(new_data, p_file)
[7969b9c]735                    #self.model.reset()
736                    QtWidgets.QApplication.processEvents()
[481ff26]737                    self.mutex.unlock()
[f721030]738
739                    if hasattr(item, 'errors'):
740                        for error_data in item.errors:
741                            data_error = True
742                            message += "\tError: {0}\n".format(error_data)
743                    else:
[5032ea68]744
[f721030]745                        logging.error("Loader returned an invalid object:\n %s" % str(item))
746                        data_error = True
747
[5032ea68]748            except Exception as ex:
[b3e8629]749                logging.error(sys.exc_info()[1])
[5032ea68]750
[f721030]751                any_error = True
752            if any_error or error_message != "":
753                if error_message == "":
754                    error = "Error: " + str(sys.exc_info()[1]) + "\n"
755                    error += "while loading Data: \n%s\n" % str(basename)
756                    error_message += "The data file you selected could not be loaded.\n"
757                    error_message += "Make sure the content of your file"
758                    error_message += " is properly formatted.\n\n"
759                    error_message += "When contacting the SasView team, mention the"
760                    error_message += " following:\n%s" % str(error)
761                elif data_error:
762                    base_message = "Errors occurred while loading "
763                    base_message += "{0}\n".format(basename)
764                    base_message += "The data file loaded but with errors.\n"
765                    error_message = base_message + error_message
766                else:
767                    error_message += "%s\n" % str(p_file)
[481ff26]768
[e540cd2]769            current_percentage = int(100.0* index/number_of_files)
770            self.communicator.progressBarUpdateSignal.emit(current_percentage)
771
[f721030]772        if any_error or error_message:
[0cd8612]773            logging.error(error_message)
774            status_bar_message = "Errors occurred while loading %s" % format(basename)
775            self.communicator.statusBarUpdateSignal.emit(status_bar_message)
[f721030]776
777        else:
778            message = "Loading Data Complete! "
779        message += log_msg
[0cd8612]780        # Notify the progress bar that the updates are over.
[e540cd2]781        self.communicator.progressBarUpdateSignal.emit(-1)
[454670d]782        self.communicator.statusBarUpdateSignal.emit(message)
[481ff26]783
[a281ab8]784        return output, message
[f721030]785
786    def getWlist(self):
787        """
[f82ab8c]788        Wildcards of files we know the format of.
[f721030]789        """
790        # Display the Qt Load File module
791        cards = self.loader.get_wildcards()
792
793        # get rid of the wx remnant in wildcards
794        # TODO: modify sasview loader get_wildcards method, after merge,
795        # so this kludge can be avoided
796        new_cards = []
797        for item in cards:
798            new_cards.append(item[:item.find("|")])
799        wlist = ';;'.join(new_cards)
800
801        return wlist
[488c49d]802
803    def selectData(self, index):
804        """
805        Callback method for modifying the TreeView on Selection Options change
806        """
807        if not isinstance(index, int):
808            msg = "Incorrect type passed to DataExplorer.selectData()"
[b3e8629]809            raise AttributeError(msg)
[488c49d]810
811        # Respond appropriately
812        if index == 0:
813            # Select All
[5032ea68]814            for index in range(self.model.rowCount()):
815                item = self.model.item(index)
816                if item.isCheckable() and item.checkState() == QtCore.Qt.Unchecked:
[488c49d]817                    item.setCheckState(QtCore.Qt.Checked)
818        elif index == 1:
819            # De-select All
[5032ea68]820            for index in range(self.model.rowCount()):
821                item = self.model.item(index)
822                if item.isCheckable() and item.checkState() == QtCore.Qt.Checked:
[488c49d]823                    item.setCheckState(QtCore.Qt.Unchecked)
824
825        elif index == 2:
826            # Select All 1-D
[5032ea68]827            for index in range(self.model.rowCount()):
828                item = self.model.item(index)
[488c49d]829                item.setCheckState(QtCore.Qt.Unchecked)
[5032ea68]830
831                try:
[8548d739]832                    is1D = isinstance(GuiUtils.dataFromItem(item), Data1D)
[5032ea68]833                except AttributeError:
834                    msg = "Bad structure of the data model."
[b3e8629]835                    raise RuntimeError(msg)
[5032ea68]836
837                if is1D:
[488c49d]838                    item.setCheckState(QtCore.Qt.Checked)
839
840        elif index == 3:
841            # Unselect All 1-D
[5032ea68]842            for index in range(self.model.rowCount()):
843                item = self.model.item(index)
844
845                try:
[8548d739]846                    is1D = isinstance(GuiUtils.dataFromItem(item), Data1D)
[5032ea68]847                except AttributeError:
848                    msg = "Bad structure of the data model."
[b3e8629]849                    raise RuntimeError(msg)
[5032ea68]850
851                if item.isCheckable() and item.checkState() == QtCore.Qt.Checked and is1D:
[488c49d]852                    item.setCheckState(QtCore.Qt.Unchecked)
853
854        elif index == 4:
855            # Select All 2-D
[5032ea68]856            for index in range(self.model.rowCount()):
857                item = self.model.item(index)
858                item.setCheckState(QtCore.Qt.Unchecked)
859                try:
[8548d739]860                    is2D = isinstance(GuiUtils.dataFromItem(item), Data2D)
[5032ea68]861                except AttributeError:
862                    msg = "Bad structure of the data model."
[b3e8629]863                    raise RuntimeError(msg)
[5032ea68]864
865                if is2D:
[488c49d]866                    item.setCheckState(QtCore.Qt.Checked)
867
868        elif index == 5:
869            # Unselect All 2-D
[5032ea68]870            for index in range(self.model.rowCount()):
871                item = self.model.item(index)
872
873                try:
[8548d739]874                    is2D = isinstance(GuiUtils.dataFromItem(item), Data2D)
[5032ea68]875                except AttributeError:
876                    msg = "Bad structure of the data model."
[b3e8629]877                    raise RuntimeError(msg)
[5032ea68]878
879                if item.isCheckable() and item.checkState() == QtCore.Qt.Checked and is2D:
[488c49d]880                    item.setCheckState(QtCore.Qt.Unchecked)
881
882        else:
883            msg = "Incorrect value in the Selection Option"
884            # Change this to a proper logging action
[b3e8629]885            raise Exception(msg)
[488c49d]886
[4b71e91]887    def contextMenu(self):
[e540cd2]888        """
[4b71e91]889        Define actions and layout of the right click context menu
[e540cd2]890        """
[4b71e91]891        # Create a custom menu based on actions defined in the UI file
[4992ff2]892        self.context_menu = QtWidgets.QMenu(self)
[4b71e91]893        self.context_menu.addAction(self.actionDataInfo)
894        self.context_menu.addAction(self.actionSaveAs)
895        self.context_menu.addAction(self.actionQuickPlot)
896        self.context_menu.addSeparator()
897        self.context_menu.addAction(self.actionQuick3DPlot)
898        self.context_menu.addAction(self.actionEditMask)
[c6fb57c]899        self.context_menu.addSeparator()
900        self.context_menu.addAction(self.actionDelete)
901
[4b71e91]902
903        # Define the callbacks
904        self.actionDataInfo.triggered.connect(self.showDataInfo)
905        self.actionSaveAs.triggered.connect(self.saveDataAs)
906        self.actionQuickPlot.triggered.connect(self.quickDataPlot)
907        self.actionQuick3DPlot.triggered.connect(self.quickData3DPlot)
908        self.actionEditMask.triggered.connect(self.showEditDataMask)
[c6fb57c]909        self.actionDelete.triggered.connect(self.deleteItem)
[e540cd2]910
911    def onCustomContextMenu(self, position):
912        """
[4b71e91]913        Show the right-click context menu in the data treeview
[e540cd2]914        """
[cbcdd2c]915        index = self.current_view.indexAt(position)
916        proxy = self.current_view.model()
917        model = proxy.sourceModel()
918
[e540cd2]919        if index.isValid():
[cbcdd2c]920            model_item = model.itemFromIndex(proxy.mapToSource(index))
[4b71e91]921            # Find the mapped index
922            orig_index = model_item.isCheckable()
923            if orig_index:
924                # Check the data to enable/disable actions
[8548d739]925                is_2D = isinstance(GuiUtils.dataFromItem(model_item), Data2D)
[4b71e91]926                self.actionQuick3DPlot.setEnabled(is_2D)
927                self.actionEditMask.setEnabled(is_2D)
928                # Fire up the menu
[cbcdd2c]929                self.context_menu.exec_(self.current_view.mapToGlobal(position))
[4b71e91]930
931    def showDataInfo(self):
932        """
933        Show a simple read-only text edit with data information.
934        """
[cbcdd2c]935        index = self.current_view.selectedIndexes()[0]
936        proxy = self.current_view.model()
937        model = proxy.sourceModel()
938        model_item = model.itemFromIndex(proxy.mapToSource(index))
939
[8548d739]940        data = GuiUtils.dataFromItem(model_item)
[28a84e9]941        if isinstance(data, Data1D):
[4b71e91]942            text_to_show = GuiUtils.retrieveData1d(data)
[28a84e9]943            # Hardcoded sizes to enable full width rendering with default font
[4b71e91]944            self.txt_widget.resize(420,600)
945        else:
946            text_to_show = GuiUtils.retrieveData2d(data)
[28a84e9]947            # Hardcoded sizes to enable full width rendering with default font
[4b71e91]948            self.txt_widget.resize(700,600)
949
950        self.txt_widget.setReadOnly(True)
951        self.txt_widget.setWindowFlags(QtCore.Qt.Window)
952        self.txt_widget.setWindowIcon(QtGui.QIcon(":/res/ball.ico"))
953        self.txt_widget.setWindowTitle("Data Info: %s" % data.filename)
[d5c5d3d]954        self.txt_widget.clear()
[4b71e91]955        self.txt_widget.insertPlainText(text_to_show)
956
957        self.txt_widget.show()
[28a84e9]958        # Move the slider all the way up, if present
[4b71e91]959        vertical_scroll_bar = self.txt_widget.verticalScrollBar()
[7969b9c]960        vertical_scroll_bar.triggerAction(QtWidgets.QScrollBar.SliderToMinimum)
[4b71e91]961
962    def saveDataAs(self):
963        """
[28a84e9]964        Save the data points as either txt or xml
[4b71e91]965        """
[cbcdd2c]966        index = self.current_view.selectedIndexes()[0]
967        proxy = self.current_view.model()
968        model = proxy.sourceModel()
969        model_item = model.itemFromIndex(proxy.mapToSource(index))
970
[8548d739]971        data = GuiUtils.dataFromItem(model_item)
[28a84e9]972        if isinstance(data, Data1D):
973            GuiUtils.saveData1D(data)
974        else:
975            GuiUtils.saveData2D(data)
[4b71e91]976
977    def quickDataPlot(self):
[1af348e]978        """
979        Frozen plot - display an image of the plot
980        """
[cbcdd2c]981        index = self.current_view.selectedIndexes()[0]
982        proxy = self.current_view.model()
983        model = proxy.sourceModel()
984        model_item = model.itemFromIndex(proxy.mapToSource(index))
985
[8548d739]986        data = GuiUtils.dataFromItem(model_item)
[39551a68]987
[ef01be4]988        method_name = 'Plotter'
989        if isinstance(data, Data2D):
990            method_name='Plotter2D'
[1af348e]991
[fce6c55]992        self.new_plot = globals()[method_name](self, quickplot=True)
993        self.new_plot.data = data
[5236449]994        #new_plot.plot(marker='o')
[fce6c55]995        self.new_plot.plot()
[39551a68]996
997        # Update the global plot counter
998        title = "Plot " + data.name
[fce6c55]999        self.new_plot.setWindowTitle(title)
[39551a68]1000
1001        # Show the plot
[fce6c55]1002        self.new_plot.show()
[4b71e91]1003
1004    def quickData3DPlot(self):
1005        """
[55d89f8]1006        Slowish 3D plot
[4b71e91]1007        """
[cbcdd2c]1008        index = self.current_view.selectedIndexes()[0]
1009        proxy = self.current_view.model()
1010        model = proxy.sourceModel()
1011        model_item = model.itemFromIndex(proxy.mapToSource(index))
1012
[55d89f8]1013        data = GuiUtils.dataFromItem(model_item)
1014
[fce6c55]1015        self.new_plot = Plotter2D(self, quickplot=True, dimension=3)
1016        self.new_plot.data = data
1017        self.new_plot.plot()
[55d89f8]1018
1019        # Update the global plot counter
1020        title = "Plot " + data.name
[fce6c55]1021        self.new_plot.setWindowTitle(title)
[55d89f8]1022
1023        # Show the plot
[fce6c55]1024        self.new_plot.show()
[4b71e91]1025
[e20870bc]1026    def showEditDataMask(self, data=None):
[4b71e91]1027        """
[cad617b]1028        Mask Editor for 2D plots
[4b71e91]1029        """
[e20870bc]1030        if data is None or not isinstance(data, Data2D):
1031            index = self.current_view.selectedIndexes()[0]
1032            proxy = self.current_view.model()
1033            model = proxy.sourceModel()
1034            model_item = model.itemFromIndex(proxy.mapToSource(index))
[cbcdd2c]1035
[e20870bc]1036            data = GuiUtils.dataFromItem(model_item)
[416fa8f]1037
1038        mask_editor = MaskEditor(self, data)
[cad617b]1039        # Modal dialog here.
[416fa8f]1040        mask_editor.exec_()
[f721030]1041
[c6fb57c]1042    def deleteItem(self):
1043        """
1044        Delete the current item
1045        """
1046        # Assure this is indeed wanted
[b1a7a81]1047        delete_msg = "This operation will delete the selected data sets " +\
1048                     "and all the dependents." +\
[c6fb57c]1049                     "\nDo you want to continue?"
1050        reply = QtWidgets.QMessageBox.question(self,
1051                                           'Warning',
1052                                           delete_msg,
1053                                           QtWidgets.QMessageBox.Yes,
1054                                           QtWidgets.QMessageBox.No)
1055
1056        if reply == QtWidgets.QMessageBox.No:
1057            return
1058
[d9150d8]1059        # Every time a row is removed, the indices change, so we'll just remove
1060        # rows and keep calling selectedIndexes until it returns an empty list.
1061        indices = self.current_view.selectedIndexes()
1062
[c6fb57c]1063        proxy = self.current_view.model()
1064        model = proxy.sourceModel()
1065
[515c23df]1066        deleted_items = []
[b1a7a81]1067        deleted_names = []
1068
1069        while len(indices) > 0:
1070            index = indices[0]
[c6fb57c]1071            row_index = proxy.mapToSource(index)
1072            item_to_delete = model.itemFromIndex(row_index)
[cb4d219]1073            if item_to_delete and item_to_delete.isCheckable():
[c6fb57c]1074                row = row_index.row()
[b1a7a81]1075
1076                # store the deleted item details so we can pass them on later
[515c23df]1077                deleted_names.append(item_to_delete.text())
1078                deleted_items.append(item_to_delete)
[b1a7a81]1079
[d9150d8]1080                # Delete corresponding open plots
1081                self.closePlotsForItem(item_to_delete)
1082
[c6fb57c]1083                if item_to_delete.parent():
1084                    # We have a child item - delete from it
1085                    item_to_delete.parent().removeRow(row)
1086                else:
1087                    # delete directly from model
1088                    model.removeRow(row)
[b1a7a81]1089            indices = self.current_view.selectedIndexes()
1090
1091        # Let others know we deleted data
[515c23df]1092        self.communicator.dataDeletedSignal.emit(deleted_items)
[b1a7a81]1093
1094        # update stored_data
1095        self.manager.update_stored_data(deleted_names)
[c6fb57c]1096
[d9150d8]1097    def closePlotsForItem(self, item):
1098        """
1099        Given standard item, close all its currently displayed plots
1100        """
1101        # item - HashableStandardItems of active plots
1102
1103        # {} -> 'Graph1' : HashableStandardItem()
1104        current_plot_items = {}
1105        for plot_name in PlotHelper.currentPlots():
1106            current_plot_items[plot_name] = PlotHelper.plotById(plot_name).item
1107
1108        # item and its hashable children
1109        items_being_deleted = []
1110        if item.rowCount() > 0:
1111            items_being_deleted = [item.child(n) for n in range(item.rowCount())
1112                                   if isinstance(item.child(n), GuiUtils.HashableStandardItem)]
1113        items_being_deleted.append(item)
1114        # Add the parent in case a child is selected
1115        if isinstance(item.parent(), GuiUtils.HashableStandardItem):
1116            items_being_deleted.append(item.parent())
1117
1118        # Compare plot items and items to delete
1119        plots_to_close = set(current_plot_items.values()) & set(items_being_deleted)
1120
1121        for plot_item in plots_to_close:
1122            for plot_name in current_plot_items.keys():
1123                if plot_item == current_plot_items[plot_name]:
1124                    plotter = PlotHelper.plotById(plot_name)
1125                    # try to delete the plot
1126                    try:
1127                        plotter.close()
1128                        #self.parent.workspace().removeSubWindow(plotter)
1129                        self.plot_widgets[plot_name].close()
1130                        self.plot_widgets.pop(plot_name, None)
1131                    except AttributeError as ex:
1132                        logging.error("Closing of %s failed:\n %s" % (plot_name, str(ex)))
1133
1134        pass # debugger anchor
1135
[8ac3551]1136    def onAnalysisUpdate(self, new_perspective=""):
1137        """
1138        Update the perspective combo index based on passed string
1139        """
1140        assert new_perspective in Perspectives.PERSPECTIVES.keys()
1141        self.cbFitting.blockSignals(True)
1142        self.cbFitting.setCurrentIndex(self.cbFitting.findText(new_perspective))
1143        self.cbFitting.blockSignals(False)
1144        pass
1145
[a281ab8]1146    def loadComplete(self, output):
[f721030]1147        """
1148        Post message to status bar and update the data manager
1149        """
[8cb6cd6]1150        assert isinstance(output, tuple)
[e540cd2]1151
[9e426c1]1152        # Reset the model so the view gets updated.
[7969b9c]1153        #self.model.reset()
[e540cd2]1154        self.communicator.progressBarUpdateSignal.emit(-1)
[a281ab8]1155
1156        output_data = output[0]
1157        message = output[1]
[f721030]1158        # Notify the manager of the new data available
[f82ab8c]1159        self.communicator.statusBarUpdateSignal.emit(message)
1160        self.communicator.fileDataReceivedSignal.emit(output_data)
[a281ab8]1161        self.manager.add_data(data_list=output_data)
[f721030]1162
[7969b9c]1163    def loadFailed(self, reason):
[7fb471d]1164        print("File Load Failed with:\n", reason)
1165        pass
1166
[f721030]1167    def updateModel(self, data, p_file):
1168        """
[481ff26]1169        Add data and Info fields to the model item
[f721030]1170        """
1171        # Structure of the model
1172        # checkbox + basename
[481ff26]1173        #     |-------> Data.D object
[f721030]1174        #     |-------> Info
1175        #                 |----> Title:
1176        #                 |----> Run:
1177        #                 |----> Type:
1178        #                 |----> Path:
1179        #                 |----> Process
1180        #                          |-----> process[0].name
[28a84e9]1181        #     |-------> THEORIES
[f721030]1182
1183        # Top-level item: checkbox with label
[6a3e1fe]1184        checkbox_item = GuiUtils.HashableStandardItem()
[f721030]1185        checkbox_item.setCheckable(True)
1186        checkbox_item.setCheckState(QtCore.Qt.Checked)
1187        checkbox_item.setText(os.path.basename(p_file))
1188
1189        # Add the actual Data1D/Data2D object
[6a3e1fe]1190        object_item = GuiUtils.HashableStandardItem()
[b3e8629]1191        object_item.setData(data)
[f721030]1192
[488c49d]1193        checkbox_item.setChild(0, object_item)
1194
[f721030]1195        # Add rows for display in the view
[0cd8612]1196        info_item = GuiUtils.infoFromData(data)
[f721030]1197
[28a84e9]1198        # Set info_item as the first child
[488c49d]1199        checkbox_item.setChild(1, info_item)
[f721030]1200
[28a84e9]1201        # Caption for the theories
1202        checkbox_item.setChild(2, QtGui.QStandardItem("THEORIES"))
1203
[f721030]1204        # New row in the model
[7969b9c]1205        self.model.beginResetModel()
[f721030]1206        self.model.appendRow(checkbox_item)
[7969b9c]1207        self.model.endResetModel()
[481ff26]1208
[5032ea68]1209    def updateModelFromPerspective(self, model_item):
1210        """
[a281ab8]1211        Receive an update model item from a perspective
1212        Make sure it is valid and if so, replace it in the model
[5032ea68]1213        """
[a281ab8]1214        # Assert the correct type
[0cd8612]1215        if not isinstance(model_item, QtGui.QStandardItem):
[5032ea68]1216            msg = "Wrong data type returned from calculations."
[b3e8629]1217            raise AttributeError(msg)
[a281ab8]1218
[1042dba]1219        # TODO: Assert other properties
[a281ab8]1220
[5032ea68]1221        # Reset the view
[7969b9c]1222        ##self.model.reset()
[5032ea68]1223        # Pass acting as a debugger anchor
1224        pass
[481ff26]1225
[5236449]1226    def updateTheoryFromPerspective(self, model_item):
1227        """
1228        Receive an update theory item from a perspective
1229        Make sure it is valid and if so, replace/add in the model
1230        """
1231        # Assert the correct type
1232        if not isinstance(model_item, QtGui.QStandardItem):
1233            msg = "Wrong data type returned from calculations."
[b3e8629]1234            raise AttributeError(msg)
[5236449]1235
1236        # Check if there are any other items for this tab
1237        # If so, delete them
[d6e38661]1238        current_tab_name = model_item.text()
1239        for current_index in range(self.theory_model.rowCount()):
[d6b8a1d]1240            #if current_tab_name in self.theory_model.item(current_index).text():
[d6e38661]1241            if current_tab_name == self.theory_model.item(current_index).text():
1242                return
1243                self.theory_model.removeRow(current_index)
1244                break
[d6b8a1d]1245
[d6e38661]1246        # send in the new item
[5236449]1247        self.theory_model.appendRow(model_item)
1248
Note: See TracBrowser for help on using the repository browser.