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

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

unit tests for constraints: FittingWidget?, FittingPerspective?

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