class _Expan(urwid.PopUpLauncher): signals = ['delete'] def __init__(self, edit_text, strike=False, new=False): self.strikethrough = strike self.new_expan = new # Spacing strings self.leading_space = ' ' self.leading_char = '- ' self.leading_STRIKE = 'x ' # Default color specs self.text_attr = AttrSpec('h6', '') self.text_STRIKE = AttrSpec('h6, strikethrough', '') self.focus_attr = AttrSpec('h6, bold', '') self.focus_STRIKE = AttrSpec('h6, bold, strikethrough', '') if not self.strikethrough: caption = self.leading_space + self.leading_char attr = self.text_attr attr_focus = self.focus_attr else: caption = self.leading_space + self.leading_STRIKE attr = self.text_STRIKE attr_focus = self.focus_STRIKE self.edit = Edit(caption, edit_text, wrap='clip') self.map = AttrMap(self.edit, attr_map=attr, focus_map=attr_focus) self.fill = Filler(self.map) super().__init__(self.map) def toggle_strike(self): if self.strikethrough: self.strikethrough = False attr = self.text_attr attr_focus = self.focus_attr self.map.set_attr_map({None: attr}) self.map.set_focus_map({None: attr_focus}) self.edit.set_caption(self.leading_space + self.leading_char) else: self.strikethrough = True caption = self.leading_space + self.leading_STRIKE attr = self.text_STRIKE attr_focus = self.focus_STRIKE self.map.set_attr_map({None: attr}) self.map.set_focus_map({None: attr_focus}) self.edit.set_caption(self.leading_space + self.leading_STRIKE) def create_pop_up(self): prompt = ConfPrompt('line') urwid.connect_signal(prompt, 'close', self.confirm_delete) return prompt def confirm_delete(self, obj): response = obj.response if response == 'yes': self.close_pop_up() self._emit('delete') else: self.close_pop_up() def get_pop_up_parameters(self): width = len(self.edit.text)-5 if len(self.edit.text)-5 > 21 else 21 return {'left': 5, 'top': 1, 'overlay_width': width, 'overlay_height': 1} def keypress(self, size, key): if self.new_expan: if self.edit.valid_char(key) or key == 'backspace': self.edit.set_edit_text('') self.new_expan= False super().keypress(size, key)
class TaskTag(urwid.PopUpLauncher): signals = ['delete'] size = None def __init__(self, tag_index, tag_text, new=False): self.new_tag = new op_char = tag_text[0] if op_char == 'o': self.strikethrough = False elif op_char == 'x': self.strikethrough = True tag_text = tag_text.lstrip(op_char) self.tag_index = tag_index self.tag_text = tag_text # Default color specs self.index_attr = AttrSpec('h11', '') self.index_STRIKE = AttrSpec('h11, strikethrough', '') self.text_attr = AttrSpec('', '') self.text_STRIKE = AttrSpec(', strikethrough', '') self.focus_attr = AttrSpec(', bold', '') self.focus_STRIKE = AttrSpec(', bold, strikethrough', '') # Build widget stack self.edit = Edit( caption=self.build_caption(), edit_text=self.tag_text, multiline=False, wrap ='clip') if not self.strikethrough: self.tag_map = AttrMap( self.edit, attr_map=self.text_attr, focus_map=self.focus_attr) else: self.tag_map = AttrMap( self.edit, attr_map=self.text_STRIKE, focus_map=self.focus_STRIKE) self.tag_fill = Filler(self.tag_map, 'top') super().__init__(self.tag_map) def build_caption(self, expan=False): trailing_space = ' ' if not expan else '*' caption_tag = '' if not self.strikethrough: caption_tag = str(self.tag_index) else: caption_tag = 'X' leading_space = ' ' if len(caption_tag) < 2 else '' if not self.strikethrough: caption = (self.index_attr, leading_space + caption_tag + trailing_space) else: caption = (self.index_STRIKE, leading_space + caption_tag + trailing_space) return caption def get_text(self): return self.edit.edit_text def move_cursor(self, translation): self.edit.edit_pos += translation def prompt_delete(self): self.open_pop_up() def toggle_strike(self): if self.strikethrough: self.strikethrough = False caption = self.build_caption() self.edit.set_caption(caption) self.tag_map.set_attr_map({None: self.text_attr}) self.tag_map.set_focus_map({None: self.focus_attr}) else: self.strikethrough = True caption = self.build_caption() self.edit.set_caption(caption) self.tag_map.set_attr_map({None: self.text_STRIKE}) self.tag_map.set_focus_map({None: self.focus_STRIKE}) def create_pop_up(self): prompt = ConfPrompt('line') urwid.connect_signal(prompt, 'close', self.confirm_delete) return prompt def confirm_delete(self, obj): response = obj.response if response == 'yes': self.close_pop_up() self._emit('delete') else: self.close_pop_up() def get_pop_up_parameters(self): width = len(self.edit.text)-3 if len(self.edit.text)-3 > 21 else 21 if width > self.size[0]-3: width = self.size[0]-3 return {'left': 3, 'top': 1, 'overlay_width': width, 'overlay_height': 1} def keypress(self, size, key): if self.new_tag: if self.edit.valid_char(key) or key == 'backspace': self.edit.set_edit_text('') self.new_tag = False super().keypress(size, key) def render(self, size, focus=False): self.size = size return super().render(size, focus)
class TList(urwid.WidgetWrap): def __init__(self, list_data=None): self.is_editing = False self.tasks = [] self.name = None self.group = None self.id = None if list_data: # Parse the data. self.parse_data(list_data) else: # Must be a new list self.name = 'untitled' self.group = 'none' self.id = uuid.uuid4().hex # AttrSpecs self.attr_spec = AttrSpec('', '') self.focus_nav = AttrSpec('h12', '') self.focus_ins = AttrSpec('h160', '') # Build widget stack self.title = TitleBar(self.name) self.group_foot = GroupFoot(self.group) self.body = urwid.SimpleFocusListWalker(self.tasks) self.list_box = ListBox(self.body) self.list_frame = Frame(self.list_box, header=self.title, footer=self.group_foot) self.line_box = LineBox(self.list_frame) self.line_attr = AttrMap(self.line_box, self.attr_spec, self.focus_nav) super().__init__(self.line_attr) def parse_data(self, list_data): self.name = list_data['name'] self.group = list_data['group'] self.id = list_data['id'] task_data = list_data['tasks'] index = 1 for task_dict in task_data: task = Task(index, task_dict) urwid.connect_signal(task, 'delete', self.delete) index += 1 self.tasks.append(task) def delete(self, obj): self.tasks.remove(obj) self.body.remove(obj) self.index_tasks() def move_task(self, trans): # FIX INDEX ERROR WHEN TRYING TO MOVE TO FAR DOWN OR UP focus_task = self.list_box.focus task_index = focus_task.tag.tag_index new_index = task_index + trans - 1 self.delete(focus_task) if new_index < 0: new_index = 0 elif new_index > len(self.tasks): new_index = len(self.tasks) self.tasks.insert(new_index, focus_task) self.body.insert(new_index, focus_task) self.list_box.focus_position = new_index self.index_tasks() def set_edit(self, editing): if editing: self.is_editing = True self.line_attr.set_focus_map({None: self.focus_ins}) else: self.is_editing = False self.line_attr.set_focus_map({None: self.focus_nav}) def export(self): data = { 'name': self.title.get_title(), 'group': self.group_foot.get_group(), 'id': self.id, 'tasks': [] } for task in self.tasks: task_dict = {} task_dict['id'] = task.id if task.tag.strikethrough: task_dict['tag'] = 'x' + task.tag.get_text() else: task_dict['tag'] = 'o' + task.tag.get_text() task_dict['expan'] = task.expan.get_lines() data['tasks'].append(task_dict) return data def keypress(self, size, key): if not self.is_editing: return self.nav_keypress(size, key) else: return self.input_keypress(size, key) def nav_keypress(self, size, key): if key == 'i': self.set_edit(True) # Move focus up/down elif key == 'j': self.move_focus(1) elif key == 'k': self.move_focus(-1) # Move cursor left/right elif key == 'J': self.move_task(1) elif key == 'K': self.move_task(-1) elif key == 'h': try: self.list_box.focus.move_cursor(-1) except AttributeError: pass elif key == 'l': try: self.list_box.focus.move_cursor(1) except AttributeError: pass # Open/close expan list elif key == 'e': try: self.list_box.focus.toggle_expan() except AttributeError: pass elif key == 'E': try: if not self.list_box.focus.show_expan: for task in self.tasks: task.open_expan() else: for task in self.tasks: task.close_expan() except AttributeError: pass # Cross out a line elif key == 'x': try: self.list_box.focus.toggle_strike() except AttributeError: pass elif key == 'n': self.title.edit() elif key == 'g': self.group_foot.edit() elif key == 'D': if self.list_box.focus: self.list_box.focus.prompt_delete() elif key == 't': new_task = Task() urwid.connect_signal(new_task, 'delete', self.delete) if self.list_box.focus: self.tasks.insert(self.list_box.focus_position + 1, new_task) self.body.insert(self.list_box.focus_position + 1, new_task) self.list_box.focus_position += 1 else: self.tasks.append(new_task) self.body.append(new_task) self.index_tasks() self.set_edit(True) elif key == 'T': if self.list_box.focus: focus = self.list_box.focus focus.new() self.set_edit(True) else: return key def input_keypress(self, size, key): if (key == 'esc' or key == 'enter'): self.set_edit(False) if self.list_frame.focus_position != 'body': self.name = self.title.edit.edit_text self.group = self.group_foot.edit.edit_text self.list_frame.focus_position = 'body' else: return super().keypress(size, key) def get_focus(self): task = self.list_box.focus return task def move_focus(self, trans): focus_task = self.list_box.focus if focus_task: sub_focus = focus_task.pile.focus if sub_focus == focus_task.expan: try: sub_focus.expan_pile.focus_position += trans except IndexError: try: focus_task.pile.focus_position += trans except IndexError: try: self.list_box.focus_position += trans except IndexError: pass else: try: focus_task.pile.focus_position += trans except IndexError: try: self.list_box.focus_position += trans except IndexError: pass def index_tasks(self): index = 1 for task in self.body: task.tag.tag_index = index task.tag.edit.set_caption(task.tag.build_caption()) index += 1