- Timestamp:
- Feb 22, 2018 8:04:57 AM (7 years ago)
- 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. - Location:
- src/sas
- Files:
-
- 10 added
- 18 edited
Legend:
- Unmodified
- Added
- Removed
-
src/sas/qtgui/GUITests.py
r50bfab0 rda9a0722 56 56 from Perspectives.Fitting.UnitTesting import FitPageTest 57 57 from Perspectives.Fitting.UnitTesting import FittingOptionsTest 58 from Perspectives.Fitting.UnitTesting import MultiConstraintTest 59 from Perspectives.Fitting.UnitTesting import ComplexConstraintTest 60 from Perspectives.Fitting.UnitTesting import ConstraintWidgetTest 61 58 62 # Invariant 59 63 from Perspectives.Invariant.UnitTesting import InvariantPerspectiveTest 60 from Perspectives.Invariant.UnitTesting import InvariantDetailsTest 64 61 65 # Inversion 62 66 from Perspectives.Inversion.UnitTesting import InversionPerspectiveTest 63 64 67 65 68 def suite(): … … 114 117 unittest.makeSuite(FitPageTest.FitPageTest, 'test'), 115 118 unittest.makeSuite(FittingOptionsTest.FittingOptionsTest, 'test'), 119 unittest.makeSuite(MultiConstraintTest.MultiConstraintTest, 'test'), 120 unittest.makeSuite(ConstraintWidgetTest.ConstraintWidgetTest, 'test'), 121 unittest.makeSuite(ComplexConstraintTest.ComplexConstraintTest, 'test'), 122 116 123 # Invariant 117 124 unittest.makeSuite(InvariantPerspectiveTest.InvariantPerspectiveTest, 'test'), 118 unittest.makeSuite(InvariantDetailsTest.InvariantDetailsTest, 'test'),119 125 # Inversion 120 126 unittest.makeSuite(InversionPerspectiveTest.InversionTest, 'test'), 121 )127 ) 122 128 return unittest.TestSuite(suites) 123 129 -
src/sas/qtgui/MainWindow/GuiManager.py
re90988c r14ec91c5 9 9 from PyQt5.QtGui import * 10 10 from PyQt5.QtCore import Qt, QLocale, QUrl 11 from PyQt5.QtWebKitWidgets import QWebView12 11 13 12 from twisted.internet import reactor … … 185 184 186 185 # Save users from themselves... 187 if isinstance(self._current_perspective, Perspectives.PERSPECTIVES[str(perspective_name)]):188 189 return186 #if isinstance(self._current_perspective, Perspectives.PERSPECTIVES[str(perspective_name)]): 187 self.setupPerspectiveMenubarOptions(self._current_perspective) 188 # return 190 189 191 190 # Close the previous perspective … … 623 622 def actionConstrained_Fit(self): 624 623 """ 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() 628 630 629 631 def actionCombine_Batch_Fit(self): -
src/sas/qtgui/MainWindow/UnitTesting/GuiManagerTest.py
r8353d90 r725d9c06 9 9 from PyQt5.QtTest import QTest 10 10 from PyQt5.QtCore import * 11 from PyQt5.QtWebKit import *12 11 from unittest.mock import MagicMock 13 12 … … 272 271 #### HELP #### 273 272 # test when PyQt5 works with html 274 def notestActionDocumentation(self):273 def testActionDocumentation(self): 275 274 """ 276 275 Menu Help/Documentation 277 276 """ 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() 283 278 284 279 # Invoke the action 285 280 self.manager.actionDocumentation() 286 281 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 289 285 290 286 def skip_testActionTutorial(self): -
src/sas/qtgui/Perspectives/Fitting/FitThread.py
- Property mode changed from 100755 to 100644
rb3e8629 r235d766 95 95 # print "ERROR IN FIT THREAD: ", traceback.format_exc() 96 96 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) 98 101 99 102 -
src/sas/qtgui/Perspectives/Fitting/FittingPerspective.py
re90988c r14ec91c5 11 11 12 12 from sas.qtgui.Perspectives.Fitting.FittingWidget import FittingWidget 13 from sas.qtgui.Perspectives.Fitting.ConstraintWidget import ConstraintWidget 13 14 from sas.qtgui.Perspectives.Fitting.FittingOptions import FittingOptions 14 15 from sas.qtgui.Perspectives.Fitting.GPUOptions import GPUOptions … … 17 18 """ 18 19 """ 20 tabsModifiedSignal = QtCore.pyqtSignal() 21 fittingStartedSignal = QtCore.pyqtSignal(list) 22 fittingStoppedSignal = QtCore.pyqtSignal(list) 23 19 24 name = "Fitting" # For displaying in the combo box in DataExplorer 20 25 def __init__(self, parent=None, data=None): … … 37 42 self.optimizer = 'Levenberg-Marquardt' 38 43 39 # Dataset inde -> Fitting tab mapping44 # Dataset index -> Fitting tab mapping 40 45 self.dataToFitTab = {} 41 46 42 47 # The tabs need to be closeable 43 48 self.setTabsClosable(True) 49 50 # The tabs need to be movabe 51 self.setMovable(True) 44 52 45 53 self.communicate = self.parent.communicator() … … 51 59 self.tabCloseRequested.connect(self.tabCloses) 52 60 self.communicate.dataDeletedSignal.connect(self.dataDeleted) 61 self.fittingStartedSignal.connect(self.onFittingStarted) 62 self.fittingStoppedSignal.connect(self.onFittingStopped) 53 63 54 64 # Perspective window not allowed to close by default … … 66 76 self.gpu_options_widget = GPUOptions(self) 67 77 68 #self.setWindowTitle('Fit panel - Active Fitting Optimizer: %s' % self.optimizer)69 78 self.updateWindowTitle() 70 79 … … 79 88 def setClosable(self, value=True): 80 89 """ 81 Allow outsiders close this widget90 Allow outsiders to close this widget 82 91 """ 83 92 assert isinstance(value, bool) … … 108 117 tab.is_batch_fitting = is_batch 109 118 # 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) 111 120 ObjectLibrary.addObject(tab_name, tab) 112 121 self.tabs.append(tab) … … 115 124 self.maxIndex += 1 116 125 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) 117 143 118 144 def updateFitDict(self, item_key, tab_name): … … 126 152 self.dataToFitTab[item_key_str] = [tab_name] 127 153 128 #print "CURRENT dict: ", self.dataToFitTab 129 130 def tabName(self, is_batch=False): 154 def getTabName(self, is_batch=False): 131 155 """ 132 156 Get the new tab name, based on the number of fitting tabs so far … … 134 158 page_name = "BatchPage" if is_batch else "FitPage" 135 159 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" 136 167 return page_name 137 168 … … 162 193 self.removeTab(index) 163 194 del self.tabs[index] 195 self.tabsModifiedSignal.emit() 164 196 except IndexError: 165 197 # The tab might have already been deleted previously … … 189 221 self.dataToFitTab.pop(index_to_delete_str) 190 222 191 #print "CURRENT dict: ", self.dataToFitTab192 193 223 def allowBatch(self): 194 224 """ … … 213 243 raise AttributeError(msg) 214 244 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 215 250 items = [data_item] if is_batch else data_item 216 217 251 for data in items: 218 252 # Find the first unassigned tab. 219 253 # 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] 221 255 222 256 if numpy.any(available_tabs): … … 238 272 self.updateWindowTitle() 239 273 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 240 289 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 23 23 24 24 import sas.qtgui.Utilities.GuiUtils as GuiUtils 25 import sas.qtgui.Utilities.LocalConfig as LocalConfig 25 26 from sas.qtgui.Utilities.CategoryInstaller import CategoryInstaller 26 27 from sas.qtgui.Plotting.PlotterData import Data1D … … 41 42 from sas.qtgui.Perspectives.Fitting.ViewDelegate import PolyViewDelegate 42 43 from sas.qtgui.Perspectives.Fitting.ViewDelegate import MagnetismViewDelegate 44 from sas.qtgui.Perspectives.Fitting.Constraint import Constraint 45 from sas.qtgui.Perspectives.Fitting.MultiConstraint import MultiConstraint 43 46 44 47 … … 51 54 DEFAULT_POLYDISP_FUNCTION = 'gaussian' 52 55 53 USING_TWISTED = True54 #USING_TWISTED = False55 56 56 57 class ToolTippedItemModel(QtGui.QStandardItemModel): … … 80 81 Main widget for selecting form and structure factor models 81 82 """ 83 constraintAddedSignal = QtCore.pyqtSignal(list) 84 newModelSignal = QtCore.pyqtSignal() 82 85 def __init__(self, parent=None, data=None, tab_id=1): 83 86 … … 178 181 # Batch/single fitting 179 182 self.is_batch_fitting = False 183 self.is_chain_fitting = False 180 184 # Current SasModel in view 181 185 self.kernel_module = None … … 207 211 self.orig_poly_index = 3 208 212 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 209 217 # Data for chosen model 210 218 self.model_data = None … … 301 309 self.lstParams.setStyleSheet(stylesheet) 302 310 self.lstParams.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) 303 self.lstParams.customContextMenuRequested.connect(self.showModel Description)311 self.lstParams.customContextMenuRequested.connect(self.showModelContextMenu) 304 312 self.lstParams.setAttribute(QtCore.Qt.WA_MacShowFocusRect, False) 305 313 # Poly model displayed in poly list … … 350 358 self.cbFileNames.addItem(filename) 351 359 self.cbFileNames.setVisible(True) 360 self.chkChainFit.setEnabled(True) 361 self.chkChainFit.setVisible(True) 352 362 # This panel is not designed to view individual fits, so disable plotting 353 363 self.cmdPlot.setVisible(False) … … 389 399 """ Enable/disable the magnetism tab """ 390 400 self.tabFitting.setTabEnabled(TAB_MAGNETISM, isChecked) 401 402 def toggleChainFit(self, isChecked): 403 """ Enable/disable chain fitting """ 404 self.is_chain_fitting = isChecked 391 405 392 406 def toggle2D(self, isChecked): … … 412 426 self.chkMagnetism.setEnabled(False) 413 427 self.chkMagnetism.setCheckState(False) 428 self.chkChainFit.setEnabled(False) 429 self.chkChainFit.setVisible(False) 414 430 # Tabs 415 431 self.tabFitting.setTabEnabled(TAB_POLY, False) … … 434 450 self.chkPolydispersity.toggled.connect(self.togglePoly) 435 451 self.chkMagnetism.toggled.connect(self.toggleMagnetism) 452 self.chkChainFit.toggled.connect(self.toggleChainFit) 436 453 # Buttons 437 454 self.cmdFit.clicked.connect(self.onFit) … … 442 459 # Respond to change in parameters from the UI 443 460 self._model_model.itemChanged.connect(self.onMainParamsChange) 461 #self.constraintAddedSignal.connect(self.modifyViewOnConstraint) 444 462 self._poly_model.itemChanged.connect(self.onPolyModelChange) 445 463 self._magnet_model.itemChanged.connect(self.onMagnetModelChange) … … 448 466 self.options_widget.plot_signal.connect(self.onOptionsUpdate) 449 467 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 453 818 """ 454 819 msg = 'Model description:\n' … … 466 831 action.setDefaultWidget(label) 467 832 menu.addAction(action) 468 menu.exec_(self.lstParams.viewport().mapToGlobal(position))833 return menu 469 834 470 835 def onSelectModel(self): … … 506 871 self.respondToModelStructure(model=model, structure_factor=structure) 507 872 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 515 891 if self.data_is_loaded: 516 892 self.cmdPlot.setText("Show Plot") … … 521 897 self.createDefaultDataset() 522 898 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 523 909 # Update state stack 524 910 self.updateUndo() 911 912 # Let others know 913 self.newModelSignal.emit() 525 914 526 915 def onSelectCategory(self): … … 596 985 # name of the function - just pass 597 986 return 598 elif model_column == self.lstPoly.itemDelegate().poly_filename:599 # filename for array - just pass600 return601 987 else: 602 988 try: … … 610 996 self.kernel_module.setParam(parameter_name + '.' + \ 611 997 self.lstPoly.itemDelegate().columnDict()[model_column], value) 998 999 # Update plot 1000 self.updateData() 612 1001 613 1002 def onMagnetModelChange(self, item): … … 693 1082 Perform fitting on the current data 694 1083 """ 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 712 1085 fit_id = 0 713 constraints = []714 smearer = None715 page_id = [210]716 1086 handler = None 717 1087 batch_inputs = {} 718 1088 batch_outputs = {} 719 list_page_id = [page_id]720 1089 #--------------------------------- 721 if USING_TWISTED:1090 if LocalConfig.USING_TWISTED: 722 1091 handler = None 723 1092 updater = None … … 728 1097 updater = handler.update_fit 729 1098 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 749 1106 750 1107 # Create the fitting thread, based on the fitter … … 752 1109 753 1110 calc_fit = FitThread(handler=handler, 754 755 756 757 page_id=list_page_id,758 759 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: 762 1119 # start the trhrhread with twisted 763 1120 calc_thread = threads.deferToThread(calc_fit.compute) 764 calc_thread.addCallback( self.fitComplete)1121 calc_thread.addCallback(completefn) 765 1122 calc_thread.addErrback(self.fitFailed) 766 1123 else: … … 769 1126 calc_fit.ready(2.5) 770 1127 771 772 #disable the Fit button773 self.cmdFit.setText('Running...')774 1128 self.communicate.statusBarUpdateSignal.emit('Fitting started...') 775 self.cmdFit.setEnabled(False) 1129 # Disable some elements 1130 self.setFittingStarted() 776 1131 777 1132 def updateFit(self): … … 792 1147 """ 793 1148 #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() 800 1150 801 1151 def fitComplete(self, result): … … 805 1155 """ 806 1156 #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 811 1164 812 1165 res_list = result[0][0] … … 816 1169 np.any(res.pvec is None) or \ 817 1170 not np.all(np.isfinite(res.pvec)): 818 msg = "Fitting did not converge! !!"1171 msg = "Fitting did not converge!" 819 1172 self.communicate.statusBarUpdateSignal.emit(msg) 820 1173 logging.error(msg) … … 846 1199 chi2_repr = GuiUtils.formatNumber(self.chi2, high=True) 847 1200 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 848 1254 849 1255 def iterateOverModel(self, func): … … 1240 1646 # Now we claim the model has been loaded 1241 1647 self.model_is_loaded = True 1648 # Change the model name to a monicker 1649 self.kernel_module.name = self.modelName() 1242 1650 1243 1651 # (Re)-create headers … … 1344 1752 self.updateUndo() 1345 1753 1754 def isCheckable(self, row): 1755 return self._model_model.item(row, 0).isCheckable() 1756 1346 1757 def checkboxSelected(self, item): 1347 1758 # Assure we're dealing with checkboxes … … 1350 1761 status = item.checkState() 1351 1762 1352 def isCheckable(row):1353 return self._model_model.item(row, 0).isCheckable()1354 1355 1763 # 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 1358 1764 # Switch off signaling from the model to avoid recursion 1359 1765 self._model_model.blockSignals(True) 1360 1766 # 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()] 1362 1769 self._model_model.blockSignals(False) 1363 1770 … … 1382 1789 for row_index in range(model.rowCount()) 1383 1790 if isChecked(row_index)] 1384 1385 def nameForFittedData(self, name):1386 """1387 Generate name for the current fit1388 """1389 if self.is2D:1390 name += "2d"1391 name = "M%i [%s]" % (self.tab_id, name)1392 return name1393 1791 1394 1792 def createNewIndex(self, fitted_data): … … 1832 2230 self.setMagneticModel() 1833 2231 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 1834 2248 def readFitPage(self, fp): 1835 2249 """ -
src/sas/qtgui/Perspectives/Fitting/UI/FittingWidgetUI.ui
rd1955d67 r7fd20fc 7 7 <x>0</x> 8 8 <y>0</y> 9 <width>4 34</width>10 <height> 466</height>9 <width>448</width> 10 <height>526</height> 11 11 </rect> 12 12 </property> … … 26 26 <string>FittingWidget</string> 27 27 </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"> 30 30 <layout class="QHBoxLayout" name="horizontalLayout"> 31 31 <item> … … 72 72 </layout> 73 73 </item> 74 <item row="1" column="0" colspan="4">74 <item row="1" column="0"> 75 75 <widget class="QTabWidget" name="tabFitting"> 76 76 <property name="currentIndex"> … … 81 81 <string>Model</string> 82 82 </attribute> 83 <layout class="QGridLayout" name="gridLayout_ 19">83 <layout class="QGridLayout" name="gridLayout_4"> 84 84 <item row="0" column="0" colspan="4"> 85 85 <widget class="QGroupBox" name="groupBox_6"> … … 161 161 <string>Options </string> 162 162 </property> 163 <layout class="Q GridLayout" name="gridLayout_16">164 <item row="0" column="0">163 <layout class="QVBoxLayout" name="verticalLayout"> 164 <item> 165 165 <widget class="QCheckBox" name="chkPolydispersity"> 166 166 <property name="enabled"> … … 178 178 </widget> 179 179 </item> 180 <item row="1" column="0">180 <item> 181 181 <widget class="QCheckBox" name="chk2DView"> 182 182 <property name="enabled"> … … 194 194 </widget> 195 195 </item> 196 <item row="2" column="0">196 <item> 197 197 <widget class="QCheckBox" name="chkMagnetism"> 198 198 <property name="enabled"> … … 204 204 <property name="text"> 205 205 <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><html><head/><body><p>Switch on magnetic scattering parameters.</p><p>This option is available only for 2D models.</p></body></html></string> 219 </property> 220 <property name="text"> 221 <string>Chain fit</string> 206 222 </property> 207 223 <property name="checkable"> … … 412 428 </item> 413 429 <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> 482 502 </item> 483 503 </layout> -
src/sas/qtgui/Perspectives/Fitting/UnitTesting/FittingOptionsTest.py
r53c771e r725d9c06 1 1 import sys 2 2 import unittest 3 import webbrowser 3 4 from bumps import options 4 5 5 6 from PyQt5 import QtGui, QtWidgets 6 from PyQt5 import QtWebKit7 7 8 8 from unittest.mock import MagicMock … … 114 114 115 115 # test disabled until pyQt5 works well 116 def notestOnHelp(self):116 def testOnHelp(self): 117 117 ''' Test help display''' 118 #Mock the QWebView method 119 QtWebKit.QWebView.show = MagicMock() 120 QtWebKit.QWebView.load = MagicMock() 118 webbrowser.open = MagicMock() 121 119 122 120 # Invoke the action on default tab 123 121 self.widget.onHelp() 124 122 # Check if show() got called 125 self.assertTrue( QtWebKit.QWebView.show.called)123 self.assertTrue(webbrowser.open.called) 126 124 # 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]) 128 126 129 127 # Change the combo index … … 131 129 self.widget.onHelp() 132 130 # Check if show() got called 133 self.assertEqual( QtWebKit.QWebView.show.call_count, 2)131 self.assertEqual(webbrowser.open.call_count, 2) 134 132 # 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]) 136 134 137 135 # Change the index again … … 139 137 self.widget.onHelp() 140 138 # Check if show() got called 141 self.assertEqual( QtWebKit.QWebView.show.call_count, 3)139 self.assertEqual(webbrowser.open.call_count, 3) 142 140 # 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]) 144 142 145 143 def testWidgetFromOptions(self): -
src/sas/qtgui/Perspectives/Fitting/UnitTesting/FittingPerspectiveTest.py
r53c771e r63319b0 19 19 if not QtWidgets.QApplication.instance(): 20 20 app = QtWidgets.QApplication(sys.argv) 21 21 22 22 23 class FittingPerspectiveTest(unittest.TestCase): … … 44 45 self.assertEqual(len(self.widget.tabs), 1) 45 46 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") 47 49 48 50 def testAddTab(self): … … 52 54 self.widget.addFit(None) 53 55 self.assertEqual(len(self.widget.tabs), 2) 54 self.assertEqual(self.widget. tabName(), "FitPage2")56 self.assertEqual(self.widget.getTabName(), "FitPage2") 55 57 self.assertEqual(self.widget.maxIndex, 2) 56 58 # Add an empty batch tab 57 59 self.widget.addFit(None, is_batch=True) 58 60 self.assertEqual(len(self.widget.tabs), 3) 59 self.assertEqual(self.widget. tabName(2), "BatchPage3")61 self.assertEqual(self.widget.getTabName(2), "BatchPage3") 60 62 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) 61 70 62 71 def testResetTab(self): 63 72 ''' Remove data from last tab''' 64 73 self.assertEqual(len(self.widget.tabs), 1) 65 self.assertEqual(self.widget. tabName(), "FitPage1")74 self.assertEqual(self.widget.getTabName(), "FitPage1") 66 75 self.assertEqual(self.widget.maxIndex, 1) 67 76 … … 71 80 # see that the tab didn't disappear, just changed the name/id 72 81 self.assertEqual(len(self.widget.tabs), 1) 73 self.assertEqual(self.widget. tabName(), "FitPage2")82 self.assertEqual(self.widget.getTabName(), "FitPage2") 74 83 self.assertEqual(self.widget.maxIndex, 2) 75 84 … … 95 104 self.assertEqual(len(self.widget.tabs), 1) 96 105 self.assertEqual(self.widget.maxIndex, 2) 97 self.assertEqual(self.widget. tabName(), "FitPage2")106 self.assertEqual(self.widget.getTabName(), "FitPage2") 98 107 99 108 # Attemtp to remove the last tab … … 102 111 self.assertEqual(len(self.widget.tabs), 1) 103 112 self.assertEqual(self.widget.maxIndex, 3) 104 self.assertEqual(self.widget. tabName(), "FitPage3")113 self.assertEqual(self.widget.getTabName(), "FitPage3") 105 114 106 115 def testAllowBatch(self): … … 138 147 self.assertEqual(len(self.widget.tabs), 4) 139 148 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") 140 173 141 174 if __name__ == "__main__": -
src/sas/qtgui/Perspectives/Fitting/UnitTesting/FittingWidgetTest.py
- Property mode changed from 100755 to 100644
r53c771e r14ec91c5 17 17 from sas.qtgui.Utilities.GuiUtils import * 18 18 from sas.qtgui.Perspectives.Fitting.FittingWidget import * 19 from sas.qtgui.Perspectives.Fitting.Constraint import Constraint 20 19 21 from sas.qtgui.UnitTesting.TestUtils import QtSignalSpy 20 22 … … 112 114 fittingWindow = self.widget 113 115 114 self.assertIsInstance(fittingWindow.lstMagnetic.itemDelegate(), Qt Gui.QStyledItemDelegate)116 self.assertIsInstance(fittingWindow.lstMagnetic.itemDelegate(), QtWidgets.QStyledItemDelegate) 115 117 #Test loading from json categories 116 118 fittingWindow.SASModelToQModel("cylinder") 117 119 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") 119 121 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") 121 123 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") 123 125 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") 125 127 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") 127 129 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") 129 131 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") 131 133 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") 133 135 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") 135 137 136 138 # test the delegate a bit 137 139 delegate = fittingWindow.lstMagnetic.itemDelegate() 138 140 self.assertEqual(delegate.editableParameters(), [1, 2, 3]) 139 self.assertIsInstance(delegate.combo_updated, QtCore.pyqtBoundSignal)140 141 141 142 def testSelectStructureFactor(self): … … 662 663 test_data = Data1D(x=[1,2], y=[1,2]) 663 664 item = QtGui.QStandardItem() 664 updateModelItem(item, [test_data], "test")665 updateModelItem(item, test_data, "test") 665 666 # Force same data into logic 666 667 self.widget.data = item … … 688 689 # Force same data into logic 689 690 item = QtGui.QStandardItem() 690 updateModelItem(item, [test_data], "test")691 updateModelItem(item, test_data, "test") 691 692 # Force same data into logic 692 693 self.widget.data = item … … 714 715 test_data = Data1D(x=[1,2], y=[1,2]) 715 716 item = QtGui.QStandardItem() 716 updateModelItem(item, [test_data], "test")717 updateModelItem(item, test_data, "test") 717 718 # Force same data into logic 718 719 self.widget.data = item … … 758 759 # Force same data into logic 759 760 item = QtGui.QStandardItem() 760 updateModelItem(item, [test_data], "test")761 updateModelItem(item, test_data, "test") 761 762 # Force same data into logic 762 763 self.widget.data = item … … 786 787 self.assertEqual(update_spy.count(), 1) 787 788 788 # test disabled until pyqt5 deals with html properly 789 def notestOnHelp(self): 789 def testOnHelp(self): 790 790 """ 791 791 Test various help pages shown in this widget 792 792 """ 793 #Mock the QWebViewmethod794 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() 796 796 797 797 # Invoke the action on default tab 798 798 self.widget.onHelp() 799 799 # Check if show() got called 800 self.assertTrue( QtWebKit.QWebView.show.called)800 self.assertTrue(self.widget.parent.showHelp.called) 801 801 # 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]) 803 803 804 804 # Change the tab to options … … 806 806 self.widget.onHelp() 807 807 # Check if show() got called 808 self.assertEqual( QtWebKit.QWebView.show.call_count, 2)808 self.assertEqual(self.widget.parent.showHelp.call_count, 2) 809 809 # 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]) 811 811 812 812 # Change the tab to smearing … … 814 814 self.widget.onHelp() 815 815 # Check if show() got called 816 self.assertEqual( QtWebKit.QWebView.show.call_count, 3)816 self.assertEqual(self.widget.parent.showHelp.call_count, 3) 817 817 # 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]) 819 819 820 820 # Change the tab to poly … … 822 822 self.widget.onHelp() 823 823 # Check if show() got called 824 self.assertEqual( QtWebKit.QWebView.show.call_count, 4)824 self.assertEqual(self.widget.parent.showHelp.call_count, 4) 825 825 # Assure the filename is correct 826 self.assertIn("p d_help.html", QtWebKit.QWebView.load.call_args[0][0].toString())826 self.assertIn("polydispersity.html", self.widget.parent.showHelp.call_args[0][0]) 827 827 828 828 # Change the tab to magnetism … … 830 830 self.widget.onHelp() 831 831 # Check if show() got called 832 self.assertEqual( QtWebKit.QWebView.show.call_count, 5)832 self.assertEqual(self.widget.parent.showHelp.call_count, 5) 833 833 # 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]) 835 835 836 836 def testReadFitPage(self): … … 1026 1026 self.assertNotIn(new_value, self.widget.kernel_module.details[name_modified_param] ) 1027 1027 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 1028 1441 1029 1442 if __name__ == "__main__": -
src/sas/qtgui/Plotting/Plotter.py
r8f83719f reb1a386 282 282 Show a dialog allowing adding custom text to the chart 283 283 """ 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() 317 319 318 320 def onRemoveText(self): … … 325 327 txt = self.textList[num_text - 1] 326 328 text_remove = txt.get_text() 327 txt.remove() 329 try: 330 txt.remove() 331 except ValueError: 332 # Text got already deleted somehow 333 pass 328 334 self.textList.remove(txt) 329 335 -
src/sas/qtgui/Plotting/PlotterBase.py
r8f83719f reb1a386 6 6 from PyQt5 import QtWidgets, QtPrintSupport 7 7 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.html11 #12 # matplotlib.use("Qt5Agg")13 8 from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas 14 9 from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar … … 36 31 37 32 #plt.style.use('ggplot') 38 plt.style.use('seaborn-darkgrid')33 #plt.style.use('seaborn-darkgrid') 39 34 40 35 # a figure instance to plot on … … 176 171 def xscale(self, scale='linear'): 177 172 """ X-axis scale setter """ 173 self.ax.cla() 178 174 self.ax.set_xscale(scale) 179 175 self._xscale = scale -
src/sas/qtgui/Plotting/UnitTesting/PlotterTest.py
- Property mode changed from 100755 to 100644
r53c771e r63319b0 50 50 self.assertEqual(self.plotter.data, self.data) 51 51 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, "") 54 54 55 55 def testPlotWithErrors(self): … … 91 91 self.plotter.data = data 92 92 self.plotter.show() 93 FigureCanvas.draw = MagicMock()93 FigureCanvas.draw_idle = MagicMock() 94 94 95 95 self.plotter.plot(hide_error=True) … … 97 97 self.assertEqual(self.plotter.ax.get_xscale(), 'linear') 98 98 self.assertEqual(self.plotter.ax.get_yscale(), 'linear') 99 self.assertTrue(FigureCanvas.draw .called)99 self.assertTrue(FigureCanvas.draw_idle.called) 100 100 101 101 def testCreateContextMenuQuick(self): … … 200 200 self.plotter.addText.exec_ = MagicMock(return_value = QtWidgets.QDialog.Accepted) 201 201 # Add text to graph 202 self.plotter.x_click = 1.0 203 self.plotter.y_click = 5.0 202 204 self.plotter.onAddText() 203 205 self.plotter.show() … … 394 396 xl = self.plotter.ax.xaxis.label.get_text() 395 397 yl = self.plotter.ax.yaxis.label.get_text() 396 self.assertEqual(xl, " $()$")397 self.assertEqual(yl, " $()$")398 self.assertEqual(xl, "") 399 self.assertEqual(yl, "") 398 400 399 401 # Prepare new data -
src/sas/qtgui/Plotting/UnitTesting/SlicerParametersTest.py
r53c771e r725d9c06 1 1 import sys 2 2 import unittest 3 import webbrowser 3 4 from unittest.mock import MagicMock 4 5 … … 6 7 from PyQt5 import QtCore 7 8 from PyQt5 import QtTest 8 from PyQt5 import QtWebKit9 9 10 10 # set up import paths … … 72 72 self.widget.show() 73 73 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() 77 76 78 77 # Invoke the action … … 80 79 81 80 # Check if show() got called 82 self.assertTrue( QtWebKit.QWebView.show.called)81 self.assertTrue(webbrowser.open.called) 83 82 84 83 # 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]) 86 85 87 86 def testSetModel(self): -
src/sas/qtgui/Utilities/GuiUtils.py
r6cb305a r63319b0 325 325 """ 326 326 assert isinstance(item, QtGui.QStandardItem) 327 assert isinstance(update_data, list)327 #assert isinstance(update_data, list) 328 328 329 329 # Add the actual Data1D/Data2D object -
src/sas/qtgui/Utilities/LocalConfig.py
- Property mode changed from 100755 to 100644
rb3e8629 r235d766 132 132 DEFAULT_PERSPECTIVE = 'None' 133 133 134 # Default threading model 135 USING_TWISTED = False 136 134 137 # Time out for updating sasview 135 138 UPDATE_TIMEOUT = 2 -
src/sas/qtgui/Utilities/ObjectLibrary.py
- Property mode changed from 100755 to 100644
-
src/sas/sascalc/fit/AbstractFitEngine.py
r574adc7 r63319b0 300 300 self.qmax = math.sqrt(x_max * x_max + y_max * y_max) 301 301 ## 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: 303 303 self.res_err_data = np.ones(len(self.data)) 304 304 else:
Note: See TracChangeset
for help on using the changeset viewer.