source: sasview/src/sas/qtgui/DataExplorer.py @ 55d89f8

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

Context menu 3D plotting

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