source: sasview/src/sas/qtgui/DataExplorer.py @ ca8b853

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

Modified behaviour of theory freeze

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