Пример #1
0
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()
Пример #2
0
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
Пример #3
0
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
Пример #4
0
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()
Пример #5
0
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")