~ubuntu-branches/ubuntu/vivid/hamster-applet/vivid

« back to all changes in this revision

Viewing changes to hamster/edit_activity.py

  • Committer: Bazaar Package Importer
  • Author(s): Emilio Pozuelo Monfort
  • Date: 2009-10-22 22:01:54 UTC
  • mfrom: (1.2.4 upstream) (5.2.2 sid)
  • mto: This revision was merged to the branch mainline in revision 18.
  • Revision ID: james.westby@ubuntu.com-20091022220154-do4zoetlf35l36pe
Tags: 2.28.1-1
New upstream release.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# -*- coding: utf-8 -*-
 
2
 
 
3
# Copyright (C) 2007-2009 Toms Bauģis <toms.baugis at gmail.com>
 
4
 
 
5
# This file is part of Project Hamster.
 
6
 
 
7
# Project Hamster is free software: you can redistribute it and/or modify
 
8
# it under the terms of the GNU General Public License as published by
 
9
# the Free Software Foundation, either version 3 of the License, or
 
10
# (at your option) any later version.
 
11
 
 
12
# Project Hamster is distributed in the hope that it will be useful,
 
13
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
15
# GNU General Public License for more details.
 
16
 
 
17
# You should have received a copy of the GNU General Public License
 
18
# along with Project Hamster.  If not, see <http://www.gnu.org/licenses/>.
 
19
 
 
20
 
 
21
import pygtk
 
22
pygtk.require('2.0')
 
23
 
 
24
import os
 
25
import gtk
 
26
import gobject
 
27
 
 
28
import stuff
 
29
import graphics, widgets
 
30
import eds
 
31
from configuration import runtime
 
32
 
 
33
import time
 
34
import datetime as dt
 
35
import colorsys
 
36
 
 
37
import cairo, pango
 
38
 
 
39
""" TODO:
 
40
     * hook into notifications and refresh our days if some evil neighbour edit
 
41
       fact window has dared to edit facts
 
42
"""
 
43
class Dayline(graphics.Area):
 
44
    def __init__(self):
 
45
        graphics.Area.__init__(self)
 
46
 
 
47
        self.set_events(gtk.gdk.EXPOSURE_MASK
 
48
                                 | gtk.gdk.LEAVE_NOTIFY_MASK
 
49
                                 | gtk.gdk.BUTTON_PRESS_MASK
 
50
                                 | gtk.gdk.BUTTON_RELEASE_MASK
 
51
                                 | gtk.gdk.POINTER_MOTION_MASK
 
52
                                 | gtk.gdk.POINTER_MOTION_HINT_MASK)
 
53
        self.connect("button_release_event", self.on_button_release)
 
54
        self.connect("motion_notify_event", self.draw_cursor)
 
55
        self.highlight_start, self.highlight_end = None, None
 
56
        self.drag_start = None
 
57
        self.move_type = ""
 
58
        self.on_time_changed = None #override this with your func to get notified when user changes date
 
59
        self.on_more_data = None #supplement with more data func that accepts single date
 
60
        self.in_progress = False
 
61
 
 
62
        self.range_start = None
 
63
        self.in_motion = False
 
64
        self.days = []
 
65
        
 
66
 
 
67
    def draw(self, day_facts, highlight = None):
 
68
        """Draw chart with given data"""
 
69
        self.facts = day_facts
 
70
        if self.facts:
 
71
            self.days.append(self.facts[0]["start_time"].date())
 
72
        
 
73
        start_time = highlight[0] - dt.timedelta(minutes = highlight[0].minute) - dt.timedelta(hours = 10)
 
74
        
 
75
        if self.range_start:
 
76
            self.range_start.target(start_time)
 
77
            self.scroll_to_range_start()
 
78
        else:
 
79
            self.range_start = graphics.Integrator(start_time, damping = 0.35, attraction = 0.5)
 
80
 
 
81
        self.highlight = highlight
 
82
        
 
83
        self.show()
 
84
        
 
85
        self.redraw_canvas()
 
86
 
 
87
 
 
88
    def on_button_release(self, area, event):
 
89
        if not self.drag_start:
 
90
            return
 
91
        
 
92
        self.drag_start, self.move_type = None, None
 
93
 
 
94
        if event.state & gtk.gdk.BUTTON1_MASK:
 
95
            self.__call_parent_time_changed()
 
96
 
 
97
    def set_in_progress(self, in_progress):
 
98
        self.in_progress = in_progress
 
99
 
 
100
    def __call_parent_time_changed(self):
 
101
        #now calculate back from pixels into minutes
 
102
        start_time = self.highlight[0]
 
103
        end_time = self.highlight[1]
 
104
 
 
105
        if self.on_time_changed:
 
106
            self.on_time_changed(start_time, end_time)
 
107
    
 
108
    def get_time(self, pixels):
 
109
        minutes = self.get_value_at_pos(x = pixels)
 
110
        return self.range_start.value + dt.timedelta(minutes = minutes) 
 
111
    
 
112
    def scroll_to_range_start(self):
 
113
        if not self.in_motion:
 
114
            self.in_motion = True
 
115
            gobject.timeout_add(1000 / 30, self.animate_scale)
 
116
        
 
117
        
 
118
    def animate_scale(self):
 
119
        moving = self.range_start.update() > 5
 
120
        
 
121
        
 
122
        # check if maybe we are approaching day boundaries and should ask for
 
123
        # more data!
 
124
        if self.on_more_data:
 
125
            now = self.range_start.value
 
126
            date_plus = (now + dt.timedelta(hours = 12 + 2*4 + 1)).date()
 
127
            date_minus = (now - dt.timedelta(hours=1)).date()
 
128
 
 
129
            if date_minus != now.date() and date_minus not in self.days:
 
130
                self.facts += self.on_more_data(date_minus)
 
131
                self.days.append(date_minus)
 
132
            elif date_plus != now.date() and date_plus not in self.days:
 
133
                self.facts += self.on_more_data(date_plus)
 
134
                self.days.append(date_plus)
 
135
        
 
136
        
 
137
        self.redraw_canvas()
 
138
        if moving:
 
139
            return True
 
140
        else:
 
141
            self.in_motion = False
 
142
            return False
 
143
 
 
144
 
 
145
        
 
146
    def draw_cursor(self, area, event):
 
147
        if event.is_hint:
 
148
            x, y, state = event.window.get_pointer()
 
149
        else:
 
150
            x = event.x
 
151
            y = event.y
 
152
            state = event.state
 
153
 
 
154
        mouse_down = state & gtk.gdk.BUTTON1_MASK
 
155
            
 
156
        #print x, self.highlight_start, self.highlight_end
 
157
        if self.highlight_start != None:
 
158
            start_drag = 10 > (self.highlight_start - x) > -1
 
159
 
 
160
            end_drag = 10 > (x - self.highlight_end) > -1
 
161
 
 
162
            if start_drag and end_drag:
 
163
                start_drag = abs(x - self.highlight_start) < abs(x - self.highlight_end)
 
164
 
 
165
            in_between = self.highlight_start <= x <= self.highlight_end
 
166
            scale = True
 
167
 
 
168
            if self.in_progress:
 
169
                end_drag = False
 
170
                in_between = False
 
171
 
 
172
            if mouse_down and not self.drag_start:
 
173
                self.drag_start = x
 
174
                if start_drag:
 
175
                    self.move_type = "start"
 
176
                elif end_drag:
 
177
                    self.move_type = "end"
 
178
                elif in_between:
 
179
                    self.move_type = "move"
 
180
                    self.drag_start = x - self.highlight_start
 
181
                elif scale:
 
182
                    self.move_type = "scale_drag"
 
183
                    self.drag_start_time = self.range_start.value
 
184
 
 
185
            
 
186
            if mouse_down and self.drag_start:
 
187
                start, end = 0, 0
 
188
                if self.move_type and self.move_type != "scale_drag":
 
189
                    if self.move_type == "start":
 
190
                        if 0 <= x <= self.width:
 
191
                            start = x
 
192
                            end = self.highlight_end
 
193
                    elif self.move_type == "end":
 
194
                        if 0 <= x <= self.width:
 
195
                            start = self.highlight_start
 
196
                            end = x
 
197
                    elif self.move_type == "move":
 
198
                        width = self.highlight_end - self.highlight_start
 
199
                        start = x - self.drag_start
 
200
                        start = max(0, min(start, self.width))
 
201
                        
 
202
                        end = start + width
 
203
                        if end > self.width:
 
204
                            end = self.width
 
205
                            start = end - width
 
206
    
 
207
                    if end - start > 1:
 
208
                        self.highlight = (self.get_time(start), self.get_time(end))
 
209
                        self.redraw_canvas()
 
210
 
 
211
                    self.__call_parent_time_changed()
 
212
                else:
 
213
                    self.range_start.target(self.drag_start_time +
 
214
                                            dt.timedelta(minutes = self.get_value_at_pos(x = self.drag_start) - self.get_value_at_pos(x = x)))
 
215
                    self.scroll_to_range_start()
 
216
 
 
217
 
 
218
 
 
219
            if start_drag:
 
220
                area.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.LEFT_SIDE))
 
221
            elif end_drag:
 
222
                area.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.RIGHT_SIDE))
 
223
            elif in_between:
 
224
                area.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.FLEUR))
 
225
            else:
 
226
                area.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.ARROW))
 
227
                
 
228
        
 
229
    def _minutes_from_start(self, date):
 
230
            delta = (date - self.range_start.value)
 
231
            return delta.days * 24 * 60 + delta.seconds / 60
 
232
            
 
233
    def _render(self):
 
234
        context = self.context
 
235
        #TODO - use system colors and fonts
 
236
 
 
237
        context.set_line_width(1)
 
238
 
 
239
        #we will buffer 4 hours to both sides so partial labels also appear
 
240
        range_end = self.range_start.value + dt.timedelta(hours = 12 + 2 * 4)        
 
241
        self.graph_x = -self.width / 3 #so x moves one third out of screen
 
242
        self.set_value_range(x_min = 0, x_max = 12 * 60)
 
243
 
 
244
        minutes = self._minutes_from_start(range_end)
 
245
 
 
246
 
 
247
 
 
248
        graph_y = 4
 
249
        graph_height = self.height - 10
 
250
        graph_y2 = graph_y + graph_height
 
251
 
 
252
        
 
253
        # graph area
 
254
        self.fill_area(0, graph_y - 1, self.width, graph_height, (1,1,1))
 
255
    
 
256
        #bars
 
257
        for fact in self.facts:
 
258
            start_minutes = self._minutes_from_start(fact["start_time"])
 
259
            
 
260
            if fact["end_time"]:
 
261
                end_minutes = self._minutes_from_start(fact["end_time"])
 
262
            else:
 
263
                if fact["start_time"].date() > dt.date.today() - dt.timedelta(days=1):
 
264
                    end_minutes = self._minutes_from_start(dt.datetime.now())
 
265
                else:
 
266
                    end_minutes = start_minutes
 
267
            
 
268
            if self.get_pixel(end_minutes) > 0 and \
 
269
                self.get_pixel(start_minutes) < self.width:
 
270
                    context.set_source_rgba(0.86, 0.86, 0.86, 0.5)
 
271
                    context.rectangle(self.get_pixel(start_minutes), graph_y,
 
272
                                      self.get_pixel(end_minutes) - self.get_pixel(start_minutes), graph_height - 1)
 
273
                    context.fill()
 
274
                    context.stroke()
 
275
 
 
276
                    context.set_source_rgba(0.86, 0.86, 0.86, 1)
 
277
                    self.move_to(start_minutes, graph_y)
 
278
                    self.line_to(start_minutes, graph_y2)
 
279
                    self.move_to(end_minutes, graph_y)
 
280
                    self.line_to(end_minutes, graph_y2)
 
281
                    context.stroke()
 
282
 
 
283
        
 
284
        
 
285
        #time scale
 
286
        context.set_source_rgb(0, 0, 0)
 
287
        self.layout.set_width(-1)
 
288
        for i in range(minutes):
 
289
            label_time = (self.range_start.value + dt.timedelta(minutes=i))
 
290
            
 
291
            if label_time.minute == 0:
 
292
                context.set_source_rgb(0.8, 0.8, 0.8)
 
293
                self.move_to(i, graph_y2 - 15)
 
294
                self.line_to(i, graph_y2)
 
295
                context.stroke()
 
296
            elif label_time.minute % 15 == 0:
 
297
                context.set_source_rgb(0.8, 0.8, 0.8)
 
298
                self.move_to(i, graph_y2 - 5)
 
299
                self.line_to(i, graph_y2)
 
300
                context.stroke()
 
301
                
 
302
                
 
303
                
 
304
            if label_time.minute == 0 and label_time.hour % 2 == 0:
 
305
                if label_time.hour == 0:
 
306
                    context.set_source_rgb(0.8, 0.8, 0.8)
 
307
                    self.move_to(i, graph_y)
 
308
                    self.line_to(i, graph_y2)
 
309
                    label_minutes = label_time.strftime("%b %d")
 
310
                else:
 
311
                    label_minutes = label_time.strftime("%H<small><sup>%M</sup></small>")
 
312
 
 
313
                context.set_source_rgb(0.4, 0.4, 0.4)
 
314
                self.layout.set_markup(label_minutes)
 
315
                label_w, label_h = self.layout.get_pixel_size()
 
316
                
 
317
                context.move_to(self.get_pixel(i) + 2, graph_y2 - label_h - 8)                
 
318
 
 
319
                context.show_layout(self.layout)
 
320
        context.stroke()
 
321
        
 
322
        #highlight rectangle
 
323
        if self.highlight:
 
324
            self.highlight_start = self.get_pixel(self._minutes_from_start(self.highlight[0]))
 
325
            self.highlight_end = self.get_pixel(self._minutes_from_start(self.highlight[1]))
 
326
 
 
327
        #TODO - make a proper range check here
 
328
        if self.highlight_end > 0 and self.highlight_start < self.width:
 
329
            rgb = colorsys.hls_to_rgb(.6, .7, .5)
 
330
 
 
331
 
 
332
            self.fill_area(self.highlight_start, graph_y,
 
333
                           self.highlight_end - self.highlight_start, graph_height,
 
334
                           (rgb[0], rgb[1], rgb[2], 0.5))
 
335
            context.stroke()
 
336
 
 
337
            context.set_source_rgb(*rgb)
 
338
            self.context.move_to(self.highlight_start, graph_y)
 
339
            self.context.line_to(self.highlight_start, graph_y + graph_height)
 
340
            self.context.move_to(self.highlight_end, graph_y)
 
341
            self.context.line_to(self.highlight_end, graph_y + graph_height)
 
342
            context.stroke()
 
343
 
 
344
        #and now put a frame around the whole thing
 
345
        context.set_source_rgb(0.7, 0.7, 0.7)
 
346
        context.rectangle(0, graph_y-1, self.width - 1, graph_height)
 
347
        context.stroke()
 
348
        
 
349
        if self.move_type == "move" and (self.highlight_start == 0 or self.highlight_end == self.width):
 
350
            if self.highlight_start == 0:
 
351
                self.range_start.target(self.range_start.value - dt.timedelta(minutes=30))
 
352
            if self.highlight_end == self.width:
 
353
                self.range_start.target(self.range_start.value + dt.timedelta(minutes=30))
 
354
            self.scroll_to_range_start()
 
355
 
 
356
 
 
357
 
 
358
class CustomFactController:
 
359
    def __init__(self,  parent = None, fact_date = None, fact_id = None):
 
360
        self._gui = stuff.load_ui_file("edit_activity.ui")
 
361
        self.window = self.get_widget('custom_fact_window')
 
362
 
 
363
        self.parent, self.fact_id = parent, fact_id
 
364
 
 
365
        start_date, end_date = None, None
 
366
        if fact_id:
 
367
            fact = runtime.storage.get_fact(fact_id)
 
368
 
 
369
            label = fact['name']
 
370
            if fact['category'] != _("Unsorted"):
 
371
                label += "@%s" %  fact['category']
 
372
            self.get_widget('activity_combo').child.set_text(label)
 
373
            
 
374
            start_date = fact["start_time"]
 
375
            end_date = fact["end_time"]
 
376
            
 
377
            buf = gtk.TextBuffer()
 
378
            buf.set_text(fact["description"] or "")
 
379
            self.get_widget('description').set_buffer(buf)
 
380
 
 
381
            if not end_date:
 
382
                self.get_widget("in_progress").set_active(True)
 
383
                if (dt.datetime.now() - start_date).days == 0:
 
384
                    end_date = dt.datetime.now()
 
385
 
 
386
            self.get_widget("save_button").set_label("gtk-save")
 
387
            self.window.set_title(_("Update activity"))
 
388
 
 
389
        elif fact_date and fact_date != dt.date.today():
 
390
            # if there is previous activity with end time - attach to it
 
391
            # otherwise let's start at 8am
 
392
            last_activity = runtime.storage.get_facts(fact_date)
 
393
            if last_activity and last_activity[len(last_activity)-1]["end_time"]:
 
394
                start_date = last_activity[len(last_activity)-1]["end_time"]
 
395
            else:
 
396
                start_date = dt.datetime(fact_date.year, fact_date.month,
 
397
                                         fact_date.day, 8)
 
398
 
 
399
        start_date = start_date or dt.datetime.now()
 
400
        end_date = end_date or start_date + dt.timedelta(minutes = 30)
 
401
 
 
402
 
 
403
        self.start_date = widgets.DateInput(start_date)
 
404
        self.get_widget("start_date_placeholder").add(self.start_date)
 
405
        self.start_date.connect("date-entered", self.on_start_date_entered)
 
406
 
 
407
        self.start_time = widgets.TimeInput(start_date)
 
408
        self.get_widget("start_time_placeholder").add(self.start_time)
 
409
        self.start_time.connect("time-entered", self.on_start_time_entered)
 
410
        
 
411
        self.end_time = widgets.TimeInput(end_date, start_date)
 
412
        self.get_widget("end_time_placeholder").add(self.end_time)
 
413
        self.end_time.connect("time-entered", self.on_end_time_entered)
 
414
        self.set_end_date_label(end_date)
 
415
 
 
416
 
 
417
        self.set_dropdown()
 
418
        self.refresh_menu()
 
419
 
 
420
        self.dayline = Dayline()
 
421
        self.dayline.on_time_changed = self.update_time
 
422
        self.dayline.on_more_data = runtime.storage.get_facts
 
423
        self._gui.get_object("day_preview").add(self.dayline)
 
424
 
 
425
        self.on_in_progress_toggled(self.get_widget("in_progress"))
 
426
        self._gui.connect_signals(self)
 
427
 
 
428
    def update_time(self, start_time, end_time):
 
429
        self.start_time.set_time(start_time)
 
430
        self.start_date.set_date(start_time)
 
431
        self.end_time.set_time(end_time)
 
432
        self.set_end_date_label(end_time)
 
433
 
 
434
        
 
435
    def draw_preview(self, date, highlight = None):
 
436
        day_facts = runtime.storage.get_facts(date)
 
437
        self.dayline.draw(day_facts, highlight)
 
438
        
 
439
        
 
440
 
 
441
    def set_dropdown(self):
 
442
        # set up drop down menu
 
443
        self.activity_list = self._gui.get_object('activity_combo')
 
444
        self.activity_list.set_model(gtk.ListStore(gobject.TYPE_STRING,
 
445
                                                   gobject.TYPE_STRING,
 
446
                                                   gobject.TYPE_STRING))
 
447
 
 
448
 
 
449
        self.activity_list.set_property("text-column", 2)
 
450
        self.activity_list.clear()
 
451
        activity_cell = gtk.CellRendererText()
 
452
        self.activity_list.pack_start(activity_cell, True)
 
453
        self.activity_list.add_attribute(activity_cell, 'text', 0)
 
454
        category_cell = stuff.CategoryCell()  
 
455
        self.activity_list.pack_start(category_cell, False)
 
456
        self.activity_list.add_attribute(category_cell, 'text', 1)
 
457
        
 
458
        self.activity_list.child.connect('key-press-event', self.on_activity_list_key_pressed)
 
459
 
 
460
 
 
461
        # set up autocompletition
 
462
        self.activities = gtk.ListStore(gobject.TYPE_STRING,
 
463
                                        gobject.TYPE_STRING,
 
464
                                        gobject.TYPE_STRING)
 
465
        completion = gtk.EntryCompletion()
 
466
        completion.set_model(self.activities)
 
467
 
 
468
        activity_cell = gtk.CellRendererText()
 
469
        completion.pack_start(activity_cell, True)
 
470
        completion.add_attribute(activity_cell, 'text', 0)
 
471
        completion.set_property("text-column", 2)
 
472
 
 
473
        category_cell = stuff.CategoryCell()  
 
474
        completion.pack_start(category_cell, False)
 
475
        completion.add_attribute(category_cell, 'text', 1)
 
476
 
 
477
        completion.set_minimum_key_length(1)
 
478
        completion.set_inline_completion(True)
 
479
 
 
480
        self.activity_list.child.set_completion(completion)
 
481
        
 
482
 
 
483
    def refresh_menu(self):
 
484
        #first populate the autocomplete - contains all entries in lowercase
 
485
        self.activities.clear()
 
486
        all_activities = runtime.storage.get_autocomplete_activities()
 
487
        for activity in all_activities:
 
488
            activity_category = activity['name']
 
489
            if activity['category']:
 
490
                activity_category += "@%s" % activity['category']
 
491
            self.activities.append([activity['name'],
 
492
                                    activity['category'],
 
493
                                    activity_category])
 
494
 
 
495
 
 
496
        #now populate the menu - contains only categorized entries
 
497
        store = self.activity_list.get_model()
 
498
        store.clear()
 
499
 
 
500
        #populate fresh list from DB
 
501
        categorized_activities = runtime.storage.get_sorted_activities()
 
502
 
 
503
        for activity in categorized_activities:
 
504
            activity_category = activity['name']
 
505
            if activity['category']:
 
506
                activity_category += "@%s" % activity['category']
 
507
            item = store.append([activity['name'],
 
508
                                 activity['category'],
 
509
                                 activity_category])
 
510
 
 
511
        # finally add TODO tasks from evolution to both lists
 
512
        tasks = eds.get_eds_tasks()
 
513
        for activity in tasks:
 
514
            activity_category = "%s@%s" % (activity['name'], activity['category'])
 
515
            self.activities.append([activity['name'],activity['category'],activity_category])
 
516
            store.append([activity['name'], activity['category'], activity_category])
 
517
 
 
518
        return True
 
519
 
 
520
    def get_widget(self, name):
 
521
        """ skip one variable (huh) """
 
522
        return self._gui.get_object(name)
 
523
 
 
524
    def show(self):
 
525
        self.window.show()
 
526
 
 
527
    def _get_datetime(self, prefix):
 
528
        start_time = self.start_time.get_time()
 
529
        start_date = self.start_date.get_date()
 
530
 
 
531
        if prefix == "end":
 
532
            end_time = self.end_time.get_time()
 
533
            end_date = start_date
 
534
            if end_time < start_time:
 
535
                end_date = start_date + dt.timedelta(days=1)
 
536
 
 
537
            if end_date:
 
538
                self.set_end_date_label(end_date)
 
539
            time, date = end_time, end_date
 
540
        else:
 
541
            time, date = start_time, start_date
 
542
        
 
543
        if time and date:
 
544
            return dt.datetime.combine(date, time.time())
 
545
        else:
 
546
            return None
 
547
    
 
548
    def figure_description(self):
 
549
        activity = self.get_widget("activity_combo").child.get_text().decode("utf-8")
 
550
 
 
551
        # juggle with description - break into parts and then put together
 
552
        buf = self.get_widget('description').get_buffer()
 
553
        description = buf.get_text(buf.get_start_iter(), buf.get_end_iter(), 0)\
 
554
                         .decode("utf-8")
 
555
        description = description.strip()
 
556
        
 
557
        # user might also type description in the activity name - strip it here
 
558
        # and remember value
 
559
        inline_description = None
 
560
        if activity.find(",") != -1:
 
561
            activity, inline_description  = activity.split(",", 1)
 
562
            inline_description = inline_description.strip()
 
563
        
 
564
        # description field is prior to inline description
 
565
        return description or inline_description
 
566
        
 
567
    def on_save_button_clicked(self, button):
 
568
        activity = self.get_widget("activity_combo").child.get_text().decode("utf-8")
 
569
        
 
570
        if not activity:
 
571
            return False
 
572
 
 
573
        description = self.figure_description()
 
574
 
 
575
        if description:
 
576
            activity = "%s, %s" % (activity, description)
 
577
 
 
578
        
 
579
        start_time = self._get_datetime("start")
 
580
 
 
581
        if self.get_widget("in_progress").get_active():
 
582
            end_time = None
 
583
        else:
 
584
            end_time = self._get_datetime("end")
 
585
 
 
586
        # we don't do updates, we do insert/delete. So now it is time to delete
 
587
        if self.fact_id:
 
588
            runtime.storage.remove_fact(self.fact_id)
 
589
 
 
590
        runtime.storage.add_fact(activity, start_time, end_time)
 
591
 
 
592
 
 
593
        # hide panel only on add - on update user will want to see changes
 
594
        if not self.fact_id: 
 
595
            runtime.dispatcher.dispatch('panel_visible', False)
 
596
        
 
597
        self.close_window()
 
598
    
 
599
    def on_activity_list_key_pressed(self, entry, event):
 
600
        #treating tab as keydown to be able to cycle through available values
 
601
        if event.keyval == gtk.keysyms.Tab:
 
602
            event.keyval = gtk.keysyms.Down
 
603
        return False
 
604
        
 
605
    def on_in_progress_toggled(self, check):
 
606
        sensitive = not check.get_active()
 
607
        self.end_time.set_sensitive(sensitive)
 
608
        self.get_widget("end_label").set_sensitive(sensitive)
 
609
        self.get_widget("end_date_label").set_sensitive(sensitive)
 
610
        self.validate_fields()
 
611
        self.dayline.set_in_progress(not sensitive)
 
612
 
 
613
    def on_cancel_clicked(self, button):
 
614
        self.close_window()
 
615
        
 
616
    def on_activity_combo_changed(self, combo):
 
617
        self.validate_fields()
 
618
 
 
619
    def on_start_date_entered(self, widget):
 
620
        self.validate_fields()
 
621
        self.start_time.grab_focus()
 
622
 
 
623
    def on_start_time_entered(self, widget):
 
624
        start_time = self.start_time.get_time()
 
625
        if not start_time:
 
626
            return
 
627
 
 
628
        self.end_time.set_time(start_time + dt.timedelta(minutes = 30))
 
629
        self.end_time.set_start_time(start_time)
 
630
        self.validate_fields()
 
631
        self.end_time.grab_focus()
 
632
        
 
633
    def on_end_time_entered(self, widget):
 
634
        self.validate_fields()
 
635
    
 
636
    def set_end_date_label(self, some_date):
 
637
        self.get_widget("end_date_label").set_text(some_date.strftime("%x"))
 
638
    
 
639
    def validate_fields(self, widget = None):
 
640
        activity_text = self.get_widget("activity_combo").child.get_text()
 
641
        start_time = self._get_datetime("start")
 
642
 
 
643
        end_time = self._get_datetime("end")
 
644
        if self.get_widget("in_progress").get_active():
 
645
            end_time = dt.datetime.now()
 
646
 
 
647
        if start_time and end_time:
 
648
            # if we are too far, just roll back for one day
 
649
            if ((end_time - start_time).days > 0): 
 
650
                end_time -= dt.timedelta(days=1)
 
651
                self.update_time(start_time, end_time)
 
652
 
 
653
            # if end time is not in proper distance, do the brutal +30 minutes reset
 
654
            if (end_time < start_time or (end_time - start_time).days > 0):
 
655
                end_time = start_time + dt.timedelta(minutes = 30)
 
656
                self.update_time(start_time, end_time)
 
657
    
 
658
            self.draw_preview(start_time.date(), [start_time, end_time])    
 
659
        else:
 
660
            self.draw_preview(dt.datetime.today().date(), [dt.datetime.now(),
 
661
                                                           dt.datetime.now()])
 
662
 
 
663
        looks_good = False
 
664
        if activity_text != "" and start_time and end_time and \
 
665
           (end_time - start_time).days == 0:
 
666
            looks_good = True
 
667
 
 
668
        self.get_widget("save_button").set_sensitive(looks_good)
 
669
        return looks_good
 
670
 
 
671
    def on_window_key_pressed(self, tree, event_key):
 
672
        if (event_key.keyval == gtk.keysyms.Escape
 
673
          or (event_key.keyval == gtk.keysyms.w 
 
674
              and event_key.state & gtk.gdk.CONTROL_MASK)):
 
675
            
 
676
            if self.start_date.popup.get_property("visible") or \
 
677
               self.start_time.popup.get_property("visible") or \
 
678
               self.end_time.popup.get_property("visible"):
 
679
                return False
 
680
 
 
681
            self.close_window()            
 
682
 
 
683
    def on_close(self, widget, event):
 
684
        self.close_window()        
 
685
 
 
686
    def close_window(self):
 
687
        if not self.parent:
 
688
            gtk.main_quit()
 
689
        else:
 
690
            self.window.destroy()
 
691
            return False
 
692