def stop(cls): """Stop a timer.""" # DO NOT call TimeDisplay.stop_time() again; it will double the time cls._set_mode_save() TimeDisplay.reset_time() if cls.stop_callback: cls.stop_callback()
def pause(cls): """Pause a running timer.""" cls._set_mode_paused() TimeDisplay.stop_time() if cls.pause_callback: cls.pause_callback()
def prompt_stop(cls): """Pause timer and confirm stop.""" cls._set_mode_prompt_stop() TimeDisplay.stop_time() if cls.pause_callback: cls.pause_callback()
def start(cls): """Start a new timer.""" cls._set_mode_running() TimeDisplay.start_time() if cls.start_callback: cls.start_callback()
def resume(cls): """Resume a paused timer.""" cls._set_mode_running() TimeDisplay.start_time() if cls.resume_callback: cls.resume_callback()
def stop(cls): """Stop a timer.""" # stop_time() already called from prompt_stop() cls._set_mode_save() TimeDisplay.reset_time() for callback in cls.stop_callback: callback()
def reset(cls): """Discard a stopped timer's time instead of saving it.""" cls._set_mode_stopped() TimeDisplay.show_default() Notes.clear() if cls.reset_callback: cls.reset_callback()
def remember(cls, hours, minutes, seconds): # Don't store when clock resets, only if minutes or hours has a value if hours or minutes: # Grab data in advance data = [ TimeDisplay.get_timestamp_string() + "\n", TimeDisplay.get_time_ms_string() + "\n", Notes.get_text() + "\n" ] # Write data to staging file with cls._staging.open('w') as file: file.writelines(data) cls._staging.replace(cls._storage) # Move into place
def check(cls): """Check if it's time to show a notification.""" if not cls.interval: return if TimeDisplay.get_time_min() >= cls.next_at: return True return False
def check_for_recall(cls): """Check if there is a backup that needs to be grabbed. If there are multiple backups, grab one (doesn't matter which).""" try: for backup in cls._backup_dir.glob('*.backup'): # Check for signs of life query = cls._backup_dir / f'{backup.stem}.query' # Create a query file to see if this instance ID is alive. query.touch(exist_ok=True) # Wait a literal second to let any other instances "respond" sleep(1) # If the other instance was alive, it would delete the query... if not query.exists(): # In that case, this one is occupied. Next! continue with backup.open('r') as file: recovered = [s.strip() for s in file.readlines()] # Attempt to restore the recovered data. timestamp = datetime(*(int(v) for v in recovered[0].split(':'))) duration = int(recovered[1]) TimeDisplay.restore_from_backup(timestamp, duration) notes = recovered[2] Notes.restore_from_backup(notes) # Allow the user to either reset or save. TimeControls._set_mode_save() # Clear the file we just loaded. backup.unlink(missing_ok=True) query.unlink(missing_ok=True) # We are now done. Quit. return except StopIteration: return # Nothing to load, move along. except (IndexError, ValueError): # Warn about corrupted backup logging.warning(f"Corrupted backup file: {cls._backup_dir}") # Delete corrupted backup file backup.unlink(missing_ok=True) # Try again...maybe a different file will work? cls.check_for_recall()
def build(): """Construct the interface.""" App.build() App.add_widget(TimeDisplay.build()) App.add_widget(Notes.build()) App.add_widget(TimeControls.build()) App.add_widget(Workspace.build()) App.add_widget(AppControls.build()) SysTray.build()
def start_monitoring(cls): """Subscribe to the TimeDisplay events for purposes of backing up the timer in case of crash. """ if cls._active: return cls._backup_dir.mkdir(parents=True, exist_ok=True) cls._active = True def tick(hours, minutes, seconds): # Prevent any other instances of Timecard from claiming recoveries cls._query.unlink(missing_ok=True) cls.remember(hours, minutes, seconds) def stop(erase): if erase: cls.forget() TimeDisplay.connect(on_minute=tick, on_stop=stop)
def build(cls): """Construct the system tray""" cls.systray.setIcon(App.icon) cls.act_status.setText("00:00:00") cls.menu.addAction(cls.act_status) cls.menu.addAction(cls.act_time) cls.set_mode_stopped() cls.menu.addSeparator() cls.act_toggle.setIcon(QIcon.fromTheme('view-restore')) cls.act_toggle.setText("Show/Hide Window") cls.act_toggle.triggered.connect(cls.toggle_window) cls.menu.addAction(cls.act_toggle) cls.menu.addSeparator() cls.act_quit.setIcon(QIcon.fromTheme('application-exit')) cls.act_quit.setText("Quit Timecard") cls.act_quit.triggered.connect(cls.quit_app) cls.menu.addAction(cls.act_quit) cls.systray.setContextMenu(cls.menu) cls.systray.show() TimeDisplay.connect(on_tick=cls.update_time) TimeControls.connect(on_start=cls.set_mode_running, on_resume=cls.set_mode_running, on_pause=cls.set_mode_paused, on_stop=cls.set_mode_save, on_save=cls.set_mode_stopped, on_reset=cls.set_mode_stopped) App.connect(on_hide=cls.popup) return cls.systray
def notify(cls, hours, minutes, seconds): """Display notification.""" if not cls.check(): return cls.last_at = TimeDisplay.get_time_min() activity = Notes.get_text() if activity: message = random.choice( cls.prompt_with_note).substitute(task=activity) else: message = random.choice(cls.prompt_no_note) # Show the notification SysTray.popup(message) # Determine when next notification should appear cls.calculate_next()
def save(cls): """Save a stopped timer's time to the log.""" cls._set_mode_stopped() notes = Notes.get_text() timestamp = TimeDisplay.get_timestamp() TimeLog.add_to_log(timestamp, *TimeDisplay.get_time(), notes) LogView.refresh() Notes.clear() TimeDisplay.stop_time() TimeDisplay.reset_time() if cls.save_callback: cls.save_callback()
def build(): """Construct the interface.""" # Build the actual interface. App.build() App.add_widget(TimeDisplay.build()) App.add_widget(Notes.build()) App.add_widget(TimeControls.build()) App.add_widget(Workspace.build()) App.add_widget(AppControls.build()) SysTray.build() # See if there's anything to recover from a damaged session. Backup.check_for_recall() # Start monitoring new timers. Backup.start_monitoring() # Initialize systems Focus.initialize() # Start the clock! Clock.start()
def initialize(cls): """Initialize focus popup notification system.""" cls.reload_settings() TimeDisplay.connect(on_minute=cls.notify)