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

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

Implemented hash method for certain QStandardItem objects, allowing them to be used as dict keys. SASVIEW-806
Minor inversion perspective fixes. SASVIEW-338

  • Property mode set to 100644
File size: 29.1 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(QtGui.QStandardItem, self).__init__()
274
275    def __hash__(self):
276        ''' just a random hash value '''
277        #return hash(self.__init__)
278        return 0
279
280
281def createModelItemWithPlot(update_data, name=""):
282    """
283    Creates a checkboxed QStandardItem named "name"
284    Adds 'update_data' to that row.
285    """
286    py_update_data = update_data
287
288    checkbox_item = HashableStandardItem()
289    checkbox_item.setCheckable(True)
290    checkbox_item.setCheckState(QtCore.Qt.Checked)
291    checkbox_item.setText(name)
292
293    # Add "Info" item
294    if isinstance(py_update_data, (Data1D, Data2D)):
295        # If Data1/2D added - extract Info from it
296        info_item = infoFromData(py_update_data)
297    else:
298        # otherwise just add a naked item
299        info_item = QtGui.QStandardItem("Info")
300
301    # Add the actual Data1D/Data2D object
302    object_item = QtGui.QStandardItem()
303    object_item.setData(update_data)
304
305    # Set the data object as the first child
306    checkbox_item.setChild(0, object_item)
307
308    # Set info_item as the second child
309    checkbox_item.setChild(1, info_item)
310
311    # And return the newly created item
312    return checkbox_item
313
314def updateModelItem(item, update_data, name=""):
315    """
316    Adds a simple named child to QStandardItem
317    """
318    assert isinstance(item, QtGui.QStandardItem)
319    assert isinstance(update_data, list)
320
321    # Add the actual Data1D/Data2D object
322    object_item = QtGui.QStandardItem()
323    object_item.setText(name)
324    object_item.setData(update_data)
325
326    # Append the new row to the main item
327    item.appendRow(object_item)
328
329def updateModelItemStatus(model_item, filename="", name="", status=2):
330    """
331    Update status of checkbox related to high- and low-Q extrapolation
332    choice in Invariant Panel
333    """
334    assert isinstance(model_item, QtGui.QStandardItemModel)
335
336    # Iterate over model looking for items with checkboxes
337    for index in range(model_item.rowCount()):
338        item = model_item.item(index)
339        if item.text() == filename and item.isCheckable() and item.checkState() == QtCore.Qt.Checked:
340            # Going 1 level deeper only
341            for index_2 in range(item.rowCount()):
342                item_2 = item.child(index_2)
343                if item_2 and item_2.isCheckable() and item_2.text() == name:
344                    item_2.setCheckState(status)
345
346    return
347
348def itemFromFilename(filename, model_item):
349    """
350    Returns the model item text=filename in the model
351    """
352    assert isinstance(model_item, QtGui.QStandardItemModel)
353    assert isinstance(filename, str)
354
355    # Iterate over model looking for named items
356    item = list([i for i in [model_item.item(index) for index in range(model_item.rowCount())] if str(i.text()) == filename])
357    return item[0] if len(item)>0 else None
358
359def plotsFromFilename(filename, model_item):
360    """
361    Returns the list of plots for the item with text=filename in the model
362    """
363    assert isinstance(model_item, QtGui.QStandardItemModel)
364    assert isinstance(filename, str)
365
366    plot_data = []
367    # Iterate over model looking for named items
368    for index in range(model_item.rowCount()):
369        item = model_item.item(index)
370        if str(item.text()) == filename:
371            # TODO: assure item type is correct (either data1/2D or Plotter)
372            plot_data.append(item.child(0).data())
373            # Going 1 level deeper only
374            for index_2 in range(item.rowCount()):
375                item_2 = item.child(index_2)
376                if item_2 and item_2.isCheckable():
377                    # TODO: assure item type is correct (either data1/2D or Plotter)
378                    plot_data.append(item_2.child(0).data())
379
380    return plot_data
381
382def plotsFromCheckedItems(model_item):
383    """
384    Returns the list of plots for items in the model which are checked
385    """
386    assert isinstance(model_item, QtGui.QStandardItemModel)
387
388    plot_data = []
389    # Iterate over model looking for items with checkboxes
390    for index in range(model_item.rowCount()):
391        item = model_item.item(index)
392        if item.isCheckable() and item.checkState() == QtCore.Qt.Checked:
393            # TODO: assure item type is correct (either data1/2D or Plotter)
394            plot_data.append((item, item.child(0).data()))
395        # Going 1 level deeper only
396        for index_2 in range(item.rowCount()):
397            item_2 = item.child(index_2)
398            if item_2 and item_2.isCheckable() and item_2.checkState() == QtCore.Qt.Checked:
399                # TODO: assure item type is correct (either data1/2D or Plotter)
400                plot_data.append((item_2, item_2.child(0).data()))
401
402    return plot_data
403
404def infoFromData(data):
405    """
406    Given Data1D/Data2D object, extract relevant Info elements
407    and add them to a model item
408    """
409    assert isinstance(data, (Data1D, Data2D))
410
411    info_item = QtGui.QStandardItem("Info")
412
413    title_item = QtGui.QStandardItem("Title: " + data.title)
414    info_item.appendRow(title_item)
415    run_item = QtGui.QStandardItem("Run: " + str(data.run))
416    info_item.appendRow(run_item)
417    type_item = QtGui.QStandardItem("Type: " + str(data.__class__.__name__))
418    info_item.appendRow(type_item)
419
420    if data.path:
421        path_item = QtGui.QStandardItem("Path: " + data.path)
422        info_item.appendRow(path_item)
423
424    if data.instrument:
425        instr_item = QtGui.QStandardItem("Instrument: " + data.instrument)
426        info_item.appendRow(instr_item)
427
428    process_item = QtGui.QStandardItem("Process")
429    if isinstance(data.process, list) and data.process:
430        for process in data.process:
431            process_date = process.date
432            process_date_item = QtGui.QStandardItem("Date: " + process_date)
433            process_item.appendRow(process_date_item)
434
435            process_descr = process.description
436            process_descr_item = QtGui.QStandardItem("Description: " + process_descr)
437            process_item.appendRow(process_descr_item)
438
439            process_name = process.name
440            process_name_item = QtGui.QStandardItem("Name: " + process_name)
441            process_item.appendRow(process_name_item)
442
443    info_item.appendRow(process_item)
444
445    return info_item
446
447def openLink(url):
448    """
449    Open a URL in an external browser.
450    Check the URL first, though.
451    """
452    parsed_url = urllib.parse.urlparse(url)
453    if parsed_url.scheme:
454        webbrowser.open(url)
455    else:
456        msg = "Attempt at opening an invalid URL"
457        raise AttributeError(msg)
458
459def retrieveData1d(data):
460    """
461    Retrieve 1D data from file and construct its text
462    representation
463    """
464    if not isinstance(data, Data1D):
465        msg = "Incorrect type passed to retrieveData1d"
466        raise AttributeError(msg)
467    try:
468        xmin = min(data.x)
469        ymin = min(data.y)
470    except:
471        msg = "Unable to find min/max of \n data named %s" % \
472                    data.filename
473        #logging.error(msg)
474        raise ValueError(msg)
475
476    text = data.__str__()
477    text += 'Data Min Max:\n'
478    text += 'X_min = %s:  X_max = %s\n' % (xmin, max(data.x))
479    text += 'Y_min = %s:  Y_max = %s\n' % (ymin, max(data.y))
480    if data.dy is not None:
481        text += 'dY_min = %s:  dY_max = %s\n' % (min(data.dy), max(data.dy))
482    text += '\nData Points:\n'
483    x_st = "X"
484    for index in range(len(data.x)):
485        if data.dy is not None and len(data.dy) > index:
486            dy_val = data.dy[index]
487        else:
488            dy_val = 0.0
489        if data.dx is not None and len(data.dx) > index:
490            dx_val = data.dx[index]
491        else:
492            dx_val = 0.0
493        if data.dxl is not None and len(data.dxl) > index:
494            if index == 0:
495                x_st = "Xl"
496            dx_val = data.dxl[index]
497        elif data.dxw is not None and len(data.dxw) > index:
498            if index == 0:
499                x_st = "Xw"
500            dx_val = data.dxw[index]
501
502        if index == 0:
503            text += "<index> \t<X> \t<Y> \t<dY> \t<d%s>\n" % x_st
504        text += "%s \t%s \t%s \t%s \t%s\n" % (index,
505                                                data.x[index],
506                                                data.y[index],
507                                                dy_val,
508                                                dx_val)
509    return text
510
511def retrieveData2d(data):
512    """
513    Retrieve 2D data from file and construct its text
514    representation
515    """
516    if not isinstance(data, Data2D):
517        msg = "Incorrect type passed to retrieveData2d"
518        raise AttributeError(msg)
519
520    text = data.__str__()
521    text += 'Data Min Max:\n'
522    text += 'I_min = %s\n' % min(data.data)
523    text += 'I_max = %s\n\n' % max(data.data)
524    text += 'Data (First 2501) Points:\n'
525    text += 'Data columns include err(I).\n'
526    text += 'ASCII data starts here.\n'
527    text += "<index> \t<Qx> \t<Qy> \t<I> \t<dI> \t<dQparal> \t<dQperp>\n"
528    di_val = 0.0
529    dx_val = 0.0
530    dy_val = 0.0
531    len_data = len(data.qx_data)
532    for index in range(0, len_data):
533        x_val = data.qx_data[index]
534        y_val = data.qy_data[index]
535        i_val = data.data[index]
536        if data.err_data is not None:
537            di_val = data.err_data[index]
538        if data.dqx_data is not None:
539            dx_val = data.dqx_data[index]
540        if data.dqy_data is not None:
541            dy_val = data.dqy_data[index]
542
543        text += "%s \t%s \t%s \t%s \t%s \t%s \t%s\n" % (index,
544                                                        x_val,
545                                                        y_val,
546                                                        i_val,
547                                                        di_val,
548                                                        dx_val,
549                                                        dy_val)
550        # Takes too long time for typical data2d: Break here
551        if index >= 2500:
552            text += ".............\n"
553            break
554
555    return text
556
557def onTXTSave(data, path):
558    """
559    Save file as formatted txt
560    """
561    with open(path,'w') as out:
562        has_errors = True
563        if data.dy is None or not data.dy:
564            has_errors = False
565        # Sanity check
566        if has_errors:
567            try:
568                if len(data.y) != len(data.dy):
569                    has_errors = False
570            except:
571                has_errors = False
572        if has_errors:
573            if data.dx is not None and data.dx:
574                out.write("<X>   <Y>   <dY>   <dX>\n")
575            else:
576                out.write("<X>   <Y>   <dY>\n")
577        else:
578            out.write("<X>   <Y>\n")
579
580        for i in range(len(data.x)):
581            if has_errors:
582                if data.dx is not None and data.dx:
583                    if  data.dx[i] is not None:
584                        out.write("%g  %g  %g  %g\n" % (data.x[i],
585                                                        data.y[i],
586                                                        data.dy[i],
587                                                        data.dx[i]))
588                    else:
589                        out.write("%g  %g  %g\n" % (data.x[i],
590                                                    data.y[i],
591                                                    data.dy[i]))
592                else:
593                    out.write("%g  %g  %g\n" % (data.x[i],
594                                                data.y[i],
595                                                data.dy[i]))
596            else:
597                out.write("%g  %g\n" % (data.x[i],
598                                        data.y[i]))
599
600def saveData1D(data):
601    """
602    Save 1D data points
603    """
604    default_name = os.path.basename(data.filename)
605    default_name, extension = os.path.splitext(default_name)
606    if not extension:
607        extension = ".txt"
608    default_name += "_out" + extension
609
610    wildcard = "Text files (*.txt);;"\
611                "CanSAS 1D files(*.xml)"
612    kwargs = {
613        'caption'   : 'Save As',
614        'directory' : default_name,
615        'filter'    : wildcard,
616        'parent'    : None,
617    }
618    # Query user for filename.
619    filename_tuple = QtWidgets.QFileDialog.getSaveFileName(**kwargs)
620    filename = filename_tuple[0]
621
622    # User cancelled.
623    if not filename:
624        return
625
626    #Instantiate a loader
627    loader = Loader()
628    if os.path.splitext(filename)[1].lower() == ".txt":
629        onTXTSave(data, filename)
630    if os.path.splitext(filename)[1].lower() == ".xml":
631        loader.save(filename, data, ".xml")
632
633def saveData2D(data):
634    """
635    Save data2d dialog
636    """
637    default_name = os.path.basename(data.filename)
638    default_name, _ = os.path.splitext(default_name)
639    ext_format = ".dat"
640    default_name += "_out" + ext_format
641
642    wildcard = "IGOR/DAT 2D file in Q_map (*.dat)"
643    kwargs = {
644        'caption'   : 'Save As',
645        'directory' : default_name,
646        'filter'    : wildcard,
647        'parent'    : None,
648    }
649    # Query user for filename.
650    filename_tuple = QtWidgets.QFileDialog.getSaveFileName(**kwargs)
651    filename = filename_tuple[0]
652
653    # User cancelled.
654    if not filename:
655        return
656
657    #Instantiate a loader
658    loader = Loader()
659
660    if os.path.splitext(filename)[1].lower() == ext_format:
661        loader.save(filename, data, ext_format)
662
663class FormulaValidator(QtGui.QValidator):
664    def __init__(self, parent=None):
665        super(FormulaValidator, self).__init__(parent)
666 
667    def validate(self, input, pos):
668
669        self._setStyleSheet("")
670        return QtGui.QValidator.Acceptable, pos
671
672        #try:
673        #    Formula(str(input))
674        #    self._setStyleSheet("")
675        #    return QtGui.QValidator.Acceptable, pos
676
677        #except Exception as e:
678        #    self._setStyleSheet("background-color:pink;")
679        #    return QtGui.QValidator.Intermediate, pos
680
681    def _setStyleSheet(self, value):
682        try:
683            if self.parent():
684                self.parent().setStyleSheet(value)
685        except:
686            pass
687
688def xyTransform(data, xLabel="", yLabel=""):
689    """
690    Transforms x and y in View and set the scale
691    """
692    # Changing the scale might be incompatible with
693    # currently displayed data (for instance, going
694    # from ln to log when all plotted values have
695    # negative natural logs).
696    # Go linear and only change the scale at the end.
697    xscale = 'linear'
698    yscale = 'linear'
699    # Local data is either 1D or 2D
700    if data.id == 'fit':
701        return
702
703    # control axis labels from the panel itself
704    yname, yunits = data.get_yaxis()
705    xname, xunits = data.get_xaxis()
706
707    # Goes through all possible scales
708    # self.x_label is already wrapped with Latex "$", so using the argument
709
710    # X
711    if xLabel == "x":
712        data.transformX(DataTransform.toX, DataTransform.errToX)
713        xLabel = "%s(%s)" % (xname, xunits)
714    if xLabel == "x^(2)":
715        data.transformX(DataTransform.toX2, DataTransform.errToX2)
716        xunits = convertUnit(2, xunits)
717        xLabel = "%s^{2}(%s)" % (xname, xunits)
718    if xLabel == "x^(4)":
719        data.transformX(DataTransform.toX4, DataTransform.errToX4)
720        xunits = convertUnit(4, xunits)
721        xLabel = "%s^{4}(%s)" % (xname, xunits)
722    if xLabel == "ln(x)":
723        data.transformX(DataTransform.toLogX, DataTransform.errToLogX)
724        xLabel = "\ln{(%s)}(%s)" % (xname, xunits)
725    if xLabel == "log10(x)":
726        data.transformX(DataTransform.toX_pos, DataTransform.errToX_pos)
727        xscale = 'log'
728        xLabel = "%s(%s)" % (xname, xunits)
729    if xLabel == "log10(x^(4))":
730        data.transformX(DataTransform.toX4, DataTransform.errToX4)
731        xunits = convertUnit(4, xunits)
732        xLabel = "%s^{4}(%s)" % (xname, xunits)
733        xscale = 'log'
734
735    # Y
736    if yLabel == "ln(y)":
737        data.transformY(DataTransform.toLogX, DataTransform.errToLogX)
738        yLabel = "\ln{(%s)}(%s)" % (yname, yunits)
739    if yLabel == "y":
740        data.transformY(DataTransform.toX, DataTransform.errToX)
741        yLabel = "%s(%s)" % (yname, yunits)
742    if yLabel == "log10(y)":
743        data.transformY(DataTransform.toX_pos, DataTransform.errToX_pos)
744        yscale = 'log'
745        yLabel = "%s(%s)" % (yname, yunits)
746    if yLabel == "y^(2)":
747        data.transformY(DataTransform.toX2, DataTransform.errToX2)
748        yunits = convertUnit(2, yunits)
749        yLabel = "%s^{2}(%s)" % (yname, yunits)
750    if yLabel == "1/y":
751        data.transformY(DataTransform.toOneOverX, DataTransform.errOneOverX)
752        yunits = convertUnit(-1, yunits)
753        yLabel = "1/%s(%s)" % (yname, yunits)
754    if yLabel == "y*x^(2)":
755        data.transformY(DataTransform.toYX2, DataTransform.errToYX2)
756        xunits = convertUnit(2, xunits)
757        yLabel = "%s \ \ %s^{2}(%s%s)" % (yname, xname, yunits, xunits)
758    if yLabel == "y*x^(4)":
759        data.transformY(DataTransform.toYX4, DataTransform.errToYX4)
760        xunits = convertUnit(4, xunits)
761        yLabel = "%s \ \ %s^{4}(%s%s)" % (yname, xname, yunits, xunits)
762    if yLabel == "1/sqrt(y)":
763        data.transformY(DataTransform.toOneOverSqrtX, DataTransform.errOneOverSqrtX)
764        yunits = convertUnit(-0.5, yunits)
765        yLabel = "1/\sqrt{%s}(%s)" % (yname, yunits)
766    if yLabel == "ln(y*x)":
767        data.transformY(DataTransform.toLogXY, DataTransform.errToLogXY)
768        yLabel = "\ln{(%s \ \ %s)}(%s%s)" % (yname, xname, yunits, xunits)
769    if yLabel == "ln(y*x^(2))":
770        data.transformY(DataTransform.toLogYX2, DataTransform.errToLogYX2)
771        xunits = convertUnit(2, xunits)
772        yLabel = "\ln (%s \ \ %s^{2})(%s%s)" % (yname, xname, yunits, xunits)
773    if yLabel == "ln(y*x^(4))":
774        data.transformY(DataTransform.toLogYX4, DataTransform.errToLogYX4)
775        xunits = convertUnit(4, xunits)
776        yLabel = "\ln (%s \ \ %s^{4})(%s%s)" % (yname, xname, yunits, xunits)
777    if yLabel == "log10(y*x^(4))":
778        data.transformY(DataTransform.toYX4, DataTransform.errToYX4)
779        xunits = convertUnit(4, xunits)
780        yscale = 'log'
781        yLabel = "%s \ \ %s^{4}(%s%s)" % (yname, xname, yunits, xunits)
782
783    # Perform the transformation of data in data1d->View
784    data.transformView()
785
786    return (xLabel, yLabel, xscale, yscale)
787
788def dataFromItem(item):
789    """
790    Retrieve Data1D/2D component from QStandardItem.
791    The assumption - data stored in SasView standard, in child 0
792    """
793    return item.child(0).data()
794
795def formatNumber(value, high=False):
796    """
797    Return a float in a standardized, human-readable formatted string.
798    This is used to output readable (e.g. x.xxxe-y) values to the panel.
799    """
800    try:
801        value = float(value)
802    except:
803        output = "NaN"
804        return output.lstrip().rstrip()
805
806    if high:
807        output = "%-7.5g" % value
808
809    else:
810        output = "%-5.3g" % value
811    return output.lstrip().rstrip()
812
813def convertUnitToHTML(unit):
814    """
815    Convert ASCII unit display into well rendering HTML
816    """
817    if unit == "1/A":
818        return "&#x212B;<sup>-1</sup>"
819    elif unit == "1/cm":
820        return "cm<sup>-1</sup>"
821    elif unit == "Ang":
822        return "&#x212B;"
823    elif unit == "1e-6/Ang^2":
824        return "10<sup>-6</sup>/&#x212B;<sup>2</sup>"
825    elif unit == "inf":
826        return "&#x221e;"
827    elif unit == "-inf":
828        return "-&#x221e;"
829    else:
830        return unit
831
832def parseName(name, expression):
833    """
834    remove "_" in front of a name
835    """
836    if re.match(expression, name) is not None:
837        word = re.split(expression, name, 1)
838        for item in word:           
839            if item.lstrip().rstrip() != '':
840                return item
841    else:
842        return name
843
844def toDouble(value_string):
845    """
846    toFloat conversion which cares deeply about user's locale
847    """
848    # Holy shit this escalated quickly in Qt5.
849    # No more float() cast on general locales.
850    value = QtCore.QLocale().toFloat(value_string)
851    if value[1]:
852        return value[0]
853
854    # Try generic locale
855    value = QtCore.QLocale(QtCore.QLocale('en_US')).toFloat(value_string)
856    if value[1]:
857        return value[0]
858    else:
859        raise ValueError
860
861class DoubleValidator(QtGui.QDoubleValidator):
862    """
863    Allow only dots as decimal separator
864    """
865    def validate(self, input, pos):
866        """
867        Return invalid for commas
868        """
869        if (',' in input):
870            return (QtGui.QValidator.Invalid, input, pos)
871        return super(DoubleValidator, self).validate(input, pos)
872
873    def fixup(self, input):
874        """
875        Correct (remove) potential preexisting content
876        """
877        QtGui.QDoubleValidator.fixup(input)
878        input = input.replace(",", "")
879
880def enum(*sequential, **named):
881    """Create an enumeration object from a list of strings"""
882    enums = dict(zip(sequential, range(len(sequential))), **named)
883    return type('Enum', (), enums)
Note: See TracBrowser for help on using the repository browser.