def __init__(self, config_dir=None, parent=None): super(QWatson, self).__init__(parent) self.setWindowIcon(icons.get_icon('master')) self.setWindowTitle(__namever__) self.setMinimumWidth(300) self.setWindowFlags(Qt.Window | Qt.WindowMinimizeButtonHint | Qt.WindowCloseButtonHint) if platform.system() == 'Windows': import ctypes myappid = __namever__ ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID( myappid) config_dir = (config_dir or os.environ.get('QWATSON_DIR') or click.get_app_dir('QWatson')) self.client = Watson(config_dir=config_dir) self.model = WatsonTableModel(self.client) self.setup_activity_overview() self.setup() if self.client.is_started: self.add_new_project(self.client.current['project']) self.stop_watson(tags=['error'], message="last session not closed correctly.") self.set_settings_from_index(-1)
def test_edit_frame_at(): client = Watson(config_dir=WORKDIR) # Edit first frame. start0 = local_arrow_from_tuple((2018, 6, 14, 15, 59, 54)) stop0 = local_arrow_from_tuple((2018, 6, 14, 16, 34, 25)) edit_frame_at(client, 0, start=start0, stop=stop0, tags=['edited']) frame = client.frames[0] assert frame.start.format('YYYY-MM-DD HH:mm:ss') == '2018-06-14 15:59:54' assert frame.stop.format('YYYY-MM-DD HH:mm:ss') == '2018-06-14 16:34:25' assert client.frames[0].tags == ['edited'] # Edit second frame. start1 = '2018-06-14 16:48:05' stop1 = '2018-06-14 17:00:00' edit_frame_at(client, 1, start=start1, stop=stop1, tags=['edited']) frame = client.frames[1] assert frame.start.format('YYYY-MM-DD HH:mm:ss') == start1 assert frame.stop.format('YYYY-MM-DD HH:mm:ss') == stop1 assert client.frames[1].tags == ['edited'] # Edit third frame. start2 = '2018-06-14 18:02:57' stop2 = '2018-06-14 23:34:25' edit_frame_at(client, 2, start=start2, stop=stop2, tags=['edited']) frame = client.frames[2] assert frame.start.format('YYYY-MM-DD HH:mm:ss') == start2 assert frame.stop.format('YYYY-MM-DD HH:mm:ss') == stop2 assert client.frames[2].tags == ['edited'] client.save()
def test_client_loading_frames(): """Test that the client saved and loaded the frames correctly.""" client = Watson(config_dir=WORKDIR) assert len(client.frames) == 3 assert client.frames[0].message == FIRSTCOMMENT assert client.frames[1].message == THIRDCOMMENT assert client.frames[2].message == SECONDCOMMENT
def test_round_frame_at(): client = Watson(config_dir=WORKDIR) # Round first frame to 1min. round_frame_at(client, 0, 1) frame = client.frames[0] assert frame.start.format('YYYY-MM-DD HH:mm') == '2018-06-14 16:00' assert frame.stop.format('YYYY-MM-DD HH:mm') == '2018-06-14 16:34' # Round second frame to 5min. round_frame_at(client, 1, 5) frame = client.frames[1] assert frame.start.format('YYYY-MM-DD HH:mm') == '2018-06-14 16:50' assert frame.stop.format('YYYY-MM-DD HH:mm') == '2018-06-14 17:00' # Round third frame to 10min. round_frame_at(client, 2, 10) frame = client.frames[2] assert frame.start.format('YYYY-MM-DD HH:mm') == '2018-06-14 18:00' assert frame.stop.format('YYYY-MM-DD HH:mm') == '2018-06-14 23:30' client.save()
def test_client_frame_insert(): """Test that inserting a frame works as expected.""" client = Watson(config_dir=WORKDIR) assert len(client.frames) == 2 client.insert(1, project='ci-tests', start=arrow.now(), stop=arrow.now(), message=THIRDCOMMENT) client.save() assert len(client.frames) == 3
def test_client_start_stop(): """Test starting and stopping the client from an empty state.""" frames_file = osp.join(WORKDIR, 'frames') delete_file_safely(frames_file) delete_file_safely(frames_file + '.bak') assert not osp.exists(frames_file) client = Watson(config_dir=WORKDIR) assert client.frames_file == frames_file assert (len(client.frames)) == 0 client.start('ci-tests') client._current['message'] = FIRSTCOMMENT client.stop() client.start('ci-tests') client._current['message'] = SECONDCOMMENT client.stop() client.save() assert osp.exists(client.frames_file) assert (len(client.frames)) == 2
def itemEnterEvent(self, index): self.set_hovered_row(index.row()) def leaveEvent(self, event): super(FormatedWatsonTableView, self).leaveEvent(event) self.set_hovered_row(None) def focusOutEvent(self, event): super(FormatedWatsonTableView, self).focusOutEvent(event) self.set_hovered_row(None) if __name__ == '__main__': from qwatson.watson_ext.watsonextends import Watson from qwatson.models.tablemodels import WatsonTableModel import os.path as osp from qwatson import __rootdir__ dirname = osp.join(__rootdir__, 'tests', 'appdir', 'activity_overview') client = Watson(config_dir=dirname) model = WatsonTableModel(client) app = QApplication(sys.argv) from PyQt5.QtWidgets import QStyleFactory app.setStyle(QStyleFactory.create('WindowsVista')) overview_window = ActivityOverviewWidget(model) overview_window.show() app.exec_()
class QWatson(QWidget, QWatsonImportMixin, QWatsonProjectMixin, QWatsonActivityMixin): def __init__(self, config_dir=None, parent=None): super(QWatson, self).__init__(parent) self.setWindowIcon(icons.get_icon('master')) self.setWindowTitle(__namever__) self.setMinimumWidth(300) self.setWindowFlags(Qt.Window | Qt.WindowMinimizeButtonHint | Qt.WindowCloseButtonHint) if platform.system() == 'Windows': import ctypes myappid = __namever__ ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID( myappid) config_dir = (config_dir or os.environ.get('QWATSON_DIR') or click.get_app_dir('QWatson')) self.client = Watson(config_dir=config_dir) self.model = WatsonTableModel(self.client) self.setup_activity_overview() self.setup() if self.client.is_started: self.add_new_project(self.client.current['project']) self.stop_watson(tags=['error'], message="last session not closed correctly.") self.set_settings_from_index(-1) # ---- Setup layout def setup(self): """Setup the main widget.""" # Setup the stack widget. self.stackwidget = QStackedWidget() self.setup_activity_tracker() self.setup_datetime_input_dialog() self.setup_close_dialog() self.setup_del_project_dialog() self.setup_merge_project_dialog() self.setup_import_dialog() # Setup the main layout of the widget layout = QVBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) layout.addWidget(self.stackwidget) def setup_close_dialog(self): """ Setup a dialog that is shown when closing QWatson while and activity is being tracked. """ self.close_dial = CloseDialog(parent=self) self.close_dial.register_dialog_to(self) def setup_datetime_input_dialog(self): """ Setup the dialog to ask the user to enter a datetime value for the starting time of the activity. """ self.datetime_input_dial = DateTimeInputDialog(parent=self) self.datetime_input_dial.register_dialog_to(self) # ---- Main interface def setup_activity_tracker(self): """Setup the widget used to start, track, and stop new activity.""" stopwatch = self.setup_stopwatch() managers = self.setup_watson_managers() statusbar = self.setup_statusbar() tracker = QWidget() layout = QVBoxLayout(tracker) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) layout.addWidget(stopwatch) layout.addWidget(managers) layout.addWidget(statusbar) layout.setStretch(1, 100) self.stackwidget.addWidget(tracker) # ---- Project, Tags and Comment def setup_watson_managers(self): """ Setup the embedded dialog to setup the current activity parameters. """ project_manager = self.setup_project_manager() self.tag_manager = TagLineEdit() self.tag_manager.setPlaceholderText("Tags (comma separated)") self.comment_manager = QLineEdit() self.comment_manager.setPlaceholderText("Comment") # ---- Setup the layout managers = ColoredFrame('light') layout = QGridLayout(managers) layout.setContentsMargins(5, 5, 5, 5) layout.addWidget(project_manager, 0, 1) layout.addWidget(self.tag_manager, 1, 1) layout.addWidget(self.comment_manager, 2, 1) layout.addWidget(QLabel('project :'), 0, 0) layout.addWidget(QLabel('tags :'), 1, 0) layout.addWidget(QLabel('comment :'), 2, 0) return managers def set_settings_from_index(self, index): """ Load the settings in the manager from the data of the frame saved at index. """ if index is not None: try: frame = self.client.frames[index] self.project_manager.blockSignals(True) self.project_manager.setCurrentProject(frame.project) self.project_manager.blockSignals(False) self.tag_manager.blockSignals(True) self.tag_manager.set_tags(frame.tags) self.tag_manager.blockSignals(False) self.comment_manager.blockSignals(True) self.comment_manager.setText(frame.message) self.comment_manager.blockSignals(False) except IndexError: print("IndexError: list index out of range") # ---- Bottom Toolbar def setup_statusbar(self): """Setup the toolbar located at the bottom of the main widget.""" self.btn_report = QToolButtonSmall('note') self.btn_report.clicked.connect(self.overview_widg.show) self.btn_report.setToolTip("<b>Activity Overview</b><br><br>" "Open the activity overview window.") self.round_time_btn = DropDownToolButton(style='text_only') self.round_time_btn.addItems(list(ROUNDMIN.keys())) self.round_time_btn.setCurrentIndex(1) self.round_time_btn.setToolTip( "<b>Round Start and Stop</b><br><br>" "Round start and stop times to the nearest" " multiple of the selected factor.") self.btn_startfrom = DropDownToolButton(style='text_only') self.btn_startfrom.addItems( ['start from now', 'start from last', 'start from other']) self.btn_startfrom.setCurrentIndex(0) self.btn_startfrom.setToolTip( "<b>Start From</b><br><br>" "Set whether the current activity starts" " from the current time (now)," " from the stop time of the last logged activity (last)," " or from a user defined time (other).") # Setup the layout of the statusbar statusbar = ToolBarWidget('window') statusbar.setSpacing(0) statusbar.addWidget(self.round_time_btn) statusbar.addWidget(self.btn_startfrom) statusbar.addStretch(100) statusbar.addWidget(self.btn_report) statusbar.setSizePolicy( QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)) return statusbar def roundTo(self): """ Return the start and stop rounding time factor, in minutes, that corresponds to the option selected in the round_time_btn. """ return ROUNDMIN[self.round_time_btn.text()] def startFrom(self): """ Return the mode to use to determine at what reference time the activity must refer to calculate its elapsed time. """ return STARTFROM[self.btn_startfrom.text()] # ---- Stackwidget handlers def addWidget(self, widget): """ Add a widget to the stackwidget and return the index where the widget was added. """ self.stackwidget.addWidget(widget) return self.stackwidget.count() - 1 def removeWidget(self, widget): """Remove a widget from the stackwidget.""" self.stackwidget.removeWidget(widget) def currentIndex(self): """Return the current index of the stackwidget.""" return self.stackwidget.currentIndex() def setCurrentIndex(self, index): """Set the current index of the stackwidget.""" self.stackwidget.setCurrentIndex(index) # ---- Stop, Start, and Cancel def setup_stopwatch(self): """ Setup the widget that contains a button to start/stop Watson and a digital clock that shows the elapsed amount of time since Watson was started. """ self.stopwatch = StopWatchWidget() self.stopwatch.sig_btn_start_clicked.connect(self.start_watson) self.stopwatch.sig_btn_stop_clicked.connect(self.stop_watson) self.stopwatch.sig_btn_cancel_clicked.connect(self.cancel_watson) return self.stopwatch def start_watson(self, start_time=None): """Start monitoring a new activity with the Watson client.""" if isinstance(start_time, arrow.Arrow): self.btn_startfrom.setEnabled(False) self.stopwatch.start(start_time) self.client.start(self.currentProject()) self.client._current['start'] = start_time else: frames = self.client.frames if self.startFrom() == 'now': self.start_watson(arrow.now()) elif self.startFrom() == 'last' and len(frames) > 0: self.start_watson(min(frames[-1].stop, arrow.now())) else: self.datetime_input_dial.show() def cancel_watson(self): """Cancel the Watson client if it is running and reset the UI.""" self.btn_startfrom.setEnabled(True) self.stopwatch.cancel() if self.client.is_started: self.client.cancel() def stop_watson(self, message=None, project=None, tags=None, round_to=None): """Stop Watson and update the table model.""" self.btn_startfrom.setEnabled(True) self.stopwatch.stop() self.client._current['message'] = \ self.comment_manager.text() if message is None else message self.client._current['project'] = \ self.currentProject() if project is None else project self.client._current['tags'] = \ self.tag_manager.tags if tags is None else tags self.model.beginInsertRows(QModelIndex(), len(self.client.frames), len(self.client.frames)) self.client.stop() # Round the start and stop times of the last added frame. round_frame_at(self.client, -1, self.roundTo() if round_to is None else round_to) self.client.save() self.model.endInsertRows() def closeEvent(self, event): """Qt method override.""" if self.client.is_started: self.close_dial.show() event.ignore() else: self.overview_widg.close() self.client.save() event.accept() print("QWatson is closed.\n")
def __init__(self, client=None, parent=None): super().__init__('Projects', client, parent) def items(self): """Base class method implementation.""" return self.client.projects class FilterTagsMenu(FilterBaseMenu): """An implementation of the FilterBaseMenu for activity tags.""" def __init__(self, client=None, parent=None): super().__init__('Tags', client, parent) def items(self): """Base class method implementation.""" return [''] + self.client.tags if __name__ == '__main__': from PyQt5.QtWidgets import QApplication, QWidget, QGridLayout from qwatson.watson_ext.watsonextends import Watson app = QApplication(sys.argv) window = QWidget() layout = QGridLayout(window) layout.addWidget(FilterButton(Watson()), 0, 0) window.show() sys.exit(app.exec_())