3
# This application is released under the GNU General Public License
4
# v3 (or, at your option, any later version). You can find the full
5
# text of the license under http://www.gnu.org/licenses/gpl.txt.
6
# By using, editing and/or distributing this software you agree to
7
# the terms and conditions of this license.
8
# Thank you for using free software!
10
# TomboyScreenlet (c) Whise 2007
13
from screenlets import utils
14
from screenlets.options import StringOption , BoolOption , IntOption , FontOption, ColorOption
15
from screenlets import DefaultMenuItem
20
from xml.dom.minidom import parse
21
from xml.parsers.expat import ExpatError
26
class TomboyScreenlet (screenlets.Screenlet):
27
"""Displays Tomboy Notes"""
29
# default meta-info for Screenlets (should be removed and put into metainfo)
30
__name__ = 'TomboyScreenlet'
32
__author__ = 'Helder Fraga aka Whise'
33
__desc__ = __doc__ # set description to docstring of class
35
# editable options (options that are editable through the UI)
38
color_odd = (0, 0, 0, 0.55)
39
color_even = (0, 0, 0, 0.65)
40
color_title = (0.0, 0.0, 0.0, 1)
41
color_text = (1,1,1,1)
42
color_back = (1.0, 1.0, 1.0, 0.65)
43
color_hover = (0, 0, 1.0, 0.65)
45
font_title = "FreeSans"
58
if os.environ.has_key("TOMBOY_PATH"):
59
note_path = os.environ["TOMBOY_PATH"]
61
note_path = "~/.tomboy"
62
note_path = os.path.expanduser(note_path)
65
def __init__ (self, **keyword_args):
66
#call super (width/height MUST match the size of graphics in the theme)
67
screenlets.Screenlet.__init__(self, width=200, height=200,
68
uses_theme=True,ask_on_option_override = False, **keyword_args)
70
self.theme_name = "yellow"
72
self.add_options_group('Options', 'Options')
73
# add editable option to the group
75
self.add_option(FontOption('Options','font_title',
76
self.font_title, 'Title Font',
79
self.add_option(ColorOption('Options','color_title',
80
self.color_title, 'Title Color',
83
self.add_option(ColorOption('Options','color_back',
84
self.color_back, 'Title Background Color',
87
self.add_option(FontOption('Options','font',
88
self.font, 'Text Font',
91
self.add_option(ColorOption('Options','color_text',
92
self.color_text, 'Text Color',
95
self.add_option(ColorOption('Options','color_even',
96
self.color_even, 'Even Color',
99
self.add_option(ColorOption('Options','color_odd',
100
self.color_odd, 'Odd Color',
103
self.add_option(ColorOption('Options','color_hover',
104
self.color_hover, 'Hover Color',
107
self.add_option(BoolOption('Options','show_shadow',
108
self.show_shadow, 'Show Shadow', '',))
110
self.add_option(BoolOption('Options','expanded',
111
self.expanded, 'Expanded', '',hidden=True))
113
# ADD a 1 second (1000) TIMER
115
self.ask_on_option_override = False
117
#fstab = self.readFile('/etc/fstab')
118
#mounts = self.readFile('/proc/mounts')
125
self.note_path_monitor = utils.FileMonitor(self.note_path)
126
self.note_path_monitor.connect("event", self._file_event)
127
self.note_path_monitor.open()
129
# Load notes in an idle handler
130
gobject.idle_add(self._idle_load_notes().next, priority=gobject.PRIORITY_LOW)
133
def get_note(self,path):
135
note_doc = parse(path)
136
except (IOError, ExpatError), err:
137
print " !!! Error parsing note '%s': %s" % (path, err)
141
title_node = note_doc.getElementsByTagName("title")[0]
142
self.title = title_node.childNodes[0].data
143
except (ValueError, IndexError, AttributeError):
147
# Parse the ISO timestamp format .NET's XmlConvert class uses:
148
# yyyy-MM-ddTHH:mm:ss.fffffffzzzzzz, where f* is a 7-digit partial
149
# second, and z* is the timezone offset from UTC in the form -08:00.
150
changed_node = note_doc.getElementsByTagName("last-change-date")[0]
151
changed_str = changed_node.childNodes[0].data
152
changed_str = re.sub("\.[0-9]*", "", changed_str) # W3Date chokes on partial seconds
153
#self.timestamp = W3CDate.W3CDate(changed_str).getSeconds()
154
except (ValueError, IndexError, AttributeError):
158
content_node = note_doc.getElementsByTagName("note-content")[0]
159
self.content_text = self._get_text_from_node(content_node).lower()
160
except (ValueError, IndexError, AttributeError):
167
def _get_text_from_node(self, node):
168
if node.nodeType == node.TEXT_NODE:
171
return "".join([self._get_text_from_node(x) for x in node.childNodes])
173
def _idle_load_notes(self):
177
for filename in os.listdir(self.note_path):
178
if filename.endswith(".note"):
179
notepath = os.path.join(self.note_path, filename)
180
notes[filename] = self.get_note(notepath)
182
except (OSError, IOError), err:
183
print " !!! Error loading Tomboy notes:", err
189
def _file_event(self, monitor, info_uri, ev):
190
filename = os.path.basename(info_uri)
192
if ev == gnomevfs.MONITOR_EVENT_CREATED:
193
notepath = os.path.join(self.note_path, filename)
194
self.notes[filename] = self.get_note(notepath)
195
if self.height != 20 + (len(self.notes)*20) +20:
196
self.height = 20 + (len(self.notes)*20) +20
199
elif self.notes.has_key(filename):
200
if ev == gnomevfs.MONITOR_EVENT_DELETED:
201
del self.notes[filename]
202
if self.height != 20 + (len(self.notes)*20) +20:
203
self.height = 20 + (len(self.notes)*20) +20
206
if self.height != 20 + (len(self.notes)*20) +20:
207
self.height = 20 + (len(self.notes)*20) +20
211
def menuitem_callback(self, widget, id):
212
screenlets.Screenlet.menuitem_callback(self, widget, id)
214
os.system("tomboy --new-note")
216
def get_items_uncached(self):
217
return [self.new_note_item] + self.notes.values()
222
# Avoid ItemSource's caching
223
return self.get_items_uncached()
225
def __setattr__(self, name, value):
226
# call Screenlet.__setattr__ in baseclass (ESSENTIAL!!!!)
227
screenlets.Screenlet.__setattr__(self, name, value)
229
if self.height != 20 + (len(self.notes)*20) +20:
230
self.height = 20 + (len(self.notes)*20) +20
234
# ONLY FOR TESTING!!!!!!!!!
235
def init_options_from_metadata (self):
236
"""Try to load metadata-file with options. The file has to be named
237
like the Screenlet, with the extension ".xml" and needs to be placed
238
in the Screenlet's personal directory.
239
NOTE: This function always uses the metadata-file relative to the
240
Screenlet's location, not the ones in SCREENLETS_PATH!!!"""
242
p = __file__.rfind('/')
243
mypath = __file__[:p]
245
self.add_options_from_file( mypath + '/' + \
246
self.__class__.__name__ + '.xml')
252
def on_after_set_atribute(self,name, value):
253
"""Called after setting screenlet atributes"""
257
def on_before_set_atribute(self,name, value):
258
"""Called before setting screenlet atributes"""
263
def on_create_drag_icon (self):
264
"""Called when the screenlet's drag-icon is created. You can supply
265
your own icon and mask by returning them as a 2-tuple."""
268
def on_composite_changed(self):
269
"""Called when composite state has changed"""
272
def on_drag_begin (self, drag_context):
273
"""Called when the Screenlet gets dragged."""
276
def on_drag_enter (self, drag_context, x, y, timestamp):
277
"""Called when something gets dragged into the Screenlets area."""
280
def on_drag_leave (self, drag_context, timestamp):
281
"""Called when something gets dragged out of the Screenlets area."""
284
def on_drop (self, x, y, sel_data, timestamp):
285
"""Called when a selection is dropped on this Screenlet."""
288
def on_focus (self, event):
289
"""Called when the Screenlet's window receives focus."""
293
"""Called when the Screenlet gets hidden."""
297
"""Called when the Screenlet's options have been applied and the
298
screenlet finished its initialization. If you want to have your
299
Screenlet do things on startup you should use this handler."""
300
self.add_menuitem("new", "Make new note")
301
# add default menu items
302
self.add_default_menuitems()
303
if self.notes != None:
304
if self.height != 20 + (len(self.notes)*20) +20:
305
self.height = 20 + (len(self.notes)*20) +20
307
#print utils.LoadBookmarks()
309
def on_key_down(self, keycode, keyvalue, event):
310
"""Called when a keypress-event occured in Screenlet's window."""
311
#key = gtk.gdk.keyval_name(event.keyval)
315
def on_load_theme (self):
316
"""Called when the theme is reloaded (after loading, before redraw)."""
319
def on_menuitem_select (self, id):
320
"""Called when a menuitem is selected."""
324
def on_mouse_down (self, event):
325
"""Called when a buttonpress-event occured in Screenlet's window.
326
Returning True causes the event to be not further propagated."""
328
x = event.x / self.scale
329
y = event.y / self.scale
330
if event.button == 1:
331
if event.type == gtk.gdk._2BUTTON_PRESS and y < 30:
332
self.expanded = not self.expanded
334
if y > (30) and self.notes != None:
335
click = int((y -10 )/ (20)) -1
337
for note in self.notes:
340
os.system("tomboy --open-note %s/%s " % (self.note_path,note))
346
def on_mouse_enter (self, event):
347
"""Called when the mouse enters the Screenlet's window."""
351
def on_mouse_leave (self, event):
352
"""Called when the mouse leaves the Screenlet's window."""
356
def on_mouse_move(self, event):
357
"""Called when the mouse moves in the Screenlet's window."""
358
x = event.x / self.scale
359
y = event.y / self.scale
361
self.__dict__['mousesel'] = int((y -10 )/ (20)) -1
362
if self.selected != self.mousesel or y > 20 + (len(self.notes)*20) +20:
365
def on_mouse_up (self, event):
366
"""Called when a buttonrelease-event occured in Screenlet's window.
367
Returning True causes the event to be not further propagated."""
371
"""Callback for handling destroy-event. Perform your cleanup here!"""
375
def on_realize (self):
376
""""Callback for handling the realize-event."""
379
"""Called when Screenlet.scale is changed."""
382
def on_scroll_up (self):
383
"""Called when mousewheel is scrolled up (button4)."""
386
def on_scroll_down (self):
387
"""Called when mousewheel is scrolled down (button5)."""
391
"""Called when the Screenlet gets shown after being hidden."""
394
def on_switch_widget_state (self, state):
395
"""Called when the Screenlet enters/leaves "Widget"-state."""
398
def on_unfocus (self, event):
399
"""Called when the Screenlet's window loses focus."""
402
def on_draw (self, ctx):
403
"""In here we draw"""
404
ctx.scale(self.scale, self.scale)
408
if self.show_shadow:self.draw_shadow(ctx, 0, 0, self.width-12, self.height-5,6,[0,0,0,0.3])
410
if self.show_shadow:self.draw_shadow(ctx, 0, 0, self.width-12,40-5,6,[0,0,0,0.3])
412
ctx.set_source_rgba(self.color_back[0],self.color_back[1],self.color_back[2],self.color_back[3])
413
self.draw_rounded_rectangle(ctx,0,y,5,self.width-20,20,round_bottom_right= False,round_bottom_left= False)
414
ctx.set_source_rgba(self.color_title[0],self.color_title[1],self.color_title[2],self.color_title[3])
415
self.draw_text(ctx, 'Notes',14,y+2,self.font_title.split(' ')[0],10,self.width-20,pango.ALIGN_LEFT)
418
self.draw_triangle(ctx,-15,-(y+17),10,10)
423
self.draw_triangle(ctx,3,-(y+15),10,10)
426
if self.expanded and self.notes != None:
430
for app in self.notes:
432
ctx.set_source_rgba(self.color_even[0],self.color_even[1],self.color_even[2],self.color_even[3])
433
#is_mounted = 'Mounted'
435
ctx.set_source_rgba(self.color_odd[0],self.color_odd[1],self.color_odd[2],self.color_odd[3])
436
if self.check_for_icon(app):
439
ico = 'stock_search-and-replace'
442
if self.mousesel == x and self.mouse_is_over:
443
ctx.set_source_rgba(self.color_hover[0],self.color_hover[1],self.color_hover[2],self.color_hover[3])
444
self.__dict__['selected'] = x
446
if y +60== self.height:
447
self.draw_rounded_rectangle(ctx,0,y,5,self.width-20,20,round_top_right= False,round_top_left= False)
449
self.draw_rectangle(ctx,0,y,self.width -20,20)
451
ctx.set_source_rgba(self.color_text[0],self.color_text[1],self.color_text[2],self.color_text[3])
453
self.draw_text(ctx,self.notes[app],5,y+2,self.font.split(' ')[0],10,self.width-20,pango.ALIGN_LEFT)
454
a = self.get_screenlet_dir() + '/themes/' + self.theme_name + '/icon.png'
455
self.draw_scaled_image(ctx,self.width-40,y+2,a,16,16)
462
def on_draw_shape (self, ctx):
465
# If the program is run directly or passed as an argument to the python
466
# interpreter then create a Screenlet instance and show it
467
if __name__ == "__main__":
469
import screenlets.session
470
screenlets.session.create_session(TomboyScreenlet)