source: sasview/src/sas/qtgui/DataExplorer.py @ 14d9c7b

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

2D plotter cleanup

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