Changeset 4bddb9ca in sasview
- Timestamp:
- Aug 6, 2018 4:15:47 AM (6 years ago)
- Branches:
- ESS_GUI, ESS_GUI_batch_fitting, ESS_GUI_bumps_abstraction, ESS_GUI_iss1116, ESS_GUI_iss879, ESS_GUI_opencl, ESS_GUI_ordering, ESS_GUI_sync_sascalc
- Children:
- bc8750eb, c0de493
- Parents:
- a688be1 (diff), 97df8a9 (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. - Files:
-
- 6 edited
Legend:
- Unmodified
- Added
- Removed
-
src/sas/qtgui/MainWindow/GuiManager.py
re4335ae r8e2cd79 520 520 def actionCopy(self): 521 521 """ 522 """ 523 print("actionCopy TRIGGERED") 522 Send a signal to the fitting perspective so parameters 523 can be saved to the clipboard 524 """ 525 self.communicate.copyFitParamsSignal.emit("") 524 526 pass 525 527 526 528 def actionPaste(self): 527 529 """ 528 """ 529 print("actionPaste TRIGGERED") 530 pass 530 Send a signal to the fitting perspective so parameters 531 from the clipboard can be used to modify the fit state 532 """ 533 self.communicate.pasteFitParamsSignal.emit() 531 534 532 535 def actionReport(self): … … 555 558 def actionExcel(self): 556 559 """ 557 """ 558 print("actionExcel TRIGGERED") 559 pass 560 Send a signal to the fitting perspective so parameters 561 can be saved to the clipboard 562 """ 563 self.communicate.copyFitParamsSignal.emit("Excel") 560 564 561 565 def actionLatex(self): 562 566 """ 563 """ 564 print("actionLatex TRIGGERED") 565 pass 567 Send a signal to the fitting perspective so parameters 568 can be saved to the clipboard 569 """ 570 self.communicate.copyFitParamsSignal.emit("Latex") 566 571 567 572 #============ VIEW ================= -
src/sas/qtgui/Perspectives/Fitting/FittingUtilities.py
rb87dc1a r04972ea 3 3 from PyQt5 import QtCore 4 4 from PyQt5 import QtGui 5 from PyQt5 import QtWidgets6 5 7 6 import numpy … … 99 98 continue 100 99 width = kernel_module.getParam(p.name+'.width') 101 type = kernel_module.getParam(p.name+'.type')100 ptype = kernel_module.getParam(p.name+'.type') 102 101 103 102 item1_2 = QtGui.QStandardItem(str(width)) … … 107 106 item1_4 = QtGui.QStandardItem() 108 107 item1_4.setEditable(False) 109 item1_5 = QtGui.QStandardItem( type)108 item1_5 = QtGui.QStandardItem(ptype) 110 109 item1_5.setEditable(False) 111 110 poly_item.appendRow([item1_1, item1_2, item1_3, item1_4, item1_5]) … … 458 457 returning the modified (deep) copy of the kernel. 459 458 """ 460 assert (isinstance(results, dict))459 assert isinstance(results, dict) 461 460 local_kernel = copy.deepcopy(kernel) 462 461 … … 479 478 for row in range(num_rows): 480 479 param_name = model.item(row, 0).text() 481 checkbox_state = model.item(row, 0).checkState() == QtCore.Qt.Checked482 value = model.item(row, 1).text()480 checkbox_state = model.item(row, 0).checkState() == QtCore.Qt.Checked 481 value = model.item(row, 1).text() 483 482 column_shift = 0 484 483 if model.columnCount() == 5: # no error column … … 509 508 """ 510 509 param = [] 511 if kernel_module is None: 510 if kernel_module is None: 512 511 return None 513 512 for param_name in list(kernel_module.params.keys()): … … 526 525 527 526 return param 527 528 def formatParameters(parameters): 529 """ 530 Prepare the parameter string in the standard SasView layout 531 """ 532 assert parameters is not None 533 assert isinstance(parameters, list) 534 output_string = "sasview_parameter_values:" 535 for parameter in parameters: 536 output_string += ",".join([p for p in parameter if p is not None]) 537 output_string += ":" 538 return output_string 539 540 def formatParametersExcel(parameters): 541 """ 542 Prepare the parameter string in the Excel format (tab delimited) 543 """ 544 assert parameters is not None 545 assert isinstance(parameters, list) 546 crlf = chr(13) + chr(10) 547 tab = chr(9) 548 549 output_string = "" 550 # names 551 names = "" 552 values = "" 553 for parameter in parameters: 554 names += parameter[0]+tab 555 # Add the error column if fitted 556 if parameter[1] == "True" and parameter[3] is not None: 557 names += parameter[0]+"_err"+tab 558 559 values += parameter[2]+tab 560 if parameter[1] == "True" and parameter[3] is not None: 561 values += parameter[3]+tab 562 # add .npts and .nsigmas when necessary 563 if parameter[0][-6:] == ".width": 564 names += parameter[0].replace('.width', '.nsigmas') + tab 565 names += parameter[0].replace('.width', '.npts') + tab 566 values += parameter[5] + tab + parameter[4] + tab 567 568 output_string = names + crlf + values 569 return output_string 570 571 def formatParametersLatex(parameters): 572 """ 573 Prepare the parameter string in latex 574 """ 575 assert parameters is not None 576 assert isinstance(parameters, list) 577 output_string = r'\begin{table}' 578 output_string += r'\begin{tabular}[h]' 579 580 crlf = chr(13) + chr(10) 581 output_string += '{|' 582 output_string += 'l|l|'*len(parameters) 583 output_string += r'}\hline' 584 output_string += crlf 585 586 for index, parameter in enumerate(parameters): 587 name = parameter[0] # Parameter name 588 output_string += name.replace('_', r'\_') # Escape underscores 589 # Add the error column if fitted 590 if parameter[1] == "True" and parameter[3] is not None: 591 output_string += ' & ' 592 output_string += parameter[0]+r'\_err' 593 594 if index < len(parameters) - 1: 595 output_string += ' & ' 596 597 # add .npts and .nsigmas when necessary 598 if parameter[0][-6:] == ".width": 599 output_string += parameter[0].replace('.width', '.nsigmas') + ' & ' 600 output_string += parameter[0].replace('.width', '.npts') 601 602 if index < len(parameters) - 1: 603 output_string += ' & ' 604 605 output_string += r'\\ \hline' 606 output_string += crlf 607 608 # Construct row of values and errors 609 for index, parameter in enumerate(parameters): 610 output_string += parameter[2] 611 if parameter[1] == "True" and parameter[3] is not None: 612 output_string += ' & ' 613 output_string += parameter[3] 614 615 if index < len(parameters) - 1: 616 output_string += ' & ' 617 618 # add .npts and .nsigmas when necessary 619 if parameter[0][-6:] == ".width": 620 output_string += parameter[5] + ' & ' 621 output_string += parameter[4] 622 623 if index < len(parameters) - 1: 624 output_string += ' & ' 625 626 output_string += r'\\ \hline' 627 output_string += crlf 628 output_string += r'\end{tabular}' 629 output_string += r'\end{table}' 630 631 return output_string -
src/sas/qtgui/Perspectives/Fitting/FittingWidget.py
r14acf92 r97df8a9 1 1 import json 2 2 import os 3 import copy4 3 from collections import defaultdict 5 4 … … 70 69 """ 71 70 def __init__(self, parent=None): 72 QtGui.QStandardItemModel.__init__(self, parent)71 QtGui.QStandardItemModel.__init__(self, parent) 73 72 74 73 def headerData(self, section, orientation, role=QtCore.Qt.DisplayRole): … … 153 152 def logic(self): 154 153 # make sure the logic contains at least one element 155 assert (self._logic)154 assert self._logic 156 155 # logic connected to the currently shown data 157 156 return self._logic[self.data_index] … … 208 207 self.is_chain_fitting = False 209 208 # Is the fit job running? 210 self.fit_started =False209 self.fit_started = False 211 210 # The current fit thread 212 211 self.calc_fit = None … … 324 323 } 325 324 325 QTreeView::item { 326 border: 1px; 327 padding: 2px 15px; 328 } 329 326 330 QTreeView::item:hover { 327 331 background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #e7effd, stop: 1 #cbdaf1); … … 517 521 self.communicate.saveAnalysisSignal.connect(self.savePageState) 518 522 self.smearing_widget.smearingChangedSignal.connect(self.onSmearingOptionsUpdate) 519 #self.communicate.saveReportSignal.connect(self.saveReport) 523 self.communicate.copyFitParamsSignal.connect(self.onParameterCopy) 524 self.communicate.pasteFitParamsSignal.connect(self.onParameterPaste) 520 525 521 526 def modelName(self): … … 557 562 return menu 558 563 # Select for fitting 559 param_string = "parameter " if num_rows ==1 else "parameters "560 to_string = "to its current value" if num_rows ==1 else "to their current values"564 param_string = "parameter " if num_rows == 1 else "parameters " 565 to_string = "to its current value" if num_rows == 1 else "to their current values" 561 566 has_constraints = any([self.rowHasConstraint(i) for i in rows]) 562 567 … … 613 618 # There have to be only two rows selected. The caller takes care of that 614 619 # but let's check the correctness. 615 assert (len(selected_rows)==2)620 assert len(selected_rows) == 2 616 621 617 622 params_list = [s.data() for s in selected_rows] … … 680 685 """ 681 686 # Create a new item and add the Constraint object as a child 682 assert (isinstance(constraint, Constraint))683 assert (0<=row<=self._model_model.rowCount())687 assert isinstance(constraint, Constraint) 688 assert 0 <= row <= self._model_model.rowCount() 684 689 685 690 item = QtGui.QStandardItem() … … 732 737 Delete constraints from selected parameters. 733 738 """ 734 params = 739 params = [s.data() for s in self.lstParams.selectionModel().selectedRows() 735 740 if self.isCheckable(s.row())] 736 741 for param in params: … … 783 788 Finds out if row of the main model has a constraint child 784 789 """ 785 item = self._model_model.item(row, 1)790 item = self._model_model.item(row, 1) 786 791 if item.hasChildren(): 787 792 c = item.child(0).data() … … 794 799 Finds out if row of the main model has an active constraint child 795 800 """ 796 item = self._model_model.item(row, 1)801 item = self._model_model.item(row, 1) 797 802 if item.hasChildren(): 798 803 c = item.child(0).data() … … 805 810 Finds out if row of the main model has an active, nontrivial constraint child 806 811 """ 807 item = self._model_model.item(row, 1)812 item = self._model_model.item(row, 1) 808 813 if item.hasChildren(): 809 814 c = item.child(0).data() … … 1443 1448 qmax = self.q_range_max 1444 1449 params_to_fit = self.parameters_to_fit 1445 if (not params_to_fit):1450 if not params_to_fit: 1446 1451 raise ValueError('Fitting requires at least one parameter to optimize.') 1447 1452 … … 1562 1567 self.has_error_column = True 1563 1568 1569 def iterateOverPolyModel(self, func): 1570 """ 1571 Take func and throw it inside the poly model row loop 1572 """ 1573 for row_i in range(self._poly_model.rowCount()): 1574 func(row_i) 1575 1564 1576 def updatePolyModelFromList(self, param_dict): 1565 1577 """ … … 1569 1581 if not dict: 1570 1582 return 1571 1572 def iterateOverPolyModel(func):1573 """1574 Take func and throw it inside the poly model row loop1575 """1576 for row_i in range(self._poly_model.rowCount()):1577 func(row_i)1578 1583 1579 1584 def updateFittedValues(row_i): … … 1613 1618 # updating charts with every single model change on the end of fitting 1614 1619 self._poly_model.blockSignals(True) 1615 iterateOverPolyModel(updateFittedValues)1620 self.iterateOverPolyModel(updateFittedValues) 1616 1621 self._poly_model.blockSignals(False) 1617 1622 … … 1621 1626 self.lstPoly.itemDelegate().addErrorColumn() 1622 1627 error_column = [] 1623 iterateOverPolyModel(createErrorColumn)1628 self.iterateOverPolyModel(createErrorColumn) 1624 1629 1625 1630 # switch off reponse to model change … … 1630 1635 1631 1636 self.has_poly_error_column = True 1637 1638 def iterateOverMagnetModel(self, func): 1639 """ 1640 Take func and throw it inside the magnet model row loop 1641 """ 1642 for row_i in range(self._model_model.rowCount()): 1643 func(row_i) 1632 1644 1633 1645 def updateMagnetModelFromList(self, param_dict): … … 1679 1691 # updating charts with every single model change on the end of fitting 1680 1692 self._magnet_model.blockSignals(True) 1681 iterateOverMagnetModel(updateFittedValues)1693 self.iterateOverMagnetModel(updateFittedValues) 1682 1694 self._magnet_model.blockSignals(False) 1683 1695 … … 1687 1699 self.lstMagnetic.itemDelegate().addErrorColumn() 1688 1700 error_column = [] 1689 iterateOverMagnetModel(createErrorColumn)1701 self.iterateOverMagnetModel(createErrorColumn) 1690 1702 1691 1703 # switch off reponse to model change … … 2803 2815 #self._copy_parameters_state(self.fixed_param, self.state.fixed_param) 2804 2816 2805 2817 def onParameterCopy(self, format=None): 2818 """ 2819 Copy current parameters into the clipboard 2820 """ 2821 # run a loop over all parameters and pull out 2822 # first - regular params 2823 param_list = [] 2824 def gatherParams(row): 2825 """ 2826 Create list of main parameters based on _model_model 2827 """ 2828 param_name = str(self._model_model.item(row, 0).text()) 2829 param_checked = str(self._model_model.item(row, 0).checkState() == QtCore.Qt.Checked) 2830 param_value = str(self._model_model.item(row, 1).text()) 2831 param_error = None 2832 column_offset = 0 2833 if self.has_error_column: 2834 param_error = str(self._model_model.item(row, 2).text()) 2835 column_offset = 1 2836 param_min = str(self._model_model.item(row, 2+column_offset).text()) 2837 param_max = str(self._model_model.item(row, 3+column_offset).text()) 2838 param_list.append([param_name, param_checked, param_value, param_error, param_min, param_max]) 2839 2840 def gatherPolyParams(row): 2841 """ 2842 Create list of polydisperse parameters based on _poly_model 2843 """ 2844 param_name = str(self._poly_model.item(row, 0).text()).split()[-1] 2845 param_checked = str(self._poly_model.item(row, 0).checkState() == QtCore.Qt.Checked) 2846 param_value = str(self._poly_model.item(row, 1).text()) 2847 param_error = None 2848 column_offset = 0 2849 if self.has_poly_error_column: 2850 param_error = str(self._poly_model.item(row, 2).text()) 2851 column_offset = 1 2852 param_min = str(self._poly_model.item(row, 2+column_offset).text()) 2853 param_max = str(self._poly_model.item(row, 3+column_offset).text()) 2854 param_npts = str(self._poly_model.item(row, 4+column_offset).text()) 2855 param_nsigs = str(self._poly_model.item(row, 5+column_offset).text()) 2856 param_fun = str(self._poly_model.item(row, 6+column_offset).text()).rstrip() 2857 # width 2858 name = param_name+".width" 2859 param_list.append([name, param_checked, param_value, param_error, 2860 param_npts, param_nsigs, param_min, param_max, param_fun]) 2861 2862 def gatherMagnetParams(row): 2863 """ 2864 Create list of magnetic parameters based on _magnet_model 2865 """ 2866 param_name = str(self._magnet_model.item(row, 0).text()) 2867 param_checked = str(self._magnet_model.item(row, 0).checkState() == QtCore.Qt.Checked) 2868 param_value = str(self._magnet_model.item(row, 1).text()) 2869 param_error = None 2870 column_offset = 0 2871 if self.has_magnet_error_column: 2872 param_error = str(self._magnet_model.item(row, 2).text()) 2873 column_offset = 1 2874 param_min = str(self._magnet_model.item(row, 2+column_offset).text()) 2875 param_max = str(self._magnet_model.item(row, 3+column_offset).text()) 2876 param_list.append([param_name, param_checked, param_value, param_error, param_min, param_max]) 2877 2878 self.iterateOverModel(gatherParams) 2879 if self.chkPolydispersity.isChecked(): 2880 self.iterateOverPolyModel(gatherPolyParams) 2881 if self.chkMagnetism.isChecked() and self.chkMagnetism.isEnabled(): 2882 self.iterateOverMagnetModel(sgatherMagnetParams) 2883 2884 if format=="": 2885 formatted_output = FittingUtilities.formatParameters(param_list) 2886 elif format == "Excel": 2887 formatted_output = FittingUtilities.formatParametersExcel(param_list) 2888 elif format == "Latex": 2889 formatted_output = FittingUtilities.formatParametersLatex(param_list) 2890 else: 2891 raise AttributeError("Bad format specifier.") 2892 2893 # Dump formatted_output to the clipboard 2894 cb = QtWidgets.QApplication.clipboard() 2895 cb.setText(formatted_output) 2896 2897 def onParameterPaste(self): 2898 """ 2899 Use the clipboard to update fit state 2900 """ 2901 # Check if the clipboard contains right stuff 2902 cb = QtWidgets.QApplication.clipboard() 2903 cb_text = cb.text() 2904 2905 context = {} 2906 # put the text into dictionary 2907 lines = cb_text.split(':') 2908 if lines[0] != 'sasview_parameter_values': 2909 return False 2910 for line in lines[1:-1]: 2911 if len(line) != 0: 2912 item = line.split(',') 2913 check = item[1] 2914 name = item[0] 2915 value = item[2] 2916 # Transfer the text to content[dictionary] 2917 context[name] = [check, value] 2918 2919 # limits 2920 limit_lo = item[3] 2921 context[name].append(limit_lo) 2922 limit_hi = item[4] 2923 context[name].append(limit_hi) 2924 2925 # Polydisp 2926 if len(item) > 5: 2927 value = item[5] 2928 context[name].append(value) 2929 try: 2930 value = item[6] 2931 context[name].append(value) 2932 value = item[7] 2933 context[name].append(value) 2934 except IndexError: 2935 pass 2936 2937 self.updateFullModel(context) 2938 self.updateFullPolyModel(context) 2939 2940 def updateFullModel(self, param_dict): 2941 """ 2942 Update the model with new parameters 2943 """ 2944 assert isinstance(param_dict, dict) 2945 if not dict: 2946 return 2947 2948 def updateFittedValues(row): 2949 # Utility function for main model update 2950 # internal so can use closure for param_dict 2951 param_name = str(self._model_model.item(row, 0).text()) 2952 if param_name not in list(param_dict.keys()): 2953 return 2954 # checkbox state 2955 param_checked = QtCore.Qt.Checked if param_dict[param_name][0] == "True" else QtCore.Qt.Unchecked 2956 self._model_model.item(row, 0).setCheckState(param_checked) 2957 2958 # modify the param value 2959 param_repr = GuiUtils.formatNumber(param_dict[param_name][1], high=True) 2960 self._model_model.item(row, 1).setText(param_repr) 2961 2962 # Potentially the error column 2963 ioffset = 0 2964 if len(param_dict[param_name])>4 and self.has_error_column: 2965 # error values are not editable - no need to update 2966 #error_repr = GuiUtils.formatNumber(param_dict[param_name][2], high=True) 2967 #self._model_model.item(row, 2).setText(error_repr) 2968 ioffset = 1 2969 # min/max 2970 param_repr = GuiUtils.formatNumber(param_dict[param_name][2+ioffset], high=True) 2971 self._model_model.item(row, 2+ioffset).setText(param_repr) 2972 param_repr = GuiUtils.formatNumber(param_dict[param_name][3+ioffset], high=True) 2973 self._model_model.item(row, 3+ioffset).setText(param_repr) 2974 2975 # block signals temporarily, so we don't end up 2976 # updating charts with every single model change on the end of fitting 2977 self._model_model.blockSignals(True) 2978 self.iterateOverModel(updateFittedValues) 2979 self._model_model.blockSignals(False) 2980 2981 def updateFullPolyModel(self, param_dict): 2982 """ 2983 Update the polydispersity model with new parameters, create the errors column 2984 """ 2985 assert isinstance(param_dict, dict) 2986 if not dict: 2987 return 2988 2989 def updateFittedValues(row): 2990 # Utility function for main model update 2991 # internal so can use closure for param_dict 2992 if row >= self._poly_model.rowCount(): 2993 return 2994 param_name = str(self._poly_model.item(row, 0).text()).rsplit()[-1] + '.width' 2995 if param_name not in list(param_dict.keys()): 2996 return 2997 # checkbox state 2998 param_checked = QtCore.Qt.Checked if param_dict[param_name][0] == "True" else QtCore.Qt.Unchecked 2999 self._poly_model.item(row,0).setCheckState(param_checked) 3000 3001 # modify the param value 3002 param_repr = GuiUtils.formatNumber(param_dict[param_name][1], high=True) 3003 self._poly_model.item(row, 1).setText(param_repr) 3004 3005 # Potentially the error column 3006 ioffset = 0 3007 if len(param_dict[param_name])>4 and self.has_poly_error_column: 3008 ioffset = 1 3009 # min 3010 param_repr = GuiUtils.formatNumber(param_dict[param_name][2+ioffset], high=True) 3011 self._poly_model.item(row, 2+ioffset).setText(param_repr) 3012 # max 3013 param_repr = GuiUtils.formatNumber(param_dict[param_name][3+ioffset], high=True) 3014 self._poly_model.item(row, 3+ioffset).setText(param_repr) 3015 # Npts 3016 param_repr = GuiUtils.formatNumber(param_dict[param_name][4+ioffset], high=True) 3017 self._poly_model.item(row, 4+ioffset).setText(param_repr) 3018 # Nsigs 3019 param_repr = GuiUtils.formatNumber(param_dict[param_name][5+ioffset], high=True) 3020 self._poly_model.item(row, 5+ioffset).setText(param_repr) 3021 3022 param_repr = GuiUtils.formatNumber(param_dict[param_name][5+ioffset], high=True) 3023 self._poly_model.item(row, 5+ioffset).setText(param_repr) 3024 3025 # block signals temporarily, so we don't end up 3026 # updating charts with every single model change on the end of fitting 3027 self._poly_model.blockSignals(True) 3028 self.iterateOverPolyModel(updateFittedValues) 3029 self._poly_model.blockSignals(False) 3030 3031 -
src/sas/qtgui/Perspectives/Fitting/ViewDelegate.py
rcf8d6c9 r97df8a9 180 180 Overwrite generic painter for certain columns 181 181 """ 182 if index.column() in (self.poly_ min, self.poly_max):182 if index.column() in (self.poly_pd, self.poly_min, self.poly_max): 183 183 # Units - present in nice HTML 184 184 options = QtWidgets.QStyleOptionViewItem(option) … … 263 263 Overwrite generic painter for certain columns 264 264 """ 265 if index.column() in (self.mag_ min, self.mag_max, self.mag_unit):265 if index.column() in (self.mag_value, self.mag_min, self.mag_max, self.mag_unit): 266 266 # Units - present in nice HTML 267 267 options = QtWidgets.QStyleOptionViewItem(option) … … 288 288 rect = textRect.topLeft() 289 289 y = rect.y() 290 y += 5.0 # magic value for rendering nice display in the table290 y += 6.0 # magic value for rendering nice display in the table 291 291 rect.setY(y) 292 292 painter.translate(rect) -
src/sas/qtgui/Utilities/GuiUtils.py
r6ff103a r8e2cd79 254 254 # Mask Editor requested 255 255 maskEditorSignal = QtCore.pyqtSignal(Data2D) 256 257 # Fitting parameter copy to clipboard 258 copyFitParamsSignal = QtCore.pyqtSignal(str) 259 260 # Fitting parameter paste from clipboard 261 pasteFitParamsSignal = QtCore.pyqtSignal() 256 262 257 263 def updateModelItemWithPlot(item, update_data, name=""): -
installers/sasview_qt5_osx.spec
r46f59ba ra688be1 44 44 # ADDITIONAL DATA ############################################################ 45 45 datas = [('../src/sas/sasview/images', 'images')] 46 47 datas = [('../src/sas/sasview/images/ball.ico', '.')]48 49 46 datas.append(('../src/sas/sasview/media','media')) 50 47 datas.append(('../src/sas/sasview/test','test')) … … 173 170 debug=False, 174 171 upx=UPX, 175 icon=os.path.join("../src/sas/sasview/images","ball.ic o"),172 icon=os.path.join("../src/sas/sasview/images","ball.icns"), 176 173 version="version.txt", 177 174 console=False ) … … 189 186 app = BUNDLE(coll, 190 187 name='sasview.app', 191 icon='../src/sas/sasview/images/ball.ic o',188 icon='../src/sas/sasview/images/ball.icns', 192 189 bundle_identifier=None) 193 190
Note: See TracChangeset
for help on using the changeset viewer.