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
« prev ^ index » next coverage.py v6.4.2, created at 2022-07-18 18:14 +0000
1from typing import List, Union, Dict
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
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`.
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']}
37def layout_factory(layout: str) -> QtWidgets.QWidget:
38 """
39 Factory function for generating instances of `PySide6.QtWidgets.QLayout`.
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()
48 if layout == 'vertical-box':
49 widget.setLayout(QtWidgets.QVBoxLayout())
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())
54 return widget
57def dialog_widget_factory(component: str, options: list) -> QtWidgets.QDialog:
58 dialog = QtWidgets.QDialog()
59 dialog.setObjectName(component)
60 dialog.setLayout(QtWidgets.QVBoxLayout())
62 user_select = QtWidgets.QComboBox()
63 user_select.insertItems(0, options)
64 user_text = argument_widget_factory('symbols', None, False)
66 input_widget = layout_factory('horizontal-box')
67 input_widget.layout().addWidget(user_select)
68 input_widget.layout().addWidget(user_text)
70 def save():
71 dialog.selection = user_select.currentText()
72 dialog.value = user_text.text()
73 dialog.accept()
75 def cancel():
76 dialog.close()
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)
85 return dialog
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.
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']
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()
135 else:
136 widget = QtWidgets.QWidget()
137 widget.setLayout(QtWidgets.QHBoxLayout())
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))
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)
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')
187 if component not in atomic_map['ITEM']:
188 widget.setObjectName(component)
190 return widget
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.
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()
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)
213 widget = QtWidgets.QGroupBox(title)
214 widget.setLayout(widget_layout)
215 widget.setObjectName('group-box')
217 return widget
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.
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()
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))
250 # Constraint Configuration
251 if component in arg_map['CONSTRAINTS']['LENGTH']:
252 main_widget.setMaxLength(100)
254 # Initial Disabled Configuration
255 if component in arg_map['DISABLED']:
256 main_widget.setEnabled(False)
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))
277 main_widget.setObjectName(component)
278 widget.layout().addWidget(label_widget)
279 widget.layout().addWidget(main_widget)
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)
290 return widget
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.
297 Parameters
298 """
299 for widget in widget_list:
300 widget.setSizePolicy(policy)