OptionUI.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327
  1. from typing import Union, Sequence, List
  2. from PyQt5 import QtWidgets, QtGui
  3. from PyQt5.QtCore import QSettings
  4. from AppGUI.GUIElements import RadioSet, FCCheckBox, FCButton, FCComboBox, FCEntry, FCSpinner, FCColorEntry, \
  5. FCSliderWithSpinner, FCDoubleSpinner, FloatEntry, FCTextArea
  6. import gettext
  7. import AppTranslation as fcTranslate
  8. import builtins
  9. fcTranslate.apply_language('strings')
  10. if '_' not in builtins.__dict__:
  11. _ = gettext.gettext
  12. class OptionUI:
  13. def __init__(self, option: str):
  14. self.option = option
  15. def add_to_grid(self, grid: QtWidgets.QGridLayout, row: int) -> int:
  16. """
  17. Adds the necessary widget to the grid, starting at the supplied row.
  18. Returns the number of rows used (normally 1)
  19. """
  20. raise NotImplementedError()
  21. def get_field(self):
  22. raise NotImplementedError()
  23. class BasicOptionUI(OptionUI):
  24. """Abstract OptionUI that has a label on the left then some other widget on the right"""
  25. def __init__(self, option: str, label_text: str, label_tooltip: Union[str, None] = None,
  26. label_bold: bool = False, label_color: Union[str, None] = None):
  27. super().__init__(option=option)
  28. self.label_text = label_text
  29. self.label_tooltip = label_tooltip
  30. self.label_bold = label_bold
  31. self.label_color = label_color
  32. self.label_widget = self.build_label_widget()
  33. self.entry_widget = self.build_entry_widget()
  34. def build_label_widget(self) -> QtWidgets.QLabel:
  35. fmt = "%s:"
  36. if self.label_bold:
  37. fmt = "<b>%s</b>" % fmt
  38. if self.label_color:
  39. fmt = "<span style=\"color:%s;\">%s</span>" % (self.label_color, fmt)
  40. label_widget = QtWidgets.QLabel(fmt % _(self.label_text))
  41. if self.label_tooltip is not None:
  42. label_widget.setToolTip(_(self.label_tooltip))
  43. return label_widget
  44. def build_entry_widget(self) -> QtWidgets.QWidget:
  45. raise NotImplementedError()
  46. def add_to_grid(self, grid: QtWidgets.QGridLayout, row: int) -> int:
  47. grid.addWidget(self.label_widget, row, 0)
  48. grid.addWidget(self.entry_widget, row, 1)
  49. return 1
  50. def get_field(self):
  51. return self.entry_widget
  52. class LineEntryOptionUI(BasicOptionUI):
  53. def build_entry_widget(self) -> QtWidgets.QWidget:
  54. return FCEntry()
  55. # Not sure why this is needed over DoubleSpinnerOptionUI
  56. class FloatEntryOptionUI(BasicOptionUI):
  57. def build_entry_widget(self) -> QtWidgets.QWidget:
  58. return FloatEntry()
  59. class RadioSetOptionUI(BasicOptionUI):
  60. def __init__(self, option: str, label_text: str, choices: list, orientation='horizontal', **kwargs):
  61. self.choices = choices
  62. self.orientation = orientation
  63. super().__init__(option=option, label_text=label_text, **kwargs)
  64. def build_entry_widget(self) -> QtWidgets.QWidget:
  65. return RadioSet(choices=self.choices, orientation=self.orientation)
  66. class TextAreaOptionUI(OptionUI):
  67. def __init__(self, option: str, label_text: str, label_tooltip: str):
  68. super().__init__(option=option)
  69. self.label_text = label_text
  70. self.label_tooltip = label_tooltip
  71. self.label_widget = self.build_label_widget()
  72. self.textarea_widget = self.build_textarea_widget()
  73. def build_label_widget(self):
  74. label = QtWidgets.QLabel("%s:" % _(self.label_text))
  75. label.setToolTip(_(self.label_tooltip))
  76. return label
  77. def build_textarea_widget(self):
  78. textarea = FCTextArea()
  79. textarea.setPlaceholderText(_(self.label_tooltip))
  80. qsettings = QSettings("Open Source", "FlatCAM")
  81. if qsettings.contains("textbox_font_size"):
  82. tb_fsize = qsettings.value('textbox_font_size', type=int)
  83. else:
  84. tb_fsize = 10
  85. font = QtGui.QFont()
  86. font.setPointSize(tb_fsize)
  87. textarea.setFont(font)
  88. return textarea
  89. def get_field(self):
  90. return self.textarea_widget
  91. def add_to_grid(self, grid: QtWidgets.QGridLayout, row: int) -> int:
  92. grid.addWidget(self.label_widget, row, 0, 1, 3)
  93. grid.addWidget(self.textarea_widget, row+1, 0, 1, 3)
  94. return 2
  95. class CheckboxOptionUI(OptionUI):
  96. def __init__(self, option: str, label_text: str, label_tooltip: str):
  97. super().__init__(option=option)
  98. self.label_text = label_text
  99. self.label_tooltip = label_tooltip
  100. self.checkbox_widget = self.build_checkbox_widget()
  101. def build_checkbox_widget(self):
  102. checkbox = FCCheckBox('%s' % _(self.label_text))
  103. checkbox.setToolTip(_(self.label_tooltip))
  104. return checkbox
  105. def add_to_grid(self, grid: QtWidgets.QGridLayout, row: int) -> int:
  106. grid.addWidget(self.checkbox_widget, row, 0, 1, 3)
  107. return 1
  108. def get_field(self):
  109. return self.checkbox_widget
  110. class ComboboxOptionUI(BasicOptionUI):
  111. def __init__(self, option: str, label_text: str, choices: Sequence, **kwargs):
  112. self.choices = choices
  113. super().__init__(option=option, label_text=label_text, **kwargs)
  114. def build_entry_widget(self):
  115. combo = FCComboBox()
  116. for choice in self.choices:
  117. # don't translate the QCombo items as they are used in QSettings and identified by name
  118. combo.addItem(choice)
  119. return combo
  120. class ColorOptionUI(BasicOptionUI):
  121. def build_entry_widget(self) -> QtWidgets.QWidget:
  122. entry = FCColorEntry()
  123. return entry
  124. class SliderWithSpinnerOptionUI(BasicOptionUI):
  125. def __init__(self, option: str, label_text: str, min_value=0, max_value=100, step=1, **kwargs):
  126. self.min_value = min_value
  127. self.max_value = max_value
  128. self.step = step
  129. super().__init__(option=option, label_text=label_text, **kwargs)
  130. def build_entry_widget(self) -> QtWidgets.QWidget:
  131. entry = FCSliderWithSpinner(min=self.min_value, max=self.max_value, step=self.step)
  132. return entry
  133. class ColorAlphaSliderOptionUI(SliderWithSpinnerOptionUI):
  134. def __init__(self, applies_to: List[str], group, label_text: str, **kwargs):
  135. self.applies_to = applies_to
  136. self.group = group
  137. super().__init__(option="__color_alpha_slider", label_text=label_text, min_value=0, max_value=255, step=1,
  138. **kwargs)
  139. self.get_field().valueChanged.connect(self._on_alpha_change)
  140. def add_to_grid(self, grid: QtWidgets.QGridLayout, row: int) -> int:
  141. for index, field in enumerate(self._get_target_fields()):
  142. field.entry.textChanged.connect(lambda value, i=index: self._on_target_change(target_index=i))
  143. return super().add_to_grid(grid, row)
  144. def _get_target_fields(self):
  145. return list(map(lambda n: self.group.option_dict()[n].get_field(), self.applies_to))
  146. def _on_target_change(self, target_index: int):
  147. field = self._get_target_fields()[target_index]
  148. color = field.get_value()
  149. alpha_part = color[7:]
  150. if len(alpha_part) != 2:
  151. return
  152. alpha = int(alpha_part, 16)
  153. if alpha < 0 or alpha > 255 or self.get_field().get_value() == alpha:
  154. return
  155. self.get_field().set_value(alpha)
  156. def _on_alpha_change(self):
  157. alpha = self.get_field().get_value()
  158. for field in self._get_target_fields():
  159. old_value = field.get_value()
  160. new_value = self._modify_color_alpha(old_value, alpha=alpha)
  161. field.set_value(new_value)
  162. @staticmethod
  163. def _modify_color_alpha(color: str, alpha: int):
  164. color_without_alpha = color[:7]
  165. if alpha > 255:
  166. return color_without_alpha + "FF"
  167. elif alpha < 0:
  168. return color_without_alpha + "00"
  169. else:
  170. hexalpha = hex(alpha)[2:]
  171. if len(hexalpha) == 1:
  172. hexalpha = "0" + hexalpha
  173. return color_without_alpha + hexalpha
  174. class SpinnerOptionUI(BasicOptionUI):
  175. def __init__(self, option: str, label_text: str, min_value: int, max_value: int, step: int = 1, **kwargs):
  176. self.min_value = min_value
  177. self.max_value = max_value
  178. self.step = step
  179. super().__init__(option=option, label_text=label_text, **kwargs)
  180. def build_entry_widget(self) -> QtWidgets.QWidget:
  181. entry = FCSpinner()
  182. entry.set_range(self.min_value, self.max_value)
  183. entry.set_step(self.step)
  184. entry.setWrapping(True)
  185. return entry
  186. class DoubleSpinnerOptionUI(BasicOptionUI):
  187. def __init__(self, option: str, label_text: str, step: float, decimals: int, min_value=None, max_value=None,
  188. suffix=None, **kwargs):
  189. self.min_value = min_value
  190. self.max_value = max_value
  191. self.step = step
  192. self.suffix = suffix
  193. self.decimals = decimals
  194. super().__init__(option=option, label_text=label_text, **kwargs)
  195. def build_entry_widget(self) -> QtWidgets.QWidget:
  196. entry = FCDoubleSpinner(suffix=self.suffix)
  197. entry.set_precision(self.decimals)
  198. entry.setSingleStep(self.step)
  199. if self.min_value is None:
  200. self.min_value = entry.minimum()
  201. else:
  202. entry.setMinimum(self.min_value)
  203. if self.max_value is None:
  204. self.max_value = entry.maximum()
  205. else:
  206. entry.setMaximum(self.max_value)
  207. return entry
  208. class HeadingOptionUI(OptionUI):
  209. def __init__(self, label_text: str, label_tooltip: Union[str, None] = None):
  210. super().__init__(option="__heading")
  211. self.label_text = label_text
  212. self.label_tooltip = label_tooltip
  213. def build_heading_widget(self):
  214. heading = QtWidgets.QLabel('<b>%s</b>' % _(self.label_text))
  215. heading.setToolTip(_(self.label_tooltip))
  216. return heading
  217. def add_to_grid(self, grid: QtWidgets.QGridLayout, row: int) -> int:
  218. grid.addWidget(self.build_heading_widget(), row, 0, 1, 2)
  219. return 1
  220. def get_field(self):
  221. return None
  222. class SeparatorOptionUI(OptionUI):
  223. def __init__(self):
  224. super().__init__(option="__separator")
  225. @staticmethod
  226. def build_separator_widget():
  227. separator = QtWidgets.QFrame()
  228. separator.setFrameShape(QtWidgets.QFrame.HLine)
  229. separator.setFrameShadow(QtWidgets.QFrame.Sunken)
  230. return separator
  231. def add_to_grid(self, grid: QtWidgets.QGridLayout, row: int) -> int:
  232. grid.addWidget(self.build_separator_widget(), row, 0, 1, 2)
  233. return 1
  234. def get_field(self):
  235. return None
  236. class FullWidthButtonOptionUI(OptionUI):
  237. def __init__(self, option: str, label_text: str, label_tooltip: Union[str, None]):
  238. super().__init__(option=option)
  239. self.label_text = label_text
  240. self.label_tooltip = label_tooltip
  241. self.button_widget = self.build_button_widget()
  242. def build_button_widget(self):
  243. button = FCButton(_(self.label_text))
  244. if self.label_tooltip is not None:
  245. button.setToolTip(_(self.label_tooltip))
  246. return button
  247. def add_to_grid(self, grid: QtWidgets.QGridLayout, row: int) -> int:
  248. grid.addWidget(self.button_widget, row, 0, 1, 3)
  249. return 1
  250. def get_field(self):
  251. return self.button_widget