Changeset 135f544 in sasview


Ignore:
Timestamp:
Feb 22, 2018 6:04:57 AM (6 years ago)
Author:
Piotr Rozyczko <rozyczko@…>
Branches:
ESS_GUI, ESS_GUI_Docs, ESS_GUI_batch_fitting, ESS_GUI_bumps_abstraction, ESS_GUI_iss1116, ESS_GUI_iss879, ESS_GUI_iss959, ESS_GUI_opencl, ESS_GUI_ordering, ESS_GUI_sync_sascalc
Children:
c3eb858
Parents:
626c7c5 (diff), 06234fc (diff)
Note: this is a merge changeset, the changes displayed below correspond to the merge itself.
Use the (diff) links above to see all the changes relative to each parent.
Message:

Merge branch 'ESS_GUI_cns' into ESS_GUI

Files:
11 added
1 deleted
20 edited

Legend:

Unmodified
Added
Removed
  • installers/installer_generator.py

    • Property mode changed from 100644 to 100755
  • setup.py

    r0261bc1 r14ec91c5  
    5353    if os.path.isfile(f_path): 
    5454        os.remove(f_path) 
    55     f_path = os.path.join(sas_dir, "categories.json") 
    56     if os.path.isfile(f_path): 
    57         os.remove(f_path) 
     55    #f_path = os.path.join(sas_dir, "categories.json") 
     56    #if os.path.isfile(f_path): 
     57    #    os.remove(f_path) 
    5858    f_path = os.path.join(sas_dir, 'config', "custom_config.py") 
    5959    if os.path.isfile(f_path): 
  • src/sas/qtgui/GUITests.py

    r50bfab0 rda9a0722  
    5656from Perspectives.Fitting.UnitTesting import FitPageTest 
    5757from Perspectives.Fitting.UnitTesting import FittingOptionsTest 
     58from Perspectives.Fitting.UnitTesting import MultiConstraintTest 
     59from Perspectives.Fitting.UnitTesting import ComplexConstraintTest 
     60from Perspectives.Fitting.UnitTesting import ConstraintWidgetTest 
     61 
    5862#  Invariant 
    5963from Perspectives.Invariant.UnitTesting import InvariantPerspectiveTest 
    60 from Perspectives.Invariant.UnitTesting import InvariantDetailsTest 
     64 
    6165#  Inversion 
    6266from Perspectives.Inversion.UnitTesting import InversionPerspectiveTest 
    63  
    6467 
    6568def suite(): 
     
    114117        unittest.makeSuite(FitPageTest.FitPageTest,                       'test'), 
    115118        unittest.makeSuite(FittingOptionsTest.FittingOptionsTest,         'test'), 
     119        unittest.makeSuite(MultiConstraintTest.MultiConstraintTest,       'test'), 
     120        unittest.makeSuite(ConstraintWidgetTest.ConstraintWidgetTest,     'test'), 
     121        unittest.makeSuite(ComplexConstraintTest.ComplexConstraintTest,   'test'), 
     122 
    116123        #  Invariant 
    117124        unittest.makeSuite(InvariantPerspectiveTest.InvariantPerspectiveTest,  'test'), 
    118         unittest.makeSuite(InvariantDetailsTest.InvariantDetailsTest,     'test'), 
    119125        #  Inversion 
    120126        unittest.makeSuite(InversionPerspectiveTest.InversionTest,  'test'), 
    121      ) 
     127        ) 
    122128    return unittest.TestSuite(suites) 
    123129 
  • src/sas/qtgui/MainWindow/GuiManager.py

    re90988c r14ec91c5  
    99from PyQt5.QtGui import * 
    1010from PyQt5.QtCore import Qt, QLocale, QUrl 
    11 from PyQt5.QtWebKitWidgets import QWebView 
    1211 
    1312from twisted.internet import reactor 
     
    185184 
    186185        # Save users from themselves... 
    187         if isinstance(self._current_perspective, Perspectives.PERSPECTIVES[str(perspective_name)]): 
    188             self.setupPerspectiveMenubarOptions(self._current_perspective) 
    189             return 
     186        #if isinstance(self._current_perspective, Perspectives.PERSPECTIVES[str(perspective_name)]): 
     187        self.setupPerspectiveMenubarOptions(self._current_perspective) 
     188        #    return 
    190189 
    191190        # Close the previous perspective 
     
    623622    def actionConstrained_Fit(self): 
    624623        """ 
    625         """ 
    626         print("actionConstrained_Fit TRIGGERED") 
    627         pass 
     624        Add a new Constrained and Simult. Fit page in the fitting perspective. 
     625        """ 
     626        per = self.perspective() 
     627        if not isinstance(per, FittingWindow): 
     628            return 
     629        per.addConstraintTab() 
    628630 
    629631    def actionCombine_Batch_Fit(self): 
  • src/sas/qtgui/MainWindow/UnitTesting/GuiManagerTest.py

    r8353d90 r725d9c06  
    99from PyQt5.QtTest import QTest 
    1010from PyQt5.QtCore import * 
    11 from PyQt5.QtWebKit import * 
    1211from unittest.mock import MagicMock 
    1312 
     
    272271    #### HELP #### 
    273272    # test when PyQt5 works with html 
    274     def notestActionDocumentation(self): 
     273    def testActionDocumentation(self): 
    275274        """ 
    276275        Menu Help/Documentation 
    277276        """ 
    278         #Mock the QWebView method 
    279         QWebView.show = MagicMock() 
    280  
    281         # Assure the filename is correct 
    282         self.assertIn("index.html", self.manager._helpLocation) 
     277        webbrowser.open = MagicMock() 
    283278 
    284279        # Invoke the action 
    285280        self.manager.actionDocumentation() 
    286281 
    287         # Check if show() got called 
    288         self.assertTrue(QWebView.show.called) 
     282        # see that webbrowser open was attempted 
     283        webbrowser.open.assert_called_once() 
     284 
    289285 
    290286    def skip_testActionTutorial(self): 
  • src/sas/qtgui/Perspectives/Fitting/FitThread.py

    • Property mode changed from 100755 to 100644
    rb3e8629 r235d766  
    9595            # print "ERROR IN FIT THREAD: ", traceback.format_exc() 
    9696            if self.handler is not None: 
    97                 self.handler.error(msg=traceback.format_exc()) 
     97                self.handler.error(msg=str(ex)) 
     98                self.completefn(None) 
     99            else: 
     100                return(None) 
    98101 
    99102 
  • src/sas/qtgui/Perspectives/Fitting/FittingPerspective.py

    re90988c r14ec91c5  
    1111 
    1212from sas.qtgui.Perspectives.Fitting.FittingWidget import FittingWidget 
     13from sas.qtgui.Perspectives.Fitting.ConstraintWidget import ConstraintWidget 
    1314from sas.qtgui.Perspectives.Fitting.FittingOptions import FittingOptions 
    1415from sas.qtgui.Perspectives.Fitting.GPUOptions import GPUOptions 
     
    1718    """ 
    1819    """ 
     20    tabsModifiedSignal = QtCore.pyqtSignal() 
     21    fittingStartedSignal = QtCore.pyqtSignal(list) 
     22    fittingStoppedSignal = QtCore.pyqtSignal(list) 
     23 
    1924    name = "Fitting" # For displaying in the combo box in DataExplorer 
    2025    def __init__(self, parent=None, data=None): 
     
    3742        self.optimizer = 'Levenberg-Marquardt' 
    3843 
    39         # Dataset inde -> Fitting tab mapping 
     44        # Dataset index -> Fitting tab mapping 
    4045        self.dataToFitTab = {} 
    4146 
    4247        # The tabs need to be closeable 
    4348        self.setTabsClosable(True) 
     49 
     50        # The tabs need to be movabe 
     51        self.setMovable(True) 
    4452 
    4553        self.communicate = self.parent.communicator() 
     
    5159        self.tabCloseRequested.connect(self.tabCloses) 
    5260        self.communicate.dataDeletedSignal.connect(self.dataDeleted) 
     61        self.fittingStartedSignal.connect(self.onFittingStarted) 
     62        self.fittingStoppedSignal.connect(self.onFittingStopped) 
    5363 
    5464        # Perspective window not allowed to close by default 
     
    6676        self.gpu_options_widget = GPUOptions(self) 
    6777 
    68         #self.setWindowTitle('Fit panel - Active Fitting Optimizer: %s' % self.optimizer) 
    6978        self.updateWindowTitle() 
    7079 
     
    7988    def setClosable(self, value=True): 
    8089        """ 
    81         Allow outsiders close this widget 
     90        Allow outsiders to close this widget 
    8291        """ 
    8392        assert isinstance(value, bool) 
     
    108117        tab.is_batch_fitting = is_batch 
    109118        # Add this tab to the object library so it can be retrieved by scripting/jupyter 
    110         tab_name = self.tabName(is_batch=is_batch) 
     119        tab_name = self.getTabName(is_batch=is_batch) 
    111120        ObjectLibrary.addObject(tab_name, tab) 
    112121        self.tabs.append(tab) 
     
    115124        self.maxIndex += 1 
    116125        self.addTab(tab, tab_name) 
     126        self.tabsModifiedSignal.emit() 
     127 
     128    def addConstraintTab(self): 
     129        """ 
     130        Add a new C&S fitting tab 
     131        """ 
     132        tabs = [isinstance(tab, ConstraintWidget) for tab in self.tabs] 
     133        if any(tabs): 
     134            # We already have a C&S tab: show it 
     135            self.setCurrentIndex(tabs.index(True)) 
     136            return 
     137        tab     = ConstraintWidget(parent=self) 
     138        # Add this tab to the object library so it can be retrieved by scripting/jupyter 
     139        tab_name = self.getCSTabName() # TODO update the tab name scheme 
     140        ObjectLibrary.addObject(tab_name, tab) 
     141        self.tabs.append(tab) 
     142        self.addTab(tab, tab_name) 
    117143 
    118144    def updateFitDict(self, item_key, tab_name): 
     
    126152            self.dataToFitTab[item_key_str] = [tab_name] 
    127153 
    128         #print "CURRENT dict: ", self.dataToFitTab 
    129  
    130     def tabName(self, is_batch=False): 
     154    def getTabName(self, is_batch=False): 
    131155        """ 
    132156        Get the new tab name, based on the number of fitting tabs so far 
     
    134158        page_name = "BatchPage" if is_batch else "FitPage" 
    135159        page_name = page_name + str(self.maxIndex) 
     160        return page_name 
     161 
     162    def getCSTabName(self): 
     163        """ 
     164        Get the new tab name, based on the number of fitting tabs so far 
     165        """ 
     166        page_name = "Const. & Simul. Fit" 
    136167        return page_name 
    137168 
     
    162193            self.removeTab(index) 
    163194            del self.tabs[index] 
     195            self.tabsModifiedSignal.emit() 
    164196        except IndexError: 
    165197            # The tab might have already been deleted previously 
     
    189221                self.dataToFitTab.pop(index_to_delete_str) 
    190222 
    191         #print "CURRENT dict: ", self.dataToFitTab 
    192  
    193223    def allowBatch(self): 
    194224        """ 
     
    213243            raise AttributeError(msg) 
    214244 
     245        if is_batch: 
     246            # Just create a new fit tab. No empty batchFit tabs 
     247            self.addFit(data_item, is_batch=is_batch) 
     248            return 
     249 
    215250        items = [data_item] if is_batch else data_item 
    216  
    217251        for data in items: 
    218252            # Find the first unassigned tab. 
    219253            # If none, open a new tab. 
    220             available_tabs = list([tab.acceptsData() for tab in self.tabs]) 
     254            available_tabs = [tab.acceptsData() for tab in self.tabs] 
    221255 
    222256            if numpy.any(available_tabs): 
     
    238272        self.updateWindowTitle() 
    239273 
     274    def onFittingStarted(self, tabs_for_fitting=None): 
     275        """ 
     276        Notify tabs listed in tabs_for_fitting 
     277        that the fitting thread started 
     278        """ 
     279        assert(isinstance(tabs_for_fitting, list)) 
     280        assert(len(tabs_for_fitting)>0) 
     281 
     282        for tab_object in self.tabs: 
     283            if not isinstance(tab_object, FittingWidget): 
     284                continue 
     285            page_name = "Page%s"%tab_object.tab_id 
     286            if any([page_name in tab for tab in tabs_for_fitting]): 
     287                tab_object.setFittingStarted() 
     288 
    240289        pass 
     290 
     291    def onFittingStopped(self, tabs_for_fitting=None): 
     292        """ 
     293        Notify tabs listed in tabs_for_fitting 
     294        that the fitting thread stopped 
     295        """ 
     296        assert(isinstance(tabs_for_fitting, list)) 
     297        assert(len(tabs_for_fitting)>0) 
     298 
     299        for tab_object in self.tabs: 
     300            if not isinstance(tab_object, FittingWidget): 
     301                continue 
     302            page_name = "Page%s"%tab_object.tab_id 
     303            if any([page_name in tab for tab in tabs_for_fitting]): 
     304                tab_object.setFittingStopped() 
     305 
     306        pass 
  • src/sas/qtgui/Perspectives/Fitting/FittingWidget.py

    re90988c r06234fc  
    2323 
    2424import sas.qtgui.Utilities.GuiUtils as GuiUtils 
     25import sas.qtgui.Utilities.LocalConfig as LocalConfig 
    2526from sas.qtgui.Utilities.CategoryInstaller import CategoryInstaller 
    2627from sas.qtgui.Plotting.PlotterData import Data1D 
     
    4142from sas.qtgui.Perspectives.Fitting.ViewDelegate import PolyViewDelegate 
    4243from sas.qtgui.Perspectives.Fitting.ViewDelegate import MagnetismViewDelegate 
     44from sas.qtgui.Perspectives.Fitting.Constraint import Constraint 
     45from sas.qtgui.Perspectives.Fitting.MultiConstraint import MultiConstraint 
    4346 
    4447 
     
    5154DEFAULT_POLYDISP_FUNCTION = 'gaussian' 
    5255 
    53 USING_TWISTED = True 
    54 #USING_TWISTED = False 
    5556 
    5657class ToolTippedItemModel(QtGui.QStandardItemModel): 
     
    8081    Main widget for selecting form and structure factor models 
    8182    """ 
     83    constraintAddedSignal = QtCore.pyqtSignal(list) 
     84    newModelSignal = QtCore.pyqtSignal() 
    8285    def __init__(self, parent=None, data=None, tab_id=1): 
    8386 
     
    178181        # Batch/single fitting 
    179182        self.is_batch_fitting = False 
     183        self.is_chain_fitting = False 
    180184        # Current SasModel in view 
    181185        self.kernel_module = None 
     
    207211        self.orig_poly_index = 3 
    208212 
     213        # Page id for fitting 
     214        # To keep with previous SasView values, use 200 as the start offset 
     215        self.page_id = 200 + self.tab_id 
     216 
    209217        # Data for chosen model 
    210218        self.model_data = None 
     
    301309        self.lstParams.setStyleSheet(stylesheet) 
    302310        self.lstParams.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) 
    303         self.lstParams.customContextMenuRequested.connect(self.showModelDescription) 
     311        self.lstParams.customContextMenuRequested.connect(self.showModelContextMenu) 
    304312        self.lstParams.setAttribute(QtCore.Qt.WA_MacShowFocusRect, False) 
    305313        # Poly model displayed in poly list 
     
    350358                self.cbFileNames.addItem(filename) 
    351359            self.cbFileNames.setVisible(True) 
     360            self.chkChainFit.setEnabled(True) 
     361            self.chkChainFit.setVisible(True) 
    352362            # This panel is not designed to view individual fits, so disable plotting 
    353363            self.cmdPlot.setVisible(False) 
     
    389399        """ Enable/disable the magnetism tab """ 
    390400        self.tabFitting.setTabEnabled(TAB_MAGNETISM, isChecked) 
     401 
     402    def toggleChainFit(self, isChecked): 
     403        """ Enable/disable chain fitting """ 
     404        self.is_chain_fitting = isChecked 
    391405 
    392406    def toggle2D(self, isChecked): 
     
    412426        self.chkMagnetism.setEnabled(False) 
    413427        self.chkMagnetism.setCheckState(False) 
     428        self.chkChainFit.setEnabled(False) 
     429        self.chkChainFit.setVisible(False) 
    414430        # Tabs 
    415431        self.tabFitting.setTabEnabled(TAB_POLY, False) 
     
    434450        self.chkPolydispersity.toggled.connect(self.togglePoly) 
    435451        self.chkMagnetism.toggled.connect(self.toggleMagnetism) 
     452        self.chkChainFit.toggled.connect(self.toggleChainFit) 
    436453        # Buttons 
    437454        self.cmdFit.clicked.connect(self.onFit) 
     
    442459        # Respond to change in parameters from the UI 
    443460        self._model_model.itemChanged.connect(self.onMainParamsChange) 
     461        #self.constraintAddedSignal.connect(self.modifyViewOnConstraint) 
    444462        self._poly_model.itemChanged.connect(self.onPolyModelChange) 
    445463        self._magnet_model.itemChanged.connect(self.onMagnetModelChange) 
     
    448466        self.options_widget.plot_signal.connect(self.onOptionsUpdate) 
    449467 
    450     def showModelDescription(self, position): 
    451         """ 
    452         Shows a window with model description, when right clicked in the treeview 
     468    def modelName(self): 
     469        """ 
     470        Returns model name, by default M<tab#>, e.g. M1, M2 
     471        """ 
     472        return "M%i" % self.tab_id 
     473 
     474    def nameForFittedData(self, name): 
     475        """ 
     476        Generate name for the current fit 
     477        """ 
     478        if self.is2D: 
     479            name += "2d" 
     480        name = "%s [%s]" % (self.modelName(), name) 
     481        return name 
     482 
     483    def showModelContextMenu(self, position): 
     484        """ 
     485        Show context specific menu in the parameter table. 
     486        When clicked on parameter(s): fitting/constraints options 
     487        When clicked on white space: model description 
     488        """ 
     489        rows = [s.row() for s in self.lstParams.selectionModel().selectedRows()] 
     490        menu = self.showModelDescription() if not rows else self.modelContextMenu(rows) 
     491        try: 
     492            menu.exec_(self.lstParams.viewport().mapToGlobal(position)) 
     493        except AttributeError as ex: 
     494            logging.error("Error generating context menu: %s" % ex) 
     495        return 
     496 
     497    def modelContextMenu(self, rows): 
     498        """ 
     499        Create context menu for the parameter selection 
     500        """ 
     501        menu = QtWidgets.QMenu() 
     502        num_rows = len(rows) 
     503        if num_rows < 1: 
     504            return menu 
     505        # Select for fitting 
     506        param_string = "parameter " if num_rows==1 else "parameters " 
     507        to_string = "to its current value" if num_rows==1 else "to their current values" 
     508        has_constraints = any([self.rowHasConstraint(i) for i in rows]) 
     509 
     510        self.actionSelect = QtWidgets.QAction(self) 
     511        self.actionSelect.setObjectName("actionSelect") 
     512        self.actionSelect.setText(QtCore.QCoreApplication.translate("self", "Select "+param_string+" for fitting")) 
     513        # Unselect from fitting 
     514        self.actionDeselect = QtWidgets.QAction(self) 
     515        self.actionDeselect.setObjectName("actionDeselect") 
     516        self.actionDeselect.setText(QtCore.QCoreApplication.translate("self", "De-select "+param_string+" from fitting")) 
     517 
     518        self.actionConstrain = QtWidgets.QAction(self) 
     519        self.actionConstrain.setObjectName("actionConstrain") 
     520        self.actionConstrain.setText(QtCore.QCoreApplication.translate("self", "Constrain "+param_string + to_string)) 
     521 
     522        self.actionRemoveConstraint = QtWidgets.QAction(self) 
     523        self.actionRemoveConstraint.setObjectName("actionRemoveConstrain") 
     524        self.actionRemoveConstraint.setText(QtCore.QCoreApplication.translate("self", "Remove constraint")) 
     525 
     526        self.actionMultiConstrain = QtWidgets.QAction(self) 
     527        self.actionMultiConstrain.setObjectName("actionMultiConstrain") 
     528        self.actionMultiConstrain.setText(QtCore.QCoreApplication.translate("self", "Constrain selected parameters to their current values")) 
     529 
     530        self.actionMutualMultiConstrain = QtWidgets.QAction(self) 
     531        self.actionMutualMultiConstrain.setObjectName("actionMutualMultiConstrain") 
     532        self.actionMutualMultiConstrain.setText(QtCore.QCoreApplication.translate("self", "Mutual constrain of selected parameters...")) 
     533 
     534        menu.addAction(self.actionSelect) 
     535        menu.addAction(self.actionDeselect) 
     536        menu.addSeparator() 
     537 
     538        if has_constraints: 
     539            menu.addAction(self.actionRemoveConstraint) 
     540            #if num_rows == 1: 
     541            #    menu.addAction(self.actionEditConstraint) 
     542        else: 
     543            menu.addAction(self.actionConstrain) 
     544            if num_rows == 2: 
     545                menu.addAction(self.actionMutualMultiConstrain) 
     546 
     547        # Define the callbacks 
     548        self.actionConstrain.triggered.connect(self.addSimpleConstraint) 
     549        self.actionRemoveConstraint.triggered.connect(self.deleteConstraint) 
     550        self.actionMutualMultiConstrain.triggered.connect(self.showMultiConstraint) 
     551        self.actionSelect.triggered.connect(self.selectParameters) 
     552        self.actionDeselect.triggered.connect(self.deselectParameters) 
     553        return menu 
     554 
     555    def showMultiConstraint(self): 
     556        """ 
     557        Show the constraint widget and receive the expression 
     558        """ 
     559        selected_rows = self.lstParams.selectionModel().selectedRows() 
     560        # There have to be only two rows selected. The caller takes care of that 
     561        # but let's check the correctness. 
     562        assert(len(selected_rows)==2) 
     563 
     564        params_list = [s.data() for s in selected_rows] 
     565        # Create and display the widget for param1 and param2 
     566        mc_widget = MultiConstraint(self, params=params_list) 
     567        if mc_widget.exec_() != QtWidgets.QDialog.Accepted: 
     568            return 
     569 
     570        constraint = Constraint() 
     571        c_text = mc_widget.txtConstraint.text() 
     572 
     573        # widget.params[0] is the parameter we're constraining 
     574        constraint.param = mc_widget.params[0] 
     575        # Function should have the model name preamble 
     576        model_name = self.kernel_module.name 
     577        constraint.func = model_name + "." + c_text 
     578        # Which row is the constrained parameter in? 
     579        row = self.getRowFromName(constraint.param) 
     580 
     581        # Create a new item and add the Constraint object as a child 
     582        self.addConstraintToRow(constraint=constraint, row=row) 
     583 
     584    def getRowFromName(self, name): 
     585        """ 
     586        Given parameter name get the row number in self._model_model 
     587        """ 
     588        for row in range(self._model_model.rowCount()): 
     589            row_name = self._model_model.item(row).text() 
     590            if row_name == name: 
     591                return row 
     592        return None 
     593 
     594    def getParamNames(self): 
     595        """ 
     596        Return list of all parameters for the current model 
     597        """ 
     598        return [self._model_model.item(row).text() for row in range(self._model_model.rowCount())] 
     599 
     600    def modifyViewOnRow(self, row, font=None, brush=None): 
     601        """ 
     602        Chage how the given row of the main model is shown 
     603        """ 
     604        fields_enabled = False 
     605        if font is None: 
     606            font = QtGui.QFont() 
     607            fields_enabled = True 
     608        if brush is None: 
     609            brush = QtGui.QBrush() 
     610            fields_enabled = True 
     611        self._model_model.blockSignals(True) 
     612        # Modify font and foreground of affected rows 
     613        for column in range(0, self._model_model.columnCount()): 
     614            self._model_model.item(row, column).setForeground(brush) 
     615            self._model_model.item(row, column).setFont(font) 
     616            self._model_model.item(row, column).setEditable(fields_enabled) 
     617        self._model_model.blockSignals(False) 
     618 
     619    def addConstraintToRow(self, constraint=None, row=0): 
     620        """ 
     621        Adds the constraint object to requested row 
     622        """ 
     623        # Create a new item and add the Constraint object as a child 
     624        assert(isinstance(constraint, Constraint)) 
     625        assert(0<=row<=self._model_model.rowCount()) 
     626 
     627        item = QtGui.QStandardItem() 
     628        item.setData(constraint) 
     629        self._model_model.item(row, 1).setChild(0, item) 
     630        # Set min/max to the value constrained 
     631        self.constraintAddedSignal.emit([row]) 
     632        # Show visual hints for the constraint 
     633        font = QtGui.QFont() 
     634        font.setItalic(True) 
     635        brush = QtGui.QBrush(QtGui.QColor('blue')) 
     636        self.modifyViewOnRow(row, font=font, brush=brush) 
     637        self.communicate.statusBarUpdateSignal.emit('Constraint added') 
     638 
     639    def addSimpleConstraint(self): 
     640        """ 
     641        Adds a constraint on a single parameter. 
     642        """ 
     643        min_col = self.lstParams.itemDelegate().param_min 
     644        max_col = self.lstParams.itemDelegate().param_max 
     645        for row in self.selectedParameters(): 
     646            param = self._model_model.item(row, 0).text() 
     647            value = self._model_model.item(row, 1).text() 
     648            min_t = self._model_model.item(row, min_col).text() 
     649            max_t = self._model_model.item(row, max_col).text() 
     650            # Create a Constraint object 
     651            constraint = Constraint(param=param, value=value, min=min_t, max=max_t) 
     652            # Create a new item and add the Constraint object as a child 
     653            item = QtGui.QStandardItem() 
     654            item.setData(constraint) 
     655            self._model_model.item(row, 1).setChild(0, item) 
     656            # Assumed correctness from the validator 
     657            value = float(value) 
     658            # BUMPS calculates log(max-min) without any checks, so let's assign minor range 
     659            min_v = value - (value/10000.0) 
     660            max_v = value + (value/10000.0) 
     661            # Set min/max to the value constrained 
     662            self._model_model.item(row, min_col).setText(str(min_v)) 
     663            self._model_model.item(row, max_col).setText(str(max_v)) 
     664            self.constraintAddedSignal.emit([row]) 
     665            # Show visual hints for the constraint 
     666            font = QtGui.QFont() 
     667            font.setItalic(True) 
     668            brush = QtGui.QBrush(QtGui.QColor('blue')) 
     669            self.modifyViewOnRow(row, font=font, brush=brush) 
     670        self.communicate.statusBarUpdateSignal.emit('Constraint added') 
     671 
     672    def deleteConstraint(self): 
     673        """ 
     674        Delete constraints from selected parameters. 
     675        """ 
     676        self.deleteConstraintOnParameter(param=None) 
     677 
     678    def deleteConstraintOnParameter(self, param=None): 
     679        """ 
     680        Delete the constraint on model parameter 'param' 
     681        """ 
     682        min_col = self.lstParams.itemDelegate().param_min 
     683        max_col = self.lstParams.itemDelegate().param_max 
     684        for row in range(self._model_model.rowCount()): 
     685            # Get the Constraint object from of the model item 
     686            item = self._model_model.item(row, 1) 
     687            if not item.hasChildren(): 
     688                continue 
     689            constraint = item.child(0).data() 
     690            if constraint is None: 
     691                continue 
     692            if not isinstance(constraint, Constraint): 
     693                continue 
     694            if param and constraint.param != param: 
     695                continue 
     696            # Now we got the right row. Delete the constraint and clean up 
     697            # Retrieve old values and put them on the model 
     698            if constraint.min is not None: 
     699                self._model_model.item(row, min_col).setText(constraint.min) 
     700            if constraint.max is not None: 
     701                self._model_model.item(row, max_col).setText(constraint.max) 
     702            # Remove constraint item 
     703            item.removeRow(0) 
     704            self.constraintAddedSignal.emit([row]) 
     705            self.modifyViewOnRow(row) 
     706 
     707        self.communicate.statusBarUpdateSignal.emit('Constraint removed') 
     708 
     709    def getConstraintForRow(self, row): 
     710        """ 
     711        For the given row, return its constraint, if any 
     712        """ 
     713        try: 
     714            item = self._model_model.item(row, 1) 
     715            return item.child(0).data() 
     716        except AttributeError: 
     717            # return none when no constraints 
     718            return None 
     719 
     720    def rowHasConstraint(self, row): 
     721        """ 
     722        Finds out if row of the main model has a constraint child 
     723        """ 
     724        item = self._model_model.item(row,1) 
     725        if item.hasChildren(): 
     726            c = item.child(0).data() 
     727            if isinstance(c, Constraint): 
     728                return True 
     729        return False 
     730 
     731    def rowHasActiveConstraint(self, row): 
     732        """ 
     733        Finds out if row of the main model has an active constraint child 
     734        """ 
     735        item = self._model_model.item(row,1) 
     736        if item.hasChildren(): 
     737            c = item.child(0).data() 
     738            if isinstance(c, Constraint) and c.active: 
     739                return True 
     740        return False 
     741 
     742    def rowHasActiveComplexConstraint(self, row): 
     743        """ 
     744        Finds out if row of the main model has an active, nontrivial constraint child 
     745        """ 
     746        item = self._model_model.item(row,1) 
     747        if item.hasChildren(): 
     748            c = item.child(0).data() 
     749            if isinstance(c, Constraint) and c.func and c.active: 
     750                return True 
     751        return False 
     752 
     753    def selectParameters(self): 
     754        """ 
     755        Selected parameter is chosen for fitting 
     756        """ 
     757        status = QtCore.Qt.Checked 
     758        self.setParameterSelection(status) 
     759 
     760    def deselectParameters(self): 
     761        """ 
     762        Selected parameters are removed for fitting 
     763        """ 
     764        status = QtCore.Qt.Unchecked 
     765        self.setParameterSelection(status) 
     766 
     767    def selectedParameters(self): 
     768        """ Returns list of selected (highlighted) parameters """ 
     769        return [s.row() for s in self.lstParams.selectionModel().selectedRows() 
     770                if self.isCheckable(s.row())] 
     771 
     772    def setParameterSelection(self, status=QtCore.Qt.Unchecked): 
     773        """ 
     774        Selected parameters are chosen for fitting 
     775        """ 
     776        # Convert to proper indices and set requested enablement 
     777        for row in self.selectedParameters(): 
     778            self._model_model.item(row, 0).setCheckState(status) 
     779 
     780    def getConstraintsForModel(self): 
     781        """ 
     782        Return a list of tuples. Each tuple contains constraints mapped as 
     783        ('constrained parameter', 'function to constrain') 
     784        e.g. [('sld','5*sld_solvent')] 
     785        """ 
     786        param_number = self._model_model.rowCount() 
     787        params = [(self._model_model.item(s, 0).text(), 
     788                    self._model_model.item(s, 1).child(0).data().func) 
     789                    for s in range(param_number) if self.rowHasActiveConstraint(s)] 
     790        return params 
     791 
     792    def getComplexConstraintsForModel(self): 
     793        """ 
     794        Return a list of tuples. Each tuple contains constraints mapped as 
     795        ('constrained parameter', 'function to constrain') 
     796        e.g. [('sld','5*M2.sld_solvent')]. 
     797        Only for constraints with defined VALUE 
     798        """ 
     799        param_number = self._model_model.rowCount() 
     800        params = [(self._model_model.item(s, 0).text(), 
     801                    self._model_model.item(s, 1).child(0).data().func) 
     802                    for s in range(param_number) if self.rowHasActiveComplexConstraint(s)] 
     803        return params 
     804 
     805    def getConstraintObjectsForModel(self): 
     806        """ 
     807        Returns Constraint objects present on the whole model 
     808        """ 
     809        param_number = self._model_model.rowCount() 
     810        constraints = [self._model_model.item(s, 1).child(0).data() 
     811                       for s in range(param_number) if self.rowHasConstraint(s)] 
     812 
     813        return constraints 
     814 
     815    def showModelDescription(self): 
     816        """ 
     817        Creates a window with model description, when right clicked in the treeview 
    453818        """ 
    454819        msg = 'Model description:\n' 
     
    466831        action.setDefaultWidget(label) 
    467832        menu.addAction(action) 
    468         menu.exec_(self.lstParams.viewport().mapToGlobal(position)) 
     833        return menu 
    469834 
    470835    def onSelectModel(self): 
     
    506871        self.respondToModelStructure(model=model, structure_factor=structure) 
    507872 
    508     def respondToModelStructure(self, model=None, structure_factor=None): 
    509         # Set enablement on calculate/plot 
    510         self.cmdPlot.setEnabled(True) 
    511  
    512         # kernel parameters -> model_model 
    513         self.SASModelToQModel(model, structure_factor) 
    514  
     873    def replaceConstraintName(self, old_name, new_name=""): 
     874        """ 
     875        Replace names of models in defined constraints 
     876        """ 
     877        param_number = self._model_model.rowCount() 
     878        # loop over parameters 
     879        for row in range(param_number): 
     880            if self.rowHasConstraint(row): 
     881                func = self._model_model.item(row, 1).child(0).data().func 
     882                if old_name in func: 
     883                    new_func = func.replace(old_name, new_name) 
     884                    self._model_model.item(row, 1).child(0).data().func = new_func 
     885 
     886    def updateData(self): 
     887        """ 
     888        Helper function for recalculation of data used in plotting 
     889        """ 
     890        # Update the chart 
    515891        if self.data_is_loaded: 
    516892            self.cmdPlot.setText("Show Plot") 
     
    521897            self.createDefaultDataset() 
    522898 
     899    def respondToModelStructure(self, model=None, structure_factor=None): 
     900        # Set enablement on calculate/plot 
     901        self.cmdPlot.setEnabled(True) 
     902 
     903        # kernel parameters -> model_model 
     904        self.SASModelToQModel(model, structure_factor) 
     905 
     906        # Update plot 
     907        self.updateData() 
     908 
    523909        # Update state stack 
    524910        self.updateUndo() 
     911 
     912        # Let others know 
     913        self.newModelSignal.emit() 
    525914 
    526915    def onSelectCategory(self): 
     
    596985            # name of the function - just pass 
    597986            return 
    598         elif model_column == self.lstPoly.itemDelegate().poly_filename: 
    599             # filename for array - just pass 
    600             return 
    601987        else: 
    602988            try: 
     
    610996            self.kernel_module.setParam(parameter_name + '.' + \ 
    611997                                        self.lstPoly.itemDelegate().columnDict()[model_column], value) 
     998 
     999            # Update plot 
     1000            self.updateData() 
    6121001 
    6131002    def onMagnetModelChange(self, item): 
     
    6931082        Perform fitting on the current data 
    6941083        """ 
    695  
    696         # Data going in 
    697         data = self.logic.data 
    698         model = self.kernel_module 
    699         qmin = self.q_range_min 
    700         qmax = self.q_range_max 
    701         params_to_fit = self.parameters_to_fit 
    702  
    703         # Potential weights added directly to data 
    704         self.addWeightingToData(data) 
    705  
    706         # Potential smearing added 
    707         # Remember that smearing_min/max can be None -> 
    708         # deal with it until Python gets discriminated unions 
    709         smearing, accuracy, smearing_min, smearing_max = self.smearing_widget.state() 
    710  
    711         # These should be updating somehow? 
     1084        # initialize fitter constants 
    7121085        fit_id = 0 
    713         constraints = [] 
    714         smearer = None 
    715         page_id = [210] 
    7161086        handler = None 
    7171087        batch_inputs = {} 
    7181088        batch_outputs = {} 
    719         list_page_id = [page_id] 
    7201089        #--------------------------------- 
    721         if USING_TWISTED: 
     1090        if LocalConfig.USING_TWISTED: 
    7221091            handler = None 
    7231092            updater = None 
     
    7281097            updater = handler.update_fit 
    7291098 
    730         # Parameterize the fitter 
    731         fitters = [] 
    732         for fit_index in self.all_data: 
    733             fitter = Fit() 
    734             data = GuiUtils.dataFromItem(fit_index) 
    735             try: 
    736                 fitter.set_model(model, fit_id, params_to_fit, data=data, 
    737                              constraints=constraints) 
    738             except ValueError as ex: 
    739                 logging.error("Setting model parameters failed with: %s" % ex) 
    740                 return 
    741  
    742             qmin, qmax, _ = self.logic.computeRangeFromData(data) 
    743             fitter.set_data(data=data, id=fit_id, smearer=smearer, qmin=qmin, 
    744                             qmax=qmax) 
    745             fitter.select_problem_for_fit(id=fit_id, value=1) 
    746             fitter.fitter_id = page_id 
    747             fit_id += 1 
    748             fitters.append(fitter) 
     1099        # Prepare the fitter object 
     1100        try: 
     1101            fitters, _ = self.prepareFitters() 
     1102        except ValueError as ex: 
     1103            # This should not happen! GUI explicitly forbids this situation 
     1104            self.communicate.statusBarUpdateSignal.emit('Fitting attempt without parameters.') 
     1105            return 
    7491106 
    7501107        # Create the fitting thread, based on the fitter 
     
    7521109 
    7531110        calc_fit = FitThread(handler=handler, 
    754                                 fn=fitters, 
    755                                 batch_inputs=batch_inputs, 
    756                                 batch_outputs=batch_outputs, 
    757                                 page_id=list_page_id, 
    758                                 updatefn=updater, 
    759                                 completefn=completefn) 
    760  
    761         if USING_TWISTED: 
     1111                            fn=fitters, 
     1112                            batch_inputs=batch_inputs, 
     1113                            batch_outputs=batch_outputs, 
     1114                            page_id=[[self.page_id]], 
     1115                            updatefn=updater, 
     1116                            completefn=completefn) 
     1117 
     1118        if LocalConfig.USING_TWISTED: 
    7621119            # start the trhrhread with twisted 
    7631120            calc_thread = threads.deferToThread(calc_fit.compute) 
    764             calc_thread.addCallback(self.fitComplete) 
     1121            calc_thread.addCallback(completefn) 
    7651122            calc_thread.addErrback(self.fitFailed) 
    7661123        else: 
     
    7691126            calc_fit.ready(2.5) 
    7701127 
    771  
    772         #disable the Fit button 
    773         self.cmdFit.setText('Running...') 
    7741128        self.communicate.statusBarUpdateSignal.emit('Fitting started...') 
    775         self.cmdFit.setEnabled(False) 
     1129        # Disable some elements 
     1130        self.setFittingStarted() 
    7761131 
    7771132    def updateFit(self): 
     
    7921147        """ 
    7931148        #re-enable the Fit button 
    794         self.cmdFit.setText("Fit") 
    795         self.cmdFit.setEnabled(True) 
    796  
    797         print ("BATCH FITTING FINISHED") 
    798         # Add the Qt version of wx.aui.AuiNotebook and populate it 
    799         pass 
     1149        self.setFittingStopped() 
    8001150 
    8011151    def fitComplete(self, result): 
     
    8051155        """ 
    8061156        #re-enable the Fit button 
    807         self.cmdFit.setText("Fit") 
    808         self.cmdFit.setEnabled(True) 
    809  
    810         assert result is not None 
     1157        self.setFittingStopped() 
     1158 
     1159        #assert result is not None 
     1160        if assert in None: 
     1161            msg = "Fitting failed after: %s s.\n" % GuiUtils.formatNumber(elapsed) 
     1162            self.communicate.statusBarUpdateSignal.emit(msg) 
     1163            return 
    8111164 
    8121165        res_list = result[0][0] 
     
    8161169            np.any(res.pvec is None) or \ 
    8171170            not np.all(np.isfinite(res.pvec)): 
    818             msg = "Fitting did not converge!!!" 
     1171            msg = "Fitting did not converge!" 
    8191172            self.communicate.statusBarUpdateSignal.emit(msg) 
    8201173            logging.error(msg) 
     
    8461199        chi2_repr = GuiUtils.formatNumber(self.chi2, high=True) 
    8471200        self.lblChi2Value.setText(chi2_repr) 
     1201 
     1202    def prepareFitters(self, fitter=None, fit_id=0): 
     1203        """ 
     1204        Prepare the Fitter object for use in fitting 
     1205        """ 
     1206        # fitter = None -> single/batch fitting 
     1207        # fitter = Fit() -> simultaneous fitting 
     1208 
     1209        # Data going in 
     1210        data = self.logic.data 
     1211        model = self.kernel_module 
     1212        qmin = self.q_range_min 
     1213        qmax = self.q_range_max 
     1214        params_to_fit = self.parameters_to_fit 
     1215        if (not params_to_fit): 
     1216            raise ValueError('Fitting requires at least one parameter to optimize.') 
     1217 
     1218        # Potential weights added directly to data 
     1219        self.addWeightingToData(data) 
     1220 
     1221        # Potential smearing added 
     1222        # Remember that smearing_min/max can be None -> 
     1223        # deal with it until Python gets discriminated unions 
     1224        smearing, accuracy, smearing_min, smearing_max = self.smearing_widget.state() 
     1225 
     1226        constraints = self.getComplexConstraintsForModel() 
     1227        smearer = None 
     1228        handler = None 
     1229        batch_inputs = {} 
     1230        batch_outputs = {} 
     1231 
     1232        fitters = [] 
     1233        for fit_index in self.all_data: 
     1234            fitter_single = Fit() if fitter is None else fitter 
     1235            data = GuiUtils.dataFromItem(fit_index) 
     1236            try: 
     1237                fitter_single.set_model(model, fit_id, params_to_fit, data=data, 
     1238                             constraints=constraints) 
     1239            except ValueError as ex: 
     1240                logging.error("Setting model parameters failed with: %s" % ex) 
     1241                return 
     1242 
     1243            qmin, qmax, _ = self.logic.computeRangeFromData(data) 
     1244            fitter_single.set_data(data=data, id=fit_id, smearer=smearer, qmin=qmin, 
     1245                            qmax=qmax) 
     1246            fitter_single.select_problem_for_fit(id=fit_id, value=1) 
     1247            if fitter is None: 
     1248                # Assign id to the new fitter only 
     1249                fitter_single.fitter_id = [self.page_id] 
     1250            fit_id += 1 
     1251            fitters.append(fitter_single) 
     1252 
     1253        return fitters, fit_id 
    8481254 
    8491255    def iterateOverModel(self, func): 
     
    12401646        # Now we claim the model has been loaded 
    12411647        self.model_is_loaded = True 
     1648        # Change the model name to a monicker 
     1649        self.kernel_module.name = self.modelName() 
    12421650 
    12431651        # (Re)-create headers 
     
    13441752        self.updateUndo() 
    13451753 
     1754    def isCheckable(self, row): 
     1755        return self._model_model.item(row, 0).isCheckable() 
     1756 
    13461757    def checkboxSelected(self, item): 
    13471758        # Assure we're dealing with checkboxes 
     
    13501761        status = item.checkState() 
    13511762 
    1352         def isCheckable(row): 
    1353             return self._model_model.item(row, 0).isCheckable() 
    1354  
    13551763        # If multiple rows selected - toggle all of them, filtering uncheckable 
    1356         rows = [s.row() for s in self.lstParams.selectionModel().selectedRows() if isCheckable(s.row())] 
    1357  
    13581764        # Switch off signaling from the model to avoid recursion 
    13591765        self._model_model.blockSignals(True) 
    13601766        # Convert to proper indices and set requested enablement 
    1361         [self._model_model.item(row, 0).setCheckState(status) for row in rows] 
     1767        self.setParameterSelection(status) 
     1768        #[self._model_model.item(row, 0).setCheckState(status) for row in self.selectedParameters()] 
    13621769        self._model_model.blockSignals(False) 
    13631770 
     
    13821789                for row_index in range(model.rowCount()) 
    13831790                if isChecked(row_index)] 
    1384  
    1385     def nameForFittedData(self, name): 
    1386         """ 
    1387         Generate name for the current fit 
    1388         """ 
    1389         if self.is2D: 
    1390             name += "2d" 
    1391         name = "M%i [%s]" % (self.tab_id, name) 
    1392         return name 
    13931791 
    13941792    def createNewIndex(self, fitted_data): 
     
    18322230        self.setMagneticModel() 
    18332231 
     2232    def setFittingStarted(self): 
     2233        """ 
     2234        Set item enablement on fitting start 
     2235        """ 
     2236        #disable the Fit button 
     2237        self.cmdFit.setText('Running...') 
     2238        self.cmdFit.setEnabled(False) 
     2239 
     2240    def setFittingStopped(self): 
     2241        """ 
     2242        Set item enablement on fitting stop 
     2243        """ 
     2244        #enable the Fit button 
     2245        self.cmdFit.setText("Fit") 
     2246        self.cmdFit.setEnabled(True) 
     2247 
    18342248    def readFitPage(self, fp): 
    18352249        """ 
  • src/sas/qtgui/Perspectives/Fitting/UI/FittingWidgetUI.ui

    rd1955d67 r7fd20fc  
    77    <x>0</x> 
    88    <y>0</y> 
    9     <width>434</width> 
    10     <height>466</height> 
     9    <width>448</width> 
     10    <height>526</height> 
    1111   </rect> 
    1212  </property> 
     
    2626   <string>FittingWidget</string> 
    2727  </property> 
    28   <layout class="QGridLayout" name="gridLayout_4"> 
    29    <item row="0" column="0" colspan="4"> 
     28  <layout class="QGridLayout" name="gridLayout_5"> 
     29   <item row="0" column="0"> 
    3030    <layout class="QHBoxLayout" name="horizontalLayout"> 
    3131     <item> 
     
    7272    </layout> 
    7373   </item> 
    74    <item row="1" column="0" colspan="4"> 
     74   <item row="1" column="0"> 
    7575    <widget class="QTabWidget" name="tabFitting"> 
    7676     <property name="currentIndex"> 
     
    8181       <string>Model</string> 
    8282      </attribute> 
    83       <layout class="QGridLayout" name="gridLayout_19"> 
     83      <layout class="QGridLayout" name="gridLayout_4"> 
    8484       <item row="0" column="0" colspan="4"> 
    8585        <widget class="QGroupBox" name="groupBox_6"> 
     
    161161          <string>Options </string> 
    162162         </property> 
    163          <layout class="QGridLayout" name="gridLayout_16"> 
    164           <item row="0" column="0"> 
     163         <layout class="QVBoxLayout" name="verticalLayout"> 
     164          <item> 
    165165           <widget class="QCheckBox" name="chkPolydispersity"> 
    166166            <property name="enabled"> 
     
    178178           </widget> 
    179179          </item> 
    180           <item row="1" column="0"> 
     180          <item> 
    181181           <widget class="QCheckBox" name="chk2DView"> 
    182182            <property name="enabled"> 
     
    194194           </widget> 
    195195          </item> 
    196           <item row="2" column="0"> 
     196          <item> 
    197197           <widget class="QCheckBox" name="chkMagnetism"> 
    198198            <property name="enabled"> 
     
    204204            <property name="text"> 
    205205             <string>Magnetism</string> 
     206            </property> 
     207            <property name="checkable"> 
     208             <bool>true</bool> 
     209            </property> 
     210           </widget> 
     211          </item> 
     212          <item> 
     213           <widget class="QCheckBox" name="chkChainFit"> 
     214            <property name="enabled"> 
     215             <bool>true</bool> 
     216            </property> 
     217            <property name="toolTip"> 
     218             <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Switch on magnetic scattering parameters.&lt;/p&gt;&lt;p&gt;This option is available only for 2D models.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> 
     219            </property> 
     220            <property name="text"> 
     221             <string>Chain fit</string> 
    206222            </property> 
    207223            <property name="checkable"> 
     
    412428   </item> 
    413429   <item row="2" column="0"> 
    414     <spacer name="horizontalSpacer"> 
    415      <property name="orientation"> 
    416       <enum>Qt::Horizontal</enum> 
    417      </property> 
    418      <property name="sizeHint" stdset="0"> 
    419       <size> 
    420        <width>273</width> 
    421        <height>20</height> 
    422       </size> 
    423      </property> 
    424     </spacer> 
    425    </item> 
    426    <item row="2" column="1"> 
    427     <widget class="QPushButton" name="cmdPlot"> 
    428      <property name="sizePolicy"> 
    429       <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> 
    430        <horstretch>0</horstretch> 
    431        <verstretch>0</verstretch> 
    432       </sizepolicy> 
    433      </property> 
    434      <property name="minimumSize"> 
    435       <size> 
    436        <width>75</width> 
    437        <height>23</height> 
    438       </size> 
    439      </property> 
    440      <property name="text"> 
    441       <string>Show Plot</string> 
    442      </property> 
    443     </widget> 
    444    </item> 
    445    <item row="2" column="2"> 
    446     <widget class="QPushButton" name="cmdFit"> 
    447      <property name="sizePolicy"> 
    448       <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> 
    449        <horstretch>0</horstretch> 
    450        <verstretch>0</verstretch> 
    451       </sizepolicy> 
    452      </property> 
    453      <property name="minimumSize"> 
    454       <size> 
    455        <width>75</width> 
    456        <height>23</height> 
    457       </size> 
    458      </property> 
    459      <property name="text"> 
    460       <string>Fit</string> 
    461      </property> 
    462     </widget> 
    463    </item> 
    464    <item row="2" column="3"> 
    465     <widget class="QPushButton" name="cmdHelp"> 
    466      <property name="sizePolicy"> 
    467       <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> 
    468        <horstretch>0</horstretch> 
    469        <verstretch>0</verstretch> 
    470       </sizepolicy> 
    471      </property> 
    472      <property name="minimumSize"> 
    473       <size> 
    474        <width>75</width> 
    475        <height>23</height> 
    476       </size> 
    477      </property> 
    478      <property name="text"> 
    479       <string>Help</string> 
    480      </property> 
    481     </widget> 
     430    <layout class="QHBoxLayout" name="horizontalLayout_3"> 
     431     <item> 
     432      <spacer name="horizontalSpacer"> 
     433       <property name="orientation"> 
     434        <enum>Qt::Horizontal</enum> 
     435       </property> 
     436       <property name="sizeHint" stdset="0"> 
     437        <size> 
     438         <width>273</width> 
     439         <height>20</height> 
     440        </size> 
     441       </property> 
     442      </spacer> 
     443     </item> 
     444     <item> 
     445      <widget class="QPushButton" name="cmdPlot"> 
     446       <property name="sizePolicy"> 
     447        <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> 
     448         <horstretch>0</horstretch> 
     449         <verstretch>0</verstretch> 
     450        </sizepolicy> 
     451       </property> 
     452       <property name="minimumSize"> 
     453        <size> 
     454         <width>75</width> 
     455         <height>23</height> 
     456        </size> 
     457       </property> 
     458       <property name="text"> 
     459        <string>Show Plot</string> 
     460       </property> 
     461      </widget> 
     462     </item> 
     463     <item> 
     464      <widget class="QPushButton" name="cmdFit"> 
     465       <property name="sizePolicy"> 
     466        <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> 
     467         <horstretch>0</horstretch> 
     468         <verstretch>0</verstretch> 
     469        </sizepolicy> 
     470       </property> 
     471       <property name="minimumSize"> 
     472        <size> 
     473         <width>75</width> 
     474         <height>23</height> 
     475        </size> 
     476       </property> 
     477       <property name="text"> 
     478        <string>Fit</string> 
     479       </property> 
     480      </widget> 
     481     </item> 
     482     <item> 
     483      <widget class="QPushButton" name="cmdHelp"> 
     484       <property name="sizePolicy"> 
     485        <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> 
     486         <horstretch>0</horstretch> 
     487         <verstretch>0</verstretch> 
     488        </sizepolicy> 
     489       </property> 
     490       <property name="minimumSize"> 
     491        <size> 
     492         <width>75</width> 
     493         <height>23</height> 
     494        </size> 
     495       </property> 
     496       <property name="text"> 
     497        <string>Help</string> 
     498       </property> 
     499      </widget> 
     500     </item> 
     501    </layout> 
    482502   </item> 
    483503  </layout> 
  • src/sas/qtgui/Perspectives/Fitting/UnitTesting/FittingOptionsTest.py

    r53c771e r725d9c06  
    11import sys 
    22import unittest 
     3import webbrowser 
    34from bumps import options 
    45 
    56from PyQt5 import QtGui, QtWidgets 
    6 from PyQt5 import QtWebKit 
    77 
    88from unittest.mock import MagicMock 
     
    114114 
    115115    # test disabled until pyQt5 works well 
    116     def notestOnHelp(self): 
     116    def testOnHelp(self): 
    117117        ''' Test help display''' 
    118         #Mock the QWebView method 
    119         QtWebKit.QWebView.show = MagicMock() 
    120         QtWebKit.QWebView.load = MagicMock() 
     118        webbrowser.open = MagicMock() 
    121119 
    122120        # Invoke the action on default tab 
    123121        self.widget.onHelp() 
    124122        # Check if show() got called 
    125         self.assertTrue(QtWebKit.QWebView.show.called) 
     123        self.assertTrue(webbrowser.open.called) 
    126124        # Assure the filename is correct 
    127         self.assertIn("optimizer.html", QtWebKit.QWebView.load.call_args[0][0].toString()) 
     125        self.assertIn("optimizer.html", webbrowser.open.call_args[0][0]) 
    128126 
    129127        # Change the combo index 
     
    131129        self.widget.onHelp() 
    132130        # Check if show() got called 
    133         self.assertEqual(QtWebKit.QWebView.show.call_count, 2) 
     131        self.assertEqual(webbrowser.open.call_count, 2) 
    134132        # Assure the filename is correct 
    135         self.assertIn("fit-dream", QtWebKit.QWebView.load.call_args[0][0].toString()) 
     133        self.assertIn("fit-dream", webbrowser.open.call_args[0][0]) 
    136134 
    137135        # Change the index again 
     
    139137        self.widget.onHelp() 
    140138        # Check if show() got called 
    141         self.assertEqual(QtWebKit.QWebView.show.call_count, 3) 
     139        self.assertEqual(webbrowser.open.call_count, 3) 
    142140        # Assure the filename is correct 
    143         self.assertIn("fit-lm", QtWebKit.QWebView.load.call_args[0][0].toString()) 
     141        self.assertIn("fit-lm", webbrowser.open.call_args[0][0]) 
    144142 
    145143    def testWidgetFromOptions(self): 
  • src/sas/qtgui/Perspectives/Fitting/UnitTesting/FittingPerspectiveTest.py

    r53c771e r63319b0  
    1919if not QtWidgets.QApplication.instance(): 
    2020    app = QtWidgets.QApplication(sys.argv) 
     21 
    2122 
    2223class FittingPerspectiveTest(unittest.TestCase): 
     
    4445        self.assertEqual(len(self.widget.tabs), 1) 
    4546        self.assertEqual(self.widget.maxIndex, 1) 
    46         self.assertEqual(self.widget.tabName(), "FitPage1") 
     47        self.assertEqual(self.widget.maxCSIndex, 0) 
     48        self.assertEqual(self.widget.getTabName(), "FitPage1") 
    4749 
    4850    def testAddTab(self): 
     
    5254        self.widget.addFit(None) 
    5355        self.assertEqual(len(self.widget.tabs), 2) 
    54         self.assertEqual(self.widget.tabName(), "FitPage2") 
     56        self.assertEqual(self.widget.getTabName(), "FitPage2") 
    5557        self.assertEqual(self.widget.maxIndex, 2) 
    5658        # Add an empty batch tab 
    5759        self.widget.addFit(None, is_batch=True) 
    5860        self.assertEqual(len(self.widget.tabs), 3) 
    59         self.assertEqual(self.widget.tabName(2), "BatchPage3") 
     61        self.assertEqual(self.widget.getTabName(2), "BatchPage3") 
    6062        self.assertEqual(self.widget.maxIndex, 3) 
     63 
     64    def testAddCSTab(self): 
     65        ''' Add a constraint/simult tab''' 
     66        self.widget.addConstraintTab() 
     67        self.assertEqual(len(self.widget.tabs), 2) 
     68        self.assertEqual(self.widget.getCSTabName(), "Const. & Simul. Fit1") 
     69        self.assertEqual(self.widget.maxCSIndex, 1) 
    6170 
    6271    def testResetTab(self): 
    6372        ''' Remove data from last tab''' 
    6473        self.assertEqual(len(self.widget.tabs), 1) 
    65         self.assertEqual(self.widget.tabName(), "FitPage1") 
     74        self.assertEqual(self.widget.getTabName(), "FitPage1") 
    6675        self.assertEqual(self.widget.maxIndex, 1) 
    6776 
     
    7180        # see that the tab didn't disappear, just changed the name/id 
    7281        self.assertEqual(len(self.widget.tabs), 1) 
    73         self.assertEqual(self.widget.tabName(), "FitPage2") 
     82        self.assertEqual(self.widget.getTabName(), "FitPage2") 
    7483        self.assertEqual(self.widget.maxIndex, 2) 
    7584 
     
    95104        self.assertEqual(len(self.widget.tabs), 1) 
    96105        self.assertEqual(self.widget.maxIndex, 2) 
    97         self.assertEqual(self.widget.tabName(), "FitPage2") 
     106        self.assertEqual(self.widget.getTabName(), "FitPage2") 
    98107 
    99108        # Attemtp to remove the last tab 
     
    102111        self.assertEqual(len(self.widget.tabs), 1) 
    103112        self.assertEqual(self.widget.maxIndex, 3) 
    104         self.assertEqual(self.widget.tabName(), "FitPage3") 
     113        self.assertEqual(self.widget.getTabName(), "FitPage3") 
    105114 
    106115    def testAllowBatch(self): 
     
    138147        self.assertEqual(len(self.widget.tabs), 4) 
    139148 
     149    def testSetBatchData(self): 
     150        ''' Assure that setting batch data is correct''' 
     151 
     152        # Mock the datafromitem() call from FittingWidget 
     153        data1 = Data1D(x=[1,2], y=[1,2]) 
     154        data2 = Data1D(x=[1,2], y=[1,2]) 
     155        data_batch = [data1, data2] 
     156        GuiUtils.dataFromItem = MagicMock(return_value=data1) 
     157 
     158        item = QtGui.QStandardItem("test") 
     159        self.widget.setData([item, item], is_batch=True) 
     160 
     161        # First tab should not accept data 
     162        self.assertEqual(len(self.widget.tabs), 2) 
     163 
     164        # Add another set of data 
     165        self.widget.setData([item, item], is_batch=True) 
     166 
     167        # Now we should have two batch tabs 
     168        self.assertEqual(len(self.widget.tabs), 3) 
     169 
     170        # Check the names of the new tabs 
     171        self.assertEqual(self.widget.tabText(1), "BatchPage1") 
     172        self.assertEqual(self.widget.tabText(2), "BatchPage2") 
    140173 
    141174if __name__ == "__main__": 
  • src/sas/qtgui/Perspectives/Fitting/UnitTesting/FittingWidgetTest.py

    • Property mode changed from 100755 to 100644
    r53c771e r14ec91c5  
    1717from sas.qtgui.Utilities.GuiUtils import * 
    1818from sas.qtgui.Perspectives.Fitting.FittingWidget import * 
     19from sas.qtgui.Perspectives.Fitting.Constraint import Constraint 
     20 
    1921from sas.qtgui.UnitTesting.TestUtils import QtSignalSpy 
    2022 
     
    112114        fittingWindow =  self.widget 
    113115 
    114         self.assertIsInstance(fittingWindow.lstMagnetic.itemDelegate(), QtGui.QStyledItemDelegate) 
     116        self.assertIsInstance(fittingWindow.lstMagnetic.itemDelegate(), QtWidgets.QStyledItemDelegate) 
    115117        #Test loading from json categories 
    116118        fittingWindow.SASModelToQModel("cylinder") 
    117119        mag_index = fittingWindow.lstMagnetic.model().index(0,0) 
    118         self.assertEqual(str(mag_index.data().toString()), "up:frac_i") 
     120        self.assertEqual(mag_index.data(), "up:frac_i") 
    119121        mag_index = fittingWindow.lstMagnetic.model().index(1,0) 
    120         self.assertEqual(str(mag_index.data().toString()), "up:frac_f") 
     122        self.assertEqual(mag_index.data(), "up:frac_f") 
    121123        mag_index = fittingWindow.lstMagnetic.model().index(2,0) 
    122         self.assertEqual(str(mag_index.data().toString()), "up:angle") 
     124        self.assertEqual(mag_index.data(), "up:angle") 
    123125        mag_index = fittingWindow.lstMagnetic.model().index(3,0) 
    124         self.assertEqual(str(mag_index.data().toString()), "M0:sld") 
     126        self.assertEqual(mag_index.data(), "M0:sld") 
    125127        mag_index = fittingWindow.lstMagnetic.model().index(4,0) 
    126         self.assertEqual(str(mag_index.data().toString()), "mtheta:sld") 
     128        self.assertEqual(mag_index.data(), "mtheta:sld") 
    127129        mag_index = fittingWindow.lstMagnetic.model().index(5,0) 
    128         self.assertEqual(str(mag_index.data().toString()), "mphi:sld") 
     130        self.assertEqual(mag_index.data(), "mphi:sld") 
    129131        mag_index = fittingWindow.lstMagnetic.model().index(6,0) 
    130         self.assertEqual(str(mag_index.data().toString()), "M0:sld_solvent") 
     132        self.assertEqual(mag_index.data(), "M0:sld_solvent") 
    131133        mag_index = fittingWindow.lstMagnetic.model().index(7,0) 
    132         self.assertEqual(str(mag_index.data().toString()), "mtheta:sld_solvent") 
     134        self.assertEqual(mag_index.data(), "mtheta:sld_solvent") 
    133135        mag_index = fittingWindow.lstMagnetic.model().index(8,0) 
    134         self.assertEqual(str(mag_index.data().toString()), "mphi:sld_solvent") 
     136        self.assertEqual(mag_index.data(), "mphi:sld_solvent") 
    135137 
    136138        # test the delegate a bit 
    137139        delegate = fittingWindow.lstMagnetic.itemDelegate() 
    138140        self.assertEqual(delegate.editableParameters(), [1, 2, 3]) 
    139         self.assertIsInstance(delegate.combo_updated, QtCore.pyqtBoundSignal) 
    140141 
    141142    def testSelectStructureFactor(self): 
     
    662663        test_data = Data1D(x=[1,2], y=[1,2]) 
    663664        item = QtGui.QStandardItem() 
    664         updateModelItem(item, [test_data], "test") 
     665        updateModelItem(item, test_data, "test") 
    665666        # Force same data into logic 
    666667        self.widget.data = item 
     
    688689        # Force same data into logic 
    689690        item = QtGui.QStandardItem() 
    690         updateModelItem(item, [test_data], "test") 
     691        updateModelItem(item, test_data, "test") 
    691692        # Force same data into logic 
    692693        self.widget.data = item 
     
    714715        test_data = Data1D(x=[1,2], y=[1,2]) 
    715716        item = QtGui.QStandardItem() 
    716         updateModelItem(item, [test_data], "test") 
     717        updateModelItem(item, test_data, "test") 
    717718        # Force same data into logic 
    718719        self.widget.data = item 
     
    758759        # Force same data into logic 
    759760        item = QtGui.QStandardItem() 
    760         updateModelItem(item, [test_data], "test") 
     761        updateModelItem(item, test_data, "test") 
    761762        # Force same data into logic 
    762763        self.widget.data = item 
     
    786787            self.assertEqual(update_spy.count(), 1) 
    787788 
    788     # test disabled until pyqt5 deals with html properly 
    789     def notestOnHelp(self): 
     789    def testOnHelp(self): 
    790790        """ 
    791791        Test various help pages shown in this widget 
    792792        """ 
    793         #Mock the QWebView method 
    794         QtWebKit.QWebView.show = MagicMock() 
    795         QtWebKit.QWebView.load = MagicMock() 
     793        #Mock the webbrowser.open method 
     794        self.widget.parent.showHelp = MagicMock() 
     795        #webbrowser.open = MagicMock() 
    796796 
    797797        # Invoke the action on default tab 
    798798        self.widget.onHelp() 
    799799        # Check if show() got called 
    800         self.assertTrue(QtWebKit.QWebView.show.called) 
     800        self.assertTrue(self.widget.parent.showHelp.called) 
    801801        # Assure the filename is correct 
    802         self.assertIn("fitting_help.html", QtWebKit.QWebView.load.call_args[0][0].toString()) 
     802        self.assertIn("fitting_help.html", self.widget.parent.showHelp.call_args[0][0]) 
    803803 
    804804        # Change the tab to options 
     
    806806        self.widget.onHelp() 
    807807        # Check if show() got called 
    808         self.assertEqual(QtWebKit.QWebView.show.call_count, 2) 
     808        self.assertEqual(self.widget.parent.showHelp.call_count, 2) 
    809809        # Assure the filename is correct 
    810         self.assertIn("residuals_help.html", QtWebKit.QWebView.load.call_args[0][0].toString()) 
     810        self.assertIn("residuals_help.html", self.widget.parent.showHelp.call_args[0][0]) 
    811811 
    812812        # Change the tab to smearing 
     
    814814        self.widget.onHelp() 
    815815        # Check if show() got called 
    816         self.assertEqual(QtWebKit.QWebView.show.call_count, 3) 
     816        self.assertEqual(self.widget.parent.showHelp.call_count, 3) 
    817817        # Assure the filename is correct 
    818         self.assertIn("sm_help.html", QtWebKit.QWebView.load.call_args[0][0].toString()) 
     818        self.assertIn("resolution.html", self.widget.parent.showHelp.call_args[0][0]) 
    819819 
    820820        # Change the tab to poly 
     
    822822        self.widget.onHelp() 
    823823        # Check if show() got called 
    824         self.assertEqual(QtWebKit.QWebView.show.call_count, 4) 
     824        self.assertEqual(self.widget.parent.showHelp.call_count, 4) 
    825825        # Assure the filename is correct 
    826         self.assertIn("pd_help.html", QtWebKit.QWebView.load.call_args[0][0].toString()) 
     826        self.assertIn("polydispersity.html", self.widget.parent.showHelp.call_args[0][0]) 
    827827 
    828828        # Change the tab to magnetism 
     
    830830        self.widget.onHelp() 
    831831        # Check if show() got called 
    832         self.assertEqual(QtWebKit.QWebView.show.call_count, 5) 
     832        self.assertEqual(self.widget.parent.showHelp.call_count, 5) 
    833833        # Assure the filename is correct 
    834         self.assertIn("mag_help.html", QtWebKit.QWebView.load.call_args[0][0].toString()) 
     834        self.assertIn("magnetism.html", self.widget.parent.showHelp.call_args[0][0]) 
    835835 
    836836    def testReadFitPage(self): 
     
    10261026        self.assertNotIn(new_value, self.widget.kernel_module.details[name_modified_param] ) 
    10271027 
     1028    def testModelContextMenu(self): 
     1029        """ 
     1030        Test the right click context menu in the parameter table 
     1031        """ 
     1032        # select model: cylinder / cylinder 
     1033        category_index = self.widget.cbCategory.findText("Cylinder") 
     1034        self.widget.cbCategory.setCurrentIndex(category_index) 
     1035 
     1036        model_index = self.widget.cbModel.findText("cylinder") 
     1037        self.widget.cbModel.setCurrentIndex(model_index) 
     1038 
     1039        # no rows selected 
     1040        menu = self.widget.modelContextMenu([]) 
     1041        self.assertEqual(len(menu.actions()), 0) 
     1042 
     1043        # 1 row selected 
     1044        menu = self.widget.modelContextMenu([1]) 
     1045        self.assertEqual(len(menu.actions()), 4) 
     1046 
     1047        # 2 rows selected 
     1048        menu = self.widget.modelContextMenu([1,3]) 
     1049        self.assertEqual(len(menu.actions()), 5) 
     1050 
     1051        # 3 rows selected 
     1052        menu = self.widget.modelContextMenu([1,2,3]) 
     1053        self.assertEqual(len(menu.actions()), 4) 
     1054 
     1055        # over 9000 
     1056        with self.assertRaises(AttributeError): 
     1057            menu = self.widget.modelContextMenu([i for i in range(9001)]) 
     1058        self.assertEqual(len(menu.actions()), 4) 
     1059 
     1060    def testShowModelContextMenu(self): 
     1061        # select model: cylinder / cylinder 
     1062        category_index = self.widget.cbCategory.findText("Cylinder") 
     1063        self.widget.cbCategory.setCurrentIndex(category_index) 
     1064 
     1065        model_index = self.widget.cbModel.findText("cylinder") 
     1066        self.widget.cbModel.setCurrentIndex(model_index) 
     1067 
     1068        # No selection 
     1069        logging.error = MagicMock() 
     1070        self.widget.showModelDescription = MagicMock() 
     1071        # Show the menu 
     1072        self.widget.showModelContextMenu(QtCore.QPoint(10,20)) 
     1073 
     1074        # Assure the description menu is shown 
     1075        self.assertTrue(self.widget.showModelDescription.called) 
     1076        self.assertFalse(logging.error.called) 
     1077 
     1078        # "select" two rows 
     1079        index1 = self.widget.lstParams.model().index(1, 0, QtCore.QModelIndex()) 
     1080        index2 = self.widget.lstParams.model().index(2, 0, QtCore.QModelIndex()) 
     1081        selection_model = self.widget.lstParams.selectionModel() 
     1082        selection_model.select(index1, selection_model.Select | selection_model.Rows) 
     1083        selection_model.select(index2, selection_model.Select | selection_model.Rows) 
     1084 
     1085        QtWidgets.QMenu.exec_ = MagicMock() 
     1086        logging.error = MagicMock() 
     1087        # Show the menu 
     1088        self.widget.showModelContextMenu(QtCore.QPoint(10,20)) 
     1089 
     1090        # Assure the menu is shown 
     1091        self.assertFalse(logging.error.called) 
     1092        self.assertTrue(QtWidgets.QMenu.exec_.called) 
     1093 
     1094    def testShowMultiConstraint(self): 
     1095        """ 
     1096        Test the widget update on new multi constraint 
     1097        """ 
     1098        # select model: cylinder / cylinder 
     1099        category_index = self.widget.cbCategory.findText("Cylinder") 
     1100        self.widget.cbCategory.setCurrentIndex(category_index) 
     1101 
     1102        model_index = self.widget.cbModel.findText("cylinder") 
     1103        self.widget.cbModel.setCurrentIndex(model_index) 
     1104 
     1105        # nothing selected 
     1106        with self.assertRaises(AssertionError): 
     1107            self.widget.showMultiConstraint() 
     1108 
     1109        # one row selected 
     1110        index = self.widget.lstParams.model().index(1, 0, QtCore.QModelIndex()) 
     1111        selection_model = self.widget.lstParams.selectionModel() 
     1112        selection_model.select(index, selection_model.Select | selection_model.Rows) 
     1113        with self.assertRaises(AssertionError): 
     1114            # should also throw 
     1115            self.widget.showMultiConstraint() 
     1116 
     1117        # two rows selected 
     1118        index1 = self.widget.lstParams.model().index(1, 0, QtCore.QModelIndex()) 
     1119        index2 = self.widget.lstParams.model().index(2, 0, QtCore.QModelIndex()) 
     1120        selection_model = self.widget.lstParams.selectionModel() 
     1121        selection_model.select(index1, selection_model.Select | selection_model.Rows) 
     1122        selection_model.select(index2, selection_model.Select | selection_model.Rows) 
     1123 
     1124        # return non-OK from dialog 
     1125        QtWidgets.QDialog.exec_ = MagicMock() 
     1126        self.widget.showMultiConstraint() 
     1127        # Check the dialog called 
     1128        self.assertTrue(QtWidgets.QDialog.exec_.called) 
     1129 
     1130        # return OK from dialog 
     1131        QtWidgets.QDialog.exec_ = MagicMock(return_value=QtWidgets.QDialog.Accepted) 
     1132        spy = QtSignalSpy(self.widget, self.widget.constraintAddedSignal) 
     1133 
     1134        self.widget.showMultiConstraint() 
     1135 
     1136        # Make sure the signal has been emitted 
     1137        self.assertEqual(spy.count(), 1) 
     1138 
     1139        # Check the argument value - should be row '1' 
     1140        self.assertEqual(spy.called()[0]['args'][0], [1]) 
     1141 
     1142    def testGetRowFromName(self): 
     1143        """ 
     1144        Helper function for parameter table 
     1145        """ 
     1146        # select model: cylinder / cylinder 
     1147        category_index = self.widget.cbCategory.findText("Cylinder") 
     1148        self.widget.cbCategory.setCurrentIndex(category_index) 
     1149 
     1150        model_index = self.widget.cbModel.findText("cylinder") 
     1151        self.widget.cbModel.setCurrentIndex(model_index) 
     1152 
     1153        # several random parameters 
     1154        self.assertEqual(self.widget.getRowFromName('scale'), 0) 
     1155        self.assertEqual(self.widget.getRowFromName('length'), 5) 
     1156 
     1157    def testGetParamNames(self): 
     1158        """ 
     1159        Helper function for parameter table 
     1160        """ 
     1161        # select model: cylinder / cylinder 
     1162        category_index = self.widget.cbCategory.findText("Cylinder") 
     1163        self.widget.cbCategory.setCurrentIndex(category_index) 
     1164 
     1165        model_index = self.widget.cbModel.findText("cylinder") 
     1166        self.widget.cbModel.setCurrentIndex(model_index) 
     1167 
     1168        cylinder_params = ['scale','background','sld','sld_solvent','radius','length'] 
     1169        # assure all parameters are returned 
     1170        self.assertEqual(self.widget.getParamNames(), cylinder_params) 
     1171 
     1172        # Switch to another model 
     1173        model_index = self.widget.cbModel.findText("pringle") 
     1174        self.widget.cbModel.setCurrentIndex(model_index) 
     1175 
     1176        # make sure the parameters are different than before 
     1177        self.assertFalse(self.widget.getParamNames() == cylinder_params) 
     1178 
     1179    def testAddConstraintToRow(self): 
     1180        """ 
     1181        Test the constraint row add operation 
     1182        """ 
     1183        # select model: cylinder / cylinder 
     1184        category_index = self.widget.cbCategory.findText("Cylinder") 
     1185        self.widget.cbCategory.setCurrentIndex(category_index) 
     1186 
     1187        model_index = self.widget.cbModel.findText("cylinder") 
     1188        self.widget.cbModel.setCurrentIndex(model_index) 
     1189 
     1190        # Create a constraint object 
     1191        const = Constraint(parent=None, value=7.0) 
     1192        row = 2 
     1193 
     1194        spy = QtSignalSpy(self.widget, self.widget.constraintAddedSignal) 
     1195 
     1196        # call the method tested 
     1197        self.widget.addConstraintToRow(constraint=const, row=row) 
     1198 
     1199        # Make sure the signal has been emitted 
     1200        self.assertEqual(spy.count(), 1) 
     1201 
     1202        # Check the argument value - should be row 'row' 
     1203        self.assertEqual(spy.called()[0]['args'][0], [row]) 
     1204 
     1205        # Assure the row has the constraint 
     1206        self.assertEqual(self.widget.getConstraintForRow(row), const) 
     1207        # but not complex constraint! 
     1208        self.assertFalse(self.widget.rowHasConstraint(row)) 
     1209 
     1210        # assign complex constraint now 
     1211        const = Constraint(parent=None, param='radius', func='5*sld') 
     1212        row = 4 
     1213        # call the method tested 
     1214        self.widget.addConstraintToRow(constraint=const, row=row) 
     1215 
     1216        # Make sure the signal has been emitted 
     1217        self.assertEqual(spy.count(), 2) 
     1218 
     1219        # Check the argument value - should be row 'row' 
     1220        self.assertEqual(spy.called()[1]['args'][0], [row]) 
     1221 
     1222        # Assure the row has the constraint 
     1223        self.assertEqual(self.widget.getConstraintForRow(row), const) 
     1224        # and it is a complex constraint 
     1225        self.assertTrue(self.widget.rowHasConstraint(row)) 
     1226 
     1227    def testAddSimpleConstraint(self): 
     1228        """ 
     1229        Test the constraint add operation 
     1230        """ 
     1231        # select model: cylinder / cylinder 
     1232        category_index = self.widget.cbCategory.findText("Cylinder") 
     1233        self.widget.cbCategory.setCurrentIndex(category_index) 
     1234 
     1235        model_index = self.widget.cbModel.findText("cylinder") 
     1236        self.widget.cbModel.setCurrentIndex(model_index) 
     1237 
     1238        # select two rows 
     1239        row1 = 1 
     1240        row2 = 4 
     1241        index1 = self.widget.lstParams.model().index(row1, 0, QtCore.QModelIndex()) 
     1242        index2 = self.widget.lstParams.model().index(row2, 0, QtCore.QModelIndex()) 
     1243        selection_model = self.widget.lstParams.selectionModel() 
     1244        selection_model.select(index1, selection_model.Select | selection_model.Rows) 
     1245        selection_model.select(index2, selection_model.Select | selection_model.Rows) 
     1246 
     1247        # define the signal spy 
     1248        spy = QtSignalSpy(self.widget, self.widget.constraintAddedSignal) 
     1249 
     1250        # call the method tested 
     1251        self.widget.addSimpleConstraint() 
     1252 
     1253        # Make sure the signal has been emitted 
     1254        self.assertEqual(spy.count(), 2) 
     1255 
     1256        # Check the argument value 
     1257        self.assertEqual(spy.called()[0]['args'][0], [row1]) 
     1258        self.assertEqual(spy.called()[1]['args'][0], [row2]) 
     1259 
     1260        # Other properties 
     1261        self.assertEqual(self.widget.getConstraintsForModel(), [('background', '0.001'), ('radius', '20')]) 
     1262 
     1263    def testDeleteConstraintOnParameter(self): 
     1264        """ 
     1265        Test the constraint deletion in model/view 
     1266        """ 
     1267        # select model: cylinder / cylinder 
     1268        category_index = self.widget.cbCategory.findText("Cylinder") 
     1269        self.widget.cbCategory.setCurrentIndex(category_index) 
     1270 
     1271        model_index = self.widget.cbModel.findText("cylinder") 
     1272        self.widget.cbModel.setCurrentIndex(model_index) 
     1273 
     1274        # select two rows 
     1275        row1 = 1 
     1276        row2 = 4 
     1277        index1 = self.widget.lstParams.model().index(row1, 0, QtCore.QModelIndex()) 
     1278        index2 = self.widget.lstParams.model().index(row2, 0, QtCore.QModelIndex()) 
     1279        selection_model = self.widget.lstParams.selectionModel() 
     1280        selection_model.select(index1, selection_model.Select | selection_model.Rows) 
     1281        selection_model.select(index2, selection_model.Select | selection_model.Rows) 
     1282 
     1283        # add constraints 
     1284        self.widget.addSimpleConstraint() 
     1285 
     1286        # deselect the model 
     1287        selection_model.clear() 
     1288 
     1289        # select a single row 
     1290        selection_model.select(index1, selection_model.Select | selection_model.Rows) 
     1291 
     1292        # delete one of the constraints 
     1293        self.widget.deleteConstraintOnParameter(param='background') 
     1294 
     1295        # see that the other constraint is still present 
     1296        self.assertEqual(self.widget.getConstraintsForModel(), [('radius', '20')]) 
     1297 
     1298        # kill the other constraint 
     1299        self.widget.deleteConstraint() 
     1300 
     1301        # see that the other constraint is still present 
     1302        self.assertEqual(self.widget.getConstraintsForModel(), []) 
     1303 
     1304    def testGetConstraintForRow(self): 
     1305        """ 
     1306        Helper function for parameter table 
     1307        """ 
     1308        # tested extensively elsewhere 
     1309        pass 
     1310 
     1311    def testRowHasConstraint(self): 
     1312        """ 
     1313        Helper function for parameter table 
     1314        """ 
     1315        # select model: cylinder / cylinder 
     1316        category_index = self.widget.cbCategory.findText("Cylinder") 
     1317        self.widget.cbCategory.setCurrentIndex(category_index) 
     1318 
     1319        model_index = self.widget.cbModel.findText("cylinder") 
     1320        self.widget.cbModel.setCurrentIndex(model_index) 
     1321 
     1322        # select two rows 
     1323        row1 = 1 
     1324        row2 = 4 
     1325        index1 = self.widget.lstParams.model().index(row1, 0, QtCore.QModelIndex()) 
     1326        index2 = self.widget.lstParams.model().index(row2, 0, QtCore.QModelIndex()) 
     1327        selection_model = self.widget.lstParams.selectionModel() 
     1328        selection_model.select(index1, selection_model.Select | selection_model.Rows) 
     1329        selection_model.select(index2, selection_model.Select | selection_model.Rows) 
     1330 
     1331        # add constraints 
     1332        self.widget.addSimpleConstraint() 
     1333 
     1334        con_list = [False, True, False, False, True, False] 
     1335        new_list = [] 
     1336        for row in range(self.widget._model_model.rowCount()): 
     1337            new_list.append(self.widget.rowHasConstraint(row)) 
     1338 
     1339        self.assertEqual(new_list, con_list) 
     1340 
     1341    def testRowHasActiveConstraint(self): 
     1342        """ 
     1343        Helper function for parameter table 
     1344        """ 
     1345        # select model: cylinder / cylinder 
     1346        category_index = self.widget.cbCategory.findText("Cylinder") 
     1347        self.widget.cbCategory.setCurrentIndex(category_index) 
     1348 
     1349        model_index = self.widget.cbModel.findText("cylinder") 
     1350        self.widget.cbModel.setCurrentIndex(model_index) 
     1351 
     1352        # select two rows 
     1353        row1 = 1 
     1354        row2 = 4 
     1355        index1 = self.widget.lstParams.model().index(row1, 0, QtCore.QModelIndex()) 
     1356        index2 = self.widget.lstParams.model().index(row2, 0, QtCore.QModelIndex()) 
     1357        selection_model = self.widget.lstParams.selectionModel() 
     1358        selection_model.select(index1, selection_model.Select | selection_model.Rows) 
     1359        selection_model.select(index2, selection_model.Select | selection_model.Rows) 
     1360 
     1361        # add constraints 
     1362        self.widget.addSimpleConstraint() 
     1363 
     1364        # deactivate the first constraint 
     1365        constraint_objects = self.widget.getConstraintObjectsForModel() 
     1366        constraint_objects[0].active = False 
     1367 
     1368        con_list = [False, False, False, False, True, False] 
     1369        new_list = [] 
     1370        for row in range(self.widget._model_model.rowCount()): 
     1371            new_list.append(self.widget.rowHasActiveConstraint(row)) 
     1372 
     1373        self.assertEqual(new_list, con_list) 
     1374 
     1375    def testGetConstraintsForModel(self): 
     1376        """ 
     1377        Test the constraint getter for constraint texts 
     1378        """ 
     1379        # select model: cylinder / cylinder 
     1380        category_index = self.widget.cbCategory.findText("Cylinder") 
     1381        self.widget.cbCategory.setCurrentIndex(category_index) 
     1382 
     1383        model_index = self.widget.cbModel.findText("cylinder") 
     1384        self.widget.cbModel.setCurrentIndex(model_index) 
     1385 
     1386        # no constraints 
     1387        self.assertEqual(self.widget.getConstraintsForModel(),[]) 
     1388 
     1389        # select two rows 
     1390        row1 = 1 
     1391        row2 = 4 
     1392        index1 = self.widget.lstParams.model().index(row1, 0, QtCore.QModelIndex()) 
     1393        index2 = self.widget.lstParams.model().index(row2, 0, QtCore.QModelIndex()) 
     1394        selection_model = self.widget.lstParams.selectionModel() 
     1395        selection_model.select(index1, selection_model.Select | selection_model.Rows) 
     1396        selection_model.select(index2, selection_model.Select | selection_model.Rows) 
     1397 
     1398        # add constraints 
     1399        self.widget.addSimpleConstraint() 
     1400 
     1401        # simple constraints 
     1402        self.assertEqual(self.widget.getConstraintsForModel(), [('background', '0.001'), ('radius', '20')]) 
     1403        objects = self.widget.getConstraintObjectsForModel() 
     1404        self.assertEqual(len(objects), 2) 
     1405        self.assertEqual(objects[1].value, '20') 
     1406        self.assertEqual(objects[0].param, 'background') 
     1407 
     1408 
     1409        # add complex constraint 
     1410        const = Constraint(parent=None, param='scale', func='5*sld') 
     1411        row = 0 
     1412        self.widget.addConstraintToRow(constraint=const, row=row) 
     1413        self.assertEqual(self.widget.getConstraintsForModel(),[('scale', '5*sld'), ('background', '0.001'), ('radius', '20')]) 
     1414        objects = self.widget.getConstraintObjectsForModel() 
     1415        self.assertEqual(len(objects), 3) 
     1416        self.assertEqual(objects[0].func, '5*sld') 
     1417 
     1418    def testReplaceConstraintName(self): 
     1419        """ 
     1420        Test the replacement of constraint moniker 
     1421        """ 
     1422        # select model: cylinder / cylinder 
     1423        category_index = self.widget.cbCategory.findText("Cylinder") 
     1424        self.widget.cbCategory.setCurrentIndex(category_index) 
     1425 
     1426        model_index = self.widget.cbModel.findText("cylinder") 
     1427        self.widget.cbModel.setCurrentIndex(model_index) 
     1428 
     1429        old_name = 'M5' 
     1430        new_name = 'poopy' 
     1431        # add complex constraint 
     1432        const = Constraint(parent=None, param='scale', func='%s.5*sld'%old_name) 
     1433        row = 0 
     1434        self.widget.addConstraintToRow(constraint=const, row=row) 
     1435 
     1436        # Replace 'm5' with 'poopy' 
     1437        self.widget.replaceConstraintName(old_name, new_name) 
     1438 
     1439        self.assertEqual(self.widget.getConstraintsForModel(),[('scale', 'poopy.5*sld')]) 
     1440 
    10281441 
    10291442if __name__ == "__main__": 
  • src/sas/qtgui/Plotting/Plotter.py

    r8f83719f reb1a386  
    282282        Show a dialog allowing adding custom text to the chart 
    283283        """ 
    284         if self.addText.exec_() == QtWidgets.QDialog.Accepted: 
    285             # Retrieve the new text, its font and color 
    286             extra_text = self.addText.text() 
    287             extra_font = self.addText.font() 
    288             extra_color = self.addText.color() 
    289  
    290             # Place the text on the screen at (0,0) 
    291             pos_x = self.x_click 
    292             pos_y = self.y_click 
    293  
    294             # Map QFont onto MPL font 
    295             mpl_font = FontProperties() 
    296             mpl_font.set_size(int(extra_font.pointSize())) 
    297             mpl_font.set_family(str(extra_font.family())) 
    298             mpl_font.set_weight(int(extra_font.weight())) 
    299             # MPL style names 
    300             styles = ['normal', 'italic', 'oblique'] 
    301             # QFont::Style maps directly onto the above 
    302             try: 
    303                 mpl_font.set_style(styles[extra_font.style()]) 
    304             except: 
    305                 pass 
    306  
    307             if len(extra_text) > 0: 
    308                 new_text = self.ax.text(str(pos_x), 
    309                                         str(pos_y), 
    310                                         extra_text, 
    311                                         color=extra_color, 
    312                                         fontproperties=mpl_font) 
    313                 # Update the list of annotations 
    314                 self.textList.append(new_text) 
    315                 self.canvas.draw_idle() 
    316                 pass 
     284        if self.addText.exec_() != QtWidgets.QDialog.Accepted: 
     285            return 
     286 
     287        # Retrieve the new text, its font and color 
     288        extra_text = self.addText.text() 
     289        extra_font = self.addText.font() 
     290        extra_color = self.addText.color() 
     291 
     292        # Place the text on the screen at the click location 
     293        pos_x = self.x_click 
     294        pos_y = self.y_click 
     295 
     296        # Map QFont onto MPL font 
     297        mpl_font = FontProperties() 
     298        mpl_font.set_size(int(extra_font.pointSize())) 
     299        mpl_font.set_family(str(extra_font.family())) 
     300        mpl_font.set_weight(int(extra_font.weight())) 
     301        # MPL style names 
     302        styles = ['normal', 'italic', 'oblique'] 
     303        # QFont::Style maps directly onto the above 
     304        try: 
     305            mpl_font.set_style(styles[extra_font.style()]) 
     306        except: 
     307            pass 
     308 
     309        if len(extra_text) > 0: 
     310            new_text = self.ax.text(pos_x, 
     311                                    pos_y, 
     312                                    extra_text, 
     313                                    color=extra_color, 
     314                                    fontproperties=mpl_font) 
     315 
     316            # Update the list of annotations 
     317            self.textList.append(new_text) 
     318            self.canvas.draw() 
    317319 
    318320    def onRemoveText(self): 
     
    325327        txt = self.textList[num_text - 1] 
    326328        text_remove = txt.get_text() 
    327         txt.remove() 
     329        try: 
     330            txt.remove() 
     331        except ValueError: 
     332            # Text got already deleted somehow 
     333            pass 
    328334        self.textList.remove(txt) 
    329335 
  • src/sas/qtgui/Plotting/PlotterBase.py

    r8f83719f reb1a386  
    66from PyQt5 import QtWidgets, QtPrintSupport 
    77 
    8 # TODO: Replace the qt4agg calls below with qt5 equivalent. 
    9 # Requires some code modifications. 
    10 # https://www.boxcontrol.net/embedding-matplotlib-plot-on-pyqt5-gui.html 
    11 # 
    12 # matplotlib.use("Qt5Agg") 
    138from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas 
    149from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar 
     
    3631 
    3732        #plt.style.use('ggplot') 
    38         plt.style.use('seaborn-darkgrid') 
     33        #plt.style.use('seaborn-darkgrid') 
    3934 
    4035        # a figure instance to plot on 
     
    176171    def xscale(self, scale='linear'): 
    177172        """ X-axis scale setter """ 
     173        self.ax.cla() 
    178174        self.ax.set_xscale(scale) 
    179175        self._xscale = scale 
  • src/sas/qtgui/Plotting/UnitTesting/PlotterTest.py

    • Property mode changed from 100755 to 100644
    r53c771e r63319b0  
    5050        self.assertEqual(self.plotter.data, self.data) 
    5151        self.assertEqual(self.plotter._title, self.data.name) 
    52         self.assertEqual(self.plotter.xLabel, "$()$") 
    53         self.assertEqual(self.plotter.yLabel, "$()$") 
     52        self.assertEqual(self.plotter.xLabel, "") 
     53        self.assertEqual(self.plotter.yLabel, "") 
    5454 
    5555    def testPlotWithErrors(self): 
     
    9191        self.plotter.data = data 
    9292        self.plotter.show() 
    93         FigureCanvas.draw = MagicMock() 
     93        FigureCanvas.draw_idle = MagicMock() 
    9494 
    9595        self.plotter.plot(hide_error=True) 
     
    9797        self.assertEqual(self.plotter.ax.get_xscale(), 'linear') 
    9898        self.assertEqual(self.plotter.ax.get_yscale(), 'linear') 
    99         self.assertTrue(FigureCanvas.draw.called) 
     99        self.assertTrue(FigureCanvas.draw_idle.called) 
    100100 
    101101    def testCreateContextMenuQuick(self): 
     
    200200        self.plotter.addText.exec_ = MagicMock(return_value = QtWidgets.QDialog.Accepted) 
    201201        # Add text to graph 
     202        self.plotter.x_click = 1.0 
     203        self.plotter.y_click = 5.0 
    202204        self.plotter.onAddText() 
    203205        self.plotter.show() 
     
    394396        xl = self.plotter.ax.xaxis.label.get_text() 
    395397        yl = self.plotter.ax.yaxis.label.get_text() 
    396         self.assertEqual(xl, "$()$") 
    397         self.assertEqual(yl, "$()$") 
     398        self.assertEqual(xl, "") 
     399        self.assertEqual(yl, "") 
    398400 
    399401        # Prepare new data 
  • src/sas/qtgui/Plotting/UnitTesting/SlicerParametersTest.py

    r53c771e r725d9c06  
    11import sys 
    22import unittest 
     3import webbrowser 
    34from unittest.mock import MagicMock 
    45 
     
    67from PyQt5 import QtCore 
    78from PyQt5 import QtTest 
    8 from PyQt5 import QtWebKit 
    99 
    1010# set up import paths 
     
    7272        self.widget.show() 
    7373 
    74         #Mock the QWebView method 
    75         QtWebKit.QWebView.show = MagicMock() 
    76         QtWebKit.QWebView.load = MagicMock() 
     74        #Mock the webbrowser.open method 
     75        webbrowser.open = MagicMock() 
    7776 
    7877        # Invoke the action 
     
    8079 
    8180        # Check if show() got called 
    82         self.assertTrue(QtWebKit.QWebView.show.called) 
     81        self.assertTrue(webbrowser.open.called) 
    8382 
    8483        # Assure the filename is correct 
    85         self.assertIn("graph_help.html", QtWebKit.QWebView.load.call_args[0][0].toString()) 
     84        self.assertIn("graph_help.html", webbrowser.open.call_args[0][0]) 
    8685         
    8786    def testSetModel(self): 
  • src/sas/qtgui/Utilities/GuiUtils.py

    r6cb305a r63319b0  
    325325    """ 
    326326    assert isinstance(item, QtGui.QStandardItem) 
    327     assert isinstance(update_data, list) 
     327    #assert isinstance(update_data, list) 
    328328 
    329329    # Add the actual Data1D/Data2D object 
  • src/sas/qtgui/Utilities/LocalConfig.py

    • Property mode changed from 100755 to 100644
    rb3e8629 r235d766  
    132132DEFAULT_PERSPECTIVE = 'None' 
    133133 
     134# Default threading model 
     135USING_TWISTED = False 
     136 
    134137# Time out for updating sasview 
    135138UPDATE_TIMEOUT = 2 
  • src/sas/qtgui/Utilities/ObjectLibrary.py

    • Property mode changed from 100755 to 100644
  • src/sas/sascalc/fit/AbstractFitEngine.py

    r574adc7 r63319b0  
    300300            self.qmax = math.sqrt(x_max * x_max + y_max * y_max) 
    301301        ## new error image for fitting purpose 
    302         if self.err_data is None or self.err_data == []: 
     302        if self.err_data is None or not self.err_data: 
    303303            self.res_err_data = np.ones(len(self.data)) 
    304304        else: 
Note: See TracChangeset for help on using the changeset viewer.