source: sasview/src/sas/qtgui/DataExplorer.py @ 630155bd

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 630155bd was 630155bd, checked in by davidm, 8 years ago

implementation of loadProject and saveProject

  • Property mode set to 100755
File size: 30.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 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 filename item
327        and copying it to a separate top-level item.
328        """
329        # Figure out which _inner_ 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.model.rowCount():
334            outer_index += 1
335            outer_item = self.model.item(outer_index)
336            if not outer_item:
337                continue
338            # Should be just two rows: data and Info
339            for inner_index in xrange(outer_item.rowCount()):
340                subitem = outer_item.child(inner_index)
341                if subitem and \
342                   subitem.isCheckable() and \
343                   subitem.checkState() == QtCore.Qt.Checked:
344                    theories_copied += 1
345                    new_item = self.recursivelyCloneItem(subitem)
346                    # Append a "unique" descriptor to the name
347                    time_bit = str(time.time())[7:-1].replace('.', '')
348                    new_name = new_item.text() + '_@' + time_bit
349                    new_item.setText(new_name)
350                    self.theory_model.appendRow(new_item)
351            self.theory_model.reset()
352
353        freeze_msg = ""
354        if theories_copied == 0:
355            return
356        elif theories_copied == 1:
357            freeze_msg = "1 theory copied to the Theory tab as a data set"
358        elif theories_copied > 1:
359            freeze_msg = "%i theories copied to the Theory tab as data sets" % theories_copied
360        else:
361            freeze_msg = "Unexpected number of theories copied: %i" % theories_copied
362            raise AttributeError, freeze_msg
363        self.communicator.statusBarUpdateSignal.emit(freeze_msg)
364        # Actively switch tabs
365        self.setCurrentIndex(1)
366
367    def recursivelyCloneItem(self, item):
368        """
369        Clone QStandardItem() object
370        """
371        new_item = item.clone()
372        # clone doesn't do deepcopy :(
373        for child_index in xrange(item.rowCount()):
374            child_item = self.recursivelyCloneItem(item.child(child_index))
375            new_item.setChild(child_index, child_item)
376        return new_item
377
378    def updateGraphCombo(self, graph_list):
379        """
380        Modify Graph combo box on graph add/delete
381        """
382        orig_text = self.cbgraph.currentText()
383        self.cbgraph.clear()
384        graph_titles = []
385        for graph in graph_list:
386            graph_titles.append("Graph"+str(graph))
387        self.cbgraph.insertItems(0, graph_titles)
388        ind = self.cbgraph.findText(orig_text)
389        if ind > 0:
390            self.cbgraph.setCurrentIndex(ind)
391        pass
392
393    def newPlot(self):
394        """
395        Create a new matplotlib chart from selected data
396
397        TODO: Add 2D-functionality
398        """
399        plots = GuiUtils.plotsFromCheckedItems(self.model)
400
401        # Call show on requested plots
402        new_plot = Plotter(self)
403        for plot_set in plots:
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
438        for plot_set in new_plots:
439            old_plot.data(plot_set)
440            old_plot.plot()
441
442    def chooseFiles(self):
443        """
444        Shows the Open file dialog and returns the chosen path(s)
445        """
446        # List of known extensions
447        wlist = self.getWlist()
448
449        # Location is automatically saved - no need to keep track of the last dir
450        # But only with Qt built-in dialog (non-platform native)
451        paths = QtGui.QFileDialog.getOpenFileNames(self, "Choose a file", "",
452                wlist, None, QtGui.QFileDialog.DontUseNativeDialog)
453        if paths is None:
454            return
455
456        if isinstance(paths, QtCore.QStringList):
457            paths = [str(f) for f in paths]
458
459        if not isinstance(paths, list):
460            paths = [paths]
461
462        return paths
463
464    def readData(self, path):
465        """
466        verbatim copy-paste from
467           sasgui.guiframe.local_perspectives.data_loader.data_loader.py
468        slightly modified for clarity
469        """
470        message = ""
471        log_msg = ''
472        output = {}
473        any_error = False
474        data_error = False
475        error_message = ""
476
477        number_of_files = len(path)
478        self.communicator.progressBarUpdateSignal.emit(0.0)
479
480        for index, p_file in enumerate(path):
481            basename = os.path.basename(p_file)
482            _, extension = os.path.splitext(basename)
483            if extension.lower() in GuiUtils.EXTENSIONS:
484                any_error = True
485                log_msg = "Data Loader cannot "
486                log_msg += "load: %s\n" % str(p_file)
487                log_msg += """Please try to open that file from "open project" """
488                log_msg += """or "open analysis" menu\n"""
489                error_message = log_msg + "\n"
490                logging.info(log_msg)
491                continue
492
493            try:
494                message = "Loading Data... " + str(basename) + "\n"
495
496                # change this to signal notification in GuiManager
497                self.communicator.statusBarUpdateSignal.emit(message)
498
499                output_objects = self.loader.load(p_file)
500
501                # Some loaders return a list and some just a single Data1D object.
502                # Standardize.
503                if not isinstance(output_objects, list):
504                    output_objects = [output_objects]
505
506                for item in output_objects:
507                    # cast sascalc.dataloader.data_info.Data1D into
508                    # sasgui.guiframe.dataFitting.Data1D
509                    # TODO : Fix it
510                    new_data = self.manager.create_gui_data(item, p_file)
511                    output[new_data.id] = new_data
512
513                    # Model update should be protected
514                    self.mutex.lock()
515                    self.updateModel(new_data, p_file)
516                    self.model.reset()
517                    QtGui.qApp.processEvents()
518                    self.mutex.unlock()
519
520                    if hasattr(item, 'errors'):
521                        for error_data in item.errors:
522                            data_error = True
523                            message += "\tError: {0}\n".format(error_data)
524                    else:
525
526                        logging.error("Loader returned an invalid object:\n %s" % str(item))
527                        data_error = True
528
529            except Exception as ex:
530                logging.error(sys.exc_value)
531
532                any_error = True
533            if any_error or error_message != "":
534                if error_message == "":
535                    error = "Error: " + str(sys.exc_info()[1]) + "\n"
536                    error += "while loading Data: \n%s\n" % str(basename)
537                    error_message += "The data file you selected could not be loaded.\n"
538                    error_message += "Make sure the content of your file"
539                    error_message += " is properly formatted.\n\n"
540                    error_message += "When contacting the SasView team, mention the"
541                    error_message += " following:\n%s" % str(error)
542                elif data_error:
543                    base_message = "Errors occurred while loading "
544                    base_message += "{0}\n".format(basename)
545                    base_message += "The data file loaded but with errors.\n"
546                    error_message = base_message + error_message
547                else:
548                    error_message += "%s\n" % str(p_file)
549
550            current_percentage = int(100.0* index/number_of_files)
551            self.communicator.progressBarUpdateSignal.emit(current_percentage)
552
553        if any_error or error_message:
554            logging.error(error_message)
555            status_bar_message = "Errors occurred while loading %s" % format(basename)
556            self.communicator.statusBarUpdateSignal.emit(status_bar_message)
557
558        else:
559            message = "Loading Data Complete! "
560        message += log_msg
561        # Notify the progress bar that the updates are over.
562        self.communicator.progressBarUpdateSignal.emit(-1)
563
564        return output, message
565
566    def getWlist(self):
567        """
568        Wildcards of files we know the format of.
569        """
570        # Display the Qt Load File module
571        cards = self.loader.get_wildcards()
572
573        # get rid of the wx remnant in wildcards
574        # TODO: modify sasview loader get_wildcards method, after merge,
575        # so this kludge can be avoided
576        new_cards = []
577        for item in cards:
578            new_cards.append(item[:item.find("|")])
579        wlist = ';;'.join(new_cards)
580
581        return wlist
582
583    def selectData(self, index):
584        """
585        Callback method for modifying the TreeView on Selection Options change
586        """
587        if not isinstance(index, int):
588            msg = "Incorrect type passed to DataExplorer.selectData()"
589            raise AttributeError, msg
590
591        # Respond appropriately
592        if index == 0:
593            # Select All
594            for index in range(self.model.rowCount()):
595                item = self.model.item(index)
596                if item.isCheckable() and item.checkState() == QtCore.Qt.Unchecked:
597                    item.setCheckState(QtCore.Qt.Checked)
598        elif index == 1:
599            # De-select All
600            for index in range(self.model.rowCount()):
601                item = self.model.item(index)
602                if item.isCheckable() and item.checkState() == QtCore.Qt.Checked:
603                    item.setCheckState(QtCore.Qt.Unchecked)
604
605        elif index == 2:
606            # Select All 1-D
607            for index in range(self.model.rowCount()):
608                item = self.model.item(index)
609                item.setCheckState(QtCore.Qt.Unchecked)
610
611                try:
612                    is1D = isinstance(item.child(0).data().toPyObject(), Data1D)
613                except AttributeError:
614                    msg = "Bad structure of the data model."
615                    raise RuntimeError, msg
616
617                if is1D:
618                    item.setCheckState(QtCore.Qt.Checked)
619
620        elif index == 3:
621            # Unselect All 1-D
622            for index in range(self.model.rowCount()):
623                item = self.model.item(index)
624
625                try:
626                    is1D = isinstance(item.child(0).data().toPyObject(), Data1D)
627                except AttributeError:
628                    msg = "Bad structure of the data model."
629                    raise RuntimeError, msg
630
631                if item.isCheckable() and item.checkState() == QtCore.Qt.Checked and is1D:
632                    item.setCheckState(QtCore.Qt.Unchecked)
633
634        elif index == 4:
635            # Select All 2-D
636            for index in range(self.model.rowCount()):
637                item = self.model.item(index)
638                item.setCheckState(QtCore.Qt.Unchecked)
639                try:
640                    is2D = isinstance(item.child(0).data().toPyObject(), Data2D)
641                except AttributeError:
642                    msg = "Bad structure of the data model."
643                    raise RuntimeError, msg
644
645                if is2D:
646                    item.setCheckState(QtCore.Qt.Checked)
647
648        elif index == 5:
649            # Unselect All 2-D
650            for index in range(self.model.rowCount()):
651                item = self.model.item(index)
652
653                try:
654                    is2D = isinstance(item.child(0).data().toPyObject(), Data2D)
655                except AttributeError:
656                    msg = "Bad structure of the data model."
657                    raise RuntimeError, msg
658
659                if item.isCheckable() and item.checkState() == QtCore.Qt.Checked and is2D:
660                    item.setCheckState(QtCore.Qt.Unchecked)
661
662        else:
663            msg = "Incorrect value in the Selection Option"
664            # Change this to a proper logging action
665            raise Exception, msg
666
667    def contextMenu(self):
668        """
669        Define actions and layout of the right click context menu
670        """
671        # Create a custom menu based on actions defined in the UI file
672        self.context_menu = QtGui.QMenu(self)
673        self.context_menu.addAction(self.actionDataInfo)
674        self.context_menu.addAction(self.actionSaveAs)
675        self.context_menu.addAction(self.actionQuickPlot)
676        self.context_menu.addSeparator()
677        self.context_menu.addAction(self.actionQuick3DPlot)
678        self.context_menu.addAction(self.actionEditMask)
679
680        # Define the callbacks
681        self.actionDataInfo.triggered.connect(self.showDataInfo)
682        self.actionSaveAs.triggered.connect(self.saveDataAs)
683        self.actionQuickPlot.triggered.connect(self.quickDataPlot)
684        self.actionQuick3DPlot.triggered.connect(self.quickData3DPlot)
685        self.actionEditMask.triggered.connect(self.showEditDataMask)
686
687    def onCustomContextMenu(self, position):
688        """
689        Show the right-click context menu in the data treeview
690        """
691        index = self.treeView.indexAt(position)
692        if index.isValid():
693            model_item = self.model.itemFromIndex(self.data_proxy.mapToSource(index))
694            # Find the mapped index
695            orig_index = model_item.isCheckable()
696            if orig_index:
697                # Check the data to enable/disable actions
698                is_2D = isinstance(model_item.child(0).data().toPyObject(), Data2D)
699                self.actionQuick3DPlot.setEnabled(is_2D)
700                self.actionEditMask.setEnabled(is_2D)
701                # Fire up the menu
702                self.context_menu.exec_(self.treeView.mapToGlobal(position))
703
704    def showDataInfo(self):
705        """
706        Show a simple read-only text edit with data information.
707        """
708        index = self.treeView.selectedIndexes()[0]
709        model_item = self.model.itemFromIndex(self.data_proxy.mapToSource(index))
710        data = model_item.child(0).data().toPyObject()
711        if isinstance(data, Data1D):
712            text_to_show = GuiUtils.retrieveData1d(data)
713            # Hardcoded sizes to enable full width rendering with default font
714            self.txt_widget.resize(420,600)
715        else:
716            text_to_show = GuiUtils.retrieveData2d(data)
717            # Hardcoded sizes to enable full width rendering with default font
718            self.txt_widget.resize(700,600)
719
720        self.txt_widget.setReadOnly(True)
721        self.txt_widget.setWindowFlags(QtCore.Qt.Window)
722        self.txt_widget.setWindowIcon(QtGui.QIcon(":/res/ball.ico"))
723        self.txt_widget.setWindowTitle("Data Info: %s" % data.filename)
724        self.txt_widget.insertPlainText(text_to_show)
725
726        self.txt_widget.show()
727        # Move the slider all the way up, if present
728        vertical_scroll_bar = self.txt_widget.verticalScrollBar()
729        vertical_scroll_bar.triggerAction(QtGui.QScrollBar.SliderToMinimum)
730
731    def saveDataAs(self):
732        """
733        Save the data points as either txt or xml
734        """
735        index = self.treeView.selectedIndexes()[0]
736        model_item = self.model.itemFromIndex(self.data_proxy.mapToSource(index))
737        data = model_item.child(0).data().toPyObject()
738        if isinstance(data, Data1D):
739            GuiUtils.saveData1D(data)
740        else:
741            GuiUtils.saveData2D(data)
742
743    def quickDataPlot(self):
744        """
745        Frozen plot - display an image of the plot
746        """
747        index = self.treeView.selectedIndexes()[0]
748        model_item = self.model.itemFromIndex(self.data_proxy.mapToSource(index))
749        data = model_item.child(0).data().toPyObject()
750
751        dimension = 1 if isinstance(data, Data1D) else 2
752
753        # TODO: Replace this with the proper MaskPlotPanel from plottools
754        new_plot = Plotter(self)
755        new_plot.data(data)
756        new_plot.plot(marker='o', linestyle='')
757
758        # Update the global plot counter
759        title = "Plot " + data.name
760        new_plot.setWindowTitle(title)
761
762        # Show the plot
763        new_plot.show()
764
765    def quickData3DPlot(self):
766        """
767        """
768        print "quickData3DPlot"
769        pass
770
771    def showEditDataMask(self):
772        """
773        """
774        print "showEditDataMask"
775        pass
776
777    def loadComplete(self, output):
778        """
779        Post message to status bar and update the data manager
780        """
781        assert isinstance(output, tuple)
782
783        # Reset the model so the view gets updated.
784        self.model.reset()
785        self.communicator.progressBarUpdateSignal.emit(-1)
786
787        output_data = output[0]
788        message = output[1]
789        # Notify the manager of the new data available
790        self.communicator.statusBarUpdateSignal.emit(message)
791        self.communicator.fileDataReceivedSignal.emit(output_data)
792        self.manager.add_data(data_list=output_data)
793
794    def updateModel(self, data, p_file):
795        """
796        Add data and Info fields to the model item
797        """
798        # Structure of the model
799        # checkbox + basename
800        #     |-------> Data.D object
801        #     |-------> Info
802        #                 |----> Title:
803        #                 |----> Run:
804        #                 |----> Type:
805        #                 |----> Path:
806        #                 |----> Process
807        #                          |-----> process[0].name
808        #     |-------> THEORIES
809
810        # Top-level item: checkbox with label
811        checkbox_item = QtGui.QStandardItem(True)
812        checkbox_item.setCheckable(True)
813        checkbox_item.setCheckState(QtCore.Qt.Checked)
814        checkbox_item.setText(os.path.basename(p_file))
815
816        # Add the actual Data1D/Data2D object
817        object_item = QtGui.QStandardItem()
818        object_item.setData(QtCore.QVariant(data))
819
820        checkbox_item.setChild(0, object_item)
821
822        # Add rows for display in the view
823        info_item = GuiUtils.infoFromData(data)
824
825        # Set info_item as the first child
826        checkbox_item.setChild(1, info_item)
827
828        # Caption for the theories
829        checkbox_item.setChild(2, QtGui.QStandardItem("THEORIES"))
830
831        # New row in the model
832        self.model.appendRow(checkbox_item)
833
834
835    def updateModelFromPerspective(self, model_item):
836        """
837        Receive an update model item from a perspective
838        Make sure it is valid and if so, replace it in the model
839        """
840        # Assert the correct type
841        if not isinstance(model_item, QtGui.QStandardItem):
842            msg = "Wrong data type returned from calculations."
843            raise AttributeError, msg
844
845        # TODO: Assert other properties
846
847        # Reset the view
848        self.model.reset()
849
850        # Pass acting as a debugger anchor
851        pass
852
853
854if __name__ == "__main__":
855    app = QtGui.QApplication([])
856    dlg = DataExplorerWindow()
857    dlg.show()
858    sys.exit(app.exec_())
Note: See TracBrowser for help on using the repository browser.