Esempio n. 1
0
 def test_get_max_length(self, schedule: Schedule):
     length = schedule.get_max_length("description")
     assert length == 19
Esempio n. 2
0
class Screen():
    """This class handles the rendering of the schedule."""
    def __init__(self,
                 refresh_rate=1,
                 hide_empty=True,
                 scheduled_before=None,
                 scheduled_after=None,
                 scheduled=None,
                 completed=True,
                 hide_projects=False):
        self.stdscr = curses.initscr()
        self.stdscr.nodelay(True)
        curses.noecho()

        self.refresh_rate = refresh_rate
        self.completed = completed

        self.scheduled = scheduled
        self.scheduled_before = scheduled_before
        self.scheduled_after = scheduled_after

        self.hide_projects = hide_projects
        self.hide_empty = hide_empty
        self.buffer = []
        self.prev_buffer = []
        self.init_colors()

        self.schedule = Schedule()

    def close(self):
        """Close the curses screen."""
        curses.endwin()

    def init_colors(self):
        """Initialize the colors."""
        curses.curs_set(0)
        curses.start_color()
        curses.init_pair(1, 20, curses.COLOR_BLACK)
        curses.init_pair(2, 8, 0)  # Hours
        curses.init_pair(3, 20, 234)  # Alternating background
        curses.init_pair(4, curses.COLOR_WHITE, curses.COLOR_BLACK)  # Header
        curses.init_pair(5, curses.COLOR_GREEN,
                         curses.COLOR_BLACK)  # Current hour
        curses.init_pair(6, 19, 234)  # Completed task - alternating background
        curses.init_pair(7, 19, 0)  # Completed task
        curses.init_pair(8, curses.COLOR_BLACK,
                         curses.COLOR_GREEN)  # Active task
        curses.init_pair(9, curses.COLOR_BLACK, curses.COLOR_BLACK)  # Glyph
        curses.init_pair(10, curses.COLOR_GREEN,
                         curses.COLOR_BLACK)  # Active task
        curses.init_pair(11, curses.COLOR_YELLOW,
                         curses.COLOR_BLACK)  # Overdue task
        curses.init_pair(12, curses.COLOR_YELLOW, 234)  # Overdue task alt
        curses.init_pair(13, curses.COLOR_GREEN,
                         234)  # Should-be-active task alt

        # pylint: disable=invalid-name
        self.COLOR_DEFAULT = curses.color_pair(1)
        self.COLOR_DEFAULT_ALTERNATE = curses.color_pair(3)
        self.COLOR_HEADER = curses.color_pair(4) | curses.A_UNDERLINE
        self.COLOR_HOUR = curses.color_pair(2)
        self.COLOR_HOUR_CURRENT = curses.color_pair(5)
        self.COLOR_ACTIVE = curses.color_pair(8)
        self.COLOR_SHOULD_BE_ACTIVE = curses.color_pair(10)
        self.COLOR_SHOULD_BE_ACTIVE_ALTERNATE = curses.color_pair(13)
        self.COLOR_OVERDUE = curses.color_pair(11)
        self.COLOR_OVERDUE_ALTERNATE = curses.color_pair(12)
        self.COLOR_COMPLETED = curses.color_pair(7)
        self.COLOR_COMPLETED_ALTERNATE = curses.color_pair(6)
        self.COLOR_GLYPH = curses.color_pair(9)

    def get_task_color(self, task, alternate):
        """Return the color for the given task."""
        color = None

        if task.completed:
            if alternate:
                color = self.COLOR_COMPLETED_ALTERNATE
            else:
                color = self.COLOR_COMPLETED
        elif task.active:
            color = self.COLOR_ACTIVE
        elif task.should_be_active:
            if alternate:
                color = self.COLOR_SHOULD_BE_ACTIVE_ALTERNATE
            else:
                color = self.COLOR_SHOULD_BE_ACTIVE
        elif task.overdue and not task.completed:
            if alternate:
                color = self.COLOR_OVERDUE_ALTERNATE
            else:
                color = self.COLOR_OVERDUE
        else:
            if alternate:
                color = self.COLOR_DEFAULT_ALTERNATE
            else:
                color = self.COLOR_DEFAULT

        return color

    def draw_footnote(self, y):
        if self.scheduled_before and self.scheduled_after:
            footnote = '{} tasks - from {} until {}'.format(
                len(self.schedule.tasks), self.scheduled_after,
                self.scheduled_before)
        else:
            footnote = '{} tasks - {}'.format(len(self.schedule.tasks),
                                              self.scheduled)

        self.stdscr.addstr(y + 2, 0, footnote, self.COLOR_DEFAULT)

    def draw(self):
        """Draw the current buffer."""
        last_line = 0
        if not self.buffer:
            self.stdscr.clear()
            self.stdscr.addstr(0, 0, 'No tasks to display.',
                               self.COLOR_DEFAULT)
            self.draw_footnote(last_line)
            self.stdscr.refresh()
        else:
            if self.prev_buffer != self.buffer:
                self.stdscr.clear()
                for line, offset, string, color in self.buffer:
                    max_y, max_x = self.stdscr.getmaxyx()
                    if line < max_y - 2:
                        self.stdscr.addstr(line, offset, string, color)
                        self.stdscr.refresh()
                    else:
                        break

                    last_line = line

                self.draw_footnote(last_line)
                self.stdscr.refresh()

    def refresh_buffer(self):
        """Refresh the buffer."""
        max_y, max_x = self.stdscr.getmaxyx()
        self.prev_buffer = self.buffer
        self.buffer = []

        self.schedule.load_tasks(scheduled_before=self.scheduled_before,
                                 scheduled_after=self.scheduled_after,
                                 scheduled=self.scheduled,
                                 completed=self.completed)

        if not self.schedule.tasks:
            return

        as_dict = self.schedule.as_dict()

        # Determine offsets
        offsets = self.schedule.get_column_offsets()
        max_project_column_length = round(max_x / 8)
        if offsets[4] - offsets[3] > max_project_column_length:
            offsets[4] = offsets[3] + max_project_column_length

        # Draw headers
        headers = ['', '', 'ID', 'Time', 'Project', 'Description']
        column_lengths = [2, 1]
        column_lengths.append(self.schedule.get_max_length('id'))
        column_lengths.append(11)
        column_lengths.append(max_project_column_length - 1)
        column_lengths.append(self.schedule.get_max_length('description'))

        for i, header in enumerate(headers):
            try:
                extra_length = column_lengths[i] - len(header)
                headers[i] += ' ' * extra_length
            except IndexError:
                pass

        self.buffer.append((0, offsets[1], headers[2], self.COLOR_HEADER))
        self.buffer.append((0, offsets[2], headers[3], self.COLOR_HEADER))
        self.buffer.append((0, offsets[3], headers[4], self.COLOR_HEADER))

        if not self.hide_projects:
            self.buffer.append((0, offsets[4], headers[5], self.COLOR_HEADER))

        # Draw schedule
        alternate = True
        current_line = 1

        if self.hide_empty:
            first_task = self.schedule.tasks[0].start
            first_hour = first_task.hour
            last_task = self.schedule.tasks[-1].start
            last_hour = last_task.hour
        else:
            first_hour = 0
            last_hour = 23

        for i in range(first_hour, last_hour + 1):
            tasks = as_dict[i]
            if not tasks:
                # Add empty line
                if alternate:
                    color = self.COLOR_DEFAULT_ALTERNATE
                else:
                    color = self.COLOR_DEFAULT

                # Fill line to screen length
                self.buffer.append((current_line, 5, ' ' * (max_x - 5), color))

                # Draw hour column, highlight current hour
                current_hour = time.localtime().tm_hour
                if i == current_hour:
                    self.buffer.append(
                        (current_line, 0, str(i), self.COLOR_HOUR_CURRENT))
                else:
                    self.buffer.append(
                        (current_line, 0, str(i), self.COLOR_HOUR))

                current_line += 1
                alternate = not alternate

            for ii, task in enumerate(tasks):
                color = self.get_task_color(task, alternate)

                # Only draw hour once for multiple tasks
                if ii == 0:
                    hour = str(i)
                else:
                    hour = ''

                # Draw hour column, highlight current hour
                current_hour = time.localtime().tm_hour
                if hour != '':
                    if int(hour) == current_hour:
                        self.buffer.append(
                            (current_line, 0, hour, self.COLOR_HOUR_CURRENT))
                    else:
                        self.buffer.append(
                            (current_line, 0, hour, self.COLOR_HOUR))

                # Fill line to screen length
                self.buffer.append((current_line, 5, ' ' * (max_x - 5), color))

                # Draw glyph column
                self.buffer.append(
                    (current_line, 3, task.glyph, self.COLOR_GLYPH))

                # Draw task id column
                if task.task_id != 0:
                    self.buffer.append(
                        (current_line, 5, str(task.task_id), color))

                # Draw time column
                if task.end is None:
                    formatted_time = '{}'.format(task.start_time)
                else:
                    end_time = '{}'.format(task.end.strftime('%H:%M'))
                    formatted_time = '{}-{}'.format(task.start_time, end_time)

                self.buffer.append(
                    (current_line, offsets[2], formatted_time, color))

                # Optionally draw project column
                offset = 0
                if not self.hide_projects:
                    if task.project is None:
                        project = ''
                    else:
                        max_length = offsets[4] - offsets[3] - 1
                        project = task.project[0:max_length]

                    self.buffer.append(
                        (current_line, offsets[3], project, color))
                    offset = offsets[4]
                else:
                    offset = offsets[3]

                # Draw description column
                description = task.description[0:max_x - offset]
                self.buffer.append((current_line, offset, description, color))

                current_line += 1
                alternate = not alternate