#!/usr/bin/env python
'''
Psyched is a scheduling and task management application.

Copyright 2007 Eric Stein
License: GPL2/GPL3, at your option.  For details see LICENSE.

$Id: psyched 77 2008-01-27 16:43:18Z eastein $
'''

import sys
import os
import gtk
import gobject
import time
import datetime
import threading

# the following line is rewritten by the makefile.
# before making changes consider the effects.
#SYSPATH#

import psyched_validate as pv
import psyched_backend as pb
import psyched_strings as ps

(
	SCOLUMN_ID,
	SCOLUMN_TEXT,
	SCOLUMN_TS_,
	SCOLUMN_DURATION_,
	SCOLUMN_COMPLETE,
	SCOLUMN_TASK,
	SCOLUMN_EDITABLE
) = range(7)
(
	TCOLUMN_ID,
	TCOLUMN_TEXT,
	TCOLUMN_DUE_,
	TCOLUMN_COMPLETE,
	TCOLUMN_EDITABLE
) = range(5)

cont = True

class Psyched(gtk.Window) :
	def __init__(self, parent=None) :
		# the following line is rewritten by the makefile.
		# before making changes consider the effects.
		iconpath = os.path.join(os.path.realpath(os.curdir), 'share')

		self.day = datetime.timedelta(days=1)
		self.begin = datetime.date.today()
		self.backend = pb.PsychedBackend()
		
		support = {'icon' : os.path.join(iconpath, 'psyched.png')}

		# create window
		gtk.Window.__init__(self)
		self.connect('destroy', self.__die__)

		# set up window
		self.set_title(ps.PROGRAM)
		self.set_default_size(500,700)
		self.set_border_width(0)

		# box panels for schedule and tasks
		schedule_ = gtk.VBox(homogeneous=False, spacing=4)
		schedule_.set_border_width(5)
		tasks_ = gtk.VBox(homogeneous=False, spacing=4)
		tasks_.set_border_width(5)

		# range buttons
		rangeh = gtk.HButtonBox()
		rangeh.set_layout(gtk.BUTTONBOX_START)

		self.rbuttons = {}
		b3 = gtk.Button(ps.PERIOD_3)
		b3.connect("clicked", self.range_modify, 3, schedule_, tasks_)
		self.rbuttons[3] = b3
		b7 = gtk.Button(ps.PERIOD_7)
		b7.connect("clicked", self.range_modify, 7, schedule_, tasks_)
		self.rbuttons[7] = b7
		b14 = gtk.Button(ps.PERIOD_14)
		b14.connect("clicked", self.range_modify, 14, schedule_, tasks_)
		self.rbuttons[14] = b14

		# paging buttons
		bn = gtk.Button(stock=gtk.STOCK_GO_FORWARD)
		bn.connect("clicked", self.date_page_forward, schedule_, tasks_)
		bb = gtk.Button(stock=gtk.STOCK_GO_BACK)
		bb.connect("clicked", self.date_page_back, schedule_, tasks_)

		rangeh.pack_start(b3, expand=False)
		rangeh.pack_start(b7, expand=False)
		rangeh.pack_start(b14, expand=False)
		rangeh.pack_start(bb, expand=False)
		rangeh.pack_start(bn, expand=False)

		# create tab gui
		schedule = gtk.ScrolledWindow()
		schedule.set_policy(gtk.POLICY_NEVER, gtk.POLICY_ALWAYS)
		tasks = gtk.ScrolledWindow()
		tasks.set_policy(gtk.POLICY_NEVER, gtk.POLICY_ALWAYS)

		schedule.add_with_viewport(schedule_)
		tasks.add_with_viewport(tasks_)

		slabel = gtk.Label(ps.SCHEDULE)
		tlabel = gtk.Label(ps.TASKS)

		tabset = gtk.Notebook()
		tabset.append_page(schedule, slabel)
		tabset.append_page(tasks, tlabel)

		# calendar day selection
		self.cal = gtk.Calendar()
		self.cal.connect('day-selected', self.date_modify, schedule_, tasks_)

		#build gui with both range buttons and tabset
		mainvbox = gtk.VBox()
		mainvbox.pack_start(self.cal, expand=False)
		mainvbox.pack_start(rangeh, expand=False)
		mainvbox.pack_start(tabset, expand=True)
		self.add(mainvbox)

		# read range of days and populate tabs
		self.range = self.backend.setting_get(pb.SETTING_RANGE)
		self.populate_tabs(schedule_, tasks_, False)
		self.rbuttons[self.range].set_state(gtk.STATE_ACTIVE)

		# schedule repopulation at midngith
		self.timer = BackgroundTimer()
		self.timer.start()
		self.timer.add_callback('d', lambda: self.populate_tabs(schedule_, tasks_, True))

		# icons
		self.shown = True
		self.icon = gtk.status_icon_new_from_file(support['icon'])
		gtk.window_set_default_icon_from_file(support['icon'])
		self.icon.set_tooltip(ps.PROGRAM)
		self.icon.connect('activate', self.icon_click)

		# show window
		self.win = None
		self.show_all()

	def date_modify(self, cal, s, t) :
		(y, m, d) = cal.get_date()
		self.begin = datetime.date(y, m + 1, d)
		self.populate_tabs(s, t, True)
	
	def date_page_forward(self, b, s, t) :
		self.begin += self.day * self.range
		self.populate_tabs(s, t, True)
		self.set_calendar(self.begin)

	def date_page_back(self, b, s, t) :
		self.begin -= self.day * self.range
		self.populate_tabs(s, t, True)
		self.set_calendar(self.begin)

	def range_modify(self, b, r, s, t) :
		self.rbuttons[self.range].set_state(gtk.STATE_NORMAL)
		self.backend.setting_set(pb.SETTING_RANGE, r)
		self.range = r
		self.rbuttons[self.range].set_state(gtk.STATE_ACTIVE)
		self.backend.action_complete()
		self.populate_tabs(s, t, True)

	def icon_click(self, i) :
		self.shown = not self.shown
		if self.shown :
			self.unmaximize()
			self.show_all()
		else :
			self.hide()
			gtk.gdk.flush()

	def __die__(self, parent=None) :
		global cont
		cont = False

	def populate_tabs(self, sched, tasks, show) :
		# reinitialize list dicts
		self.stores = {'sched' : {}, 'task' : {}}

		# columns for each
		scols = [(SCOLUMN_COMPLETE, ps.ATTENDED), (SCOLUMN_TS_, ps.TIME), (SCOLUMN_DURATION_, ps.DURATION), (SCOLUMN_TEXT, ps.DESCRIPTION)]
		tcols = [(TCOLUMN_COMPLETE, ps.DONE), (TCOLUMN_DUE_, ps.TIME), (TCOLUMN_TEXT, ps.DESCRIPTION)]

		# clear boxes
		self.clear_vbox(sched)
		self.clear_vbox(tasks)
		# fill boxes
		self.add_items_to_vbox(sched, self.iterate_get(self.range, lambda t, d: self.create_list_sched(self.backend.fetch_schedule(t, d), t), (scols, SCOLUMN_EDITABLE), self.create_sched, self.on_s_toggled, self.on_s_edited, 'sched'), show)
		self.add_items_to_vbox(tasks, self.iterate_get(self.range, lambda t, d: self.create_list_task(self.backend.fetch_dated_tasks(t, d), t), (tcols, TCOLUMN_EDITABLE), self.create_task, self.on_t_toggled, self.on_t_edited, 'task'), show)

	def iterate_get(self, days, fetch, colargs, create, toggle, edit, kind) :
		(l, e) = colargs
		r = []
		for i in range(days) :
			day = self.begin + self.day * i
			lset = gtk.HBox()
			dlab = gtk.Label(day.isoformat())
			dlab.show()
			lset.pack_start(dlab)
			if create != None :
				abutton = gtk.Button(stock=gtk.STOCK_ADD)
				abutton.connect("clicked", lambda t, d: create(d), (day.year, day.month, day.day))
				abutton.show()
				lset.pack_start(abutton)
			r.append(lset)
			(t, d) = self.getperiod(self.begin + self.day * i)

			dl = fetch(t, d)
			tv = self.create_tree(dl, kind)
			self.add_columns(tv, l, e, toggle, edit)
			r.append(tv)
		return r

	def getperiod(self, d) :
		f1 = int(time.mktime(d.timetuple()))
		f2 = int(time.mktime((d + self.day).timetuple()))
		return (f1, f2 - f1)
	
	def set_calendar(self, dt) :
		(y, m, d, a, aa, aaa, aaaa, aaaaa, aaaaaa) = dt.timetuple()
		self.cal.select_month(m - 1, y)
		self.cal.select_day(d)

	def getter(self, type) :
		if type == gtk.Calendar :
			return gtk.Calendar.get_date
		if type == gtk.Entry :
			return gtk.Entry.get_text

	def input_dialog(self, title, fields) :
		d = gtk.Dialog(title, self, gtk.DIALOG_MODAL,
			(gtk.STOCK_OK, gtk.RESPONSE_OK,
			gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL))

		# create table for storing fields
		table = gtk.Table(len(fields), 2)
		table.set_row_spacings(4)
		table.set_col_spacings(4)

		# create status label
		d.vbox.add(table)
		status = gtk.Label("\n" + ps.OK)
		d.vbox.add(status)

		row = 0
		gui = []
		# for each field, create label and appropriate gtk element
		for field in fields :
			(type, name, valid, init) = field
			label = gtk.Label(name)
			table.attach(label, 0, 1, row, row + 1)

			f = type()
			for i in init :
				i(f)

			table.attach(f, 1, 2, row, row + 1)
			gui.append((f, type, name, valid))

			row += 1

		d.show_all()

		# until valid, loop
		comp = False
		while not comp :
			comp = True
			response = d.run()
			r = None
			# if ok pressed, get and validate input
			if response == gtk.RESPONSE_OK :
				r = {}
				for g in gui :
					(element, type, name, valid) = g
					# get gui element's value
					value = self.getter(type)(element)
					if valid != None :
						# if there is validation to run, check it and alert
						# the user if there is an issue
						v = valid(value)
						if not v :
							comp = False
							status.set_text("\n" + name + ps.IMPROPER)
					# store value
					r[name] = value

		# destroy dialog and return
		d.destroy()
		return r

	def dialog(self, s) :
		dialog = gtk.MessageDialog(self,
			gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
			gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, s)
		dialog.run()
		dialog.destroy()

	# get midnight on the day the timestamp is in
	def midnight(self, ts) :
		(y, m, d, h, mi, s, a, b, c) = time.localtime(ts)
		return int(time.mktime((y, m, d, 0, 0, 0, a, b, -1)))

	def add_items_to_vbox(self, vb, list, show) :
		for item in list :
			if show :
				item.show()
			vb.pack_start(item, expand=False)

	def clear_vbox(self, vb) :
		vb.forall(vb.remove)

	def opt_add_row(self, kind, ts, item, function) :
		if self.stores[kind].has_key(ts) :
			function(self.stores[kind][ts], item)

	def create_tree(self, list, type) :
		treeview = gtk.TreeView(list)
		treeview.set_rules_hint(True)
		treeview.get_selection().set_mode(gtk.SELECTION_SINGLE)
		treeview.connect("button-press-event", self.tree_clicked, type)

		return treeview

	# crete a menu with options to take on the row item: needs more data, such as which tree it is/which type the row is
	def tree_clicked(self, tree, event, kind) :
		if event.button == 3 :
			pathinfo = tree.get_path_at_pos(int(event.x), int(event.y))
			if type(pathinfo) == tuple :
				((path, ), tvc, x, y) = pathinfo
				model = tree.get_model()
				iter = model.get_iter(path)
				callback = None
				if kind == "sched" :
					callback = self.remove_sched
					res = self.reschedule_sched
				elif kind == "task" :
					callback = self.remove_task
					res = self.reschedule_task
				argument = (model, iter)
				self.create_menu([(ps.RESCHEDULE, res, argument), (ps.DELETE, callback, argument)], event.button)

	def remove_sched(self, menu, t) :
		(m, i) = t
		id = m.get_value(i, SCOLUMN_ID)
		self.backend.remove_sched(id)
		self.backend.action_complete()
		m.remove(i)

	def remove_task(self, menu, t) :
		(m, i) = t
		id = m.get_value(i, TCOLUMN_ID)
		self.backend.remove_task(id)
		self.backend.action_complete()
		m.remove(i)

	def create_menu(self, list, button) :
		menu = gtk.Menu()
		items = []
		for l in list :
			(t, c, a) = l
			i = gtk.MenuItem(t)
			if c != None :
				i.connect('activate', c, a)
			i.show()
			menu.append(i)
		menu.popup(None, None, None, button, 0)

	# colon delimited string to list of integers
	def s2loi(self, s) :
		return map(lambda s: int(s), s.split(':'))

	def dur2sec(self, list) :
		c = pow(60, len(list))
		r = 0
		for i in list :
			r += c * i
			c /= 60
		return r

	def create_calmod(self, date) :
		(y, m, d) = date
		calmods = []
		calmods.append(lambda c: c.select_month(m - 1, y))
		calmods.append(lambda c: c.select_day(d))
		return calmods

	def reschedule_sched(self, menu, t) :
		(model, iter) = t
		id = model.get_value(iter, SCOLUMN_ID)
		t = self.backend.get_sched_ts(id)

		od = pb.ts2dt(t)
		calmods = self.create_calmod(od)
		tmods = [lambda e: e.set_text(pb.tod(t))]

		v = self.input_dialog(ps.RESCHEDULE,
			[
			(gtk.Calendar, ps.DATE, None, calmods),
			(gtk.Entry, ps.TIME, pv.check_time, tmods)])

		if v != None :
			# get date and time
			(y, mo, d) = v[ps.DATE]
			(h, mi) = self.s2loi(v[ps.TIME])

			# create unix timestamp
			ts = time.mktime((y, mo + 1, d, h, mi, 0, 0, 0, -1))

			# commit to database
			self.backend.set_sched_ts(id, ts)
			self.backend.action_complete()

			# same day, modityf row
			if od == (y, mo + 1, d) :
				model.set(iter, SCOLUMN_TS_, pb.tod(ts))
			# different day, move row
			else :
				model.remove(iter)
				rec = self.backend.fetch_sched(id)
				self.opt_add_row('sched', self.midnight(ts), self.sched_db2li(rec), self.sched_model_append)

	def create_task(self, date=None) :
		calmods = []
		if date != None :
			calmods = self.create_calmod(date)

		v = self.input_dialog(ps.NEWTASK,
			[
			(gtk.Calendar, ps.DUEDATE, None, calmods),
			(gtk.Entry, ps.DUETIME, pv.check_time, []),
			(gtk.Entry, ps.DESCRIPTION, None, [])])

		if v != None :
			# get date and time
			(y, mo, d) = v[ps.DUEDATE]
			(h, mi) = self.s2loi(v[ps.DUETIME])
			
			# create unix timestamp
			ts = time.mktime((y, mo + 1, d, h, mi, 0, 0, 0, -1))

			# fetch description
			text = v[ps.DESCRIPTION]

			# insert task into database
			ns = self.backend.insert_task(text, ts)
			# commit
			self.backend.action_complete()

			# fetch item and add it to the GUI if in range
			self.opt_add_row('task', self.midnight(ts), self.task_db2li(self.backend.fetch_task(ns)), self.task_model_append)

	def task_db2li(self, t) :
		(id, text, due, complete) = t
		due_ = pb.tod(due)
		complete = bool(complete)
		editable = True

		return (id, text, due_, complete, editable)

	def task_model_append(self, model, item) :
		iter = model.append()
		(id, text, due_, complete, editable) = item
		model.set(iter,
			TCOLUMN_ID, id,
			TCOLUMN_TEXT, text,
			TCOLUMN_DUE_, due_,
			TCOLUMN_COMPLETE, complete,
			TCOLUMN_EDITABLE, editable
		)

	def create_list_task(self, list, ts) :
		model = gtk.ListStore(
			gobject.TYPE_INT,	#id
			gobject.TYPE_STRING,	#text
			gobject.TYPE_STRING,	#text version of due
			gobject.TYPE_BOOLEAN,	#complete
			gobject.TYPE_BOOLEAN	#editable
		)

		model.set_sort_column_id(TCOLUMN_DUE_, gtk.SORT_ASCENDING)
		self.stores['task'][ts] = model

		for item in list :
			self.task_model_append(model, self.task_db2li(item))

		return model

	def reschedule_task(self, menu, t) :
		(model, iter) = t
		id = model.get_value(iter, TCOLUMN_ID)
		due = self.backend.get_task_due(id)

		od = pb.ts2dt(due)
		calmods = self.create_calmod(od)
		tmods = [lambda e: e.set_text(pb.tod(due))]

		v = self.input_dialog(ps.RESCHEDULE,
			[
			(gtk.Calendar, ps.DUEDATE, None, calmods),
			(gtk.Entry, ps.DUETIME, pv.check_time, tmods)])

		if v != None :
			# get date and time
			(y, mo, d) = v[ps.DUEDATE]
			(h, mi) = self.s2loi(v[ps.DUETIME])

			# create unix timestamp
			ts = time.mktime((y, mo + 1, d, h, mi, 0, 0, 0, -1))

			# commit to database
			self.backend.set_task_due(id, ts)
			self.backend.action_complete()

			# same day, modityf row
			if od == (y, mo + 1, d) :
				model.set(iter, TCOLUMN_DUE_, pb.tod(ts))
			# different day, move row
			else :
				model.remove(iter)
				rec = self.backend.fetch_task(id)
				self.opt_add_row('task', self.midnight(ts), self.task_db2li(rec), self.task_model_append)

	def create_sched(self, date=None) :
		calmods = []
		if date != None :
			calmods = self.create_calmod(date)

		v = self.input_dialog(ps.NEWAPPOINTMENT,
			[
			(gtk.Calendar, ps.DATE, None, calmods),
			(gtk.Entry, ps.TIME, pv.check_time, []),
			(gtk.Entry, ps.DURATION, pv.check_interval, []),
			(gtk.Entry, ps.DESCRIPTION, None, [])])

		if v != None :
			# get date and time
			(y, mo, d) = v[ps.DATE]
			(h, mi) = self.s2loi(v[ps.TIME])

			# create unix timestamp
			ts = time.mktime((y, mo + 1, d, h, mi, 0, 0, 0, -1))

			# convert duration to seconds
			duration = self.dur2sec(self.s2loi(v[ps.DURATION]))

			# fetch description
			text = v[ps.DESCRIPTION]

			# insert schedule item into database
			ns = self.backend.insert_sched(text, ts, duration, 0, 0)
			# commit
			self.backend.action_complete()

			# fetch item and add it to the GUI if in range
			self.opt_add_row('sched', self.midnight(ts), self.sched_db2li(self.backend.fetch_sched(ns)), self.sched_model_append)

	def sched_db2li(self, t) :
		(id, text, ts, duration, complete, task) = t
		ts_ = pb.tod(ts)
		duration_ = pb.sec2dur(duration)
		complete = bool(complete)
		editable = True

		return (id, text, ts_, duration_, complete, task, editable)

	def sched_model_append(self, model, item) :
		iter = model.append()
		(id, text, ts_, duration_, complete, task, editable) = item
		model.set(iter,
			SCOLUMN_ID, id,
			SCOLUMN_TEXT, text,
			SCOLUMN_TS_, ts_,
			SCOLUMN_DURATION_, duration_,
			SCOLUMN_COMPLETE, complete,
			SCOLUMN_TASK, task,
			SCOLUMN_EDITABLE, editable
		)

	def create_list_sched(self, list, ts) :
		model = gtk.ListStore(
			gobject.TYPE_INT,	#id
			gobject.TYPE_STRING,	#text
			gobject.TYPE_STRING,	#text version of time
			gobject.TYPE_STRING,	#text version of duration
			gobject.TYPE_BOOLEAN,	#complete
			gobject.TYPE_INT,	#task
			gobject.TYPE_BOOLEAN	#editable
		)

		model.set_sort_column_id(SCOLUMN_TS_, gtk.SORT_ASCENDING)
		self.stores['sched'][ts] = model

		for item in list :
			self.sched_model_append(model, self.sched_db2li(item))

		return model

	def add_columns(self, treeview, colset, col_e, toggle, edit):
		model = treeview.get_model()

		for col in colset :
			(cid, ctext) = col
			renderer = None
			isbool = model.get_column_type(cid) == gobject.TYPE_BOOLEAN
			if isbool :
				# create a toggle for boolean types
				renderer = gtk.CellRendererToggle()
				renderer.connect("toggled", toggle, model)
			else :
				# otherwise input is text, create text entry
				renderer = gtk.CellRendererText()
				renderer.connect("edited", edit, model)

			# align elements to the left
			renderer.set_property("xalign", 0.0)
			renderer.set_data("column", cid)

			# create column
			if isbool :
				column = gtk.TreeViewColumn(ctext, renderer, active=cid, activatable=col_e)
			else :
				column = gtk.TreeViewColumn(ctext, renderer, text=cid, editable=col_e)
			treeview.append_column(column)

	def on_s_toggled(self, cell, path_string, model) :
		column = cell.get_data('column')
		iter = model.get_iter_from_string(path_string)
		toggle_item = model.get_value(iter, column)

		toggle_item = not toggle_item

		id = model.get_value(iter, 0)
		if column == SCOLUMN_COMPLETE :
			self.backend.set_sched_complete(id, toggle_item)
		else :
			raise RuntimeError, 'Unknown toggle column: ' + str(column)

		self.backend.action_complete()
		model.set(iter, column, toggle_item)

	def on_t_toggled(self, cell, path_string, model) :
		column = cell.get_data('column')
		iter = model.get_iter_from_string(path_string)
		toggle_item = model.get_value(iter, column)

		toggle_item = not toggle_item

		id = model.get_value(iter, 0)
		if column == TCOLUMN_COMPLETE :
			self.backend.set_task_complete(id, toggle_item)
		else :
			raise RuntimeError, 'Unknown toggle column: ' + str(column)

		self.backend.action_complete()
		model.set(iter, column, toggle_item)

	def on_s_edited(self, cell, path_string, new_text, model) :
		iter = model.get_iter_from_string(path_string)
		path = model.get_path(iter)[0]
		column = cell.get_data("column")

		update = None
		id = model.get_value(iter, 0)
		if column == SCOLUMN_TEXT :
			update = new_text
			self.backend.set_sched_text(id, new_text)
		elif column == SCOLUMN_TS_ :
			if (pv.check_time(new_text)) :
				update = new_text

				ts = self.backend.get_sched_ts(id)

				(y, m, d, h, mi, s, a, b, t) = time.localtime(ts)
				(hn, mn) = self.s2loi(new_text)
				tsn = time.mktime((y, m, d, hn, mn, 0, a, b, -1))

				self.backend.set_sched_ts(id, tsn)
		elif column == SCOLUMN_DURATION_ :
			if (pv.check_interval(new_text)) :
				update = new_text

				d = self.dur2sec(self.s2loi(new_text))
				self.backend.set_sched_duration(id, d)

		self.backend.action_complete()
				
		if update == None :
			self.dialog(ps.ERR_FORMATTING)
		else :
			model.set(iter, column, update)

	def on_t_edited(self, cell, path_string, new_text, model) :
		iter = model.get_iter_from_string(path_string)
		path = model.get_path(iter)[0]
		column = cell.get_data("column")

		update = None
		id = model.get_value(iter, 0)
		if column == TCOLUMN_TEXT :
			update = new_text
			self.backend.set_task_text(id, new_text)
		elif column == TCOLUMN_DUE_ :
			if (pv.check_time(new_text)) :
				update = new_text

				ts = self.backend.get_task_due(id)

				(y, m, d, h, mi, s, a, b, t) = time.localtime(ts)
				(hn, mn) = self.s2loi(new_text)
				tsn = time.mktime((y, m, d, hn, mn, 0, a, b, -1))

				self.backend.set_task_due(id, tsn)

		self.backend.action_complete()
				
		if update == None :
			self.dialog(ps.ERR_FORMATTING)
		else :
			model.set(iter, column, update)


class BackgroundTimer(threading.Thread) :
	def __init__(self) :
		# initialize empty callback list
		self.callbacks = {'d':[], 'h':[], 'm':[], 's':[]}
		# start thread
		threading.Thread.__init__(self)

	def run(self) :
		global cont

		# initial time info
		self.gmt = time.gmtime()
		self.day = time.strftime('%j', self.gmt)
		self.hour = time.strftime('%H', self.gmt)
		self.min = time.strftime('%M', self.gmt)
		self.sec = time.strftime('%S', self.gmt)

		while cont :
			# current time info
			gmt = time.gmtime()
			_day = time.strftime('%j', gmt)
			_hour = time.strftime('%H', gmt)
			_min = time.strftime('%M', gmt)
			_sec = time.strftime('%S', gmt)

			# for each interval, run the callbacks if interval
			# has passed
			if self.day != _day :
				for c in self.callbacks['d'] :
					c()
			if self.hour != _hour :
				for c in self.callbacks['h'] :
					c()
			if self.min != _min :
				for c in self.callbacks['m'] :
					c()
			if self.sec != _sec :
				for c in self.callbacks['s'] :
					c()

			# copy current time to old time for use in next cycle
			self.day = _day
			self.hour = _hour
			self.min = _min
			self.sec = _sec

			time.sleep(0.25)

	def add_callback(self, period, callback) :
		self.callbacks[period].append(callback)

def gtkm() :
	global cont
	while cont :
		while gtk.events_pending() :
			gtk.main_iteration()
		time.sleep(0.02)

if __name__ == '__main__':
	psyched = Psyched()
	try:
		gtkm()
	except KeyboardInterrupt:
		cont = False
