diff --git a/src/robotide/preferences/saving.py b/src/robotide/preferences/saving.py index 58cc8a9a4..25c38361d 100644 --- a/src/robotide/preferences/saving.py +++ b/src/robotide/preferences/saving.py @@ -68,6 +68,10 @@ def _create_editors(settings): IntegerChoiceEditor(settings, 'txt number of spaces', _('Separating spaces'), [str(i) for i in range(2, 11)], _('Number of spaces between cells when saving in txt format') + ), + IntegerChoiceEditor(settings, 'auto save interval', _('Auto save interval (minutes)'), + ['0', '1', '2', '3', '5', '10', '15', '20', '30'], + _('Automatically save all files after specified minutes (0 = disabled)') ) ] diff --git a/src/robotide/preferences/settings.cfg b/src/robotide/preferences/settings.cfg index 3bb97fa6d..0b5cbeb12 100644 --- a/src/robotide/preferences/settings.cfg +++ b/src/robotide/preferences/settings.cfg @@ -28,6 +28,8 @@ reformat = False doc language = None # the style of the tabs in notebook pages, Edit, Text, Run. Values from 0 to 5. notebook theme = 0 +# auto save interval: Automatically save all files after specified minutes (0 = disabled) +auto save interval = 0 [General] font size = 11 @@ -71,8 +73,6 @@ tc_kw_name = '#1A5FB4' variable = '#008080' background = '#F6F5F4' enable auto suggestions = True -enable visible spaces = True -enable visible newlines = True [Grid] font size = 10 diff --git a/src/robotide/ui/mainframe.py b/src/robotide/ui/mainframe.py index fca0d2070..c348668e9 100644 --- a/src/robotide/ui/mainframe.py +++ b/src/robotide/ui/mainframe.py @@ -167,7 +167,9 @@ def __init__(self, application, controller): self.color_foreground = self.general_settings.get('foreground', '#5E5C64') if self.general_settings else '#5E5C64' self.font_face = self.general_settings.get('font face', '') if self.general_settings else '' self.font_size = self.general_settings.get('font size', 11) if self.general_settings else 11 - self.ui_language = self.general_settings.get('ui language', 'English') if self.general_settings else 'English' + self.ui_language = self.general_settings.get('ui language', 'English') if self.general_settings else 'English' + self._auto_save_interval = application.settings.get('auto save interval', 0) + self._auto_save_timer = None self.main_menu = None self._init_ui() self.SetIcon(wx.Icon(self._image_provider.RIDE_ICON)) @@ -187,6 +189,7 @@ def __init__(self, application, controller): self.Bind(aui.EVT_AUI_PANE_DOCKING, self.OnFloatDock) self.Bind(aui.EVT_AUI_PANE_DOCKED, self.OnFloatDock) self._subscribe_messages() + self._start_auto_save_timer() wx.CallAfter(self.actions.register_tools) # DEBUG # DEBUG wx.CallAfter(self.OnSettingsChanged, self.general_settings) @@ -197,7 +200,8 @@ def _subscribe_messages(self): (self._set_label, RideTreeSelection), (self._show_validation_error, RideInputValidationError), (self._show_modification_prevented_error, RideModificationPrevented), - (self.on_ui_language_changed, RideSettingsChanged) + (self.on_ui_language_changed, RideSettingsChanged), + (self._on_auto_save_settings_changed, RideSettingsChanged) ]: PUBLISHER.subscribe(listener, topic) @@ -364,6 +368,31 @@ def get_selected_datafile(self): def get_selected_datafile_controller(self): return self.tree.get_selected_datafile_controller() + def _start_auto_save_timer(self): + """Start the auto-save timer if interval is set.""" + if self._auto_save_timer: + self._auto_save_timer.Stop() + self._auto_save_timer = None + if self._auto_save_interval > 0: + self._auto_save_timer = wx.Timer(self) + self.Bind(wx.EVT_TIMER, self._on_auto_save, self._auto_save_timer) + self._auto_save_timer.Start(self._auto_save_interval * 60 * 1000) # minutes to milliseconds + + def _on_auto_save(self, event): + """Auto-save all files when timer fires.""" + __ = event + if self.controller and self.controller.is_dirty(): + RideBeforeSaving().publish() + self.save_all() + self.SetStatusText(_('Auto-saved all files')) + wx.CallAfter(self._start_auto_save_timer) + + def _on_auto_save_settings_changed(self, message): + """Update auto-save timer when settings change.""" + if message.keys and 'auto save interval' in message.keys: + self._auto_save_interval = self._application.settings.get('auto save interval', 0) + self._start_auto_save_timer() + def on_close(self, event): from ..preferences import RideSettings if self._allowed_to_exit(): diff --git a/utest/ui/test_mainframe.py b/utest/ui/test_mainframe.py index 4d71a6ef4..c80eb4bad 100644 --- a/utest/ui/test_mainframe.py +++ b/utest/ui/test_mainframe.py @@ -171,6 +171,44 @@ def my_call(*argument, **options): for _ in range(16): # Hoping to cover all 4 cases start_external_app(__file__) + def test_on_auto_save_restarts_timer_after_save(self): + calls = [] + + class DirtyController: + @staticmethod + def is_dirty(): + return True + + class FakeBeforeSaving: + def publish(self): + calls.append("publish") + + def restart_timer(): + calls.append("restart") + + def fake_call_after(callback, *args, **kwargs): + calls.append(("callafter", callback, args, kwargs)) + + self.frame.controller = DirtyController() + with MonkeyPatch().context() as m: + m.setattr(mainframe, 'RideBeforeSaving', FakeBeforeSaving) + m.setattr(wx, 'CallAfter', fake_call_after) + m.setattr(self.frame, 'save_all', lambda: calls.append("save_all")) + m.setattr(self.frame, 'SetStatusText', lambda text: calls.append(("status", text))) + m.setattr(self.frame, '_start_auto_save_timer', restart_timer) + + self.frame._on_auto_save(object()) + + self.assertEqual( + calls, + [ + "publish", + "save_all", + ("status", "Auto-saved all files"), + ("callafter", restart_timer, (), {}), + ], + ) + if __name__ == '__main__': unittest.main()