def __init__(self): super().__init__() self._process_flags() self.column_width = config().column_width() self.todofile = TodoFile.TodoFile(config().todotxt()) self.todolist = TodoList.TodoList(self.todofile.read()) self.marked_todos = [] self.columns = urwid.Columns([], dividechars=0, min_width=config().column_width()) self.commandline = CommandLineWidget('topydo> ') self.keystate_widget = KeystateWidget() self.status_line = urwid.Columns([ ('weight', 1, urwid.Filler(self.commandline)), ]) self.keymap = config().column_keymap() self._alarm = None self._last_cmd = None # console widget self.console = ConsoleWidget() get_terminal_size(self._console_width) urwid.connect_signal(self.commandline, 'blur', self._blur_commandline) urwid.connect_signal(self.commandline, 'execute_command', self._execute_handler) def hide_console(p_focus_commandline=False): self._console_visible = False if p_focus_commandline: self._focus_commandline() urwid.connect_signal(self.console, 'close', hide_console) # view widget self.viewwidget = ViewWidget(self.todolist) urwid.connect_signal(self.viewwidget, 'save', lambda: self._update_view(self.viewwidget.data)) def hide_viewwidget(): self._viewwidget_visible = False self._blur_commandline() urwid.connect_signal(self.viewwidget, 'close', hide_viewwidget) self.mainwindow = MainPile([ ('weight', 1, self.columns), (1, self.status_line), ]) urwid.connect_signal(self.mainwindow, 'blur_console', hide_console) # the columns should have keyboard focus self._blur_commandline() self._screen = urwid.raw_display.Screen() if config().colors(): self._screen.register_palette(self._create_color_palette()) else: self._screen.register_palette(self._create_mono_palette()) self._screen.set_terminal_properties(256) self.mainloop = urwid.MainLoop( self.mainwindow, screen=self._screen, unhandled_input=self._handle_input, pop_ups=True ) self.column_mode = _APPEND_COLUMN self._set_alarm_for_next_midnight_update()
class UIApplication(CLIApplicationBase): def __init__(self): super().__init__() self._process_flags() self.column_width = config().column_width() self.todofile = TodoFile.TodoFile(config().todotxt()) self.todolist = TodoList.TodoList(self.todofile.read()) self.marked_todos = [] self.columns = urwid.Columns([], dividechars=0, min_width=config().column_width()) self.commandline = CommandLineWidget('topydo> ') self.keystate_widget = KeystateWidget() self.status_line = urwid.Columns([ ('weight', 1, urwid.Filler(self.commandline)), ]) self.keymap = config().column_keymap() self._alarm = None self._last_cmd = None # console widget self.console = ConsoleWidget() get_terminal_size(self._console_width) urwid.connect_signal(self.commandline, 'blur', self._blur_commandline) urwid.connect_signal(self.commandline, 'execute_command', self._execute_handler) def hide_console(p_focus_commandline=False): self._console_visible = False if p_focus_commandline: self._focus_commandline() urwid.connect_signal(self.console, 'close', hide_console) # view widget self.viewwidget = ViewWidget(self.todolist) urwid.connect_signal(self.viewwidget, 'save', lambda: self._update_view(self.viewwidget.data)) def hide_viewwidget(): self._viewwidget_visible = False self._blur_commandline() urwid.connect_signal(self.viewwidget, 'close', hide_viewwidget) self.mainwindow = MainPile([ ('weight', 1, self.columns), (1, self.status_line), ]) urwid.connect_signal(self.mainwindow, 'blur_console', hide_console) # the columns should have keyboard focus self._blur_commandline() self._screen = urwid.raw_display.Screen() if config().colors(): self._screen.register_palette(self._create_color_palette()) else: self._screen.register_palette(self._create_mono_palette()) self._screen.set_terminal_properties(256) self.mainloop = urwid.MainLoop( self.mainwindow, screen=self._screen, unhandled_input=self._handle_input, pop_ups=True ) self.column_mode = _APPEND_COLUMN self._set_alarm_for_next_midnight_update() def _create_color_palette(self): project_color = to_urwid_color(config().project_color()) context_color = to_urwid_color(config().context_color()) metadata_color = to_urwid_color(config().metadata_color()) link_color = to_urwid_color(config().link_color()) palette = [ (PaletteItem.PROJECT, '', '', '', project_color, ''), (PaletteItem.PROJECT_FOCUS, '', 'light gray', '', project_color, None), (PaletteItem.CONTEXT, '', '', '', context_color, ''), (PaletteItem.CONTEXT_FOCUS, '', 'light gray', '', context_color, None), (PaletteItem.METADATA, '', '', '', metadata_color, ''), (PaletteItem.METADATA_FOCUS, '', 'light gray', '', metadata_color, None), (PaletteItem.LINK, '', '', '', link_color, ''), (PaletteItem.LINK_FOCUS, '', 'light gray', '', link_color, None), (PaletteItem.DEFAULT_FOCUS, 'black', 'light gray'), (PaletteItem.MARKED, '', 'light blue'), ] for C in ascii_uppercase: pri_color_cfg = config().priority_color(C) pri_color = to_urwid_color(pri_color_cfg) pri_color_focus = pri_color if not pri_color_cfg.is_neutral() else 'black' palette.append(( 'pri_' + C, '', '', '', pri_color, '' )) palette.append(( 'pri_' + C + '_focus', '', 'light gray', '', pri_color_focus, None )) return palette def _create_mono_palette(self): palette = [ (PaletteItem.DEFAULT_FOCUS, 'black', 'light gray'), (PaletteItem.PROJECT_FOCUS, PaletteItem.DEFAULT_FOCUS), (PaletteItem.CONTEXT_FOCUS, PaletteItem.DEFAULT_FOCUS), (PaletteItem.METADATA_FOCUS, PaletteItem.DEFAULT_FOCUS), (PaletteItem.LINK_FOCUS, PaletteItem.DEFAULT_FOCUS), (PaletteItem.MARKED, 'default,underline,bold', 'default'), ] for C in ascii_uppercase: palette.append( ('pri_' + C + '_focus', PaletteItem.DEFAULT_FOCUS) ) return palette def _set_alarm_for_next_midnight_update(self): def callback(p_loop, p_data): TodoWidget.wipe_cache() self._update_all_columns() self._set_alarm_for_next_midnight_update() tomorrow = datetime.datetime.now() + datetime.timedelta(days=1) # turn it into midnight tomorrow = tomorrow.replace(hour=0, minute=0, second=0, microsecond=0) self.mainloop.set_alarm_at(time.mktime(tomorrow.timetuple()), callback) def _output(self, p_text): self._print_to_console(p_text) def _execute_handler(self, p_command, p_todo_id=None, p_output=None): """ Executes a command, given as a string. """ p_output = p_output or self._output self._last_cmd = (p_command, p_output == self._output) if '{}' in p_command: if self._has_marked_todos(): p_todo_id = ' '.join(self.marked_todos) p_command = p_command.format(p_todo_id) try: p_command = shlex.split(p_command) except ValueError as verr: self._print_to_console('Error: ' + str(verr)) return try: (subcommand, args) = get_subcommand(p_command) except ConfigError as cerr: self._print_to_console( 'Error: {}. Check your aliases configuration.'.format(cerr)) return self._backup(subcommand, args) try: command = subcommand( args, self.todolist, p_output, self._output, self._input, ) if command.execute() != False: self._post_execute() except TypeError: # TODO: show error message pass def _update_all_columns(self): for column, _ in self.columns.contents: column.update() column.keystate = None def _post_execute(self): # store dirty flag because base _post_execute will reset it after flush dirty = self.todolist.dirty super()._post_execute() if dirty or self.marked_todos: self._reset_state() def _repeat_last_cmd(self, p_todo_id=None): try: cmd, verbosity = self._last_cmd except TypeError: return self._execute_handler(cmd, p_todo_id, self._output if verbosity else lambda _: None) def _reset_state(self): for widget in TodoWidget.cache.values(): widget.unmark() self.marked_todos = [] self._update_all_columns() def _blur_commandline(self): self.mainwindow.focus_item = 0 def _focus_commandline(self): self.mainwindow.focus_item = 1 def _focus_first_column(self): self.columns.focus_position = 0 def _focus_last_column(self): end_pos = len(self.columns.contents) - 1 self.columns.focus_position = end_pos def _focus_next_column(self): size = len(self.columns.contents) if self.columns.focus_position < size -1: self.columns.focus_position += 1 def _focus_previous_column(self): if self.columns.focus_position > 0: self.columns.focus_position -= 1 def _append_column(self): self.viewwidget.reset() self.column_mode = _APPEND_COLUMN self._viewwidget_visible = True def _insert_column(self): self.viewwidget.reset() self.column_mode = _INSERT_COLUMN self._viewwidget_visible = True def _edit_column(self): self.viewwidget.data = self.columns.focus.view.data self.column_mode = _EDIT_COLUMN self._viewwidget_visible = True def _delete_column(self): try: focus = self.columns.focus_position del self.columns.contents[focus] if self.columns.contents: self.columns.focus_position = focus else: self._focus_commandline() except IndexError: # no columns pass def _copy_column(self): self.viewwidget.data = self.columns.focus.view.data self.column_mode = _COPY_COLUMN self._viewwidget_visible = True def _column_action_handler(self, p_action): dispatch = { 'first_column': self._focus_first_column, 'last_column': self._focus_last_column, 'prev_column': self._focus_previous_column, 'next_column': self._focus_next_column, 'append_column': self._append_column, 'insert_column': self._insert_column, 'edit_column': self._edit_column, 'delete_column': self._delete_column, 'copy_column': self._copy_column, 'swap_left': self._swap_column_left, 'swap_right': self._swap_column_right, 'reset': self._reset_state, } dispatch[p_action]() def _handle_input(self, p_input): dispatch = { ':': self._focus_commandline, } try: dispatch[p_input]() except KeyError: # the key is unknown, ignore pass def _viewdata_to_view(self, p_data): """ Converts a dictionary describing a view to an actual UIView instance. """ sorter = Sorter(p_data['sortexpr']) filters = [] if not p_data['show_all']: filters.append(DependencyFilter(self.todolist)) filters.append(RelevanceFilter()) filters += get_filter_list(p_data['filterexpr'].split()) return UIView(sorter, filters, self.todolist, p_data) def _update_view(self, p_data): """ Creates a view from the data entered in the view widget. """ view = self._viewdata_to_view(p_data) if self.column_mode == _APPEND_COLUMN or self.column_mode == _COPY_COLUMN: self._add_column(view) elif self.column_mode == _INSERT_COLUMN: self._add_column(view, self.columns.focus_position) elif self.column_mode == _EDIT_COLUMN: current_column = self.columns.focus current_column.title = p_data['title'] current_column.view = view self._viewwidget_visible = False self._blur_commandline() def _add_column(self, p_view, p_pos=None): """ Given an UIView, adds a new column widget with the todos in that view. When no position is given, it is added to the end, otherwise inserted before that position. """ def execute_silent(p_cmd, p_todo_id=None): self._execute_handler(p_cmd, p_todo_id, lambda _: None) todolist = TodoListWidget(p_view, p_view.data['title'], self.keymap) urwid.connect_signal(todolist, 'execute_command_silent', execute_silent) urwid.connect_signal(todolist, 'execute_command', self._execute_handler) urwid.connect_signal(todolist, 'repeat_cmd', self._repeat_last_cmd) urwid.connect_signal(todolist, 'refresh', self.mainloop.screen.clear) urwid.connect_signal(todolist, 'add_pending_action', self._set_alarm) urwid.connect_signal(todolist, 'remove_pending_action', self._remove_alarm) urwid.connect_signal(todolist, 'column_action', self._column_action_handler) urwid.connect_signal(todolist, 'show_keystate', self._print_keystate) urwid.connect_signal(todolist, 'toggle_mark', self._process_mark_toggle) options = self.columns.options( width_type='given', width_amount=config().column_width(), box_widget=True ) item = (todolist, options) if p_pos == None: p_pos = len(self.columns.contents) self.columns.contents.insert(p_pos, item) self.columns.focus_position = p_pos self._blur_commandline() def _print_keystate(self, p_keystate): self.keystate_widget.set_text(p_keystate) self._keystate_visible = len(p_keystate) > 0 def _set_alarm(self, p_callback): """ Sets alarm to execute p_action specified in 0.5 sec. """ self._alarm = self.mainloop.set_alarm_in(0.5, p_callback) def _remove_alarm(self): """ Removes pending action alarm stored in _alarm attribute. """ self.mainloop.remove_alarm(self._alarm) self._alarm = None def _swap_column_left(self): pos = self.columns.focus_position if pos > 0: _columns = self.columns.contents _columns[pos], _columns[pos - 1] = _columns[pos - 1], _columns[pos] self.columns.focus_position -= 1 def _swap_column_right(self): pos = self.columns.focus_position _columns = self.columns.contents if pos < len(_columns) - 1: _columns[pos], _columns[pos + 1] = _columns[pos + 1], _columns[pos] self.columns.focus_position += 1 @property def _console_visible(self): contents = self.mainwindow.contents return len(contents) == 3 and isinstance(contents[2][0], ConsoleWidget) @_console_visible.setter def _console_visible(self, p_enabled): contents = self.mainwindow.contents if p_enabled == True and len(contents) == 2: contents.append((self.console, ('pack', None))) self.mainwindow.focus_position = 2 elif p_enabled == False and self._console_visible: self.console.clear() del contents[2] self.mainwindow.focus_position = 0 @property def _keystate_visible(self): contents = self.status_line.contents return len(contents) == 2 and isinstance(contents[1][0].original_widget, KeystateWidget) @_keystate_visible.setter def _keystate_visible(self, p_enabled): contents = self.status_line.contents if p_enabled and len(contents) == 1: contents.append((urwid.Filler(self.keystate_widget), ('weight', 1, True))) elif not p_enabled and self._keystate_visible: del contents[1] @property def _viewwidget_visible(self): contents = self.mainwindow.contents return len(contents) == 3 and isinstance(contents[2][0], ViewWidget) @_viewwidget_visible.setter def _viewwidget_visible(self, p_enabled): contents = self.mainwindow.contents if p_enabled == True and len(contents) == 2: contents.append((self.viewwidget, ('pack', None))) self.mainwindow.focus_position = 2 elif p_enabled == False and self._viewwidget_visible: del contents[2] def _print_to_console(self, p_text): self._console_visible = True self.console.print_text(p_text) def _input(self, p_question): self._print_to_console(p_question) # don't wait for the event loop to enter idle, there is a command # waiting for input right now, so already go ahead and draw the # question on screen. self.mainloop.draw_screen() user_input = self.mainloop.screen.get_input() self._console_visible = False return user_input[0] def _console_width(self): terminal_size = namedtuple('Terminal_Size', 'columns lines') width = self.console.console_width() - 2 sz = terminal_size(width, 1) return sz def _has_marked_todos(self): return len(self.marked_todos) > 0 def _process_mark_toggle(self, p_todo_id): """ Adds p_todo_id to marked_todos attribute and returns True if p_todo_id is not already present. Removes p_todo_id from marked_todos and returns False otherwise. """ if p_todo_id not in self.marked_todos: self.marked_todos.append(p_todo_id) return True else: self.marked_todos.remove(p_todo_id) return False def run(self): layout = columns() if len(layout) > 0: for column in layout: self._add_column(self._viewdata_to_view(column)) else: dummy = { "title": "All tasks", "sortexpr": "desc:prio", "filterexpr": "", "show_all": True, } self._add_column(self._viewdata_to_view(dummy)) # make sure that the first column is focused on startup self.columns.focus_position = 0 self.mainloop.run()
def __init__(self): super().__init__() args = self._process_flags() try: opts, args = getopt.getopt(args[1:], 'l:') except getopt.GetoptError as e: error(str(e)) sys.exit(1) self.alt_layout_path = None for opt, value in opts: if opt == "-l": self.alt_layout_path = value def callback(): self.todolist.erase() self.todolist.add_list(self.todofile.read()) self._update_all_columns() self._redraw() self.column_width = config().column_width() self.todofile = TodoFileWatched(config().todotxt(), callback) self.todolist = TodoList.TodoList(self.todofile.read()) self.marked_todos = set() self.columns = urwid.Columns([], dividechars=0, min_width=config().column_width()) self.columns.contents.set_focus_changed_callback(self._move_highlight) completer = ColumnCompleter(self.todolist) self.commandline = CommandLineWidget(completer, 'topydo> ') self.keystate_widget = KeystateWidget() self.status_line = urwid.Columns([ ('weight', 1, urwid.Filler(self.commandline)), ]) self.cli_wrapper = CliWrapper([(1, self.status_line)]) self.keymap = config().column_keymap() self._alarm = None self._last_cmd = None # console widget self.console = ConsoleWidget() get_terminal_size(self._console_width) urwid.connect_signal(self.commandline, 'blur', self._blur_commandline) urwid.connect_signal(self.commandline, 'execute_command', self._execute_handler) urwid.connect_signal(self.commandline, 'show_completions', self._show_completion_box) urwid.connect_signal(self.commandline, 'hide_completions', self._hide_completion_box) def hide_console(p_focus_commandline=False): if p_focus_commandline: self._focus_commandline() else: self._console_visible = False urwid.connect_signal(self.console, 'close', hide_console) # view widget self.viewwidget = ViewWidget(self.todolist) urwid.connect_signal(self.viewwidget, 'save', lambda: self._update_view(self.viewwidget.data)) def hide_viewwidget(): # prevent the view widget to be hidden when the last column was # deleted if self.columns.contents: self._viewwidget_visible = False self._blur_commandline() urwid.connect_signal(self.viewwidget, 'close', hide_viewwidget) self.mainwindow = MainPile([ ('weight', 1, self.columns), ('pack', self.cli_wrapper), ]) urwid.connect_signal(self.mainwindow, 'blur_console', hide_console) # the columns should have keyboard focus self._blur_commandline() self._screen = urwid.raw_display.Screen() def create_color_palette(): project_color = to_urwid_color(config().project_color()) context_color = to_urwid_color(config().context_color()) metadata_color = to_urwid_color(config().metadata_color()) link_color = to_urwid_color(config().link_color()) focus_background_color = to_urwid_color( config().focus_background_color()) marked_background_color = to_urwid_color( config().marked_background_color()) palette = [ (PaletteItem.PROJECT, '', '', '', project_color, ''), (PaletteItem.PROJECT_FOCUS, '', 'light gray', '', project_color, focus_background_color), (PaletteItem.CONTEXT, '', '', '', context_color, ''), (PaletteItem.CONTEXT_FOCUS, '', 'light gray', '', context_color, focus_background_color), (PaletteItem.METADATA, '', '', '', metadata_color, ''), (PaletteItem.METADATA_FOCUS, '', 'light gray', '', metadata_color, focus_background_color), (PaletteItem.LINK, '', '', '', link_color, ''), (PaletteItem.LINK_FOCUS, '', 'light gray', '', link_color, focus_background_color), (PaletteItem.DEFAULT_FOCUS, '', 'light gray', '', '', focus_background_color), (PaletteItem.MARKED, '', 'light blue', '', '', marked_background_color), ] for C in ascii_uppercase: pri_color_cfg = config().priority_color(C) pri_color = to_urwid_color(pri_color_cfg) pri_color_focus = pri_color if not pri_color_cfg.is_neutral( ) else 'black' palette.append(('pri_' + C, '', '', '', pri_color, '')) palette.append(('pri_' + C + '_focus', '', 'light gray', '', pri_color_focus, focus_background_color)) return palette def create_mono_palette(): palette = [ (PaletteItem.DEFAULT_FOCUS, 'black', 'light gray'), (PaletteItem.PROJECT_FOCUS, PaletteItem.DEFAULT_FOCUS), (PaletteItem.CONTEXT_FOCUS, PaletteItem.DEFAULT_FOCUS), (PaletteItem.METADATA_FOCUS, PaletteItem.DEFAULT_FOCUS), (PaletteItem.LINK_FOCUS, PaletteItem.DEFAULT_FOCUS), (PaletteItem.MARKED, 'default,underline,bold', 'default'), ] for C in ascii_uppercase: palette.append( ('pri_' + C + '_focus', PaletteItem.DEFAULT_FOCUS)) return palette if config().colors(): self._screen.register_palette(create_color_palette()) else: self._screen.register_palette(create_mono_palette()) self._screen.set_terminal_properties(256) self.mainloop = urwid.MainLoop(self.mainwindow, screen=self._screen, unhandled_input=self._handle_input, pop_ups=True) self.column_mode = _APPEND_COLUMN self._set_alarm_for_next_midnight_update()
class UIApplication(CLIApplicationBase): def __init__(self): super().__init__() args = self._process_flags() try: opts, args = getopt.getopt(args[1:], 'l:') except getopt.GetoptError as e: error(str(e)) sys.exit(1) self.alt_layout_path = None for opt, value in opts: if opt == "-l": self.alt_layout_path = value def callback(): self.todolist.erase() self.todolist.add_list(self.todofile.read()) self._update_all_columns() self._redraw() self.column_width = config().column_width() self.todofile = TodoFileWatched(config().todotxt(), callback) self.todolist = TodoList.TodoList(self.todofile.read()) self.marked_todos = set() self.columns = urwid.Columns([], dividechars=0, min_width=config().column_width()) self.columns.contents.set_focus_changed_callback(self._move_highlight) completer = ColumnCompleter(self.todolist) self.commandline = CommandLineWidget(completer, 'topydo> ') self.keystate_widget = KeystateWidget() self.status_line = urwid.Columns([ ('weight', 1, urwid.Filler(self.commandline)), ]) self.cli_wrapper = CliWrapper([(1, self.status_line)]) self.keymap = config().column_keymap() self._alarm = None self._last_cmd = None # console widget self.console = ConsoleWidget() get_terminal_size(self._console_width) urwid.connect_signal(self.commandline, 'blur', self._blur_commandline) urwid.connect_signal(self.commandline, 'execute_command', self._execute_handler) urwid.connect_signal(self.commandline, 'show_completions', self._show_completion_box) urwid.connect_signal(self.commandline, 'hide_completions', self._hide_completion_box) def hide_console(p_focus_commandline=False): if p_focus_commandline: self._focus_commandline() else: self._console_visible = False urwid.connect_signal(self.console, 'close', hide_console) # view widget self.viewwidget = ViewWidget(self.todolist) urwid.connect_signal(self.viewwidget, 'save', lambda: self._update_view(self.viewwidget.data)) def hide_viewwidget(): # prevent the view widget to be hidden when the last column was # deleted if self.columns.contents: self._viewwidget_visible = False self._blur_commandline() urwid.connect_signal(self.viewwidget, 'close', hide_viewwidget) self.mainwindow = MainPile([ ('weight', 1, self.columns), ('pack', self.cli_wrapper), ]) urwid.connect_signal(self.mainwindow, 'blur_console', hide_console) # the columns should have keyboard focus self._blur_commandline() self._screen = urwid.raw_display.Screen() def create_color_palette(): project_color = to_urwid_color(config().project_color()) context_color = to_urwid_color(config().context_color()) metadata_color = to_urwid_color(config().metadata_color()) link_color = to_urwid_color(config().link_color()) focus_background_color = to_urwid_color( config().focus_background_color()) marked_background_color = to_urwid_color( config().marked_background_color()) palette = [ (PaletteItem.PROJECT, '', '', '', project_color, ''), (PaletteItem.PROJECT_FOCUS, '', 'light gray', '', project_color, focus_background_color), (PaletteItem.CONTEXT, '', '', '', context_color, ''), (PaletteItem.CONTEXT_FOCUS, '', 'light gray', '', context_color, focus_background_color), (PaletteItem.METADATA, '', '', '', metadata_color, ''), (PaletteItem.METADATA_FOCUS, '', 'light gray', '', metadata_color, focus_background_color), (PaletteItem.LINK, '', '', '', link_color, ''), (PaletteItem.LINK_FOCUS, '', 'light gray', '', link_color, focus_background_color), (PaletteItem.DEFAULT_FOCUS, '', 'light gray', '', '', focus_background_color), (PaletteItem.MARKED, '', 'light blue', '', '', marked_background_color), ] for C in ascii_uppercase: pri_color_cfg = config().priority_color(C) pri_color = to_urwid_color(pri_color_cfg) pri_color_focus = pri_color if not pri_color_cfg.is_neutral( ) else 'black' palette.append(('pri_' + C, '', '', '', pri_color, '')) palette.append(('pri_' + C + '_focus', '', 'light gray', '', pri_color_focus, focus_background_color)) return palette def create_mono_palette(): palette = [ (PaletteItem.DEFAULT_FOCUS, 'black', 'light gray'), (PaletteItem.PROJECT_FOCUS, PaletteItem.DEFAULT_FOCUS), (PaletteItem.CONTEXT_FOCUS, PaletteItem.DEFAULT_FOCUS), (PaletteItem.METADATA_FOCUS, PaletteItem.DEFAULT_FOCUS), (PaletteItem.LINK_FOCUS, PaletteItem.DEFAULT_FOCUS), (PaletteItem.MARKED, 'default,underline,bold', 'default'), ] for C in ascii_uppercase: palette.append( ('pri_' + C + '_focus', PaletteItem.DEFAULT_FOCUS)) return palette if config().colors(): self._screen.register_palette(create_color_palette()) else: self._screen.register_palette(create_mono_palette()) self._screen.set_terminal_properties(256) self.mainloop = urwid.MainLoop(self.mainwindow, screen=self._screen, unhandled_input=self._handle_input, pop_ups=True) self.column_mode = _APPEND_COLUMN self._set_alarm_for_next_midnight_update() def _move_highlight(self, p_new_focus): """ Removes highlight from currently focused column and applies it on column with index equal to p_new_focus. """ self.columns.focus.highlight(False) self.columns.contents[p_new_focus][0].highlight(True) self.columns._invalidate() def _set_alarm_for_next_midnight_update(self): def callback(p_loop, p_data): TodoWidget.wipe_cache() self._update_all_columns() self._set_alarm_for_next_midnight_update() tomorrow = datetime.datetime.now() + datetime.timedelta(days=1) # turn it into midnight tomorrow = tomorrow.replace(hour=0, minute=0, second=0, microsecond=0) self.mainloop.set_alarm_at(time.mktime(tomorrow.timetuple()), callback) def _output(self, p_text): self._print_to_console(p_text) def _check_id_validity(self, p_ids): """ Checks if there are any invalid todo IDs in p_ids list. Returns proper error message if any ID is invalid and None otherwise. """ errors = [] valid_ids = self.todolist.ids() if len(p_ids) == 0: errors.append('No todo item was selected') else: errors = [ "Invalid todo ID: {}".format(todo_id) for todo_id in p_ids - valid_ids ] errors = '\n'.join(errors) if errors else None return errors def _execute_handler(self, p_command, p_todo_id=None, p_output=None): """ Executes a command, given as a string. """ p_output = p_output or self._output self._console_visible = False self._last_cmd = (p_command, p_output == self._output) try: p_command = shlex.split(p_command) except ValueError as verr: self._print_to_console('Error: ' + str(verr)) return try: subcommand, args = get_subcommand(p_command) except ConfigError as cerr: self._print_to_console( 'Error: {}. Check your aliases configuration.'.format(cerr)) return if subcommand is None: self._print_to_console(GENERIC_HELP) return env_args = (self.todolist, p_output, self._output, self._input) ids = None if '{}' in args: if self._has_marked_todos(): ids = self.marked_todos else: ids = {p_todo_id} if p_todo_id else set() invalid_ids = self._check_id_validity(ids) if invalid_ids: self._print_to_console('Error: ' + invalid_ids) return transaction = Transaction(subcommand, env_args, ids) transaction.prepare(args) label = transaction.label self._backup(subcommand, p_label=label) try: if transaction.execute(): post_archive_action = transaction.execute_post_archive_actions self._post_archive_action = post_archive_action self._post_execute() else: self._rollback() except TypeError: # TODO: show error message pass def _update_all_columns(self): for column, _ in self.columns.contents: column.update() column.keystate = None def _post_execute(self): # store dirty flag because base _post_execute will reset it after flush dirty = self.todolist.dirty super()._post_execute() if dirty or self.marked_todos: self._reset_state() def _rollback(self): try: self.backup.apply(self.todolist, p_archive=None) except AttributeError: pass def _repeat_last_cmd(self, p_todo_id=None): try: cmd, verbosity = self._last_cmd except TypeError: return self._execute_handler(cmd, p_todo_id, self._output if verbosity else lambda _: None) def _reset_state(self): for widget in TodoWidget.cache.values(): widget.unmark() self.marked_todos.clear() self._update_all_columns() def _blur_commandline(self): self._console_visible = False self.mainwindow.focus_item = 0 def _focus_commandline(self): self.mainwindow.focus_item = 1 def _focus_first_column(self): self.columns.focus_position = 0 def _focus_last_column(self): end_pos = len(self.columns.contents) - 1 self.columns.focus_position = end_pos def _focus_next_column(self): size = len(self.columns.contents) if self.columns.focus_position < size - 1: self.columns.focus_position += 1 def _focus_previous_column(self): if self.columns.focus_position > 0: self.columns.focus_position -= 1 def _append_column(self): self.viewwidget.reset() self.column_mode = _APPEND_COLUMN self._viewwidget_visible = True def _insert_column(self): self.viewwidget.reset() self.column_mode = _INSERT_COLUMN self._viewwidget_visible = True def _edit_column(self): self.viewwidget.data = self.columns.focus.view.data self.column_mode = _EDIT_COLUMN self._viewwidget_visible = True def _delete_column(self): try: focus = self.columns.focus_position del self.columns.contents[focus] if self.columns.contents: self.columns.focus_position = focus else: self._append_column() except IndexError: # no columns pass def _copy_column(self): self.viewwidget.data = self.columns.focus.view.data self.column_mode = _COPY_COLUMN self._viewwidget_visible = True def _column_action_handler(self, p_action): dispatch = { 'first_column': self._focus_first_column, 'last_column': self._focus_last_column, 'prev_column': self._focus_previous_column, 'next_column': self._focus_next_column, 'append_column': self._append_column, 'insert_column': self._insert_column, 'edit_column': self._edit_column, 'delete_column': self._delete_column, 'copy_column': self._copy_column, 'swap_left': self._swap_column_left, 'swap_right': self._swap_column_right, 'reset': self._reset_state, } dispatch[p_action]() def _handle_input(self, p_input): dispatch = { ':': self._focus_commandline, } try: dispatch[p_input]() except KeyError: # the key is unknown, ignore pass def _viewdata_to_view(self, p_data): """ Converts a dictionary describing a view to an actual UIView instance. """ sorter = Sorter(p_data['sortexpr'], p_data['groupexpr']) filters = [] if not p_data['show_all']: filters.append(DependencyFilter(self.todolist)) filters.append(RelevanceFilter()) filters.append(HiddenTagFilter()) filters += get_filter_list(p_data['filterexpr'].split()) return UIView(sorter, filters, self.todolist, p_data) def _update_view(self, p_data): """ Creates a view from the data entered in the view widget. """ view = self._viewdata_to_view(p_data) if self.column_mode == _APPEND_COLUMN or self.column_mode == _COPY_COLUMN: self._add_column(view) elif self.column_mode == _INSERT_COLUMN: self._add_column(view, self.columns.focus_position) elif self.column_mode == _EDIT_COLUMN: current_column = self.columns.focus current_column.title = p_data['title'] current_column.view = view self._viewwidget_visible = False self._blur_commandline() def _add_column(self, p_view, p_pos=None): """ Given an UIView, adds a new column widget with the todos in that view. When no position is given, it is added to the end, otherwise inserted before that position. """ def execute_silent(p_cmd, p_todo_id=None): self._execute_handler(p_cmd, p_todo_id, lambda _: None) todolist = TodoListWidget(p_view, p_view.data['title'], self.keymap) urwid.connect_signal(todolist, 'execute_command_silent', execute_silent) urwid.connect_signal(todolist, 'execute_command', self._execute_handler) urwid.connect_signal(todolist, 'repeat_cmd', self._repeat_last_cmd) urwid.connect_signal(todolist, 'refresh', self.mainloop.screen.clear) urwid.connect_signal(todolist, 'add_pending_action', self._set_alarm) urwid.connect_signal(todolist, 'remove_pending_action', self._remove_alarm) urwid.connect_signal(todolist, 'column_action', self._column_action_handler) urwid.connect_signal(todolist, 'show_keystate', self._print_keystate) urwid.connect_signal(todolist, 'toggle_mark', self._process_mark_toggle) options = self.columns.options(width_type='given', width_amount=config().column_width(), box_widget=True) item = (todolist, options) if p_pos == None: p_pos = len(self.columns.contents) self.columns.contents.insert(p_pos, item) self.columns.focus_position = p_pos self._blur_commandline() def _print_keystate(self, p_keystate): self.keystate_widget.set_text(p_keystate) self._keystate_visible = len(p_keystate) > 0 def _show_completion_box(self): contents = self.cli_wrapper.contents if len(contents) == 1: completion_box = self.commandline.completion_box opts = ('given', completion_box.height) max_width = self.cli_wrapper.width pos = self.commandline.get_cursor_coords((max_width, ))[0] l_margin = pos - completion_box.margin r_margin = max_width - pos - completion_box.min_width + completion_box.margin padding = urwid.Padding(completion_box, min_width=completion_box.min_width, left=l_margin, right=r_margin) contents.insert(0, (padding, opts)) completion_box.focus.set_attr_map({None: PaletteItem.MARKED}) self.cli_wrapper.focus_position = 1 def _hide_completion_box(self): contents = self.cli_wrapper.contents if len(contents) == 2: del contents[0] self.cli_wrapper.focus_position = 0 def _set_alarm(self, p_callback): """ Sets alarm to execute p_action specified in 0.5 sec. """ self._alarm = self.mainloop.set_alarm_in(0.5, p_callback) def _remove_alarm(self): """ Removes pending action alarm stored in _alarm attribute. """ self.mainloop.remove_alarm(self._alarm) self._alarm = None def _swap_column_left(self): pos = self.columns.focus_position if pos > 0: _columns = self.columns.contents _columns[pos], _columns[pos - 1] = _columns[pos - 1], _columns[pos] self.columns.focus_position -= 1 def _swap_column_right(self): pos = self.columns.focus_position _columns = self.columns.contents if pos < len(_columns) - 1: _columns[pos], _columns[pos + 1] = _columns[pos + 1], _columns[pos] self.columns.focus_position += 1 @property def _console_visible(self): contents = self.mainwindow.contents return len(contents) == 3 and isinstance(contents[2][0], ConsoleWidget) @_console_visible.setter def _console_visible(self, p_enabled): contents = self.mainwindow.contents if p_enabled == True and len(contents) == 2: contents.append((self.console, ('pack', None))) self.mainwindow.focus_position = 2 elif p_enabled == False and self._console_visible: self.console.clear() del contents[2] self.mainwindow.focus_position = 0 @property def _keystate_visible(self): contents = self.status_line.contents return len(contents) == 2 and isinstance( contents[1][0].original_widget, KeystateWidget) @_keystate_visible.setter def _keystate_visible(self, p_enabled): contents = self.status_line.contents if p_enabled and len(contents) == 1: contents.append( (urwid.Filler(self.keystate_widget), ('weight', 1, True))) elif not p_enabled and self._keystate_visible: del contents[1] @property def _viewwidget_visible(self): contents = self.mainwindow.contents return len(contents) == 3 and isinstance(contents[2][0], ViewWidget) @_viewwidget_visible.setter def _viewwidget_visible(self, p_enabled): contents = self.mainwindow.contents if p_enabled == True and len(contents) == 2: contents.append((self.viewwidget, ('pack', None))) self.mainwindow.focus_position = 2 elif p_enabled == False and self._viewwidget_visible: del contents[2] def _print_to_console(self, p_text): self._console_visible = True self.console.print_text(p_text) def _redraw(self): self.mainloop.draw_screen() def _input(self, p_question): self._print_to_console(p_question) # don't wait for the event loop to enter idle, there is a command # waiting for input right now, so already go ahead and draw the # question on screen. self._redraw() user_input = self.mainloop.screen.get_input() self._console_visible = False return user_input[0] def _console_width(self): terminal_size = namedtuple('Terminal_Size', 'columns lines') width = self.cli_wrapper.width - 2 sz = terminal_size(width, 1) return sz def _has_marked_todos(self): return len(self.marked_todos) > 0 def _process_mark_toggle(self, p_todo_id, p_force=None): """ Adds p_todo_id to marked_todos attribute and returns True if p_todo_id is not already marked. Removes p_todo_id from marked_todos and returns False otherwise. p_force parameter accepting 'mark' or 'unmark' values, if set, can force desired action without checking p_todo_id presence in marked_todos. """ if p_force in ['mark', 'unmark']: action = p_force else: action = 'mark' if p_todo_id not in self.marked_todos else 'unmark' if action == 'mark': self.marked_todos.add(p_todo_id) return True else: self.marked_todos.remove(p_todo_id) return False def run(self): layout = columns(self.alt_layout_path) if len(layout) > 0: for column in layout: self._add_column(self._viewdata_to_view(column)) else: dummy = { "title": "All tasks", "sortexpr": "desc:prio", "groupexpr": "", "filterexpr": "", "show_all": True, } self._add_column(self._viewdata_to_view(dummy)) # make sure that the first column is focused on startup self.columns.focus_position = 0 while True: try: self.mainloop.run() except KeyboardInterrupt: self._print_to_console( "Use the 'quit' command to exit topydo.")
def __init__(self): super().__init__() args = self._process_flags() try: opts, args = getopt.getopt(args[1:], 'l:') except getopt.GetoptError as e: error(str(e)) sys.exit(1) self.alt_layout_path = None for opt, value in opts: if opt == "-l": self.alt_layout_path = value def callback(): self.todolist.erase() self.todolist.add_list(self.todofile.read()) self._update_all_columns() self._redraw() self.column_width = config().column_width() self.todofile = TodoFileWatched(config().todotxt(), callback) self.todolist = TodoList.TodoList(self.todofile.read()) self.marked_todos = set() self.columns = urwid.Columns([], dividechars=0, min_width=config().column_width()) completer = ColumnCompleter(self.todolist) self.commandline = CommandLineWidget(completer, 'topydo> ') self.keystate_widget = KeystateWidget() self.status_line = urwid.Columns([ ('weight', 1, urwid.Filler(self.commandline)), ]) self.cli_wrapper = CliWrapper([(1, self.status_line)]) self.keymap = config().column_keymap() self._alarm = None self._last_cmd = None # console widget self.console = ConsoleWidget() get_terminal_size(self._console_width) urwid.connect_signal(self.commandline, 'blur', self._blur_commandline) urwid.connect_signal(self.commandline, 'execute_command', self._execute_handler) urwid.connect_signal(self.commandline, 'show_completions', self._show_completion_box) urwid.connect_signal(self.commandline, 'hide_completions', self._hide_completion_box) def hide_console(p_focus_commandline=False): if p_focus_commandline: self._focus_commandline() else: self._console_visible = False urwid.connect_signal(self.console, 'close', hide_console) # view widget self.viewwidget = ViewWidget(self.todolist) urwid.connect_signal(self.viewwidget, 'save', lambda: self._update_view(self.viewwidget.data)) def hide_viewwidget(): # prevent the view widget to be hidden when the last column was # deleted if self.columns.contents: self._viewwidget_visible = False self._blur_commandline() urwid.connect_signal(self.viewwidget, 'close', hide_viewwidget) self.mainwindow = MainPile([ ('weight', 1, self.columns), ('pack', self.cli_wrapper), ]) urwid.connect_signal(self.mainwindow, 'blur_console', hide_console) # the columns should have keyboard focus self._blur_commandline() self._screen = urwid.raw_display.Screen() if config().colors(): self._screen.register_palette(self._create_color_palette()) else: self._screen.register_palette(self._create_mono_palette()) self._screen.set_terminal_properties(256) self.mainloop = urwid.MainLoop(self.mainwindow, screen=self._screen, unhandled_input=self._handle_input, pop_ups=True) self.column_mode = _APPEND_COLUMN self._set_alarm_for_next_midnight_update()
def __init__(self): super().__init__() args = self._process_flags() try: opts, args = getopt.getopt(args[1:], 'l:') except getopt.GetoptError as e: error(str(e)) sys.exit(1) self.alt_layout_path = None for opt, value in opts: if opt == "-l": self.alt_layout_path = value def callback(): self.todolist.erase() self.todolist.add_list(self.todofile.read()) self._update_all_columns() self._redraw() self.column_width = config().column_width() self.todofile = TodoFileWatched(config().todotxt(), callback) self.todolist = TodoList.TodoList(self.todofile.read()) self.marked_todos = set() self.columns = urwid.Columns([], dividechars=0, min_width=config().column_width()) completer = ColumnCompleter(self.todolist) self.commandline = CommandLineWidget(completer, 'topydo> ') self.keystate_widget = KeystateWidget() self.status_line = urwid.Columns([ ('weight', 1, urwid.Filler(self.commandline)), ]) self.cli_wrapper = CliWrapper([(1, self.status_line)]) self.keymap = config().column_keymap() self._alarm = None self._last_cmd = None # console widget self.console = ConsoleWidget() get_terminal_size(self._console_width) urwid.connect_signal(self.commandline, 'blur', self._blur_commandline) urwid.connect_signal(self.commandline, 'execute_command', self._execute_handler) urwid.connect_signal(self.commandline, 'show_completions', self._show_completion_box) urwid.connect_signal(self.commandline, 'hide_completions', self._hide_completion_box) def hide_console(p_focus_commandline=False): if p_focus_commandline: self._focus_commandline() else: self._console_visible = False urwid.connect_signal(self.console, 'close', hide_console) # view widget self.viewwidget = ViewWidget(self.todolist) urwid.connect_signal(self.viewwidget, 'save', lambda: self._update_view(self.viewwidget.data)) def hide_viewwidget(): # prevent the view widget to be hidden when the last column was # deleted if self.columns.contents: self._viewwidget_visible = False self._blur_commandline() urwid.connect_signal(self.viewwidget, 'close', hide_viewwidget) self.mainwindow = MainPile([ ('weight', 1, self.columns), ('pack', self.cli_wrapper), ]) urwid.connect_signal(self.mainwindow, 'blur_console', hide_console) # the columns should have keyboard focus self._blur_commandline() self._screen = urwid.raw_display.Screen() def create_color_palette(): project_color = to_urwid_color(config().project_color()) context_color = to_urwid_color(config().context_color()) metadata_color = to_urwid_color(config().metadata_color()) link_color = to_urwid_color(config().link_color()) focus_background_color = to_urwid_color(config().focus_background_color()) marked_background_color = to_urwid_color(config().marked_background_color()) palette = [ (PaletteItem.PROJECT, '', '', '', project_color, ''), (PaletteItem.PROJECT_FOCUS, '', 'light gray', '', project_color, focus_background_color), (PaletteItem.CONTEXT, '', '', '', context_color, ''), (PaletteItem.CONTEXT_FOCUS, '', 'light gray', '', context_color, focus_background_color), (PaletteItem.METADATA, '', '', '', metadata_color, ''), (PaletteItem.METADATA_FOCUS, '', 'light gray', '', metadata_color, focus_background_color), (PaletteItem.LINK, '', '', '', link_color, ''), (PaletteItem.LINK_FOCUS, '', 'light gray', '', link_color, focus_background_color), (PaletteItem.DEFAULT_FOCUS, '', 'light gray', '', '', focus_background_color), (PaletteItem.MARKED, '', 'light blue', '', '', marked_background_color), ] for C in ascii_uppercase: pri_color_cfg = config().priority_color(C) pri_color = to_urwid_color(pri_color_cfg) pri_color_focus = pri_color if not pri_color_cfg.is_neutral() else 'black' palette.append(( 'pri_' + C, '', '', '', pri_color, '' )) palette.append(( 'pri_' + C + '_focus', '', 'light gray', '', pri_color_focus, focus_background_color )) return palette def create_mono_palette(): palette = [ (PaletteItem.DEFAULT_FOCUS, 'black', 'light gray'), (PaletteItem.PROJECT_FOCUS, PaletteItem.DEFAULT_FOCUS), (PaletteItem.CONTEXT_FOCUS, PaletteItem.DEFAULT_FOCUS), (PaletteItem.METADATA_FOCUS, PaletteItem.DEFAULT_FOCUS), (PaletteItem.LINK_FOCUS, PaletteItem.DEFAULT_FOCUS), (PaletteItem.MARKED, 'default,underline,bold', 'default'), ] for C in ascii_uppercase: palette.append( ('pri_' + C + '_focus', PaletteItem.DEFAULT_FOCUS) ) return palette if config().colors(): self._screen.register_palette(create_color_palette()) else: self._screen.register_palette(create_mono_palette()) self._screen.set_terminal_properties(256) self.mainloop = urwid.MainLoop( self.mainwindow, screen=self._screen, unhandled_input=self._handle_input, pop_ups=True ) self.column_mode = _APPEND_COLUMN self._set_alarm_for_next_midnight_update()