source: sasview/src/sas/qtgui/DataExplorer.py @ 488c49d

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 488c49d was 488c49d, checked in by Piotr Rozyczko <piotr.rozyczko@…>, 8 years ago

Hold data objects in model. Added more Data Explorer functionality. Added unit tests.

  • Property mode set to 100755
File size: 14.0 KB
Line 
1# global
2import sys
3import os
4import logging
5
6from PyQt4 import QtCore
7from PyQt4 import QtGui
8from PyQt4 import QtWebKit
9from twisted.internet import threads
10
11# SAS
12from GuiUtils import *
13from sas.sascalc.dataloader.loader import Loader
14from sas.sasgui.guiframe.data_manager import DataManager
15
16# UI
17from UI.TabbedFileLoadUI import DataLoadWidget
18
19class DataExplorerWindow(DataLoadWidget):
20    # The controller which is responsible for managing signal slots connections
21    # for the gui and providing an interface to the data model.
22
23    def __init__(self, parent=None, guimanager=None):
24        super(DataExplorerWindow, self).__init__(parent)
25
26        # Main model for keeping loaded data
27        self.model = QtGui.QStandardItemModel(self)
28        self._default_save_location = None
29
30        # GuiManager is the actual parent, but we needed to also pass the QMainWindow
31        # in order to set the widget parentage properly.
32        self.parent = guimanager
33        self.loader = Loader()
34        self.manager = DataManager()
35
36        # Connect the buttons
37        self.cmdLoad.clicked.connect(self.loadFile)
38        self.cmdDelete.clicked.connect(self.deleteFile)
39        self.cmdSendTo.clicked.connect(self.sendData)
40
41        # Connect the comboboxes
42        self.cbSelect.currentIndexChanged.connect(self.selectData)
43
44        # Communicator for signal definitions
45        self.communicate = self.parent.communicator()
46
47        # Proxy model for showing a subset of Data1D/Data2D content
48        self.proxy = QtGui.QSortFilterProxyModel(self)
49        self.proxy.setSourceModel(self.model)
50
51        # The Data viewer is QTreeView showing the proxy model
52        self.treeView.setModel(self.proxy)
53
54    def loadFile(self, event):
55        """
56        Called when the "Load" button pressed.
57        Opens the Qt "Open File..." dialog
58        """
59        path_str = self.chooseFiles()
60        if not path_str:
61            return
62
63        # Notify the manager of the new data available
64        self.communicate.fileReadSignal.emit(path_str)
65
66        # Read in the data from chosen file(s)
67        self.readData(path_str)
68
69        return
70
71    def deleteFile(self, event):
72        """
73        """
74        # Figure out which rows are checked
75
76        # Delete these rows from the model
77
78        # Call data_manager update with delete_data()
79
80        pass
81
82    def sendData(self, event):
83        """
84        """
85        # Figure out which rows are checked
86
87        # Dig up data from model
88        # To get the original Data1D object back use:
89        # object_item.data().toPyObject()
90
91
92        # Which perspective has been selected?
93
94        # New plot or appended?
95
96        # Notify the GuiManager about the send request
97        # updatePerspectiveWithDataSignal()
98        pass
99
100    def chooseFiles(self):
101        """
102        """
103        # List of known extensions
104        wlist = self.getWlist()
105
106        # Location is automatically saved - no need to keep track of the last dir
107        # TODO: is it really?
108        paths = QtGui.QFileDialog.getOpenFileName(self, "Choose a file", "", wlist)
109        if paths is None:
110            return
111
112        if paths.__class__.__name__ != "list":
113            paths = [paths]
114
115        path_str=[]
116        for path in paths:
117            if str(path):
118                path_str.append(str(path))
119
120        return path_str
121
122    def readData(self, path):
123        """
124        verbatim copy/paste from
125            sasgui\guiframe\local_perspectives\data_loader\data_loader.py
126        slightly modified for clarity
127        """
128        message = ""
129        log_msg = ''
130        output = {}
131        any_error = False
132        data_error = False
133        error_message = ""
134        for p_file in path:
135            info = "info"
136            basename = os.path.basename(p_file)
137            _, extension = os.path.splitext(basename)
138            if extension.lower() in EXTENSIONS:
139                any_error = True
140                log_msg = "Data Loader cannot "
141                log_msg += "load: %s\n" % str(p_file)
142                log_msg += """Please try to open that file from "open project" """
143                log_msg += """or "open analysis" menu\n"""
144                error_message = log_msg + "\n"
145                logging.info(log_msg)
146                continue
147
148            try:
149                message = "Loading Data... " + str(p_file) + "\n"
150
151                # change this to signal notification in GuiManager
152                self.communicate.statusBarUpdateSignal.emit(message)
153
154                # threaded file load
155                # load_thread = threads.deferToThread(self.loadThread, p_file)
156                # Add deferred callback for call return
157                # load_thread.addCallback(self.plotResult)
158
159                output_objects = self.loader.load(p_file)
160
161                # Some loaders return a list and some just a single Data1D object.
162                # Standardize.
163                if not isinstance(output_objects, list):
164                    output_objects = [output_objects]
165
166                for item in output_objects:
167                    # cast sascalc.dataloader.data_info.Data1D into sasgui.guiframe.dataFitting.Data1D
168                    # TODO : Fix it
169                    new_data = self.manager.create_gui_data(item, p_file)
170                    output[new_data.id] = new_data
171                    self.updateModel(new_data, p_file)
172
173                    if hasattr(item, 'errors'):
174                        for error_data in item.errors:
175                            data_error = True
176                            message += "\tError: {0}\n".format(error_data)
177                    else:
178                        logging.error("Loader returned an invalid object:\n %s" % str(item))
179                        data_error = True
180
181            except:
182                logging.error(sys.exc_value)
183                any_error = True
184            if any_error or error_message != "":
185                if error_message == "":
186                    error = "Error: " + str(sys.exc_info()[1]) + "\n"
187                    error += "while loading Data: \n%s\n" % str(basename)
188                    error_message += "The data file you selected could not be loaded.\n"
189                    error_message += "Make sure the content of your file"
190                    error_message += " is properly formatted.\n\n"
191                    error_message += "When contacting the SasView team, mention the"
192                    error_message += " following:\n%s" % str(error)
193                elif data_error:
194                    base_message = "Errors occurred while loading "
195                    base_message += "{0}\n".format(basename)
196                    base_message += "The data file loaded but with errors.\n"
197                    error_message = base_message + error_message
198                else:
199                    error_message += "%s\n" % str(p_file)
200                info = "error"
201       
202        if any_error or error_message:
203            # self.loadUpdate(output=output, message=error_message, info=info)
204            self.communicate.statusBarUpdateSignal.emit(error_message)
205
206        else:
207            message = "Loading Data Complete! "
208        message += log_msg
209        self.loadComplete(output=output, message=message)
210
211    def getWlist(self):
212        """
213        """
214        # Display the Qt Load File module
215        cards = self.loader.get_wildcards()
216
217        # get rid of the wx remnant in wildcards
218        # TODO: modify sasview loader get_wildcards method, after merge,
219        # so this kludge can be avoided
220        new_cards = []
221        for item in cards:
222            new_cards.append(item[:item.find("|")])
223        wlist = ';;'.join(new_cards)
224
225        return wlist
226
227    def selectData(self, index):
228        """
229        Callback method for modifying the TreeView on Selection Options change
230        """
231        if not isinstance(index, int):
232            msg = "Incorrect type passed to DataExplorer.selectData()"
233            raise AttributeError, msg
234
235        # Respond appropriately
236        if index == 0:
237            # Select All
238            for index in range(self.model.rowCount()):
239                item = self.model.item(index)
240                if item.isCheckable() and item.checkState() == QtCore.Qt.Unchecked:
241                    item.setCheckState(QtCore.Qt.Checked)
242        elif index == 1:
243            # De-select All
244            for index in range(self.model.rowCount()):
245                item = self.model.item(index)
246                if item.isCheckable() and item.checkState() == QtCore.Qt.Checked:
247                    item.setCheckState(QtCore.Qt.Unchecked)
248
249        elif index == 2:
250            # Select All 1-D
251            for index in range(self.model.rowCount()):
252                item = self.model.item(index)
253                item.setCheckState(QtCore.Qt.Unchecked)
254
255                try:
256                    is1D = item.child(0).data().toPyObject().__class__.__name__ == 'Data1D'
257                except AttributeError:
258                    msg = "Bad structure of the data model."
259                    raise RuntimeError, msg
260
261                if is1D:
262                    item.setCheckState(QtCore.Qt.Checked)
263
264        elif index == 3:
265            # Unselect All 1-D
266            for index in range(self.model.rowCount()):
267                item = self.model.item(index)
268
269                try:
270                    is1D = item.child(0).data().toPyObject().__class__.__name__ == 'Data1D'
271                except AttributeError:
272                    msg = "Bad structure of the data model."
273                    raise RuntimeError, msg
274
275                if item.isCheckable() and item.checkState() == QtCore.Qt.Checked and is1D:
276                    item.setCheckState(QtCore.Qt.Unchecked)
277
278        elif index == 4:
279            # Select All 2-D
280            for index in range(self.model.rowCount()):
281                item = self.model.item(index)
282                item.setCheckState(QtCore.Qt.Unchecked)
283                try:
284                    is2D = item.child(0).data().toPyObject().__class__.__name__ == 'Data2D'
285                except AttributeError:
286                    msg = "Bad structure of the data model."
287                    raise RuntimeError, msg
288
289                if is2D:
290                    item.setCheckState(QtCore.Qt.Checked)
291
292        elif index == 5:
293            # Unselect All 2-D
294            for index in range(self.model.rowCount()):
295                item = self.model.item(index)
296
297                try:
298                    is2D = item.child(0).data().toPyObject().__class__.__name__ == 'Data2D'
299                except AttributeError:
300                    msg = "Bad structure of the data model."
301                    raise RuntimeError, msg
302
303                if item.isCheckable() and item.checkState() == QtCore.Qt.Checked and is2D:
304                    item.setCheckState(QtCore.Qt.Unchecked)
305
306        else:
307            msg = "Incorrect value in the Selection Option"
308            # Change this to a proper logging action
309            raise Exception, msg
310
311
312    def loadComplete(self, output, message=""):
313        """
314        Post message to status bar and update the data manager
315        """
316        # Notify the manager of the new data available
317        self.communicate.statusBarUpdateSignal.emit(message)
318        self.communicate.fileDataReceivedSignal.emit(output)
319
320        self.manager.add_data(data_list=output)
321
322    def updateModel(self, data, p_file):
323        """
324        """
325        # Structure of the model
326        # checkbox + basename
327        #     |-------> Info
328        #                 |----> Data.D object
329        #                 |----> Title:
330        #                 |----> Run:
331        #                 |----> Type:
332        #                 |----> Path:
333        #                 |----> Process
334        #                          |-----> process[0].name
335        #
336
337        # Top-level item: checkbox with label
338        checkbox_item = QtGui.QStandardItem(True)
339        checkbox_item.setCheckable(True)
340        checkbox_item.setCheckState(QtCore.Qt.Checked)
341        checkbox_item.setText(os.path.basename(p_file))
342
343        # Add "Info" item
344        info_item = QtGui.QStandardItem("Info")
345
346        # Add the actual Data1D/Data2D object
347        object_item = QtGui.QStandardItem()
348        object_item.setData(QtCore.QVariant(data))
349
350        checkbox_item.setChild(0, object_item)
351
352        # Add rows for display in the view
353        self.addExtraRows(info_item, data)
354
355        # Set info_item as the only child
356        checkbox_item.setChild(1, info_item)
357
358        # New row in the model
359        self.model.appendRow(checkbox_item)
360       
361        # Don't show "empty" rows with data objects
362        self.proxy.setFilterRegExp(r"[^()]")
363
364
365    def addExtraRows(self, info_item, data):
366        """
367        """
368        title_item   = QtGui.QStandardItem("Title: "      + data.title)
369        run_item     = QtGui.QStandardItem("Run: "        + str(data.run))
370        type_item    = QtGui.QStandardItem("Type: "       + str(data.__class__.__name__))
371        path_item    = QtGui.QStandardItem("Path: "       + data.path)
372        instr_item   = QtGui.QStandardItem("Instrument: " + data.instrument)
373        process_item = QtGui.QStandardItem("Process")
374        if isinstance(data.process, list) and data.process:
375            for process in data.process:
376                process_date = process.date
377                process_date_item = QtGui.QStandardItem("Date: " + process_date)
378                process_item.appendRow(process_date_item)
379
380                process_descr = process.description
381                process_descr_item = QtGui.QStandardItem("Description: " + process_descr)
382                process_item.appendRow(process_descr_item)
383
384                process_name = process.name
385                process_name_item = QtGui.QStandardItem("Name: " + process_name)
386                process_item.appendRow(process_name_item)
387
388        info_item.appendRow(title_item)
389        info_item.appendRow(run_item)
390        info_item.appendRow(type_item)
391        info_item.appendRow(path_item)
392        info_item.appendRow(instr_item)
393        info_item.appendRow(process_item)
394       
395
396if __name__ == "__main__":
397    app = QtGui.QApplication([])
398    dlg = DataExplorerWindow()
399    dlg.show()
400    sys.exit(app.exec_())
Note: See TracBrowser for help on using the repository browser.