示例#1
0
    def __init__(self, requester):
        self.req = requester
        self.config = self.req.get_config("plugins")
        builder = Gtk.Builder()

        builder.add_from_file(ViewConfig.PLUGINS_UI_FILE)

        self.dialog = builder.get_object("PluginsDialog")
        self.dialog.set_title(_(f"Plugins - {info.NAME}"))
        self.plugin_tree = builder.get_object("PluginTree")
        self.plugin_configure = builder.get_object("plugin_configure")
        self.plugin_about = builder.get_object("PluginAboutDialog")
        self.plugin_depends = builder.get_object('PluginDepends')

        self.pengine = PluginEngine()

        # see constants PLUGINS_COL_* for column meanings
        self.plugin_store = Gtk.ListStore(str, bool, str, str, bool)

        builder.connect_signals({
            'on_PluginsDialog_delete_event':
            self.on_close,
            'on_PluginTree_cursor_changed':
            self.on_plugin_select,
            'on_plugin_about':
            self.on_plugin_about,
            'on_plugin_configure':
            self.on_plugin_configure,
            'on_PluginAboutDialog_close':
            self.on_plugin_about_close,
        })
示例#2
0
文件: manager.py 项目: thperret/gtg
 def __init_plugin_engine(self):
     self.pengine = PluginEngine(GTG.PLUGIN_DIR)
     # initializes the plugin api class
     self.plugin_api = PluginAPI(self.req, self)
     self.pengine.register_api(self.plugin_api)
     # checks the conf for user settings
     try:
         plugins_enabled = self.plugins_config.get("enabled")
     except configparser.Error:
         plugins_enabled = []
     for plugin in self.pengine.get_plugins():
         plugin.enabled = plugin.module_name in plugins_enabled
     # initializes and activates each plugin (that is enabled)
     self.pengine.activate_plugins()
    def __init__(self, config_obj):
        """Constructor."""
        self.config_obj = config_obj
        self.config = self.config_obj.conf_dict
        self.builder = gtk.Builder() 
        self.builder.add_from_file(ViewConfig.PREFERENCES_GLADE_FILE)
        # store references to some objects
        widgets = {
          'dialog': 'PreferencesDialog',
          'backend_tree': 'BackendTree',
          'plugin_tree': 'PluginTree',
          'plugin_about_dialog': 'PluginAboutDialog',
          'plugin_configure': 'plugin_configure',
          'plugin_depends': 'PluginDepends',
          'plugin_config_dialog': 'PluginConfigDialog',
          'pref_autostart': 'pref_autostart',
          'pref_show_preview': 'pref_show_preview'
          }
        for attr, widget in widgets.iteritems():
            setattr(self, attr, self.builder.get_object(widget))
        # keep a reference to the parent task browser
        #FIXME: this is not needed and should be removed
#        self.tb = taskbrowser
        self.pengine = PluginEngine()
        # initialize tree models
        self._init_backend_tree()
        # this can't happen yet, due to the order of things in
        #  TaskBrowser.__init__(). Happens in activate() instead.
        # self._init_plugin_tree()
        pref_signals_dic = self.get_signals_dict()
        self.builder.connect_signals(pref_signals_dic)
示例#4
0
文件: plugins.py 项目: jdiez17/gtg
    def __init__(self, requester):
        self.req = requester
        self.config = self.req.get_config("plugins")
        builder = Gtk.Builder()
        builder.add_from_file(ViewConfig.PLUGINS_UI_FILE)

        self.dialog = builder.get_object("PluginsDialog")
        self.dialog.set_title(_("Plugins - %s" % info.NAME))
        self.plugin_tree = builder.get_object("PluginTree")
        self.plugin_configure = builder.get_object("plugin_configure")
        self.plugin_about = builder.get_object("PluginAboutDialog")
        self.plugin_depends = builder.get_object("PluginDepends")

        help.add_help_shortcut(self.dialog, "plugins")

        self.pengine = PluginEngine()
        # plugin config initiation
        if self.pengine.get_plugins():
            self.config.set("disabled", [p.module_name for p in self.pengine.get_plugins("disabled")])
            self.config.set("enabled", [p.module_name for p in self.pengine.get_plugins("enabled")])

        # see constants PLUGINS_COL_* for column meanings
        self.plugin_store = Gtk.ListStore(str, bool, str, str, bool)

        builder.connect_signals(
            {
                "on_plugins_help": self.on_help,
                "on_plugins_close": self.on_close,
                "on_PluginsDialog_delete_event": self.on_close,
                "on_PluginTree_cursor_changed": self.on_plugin_select,
                "on_plugin_about": self.on_plugin_about,
                "on_plugin_configure": self.on_plugin_configure,
                "on_PluginAboutDialog_close": self.on_plugin_about_close,
            }
        )
示例#5
0
文件: application.py 项目: jscn/gtg
    def init_plugin_engine(self):
        """Setup the plugin engine."""

        self.plugin_engine = PluginEngine()
        plugin_api = PluginAPI(self.req, self)
        self.plugin_engine.register_api(plugin_api)

        try:
            enabled_plugins = self.config_plugins.get("enabled")
        except configparser.Error:
            enabled_plugins = []

        for plugin in self.plugin_engine.get_plugins():
            plugin.enabled = plugin.module_name in enabled_plugins

        self.plugin_engine.activate_plugins()
示例#6
0
    def __init__(self, config_obj):
        self.config_obj = config_obj
        self.config = self.config_obj.conf_dict
        builder = gtk.Builder()
        builder.add_from_file(ViewConfig.PLUGINS_GLADE_FILE)

        self.dialog = builder.get_object("PluginsDialog")
        self.dialog.set_title(_("Plugins - %s" % info.NAME))
        self.plugin_tree = builder.get_object("PluginTree")
        self.plugin_configure = builder.get_object("plugin_configure")
        self.plugin_about = builder.get_object("PluginAboutDialog")
        self.plugin_depends = builder.get_object('PluginDepends')

        self.pengine = PluginEngine()
        # plugin config initiation, if never used
        if "plugins" in self.config:
            if "enabled" not in self.config["plugins"]:
                self.config["plugins"]["enabled"] = []

            if "disabled" not in self.config["plugins"]:
                self.config["plugins"]["disabled"] = []
        elif self.pengine.get_plugins():
            self.config["plugins"] = {}
            self.config["plugins"]["disabled"] = \
                [p.module_name for p in self.pengine.get_plugins("disabled")]
            self.config["plugins"]["enabled"] = \
                [p.module_name for p in self.pengine.get_plugins("enabled")]

        # see constants PLUGINS_COL_* for column meanings
        self.plugin_store = gtk.ListStore(str, bool, str, str, bool)

        builder.connect_signals({
            'on_plugins_help':
            self.on_help,
            'on_plugins_close':
            self.on_close,
            'on_PluginsDialog_delete_event':
            self.on_close,
            'on_PluginTree_cursor_changed':
            self.on_plugin_select,
            'on_plugin_about':
            self.on_plugin_about,
            'on_plugin_configure':
            self.on_plugin_configure,
            'on_PluginAboutDialog_close':
            self.on_plugin_about_close,
        })
示例#7
0
    def __init__(self, requester):
        self.req = requester
        self.config = self.req.get_config("plugins")
        builder = Gtk.Builder()
        builder.add_from_file(ViewConfig.PLUGINS_UI_FILE)

        self.dialog = builder.get_object("PluginsDialog")
        self.dialog.set_title(_("Plugins - %s" % info.NAME))
        self.plugin_tree = builder.get_object("PluginTree")
        self.plugin_configure = builder.get_object("plugin_configure")
        self.plugin_about = builder.get_object("PluginAboutDialog")
        self.plugin_depends = builder.get_object('PluginDepends')

        help.add_help_shortcut(self.dialog, "plugins")

        self.pengine = PluginEngine()
        # plugin config initiation
        if self.pengine.get_plugins():
            self.config.set(
                "disabled",
                [p.module_name for p in self.pengine.get_plugins("disabled")],
            )
            self.config.set(
                "enabled",
                [p.module_name for p in self.pengine.get_plugins("enabled")],
            )

        # see constants PLUGINS_COL_* for column meanings
        self.plugin_store = Gtk.ListStore(str, bool, str, str, bool)

        builder.connect_signals({
            'on_plugins_help':
            self.on_help,
            'on_plugins_close':
            self.on_close,
            'on_PluginsDialog_delete_event':
            self.on_close,
            'on_PluginTree_cursor_changed':
            self.on_plugin_select,
            'on_plugin_about':
            self.on_plugin_about,
            'on_plugin_configure':
            self.on_plugin_configure,
            'on_PluginAboutDialog_close':
            self.on_plugin_about_close,
        })
示例#8
0
 def __init_plugin_engine(self):
     self.pengine = PluginEngine(GTG.PLUGIN_DIR)
     # initializes the plugin api class
     self.plugin_api = PluginAPI(self.req, self)
     self.pengine.register_api(self.plugin_api)
     # checks the conf for user settings
     try:
         plugins_enabled = self.config["plugins"]["enabled"]
     except KeyError:
         plugins_enabled = []
     for plugin in self.pengine.get_plugins():
         plugin.enabled = plugin.module_name in plugins_enabled
     # initializes and activates each plugin (that is enabled)
     self.pengine.activate_plugins()
示例#9
0
文件: manager.py 项目: jdiez17/gtg
 def __init_plugin_engine(self):
     self.pengine = PluginEngine()
     # initializes the plugin api class
     self.plugin_api = PluginAPI(self.req, self)
     self.pengine.register_api(self.plugin_api)
     # checks the conf for user settings
     try:
         plugins_enabled = self.plugins_config.get("enabled")
     except configparser.Error:
         plugins_enabled = []
     for plugin in self.pengine.get_plugins():
         plugin.enabled = plugin.module_name in plugins_enabled
     # initializes and activates each plugin (that is enabled)
     self.pengine.activate_plugins()
示例#10
0
    def __init__(self, config_obj):
        self.config_obj = config_obj
        self.config = self.config_obj.conf_dict
        builder = gtk.Builder()
        builder.add_from_file(ViewConfig.PLUGINS_GLADE_FILE)

        self.dialog = builder.get_object("PluginsDialog")
        self.dialog.set_title(_("Plugins - %s" % info.NAME))
        self.plugin_tree = builder.get_object("PluginTree")
        self.plugin_configure = builder.get_object("plugin_configure")
        self.plugin_about = builder.get_object("PluginAboutDialog")
        self.plugin_depends = builder.get_object('PluginDepends')

        self.pengine = PluginEngine()
        # plugin config initiation, if never used
        if "plugins" in self.config:
            if "enabled" not in self.config["plugins"]:
                self.config["plugins"]["enabled"] = []

            if "disabled" not in self.config["plugins"]:
                self.config["plugins"]["disabled"] = []
        elif self.pengine.get_plugins():
            self.config["plugins"] = {}
            self.config["plugins"]["disabled"] = \
                [p.module_name for p in self.pengine.get_plugins("disabled")]
            self.config["plugins"]["enabled"] = \
                [p.module_name for p in self.pengine.get_plugins("enabled")]

        # see constants PLUGINS_COL_* for column meanings
        self.plugin_store = gtk.ListStore(str, bool, str, str, bool)

        builder.connect_signals({
                                'on_plugins_help':
                                self.on_help,
                                'on_plugins_close':
                                self.on_close,
                                'on_PluginsDialog_delete_event':
                                self.on_close,
                                'on_PluginTree_cursor_changed':
                                self.on_plugin_select,
                                'on_plugin_about':
                                self.on_plugin_about,
                                'on_plugin_configure':
                                self.on_plugin_configure,
                                'on_PluginAboutDialog_close':
                                self.on_plugin_about_close,
                                })
示例#11
0
class Manager(object):
    

    ############## init #####################################################
    def __init__(self, req):
        self.req = req
        self.config_obj = self.req.get_global_config()
        self.config = self.config_obj.conf_dict
        self.task_config = self.config_obj.task_conf_dict
        
        # Editors
        self.opened_task  = {}   # This is the list of tasks that are already
                                 # opened in an editor of course it's empty
                                 # right now
                                 
        self.browser = None
        self.__start_browser_hidden = False
        self.gtk_terminate = False #if true, the gtk main is not started
                                 
        #Shared clipboard
        self.clipboard = clipboard.TaskClipboard(self.req)

        #Browser (still hidden)
        self.browser = TaskBrowser(self.req, self)
        
        self.__init_plugin_engine()
        
        if not self.__start_browser_hidden:
            self.show_browser()
        
        #Deletion UI
        self.delete_dialog = None
        
        #Preferences and Backends windows
        # Initialize  dialogs
        self.preferences_dialog = None
        self.edit_backends_dialog = None
        
        #DBus
        DBusTaskWrapper(self.req, self)
        Log.debug("Manager initialization finished")
        
    def __init_plugin_engine(self):
        self.pengine = PluginEngine(GTG.PLUGIN_DIR)
        # initializes the plugin api class
        self.plugin_api = PluginAPI(self.req, self)
        self.pengine.register_api(self.plugin_api)
        # checks the conf for user settings
        try:
            plugins_enabled = self.config["plugins"]["enabled"]
        except KeyError:
            plugins_enabled = []
        for plugin in self.pengine.get_plugins():
            plugin.enabled = plugin.module_name in plugins_enabled
        # initializes and activates each plugin (that is enabled)
        self.pengine.activate_plugins()
        
    ############## Browser #################################################

    def open_browser(self):
        if not self.browser:
            self.browser = TaskBrowser(self.req, self)
        Log.debug("Browser is open")

    #FIXME : the browser should not be the center of the universe.
    # In fact, we should build a system where view can register themselves
    # as "stay_alive" views. As long as at least one "stay_alive" view
    # is registered, gtg keeps running. It quit only when the last 
    # "stay_alive view" is closed (and then unregistered).
    # Currently, the browser is our only "stay_alive" view.
    def close_browser(self,sender=None):
        self.hide_browser()
        #may take a while to quit
        self.quit()

    def hide_browser(self,sender=None):
        self.browser.hide()

    def iconify_browser(self,sender=None):
        self.browser.iconify()

    def show_browser(self,sender=None):
        self.browser.show()
        
    def is_browser_visible(self,sender=None):
        return self.browser.is_visible()

    def get_browser(self):
        #used by the plugin api to hook in the browser
        return self.browser

    def start_browser_hidden(self):
        self.__start_browser_hidden = True

################# Task Editor ############################################

    def get_opened_editors(self):
        '''
        Returns a dict of task_uid -> TaskEditor, one for each opened editor
        window
        '''
        return self.opened_task

    def open_task(self, uid,thisisnew = False):
        """Open the task identified by 'uid'.

        If a Task editor is already opened for a given task, we present it.
        Else, we create a new one.
        """
        t = self.req.get_task(uid)
        tv = None
        if uid in self.opened_task:
            tv = self.opened_task[uid]
            tv.present()
        elif t:
            tv = TaskEditor(
                requester = self.req, \
                vmanager = self, \
                task = t, \
                taskconfig = self.task_config, \
                thisisnew = thisisnew,\
                clipboard = self.clipboard)
            #registering as opened
            self.opened_task[uid] = tv
        return tv

    def close_task(self, tid):
        # When an editor is closed, it should de-register itself.
        if tid in self.opened_task:
            #the following line has the side effect of removing the 
            # tid key in the opened_task dictionary.
            editor = self.opened_task[tid]
            if editor:
                del self.opened_task[tid]
                #we have to remove the tid from opened_task first
                #else, it close_task would be called once again 
                #by editor.close
                editor.close()
        self.check_quit_condition()

    def check_quit_condition(self):
        '''
        checking if we need to shut down the whole GTG (if no window is open)
        '''
        if not self.is_browser_visible() and not self.opened_task:
            #no need to live
            print "AAAAAAAAAAA"
            self.quit()
        print self.opened_task
            
################ Others dialog ############################################

    def open_edit_backends(self, sender = None, backend_id = None):
        if not self.edit_backends_dialog:
            self.edit_backends_dialog = BackendsDialog(self.req)
        self.edit_backends_dialog.activate()
        if backend_id != None:
            self.edit_backends_dialog.show_config_for_backend(backend_id)

    def configure_backend(self, backend_id):
        self.open_edit_backends(None, backend_id)

    def open_preferences(self, config_priv, sender=None):
        if not hasattr(self, "preferences"):
            self.preferences = PreferencesDialog(self.config_obj)
        self.preferences.activate(config_priv)
        
    def ask_delete_tasks(self, tids):
        if not self.delete_dialog:
            self.delete_dialog = DeletionUI(self.req)
        if self.delete_dialog.delete_tasks(tids):
            for t in tids:
                if t in self.opened_task:
                    self.close_task(t)

### URIS ###################################################################

    def open_uri_list(self, unused, uri_list):
        '''
        Open the Editor windows of the tasks associated with the uris given.
        Uris are of the form gtg://<taskid>
        '''
        print self.req.get_all_tasks_list()
        for uri in uri_list:
            if uri.startswith("gtg://"):
                self.open_task(uri[6:])
        #if no window was opened, we just quit
        self.check_quit_condition()

            
### MAIN ###################################################################
    def main(self, once_thru = False,  uri_list = []):
        if uri_list:
            #before opening the requested tasks, we make sure that all of them
            #are loaded.
            BackendSignals().connect('default-backend-loaded',
                                     self.open_uri_list,
                                     uri_list)
        else:
            self.open_browser()
        gobject.threads_init()
        if not self.gtk_terminate:
            if once_thru:
                gtk.main_iteration()
            else:
                gtk.main()
        return 0
        
    def quit(self,sender=None):
        gtk.main_quit()
        #save opened tasks and their positions.
        open_task = []
        for otid in self.opened_task.keys():     
            open_task.append(otid)
            self.opened_task[otid].close()
        self.config["browser"]["opened_tasks"] = open_task
        
        # adds the plugin settings to the conf
        #FIXME: this code is replicated in the preference window.
        if len(self.pengine.plugins) > 0:
            self.config["plugins"] = {}
            self.config["plugins"]["disabled"] = \
              [p.module_name for p in self.pengine.get_plugins("disabled")]
            self.config["plugins"]["enabled"] = \
              [p.module_name for p in self.pengine.get_plugins("enabled")]
        # plugins are deactivated
        self.pengine.deactivate_plugins()
示例#12
0
class PluginsDialog(object):
    """ Dialog for Plugins configuration """
    def __init__(self, requester):
        self.req = requester
        self.config = self.req.get_config("plugins")
        builder = Gtk.Builder()
        builder.add_from_file(ViewConfig.PLUGINS_UI_FILE)

        self.dialog = builder.get_object("PluginsDialog")
        self.dialog.set_title(_("Plugins - %s" % info.NAME))
        self.plugin_tree = builder.get_object("PluginTree")
        self.plugin_configure = builder.get_object("plugin_configure")
        self.plugin_about = builder.get_object("PluginAboutDialog")
        self.plugin_depends = builder.get_object('PluginDepends')

        help.add_help_shortcut(self.dialog, "plugins")

        self.pengine = PluginEngine()
        # plugin config initiation
        if self.pengine.get_plugins():
            self.config.set(
                "disabled",
                [p.module_name for p in self.pengine.get_plugins("disabled")],
            )
            self.config.set(
                "enabled",
                [p.module_name for p in self.pengine.get_plugins("enabled")],
            )

        # see constants PLUGINS_COL_* for column meanings
        self.plugin_store = Gtk.ListStore(str, bool, str, str, bool)

        builder.connect_signals({
            'on_plugins_help':
            self.on_help,
            'on_plugins_close':
            self.on_close,
            'on_PluginsDialog_delete_event':
            self.on_close,
            'on_PluginTree_cursor_changed':
            self.on_plugin_select,
            'on_plugin_about':
            self.on_plugin_about,
            'on_plugin_configure':
            self.on_plugin_configure,
            'on_PluginAboutDialog_close':
            self.on_plugin_about_close,
        })

    def _init_plugin_tree(self):
        """ Initialize the PluginTree Gtk.TreeView.

        The format is modelled after the one used in gedit; see
        http://git.gnome.org/browse/gedit/tree/gedit/gedit-plugin-mapnager.c
        """
        # force creation of the Gtk.ListStore so we can reference it
        self._refresh_plugin_store()

        # renderer for the toggle column
        renderer = Gtk.CellRendererToggle()
        renderer.set_property('xpad', 6)
        renderer.connect('toggled', self.on_plugin_toggle)
        # toggle column
        column = Gtk.TreeViewColumn(None,
                                    renderer,
                                    active=PLUGINS_COL_ENABLED,
                                    activatable=PLUGINS_COL_ACTIVATABLE,
                                    sensitive=PLUGINS_COL_ACTIVATABLE)
        self.plugin_tree.append_column(column)

        # plugin name column
        column = Gtk.TreeViewColumn()
        column.set_spacing(6)
        # icon renderer for the plugin name column
        icon_renderer = Gtk.CellRendererPixbuf()
        icon_renderer.set_property('stock-size', Gtk.IconSize.SMALL_TOOLBAR)
        icon_renderer.set_property('xpad', 3)
        column.pack_start(icon_renderer, False)
        column.set_cell_data_func(icon_renderer, plugin_icon)
        # text renderer for the plugin name column
        name_renderer = Gtk.CellRendererText()
        name_renderer.set_property('ellipsize', Pango.EllipsizeMode.END)
        column.pack_start(name_renderer, True)
        column.set_cell_data_func(name_renderer, plugin_markup, self)

        self.plugin_tree.append_column(column)

        # finish setup
        self.plugin_tree.set_model(self.plugin_store)
        self.plugin_tree.set_search_column(2)

    def _refresh_plugin_store(self):
        """ Refresh status of plugins and put it in a Gtk.ListStore """
        self.plugin_store.clear()
        self.pengine.recheck_plugin_errors(True)
        for name, plugin in self.pengine.plugins.items():
            # activateable if there is no error
            self.plugin_store.append(
                (name, plugin.enabled, plugin.full_name,
                 plugin.short_description, not plugin.error))

    def activate(self):
        """ Refresh status of plugins and show the dialog """
        if len(self.plugin_tree.get_columns()) == 0:
            self._init_plugin_tree()
        else:
            self._refresh_plugin_store()
        self.dialog.show_all()

    def on_close(self, widget, data=None):
        """ Close the plugins dialog."""
        self.dialog.hide()
        return True

    @classmethod
    def on_help(cls, widget):
        """ Open help for plugins """
        help.show_help("plugins")
        return True

    def on_plugin_toggle(self, widget, path):
        """Toggle a plugin enabled/disabled."""
        iterator = self.plugin_store.get_iter(path)
        plugin_id = self.plugin_store.get_value(iterator, PLUGINS_COL_ID)
        plugin = self.pengine.get_plugin(plugin_id)
        plugin.enabled = not self.plugin_store.get_value(
            iterator, PLUGINS_COL_ENABLED)
        plugins_enabled = self.config.get("enabled")
        plugins_disabled = self.config.get("disabled")
        if plugin.enabled:
            self.pengine.activate_plugins([plugin])
            plugins_enabled.append(plugin.module_name)
            if plugin.module_name in plugins_disabled:
                plugins_disabled.remove(plugin.module_name)
        else:
            self.pengine.deactivate_plugins([plugin])
            plugins_disabled.append(plugin.module_name)
            if plugin.module_name in plugins_enabled:
                plugins_enabled.remove(plugin.module_name)
        self.config.set("enabled", plugins_enabled)
        self.config.set("disabled", plugins_disabled)
        self.plugin_store.set_value(iterator, PLUGINS_COL_ENABLED,
                                    plugin.enabled)
        self._update_plugin_configure(plugin)

    def on_plugin_select(self, plugin_tree):
        """ Callback when user select/unselect a plugin

        Update the button "Configure plugin" sensitivity """
        model, iterator = plugin_tree.get_selection().get_selected()
        if iterator is not None:
            plugin_id = model.get_value(iterator, PLUGINS_COL_ID)
            plugin = self.pengine.get_plugin(plugin_id)
            self._update_plugin_configure(plugin)

    def _update_plugin_configure(self, plugin):
        """ Enable the button "Configure Plugin" appropriate. """
        configurable = plugin.active and plugin.is_configurable()
        self.plugin_configure.set_property('sensitive', configurable)

    def on_plugin_configure(self, widget):
        """ Show the dialog for plugin configuration """
        _, iterator = self.plugin_tree.get_selection().get_selected()
        if iterator is None:
            return
        plugin_id = self.plugin_store.get_value(iterator, PLUGINS_COL_ID)
        plugin = self.pengine.get_plugin(plugin_id)
        plugin.instance.configure_dialog(self.dialog)

    def on_plugin_about(self, widget):
        """ Display information about a plugin. """
        _, iterator = self.plugin_tree.get_selection().get_selected()
        if iterator is None:
            return
        plugin_id = self.plugin_store.get_value(iterator, PLUGINS_COL_ID)
        plugin = self.pengine.get_plugin(plugin_id)

        # FIXME About plugin dialog looks much more different than
        # it is in the current trunk
        # FIXME repair it!
        # FIXME Author is not usually set and is preserved from
        # previous plugin... :/
        self.plugin_about.set_program_name(plugin.full_name)
        self.plugin_about.set_version(plugin.version)
        authors = plugin.authors
        if isinstance(authors, str):
            authors = "\n".join(author.strip()
                                for author in authors.split(','))
            authors = [
                authors,
            ]
        self.plugin_about.set_authors(authors)
        description = plugin.description.replace(r'\n', "\n")
        self.plugin_about.set_comments(description)
        self.plugin_depends.set_label(plugin_error_text(plugin))
        self.plugin_about.show_all()

    def on_plugin_about_close(self, widget, data=None):
        """ Close the PluginAboutDialog. """
        self.plugin_about.hide()
        return True
示例#13
0
    def __init__(self, 
                 requester, 
                 vmanager, 
                 task, 
                 taskconfig = None,
                 thisisnew = False,
                 clipboard = None) :
        '''
        req is the requester
        vmanager is the view manager
        taskconfig is a ConfigObj dic to save infos about tasks
        thisisnew is True when a new task is created and opened
        '''
        self.req = requester
        self.vmanager = vmanager
        self.config = taskconfig
        self.time = None
        self.clipboard = clipboard
        self.builder = gtk.Builder()
        self.builder.add_from_file(GnomeConfig.GLADE_FILE)
        self.donebutton = self.builder.get_object("mark_as_done_editor")
        self.dismissbutton = self.builder.get_object("dismiss_editor")
        self.deletebutton = self.builder.get_object("delete_editor")
        self.deletebutton.set_tooltip_text(GnomeConfig.DELETE_TOOLTIP)
        self.subtask_button = self.builder.get_object("insert_subtask")
        self.subtask_button.set_tooltip_text(GnomeConfig.SUBTASK_TOOLTIP)
        self.inserttag_button = self.builder.get_object("inserttag")
        self.inserttag_button.set_tooltip_text(GnomeConfig.TAG_TOOLTIP)
        #Create our dictionary and connect it
        dic = {
                "mark_as_done_clicked"      : self.change_status,
                "on_dismiss"                : self.dismiss,
                "delete_clicked"            : self.delete_task,
                "on_duedate_pressed"        : (self.on_date_pressed,
                                               GTGCalendar.DATE_KIND_DUE),
                "on_startdate_pressed"      : (self.on_date_pressed,
                                               GTGCalendar.DATE_KIND_START),
                "on_closeddate_pressed"     : (self.on_date_pressed,
                                               GTGCalendar.DATE_KIND_CLOSED),
                "close_clicked"             : self.close,
                "duedate_changed"           : (self.date_changed,
                                               GTGCalendar.DATE_KIND_DUE),
                "startingdate_changed"      : (self.date_changed,
                                               GTGCalendar.DATE_KIND_START),
                "closeddate_changed"        : (self.date_changed,
                                               GTGCalendar.DATE_KIND_CLOSED),
                "on_insert_subtask_clicked" : self.insert_subtask,
                "on_inserttag_clicked"      : self.inserttag_clicked,
                "on_move"                   : self.on_move,
        }
        self.builder.connect_signals(dic)
        self.window         = self.builder.get_object("TaskEditor")
        #Removing the Normal textview to replace it by our own
        #So don't try to change anything with glade, this is a home-made widget
        textview = self.builder.get_object("textview")
        scrolled = self.builder.get_object("scrolledtask")
        scrolled.remove(textview)
        self.textview   = TaskView(self.req,self.clipboard)
        self.textview.show()
        self.textview.set_subtask_callback(self.new_subtask)
        self.textview.open_task_callback(self.vmanager.open_task)
        self.textview.set_left_margin(7)
        self.textview.set_right_margin(5)
        scrolled.add(self.textview)
        #Voila! it's done
        self.calendar       = GTGCalendar(self.builder)
        self.duedate_widget = self.builder.get_object("duedate_entry")
        self.startdate_widget = self.builder.get_object("startdate_entry")
        self.closeddate_widget = self.builder.get_object("closeddate_entry")
        self.dayleft_label  = self.builder.get_object("dayleft")
        self.tasksidebar = self.builder.get_object("tasksidebar")
        # Define accelerator keys
        self.init_accelerators()

        self.task = task
        tags = task.get_tags()
        self.textview.subtasks_callback(task.get_children)
        self.textview.removesubtask_callback(task.remove_child)
        self.textview.set_get_tagslist_callback(task.get_tags_name)
        self.textview.set_add_tag_callback(task.add_tag)
        self.textview.set_remove_tag_callback(task.remove_tag)
        self.textview.save_task_callback(self.light_save)

        texte = self.task.get_text()
        title = self.task.get_title()
        #the first line is the title
        self.textview.set_text("%s\n"%title)
        #we insert the rest of the task
        if texte :
            self.textview.insert("%s"%texte)
        else :
            #If not text, we insert tags
            if tags :
                for t in tags :
                    self.textview.insert_text("%s, "%t.get_name())
                self.textview.insert_text("\n")
            #If we don't have text, we still need to insert subtasks if any
            subtasks = task.get_children()
            if subtasks :
                self.textview.insert_subtasks(subtasks)
        #We select the title if it's a new task
        if thisisnew :
            self.textview.select_title()
        else :
            self.task.set_to_keep()
        self.textview.modified(full=True)
        self.window.connect("destroy", self.destruction)
        self.calendar.connect("date-changed", self.on_date_changed)

        # plugins
        self.pengine = PluginEngine()
        self.plugin_api = PluginAPI(self.req, self.vmanager, self)
        self.pengine.register_api(self.plugin_api)
        self.pengine.onTaskLoad(self.plugin_api)

        #Putting the refresh callback at the end make the start a lot faster
        self.textview.refresh_callback(self.refresh_editor)
        self.refresh_editor()
        self.textview.grab_focus()

        #restoring size and position, spatial tasks
        if self.config :
            tid = self.task.get_id()
            if tid in self.config:
                if "position" in self.config[tid]:
                    pos = self.config[tid]["position"]
                    self.move(pos[0],pos[1])
                    #print "restoring position %s %s" %(pos[0],pos[1])
                if "size" in self.config[tid]:
                    size = self.config[tid]["size"]
                    #print "size %s - %s" %(str(size[0]),str(size[1]))
                    #this eval(str()) is a ugly (!) hack to accept both int and str
                    #FIXME: Fix this!
                    self.window.resize(eval(str(size[0])),eval(str(size[1])))

        self.textview.set_editable(True)
        #Connection for the update
        self.req.connect('task-modified',self.task_modified)
        self.window.show()
示例#14
0
文件: editor.py 项目: kunaaljain/gtg
class TaskEditor(object):

    def __init__(self,
                 requester,
                 vmanager,
                 task,
                 taskconfig=None,
                 thisisnew=False,
                 clipboard=None):
        '''
        req is the requester
        vmanager is the view manager
        taskconfig is a ConfigParser to save infos about tasks
        thisisnew is True when a new task is created and opened
        '''
        self.req = requester
        self.browser_config = self.req.get_config('browser')
        self.vmanager = vmanager
        self.config = taskconfig
        self.time = None
        self.clipboard = clipboard
        self.builder = Gtk.Builder()
        self.builder.add_from_file(GnomeConfig.EDITOR_UI_FILE)
        self.donebutton = self.builder.get_object("mark_as_done_editor")
        self.dismissbutton = self.builder.get_object("dismiss_editor")
        self.deletebutton = self.builder.get_object("delete_editor")
        self.deletebutton.set_tooltip_text(GnomeConfig.DELETE_TOOLTIP)
        self.subtask_button = self.builder.get_object("insert_subtask")
        self.subtask_button.set_tooltip_text(GnomeConfig.SUBTASK_TOOLTIP)
        self.inserttag_button = self.builder.get_object("inserttag")
        self.inserttag_button.set_tooltip_text(GnomeConfig.TAG_TOOLTIP)
        self.open_parents_button = self.builder.get_object("open_parents")
        self.open_parents_button.set_tooltip_text(
            GnomeConfig.OPEN_PARENT_TOOLTIP)

        # Create our dictionary and connect it
        dic = {
            "mark_as_done_clicked": self.change_status,
            "on_dismiss": self.dismiss,
            "delete_clicked": self.delete_task,
            "on_duedate_pressed": lambda w: self.on_date_pressed(
                w, GTGCalendar.DATE_KIND_DUE),
            "on_startdate_pressed": lambda w: self.on_date_pressed(
                w, GTGCalendar.DATE_KIND_START),
            "on_closeddate_pressed": lambda w: self.on_date_pressed(
                w, GTGCalendar.DATE_KIND_CLOSED),
            "close_clicked": self.close,
            "duedate_changed": lambda w: self.date_changed(
                w, GTGCalendar.DATE_KIND_DUE),
            "duedate_focus_out": lambda w, e: self.date_focus_out(
                w, e, GTGCalendar.DATE_KIND_DUE),
            "startingdate_changed": lambda w: self.date_changed(
                w, GTGCalendar.DATE_KIND_START),
            "startdate_focus_out": lambda w, e: self.date_focus_out(
                w, e, GTGCalendar.DATE_KIND_START),
            "closeddate_changed": lambda w: self.date_changed(
                w, GTGCalendar.DATE_KIND_CLOSED),
            "closeddate_focus_out": lambda w, e: self.date_focus_out(
                w, e, GTGCalendar.DATE_KIND_CLOSED),
            "on_insert_subtask_clicked": self.insert_subtask,
            "on_inserttag_clicked": self.inserttag_clicked,
            "on_open_parent_clicked": self.open_parent_clicked,
            "on_move": self.on_move,
        }
        self.builder.connect_signals(dic)
        self.window = self.builder.get_object("TaskEditor")
        # Removing the Normal textview to replace it by our own
        # So don't try to change anything with glade, this is a home-made
        # widget
        textview = self.builder.get_object("textview")
        scrolled = self.builder.get_object("scrolledtask")
        scrolled.remove(textview)
        self.textview = TaskView(self.req, self.clipboard)
        self.textview.show()
        self.textview.set_subtask_callback(self.new_subtask)
        self.textview.open_task_callback(self.vmanager.open_task)
        self.textview.set_left_margin(7)
        self.textview.set_right_margin(5)
        scrolled.add(self.textview)
        conf_font_value = self.browser_config.get("font_name")
        if conf_font_value != "":
            self.textview.override_font(Pango.FontDescription(conf_font_value))
        # Voila! it's done
        self.calendar = GTGCalendar()
        self.calendar.set_transient_for(self.window)
        self.calendar.set_decorated(False)
        self.duedate_widget = self.builder.get_object("duedate_entry")
        self.startdate_widget = self.builder.get_object("startdate_entry")
        self.closeddate_widget = self.builder.get_object("closeddate_entry")
        self.dayleft_label = self.builder.get_object("dayleft")
        self.tasksidebar = self.builder.get_object("tasksidebar")
        # Define accelerator keys
        self.init_accelerators()

        self.task = task
        tags = task.get_tags()
        self.textview.subtasks_callback(task.get_children)
        self.textview.removesubtask_callback(task.remove_child)
        self.textview.set_get_tagslist_callback(task.get_tags_name)
        self.textview.set_add_tag_callback(task.add_tag)
        self.textview.set_remove_tag_callback(task.remove_tag)
        self.textview.save_task_callback(self.light_save)

        texte = self.task.get_text()
        title = self.task.get_title()
        # the first line is the title
        self.textview.set_text("%s\n" % title)
        # we insert the rest of the task
        if texte:
            self.textview.insert("%s" % texte)
        else:
            # If not text, we insert tags
            if tags:
                for t in tags:
                    self.textview.insert_text("%s, " % t.get_name())
                self.textview.insert_text("\n")
            # If we don't have text, we still need to insert subtasks if any
            subtasks = task.get_children()
            if subtasks:
                self.textview.insert_subtasks(subtasks)
        # We select the title if it's a new task
        if thisisnew:
            self.textview.select_title()
        else:
            self.task.set_to_keep()
        self.textview.modified(full=True)
        self.window.connect("destroy", self.destruction)
        self.calendar.connect("date-changed", self.on_date_changed)

        # plugins
        self.pengine = PluginEngine()
        self.plugin_api = PluginAPI(self.req, self.vmanager, self)
        self.pengine.register_api(self.plugin_api)
        self.pengine.onTaskLoad(self.plugin_api)

        # Putting the refresh callback at the end make the start a lot faster
        self.textview.refresh_callback(self.refresh_editor)
        self.refresh_editor()
        self.textview.grab_focus()

        # restoring size and position, spatial tasks
        if self.config is not None:
            tid = self.task.get_id()
            if self.config.has_section(tid):
                if self.config.has_option(tid, "position"):
                    pos_x, pos_y = self.config.get(tid, "position")
                    self.move(int(pos_x), int(pos_y))
                if self.config.has_option(tid, "size"):
                    width, height = self.config.get(tid, "size")
                    self.window.resize(int(width), int(height))

        self.textview.set_editable(True)
        self.window.show()

    # Define accelerator-keys for this dialog
    # TODO: undo/redo
    def init_accelerators(self):
        agr = Gtk.AccelGroup()
        self.window.add_accel_group(agr)

        # Escape and Ctrl-W close the dialog. It's faster to call close
        # directly, rather than use the close button widget
        key, modifier = Gtk.accelerator_parse('Escape')
        agr.connect(key, modifier, Gtk.AccelFlags.VISIBLE, self.close)

        key, modifier = Gtk.accelerator_parse('<Control>w')
        agr.connect(key, modifier, Gtk.AccelFlags.VISIBLE, self.close)

        # F1 shows help
        add_help_shortcut(self.window, "editor")

        # Ctrl-N creates a new task
        key, modifier = Gtk.accelerator_parse('<Control>n')
        agr.connect(key, modifier, Gtk.AccelFlags.VISIBLE, self.new_task)

        # Ctrl-Shift-N creates a new subtask
        insert_subtask = self.builder.get_object("insert_subtask")
        key, mod = Gtk.accelerator_parse("<Control><Shift>n")
        insert_subtask.add_accelerator('clicked', agr, key, mod,
                                       Gtk.AccelFlags.VISIBLE)

        # Ctrl-D marks task as done
        mark_as_done_editor = self.builder.get_object('mark_as_done_editor')
        key, mod = Gtk.accelerator_parse('<Control>d')
        mark_as_done_editor.add_accelerator('clicked', agr, key, mod,
                                            Gtk.AccelFlags.VISIBLE)

        # Ctrl-I marks task as dismissed
        dismiss_editor = self.builder.get_object('dismiss_editor')
        key, mod = Gtk.accelerator_parse('<Control>i')
        dismiss_editor.add_accelerator('clicked', agr, key, mod,
                                       Gtk.AccelFlags.VISIBLE)

        # Ctrl-Q quits GTG
        key, modifier = Gtk.accelerator_parse('<Control>q')
        agr.connect(key, modifier, Gtk.AccelFlags.VISIBLE, self.quit)

    # Can be called at any time to reflect the status of the Task
    # Refresh should never interfere with the TaskView.
    # If a title is passed as a parameter, it will become
    # the new window title. If not, we will look for the task title.
    # Refreshtext is whether or not we should refresh the TaskView
    # (doing it all the time is dangerous if the task is empty)
    def refresh_editor(self, title=None, refreshtext=False):
        if self.window is None:
            return
        to_save = False
        # title of the window
        if title:
            self.window.set_title(title)
            to_save = True
        else:
            self.window.set_title(self.task.get_title())

        status = self.task.get_status()
        dismiss_tooltip = GnomeConfig.MARK_DISMISS_TOOLTIP
        undismiss_tooltip = GnomeConfig.MARK_UNDISMISS_TOOLTIP
        if status == Task.STA_DISMISSED:
            self.donebutton.set_label(GnomeConfig.MARK_DONE)
            self.donebutton.set_tooltip_text(GnomeConfig.MARK_DONE_TOOLTIP)
            self.donebutton.set_icon_name("gtg-task-done")
            self.dismissbutton.set_label(GnomeConfig.MARK_UNDISMISS)
            self.dismissbutton.set_tooltip_text(undismiss_tooltip)
            self.dismissbutton.set_icon_name("gtg-task-undismiss")
        elif status == Task.STA_DONE:
            self.donebutton.set_label(GnomeConfig.MARK_UNDONE)
            self.donebutton.set_tooltip_text(GnomeConfig.MARK_UNDONE_TOOLTIP)
            self.donebutton.set_icon_name("gtg-task-undone")
            self.dismissbutton.set_label(GnomeConfig.MARK_DISMISS)
            self.dismissbutton.set_tooltip_text(dismiss_tooltip)
            self.dismissbutton.set_icon_name("gtg-task-dismiss")
        else:
            self.donebutton.set_label(GnomeConfig.MARK_DONE)
            self.donebutton.set_tooltip_text(GnomeConfig.MARK_DONE_TOOLTIP)
            self.donebutton.set_icon_name("gtg-task-done")
            self.dismissbutton.set_label(GnomeConfig.MARK_DISMISS)
            self.dismissbutton.set_tooltip_text(dismiss_tooltip)
            self.dismissbutton.set_icon_name("gtg-task-dismiss")
        self.donebutton.show()
        self.tasksidebar.show()

        # Refreshing the status bar labels and date boxes
        if status in [Task.STA_DISMISSED, Task.STA_DONE]:
            self.builder.get_object("label2").hide()
            self.builder.get_object("box1").hide()
            self.builder.get_object("label4").show()
            self.builder.get_object("box4").show()
        else:
            self.builder.get_object("label4").hide()
            self.builder.get_object("box4").hide()
            self.builder.get_object("label2").show()
            self.builder.get_object("box1").show()

        # refreshing the start date field
        startdate = self.task.get_start_date()
        try:
            prevdate = Date.parse(self.startdate_widget.get_text())
            update_date = startdate != prevdate
        except ValueError:
            update_date = True

        if update_date:
            self.startdate_widget.set_text(str(startdate))

        # refreshing the due date field
        duedate = self.task.get_due_date()
        try:
            prevdate = Date.parse(self.duedate_widget.get_text())
            update_date = duedate != prevdate
        except ValueError:
            update_date = True

        if update_date:
            self.duedate_widget.set_text(str(duedate))

        # refreshing the closed date field
        closeddate = self.task.get_closed_date()
        prevcldate = Date.parse(self.closeddate_widget.get_text())
        if closeddate != prevcldate:
            self.closeddate_widget.set_text(str(closeddate))

        # refreshing the day left label
        # If the task is marked as done, we display the delay between the
        # due date and the actual closing date. If the task isn't marked
        # as done, we display the number of days left.
        if status in [Task.STA_DISMISSED, Task.STA_DONE]:
            delay = self.task.get_days_late()
            if delay is None:
                txt = ""
            elif delay == 0:
                txt = "Completed on time"
            elif delay >= 1:
                txt = ngettext("Completed %(days)d day late",
                               "Completed %(days)d days late", delay) % \
                    {'days': delay}
            elif delay <= -1:
                abs_delay = abs(delay)
                txt = ngettext("Completed %(days)d day early",
                               "Completed %(days)d days early", abs_delay) % \
                    {'days': abs_delay}
        else:
            due_date = self.task.get_due_date()
            result = due_date.days_left()
            if due_date.is_fuzzy():
                txt = ""
            elif result > 0:
                txt = ngettext("Due tomorrow!", "%(days)d days left", result) \
                    % {'days': result}
            elif result == 0:
                txt = _("Due today!")
            elif result < 0:
                abs_result = abs(result)
                txt = ngettext("Due yesterday!", "Was %(days)d days ago",
                               abs_result) % {'days': abs_result}

        style_context = self.window.get_style_context()
        color = style_context.get_color(Gtk.StateFlags.INSENSITIVE).to_color()
        self.dayleft_label.set_markup(
            "<span color='%s'>%s</span>" % (color.to_string(), txt))

        # Refreshing the tag list in the insert tag button
        taglist = self.req.get_used_tags()
        menu = Gtk.Menu()
        tag_count = 0
        for tagname in taglist:
            tag_object = self.req.get_tag(tagname)
            if not tag_object.is_special() and \
                    not self.task.has_tags(tag_list=[tagname]):
                tag_count += 1
                mi = Gtk.MenuItem(label=tagname, use_underline=False)
                mi.connect("activate", self.inserttag, tagname)
                mi.show()
                menu.append(mi)
        if tag_count > 0:
            self.inserttag_button.set_menu(menu)

        # Refreshing the parent list in open_parent_button
        menu = Gtk.Menu()
        parents = self.task.get_parents()
        if len(parents) > 0:
            for parent in self.task.get_parents():
                task = self.req.get_task(parent)
                mi = Gtk.MenuItem(label=task.get_title(), use_underline=False)
                mi.connect("activate", self.open_parent, parent)
                mi.show()
                menu.append(mi)
            self.open_parents_button.set_menu(menu)
        else:
            self.open_parents_button.set_sensitive(False)

        if refreshtext:
            self.textview.modified(refresheditor=False)
        if to_save:
            self.light_save()

    def date_changed(self, widget, data):
        try:
            Date.parse(widget.get_text())
            valid = True
        except ValueError:
            valid = False

        if valid:
            # If the date is valid, we write with default color in the widget
            # "none" will set the default color.
            widget.override_color(Gtk.StateType.NORMAL, None)
            widget.override_background_color(Gtk.StateType.NORMAL, None)
        else:
            # We should write in red in the entry if the date is not valid
            text_color = Gdk.RGBA()
            text_color.parse("#F00")
            widget.override_color(Gtk.StateType.NORMAL, text_color)

            bg_color = Gdk.RGBA()
            bg_color.parse("#F88")
            widget.override_background_color(Gtk.StateType.NORMAL, bg_color)

    def date_focus_out(self, widget, event, date_kind):
        try:
            datetoset = Date.parse(widget.get_text())
        except ValueError:
            datetoset = None

        if datetoset is not None:
            if date_kind == GTGCalendar.DATE_KIND_START:
                self.task.set_start_date(datetoset)
            elif date_kind == GTGCalendar.DATE_KIND_DUE:
                self.task.set_due_date(datetoset)
            elif date_kind == GTGCalendar.DATE_KIND_CLOSED:
                self.task.set_closed_date(datetoset)
            self.refresh_editor()

    def on_date_pressed(self, widget, date_kind):
        """Called when a date-changing button is clicked."""
        if date_kind == GTGCalendar.DATE_KIND_DUE:
            if not self.task.get_due_date():
                date = self.task.get_start_date()
            else:
                date = self.task.get_due_date()
        elif date_kind == GTGCalendar.DATE_KIND_START:
            date = self.task.get_start_date()
        elif date_kind == GTGCalendar.DATE_KIND_CLOSED:
            date = self.task.get_closed_date()
        self.calendar.set_date(date, date_kind)
        # we show the calendar at the right position
        rect = widget.get_allocation()
        result, x, y = widget.get_window().get_origin()
        self.calendar.show_at_position(x + rect.x + rect.width,
                                       y + rect.y)

    def on_date_changed(self, calendar):
        date, date_kind = calendar.get_selected_date()
        if date_kind == GTGCalendar.DATE_KIND_DUE:
            self.task.set_due_date(date)
        elif date_kind == GTGCalendar.DATE_KIND_START:
            self.task.set_start_date(date)
        elif date_kind == GTGCalendar.DATE_KIND_CLOSED:
            self.task.set_closed_date(date)
        self.refresh_editor()

    def close_all_subtasks(self):
        all_subtasks = []

        def trace_subtasks(root):
            for i in root.get_subtasks():
                if i not in all_subtasks:
                    all_subtasks.append(i)
                    trace_subtasks(i)

        trace_subtasks(self.task)

        for task in all_subtasks:
            self.vmanager.close_task(task.get_id())

    def dismiss(self, widget):
        stat = self.task.get_status()
        if stat == Task.STA_DISMISSED:
            self.vmanager.ask_set_task_status(self.task, Task.STA_ACTIVE)
            self.refresh_editor()
        else:
            self.vmanager.ask_set_task_status(self.task, Task.STA_DISMISSED)
            self.close_all_subtasks()
            self.close(None)

    def change_status(self, widget):
        stat = self.task.get_status()
        if stat == Task.STA_DONE:
            self.vmanager.ask_set_task_status(self.task, Task.STA_ACTIVE)
            self.refresh_editor()
        else:
            self.vmanager.ask_set_task_status(self.task, Task.STA_DONE)
            self.close_all_subtasks()
            self.close(None)

    def delete_task(self, widget):
        # this triggers the closing of the window in the view manager
        if self.task.is_new():
            # self.req.delete_task(self.task.get_id())
            self.vmanager.close_task(self.task.get_id())
        else:
            self.vmanager.ask_delete_tasks([self.task.get_id()])

    # Take the title as argument and return the subtask ID
    def new_subtask(self, title=None, tid=None):
        if tid:
            self.task.add_child(tid)
        elif title:
            subt = self.task.new_subtask()
            subt.set_title(title)
            tid = subt.get_id()
        return tid

    # Create a new task
    def new_task(self, *args):
        task = self.req.new_task(newtask=True)
        task_id = task.get_id()
        self.vmanager.open_task(task_id)

    def insert_subtask(self, widget):
        self.textview.insert_newtask()
        self.textview.grab_focus()

    def inserttag_clicked(self, widget):
        itera = self.textview.get_insert()
        if itera.starts_line():
            self.textview.insert_text("@", itera)
        else:
            self.textview.insert_text(" @", itera)
        self.textview.grab_focus()

    def inserttag(self, widget, tag):
        self.textview.insert_tags([tag])
        self.textview.grab_focus()

    def open_parent_clicked(self, widget):
        self.vmanager.open_task(self.task.get_parents()[0])

    # On click handler for open_parent_button's menu items
    def open_parent(self, widget, tid):
        self.vmanager.open_task(tid)

    def save(self):
        self.task.set_title(self.textview.get_title())
        self.task.set_text(self.textview.get_text())
        self.task.sync()
        if self.config is not None:
            self.config.save()
        self.time = time.time()
    # light_save save the task without refreshing every 30seconds
    # We will reduce the time when the get_text will be in another thread

    def light_save(self):
        # if self.time is none, we never called any save
        if self.time:
            diff = time.time() - self.time
            tosave = diff > GnomeConfig.SAVETIME
        else:
            # we don't want to save a task while opening it
            tosave = self.textview.get_editable()
            diff = None
        if tosave:
            self.save()

    # This will bring the Task Editor to front
    def present(self):
        self.window.present()

    def move(self, x, y):
        try:
            xx = int(x)
            yy = int(y)
            self.window.move(xx, yy)
        except:
            pass

    def get_position(self):
        return self.window.get_position()

    def on_move(self, widget, event):
        # saving the position
        if self.config is not None:
            tid = self.task.get_id()
            if not self.config.has_section(tid):
                self.config.add_section(tid)
            self.config.set(tid, "position", self.get_position())
            self.config.set(tid, "size", self.window.get_size())

    # We define dummy variable for when close is called from a callback
    def close(self, window=None, a=None, b=None, c=None):

        # We should also destroy the whole taskeditor object.
        if self.window:
            self.window.destroy()
            self.window = None

    # The destroy signal is linked to the "close" button. So if we call
    # destroy in the close function, this will cause the close to be called
    # twice
    # To solve that, close will just call "destroy" and the destroy signal
    # Will be linked to this destruction method that will save the task
    def destruction(self, a=None):
        # Save should be also called when buffer is modified
        self.pengine.onTaskClose(self.plugin_api)
        self.pengine.remove_api(self.plugin_api)
        tid = self.task.get_id()
        if self.task.is_new():
            self.req.delete_task(tid)
        else:
            self.save()
            for i in self.task.get_subtasks():
                if i:
                    i.set_to_keep()
        self.vmanager.close_task(tid)

    def get_builder(self):
        return self.builder

    def get_task(self):
        return self.task

    def get_textview(self):
        return self.textview

    def get_window(self):
        return self.window

    def quit(self, accel_group=None, acceleratable=None, keyval=None,
             modifier=None):
        """Handles the accelerator for quitting GTG."""
        self.vmanager.quit()
示例#15
0
文件: editor.py 项目: tkdchen/gtg
class TaskEditor():

    EDITOR_UI_FILE = os.path.join(UI_DIR, "task_editor.ui")

    def __init__(self, requester, app, task, thisisnew=False, clipboard=None):
        """
        req is the requester
        app is the view manager
        thisisnew is True when a new task is created and opened
        """
        self.req = requester
        self.app = app
        self.browser_config = self.req.get_config('browser')
        self.config = self.req.get_task_config(task.get_id())
        self.time = None
        self.clipboard = clipboard
        self.builder = Gtk.Builder()
        self.builder.add_from_file(self.EDITOR_UI_FILE)
        self.donebutton = self.builder.get_object("mark_as_done")
        self.undonebutton = self.builder.get_object("mark_as_undone")
        self.dismissbutton = self.builder.get_object("dismiss")
        self.undismissbutton = self.builder.get_object("undismiss")
        self.add_subtask = self.builder.get_object("add_subtask")
        self.tag_store = self.builder.get_object("tag_store")
        self.parent_button = self.builder.get_object("parent")

        # Closed date
        self.closed_popover = self.builder.get_object("closed_popover")
        self.closed_entry = self.builder.get_object("closeddate_entry")
        self.closed_calendar = self.builder.get_object("calendar_closed")

        # Start date
        self.start_popover = self.builder.get_object("start_popover")
        self.start_entry = self.builder.get_object("startdate_entry")
        self.start_calendar = self.builder.get_object("calendar_start")

        # Due date
        self.due_popover = self.builder.get_object("due_popover")
        self.due_entry = self.builder.get_object("duedate_entry")
        self.due_calendar = self.builder.get_object("calendar_due")

        # Recurrence
        self.recurring_menu = RecurringMenu(self.req, task.tid, self.builder)

        # Create our dictionary and connect it
        dic = {
            "on_tags_popover":
            self.open_tags_popover,
            "on_tag_toggled":
            self.on_tag_toggled,
            "on_move":
            self.on_move,
            "set_recurring_term_every_day":
            self.set_recurring_term_every_day,
            "set_recurring_term_every_otherday":
            self.set_recurring_term_every_otherday,
            "set_recurring_term_every_week":
            self.set_recurring_term_every_week,
            "set_recurring_term_every_month":
            self.set_recurring_term_every_month,
            "set_recurring_term_every_year":
            self.set_recurring_term_every_year,
            "set_recurring_term_week_day":
            self.set_recurring_term_week_day,
            "set_recurring_term_calender_month":
            self.set_recurring_term_month,
            "set_recurring_term_calender_year":
            self.set_recurring_term_year,
            "toggle_recurring_status":
            self.toggle_recurring_status,
            "on_repeat_icon_toggled":
            self.on_repeat_icon_toggled,
            "show_popover_start":
            self.show_popover_start,
            "startingdate_changed":
            lambda w: self.date_changed(w, GTGCalendar.DATE_KIND_START),
            "startdate_cleared":
            lambda w: self.on_date_cleared(w, GTGCalendar.DATE_KIND_START),
            "startdate_focus_out":
            lambda w, e: self.date_focus_out(w, e, GTGCalendar.DATE_KIND_START
                                             ),
            "show_popover_due":
            self.show_popover_due,
            "duedate_changed":
            lambda w: self.date_changed(w, GTGCalendar.DATE_KIND_DUE),
            "duedate_now_selected":
            lambda w: self.on_duedate_fuzzy(w, Date.now()),
            "duedate_soon_selected":
            lambda w: self.on_duedate_fuzzy(w, Date.soon()),
            "duedate_someday_selected":
            lambda w: self.on_duedate_fuzzy(w, Date.someday()),
            "duedate_cleared":
            lambda w: self.on_date_cleared(w, GTGCalendar.DATE_KIND_DUE),
            "duedate_focus_out":
            lambda w, e: self.date_focus_out(w, e, GTGCalendar.DATE_KIND_DUE),
            "show_popover_closed":
            self.show_popover_closed,
            "closeddate_changed":
            lambda w: self.date_changed(w, GTGCalendar.DATE_KIND_CLOSED),
            "closeddate_focus_out":
            lambda w, e: self.date_focus_out(w, e, GTGCalendar.DATE_KIND_CLOSED
                                             ),
        }

        self.window = self.builder.get_object("TaskEditor")
        self.builder.connect_signals(dic)
        self.window.set_application(app)

        if task.has_parent():
            self.parent_button.set_label(_('Open Parent'))
        else:
            self.parent_button.set_label(_('Add Parent'))

        # Connect signals for the calendar
        self.start_handle = self.start_calendar.connect(
            'day-selected',
            lambda c: self.on_date_selected(c, GTGCalendar.DATE_KIND_START))

        self.due_handle = self.due_calendar.connect(
            'day-selected',
            lambda c: self.on_date_selected(c, GTGCalendar.DATE_KIND_DUE))

        self.closed_handle = self.closed_calendar.connect(
            'day-selected',
            lambda c: self.on_date_selected(c, GTGCalendar.DATE_KIND_CLOSED))

        # Removing the Normal textview to replace it by our own
        # So don't try to change anything with glade, this is a home-made
        # widget
        textview = self.builder.get_object("textview")
        scrolled = self.builder.get_object("scrolledtask")
        scrolled.remove(textview)
        self.textview = TaskView(self.req, self.clipboard)
        self.textview.show()
        scrolled.add(self.textview)
        conf_font_value = self.browser_config.get("font_name")
        if conf_font_value != "":
            self.textview.override_font(Pango.FontDescription(conf_font_value))

        self.textview.browse_tag_cb = app.select_tag
        self.textview.new_subtask_cb = self.new_subtask
        self.textview.get_subtasks_cb = task.get_children
        self.textview.delete_subtask_cb = self.remove_subtask
        self.textview.rename_subtask_cb = self.rename_subtask
        self.textview.open_subtask_cb = self.open_subtask
        self.textview.save_cb = self.light_save
        self.textview.add_tasktag_cb = task.tag_added
        self.textview.remove_tasktag_cb = task.remove_tag
        self.textview.refresh_cb = self.refresh_editor
        self.textview.get_tagslist_cb = task.get_tags_name
        self.textview.tid = task.tid

        # Voila! it's done
        self.textview.connect('focus-in-event', self.on_textview_focus_in)
        self.textview.connect('focus-out-event', self.on_textview_focus_out)
        """
        TODO(jakubbrindza): Once all the functionality in editor is back and
        working, bring back also the accelerators! Dayleft_label needs to be
        brought back, however its position is unsure.
        """
        # self.dayleft_label = self.builder.get_object("dayleft")

        self.task = task
        tags = task.get_tags()
        text = self.task.get_text()
        title = self.task.get_title()

        self.textview.buffer.set_text(f"{title}\n")

        if text:
            self.textview.insert(text)

            # Insert any remaining tags
            if tags:
                tag_names = [t.get_name() for t in tags]
                self.textview.insert_tags(tag_names)
        else:
            # If not text, we insert tags
            if tags:
                tag_names = [t.get_name() for t in tags]
                self.textview.insert_tags(tag_names)
                start = self.textview.buffer.get_end_iter()
                self.textview.buffer.insert(start, '\n')

        # Insert subtasks if they weren't inserted in the text
        subtasks = task.get_children()
        for sub in subtasks:
            if sub not in self.textview.subtasks['tags']:
                self.textview.insert_existing_subtask(sub)

        if thisisnew:
            self.textview.select_title()
        else:
            self.task.set_to_keep()

        self.window.connect("destroy", self.destruction)

        # Connect search field to tags popup
        self.tags_entry = self.builder.get_object("tags_entry")
        self.tags_tree = self.builder.get_object("tags_tree")

        self.tags_tree.set_search_entry(self.tags_entry)
        self.tags_tree.set_search_equal_func(self.search_function, None)

        # plugins
        self.pengine = PluginEngine()
        self.plugin_api = PluginAPI(self.req, self.app, self)
        self.pengine.register_api(self.plugin_api)
        self.pengine.onTaskLoad(self.plugin_api)

        # Putting the refresh callback at the end make the start a lot faster
        self.refresh_editor()
        self.textview.grab_focus()

        self.init_dimensions()

        self.window.insert_action_group('app', app)
        self.window.insert_action_group('win', app.browser)

        self.textview.set_editable(True)
        self.window.set_transient_for(self.app.browser)
        self.window.show()

    def show_popover_start(self, widget, event):
        """Open the start date calendar popup."""

        start_date = self.task.get_start_date() or Date.today()

        with signal_handler_block(self.start_calendar, self.start_handle):
            self.start_calendar.select_day(start_date.day)
            self.start_calendar.select_month(start_date.month - 1,
                                             start_date.year)

        self.start_popover.popup()

    def show_popover_due(self, widget, popover):
        """Open the due date calendar popup."""

        due_date = self.task.get_due_date()

        if not due_date or due_date.is_fuzzy():
            due_date = Date.today()

        with signal_handler_block(self.due_calendar, self.due_handle):
            self.due_calendar.select_day(due_date.day)
            self.due_calendar.select_month(due_date.month - 1, due_date.year)

        self.due_popover.popup()

    def show_popover_closed(self, widget, popover):
        """Open the closed date calendar popup."""

        closed_date = self.task.get_closed_date()

        with signal_handler_block(self.closed_calendar, self.closed_handle):
            self.closed_calendar.select_day(closed_date.day)
            self.closed_calendar.select_month(closed_date.month - 1,
                                              closed_date.year)

        self.closed_popover.popup()

    def open_tags_popover(self):
        self.tag_store.clear()

        tags = self.req.get_tag_tree().get_all_nodes()

        used_tags = self.task.get_tags()

        for tagname in tags:
            tag = self.req.get_tag(tagname)
            if tag_filter(tag):
                is_used = tag in used_tags
                self.tag_store.append([is_used, tagname])
                """
                TODO(jakubbrindza): add sorting of the tags based on
                True | False and within each sub-group arrange them
                alphabetically
                """

    def on_tag_toggled(self, widget, path, column):
        """We toggle by tag_row variable. tag_row is
        meant to be a tuple (is_used, tagname)"""
        tag_row = self.tag_store[path]
        tag_row[0] = not tag_row[0]

        if tag_row[0]:
            self.textview.insert_tags([tag_row[1]])
        """
        TODO(jakubbrindza): Add else case that will remove tag.
        """

    def on_repeat_icon_toggled(self, widget):
        """ Reset popup stack to the first page every time you open it """
        if widget.get_active():
            self.recurring_menu.reset_stack()

    def toggle_recurring_status(self, widget):
        self.recurring_menu.update_repeat_checkbox()
        self.refresh_editor()

    def set_recurring_term_every_day(self, widget):
        self.recurring_menu.set_selected_term('day')
        self.recurring_menu.update_term()
        self.refresh_editor()

    def set_recurring_term_every_otherday(self, widget):
        self.recurring_menu.set_selected_term('other-day')
        self.recurring_menu.update_term()
        self.refresh_editor()

    def set_recurring_term_every_week(self, widget):
        self.recurring_menu.set_selected_term('week')
        self.recurring_menu.update_term()
        self.refresh_editor()

    def set_recurring_term_every_month(self, widget):
        self.recurring_menu.set_selected_term('month')
        self.recurring_menu.update_term()
        self.refresh_editor()

    def set_recurring_term_every_year(self, widget):
        self.recurring_menu.set_selected_term('year')
        self.recurring_menu.update_term()
        self.refresh_editor()

    def set_recurring_term_week_day(self, widget):
        self.recurring_menu.set_selected_term(widget.get_property("name"))
        self.recurring_menu.update_term()
        self.refresh_editor()

    def set_recurring_term_month(self, widget):
        self.recurring_menu.set_selected_term(str(widget.get_date()[2]))
        self.recurring_menu.update_term()
        self.refresh_editor()

    def set_recurring_term_year(self, widget):
        month = str(widget.get_date()[1] + 1)
        day = str(widget.get_date()[2])
        if len(month) < 2:
            month = "0" + month
        if len(day) < 2:
            day = "0" + day
        self.recurring_menu.set_selected_term(month + day)
        self.recurring_menu.update_term()
        self.refresh_editor()

    def search_function(self, model, column, key, iter, *search_data):
        """Callback when searching in the tags popup."""

        if not key.startswith('@'):
            key = f'@{key}'

        # The return value is reversed. False if it matches, True
        # otherwise.
        return not model.get(iter, column)[0].startswith(key)

    def get_monitor_dimensions(self) -> Gdk.Rectangle:
        """Get dimensions for the first monitor."""

        monitor = Gdk.Display.get_default().get_monitor(0)
        return monitor.get_geometry()

    def init_dimensions(self):
        """ Restores position and size of task if possible """

        position = self.config.get('position')
        size = self.config.get('size')
        screen_size = self.get_monitor_dimensions()

        if size and len(size) == 2:
            try:
                self.window.resize(int(size[0]), int(size[1]))
            except ValueError:
                log.warning('Invalid size configuration for task %s: %s',
                            self.task.get_id(), size)

        can_move = True
        if position and len(position) == 2:
            try:
                x = max(0, int(position[0]))
                y = max(0, int(position[1]))
                can_move = True
            except ValueError:
                can_move = False
                log.warning('Invalid position configuration for task %s:%s',
                            self.task.get_id(), position)
        else:
            gdk_window = self.window.get_window()
            if gdk_window is None:
                log.debug("Using default display to position editor window")
                display = Gdk.Display.get_default()
            else:
                # TODO: AFAIK never happens because window is not realized at
                #       this point, but maybe we should just in case the display
                #       is actually different.
                display = gdk_window.get_display()
            seat = display.get_default_seat()
            pointer = seat.get_pointer()
            if pointer is None:
                can_move = False
                log.debug(
                    "Didn't receiver pointer info, can't move editor window")
            else:
                screen, x, y = pointer.get_position()
                assert isinstance(x, int)
                assert isinstance(y, int)

        if can_move:
            width, height = self.window.get_size()

            # Clamp positions to current screen size
            x = min(x, screen_size.width - width)
            y = min(y, screen_size.height - height)

            self.window.move(x, y)

    # Can be called at any time to reflect the status of the Task
    # Refresh should never interfere with the TaskView.
    # If a title is passed as a parameter, it will become
    # the new window title. If not, we will look for the task title.
    # Refreshtext is whether or not we should refresh the TaskView
    # (doing it all the time is dangerous if the task is empty)
    def refresh_editor(self, title=None, refreshtext=False):
        if self.window is None:
            return
        to_save = False
        # title of the window
        if title:
            self.window.set_title(title)
            to_save = True
        else:
            self.window.set_title(self.task.get_title())

        status = self.task.get_status()
        if status == Task.STA_DISMISSED:
            self.donebutton.show()
            self.undonebutton.hide()
            self.dismissbutton.hide()
            self.undismissbutton.show()
        elif status == Task.STA_DONE:
            self.donebutton.hide()
            self.undonebutton.show()
            self.dismissbutton.show()
            self.undismissbutton.hide
        else:
            self.donebutton.show()
            self.undonebutton.hide()
            self.dismissbutton.show()
            self.undismissbutton.hide()

        # Refreshing the parent button
        if self.task.has_parent():
            # Translators: Button label to open the parent task
            self.parent_button.set_label(_('Open Parent'))
        else:
            # Translators: Button label to add an new parent task
            self.parent_button.set_label(_('Add Parent'))

        # Refreshing the status bar labels and date boxes
        if status in [Task.STA_DISMISSED, Task.STA_DONE]:
            self.builder.get_object("start_box").hide()
            self.builder.get_object("closed_box").show()
        else:
            self.builder.get_object("closed_box").hide()
            self.builder.get_object("start_box").show()

        # refreshing the start date field
        startdate = self.task.get_start_date()
        try:
            prevdate = Date.parse(self.start_entry.get_text())
            update_date = startdate != prevdate
        except ValueError:
            update_date = True

        if update_date:
            self.start_entry.set_text(str(startdate))

        # refreshing the due date field
        duedate = self.task.get_due_date()
        try:
            prevdate = Date.parse(self.due_entry.get_text())
            update_date = duedate != prevdate
        except ValueError:
            update_date = True

        if update_date:
            self.due_entry.set_text(str(duedate))

        # refreshing the closed date field
        closeddate = self.task.get_closed_date()
        prevcldate = Date.parse(self.closed_entry.get_text())
        if closeddate != prevcldate:
            self.closed_entry.set_text(str(closeddate))

        # refreshing the day left label
        """
        TODO(jakubbrindza): re-enable refreshing the day left.
        We need to come up how and where this information is viewed
        in the editor window.
        """
        # self.refresh_day_left()

        if refreshtext:
            self.textview.modified(refresheditor=False)
        if to_save:
            self.light_save()

    def refresh_day_left(self):
        # If the task is marked as done, we display the delay between the
        # due date and the actual closing date. If the task isn't marked
        # as done, we display the number of days left.
        status = self.task.get_status()
        if status in [Task.STA_DISMISSED, Task.STA_DONE]:
            delay = self.task.get_days_late()
            if delay is None:
                txt = ""
            elif delay == 0:
                txt = "Completed on time"
            elif delay >= 1:
                txt = ngettext("Completed %(days)d day late",
                               "Completed %(days)d days late", delay) % \
                    {'days': delay}
            elif delay <= -1:
                abs_delay = abs(delay)
                txt = ngettext("Completed %(days)d day early",
                               "Completed %(days)d days early", abs_delay) % \
                    {'days': abs_delay}
        else:
            due_date = self.task.get_due_date()
            result = due_date.days_left()
            if due_date.is_fuzzy():
                txt = ""
            elif result > 0:
                txt = ngettext("Due tomorrow!", "%(days)d days left", result)\
                    % {'days': result}
            elif result == 0:
                txt = _("Due today!")
            elif result < 0:
                abs_result = abs(result)
                txt = ngettext("Due yesterday!", "Was %(days)d days ago",
                               abs_result) % {
                                   'days': abs_result
                               }

        style_context = self.window.get_style_context()
        color = style_context.get_color(Gtk.StateFlags.INSENSITIVE).to_color()
        self.dayleft_label.set_markup(
            f"<span color='{color.to_string()}'>{txt}</span>")

    def reload_editor(self):
        task = self.task
        textview = self.textview
        task_text = task.get_text()
        task_title = task.get_title()
        textview.set_text(f"{task_title}\n")
        if task_text:
            textview.insert(f"{task_text}")
        task.set_title(task_title)
        textview.modified(full=True)

    def date_changed(self, widget, data):
        try:
            Date.parse(widget.get_text())
            valid = True
        except ValueError:
            valid = False

        if valid:
            # If the date is valid, we write with default color in the widget
            # "none" will set the default color.
            widget.override_color(Gtk.StateType.NORMAL, None)
            widget.override_background_color(Gtk.StateType.NORMAL, None)
        else:
            # We should write in red in the entry if the date is not valid
            text_color = Gdk.RGBA()
            text_color.parse("#F00")
            widget.override_color(Gtk.StateType.NORMAL, text_color)

            bg_color = Gdk.RGBA()
            bg_color.parse("#F88")
            widget.override_background_color(Gtk.StateType.NORMAL, bg_color)

    def date_focus_out(self, widget, event, date_kind):
        try:
            datetoset = Date.parse(widget.get_text())
        except ValueError:
            datetoset = None

        if datetoset is not None:
            if date_kind == GTGCalendar.DATE_KIND_START:
                self.task.set_start_date(datetoset)
                self.start_popover.popdown()

            elif date_kind == GTGCalendar.DATE_KIND_DUE:
                self.task.set_due_date(datetoset)
                self.due_popover.popdown()

            elif date_kind == GTGCalendar.DATE_KIND_CLOSED:
                self.task.set_closed_date(datetoset)
                self.closed_popover.popdown()

            self.refresh_editor()

    def calendar_to_datetime(self, calendar):
        """
        Gtk.Calendar uses a 0-based convention for counting months.
        The rest of the world, including the datetime module, starts from 1.
        This is a converter between the two. GTG follows the datetime
        convention.
        """

        year, month, day = calendar.get_date()
        return datetime.date(year, month + 1, day)

    def on_duedate_fuzzy(self, widget, date):
        """ Callback when a fuzzy date is selected through the popup. """

        self.task.set_due_date(date)
        self.due_entry.set_text(str(date))

    def on_date_cleared(self, widget, kind):
        """ Callback when a date is cleared through the popups. """

        if kind == GTGCalendar.DATE_KIND_START:
            self.task.set_start_date(Date.no_date())
            self.start_entry.set_text('')

        elif kind == GTGCalendar.DATE_KIND_DUE:
            self.task.set_due_date(Date.no_date())
            self.due_entry.set_text('')

    def on_date_selected(self, calendar, kind):
        """ Callback when a day is selected in the calendars."""

        date = self.calendar_to_datetime(calendar)

        if kind == GTGCalendar.DATE_KIND_START:
            self.task.set_start_date(Date(date))
            self.start_entry.set_text(str(Date(date)))

        elif kind == GTGCalendar.DATE_KIND_DUE:
            self.task.set_due_date(Date(date))
            self.due_entry.set_text(str(Date(date)))

        elif kind == GTGCalendar.DATE_KIND_CLOSED:
            self.task.set_closed_date(Date(date))
            self.closed_entry.set_text(str(Date(date)))

    def on_date_changed(self, calendar):
        date, date_kind = calendar.get_selected_date()
        if date_kind == GTGCalendar.DATE_KIND_DUE:
            self.task.set_due_date(date)
        elif date_kind == GTGCalendar.DATE_KIND_START:
            self.task.set_start_date(date)
        elif date_kind == GTGCalendar.DATE_KIND_CLOSED:
            self.task.set_closed_date(date)
        self.refresh_editor()

    def close_all_subtasks(self):
        all_subtasks = []

        def trace_subtasks(root):
            for i in root.get_subtasks():
                if i not in all_subtasks:
                    all_subtasks.append(i)
                    trace_subtasks(i)

        trace_subtasks(self.task)

        for task in all_subtasks:
            self.app.close_task(task.get_id())

    def dismiss(self):
        stat = self.task.get_status()
        if stat == Task.STA_DISMISSED:
            self.task.set_status(Task.STA_ACTIVE)
            self.refresh_editor()
        else:
            self.task.set_status(Task.STA_DISMISSED)
            self.close_all_subtasks()
            self.close(None)

    def change_status(self):
        stat = self.task.get_status()
        if stat == Task.STA_DONE:
            self.task.set_status(Task.STA_ACTIVE)
            self.refresh_editor()
        else:
            self.task.set_status(Task.STA_DONE)
            self.close_all_subtasks()
            self.close(None)

    def open_subtask(self, tid):
        """Open subtask (closing parent task)."""

        task = self.req.get_task(tid)
        self.app.open_task(tid)
        self.app.close_task(task.parents[0])

    # Take the title as argument and return the subtask ID
    def new_subtask(self, title=None, tid=None):
        if tid:
            self.task.add_child(tid)
        elif title:
            subt = self.task.new_subtask()
            subt.set_title(title)
            tid = subt.get_id()
        return tid

    def remove_subtask(self, tid):
        """Remove a subtask of this task."""

        self.task.remove_child(tid)

    def rename_subtask(self, tid, new_title):
        """Rename a subtask of this task."""

        try:
            self.req.get_task(tid).set_title(new_title)
        except AttributeError:
            # There's no task at that tid
            pass

    def insert_subtask(self, action=None, param=None):
        self.textview.insert_new_subtask()
        self.textview.grab_focus()

    def inserttag_clicked(self, widget):
        itera = self.textview.get_insert()
        if itera.starts_line():
            self.textview.insert_text("@", itera)
        else:
            self.textview.insert_text(" @", itera)

    def open_parent(self):
        """
        Open (or create) the parent task,
        then close the child to avoid various window management issues
        and to prevent visible content divergence when the child title changes.
        """
        parents = self.task.get_parents()

        if not parents:
            tags = [t.get_name() for t in self.task.get_tags()]
            parent = self.req.new_task(tags=tags, newtask=True)
            parent_id = parent.get_id()

            self.task.set_parent(parent_id)
            self.app.open_task(parent_id)
            # Prevent WM issues and risks of conflicting content changes:
            self.close()

        elif len(parents) == 1:
            self.app.open_task(parents[0])
            # Prevent WM issues and risks of conflicting content changes:
            self.close()

        elif len(parents) > 1:
            self.show_multiple_parent_popover(parents)

    def show_multiple_parent_popover(self, parent_ids):
        parent_box = Gtk.Box.new(Gtk.Orientation.VERTICAL, 0)
        for parent in parent_ids:
            parent_name = self.req.get_task(parent).get_title()
            button = Gtk.ToolButton.new(None, parent_name)
            button.connect("clicked", self.on_parent_item_clicked, parent)
            parent_box.add(button)

        self.parent_popover = Gtk.Popover.new(self.parent_button)
        self.parent_popover.add(parent_box)
        self.parent_popover.set_property("border-width", 0)
        self.parent_popover.set_position(Gtk.PositionType.BOTTOM)
        self.parent_popover.set_transitions_enabled(True)
        self.parent_popover.show_all()

    # On click handler for open_parent menu items in the case of multiple parents
    def on_parent_item_clicked(self, widget, parent_id):
        self.app.open_task(parent_id)
        if self.parent_popover.get_visible():
            self.parent_popover.hide()
        # Prevent WM issues and risks of conflicting content changes:
        self.close()

    def save(self):
        self.task.set_title(self.textview.get_title())
        self.task.set_text(self.textview.get_text())
        self.task.sync()
        if self.config is not None:
            self.config.save()
        self.time = time.time()

    # light_save save the task without refreshing every 30seconds
    # We will reduce the time when the get_text will be in another thread

    def light_save(self):
        # if self.time is none, we never called any save
        if self.time:
            diff = time.time() - self.time
            tosave = diff > GnomeConfig.SAVETIME
        else:
            # we don't want to save a task while opening it
            tosave = self.textview.get_editable()
            diff = None
        if tosave:
            self.save()

    def present(self):
        # This tries to bring the Task Editor to the front.
        # If TaskEditor is a "utility" window type, this doesn't work on X11,
        # it only works on GNOME's Wayland session, unless the child is closed.
        # This is partly why we use self.close() in various places elsewhere.
        self.window.present()

    def get_position(self):
        return self.window.get_position()

    def on_move(self, widget, event):
        """ Save position and size of window """

        self.config.set('position', list(self.window.get_position()))
        self.config.set('size', list(self.window.get_size()))

    def on_textview_focus_in(self, widget, event):
        self.app.browser.toggle_delete_accel(False)

    def on_textview_focus_out(self, widget, event):
        self.app.browser.toggle_delete_accel(True)

    # We define dummy variable for when close is called from a callback
    def close(self, action=None, param=None):

        # We should also destroy the whole taskeditor object.
        if self.window:
            self.window.destroy()
            self.window = None

    def destruction(self, _=None):
        """Callback when destroying the window."""

        # Save should be also called when buffer is modified
        self.pengine.onTaskClose(self.plugin_api)
        self.pengine.remove_api(self.plugin_api)

        tid = self.task.get_id()

        if self.task.is_new():
            self.req.delete_task(tid)
        else:
            self.save()
            [sub.set_to_keep() for sub in self.task.get_subtasks() if sub]

        try:
            del self.app.open_tasks[tid]
        except KeyError:
            log.debug('Task %s was already removed from the open list', tid)

    def get_builder(self):
        return self.builder

    def get_task(self):
        return self.task

    def get_textview(self):
        return self.textview

    def get_window(self):
        return self.window
示例#16
0
    def __init__(self,
                 requester,
                 vmanager,
                 task,
                 taskconfig=None,
                 thisisnew=False,
                 clipboard=None):
        '''
        req is the requester
        vmanager is the view manager
        taskconfig is a ConfigObj dic to save infos about tasks
        thisisnew is True when a new task is created and opened
        '''
        self.req = requester
        self.browser_config = self.req.get_config('browser')
        self.vmanager = vmanager
        self.config = taskconfig
        self.time = None
        self.clipboard = clipboard
        self.builder = gtk.Builder()
        self.builder.add_from_file(GnomeConfig.GLADE_FILE)
        self.donebutton = self.builder.get_object("mark_as_done_editor")
        self.dismissbutton = self.builder.get_object("dismiss_editor")
        self.deletebutton = self.builder.get_object("delete_editor")
        self.deletebutton.set_tooltip_text(GnomeConfig.DELETE_TOOLTIP)
        self.subtask_button = self.builder.get_object("insert_subtask")
        self.subtask_button.set_tooltip_text(GnomeConfig.SUBTASK_TOOLTIP)
        self.inserttag_button = self.builder.get_object("inserttag")
        self.inserttag_button.set_tooltip_text(GnomeConfig.TAG_TOOLTIP)

        # Create our dictionary and connect it
        dic = {
            "mark_as_done_clicked":
            self.change_status,
            "on_dismiss":
            self.dismiss,
            "delete_clicked":
            self.delete_task,
            "on_duedate_pressed":
            (self.on_date_pressed, GTGCalendar.DATE_KIND_DUE),
            "on_startdate_pressed":
            (self.on_date_pressed, GTGCalendar.DATE_KIND_START),
            "on_closeddate_pressed":
            (self.on_date_pressed, GTGCalendar.DATE_KIND_CLOSED),
            "close_clicked":
            self.close,
            "duedate_changed": (self.date_changed, GTGCalendar.DATE_KIND_DUE),
            "duedate_focus_out":
            (self.date_focus_out, GTGCalendar.DATE_KIND_DUE),
            "startingdate_changed": (self.date_changed,
                                     GTGCalendar.DATE_KIND_START),
            "startdate_focus_out": (self.date_focus_out,
                                    GTGCalendar.DATE_KIND_START),
            "closeddate_changed": (self.date_changed,
                                   GTGCalendar.DATE_KIND_CLOSED),
            "closeddate_focus_out": (self.date_focus_out,
                                     GTGCalendar.DATE_KIND_CLOSED),
            "on_insert_subtask_clicked":
            self.insert_subtask,
            "on_inserttag_clicked":
            self.inserttag_clicked,
            "on_move":
            self.on_move,
        }
        self.builder.connect_signals(dic)
        self.window = self.builder.get_object("TaskEditor")
        # Removing the Normal textview to replace it by our own
        # So don't try to change anything with glade, this is a home-made
        # widget
        textview = self.builder.get_object("textview")
        scrolled = self.builder.get_object("scrolledtask")
        scrolled.remove(textview)
        self.textview = TaskView(self.req, self.clipboard)
        self.textview.show()
        self.textview.set_subtask_callback(self.new_subtask)
        self.textview.open_task_callback(self.vmanager.open_task)
        self.textview.set_left_margin(7)
        self.textview.set_right_margin(5)
        scrolled.add(self.textview)
        conf_font_value = self.browser_config.get("font_name")
        if conf_font_value != "":
            self.textview.modify_font(pango.FontDescription(conf_font_value))
        # Voila! it's done
        self.calendar = GTGCalendar(self.builder)
        self.duedate_widget = self.builder.get_object("duedate_entry")
        self.startdate_widget = self.builder.get_object("startdate_entry")
        self.closeddate_widget = self.builder.get_object("closeddate_entry")
        self.dayleft_label = self.builder.get_object("dayleft")
        self.tasksidebar = self.builder.get_object("tasksidebar")
        # Define accelerator keys
        self.init_accelerators()

        self.task = task
        tags = task.get_tags()
        self.textview.subtasks_callback(task.get_children)
        self.textview.removesubtask_callback(task.remove_child)
        self.textview.set_get_tagslist_callback(task.get_tags_name)
        self.textview.set_add_tag_callback(task.add_tag)
        self.textview.set_remove_tag_callback(task.remove_tag)
        self.textview.save_task_callback(self.light_save)

        texte = self.task.get_text()
        title = self.task.get_title()
        # the first line is the title
        self.textview.set_text("%s\n" % title)
        # we insert the rest of the task
        if texte:
            self.textview.insert("%s" % texte)
        else:
            # If not text, we insert tags
            if tags:
                for t in tags:
                    self.textview.insert_text("%s, " % t.get_name())
                self.textview.insert_text("\n")
            # If we don't have text, we still need to insert subtasks if any
            subtasks = task.get_children()
            if subtasks:
                self.textview.insert_subtasks(subtasks)
        # We select the title if it's a new task
        if thisisnew:
            self.textview.select_title()
        else:
            self.task.set_to_keep()
        self.textview.modified(full=True)
        self.window.connect("destroy", self.destruction)
        self.calendar.connect("date-changed", self.on_date_changed)

        # plugins
        self.pengine = PluginEngine()
        self.plugin_api = PluginAPI(self.req, self.vmanager, self)
        self.pengine.register_api(self.plugin_api)
        self.pengine.onTaskLoad(self.plugin_api)

        # Putting the refresh callback at the end make the start a lot faster
        self.textview.refresh_callback(self.refresh_editor)
        self.refresh_editor()
        self.textview.grab_focus()

        # restoring size and position, spatial tasks
        if self.config:
            tid = self.task.get_id()
            if tid in self.config:
                if "position" in self.config[tid]:
                    pos_x, pos_y = self.config[tid]["position"]
                    self.move(int(pos_x), int(pos_y))
                if "size" in self.config[tid]:
                    width, height = self.config[tid]["size"]
                    self.window.resize(int(width), int(height))

        self.textview.set_editable(True)
        self.window.show()
示例#17
0
class Application(Gtk.Application):

    ds: Datastore2 = Datastore2()
    """Datastore loaded with the default data file"""

    # Requester
    req = None

    # List of opened tasks (task editor windows). Task IDs are keys,
    # while the editors are their values.
    open_tasks = {}

    # The main window (AKA Task Browser)
    browser = None

    # Configuration sections
    config = None
    config_plugins = None

    # Shared clipboard
    clipboard = None

    # Timer to refresh views and purge tasks
    timer = None

    # Plugin Engine instance
    plugin_engine = None

    # Dialogs
    preferences_dialog = None
    plugins_dialog = None
    backends_dialog = None
    delete_task_dialog = None
    edit_tag_dialog = None

    def __init__(self, app_id):
        """Setup Application."""

        self._exception = None
        """Exception that occurred in startup, None otherwise"""

        self._exception_dialog_timeout_id = None
        """
        Gio Source ID of an timer used to automatically kill GTG on
        startup error.
        """

        super().__init__(application_id=app_id,
                         flags=Gio.ApplicationFlags.HANDLES_OPEN)
        self.set_option_context_parameter_string("[gtg://TASK-ID…]")

    # --------------------------------------------------------------------------
    # INIT
    # --------------------------------------------------------------------------

    def do_startup(self):
        """Callback when primary instance should initialize"""
        try:
            Gtk.Application.do_startup(self)
            Gtk.Window.set_default_icon_name(self.props.application_id)

            # Load default file
            data_file = os.path.join(DATA_DIR, 'gtg_data.xml')
            self.ds.find_and_load_file(data_file)

            # TODO: Remove this once the new core is stable
            self.ds.data_path = os.path.join(DATA_DIR, 'gtg_data2.xml')

            # Register backends
            datastore = DataStore()

            for backend_dic in BackendFactory().get_saved_backends_list():
                datastore.register_backend(backend_dic)

            # Save the backends directly to be sure projects.xml is written
            datastore.save(quit=False)

            self.req = datastore.get_requester()

            self.config = self.req.get_config("browser")
            self.config_plugins = self.req.get_config("plugins")

            self.clipboard = clipboard.TaskClipboard(self.req)

            self.timer = Timer(self.config)
            self.timer.connect('refresh', self.autoclean)

            self.preferences_dialog = Preferences(self.req, self)
            self.plugins_dialog = PluginsDialog(self.req)

            if self.config.get('dark_mode'):
                self.toggle_darkmode()

            self.init_style()
        except Exception as e:
            self._exception = e
            log.exception("Exception during startup")
            self._exception_dialog_timeout_id = GLib.timeout_add(
                # priority is a kwarg for some reason not reflected in the docs
                5000, self._startup_exception_timeout, None)
             # Don't re-raise to not trigger the global exception hook

    def do_activate(self):
        """Callback when launched from the desktop."""

        if self._check_exception():
            return

        try:
            self.init_shared()
            self.browser.present()

            log.debug("Application activation finished")
        except Exception as e:
            log.exception("Exception during activation")
            dialog = do_error_dialog(self._exception, "Activation", ignorable=False)
            dialog.set_application(self)  # Keep application alive to show it

    def do_open(self, files, n_files, hint):
        """Callback when opening files/tasks"""

        if self._check_exception():
            return

        try:
            self.init_shared()
            len_files = len(files)
            log.debug("Received %d Task URIs", len_files)
            if len_files != n_files:
                log.warning("Length of files %d != supposed length %d", len_files, n_files)

            for file in files:
                if file.get_uri_scheme() == 'gtg':
                    uri = file.get_uri()
                    if uri[4:6] != '//':
                        log.info("Malformed URI, needs gtg://:%s", uri)
                    else:
                        parsed = urllib.parse.urlparse(uri)
                        task_id = parsed.netloc
                        log.debug("Opening task %s", task_id)
                        self.open_task(task_id)
                else:
                    log.info("Unknown task to open: %s", file.get_uri())

            log.debug("Application opening finished")
        except Exception as e:
            log.exception("Exception during opening")
            dialog = do_error_dialog(self._exception, "Opening", ignorable=False)
            dialog.set_application(self)  # Keep application alive to show it

    def _check_exception(self) -> bool:
        """
        Checks whenever an error occured before at startup, and shows an dialog.
        Returns True whenever such error occurred, False otherwise.
        """
        if self._exception is not None:
            GLib.Source.remove(self._exception_dialog_timeout_id)
            self._exception_dialog_timeout_id = None
            dialog = do_error_dialog(self._exception, "Startup", ignorable=False)
            dialog.set_application(self)  # Keep application alive, for now
        return self._exception is not None

    def _startup_exception_timeout(self, user_data):
        """
        Called when an exception in startup occurred, but didn't go over
        activation/opening a "file" within a specified amount of time.
        This can be caused by for example trying to call an DBus service,
        get an error during startup.

        It also means GTG was started in the background, so showing the user
        suddenly an error message isn't great, and thus this will just exit
        the application.

        Since this is an error case, the normal shutdown procedure isn't used
        but rather a python exit.
        """
        log.info("Exiting because of startup exception timeout")
        GLib.Source.remove(self._exception_dialog_timeout_id)
        self._exception_dialog_timeout_id = None
        sys.exit(1)

    def init_shared(self):
        """
        Initialize stuff that can't be done in the startup signal,
        but in the open or activate signals, otherwise GTK will segfault
        when creating windows in the startup signal
        """
        if not self.browser:  # Prevent multiple inits
            self.init_browser()
            self.init_actions()
            self.init_plugin_engine()
            self.browser.present()

    def init_browser(self):
        # Browser (still hidden)
        if not self.browser:
            self.browser = MainWindow(self.req, self)

            if self.props.application_id == 'org.gnome.GTGDevel':
                self.browser.get_style_context().add_class('devel')

    def init_plugin_engine(self):
        """Setup the plugin engine."""

        self.plugin_engine = PluginEngine()
        plugin_api = PluginAPI(self.req, self)
        self.plugin_engine.register_api(plugin_api)

        try:
            enabled_plugins = self.config_plugins.get("enabled")
        except configparser.Error:
            enabled_plugins = []

        for plugin in self.plugin_engine.get_plugins():
            plugin.enabled = plugin.module_name in enabled_plugins

        self.plugin_engine.activate_plugins()

    def init_style(self):
        """Load the application's CSS file."""

        screen = Gdk.Screen.get_default()
        provider = Gtk.CssProvider()
        add_provider = Gtk.StyleContext.add_provider_for_screen
        css_path = os.path.join(CSS_DIR, 'style.css')

        provider.load_from_path(css_path)
        add_provider(screen, provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)

    def toggle_darkmode(self, state=True):
        """Use dark mode theme."""

        settings = Gtk.Settings.get_default()
        prefs_css = self.preferences_dialog.window.get_style_context()
        settings.set_property("gtk-application-prefer-dark-theme", state)

        # Toggle dark mode for preferences and editors
        if state:
            prefs_css.add_class('dark')
            text_tags.use_dark_mode()
        else:
            prefs_css.remove_class('dark')
            text_tags.use_light_mode()

    def init_actions(self):
        """Setup actions."""

        action_entries = [
            ('quit', lambda a, p: self.quit(), ('app.quit', ['<ctrl>Q'])),
            ('open_about', self.open_about, None),
            ('open_plugins', self.open_plugins_manager, None),
            ('new_task', self.new_task, ('app.new_task', ['<ctrl>N'])),
            ('new_subtask', self.new_subtask, ('app.new_subtask', ['<ctrl><shift>N'])),
            ('add_parent', self.add_parent, ('app.add_parent', ['<ctrl><shift>P'])),
            ('edit_task', self.edit_task, ('app.edit_task', ['<ctrl>E'])),
            ('mark_as_done', self.mark_as_done, ('app.mark_as_done', ['<ctrl>D'])),
            ('dismiss', self.dismiss, ('app.dismiss', ['<ctrl><shift>D'])),
            ('reopen', self.reopen, ('app.reopen', ['<ctrl>O'])),
            ('open_backends', self.open_backends_manager, None),
            ('open_help', self.open_help, ('app.open_help', ['F1'])),
            ('open_preferences', self.open_preferences, ('app.open_preferences', ['<ctrl>comma'])),
            ('close', self.close_context, ('app.close', ['Escape'])),
            ('editor.close', self.close_focused_task, ('app.editor.close', ['<ctrl>w'])),
            ('editor.show_parent', self.open_parent_task, None),
            ('editor.delete', self.delete_editor_task, None),
            ('editor.open_tags_popup', self.open_tags_popup_in_editor, None),
        ]

        for action, callback, accel in action_entries:
            simple_action = Gio.SimpleAction.new(action, None)
            simple_action.connect('activate', callback)
            simple_action.set_enabled(True)

            self.add_action(simple_action)

            if accel is not None:
                self.set_accels_for_action(*accel)

        self.plugins_dialog.dialog.insert_action_group('app', self)

    # --------------------------------------------------------------------------
    # ACTIONS
    # --------------------------------------------------------------------------

    def new_task(self, param=None, action=None):
        """Callback to add a new task."""

        self.browser.on_add_task()

    def new_subtask(self, param, action):
        """Callback to add a new subtask."""

        try:
            self.get_active_editor().insert_subtask()
        except AttributeError:
            self.browser.on_add_subtask()

    def add_parent(self, param, action):
        """Callback to add a parent to a task"""
        
        try:
            if self.browser.have_same_parent():
                self.browser.on_add_parent()

        # When no task has been selected
        except IndexError:
            return

    def edit_task(self, param, action):
        """Callback to edit a task."""

        self.browser.on_edit_active_task()

    def mark_as_done(self, param, action):
        """Callback to mark a task as done."""
        try:
            self.get_active_editor().change_status()
        except AttributeError:
            self.browser.on_mark_as_done()

    def dismiss(self, param, action):
        """Callback to mark a task as done."""

        try:
            self.get_active_editor().toggle_dismiss()
        except AttributeError:
            self.browser.on_dismiss_task()
    
    def reopen(self, param, action):
        """Callback to mark task as open."""

        try:
            self.get_active_editor().reopen()
        except AttributeError:
            self.browser.on_reopen_task()

    def open_help(self, action, param):
        """Open help callback."""

        try:
            Gtk.show_uri(None, "help:gtg", Gdk.CURRENT_TIME)
        except GLib.Error:
            log.error('Could not open help')

    def open_backends_manager(self, action, param):
        """Callback to open the backends manager dialog."""

        self.open_edit_backends()

    def open_preferences(self, action, param):
        """Callback to open the preferences dialog."""

        self.preferences_dialog.activate()
        self.preferences_dialog.window.set_transient_for(self.browser)

    def open_about(self, action, param):
        """Callback to open the about dialog."""

        self.browser.about.show()

    def open_plugins_manager(self, action, params):
        """Callback to open the plugins manager dialog."""

        self.plugins_dialog.activate()
        self.plugins_dialog.dialog.set_transient_for(self.browser)


    def close_context(self, action, params):
        """Callback to close based on the focus widget."""

        editor = self.get_active_editor()
        search = self.browser.search_entry.is_focus()

        if editor:
            self.close_task(editor.task.get_id())
        elif search:
            self.browser.toggle_search(action, params)

    def close_focused_task(self, action, params):
        """Callback to close currently focused task editor."""

        editor = self.get_active_editor()

        if editor:
            self.close_task(editor.task.get_id())

    def delete_editor_task(self, action, params):
        """Callback to delete the task currently open."""

        editor = self.get_active_editor()
        task = editor.task

        if task.is_new():
            self.close_task(task.get_id())
        else:
            self.delete_tasks([task.get_id()], editor.window)

    def open_tags_popup_in_editor(self, action, params):
        """Callback to open the tags popup in the focused task editor."""

        editor = self.get_active_editor()
        editor.open_tags_popover()

    def open_parent_task(self, action, params):
        """Callback to open the parent of the currently open task."""

        editor = self.get_active_editor()
        editor.open_parent()

    # --------------------------------------------------------------------------
    # TASKS AUTOCLEANING
    # --------------------------------------------------------------------------

    def purge_old_tasks(self, widget=None):
        """Remove closed tasks older than N days."""

        log.debug("Deleting old tasks")

        today = Date.today()
        max_days = self.config.get('autoclean_days')
        closed_tree = self.req.get_tasks_tree(name='inactive')

        closed_tasks = [self.req.get_task(tid) for tid in
                        closed_tree.get_all_nodes()]

        to_remove = [t for t in closed_tasks
                     if (today - t.get_closed_date()).days > max_days]

        [self.req.delete_task(task.get_id())
         for task in to_remove
         if self.req.has_task(task.get_id())]

    def autoclean(self, timer):
        """Run Automatic cleanup of old tasks."""

        if self.config.get('autoclean'):
            self.purge_old_tasks()

    # --------------------------------------------------------------------------
    # TASK BROWSER API
    # --------------------------------------------------------------------------

    def open_edit_backends(self, sender=None, backend_id=None):
        """Open the backends dialog."""

        self.backends_dialog = BackendsDialog(self.req)
        self.backends_dialog.dialog.insert_action_group('app', self)

        self.backends_dialog.activate()

        if backend_id:
            self.backends_dialog.show_config_for_backend(backend_id)

    def delete_tasks(self, tids, window):
        """Present the delete task confirmation dialog."""

        if not self.delete_task_dialog:
            self.delete_task_dialog = DeletionUI(self.req, window)

        tasks_to_delete = self.delete_task_dialog.show(tids)

        [self.close_task(task.get_id()) for task in tasks_to_delete
         if task.get_id() in self.open_tasks]

    def open_tag_editor(self, tag):
        """Open Tag editor dialog."""

        self.edit_tag_dialog = TagEditor(self.req, self, tag)
        self.edit_tag_dialog.set_transient_for(self.browser)
        self.edit_tag_dialog.insert_action_group('app', self)

    def close_tag_editor(self):
        """Close tag editor dialog."""

        self.edit_tag_dialog = None

    def select_tag(self, tag):
        """Select a tag in the browser."""

        self.browser.select_on_sidebar(tag)

    # --------------------------------------------------------------------------
    # TASK EDITOR API
    # --------------------------------------------------------------------------

    def reload_opened_editors(self, task_uid_list=None):
        """Reloads all the opened editors passed in the list 'task_uid_list'.

        If 'task_uid_list' is not passed or None, we reload all the opened
        editors.
        """

        if task_uid_list:
            [self.open_tasks[tid].reload_editor() for tid in self.open_tasks
             if tid in task_uid_list]
        else:
            [task.reload_editor() for task in self.open_tasks]

    def open_task(self, uid, new=False):
        """Open the task identified by 'uid'.

            If a Task editor is already opened for a given task, we present it.
            Otherwise, we create a new one.
        """

        if uid in self.open_tasks:
            editor = self.open_tasks[uid]
            editor.present()

        else:
            task = self.req.get_task(uid)
            editor = None

            if task:
                editor = TaskEditor(requester=self.req, app=self, task=task,
                                    thisisnew=new, clipboard=self.clipboard)

                editor.present()
                self.open_tasks[uid] = editor

                # Save open tasks to config
                open_tasks = self.config.get("opened_tasks")

                if uid not in open_tasks:
                    open_tasks.append(uid)

                self.config.set("opened_tasks", open_tasks)

            else:
                log.error('Task %s could not be found!', uid)

        return editor

    def get_active_editor(self):
        """Get focused task editor window."""

        for editor in self.open_tasks.values():
            if editor.window.is_active():
                return editor

    def close_task(self, tid):
        """Close a task editor window."""

        try:
            editor = self.open_tasks[tid]
            editor.close()

            open_tasks = self.config.get("opened_tasks")

            if tid in open_tasks:
                open_tasks.remove(tid)

            self.config.set("opened_tasks", open_tasks)

        except KeyError:
            log.debug('Tried to close tid %s but it is not open', tid)

    # --------------------------------------------------------------------------
    # SHUTDOWN
    # --------------------------------------------------------------------------

    def save_tasks(self):
        """Save opened tasks and their positions."""

        open_task = []

        for otid in list(self.open_tasks.keys()):
            open_task.append(otid)
            self.open_tasks[otid].close()

        self.config.set("opened_tasks", open_task)

    def save_plugin_settings(self):
        """Save plugin settings to configuration."""

        if self.plugin_engine is None:
            return  # Can't save when none has been loaded

        if self.plugin_engine.plugins:
            self.config_plugins.set(
                'disabled',
                [p.module_name
                 for p in self.plugin_engine.get_plugins('disabled')])

            self.config_plugins.set(
                'enabled',
                [p.module_name
                 for p in self.plugin_engine.get_plugins('enabled')])

        self.plugin_engine.deactivate_plugins()

    def quit(self):
        """Quit the application."""

        # This is needed to avoid warnings when closing the browser
        # with editor windows open, because of the "win"
        # group of actions.

        self.save_tasks()
        Gtk.Application.quit(self)

    def do_shutdown(self):
        """Callback when GTG is closed."""

        self.save_plugin_settings()
        self.ds.save()

        if self.req is not None:
            # Save data and shutdown datastore backends
            self.req.save_datastore(quit=True)

        Gtk.Application.do_shutdown(self)

    # --------------------------------------------------------------------------
    # MISC
    # --------------------------------------------------------------------------

    @staticmethod
    def set_logging(debug: bool = False):
        """Set whenever it should activate debug stuff like logging or not"""
        level = logging.DEBUG if debug else logging.INFO
        handler = logging.StreamHandler()
        formatter = logging.Formatter("%(asctime)s - %(levelname)s - "
                                      "%(module)s:%(funcName)s:%(lineno)d - "
                                      "%(message)s")
        handler.setFormatter(formatter)
        logger_ = logging.getLogger('GTG')
        handler.setLevel(level)
        logger_.addHandler(handler)
        logger_.setLevel(level)
        if debug:
            logger_.debug("Debug output enabled.")
示例#18
0
文件: editor.py 项目: heavensmile/gtg
    def __init__(self,
                 requester,
                 vmanager,
                 task,
                 thisisnew=False,
                 clipboard=None):
        '''
        req is the requester
        vmanager is the view manager
        thisisnew is True when a new task is created and opened
        '''
        self.req = requester
        self.vmanager = vmanager
        self.browser_config = self.req.get_config('browser')
        self.config = self.req.get_task_config(task.get_id())
        self.time = None
        self.clipboard = clipboard
        self.builder = Gtk.Builder()
        self.builder.add_from_file(self.EDITOR_UI_FILE)
        self.donebutton = self.builder.get_object("mark_as_done")
        self.undonebutton = self.builder.get_object("mark_as_undone")
        self.dismissbutton = self.builder.get_object("dismiss")
        self.undismissbutton = self.builder.get_object("undismiss")
        self.add_subtask = self.builder.get_object("add_subtask")
        self.tag_store = self.builder.get_object("tag_store")
        self.parent_button = self.builder.get_object("parent")

        # Create our dictionary and connect it
        dic = {
            "on_mark_as_done":
            self.change_status,
            "on_dismiss":
            self.dismiss,
            "delete_clicked":
            self.delete_task,
            "on_duedate_pressed":
            lambda w: self.on_date_pressed(w, GTGCalendar.DATE_KIND_DUE),
            "on_tags_popover":
            self.open_tags_popover,
            "on_startdate_pressed":
            lambda w: self.on_date_pressed(w, GTGCalendar.DATE_KIND_START),
            "on_closeddate_pressed":
            lambda w: self.on_date_pressed(w, GTGCalendar.DATE_KIND_CLOSED),
            "duedate_changed":
            lambda w: self.date_changed(w, GTGCalendar.DATE_KIND_DUE),
            "duedate_focus_out":
            lambda w, e: self.date_focus_out(w, e, GTGCalendar.DATE_KIND_DUE),
            "startingdate_changed":
            lambda w: self.date_changed(w, GTGCalendar.DATE_KIND_START),
            "startdate_focus_out":
            lambda w, e: self.date_focus_out(w, e, GTGCalendar.DATE_KIND_START
                                             ),
            "closeddate_changed":
            lambda w: self.date_changed(w, GTGCalendar.DATE_KIND_CLOSED),
            "closeddate_focus_out":
            lambda w, e: self.date_focus_out(w, e, GTGCalendar.DATE_KIND_CLOSED
                                             ),
            "on_insert_subtask_clicked":
            self.insert_subtask,
            "on_inserttag_clicked":
            self.inserttag_clicked,
            "on_parent_select":
            self.on_parent_select,
            "on_move":
            self.on_move,
            "show_popover_start":
            self.show_popover_start,
            "show_popover_due":
            self.show_popover_due,
            "show_popover_closed":
            self.show_popover_closed,
            "on_tag_toggled":
            self.on_tag_toggled,
        }
        self.builder.connect_signals(dic)
        self.window = self.builder.get_object("TaskEditor")
        # Removing the Normal textview to replace it by our own
        # So don't try to change anything with glade, this is a home-made
        # widget
        textview = self.builder.get_object("textview")
        scrolled = self.builder.get_object("scrolledtask")
        scrolled.remove(textview)
        self.textview = TaskView(self.req, self.clipboard)
        self.textview.show()
        self.textview.set_subtask_callback(self.new_subtask)
        self.textview.open_task_callback(self.vmanager.open_task)
        self.textview.set_left_margin(7)
        self.textview.set_right_margin(5)
        scrolled.add(self.textview)
        conf_font_value = self.browser_config.get("font_name")
        if conf_font_value != "":
            self.textview.override_font(Pango.FontDescription(conf_font_value))
        # Voila! it's done
        self.calendar = GTGCalendar()
        self.calendar.set_transient_for(self.window)
        self.calendar.set_decorated(False)
        self.duedate_widget = self.builder.get_object("duedate_entry")
        self.startdate_widget = self.builder.get_object("startdate_entry")
        self.closeddate_widget = self.builder.get_object("closeddate_entry")
        '''
        TODO(jakubbrindza): Once all the functionality in editor is back and
        working, bring back also the accelerators! Dayleft_label needs to be
        brought back, however its position is unsure.
        '''
        # self.dayleft_label = self.builder.get_object("dayleft")
        # Define accelerator keys
        self.init_accelerators()

        self.task = task
        tags = task.get_tags()
        self.textview.subtasks_callback(task.get_children)
        self.textview.removesubtask_callback(task.remove_child)
        self.textview.set_get_tagslist_callback(task.get_tags_name)
        self.textview.set_add_tag_callback(task.add_tag)
        self.textview.set_remove_tag_callback(task.remove_tag)
        self.textview.save_task_callback(self.light_save)

        texte = self.task.get_text()
        title = self.task.get_title()
        # the first line is the title
        self.textview.set_text("%s\n" % title)
        # we insert the rest of the task
        if texte:
            self.textview.insert("%s" % texte)
        else:
            # If not text, we insert tags
            if tags:
                for t in tags:
                    self.textview.insert_text("%s, " % t.get_name())
                self.textview.insert_text("\n")
            # If we don't have text, we still need to insert subtasks if any
            subtasks = task.get_children()
            if subtasks:
                self.textview.insert_subtasks(subtasks)
        # We select the title if it's a new task
        if thisisnew:
            self.textview.select_title()
        else:
            self.task.set_to_keep()
        self.textview.modified(full=True)
        self.window.connect("destroy", self.destruction)
        '''
        TODO(jakubbrindza): make on_date_changed work alongside
        the new popover calendar
        '''
        # self.calendar.connect("date-changed", self.on_date_changed)

        # plugins
        self.pengine = PluginEngine()
        self.plugin_api = PluginAPI(self.req, self.vmanager, self)
        self.pengine.register_api(self.plugin_api)
        self.pengine.onTaskLoad(self.plugin_api)

        # Putting the refresh callback at the end make the start a lot faster
        self.textview.refresh_callback(self.refresh_editor)
        self.refresh_editor()
        self.textview.grab_focus()

        self.init_dimensions()

        self.textview.set_editable(True)
        self.window.show()
示例#19
0
文件: editor.py 项目: heavensmile/gtg
class TaskEditor(object):

    EDITOR_UI_FILE = os.path.join(UI_DIR, "taskeditor.ui")

    def __init__(self,
                 requester,
                 vmanager,
                 task,
                 thisisnew=False,
                 clipboard=None):
        '''
        req is the requester
        vmanager is the view manager
        thisisnew is True when a new task is created and opened
        '''
        self.req = requester
        self.vmanager = vmanager
        self.browser_config = self.req.get_config('browser')
        self.config = self.req.get_task_config(task.get_id())
        self.time = None
        self.clipboard = clipboard
        self.builder = Gtk.Builder()
        self.builder.add_from_file(self.EDITOR_UI_FILE)
        self.donebutton = self.builder.get_object("mark_as_done")
        self.undonebutton = self.builder.get_object("mark_as_undone")
        self.dismissbutton = self.builder.get_object("dismiss")
        self.undismissbutton = self.builder.get_object("undismiss")
        self.add_subtask = self.builder.get_object("add_subtask")
        self.tag_store = self.builder.get_object("tag_store")
        self.parent_button = self.builder.get_object("parent")

        # Create our dictionary and connect it
        dic = {
            "on_mark_as_done":
            self.change_status,
            "on_dismiss":
            self.dismiss,
            "delete_clicked":
            self.delete_task,
            "on_duedate_pressed":
            lambda w: self.on_date_pressed(w, GTGCalendar.DATE_KIND_DUE),
            "on_tags_popover":
            self.open_tags_popover,
            "on_startdate_pressed":
            lambda w: self.on_date_pressed(w, GTGCalendar.DATE_KIND_START),
            "on_closeddate_pressed":
            lambda w: self.on_date_pressed(w, GTGCalendar.DATE_KIND_CLOSED),
            "duedate_changed":
            lambda w: self.date_changed(w, GTGCalendar.DATE_KIND_DUE),
            "duedate_focus_out":
            lambda w, e: self.date_focus_out(w, e, GTGCalendar.DATE_KIND_DUE),
            "startingdate_changed":
            lambda w: self.date_changed(w, GTGCalendar.DATE_KIND_START),
            "startdate_focus_out":
            lambda w, e: self.date_focus_out(w, e, GTGCalendar.DATE_KIND_START
                                             ),
            "closeddate_changed":
            lambda w: self.date_changed(w, GTGCalendar.DATE_KIND_CLOSED),
            "closeddate_focus_out":
            lambda w, e: self.date_focus_out(w, e, GTGCalendar.DATE_KIND_CLOSED
                                             ),
            "on_insert_subtask_clicked":
            self.insert_subtask,
            "on_inserttag_clicked":
            self.inserttag_clicked,
            "on_parent_select":
            self.on_parent_select,
            "on_move":
            self.on_move,
            "show_popover_start":
            self.show_popover_start,
            "show_popover_due":
            self.show_popover_due,
            "show_popover_closed":
            self.show_popover_closed,
            "on_tag_toggled":
            self.on_tag_toggled,
        }
        self.builder.connect_signals(dic)
        self.window = self.builder.get_object("TaskEditor")
        # Removing the Normal textview to replace it by our own
        # So don't try to change anything with glade, this is a home-made
        # widget
        textview = self.builder.get_object("textview")
        scrolled = self.builder.get_object("scrolledtask")
        scrolled.remove(textview)
        self.textview = TaskView(self.req, self.clipboard)
        self.textview.show()
        self.textview.set_subtask_callback(self.new_subtask)
        self.textview.open_task_callback(self.vmanager.open_task)
        self.textview.set_left_margin(7)
        self.textview.set_right_margin(5)
        scrolled.add(self.textview)
        conf_font_value = self.browser_config.get("font_name")
        if conf_font_value != "":
            self.textview.override_font(Pango.FontDescription(conf_font_value))
        # Voila! it's done
        self.calendar = GTGCalendar()
        self.calendar.set_transient_for(self.window)
        self.calendar.set_decorated(False)
        self.duedate_widget = self.builder.get_object("duedate_entry")
        self.startdate_widget = self.builder.get_object("startdate_entry")
        self.closeddate_widget = self.builder.get_object("closeddate_entry")
        '''
        TODO(jakubbrindza): Once all the functionality in editor is back and
        working, bring back also the accelerators! Dayleft_label needs to be
        brought back, however its position is unsure.
        '''
        # self.dayleft_label = self.builder.get_object("dayleft")
        # Define accelerator keys
        self.init_accelerators()

        self.task = task
        tags = task.get_tags()
        self.textview.subtasks_callback(task.get_children)
        self.textview.removesubtask_callback(task.remove_child)
        self.textview.set_get_tagslist_callback(task.get_tags_name)
        self.textview.set_add_tag_callback(task.add_tag)
        self.textview.set_remove_tag_callback(task.remove_tag)
        self.textview.save_task_callback(self.light_save)

        texte = self.task.get_text()
        title = self.task.get_title()
        # the first line is the title
        self.textview.set_text("%s\n" % title)
        # we insert the rest of the task
        if texte:
            self.textview.insert("%s" % texte)
        else:
            # If not text, we insert tags
            if tags:
                for t in tags:
                    self.textview.insert_text("%s, " % t.get_name())
                self.textview.insert_text("\n")
            # If we don't have text, we still need to insert subtasks if any
            subtasks = task.get_children()
            if subtasks:
                self.textview.insert_subtasks(subtasks)
        # We select the title if it's a new task
        if thisisnew:
            self.textview.select_title()
        else:
            self.task.set_to_keep()
        self.textview.modified(full=True)
        self.window.connect("destroy", self.destruction)
        '''
        TODO(jakubbrindza): make on_date_changed work alongside
        the new popover calendar
        '''
        # self.calendar.connect("date-changed", self.on_date_changed)

        # plugins
        self.pengine = PluginEngine()
        self.plugin_api = PluginAPI(self.req, self.vmanager, self)
        self.pengine.register_api(self.plugin_api)
        self.pengine.onTaskLoad(self.plugin_api)

        # Putting the refresh callback at the end make the start a lot faster
        self.textview.refresh_callback(self.refresh_editor)
        self.refresh_editor()
        self.textview.grab_focus()

        self.init_dimensions()

        self.textview.set_editable(True)
        self.window.show()

    # Define accelerator-keys for this dialog
    '''
    TODO: undo/redo
    + RE-enable all the features so that they work properly.
    + new shortcuts for bold and italic once implemented.
    '''

    def init_accelerators(self):
        agr = Gtk.AccelGroup()
        self.window.add_accel_group(agr)

        # Escape and Ctrl-W close the dialog. It's faster to call close
        # directly, rather than use the close button widget
        key, modifier = Gtk.accelerator_parse('Escape')
        agr.connect(key, modifier, Gtk.AccelFlags.VISIBLE, self.close)

        key, modifier = Gtk.accelerator_parse('<Control>w')
        agr.connect(key, modifier, Gtk.AccelFlags.VISIBLE, self.close)

        # F1 shows help
        add_help_shortcut(self.window, "editor")

        # Ctrl-N creates a new task
        key, modifier = Gtk.accelerator_parse('<Control>n')
        agr.connect(key, modifier, Gtk.AccelFlags.VISIBLE, self.new_task)

        # Ctrl-Shift-N creates a new subtask
        key, mod = Gtk.accelerator_parse("<Control><Shift>n")
        self.add_subtask.add_accelerator('clicked', agr, key, mod,
                                         Gtk.AccelFlags.VISIBLE)

        # Ctrl-D marks task as done
        key, mod = Gtk.accelerator_parse('<Control>d')
        self.donebutton.add_accelerator('clicked', agr, key, mod,
                                        Gtk.AccelFlags.VISIBLE)

        # Ctrl-I marks task as dismissed
        key, mod = Gtk.accelerator_parse('<Control>i')
        self.dismissbutton.add_accelerator('clicked', agr, key, mod,
                                           Gtk.AccelFlags.VISIBLE)

        # Ctrl-Q quits GTG
        key, modifier = Gtk.accelerator_parse('<Control>q')
        agr.connect(key, modifier, Gtk.AccelFlags.VISIBLE, self.quit)

    '''
    TODO(jakubbrindza): Add the functionality to the existing calendar widgets.
    This will require ammending and re-factoring the entire calendar.py.
    '''

    def show_popover_start(self, widget, event):
        popover = self.builder.get_object("date_popover")
        popover.set_relative_to(self.startdate_widget)
        popover.set_modal(False)
        popover.show_all()

    def show_popover_due(self, widget, popover):
        popover = self.builder.get_object("date_popover")
        popover.set_relative_to(self.duedate_widget)
        popover.set_modal(False)
        popover.show_all()

    def show_popover_closed(self, widget, popover):
        closed_popover = self.builder.get_object("closed_popover")
        closed_popover.set_relative_to(self.closeddate_widget)
        closed_popover.set_modal(False)
        closed_popover.show_all()

    def open_tags_popover(self, widget):
        self.tag_store.clear()

        tags = self.req.get_tag_tree().get_all_nodes()

        used_tags = self.task.get_tags()

        for tagname in tags:
            tag = self.req.get_tag(tagname)
            if tag_filter(tag):
                is_used = tag in used_tags
                self.tag_store.append([is_used, tagname])
                '''
                TODO(jakubbrindza): add sorting of the tags based on
                True | False and within each sub-group arrange them
                alphabetically
                '''

    def on_tag_toggled(self, widget, path, column):
        """We toggle by tag_row variable. tag_row is
        meant to be a tuple (is_used, tagname)"""
        tag_row = self.tag_store[path]
        tag_row[0] = not tag_row[0]

        if tag_row[0]:
            self.textview.insert_tags([tag_row[1]])
        '''
        TODO(jakubbrindza): Add else case that will remove tag.
        '''

    def init_dimensions(self):
        """ Restores position and size of task if possible """
        position = self.config.get('position')
        if position and len(position) == 2:
            try:
                self.window.move(int(position[0]), int(position[1]))
            except ValueError:
                Log.warning('Invalid position configuration for task %s: %s',
                            self.task.get_id(), position)

        size = self.config.get('size')
        if size and len(size) == 2:
            try:
                self.window.resize(int(size[0]), int(size[1]))
            except ValueError:
                Log.warning('Invalid size configuration for task %s: %s',
                            self.task.get_id(), size)

    # Can be called at any time to reflect the status of the Task
    # Refresh should never interfere with the TaskView.
    # If a title is passed as a parameter, it will become
    # the new window title. If not, we will look for the task title.
    # Refreshtext is whether or not we should refresh the TaskView
    # (doing it all the time is dangerous if the task is empty)
    def refresh_editor(self, title=None, refreshtext=False):
        if self.window is None:
            return
        to_save = False
        # title of the window
        if title:
            self.window.set_title(title)
            to_save = True
        else:
            self.window.set_title(self.task.get_title())

        status = self.task.get_status()
        if status == Task.STA_DISMISSED:
            self.donebutton.show()
            self.undonebutton.hide()
            self.dismissbutton.hide()
            self.undismissbutton.show()
        elif status == Task.STA_DONE:
            self.donebutton.hide()
            self.undonebutton.show()
            self.dismissbutton.show()
            self.undismissbutton.hide
        else:
            self.donebutton.show()
            self.undonebutton.hide()
            self.dismissbutton.show()
            self.undismissbutton.hide()

        # Refreshing the the parent button
        has_parents = len(self.task.get_parents()) > 0
        self.parent_button.set_sensitive(has_parents)

        # Refreshing the status bar labels and date boxes
        if status in [Task.STA_DISMISSED, Task.STA_DONE]:
            self.builder.get_object("start_box").hide()
            self.builder.get_object("closed_box").show()
        else:
            self.builder.get_object("closed_box").hide()
            self.builder.get_object("start_box").show()

        # refreshing the start date field
        startdate = self.task.get_start_date()
        try:
            prevdate = Date.parse(self.startdate_widget.get_text())
            update_date = startdate != prevdate
        except ValueError:
            update_date = True

        if update_date:
            self.startdate_widget.set_text(str(startdate))

        # refreshing the due date field
        duedate = self.task.get_due_date()
        try:
            prevdate = Date.parse(self.duedate_widget.get_text())
            update_date = duedate != prevdate
        except ValueError:
            update_date = True

        if update_date:
            self.duedate_widget.set_text(str(duedate))

        # refreshing the closed date field
        closeddate = self.task.get_closed_date()
        prevcldate = Date.parse(self.closeddate_widget.get_text())
        if closeddate != prevcldate:
            self.closeddate_widget.set_text(str(closeddate))

        # refreshing the day left label
        '''
        TODO(jakubbrindza): re-enable refreshing the day left.
        We need to come up how and where this information is viewed
        in the editor window.
        '''
        # self.refresh_day_left()

        if refreshtext:
            self.textview.modified(refresheditor=False)
        if to_save:
            self.light_save()

    def refresh_day_left(self):
        # If the task is marked as done, we display the delay between the
        # due date and the actual closing date. If the task isn't marked
        # as done, we display the number of days left.
        status = self.task.get_status()
        if status in [Task.STA_DISMISSED, Task.STA_DONE]:
            delay = self.task.get_days_late()
            if delay is None:
                txt = ""
            elif delay == 0:
                txt = "Completed on time"
            elif delay >= 1:
                txt = ngettext("Completed %(days)d day late",
                               "Completed %(days)d days late", delay) % \
                    {'days': delay}
            elif delay <= -1:
                abs_delay = abs(delay)
                txt = ngettext("Completed %(days)d day early",
                               "Completed %(days)d days early", abs_delay) % \
                    {'days': abs_delay}
        else:
            due_date = self.task.get_due_date()
            result = due_date.days_left()
            if due_date.is_fuzzy():
                txt = ""
            elif result > 0:
                txt = ngettext("Due tomorrow!", "%(days)d days left", result)\
                    % {'days': result}
            elif result == 0:
                txt = _("Due today!")
            elif result < 0:
                abs_result = abs(result)
                txt = ngettext("Due yesterday!", "Was %(days)d days ago",
                               abs_result) % {
                                   'days': abs_result
                               }

        style_context = self.window.get_style_context()
        color = style_context.get_color(Gtk.StateFlags.INSENSITIVE).to_color()
        self.dayleft_label.set_markup("<span color='%s'>%s</span>" %
                                      (color.to_string(), txt))

    def date_changed(self, widget, data):
        try:
            Date.parse(widget.get_text())
            valid = True
        except ValueError:
            valid = False

        if valid:
            # If the date is valid, we write with default color in the widget
            # "none" will set the default color.
            widget.override_color(Gtk.StateType.NORMAL, None)
            widget.override_background_color(Gtk.StateType.NORMAL, None)
        else:
            # We should write in red in the entry if the date is not valid
            text_color = Gdk.RGBA()
            text_color.parse("#F00")
            widget.override_color(Gtk.StateType.NORMAL, text_color)

            bg_color = Gdk.RGBA()
            bg_color.parse("#F88")
            widget.override_background_color(Gtk.StateType.NORMAL, bg_color)

    def date_focus_out(self, widget, event, date_kind):
        try:
            datetoset = Date.parse(widget.get_text())
        except ValueError:
            datetoset = None

        if datetoset is not None:
            if date_kind == GTGCalendar.DATE_KIND_START:
                self.task.set_start_date(datetoset)
            elif date_kind == GTGCalendar.DATE_KIND_DUE:
                self.task.set_due_date(datetoset)
            elif date_kind == GTGCalendar.DATE_KIND_CLOSED:
                self.task.set_closed_date(datetoset)
            self.refresh_editor()

    def on_date_pressed(self, widget, date_kind):
        """Called when a date-changing button is clicked."""
        if date_kind == GTGCalendar.DATE_KIND_DUE:
            if not self.task.get_due_date():
                date = self.task.get_start_date()
            else:
                date = self.task.get_due_date()
        elif date_kind == GTGCalendar.DATE_KIND_START:
            date = self.task.get_start_date()
        elif date_kind == GTGCalendar.DATE_KIND_CLOSED:
            date = self.task.get_closed_date()
        self.calendar.set_date(date, date_kind)
        # we show the calendar at the right position
        rect = widget.get_allocation()
        result, x, y = widget.get_window().get_origin()
        self.calendar.show_at_position(x + rect.x + rect.width, y + rect.y)

    def on_date_changed(self, calendar):
        date, date_kind = calendar.get_selected_date()
        if date_kind == GTGCalendar.DATE_KIND_DUE:
            self.task.set_due_date(date)
        elif date_kind == GTGCalendar.DATE_KIND_START:
            self.task.set_start_date(date)
        elif date_kind == GTGCalendar.DATE_KIND_CLOSED:
            self.task.set_closed_date(date)
        self.refresh_editor()

    def close_all_subtasks(self):
        all_subtasks = []

        def trace_subtasks(root):
            for i in root.get_subtasks():
                if i not in all_subtasks:
                    all_subtasks.append(i)
                    trace_subtasks(i)

        trace_subtasks(self.task)

        for task in all_subtasks:
            self.vmanager.close_task(task.get_id())

    def dismiss(self, widget):
        stat = self.task.get_status()
        if stat == Task.STA_DISMISSED:
            self.task.set_status(Task.STA_ACTIVE)
            self.refresh_editor()
        else:
            self.task.set_status(Task.STA_DISMISSED)
            self.close_all_subtasks()
            self.close(None)

    def change_status(self, widget):
        stat = self.task.get_status()
        if stat == Task.STA_DONE:
            self.task.set_status(Task.STA_ACTIVE)
            self.refresh_editor()
        else:
            self.task.set_status(Task.STA_DONE)
            self.close_all_subtasks()
            self.close(None)

    def delete_task(self, widget):
        # this triggers the closing of the window in the view manager
        if self.task.is_new():
            self.vmanager.close_task(self.task.get_id())
        else:
            self.vmanager.ask_delete_tasks([self.task.get_id()])

    # Take the title as argument and return the subtask ID
    def new_subtask(self, title=None, tid=None):
        if tid:
            self.task.add_child(tid)
        elif title:
            subt = self.task.new_subtask()
            subt.set_title(title)
            tid = subt.get_id()
        return tid

    # Create a new task
    def new_task(self, *args):
        task = self.req.new_task(newtask=True)
        task_id = task.get_id()
        self.vmanager.open_task(task_id)

    def insert_subtask(self, widget):
        self.textview.insert_newtask()
        self.textview.grab_focus()

    def inserttag_clicked(self, widget):
        itera = self.textview.get_insert()
        if itera.starts_line():
            self.textview.insert_text("@", itera)
        else:
            self.textview.insert_text(" @", itera)

    def on_parent_select(self, widget):
        parents = self.task.get_parents()

        if len(parents) == 1:
            self.vmanager.open_task(parents[0])
        elif len(parents) > 1:
            self.show_multiple_parent_popover(parents)

    def show_multiple_parent_popover(self, parent_ids):
        parent_box = Gtk.Box.new(Gtk.Orientation.VERTICAL, 0)
        for parent in parent_ids:
            parent_name = self.req.get_task(parent).get_title()
            button = Gtk.ToolButton.new(None, parent_name)
            button.connect("clicked", self.on_parent_item_clicked, parent)
            parent_box.add(button)

        self.parent_popover = Gtk.Popover.new(self.parent_button)
        self.parent_popover.add(parent_box)
        self.parent_popover.set_property("border-width", 0)
        self.parent_popover.set_position(Gtk.PositionType.BOTTOM)
        self.parent_popover.set_transitions_enabled(True)
        self.parent_popover.show_all()

    # On click handler for open_parent_button's menu items
    def on_parent_item_clicked(self, widget, parent_id):
        self.vmanager.open_task(parent_id)
        if self.parent_popover.get_visible():
            self.parent_popover.hide()

    def save(self):
        self.task.set_title(self.textview.get_title())
        self.task.set_text(self.textview.get_text())
        self.task.sync()
        if self.config is not None:
            self.config.save()
        self.time = time.time()

    # light_save save the task without refreshing every 30seconds
    # We will reduce the time when the get_text will be in another thread

    def light_save(self):
        # if self.time is none, we never called any save
        if self.time:
            diff = time.time() - self.time
            tosave = diff > GnomeConfig.SAVETIME
        else:
            # we don't want to save a task while opening it
            tosave = self.textview.get_editable()
            diff = None
        if tosave:
            self.save()

    # This will bring the Task Editor to front
    def present(self):
        self.window.present()

    def get_position(self):
        return self.window.get_position()

    def on_move(self, widget, event):
        """ Save position and size of window """
        self.config.set('position', self.window.get_position())
        self.config.set('size', self.window.get_size())

    # We define dummy variable for when close is called from a callback
    def close(self, window=None, a=None, b=None, c=None):

        # We should also destroy the whole taskeditor object.
        if self.window:
            self.window.destroy()
            self.window = None

    # The destroy signal is linked to the "close" button. So if we call
    # destroy in the close function, this will cause the close to be called
    # twice
    # To solve that, close will just call "destroy" and the destroy signal
    # Will be linked to this destruction method that will save the task
    def destruction(self, a=None):
        # Save should be also called when buffer is modified
        self.pengine.onTaskClose(self.plugin_api)
        self.pengine.remove_api(self.plugin_api)
        tid = self.task.get_id()
        if self.task.is_new():
            self.req.delete_task(tid)
        else:
            self.save()
            for i in self.task.get_subtasks():
                if i:
                    i.set_to_keep()
        self.vmanager.close_task(tid)

    def get_builder(self):
        return self.builder

    def get_task(self):
        return self.task

    def get_textview(self):
        return self.textview

    def get_window(self):
        return self.window

    def quit(self,
             accel_group=None,
             acceleratable=None,
             keyval=None,
             modifier=None):
        """Handles the accelerator for quitting GTG."""
        self.vmanager.quit()
class PreferencesDialog:

    __AUTOSTART_DIRECTORY = os.path.join(xdg_config_home, "autostart")
    __AUTOSTART_FILE = "gtg.desktop"

    def __init__(self, config_obj):
        """Constructor."""
        self.config_obj = config_obj
        self.config = self.config_obj.conf_dict
        self.builder = gtk.Builder() 
        self.builder.add_from_file(ViewConfig.PREFERENCES_GLADE_FILE)
        # store references to some objects
        widgets = {
          'dialog': 'PreferencesDialog',
          'backend_tree': 'BackendTree',
          'plugin_tree': 'PluginTree',
          'plugin_about_dialog': 'PluginAboutDialog',
          'plugin_configure': 'plugin_configure',
          'plugin_depends': 'PluginDepends',
          'plugin_config_dialog': 'PluginConfigDialog',
          'pref_autostart': 'pref_autostart',
          'pref_show_preview': 'pref_show_preview'
          }
        for attr, widget in widgets.iteritems():
            setattr(self, attr, self.builder.get_object(widget))
        # keep a reference to the parent task browser
        #FIXME: this is not needed and should be removed
#        self.tb = taskbrowser
        self.pengine = PluginEngine()
        # initialize tree models
        self._init_backend_tree()
        # this can't happen yet, due to the order of things in
        #  TaskBrowser.__init__(). Happens in activate() instead.
        # self._init_plugin_tree()
        pref_signals_dic = self.get_signals_dict()
        self.builder.connect_signals(pref_signals_dic)

    def _init_backend_tree(self):
        """Initialize the BackendTree gtk.TreeView."""
        self._refresh_backend_store()
        # TODO

    def _refresh_backend_store(self):
        """Populate a gtk.ListStore with backend information."""
        # create and clear a gtk.ListStore for backend information
        if not hasattr(self, 'backend_store'):
            # TODO: create the liststore. It should have one column for each
            # backend.
            self.backend_store = gtk.ListStore(str)
        self.backend_store.clear()
        # TODO

    def _refresh_plugin_store(self):
        """Populate a gtk.ListStore with plugin information."""
        # create and clear a gtk.ListStore
        if not hasattr(self, 'plugin_store'):
            # see constants PLUGINS_COL_* for column meanings
            self.plugin_store = gtk.ListStore(str, 'gboolean', str, str, str,
              'gboolean',)
        self.plugin_store.clear()
        # refresh the status of all plugins
        self.pengine.recheck_plugin_errors(True)
        # repopulate the store
        for name, p in self.pengine.plugins.iteritems():
            self.plugin_store.append([name, p.enabled, p.full_name,
              p.short_description, p.description, not p.error,]) # activateable if there is no error

    def  _refresh_preferences_store(self):
        """Sets the correct value in the preferences checkboxes"""
        autostart_path = os.path.join(self.__AUTOSTART_DIRECTORY, \
                                      self.__AUTOSTART_FILE)
        self.pref_autostart.set_active(os.path.isfile(autostart_path))
        #This set_active method doesn't even understand what a boolean is!
        #what a PITA !
        if self.config_priv.get("contents_preview_enable"):
            toset = 1
        else:
            toset = 0
        self.pref_show_preview.set_active(toset)


    def _init_plugin_tree(self):
        """Initialize the PluginTree gtk.TreeView.
        
        The format is modelled after the one used in gedit; see
        http://git.gnome.org/browse/gedit/tree/gedit/gedit-plugin-mapnager.c
        
        """
        # force creation of the gtk.ListStore so we can reference it
        self._refresh_plugin_store()

        # renderer for the toggle column
        renderer = gtk.CellRendererToggle()
        renderer.set_property('xpad', 6)
        renderer.connect('toggled', self.on_plugin_toggle)
        # toggle column
        column = gtk.TreeViewColumn(None, renderer, active=PLUGINS_COL_ENABLED,
          activatable=PLUGINS_COL_ACTIVATABLE,
          sensitive=PLUGINS_COL_ACTIVATABLE)
        self.plugin_tree.append_column(column)

        # plugin name column
        column = gtk.TreeViewColumn()
        column.set_spacing(6)
        # icon renderer for the plugin name column
        icon_renderer = gtk.CellRendererPixbuf()
        icon_renderer.set_property('stock-size', gtk.ICON_SIZE_SMALL_TOOLBAR)
        icon_renderer.set_property('xpad', 3)
        column.pack_start(icon_renderer, expand=False)
        column.set_cell_data_func(icon_renderer, plugin_icon)
        # text renderer for the plugin name column
        name_renderer = gtk.CellRendererText()
        name_renderer.set_property('ellipsize', pango.ELLIPSIZE_END)
        column.pack_start(name_renderer)
        column.set_cell_data_func(name_renderer, plugin_markup)

        self.plugin_tree.append_column(column)

        # finish setup
        self.plugin_tree.set_model(self.plugin_store)
        self.plugin_tree.set_search_column(2)

    ## GTK signals & related functions
    def get_signals_dict(self):
        """A dictionary of signals and functions to be connected."""
        SIGNAL_CONNECTIONS_DIC = {
#          'on_preferences_activate':
#            self.activate,
          # buttons in the dialog itself
          'on_prefs_close':
            self.on_close,
          'on_prefs_help':
            self.on_help,
          # preferences on the Tasks tab
          'on_pref_show_preview_toggled':
            self.toggle_preview,
          'on_pref_check_spelling_toggled':
            self.toggle_spellcheck,
          # buttons on the Plugins tab
          'on_PluginTree_cursor_changed':
            self.on_plugin_select,
          'on_plugin_about':
            self.on_plugin_about,
          'on_plugin_configure':
            self.on_plugin_configure,
          # the PluginAboutDialog
          'on_PluginAboutDialog_close':
            self.on_plugin_about_close,
          'on_PluginAboutDialog_response':
            self.on_plugin_about_close,
          # the PluginConfigDialog
          'on_PluginConfigClose_released':
            self.on_plugin_config_close,
          'on_PreferencesDialog_delete_event':
            self.on_close,
            'on_pref_autostart_toggled':
            self.on_autostart_toggled,
          }
        return SIGNAL_CONNECTIONS_DIC

    def activate(self, config_priv, widget=None):
        """Activate the preferences dialog."""
        self.config_priv = config_priv
        if len(self.plugin_tree.get_columns()) == 0:
            # late setup of PluginTree
            self._init_plugin_tree()
        else:
            self._refresh_plugin_store()
        self._refresh_backend_store()
        self._refresh_preferences_store()
        self.dialog.present()
        self.dialog.show_all()

    def on_close(self, widget, data = None):
        """Close the preferences dialog."""

        if self.pengine.get_plugins():
            self.config["plugins"] = {}
            self.config["plugins"]["disabled"] = \
              [p.module_name for p in self.pengine.get_plugins("disabled")]
            self.config["plugins"]["enabled"] = \
              [p.module_name for p in self.pengine.get_plugins("enabled")]

        self.config_obj.save()

        self.dialog.hide()
        return True

    def on_help(self, widget):
        """Provide help for the preferences dialog."""
        return True

    def on_plugin_about(self, widget):
        """Display information about a plugin."""
        (junk, iter) = self.plugin_tree.get_selection().get_selected()
        if iter == None:
            return
        plugin_id = self.plugin_store.get_value(iter, PLUGINS_COL_ID)
        p = self.pengine.get_plugin(plugin_id)
        pad = self.plugin_about_dialog
        pad.set_name(p.full_name)
        pad.set_version(p.version)
        authors = p.authors
        if isinstance(authors, str):
            authors = [authors, ]
        pad.set_authors(authors)
        pad.set_comments(p.description.replace(r'\n', "\n"))
        self.plugin_depends.set_label(plugin_error_text(p))
        pad.show_all()

    def on_plugin_about_close(self, widget, *args):
        """Close the PluginAboutDialog."""
        self.plugin_about_dialog.hide()

    def on_plugin_configure(self, widget):
        """Configure a plugin."""
        (junk, iter) = self.plugin_tree.get_selection().get_selected()
        plugin_id = self.plugin_store.get_value(iter, PLUGINS_COL_ID)
        # TODO: load plugin's configuration UI and insert into pc-vbox1 in
        #  position 0. Something like...
        #pcd = self.plugin_config_dialog
        #pcd.show_all()
        # ...for now, use existing code.
        self.pengine.get_plugin(plugin_id).instance.configure_dialog(self.dialog)

    def on_plugin_config_close(self, widget):
        """Close the PluginConfigDialog."""
        self.plugin_config_dialog.hide()

    def on_plugin_select(self, plugin_tree):
        (model, iter) = plugin_tree.get_selection().get_selected()
        if iter is not None:
            plugin_id = model.get_value(iter, PLUGINS_COL_ID)
            self._update_plugin_configure(self.pengine.get_plugin(plugin_id))

    def on_plugin_toggle(self, widget, path):
        """Toggle a plugin enabled/disabled."""
        iter = self.plugin_store.get_iter(path)
        plugin_id = self.plugin_store.get_value(iter, PLUGINS_COL_ID)
        p = self.pengine.get_plugin(plugin_id)
        p.enabled = not self.plugin_store.get_value(iter, PLUGINS_COL_ENABLED)
        if p.enabled:
            self.pengine.activate_plugins([p])
        else:
            self.pengine.deactivate_plugins([p])
        self.plugin_store.set_value(iter, PLUGINS_COL_ENABLED, p.enabled)
        self._update_plugin_configure(p)
    
    def toggle_preview(self, widget):
        """Toggle previews in the task view on or off."""
        self.config_priv.set("contents_preview_enable",widget.get_active())
    
    def toggle_spellcheck(self, widget):
        """Toggle spell checking on or off."""
        print __name__
    
    def _update_plugin_configure(self, plugin):
        """Enable the "Configure Plugin" button if appropriate."""
        configurable = plugin.active and plugin.is_configurable()
        self.plugin_configure.set_property('sensitive', configurable)

    def on_autostart_toggled(self, widget):
        """Toggle GTG autostarting with the GNOME desktop"""
        autostart_path = os.path.join(self.__AUTOSTART_DIRECTORY, \
                                      self.__AUTOSTART_FILE)
        if widget.get_active() == False: 
            #Disable autostart, removing the file in autostart_path
            if os.path.isfile(autostart_path):
                os.remove(autostart_path)
        else:
            #Enable autostart
            #We look for the desktop file
            desktop_file_path = None
            desktop_file_directories = ["../..",
                                  "../../../applications",
                                  "../../../../../share/applications"]
            this_directory = os.path.dirname(os.path.abspath(__file__))
            for path in desktop_file_directories:
                fullpath = os.path.normpath(os.path.join(this_directory, path, \
                                                        self.__AUTOSTART_FILE))
                if os.path.isfile(fullpath):
                    desktop_file_path = fullpath
                    break
            #If we have found the desktop file, we make a link to in in
            # autostart_path. If symbolic linking is not possible
            # (that is, if we are running on Windows), then copy the file
            if desktop_file_path:
                if not os.path.exists(self.__AUTOSTART_DIRECTORY):
                    os.mkdir(self.__AUTOSTART_DIRECTORY)
                if os.path.isdir(self.__AUTOSTART_DIRECTORY) and \
                   not os.path.exists(autostart_path):
                    if hasattr(os, "symlink"):
                        os.symlink(desktop_file_path, \
                                   autostart_path)
                    else:
                        shutil.copyfile(desktop_file_path, \
                                         autostart_path)
示例#21
0
class TaskEditor:
    def __init__(self,
                 requester,
                 vmanager,
                 task,
                 taskconfig=None,
                 thisisnew=False,
                 clipboard=None):
        '''
        req is the requester
        vmanager is the view manager
        taskconfig is a ConfigObj dic to save infos about tasks
        thisisnew is True when a new task is created and opened
        '''
        self.req = requester
        self.browser_config = self.req.get_config('browser')
        self.vmanager = vmanager
        self.config = taskconfig
        self.time = None
        self.clipboard = clipboard
        self.builder = gtk.Builder()
        self.builder.add_from_file(GnomeConfig.GLADE_FILE)
        self.donebutton = self.builder.get_object("mark_as_done_editor")
        self.dismissbutton = self.builder.get_object("dismiss_editor")
        self.deletebutton = self.builder.get_object("delete_editor")
        self.deletebutton.set_tooltip_text(GnomeConfig.DELETE_TOOLTIP)
        self.subtask_button = self.builder.get_object("insert_subtask")
        self.subtask_button.set_tooltip_text(GnomeConfig.SUBTASK_TOOLTIP)
        self.inserttag_button = self.builder.get_object("inserttag")
        self.inserttag_button.set_tooltip_text(GnomeConfig.TAG_TOOLTIP)

        # Create our dictionary and connect it
        dic = {
            "mark_as_done_clicked":
            self.change_status,
            "on_dismiss":
            self.dismiss,
            "delete_clicked":
            self.delete_task,
            "on_duedate_pressed":
            (self.on_date_pressed, GTGCalendar.DATE_KIND_DUE),
            "on_startdate_pressed":
            (self.on_date_pressed, GTGCalendar.DATE_KIND_START),
            "on_closeddate_pressed":
            (self.on_date_pressed, GTGCalendar.DATE_KIND_CLOSED),
            "close_clicked":
            self.close,
            "duedate_changed": (self.date_changed, GTGCalendar.DATE_KIND_DUE),
            "duedate_focus_out":
            (self.date_focus_out, GTGCalendar.DATE_KIND_DUE),
            "startingdate_changed": (self.date_changed,
                                     GTGCalendar.DATE_KIND_START),
            "startdate_focus_out": (self.date_focus_out,
                                    GTGCalendar.DATE_KIND_START),
            "closeddate_changed": (self.date_changed,
                                   GTGCalendar.DATE_KIND_CLOSED),
            "closeddate_focus_out": (self.date_focus_out,
                                     GTGCalendar.DATE_KIND_CLOSED),
            "on_insert_subtask_clicked":
            self.insert_subtask,
            "on_inserttag_clicked":
            self.inserttag_clicked,
            "on_move":
            self.on_move,
        }
        self.builder.connect_signals(dic)
        self.window = self.builder.get_object("TaskEditor")
        # Removing the Normal textview to replace it by our own
        # So don't try to change anything with glade, this is a home-made
        # widget
        textview = self.builder.get_object("textview")
        scrolled = self.builder.get_object("scrolledtask")
        scrolled.remove(textview)
        self.textview = TaskView(self.req, self.clipboard)
        self.textview.show()
        self.textview.set_subtask_callback(self.new_subtask)
        self.textview.open_task_callback(self.vmanager.open_task)
        self.textview.set_left_margin(7)
        self.textview.set_right_margin(5)
        scrolled.add(self.textview)
        conf_font_value = self.browser_config.get("font_name")
        if conf_font_value != "":
            self.textview.modify_font(pango.FontDescription(conf_font_value))
        # Voila! it's done
        self.calendar = GTGCalendar(self.builder)
        self.duedate_widget = self.builder.get_object("duedate_entry")
        self.startdate_widget = self.builder.get_object("startdate_entry")
        self.closeddate_widget = self.builder.get_object("closeddate_entry")
        self.dayleft_label = self.builder.get_object("dayleft")
        self.tasksidebar = self.builder.get_object("tasksidebar")
        # Define accelerator keys
        self.init_accelerators()

        self.task = task
        tags = task.get_tags()
        self.textview.subtasks_callback(task.get_children)
        self.textview.removesubtask_callback(task.remove_child)
        self.textview.set_get_tagslist_callback(task.get_tags_name)
        self.textview.set_add_tag_callback(task.add_tag)
        self.textview.set_remove_tag_callback(task.remove_tag)
        self.textview.save_task_callback(self.light_save)

        texte = self.task.get_text()
        title = self.task.get_title()
        # the first line is the title
        self.textview.set_text("%s\n" % title)
        # we insert the rest of the task
        if texte:
            self.textview.insert("%s" % texte)
        else:
            # If not text, we insert tags
            if tags:
                for t in tags:
                    self.textview.insert_text("%s, " % t.get_name())
                self.textview.insert_text("\n")
            # If we don't have text, we still need to insert subtasks if any
            subtasks = task.get_children()
            if subtasks:
                self.textview.insert_subtasks(subtasks)
        # We select the title if it's a new task
        if thisisnew:
            self.textview.select_title()
        else:
            self.task.set_to_keep()
        self.textview.modified(full=True)
        self.window.connect("destroy", self.destruction)
        self.calendar.connect("date-changed", self.on_date_changed)

        # plugins
        self.pengine = PluginEngine()
        self.plugin_api = PluginAPI(self.req, self.vmanager, self)
        self.pengine.register_api(self.plugin_api)
        self.pengine.onTaskLoad(self.plugin_api)

        # Putting the refresh callback at the end make the start a lot faster
        self.textview.refresh_callback(self.refresh_editor)
        self.refresh_editor()
        self.textview.grab_focus()

        # restoring size and position, spatial tasks
        if self.config:
            tid = self.task.get_id()
            if tid in self.config:
                if "position" in self.config[tid]:
                    pos_x, pos_y = self.config[tid]["position"]
                    self.move(int(pos_x), int(pos_y))
                if "size" in self.config[tid]:
                    width, height = self.config[tid]["size"]
                    self.window.resize(int(width), int(height))

        self.textview.set_editable(True)
        self.window.show()

    # Define accelerator-keys for this dialog
    # TODO: undo/redo
    def init_accelerators(self):
        agr = gtk.AccelGroup()
        self.window.add_accel_group(agr)

        # Escape and Ctrl-W close the dialog. It's faster to call close
        # directly, rather than use the close button widget
        key, modifier = gtk.accelerator_parse('Escape')
        agr.connect_group(key, modifier, gtk.ACCEL_VISIBLE, self.close)

        key, modifier = gtk.accelerator_parse('<Control>w')
        agr.connect_group(key, modifier, gtk.ACCEL_VISIBLE, self.close)

        # Ctrl-N creates a new task
        key, modifier = gtk.accelerator_parse('<Control>n')
        agr.connect_group(key, modifier, gtk.ACCEL_VISIBLE, self.new_task)

        # Ctrl-Shift-N creates a new subtask
        insert_subtask = self.builder.get_object("insert_subtask")
        key, mod = gtk.accelerator_parse("<Control><Shift>n")
        insert_subtask.add_accelerator('clicked', agr, key, mod,
                                       gtk.ACCEL_VISIBLE)

        # Ctrl-D marks task as done
        mark_as_done_editor = self.builder.get_object('mark_as_done_editor')
        key, mod = gtk.accelerator_parse('<Control>d')
        mark_as_done_editor.add_accelerator('clicked', agr, key, mod,
                                            gtk.ACCEL_VISIBLE)

        # Ctrl-I marks task as dismissed
        dismiss_editor = self.builder.get_object('dismiss_editor')
        key, mod = gtk.accelerator_parse('<Control>i')
        dismiss_editor.add_accelerator('clicked', agr, key, mod,
                                       gtk.ACCEL_VISIBLE)

    # Can be called at any time to reflect the status of the Task
    # Refresh should never interfere with the TaskView.
    # If a title is passed as a parameter, it will become
    # the new window title. If not, we will look for the task title.
    # Refreshtext is whether or not we should refresh the TaskView
    #(doing it all the time is dangerous if the task is empty)
    def refresh_editor(self, title=None, refreshtext=False):
        if self.window is None:
            return
        to_save = False
        # title of the window
        if title:
            self.window.set_title(title)
            to_save = True
        else:
            self.window.set_title(self.task.get_title())

        status = self.task.get_status()
        dismiss_tooltip = GnomeConfig.MARK_UNDISMISS_TOOLTIP
        if status == Task.STA_DISMISSED:
            self.donebutton.set_label(GnomeConfig.MARK_DONE)
            self.donebutton.set_tooltip_text(GnomeConfig.MARK_DONE_TOOLTIP)
            self.donebutton.set_icon_name("gtg-task-done")
            self.dismissbutton.set_label(GnomeConfig.MARK_UNDISMISS)
            self.dismissbutton.set_tooltip_text(dismiss_tooltip)
            self.dismissbutton.set_icon_name("gtg-task-undismiss")
        elif status == Task.STA_DONE:
            self.donebutton.set_label(GnomeConfig.MARK_UNDONE)
            self.donebutton.set_tooltip_text(GnomeConfig.MARK_UNDONE_TOOLTIP)
            self.donebutton.set_icon_name("gtg-task-undone")
            self.dismissbutton.set_label(GnomeConfig.MARK_DISMISS)
            self.dismissbutton.set_tooltip_text(dismiss_tooltip)
            self.dismissbutton.set_icon_name("gtg-task-dismiss")
        else:
            self.donebutton.set_label(GnomeConfig.MARK_DONE)
            self.donebutton.set_tooltip_text(GnomeConfig.MARK_DONE_TOOLTIP)
            self.donebutton.set_icon_name("gtg-task-done")
            self.dismissbutton.set_label(GnomeConfig.MARK_DISMISS)
            self.dismissbutton.set_tooltip_text(dismiss_tooltip)
            self.dismissbutton.set_icon_name("gtg-task-dismiss")
        self.donebutton.show()
        self.tasksidebar.show()

        # Refreshing the status bar labels and date boxes
        if status in [Task.STA_DISMISSED, Task.STA_DONE]:
            self.builder.get_object("label2").hide()
            self.builder.get_object("hbox1").hide()
            self.builder.get_object("label4").show()
            self.builder.get_object("hbox4").show()
        else:
            self.builder.get_object("label4").hide()
            self.builder.get_object("hbox4").hide()
            self.builder.get_object("label2").show()
            self.builder.get_object("hbox1").show()

        # refreshing the start date field
        startdate = self.task.get_start_date()
        try:
            prevdate = Date.parse(self.startdate_widget.get_text())
            update_date = startdate != prevdate
        except ValueError:
            update_date = True

        if update_date:
            self.startdate_widget.set_text(str(startdate))

        # refreshing the due date field
        duedate = self.task.get_due_date()
        try:
            prevdate = Date.parse(self.duedate_widget.get_text())
            update_date = duedate != prevdate
        except ValueError:
            update_date = True

        if update_date:
            self.duedate_widget.set_text(str(duedate))

        # refreshing the closed date field
        closeddate = self.task.get_closed_date()
        prevcldate = Date.parse(self.closeddate_widget.get_text())
        if closeddate != prevcldate:
            self.closeddate_widget.set_text(str(closeddate))

        # refreshing the day left label
        # If the task is marked as done, we display the delay between the
        # due date and the actual closing date. If the task isn't marked
        # as done, we display the number of days left.
        if status in [Task.STA_DISMISSED, Task.STA_DONE]:
            delay = self.task.get_days_late()
            if delay is None:
                txt = ""
            elif delay == 0:
                txt = "Completed on time"
            elif delay >= 1:
                txt = ngettext("Completed %(days)d day late",
                               "Completed %(days)d days late", delay) % \
                    {'days': delay}
            elif delay <= -1:
                abs_delay = abs(delay)
                txt = ngettext("Completed %(days)d day early",
                               "Completed %(days)d days early", abs_delay) % \
                    {'days': abs_delay}
        else:
            due_date = self.task.get_due_date()
            result = due_date.days_left()
            if due_date.is_fuzzy():
                txt = ""
            elif result > 0:
                txt = ngettext("Due tomorrow!", "%(days)d days left", result) \
                    % {'days': result}
            elif result == 0:
                txt = _("Due today!")
            elif result < 0:
                abs_result = abs(result)
                txt = ngettext("Due yesterday!", "Was %(days)d days ago",
                               abs_result) % {
                                   'days': abs_result
                               }
        window_style = self.window.get_style()
        color = str(window_style.text[gtk.STATE_INSENSITIVE])
        self.dayleft_label.set_markup("<span color='" + color + "'>" + txt +
                                      "</span>")

        # Refreshing the tag list in the insert tag button
        taglist = self.req.get_used_tags()
        menu = gtk.Menu()
        tag_count = 0
        for tagname in taglist:
            tag_object = self.req.get_tag(tagname)
            if not tag_object.is_special() and \
                    not self.task.has_tags(tag_list=[tagname]):
                tag_count += 1
                mi = gtk.MenuItem(label=tagname, use_underline=False)
                mi.connect("activate", self.inserttag, tagname)
                mi.show()
                menu.append(mi)
        if tag_count > 0:
            self.inserttag_button.set_menu(menu)

        if refreshtext:
            self.textview.modified(refresheditor=False)
        if to_save:
            self.light_save()

    def date_changed(self, widget, data):
        try:
            Date.parse(widget.get_text())
            valid = True
        except ValueError:
            valid = False

        if valid:
            # If the date is valid, we write with default color in the widget
            # "none" will set the default color.
            widget.modify_text(gtk.STATE_NORMAL, None)
            widget.modify_base(gtk.STATE_NORMAL, None)
        else:
            # We should write in red in the entry if the date is not valid
            widget.modify_text(gtk.STATE_NORMAL, gtk.gdk.color_parse("#F00"))
            widget.modify_base(gtk.STATE_NORMAL, gtk.gdk.color_parse("#F88"))

    def date_focus_out(self, widget, event, data):
        try:
            datetoset = Date.parse(widget.get_text())
        except ValueError:
            datetoset = None

        if datetoset is not None:
            if data == "start":
                self.task.set_start_date(datetoset)
            elif data == "due":
                self.task.set_due_date(datetoset)
            elif data == "closed":
                self.task.set_closed_date(datetoset)
            self.refresh_editor()

    def on_date_pressed(self, widget, date_kind):
        """Called when a date-changing button is clicked."""
        if date_kind == GTGCalendar.DATE_KIND_DUE:
            if not self.task.get_due_date():
                date = self.task.get_start_date()
            else:
                date = self.task.get_due_date()
        elif date_kind == GTGCalendar.DATE_KIND_START:
            date = self.task.get_start_date()
        elif date_kind == GTGCalendar.DATE_KIND_CLOSED:
            date = self.task.get_closed_date()
        self.calendar.set_date(date, date_kind)
        # we show the calendar at the right position
        rect = widget.get_allocation()
        x, y = widget.window.get_origin()
        self.calendar.show_at_position(x + rect.x + rect.width, y + rect.y)

    def on_date_changed(self, calendar):
        date, date_kind = calendar.get_selected_date()
        if date_kind == GTGCalendar.DATE_KIND_DUE:
            self.task.set_due_date(date)
        elif date_kind == GTGCalendar.DATE_KIND_START:
            self.task.set_start_date(date)
        elif date_kind == GTGCalendar.DATE_KIND_CLOSED:
            self.task.set_closed_date(date)
        self.refresh_editor()

    def close_all_subtasks(self):
        all_subtasks = []

        def trace_subtasks(root):
            for i in root.get_subtasks():
                if i not in all_subtasks:
                    all_subtasks.append(i)
                    trace_subtasks(i)

        trace_subtasks(self.task)

        for task in all_subtasks:
            self.vmanager.close_task(task.get_id())

    def dismiss(self, widget):
        stat = self.task.get_status()
        if stat == "Dismiss":
            self.task.set_status("Active")
            self.refresh_editor()
        else:
            self.task.set_status("Dismiss")
            self.close_all_subtasks()
            self.close(None)

    def change_status(self, widget):
        stat = self.task.get_status()
        if stat == "Done":
            self.task.set_status("Active")
            self.refresh_editor()
        else:
            self.task.set_status("Done")
            self.close_all_subtasks()
            self.close(None)

    def delete_task(self, widget):
        # this triggers the closing of the window in the view manager
        if self.task.is_new():
            #            self.req.delete_task(self.task.get_id())
            self.vmanager.close_task(self.task.get_id())
        else:
            self.vmanager.ask_delete_tasks([self.task.get_id()])

    # Take the title as argument and return the subtask ID
    def new_subtask(self, title=None, tid=None):
        if tid:
            self.task.add_child(tid)
        elif title:
            subt = self.task.new_subtask()
            subt.set_title(title)
            tid = subt.get_id()
        return tid

    # Create a new task
    def new_task(self, *args):
        task = self.req.new_task(newtask=True)
        task_id = task.get_id()
        self.vmanager.open_task(task_id)

    def insert_subtask(self, widget):
        self.textview.insert_newtask()
        self.textview.grab_focus()

    def inserttag_clicked(self, widget):
        itera = self.textview.get_insert()
        if itera.starts_line():
            self.textview.insert_text("@", itera)
        else:
            self.textview.insert_text(" @", itera)
        self.textview.grab_focus()

    def inserttag(self, widget, tag):
        self.textview.insert_tags([tag])
        self.textview.grab_focus()

    def save(self):
        self.task.set_title(self.textview.get_title())
        self.task.set_text(self.textview.get_text())
        self.task.sync()
        if self.config is not None:
            self.config.write()
        self.time = time.time()

    # light_save save the task without refreshing every 30seconds
    # We will reduce the time when the get_text will be in another thread

    def light_save(self):
        # if self.time is none, we never called any save
        if self.time:
            diff = time.time() - self.time
            tosave = diff > GnomeConfig.SAVETIME
        else:
            # we don't want to save a task while opening it
            tosave = self.textview.get_editable()
            diff = None
        if tosave:
            self.save()

    # This will bring the Task Editor to front
    def present(self):
        self.window.present()

    def move(self, x, y):
        try:
            xx = int(x)
            yy = int(y)
            self.window.move(xx, yy)
        except:
            pass

    def get_position(self):
        return self.window.get_position()

    def on_move(self, widget, event):
        # saving the position
        if self.config is not None:
            tid = self.task.get_id()
            if not tid in self.config:
                self.config[tid] = dict()
            self.config[tid]["position"] = self.get_position()
            self.config[tid]["size"] = self.window.get_size()

    # We define dummy variable for when close is called from a callback
    def close(self, window=None, a=None, b=None, c=None):

        # We should also destroy the whole taskeditor object.
        if self.window:
            self.window.destroy()
            self.window = None

    # The destroy signal is linked to the "close" button. So if we call
    # destroy in the close function, this will cause the close to be called
    # twice
    # To solve that, close will just call "destroy" and the destroy signal
    # Will be linked to this destruction method that will save the task
    def destruction(self, a=None):
        # Save should be also called when buffer is modified
        self.pengine.onTaskClose(self.plugin_api)
        self.pengine.remove_api(self.plugin_api)
        tid = self.task.get_id()
        if self.task.is_new():
            self.req.delete_task(tid)
        else:
            self.save()
            for i in self.task.get_subtasks():
                if i:
                    i.set_to_keep()
        self.vmanager.close_task(tid)

    def get_builder(self):
        return self.builder

    def get_task(self):
        return self.task

    def get_textview(self):
        return self.textview

    def get_window(self):
        return self.window
示例#22
0
文件: editor.py 项目: sramkrishna/gtg
class TaskEditor():

    EDITOR_UI_FILE = os.path.join(UI_DIR, "task_editor.ui")

    def __init__(self, requester, app, task, thisisnew=False, clipboard=None):
        """
        req is the requester
        app is the view manager
        thisisnew is True when a new task is created and opened
        """
        self.req = requester
        self.app = app
        self.browser_config = self.req.get_config('browser')
        self.config = self.req.get_task_config(task.get_id())
        self.time = None
        self.clipboard = clipboard
        self.builder = Gtk.Builder()
        self.builder.add_from_file(self.EDITOR_UI_FILE)
        self.donebutton = self.builder.get_object("mark_as_done")
        self.undonebutton = self.builder.get_object("mark_as_undone")
        self.dismissbutton = self.builder.get_object("dismiss")
        self.undismissbutton = self.builder.get_object("undismiss")
        self.add_subtask = self.builder.get_object("add_subtask")
        self.tag_store = self.builder.get_object("tag_store")
        self.parent_button = self.builder.get_object("parent")

        # Closed date
        self.closed_popover = self.builder.get_object("closed_popover")
        self.closed_entry = self.builder.get_object("closeddate_entry")
        self.closed_calendar = self.builder.get_object("calendar_closed")

        # Start date
        self.start_popover = self.builder.get_object("start_popover")
        self.start_entry = self.builder.get_object("startdate_entry")
        self.start_calendar = self.builder.get_object("calendar_start")

        # Due date
        self.due_popover = self.builder.get_object("due_popover")
        self.due_entry = self.builder.get_object("duedate_entry")
        self.due_calendar = self.builder.get_object("calendar_due")

        # Create our dictionary and connect it
        dic = {
            "on_tags_popover":
            self.open_tags_popover,
            "on_tag_toggled":
            self.on_tag_toggled,
            "on_move":
            self.on_move,
            "show_popover_start":
            self.show_popover_start,
            "startingdate_changed":
            lambda w: self.date_changed(w, GTGCalendar.DATE_KIND_START),
            "startdate_cleared":
            lambda w: self.on_date_cleared(w, GTGCalendar.DATE_KIND_START),
            "startdate_focus_out":
            lambda w, e: self.date_focus_out(w, e, GTGCalendar.DATE_KIND_START
                                             ),
            "show_popover_due":
            self.show_popover_due,
            "duedate_changed":
            lambda w: self.date_changed(w, GTGCalendar.DATE_KIND_DUE),
            "duedate_now_selected":
            lambda w: self.on_duedate_fuzzy(w, Date.now()),
            "duedate_soon_selected":
            lambda w: self.on_duedate_fuzzy(w, Date.soon()),
            "duedate_someday_selected":
            lambda w: self.on_duedate_fuzzy(w, Date.someday()),
            "duedate_cleared":
            lambda w: self.on_date_cleared(w, GTGCalendar.DATE_KIND_DUE),
            "duedate_focus_out":
            lambda w, e: self.date_focus_out(w, e, GTGCalendar.DATE_KIND_DUE),
            "show_popover_closed":
            self.show_popover_closed,
            "closeddate_changed":
            lambda w: self.date_changed(w, GTGCalendar.DATE_KIND_CLOSED),
            "closeddate_focus_out":
            lambda w, e: self.date_focus_out(w, e, GTGCalendar.DATE_KIND_CLOSED
                                             ),
        }

        self.builder.connect_signals(dic)
        self.window = self.builder.get_object("TaskEditor")
        self.window.set_application(app)

        # Connect signals for the calendar
        self.start_handle = self.start_calendar.connect(
            'day-selected',
            lambda c: self.on_date_selected(c, GTGCalendar.DATE_KIND_START))

        self.due_handle = self.due_calendar.connect(
            'day-selected',
            lambda c: self.on_date_selected(c, GTGCalendar.DATE_KIND_DUE))

        self.closed_handle = self.closed_calendar.connect(
            'day-selected',
            lambda c: self.on_date_selected(c, GTGCalendar.DATE_KIND_CLOSED))

        # Removing the Normal textview to replace it by our own
        # So don't try to change anything with glade, this is a home-made
        # widget
        textview = self.builder.get_object("textview")
        scrolled = self.builder.get_object("scrolledtask")
        scrolled.remove(textview)
        self.textview = TaskView(self.req, self.clipboard)
        self.textview.show()
        self.textview.set_subtask_callback(self.new_subtask)
        self.textview.open_task_callback(self.app.open_task)
        self.textview.set_left_margin(20)
        self.textview.set_right_margin(20)
        scrolled.add(self.textview)
        conf_font_value = self.browser_config.get("font_name")
        if conf_font_value != "":
            self.textview.override_font(Pango.FontDescription(conf_font_value))
        # Voila! it's done
        """
        TODO(jakubbrindza): Once all the functionality in editor is back and
        working, bring back also the accelerators! Dayleft_label needs to be
        brought back, however its position is unsure.
        """
        # self.dayleft_label = self.builder.get_object("dayleft")

        self.task = task
        tags = task.get_tags()
        self.textview.subtasks_callback(task.get_children)
        self.textview.removesubtask_callback(task.remove_child)
        self.textview.set_get_tagslist_callback(task.get_tags_name)
        self.textview.set_add_tag_callback(task.add_tag)
        self.textview.set_remove_tag_callback(task.remove_tag)
        self.textview.save_task_callback(self.light_save)

        texte = self.task.get_text()
        title = self.task.get_title()
        # the first line is the title
        self.textview.set_text(f"{title}\n")
        # we insert the rest of the task
        if texte:
            self.textview.insert(f"{texte}")
        else:
            # If not text, we insert tags
            if tags:
                for t in tags:
                    self.textview.insert_text("%s, " % t.get_name())
                self.textview.insert_text("\n")
            # If we don't have text, we still need to insert subtasks if any
            subtasks = task.get_children()
            if subtasks:
                self.textview.insert_subtasks(subtasks)
        # We select the title if it's a new task
        if thisisnew:
            self.textview.select_title()
        else:
            self.task.set_to_keep()
        self.textview.modified(full=True)
        self.window.connect("destroy", self.destruction)

        # Connect search field to tags popup
        self.tags_entry = self.builder.get_object("tags_entry")
        self.tags_tree = self.builder.get_object("tags_tree")

        self.tags_tree.set_search_entry(self.tags_entry)
        self.tags_tree.set_search_equal_func(self.search_function, None)

        # plugins
        self.pengine = PluginEngine()
        self.plugin_api = PluginAPI(self.req, self.app, self)
        self.pengine.register_api(self.plugin_api)
        self.pengine.onTaskLoad(self.plugin_api)

        # Putting the refresh callback at the end make the start a lot faster
        self.textview.refresh_callback(self.refresh_editor)
        self.refresh_editor()
        self.textview.grab_focus()

        self.init_dimensions()

        self.window.insert_action_group('app', app)
        self.window.insert_action_group('win', app.browser)
        self._set_actions()

        self.textview.set_editable(True)
        self.window.set_transient_for(self.app.browser)
        self.window.show()

    def _set_actions(self):
        """Setup actions."""

        action_entries = [
            ('editor.close', self.close, ('app.editor.close',
                                          ['Escape', '<ctrl>w'])),
            ('editor.show_parent', self.on_parent_select, None),
            ('editor.delete', self.delete_task, None),
            ('editor.open_tags_popup', self.open_tags_popover, None),
        ]

        for action, callback, accel in action_entries:
            simple_action = Gio.SimpleAction.new(action, None)
            simple_action.connect('activate', callback)
            simple_action.set_enabled(True)

            self.app.add_action(simple_action)

            if accel is not None:
                self.app.set_accels_for_action(*accel)

    def show_popover_start(self, widget, event):
        """Open the start date calendar popup."""

        start_date = self.task.get_start_date() or Date.today()

        with signal_handler_block(self.start_calendar, self.start_handle):
            self.start_calendar.select_day(start_date.day)
            self.start_calendar.select_month(start_date.month - 1,
                                             start_date.year)

        self.start_popover.popup()

    def show_popover_due(self, widget, popover):
        """Open the due date calendar popup."""

        due_date = self.task.get_due_date()

        if not due_date or due_date.is_fuzzy():
            due_date = Date.today()

        with signal_handler_block(self.due_calendar, self.due_handle):
            self.due_calendar.select_day(due_date.day)
            self.due_calendar.select_month(due_date.month - 1, due_date.year)

        self.due_popover.popup()

    def show_popover_closed(self, widget, popover):
        """Open the closed date calendar popup."""

        closed_date = self.task.get_closed_date()

        with signal_handler_block(self.closed_calendar, self.closed_handle):
            self.closed_calendar.select_day(closed_date.day)
            self.closed_calendar.select_month(closed_date.month - 1,
                                              closed_date.year)

        self.closed_popover.popup()

    def open_tags_popover(self, action, param):
        self.tag_store.clear()

        tags = self.req.get_tag_tree().get_all_nodes()

        used_tags = self.task.get_tags()

        for tagname in tags:
            tag = self.req.get_tag(tagname)
            if tag_filter(tag):
                is_used = tag in used_tags
                self.tag_store.append([is_used, tagname])
                """
                TODO(jakubbrindza): add sorting of the tags based on
                True | False and within each sub-group arrange them
                alphabetically
                """

    def on_tag_toggled(self, widget, path, column):
        """We toggle by tag_row variable. tag_row is
        meant to be a tuple (is_used, tagname)"""
        tag_row = self.tag_store[path]
        tag_row[0] = not tag_row[0]

        if tag_row[0]:
            self.textview.insert_tags([tag_row[1]])
        """
        TODO(jakubbrindza): Add else case that will remove tag.
        """

    def search_function(self, model, column, key, iter, *search_data):
        """Callback when searching in the tags popup."""

        if not key.startswith('@'):
            key = f'@{key}'

        # The return value is reversed. False if it matches, True
        # otherwise.
        return not model.get(iter, column)[0].startswith(key)

    def init_dimensions(self):
        """ Restores position and size of task if possible """

        position = self.config.get('position')
        if position and len(position) == 2:
            try:
                self.window.move(int(position[0]), int(position[1]))
            except ValueError:
                log.warning('Invalid position configuration for task %s: %s',
                            self.task.get_id(), position)

        size = self.config.get('size')
        if size and len(size) == 2:
            try:
                self.window.resize(int(size[0]), int(size[1]))
            except ValueError:
                log.warning('Invalid size configuration for task %s: %s',
                            self.task.get_id(), size)

    # Can be called at any time to reflect the status of the Task
    # Refresh should never interfere with the TaskView.
    # If a title is passed as a parameter, it will become
    # the new window title. If not, we will look for the task title.
    # Refreshtext is whether or not we should refresh the TaskView
    # (doing it all the time is dangerous if the task is empty)
    def refresh_editor(self, title=None, refreshtext=False):
        if self.window is None:
            return
        to_save = False
        # title of the window
        if title:
            self.window.set_title(title)
            to_save = True
        else:
            self.window.set_title(self.task.get_title())

        status = self.task.get_status()
        if status == Task.STA_DISMISSED:
            self.donebutton.show()
            self.undonebutton.hide()
            self.dismissbutton.hide()
            self.undismissbutton.show()
        elif status == Task.STA_DONE:
            self.donebutton.hide()
            self.undonebutton.show()
            self.dismissbutton.show()
            self.undismissbutton.hide
        else:
            self.donebutton.show()
            self.undonebutton.hide()
            self.dismissbutton.show()
            self.undismissbutton.hide()

        # Refreshing the the parent button
        has_parents = len(self.task.get_parents()) > 0
        self.parent_button.set_sensitive(has_parents)

        # Refreshing the status bar labels and date boxes
        if status in [Task.STA_DISMISSED, Task.STA_DONE]:
            self.builder.get_object("start_box").hide()
            self.builder.get_object("closed_box").show()
        else:
            self.builder.get_object("closed_box").hide()
            self.builder.get_object("start_box").show()

        # refreshing the start date field
        startdate = self.task.get_start_date()
        try:
            prevdate = Date.parse(self.start_entry.get_text())
            update_date = startdate != prevdate
        except ValueError:
            update_date = True

        if update_date:
            self.start_entry.set_text(str(startdate))

        # refreshing the due date field
        duedate = self.task.get_due_date()
        try:
            prevdate = Date.parse(self.due_entry.get_text())
            update_date = duedate != prevdate
        except ValueError:
            update_date = True

        if update_date:
            self.due_entry.set_text(str(duedate))

        # refreshing the closed date field
        closeddate = self.task.get_closed_date()
        prevcldate = Date.parse(self.closed_entry.get_text())
        if closeddate != prevcldate:
            self.closed_entry.set_text(str(closeddate))

        # refreshing the day left label
        """
        TODO(jakubbrindza): re-enable refreshing the day left.
        We need to come up how and where this information is viewed
        in the editor window.
        """
        # self.refresh_day_left()

        if refreshtext:
            self.textview.modified(refresheditor=False)
        if to_save:
            self.light_save()

    def refresh_day_left(self):
        # If the task is marked as done, we display the delay between the
        # due date and the actual closing date. If the task isn't marked
        # as done, we display the number of days left.
        status = self.task.get_status()
        if status in [Task.STA_DISMISSED, Task.STA_DONE]:
            delay = self.task.get_days_late()
            if delay is None:
                txt = ""
            elif delay == 0:
                txt = "Completed on time"
            elif delay >= 1:
                txt = ngettext("Completed %(days)d day late",
                               "Completed %(days)d days late", delay) % \
                    {'days': delay}
            elif delay <= -1:
                abs_delay = abs(delay)
                txt = ngettext("Completed %(days)d day early",
                               "Completed %(days)d days early", abs_delay) % \
                    {'days': abs_delay}
        else:
            due_date = self.task.get_due_date()
            result = due_date.days_left()
            if due_date.is_fuzzy():
                txt = ""
            elif result > 0:
                txt = ngettext("Due tomorrow!", "%(days)d days left", result)\
                    % {'days': result}
            elif result == 0:
                txt = _("Due today!")
            elif result < 0:
                abs_result = abs(result)
                txt = ngettext("Due yesterday!", "Was %(days)d days ago",
                               abs_result) % {
                                   'days': abs_result
                               }

        style_context = self.window.get_style_context()
        color = style_context.get_color(Gtk.StateFlags.INSENSITIVE).to_color()
        self.dayleft_label.set_markup(
            f"<span color='{color.to_string()}'>{txt}</span>")

    def reload_editor(self):
        task = self.task
        textview = self.textview
        task_text = task.get_text()
        task_title = task.get_title()
        textview.set_text(f"{task_title}\n")
        if task_text:
            textview.insert(f"{task_text}")
        task.set_title(task_title)
        textview.modified(full=True)

    def date_changed(self, widget, data):
        try:
            Date.parse(widget.get_text())
            valid = True
        except ValueError:
            valid = False

        if valid:
            # If the date is valid, we write with default color in the widget
            # "none" will set the default color.
            widget.override_color(Gtk.StateType.NORMAL, None)
            widget.override_background_color(Gtk.StateType.NORMAL, None)
        else:
            # We should write in red in the entry if the date is not valid
            text_color = Gdk.RGBA()
            text_color.parse("#F00")
            widget.override_color(Gtk.StateType.NORMAL, text_color)

            bg_color = Gdk.RGBA()
            bg_color.parse("#F88")
            widget.override_background_color(Gtk.StateType.NORMAL, bg_color)

    def date_focus_out(self, widget, event, date_kind):
        try:
            datetoset = Date.parse(widget.get_text())
        except ValueError:
            datetoset = None

        if datetoset is not None:
            if date_kind == GTGCalendar.DATE_KIND_START:
                self.task.set_start_date(datetoset)
                self.start_popover.popdown()

            elif date_kind == GTGCalendar.DATE_KIND_DUE:
                self.task.set_due_date(datetoset)
                self.due_popover.popdown()

            elif date_kind == GTGCalendar.DATE_KIND_CLOSED:
                self.task.set_closed_date(datetoset)
                self.closed_popover.popdown()

            self.refresh_editor()

    def calendar_to_datetime(self, calendar):
        """
        Gtk.Calendar uses a 0-based convention for counting months.
        The rest of the world, including the datetime module, starts from 1.
        This is a converter between the two. GTG follows the datetime
        convention.
        """

        year, month, day = calendar.get_date()
        return datetime.date(year, month + 1, day)

    def on_duedate_fuzzy(self, widget, date):
        """ Callback when a fuzzy date is selected through the popup. """

        self.task.set_due_date(date)
        self.due_entry.set_text(str(date))

    def on_date_cleared(self, widget, kind):
        """ Callback when a date is cleared through the popups. """

        if kind == GTGCalendar.DATE_KIND_START:
            self.task.set_start_date(Date.no_date())
            self.start_entry.set_text('')

        elif kind == GTGCalendar.DATE_KIND_DUE:
            self.task.set_due_date(Date.no_date())
            self.due_entry.set_text('')

    def on_date_selected(self, calendar, kind):
        """ Callback when a day is selected in the calendars."""

        date = self.calendar_to_datetime(calendar)

        if kind == GTGCalendar.DATE_KIND_START:
            self.task.set_start_date(Date(date))
            self.start_entry.set_text(str(Date(date)))

        elif kind == GTGCalendar.DATE_KIND_DUE:
            self.task.set_due_date(Date(date))
            self.due_entry.set_text(str(Date(date)))

        elif kind == GTGCalendar.DATE_KIND_CLOSED:
            self.task.set_closed_date(Date(date))
            self.closed_entry.set_text(str(Date(date)))

    def on_date_changed(self, calendar):
        date, date_kind = calendar.get_selected_date()
        if date_kind == GTGCalendar.DATE_KIND_DUE:
            self.task.set_due_date(date)
        elif date_kind == GTGCalendar.DATE_KIND_START:
            self.task.set_start_date(date)
        elif date_kind == GTGCalendar.DATE_KIND_CLOSED:
            self.task.set_closed_date(date)
        self.refresh_editor()

    def close_all_subtasks(self):
        all_subtasks = []

        def trace_subtasks(root):
            for i in root.get_subtasks():
                if i not in all_subtasks:
                    all_subtasks.append(i)
                    trace_subtasks(i)

        trace_subtasks(self.task)

        for task in all_subtasks:
            self.app.close_task(task.get_id())

    def dismiss(self):
        stat = self.task.get_status()
        if stat == Task.STA_DISMISSED:
            self.task.set_status(Task.STA_ACTIVE)
            self.refresh_editor()
        else:
            self.task.set_status(Task.STA_DISMISSED)
            self.close_all_subtasks()
            self.close(None)

    def change_status(self):
        stat = self.task.get_status()
        if stat == Task.STA_DONE:
            self.task.set_status(Task.STA_ACTIVE)
            self.refresh_editor()
        else:
            self.task.set_status(Task.STA_DONE)
            self.close_all_subtasks()
            self.close(None)

    def delete_task(self, action, param):
        # this triggers the closing of the window in the view manager
        if self.task.is_new():
            self.app.close_task(self.task.get_id(), self.window)
        else:
            self.app.ask_delete_tasks([self.task.get_id()], self.window)

    # Take the title as argument and return the subtask ID
    def new_subtask(self, title=None, tid=None):
        if tid:
            self.task.add_child(tid)
        elif title:
            subt = self.task.new_subtask()
            subt.set_title(title)
            tid = subt.get_id()
        return tid

    def insert_subtask(self, action=None, param=None):
        self.textview.insert_newtask()
        self.textview.grab_focus()

    def inserttag_clicked(self, widget):
        itera = self.textview.get_insert()
        if itera.starts_line():
            self.textview.insert_text("@", itera)
        else:
            self.textview.insert_text(" @", itera)

    def on_parent_select(self, action, param):
        parents = self.task.get_parents()

        if not parents:
            tags = [t.get_name() for t in self.task.get_tags()]
            parent = self.req.new_task(tags=tags, newtask=True)
            parent_id = parent.get_id()

            self.task.set_parent(parent_id)
            self.app.open_task(parent_id)

        elif len(parents) == 1:
            self.app.open_task(parents[0])

        elif len(parents) > 1:
            self.show_multiple_parent_popover(parents)

    def show_multiple_parent_popover(self, parent_ids):
        parent_box = Gtk.Box.new(Gtk.Orientation.VERTICAL, 0)
        for parent in parent_ids:
            parent_name = self.req.get_task(parent).get_title()
            button = Gtk.ToolButton.new(None, parent_name)
            button.connect("clicked", self.on_parent_item_clicked, parent)
            parent_box.add(button)

        self.parent_popover = Gtk.Popover.new(self.parent_button)
        self.parent_popover.add(parent_box)
        self.parent_popover.set_property("border-width", 0)
        self.parent_popover.set_position(Gtk.PositionType.BOTTOM)
        self.parent_popover.set_transitions_enabled(True)
        self.parent_popover.show_all()

    # On click handler for open_parent_button's menu items
    def on_parent_item_clicked(self, widget, parent_id):
        self.app.open_task(parent_id)
        if self.parent_popover.get_visible():
            self.parent_popover.hide()

    def save(self):
        self.task.set_title(self.textview.get_title())
        self.task.set_text(self.textview.get_text())
        self.task.sync()
        if self.config is not None:
            self.config.save()
        self.time = time.time()

    # light_save save the task without refreshing every 30seconds
    # We will reduce the time when the get_text will be in another thread

    def light_save(self):
        # if self.time is none, we never called any save
        if self.time:
            diff = time.time() - self.time
            tosave = diff > GnomeConfig.SAVETIME
        else:
            # we don't want to save a task while opening it
            tosave = self.textview.get_editable()
            diff = None
        if tosave:
            self.save()

    # This will bring the Task Editor to front
    def present(self):
        self.window.present()

    def get_position(self):
        return self.window.get_position()

    def on_move(self, widget, event):
        """ Save position and size of window """

        self.config.set('position', list(self.window.get_position()))
        self.config.set('size', list(self.window.get_size()))

    # We define dummy variable for when close is called from a callback
    def close(self, action=None, param=None):

        # We should also destroy the whole taskeditor object.
        if self.window:
            self.window.destroy()
            self.window = None

    # The destroy signal is linked to the "close" button. So if we call
    # destroy in the close function, this will cause the close to be called
    # twice
    # To solve that, close will just call "destroy" and the destroy signal
    # Will be linked to this destruction method that will save the task
    def destruction(self, a=None):
        # Save should be also called when buffer is modified
        self.pengine.onTaskClose(self.plugin_api)
        self.pengine.remove_api(self.plugin_api)
        tid = self.task.get_id()
        if self.task.is_new():
            self.req.delete_task(tid)
        else:
            self.save()
            for i in self.task.get_subtasks():
                if i:
                    i.set_to_keep()
        self.app.close_task(tid)

    def get_builder(self):
        return self.builder

    def get_task(self):
        return self.task

    def get_textview(self):
        return self.textview

    def get_window(self):
        return self.window
示例#23
0
class Application(Gtk.Application):

    # Requester
    req = None

    # List of opened tasks (task editor windows). Task IDs are keys,
    # while the editors are their values.
    open_tasks = {}

    # The main window (AKA Task Browser)
    browser = None

    # Configuration sections
    config = None
    config_plugins = None

    # Shared clipboard
    clipboard = None

    # Timer to refresh views and purge tasks
    timer = None

    # Plugin Engine instance
    plugin_engine = None

    # Dialogs
    preferences_dialog = None
    plugins_dialog = None
    backends_dialog = None
    delete_task_dialog = None
    edit_tag_dialog = None

    def __init__(self, app_id):
        """Setup Application."""

        super().__init__(application_id=app_id,
                         flags=Gio.ApplicationFlags.HANDLES_OPEN)

    # --------------------------------------------------------------------------
    # INIT
    # --------------------------------------------------------------------------

    def do_startup(self):
        """Callback when primary instance should initialize"""
        Gtk.Application.do_startup(self)

        # Register backends
        datastore = DataStore()

        for backend_dic in BackendFactory().get_saved_backends_list():
            datastore.register_backend(backend_dic)

        # Save the backends directly to be sure projects.xml is written
        datastore.save(quit=False)

        self.req = datastore.get_requester()

        self.config = self.req.get_config("browser")
        self.config_plugins = self.req.get_config("plugins")

        self.clipboard = clipboard.TaskClipboard(self.req)

        self.timer = Timer(self.config)
        self.timer.connect('refresh', self.autoclean)

        self.preferences_dialog = Preferences(self.req, self)
        self.plugins_dialog = PluginsDialog(self.req)

        if self.config.get('dark_mode'):
            self.toggle_darkmode()

        self.init_style()

    def do_activate(self):
        """Callback when launched from the desktop."""

        self.init_shared()
        self.browser.present()

        log.debug("Application activation finished")

    def do_open(self, files, n_files, hint):
        """Callback when opening files/tasks"""

        self.init_shared()

        log.debug(f'Received {len(files)} Task URIs')
        if len(files) != n_files:
            log.warning(
                f"Length of files {len(files)} != supposed length {n_files}")

        for file in files:
            if file.get_uri_scheme() == 'gtg':
                uri = file.get_uri()
                if uri[4:6] != '//':
                    log.info(f"Malformed URI, needs gtg://: {uri}")
                else:
                    parsed = urllib.parse.urlparse(uri)
                    task_id = parsed.netloc
                    log.debug(f'Opening task {task_id}')
                    self.open_task(task_id)
            else:
                log.info(f"Unknown task to open: {file.get_uri()}")

        log.debug("Application opening finished")

    def init_shared(self):
        """
        Initialize stuff that can't be done in the startup signal,
        but in the open or activate signals, otherwise GTK will segfault
        when creating windows in the startup signal
        """
        if not self.browser:  # Prevent multiple inits
            self.init_browser()
            self.init_actions()
            self.init_plugin_engine()
            self.browser.present()

    def init_browser(self):
        # Browser (still hidden)
        if not self.browser:
            self.browser = MainWindow(self.req, self)

            if self.props.application_id == 'org.gnome.GTGDevel':
                self.browser.get_style_context().add_class('devel')

    def init_plugin_engine(self):
        """Setup the plugin engine."""

        self.plugin_engine = PluginEngine()
        plugin_api = PluginAPI(self.req, self)
        self.plugin_engine.register_api(plugin_api)

        try:
            enabled_plugins = self.config_plugins.get("enabled")
        except configparser.Error:
            enabled_plugins = []

        for plugin in self.plugin_engine.get_plugins():
            plugin.enabled = plugin.module_name in enabled_plugins

        self.plugin_engine.activate_plugins()

    def init_style(self):
        """Load the application's CSS file."""

        screen = Gdk.Screen.get_default()
        provider = Gtk.CssProvider()
        add_provider = Gtk.StyleContext.add_provider_for_screen
        css_path = os.path.join(CSS_DIR, 'style.css')

        provider.load_from_path(css_path)
        add_provider(screen, provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)

    def toggle_darkmode(self, state=True):
        """Use dark mode theme."""

        settings = Gtk.Settings.get_default()
        prefs_css = self.preferences_dialog.window.get_style_context()
        settings.set_property("gtk-application-prefer-dark-theme", state)

        # Toggle dark mode for preferences and editors
        if state:
            prefs_css.add_class('dark')
            text_tags.use_dark_mode()
        else:
            prefs_css.remove_class('dark')
            text_tags.use_light_mode()

    def init_actions(self):
        """Setup actions."""

        action_entries = [
            ('quit', lambda a, p: self.quit(), ('app.quit', ['<ctrl>Q'])),
            ('open_about', self.open_about, None),
            ('open_plugins', self.open_plugins_manager, None),
            ('new_task', self.new_task, ('app.new_task', ['<ctrl>N'])),
            ('new_subtask', self.new_subtask, ('app.new_subtask',
                                               ['<ctrl><shift>N'])),
            ('add_parent', self.add_parent, ('app.add_parent',
                                             ['<ctrl><shift>P'])),
            ('edit_task', self.edit_task, ('app.edit_task', ['<ctrl>E'])),
            ('mark_as_done', self.mark_as_done, ('app.mark_as_done',
                                                 ['<ctrl>D'])),
            ('dismiss', self.dismiss, ('app.dismiss', ['<ctrl>I'])),
            ('open_backends', self.open_backends_manager, None),
            ('open_help', self.open_help, ('app.open_help', ['F1'])),
            ('open_preferences', self.open_preferences,
             ('app.open_preferences', ['<ctrl>comma'])),
            ('close', self.close_context, ('app.close', ['Escape'])),
            ('editor.close', self.close_focused_task, ('app.editor.close',
                                                       ['<ctrl>w'])),
            ('editor.show_parent', self.open_parent_task, None),
            ('editor.delete', self.delete_editor_task, None),
            ('editor.open_tags_popup', self.open_tags_popup_in_editor, None),
        ]

        for action, callback, accel in action_entries:
            simple_action = Gio.SimpleAction.new(action, None)
            simple_action.connect('activate', callback)
            simple_action.set_enabled(True)

            self.add_action(simple_action)

            if accel is not None:
                self.set_accels_for_action(*accel)

        self.plugins_dialog.dialog.insert_action_group('app', self)

    # --------------------------------------------------------------------------
    # ACTIONS
    # --------------------------------------------------------------------------

    def new_task(self, param=None, action=None):
        """Callback to add a new task."""

        self.browser.on_add_task()

    def new_subtask(self, param, action):
        """Callback to add a new subtask."""

        try:
            self.get_active_editor().insert_subtask()
        except AttributeError:
            self.browser.on_add_subtask()

    def add_parent(self, param, action):
        """Callback to add a parent to a task"""
        if self.browser.have_same_parent():
            self.browser.on_add_parent()

    def edit_task(self, param, action):
        """Callback to edit a task."""

        self.browser.on_edit_active_task()

    def mark_as_done(self, param, action):
        """Callback to mark a task as done."""
        try:
            self.get_active_editor().change_status()
        except AttributeError:
            self.browser.on_mark_as_done()

    def dismiss(self, param, action):
        """Callback to mark a task as done."""

        try:
            self.get_active_editor().dismiss()
        except AttributeError:
            self.browser.on_dismiss_task()

    def open_help(self, action, param):
        """Open help callback."""

        try:
            Gtk.show_uri(None, "help:gtg", Gdk.CURRENT_TIME)
        except GLib.Error:
            log.error('Could not open help')

    def open_backends_manager(self, action, param):
        """Callback to open the backends manager dialog."""

        self.open_edit_backends()

    def open_preferences(self, action, param):
        """Callback to open the preferences dialog."""

        self.preferences_dialog.activate()
        self.preferences_dialog.window.set_transient_for(self.browser)

    def open_about(self, action, param):
        """Callback to open the about dialog."""

        self.browser.about.show()

    def open_plugins_manager(self, action, params):
        """Callback to open the plugins manager dialog."""

        self.plugins_dialog.activate()
        self.plugins_dialog.dialog.set_transient_for(self.browser)

    def close_context(self, action, params):
        """Callback to close based on the focus widget."""

        editor = self.get_active_editor()
        search = self.browser.search_entry.is_focus()

        if editor:
            self.close_task(editor.task.get_id())
        elif search:
            self.browser.toggle_search(action, params)

    def close_focused_task(self, action, params):
        """Callback to close currently focused task editor."""

        editor = self.get_active_editor()

        if editor:
            self.close_task(editor.task.get_id())

    def delete_editor_task(self, action, params):
        """Callback to delete the task currently open."""

        editor = self.get_active_editor()
        task = editor.task

        if task.is_new():
            self.close_task(task.get_id())
        else:
            self.delete_tasks([task.get_id()], editor.window)

    def open_tags_popup_in_editor(self, action, params):
        """Callback to open the tags popup in the focused task editor."""

        editor = self.get_active_editor()
        editor.open_tags_popover()

    def open_parent_task(self, action, params):
        """Callback to open the parent of the currently open task."""

        editor = self.get_active_editor()
        editor.open_parent()

    # --------------------------------------------------------------------------
    # TASKS AUTOCLEANING
    # --------------------------------------------------------------------------

    def purge_old_tasks(self, widget=None):
        """Remove closed tasks older than N days."""

        log.debug("Deleting old tasks")

        today = Date.today()
        max_days = self.config.get('autoclean_days')
        closed_tree = self.req.get_tasks_tree(name='inactive')

        closed_tasks = [
            self.req.get_task(tid) for tid in closed_tree.get_all_nodes()
        ]

        to_remove = [
            t for t in closed_tasks
            if (today - t.get_closed_date()).days > max_days
        ]

        [
            self.req.delete_task(task.get_id()) for task in to_remove
            if self.req.has_task(task.get_id())
        ]

    def autoclean(self, timer):
        """Run Automatic cleanup of old tasks."""

        if self.config.get('autoclean'):
            self.purge_old_tasks()

    # --------------------------------------------------------------------------
    # TASK BROWSER API
    # --------------------------------------------------------------------------

    def open_edit_backends(self, sender=None, backend_id=None):
        """Open the backends dialog."""

        self.backends_dialog = BackendsDialog(self.req)
        self.backends_dialog.dialog.insert_action_group('app', self)

        self.backends_dialog.activate()

        if backend_id:
            self.backends_dialog.show_config_for_backend(backend_id)

    def delete_tasks(self, tids, window):
        """Present the delete task confirmation dialog."""

        if not self.delete_task_dialog:
            self.delete_task_dialog = DeletionUI(self.req, window)

        tasks_to_delete = self.delete_task_dialog.show(tids)

        [
            self.close_task(task.get_id()) for task in tasks_to_delete
            if task.get_id() in self.open_tasks
        ]

    def open_tag_editor(self, tag):
        """Open Tag editor dialog."""

        if not self.edit_tag_dialog:
            self.edit_tag_dialog = TagEditor(self.req, self, tag)
            self.edit_tag_dialog.set_transient_for(self.browser)
            self.edit_tag_dialog.insert_action_group('app', self)
        else:
            self.edit_tag_dialog.set_tag(tag)

        self.edit_tag_dialog.present()

    def close_tag_editor(self):
        """Close tag editor dialog."""

        self.edit_tag_dialog.hide()

    def select_tag(self, tag):
        """Select a tag in the browser."""

        self.browser.select_on_sidebar(tag)

    # --------------------------------------------------------------------------
    # TASK EDITOR API
    # --------------------------------------------------------------------------

    def reload_opened_editors(self, task_uid_list=None):
        """Reloads all the opened editors passed in the list 'task_uid_list'.

        If 'task_uid_list' is not passed or None, we reload all the opened
        editors.
        """

        if task_uid_list:
            [
                self.open_tasks[tid].reload_editor() for tid in self.open_tasks
                if tid in task_uid_list
            ]
        else:
            [task.reload_editor() for task in self.open_tasks]

    def open_task(self, uid, new=False):
        """Open the task identified by 'uid'.

            If a Task editor is already opened for a given task, we present it.
            Otherwise, we create a new one.
        """

        if uid in self.open_tasks:
            editor = self.open_tasks[uid]
            editor.present()

        else:
            task = self.req.get_task(uid)
            editor = None

            if task:
                editor = TaskEditor(requester=self.req,
                                    app=self,
                                    task=task,
                                    thisisnew=new,
                                    clipboard=self.clipboard)

                editor.present()
                self.open_tasks[uid] = editor

                # Save open tasks to config
                open_tasks = self.config.get("opened_tasks")

                if uid not in open_tasks:
                    open_tasks.append(uid)

                self.config.set("opened_tasks", open_tasks)

            else:
                log.error(f'Task {uid} could not be found!')

        return editor

    def get_active_editor(self):
        """Get focused task editor window."""

        for editor in self.open_tasks.values():
            if editor.window.is_active():
                return editor

    def close_task(self, tid):
        """Close a task editor window."""

        try:
            editor = self.open_tasks[tid]
            editor.close()

            open_tasks = self.config.get("opened_tasks")

            if tid in open_tasks:
                open_tasks.remove(tid)

            self.config.set("opened_tasks", open_tasks)

        except KeyError:
            log.debug(f'Tried to close tid {tid} but it is not open')

    # --------------------------------------------------------------------------
    # SHUTDOWN
    # --------------------------------------------------------------------------

    def save_tasks(self):
        """Save opened tasks and their positions."""

        open_task = []

        for otid in list(self.open_tasks.keys()):
            open_task.append(otid)
            self.open_tasks[otid].close()

        self.config.set("opened_tasks", open_task)

    def save_plugin_settings(self):
        """Save plugin settings to configuration."""

        if self.plugin_engine is None:
            return  # Can't save when none has been loaded

        if self.plugin_engine.plugins:
            self.config_plugins.set('disabled', [
                p.module_name
                for p in self.plugin_engine.get_plugins('disabled')
            ])

            self.config_plugins.set('enabled', [
                p.module_name
                for p in self.plugin_engine.get_plugins('enabled')
            ])

        self.plugin_engine.deactivate_plugins()

    def quit(self):
        """Quit the application."""

        # This is needed to avoid warnings when closing the browser
        # with editor windows open, because of the "win"
        # group of actions.

        self.save_tasks()
        Gtk.Application.quit(self)

    def do_shutdown(self):
        """Callback when GTG is closed."""

        self.save_plugin_settings()

        if self.req is not None:
            # Save data and shutdown datastore backends
            self.req.save_datastore(quit=True)

        Gtk.Application.do_shutdown(self)

    # --------------------------------------------------------------------------
    # MISC
    # --------------------------------------------------------------------------

    def set_debug_flag(self, debug):
        """Set whenever it should activate debug stuff like logging or not"""
        if debug:
            log.setLevel(logging.DEBUG)
            log.debug("Debug output enabled.")
        else:
            log.setLevel(logging.INFO)
示例#24
0
文件: editor.py 项目: sramkrishna/gtg
    def __init__(self, requester, app, task, thisisnew=False, clipboard=None):
        """
        req is the requester
        app is the view manager
        thisisnew is True when a new task is created and opened
        """
        self.req = requester
        self.app = app
        self.browser_config = self.req.get_config('browser')
        self.config = self.req.get_task_config(task.get_id())
        self.time = None
        self.clipboard = clipboard
        self.builder = Gtk.Builder()
        self.builder.add_from_file(self.EDITOR_UI_FILE)
        self.donebutton = self.builder.get_object("mark_as_done")
        self.undonebutton = self.builder.get_object("mark_as_undone")
        self.dismissbutton = self.builder.get_object("dismiss")
        self.undismissbutton = self.builder.get_object("undismiss")
        self.add_subtask = self.builder.get_object("add_subtask")
        self.tag_store = self.builder.get_object("tag_store")
        self.parent_button = self.builder.get_object("parent")

        # Closed date
        self.closed_popover = self.builder.get_object("closed_popover")
        self.closed_entry = self.builder.get_object("closeddate_entry")
        self.closed_calendar = self.builder.get_object("calendar_closed")

        # Start date
        self.start_popover = self.builder.get_object("start_popover")
        self.start_entry = self.builder.get_object("startdate_entry")
        self.start_calendar = self.builder.get_object("calendar_start")

        # Due date
        self.due_popover = self.builder.get_object("due_popover")
        self.due_entry = self.builder.get_object("duedate_entry")
        self.due_calendar = self.builder.get_object("calendar_due")

        # Create our dictionary and connect it
        dic = {
            "on_tags_popover":
            self.open_tags_popover,
            "on_tag_toggled":
            self.on_tag_toggled,
            "on_move":
            self.on_move,
            "show_popover_start":
            self.show_popover_start,
            "startingdate_changed":
            lambda w: self.date_changed(w, GTGCalendar.DATE_KIND_START),
            "startdate_cleared":
            lambda w: self.on_date_cleared(w, GTGCalendar.DATE_KIND_START),
            "startdate_focus_out":
            lambda w, e: self.date_focus_out(w, e, GTGCalendar.DATE_KIND_START
                                             ),
            "show_popover_due":
            self.show_popover_due,
            "duedate_changed":
            lambda w: self.date_changed(w, GTGCalendar.DATE_KIND_DUE),
            "duedate_now_selected":
            lambda w: self.on_duedate_fuzzy(w, Date.now()),
            "duedate_soon_selected":
            lambda w: self.on_duedate_fuzzy(w, Date.soon()),
            "duedate_someday_selected":
            lambda w: self.on_duedate_fuzzy(w, Date.someday()),
            "duedate_cleared":
            lambda w: self.on_date_cleared(w, GTGCalendar.DATE_KIND_DUE),
            "duedate_focus_out":
            lambda w, e: self.date_focus_out(w, e, GTGCalendar.DATE_KIND_DUE),
            "show_popover_closed":
            self.show_popover_closed,
            "closeddate_changed":
            lambda w: self.date_changed(w, GTGCalendar.DATE_KIND_CLOSED),
            "closeddate_focus_out":
            lambda w, e: self.date_focus_out(w, e, GTGCalendar.DATE_KIND_CLOSED
                                             ),
        }

        self.builder.connect_signals(dic)
        self.window = self.builder.get_object("TaskEditor")
        self.window.set_application(app)

        # Connect signals for the calendar
        self.start_handle = self.start_calendar.connect(
            'day-selected',
            lambda c: self.on_date_selected(c, GTGCalendar.DATE_KIND_START))

        self.due_handle = self.due_calendar.connect(
            'day-selected',
            lambda c: self.on_date_selected(c, GTGCalendar.DATE_KIND_DUE))

        self.closed_handle = self.closed_calendar.connect(
            'day-selected',
            lambda c: self.on_date_selected(c, GTGCalendar.DATE_KIND_CLOSED))

        # Removing the Normal textview to replace it by our own
        # So don't try to change anything with glade, this is a home-made
        # widget
        textview = self.builder.get_object("textview")
        scrolled = self.builder.get_object("scrolledtask")
        scrolled.remove(textview)
        self.textview = TaskView(self.req, self.clipboard)
        self.textview.show()
        self.textview.set_subtask_callback(self.new_subtask)
        self.textview.open_task_callback(self.app.open_task)
        self.textview.set_left_margin(20)
        self.textview.set_right_margin(20)
        scrolled.add(self.textview)
        conf_font_value = self.browser_config.get("font_name")
        if conf_font_value != "":
            self.textview.override_font(Pango.FontDescription(conf_font_value))
        # Voila! it's done
        """
        TODO(jakubbrindza): Once all the functionality in editor is back and
        working, bring back also the accelerators! Dayleft_label needs to be
        brought back, however its position is unsure.
        """
        # self.dayleft_label = self.builder.get_object("dayleft")

        self.task = task
        tags = task.get_tags()
        self.textview.subtasks_callback(task.get_children)
        self.textview.removesubtask_callback(task.remove_child)
        self.textview.set_get_tagslist_callback(task.get_tags_name)
        self.textview.set_add_tag_callback(task.add_tag)
        self.textview.set_remove_tag_callback(task.remove_tag)
        self.textview.save_task_callback(self.light_save)

        texte = self.task.get_text()
        title = self.task.get_title()
        # the first line is the title
        self.textview.set_text(f"{title}\n")
        # we insert the rest of the task
        if texte:
            self.textview.insert(f"{texte}")
        else:
            # If not text, we insert tags
            if tags:
                for t in tags:
                    self.textview.insert_text("%s, " % t.get_name())
                self.textview.insert_text("\n")
            # If we don't have text, we still need to insert subtasks if any
            subtasks = task.get_children()
            if subtasks:
                self.textview.insert_subtasks(subtasks)
        # We select the title if it's a new task
        if thisisnew:
            self.textview.select_title()
        else:
            self.task.set_to_keep()
        self.textview.modified(full=True)
        self.window.connect("destroy", self.destruction)

        # Connect search field to tags popup
        self.tags_entry = self.builder.get_object("tags_entry")
        self.tags_tree = self.builder.get_object("tags_tree")

        self.tags_tree.set_search_entry(self.tags_entry)
        self.tags_tree.set_search_equal_func(self.search_function, None)

        # plugins
        self.pengine = PluginEngine()
        self.plugin_api = PluginAPI(self.req, self.app, self)
        self.pengine.register_api(self.plugin_api)
        self.pengine.onTaskLoad(self.plugin_api)

        # Putting the refresh callback at the end make the start a lot faster
        self.textview.refresh_callback(self.refresh_editor)
        self.refresh_editor()
        self.textview.grab_focus()

        self.init_dimensions()

        self.window.insert_action_group('app', app)
        self.window.insert_action_group('win', app.browser)
        self._set_actions()

        self.textview.set_editable(True)
        self.window.set_transient_for(self.app.browser)
        self.window.show()
示例#25
0
文件: editor.py 项目: tkdchen/gtg
    def __init__(self, requester, app, task, thisisnew=False, clipboard=None):
        """
        req is the requester
        app is the view manager
        thisisnew is True when a new task is created and opened
        """
        self.req = requester
        self.app = app
        self.browser_config = self.req.get_config('browser')
        self.config = self.req.get_task_config(task.get_id())
        self.time = None
        self.clipboard = clipboard
        self.builder = Gtk.Builder()
        self.builder.add_from_file(self.EDITOR_UI_FILE)
        self.donebutton = self.builder.get_object("mark_as_done")
        self.undonebutton = self.builder.get_object("mark_as_undone")
        self.dismissbutton = self.builder.get_object("dismiss")
        self.undismissbutton = self.builder.get_object("undismiss")
        self.add_subtask = self.builder.get_object("add_subtask")
        self.tag_store = self.builder.get_object("tag_store")
        self.parent_button = self.builder.get_object("parent")

        # Closed date
        self.closed_popover = self.builder.get_object("closed_popover")
        self.closed_entry = self.builder.get_object("closeddate_entry")
        self.closed_calendar = self.builder.get_object("calendar_closed")

        # Start date
        self.start_popover = self.builder.get_object("start_popover")
        self.start_entry = self.builder.get_object("startdate_entry")
        self.start_calendar = self.builder.get_object("calendar_start")

        # Due date
        self.due_popover = self.builder.get_object("due_popover")
        self.due_entry = self.builder.get_object("duedate_entry")
        self.due_calendar = self.builder.get_object("calendar_due")

        # Recurrence
        self.recurring_menu = RecurringMenu(self.req, task.tid, self.builder)

        # Create our dictionary and connect it
        dic = {
            "on_tags_popover":
            self.open_tags_popover,
            "on_tag_toggled":
            self.on_tag_toggled,
            "on_move":
            self.on_move,
            "set_recurring_term_every_day":
            self.set_recurring_term_every_day,
            "set_recurring_term_every_otherday":
            self.set_recurring_term_every_otherday,
            "set_recurring_term_every_week":
            self.set_recurring_term_every_week,
            "set_recurring_term_every_month":
            self.set_recurring_term_every_month,
            "set_recurring_term_every_year":
            self.set_recurring_term_every_year,
            "set_recurring_term_week_day":
            self.set_recurring_term_week_day,
            "set_recurring_term_calender_month":
            self.set_recurring_term_month,
            "set_recurring_term_calender_year":
            self.set_recurring_term_year,
            "toggle_recurring_status":
            self.toggle_recurring_status,
            "on_repeat_icon_toggled":
            self.on_repeat_icon_toggled,
            "show_popover_start":
            self.show_popover_start,
            "startingdate_changed":
            lambda w: self.date_changed(w, GTGCalendar.DATE_KIND_START),
            "startdate_cleared":
            lambda w: self.on_date_cleared(w, GTGCalendar.DATE_KIND_START),
            "startdate_focus_out":
            lambda w, e: self.date_focus_out(w, e, GTGCalendar.DATE_KIND_START
                                             ),
            "show_popover_due":
            self.show_popover_due,
            "duedate_changed":
            lambda w: self.date_changed(w, GTGCalendar.DATE_KIND_DUE),
            "duedate_now_selected":
            lambda w: self.on_duedate_fuzzy(w, Date.now()),
            "duedate_soon_selected":
            lambda w: self.on_duedate_fuzzy(w, Date.soon()),
            "duedate_someday_selected":
            lambda w: self.on_duedate_fuzzy(w, Date.someday()),
            "duedate_cleared":
            lambda w: self.on_date_cleared(w, GTGCalendar.DATE_KIND_DUE),
            "duedate_focus_out":
            lambda w, e: self.date_focus_out(w, e, GTGCalendar.DATE_KIND_DUE),
            "show_popover_closed":
            self.show_popover_closed,
            "closeddate_changed":
            lambda w: self.date_changed(w, GTGCalendar.DATE_KIND_CLOSED),
            "closeddate_focus_out":
            lambda w, e: self.date_focus_out(w, e, GTGCalendar.DATE_KIND_CLOSED
                                             ),
        }

        self.window = self.builder.get_object("TaskEditor")
        self.builder.connect_signals(dic)
        self.window.set_application(app)

        if task.has_parent():
            self.parent_button.set_label(_('Open Parent'))
        else:
            self.parent_button.set_label(_('Add Parent'))

        # Connect signals for the calendar
        self.start_handle = self.start_calendar.connect(
            'day-selected',
            lambda c: self.on_date_selected(c, GTGCalendar.DATE_KIND_START))

        self.due_handle = self.due_calendar.connect(
            'day-selected',
            lambda c: self.on_date_selected(c, GTGCalendar.DATE_KIND_DUE))

        self.closed_handle = self.closed_calendar.connect(
            'day-selected',
            lambda c: self.on_date_selected(c, GTGCalendar.DATE_KIND_CLOSED))

        # Removing the Normal textview to replace it by our own
        # So don't try to change anything with glade, this is a home-made
        # widget
        textview = self.builder.get_object("textview")
        scrolled = self.builder.get_object("scrolledtask")
        scrolled.remove(textview)
        self.textview = TaskView(self.req, self.clipboard)
        self.textview.show()
        scrolled.add(self.textview)
        conf_font_value = self.browser_config.get("font_name")
        if conf_font_value != "":
            self.textview.override_font(Pango.FontDescription(conf_font_value))

        self.textview.browse_tag_cb = app.select_tag
        self.textview.new_subtask_cb = self.new_subtask
        self.textview.get_subtasks_cb = task.get_children
        self.textview.delete_subtask_cb = self.remove_subtask
        self.textview.rename_subtask_cb = self.rename_subtask
        self.textview.open_subtask_cb = self.open_subtask
        self.textview.save_cb = self.light_save
        self.textview.add_tasktag_cb = task.tag_added
        self.textview.remove_tasktag_cb = task.remove_tag
        self.textview.refresh_cb = self.refresh_editor
        self.textview.get_tagslist_cb = task.get_tags_name
        self.textview.tid = task.tid

        # Voila! it's done
        self.textview.connect('focus-in-event', self.on_textview_focus_in)
        self.textview.connect('focus-out-event', self.on_textview_focus_out)
        """
        TODO(jakubbrindza): Once all the functionality in editor is back and
        working, bring back also the accelerators! Dayleft_label needs to be
        brought back, however its position is unsure.
        """
        # self.dayleft_label = self.builder.get_object("dayleft")

        self.task = task
        tags = task.get_tags()
        text = self.task.get_text()
        title = self.task.get_title()

        self.textview.buffer.set_text(f"{title}\n")

        if text:
            self.textview.insert(text)

            # Insert any remaining tags
            if tags:
                tag_names = [t.get_name() for t in tags]
                self.textview.insert_tags(tag_names)
        else:
            # If not text, we insert tags
            if tags:
                tag_names = [t.get_name() for t in tags]
                self.textview.insert_tags(tag_names)
                start = self.textview.buffer.get_end_iter()
                self.textview.buffer.insert(start, '\n')

        # Insert subtasks if they weren't inserted in the text
        subtasks = task.get_children()
        for sub in subtasks:
            if sub not in self.textview.subtasks['tags']:
                self.textview.insert_existing_subtask(sub)

        if thisisnew:
            self.textview.select_title()
        else:
            self.task.set_to_keep()

        self.window.connect("destroy", self.destruction)

        # Connect search field to tags popup
        self.tags_entry = self.builder.get_object("tags_entry")
        self.tags_tree = self.builder.get_object("tags_tree")

        self.tags_tree.set_search_entry(self.tags_entry)
        self.tags_tree.set_search_equal_func(self.search_function, None)

        # plugins
        self.pengine = PluginEngine()
        self.plugin_api = PluginAPI(self.req, self.app, self)
        self.pengine.register_api(self.plugin_api)
        self.pengine.onTaskLoad(self.plugin_api)

        # Putting the refresh callback at the end make the start a lot faster
        self.refresh_editor()
        self.textview.grab_focus()

        self.init_dimensions()

        self.window.insert_action_group('app', app)
        self.window.insert_action_group('win', app.browser)

        self.textview.set_editable(True)
        self.window.set_transient_for(self.app.browser)
        self.window.show()
示例#26
0
class Manager():

    # init ##################################################################
    def __init__(self, req):
        self.req = req
        self.browser_config = self.req.get_config("browser")
        self.plugins_config = self.req.get_config("plugins")

        # Editors
        # This is the list of tasks that are already opened in an editor
        # of course it's empty right now
        self.opened_task = {}

        self.browser = None
        self.__start_browser_hidden = False
        self.gtk_terminate = False  # if true, the gtk main is not started

        # Shared clipboard
        self.clipboard = clipboard.TaskClipboard(self.req)

        # Initialize Timer
        self.config = self.req.get_config('browser')
        self.timer = Timer(self.config)
        self.timer.connect('refresh', self.autoclean)

        # Load custom css
        self.__init_css()

        # Browser (still hidden)
        self.browser = TaskBrowser(self.req, self)

        self.__init_plugin_engine()

        if not self.__start_browser_hidden:
            self.show_browser()

        # Deletion UI
        self.delete_dialog = None

        # Preferences and Backends windows
        # Initialize  dialogs
        self.preferences = Preferences(self.req, self)
        self.plugins = PluginsDialog(self.req)
        self.edit_backends_dialog = None

        # Tag Editor
        self.tag_editor_dialog = None

        # DBus
        DBusTaskWrapper(self.req, self)
        log.debug("Manager initialization finished")

    def __init_plugin_engine(self):
        self.pengine = PluginEngine()
        # initializes the plugin api class
        self.plugin_api = PluginAPI(self.req, self)
        self.pengine.register_api(self.plugin_api)
        # checks the conf for user settings
        try:
            plugins_enabled = self.plugins_config.get("enabled")
        except configparser.Error:
            plugins_enabled = []
        for plugin in self.pengine.get_plugins():
            plugin.enabled = plugin.module_name in plugins_enabled
        # initializes and activates each plugin (that is enabled)
        self.pengine.activate_plugins()

    def __init_css(self):
        """Load the application's CSS file."""

        screen = Gdk.Screen.get_default()
        provider = Gtk.CssProvider()
        css_path = os.path.join(CSS_DIR, 'style.css')

        provider.load_from_path(css_path)
        Gtk.StyleContext.add_provider_for_screen(
            screen, provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)

    # Browser ##############################################################
    def open_browser(self):
        if not self.browser:
            self.browser = TaskBrowser(self.req, self)
        # notify user if backup was used
        backend_dic = self.req.get_all_backends()
        for backend in backend_dic:
            if backend.get_name() == "backend_localfile" and \
                    backend.used_backup():
                backend.notify_user_about_backup()
        log.debug("Browser is open")

    # FIXME : the browser should not be the center of the universe.
    # In fact, we should build a system where view can register themselves
    # as "stay_alive" views. As long as at least one "stay_alive" view
    # is registered, gtg keeps running. It quit only when the last
    # "stay_alive view" is closed (and then unregistered).
    # Currently, the browser is our only "stay_alive" view.
    def close_browser(self, sender=None):
        self.hide_browser()
        # may take a while to quit
        self.quit()

    def hide_browser(self, sender=None):
        self.browser.hide()

    def iconify_browser(self, sender=None):
        self.browser.iconify()

    def show_browser(self, sender=None):
        self.browser.show()

    def is_browser_visible(self, sender=None):
        return self.browser.is_visible()

    def get_browser(self):
        # used by the plugin api to hook in the browser
        return self.browser

    def start_browser_hidden(self):
        self.__start_browser_hidden = True

    def purge_old_tasks(self, widget=None):
        log.debug("Deleting old tasks")

        today = Date.today()
        max_days = self.config.get('autoclean_days')
        closed_tree = self.req.get_tasks_tree(name='inactive')

        closed_tasks = [
            self.req.get_task(tid) for tid in closed_tree.get_all_nodes()
        ]

        to_remove = [
            t for t in closed_tasks
            if (today - t.get_closed_date()).days > max_days
        ]

        [
            self.req.delete_task(task.get_id()) for task in to_remove
            if self.req.has_task(task.get_id())
        ]

    def autoclean(self, timer):
        """Run Automatic cleanup of old tasks."""

        if self.config.get('autoclean'):
            self.purge_old_tasks()

# Task Editor ############################################################

    def get_opened_editors(self):
        """
        Returns a dict of task_uid -> TaskEditor, one for each opened editor
        window
        """
        return self.opened_task

    def reload_opened_editors(self, task_uid_list=None):
        """Reloads all the opened editors passed in the list 'task_uid_list'.

        If 'task_uid_list' is not passed or None, we reload all the opened editors.
        Else, we reload the editors of tasks in 'task_uid_list' only.
        """
        opened_editors = self.get_opened_editors()
        for t in opened_editors:
            if not task_uid_list or t in task_uid_list:
                opened_editors[t].reload_editor()

    def open_task(self, uid, thisisnew=False):
        """Open the task identified by 'uid'.

        If a Task editor is already opened for a given task, we present it.
        Else, we create a new one.
        """
        t = self.req.get_task(uid)
        tv = None
        if uid in self.opened_task:
            tv = self.opened_task[uid]
            tv.present()
        elif t:
            tv = TaskEditor(requester=self.req,
                            vmanager=self,
                            task=t,
                            thisisnew=thisisnew,
                            clipboard=self.clipboard)
            tv.present()
            # registering as opened
            self.opened_task[uid] = tv
            # save that we opened this task
            opened_tasks = self.browser_config.get("opened_tasks")
            if uid not in opened_tasks:
                opened_tasks.append(uid)
            self.browser_config.set("opened_tasks", opened_tasks)
        return tv

    def close_task(self, tid):
        # When an editor is closed, it should de-register itself.
        if tid in self.opened_task:
            # the following line has the side effect of removing the
            # tid key in the opened_task dictionary.
            editor = self.opened_task[tid]
            if editor:
                del self.opened_task[tid]
                # we have to remove the tid from opened_task first
                # else, it close_task would be called once again
                # by editor.close
                editor.close()
            opened_tasks = self.browser_config.get("opened_tasks")
            if tid in opened_tasks:
                opened_tasks.remove(tid)
            self.browser_config.set("opened_tasks", opened_tasks)
        self.check_quit_condition()

    def check_quit_condition(self):
        """
        checking if we need to shut down the whole GTG (if no window is open)
        """

        if not self.is_browser_visible() and not self.opened_task:
            self.quit()

# Others dialog ###########################################################

    def open_edit_backends(self, sender=None, backend_id=None):
        if not self.edit_backends_dialog:
            self.edit_backends_dialog = BackendsDialog(self.req)
        self.edit_backends_dialog.activate()
        if backend_id is not None:
            self.edit_backends_dialog.show_config_for_backend(backend_id)

    def configure_backend(self, backend_id):
        self.open_edit_backends(None, backend_id)

    def open_preferences(self, config_priv):
        self.preferences.activate()

    def configure_plugins(self):
        self.plugins.activate()

    def ask_delete_tasks(self, tids, window):
        if not self.delete_dialog:
            self.delete_dialog = DeletionUI(self.req, window)
        finallist = self.delete_dialog.show(tids)
        for t in finallist:
            if t.get_id() in self.opened_task:
                self.close_task(t.get_id())

    def open_tag_editor(self, tag):
        if not self.tag_editor_dialog:
            self.tag_editor_dialog = TagEditor(self.req, self, tag)
        else:
            self.tag_editor_dialog.set_tag(tag)
        self.tag_editor_dialog.show()
        self.tag_editor_dialog.present()

    def close_tag_editor(self):
        self.tag_editor_dialog.hide()

# URIS #####################################################################

    def open_uri_list(self, unused, uri_list):
        """
        Open the Editor windows of the tasks associated with the uris given.
        Uris are of the form gtg://<taskid>
        """
        for uri in uri_list:
            if uri.startswith("gtg://"):
                self.open_task(uri[6:])
        # if no window was opened, we just quit
        self.check_quit_condition()

# MAIN #####################################################################

    def main(self, once_thru=False, uri_list=[]):
        if uri_list:
            # before opening the requested tasks, we make sure that all of them
            # are loaded.
            BackendSignals().connect('default-backend-loaded',
                                     self.open_uri_list, uri_list)
        else:
            self.open_browser()
        GObject.threads_init()
        if not self.gtk_terminate:
            if once_thru:
                Gtk.main_iteration()
            else:
                Gtk.main()
        return 0

    def quit(self, sender=None):
        Gtk.main_quit()
        # save opened tasks and their positions.
        open_task = []
        for otid in list(self.opened_task.keys()):
            open_task.append(otid)
            self.opened_task[otid].close()
        self.browser_config.set("opened_tasks", open_task)

        # adds the plugin settings to the conf
        # FIXME: this code is replicated in the preference window.
        if len(self.pengine.plugins) > 0:
            self.plugins_config.set(
                "disabled",
                [p.module_name for p in self.pengine.get_plugins("disabled")],
            )
            self.plugins_config.set(
                "enabled",
                [p.module_name for p in self.pengine.get_plugins("enabled")],
            )
        # plugins are deactivated
        self.pengine.deactivate_plugins()
示例#27
0
文件: application.py 项目: jscn/gtg
class Application(Gtk.Application):

    # Requester
    req = None

    # List of Task URIs to open
    uri_list = None

    # List of opened tasks (task editor windows). Task IDs are keys,
    # while the editors are their values.
    open_tasks = {}

    # The main window (AKA Task Browser)
    browser = None

    # Configuration sections
    config = None
    config_plugins = None

    # Shared clipboard
    clipboard = None

    # Timer to refresh views and purge tasks
    timer = None

    # Plugin Engine instance
    plugin_engine = None

    # Dialogs
    preferences_dialog = None
    plugins_dialog = None
    backends_dialog = None
    delete_task_dialog = None
    edit_tag_dialog = None

    def __init__(self, app_id, debug):
        """Setup Application."""

        super().__init__(application_id=app_id)

        if debug:
            log.setLevel(logging.DEBUG)
            log.debug("Debug output enabled.")
        else:
            log.setLevel(logging.INFO)

        # Register backends
        datastore = DataStore()

        [datastore.register_backend(backend_dic)
         for backend_dic in BackendFactory().get_saved_backends_list()]

        # Save the backends directly to be sure projects.xml is written
        datastore.save(quit=False)

        self.req = datastore.get_requester()

        self.config = self.req.get_config("browser")
        self.config_plugins = self.req.get_config("plugins")

        self.clipboard = clipboard.TaskClipboard(self.req)

        self.timer = Timer(self.config)
        self.timer.connect('refresh', self.autoclean)

        self.preferences_dialog = Preferences(self.req, self)
        self.plugins_dialog = PluginsDialog(self.req)

        self.init_style()


    # --------------------------------------------------------------------------
    # INIT
    # --------------------------------------------------------------------------

    def do_activate(self):
        """Callback when launched from the desktop."""

        # Browser (still hidden)
        if not self.browser:
            self.browser = MainWindow(self.req, self)

        if self.props.application_id == 'org.gnome.GTGDevel':
            self.browser.get_style_context().add_class('devel')

        self.init_actions()
        self.init_plugin_engine()
        self.browser.present()
        self.open_uri_list()

        log.debug("Application activation finished")

    def init_plugin_engine(self):
        """Setup the plugin engine."""

        self.plugin_engine = PluginEngine()
        plugin_api = PluginAPI(self.req, self)
        self.plugin_engine.register_api(plugin_api)

        try:
            enabled_plugins = self.config_plugins.get("enabled")
        except configparser.Error:
            enabled_plugins = []

        for plugin in self.plugin_engine.get_plugins():
            plugin.enabled = plugin.module_name in enabled_plugins

        self.plugin_engine.activate_plugins()

    def init_style(self):
        """Load the application's CSS file."""

        screen = Gdk.Screen.get_default()
        provider = Gtk.CssProvider()
        add_provider = Gtk.StyleContext.add_provider_for_screen
        css_path = os.path.join(CSS_DIR, 'style.css')

        provider.load_from_path(css_path)
        add_provider(screen, provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)

    def init_actions(self):
        """Setup actions."""

        action_entries = [
            ('quit', lambda a, p: self.quit(), ('app.quit', ['<ctrl>Q'])),
            ('open_about', self.open_about, None),
            ('open_plugins', self.open_plugins_manager, None),
            ('new_task', self.new_task, ('app.new_task', ['<ctrl>N'])),
            ('new_subtask', self.new_subtask, ('app.new_subtask', ['<ctrl><shift>N'])),
            ('edit_task', self.edit_task, ('app.edit_task', ['<ctrl>E'])),
            ('mark_as_done', self.mark_as_done, ('app.mark_as_done', ['<ctrl>D'])),
            ('dismiss', self.dismiss, ('app.dismiss', ['<ctrl>I'])),
            ('open_backends', self.open_backends_manager, None),
            ('open_help', self.open_help, ('app.open_help', ['F1'])),
            ('open_preferences', self.open_preferences, ('app.open_preferences', ['<ctrl>comma'])),
            ('editor.close', self.close_focused_task, ('app.editor.close', ['Escape', '<ctrl>w'])),
            ('editor.show_parent', self.open_parent_task, None),
            ('editor.delete', self.delete_editor_task, None),
            ('editor.open_tags_popup', self.open_tags_popup_in_editor, None),
        ]

        for action, callback, accel in action_entries:
            simple_action = Gio.SimpleAction.new(action, None)
            simple_action.connect('activate', callback)
            simple_action.set_enabled(True)

            self.add_action(simple_action)

            if accel is not None:
                self.set_accels_for_action(*accel)

        self.plugins_dialog.dialog.insert_action_group('app', self)

    def open_uri_list(self):
        """Open the Editor windows of the tasks associated with the uris given.
           Uris are of the form gtg://<taskid>
        """

        log.debug(f'Received {len(self.uri_list)} Task URIs')

        for uri in self.uri_list:
            if uri.startswith('gtg://'):
                log.debug(f'Opening task {uri[6:]}')
                self.open_task(uri[6:])

        # if no window was opened, we just quit
        if not self.browser.is_visible() and not self.open_tasks:
            self.quit()

    # --------------------------------------------------------------------------
    # ACTIONS
    # --------------------------------------------------------------------------

    def new_task(self, param=None, action=None):
        """Callback to add a new task."""

        self.browser.on_add_task()

    def new_subtask(self, param, action):
        """Callback to add a new subtask."""

        try:
            self.get_active_editor().insert_subtask()
        except AttributeError:
            self.browser.on_add_subtask()

    def edit_task(self, param, action):
        """Callback to edit a task."""

        self.browser.on_edit_active_task()

    def mark_as_done(self, param, action):
        """Callback to mark a task as done."""
        try:
            self.get_active_editor().change_status()
        except AttributeError:
            self.browser.on_mark_as_done()

    def dismiss(self, param, action):
        """Callback to mark a task as done."""

        try:
            self.get_active_editor().dismiss()
        except AttributeError:
            self.browser.on_dismiss_task()

    def open_help(self, action, param):
        """Open help callback."""

        openurl("help:gtg")

    def open_backends_manager(self, action, param):
        """Callback to open the backends manager dialog."""

        self.open_edit_backends()

    def open_preferences(self, action, param):
        """Callback to open the preferences dialog."""

        self.preferences_dialog.activate()
        self.preferences_dialog.window.set_transient_for(self.browser)

    def open_about(self, action, param):
        """Callback to open the about dialog."""

        self.browser.about.show()

    def open_plugins_manager(self, action, params):
        """Callback to open the plugins manager dialog."""

        self.plugins_dialog.activate()
        self.plugins_dialog.dialog.set_transient_for(self.browser)

    def close_focused_task(self, action, params):
        """Callback to close currently focused task editor."""

        editor = self.get_active_editor()

        if editor:
            self.close_task(editor.task.get_id())

    def delete_editor_task(self, action, params):
        """Callback to delete the task currently open."""

        editor = self.get_active_editor()
        task = editor.task

        if task.is_new():
            self.close_task(task.get_id())
        else:
            self.delete_tasks([task.get_id()], editor.window)

    def open_tags_popup_in_editor(self, action, params):
        """Callback to open the tags popup in the focused task editor."""

        editor = self.get_active_editor()
        editor.open_tags_popover()

    def open_parent_task(self, action, params):
        """Callback to open the parent of the currently open task."""

        editor = self.get_active_editor()
        editor.open_parent()

    # --------------------------------------------------------------------------
    # TASKS AUTOCLEANING
    # --------------------------------------------------------------------------

    def purge_old_tasks(self, widget=None):
        """Remove closed tasks older than N days."""

        log.debug("Deleting old tasks")

        today = Date.today()
        max_days = self.config.get('autoclean_days')
        closed_tree = self.req.get_tasks_tree(name='inactive')

        closed_tasks = [self.req.get_task(tid) for tid in
                        closed_tree.get_all_nodes()]

        to_remove = [t for t in closed_tasks
                     if (today - t.get_closed_date()).days > max_days]

        [self.req.delete_task(task.get_id())
         for task in to_remove
         if self.req.has_task(task.get_id())]

    def autoclean(self, timer):
        """Run Automatic cleanup of old tasks."""

        if self.config.get('autoclean'):
            self.purge_old_tasks()

    # --------------------------------------------------------------------------
    # TASK BROWSER API
    # --------------------------------------------------------------------------

    def open_edit_backends(self, sender=None, backend_id=None):
        """Open the backends dialog."""

        self.backends_dialog = BackendsDialog(self.req)
        self.backends_dialog.dialog.insert_action_group('app', self)

        self.backends_dialog.activate()

        if backend_id:
            self.backends_dialog.show_config_for_backend(backend_id)

    def delete_tasks(self, tids, window):
        """Present the delete task confirmation dialog."""

        if not self.delete_task_dialog:
            self.delete_task_dialog = DeletionUI(self.req, window)

        tags_to_delete = self.delete_task_dialog.show(tids)

        [self.close_task(task.get_id()) for task in tags_to_delete
         if task.get_id() in self.open_tasks]

    def open_tag_editor(self, tag):
        """Open Tag editor dialog."""

        if not self.edit_tag_dialog:
            self.edit_tag_dialog = TagEditor(self.req, self, tag)
            self.edit_tag_dialog.set_transient_for(self.browser)
        else:
            self.edit_tag_dialog.set_tag(tag)

        self.edit_tag_dialog.present()

    def close_tag_editor(self):
        """Close tag editor dialog."""

        self.edit_tag_dialog.hide()

    # --------------------------------------------------------------------------
    # TASK EDITOR API
    # --------------------------------------------------------------------------

    def reload_opened_editors(self, task_uid_list=None):
        """Reloads all the opened editors passed in the list 'task_uid_list'.

        If 'task_uid_list' is not passed or None, we reload all the opened
        editors.
        """

        if task_uid_list:
            [self.open_tasks[tid].reload_editor() for tid in self.open_tasks
             if tid in task_uid_list]
        else:
            [task.reload_editor() for task in self.open_tasks]

    def open_task(self, uid, new=False):
        """Open the task identified by 'uid'.

            If a Task editor is already opened for a given task, we present it.
            Otherwise, we create a new one.
        """

        if uid in self.open_tasks:
            editor = self.open_tasks[uid]
            editor.present()

        else:
            task = self.req.get_task(uid)
            editor = None

            if task:
                editor = TaskEditor(requester=self.req, app=self, task=task,
                                    thisisnew=new, clipboard=self.clipboard)

                editor.present()
                self.open_tasks[uid] = editor

                # Save open tasks to config
                open_tasks = self.config.get("opened_tasks")

                if uid not in open_tasks:
                    open_tasks.append(uid)

                self.config.set("opened_tasks", open_tasks)

            else:
                log.error(f'Task {uid} could not be found!')

        return editor

    def get_active_editor(self):
        """Get focused task editor window."""

        for editor in self.open_tasks.values():
            if editor.window.is_active():
                return editor

    def close_task(self, tid):
        """Close a task editor window."""

        try:
            editor = self.open_tasks[tid]
            editor.close()

            open_tasks = self.config.get("opened_tasks")

            if tid in open_tasks:
                open_tasks.remove(tid)

            self.config.set("opened_tasks", open_tasks)

        except KeyError:
            log.debug(f'Tried to close tid {tid} but it is not open')

    # --------------------------------------------------------------------------
    # SHUTDOWN
    # --------------------------------------------------------------------------

    def save_tasks(self):
        """Save opened tasks and their positions."""

        open_task = []

        for otid in list(self.open_tasks.keys()):
            open_task.append(otid)
            self.open_tasks[otid].close()

        self.config.set("opened_tasks", open_task)

    def save_plugin_settings(self):
        """Save plugin settings to configuration."""

        if self.plugin_engine.plugins:
            self.config_plugins.set(
                'disabled',
                [p.module_name
                 for p in self.plugin_engine.get_plugins('disabled')])

            self.config_plugins.set(
                'enabled',
                [p.module_name
                 for p in self.plugin_engine.get_plugins('enabled')])

        self.plugin_engine.deactivate_plugins()

    def quit(self):
        """Quit the application."""

        # This is needed to avoid warnings when closing the browser
        # with editor windows open, because of the "win"
        # group of actions.

        self.save_tasks()
        Gtk.Application.quit(self)

    def do_shutdown(self):
        """Callback when GTG is closed."""

        self.save_plugin_settings()

        # Save data and shutdown datastore backends
        self.req.save_datastore(quit=True)

        Gtk.Application.do_shutdown(self)
示例#28
0
文件: plugins.py 项目: Ethcelon/gtg
class PluginsDialog:
    """ Dialog for Plugins configuration """

    def __init__(self, config_obj):
        self.config_obj = config_obj
        self.config = self.config_obj.get_subconfig("plugins")
        builder = Gtk.Builder()
        builder.add_from_file(ViewConfig.PLUGINS_UI_FILE)

        self.dialog = builder.get_object("PluginsDialog")
        self.dialog.set_title(_("Plugins - %s" % info.NAME))
        self.plugin_tree = builder.get_object("PluginTree")
        self.plugin_configure = builder.get_object("plugin_configure")
        self.plugin_about = builder.get_object("PluginAboutDialog")
        self.plugin_depends = builder.get_object('PluginDepends')

        help.add_help_shortcut(self.dialog, "plugins")

        self.pengine = PluginEngine()
        # plugin config initiation
        if self.pengine.get_plugins():
            self.config.set(
                "disabled",
                [p.module_name for p in self.pengine.get_plugins("disabled")],
            )
            self.config.set(
                "enabled",
                [p.module_name for p in self.pengine.get_plugins("enabled")],
            )

        # see constants PLUGINS_COL_* for column meanings
        self.plugin_store = Gtk.ListStore(str, bool, str, str, bool)

        builder.connect_signals({
                                'on_plugins_help':
                                self.on_help,
                                'on_plugins_close':
                                self.on_close,
                                'on_PluginsDialog_delete_event':
                                self.on_close,
                                'on_PluginTree_cursor_changed':
                                self.on_plugin_select,
                                'on_plugin_about':
                                self.on_plugin_about,
                                'on_plugin_configure':
                                self.on_plugin_configure,
                                'on_PluginAboutDialog_close':
                                self.on_plugin_about_close,
                                })

    def _init_plugin_tree(self):
        """ Initialize the PluginTree Gtk.TreeView.

        The format is modelled after the one used in gedit; see
        http://git.gnome.org/browse/gedit/tree/gedit/gedit-plugin-mapnager.c
        """
        # force creation of the Gtk.ListStore so we can reference it
        self._refresh_plugin_store()

        # renderer for the toggle column
        renderer = Gtk.CellRendererToggle()
        renderer.set_property('xpad', 6)
        renderer.connect('toggled', self.on_plugin_toggle)
        # toggle column
        column = Gtk.TreeViewColumn(None, renderer, active=PLUGINS_COL_ENABLED,
                                    activatable=PLUGINS_COL_ACTIVATABLE,
                                    sensitive=PLUGINS_COL_ACTIVATABLE)
        self.plugin_tree.append_column(column)

        # plugin name column
        column = Gtk.TreeViewColumn()
        column.set_spacing(6)
        # icon renderer for the plugin name column
        icon_renderer = Gtk.CellRendererPixbuf()
        icon_renderer.set_property('stock-size', Gtk.IconSize.SMALL_TOOLBAR)
        icon_renderer.set_property('xpad', 3)
        column.pack_start(icon_renderer, False)
        column.set_cell_data_func(icon_renderer, plugin_icon)
        # text renderer for the plugin name column
        name_renderer = Gtk.CellRendererText()
        name_renderer.set_property('ellipsize', Pango.EllipsizeMode.END)
        column.pack_start(name_renderer, True)
        column.set_cell_data_func(name_renderer, plugin_markup, self)

        self.plugin_tree.append_column(column)

        # finish setup
        self.plugin_tree.set_model(self.plugin_store)
        self.plugin_tree.set_search_column(2)

    def _refresh_plugin_store(self):
        """ Refresh status of plugins and put it in a Gtk.ListStore """
        self.plugin_store.clear()
        self.pengine.recheck_plugin_errors(True)
        for name, plugin in self.pengine.plugins.items():
            # activateable if there is no error
            self.plugin_store.append((name, plugin.enabled, plugin.full_name,
                                      plugin.short_description,
                                      not plugin.error))

    def activate(self):
        """ Refresh status of plugins and show the dialog """
        if len(self.plugin_tree.get_columns()) == 0:
            self._init_plugin_tree()
        else:
            self._refresh_plugin_store()
        self.dialog.show_all()

    def on_close(self, widget, data=None):
        """ Close the plugins dialog."""
        self.dialog.hide()
        return True

    @classmethod
    def on_help(cls, widget):
        """ Open help for plugins """
        help.show_help("plugins")
        return True

    def on_plugin_toggle(self, widget, path):
        """Toggle a plugin enabled/disabled."""
        iterator = self.plugin_store.get_iter(path)
        plugin_id = self.plugin_store.get_value(iterator, PLUGINS_COL_ID)
        plugin = self.pengine.get_plugin(plugin_id)
        plugin.enabled = not self.plugin_store.get_value(iterator,
                                                         PLUGINS_COL_ENABLED)
        plugins_enabled = self.config.get("enabled")
        plugins_disabled = self.config.get("disabled")
        if plugin.enabled:
            self.pengine.activate_plugins([plugin])
            plugins_enabled.append(plugin.module_name)
            if plugin.module_name in plugins_disabled:
                plugins_disabled.remove(plugin.module_name)
        else:
            self.pengine.deactivate_plugins([plugin])
            plugins_disabled.append(plugin.module_name)
            if plugin.module_name in plugins_enabled:
                plugins_enabled.remove(plugin.module_name)
        self.config.set("enabled", plugins_enabled)
        self.config.set("disabled", plugins_disabled)
        self.plugin_store.set_value(iterator, PLUGINS_COL_ENABLED,
                                    plugin.enabled)
        self._update_plugin_configure(plugin)

        self.config_obj.save()

    def on_plugin_select(self, plugin_tree):
        """ Callback when user select/unselect a plugin

        Update the button "Configure plugin" sensitivity """
        model, iterator = plugin_tree.get_selection().get_selected()
        if iterator is not None:
            plugin_id = model.get_value(iterator, PLUGINS_COL_ID)
            plugin = self.pengine.get_plugin(plugin_id)
            self._update_plugin_configure(plugin)

    def _update_plugin_configure(self, plugin):
        """ Enable the button "Configure Plugin" appropriate. """
        configurable = plugin.active and plugin.is_configurable()
        self.plugin_configure.set_property('sensitive', configurable)

    def on_plugin_configure(self, widget):
        """ Show the dialog for plugin configuration """
        _, iterator = self.plugin_tree.get_selection().get_selected()
        if iterator is None:
            return
        plugin_id = self.plugin_store.get_value(iterator, PLUGINS_COL_ID)
        plugin = self.pengine.get_plugin(plugin_id)
        plugin.instance.configure_dialog(self.dialog)

    def on_plugin_about(self, widget):
        """ Display information about a plugin. """
        _, iterator = self.plugin_tree.get_selection().get_selected()
        if iterator is None:
            return
        plugin_id = self.plugin_store.get_value(iterator, PLUGINS_COL_ID)
        plugin = self.pengine.get_plugin(plugin_id)

        #FIXME About plugin dialog looks much more different than
        #it is in the current trunk
        #FIXME repair it!
        #FIXME Author is not usually set and is preserved from
        #previous plugin... :/
        self.plugin_about.set_program_name(plugin.full_name)
        self.plugin_about.set_version(plugin.version)
        authors = plugin.authors
        if isinstance(authors, str):
            authors = "\n".join(author.strip()
                                for author in authors.split(','))
            authors = [authors, ]
        self.plugin_about.set_authors(authors)
        description = plugin.description.replace(r'\n', "\n")
        self.plugin_about.set_comments(description)
        self.plugin_depends.set_label(plugin_error_text(plugin))
        self.plugin_about.show_all()

    def on_plugin_about_close(self, widget, data=None):

        """ Close the PluginAboutDialog. """
        self.plugin_about.hide()
        return True
示例#29
0
文件: manager.py 项目: thperret/gtg
class Manager(GObject.GObject):

    __object_signal__ = (GObject.SIGNAL_RUN_FIRST, GObject.TYPE_NONE,
                         (GObject.TYPE_PYOBJECT, ))
    __object_string_signal__ = (GObject.SIGNAL_RUN_FIRST, GObject.TYPE_NONE, (
        GObject.TYPE_PYOBJECT,
        GObject.TYPE_STRING,
    ))
    __gsignals__ = {
        'tasks-deleted': __object_signal__,
        'task-status-changed': __object_string_signal__,
    }

    ############## init #####################################################
    def __init__(self, req):
        GObject.GObject.__init__(self)
        self.req = req
        self.config_obj = self.req.get_global_config()
        self.browser_config = self.config_obj.get_subconfig("browser")
        self.plugins_config = self.config_obj.get_subconfig("plugins")
        self.task_config = self.config_obj.get_taskconfig()

        # Editors
        self.opened_task = {}  # This is the list of tasks that are already
        # opened in an editor of course it's empty
        # right now

        self.browser = None
        self.__start_browser_hidden = False
        self.gtk_terminate = False  # if true, the gtk main is not started

        # if true, closing the last window doesn't quit GTG
        # (GTG lives somewhere else without GUI, e.g. notification area)
        self.daemon_mode = False

        # Shared clipboard
        self.clipboard = clipboard.TaskClipboard(self.req)

        # Initialize Timer
        self.config = self.req.get_config('browser')
        self.timer = Timer(self.config)

        # Browser (still hidden)
        self.browser = TaskBrowser(self.req, self)

        self.__init_plugin_engine()

        if not self.__start_browser_hidden:
            self.show_browser()

        # Deletion UI
        self.delete_dialog = None

        # Preferences and Backends windows
        # Initialize  dialogs
        self.preferences = PreferencesDialog(self.req, self)
        self.plugins = PluginsDialog(self.config_obj)
        self.edit_backends_dialog = None

        # Tag Editor
        self.tag_editor_dialog = None

        # DBus
        DBusTaskWrapper(self.req, self)
        Log.debug("Manager initialization finished")

    def __init_plugin_engine(self):
        self.pengine = PluginEngine(GTG.PLUGIN_DIR)
        # initializes the plugin api class
        self.plugin_api = PluginAPI(self.req, self)
        self.pengine.register_api(self.plugin_api)
        # checks the conf for user settings
        try:
            plugins_enabled = self.plugins_config.get("enabled")
        except configparser.Error:
            plugins_enabled = []
        for plugin in self.pengine.get_plugins():
            plugin.enabled = plugin.module_name in plugins_enabled
        # initializes and activates each plugin (that is enabled)
        self.pengine.activate_plugins()

    ############## Browser #################################################
    def open_browser(self):
        if not self.browser:
            self.browser = TaskBrowser(self.req, self)
        # notify user if backup was used
        backend_dic = self.req.get_all_backends()
        for backend in backend_dic:
            if backend.get_name() == "backend_localfile" and \
                    backend.used_backup():
                backend.notify_user_about_backup()
        Log.debug("Browser is open")

    # FIXME : the browser should not be the center of the universe.
    # In fact, we should build a system where view can register themselves
    # as "stay_alive" views. As long as at least one "stay_alive" view
    # is registered, gtg keeps running. It quit only when the last
    # "stay_alive view" is closed (and then unregistered).
    # Currently, the browser is our only "stay_alive" view.
    def close_browser(self, sender=None):
        self.hide_browser()
        # may take a while to quit
        self.quit()

    def hide_browser(self, sender=None):
        self.browser.hide()

    def iconify_browser(self, sender=None):
        self.browser.iconify()

    def show_browser(self, sender=None):
        self.browser.show()

    def is_browser_visible(self, sender=None):
        return self.browser.is_visible()

    def get_browser(self):
        # used by the plugin api to hook in the browser
        return self.browser

    def start_browser_hidden(self):
        self.__start_browser_hidden = True

    def set_daemon_mode(self, in_daemon_mode):
        """ Used by notification area plugin to override the behavior:
        last closed window quits GTG """
        self.daemon_mode = in_daemon_mode

################# Task Editor ############################################

    def get_opened_editors(self):
        '''
        Returns a dict of task_uid -> TaskEditor, one for each opened editor
        window
        '''
        return self.opened_task

    def open_task(self, uid, thisisnew=False):
        """Open the task identified by 'uid'.

        If a Task editor is already opened for a given task, we present it.
        Else, we create a new one.
        """
        t = self.req.get_task(uid)
        tv = None
        if uid in self.opened_task:
            tv = self.opened_task[uid]
            tv.present()
        elif t:
            tv = TaskEditor(requester=self.req,
                            vmanager=self,
                            task=t,
                            taskconfig=self.task_config,
                            thisisnew=thisisnew,
                            clipboard=self.clipboard)
            tv.present()
            # registering as opened
            self.opened_task[uid] = tv
            # save that we opened this task
            opened_tasks = self.browser_config.get("opened_tasks")
            if uid not in opened_tasks:
                opened_tasks.append(uid)
            self.browser_config.set("opened_tasks", opened_tasks)
        return tv

    def close_task(self, tid):
        # When an editor is closed, it should de-register itself.
        if tid in self.opened_task:
            # the following line has the side effect of removing the
            # tid key in the opened_task dictionary.
            editor = self.opened_task[tid]
            if editor:
                del self.opened_task[tid]
                # we have to remove the tid from opened_task first
                # else, it close_task would be called once again
                # by editor.close
                editor.close()
            opened_tasks = self.browser_config.get("opened_tasks")
            if tid in opened_tasks:
                opened_tasks.remove(tid)
            self.browser_config.set("opened_tasks", opened_tasks)
        self.check_quit_condition()

    def check_quit_condition(self):
        '''
        checking if we need to shut down the whole GTG (if no window is open)
        '''
        if not self.daemon_mode and not self.is_browser_visible() and \
                not self.opened_task:
            # no need to live"
            self.quit()

################ Others dialog ############################################

    def open_edit_backends(self, sender=None, backend_id=None):
        if not self.edit_backends_dialog:
            self.edit_backends_dialog = BackendsDialog(self.req)
        self.edit_backends_dialog.activate()
        if backend_id is not None:
            self.edit_backends_dialog.show_config_for_backend(backend_id)

    def configure_backend(self, backend_id):
        self.open_edit_backends(None, backend_id)

    def open_preferences(self, config_priv):
        self.preferences.activate()

    def configure_plugins(self):
        self.plugins.activate()

    def ask_delete_tasks(self, tids):
        if not self.delete_dialog:
            self.delete_dialog = DeletionUI(self.req)
        finallist = self.delete_dialog.delete_tasks(tids)
        for t in finallist:
            if t.get_id() in self.opened_task:
                self.close_task(t.get_id())
        GObject.idle_add(self.emit, "tasks-deleted", finallist)
        return finallist

    def open_tag_editor(self, tag):
        if not self.tag_editor_dialog:
            self.tag_editor_dialog = TagEditor(self.req, self, tag)
        else:
            self.tag_editor_dialog.set_tag(tag)
        self.tag_editor_dialog.show()
        self.tag_editor_dialog.present()

    def close_tag_editor(self):
        self.tag_editor_dialog.hide()

### STATUS ###################################################################

    def ask_set_task_status(self, task, new_status):
        '''
        Both browser and editor have to use this central method to set
        task status. It also emits a signal with the task instance as first
        and the new status as second parameter
        '''
        task.set_status(new_status)
        GObject.idle_add(self.emit, "task-status-changed", task, new_status)

### URIS ###################################################################

    def open_uri_list(self, unused, uri_list):
        '''
        Open the Editor windows of the tasks associated with the uris given.
        Uris are of the form gtg://<taskid>
        '''
        for uri in uri_list:
            if uri.startswith("gtg://"):
                self.open_task(uri[6:])
        # if no window was opened, we just quit
        self.check_quit_condition()

### MAIN ###################################################################

    def main(self, once_thru=False, uri_list=[]):
        if uri_list:
            # before opening the requested tasks, we make sure that all of them
            # are loaded.
            BackendSignals().connect('default-backend-loaded',
                                     self.open_uri_list, uri_list)
        else:
            self.open_browser()
        GObject.threads_init()
        if not self.gtk_terminate:
            if once_thru:
                Gtk.main_iteration()
            else:
                Gtk.main()
        return 0

    def quit(self, sender=None):
        Gtk.main_quit()
        # save opened tasks and their positions.
        open_task = []
        for otid in list(self.opened_task.keys()):
            open_task.append(otid)
            self.opened_task[otid].close()
        self.browser_config.set("opened_tasks", open_task)

        # adds the plugin settings to the conf
        # FIXME: this code is replicated in the preference window.
        if len(self.pengine.plugins) > 0:
            self.plugins_config.clear()
            self.plugins_config.set(
                "disabled",
                [p.module_name for p in self.pengine.get_plugins("disabled")],
            )
            self.plugins_config.set(
                "enabled",
                [p.module_name for p in self.pengine.get_plugins("enabled")],
            )
        # plugins are deactivated
        self.pengine.deactivate_plugins()
示例#30
0
文件: manager.py 项目: kunaaljain/gtg
class Manager(GObject.GObject):

    __object_signal__ = (GObject.SIGNAL_RUN_FIRST, GObject.TYPE_NONE,
                         (GObject.TYPE_PYOBJECT,))
    __object_string_signal__ = (GObject.SIGNAL_RUN_FIRST, GObject.TYPE_NONE,
                                (GObject.TYPE_PYOBJECT, GObject.TYPE_STRING, ))
    __gsignals__ = {'tasks-deleted': __object_signal__,
                    'task-status-changed': __object_string_signal__,
                    }

    # init ##################################################################
    def __init__(self, req):
        GObject.GObject.__init__(self)
        self.req = req
        self.config_obj = self.req.get_global_config()
        self.browser_config = self.config_obj.get_subconfig("browser")
        self.plugins_config = self.config_obj.get_subconfig("plugins")
        self.task_config = self.config_obj.get_taskconfig()

        # Editors
        # This is the list of tasks that are already opened in an editor
        # of course it's empty right now
        self.opened_task = {}

        self.browser = None
        self.__start_browser_hidden = False
        self.gtk_terminate = False  # if true, the gtk main is not started

        # if true, closing the last window doesn't quit GTG
        # (GTG lives somewhere else without GUI, e.g. notification area)
        self.daemon_mode = False

        # Shared clipboard
        self.clipboard = clipboard.TaskClipboard(self.req)

        # Initialize Timer
        self.config = self.req.get_config('browser')
        self.timer = Timer(self.config)

        # Browser (still hidden)
        self.browser = TaskBrowser(self.req, self)

        self.__init_plugin_engine()

        if not self.__start_browser_hidden:
            self.show_browser()

        # Deletion UI
        self.delete_dialog = None

        # Preferences and Backends windows
        # Initialize  dialogs
        self.preferences = PreferencesDialog(self.req, self)
        self.plugins = PluginsDialog(self.config_obj)
        self.edit_backends_dialog = None

        # Tag Editor
        self.tag_editor_dialog = None

        # DBus
        DBusTaskWrapper(self.req, self)
        Log.debug("Manager initialization finished")

    def __init_plugin_engine(self):
        self.pengine = PluginEngine(GTG.PLUGIN_DIR)
        # initializes the plugin api class
        self.plugin_api = PluginAPI(self.req, self)
        self.pengine.register_api(self.plugin_api)
        # checks the conf for user settings
        try:
            plugins_enabled = self.plugins_config.get("enabled")
        except configparser.Error:
            plugins_enabled = []
        for plugin in self.pengine.get_plugins():
            plugin.enabled = plugin.module_name in plugins_enabled
        # initializes and activates each plugin (that is enabled)
        self.pengine.activate_plugins()

    # Browser ##############################################################
    def open_browser(self):
        if not self.browser:
            self.browser = TaskBrowser(self.req, self)
        # notify user if backup was used
        backend_dic = self.req.get_all_backends()
        for backend in backend_dic:
            if backend.get_name() == "backend_localfile" and \
                    backend.used_backup():
                backend.notify_user_about_backup()
        Log.debug("Browser is open")

    # FIXME : the browser should not be the center of the universe.
    # In fact, we should build a system where view can register themselves
    # as "stay_alive" views. As long as at least one "stay_alive" view
    # is registered, gtg keeps running. It quit only when the last
    # "stay_alive view" is closed (and then unregistered).
    # Currently, the browser is our only "stay_alive" view.
    def close_browser(self, sender=None):
        self.hide_browser()
        # may take a while to quit
        self.quit()

    def hide_browser(self, sender=None):
        self.browser.hide()

    def iconify_browser(self, sender=None):
        self.browser.iconify()

    def show_browser(self, sender=None):
        self.browser.show()

    def is_browser_visible(self, sender=None):
        return self.browser.is_visible()

    def get_browser(self):
        # used by the plugin api to hook in the browser
        return self.browser

    def start_browser_hidden(self):
        self.__start_browser_hidden = True

    def set_daemon_mode(self, in_daemon_mode):
        """ Used by notification area plugin to override the behavior:
        last closed window quits GTG """
        self.daemon_mode = in_daemon_mode

# Task Editor ############################################################
    def get_opened_editors(self):
        '''
        Returns a dict of task_uid -> TaskEditor, one for each opened editor
        window
        '''
        return self.opened_task

    def open_task(self, uid, thisisnew=False):
        """Open the task identified by 'uid'.

        If a Task editor is already opened for a given task, we present it.
        Else, we create a new one.
        """
        t = self.req.get_task(uid)
        tv = None
        if uid in self.opened_task:
            tv = self.opened_task[uid]
            tv.present()
        elif t:
            tv = TaskEditor(
                requester=self.req,
                vmanager=self,
                task=t,
                taskconfig=self.task_config,
                thisisnew=thisisnew,
                clipboard=self.clipboard)
            tv.present()
            # registering as opened
            self.opened_task[uid] = tv
            # save that we opened this task
            opened_tasks = self.browser_config.get("opened_tasks")
            if uid not in opened_tasks:
                opened_tasks.append(uid)
            self.browser_config.set("opened_tasks", opened_tasks)
        return tv

    def close_task(self, tid):
        # When an editor is closed, it should de-register itself.
        if tid in self.opened_task:
            # the following line has the side effect of removing the
            # tid key in the opened_task dictionary.
            editor = self.opened_task[tid]
            if editor:
                del self.opened_task[tid]
                # we have to remove the tid from opened_task first
                # else, it close_task would be called once again
                # by editor.close
                editor.close()
            opened_tasks = self.browser_config.get("opened_tasks")
            if tid in opened_tasks:
                opened_tasks.remove(tid)
            self.browser_config.set("opened_tasks", opened_tasks)
        self.check_quit_condition()

    def check_quit_condition(self):
        '''
        checking if we need to shut down the whole GTG (if no window is open)
        '''
        if not self.daemon_mode and not self.is_browser_visible() and \
                not self.opened_task:
            # no need to live"
            self.quit()

# Others dialog ###########################################################
    def open_edit_backends(self, sender=None, backend_id=None):
        if not self.edit_backends_dialog:
            self.edit_backends_dialog = BackendsDialog(self.req)
        self.edit_backends_dialog.activate()
        if backend_id is not None:
            self.edit_backends_dialog.show_config_for_backend(backend_id)

    def configure_backend(self, backend_id):
        self.open_edit_backends(None, backend_id)

    def open_preferences(self, config_priv):
        self.preferences.activate()

    def configure_plugins(self):
        self.plugins.activate()

    def ask_delete_tasks(self, tids):
        if not self.delete_dialog:
            self.delete_dialog = DeletionUI(self.req)
        finallist = self.delete_dialog.delete_tasks(tids)
        for t in finallist:
            if t.get_id() in self.opened_task:
                self.close_task(t.get_id())
        GObject.idle_add(self.emit, "tasks-deleted", finallist)
        return finallist

    def open_tag_editor(self, tag):
        if not self.tag_editor_dialog:
            self.tag_editor_dialog = TagEditor(self.req, self, tag)
        else:
            self.tag_editor_dialog.set_tag(tag)
        self.tag_editor_dialog.show()
        self.tag_editor_dialog.present()

    def close_tag_editor(self):
        self.tag_editor_dialog.hide()

# STATUS #####################################################################
    def ask_set_task_status(self, task, new_status):
        '''
        Both browser and editor have to use this central method to set
        task status. It also emits a signal with the task instance as first
        and the new status as second parameter
        '''
        task.set_status(new_status)
        GObject.idle_add(self.emit, "task-status-changed", task, new_status)

# URIS #####################################################################
    def open_uri_list(self, unused, uri_list):
        '''
        Open the Editor windows of the tasks associated with the uris given.
        Uris are of the form gtg://<taskid>
        '''
        for uri in uri_list:
            if uri.startswith("gtg://"):
                self.open_task(uri[6:])
        # if no window was opened, we just quit
        self.check_quit_condition()

# MAIN #####################################################################
    def main(self, once_thru=False, uri_list=[]):
        if uri_list:
            # before opening the requested tasks, we make sure that all of them
            # are loaded.
            BackendSignals().connect('default-backend-loaded',
                                     self.open_uri_list,
                                     uri_list)
        else:
            self.open_browser()
        GObject.threads_init()
        if not self.gtk_terminate:
            if once_thru:
                Gtk.main_iteration()
            else:
                Gtk.main()
        return 0

    def quit(self, sender=None):
        Gtk.main_quit()
        # save opened tasks and their positions.
        open_task = []
        for otid in list(self.opened_task.keys()):
            open_task.append(otid)
            self.opened_task[otid].close()
        self.browser_config.set("opened_tasks", open_task)

        # adds the plugin settings to the conf
        # FIXME: this code is replicated in the preference window.
        if len(self.pengine.plugins) > 0:
            self.plugins_config.clear()
            self.plugins_config.set(
                "disabled",
                [p.module_name for p in self.pengine.get_plugins("disabled")],
            )
            self.plugins_config.set(
                "enabled",
                [p.module_name for p in self.pengine.get_plugins("enabled")],
            )
        # plugins are deactivated
        self.pengine.deactivate_plugins()
示例#31
0
文件: editor.py 项目: kunaaljain/gtg
    def __init__(self,
                 requester,
                 vmanager,
                 task,
                 taskconfig=None,
                 thisisnew=False,
                 clipboard=None):
        '''
        req is the requester
        vmanager is the view manager
        taskconfig is a ConfigParser to save infos about tasks
        thisisnew is True when a new task is created and opened
        '''
        self.req = requester
        self.browser_config = self.req.get_config('browser')
        self.vmanager = vmanager
        self.config = taskconfig
        self.time = None
        self.clipboard = clipboard
        self.builder = Gtk.Builder()
        self.builder.add_from_file(GnomeConfig.EDITOR_UI_FILE)
        self.donebutton = self.builder.get_object("mark_as_done_editor")
        self.dismissbutton = self.builder.get_object("dismiss_editor")
        self.deletebutton = self.builder.get_object("delete_editor")
        self.deletebutton.set_tooltip_text(GnomeConfig.DELETE_TOOLTIP)
        self.subtask_button = self.builder.get_object("insert_subtask")
        self.subtask_button.set_tooltip_text(GnomeConfig.SUBTASK_TOOLTIP)
        self.inserttag_button = self.builder.get_object("inserttag")
        self.inserttag_button.set_tooltip_text(GnomeConfig.TAG_TOOLTIP)
        self.open_parents_button = self.builder.get_object("open_parents")
        self.open_parents_button.set_tooltip_text(
            GnomeConfig.OPEN_PARENT_TOOLTIP)

        # Create our dictionary and connect it
        dic = {
            "mark_as_done_clicked": self.change_status,
            "on_dismiss": self.dismiss,
            "delete_clicked": self.delete_task,
            "on_duedate_pressed": lambda w: self.on_date_pressed(
                w, GTGCalendar.DATE_KIND_DUE),
            "on_startdate_pressed": lambda w: self.on_date_pressed(
                w, GTGCalendar.DATE_KIND_START),
            "on_closeddate_pressed": lambda w: self.on_date_pressed(
                w, GTGCalendar.DATE_KIND_CLOSED),
            "close_clicked": self.close,
            "duedate_changed": lambda w: self.date_changed(
                w, GTGCalendar.DATE_KIND_DUE),
            "duedate_focus_out": lambda w, e: self.date_focus_out(
                w, e, GTGCalendar.DATE_KIND_DUE),
            "startingdate_changed": lambda w: self.date_changed(
                w, GTGCalendar.DATE_KIND_START),
            "startdate_focus_out": lambda w, e: self.date_focus_out(
                w, e, GTGCalendar.DATE_KIND_START),
            "closeddate_changed": lambda w: self.date_changed(
                w, GTGCalendar.DATE_KIND_CLOSED),
            "closeddate_focus_out": lambda w, e: self.date_focus_out(
                w, e, GTGCalendar.DATE_KIND_CLOSED),
            "on_insert_subtask_clicked": self.insert_subtask,
            "on_inserttag_clicked": self.inserttag_clicked,
            "on_open_parent_clicked": self.open_parent_clicked,
            "on_move": self.on_move,
        }
        self.builder.connect_signals(dic)
        self.window = self.builder.get_object("TaskEditor")
        # Removing the Normal textview to replace it by our own
        # So don't try to change anything with glade, this is a home-made
        # widget
        textview = self.builder.get_object("textview")
        scrolled = self.builder.get_object("scrolledtask")
        scrolled.remove(textview)
        self.textview = TaskView(self.req, self.clipboard)
        self.textview.show()
        self.textview.set_subtask_callback(self.new_subtask)
        self.textview.open_task_callback(self.vmanager.open_task)
        self.textview.set_left_margin(7)
        self.textview.set_right_margin(5)
        scrolled.add(self.textview)
        conf_font_value = self.browser_config.get("font_name")
        if conf_font_value != "":
            self.textview.override_font(Pango.FontDescription(conf_font_value))
        # Voila! it's done
        self.calendar = GTGCalendar()
        self.calendar.set_transient_for(self.window)
        self.calendar.set_decorated(False)
        self.duedate_widget = self.builder.get_object("duedate_entry")
        self.startdate_widget = self.builder.get_object("startdate_entry")
        self.closeddate_widget = self.builder.get_object("closeddate_entry")
        self.dayleft_label = self.builder.get_object("dayleft")
        self.tasksidebar = self.builder.get_object("tasksidebar")
        # Define accelerator keys
        self.init_accelerators()

        self.task = task
        tags = task.get_tags()
        self.textview.subtasks_callback(task.get_children)
        self.textview.removesubtask_callback(task.remove_child)
        self.textview.set_get_tagslist_callback(task.get_tags_name)
        self.textview.set_add_tag_callback(task.add_tag)
        self.textview.set_remove_tag_callback(task.remove_tag)
        self.textview.save_task_callback(self.light_save)

        texte = self.task.get_text()
        title = self.task.get_title()
        # the first line is the title
        self.textview.set_text("%s\n" % title)
        # we insert the rest of the task
        if texte:
            self.textview.insert("%s" % texte)
        else:
            # If not text, we insert tags
            if tags:
                for t in tags:
                    self.textview.insert_text("%s, " % t.get_name())
                self.textview.insert_text("\n")
            # If we don't have text, we still need to insert subtasks if any
            subtasks = task.get_children()
            if subtasks:
                self.textview.insert_subtasks(subtasks)
        # We select the title if it's a new task
        if thisisnew:
            self.textview.select_title()
        else:
            self.task.set_to_keep()
        self.textview.modified(full=True)
        self.window.connect("destroy", self.destruction)
        self.calendar.connect("date-changed", self.on_date_changed)

        # plugins
        self.pengine = PluginEngine()
        self.plugin_api = PluginAPI(self.req, self.vmanager, self)
        self.pengine.register_api(self.plugin_api)
        self.pengine.onTaskLoad(self.plugin_api)

        # Putting the refresh callback at the end make the start a lot faster
        self.textview.refresh_callback(self.refresh_editor)
        self.refresh_editor()
        self.textview.grab_focus()

        # restoring size and position, spatial tasks
        if self.config is not None:
            tid = self.task.get_id()
            if self.config.has_section(tid):
                if self.config.has_option(tid, "position"):
                    pos_x, pos_y = self.config.get(tid, "position")
                    self.move(int(pos_x), int(pos_y))
                if self.config.has_option(tid, "size"):
                    width, height = self.config.get(tid, "size")
                    self.window.resize(int(width), int(height))

        self.textview.set_editable(True)
        self.window.show()
示例#32
0
class TaskEditor:


    def __init__(self, 
                 requester, 
                 vmanager, 
                 task, 
                 taskconfig = None,
                 thisisnew = False,
                 clipboard = None) :
        '''
        req is the requester
        vmanager is the view manager
        taskconfig is a ConfigObj dic to save infos about tasks
        thisisnew is True when a new task is created and opened
        '''
        self.req = requester
        self.vmanager = vmanager
        self.config = taskconfig
        self.time = None
        self.clipboard = clipboard
        self.builder = gtk.Builder()
        self.builder.add_from_file(GnomeConfig.GLADE_FILE)
        self.donebutton = self.builder.get_object("mark_as_done_editor")
        self.dismissbutton = self.builder.get_object("dismiss_editor")
        self.deletebutton = self.builder.get_object("delete_editor")
        self.deletebutton.set_tooltip_text(GnomeConfig.DELETE_TOOLTIP)
        self.subtask_button = self.builder.get_object("insert_subtask")
        self.subtask_button.set_tooltip_text(GnomeConfig.SUBTASK_TOOLTIP)
        self.inserttag_button = self.builder.get_object("inserttag")
        self.inserttag_button.set_tooltip_text(GnomeConfig.TAG_TOOLTIP)
        #Create our dictionary and connect it
        dic = {
                "mark_as_done_clicked"      : self.change_status,
                "on_dismiss"                : self.dismiss,
                "delete_clicked"            : self.delete_task,
                "on_duedate_pressed"        : (self.on_date_pressed,
                                               GTGCalendar.DATE_KIND_DUE),
                "on_startdate_pressed"      : (self.on_date_pressed,
                                               GTGCalendar.DATE_KIND_START),
                "on_closeddate_pressed"     : (self.on_date_pressed,
                                               GTGCalendar.DATE_KIND_CLOSED),
                "close_clicked"             : self.close,
                "duedate_changed"           : (self.date_changed,
                                               GTGCalendar.DATE_KIND_DUE),
                "startingdate_changed"      : (self.date_changed,
                                               GTGCalendar.DATE_KIND_START),
                "closeddate_changed"        : (self.date_changed,
                                               GTGCalendar.DATE_KIND_CLOSED),
                "on_insert_subtask_clicked" : self.insert_subtask,
                "on_inserttag_clicked"      : self.inserttag_clicked,
                "on_move"                   : self.on_move,
        }
        self.builder.connect_signals(dic)
        self.window         = self.builder.get_object("TaskEditor")
        #Removing the Normal textview to replace it by our own
        #So don't try to change anything with glade, this is a home-made widget
        textview = self.builder.get_object("textview")
        scrolled = self.builder.get_object("scrolledtask")
        scrolled.remove(textview)
        self.textview   = TaskView(self.req,self.clipboard)
        self.textview.show()
        self.textview.set_subtask_callback(self.new_subtask)
        self.textview.open_task_callback(self.vmanager.open_task)
        self.textview.set_left_margin(7)
        self.textview.set_right_margin(5)
        scrolled.add(self.textview)
        #Voila! it's done
        self.calendar       = GTGCalendar(self.builder)
        self.duedate_widget = self.builder.get_object("duedate_entry")
        self.startdate_widget = self.builder.get_object("startdate_entry")
        self.closeddate_widget = self.builder.get_object("closeddate_entry")
        self.dayleft_label  = self.builder.get_object("dayleft")
        self.tasksidebar = self.builder.get_object("tasksidebar")
        # Define accelerator keys
        self.init_accelerators()

        self.task = task
        tags = task.get_tags()
        self.textview.subtasks_callback(task.get_children)
        self.textview.removesubtask_callback(task.remove_child)
        self.textview.set_get_tagslist_callback(task.get_tags_name)
        self.textview.set_add_tag_callback(task.add_tag)
        self.textview.set_remove_tag_callback(task.remove_tag)
        self.textview.save_task_callback(self.light_save)

        texte = self.task.get_text()
        title = self.task.get_title()
        #the first line is the title
        self.textview.set_text("%s\n"%title)
        #we insert the rest of the task
        if texte :
            self.textview.insert("%s"%texte)
        else :
            #If not text, we insert tags
            if tags :
                for t in tags :
                    self.textview.insert_text("%s, "%t.get_name())
                self.textview.insert_text("\n")
            #If we don't have text, we still need to insert subtasks if any
            subtasks = task.get_children()
            if subtasks :
                self.textview.insert_subtasks(subtasks)
        #We select the title if it's a new task
        if thisisnew :
            self.textview.select_title()
        else :
            self.task.set_to_keep()
        self.textview.modified(full=True)
        self.window.connect("destroy", self.destruction)
        self.calendar.connect("date-changed", self.on_date_changed)

        # plugins
        self.pengine = PluginEngine()
        self.plugin_api = PluginAPI(self.req, self.vmanager, self)
        self.pengine.register_api(self.plugin_api)
        self.pengine.onTaskLoad(self.plugin_api)

        #Putting the refresh callback at the end make the start a lot faster
        self.textview.refresh_callback(self.refresh_editor)
        self.refresh_editor()
        self.textview.grab_focus()

        #restoring size and position, spatial tasks
        if self.config :
            tid = self.task.get_id()
            if tid in self.config:
                if "position" in self.config[tid]:
                    pos = self.config[tid]["position"]
                    self.move(pos[0],pos[1])
                    #print "restoring position %s %s" %(pos[0],pos[1])
                if "size" in self.config[tid]:
                    size = self.config[tid]["size"]
                    #print "size %s - %s" %(str(size[0]),str(size[1]))
                    #this eval(str()) is a ugly (!) hack to accept both int and str
                    #FIXME: Fix this!
                    self.window.resize(eval(str(size[0])),eval(str(size[1])))

        self.textview.set_editable(True)
        #Connection for the update
        self.req.connect('task-modified',self.task_modified)
        self.window.show()


    #FIXME: avoid to update to many time when we modify from the editor itself
    def task_modified(self,sender,tid):
        self.refresh_editor(refreshtext=True)

    # Define accelerator-keys for this dialog
    # TODO: undo/redo
    def init_accelerators(self):
        agr = gtk.AccelGroup()
        self.window.add_accel_group(agr)

        # Escape and Ctrl-W close the dialog. It's faster to call close
        # directly, rather than use the close button widget
        key, modifier = gtk.accelerator_parse('Escape')
        agr.connect_group(key, modifier, gtk.ACCEL_VISIBLE, self.close)

        key, modifier = gtk.accelerator_parse('<Control>w')
        agr.connect_group(key, modifier, gtk.ACCEL_VISIBLE, self.close)

        # Ctrl-N creates a new task
        key, modifier = gtk.accelerator_parse('<Control>n')
        agr.connect_group(key, modifier, gtk.ACCEL_VISIBLE, self.new_task)

        # Ctrl-Shift-N creates a new subtask
        insert_subtask = self.builder.get_object("insert_subtask")
        key, mod       = gtk.accelerator_parse("<Control><Shift>n")
        insert_subtask.add_accelerator('clicked', agr, key, mod, gtk.ACCEL_VISIBLE)

        # Ctrl-D marks task as done
        mark_as_done_editor = self.builder.get_object('mark_as_done_editor')
        key, mod = gtk.accelerator_parse('<Control>d')
        mark_as_done_editor.add_accelerator('clicked', agr, key, mod, gtk.ACCEL_VISIBLE)

        # Ctrl-I marks task as dismissed
        dismiss_editor = self.builder.get_object('dismiss_editor')
        key, mod = gtk.accelerator_parse('<Control>i')
        dismiss_editor.add_accelerator('clicked', agr, key, mod, gtk.ACCEL_VISIBLE)

    #Can be called at any time to reflect the status of the Task
    #Refresh should never interfere with the TaskView.
    #If a title is passed as a parameter, it will become
    #the new window title. If not, we will look for the task title.
    #Refreshtext is whether or not we should refresh the TaskView
    #(doing it all the time is dangerous if the task is empty)
    def refresh_editor(self, title=None, refreshtext=False):
        if self.window == None:
            return
        to_save = False
        #title of the window 
        if title :
            self.window.set_title(title)
            to_save = True
        else :
            self.window.set_title(self.task.get_title())

        status = self.task.get_status() 
        if status == Task.STA_DISMISSED:
            self.donebutton.set_label(GnomeConfig.MARK_DONE)
            self.donebutton.set_tooltip_text(GnomeConfig.MARK_DONE_TOOLTIP)
            self.donebutton.set_icon_name("gtg-task-done")
            self.dismissbutton.set_label(GnomeConfig.MARK_UNDISMISS)
            self.dismissbutton.set_tooltip_text(GnomeConfig.MARK_UNDISMISS_TOOLTIP)
            self.dismissbutton.set_icon_name("gtg-task-undismiss")
        elif status == Task.STA_DONE:
            self.donebutton.set_label(GnomeConfig.MARK_UNDONE)
            self.donebutton.set_tooltip_text(GnomeConfig.MARK_UNDONE_TOOLTIP)
            self.donebutton.set_icon_name("gtg-task-undone")
            self.dismissbutton.set_label(GnomeConfig.MARK_DISMISS)
            self.dismissbutton.set_tooltip_text(GnomeConfig.MARK_DISMISS_TOOLTIP)
            self.dismissbutton.set_icon_name("gtg-task-dismiss")
        else:
            self.donebutton.set_label(GnomeConfig.MARK_DONE)
            self.donebutton.set_tooltip_text(GnomeConfig.MARK_DONE_TOOLTIP)
            self.donebutton.set_icon_name("gtg-task-done")
            self.dismissbutton.set_label(GnomeConfig.MARK_DISMISS)
            self.dismissbutton.set_tooltip_text(GnomeConfig.MARK_DISMISS_TOOLTIP)
            self.dismissbutton.set_icon_name("gtg-task-dismiss")
        self.donebutton.show()
        self.tasksidebar.show()

        #Refreshing the status bar labels and date boxes
        if status in [Task.STA_DISMISSED, Task.STA_DONE]:
            self.builder.get_object("label2").hide()
            self.builder.get_object("hbox1").hide()
            self.builder.get_object("label4").show()
            self.builder.get_object("hbox4").show()
        else:
            self.builder.get_object("label4").hide()
            self.builder.get_object("hbox4").hide()
            self.builder.get_object("label2").show() 
            self.builder.get_object("hbox1").show()

        #refreshing the due date field
        duedate = self.task.get_due_date()
        prevdate = dates.strtodate(self.duedate_widget.get_text())
        if duedate != prevdate or type(duedate) is not type(prevdate):
            zedate = str(duedate).replace("-", date_separator)
            self.duedate_widget.set_text(zedate)
        # refreshing the closed date field
        closeddate = self.task.get_closed_date()
        prevcldate = dates.strtodate(self.closeddate_widget.get_text())
        if closeddate != prevcldate or type(closeddate) is not type(prevcldate):
            zecldate = str(closeddate).replace("-", date_separator)
            self.closeddate_widget.set_text(zecldate)
        #refreshing the day left label
        #If the task is marked as done, we display the delay between the 
        #due date and the actual closing date. If the task isn't marked 
        #as done, we display the number of days left.
        if status in [Task.STA_DISMISSED, Task.STA_DONE]:
            delay = self.task.get_days_late()
            if delay is None:
                txt = ""
            elif delay == 0:
                txt = "Completed on time"
            elif delay >= 1:
                txt = ngettext("Completed %(days)d day late", "Completed %(days)d days late", delay) % {'days': delay}
            elif delay <= -1:
                abs_delay = abs(delay)
                txt = ngettext("Completed %(days)d day early", "Completed %(days)d days early", abs_delay) % {'days': abs_delay}
        else:
            result = self.task.get_days_left()
            if result is None:
                txt = ""
            elif result > 0:
                txt = ngettext("Due tomorrow!", "%(days)d days left", result) % {'days': result}
            elif result == 0:
                txt = _("Due today!")
            elif result < 0:
                abs_result = abs(result)
                txt = ngettext("Due yesterday!", "Was %(days)d days ago", abs_result) % {'days': abs_result}
        window_style = self.window.get_style()
        color = str(window_style.text[gtk.STATE_INSENSITIVE])
        self.dayleft_label.set_markup("<span color='"+color+"'>"+txt+"</span>")

        startdate = self.task.get_start_date()
        prevdate = dates.strtodate(self.startdate_widget.get_text())
        if startdate != prevdate or type(startdate) is not type(prevdate):
            zedate = str(startdate).replace("-",date_separator)
            self.startdate_widget.set_text(zedate) 
        #Refreshing the tag list in the insert tag button
        taglist = self.req.get_used_tags()
        menu = gtk.Menu()
        tag_count = 0
        for tagname in taglist:
            tag_object = self.req.get_tag(tagname)
            if not tag_object.is_special() and \
               not self.task.has_tags(tag_list=[tagname]):
                tag_count += 1
                mi = gtk.MenuItem(label = tagname, use_underline=False)
                mi.connect("activate", self.inserttag, tagname)
                mi.show()
                menu.append(mi)
        if tag_count > 0 :
            self.inserttag_button.set_menu(menu)

        if refreshtext:
            self.textview.modified(refresheditor=False)
        if to_save:
            self.light_save()

    def date_changed(self,widget,data):
        text = widget.get_text()
        validdate = False
        if not text :
            validdate = True
            datetoset = dates.no_date
        else :
            datetoset = dates.strtodate(text)
            if datetoset :
                validdate = True

        if validdate :
            #If the date is valid, we write with default color in the widget
            # "none" will set the default color.
            widget.modify_text(gtk.STATE_NORMAL, None)
            widget.modify_base(gtk.STATE_NORMAL, None)
            if data == "start" :
                self.task.set_start_date(datetoset)
            elif data == "due" :
                self.task.set_due_date(datetoset)
            elif data == "closed" :
                self.task.set_closed_date(datetoset)
            #Set the due date to be equal to the start date
            # when it happens that the start date is later than the due date
            start_date = self.task.get_start_date()
            due_date = self.task.get_due_date()
            if start_date and (start_date > due_date):
                self.task.set_due_date(self.task.get_start_date())
        else :
            #We should write in red in the entry if the date is not valid
            widget.modify_text(gtk.STATE_NORMAL, gtk.gdk.color_parse("#F00"))
            widget.modify_base(gtk.STATE_NORMAL, gtk.gdk.color_parse("#F88"))



    def on_date_pressed(self, widget, date_kind):
        """Called when a date-changing button is clicked."""
        if date_kind == GTGCalendar.DATE_KIND_DUE:
            #we display a calendar open on a day:
            #    the due date, the start date (if due is not set), or today
            #    (which is the default of the GTGCalendar class)
            date = self.task.get_due_date()
            if not date or self.task.get_start_date() > date:
                date = self.task.get_start_date()
        elif date_kind == GTGCalendar.DATE_KIND_START:
            date = self.task.get_start_date()
        elif date_kind == GTGCalendar.DATE_KIND_CLOSED:
            date = self.task.get_closed_date()
        self.calendar.set_date(date, date_kind)
        #we show the calendar at the right position
        rect = widget.get_allocation()
        x, y = widget.window.get_origin()
        self.calendar.show_at_position(x + rect.x + rect.width,
                                       y + rect.y)


    def on_date_changed(self, calendar):
        date, date_kind = calendar.get_selected_date()
        if date_kind == GTGCalendar.DATE_KIND_DUE:
            self.task.set_due_date(date)
        elif date_kind == GTGCalendar.DATE_KIND_START:
            self.task.set_start_date(date)
        elif date_kind == GTGCalendar.DATE_KIND_CLOSED:
            self.task.set_closed_date(date)
        self.refresh_editor()

    def dismiss(self,widget) : #pylint: disable-msg=W0613
        stat = self.task.get_status()
        if stat == "Dismiss":
            self.task.set_status("Active")
            self.refresh_editor()
        else:
            self.task.set_status("Dismiss")
            self.close(None)

    def change_status(self,widget) : #pylint: disable-msg=W0613
        stat = self.task.get_status()
        if stat == "Done":
            self.task.set_status("Active")
            self.refresh_editor()
        else:
            self.task.set_status("Done")
            self.close(None)

    def delete_task(self, widget) :
        #this triggers the closing of the window in the view manager
        self.vmanager.ask_delete_tasks([self.task.get_id()])

    #Take the title as argument and return the subtask ID
    def new_subtask(self,title=None,tid=None) :
        if tid:
            self.task.add_child(tid)
        elif title:
            subt = self.task.new_subtask()
            subt.set_title(title)
            tid = subt.get_id()
        return tid

    # Create a new task
    def new_task(self, *args):
        task = self.req.new_task(newtask=True)
        task_id = task.get_id()
        self.vmanager.open_task(task_id)

    def insert_subtask(self,widget) : #pylint: disable-msg=W0613
        self.textview.insert_newtask()
        self.textview.grab_focus()

    def inserttag_clicked(self,widget) : #pylint: disable-msg=W0613
        itera = self.textview.get_insert()
        if itera.starts_line() :
            self.textview.insert_text("@",itera)
        else :
            self.textview.insert_text(" @",itera)
        self.textview.grab_focus()

    def inserttag(self,widget,tag) : #pylint: disable-msg=W0613
        self.textview.insert_tags([tag])
        self.textview.grab_focus()

    def save(self) :
        self.task.set_title(self.textview.get_title())
        self.task.set_text(self.textview.get_text()) 
        self.task.sync()
        if self.config != None:
            self.config.write()
        self.time = time.time()
    #light_save save the task without refreshing every 30seconds
    #We will reduce the time when the get_text will be in another thread
    def light_save(self) :
        #if self.time is none, we never called any save
        if self.time:
            diff = time.time() - self.time
            tosave = diff > GnomeConfig.SAVETIME
        else:
            #we don't want to save a task while opening it
            tosave = self.textview.get_editable()
            diff = None
        if tosave:
            self.save()

    #This will bring the Task Editor to front    
    def present(self):
        self.window.present()

    def move(self,x,y):
        try:
            xx=int(x)
            yy=int(y)
            self.window.move(xx,yy)
        except:
            pass

    def get_position(self):
        return self.window.get_position()

    def on_move(self,widget,event):
        #saving the position
        if self.config != None:
            tid = self.task.get_id()
            if not tid in self.config :
                self.config[tid] = dict()
            #print "saving task position %s" %str(self.get_position())
            self.config[tid]["position"] = self.get_position()
            self.config[tid]["size"] = self.window.get_size()

    #We define dummy variable for when close is called from a callback
    def close(self,window=None,a=None,b=None,c=None): #pylint: disable-msg=W0613
        #We should also destroy the whole taskeditor object.
        if self.window:
            self.window.destroy()
            self.window = None

    #The destroy signal is linked to the "close" button. So if we call
    #destroy in the close function, this will cause the close to be called twice
    #To solve that, close will just call "destroy" and the destroy signal
    #Will be linked to this destruction method that will save the task
    def destruction(self,a=None):
        #Save should be also called when buffer is modified
        self.pengine.onTaskClose(self.plugin_api)
        self.pengine.remove_api(self.plugin_api)
        tid = self.task.get_id()
        if self.task.is_new():
            self.req.delete_task(tid)
        else:
            self.save()
            for i in self.task.get_subtasks():
                if i:
                    i.set_to_keep()
        self.vmanager.close_task(tid)

    def get_builder(self):
        return self.builder

    def get_task(self):
        return self.task

    def get_textview(self):
        return self.textview

    def get_window(self):
        return self.window