diff --git a/data/preferences.ui b/data/preferences.ui
index 7c3188667..34529093c 100644
--- a/data/preferences.ui
+++ b/data/preferences.ui
@@ -1,13 +1,7 @@
-
+
-
-
+
diff --git a/src/hamster-service b/src/hamster-service
index 64d46f37b..b4cf954ce 100755
--- a/src/hamster-service
+++ b/src/hamster-service
@@ -12,7 +12,7 @@ from hamster.lib import i18n
i18n.setup_i18n()
from hamster.storage import db
-from hamster.lib import default_logger, desktop, Fact, stuff
+from hamster.lib import default_logger, Fact, stuff
from hamster.lib.dbus import (
DBusMainLoop,
dbus,
@@ -51,10 +51,6 @@ class Storage(db.Storage, dbus.service.Object):
None)
self.__monitor.connect("changed", self._on_us_change)
- # central place were we plug in all the notifications and such
- self.integrations = desktop.DesktopIntegrations(self)
-
-
def run_fixtures(self):
"""we start with an empty database and then populate with default
values. This way defaults can be localized!"""
diff --git a/src/hamster/external.py b/src/hamster/external.py
deleted file mode 100644
index 2426652b8..000000000
--- a/src/hamster/external.py
+++ /dev/null
@@ -1,146 +0,0 @@
-# - coding: utf-8 -
-
-# Copyright (C) 2007 Patryk Zawadzki
-# Copyright (C) 2008, 2010 Toms Bauģis
-
-# This file is part of Project Hamster.
-
-# Project Hamster is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-
-# Project Hamster is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-
-# You should have received a copy of the GNU General Public License
-# along with Project Hamster. If not, see .
-
-import logging
-logger = logging.getLogger(__name__) # noqa: E402
-
-from hamster.lib.configuration import conf
-from gi.repository import GObject as gobject
-import dbus, dbus.mainloop.glib
-
-try:
- import evolution
- from evolution import ecal
-except:
- evolution = None
-
-try:
- import taskw
- from taskw import TaskWarrior
-except:
- taskw = None
-
-class ActivitiesSource(gobject.GObject):
- def __init__(self):
- gobject.GObject.__init__(self)
- self.source = conf.get("activities_source")
- self.__gtg_connection = None
-
- if self.source == "evo" and not evolution:
- self.source = "" # on failure pretend that there is no evolution
- elif self.source == "gtg":
- gobject.GObject.__init__(self)
- dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
- elif self.source == "task" and not taskw:
- self.source = ""
-
-
- def get_activities(self, query = None):
- if not self.source:
- return []
-
- if self.source == "evo":
- return [activity for activity in get_eds_tasks()
- if query is None or activity['name'].startswith(query)]
-
- elif self.source == "gtg":
- conn = self.__get_gtg_connection()
- if not conn:
- return []
-
- activities = []
-
- tasks = []
- try:
- tasks = conn.GetTasks()
- except dbus.exceptions.DBusException: #TODO too lame to figure out how to connect to the disconnect signal
- self.__gtg_connection = None
- return self.get_activities(query) # reconnect
-
-
- for task in tasks:
- if query is None or task['title'].lower().startswith(query):
- name = task['title']
- if len(task['tags']):
- name = "%s, %s" % (name, " ".join([tag.replace("@", "#") for tag in task['tags']]))
-
- activities.append({"name": name,
- "category": ""})
-
- return activities
-
- elif self.source == "task":
- conn = TaskWarrior ()
- if not conn:
- return []
-
- activities = []
- tasks = []
-
- task_filter = {'status':'pending'}
- tasks = conn.filter_tasks(task_filter)
-
- for task in tasks:
- name = task['description'].replace(",","") # replace comma
- category = ""
- if 'tags' in task:
- name = "%s, %s " % (name, " ".join(task['tags']))
-
- if 'project' in task:
- category = task['project']
-
- activities.append({"name":name,"category":category})
-
- return activities
-
- def __get_gtg_connection(self):
- bus = dbus.SessionBus()
- if self.__gtg_connection and bus.name_has_owner("org.gnome.GTG"):
- return self.__gtg_connection
-
- if bus.name_has_owner("org.gnome.GTG"):
- self.__gtg_connection = dbus.Interface(bus.get_object('org.gnome.GTG', '/org/gnome/GTG'),
- dbus_interface='org.gnome.GTG')
- return self.__gtg_connection
- else:
- return None
-
-
-
-def get_eds_tasks():
- try:
- sources = ecal.list_task_sources()
- tasks = []
- if not sources:
- # BUG - http://bugzilla.gnome.org/show_bug.cgi?id=546825
- sources = [('default', 'default')]
-
- for source in sources:
- category = source[0]
-
- data = ecal.open_calendar_source(source[1], ecal.CAL_SOURCE_TYPE_TODO)
- if data:
- for task in data.get_all_objects():
- if task.get_status() in [ecal.ICAL_STATUS_NONE, ecal.ICAL_STATUS_INPROCESS]:
- tasks.append({'name': task.get_summary(), 'category' : category})
- return tasks
- except Exception as e:
- logger.warn(e)
- return []
diff --git a/src/hamster/idle.py b/src/hamster/idle.py
deleted file mode 100644
index 5d70e019d..000000000
--- a/src/hamster/idle.py
+++ /dev/null
@@ -1,146 +0,0 @@
-# - coding: utf-8 -
-
-# Copyright (C) 2008 Patryk Zawadzki
-
-# This file is part of Project Hamster.
-
-# Project Hamster is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-
-# Project Hamster is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-
-# You should have received a copy of the GNU General Public License
-# along with Project Hamster. If not, see .
-
-import logging
-logger = logging.getLogger(__name__) # noqa: E402
-
-import dbus
-import datetime as dt
-import gi
-
-from dbus.lowlevel import Message
-
-gi.require_version('GConf', '2.0')
-from gi.repository import GConf as gconf
-from gi.repository import GObject as gobject
-
-class DbusIdleListener(gobject.GObject):
- """
- Listen for idleness coming from org.gnome.ScreenSaver
-
- Monitors org.gnome.ScreenSaver for idleness. There are two types,
- implicit (due to inactivity) and explicit (lock screen), that need to be
- handled differently. An implicit idle state should subtract the
- time-to-become-idle (as specified in the gconf) from the last activity,
- but an explicit idle state should not.
-
- The signals are inspected for the "ActiveChanged" and "Lock"
- members coming from the org.gnome.ScreenSaver interface and the
- and is_screen_locked members are updated appropriately.
- """
- __gsignals__ = {
- "idle-changed": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,))
- }
- def __init__(self):
- gobject.GObject.__init__(self)
-
- self.screensaver_uri = "org.gnome.ScreenSaver"
- self.screen_locked = False
- self.idle_from = None
- self.timeout_minutes = 0 # minutes after session is considered idle
- self.idle_was_there = False # a workaround variable for pre 2.26
-
- try:
- self.bus = dbus.SessionBus()
- except:
- return 0
- # Listen for chatter on the screensaver interface.
- # We cannot just add additional match strings to narrow down
- # what we hear because match strings are ORed together.
- # E.g., if we were to make the match string
- # "interface='org.gnome.ScreenSaver', type='method_call'",
- # we would not get only screensaver's method calls, rather
- # we would get anything on the screensaver interface, as well
- # as any method calls on *any* interface. Therefore the
- # bus_inspector needs to do some additional filtering.
- self.bus.add_match_string_non_blocking("interface='%s'" %
- self.screensaver_uri)
- self.bus.add_message_filter(self.bus_inspector)
-
-
- def bus_inspector(self, bus, message):
- """
- Inspect the bus for screensaver messages of interest
- """
-
- # We only care about stuff on this interface. We did filter
- # for it above, but even so we still hear from ourselves
- # (hamster messages).
- if message.get_interface() != self.screensaver_uri:
- return True
-
- member = message.get_member()
-
- if member in ("SessionIdleChanged", "ActiveChanged"):
- logger.debug("%s -> %s" % (member, message.get_args_list()))
-
- idle_state = message.get_args_list()[0]
- if idle_state:
- self.idle_from = dt.datetime.now()
-
- # from gnome screensaver 2.24 to 2.28 they have switched
- # configuration keys and signal types.
- # luckily we can determine key by signal type
- if member == "SessionIdleChanged":
- delay_key = "/apps/gnome-screensaver/idle_delay"
- else:
- delay_key = "/desktop/gnome/session/idle_delay"
-
- client = gconf.Client.get_default()
- self.timeout_minutes = client.get_int(delay_key)
-
- else:
- self.screen_locked = False
- self.idle_from = None
-
- if member == "ActiveChanged":
- # ActiveChanged comes before SessionIdleChanged signal
- # as a workaround for pre 2.26, we will wait a second - maybe
- # SessionIdleChanged signal kicks in
- def dispatch_active_changed(idle_state):
- if not self.idle_was_there:
- self.emit('idle-changed', idle_state)
- self.idle_was_there = False
-
- gobject.timeout_add_seconds(1, dispatch_active_changed, idle_state)
-
- else:
- # dispatch idle status change to interested parties
- self.idle_was_there = True
- self.emit('idle-changed', idle_state)
-
- elif member == "Lock":
- # in case of lock, lock signal will be sent first, followed by
- # ActiveChanged and SessionIdle signals
- logger.debug("Screen Lock Requested")
- self.screen_locked = True
-
- return
-
-
- def getIdleFrom(self):
- if not self.idle_from:
- return dt.datetime.now()
-
- if self.screen_locked:
- return self.idle_from
- else:
- # Only subtract idle time from the running task when
- # idleness is due to time out, not a screen lock.
- return self.idle_from - dt.timedelta(minutes = self.timeout_minutes)
diff --git a/src/hamster/lib/configuration.py b/src/hamster/lib/configuration.py
index 0e8f48465..1ce5df639 100644
--- a/src/hamster/lib/configuration.py
+++ b/src/hamster/lib/configuration.py
@@ -199,16 +199,11 @@ class GConfStore(gobject.GObject, Singleton):
GCONF_DIR = "/apps/hamster/"
VALID_KEY_TYPES = (bool, str, int, list, tuple)
DEFAULTS = {
- 'enable_timeout' : False, # Should hamster stop tracking on idle
- 'stop_on_shutdown' : False, # Should hamster stop tracking on shutdown
- 'notify_on_idle' : False, # Remind also if no activity is set
- 'notify_interval' : 27, # Remind of current activity every X minutes
'day_start_minutes' : 5 * 60 + 30, # At what time does the day start (5:30AM)
'overview_window_box' : [], # X, Y, W, H
'overview_window_maximized' : False, # Is overview window maximized
'standalone_window_box' : [], # X, Y, W, H
'standalone_window_maximized' : False, # Is overview window maximized
- 'activities_source' : "", # Source of TODO items ("", "evo", "gtg")
'last_report_folder' : "~", # Path to directory where the last report was saved
}
diff --git a/src/hamster/lib/desktop.py b/src/hamster/lib/desktop.py
deleted file mode 100644
index b164d6558..000000000
--- a/src/hamster/lib/desktop.py
+++ /dev/null
@@ -1,120 +0,0 @@
-# - coding: utf-8 -
-
-# Copyright (C) 2007-2012 Toms Baugis
-
-# This file is part of Project Hamster.
-
-# Project Hamster is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-
-# Project Hamster is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-
-# You should have received a copy of the GNU General Public License
-# along with Project Hamster. If not, see .
-
-import logging
-logger = logging.getLogger(__name__) # noqa: E402
-
-import datetime as dt
-from calendar import timegm
-from gi.repository import GObject as gobject
-
-
-from hamster import idle
-from hamster.lib.configuration import conf
-import dbus
-
-
-class DesktopIntegrations(object):
- def __init__(self, storage):
- self.storage = storage # can't use client as then we get in a dbus loop
- self._last_notification = None
-
- dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
- self.bus = dbus.SessionBus()
-
- self.conf_enable_timeout = conf.get("enable_timeout")
- self.conf_notify_on_idle = conf.get("notify_on_idle")
- self.conf_notify_interval = conf.get("notify_interval")
- conf.connect('conf-changed', self.on_conf_changed)
-
- self.idle_listener = idle.DbusIdleListener()
- self.idle_listener.connect('idle-changed', self.on_idle_changed)
-
- gobject.timeout_add_seconds(60, self.check_hamster)
-
-
- def check_hamster(self):
- """refresh hamster every x secs - load today, check last activity etc."""
- try:
- # can't use the client because then we end up in a dbus loop
- # as this is initiated in storage
- todays_facts = self.storage._Storage__get_todays_facts()
- self.check_user(todays_facts)
- except Exception as e:
- logger.error("Error while refreshing: %s" % e)
- finally: # we want to go on no matter what, so in case of any error we find out about it sooner
- return True
-
-
- def check_user(self, todays_facts):
- """check if we need to notify user perhaps"""
- interval = self.conf_notify_interval
- if interval <= 0 or interval >= 121:
- return
-
- now = dt.datetime.now()
- message = None
-
- last_activity = todays_facts[-1] if todays_facts else None
-
- # update duration of current task
- if last_activity and not last_activity.end_time:
- delta = now - last_activity.start_time
- duration = delta.seconds / 60
-
- if duration and duration % interval == 0:
- message = _("Working on %s") % last_activity.activity
- self.notify_user(message)
-
- elif self.conf_notify_on_idle:
- #if we have no last activity, let's just calculate duration from 00:00
- if (now.minute + now.hour * 60) % interval == 0:
- self.notify_user(_("No activity"))
-
-
- def notify_user(self, summary="", details=""):
- if not hasattr(self, "_notification_conn"):
- self._notification_conn = dbus.Interface(self.bus.get_object('org.freedesktop.Notifications',
- '/org/freedesktop/Notifications',
- follow_name_owner_changes=True),
- dbus_interface='org.freedesktop.Notifications')
- conn = self._notification_conn
-
- notification = conn.Notify("Project Hamster",
- self._last_notification or 0,
- "hamster",
- summary,
- details,
- [],
- {"urgency": dbus.Byte(0), "transient" : True},
- -1)
- self._last_notification = notification
-
-
- def on_idle_changed(self, event, state):
- # state values: 0 = active, 1 = idle
- if state == 1 and self.conf_enable_timeout:
- idle_from = self.idle_listener.getIdleFrom()
- idle_from = timegm(idle_from.timetuple())
- self.storage.StopTracking(idle_from)
-
-
- def on_conf_changed(self, event, key, value):
- if hasattr(self, "conf_%s" % key):
- setattr(self, "conf_%s" % key, value)
diff --git a/src/hamster/preferences.py b/src/hamster/preferences.py
index 38ade3803..1f4a32225 100755
--- a/src/hamster/preferences.py
+++ b/src/hamster/preferences.py
@@ -23,9 +23,9 @@
import datetime as dt
-from gettext import ngettext
-
-from hamster.lib.configuration import Controller
+from hamster import widgets
+from hamster.lib import stuff
+from hamster.lib.configuration import Controller, runtime, conf
def get_prev(selection, model):
@@ -38,6 +38,7 @@ def get_prev(selection, model):
else:
return None
+
class CategoryStore(gtk.ListStore):
def __init__(self):
#id, name, color_code, order
@@ -71,35 +72,14 @@ def load(self, category_id):
activity['category_id']])
-formats = ["fixed", "symbolic", "minutes"]
-appearances = ["text", "icon", "both"]
-
-from hamster.lib.configuration import runtime, conf
-from hamster import widgets
-from hamster.lib import stuff
-
-
-
class PreferencesEditor(Controller):
TARGETS = [
('MY_TREE_MODEL_ROW', gtk.TargetFlags.SAME_WIDGET, 0),
('MY_TREE_MODEL_ROW', gtk.TargetFlags.SAME_APP, 0),
]
-
def __init__(self, parent = None):
Controller.__init__(self, parent, ui_file="preferences.ui")
- # Translators: 'None' refers here to the Todo list choice in Hamster preferences (Tracking tab)
- self.activities_sources = [("", _("None")),
- ("evo", "Evolution"),
- ("gtg", "Getting Things Gnome"),
- ("task", "Taskwarrior")]
- self.todo_combo = gtk.ComboBoxText()
- for code, label in self.activities_sources:
- self.todo_combo.append_text(label)
- self.todo_combo.connect("changed", self.on_todo_combo_changed)
- self.get_widget("todo_pick").add(self.todo_combo)
-
# create and fill activity tree
self.activity_tree = self.get_widget('activity_list')
@@ -127,7 +107,6 @@ def __init__(self, parent = None):
(self.selection, self.selection.connect('changed', self.activity_changed, self.activity_store))
])
-
# create and fill category tree
self.category_tree = self.get_widget('category_list')
self.get_widget("categories_label").set_mnemonic_widget(self.category_tree)
@@ -158,7 +137,6 @@ def __init__(self, parent = None):
self.day_start = widgets.TimeInput(dt.time(5,30))
self.get_widget("day_start_placeholder").add(self.day_start)
-
self.load_config()
# Allow enable drag and drop of rows including row move
@@ -187,36 +165,16 @@ def __init__(self, parent = None):
self.show()
-
def show(self):
self.get_widget("notebook1").set_current_page(0)
self.window.show_all()
-
- def on_todo_combo_changed(self, combo):
- conf.set("activities_source", self.activities_sources[combo.get_active()][0])
-
-
def load_config(self, *args):
- self.get_widget("shutdown_track").set_active(conf.get("stop_on_shutdown"))
- self.get_widget("idle_track").set_active(conf.get("enable_timeout"))
- self.get_widget("notify_interval").set_value(conf.get("notify_interval"))
-
- self.get_widget("notify_on_idle").set_active(conf.get("notify_on_idle"))
- self.get_widget("notify_on_idle").set_sensitive(conf.get("notify_interval") <=120)
-
self.day_start.time = conf.day_start
self.tags = [tag["name"] for tag in runtime.storage.get_tags(only_autocomplete=True)]
self.get_widget("autocomplete_tags").set_text(", ".join(self.tags))
-
- current_source = conf.get("activities_source")
- for i, (code, label) in enumerate(self.activities_sources):
- if code == current_source:
- self.todo_combo.set_active(i)
-
-
def on_autocomplete_tags_view_focus_out_event(self, view, event):
buf = self.get_widget("autocomplete_tags")
updated_tags = buf.get_text(buf.get_start_iter(), buf.get_end_iter(), 0)
@@ -227,7 +185,6 @@ def on_autocomplete_tags_view_focus_out_event(self, view, event):
runtime.storage.update_autocomplete_tags(updated_tags)
-
def drag_data_get_data(self, treeview, context, selection, target_id,
etime):
treeselection = treeview.get_selection()
@@ -251,8 +208,6 @@ def select_category(self, id):
self.category_tree.set_cursor((i, ))
i += 1
-
-
def on_category_list_drag_motion(self, treeview, drag_context, x, y, eventtime):
self.prev_selected_category = None
try:
@@ -271,8 +226,6 @@ def on_category_list_drag_motion(self, treeview, drag_context, x, y, eventtime):
else:
treeview.enable_model_drag_dest([drop_no], gdk.DragAction.MOVE)
-
-
def on_category_drop(self, treeview, context, x, y, selection,
info, etime):
model = self.category_tree.get_model()
@@ -313,7 +266,6 @@ def category_edited_cb(self, cell, path, new_text, model):
model[path][1] = new_text
-
def activity_name_edited_cb(self, cell, path, new_text, model):
id = model[path][0]
category_id = model[path][2]
@@ -340,7 +292,6 @@ def activity_name_edited_cb(self, cell, path, new_text, model):
model[path][1] = new_text
return True
-
def category_changed_cb(self, selection, model):
""" enables and disables action buttons depending on selected item """
(model, iter) = selection.get_selected()
@@ -365,7 +316,6 @@ def _get_selected_category(self):
return model[iter][0] if iter else None
-
def activity_changed(self, selection, model):
""" enables and disables action buttons depending on selected item """
(model, iter) = selection.get_selected()
@@ -375,7 +325,6 @@ def activity_changed(self, selection, model):
self.get_widget('activity_edit').set_sensitive(iter != None)
self.get_widget('activity_remove').set_sensitive(iter != None)
-
def _del_selected_row(self, tree):
selection = tree.get_selection()
(model, iter) = selection.get_selected()
@@ -407,7 +356,6 @@ def unsorted_painter(self, column, cell, model, iter, data):
def on_activity_list_button_pressed(self, tree, event):
self.activityCell.set_property("editable", False)
-
def on_activity_list_button_released(self, tree, event):
if event.button == 1 and tree.get_path_at_pos(int(event.x), int(event.y)):
# Get treeview path.
@@ -437,7 +385,6 @@ def on_category_list_button_released(self, tree, event):
self.prev_selected_category = path
-
def on_activity_remove_clicked(self, button):
self.remove_current_activity()
@@ -449,8 +396,6 @@ def on_activity_edit_clicked(self, button):
path = model.get_path(iter)
self.activity_tree.set_cursor_on_cell(path, self.activityColumn, self.activityCell, True)
-
-
"""keyboard events"""
def on_activity_list_key_pressed(self, tree, event_key):
key = event_key.keyval
@@ -470,7 +415,6 @@ def remove_current_activity(self):
runtime.storage.remove_activity(model[iter][0])
self._del_selected_row(self.activity_tree)
-
def on_category_remove_clicked(self, button):
self.remove_current_category()
@@ -482,7 +426,6 @@ def on_category_edit_clicked(self, button):
path = model.get_path(iter)
self.category_tree.set_cursor_on_cell(path, self.categoryColumn, self.categoryCell, True)
-
def on_category_list_key_pressed(self, tree, event_key):
key = event_key.keyval
@@ -540,7 +483,6 @@ def on_category_add_clicked(self, button):
focus_cell = None,
start_editing = True)
-
def on_activity_add_clicked(self, button):
""" appends row, jumps to it and allows user to input name """
category_id = self._get_selected_category()
@@ -559,33 +501,6 @@ def on_activity_remove_clicked(self, button):
removable_id = self._del_selected_row(self.activity_tree)
runtime.storage.remove_activity(removable_id)
-
- def on_shutdown_track_toggled(self, checkbox):
- conf.set("stop_on_shutdown", checkbox.get_active())
-
- def on_idle_track_toggled(self, checkbox):
- conf.set("enable_timeout", checkbox.get_active())
-
- def on_notify_on_idle_toggled(self, checkbox):
- conf.set("notify_on_idle", checkbox.get_active())
-
- def on_notify_interval_format_value(self, slider, value):
- if value <=120:
- # notify interval slider value label
- label = ngettext("%(interval_minutes)d minute",
- "%(interval_minutes)d minutes",
- value) % {'interval_minutes': value}
- else:
- # notify interval slider value label
- label = _("Never")
-
- return label
-
- def on_notify_interval_value_changed(self, scale):
- value = int(scale.get_value())
- conf.set("notify_interval", value)
- self.get_widget("notify_on_idle").set_sensitive(value <= 120)
-
def on_day_start_changed(self, widget):
day_start = self.day_start.time
if day_start is None:
@@ -595,12 +510,9 @@ def on_day_start_changed(self, widget):
conf.set("day_start_minutes", day_start)
-
-
def on_close_button_clicked(self, button):
self.close_window()
-
def close_window(self):
if self.parent:
for obj, handler in self.external_listeners: