From ede90d6775a46e9100c5cfc5dddccf42d73075bf Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Sun, 31 May 2020 01:55:11 +0300 Subject: [PATCH] - structural changes in Preferences from David Robertson --- AppGUI/ColumnarFlowLayout.py | 181 ++++++++++ AppGUI/GUIElements.py | 94 +++++ AppGUI/preferences/OptionUI.py | 327 ++++++++++++++++++ AppGUI/preferences/OptionsGroupUI.py | 62 +++- AppGUI/preferences/PreferencesSectionUI.py | 41 +++ .../general/GeneralAppSettingsGroupUI.py | 302 ++++++++++++++++ AppObjects/FlatCAMCNCJob.py | 2 +- CHANGELOG.md | 4 + 8 files changed, 1010 insertions(+), 3 deletions(-) create mode 100644 AppGUI/ColumnarFlowLayout.py create mode 100644 AppGUI/preferences/OptionUI.py create mode 100644 AppGUI/preferences/PreferencesSectionUI.py create mode 100644 AppGUI/preferences/general/GeneralAppSettingsGroupUI.py diff --git a/AppGUI/ColumnarFlowLayout.py b/AppGUI/ColumnarFlowLayout.py new file mode 100644 index 00000000..3c92efd0 --- /dev/null +++ b/AppGUI/ColumnarFlowLayout.py @@ -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) diff --git a/AppGUI/GUIElements.py b/AppGUI/GUIElements.py index a7829719..5d6d4c07 100644 --- a/AppGUI/GUIElements.py +++ b/AppGUI/GUIElements.py @@ -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() diff --git a/AppGUI/preferences/OptionUI.py b/AppGUI/preferences/OptionUI.py new file mode 100644 index 00000000..d05691d5 --- /dev/null +++ b/AppGUI/preferences/OptionUI.py @@ -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 = "%s" % fmt + if self.label_color: + fmt = "%s" % (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('%s' % _(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 diff --git a/AppGUI/preferences/OptionsGroupUI.py b/AppGUI/preferences/OptionsGroupUI.py index 98780004..079b84cc 100644 --- a/AppGUI/preferences/OptionsGroupUI.py +++ b/AppGUI/preferences/OptionsGroupUI.py @@ -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) \ No newline at end of file + 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 diff --git a/AppGUI/preferences/PreferencesSectionUI.py b/AppGUI/preferences/PreferencesSectionUI.py new file mode 100644 index 00000000..6daae820 --- /dev/null +++ b/AppGUI/preferences/PreferencesSectionUI.py @@ -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 diff --git a/AppGUI/preferences/general/GeneralAppSettingsGroupUI.py b/AppGUI/preferences/general/GeneralAppSettingsGroupUI.py new file mode 100644 index 00000000..7a7467d0 --- /dev/null +++ b/AppGUI/preferences/general/GeneralAppSettingsGroupUI.py @@ -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" + "<>: 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"] diff --git a/AppObjects/FlatCAMCNCJob.py b/AppObjects/FlatCAMCNCJob.py index f0fe0765..cad98460 100644 --- a/AppObjects/FlatCAMCNCJob.py +++ b/AppObjects/FlatCAMCNCJob.py @@ -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): """ diff --git a/CHANGELOG.md b/CHANGELOG.md index 1dac8877..c30e4c7b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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