source: sasview/src/sas/qtgui/Utilities/GuiUtils.py @ 3b8cc00

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

Code review changes

  • Property mode set to 100644
File size: 31.5 KB
Line 
1"""
2Global defaults and various utility functions usable by the general GUI
3"""
4
5import os
6import re
7import sys
8import imp
9import warnings
10import webbrowser
11import urllib.parse
12
13import numpy as np
14
15warnings.simplefilter("ignore")
16import logging
17
18from PyQt5 import QtCore
19from PyQt5 import QtGui
20from PyQt5 import QtWidgets
21
22from periodictable import formula as Formula
23from sas.qtgui.Plotting import DataTransform
24from sas.qtgui.Plotting.ConvertUnits import convertUnit
25from sas.qtgui.Plotting.PlotterData import Data1D
26from sas.qtgui.Plotting.PlotterData import Data2D
27from sas.sascalc.dataloader.loader import Loader
28from sas.qtgui.Utilities import CustomDir
29
30## TODO: CHANGE FOR SHIPPED PATH IN RELEASE
31if os.path.splitext(sys.argv[0])[1].lower() != ".py":
32        HELP_DIRECTORY_LOCATION = "doc"
33else:
34        HELP_DIRECTORY_LOCATION = "docs/sphinx-docs/build/html"
35IMAGES_DIRECTORY_LOCATION = HELP_DIRECTORY_LOCATION + "/_images"
36
37def get_app_dir():
38    """
39        The application directory is the one where the default custom_config.py
40        file resides.
41
42        :returns: app_path - the path to the applicatin directory
43    """
44    # First, try the directory of the executable we are running
45    app_path = sys.path[0]
46    if os.path.isfile(app_path):
47        app_path = os.path.dirname(app_path)
48    if os.path.isfile(os.path.join(app_path, "custom_config.py")):
49        app_path = os.path.abspath(app_path)
50        #logging.info("Using application path: %s", app_path)
51        return app_path
52
53    # Next, try the current working directory
54    if os.path.isfile(os.path.join(os.getcwd(), "custom_config.py")):
55        #logging.info("Using application path: %s", os.getcwd())
56        return os.path.abspath(os.getcwd())
57
58    # Finally, try the directory of the sasview module
59    # TODO: gui_manager will have to know about sasview until we
60    # clean all these module variables and put them into a config class
61    # that can be passed by sasview.py.
62    # logging.info(sys.executable)
63    # logging.info(str(sys.argv))
64    from sas import sasview as sasview
65    app_path = os.path.dirname(sasview.__file__)
66    # logging.info("Using application path: %s", app_path)
67    return app_path
68
69def get_user_directory():
70    """
71        Returns the user's home directory
72    """
73    userdir = os.path.join(os.path.expanduser("~"), ".sasview")
74    if not os.path.isdir(userdir):
75        os.makedirs(userdir)
76    return userdir
77
78def _find_local_config(confg_file, path):
79    """
80        Find configuration file for the current application
81    """
82    config_module = None
83    fObj = None
84    try:
85        fObj, path_config, descr = imp.find_module(confg_file, [path])
86        config_module = imp.load_module(confg_file, fObj, path_config, descr)
87    except ImportError:
88        pass
89        #logging.error("Error loading %s/%s: %s" % (path, confg_file, sys.exc_value))
90    except ValueError:
91        print("Value error")
92        pass
93    finally:
94        if fObj is not None:
95            fObj.close()
96    #logging.info("GuiManager loaded %s/%s" % (path, confg_file))
97    return config_module
98
99
100# Get APP folder
101PATH_APP = get_app_dir()
102DATAPATH = PATH_APP
103
104# Read in the local config, which can either be with the main
105# application or in the installation directory
106config = _find_local_config('local_config', PATH_APP)
107
108if config is None:
109    config = _find_local_config('local_config', os.getcwd())
110else:
111    pass
112
113c_conf_dir = CustomDir.setup_conf_dir(PATH_APP)
114
115custom_config = _find_local_config('custom_config', c_conf_dir)
116if custom_config is None:
117    custom_config = _find_local_config('custom_config', os.getcwd())
118    if custom_config is None:
119        msgConfig = "Custom_config file was not imported"
120
121#read some constants from config
122APPLICATION_STATE_EXTENSION = config.APPLICATION_STATE_EXTENSION
123APPLICATION_NAME = config.__appname__
124SPLASH_SCREEN_PATH = config.SPLASH_SCREEN_PATH
125WELCOME_PANEL_ON = config.WELCOME_PANEL_ON
126SPLASH_SCREEN_WIDTH = config.SPLASH_SCREEN_WIDTH
127SPLASH_SCREEN_HEIGHT = config.SPLASH_SCREEN_HEIGHT
128SS_MAX_DISPLAY_TIME = config.SS_MAX_DISPLAY_TIME
129if not WELCOME_PANEL_ON:
130    WELCOME_PANEL_SHOW = False
131else:
132    WELCOME_PANEL_SHOW = True
133try:
134    DATALOADER_SHOW = custom_config.DATALOADER_SHOW
135    TOOLBAR_SHOW = custom_config.TOOLBAR_SHOW
136    FIXED_PANEL = custom_config.FIXED_PANEL
137    if WELCOME_PANEL_ON:
138        WELCOME_PANEL_SHOW = custom_config.WELCOME_PANEL_SHOW
139    PLOPANEL_WIDTH = custom_config.PLOPANEL_WIDTH
140    DATAPANEL_WIDTH = custom_config.DATAPANEL_WIDTH
141    GUIFRAME_WIDTH = custom_config.GUIFRAME_WIDTH
142    GUIFRAME_HEIGHT = custom_config.GUIFRAME_HEIGHT
143    CONTROL_WIDTH = custom_config.CONTROL_WIDTH
144    CONTROL_HEIGHT = custom_config.CONTROL_HEIGHT
145    DEFAULT_PERSPECTIVE = custom_config.DEFAULT_PERSPECTIVE
146    CLEANUP_PLOT = custom_config.CLEANUP_PLOT
147    # custom open_path
148    open_folder = custom_config.DEFAULT_OPEN_FOLDER
149    if open_folder is not None and os.path.isdir(open_folder):
150        DEFAULT_OPEN_FOLDER = os.path.abspath(open_folder)
151    else:
152        DEFAULT_OPEN_FOLDER = PATH_APP
153except AttributeError:
154    DATALOADER_SHOW = True
155    TOOLBAR_SHOW = True
156    FIXED_PANEL = True
157    WELCOME_PANEL_SHOW = False
158    PLOPANEL_WIDTH = config.PLOPANEL_WIDTH
159    DATAPANEL_WIDTH = config.DATAPANEL_WIDTH
160    GUIFRAME_WIDTH = config.GUIFRAME_WIDTH
161    GUIFRAME_HEIGHT = config.GUIFRAME_HEIGHT
162    CONTROL_WIDTH = -1
163    CONTROL_HEIGHT = -1
164    DEFAULT_PERSPECTIVE = None
165    CLEANUP_PLOT = False
166    DEFAULT_OPEN_FOLDER = PATH_APP
167
168#DEFAULT_STYLE = config.DEFAULT_STYLE
169
170PLUGIN_STATE_EXTENSIONS = config.PLUGIN_STATE_EXTENSIONS
171OPEN_SAVE_MENU = config.OPEN_SAVE_PROJECT_MENU
172VIEW_MENU = config.VIEW_MENU
173EDIT_MENU = config.EDIT_MENU
174extension_list = []
175if APPLICATION_STATE_EXTENSION is not None:
176    extension_list.append(APPLICATION_STATE_EXTENSION)
177EXTENSIONS = PLUGIN_STATE_EXTENSIONS + extension_list
178try:
179    PLUGINS_WLIST = '|'.join(config.PLUGINS_WLIST)
180except AttributeError:
181    PLUGINS_WLIST = ''
182APPLICATION_WLIST = config.APPLICATION_WLIST
183IS_WIN = True
184IS_LINUX = False
185CLOSE_SHOW = True
186TIME_FACTOR = 2
187NOT_SO_GRAPH_LIST = ["BoxSum"]
188
189
190class Communicate(QtCore.QObject):
191    """
192    Utility class for tracking of the Qt signals
193    """
194    # File got successfully read
195    fileReadSignal = QtCore.pyqtSignal(list)
196
197    # Open File returns "list" of paths
198    fileDataReceivedSignal = QtCore.pyqtSignal(dict)
199
200    # Update Main window status bar with "str"
201    # Old "StatusEvent"
202    statusBarUpdateSignal = QtCore.pyqtSignal(str)
203
204    # Send data to the current perspective
205    updatePerspectiveWithDataSignal = QtCore.pyqtSignal(list)
206
207    # New data in current perspective
208    updateModelFromPerspectiveSignal = QtCore.pyqtSignal(QtGui.QStandardItem)
209
210    # New theory data in current perspective
211    updateTheoryFromPerspectiveSignal = QtCore.pyqtSignal(QtGui.QStandardItem)
212
213    # New plot requested from the GUI manager
214    # Old "NewPlotEvent"
215    plotRequestedSignal = QtCore.pyqtSignal(list)
216
217    # Plot from file names
218    plotFromFilenameSignal = QtCore.pyqtSignal(str)
219
220    # Plot update requested from a perspective
221    plotUpdateSignal = QtCore.pyqtSignal(list)
222
223    # Progress bar update value
224    progressBarUpdateSignal = QtCore.pyqtSignal(int)
225
226    # Workspace charts added/removed
227    activeGraphsSignal = QtCore.pyqtSignal(list)
228
229    # Current workspace chart's name changed
230    activeGraphName = QtCore.pyqtSignal(tuple)
231
232    # Current perspective changed
233    perspectiveChangedSignal = QtCore.pyqtSignal(str)
234
235    # File/dataset got deleted
236    dataDeletedSignal = QtCore.pyqtSignal(list)
237
238    # Send data to Data Operation Utility panel
239    sendDataToPanelSignal = QtCore.pyqtSignal(dict)
240
241    # Send result of Data Operation Utility panel to Data Explorer
242    updateModelFromDataOperationPanelSignal = QtCore.pyqtSignal(QtGui.QStandardItem, dict)
243
244    # Notify about a new custom plugin being written/deleted/modified
245    customModelDirectoryChanged = QtCore.pyqtSignal()
246
247def updateModelItemWithPlot(item, update_data, name=""):
248    """
249    Adds a checkboxed row named "name" to QStandardItem
250    Adds 'update_data' to that row.
251    """
252    assert isinstance(item, QtGui.QStandardItem)
253
254    # Check if data with the same ID is already present
255    for index in range(item.rowCount()):
256        plot_item = item.child(index)
257        if plot_item.isCheckable():
258            plot_data = plot_item.child(0).data()
259            if plot_data.id is not None and \
260                   (plot_data.name == update_data.name or plot_data.id == update_data.id):
261            # if plot_data.id is not None and plot_data.id == update_data.id:
262                # replace data section in item
263                plot_item.child(0).setData(update_data)
264                plot_item.setText(name)
265                # Plot title if any
266                if plot_item.child(1).hasChildren():
267                    plot_item.child(1).child(0).setText("Title: %s"%name)
268                # Force redisplay
269                return
270
271    # Create the new item
272    checkbox_item = createModelItemWithPlot(update_data, name)
273
274    # Append the new row to the main item
275    item.appendRow(checkbox_item)
276
277class HashableStandardItem(QtGui.QStandardItem):
278    """
279    Subclassed standard item with reimplemented __hash__
280    to allow for use as an index.
281    """
282    def __init__(self, parent=None):
283        super(HashableStandardItem, self).__init__()
284
285    def __hash__(self):
286        ''' just a random hash value '''
287        #return hash(self.__init__)
288        return 0
289
290    def clone(self):
291        ''' Assure __hash__ is cloned as well'''
292        clone = super(HashableStandardItem, self).clone()
293        clone.__hash__ = self.__hash__
294        return clone
295
296
297def createModelItemWithPlot(update_data, name=""):
298    """
299    Creates a checkboxed QStandardItem named "name"
300    Adds 'update_data' to that row.
301    """
302    py_update_data = update_data
303
304    checkbox_item = HashableStandardItem()
305    checkbox_item.setCheckable(True)
306    checkbox_item.setCheckState(QtCore.Qt.Checked)
307    checkbox_item.setText(name)
308
309    # Add "Info" item
310    if isinstance(py_update_data, (Data1D, Data2D)):
311        # If Data1/2D added - extract Info from it
312        info_item = infoFromData(py_update_data)
313    else:
314        # otherwise just add a naked item
315        info_item = QtGui.QStandardItem("Info")
316
317    # Add the actual Data1D/Data2D object
318    object_item = QtGui.QStandardItem()
319    object_item.setData(update_data)
320
321    # Set the data object as the first child
322    checkbox_item.setChild(0, object_item)
323
324    # Set info_item as the second child
325    checkbox_item.setChild(1, info_item)
326
327    # And return the newly created item
328    return checkbox_item
329
330def updateModelItem(item, update_data, name=""):
331    """
332    Adds a simple named child to QStandardItem
333    """
334    assert isinstance(item, QtGui.QStandardItem)
335    #assert isinstance(update_data, list)
336
337    # Add the actual Data1D/Data2D object
338    object_item = QtGui.QStandardItem()
339    object_item.setText(name)
340    object_item.setData(update_data)
341
342    # Append the new row to the main item
343    item.appendRow(object_item)
344
345def updateModelItemStatus(model_item, filename="", name="", status=2):
346    """
347    Update status of checkbox related to high- and low-Q extrapolation
348    choice in Invariant Panel
349    """
350    assert isinstance(model_item, QtGui.QStandardItemModel)
351
352    # Iterate over model looking for items with checkboxes
353    for index in range(model_item.rowCount()):
354        item = model_item.item(index)
355        if item.text() == filename and item.isCheckable() \
356                and item.checkState() == QtCore.Qt.Checked:
357            # Going 1 level deeper only
358            for index_2 in range(item.rowCount()):
359                item_2 = item.child(index_2)
360                if item_2 and item_2.isCheckable() and item_2.text() == name:
361                    item_2.setCheckState(status)
362
363    return
364
365def itemFromFilename(filename, model_item):
366    """
367    Returns the model item text=filename in the model
368    """
369    assert isinstance(model_item, QtGui.QStandardItemModel)
370    assert isinstance(filename, str)
371
372    # Iterate over model looking for named items
373    item = list([i for i in [model_item.item(index)
374                             for index in range(model_item.rowCount())]
375                 if str(i.text()) == filename])
376    return item[0] if len(item)>0 else None
377
378def plotsFromFilename(filename, model_item):
379    """
380    Returns the list of plots for the item with text=filename in the model
381    """
382    assert isinstance(model_item, QtGui.QStandardItemModel)
383    assert isinstance(filename, str)
384
385    plot_data = []
386    # Iterate over model looking for named items
387    for index in range(model_item.rowCount()):
388        item = model_item.item(index)
389        if str(item.text()) == filename:
390            # TODO: assure item type is correct (either data1/2D or Plotter)
391            plot_data.append(item.child(0).data())
392            # Going 1 level deeper only
393            for index_2 in range(item.rowCount()):
394                item_2 = item.child(index_2)
395                if item_2 and item_2.isCheckable():
396                    # TODO: assure item type is correct (either data1/2D or Plotter)
397                    plot_data.append(item_2.child(0).data())
398
399    return plot_data
400
401def plotsFromCheckedItems(model_item):
402    """
403    Returns the list of plots for items in the model which are checked
404    """
405    assert isinstance(model_item, QtGui.QStandardItemModel)
406
407    plot_data = []
408    # Iterate over model looking for items with checkboxes
409    for index in range(model_item.rowCount()):
410        item = model_item.item(index)
411
412        # Going 1 level deeper only
413        for index_2 in range(item.rowCount()):
414            item_2 = item.child(index_2)
415            if item_2 and item_2.isCheckable() and item_2.checkState() == QtCore.Qt.Checked:
416                # TODO: assure item type is correct (either data1/2D or Plotter)
417                plot_data.append((item_2, item_2.child(0).data()))
418
419        if item.isCheckable() and item.checkState() == QtCore.Qt.Checked:
420            # TODO: assure item type is correct (either data1/2D or Plotter)
421            plot_data.append((item, item.child(0).data()))
422
423    return plot_data
424
425def infoFromData(data):
426    """
427    Given Data1D/Data2D object, extract relevant Info elements
428    and add them to a model item
429    """
430    assert isinstance(data, (Data1D, Data2D))
431
432    info_item = QtGui.QStandardItem("Info")
433
434    title_item = QtGui.QStandardItem("Title: " + data.title)
435    info_item.appendRow(title_item)
436    run_item = QtGui.QStandardItem("Run: " + str(data.run))
437    info_item.appendRow(run_item)
438    type_item = QtGui.QStandardItem("Type: " + str(data.__class__.__name__))
439    info_item.appendRow(type_item)
440
441    if data.path:
442        path_item = QtGui.QStandardItem("Path: " + data.path)
443        info_item.appendRow(path_item)
444
445    if data.instrument:
446        instr_item = QtGui.QStandardItem("Instrument: " + data.instrument)
447        info_item.appendRow(instr_item)
448
449    process_item = QtGui.QStandardItem("Process")
450    if isinstance(data.process, list) and data.process:
451        for process in data.process:
452            process_date = process.date
453            process_date_item = QtGui.QStandardItem("Date: " + process_date)
454            process_item.appendRow(process_date_item)
455
456            process_descr = process.description
457            process_descr_item = QtGui.QStandardItem("Description: " + process_descr)
458            process_item.appendRow(process_descr_item)
459
460            process_name = process.name
461            process_name_item = QtGui.QStandardItem("Name: " + process_name)
462            process_item.appendRow(process_name_item)
463
464    info_item.appendRow(process_item)
465
466    return info_item
467
468def openLink(url):
469    """
470    Open a URL in an external browser.
471    Check the URL first, though.
472    """
473    parsed_url = urllib.parse.urlparse(url)
474    if parsed_url.scheme:
475        webbrowser.open(url)
476    else:
477        msg = "Attempt at opening an invalid URL"
478        raise AttributeError(msg)
479
480def retrieveData1d(data):
481    """
482    Retrieve 1D data from file and construct its text
483    representation
484    """
485    if not isinstance(data, Data1D):
486        msg = "Incorrect type passed to retrieveData1d"
487        raise AttributeError(msg)
488    try:
489        xmin = min(data.x)
490        ymin = min(data.y)
491    except:
492        msg = "Unable to find min/max of \n data named %s" % \
493                    data.filename
494        #logging.error(msg)
495        raise ValueError(msg)
496
497    text = data.__str__()
498    text += 'Data Min Max:\n'
499    text += 'X_min = %s:  X_max = %s\n' % (xmin, max(data.x))
500    text += 'Y_min = %s:  Y_max = %s\n' % (ymin, max(data.y))
501    if data.dy is not None:
502        text += 'dY_min = %s:  dY_max = %s\n' % (min(data.dy), max(data.dy))
503    text += '\nData Points:\n'
504    x_st = "X"
505    for index in range(len(data.x)):
506        if data.dy is not None and len(data.dy) > index:
507            dy_val = data.dy[index]
508        else:
509            dy_val = 0.0
510        if data.dx is not None and len(data.dx) > index:
511            dx_val = data.dx[index]
512        else:
513            dx_val = 0.0
514        if data.dxl is not None and len(data.dxl) > index:
515            if index == 0:
516                x_st = "Xl"
517            dx_val = data.dxl[index]
518        elif data.dxw is not None and len(data.dxw) > index:
519            if index == 0:
520                x_st = "Xw"
521            dx_val = data.dxw[index]
522
523        if index == 0:
524            text += "<index> \t<X> \t<Y> \t<dY> \t<d%s>\n" % x_st
525        text += "%s \t%s \t%s \t%s \t%s\n" % (index,
526                                                data.x[index],
527                                                data.y[index],
528                                                dy_val,
529                                                dx_val)
530    return text
531
532def retrieveData2d(data):
533    """
534    Retrieve 2D data from file and construct its text
535    representation
536    """
537    if not isinstance(data, Data2D):
538        msg = "Incorrect type passed to retrieveData2d"
539        raise AttributeError(msg)
540
541    text = data.__str__()
542    text += 'Data Min Max:\n'
543    text += 'I_min = %s\n' % min(data.data)
544    text += 'I_max = %s\n\n' % max(data.data)
545    text += 'Data (First 2501) Points:\n'
546    text += 'Data columns include err(I).\n'
547    text += 'ASCII data starts here.\n'
548    text += "<index> \t<Qx> \t<Qy> \t<I> \t<dI> \t<dQparal> \t<dQperp>\n"
549    di_val = 0.0
550    dx_val = 0.0
551    dy_val = 0.0
552    len_data = len(data.qx_data)
553    for index in range(0, len_data):
554        x_val = data.qx_data[index]
555        y_val = data.qy_data[index]
556        i_val = data.data[index]
557        if data.err_data is not None:
558            di_val = data.err_data[index]
559        if data.dqx_data is not None:
560            dx_val = data.dqx_data[index]
561        if data.dqy_data is not None:
562            dy_val = data.dqy_data[index]
563
564        text += "%s \t%s \t%s \t%s \t%s \t%s \t%s\n" % (index,
565                                                        x_val,
566                                                        y_val,
567                                                        i_val,
568                                                        di_val,
569                                                        dx_val,
570                                                        dy_val)
571        # Takes too long time for typical data2d: Break here
572        if index >= 2500:
573            text += ".............\n"
574            break
575
576    return text
577
578def onTXTSave(data, path):
579    """
580    Save file as formatted txt
581    """
582    with open(path,'w') as out:
583        has_errors = True
584        if data.dy is None or not data.dy.any():
585            has_errors = False
586        # Sanity check
587        if has_errors:
588            try:
589                if len(data.y) != len(data.dy):
590                    has_errors = False
591            except:
592                has_errors = False
593        if has_errors:
594            if data.dx is not None and data.dx.any():
595                out.write("<X>   <Y>   <dY>   <dX>\n")
596            else:
597                out.write("<X>   <Y>   <dY>\n")
598        else:
599            out.write("<X>   <Y>\n")
600
601        for i in range(len(data.x)):
602            if has_errors:
603                if data.dx is not None and data.dx.any():
604                    if  data.dx[i] is not None:
605                        out.write("%g  %g  %g  %g\n" % (data.x[i],
606                                                        data.y[i],
607                                                        data.dy[i],
608                                                        data.dx[i]))
609                    else:
610                        out.write("%g  %g  %g\n" % (data.x[i],
611                                                    data.y[i],
612                                                    data.dy[i]))
613                else:
614                    out.write("%g  %g  %g\n" % (data.x[i],
615                                                data.y[i],
616                                                data.dy[i]))
617            else:
618                out.write("%g  %g\n" % (data.x[i],
619                                        data.y[i]))
620
621def saveData1D(data):
622    """
623    Save 1D data points
624    """
625    default_name = os.path.basename(data.filename)
626    default_name, extension = os.path.splitext(default_name)
627    if not extension:
628        extension = ".txt"
629    default_name += "_out" + extension
630
631    wildcard = "Text files (*.txt);;"\
632                "CanSAS 1D files(*.xml)"
633    kwargs = {
634        'caption'   : 'Save As',
635        'directory' : default_name,
636        'filter'    : wildcard,
637        'parent'    : None,
638    }
639    # Query user for filename.
640    filename_tuple = QtWidgets.QFileDialog.getSaveFileName(**kwargs)
641    filename = filename_tuple[0]
642
643    # User cancelled.
644    if not filename:
645        return
646
647    #Instantiate a loader
648    loader = Loader()
649    if os.path.splitext(filename)[1].lower() == ".txt":
650        onTXTSave(data, filename)
651    if os.path.splitext(filename)[1].lower() == ".xml":
652        loader.save(filename, data, ".xml")
653
654def saveData2D(data):
655    """
656    Save data2d dialog
657    """
658    default_name = os.path.basename(data.filename)
659    default_name, _ = os.path.splitext(default_name)
660    ext_format = ".dat"
661    default_name += "_out" + ext_format
662
663    wildcard = "IGOR/DAT 2D file in Q_map (*.dat)"
664    kwargs = {
665        'caption'   : 'Save As',
666        'directory' : default_name,
667        'filter'    : wildcard,
668        'parent'    : None,
669    }
670    # Query user for filename.
671    filename_tuple = QtWidgets.QFileDialog.getSaveFileName(**kwargs)
672    filename = filename_tuple[0]
673
674    # User cancelled.
675    if not filename:
676        return
677
678    #Instantiate a loader
679    loader = Loader()
680
681    if os.path.splitext(filename)[1].lower() == ext_format:
682        loader.save(filename, data, ext_format)
683
684class FormulaValidator(QtGui.QValidator):
685    def __init__(self, parent=None):
686        super(FormulaValidator, self).__init__(parent)
687 
688    def validate(self, input, pos):
689
690        self._setStyleSheet("")
691        return QtGui.QValidator.Acceptable, pos
692
693        #try:
694        #    Formula(str(input))
695        #    self._setStyleSheet("")
696        #    return QtGui.QValidator.Acceptable, pos
697
698        #except Exception as e:
699        #    self._setStyleSheet("background-color:pink;")
700        #    return QtGui.QValidator.Intermediate, pos
701
702    def _setStyleSheet(self, value):
703        try:
704            if self.parent():
705                self.parent().setStyleSheet(value)
706        except:
707            pass
708
709def xyTransform(data, xLabel="", yLabel=""):
710    """
711    Transforms x and y in View and set the scale
712    """
713    # Changing the scale might be incompatible with
714    # currently displayed data (for instance, going
715    # from ln to log when all plotted values have
716    # negative natural logs).
717    # Go linear and only change the scale at the end.
718    xscale = 'linear'
719    yscale = 'linear'
720    # Local data is either 1D or 2D
721    if data.id == 'fit':
722        return
723
724    # make sure we have some function to operate on
725    if xLabel is None:
726        xLabel = 'log10(x)'
727    if yLabel is None:
728        yLabel = 'log10(y)'
729
730    # control axis labels from the panel itself
731    yname, yunits = data.get_yaxis()
732    xname, xunits = data.get_xaxis()
733
734    # Goes through all possible scales
735    # self.x_label is already wrapped with Latex "$", so using the argument
736
737    # X
738    if xLabel == "x":
739        data.transformX(DataTransform.toX, DataTransform.errToX)
740        xLabel = "%s(%s)" % (xname, xunits)
741    if xLabel == "x^(2)":
742        data.transformX(DataTransform.toX2, DataTransform.errToX2)
743        xunits = convertUnit(2, xunits)
744        xLabel = "%s^{2}(%s)" % (xname, xunits)
745    if xLabel == "x^(4)":
746        data.transformX(DataTransform.toX4, DataTransform.errToX4)
747        xunits = convertUnit(4, xunits)
748        xLabel = "%s^{4}(%s)" % (xname, xunits)
749    if xLabel == "ln(x)":
750        data.transformX(DataTransform.toLogX, DataTransform.errToLogX)
751        xLabel = "\ln{(%s)}(%s)" % (xname, xunits)
752    if xLabel == "log10(x)":
753        data.transformX(DataTransform.toX_pos, DataTransform.errToX_pos)
754        xscale = 'log'
755        xLabel = "%s(%s)" % (xname, xunits)
756    if xLabel == "log10(x^(4))":
757        data.transformX(DataTransform.toX4, DataTransform.errToX4)
758        xunits = convertUnit(4, xunits)
759        xLabel = "%s^{4}(%s)" % (xname, xunits)
760        xscale = 'log'
761
762    # Y
763    if yLabel == "ln(y)":
764        data.transformY(DataTransform.toLogX, DataTransform.errToLogX)
765        yLabel = "\ln{(%s)}(%s)" % (yname, yunits)
766    if yLabel == "y":
767        data.transformY(DataTransform.toX, DataTransform.errToX)
768        yLabel = "%s(%s)" % (yname, yunits)
769    if yLabel == "log10(y)":
770        data.transformY(DataTransform.toX_pos, DataTransform.errToX_pos)
771        yscale = 'log'
772        yLabel = "%s(%s)" % (yname, yunits)
773    if yLabel == "y^(2)":
774        data.transformY(DataTransform.toX2, DataTransform.errToX2)
775        yunits = convertUnit(2, yunits)
776        yLabel = "%s^{2}(%s)" % (yname, yunits)
777    if yLabel == "1/y":
778        data.transformY(DataTransform.toOneOverX, DataTransform.errOneOverX)
779        yunits = convertUnit(-1, yunits)
780        yLabel = "1/%s(%s)" % (yname, yunits)
781    if yLabel == "y*x^(2)":
782        data.transformY(DataTransform.toYX2, DataTransform.errToYX2)
783        xunits = convertUnit(2, xunits)
784        yLabel = "%s \ \ %s^{2}(%s%s)" % (yname, xname, yunits, xunits)
785    if yLabel == "y*x^(4)":
786        data.transformY(DataTransform.toYX4, DataTransform.errToYX4)
787        xunits = convertUnit(4, xunits)
788        yLabel = "%s \ \ %s^{4}(%s%s)" % (yname, xname, yunits, xunits)
789    if yLabel == "1/sqrt(y)":
790        data.transformY(DataTransform.toOneOverSqrtX, DataTransform.errOneOverSqrtX)
791        yunits = convertUnit(-0.5, yunits)
792        yLabel = "1/\sqrt{%s}(%s)" % (yname, yunits)
793    if yLabel == "ln(y*x)":
794        data.transformY(DataTransform.toLogXY, DataTransform.errToLogXY)
795        yLabel = "\ln{(%s \ \ %s)}(%s%s)" % (yname, xname, yunits, xunits)
796    if yLabel == "ln(y*x^(2))":
797        data.transformY(DataTransform.toLogYX2, DataTransform.errToLogYX2)
798        xunits = convertUnit(2, xunits)
799        yLabel = "\ln (%s \ \ %s^{2})(%s%s)" % (yname, xname, yunits, xunits)
800    if yLabel == "ln(y*x^(4))":
801        data.transformY(DataTransform.toLogYX4, DataTransform.errToLogYX4)
802        xunits = convertUnit(4, xunits)
803        yLabel = "\ln (%s \ \ %s^{4})(%s%s)" % (yname, xname, yunits, xunits)
804    if yLabel == "log10(y*x^(4))":
805        data.transformY(DataTransform.toYX4, DataTransform.errToYX4)
806        xunits = convertUnit(4, xunits)
807        yscale = 'log'
808        yLabel = "%s \ \ %s^{4}(%s%s)" % (yname, xname, yunits, xunits)
809
810    # Perform the transformation of data in data1d->View
811    data.transformView()
812
813    return (xLabel, yLabel, xscale, yscale)
814
815def dataFromItem(item):
816    """
817    Retrieve Data1D/2D component from QStandardItem.
818    The assumption - data stored in SasView standard, in child 0
819    """
820    return item.child(0).data()
821
822def formatNumber(value, high=False):
823    """
824    Return a float in a standardized, human-readable formatted string.
825    This is used to output readable (e.g. x.xxxe-y) values to the panel.
826    """
827    try:
828        value = float(value)
829    except:
830        output = "NaN"
831        return output.lstrip().rstrip()
832
833    if high:
834        output = "%-7.5g" % value
835
836    else:
837        output = "%-5.3g" % value
838    return output.lstrip().rstrip()
839
840def convertUnitToHTML(unit):
841    """
842    Convert ASCII unit display into well rendering HTML
843    """
844    if unit == "1/A":
845        return "&#x212B;<sup>-1</sup>"
846    elif unit == "1/cm":
847        return "cm<sup>-1</sup>"
848    elif unit == "Ang":
849        return "&#x212B;"
850    elif unit == "1e-6/Ang^2":
851        return "10<sup>-6</sup>/&#x212B;<sup>2</sup>"
852    elif unit == "inf":
853        return "&#x221e;"
854    elif unit == "-inf":
855        return "-&#x221e;"
856    else:
857        return unit
858
859def parseName(name, expression):
860    """
861    remove "_" in front of a name
862    """
863    if re.match(expression, name) is not None:
864        word = re.split(expression, name, 1)
865        for item in word:           
866            if item.lstrip().rstrip() != '':
867                return item
868    else:
869        return name
870
871def toDouble(value_string):
872    """
873    toFloat conversion which cares deeply about user's locale
874    """
875    # Holy shit this escalated quickly in Qt5.
876    # No more float() cast on general locales.
877    value = QtCore.QLocale().toFloat(value_string)
878    if value[1]:
879        return value[0]
880
881    # Try generic locale
882    value = QtCore.QLocale(QtCore.QLocale('en_US')).toFloat(value_string)
883    if value[1]:
884        return value[0]
885    else:
886        raise TypeError
887
888def findNextFilename(filename, directory):
889    """
890    Finds the next available (non-existing) name for 'filename' in 'directory'.
891    plugin.py -> plugin (n).py  - for first 'n' for which the file doesn't exist
892    """
893    basename, ext = os.path.splitext(filename)
894    # limit the number of copies
895    MAX_FILENAMES = 1000
896    # Start with (1)
897    number_ext = 1
898    proposed_filename = ""
899    found_filename = False
900    # Find the next available filename or exit if too many copies
901    while not found_filename or number_ext > MAX_FILENAMES:
902        proposed_filename = basename + " ("+str(number_ext)+")" + ext
903        if os.path.exists(os.path.join(directory, proposed_filename)):
904            number_ext += 1
905        else:
906            found_filename = True
907
908    return proposed_filename
909
910
911class DoubleValidator(QtGui.QDoubleValidator):
912    """
913    Allow only dots as decimal separator
914    """
915    def validate(self, input, pos):
916        """
917        Return invalid for commas
918        """
919        if input is not None and ',' in input:
920            return (QtGui.QValidator.Invalid, input, pos)
921        return super(DoubleValidator, self).validate(input, pos)
922
923    def fixup(self, input):
924        """
925        Correct (remove) potential preexisting content
926        """
927        super(DoubleValidator, self).fixup(input)
928        input = input.replace(",", "")
929
930def checkModel(path):
931    """
932    Check that the model save in file 'path' can run.
933    """
934    # try running the model
935    from sasmodels.sasview_model import load_custom_model
936    Model = load_custom_model(path)
937    model = Model()
938    q =  np.array([0.01, 0.1])
939    _ = model.evalDistribution(q)
940    qx, qy =  np.array([0.01, 0.01]), np.array([0.1, 0.1])
941    _ = model.evalDistribution([qx, qy])
942
943    # check the model's unit tests run
944    from sasmodels.model_test import run_one
945    # TestSuite module in Qt5 now deletes tests in the suite after running,
946    # so suite[0] in run_one() in sasmodels/model_test.py will contain [None] and
947    # test.info.tests will raise.
948    # Not sure how to change the behaviour here, most likely sasmodels will have to
949    # be modified
950    result = run_one(path)
951
952    return result
953
954
955def enum(*sequential, **named):
956    """Create an enumeration object from a list of strings"""
957    enums = dict(zip(sequential, range(len(sequential))), **named)
958    return type('Enum', (), enums)
Note: See TracBrowser for help on using the repository browser.