コード例 #1
0
    def __init__(self, todo_filename, text_editor=None, invert_icon=False):
        """Sets the filename, loads the list of items from the file, builds the
        indicator, &c."""
        if text_editor:
            self.text_editor = text_editor
        else:
            self.text_editor = DEFAULT_EDITOR

        if invert_icon:
            self.icon_path = DARK_ICON
        else:
            # Default to light icon, assuming dark panel background:
            self.icon_path = LIGHT_ICON

        # Initialize the main list object:
        self.todo_list = TodoTxtList(todo_filename)

        # Non-list menu items:
        self._setup_menu_items()

        # Necessary for threaded notifications:
        GObject.threads_init()

        # Does the GUI need to catch up with our list file?
        self.list_updated_flag = False

        # Creates self.ind, the main indicator object:
        self._build_indicator()

        # Starts up inotify, watches our list file:
        self._setup_inotify()

        # Add timeout function, allows threading to not fart all over itself.
        # Can't use Gobject.idle_add() since it rudely 100%s the CPU.
        GObject.timeout_add(500, self._update_if_todo_file_changed)
コード例 #2
0
    def test_to_text(self):
        test_list = TodoTxtList()

        # Empty list yields empty string:
        self.assertEqual('', str(test_list))

        todo_text = "(A) Do one thing\n         (B) Do another thing\n x One last thing"
        expected_output = "(A) Do one thing\n(B) Do another thing\nx One last thing"
        test_list.init_from_text(todo_text)
        self.assertEqual(expected_output, str(test_list))
コード例 #3
0
    def test_write_to_file(self):
        todo_text = "(A) Do one thing\n         (B) Do another thing\n x One last thing"
        expected_output = "(A) Do one thing\n(B) Do another thing\nx One last thing"
        test_list = TodoTxtList(None, todo_text)

        # Write to a temporary output file:
        output_file = tempfile.NamedTemporaryFile(mode='w+')
        test_list.todo_filename = output_file.name
        test_list.write_to_file()

        # Now read the file in and see that it all matches up:
        self.assertEqual(expected_output, output_file.read())
コード例 #4
0
    def test_remove_completed_items(self):
        todo_text = "(A) Item one\n(Z) Item two\nx Item three\n\n \n"
        test_list = TodoTxtList(None, todo_text)

        self.assertEqual(3, test_list.num_items())
        test_list.remove_completed_items()
        self.assertEqual(2, test_list.num_items())

        self.assertEqual('Item one', test_list.items[0].text)
        self.assertEqual('A', test_list.items[0].priority)
        self.assertFalse(test_list.items[0].is_completed)

        self.assertEqual('Item two', test_list.items[1].text)
        self.assertEqual('Z', test_list.items[1].priority)
        self.assertFalse(test_list.items[1].is_completed)
コード例 #5
0
    def test_mark_item_completed_with_full_text(self):
        todo_text = "(A) Item one\n(Z) Item two\nx Item three\n\n \n"
        test_list = TodoTxtList(None, todo_text)

        test_list.mark_item_completed_with_full_text('(Z) Item two')

        self.assertEqual('Item one', test_list.items[0].text)
        self.assertEqual('A', test_list.items[0].priority)
        self.assertFalse(test_list.items[0].is_completed)

        self.assertEqual('Item two', test_list.items[1].text)
        self.assertEqual('Z', test_list.items[1].priority)
        self.assertTrue(test_list.items[1].is_completed)

        self.assertEqual('Item three', test_list.items[2].text)
        self.assertEqual(None, test_list.items[2].priority)
        self.assertTrue(test_list.items[2].is_completed)
コード例 #6
0
    def test_sort_list(self):
        todo_text = "x (C) No biggie\n(Z) aaaaa\nNothing\n(B) hey hey\n(Z) bbbbb\n(A) aaaaa\nx Item three\n\nx (B) Done it\n"
        test_list = TodoTxtList(None, todo_text)

        test_list.sort_list()

        self.assertEqual(8, test_list.num_items())

        self.assertEqual('aaaaa', test_list.items[0].text)
        self.assertEqual('A', test_list.items[0].priority)
        self.assertFalse(test_list.items[0].is_completed)

        self.assertEqual('hey hey', test_list.items[1].text)
        self.assertEqual('B', test_list.items[1].priority)
        self.assertFalse(test_list.items[1].is_completed)

        self.assertEqual('aaaaa', test_list.items[2].text)
        self.assertEqual('Z', test_list.items[2].priority)
        self.assertFalse(test_list.items[2].is_completed)

        self.assertEqual('bbbbb', test_list.items[3].text)
        self.assertEqual('Z', test_list.items[3].priority)
        self.assertFalse(test_list.items[3].is_completed)

        self.assertEqual('Nothing', test_list.items[4].text)
        self.assertEqual(None, test_list.items[4].priority)
        self.assertFalse(test_list.items[4].is_completed)

        self.assertEqual('Done it', test_list.items[5].text)
        self.assertEqual('B', test_list.items[5].priority)
        self.assertTrue(test_list.items[5].is_completed)

        self.assertEqual('No biggie', test_list.items[6].text)
        self.assertEqual('C', test_list.items[6].priority)
        self.assertTrue(test_list.items[6].is_completed)

        self.assertEqual('Item three', test_list.items[7].text)
        self.assertEqual(None, test_list.items[7].priority)
        self.assertTrue(test_list.items[7].is_completed)
コード例 #7
0
    def test_init_from_file(self):
        file_name = 'sample-todo.txt'
        test_list = TodoTxtList(file_name)

        self.assertEqual(8, test_list.num_items())

        self.assertEqual('Do that really important thing', test_list.items[0].text)
        self.assertEqual('A', test_list.items[0].priority)
        self.assertFalse(test_list.items[0].is_completed)

        self.assertEqual('Summon AppIndicator documentation from my ass', test_list.items[1].text)
        self.assertEqual('D', test_list.items[1].priority)
        self.assertFalse(test_list.items[1].is_completed)

        self.assertEqual('This other important thing', test_list.items[2].text)
        self.assertEqual('A', test_list.items[2].priority)
        self.assertFalse(test_list.items[2].is_completed)

        self.assertEqual('Walk the cat', test_list.items[3].text)
        self.assertEqual('B', test_list.items[3].priority)
        self.assertFalse(test_list.items[3].is_completed)

        self.assertEqual('Something with no priority!', test_list.items[4].text)
        self.assertEqual(None, test_list.items[4].priority)
        self.assertFalse(test_list.items[4].is_completed)

        self.assertEqual('Cook the dog', test_list.items[5].text)
        self.assertEqual('C', test_list.items[5].priority)
        self.assertFalse(test_list.items[5].is_completed)

        self.assertEqual('Be annoyed at GTK3 docs', test_list.items[6].text)
        self.assertEqual(None, test_list.items[6].priority)
        self.assertTrue(test_list.items[6].is_completed)

        self.assertEqual('Something I already did', test_list.items[7].text)
        self.assertEqual(None, test_list.items[7].priority)
        self.assertTrue(test_list.items[7].is_completed)
コード例 #8
0
    def test_has_items(self):
        test_list = TodoTxtList()
        self.assertFalse(test_list.has_items())

        test_list = TodoTxtList(None, 'An item')
        self.assertTrue(test_list.has_items())
コード例 #9
0
class TodoTxtIndicator(object):

    def __init__(self, todo_filename, text_editor=None, invert_icon=False):
        """Sets the filename, loads the list of items from the file, builds the
        indicator, &c."""
        if text_editor:
            self.text_editor = text_editor
        else:
            self.text_editor = DEFAULT_EDITOR

        if invert_icon:
            self.icon_path = DARK_ICON
        else:
            # Default to light icon, assuming dark panel background:
            self.icon_path = LIGHT_ICON

        # Initialize the main list object:
        self.todo_list = TodoTxtList(todo_filename)

        # Non-list menu items:
        self._setup_menu_items()

        # Necessary for threaded notifications:
        GObject.threads_init()

        # Does the GUI need to catch up with our list file?
        self.list_updated_flag = False

        # Creates self.ind, the main indicator object:
        self._build_indicator()

        # Starts up inotify, watches our list file:
        self._setup_inotify()

        # Add timeout function, allows threading to not fart all over itself.
        # Can't use Gobject.idle_add() since it rudely 100%s the CPU.
        GObject.timeout_add(500, self._update_if_todo_file_changed)

    def _setup_inotify(self):
        """Watch for modifications of the todo file with pyinotify. We have to
        watch the entire path, since inotify is very inconsistent about what
        events it catches for a single file."""
        self.wm = pyinotify.WatchManager()
        self.notifier = pyinotify.ThreadedNotifier(self.wm,
                                                   self._process_inotify_event)
        self.notifier.start()

        # The IN_MOVED_TO watch catches Dropbox updates, which don't trigger
        # normal IN_MODIFY events.
        todo_path = os.path.dirname(self.todo_list.todo_filename)
        self.wm.add_watch(todo_path,
                          pyinotify.IN_MODIFY | pyinotify.IN_MOVED_TO)

    def _setup_menu_items(self):
        """Menu items (aside from the todo items themselves). An association of
        text and callback functions. Can't use a dict because we need to
        preserve order."""
        self._menu_items = [ ('Edit todo.txt', self._edit_handler),
                             ('Clear completed', self._clear_completed_handler),
                             ('Refresh', self._refresh_handler),
                             ('Quit', self._quit_handler) ]

    def _update_if_todo_file_changed(self):
        """This will be called by the main GTK thread every half second or so.
        If the self.list_updated_flag is False, it will immediately return. If
        it's True, it will rebuild the GUI with the updated list and reset the
        flag.  This is necessary since threads + GTK are wonky as f**k."""
        if self.list_updated_flag:
            self._build_indicator() # rebuild
            self.list_updated_flag = False # reset flag

        # If we don't explicitly return True here the callback will be removed
        # from the queue after one call and will never be called again.
        return True

    def _process_inotify_event(self, event):
        """This callback is typically an instance of the ProcessEvent class,
        but after digging through the pyinotify source it looks like it can
        also be a function? This makes things much easier in our case, avoids
        nested classes, etc.

        This function can't explicitly update the GUI since it's on a different
        thread, so it just flips a flag and lets another function called by the
        GTK main loop do the real work."""
        if event.pathname == self.todo_list.todo_filename:
            self.list_updated_flag = True

    def _check_off_handler(self, menu_item):
        """Checks off the item in our list that matches the clicked label. If
        you have multiple todo items that are exactly the same, this will check
        them all off. Also, you're stupid for doing that."""
        self.todo_list.mark_item_completed_with_full_text(menu_item.get_label())
        self.todo_list.write_to_file()
        self._build_indicator() # rebuild!

    def _edit_handler(self, menu_item):
        """Opens the todo.txt file with selected editor."""
        os.system(self.text_editor + " " + self.todo_list.todo_filename)

    def _clear_completed_handler(self, menu_item):
        """Remove checked off items, rebuild list menu."""
        self.todo_list.remove_completed_items()
        self.todo_list.write_to_file()
        self._build_indicator()

    def _refresh_handler(self, menu_item):
        """Manually refreshes the list."""
        self._build_indicator() # rebuild indicator

    def _quit_handler(self, menu_item):
        """Quits our fancy little program."""
        self.notifier.stop() # stop watching the file!
        Gtk.main_quit()

    def _add_list_menu_items(self, menu):
        """Creates menu items for each of our todo list items. Pass it a GTK
        menu object, it returns that object with menu items added."""
        for todo_item in sorted(self.todo_list.items): # Display items sorted
            menu_item = Gtk.MenuItem(str(todo_item))
            if todo_item.is_completed: # gray out completed items
                menu_item.set_sensitive(False)
            menu_item.connect("activate", self._check_off_handler)
            menu_item.show()
            menu.append(menu_item)
        return menu

    def _build_indicator(self):
        """Builds the Indicator object."""
        if not hasattr(self, 'ind'): # self.ind needs to be created
            self.ind = appindicator.Indicator.new("todo-txt-indicator",
                self.icon_path, appindicator.IndicatorCategory.OTHER)
            self.ind.set_status(appindicator.IndicatorStatus.ACTIVE)

        # Make sure the list is loaded:
        self.todo_list.reload_from_file()

        menu = Gtk.Menu()
        if self.todo_list.has_items():
            # Create todo menu items, if they exist:
            menu = self._add_list_menu_items(menu)
        else:
            # If the list is empty, show helpful message:
            menu_item = Gtk.MenuItem('[ No items. Click \'Edit\' to add some! ]')
            menu_item.set_sensitive(False)
            menu_item.show()
            menu.append(menu_item)

        # Add a separator:
        menu_item = Gtk.SeparatorMenuItem()
        menu_item.show()
        menu.append(menu_item)

        # Our menu:
        for text, callback in self._menu_items:
            menu_item = Gtk.MenuItem(text)
            menu_item.connect("activate", callback)
            menu_item.show()
            menu.append(menu_item)

        # Do it!
        self.ind.set_menu(menu)

    def main(self):
        """The indicator's main loop."""
        Gtk.main()