Coverage for src/scrilla/gui/widgets/factories.py: 97%

163 statements  

« prev     ^ index     » next       coverage.py v6.4.2, created at 2022-07-18 18:14 +0000

1from typing import List, Union, Dict 

2 

3from PySide6 import QtWidgets, QtCore, QtGui 

4from scrilla.gui import utilities 

5from scrilla.gui import definitions as gui_definitions 

6from scrilla.static import definitions, constants 

7 

8 

9def generate_control_skeleton() -> Dict[str, bool]: 

10 """ 

11 Generates a control dictionary with all argument controls initialized to `False` using the `scrilla.static.definitions.ARG_DICT`. This dictionary is used to configured the input widgets enabled on `scrilla.gui.widgets.components.ArgumentWidget`. By switching controls in this dictionary on before passing it into the constructor, the widgets for that specific input control will be displayed on `scrilla.gui.widgets.components.ArgumentWidget`. 

12 

13 .. notes :: 

14 - Output will be formatted as follows, 

15 ```python 

16 { 

17 'criteria': False, 

18 'discount': False, 

19 'end_date': False, 

20 'expiry': False, 

21 'investment': False, 

22 'likelihood': False, 

23 'moments': False, 

24 'optimize_sharpe': False, 

25 'percentiles': False, 

26 'probability': False, 

27 'start_date': False, 

28 'steps': False, 

29 'target': False 

30 } 

31 ``` 

32 - Function widgets from `scrilla.gui.widgets.function` will filter through the `scrilla.static.definitions.FUNC_DICT` dictionary to determine which arguments are enabled for a given function. 

33 """ 

34 return {arg: False for arg in definitions.ARG_DICT if not definitions.ARG_DICT[arg]['cli_only']} 

35 

36 

37def layout_factory(layout: str) -> QtWidgets.QWidget: 

38 """ 

39 Factory function for generating instances of `PySide6.QtWidgets.QLayout`.  

40 

41 Parameters 

42 ---------- 

43 1. **layout** : ``str`` 

44 Type of layout being constructed. Allowable values: `vertical-box`, `horizontal-box`. If `layout=None`, a `PySide6.QtWidgets.QBoxLayout` will be returned. 

45 """ 

46 widget = QtWidgets.QWidget() 

47 

48 if layout == 'vertical-box': 

49 widget.setLayout(QtWidgets.QVBoxLayout()) 

50 

51 elif layout == 'horizontal-box': 51 ↛ 54line 51 didn't jump to line 54, because the condition on line 51 was never false

52 widget.setLayout(QtWidgets.QHBoxLayout()) 

53 

54 return widget 

55 

56 

57def dialog_widget_factory(component: str, options: list) -> QtWidgets.QDialog: 

58 dialog = QtWidgets.QDialog() 

59 dialog.setObjectName(component) 

60 dialog.setLayout(QtWidgets.QVBoxLayout()) 

61 

62 user_select = QtWidgets.QComboBox() 

63 user_select.insertItems(0, options) 

64 user_text = argument_widget_factory('symbols', None, False) 

65 

66 input_widget = layout_factory('horizontal-box') 

67 input_widget.layout().addWidget(user_select) 

68 input_widget.layout().addWidget(user_text) 

69 

70 def save(): 

71 dialog.selection = user_select.currentText() 

72 dialog.value = user_text.text() 

73 dialog.accept() 

74 

75 def cancel(): 

76 dialog.close() 

77 

78 btn = QtWidgets.QDialogButtonBox.Save | QtWidgets.QDialogButtonBox.Cancel 

79 box = QtWidgets.QDialogButtonBox(btn) 

80 box.accepted.connect(save) 

81 box.rejected.connect(cancel) 

82 dialog.layout().addWidget(input_widget) 

83 dialog.layout().addWidget(box) 

84 

85 return dialog 

86 

87 

88def atomic_widget_factory(component: str, title: str = None) -> Union[QtWidgets.QWidget, QtWidgets.QLabel, QtWidgets.QPushButton, QtWidgets.QFileDialog, QtWidgets.QTableWidget, QtWidgets.QTableWidgetItem, QtWidgets.QMenuBar]: 

89 """ 

90 Factory function for generating various subclasses of `PySide6.QtWidgets.QWidget` pre-configured for application display. 

91 

92 Parameters 

93 ---------- 

94 1. **component**: ``str`` 

95 Allowable values can be assessed through the `scrilla.gui.definitions.FACTORIES['ATOMIC']` dictionary, underneath the successive `TYPES` key. If `component=None` is provided, a `PySide6.QtWidgets.QWidget` constructed with a `PySide6.QtWidgets.QHBoxLayout` will be returned. 

96 2. **title**: ``str`` 

97 Name assigned to the widget. 

98 """ 

99 atomic_map = gui_definitions.FACTORIES['ATOMIC'] 

100 

101 # Type Configuration 

102 if component in atomic_map['LABEL']: 

103 # Template Configuration 

104 if component in atomic_map['TEMPLATE']: 

105 widget = QtWidgets.QLabel(utilities.load_html_template(component)) 

106 widget.setWordWrap(True) 

107 widget.setOpenExternalLinks(True) 

108 elif component in atomic_map['UNTITLED']: 

109 widget = QtWidgets.QLabel() 

110 else: 

111 widget = QtWidgets.QLabel(title) 

112 elif component in atomic_map['BUTTON']: 

113 widget = QtWidgets.QPushButton(title) 

114 widget.setAutoDefault(True) 

115 widget.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor)) 

116 elif component in atomic_map['DIALOG']: 

117 widget = QtWidgets.QFileDialog() 

118 widget.setNameFilter(title) 

119 widget.setFileMode(QtWidgets.QFileDialog.AnyFile) 

120 widget.setViewMode(QtWidgets.QFileDialog.Detail) 

121 widget.setAcceptMode(QtWidgets.QFileDialog.AcceptSave) 

122 elif component in atomic_map['TABLE']: 

123 widget = QtWidgets.QTableWidget() 

124 widget.setHorizontalHeader(QtWidgets.QHeaderView(QtCore.Qt.Horizontal)) 

125 widget.horizontalHeader().setSectionResizeMode(QtWidgets.QHeaderView.Stretch) 

126 widget.setVerticalHeader(QtWidgets.QHeaderView(QtCore.Qt.Vertical)) 

127 widget.setSizeAdjustPolicy( 

128 QtWidgets.QAbstractScrollArea.AdjustToContents) 

129 elif component in atomic_map['ITEM']: 

130 widget = QtWidgets.QTableWidgetItem(title) 

131 widget.setTextAlignment(QtCore.Qt.AlignHCenter) 

132 elif component in atomic_map['MENU']: 

133 widget = QtWidgets.QMenuBar() 

134 

135 else: 

136 widget = QtWidgets.QWidget() 

137 widget.setLayout(QtWidgets.QHBoxLayout()) 

138 

139 

140 # Size Configuration 

141 if component in atomic_map['SIZING']['EXPANDEXPAND']: 

142 widget.setSizePolicy(QtWidgets.QSizePolicy( 

143 QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)) 

144 elif component in atomic_map['SIZING']['EXPANDMIN']: 

145 widget.setSizePolicy(QtWidgets.QSizePolicy( 

146 QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)) 

147 elif component in atomic_map['SIZING']['MINMAX']: 

148 widget.setSizePolicy(QtWidgets.QSizePolicy( 

149 QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Maximum)) 

150 elif component in atomic_map['SIZING']['MINMIN']: 150 ↛ 151line 150 didn't jump to line 151, because the condition on line 150 was never true

151 widget.setSizePolicy(QtWidgets.QSizePolicy( 

152 QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum)) 

153 elif component in atomic_map['SIZING']['MAXMAX']: 

154 widget.setSizePolicy(QtWidgets.QSizePolicy( 

155 QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Maximum)) 

156 

157 # Alignment Configuration 

158 if component in atomic_map['ALIGN']['TOP']: 

159 widget.setAlignment(QtCore.Qt.AlignTop) 

160 elif component in atomic_map['ALIGN']['LEFT']: 

161 widget.setAlignment(QtCore.Qt.AlignLeft) 

162 elif component in atomic_map['ALIGN']['HCENTER']: 

163 widget.setAlignment(QtCore.Qt.AlignHCenter) 

164 elif component in atomic_map['ALIGN']['BOTTOM']: 

165 widget.setAlignment(QtCore.Qt.AlignBottom) 

166 elif component in atomic_map['ALIGN']['CENTER']: 

167 widget.setAlignment(QtCore.Qt.AlignCenter) 

168 

169 # Type Specific Configuration 

170 # TODO: think about how to parametrize this in `scrilla.gui.definitions 

171 # could add a dictionary for tooltips and then use a generator-filter...` 

172 if component == 'hide-button': 

173 widget.setToolTip('Hide') 

174 elif component == 'clear-button': 

175 widget.setToolTip('Cancel') 

176 elif component == 'download-button': 

177 widget.setToolTip('Save As') 

178 elif component == 'source-button': 

179 widget.setToolTip('View Source') 

180 elif component == 'package-button': 

181 widget.setToolTip('View PyPi Package') 

182 elif component == 'documentation-button': 

183 widget.setToolTip('View Documentation') 

184 elif component == 'okay-button': 

185 widget.setToolTip('Okay') 

186 

187 if component not in atomic_map['ITEM']: 

188 widget.setObjectName(component) 

189 

190 return widget 

191 

192 

193def group_widget_factory(components: List[str], title: str) -> QtWidgets.QGroupBox: 

194 """ 

195 Embeds a group of `PySide6.QtWidgets.QRadioButton` in a `PySide6.QtWidgets.GroupBox` with a vertical layout. 

196 

197 Parameters 

198 ---------- 

199 1. **components**: ``List[str]`` 

200 A list of strings containing the text used for the radio button's display. Order of list corresponds to the displayed order of buttons. 

201 2. **title**: ``str`` 

202 Title assigned to the container of the radio buttons, `PySide6.QtWidgets.GroupBox`. 

203 """ 

204 widget_layout = QtWidgets.QVBoxLayout() 

205 

206 for i, component in enumerate(components): 

207 radio_button = QtWidgets.QRadioButton(component) 

208 radio_button.setObjectName('group-toggle') 

209 widget_layout.addWidget(radio_button) 

210 if i == 0: 

211 radio_button.setChecked(True) 

212 

213 widget = QtWidgets.QGroupBox(title) 

214 widget.setLayout(widget_layout) 

215 widget.setObjectName('group-box') 

216 

217 return widget 

218 

219 

220def argument_widget_factory(component: str, title: str = None, optional: bool = True) -> QtWidgets.QWidget: 

221 """ 

222 Factory function for generating various subclasses of instance `PySide6.QtWidgets.QWidgets` pre-configured for user-input.  

223 

224 Parameters 

225 ---------- 

226 1. **components** : ``str`` 

227 Allowable values: `date`, `decimal`, `currency`, `integer`, `select`, `flag`, `symbols`, `symbol`. If `components=None`, a `PySide6.QtWidgets.QWidget` will be returned. 

228 """ 

229 widget = atomic_widget_factory(None, 'input-container') 

230 label_widget = atomic_widget_factory('input-label') 

231 arg_map = gui_definitions.FACTORIES['ARGUMENTS'] 

232 # Type Configuration 

233 if component in arg_map['LINE']: 

234 main_widget = QtWidgets.QLineEdit() 

235 elif component in arg_map['DATE']: 

236 main_widget = QtWidgets.QDateEdit() 

237 elif component in arg_map['RADIO']: 

238 main_widget = QtWidgets.QRadioButton(title) 

239 else: 

240 main_widget = QtWidgets.QWidget() 

241 

242 # Sizing Configuration 

243 if component in arg_map['SIZING']['MAXMAX']: 

244 main_widget.setSizePolicy(QtWidgets.QSizePolicy( 

245 QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Maximum)) 

246 elif component in arg_map['SIZING']['MINMAX']: 

247 main_widget.setSizePolicy(QtWidgets.QSizePolicy( 

248 QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Maximum)) 

249 

250 # Constraint Configuration 

251 if component in arg_map['CONSTRAINTS']['LENGTH']: 

252 main_widget.setMaxLength(100) 

253 

254 # Initial Disabled Configuration 

255 if component in arg_map['DISABLED']: 

256 main_widget.setEnabled(False) 

257 

258 # Type Specific Configuration 

259 if component == 'date': 

260 main_widget.setDate(QtCore.QDate.currentDate()) 

261 main_widget.setMaximumDate(QtCore.QDate.currentDate()) 

262 main_widget.setMinimumDate(QtCore.QDate( 

263 constants.constants['PRICE_YEAR_CUTOFF'], 1, 1)) 

264 elif component == 'decimal': 

265 main_widget.setValidator( 

266 QtGui.QDoubleValidator(-100, 100, 5, main_widget)) 

267 elif component == 'currency': 

268 # https://stackoverflow.com/questions/354044/what-is-the-best-u-s-currency-regex 

269 main_widget.setValidator(QtGui.QRegularExpressionValidator( 

270 r"[+-]?[0-9]{1,3}(?:,?[0-9]{3})*(?:\.[0-9]{2})", main_widget)) 

271 elif component == 'integer': 

272 main_widget.setValidator(QtGui.QIntValidator(0, 100, main_widget)) 

273 elif component == 'symbol': 

274 main_widget.setValidator( 

275 QtGui.QRegularExpressionValidator(r"[A-Za-z]+", main_widget)) 

276 

277 main_widget.setObjectName(component) 

278 widget.layout().addWidget(label_widget) 

279 widget.layout().addWidget(main_widget) 

280 

281 if optional: 

282 toggle_widget = QtWidgets.QCheckBox() 

283 toggle_widget.setObjectName('input-toggle') 

284 toggle_widget.setSizePolicy(QtWidgets.QSizePolicy( 

285 QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Maximum)) 

286 toggle_widget.stateChanged.connect( 286 ↛ exitline 286 didn't jump to the function exit

287 lambda: main_widget.setEnabled((not main_widget.isEnabled()))) 

288 widget.layout().addWidget(toggle_widget) 

289 

290 return widget 

291 

292 

293def set_policy_on_widget_list(widget_list: List[QtWidgets.QWidget], policy: QtWidgets.QSizePolicy) -> None: 

294 """ 

295 Sets the same policy on a list of widgets. 

296 

297 Parameters 

298 """ 

299 for widget in widget_list: 

300 widget.setSizePolicy(policy)