- structural changes in Preferences from David Robertson
This commit is contained in:
parent
808e1c5875
commit
ede90d6775
|
@ -0,0 +1,181 @@
|
|||
# ##########################################################
|
||||
# FlatCAM: 2D Post-processing for Manufacturing #
|
||||
# File by: David Robertson (c) #
|
||||
# Date: 5/2020 #
|
||||
# License: MIT Licence #
|
||||
# ##########################################################
|
||||
|
||||
import sys
|
||||
|
||||
from PyQt5.QtCore import QPoint, QRect, QSize, Qt
|
||||
from PyQt5.QtWidgets import QLayout, QSizePolicy
|
||||
import math
|
||||
|
||||
|
||||
class ColumnarFlowLayout(QLayout):
|
||||
def __init__(self, parent=None, margin=0, spacing=-1):
|
||||
super().__init__(parent)
|
||||
|
||||
if parent is not None:
|
||||
self.setContentsMargins(margin, margin, margin, margin)
|
||||
|
||||
self.setSpacing(spacing)
|
||||
self.itemList = []
|
||||
|
||||
def __del__(self):
|
||||
del_item = self.takeAt(0)
|
||||
while del_item:
|
||||
del_item = self.takeAt(0)
|
||||
|
||||
def addItem(self, item):
|
||||
self.itemList.append(item)
|
||||
|
||||
def count(self):
|
||||
return len(self.itemList)
|
||||
|
||||
def itemAt(self, index):
|
||||
if 0 <= index < len(self.itemList):
|
||||
return self.itemList[index]
|
||||
return None
|
||||
|
||||
def takeAt(self, index):
|
||||
if 0 <= index < len(self.itemList):
|
||||
return self.itemList.pop(index)
|
||||
return None
|
||||
|
||||
def expandingDirections(self):
|
||||
return Qt.Orientations(Qt.Orientation(0))
|
||||
|
||||
def hasHeightForWidth(self):
|
||||
return True
|
||||
|
||||
def heightForWidth(self, width):
|
||||
height = self.doLayout(QRect(0, 0, width, 0), True)
|
||||
return height
|
||||
|
||||
def setGeometry(self, rect):
|
||||
super().setGeometry(rect)
|
||||
self.doLayout(rect, False)
|
||||
|
||||
def sizeHint(self):
|
||||
return self.minimumSize()
|
||||
|
||||
def minimumSize(self):
|
||||
size = QSize()
|
||||
|
||||
for item in self.itemList:
|
||||
size = size.expandedTo(item.minimumSize())
|
||||
|
||||
margin, _, _, _ = self.getContentsMargins()
|
||||
|
||||
size += QSize(2 * margin, 2 * margin)
|
||||
return size
|
||||
|
||||
def doLayout(self, rect: QRect, testOnly: bool) -> int:
|
||||
spacing = self.spacing()
|
||||
x = rect.x()
|
||||
y = rect.y()
|
||||
|
||||
# Determine width of widest item
|
||||
widest = 0
|
||||
for item in self.itemList:
|
||||
widest = max(widest, item.sizeHint().width())
|
||||
|
||||
# Determine how many equal-width columns we can get, and how wide each one should be
|
||||
column_count = math.floor(rect.width() / (widest + spacing))
|
||||
column_count = min(column_count, len(self.itemList))
|
||||
column_count = max(1, column_count)
|
||||
column_width = math.floor((rect.width() - (column_count-1)*spacing - 1) / column_count)
|
||||
|
||||
# Get the heights for all of our items
|
||||
item_heights = {}
|
||||
for item in self.itemList:
|
||||
height = item.heightForWidth(column_width) if item.hasHeightForWidth() else item.sizeHint().height()
|
||||
item_heights[item] = height
|
||||
|
||||
# Prepare our column representation
|
||||
column_contents = []
|
||||
column_heights = []
|
||||
for column_index in range(column_count):
|
||||
column_contents.append([])
|
||||
column_heights.append(0)
|
||||
|
||||
def add_to_column(column: int, item):
|
||||
column_contents[column].append(item)
|
||||
column_heights[column] += (item_heights[item] + spacing)
|
||||
|
||||
def shove_one(from_column: int) -> bool:
|
||||
if len(column_contents[from_column]) >= 1:
|
||||
item = column_contents[from_column].pop(0)
|
||||
column_heights[from_column] -= (item_heights[item] + spacing)
|
||||
add_to_column(from_column-1, item)
|
||||
return True
|
||||
return False
|
||||
|
||||
def shove_cascade_consider(from_column: int) -> bool:
|
||||
changed_item = False
|
||||
|
||||
if len(column_contents[from_column]) > 1:
|
||||
item = column_contents[from_column][0]
|
||||
item_height = item_heights[item]
|
||||
if column_heights[from_column-1] + item_height < max(column_heights):
|
||||
changed_item = shove_one(from_column) or changed_item
|
||||
|
||||
if from_column+1 < column_count:
|
||||
changed_item = shove_cascade_consider(from_column+1) or changed_item
|
||||
|
||||
return changed_item
|
||||
|
||||
def shove_cascade() -> bool:
|
||||
if column_count < 2:
|
||||
return False
|
||||
changed_item = True
|
||||
while changed_item:
|
||||
changed_item = shove_cascade_consider(1)
|
||||
return changed_item
|
||||
|
||||
def pick_best_shoving_position() -> int:
|
||||
best_pos = 1
|
||||
best_height = sys.maxsize
|
||||
for column_idx in range(1, column_count):
|
||||
if len(column_contents[column_idx]) == 0:
|
||||
continue
|
||||
item = column_contents[column_idx][0]
|
||||
height_after_shove = column_heights[column_idx-1] + item_heights[item]
|
||||
if height_after_shove < best_height:
|
||||
best_height = height_after_shove
|
||||
best_pos = column_idx
|
||||
return best_pos
|
||||
|
||||
# Calculate the best layout
|
||||
column_index = 0
|
||||
for item in self.itemList:
|
||||
item_height = item_heights[item]
|
||||
if column_heights[column_index] != 0 and (column_heights[column_index] + item_height) > max(column_heights):
|
||||
column_index += 1
|
||||
if column_index >= column_count:
|
||||
# Run out of room, need to shove more stuff in each column
|
||||
if column_count >= 2:
|
||||
changed = shove_cascade()
|
||||
if not changed:
|
||||
shoving_pos = pick_best_shoving_position()
|
||||
shove_one(shoving_pos)
|
||||
shove_cascade()
|
||||
column_index = column_count-1
|
||||
|
||||
add_to_column(column_index, item)
|
||||
|
||||
shove_cascade()
|
||||
|
||||
# Set geometry according to the layout we have calculated
|
||||
if not testOnly:
|
||||
for column_index, items in enumerate(column_contents):
|
||||
x = column_index * (column_width + spacing)
|
||||
y = 0
|
||||
for item in items:
|
||||
height = item_heights[item]
|
||||
item.setGeometry(QRect(x, y, column_width, height))
|
||||
y += (height + spacing)
|
||||
|
||||
# Return the overall height
|
||||
return max(column_heights)
|
|
@ -683,6 +683,100 @@ class NumericalEvalTupleEntry(FCEntry):
|
|||
self.setValidator(validator)
|
||||
|
||||
|
||||
class FCColorEntry(QtWidgets.QFrame):
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self.entry = FCEntry()
|
||||
|
||||
self.button = QtWidgets.QPushButton()
|
||||
self.button.setFixedSize(15, 15)
|
||||
self.button.setStyleSheet("border-color: dimgray;")
|
||||
|
||||
self.layout = QtWidgets.QHBoxLayout()
|
||||
self.layout.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
|
||||
self.layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.layout.addWidget(self.entry)
|
||||
self.layout.addWidget(self.button)
|
||||
self.setLayout(self.layout)
|
||||
|
||||
self.entry.editingFinished.connect(self._sync_button_color)
|
||||
self.button.clicked.connect(self._on_button_clicked)
|
||||
|
||||
def get_value(self) -> str:
|
||||
return self.entry.get_value()
|
||||
|
||||
def set_value(self, value: str):
|
||||
self.entry.set_value(value)
|
||||
self._sync_button_color()
|
||||
|
||||
def _sync_button_color(self):
|
||||
value = self.get_value()
|
||||
self.button.setStyleSheet("background-color:%s;" % self._extract_color(value))
|
||||
|
||||
def _on_button_clicked(self):
|
||||
value = self.entry.get_value()
|
||||
current_color = QtGui.QColor(self._extract_color(value))
|
||||
|
||||
color_dialog = QtWidgets.QColorDialog()
|
||||
selected_color = color_dialog.getColor(initial=current_color, options=QtWidgets.QColorDialog.ShowAlphaChannel)
|
||||
|
||||
if selected_color.isValid() is False:
|
||||
return
|
||||
|
||||
new_value = str(selected_color.name()) + self._extract_alpha(value)
|
||||
self.set_value(new_value)
|
||||
|
||||
def _extract_color(self, value: str) -> str:
|
||||
return value[:7]
|
||||
|
||||
def _extract_alpha(self, value: str) -> str:
|
||||
return value[7:9]
|
||||
|
||||
|
||||
class FCSliderWithSpinner(QtWidgets.QFrame):
|
||||
|
||||
def __init__(self, min=0, max=100, step=1, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self.slider = QtWidgets.QSlider(QtCore.Qt.Horizontal)
|
||||
self.slider.setMinimum(min)
|
||||
self.slider.setMaximum(max)
|
||||
self.slider.setSingleStep(step)
|
||||
|
||||
self.spinner = FCSpinner()
|
||||
self.spinner.set_range(min, max)
|
||||
self.spinner.set_step(step)
|
||||
self.spinner.setMinimumWidth(70)
|
||||
|
||||
self.layout = QtWidgets.QHBoxLayout()
|
||||
self.layout.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
|
||||
self.layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.layout.addWidget(self.slider)
|
||||
self.layout.addWidget(self.spinner)
|
||||
self.setLayout(self.layout)
|
||||
|
||||
self.slider.valueChanged.connect(self._on_slider)
|
||||
self.spinner.valueChanged.connect(self._on_spinner)
|
||||
|
||||
self.valueChanged = self.spinner.valueChanged
|
||||
|
||||
def get_value(self) -> int:
|
||||
return self.spinner.get_value()
|
||||
|
||||
def set_value(self, value: int):
|
||||
self.spinner.set_value(value)
|
||||
|
||||
def _on_spinner(self):
|
||||
spinner_value = self.spinner.value()
|
||||
self.slider.setValue(spinner_value)
|
||||
|
||||
def _on_slider(self):
|
||||
slider_value = self.slider.value()
|
||||
self.spinner.set_value(slider_value)
|
||||
|
||||
|
||||
class FCSpinner(QtWidgets.QSpinBox):
|
||||
|
||||
returnPressed = QtCore.pyqtSignal()
|
||||
|
|
|
@ -0,0 +1,327 @@
|
|||
from typing import Union, Sequence, List
|
||||
|
||||
from PyQt5 import QtWidgets, QtGui
|
||||
from PyQt5.QtCore import QSettings
|
||||
|
||||
from AppGUI.GUIElements import RadioSet, FCCheckBox, FCButton, FCComboBox, FCEntry, FCSpinner, FCColorEntry, \
|
||||
FCSliderWithSpinner, FCDoubleSpinner, FloatEntry, FCTextArea
|
||||
|
||||
import gettext
|
||||
import AppTranslation as fcTranslate
|
||||
import builtins
|
||||
|
||||
fcTranslate.apply_language('strings')
|
||||
if '_' not in builtins.__dict__:
|
||||
_ = gettext.gettext
|
||||
|
||||
|
||||
class OptionUI:
|
||||
|
||||
def __init__(self, option: str):
|
||||
self.option = option
|
||||
|
||||
def add_to_grid(self, grid: QtWidgets.QGridLayout, row: int) -> int:
|
||||
"""
|
||||
Adds the necessary widget to the grid, starting at the supplied row.
|
||||
Returns the number of rows used (normally 1)
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def get_field(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
class BasicOptionUI(OptionUI):
|
||||
"""Abstract OptionUI that has a label on the left then some other widget on the right"""
|
||||
def __init__(self, option: str, label_text: str, label_tooltip: Union[str, None] = None,
|
||||
label_bold: bool = False, label_color: Union[str, None] = None):
|
||||
super().__init__(option=option)
|
||||
self.label_text = label_text
|
||||
self.label_tooltip = label_tooltip
|
||||
self.label_bold = label_bold
|
||||
self.label_color = label_color
|
||||
self.label_widget = self.build_label_widget()
|
||||
self.entry_widget = self.build_entry_widget()
|
||||
|
||||
def build_label_widget(self) -> QtWidgets.QLabel:
|
||||
fmt = "%s:"
|
||||
if self.label_bold:
|
||||
fmt = "<b>%s</b>" % fmt
|
||||
if self.label_color:
|
||||
fmt = "<span style=\"color:%s;\">%s</span>" % (self.label_color, fmt)
|
||||
label_widget = QtWidgets.QLabel(fmt % _(self.label_text))
|
||||
if self.label_tooltip is not None:
|
||||
label_widget.setToolTip(_(self.label_tooltip))
|
||||
return label_widget
|
||||
|
||||
def build_entry_widget(self) -> QtWidgets.QWidget:
|
||||
raise NotImplementedError()
|
||||
|
||||
def add_to_grid(self, grid: QtWidgets.QGridLayout, row: int) -> int:
|
||||
grid.addWidget(self.label_widget, row, 0)
|
||||
grid.addWidget(self.entry_widget, row, 1)
|
||||
return 1
|
||||
|
||||
def get_field(self):
|
||||
return self.entry_widget
|
||||
|
||||
|
||||
class LineEntryOptionUI(BasicOptionUI):
|
||||
def build_entry_widget(self) -> QtWidgets.QWidget:
|
||||
return FCEntry()
|
||||
|
||||
|
||||
# Not sure why this is needed over DoubleSpinnerOptionUI
|
||||
class FloatEntryOptionUI(BasicOptionUI):
|
||||
def build_entry_widget(self) -> QtWidgets.QWidget:
|
||||
return FloatEntry()
|
||||
|
||||
|
||||
class RadioSetOptionUI(BasicOptionUI):
|
||||
|
||||
def __init__(self, option: str, label_text: str, choices: list, orientation='horizontal', **kwargs):
|
||||
self.choices = choices
|
||||
self.orientation = orientation
|
||||
super().__init__(option=option, label_text=label_text, **kwargs)
|
||||
|
||||
def build_entry_widget(self) -> QtWidgets.QWidget:
|
||||
return RadioSet(choices=self.choices, orientation=self.orientation)
|
||||
|
||||
|
||||
class TextAreaOptionUI(OptionUI):
|
||||
|
||||
def __init__(self, option: str, label_text: str, label_tooltip: str):
|
||||
super().__init__(option=option)
|
||||
self.label_text = label_text
|
||||
self.label_tooltip = label_tooltip
|
||||
self.label_widget = self.build_label_widget()
|
||||
self.textarea_widget = self.build_textarea_widget()
|
||||
|
||||
def build_label_widget(self):
|
||||
label = QtWidgets.QLabel("%s:" % _(self.label_text))
|
||||
label.setToolTip(_(self.label_tooltip))
|
||||
return label
|
||||
|
||||
def build_textarea_widget(self):
|
||||
textarea = FCTextArea()
|
||||
textarea.setPlaceholderText(_(self.label_tooltip))
|
||||
|
||||
qsettings = QSettings("Open Source", "FlatCAM")
|
||||
if qsettings.contains("textbox_font_size"):
|
||||
tb_fsize = qsettings.value('textbox_font_size', type=int)
|
||||
else:
|
||||
tb_fsize = 10
|
||||
font = QtGui.QFont()
|
||||
font.setPointSize(tb_fsize)
|
||||
textarea.setFont(font)
|
||||
|
||||
return textarea
|
||||
|
||||
def get_field(self):
|
||||
return self.textarea_widget
|
||||
|
||||
def add_to_grid(self, grid: QtWidgets.QGridLayout, row: int) -> int:
|
||||
grid.addWidget(self.label_widget, row, 0, 1, 3)
|
||||
grid.addWidget(self.textarea_widget, row+1, 0, 1, 3)
|
||||
return 2
|
||||
|
||||
|
||||
class CheckboxOptionUI(OptionUI):
|
||||
|
||||
def __init__(self, option: str, label_text: str, label_tooltip: str):
|
||||
super().__init__(option=option)
|
||||
self.label_text = label_text
|
||||
self.label_tooltip = label_tooltip
|
||||
self.checkbox_widget = self.build_checkbox_widget()
|
||||
|
||||
def build_checkbox_widget(self):
|
||||
checkbox = FCCheckBox('%s' % _(self.label_text))
|
||||
checkbox.setToolTip(_(self.label_tooltip))
|
||||
return checkbox
|
||||
|
||||
def add_to_grid(self, grid: QtWidgets.QGridLayout, row: int) -> int:
|
||||
grid.addWidget(self.checkbox_widget, row, 0, 1, 3)
|
||||
return 1
|
||||
|
||||
def get_field(self):
|
||||
return self.checkbox_widget
|
||||
|
||||
|
||||
class ComboboxOptionUI(BasicOptionUI):
|
||||
|
||||
def __init__(self, option: str, label_text: str, choices: Sequence, **kwargs):
|
||||
self.choices = choices
|
||||
super().__init__(option=option, label_text=label_text, **kwargs)
|
||||
|
||||
def build_entry_widget(self):
|
||||
combo = FCComboBox()
|
||||
for choice in self.choices:
|
||||
# don't translate the QCombo items as they are used in QSettings and identified by name
|
||||
combo.addItem(choice)
|
||||
return combo
|
||||
|
||||
|
||||
class ColorOptionUI(BasicOptionUI):
|
||||
def build_entry_widget(self) -> QtWidgets.QWidget:
|
||||
entry = FCColorEntry()
|
||||
return entry
|
||||
|
||||
|
||||
class SliderWithSpinnerOptionUI(BasicOptionUI):
|
||||
def __init__(self, option: str, label_text: str, min_value=0, max_value=100, step=1, **kwargs):
|
||||
self.min_value = min_value
|
||||
self.max_value = max_value
|
||||
self.step = step
|
||||
super().__init__(option=option, label_text=label_text, **kwargs)
|
||||
|
||||
def build_entry_widget(self) -> QtWidgets.QWidget:
|
||||
entry = FCSliderWithSpinner(min=self.min_value, max=self.max_value, step=self.step)
|
||||
return entry
|
||||
|
||||
|
||||
class ColorAlphaSliderOptionUI(SliderWithSpinnerOptionUI):
|
||||
def __init__(self, applies_to: List[str], group, label_text: str, **kwargs):
|
||||
self.applies_to = applies_to
|
||||
self.group = group
|
||||
super().__init__(option="__color_alpha_slider", label_text=label_text, min_value=0, max_value=255, step=1,
|
||||
**kwargs)
|
||||
self.get_field().valueChanged.connect(self._on_alpha_change)
|
||||
|
||||
def add_to_grid(self, grid: QtWidgets.QGridLayout, row: int) -> int:
|
||||
for index, field in enumerate(self._get_target_fields()):
|
||||
field.entry.textChanged.connect(lambda value, i=index: self._on_target_change(target_index=i))
|
||||
return super().add_to_grid(grid, row)
|
||||
|
||||
def _get_target_fields(self):
|
||||
return list(map(lambda n: self.group.option_dict()[n].get_field(), self.applies_to))
|
||||
|
||||
def _on_target_change(self, target_index: int):
|
||||
field = self._get_target_fields()[target_index]
|
||||
color = field.get_value()
|
||||
alpha_part = color[7:]
|
||||
if len(alpha_part) != 2:
|
||||
return
|
||||
alpha = int(alpha_part, 16)
|
||||
if alpha < 0 or alpha > 255 or self.get_field().get_value() == alpha:
|
||||
return
|
||||
self.get_field().set_value(alpha)
|
||||
|
||||
def _on_alpha_change(self):
|
||||
alpha = self.get_field().get_value()
|
||||
for field in self._get_target_fields():
|
||||
old_value = field.get_value()
|
||||
new_value = self._modify_color_alpha(old_value, alpha=alpha)
|
||||
field.set_value(new_value)
|
||||
|
||||
@staticmethod
|
||||
def _modify_color_alpha(color: str, alpha: int):
|
||||
color_without_alpha = color[:7]
|
||||
if alpha > 255:
|
||||
return color_without_alpha + "FF"
|
||||
elif alpha < 0:
|
||||
return color_without_alpha + "00"
|
||||
else:
|
||||
hexalpha = hex(alpha)[2:]
|
||||
if len(hexalpha) == 1:
|
||||
hexalpha = "0" + hexalpha
|
||||
return color_without_alpha + hexalpha
|
||||
|
||||
|
||||
class SpinnerOptionUI(BasicOptionUI):
|
||||
def __init__(self, option: str, label_text: str, min_value: int, max_value: int, step: int = 1, **kwargs):
|
||||
self.min_value = min_value
|
||||
self.max_value = max_value
|
||||
self.step = step
|
||||
super().__init__(option=option, label_text=label_text, **kwargs)
|
||||
|
||||
def build_entry_widget(self) -> QtWidgets.QWidget:
|
||||
entry = FCSpinner()
|
||||
entry.set_range(self.min_value, self.max_value)
|
||||
entry.set_step(self.step)
|
||||
entry.setWrapping(True)
|
||||
return entry
|
||||
|
||||
|
||||
class DoubleSpinnerOptionUI(BasicOptionUI):
|
||||
def __init__(self, option: str, label_text: str, step: float, decimals: int, min_value=None, max_value=None,
|
||||
suffix=None, **kwargs):
|
||||
self.min_value = min_value
|
||||
self.max_value = max_value
|
||||
self.step = step
|
||||
self.suffix = suffix
|
||||
self.decimals = decimals
|
||||
super().__init__(option=option, label_text=label_text, **kwargs)
|
||||
|
||||
def build_entry_widget(self) -> QtWidgets.QWidget:
|
||||
entry = FCDoubleSpinner(suffix=self.suffix)
|
||||
entry.set_precision(self.decimals)
|
||||
entry.setSingleStep(self.step)
|
||||
if self.min_value is None:
|
||||
self.min_value = entry.minimum()
|
||||
else:
|
||||
entry.setMinimum(self.min_value)
|
||||
if self.max_value is None:
|
||||
self.max_value = entry.maximum()
|
||||
else:
|
||||
entry.setMaximum(self.max_value)
|
||||
return entry
|
||||
|
||||
|
||||
class HeadingOptionUI(OptionUI):
|
||||
def __init__(self, label_text: str, label_tooltip: Union[str, None] = None):
|
||||
super().__init__(option="__heading")
|
||||
self.label_text = label_text
|
||||
self.label_tooltip = label_tooltip
|
||||
|
||||
def build_heading_widget(self):
|
||||
heading = QtWidgets.QLabel('<b>%s</b>' % _(self.label_text))
|
||||
heading.setToolTip(_(self.label_tooltip))
|
||||
return heading
|
||||
|
||||
def add_to_grid(self, grid: QtWidgets.QGridLayout, row: int) -> int:
|
||||
grid.addWidget(self.build_heading_widget(), row, 0, 1, 2)
|
||||
return 1
|
||||
|
||||
def get_field(self):
|
||||
return None
|
||||
|
||||
|
||||
class SeparatorOptionUI(OptionUI):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(option="__separator")
|
||||
|
||||
@staticmethod
|
||||
def build_separator_widget():
|
||||
separator = QtWidgets.QFrame()
|
||||
separator.setFrameShape(QtWidgets.QFrame.HLine)
|
||||
separator.setFrameShadow(QtWidgets.QFrame.Sunken)
|
||||
return separator
|
||||
|
||||
def add_to_grid(self, grid: QtWidgets.QGridLayout, row: int) -> int:
|
||||
grid.addWidget(self.build_separator_widget(), row, 0, 1, 2)
|
||||
return 1
|
||||
|
||||
def get_field(self):
|
||||
return None
|
||||
|
||||
|
||||
class FullWidthButtonOptionUI(OptionUI):
|
||||
def __init__(self, option: str, label_text: str, label_tooltip: Union[str, None]):
|
||||
super().__init__(option=option)
|
||||
self.label_text = label_text
|
||||
self.label_tooltip = label_tooltip
|
||||
self.button_widget = self.build_button_widget()
|
||||
|
||||
def build_button_widget(self):
|
||||
button = FCButton(_(self.label_text))
|
||||
if self.label_tooltip is not None:
|
||||
button.setToolTip(_(self.label_tooltip))
|
||||
return button
|
||||
|
||||
def add_to_grid(self, grid: QtWidgets.QGridLayout, row: int) -> int:
|
||||
grid.addWidget(self.button_widget, row, 0, 1, 3)
|
||||
return 1
|
||||
|
||||
def get_field(self):
|
||||
return self.button_widget
|
|
@ -1,4 +1,30 @@
|
|||
# ##########################################################
|
||||
# FlatCAM: 2D Post-processing for Manufacturing #
|
||||
# File by: David Robertson (c) #
|
||||
# Date: 5/2020 #
|
||||
# License: MIT Licence #
|
||||
# ##########################################################
|
||||
|
||||
from typing import Dict
|
||||
|
||||
from PyQt5 import QtWidgets
|
||||
from PyQt5.QtCore import QSettings
|
||||
|
||||
import gettext
|
||||
import AppTranslation as fcTranslate
|
||||
import builtins
|
||||
|
||||
from AppGUI.preferences.OptionUI import OptionUI
|
||||
|
||||
fcTranslate.apply_language('strings')
|
||||
if '_' not in builtins.__dict__:
|
||||
_ = gettext.gettext
|
||||
|
||||
settings = QSettings("Open Source", "FlatCAM")
|
||||
if settings.contains("machinist"):
|
||||
machinist_setting = settings.value('machinist', type=int)
|
||||
else:
|
||||
machinist_setting = 0
|
||||
|
||||
|
||||
class OptionsGroupUI(QtWidgets.QGroupBox):
|
||||
|
@ -6,7 +32,7 @@ class OptionsGroupUI(QtWidgets.QGroupBox):
|
|||
|
||||
def __init__(self, title, parent=None):
|
||||
# QtGui.QGroupBox.__init__(self, title, parent=parent)
|
||||
super(OptionsGroupUI, self).__init__()
|
||||
super(OptionsGroupUI, self).__init__(title=title, parent=parent)
|
||||
self.setStyleSheet("""
|
||||
QGroupBox
|
||||
{
|
||||
|
@ -16,4 +42,36 @@ class OptionsGroupUI(QtWidgets.QGroupBox):
|
|||
""")
|
||||
|
||||
self.layout = QtWidgets.QVBoxLayout()
|
||||
self.setLayout(self.layout)
|
||||
self.setLayout(self.layout)
|
||||
|
||||
def option_dict(self) -> Dict[str, OptionUI]:
|
||||
# FIXME!
|
||||
return {}
|
||||
|
||||
|
||||
class OptionsGroupUI2(OptionsGroupUI):
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self.grid = QtWidgets.QGridLayout()
|
||||
self.layout.addLayout(self.grid)
|
||||
self.grid.setColumnStretch(0, 0)
|
||||
self.grid.setColumnStretch(1, 1)
|
||||
|
||||
self.options = self.build_options()
|
||||
|
||||
row = 0
|
||||
for option in self.options:
|
||||
row += option.add_to_grid(grid=self.grid, row=row)
|
||||
|
||||
self.layout.addStretch()
|
||||
|
||||
def build_options(self) -> [OptionUI]:
|
||||
return []
|
||||
|
||||
def option_dict(self) -> Dict[str, OptionUI]:
|
||||
result = {}
|
||||
for optionui in self.options:
|
||||
result[optionui.option] = optionui
|
||||
return result
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
from typing import Dict
|
||||
from PyQt5 import QtWidgets, QtCore
|
||||
|
||||
from AppGUI.ColumnarFlowLayout import ColumnarFlowLayout
|
||||
from AppGUI.preferences.OptionUI import OptionUI
|
||||
from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
|
||||
|
||||
|
||||
class PreferencesSectionUI(QtWidgets.QWidget):
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.layout = ColumnarFlowLayout() # QtWidgets.QHBoxLayout()
|
||||
self.setLayout(self.layout)
|
||||
|
||||
self.groups = self.build_groups()
|
||||
for group in self.groups:
|
||||
group.setMinimumWidth(250)
|
||||
self.layout.addWidget(group)
|
||||
|
||||
def build_groups(self) -> [OptionsGroupUI]:
|
||||
return []
|
||||
|
||||
def option_dict(self) -> Dict[str, OptionUI]:
|
||||
result = {}
|
||||
for group in self.groups:
|
||||
groupoptions = group.option_dict()
|
||||
result.update(groupoptions)
|
||||
return result
|
||||
|
||||
def build_tab(self):
|
||||
scroll_area = QtWidgets.QScrollArea()
|
||||
scroll_area.setWidget(self)
|
||||
scroll_area.setWidgetResizable(True)
|
||||
return scroll_area
|
||||
|
||||
def get_tab_id(self) -> str:
|
||||
raise NotImplementedError
|
||||
|
||||
def get_tab_label(self) -> str:
|
||||
raise NotImplementedError
|
|
@ -0,0 +1,302 @@
|
|||
|
||||
from PyQt5 import QtCore
|
||||
from PyQt5.QtCore import QSettings
|
||||
from AppGUI.GUIElements import OptionalInputSection
|
||||
from AppGUI.preferences import settings
|
||||
from AppGUI.preferences.OptionUI import *
|
||||
from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI2
|
||||
|
||||
import gettext
|
||||
import AppTranslation as fcTranslate
|
||||
import builtins
|
||||
fcTranslate.apply_language('strings')
|
||||
if '_' not in builtins.__dict__:
|
||||
_ = gettext.gettext
|
||||
|
||||
|
||||
class GeneralAppSettingsGroupUI(OptionsGroupUI2):
|
||||
def __init__(self, decimals=4, **kwargs):
|
||||
self.decimals = decimals
|
||||
self.pagesize = {}
|
||||
self.pagesize.update(
|
||||
{
|
||||
'A0': (841, 1189),
|
||||
'A1': (594, 841),
|
||||
'A2': (420, 594),
|
||||
'A3': (297, 420),
|
||||
'A4': (210, 297),
|
||||
'A5': (148, 210),
|
||||
'A6': (105, 148),
|
||||
'A7': (74, 105),
|
||||
'A8': (52, 74),
|
||||
'A9': (37, 52),
|
||||
'A10': (26, 37),
|
||||
|
||||
'B0': (1000, 1414),
|
||||
'B1': (707, 1000),
|
||||
'B2': (500, 707),
|
||||
'B3': (353, 500),
|
||||
'B4': (250, 353),
|
||||
'B5': (176, 250),
|
||||
'B6': (125, 176),
|
||||
'B7': (88, 125),
|
||||
'B8': (62, 88),
|
||||
'B9': (44, 62),
|
||||
'B10': (31, 44),
|
||||
|
||||
'C0': (917, 1297),
|
||||
'C1': (648, 917),
|
||||
'C2': (458, 648),
|
||||
'C3': (324, 458),
|
||||
'C4': (229, 324),
|
||||
'C5': (162, 229),
|
||||
'C6': (114, 162),
|
||||
'C7': (81, 114),
|
||||
'C8': (57, 81),
|
||||
'C9': (40, 57),
|
||||
'C10': (28, 40),
|
||||
|
||||
# American paper sizes
|
||||
'LETTER': (8.5, 11),
|
||||
'LEGAL': (8.5, 14),
|
||||
'ELEVENSEVENTEEN': (11, 17),
|
||||
|
||||
# From https://en.wikipedia.org/wiki/Paper_size
|
||||
'JUNIOR_LEGAL': (5, 8),
|
||||
'HALF_LETTER': (5.5, 8),
|
||||
'GOV_LETTER': (8, 10.5),
|
||||
'GOV_LEGAL': (8.5, 13),
|
||||
'LEDGER': (17, 11),
|
||||
}
|
||||
)
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self.setTitle(str(_("App Settings")))
|
||||
|
||||
qsettings = QSettings("Open Source", "FlatCAM")
|
||||
|
||||
self.notebook_font_size_field = self.option_dict()["notebook_font_size"].get_field()
|
||||
if qsettings.contains("notebook_font_size"):
|
||||
self.notebook_font_size_field.set_value(qsettings.value('notebook_font_size', type=int))
|
||||
else:
|
||||
self.notebook_font_size_field.set_value(12)
|
||||
|
||||
self.axis_font_size_field = self.option_dict()["axis_font_size"].get_field()
|
||||
if qsettings.contains("axis_font_size"):
|
||||
self.axis_font_size_field.set_value(qsettings.value('axis_font_size', type=int))
|
||||
else:
|
||||
self.axis_font_size_field.set_value(8)
|
||||
|
||||
self.textbox_font_size_field = self.option_dict()["textbox_font_size"].get_field()
|
||||
if qsettings.contains("textbox_font_size"):
|
||||
self.textbox_font_size_field.set_value(settings.value('textbox_font_size', type=int))
|
||||
else:
|
||||
self.textbox_font_size_field.set_value(10)
|
||||
|
||||
self.workspace_enabled_field = self.option_dict()["global_workspace"].get_field()
|
||||
self.workspace_type_field = self.option_dict()["global_workspaceT"].get_field()
|
||||
self.workspace_type_label = self.option_dict()["global_workspaceT"].label_widget
|
||||
self.workspace_orientation_field = self.option_dict()["global_workspace_orientation"].get_field()
|
||||
self.workspace_orientation_label = self.option_dict()["global_workspace_orientation"].label_widget
|
||||
self.wks = OptionalInputSection(self.workspace_enabled_field, [self.workspace_type_label, self.workspace_type_field, self.workspace_orientation_label, self.workspace_orientation_field])
|
||||
|
||||
self.mouse_cursor_color_enabled_field = self.option_dict()["global_cursor_color_enabled"].get_field()
|
||||
self.mouse_cursor_color_field = self.option_dict()["global_cursor_color"].get_field()
|
||||
self.mouse_cursor_color_label = self.option_dict()["global_cursor_color"].label_widget
|
||||
self.mois = OptionalInputSection(self.mouse_cursor_color_enabled_field, [self.mouse_cursor_color_label, self.mouse_cursor_color_field])
|
||||
self.mouse_cursor_color_enabled_field.stateChanged.connect(self.on_mouse_cursor_color_enable)
|
||||
self.mouse_cursor_color_field.entry.editingFinished.connect(self.on_mouse_cursor_entry)
|
||||
|
||||
def build_options(self) -> [OptionUI]:
|
||||
return [
|
||||
HeadingOptionUI(label_text="Grid Settings", label_tooltip=None),
|
||||
DoubleSpinnerOptionUI(
|
||||
option="global_gridx",
|
||||
label_text="X value",
|
||||
label_tooltip="This is the Grid snap value on X axis.",
|
||||
step=0.1,
|
||||
decimals=self.decimals
|
||||
),
|
||||
DoubleSpinnerOptionUI(
|
||||
option="global_gridy",
|
||||
label_text='Y value',
|
||||
label_tooltip="This is the Grid snap value on Y axis.",
|
||||
step=0.1,
|
||||
decimals=self.decimals
|
||||
),
|
||||
DoubleSpinnerOptionUI(
|
||||
option="global_snap_max",
|
||||
label_text="Snap Max",
|
||||
label_tooltip="Max. magnet distance",
|
||||
step=0.1,
|
||||
decimals=self.decimals
|
||||
),
|
||||
SeparatorOptionUI(),
|
||||
|
||||
HeadingOptionUI(label_text="Workspace Settings", label_tooltip=None),
|
||||
CheckboxOptionUI(
|
||||
option="global_workspace",
|
||||
label_text="Active",
|
||||
label_tooltip="Draw a delimiting rectangle on canvas.\n"
|
||||
"The purpose is to illustrate the limits for our work."
|
||||
),
|
||||
ComboboxOptionUI(
|
||||
option="global_workspaceT",
|
||||
label_text="Size",
|
||||
label_tooltip="Select the type of rectangle to be used on canvas,\nas valid workspace.",
|
||||
choices=list(self.pagesize.keys())
|
||||
),
|
||||
RadioSetOptionUI(
|
||||
option="global_workspace_orientation",
|
||||
label_text="Orientation",
|
||||
label_tooltip="Can be:\n- Portrait\n- Landscape",
|
||||
choices=[
|
||||
{'label': _('Portrait'), 'value': 'p'},
|
||||
{'label': _('Landscape'), 'value': 'l'},
|
||||
]
|
||||
),
|
||||
# FIXME enabling OptionalInputSection ??
|
||||
SeparatorOptionUI(),
|
||||
|
||||
HeadingOptionUI(label_text="Font Size", label_tooltip=None),
|
||||
SpinnerOptionUI(
|
||||
option="notebook_font_size",
|
||||
label_text="Notebook",
|
||||
label_tooltip="This sets the font size for the elements found in the Notebook.\n"
|
||||
"The notebook is the collapsible area in the left side of the GUI,\n"
|
||||
"and include the Project, Selected and Tool tabs.",
|
||||
min_value=8, max_value=40, step=1
|
||||
),
|
||||
SpinnerOptionUI(
|
||||
option="axis_font_size",
|
||||
label_text="Axis",
|
||||
label_tooltip="This sets the font size for canvas axis.",
|
||||
min_value=8, max_value=40, step=1
|
||||
),
|
||||
SpinnerOptionUI(
|
||||
option="textbox_font_size",
|
||||
label_text="Textbox",
|
||||
label_tooltip="This sets the font size for the Textbox GUI\n"
|
||||
"elements that are used in FlatCAM.",
|
||||
min_value=8, max_value=40, step=1
|
||||
),
|
||||
SeparatorOptionUI(),
|
||||
|
||||
HeadingOptionUI(label_text="Mouse Settings", label_tooltip=None),
|
||||
RadioSetOptionUI(
|
||||
option="global_cursor_type",
|
||||
label_text="Cursor Shape",
|
||||
label_tooltip="Choose a mouse cursor shape.\n"
|
||||
"- Small -> with a customizable size.\n"
|
||||
"- Big -> Infinite lines",
|
||||
choices=[
|
||||
{"label": _("Small"), "value": "small"},
|
||||
{"label": _("Big"), "value": "big"}
|
||||
]
|
||||
),
|
||||
SpinnerOptionUI(
|
||||
option="global_cursor_size",
|
||||
label_text="Cursor Size",
|
||||
label_tooltip="Set the size of the mouse cursor, in pixels.",
|
||||
min_value=10, max_value=70, step=1
|
||||
),
|
||||
SpinnerOptionUI(
|
||||
option="global_cursor_width",
|
||||
label_text="Cursor Width",
|
||||
label_tooltip="Set the line width of the mouse cursor, in pixels.",
|
||||
min_value=1, max_value=10, step=1
|
||||
),
|
||||
CheckboxOptionUI(
|
||||
option="global_cursor_color_enabled",
|
||||
label_text="Cursor Color",
|
||||
label_tooltip="Check this box to color mouse cursor."
|
||||
),
|
||||
ColorOptionUI(
|
||||
option="global_cursor_color",
|
||||
label_text="Cursor Color",
|
||||
label_tooltip="Set the color of the mouse cursor."
|
||||
),
|
||||
# FIXME enabling of cursor color
|
||||
RadioSetOptionUI(
|
||||
option="global_pan_button",
|
||||
label_text="Pan Button",
|
||||
label_tooltip="Select the mouse button to use for panning:\n"
|
||||
"- MMB --> Middle Mouse Button\n"
|
||||
"- RMB --> Right Mouse Button",
|
||||
choices=[{'label': _('MMB'), 'value': '3'},
|
||||
{'label': _('RMB'), 'value': '2'}]
|
||||
),
|
||||
RadioSetOptionUI(
|
||||
option="global_mselect_key",
|
||||
label_text="Multiple Selection",
|
||||
label_tooltip="Select the key used for multiple selection.",
|
||||
choices=[{'label': _('CTRL'), 'value': 'Control'},
|
||||
{'label': _('SHIFT'), 'value': 'Shift'}]
|
||||
),
|
||||
SeparatorOptionUI(),
|
||||
|
||||
CheckboxOptionUI(
|
||||
option="global_delete_confirmation",
|
||||
label_text="Delete object confirmation",
|
||||
label_tooltip="When checked the application will ask for user confirmation\n"
|
||||
"whenever the Delete object(s) event is triggered, either by\n"
|
||||
"menu shortcut or key shortcut."
|
||||
),
|
||||
CheckboxOptionUI(
|
||||
option="global_open_style",
|
||||
label_text='"Open" behavior',
|
||||
label_tooltip="When checked the path for the last saved file is used when saving files,\n"
|
||||
"and the path for the last opened file is used when opening files.\n\n"
|
||||
"When unchecked the path for opening files is the one used last: either the\n"
|
||||
"path for saving files or the path for opening files."
|
||||
),
|
||||
CheckboxOptionUI(
|
||||
option="global_toggle_tooltips",
|
||||
label_text="Enable ToolTips",
|
||||
label_tooltip="Check this box if you want to have toolTips displayed\n"
|
||||
"when hovering with mouse over items throughout the App."
|
||||
),
|
||||
CheckboxOptionUI(
|
||||
option="global_machinist_setting",
|
||||
label_text="Allow Machinist Unsafe Settings",
|
||||
label_tooltip="If checked, some of the application settings will be allowed\n"
|
||||
"to have values that are usually unsafe to use.\n"
|
||||
"Like Z travel negative values or Z Cut positive values.\n"
|
||||
"It will applied at the next application start.\n"
|
||||
"<<WARNING>>: Don't change this unless you know what you are doing !!!"
|
||||
),
|
||||
SpinnerOptionUI(
|
||||
option="global_bookmarks_limit",
|
||||
label_text="Bookmarks limit",
|
||||
label_tooltip="The maximum number of bookmarks that may be installed in the menu.\n"
|
||||
"The number of bookmarks in the bookmark manager may be greater\n"
|
||||
"but the menu will hold only so much.",
|
||||
min_value=0, max_value=9999, step=1
|
||||
),
|
||||
ComboboxOptionUI(
|
||||
option="global_activity_icon",
|
||||
label_text="Activity Icon",
|
||||
label_tooltip="Select the GIF that show activity when FlatCAM is active.",
|
||||
choices=['Ball black', 'Ball green', 'Arrow green', 'Eclipse green']
|
||||
)
|
||||
|
||||
]
|
||||
|
||||
def on_mouse_cursor_color_enable(self, val):
|
||||
if val:
|
||||
self.app.cursor_color_3D = self.app.defaults["global_cursor_color"]
|
||||
else:
|
||||
theme_settings = QtCore.QSettings("Open Source", "FlatCAM")
|
||||
if theme_settings.contains("theme"):
|
||||
theme = theme_settings.value('theme', type=str)
|
||||
else:
|
||||
theme = 'white'
|
||||
|
||||
if theme == 'white':
|
||||
self.app.cursor_color_3D = 'black'
|
||||
else:
|
||||
self.app.cursor_color_3D = 'gray'
|
||||
|
||||
def on_mouse_cursor_entry(self):
|
||||
self.app.defaults['global_cursor_color'] = self.mouse_cursor_color_field.get_value()
|
||||
self.app.cursor_color_3D = self.app.defaults["global_cursor_color"]
|
|
@ -535,7 +535,7 @@ class CNCJobObject(FlatCAMObj, CNCjob):
|
|||
if self.app.defaults["global_open_style"] is False:
|
||||
self.app.file_opened.emit("gcode", filename)
|
||||
self.app.file_saved.emit("gcode", filename)
|
||||
self.app.inform.emit('[success] %s: %s' % (_("Machine Code file saved to"), filename))
|
||||
self.app.inform.emit('[success] %s: %s' % (_("File saved to"), filename))
|
||||
|
||||
def on_edit_code_click(self, *args):
|
||||
"""
|
||||
|
|
|
@ -7,6 +7,10 @@ CHANGELOG for FlatCAM beta
|
|||
|
||||
=================================================
|
||||
|
||||
31.05.2020
|
||||
|
||||
- structural changes in Preferences from David Robertson
|
||||
|
||||
30.05.2020
|
||||
|
||||
- made confirmation messages for the values that are modified not to be printed in the Shell
|
||||
|
|
Loading…
Reference in New Issue