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

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

Added unit tests for recent functionality

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