[hamster-applet] hooray for tags autocomplete
- From: Toms Baugis <tbaugis src gnome org>
- To: svn-commits-list gnome org
- Cc:
- Subject: [hamster-applet] hooray for tags autocomplete
- Date: Sun, 22 Nov 2009 18:42:09 +0000 (UTC)
commit 11b85b293396fe0000d8d9bd2d6c0ddc90cd04c8
Author: Toms Bauģis <toms baugis gmail com>
Date: Sun Nov 22 18:41:21 2009 +0000
hooray for tags autocomplete
data/hamster.ui | 17 +---
hamster/standalone.py | 251 +++++++++++++++++++++++++++++++++++++++++++------
2 files changed, 226 insertions(+), 42 deletions(-)
---
diff --git a/data/hamster.ui b/data/hamster.ui
index bb341c1..abff98d 100644
--- a/data/hamster.ui
+++ b/data/hamster.ui
@@ -139,15 +139,11 @@
<property name="visible">True</property>
<property name="spacing">4</property>
<child>
- <object class="GtkAlignment" id="alignment1">
+ <object class="GtkAlignment" id="new_name_box">
<property name="width_request">300</property>
<property name="visible">True</property>
<child>
- <object class="GtkEntry" id="new_name">
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="invisible_char">•</property>
- </object>
+ <placeholder/>
</child>
</object>
<packing>
@@ -156,14 +152,11 @@
</packing>
</child>
<child>
- <object class="GtkAlignment" id="alignment4">
+ <object class="GtkAlignment" id="new_tags_box">
+ <property name="width_request">300</property>
<property name="visible">True</property>
<child>
- <object class="GtkEntry" id="new_description">
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="invisible_char">•</property>
- </object>
+ <placeholder/>
</child>
</object>
<packing>
diff --git a/hamster/standalone.py b/hamster/standalone.py
index e699517..1156b98 100755
--- a/hamster/standalone.py
+++ b/hamster/standalone.py
@@ -36,13 +36,191 @@ import pango, cairo
from math import pi
-class TagEntry(graphics.Area):
+class TagsEntry(gtk.Entry):
+ __gsignals__ = {
+ 'tags-selected': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),
+ }
+
+ def __init__(self):
+ gtk.Entry.__init__(self)
+ self.tags = None
+ self.filter = None # currently applied filter string
+ self.filter_tags = None #filtered tags
+
+ self.popup = gtk.Window(type = gtk.WINDOW_POPUP)
+ self.scroll_box = gtk.ScrolledWindow()
+ self.scroll_box.set_shadow_type(gtk.SHADOW_IN)
+ self.scroll_box.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
+ viewport = gtk.Viewport()
+ viewport.set_shadow_type(gtk.SHADOW_NONE)
+
+ self.tag_box = TagBox()
+ self.tag_box.connect("tag-selected", self.on_tag_selected)
+ self.tag_box.connect("tag-unselected", self.on_tag_unselected)
+
+
+ viewport.add(self.tag_box)
+ self.scroll_box.add(viewport)
+ self.popup.add(self.scroll_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-out-event", self._on_focus_out_event)
+ self.show()
+ self.populate_suggestions()
+
+ def get_tags(self):
+ # splits the string by comma and filters out blanks
+ return [tag.strip() for tag in self.get_text().split(",") if tag.strip()]
+
+ def on_tag_selected(self, tag_box, tag):
+ tags = self.get_tags()
+ tags.append(tag)
+
+ self.set_text(", ".join(tags))
+ self.set_position(len(self.get_text()))
+
+ def on_tag_unselected(self, tag_box, tag):
+ tags = self.get_tags()
+ print tags
+ while tag in tags: #it could be that dear user is mocking us and entering same tag over and over again
+ tags.remove(tag)
+
+ print tags
+
+ self.set_text(", ".join(tags))
+ self.set_position(len(self.get_text()))
+
+
+ def hide_popup(self):
+ self.popup.hide()
+
+ def show_popup(self):
+ if not self.filter_tags:
+ self.popup.hide()
+ return
+
+ 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
+
+
+ self.popup.show_all()
+ self.scroll_box.set_size_request(w, self.tag_box.count_height())
+ self.popup.resize(w, self.tag_box.count_height())
+
+
+ def complete_inline(self):
+ return
+
+ def refresh_activities(self):
+ # scratch activities and categories so that they get repopulated on demand
+ self.activities = None
+ self.categories = None
+
+ def populate_suggestions(self):
+ cursor_tag = self.get_cursor_tag()
+
+ self.tags = ["peh", "poh", "and so on", "etc", "true magic",
+ "and so we go", "on and on", "until you drop", "somewhere",
+ "and forget", "what we", "were", "actually doing"]
+
+
+ self.filter = cursor_tag
+
+ entered_tags = self.get_tags()
+ self.tag_box.selected_tags = entered_tags
+
+ self.filter_tags = [tag for tag in self.tags if tag.startswith(self.filter)]
+
+ self.tag_box.draw(self.filter_tags)
+
+
+
+ def _on_focus_out_event(self, widget, event):
+ self.hide_popup()
+
+ def _on_button_press_event(self, button, event):
+ self.populate_suggestions()
+ self.show_popup()
+
+ 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"):
+ self.hide_popup()
+ return True
+ else:
+ self.emit("tags-selected")
+ return False
+ elif (event.keyval == gtk.keysyms.Escape):
+ if self.popup.get_property("visible"):
+ self.hide_popup()
+ return True
+ else:
+ return False
+ else:
+ self.populate_suggestions()
+ self.show_popup()
+
+ if event.keyval not in (gtk.keysyms.Delete, gtk.keysyms.BackSpace):
+ self.complete_inline()
+
+
+ def get_cursor_tag(self):
+ #returns the tag on which the cursor is on right now
+ if self.get_selection_bounds():
+ cursor = self.get_selection_bounds()[0]
+ else:
+ cursor = self.get_position()
+
+ text = self.get_text()
+
+ return text[text.rfind(",", 0, cursor)+1:cursor].strip()
+
+
+ def replace_tag(self, old_tag, new_tag):
+ tags = self.get_tags()
+ if old_tag in tags:
+ tags[tags.index(old_tag)] = new_tag
+
+ if self.get_selection_bounds():
+ cursor = self.get_selection_bounds()[0]
+ else:
+ cursor = self.get_position()
+
+ self.set_text(", ".join(tags))
+ self.set_position(cursor + len(new_tag)-len(old_tag)) # put the cursor back
+
+ def _on_key_press_event(self, entry, event):
+
+ if event.keyval == gtk.keysyms.Tab:
+ if self.popup.get_property("visible"):
+ #we have to replace
+ self.replace_tag(self.get_cursor_tag(), self.filter_tags[0])
+ return True
+ else:
+ return False
+
+ return False
+
+
+class TagBox(graphics.Area):
+ __gsignals__ = {
+ 'tag-selected': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (str,)),
+ 'tag-unselected': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (str,)),
+ }
+
def __init__(self):
graphics.Area.__init__(self)
self.font_size = 10
self.connect("mouse-over", self.on_tag_hover)
self.connect("button-release", self.on_tag_click)
self.hover_tag = None
+ self.tags = []
self.selected_tags = []
def on_tag_hover(self, widget, regions):
@@ -57,13 +235,16 @@ class TagEntry(graphics.Area):
tag = regions[0]
if tag in self.selected_tags:
self.selected_tags.remove(tag)
+ self.emit("tag-unselected", tag)
else:
self.selected_tags.append(tag)
+ self.emit("tag-selected", tag)
self.redraw_canvas()
- def draw(self):
+ def draw(self, tags):
"""Draw chart with given data"""
+ self.tags = tags
self.show()
self.redraw_canvas()
@@ -72,8 +253,7 @@ class TagEntry(graphics.Area):
w = text_w + 18 # padding (we have some diagonals to draw)
h = text_h + 4
return w, h
-
-
+
def draw_tag(self, label, x, y, color):
self.context.set_line_width(1)
@@ -109,16 +289,34 @@ class TagEntry(graphics.Area):
self.context.show_layout(self.layout)
+
+ def count_height(self):
+ if not self.width:
+ return 120
+
+ # tells you how much height in this width is needed to show all tags
+ if not self.tags:
+ return None
+
+ cur_x, cur_y = 4, 4
+ for tag in self.tags:
+ w, h = self.tag_size(tag)
+ if cur_x + w >= self.width - 5: #if we don't fit, we wrap
+ cur_x = 5
+ cur_y += h + 6
+
+ cur_x += w + 8 #some padding too, please
+
+ cur_y = cur_y + h + 6
+
+ return cur_y
+
def _render(self):
self.fill_area(0, 0, self.width, self.height, (255,255,255))
-
- tags = ["peh", "poh", "and so on", "etc", "true magic",
- "and so we go", "on and on", "until you drop", "somewhere",
- "and forget", "what we", "were", "actually doing"]
-
- cur_x, cur_y = 5, 5
- for tag in tags:
+
+ cur_x, cur_y = 4, 4
+ for tag in self.tags:
w, h = self.tag_size(tag)
if cur_x + w >= self.width - 5: #if we don't fit, we wrap
cur_x = 5
@@ -136,16 +334,23 @@ class TagEntry(graphics.Area):
cur_x += w + 8 #some padding too, please
-
-
-
class MainWindow(object):
def __init__(self):
self._gui = stuff.load_ui_file("hamster.ui")
self.window = self._gui.get_object('main-window')
- self.style_widgets()
+ #TODO - replace with the tree background color (can't get it atm!)
+ self.get_widget("todays_activities_ebox").modify_bg(gtk.STATE_NORMAL,
+ gtk.gdk.Color(65536.0,65536.0,65536.0))
+
+ self.new_name = widgets.ActivityEntry()
+ widgets.add_hint(self.new_name, _("Time and Name"))
+ self.get_widget("new_name_box").add(self.new_name)
+
+ self.new_tags = TagsEntry()
+ widgets.add_hint(self.new_tags, _("Tags or Description"))
+ self.get_widget("new_tags_box").add(self.new_tags)
self.get_widget("tabs").set_current_page(0)
@@ -154,7 +359,7 @@ class MainWindow(object):
gtk.link_button_set_uri_hook(self.magic)
- self.tag_box = TagEntry()
+ self.tag_box = TagBox()
self.get_widget("tag_box").add(self.tag_box)
self._gui.connect_signals(self)
@@ -163,20 +368,6 @@ class MainWindow(object):
def magic(self, button, uri):
print uri, button
- def style_widgets(self):
- #TODO - replace with the tree background color (can't get it atm!)
- self.get_widget("todays_activities_ebox").modify_bg(gtk.STATE_NORMAL,
- gtk.gdk.Color(65536.0,65536.0,65536.0))
-
- self.new_name = widgets.ActivityEntry()
- widgets.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"))
-
def set_last_activity(self):
activity = runtime.storage.get_last_activity()
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]