source: sasview/src/sas/qtgui/DataExplorer.py @ 31c5b58

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

Refactored to allow running with run.py.
Minor fixes to plotting.

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