diff --git a/FlatCAMApp.py b/FlatCAMApp.py index 1936e044..b51f55d4 100644 --- a/FlatCAMApp.py +++ b/FlatCAMApp.py @@ -255,6 +255,15 @@ class App(QtCore.QObject): json.dump([], f) f.close() + try: + fp = open(self.data_path + '/recent_projects.json') + fp.close() + except IOError: + App.log.debug('Creating empty recent_projects.json') + fp = open(self.data_path + '/recent_projects.json', 'w') + json.dump([], fp) + fp.close() + # Application directory. CHDIR to it. Otherwise, trying to load # GUI icons will fail as their path is relative. # This will fail under cx_freeze ... @@ -294,6 +303,8 @@ class App(QtCore.QObject): # ### Data #### # ############# self.recent = [] + self.recent_projects = [] + self.clipboard = QtWidgets.QApplication.clipboard() self.proc_container = FCVisibleProcessContainer(self.ui.activity_view) @@ -1499,6 +1510,7 @@ class App(QtCore.QObject): self.ui.general_defaults_form.general_app_group.units_radio.activated_custom.connect( lambda: self.on_toggle_units(no_pref=False)) + # ############################## # ### GUI PREFERENCES SIGNALS ## # ############################## @@ -1960,6 +1972,9 @@ class App(QtCore.QObject): # Variable to store the GCODE that was edited self.gcode_edited = "" + # if Preferences are changed in the Edit -> Preferences tab the value will be set to True + self.preferences_changed_flag = False + self.grb_list = ['gbr', 'ger', 'gtl', 'gbl', 'gts', 'gbs', 'gtp', 'gbp', 'gto', 'gbo', 'gm1', 'gm2', 'gm3', 'gko', 'cmp', 'sol', 'stc', 'sts', 'plc', 'pls', 'crc', 'crs', 'tsm', 'bsm', 'ly2', 'ly15', 'dim', 'mil', 'grb', 'top', 'bot', 'smt', 'smb', 'sst', 'ssb', 'spt', 'spb', 'pho', 'gdo', @@ -2922,12 +2937,19 @@ class App(QtCore.QObject): record = {'kind': str(kind), 'filename': str(filename)} if record in self.recent: return - - self.recent.insert(0, record) + if record in self.recent_projects: + return + if record['kind'] == 'project': + self.recent_projects.insert(0, record) + else: + self.recent.insert(0, record) if len(self.recent) > self.defaults['global_recent_limit']: # Limit reached self.recent.pop() + if len(self.recent_projects) > self.defaults['global_recent_limit']: # Limit reached + self.recent_projects.pop() + try: f = open(self.data_path + '/recent.json', 'w') except IOError: @@ -2938,6 +2960,16 @@ class App(QtCore.QObject): json.dump(self.recent, f, default=to_dict, indent=2, sort_keys=True) f.close() + try: + fp = open(self.data_path + '/recent_projects.json', 'w') + except IOError: + App.log.error("Failed to open recent items file for writing.") + self.inform.emit(_('[ERROR_NOTCL] Failed to open recent projects file for writing.')) + return + + json.dump(self.recent_projects, fp, default=to_dict, indent=2, sort_keys=True) + fp.close() + # Re-build the recent items menu self.setup_recent_items() @@ -4541,6 +4573,7 @@ class App(QtCore.QObject): def on_save_button(self): log.debug("App.on_save_button() --> Saving preferences to file.") + self.preferences_changed_flag = False self.save_defaults(silent=False) # load the defaults so they are updated into the app @@ -5133,7 +5166,6 @@ class App(QtCore.QObject): self.draw_selection_shape(curr_sel_obj) def on_preferences(self): - # add the tab if it was closed self.ui.plot_tab_area.addTab(self.ui.preferences_tab, _("Preferences")) @@ -5145,6 +5177,115 @@ class App(QtCore.QObject): self.ui.plot_tab_area.setCurrentWidget(self.ui.preferences_tab) self.ui.show() + # this disconnect() is done so the slot will be connected only once + try: + self.ui.plot_tab_area.tab_closed_signal.disconnect(self.on_preferences_closed) + except TypeError: + pass + self.ui.plot_tab_area.tab_closed_signal.connect(self.on_preferences_closed) + + # detect changes in the preferences + for idx in range(self.ui.pref_tab_area.count()): + for tb in self.ui.pref_tab_area.widget(idx).findChildren(QtCore.QObject): + try: + try: + tb.textEdited.disconnect() + except TypeError: + pass + tb.textEdited.connect(self.on_preferences_edited) + except AttributeError: + pass + + try: + try: + tb.modificationChanged.disconnect() + except TypeError: + pass + tb.modificationChanged.connect(self.on_preferences_edited) + except AttributeError: + pass + + try: + try: + tb.toggled.disconnect() + except TypeError: + pass + tb.toggled.connect(self.on_preferences_edited) + except AttributeError: + pass + + try: + try: + tb.valueChanged.disconnect() + except TypeError: + pass + tb.valueChanged.connect(self.on_preferences_edited) + except AttributeError: + pass + + try: + try: + tb.currentIndexChanged.disconnect() + except TypeError: + pass + tb.currentIndexChanged.connect(self.on_preferences_edited) + except AttributeError: + pass + + def on_preferences_edited(self): + self.inform.emit(_("[WARNING_NOTCL] Preferences edited but not saved.")) + self.preferences_changed_flag = True + + def on_preferences_closed(self): + # disconnect + for idx in range(self.ui.pref_tab_area.count()): + for tb in self.ui.pref_tab_area.widget(idx).findChildren(QtCore.QObject): + try: + tb.textEdited.disconnect() + except (TypeError, AttributeError): + pass + + try: + tb.modificationChanged.disconnect() + except (TypeError, AttributeError): + pass + + try: + tb.toggled.disconnect() + except (TypeError, AttributeError): + pass + + try: + tb.valueChanged.disconnect() + except (TypeError, AttributeError): + pass + + try: + tb.currentIndexChanged.disconnect() + except (TypeError, AttributeError): + pass + + if self.preferences_changed_flag is True: + msgbox = QtWidgets.QMessageBox() + msgbox.setText(_("One or more values are changed.\n" + "Do you want to save the Preferences?")) + msgbox.setWindowTitle(_("Save Preferences")) + msgbox.setWindowIcon(QtGui.QIcon('share/save_as.png')) + + bt_yes = msgbox.addButton(_('Yes'), QtWidgets.QMessageBox.YesRole) + bt_no = msgbox.addButton(_('No'), QtWidgets.QMessageBox.NoRole) + + msgbox.setDefaultButton(bt_yes) + msgbox.exec_() + response = msgbox.clickedButton() + + if response == bt_yes: + self.on_save_button() + self.inform.emit(_("[success] Defaults saved.")) + else: + self.preferences_changed_flag = False + return + def on_flipy(self): self.report_usage("on_flipy()") @@ -8253,7 +8394,7 @@ class App(QtCore.QObject): 'pdf': lambda fname: self.worker_task.emit({'fcn': self.pdf_tool.open_pdf, 'params': [fname]}) } - # Open file + # Open recent file for files try: f = open(self.data_path + '/recent.json') except IOError: @@ -8270,6 +8411,23 @@ class App(QtCore.QObject): return f.close() + # Open recent file for projects + try: + fp = open(self.data_path + '/recent_projects.json') + except IOError: + App.log.error("Failed to load recent project item list.") + self.inform.emit(_("[ERROR_NOTCL] Failed to load recent projects item list.")) + return + + try: + self.recent_projects = json.load(fp) + except json.scanner.JSONDecodeError: + App.log.error("Failed to parse recent project item list.") + self.inform.emit(_("[ERROR_NOTCL] Failed to parse recent project item list.")) + fp.close() + return + fp.close() + # Closure needed to create callbacks in a loop. # Otherwise late binding occurs. def make_callback(func, fname): @@ -8277,7 +8435,7 @@ class App(QtCore.QObject): func(fname) return opener - def reset_recent(): + def reset_recent_files(): # Reset menu self.ui.recent.clear() self.recent = [] @@ -8289,18 +8447,25 @@ class App(QtCore.QObject): json.dump(self.recent, f) + def reset_recent_projects(): + # Reset menu + self.ui.recent_projects.clear() + self.recent_projects = [] + + try: + fp = open(self.data_path + '/recent_projects.json', 'w') + except IOError: + App.log.error("Failed to open recent projects items file for writing.") + return + + json.dump(self.recent, fp) + # Reset menu self.ui.recent.clear() + self.ui.recent_projects.clear() - # Create menu items - # First add tbe projects - # Title - action = QtWidgets.QAction("Recent Projects", self) - myFont = QtGui.QFont() - myFont.setBold(True) - action.setFont(myFont) - self.ui.recent.addAction(action) - for recent in self.recent: + # Create menu items for projects + for recent in self.recent_projects: filename = recent['filename'].split('/')[-1].split('\\')[-1] if recent['kind'] == 'project': @@ -8311,21 +8476,18 @@ class App(QtCore.QObject): o = make_callback(openers[recent["kind"]], recent['filename']) action.triggered.connect(o) - self.ui.recent.addAction(action) + self.ui.recent_projects.addAction(action) except KeyError: App.log.error("Unsupported file type: %s" % recent["kind"]) - # Second, add a separator in the menu - self.ui.recent.addSeparator() + # Last action in Recent Files menu is one that Clear the content + clear_action_proj = QtWidgets.QAction(QtGui.QIcon('share/trash32.png'), (_("Clear Recent files")), self) + clear_action_proj.triggered.connect(reset_recent_projects) + self.ui.recent_projects.addSeparator() + self.ui.recent_projects.addAction(clear_action_proj) - # Then add tbe rest of the files - # Title - action = QtWidgets.QAction("Recent Files", self) - myFont = QtGui.QFont() - myFont.setBold(True) - action.setFont(myFont) - self.ui.recent.addAction(action) + # Create menu items for files for recent in self.recent: filename = recent['filename'].split('/')[-1].split('\\')[-1] @@ -8344,7 +8506,7 @@ class App(QtCore.QObject): # Last action in Recent Files menu is one that Clear the content clear_action = QtWidgets.QAction(QtGui.QIcon('share/trash32.png'), (_("Clear Recent files")), self) - clear_action.triggered.connect(reset_recent) + clear_action.triggered.connect(reset_recent_files) self.ui.recent.addSeparator() self.ui.recent.addAction(clear_action) diff --git a/README.md b/README.md index bbb7c921..a4add998 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,8 @@ CAD program, and create G-Code for Isolation routing. - organized the list of recent files so the Project entries are to the top and separated from the other types of file - work on identification of changes in Preferences tab - added categories names for the recent files +- added a detection if any values are changed in the Edit -> Preferences window and on close it will ask the user if he wants to save the changes or not +- created a new menu entry in the File menu named Recent projects that will hold the recent projects and the previous "Recent files" will hold only the previous loaded files 30.07.2019 diff --git a/flatcamGUI/FlatCAMGUI.py b/flatcamGUI/FlatCAMGUI.py index 09ec9283..2d30a097 100644 --- a/flatcamGUI/FlatCAMGUI.py +++ b/flatcamGUI/FlatCAMGUI.py @@ -105,6 +105,7 @@ class FlatCAMGUI(QtWidgets.QMainWindow): self.menufile_open.addAction(self.menufileopenconfig) # Recent + self.recent_projects = self.menufile.addMenu(QtGui.QIcon('share/recent_files.png'), _("Recent projects")) self.recent = self.menufile.addMenu(QtGui.QIcon('share/recent_files.png'), _("Recent files")) # Separator @@ -756,10 +757,9 @@ class FlatCAMGUI(QtWidgets.QMainWindow): self.snap_max_dist_entry.setToolTip(_("Max. magnet distance")) self.snap_magnet = self.snap_toolbar.addWidget(self.snap_max_dist_entry) - - ############## ## + # ############# ## # ## Notebook # ## - ############## ## + # ############# ## # ## Project # ## # self.project_tab = QtWidgets.QWidget() @@ -827,9 +827,9 @@ class FlatCAMGUI(QtWidgets.QMainWindow): # remove the close button from the Plot Area tab (first tab index = 0) as this one will always be ON self.plot_tab_area.protectTab(0) - ###################################### ## + # ##################################### ## # ## HERE WE BUILD THE PREF. TAB AREA # ## - ###################################### ## + # ##################################### ## self.preferences_tab = QtWidgets.QWidget() self.pref_tab_layout = QtWidgets.QVBoxLayout(self.preferences_tab) self.pref_tab_layout.setContentsMargins(2, 2, 2, 2) @@ -842,6 +842,7 @@ class FlatCAMGUI(QtWidgets.QMainWindow): self.pref_tab_layout.addWidget(self.pref_tab_area) self.general_tab = QtWidgets.QWidget() + self.general_tab.setObjectName("general_tab") self.pref_tab_area.addTab(self.general_tab, _("General")) self.general_tab_lay = QtWidgets.QVBoxLayout() self.general_tab_lay.setContentsMargins(2, 2, 2, 2) @@ -863,6 +864,7 @@ class FlatCAMGUI(QtWidgets.QMainWindow): self.general_tab_lay.addWidget(self.general_scroll_area) self.gerber_tab = QtWidgets.QWidget() + self.gerber_tab.setObjectName("gerber_tab") self.pref_tab_area.addTab(self.gerber_tab, _("GERBER")) self.gerber_tab_lay = QtWidgets.QVBoxLayout() self.gerber_tab_lay.setContentsMargins(2, 2, 2, 2) @@ -872,6 +874,7 @@ class FlatCAMGUI(QtWidgets.QMainWindow): self.gerber_tab_lay.addWidget(self.gerber_scroll_area) self.excellon_tab = QtWidgets.QWidget() + self.excellon_tab.setObjectName("excellon_tab") self.pref_tab_area.addTab(self.excellon_tab, _("EXCELLON")) self.excellon_tab_lay = QtWidgets.QVBoxLayout() self.excellon_tab_lay.setContentsMargins(2, 2, 2, 2) @@ -881,6 +884,7 @@ class FlatCAMGUI(QtWidgets.QMainWindow): self.excellon_tab_lay.addWidget(self.excellon_scroll_area) self.geometry_tab = QtWidgets.QWidget() + self.geometry_tab.setObjectName("geometry_tab") self.pref_tab_area.addTab(self.geometry_tab, _("GEOMETRY")) self.geometry_tab_lay = QtWidgets.QVBoxLayout() self.geometry_tab_lay.setContentsMargins(2, 2, 2, 2) @@ -2483,7 +2487,7 @@ class FlatCAMGUI(QtWidgets.QMainWindow): if key == QtCore.Qt.Key_3 or key == '3': self.app.on_select_tab('tool') - if self.app.geo_editor.active_tool is not None and self.geo_select_btn.isChecked() == False: + if self.app.geo_editor.active_tool is not None and self.geo_select_btn.isChecked() is False: response = self.app.geo_editor.active_tool.on_key(key=key) if response is not None: self.app.inform.emit(response) diff --git a/flatcamGUI/GUIElements.py b/flatcamGUI/GUIElements.py index e24a9512..c99c4d11 100644 --- a/flatcamGUI/GUIElements.py +++ b/flatcamGUI/GUIElements.py @@ -1350,6 +1350,7 @@ class FCDetachableTab(QtWidgets.QTabWidget): class FCDetachableTab2(FCDetachableTab): + tab_closed_signal = pyqtSignal() def __init__(self, protect=None, protect_by_name=None, parent=None): super(FCDetachableTab2, self).__init__(protect=protect, protect_by_name=protect_by_name, parent=parent) @@ -1362,12 +1363,14 @@ class FCDetachableTab2(FCDetachableTab): :return: """ idx = self.currentIndex() + + # emit the signal only if the name is the one we want; the name should be a parameter somehow if self.tabText(idx) == _("Preferences"): - #TODO work on this, identify if widget changed and print a status message - pass + self.tab_closed_signal.emit() self.removeTab(currentIndex) + class VerticalScrollArea(QtWidgets.QScrollArea): """ This widget extends QtGui.QScrollArea to make a vertical-only