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

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

Initial implementation of Adam Washington's Corfunc perspective.
Converted to py3/Qt5.

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