[hamster-applet] moving widgets out to separate tools module as the code base has grown rather lengthy
- From: Toms Baugis <tbaugis src gnome org>
- To: svn-commits-list gnome org
- Cc:
- Subject: [hamster-applet] moving widgets out to separate tools module as the code base has grown rather lengthy
- Date: Thu, 19 Nov 2009 15:44:56 +0000 (UTC)
commit b952ee5920bc8f8123b38adf4fdc61f7f23cba0f
Author: Toms Bauģis <toms baugis gmail com>
Date: Thu Nov 19 12:33:07 2009 +0000
moving widgets out to separate tools module as the code base has grown rather lengthy
hamster/Makefile.am | 2 +-
hamster/edit_activity.py | 12 +-
hamster/preferences.py | 4 +-
hamster/standalone.py | 272 +------------------------
hamster/stats.py | 14 +-
hamster/stuff.py | 36 ----
hamster/tools/Makefile.am | 9 +
hamster/tools/__init__.py | 92 +++++++++
hamster/tools/activityentry.py | 291 ++++++++++++++++++++++++++
hamster/tools/dateinput.py | 176 ++++++++++++++++
hamster/tools/timeinput.py | 249 +++++++++++++++++++++++
hamster/widgets.py | 439 ----------------------------------------
12 files changed, 844 insertions(+), 752 deletions(-)
---
diff --git a/hamster/Makefile.am b/hamster/Makefile.am
index 273752f..3adc9e2 100644
--- a/hamster/Makefile.am
+++ b/hamster/Makefile.am
@@ -1,4 +1,4 @@
-SUBDIRS = keybinder
+SUBDIRS = keybinder tools
ACLOCAL_AMFLAGS = -I m4
CPPFLAGS = \
diff --git a/hamster/edit_activity.py b/hamster/edit_activity.py
index 14383b9..a1e8986 100644
--- a/hamster/edit_activity.py
+++ b/hamster/edit_activity.py
@@ -26,7 +26,11 @@ import gtk
import gobject
import stuff
-import graphics, widgets
+import graphics
+
+from tools.dateinput import DateInput
+from tools.timeinput import TimeInput
+
import eds
from configuration import runtime
@@ -414,15 +418,15 @@ class CustomFactController:
end_date = end_date or start_date + dt.timedelta(minutes = 30)
- self.start_date = widgets.DateInput(start_date)
+ self.start_date = DateInput(start_date)
self.get_widget("start_date_placeholder").add(self.start_date)
self.start_date.connect("date-entered", self.on_start_date_entered)
- self.start_time = widgets.TimeInput(start_date)
+ self.start_time = TimeInput(start_date)
self.get_widget("start_time_placeholder").add(self.start_time)
self.start_time.connect("time-entered", self.on_start_time_entered)
- self.end_time = widgets.TimeInput(end_date, start_date)
+ self.end_time = TimeInput(end_date, start_date)
self.get_widget("end_time_placeholder").add(self.end_time)
self.end_time.connect("time-entered", self.on_end_time_entered)
self.set_end_date_label(end_date)
diff --git a/hamster/preferences.py b/hamster/preferences.py
index d773eb1..e852f38 100755
--- a/hamster/preferences.py
+++ b/hamster/preferences.py
@@ -27,7 +27,7 @@ import gtk
import dispatcher, storage, stuff
import datetime as dt
-import widgets
+from tools.timeinput import TimeInput
from configuration import GconfStore, runtime
@@ -143,7 +143,7 @@ class PreferencesEditor:
selection = self.category_tree.get_selection()
selection.connect('changed', self.category_changed_cb, self.category_store)
- self.day_start = widgets.TimeInput(dt.time(5,30))
+ self.day_start = TimeInput(dt.time(5,30))
self.get_widget("day_start_placeholder").add(self.day_start)
self.day_start.connect("time-entered", self.on_day_start_changed)
diff --git a/hamster/standalone.py b/hamster/standalone.py
index b99a838..7152a43 100755
--- a/hamster/standalone.py
+++ b/hamster/standalone.py
@@ -27,272 +27,14 @@ import gtk
#gtk.gdk.threads_init()
from configuration import GconfStore, runtime
-import stuff, widgets
+import tools
+from tools.activityentry import ActivityEntry
-import gobject
-
-class ActivityEntry(gtk.Entry):
- __gsignals__ = {
- 'value-entered': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),
- }
-
-
- def __init__(self):
- gtk.Entry.__init__(self)
- self.news = False
- self.activities = None
- self.categories = None
- self.filter = None
- self.max_results = 10 # limit popup size to 10 results
-
- self.popup = gtk.Window(type = gtk.WINDOW_POPUP)
-
- box = gtk.ScrolledWindow()
- box.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
-
- self.tree = gtk.TreeView()
- self.tree.set_headers_visible(False)
- self.tree.set_hover_selection(True)
-
- bgcolor = gtk.Style().bg[gtk.STATE_NORMAL].to_string()
- time_cell = gtk.CellRendererPixbuf()
- time_cell.set_property("icon-name", "appointment-new")
- time_cell.set_property("cell-background", bgcolor)
-
- self.time_icon_column = gtk.TreeViewColumn("",
- time_cell)
- self.tree.append_column(self.time_icon_column)
-
- time_cell = gtk.CellRendererText()
- time_cell.set_property("scale", 0.8)
- time_cell.set_property("cell-background", bgcolor)
-
- self.time_column = gtk.TreeViewColumn("Time",
- time_cell,
- text = 3)
- self.tree.append_column(self.time_column)
-
-
- self.activity_column = gtk.TreeViewColumn("Activity",
- gtk.CellRendererText(),
- text=1)
- self.activity_column.set_expand(True)
- self.tree.append_column(self.activity_column)
-
- self.category_column = gtk.TreeViewColumn("Category",
- stuff.CategoryCell(),
- text=2)
- self.tree.append_column(self.category_column)
-
-
-
- self.tree.connect("button-press-event", self._on_tree_button_press_event)
-
- box.add(self.tree)
- self.popup.add(box)
-
- self.connect("button-press-event", self._on_button_press_event)
- self.connect("key-press-event", self._on_key_press_event)
- self.connect("key-release-event", self._on_key_release_event)
- self.connect("focus-in-event", self._on_focus_in_event)
- self.connect("focus-out-event", self._on_focus_out_event)
- self.connect("changed", self._on_text_changed)
- self.show()
- self.populate_suggestions()
-
- def populate_suggestions(self):
- self.activities = self.activities or runtime.storage.get_autocomplete_activities()
- self.categories = self.categories or runtime.storage.get_category_list()
-
- if self.get_selection_bounds():
- cursor = self.get_selection_bounds()[0]
- else:
- cursor = self.get_position()
-
-
- if self.filter == self.get_text()[:cursor]:
- return #same thing, no need to repopulate
-
- self.filter = self.get_text()[:cursor]
-
- input_activity = stuff.parse_activity_input(self.filter)
-
- time = ''
- if input_activity.start_time:
- time = input_activity.start_time.strftime("%H:%M")
- if input_activity.end_time:
- time += "-%s" % input_activity.end_time.strftime("%H:%M")
-
-
- store = self.tree.get_model()
- if not store:
- store = gtk.ListStore(str, str, str, str)
- self.tree.set_model(store)
- store.clear()
-
- if self.filter.find("@") > 0:
- key = self.filter[self.filter.find("@")+1:].lower()
- for category in self.categories:
- if key in category['name'].lower():
- fillable = (self.filter[:self.filter.find("@") + 1] + category['name'])
- store.append([fillable, category['name'], fillable, time])
- else:
- for activity in self.activities:
- if input_activity.activity_name == "" or activity['name'].startswith(input_activity.activity_name): #self.filter in activity['name']:
- fillable = activity['name']
- if activity['category']:
- fillable += "@%s" % activity['category']
-
- if time:
- fillable = "%s %s" % (time, fillable)
-
- store.append([fillable, activity['name'], activity['category'], time])
-
-
- def show_popup(self):
- result_count = self.tree.get_model().iter_n_children(None)
- if result_count <= 1:
- self.popup.hide()
- return
-
- activity = stuff.parse_activity_input(self.filter)
- time = ''
- if activity.start_time:
- time = activity.start_time.strftime("%H:%M")
- if activity.end_time:
- time += "-%s" % activity.end_time.strftime("%H:%M")
-
- self.time_icon_column.set_visible(activity.start_time != None and self.filter.find("@") == -1)
- self.time_column.set_visible(activity.start_time != None and self.filter.find("@") == -1)
-
-
- self.category_column.set_visible(self.filter.find("@") == -1)
-
-
- #move popup under the widget
- alloc = self.get_allocation()
- x, y = self.get_parent_window().get_origin()
-
- self.popup.move(x + alloc.x,y + alloc.y + alloc.height)
-
- w = alloc.width
-
- #TODO - this is clearly unreliable as we calculate tree row size based on our gtk entry
- self.tree.parent.set_size_request(w,(alloc.height-6) * min([result_count, self.max_results]))
- self.popup.resize(w, (alloc.height-6) * min([result_count, self.max_results]))
-
-
- self.popup.show_all()
-
- def complete_inline(self):
- model = self.tree.get_model()
- activity = stuff.parse_activity_input(self.filter)
- subject = self.get_text()
-
- if not subject or model.iter_n_children(None) == 0:
- return
-
- prefix_length = 0
-
- labels = [row[0] for row in model]
- shortest = min([len(label) for label in labels])
- first = labels[0] #since we are looking for common prefix, we don't care which label we use for comparisons
-
- for i in range(len(subject), shortest):
- letter_matching = all([label[i]==first[i] for label in labels])
-
- if not letter_matching:
- break
-
- prefix_length +=1
-
- if prefix_length:
- prefix = first[len(subject):len(subject)+prefix_length]
- self.set_text("%s%s" % (self.filter, prefix))
- self.select_region(len(self.filter), len(self.filter) + prefix_length)
+import stuff
+import gobject
- def _on_text_changed(self, widget):
- self.news = True
-
-
- def _on_button_press_event(self, button, event):
- self.populate_suggestions()
- self.show_popup()
-
- def _on_focus_in_event(self, entry, event):
- self.populate_suggestions()
- self.show_popup()
-
- def _on_focus_out_event(self, event, something):
- self.popup.hide()
- if self.news:
- self.emit("value-entered")
- self.news = False
-
- def _on_key_release_event(self, entry, event):
- if (event.keyval in (gtk.keysyms.Return, gtk.keysyms.KP_Enter)):
- if self.popup.get_property("visible"):
- if self.tree.get_cursor()[0]:
- self.set_text(self.tree.get_model()[self.tree.get_cursor()[0][0]][0])
-
- self._on_selected()
-
- self.popup.hide()
- else:
- self._on_selected()
- elif (event.keyval == gtk.keysyms.Escape):
- self.popup.hide()
- elif event.keyval in (gtk.keysyms.Up, gtk.keysyms.Down):
- return False
- else:
- self.populate_suggestions()
- self.show_popup()
-
- if event.keyval not in (gtk.keysyms.Delete, gtk.keysyms.BackSpace):
- self.complete_inline()
-
-
-
-
- def _on_key_press_event(self, entry, event):
-
- if event.keyval in (gtk.keysyms.Up, gtk.keysyms.Down):
- cursor = self.tree.get_cursor()
-
- if not cursor or not cursor[0]:
- self.tree.set_cursor(0)
- return True
-
- i = cursor[0][0]
-
- if event.keyval == gtk.keysyms.Up:
- i-=1
- elif event.keyval == gtk.keysyms.Down:
- i+=1
-
- # keep it in the sane borders
- i = min(max(i, 0), len(self.tree.get_model()) - 1)
-
- self.tree.set_cursor(i)
- self.tree.scroll_to_cell(i, use_align = True, row_align = 0.4)
- return True
- else:
- return False
-
- def _on_tree_button_press_event(self, tree, event):
- model, iter = tree.get_selection().get_selected()
- value = model.get_value(iter, 0)
- self.set_text(value)
- self.popup.hide()
- self._on_selected()
-
- def _on_selected(self):
- if self.news:
- self.emit("value-entered")
- self.news = False
- self.set_position(len(self.get_text()))
class MainWindow(object):
@@ -319,14 +61,14 @@ class MainWindow(object):
self.get_widget("todays_activities_ebox").modify_bg(gtk.STATE_NORMAL,
gtk.gdk.Color(65536.0,65536.0,65536.0))
- self.new_name = ActivityEntry() #widgets.HintEntry(_("Time and Name"), self.get_widget("new_name"))
- widgets.add_hint(self.new_name, _("Time and Name"))
+ self.new_name = ActivityEntry()
+ tools.add_hint(self.new_name, _("Time and Name"))
parent = self.get_widget("new_name").parent
parent.remove(self.get_widget("new_name"))
parent.add(self.new_name)
self.new_description = self.get_widget("new_description")
- widgets.add_hint(self.new_description, _("Tags or Description"))
+ tools.add_hint(self.new_description, _("Tags or Description"))
def set_last_activity(self):
diff --git a/hamster/stats.py b/hamster/stats.py
index 675a677..9b9413d 100644
--- a/hamster/stats.py
+++ b/hamster/stats.py
@@ -29,7 +29,11 @@ import stuff
import charting
from edit_activity import CustomFactController
-import reports, widgets, graphics
+import reports, graphics
+
+import tools
+from tools.dateinput import DateInput
+
from configuration import runtime, GconfStore
import webbrowser
@@ -95,9 +99,9 @@ class ReportChooserDialog(gtk.Dialog):
filter.add_pattern("*")
self.dialog.add_filter(filter)
- self.start_date = widgets.DateInput()
+ self.start_date = DateInput()
ui.get_object('from_date_box').add(self.start_date)
- self.end_date = widgets.DateInput()
+ self.end_date = DateInput()
ui.get_object('to_date_box').add(self.end_date)
self.category_box = ui.get_object('category_box')
@@ -852,7 +856,7 @@ than 15 minutes you seem to be a busy bee." % ("<b>%d</b>" % short_percent))
day_row = self.fact_store.append(None,
[-1,
fact_date,
- stuff.format_duration(day_total),
+ tools.format_duration(day_total),
current_date.strftime('%Y-%m-%d'),
"",
"",
@@ -863,7 +867,7 @@ than 15 minutes you seem to be a busy bee." % ("<b>%d</b>" % short_percent))
[fact["id"],
fact["start_time"].strftime('%H:%M') + " " +
fact["name"],
- stuff.format_duration(fact["delta"]),
+ tools.format_duration(fact["delta"]),
fact["start_time"].strftime('%Y-%m-%d'),
fact["description"],
fact["category"],
diff --git a/hamster/stuff.py b/hamster/stuff.py
index 428c1db..c52ae8e 100644
--- a/hamster/stuff.py
+++ b/hamster/stuff.py
@@ -124,42 +124,6 @@ class ActivityColumn(gtk.TreeViewColumn):
cell.set_property("ellipsize", pango.ELLIPSIZE_END)
self.set_cell_data_func(cell, self.activity_painter)
-def duration_minutes(duration):
- """returns minutes from duration, otherwise we keep bashing in same math"""
- return duration.seconds / 60 + duration.days * 24 * 60
-
-def format_duration(minutes, human = True):
- """formats duration in a human readable format.
- accepts either minutes or timedelta"""
-
- if isinstance(minutes, dt.timedelta):
- minutes = duration_minutes(minutes)
-
- if not minutes:
- if human:
- return ""
- else:
- return "00:00"
-
- hours = minutes / 60
- minutes = minutes % 60
- formatted_duration = ""
-
- if human:
- if minutes % 60 == 0:
- # duration in round hours
- formatted_duration += _("%dh") % (hours)
- elif hours == 0:
- # duration less than hour
- formatted_duration += _("%dmin") % (minutes % 60.0)
- else:
- # x hours, y minutes
- formatted_duration += _("%dh %dmin") % (hours, minutes % 60)
- else:
- formatted_duration += "%02d:%02d" % (hours, minutes)
-
-
- return formatted_duration
def totals(iter, keyfunc, sumfunc):
"""groups items by field described in keyfunc and counts totals using value
diff --git a/hamster/tools/Makefile.am b/hamster/tools/Makefile.am
new file mode 100644
index 0000000..059791c
--- /dev/null
+++ b/hamster/tools/Makefile.am
@@ -0,0 +1,9 @@
+hamsterdir = $(pyexecdir)/hamster/tools
+hamster_PYTHON = \
+ __init__.py \
+ activityentry.py \
+ timeinput.py \
+ dateinput.py
+
+clean-local:
+ rm -rf *.pyc *.pyo
diff --git a/hamster/tools/__init__.py b/hamster/tools/__init__.py
new file mode 100644
index 0000000..dc5fbde
--- /dev/null
+++ b/hamster/tools/__init__.py
@@ -0,0 +1,92 @@
+# - coding: utf-8 -
+
+# Copyright (C) 2007-2009 Toms Bauģis <toms.baugis at gmail.com>
+
+# 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 <http://www.gnu.org/licenses/>.
+
+import datetime as dt
+
+def add_hint(entry, hint):
+ entry.hint = hint
+
+ def _set_hint(self, widget, event):
+ if self.get_text(): # don't mess with user entered text
+ return
+
+ self.modify_text(gtk.STATE_NORMAL, gtk.gdk.Color("gray"))
+ hint_font = pango.FontDescription(gtk.Style().font_desc.to_string())
+ hint_font.set_style(pango.STYLE_ITALIC)
+ self.modify_font(hint_font)
+
+ self.set_text(self.hint)
+
+ def _set_normal(self, widget, event):
+ self.modify_text(gtk.STATE_NORMAL, gtk.Style().fg[gtk.STATE_NORMAL])
+ hint_font = pango.FontDescription(gtk.Style().font_desc.to_string())
+ self.modify_font(hint_font)
+
+ if self.get_text() == self.hint:
+ self.set_text("")
+
+ import types
+ instancemethod = types.MethodType
+
+ entry._set_hint = instancemethod(_set_hint, entry, gtk.Entry)
+ entry._set_normal = instancemethod(_set_normal, entry, gtk.Entry)
+
+ entry.connect('focus-in-event', entry._set_normal)
+ entry.connect('focus-out-event', entry._set_hint)
+
+ entry._set_hint(entry, None)
+
+
+
+def format_duration(minutes, human = True):
+ """formats duration in a human readable format.
+ accepts either minutes or timedelta"""
+
+ if isinstance(minutes, dt.timedelta):
+ minutes = duration_minutes(minutes)
+
+ if not minutes:
+ if human:
+ return ""
+ else:
+ return "00:00"
+
+ hours = minutes / 60
+ minutes = minutes % 60
+ formatted_duration = ""
+
+ if human:
+ if minutes % 60 == 0:
+ # duration in round hours
+ formatted_duration += _("%dh") % (hours)
+ elif hours == 0:
+ # duration less than hour
+ formatted_duration += _("%dmin") % (minutes % 60.0)
+ else:
+ # x hours, y minutes
+ formatted_duration += _("%dh %dmin") % (hours, minutes % 60)
+ else:
+ formatted_duration += "%02d:%02d" % (hours, minutes)
+
+
+ return formatted_duration
+
+def duration_minutes(duration):
+ """returns minutes from duration, otherwise we keep bashing in same math"""
+ return duration.seconds / 60 + duration.days * 24 * 60
diff --git a/hamster/tools/activityentry.py b/hamster/tools/activityentry.py
new file mode 100644
index 0000000..d9692ff
--- /dev/null
+++ b/hamster/tools/activityentry.py
@@ -0,0 +1,291 @@
+# - coding: utf-8 -
+
+# Copyright (C) 2008-2009 Toms Bauģis <toms.baugis at gmail.com>
+
+# 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 <http://www.gnu.org/licenses/>.
+
+from hamster import tools
+from hamster.configuration import GconfStore, runtime
+
+from stuff import format_duration
+import gtk
+import datetime as dt
+import calendar
+import gobject
+import re
+
+class ActivityEntry(gtk.Entry):
+ __gsignals__ = {
+ 'value-entered': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),
+ }
+
+
+ def __init__(self):
+ gtk.Entry.__init__(self)
+ self.news = False
+ self.activities = None
+ self.categories = None
+ self.filter = None
+ self.max_results = 10 # limit popup size to 10 results
+
+ self.popup = gtk.Window(type = gtk.WINDOW_POPUP)
+
+ box = gtk.ScrolledWindow()
+ box.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
+
+ self.tree = gtk.TreeView()
+ self.tree.set_headers_visible(False)
+ self.tree.set_hover_selection(True)
+
+ bgcolor = gtk.Style().bg[gtk.STATE_NORMAL].to_string()
+ time_cell = gtk.CellRendererPixbuf()
+ time_cell.set_property("icon-name", "appointment-new")
+ time_cell.set_property("cell-background", bgcolor)
+
+ self.time_icon_column = gtk.TreeViewColumn("",
+ time_cell)
+ self.tree.append_column(self.time_icon_column)
+
+ time_cell = gtk.CellRendererText()
+ time_cell.set_property("scale", 0.8)
+ time_cell.set_property("cell-background", bgcolor)
+
+ self.time_column = gtk.TreeViewColumn("Time",
+ time_cell,
+ text = 3)
+ self.tree.append_column(self.time_column)
+
+
+ self.activity_column = gtk.TreeViewColumn("Activity",
+ gtk.CellRendererText(),
+ text=1)
+ self.activity_column.set_expand(True)
+ self.tree.append_column(self.activity_column)
+
+ self.category_column = gtk.TreeViewColumn("Category",
+ stuff.CategoryCell(),
+ text=2)
+ self.tree.append_column(self.category_column)
+
+
+
+ self.tree.connect("button-press-event", self._on_tree_button_press_event)
+
+ box.add(self.tree)
+ self.popup.add(box)
+
+ self.connect("button-press-event", self._on_button_press_event)
+ self.connect("key-press-event", self._on_key_press_event)
+ self.connect("key-release-event", self._on_key_release_event)
+ self.connect("focus-in-event", self._on_focus_in_event)
+ self.connect("focus-out-event", self._on_focus_out_event)
+ self.connect("changed", self._on_text_changed)
+ self.show()
+ self.populate_suggestions()
+
+ def populate_suggestions(self):
+ self.activities = self.activities or runtime.storage.get_autocomplete_activities()
+ self.categories = self.categories or runtime.storage.get_category_list()
+
+ if self.get_selection_bounds():
+ cursor = self.get_selection_bounds()[0]
+ else:
+ cursor = self.get_position()
+
+
+ if self.filter == self.get_text()[:cursor]:
+ return #same thing, no need to repopulate
+
+ self.filter = self.get_text()[:cursor]
+
+ input_activity = stuff.parse_activity_input(self.filter)
+
+ time = ''
+ if input_activity.start_time:
+ time = input_activity.start_time.strftime("%H:%M")
+ if input_activity.end_time:
+ time += "-%s" % input_activity.end_time.strftime("%H:%M")
+
+
+ store = self.tree.get_model()
+ if not store:
+ store = gtk.ListStore(str, str, str, str)
+ self.tree.set_model(store)
+ store.clear()
+
+ if self.filter.find("@") > 0:
+ key = self.filter[self.filter.find("@")+1:].lower()
+ for category in self.categories:
+ if key in category['name'].lower():
+ fillable = (self.filter[:self.filter.find("@") + 1] + category['name'])
+ store.append([fillable, category['name'], fillable, time])
+ else:
+ for activity in self.activities:
+ if input_activity.activity_name == "" or activity['name'].startswith(input_activity.activity_name): #self.filter in activity['name']:
+ fillable = activity['name']
+ if activity['category']:
+ fillable += "@%s" % activity['category']
+
+ if time:
+ fillable = "%s %s" % (time, fillable)
+
+ store.append([fillable, activity['name'], activity['category'], time])
+
+
+ def show_popup(self):
+ result_count = self.tree.get_model().iter_n_children(None)
+ if result_count <= 1:
+ self.popup.hide()
+ return
+
+ activity = stuff.parse_activity_input(self.filter)
+ time = ''
+ if activity.start_time:
+ time = activity.start_time.strftime("%H:%M")
+ if activity.end_time:
+ time += "-%s" % activity.end_time.strftime("%H:%M")
+
+ self.time_icon_column.set_visible(activity.start_time != None and self.filter.find("@") == -1)
+ self.time_column.set_visible(activity.start_time != None and self.filter.find("@") == -1)
+
+
+ self.category_column.set_visible(self.filter.find("@") == -1)
+
+
+ #move popup under the widget
+ alloc = self.get_allocation()
+ x, y = self.get_parent_window().get_origin()
+
+ self.popup.move(x + alloc.x,y + alloc.y + alloc.height)
+
+ w = alloc.width
+
+ #TODO - this is clearly unreliable as we calculate tree row size based on our gtk entry
+ self.tree.parent.set_size_request(w,(alloc.height-6) * min([result_count, self.max_results]))
+ self.popup.resize(w, (alloc.height-6) * min([result_count, self.max_results]))
+
+
+ self.popup.show_all()
+
+ def complete_inline(self):
+ model = self.tree.get_model()
+ activity = stuff.parse_activity_input(self.filter)
+ subject = self.get_text()
+
+ if not subject or model.iter_n_children(None) == 0:
+ return
+
+ prefix_length = 0
+
+ labels = [row[0] for row in model]
+ shortest = min([len(label) for label in labels])
+ first = labels[0] #since we are looking for common prefix, we don't care which label we use for comparisons
+
+ for i in range(len(subject), shortest):
+ letter_matching = all([label[i]==first[i] for label in labels])
+
+ if not letter_matching:
+ break
+
+ prefix_length +=1
+
+ if prefix_length:
+ prefix = first[len(subject):len(subject)+prefix_length]
+ self.set_text("%s%s" % (self.filter, prefix))
+ self.select_region(len(self.filter), len(self.filter) + prefix_length)
+
+
+
+ def _on_text_changed(self, widget):
+ self.news = True
+
+
+ def _on_button_press_event(self, button, event):
+ self.populate_suggestions()
+ self.show_popup()
+
+ def _on_focus_in_event(self, entry, event):
+ self.populate_suggestions()
+ self.show_popup()
+
+ def _on_focus_out_event(self, event, something):
+ self.popup.hide()
+ if self.news:
+ self.emit("value-entered")
+ self.news = False
+
+ def _on_key_release_event(self, entry, event):
+ if (event.keyval in (gtk.keysyms.Return, gtk.keysyms.KP_Enter)):
+ if self.popup.get_property("visible"):
+ if self.tree.get_cursor()[0]:
+ self.set_text(self.tree.get_model()[self.tree.get_cursor()[0][0]][0])
+
+ self._on_selected()
+
+ self.popup.hide()
+ else:
+ self._on_selected()
+ elif (event.keyval == gtk.keysyms.Escape):
+ self.popup.hide()
+ elif event.keyval in (gtk.keysyms.Up, gtk.keysyms.Down):
+ return False
+ else:
+ self.populate_suggestions()
+ self.show_popup()
+
+ if event.keyval not in (gtk.keysyms.Delete, gtk.keysyms.BackSpace):
+ self.complete_inline()
+
+
+
+
+ def _on_key_press_event(self, entry, event):
+
+ if event.keyval in (gtk.keysyms.Up, gtk.keysyms.Down):
+ cursor = self.tree.get_cursor()
+
+ if not cursor or not cursor[0]:
+ self.tree.set_cursor(0)
+ return True
+
+ i = cursor[0][0]
+
+ if event.keyval == gtk.keysyms.Up:
+ i-=1
+ elif event.keyval == gtk.keysyms.Down:
+ i+=1
+
+ # keep it in the sane borders
+ i = min(max(i, 0), len(self.tree.get_model()) - 1)
+
+ self.tree.set_cursor(i)
+ self.tree.scroll_to_cell(i, use_align = True, row_align = 0.4)
+ return True
+ else:
+ return False
+
+ def _on_tree_button_press_event(self, tree, event):
+ model, iter = tree.get_selection().get_selected()
+ value = model.get_value(iter, 0)
+ self.set_text(value)
+ self.popup.hide()
+ self._on_selected()
+
+ def _on_selected(self):
+ if self.news:
+ self.emit("value-entered")
+ self.news = False
+ self.set_position(len(self.get_text()))
diff --git a/hamster/tools/dateinput.py b/hamster/tools/dateinput.py
new file mode 100644
index 0000000..ef18365
--- /dev/null
+++ b/hamster/tools/dateinput.py
@@ -0,0 +1,176 @@
+# - coding: utf-8 -
+
+# Copyright (C) 2008-2009 Toms Bauģis <toms.baugis at gmail.com>
+
+# 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 <http://www.gnu.org/licenses/>.
+
+from hamster.tools import format_duration
+import gtk
+import datetime as dt
+import calendar
+import gobject
+import re
+
+class DateInput(gtk.Entry):
+ """ a text entry widget with calendar popup"""
+ __gsignals__ = {
+ 'date-entered': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),
+ }
+
+
+ def __init__(self, date = None):
+ gtk.Entry.__init__(self)
+
+ self.set_width_chars(12) #12 is enough for 12-oct-2009, which is verbose
+ self.date = date
+ if date:
+ self.set_date(date)
+
+ self.news = False
+ self.prev_cal_day = None #workaround
+ self.popup = gtk.Window(type = gtk.WINDOW_POPUP)
+ calendar_box = gtk.HBox()
+
+ self.date_calendar = gtk.Calendar()
+ self.date_calendar.connect("day-selected", self._on_day_selected)
+ self.date_calendar.connect("day-selected-double-click",
+ self.__on_day_selected_double_click)
+ self.date_calendar.connect("button-press-event",
+ self._on_cal_button_press_event)
+ calendar_box.add(self.date_calendar)
+ self.popup.add(calendar_box)
+
+ self.connect("button-press-event", self._on_button_press_event)
+ self.connect("key-press-event", self._on_key_press_event)
+ self.connect("focus-in-event", self._on_focus_in_event)
+ self.connect("focus-out-event", self._on_focus_out_event)
+ self.connect("changed", self._on_text_changed)
+ self.show()
+
+ def set_date(self, date):
+ """sets date to specified, using default format"""
+ self.date = date
+ self.set_text(self._format_date(self.date))
+
+ def get_date(self):
+ """sets date to specified, using default format"""
+ self.date = self._figure_date(self.get_text())
+ self.set_text(self._format_date(self.date))
+ return self.date
+
+ def _figure_date(self, date_str):
+ try:
+ return dt.datetime.strptime(date_str, "%x")
+ except:
+ return self.date
+
+ def _format_date(self, date):
+ if not date:
+ return ""
+ else:
+ return date.strftime("%x")
+
+ def _on_text_changed(self, widget):
+ self.news = True
+
+ def __on_day_selected_double_click(self, calendar):
+ self.prev_cal_day = None
+ self._on_day_selected(calendar) #forward
+
+ def _on_cal_button_press_event(self, calendar, event):
+ self.prev_cal_day = calendar.get_date()[2]
+
+ def _on_day_selected(self, calendar):
+ if self.popup.get_property("visible") == False:
+ return
+
+ if self.prev_cal_day == calendar.get_date()[2]:
+ return
+
+ cal_date = calendar.get_date()
+
+ self.date = dt.date(cal_date[0], cal_date[1] + 1, cal_date[2])
+ self.set_text(self._format_date(self.date))
+
+ self.popup.hide()
+ if self.news:
+ self.emit("date-entered")
+ self.news = False
+
+
+ def show_popup(self):
+ window = self.get_parent_window()
+ x, y= window.get_origin()
+
+ alloc = self.get_allocation()
+
+ date = self._figure_date(self.get_text())
+ if date:
+ self.prev_cal_day = date.day #avoid
+ self.date_calendar.select_month(date.month-1, date.year)
+ self.date_calendar.select_day(date.day)
+
+ self.popup.move(x + alloc.x,y + alloc.y + alloc.height)
+ self.popup.show_all()
+
+ def _on_focus_in_event(self, entry, event):
+ self.show_popup()
+
+ def _on_button_press_event(self, button, event):
+ self.show_popup()
+
+
+ def _on_focus_out_event(self, event, something):
+ self.popup.hide()
+ if self.news:
+ self.emit("date-entered")
+ self.news = False
+
+ def _on_key_press_event(self, entry, event):
+ if self.popup.get_property("visible"):
+ cal_date = self.date_calendar.get_date()
+ date = dt.date(cal_date[0], cal_date[1], cal_date[2])
+ else:
+ date = self._figure_date(entry.get_text())
+ if not date:
+ return
+
+ enter_pressed = False
+
+ if event.keyval == gtk.keysyms.Up:
+ date = date - dt.timedelta(days=1)
+ elif event.keyval == gtk.keysyms.Down:
+ date = date + dt.timedelta(days=1)
+ elif (event.keyval == gtk.keysyms.Return or
+ event.keyval == gtk.keysyms.KP_Enter):
+ enter_pressed = True
+ elif (event.keyval == gtk.keysyms.Escape):
+ self.popup.hide()
+ elif event.keyval in (gtk.keysyms.Left, gtk.keysyms.Right):
+ return False #keep calendar open and allow user to walk in text
+ else:
+ self.popup.hide()
+ return False
+
+ if enter_pressed:
+ self.prev_cal_day = "borken"
+ else:
+ #prev_cal_day is our only way of checking that date is right
+ self.prev_cal_day = date.day
+
+ self.date_calendar.select_month(date.month, date.year)
+ self.date_calendar.select_day(date.day)
+ return True
diff --git a/hamster/tools/timeinput.py b/hamster/tools/timeinput.py
new file mode 100644
index 0000000..a060813
--- /dev/null
+++ b/hamster/tools/timeinput.py
@@ -0,0 +1,249 @@
+# - coding: utf-8 -
+
+# Copyright (C) 2008-2009 Toms Bauģis <toms.baugis at gmail.com>
+
+# 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 <http://www.gnu.org/licenses/>.
+
+from hamster.tools import format_duration
+import gtk
+import datetime as dt
+import calendar
+import gobject
+import re
+
+class TimeInput(gtk.Entry):
+ __gsignals__ = {
+ 'time-entered': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),
+ }
+
+
+ def __init__(self, time = None, start_time = None):
+ gtk.Entry.__init__(self)
+
+ self.start_time = start_time
+ self.news = False
+
+ self.set_width_chars(7) #7 is like 11:24pm
+ self.time = time
+ if time:
+ self.set_time(time)
+
+
+ self.popup = gtk.Window(type = gtk.WINDOW_POPUP)
+ time_box = gtk.ScrolledWindow()
+ time_box.set_policy(gtk.POLICY_NEVER, gtk.POLICY_ALWAYS)
+
+ self.time_tree = gtk.TreeView()
+ self.time_tree.set_headers_visible(False)
+ self.time_tree.set_hover_selection(True)
+
+ self.time_tree.append_column(gtk.TreeViewColumn("Time",
+ gtk.CellRendererText(),
+ text=0))
+ self.time_tree.connect("button-press-event",
+ self._on_time_tree_button_press_event)
+
+ time_box.add(self.time_tree)
+ self.popup.add(time_box)
+
+ self.connect("button-press-event", self._on_button_press_event)
+ self.connect("key-press-event", self._on_key_press_event)
+ self.connect("focus-in-event", self._on_focus_in_event)
+ self.connect("focus-out-event", self._on_focus_out_event)
+ self.connect("changed", self._on_text_changed)
+ self.show()
+
+
+ def set_start_time(self, start_time):
+ """ set the start time. when start time is set, drop down list
+ will start from start time and duration will be displayed in
+ brackets
+ """
+ self.start_time = start_time
+
+ def set_time(self, time):
+ self.time = time
+ self.set_text(self._format_time(time))
+
+ def _on_text_changed(self, widget):
+ self.news = True
+
+ def figure_time(self, str_time):
+ if not str_time:
+ return self.time
+
+ # strip everything non-numeric and consider hours to be first number
+ # and minutes - second number
+ numbers = re.split("\D", str_time)
+ numbers = filter(lambda x: x!="", numbers)
+
+ hours, minutes = None, None
+
+ if len(numbers) == 1 and len(numbers[0]) == 4:
+ hours, minutes = int(numbers[0][:2]), int(numbers[0][2:])
+ else:
+ if len(numbers) >= 1:
+ hours = int(numbers[0])
+ if len(numbers) >= 2:
+ minutes = int(numbers[1])
+
+ if (hours is None or minutes is None) or hours > 24 or minutes > 60:
+ return self.time #no can do
+
+ return dt.datetime.now().replace(hour = hours, minute = minutes,
+ second = 0, microsecond = 0)
+
+
+ def _select_time(self, time_text):
+ #convert forth and back so we have text formated as we want
+ time = self.figure_time(time_text)
+ time_text = self._format_time(time)
+
+ self.set_text(time_text)
+ self.set_position(len(time_text))
+ self.popup.hide()
+ if self.news:
+ self.emit("time-entered")
+ self.news = False
+
+ def get_time(self):
+ self.time = self.figure_time(self.get_text())
+ self.set_text(self._format_time(self.time))
+ return self.time
+
+ def _format_time(self, time):
+ if time is None:
+ return None
+
+ #return time.strftime("%I:%M%p").lstrip("0").lower()
+ return time.strftime("%H:%M").lower()
+
+
+ def _on_focus_in_event(self, entry, event):
+ self.show_popup()
+
+ def _on_button_press_event(self, button, event):
+ self.show_popup()
+
+ def _on_focus_out_event(self, event, something):
+ self.popup.hide()
+ if self.news:
+ self.emit("time-entered")
+ self.news = False
+
+
+ def show_popup(self):
+ focus_time = self.figure_time(self.get_text())
+
+ hours = gtk.ListStore(gobject.TYPE_STRING)
+
+ # populate times
+ i_time = self.start_time or dt.datetime(1900, 1, 1, 0, 0)
+
+ if focus_time and focus_time < i_time:
+ focus_time += dt.timedelta(days = 1)
+
+ if self.start_time:
+ end_time = i_time + dt.timedelta(hours = 12)
+ i_time += dt.timedelta(minutes = 15)
+ else:
+ end_time = i_time + dt.timedelta(hours = 24)
+
+ i, focus_row = 0, None
+
+ while i_time < end_time:
+ row_text = self._format_time(i_time)
+ if self.start_time:
+ delta = (i_time - self.start_time).seconds / 60
+ delta_text = format_duration(delta)
+
+ row_text += " (%s)" % delta_text
+
+ hours.append([row_text])
+
+ if focus_time and i_time <= focus_time <= i_time + \
+ dt.timedelta(minutes = 30):
+ focus_row = i
+
+ if self.start_time:
+ i_time += dt.timedelta(minutes = 15)
+ else:
+ i_time += dt.timedelta(minutes = 30)
+
+ i += 1
+
+ self.time_tree.set_model(hours)
+
+ #focus on row
+ if focus_row != None:
+ self.time_tree.set_cursor(focus_row)
+ self.time_tree.scroll_to_cell(focus_row, use_align = True, row_align = 0.4)
+
+ #move popup under the widget
+ alloc = self.get_allocation()
+ w = alloc.width
+ if self.start_time:
+ w = w * 2
+ self.time_tree.set_size_request(w, alloc.height * 5)
+
+ window = self.get_parent_window()
+ x, y= window.get_origin()
+
+ self.popup.move(x + alloc.x,y + alloc.y + alloc.height)
+ self.popup.show_all()
+
+
+ def _on_time_tree_button_press_event(self, tree, event):
+ model, iter = tree.get_selection().get_selected()
+ time = model.get_value(iter, 0)
+ self._select_time(time)
+
+
+ def _on_key_press_event(self, entry, event):
+ cursor = self.time_tree.get_cursor()
+
+ if not cursor or not cursor[0]:
+ return
+
+ i = cursor[0][0]
+
+ if event.keyval == gtk.keysyms.Up:
+ i-=1
+ elif event.keyval == gtk.keysyms.Down:
+ i+=1
+ elif (event.keyval == gtk.keysyms.Return or
+ event.keyval == gtk.keysyms.KP_Enter):
+
+ if self.popup.get_property("visible"):
+ self._select_time(self.time_tree.get_model()[i][0])
+ else:
+ self._select_time(entry.get_text())
+ elif (event.keyval == gtk.keysyms.Escape):
+ self.popup.hide()
+ else:
+ #any kind of other input
+ self.popup.hide()
+ return False
+
+ # keep it in the sane borders
+ i = min(max(i, 0), len(self.time_tree.get_model()) - 1)
+
+ self.time_tree.set_cursor(i)
+ self.time_tree.scroll_to_cell(i, use_align = True, row_align = 0.4)
+ return True
+
+
+
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]