~ubuntu-branches/ubuntu/trusty/gtg/trusty

« back to all changes in this revision

Viewing changes to GTG/taskeditor/editor.py

  • Committer: Package Import Robot
  • Author(s): Luca Falavigna
  • Date: 2012-04-10 23:08:21 UTC
  • mfrom: (1.1.8)
  • Revision ID: package-import@ubuntu.com-20120410230821-q6it7f4d7elut6pv
Tags: 0.2.9-1
* New upstream release (Closes: #668096).
  - Implement a search text box (Closes: #650279).
  - Window title reflects active tasks (LP: #537096).
  - Fix misbehaviours of the indicator applet (LP: #548836, #676353).
  - Fix crash when selecting notification area plugin twice (LP: #550321).
  - Fix sorting of tasks by date (LP: #556159).
  - Fix excessive delays at startup (LP: #558600).
  - Fix crash with dates having unknown values (LP: #561449).
  - Fix crash issued when pressing delete key (LP: #583103).
  - Keep notification plugin enabled after logoff (LP: #617257).
  - Fix Hamster plugin to work with recent Hamster versions (LP: #620313).
  - No longer use non-unicode strings (LP: #680632).
  - New RTM sync mechanism (LP: #753327).
  - Fix crashes while handling XML storage file (LP: #916474, #917634).
* debian/patches/*:
  - Drop all patches, they have been merged upstream.
* debian/patches/shebang.patch:
  - Fix shebang line.
* debian/patches/manpages.patch:
  - Fix some groff warnings in gtg_new_task man page
* debian/compat:
  - Bump compatibility level to 9.
* debian/control:
  - Bump X-Python-Version to >= 2.6.
  - Add python-liblarch and python-liblarch-gtk to Depends field.
  - Add python-cheetah, python-geoclue, python-gnomekeyring,
    python-launchpadlib and python-suds to Suggests field.
  - Bump Standards-Version to 3.9.3.
* debian/copyright:
  - Refresh copyright information.
  - Format now points to copyright-format site.
* debian/rules:
  - Make gtcli_bash_completion script executable.
* debian/watch:
  - Update watch file.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# -*- coding: utf-8 -*-
2
 
# -----------------------------------------------------------------------------
3
 
# Gettings Things Gnome! - a personal organizer for the GNOME desktop
4
 
# Copyright (c) 2008-2009 - Lionel Dricot & Bertrand Rousseau
5
 
#
6
 
# This program is free software: you can redistribute it and/or modify it under
7
 
# the terms of the GNU General Public License as published by the Free Software
8
 
# Foundation, either version 3 of the License, or (at your option) any later
9
 
# version.
10
 
#
11
 
# This program is distributed in the hope that it will be useful, but WITHOUT
12
 
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13
 
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
14
 
# details.
15
 
#
16
 
# You should have received a copy of the GNU General Public License along with
17
 
# this program.  If not, see <http://www.gnu.org/licenses/>.
18
 
# -----------------------------------------------------------------------------
19
 
 
20
 
#This is the TaskEditor
21
 
#
22
 
#It's the window you see when you double-click on a Task
23
 
#The main text widget is a home-made TextView called TaskView (see taskview.py)
24
 
#The rest is the logic of the widget : date changing widgets, buttons, ...
25
 
import sys
26
 
import time
27
 
 
28
 
from GTG import _
29
 
from GTG import ngettext
30
 
from GTG import PLUGIN_DIR
31
 
from GTG import DATA_DIR
32
 
from GTG.taskeditor          import GnomeConfig
33
 
from GTG.tools               import dates
34
 
from GTG.taskeditor.taskview import TaskView
35
 
from GTG.core.plugins.engine import PluginEngine
36
 
from GTG.core.plugins.api    import PluginAPI
37
 
from GTG.core.task           import Task
38
 
try:
39
 
    import pygtk
40
 
    pygtk.require("2.0")
41
 
except: # pylint: disable-msg=W0702
42
 
    sys.exit(1)
43
 
try:
44
 
    import gtk
45
 
    from gtk import gdk
46
 
except: # pylint: disable-msg=W0702
47
 
    sys.exit(1)
48
 
    
49
 
date_separator = "-"
50
 
 
51
 
class TaskEditor :
52
 
    #delete_callback is the function called on deletion
53
 
    #close_callback is the function called on close
54
 
    #opentask_callback is the function to open a new editor
55
 
    #tasktitle_callback is called when title changes
56
 
    #notes is experimental (bool)
57
 
    #taskconfig is a ConfigObj dic to save infos about tasks
58
 
    #thisisnew is True when a new task is created and opened
59
 
    def __init__(self, requester, task, plugins, taskbrowser,
60
 
                delete_callback=None, close_callback=None,opentask_callback=None, \
61
 
                tasktitle_callback=None, notes=False,taskconfig=None,\
62
 
                plugin_apis=None,thisisnew=False, clipboard=None) :
63
 
        self.req = requester
64
 
        self.config = taskconfig
65
 
        self.p_apis = plugin_apis
66
 
        self.pengine = taskbrowser.pengine
67
 
        self.time = None
68
 
        self.taskbrowser = taskbrowser
69
 
        self.clipboard = clipboard
70
 
        self.builder = gtk.Builder() 
71
 
        self.builder.add_from_file(GnomeConfig.GLADE_FILE)
72
 
        self.donebutton = self.builder.get_object("mark_as_done_editor")
73
 
        self.dismissbutton = self.builder.get_object("dismiss_editor")
74
 
        self.deletebutton = self.builder.get_object("delete_editor")
75
 
        self.deletebutton.set_tooltip_text(GnomeConfig.DELETE_TOOLTIP)
76
 
        self.subtask_button = self.builder.get_object("insert_subtask")
77
 
        self.subtask_button.set_tooltip_text(GnomeConfig.SUBTASK_TOOLTIP)
78
 
        self.inserttag_button = self.builder.get_object("inserttag")
79
 
        self.inserttag_button.set_tooltip_text(GnomeConfig.TAG_TOOLTIP)
80
 
        #Create our dictionary and connect it
81
 
        dic = {
82
 
                "mark_as_done_clicked"  : self.change_status,
83
 
                "on_dismiss"            : self.dismiss,
84
 
                "on_keepnote_clicked"   : self.keepnote,
85
 
                "delete_clicked"        : self.delete_task,
86
 
                "on_duedate_pressed"    : (self.on_date_pressed,"due"),
87
 
                "on_startdate_pressed"    : (self.on_date_pressed,"start"),
88
 
                "on_closeddate_pressed"   : (self.on_date_pressed, "closed"),
89
 
                "close_clicked"         : self.close,
90
 
                "startingdate_changed" : (self.date_changed,"start"),
91
 
                "duedate_changed" : (self.date_changed,"due"),
92
 
                "closeddate_changed"    : (self.date_changed, "closed"),
93
 
                "on_insert_subtask_clicked" : self.insert_subtask,
94
 
                "on_inserttag_clicked" : self.inserttag_clicked,
95
 
                "on_move" : self.on_move,
96
 
                "on_nodate"             : self.nodate_pressed,
97
 
                "on_set_fuzzydate_now"  : self.set_fuzzydate_now,
98
 
                "on_set_fuzzydate_soon" : self.set_fuzzydate_soon,
99
 
                "on_set_fuzzydate_later": self.set_fuzzydate_later,
100
 
        }
101
 
        self.builder.connect_signals(dic)
102
 
        self.window         = self.builder.get_object("TaskEditor")
103
 
        #Removing the Normal textview to replace it by our own
104
 
        #So don't try to change anything with glade, this is a home-made widget
105
 
        textview = self.builder.get_object("textview")
106
 
        scrolled = self.builder.get_object("scrolledtask")
107
 
        scrolled.remove(textview)
108
 
        self.open_task  = opentask_callback
109
 
        self.task_title = tasktitle_callback
110
 
        self.textview   = TaskView(self.req,self.clipboard)
111
 
        self.textview.show()
112
 
        self.textview.set_subtask_callback(self.new_subtask)
113
 
        self.textview.open_task_callback(self.open_task)
114
 
        self.textview.tasktitle_callback(self.task_title)
115
 
        self.textview.set_left_margin(7)
116
 
        self.textview.set_right_margin(5)
117
 
        scrolled.add(self.textview)
118
 
        #Voila! it's done
119
 
        self.calendar       = self.builder.get_object("calendar")
120
 
        self.cal_widget       = self.builder.get_object("calendar1")
121
 
        self.calendar_fuzzydate_btns       = self.builder.get_object("fuzzydate_btns")
122
 
        #self.cal_widget.set_property("no-month-change",True)
123
 
        self.sigid = None
124
 
        self.sigid_month = None
125
 
        #Do we have to close the calendar when date is changed ?
126
 
        #This is a ugly hack to close the calendar on the first click
127
 
        self.close_when_changed = True
128
 
        self.duedate_widget = self.builder.get_object("duedate_entry")
129
 
        self.startdate_widget = self.builder.get_object("startdate_entry")
130
 
        self.closeddate_widget = self.builder.get_object("closeddate_entry")
131
 
        self.dayleft_label  = self.builder.get_object("dayleft")
132
 
        self.tasksidebar = self.builder.get_object("tasksidebar")
133
 
        self.keepnote_button = self.builder.get_object("keepnote")
134
 
        if not notes :
135
 
            self.keepnote_button.hide()
136
 
            separator = self.builder.get_object("separator_note")
137
 
            separator.hide()
138
 
        #We will keep the name of the opened calendar
139
 
        #Empty means that no calendar is opened
140
 
        self.__opened_date = ''
141
 
        
142
 
        # Define accelerator keys
143
 
        self.init_accelerators()
144
 
        
145
 
        self.task = task
146
 
        tags = task.get_tags()
147
 
        self.textview.subtasks_callback(task.get_subtask_tids)
148
 
        self.textview.removesubtask_callback(task.remove_subtask)
149
 
        self.textview.set_get_tagslist_callback(task.get_tags_name)
150
 
        self.textview.set_add_tag_callback(task.add_tag)
151
 
        self.textview.set_remove_tag_callback(task.remove_tag)
152
 
        self.textview.save_task_callback(self.light_save)
153
 
        self.delete  = delete_callback
154
 
        self.closing = close_callback
155
 
 
156
 
        texte = self.task.get_text()
157
 
        title = self.task.get_title()
158
 
        #the first line is the title
159
 
        self.textview.set_text("%s\n"%title)
160
 
        #we insert the rest of the task
161
 
        if texte :
162
 
            self.textview.insert("%s"%texte)
163
 
        else :
164
 
            #If not text, we insert tags
165
 
            if tags :
166
 
                for t in tags :
167
 
                    self.textview.insert_text("%s, "%t.get_name())
168
 
                self.textview.insert_text("\n")
169
 
            #If we don't have text, we still need to insert subtasks if any
170
 
            subtasks = task.get_subtask_tids()
171
 
            if subtasks :
172
 
                self.textview.insert_subtasks(subtasks)
173
 
        #We select the title if it's a new task
174
 
        if thisisnew :
175
 
            self.textview.select_title()
176
 
        else :
177
 
            self.task.set_to_keep()
178
 
        self.textview.modified(full=True)
179
 
        self.window.connect("destroy", self.destruction)
180
 
        
181
 
        # plugins
182
 
        self.plugins = plugins
183
 
        self.te_plugin_api = PluginAPI(window = self.window,
184
 
                                       config = None,
185
 
                                       data_dir = DATA_DIR,
186
 
                                       builder = self.builder, 
187
 
                                       requester = self.req,
188
 
                                       taskview = None, 
189
 
                                       task_modelsort = None,
190
 
                                       ctaskview = None, 
191
 
                                       ctask_modelsort = None,
192
 
                                       filter_cbs = None,
193
 
                                       tagpopup = None,
194
 
                                       tagview = None,
195
 
                                       task = task, 
196
 
                                       browser = self.taskbrowser,
197
 
                                       texteditor = self)
198
 
        self.p_apis.append(self.te_plugin_api)
199
 
        self.pengine.onTaskLoad(self.te_plugin_api)
200
 
        
201
 
        #Putting the refresh callback at the end make the start a lot faster
202
 
        self.textview.refresh_callback(self.refresh_editor)
203
 
        self.refresh_editor()
204
 
        self.textview.grab_focus()
205
 
        
206
 
        #restoring size and position, spatial tasks
207
 
        if self.config :
208
 
            tid = self.task.get_id()
209
 
            if tid in self.config:
210
 
                if "position" in self.config[tid]:
211
 
                    pos = self.config[tid]["position"]
212
 
                    self.move(pos[0],pos[1])
213
 
                    #print "restoring position %s %s" %(pos[0],pos[1])
214
 
                if "size" in self.config[tid]:
215
 
                    size = self.config[tid]["size"]
216
 
                    #print "size %s - %s" %(str(size[0]),str(size[1]))
217
 
                    #this eval(str()) is a hack to accept both int and str
218
 
                    self.window.resize(eval(str(size[0])),eval(str(size[1])))
219
 
 
220
 
        self.window.show()
221
 
        self.textview.set_editable(True)
222
 
 
223
 
    # Define accelerator-keys for this dialog
224
 
    # TODO: undo/redo
225
 
    def init_accelerators(self):
226
 
        agr = gtk.AccelGroup()
227
 
        self.window.add_accel_group(agr)
228
 
        
229
 
        # Escape and Ctrl-W close the dialog. It's faster to call close
230
 
        # directly, rather than use the close button widget
231
 
        key, modifier = gtk.accelerator_parse('Escape')
232
 
        agr.connect_group(key, modifier, gtk.ACCEL_VISIBLE, self.close)
233
 
        
234
 
        key, modifier = gtk.accelerator_parse('<Control>w')
235
 
        agr.connect_group(key, modifier, gtk.ACCEL_VISIBLE, self.close)
236
 
        
237
 
        # Ctrl-N creates a new task
238
 
        key, modifier = gtk.accelerator_parse('<Control>n')
239
 
        agr.connect_group(key, modifier, gtk.ACCEL_VISIBLE, self.new_task)
240
 
        
241
 
        # Ctrl-Shift-N creates a new subtask
242
 
        insert_subtask = self.builder.get_object("insert_subtask")
243
 
        key, mod       = gtk.accelerator_parse("<Control><Shift>n")
244
 
        insert_subtask.add_accelerator('clicked', agr, key, mod, gtk.ACCEL_VISIBLE)
245
 
        
246
 
        # Ctrl-D marks task as done
247
 
        mark_as_done_editor = self.builder.get_object('mark_as_done_editor')
248
 
        key, mod = gtk.accelerator_parse('<Control>d')
249
 
        mark_as_done_editor.add_accelerator('clicked', agr, key, mod, gtk.ACCEL_VISIBLE)
250
 
        
251
 
        # Ctrl-I marks task as dismissed
252
 
        dismiss_editor = self.builder.get_object('dismiss_editor')
253
 
        key, mod = gtk.accelerator_parse('<Control>i')
254
 
        dismiss_editor.add_accelerator('clicked', agr, key, mod, gtk.ACCEL_VISIBLE)
255
 
        
256
 
    
257
 
    #Can be called at any time to reflect the status of the Task
258
 
    #Refresh should never interfere with the TaskView.
259
 
    #If a title is passed as a parameter, it will become
260
 
    #the new window title. If not, we will look for the task title.
261
 
    #Refreshtext is whether or not we should refresh the TaskView
262
 
    #(doing it all the time is dangerous if the task is empty)
263
 
    def refresh_editor(self, title=None, refreshtext=False):
264
 
        to_save = False
265
 
        #title of the window 
266
 
        if title :
267
 
            self.window.set_title(title)
268
 
            to_save = True
269
 
        else :
270
 
            self.window.set_title(self.task.get_title())
271
 
           
272
 
        status = self.task.get_status() 
273
 
        if status == Task.STA_DISMISSED:
274
 
            self.donebutton.set_label(GnomeConfig.MARK_DONE)
275
 
            self.donebutton.set_tooltip_text(GnomeConfig.MARK_DONE_TOOLTIP)
276
 
            self.donebutton.set_icon_name("gtg-task-done")
277
 
            self.dismissbutton.set_label(GnomeConfig.MARK_UNDISMISS)
278
 
            self.dismissbutton.set_tooltip_text(GnomeConfig.MARK_UNDISMISS_TOOLTIP)
279
 
            self.dismissbutton.set_icon_name("gtg-task-undismiss")
280
 
        elif status == Task.STA_DONE:
281
 
            self.donebutton.set_label(GnomeConfig.MARK_UNDONE)
282
 
            self.donebutton.set_tooltip_text(GnomeConfig.MARK_UNDONE_TOOLTIP)
283
 
            self.donebutton.set_icon_name("gtg-task-undone")
284
 
            self.dismissbutton.set_label(GnomeConfig.MARK_DISMISS)
285
 
            self.dismissbutton.set_tooltip_text(GnomeConfig.MARK_DISMISS_TOOLTIP)
286
 
            self.dismissbutton.set_icon_name("gtg-task-dismiss")
287
 
        else:
288
 
            self.donebutton.set_label(GnomeConfig.MARK_DONE)
289
 
            self.donebutton.set_tooltip_text(GnomeConfig.MARK_DONE_TOOLTIP)
290
 
            self.donebutton.set_icon_name("gtg-task-done")
291
 
            self.dismissbutton.set_label(GnomeConfig.MARK_DISMISS)
292
 
            self.dismissbutton.set_tooltip_text(GnomeConfig.MARK_DISMISS_TOOLTIP)
293
 
            self.dismissbutton.set_icon_name("gtg-task-dismiss")
294
 
            
295
 
        if status == "Note":
296
 
            self.donebutton.hide()
297
 
            self.tasksidebar.hide()
298
 
            self.keepnote_button.set_label(GnomeConfig.MAKE_TASK)
299
 
        else :
300
 
            self.donebutton.show()
301
 
            self.tasksidebar.show()
302
 
            self.keepnote_button.set_label(GnomeConfig.KEEP_NOTE)
303
 
        
304
 
        #Refreshing the status bar labels and date boxes
305
 
        if status in [Task.STA_DISMISSED, Task.STA_DONE]:
306
 
            self.builder.get_object("label2").hide()
307
 
            self.builder.get_object("hbox1").hide()
308
 
            self.builder.get_object("label4").show()
309
 
            self.builder.get_object("hbox4").show()
310
 
        else:
311
 
            self.builder.get_object("label4").hide()
312
 
            self.builder.get_object("hbox4").hide()
313
 
            self.builder.get_object("label2").show() 
314
 
            self.builder.get_object("hbox1").show()
315
 
             
316
 
        #refreshing the due date field
317
 
        duedate = self.task.get_due_date()
318
 
        prevdate = dates.strtodate(self.duedate_widget.get_text())
319
 
        if duedate != prevdate or type(duedate) is not type(prevdate):
320
 
            zedate = str(duedate).replace("-", date_separator)
321
 
            self.duedate_widget.set_text(zedate)
322
 
        # refreshing the closed date field
323
 
        closeddate = self.task.get_closed_date()
324
 
        prevcldate = dates.strtodate(self.closeddate_widget.get_text())
325
 
        if closeddate != prevcldate or type(closeddate) is not type(prevcldate):
326
 
            zecldate = str(closeddate).replace("-", date_separator)
327
 
            self.closeddate_widget.set_text(zecldate)
328
 
        #refreshing the day left label
329
 
        #If the task is marked as done, we display the delay between the 
330
 
        #due date and the actual closing date. If the task isn't marked 
331
 
        #as done, we display the number of days left.
332
 
        if status in [Task.STA_DISMISSED, Task.STA_DONE]:
333
 
            delay = self.task.get_days_late()
334
 
            if delay is None:
335
 
                txt = ""
336
 
            elif delay == 0:
337
 
                txt = "Completed on time"
338
 
            elif delay >= 1:
339
 
                txt = ngettext("Completed %(days)d day late", "Completed %(days)d days late", delay) % {'days': delay}
340
 
            elif delay <= -1:
341
 
                abs_delay = abs(delay)
342
 
                txt = ngettext("Completed %(days)d day early", "Completed %(days)d days early", abs_delay) % {'days': abs_delay}
343
 
        else:
344
 
            result = self.task.get_days_left()
345
 
            if result is None:
346
 
                txt = ""
347
 
            elif result == 1:
348
 
                txt = _("Due tomorrow !")
349
 
            elif result > 0:
350
 
                txt = ngettext("%(days)d day left", "%(days)d days left", result) % {'days': result}
351
 
            elif result == 0:
352
 
                txt = _("Due today !")
353
 
            elif result == -1:
354
 
                txt = _("Due yesterday")
355
 
            elif result < 0:
356
 
                abs_result = abs(result)
357
 
                txt = ngettext("Was %(days)d day ago", "Was %(days)d days ago", abs_result) % {'days': abs_result}
358
 
        window_style = self.window.get_style()
359
 
        color = str(window_style.text[gtk.STATE_INSENSITIVE])
360
 
        self.dayleft_label.set_markup("<span color='"+color+"'>"+txt+"</span>")
361
 
 
362
 
        startdate = self.task.get_start_date()
363
 
        prevdate = dates.strtodate(self.startdate_widget.get_text())
364
 
        if startdate != prevdate or type(startdate) is not type(prevdate):
365
 
            zedate = str(startdate).replace("-",date_separator)
366
 
            self.startdate_widget.set_text(zedate) 
367
 
        #Refreshing the tag list in the insert tag button
368
 
        taglist = self.req.get_used_tags()
369
 
        menu = gtk.Menu()
370
 
        tag_count = 0
371
 
        for t in taglist :
372
 
            tt = t.get_name()
373
 
            if not self.task.has_tags(tag_list=[t]) :
374
 
                tag_count += 1
375
 
                mi = gtk.MenuItem(label=tt, use_underline=False)
376
 
                mi.connect("activate",self.inserttag,tt)
377
 
                mi.show()
378
 
                menu.append(mi)
379
 
        if tag_count > 0 :
380
 
            self.inserttag_button.set_menu(menu)
381
 
 
382
 
        if refreshtext:
383
 
            self.textview.modified(refresheditor=False)            
384
 
        if to_save:
385
 
            self.light_save()
386
 
            
387
 
        
388
 
    def date_changed(self,widget,data):
389
 
        text = widget.get_text()
390
 
        validdate = False
391
 
        if not text :
392
 
            validdate = True
393
 
            datetoset = dates.no_date
394
 
        else :
395
 
            datetoset = dates.strtodate(text)
396
 
            if datetoset :
397
 
                validdate = True
398
 
                
399
 
        if validdate :
400
 
            #If the date is valid, we write with default color in the widget
401
 
            # "none" will set the default color.
402
 
            widget.modify_text(gtk.STATE_NORMAL, None)
403
 
            widget.modify_base(gtk.STATE_NORMAL, None)
404
 
            if data == "start" :
405
 
                self.task.set_start_date(datetoset)
406
 
            elif data == "due" :
407
 
                self.task.set_due_date(datetoset)
408
 
            elif data == "closed" :
409
 
                self.task.set_closed_date(datetoset)
410
 
        else :
411
 
            #We should write in red in the entry if the date is not valid
412
 
            widget.modify_text(gtk.STATE_NORMAL, gtk.gdk.color_parse("#F00"))
413
 
            widget.modify_base(gtk.STATE_NORMAL, gtk.gdk.color_parse("#F88"))
414
 
 
415
 
    def _mark_today_in_bold(self):
416
 
        today = dates.date_today()
417
 
        #selected is a tuple containing (year, month, day)
418
 
        selected = self.cal_widget.get_date()
419
 
        #the following "-1" is because in pygtk calendar the month is 0-based,
420
 
        # in gtg (and datetime.date) is 1-based.
421
 
        if selected[1] == today.month() - 1 and selected[0] == today.year():
422
 
            self.cal_widget.mark_day(today.day())
423
 
        else:
424
 
            self.cal_widget.unmark_day(today.day())
425
 
        
426
 
        
427
 
    def on_date_pressed(self, widget,data): 
428
 
        """Called when the due button is clicked."""
429
 
        
430
 
        self.__opened_date = data
431
 
        self._mark_today_in_bold()
432
 
        if self.__opened_date == "due" :
433
 
            toset = self.task.get_due_date()
434
 
            self.calendar_fuzzydate_btns.show()
435
 
        elif self.__opened_date == "start" :
436
 
            toset = self.task.get_start_date()
437
 
            self.calendar_fuzzydate_btns.hide()
438
 
        elif self.__opened_date == "closed" :
439
 
            toset = self.task.get_closed_date()
440
 
            self.calendar_fuzzydate_btns.hide()
441
 
        
442
 
        rect = widget.get_allocation()
443
 
        x, y = widget.window.get_origin()
444
 
        cal_width, cal_height = self.calendar.get_size()
445
 
        self.calendar.move((x + rect.x - cal_width + rect.width)
446
 
                                            , (y + rect.y - cal_height))
447
 
        self.calendar.show()
448
 
        """Because some window managers ignore move before you show a window."""
449
 
        self.calendar.move((x + rect.x - cal_width + rect.width)
450
 
                                            , (y + rect.y - cal_height))
451
 
        
452
 
        self.calendar.grab_add()
453
 
        #We grab the pointer in the calendar
454
 
        gdk.pointer_grab(self.calendar.window, True,gdk.BUTTON1_MASK|gdk.MOD2_MASK)
455
 
        #we will close the calendar if the user clicks outside
456
 
        
457
 
        if not isinstance(toset, dates.FuzzyDate):
458
 
            if not toset:
459
 
                # we set the widget to today's date if there is not a date defined
460
 
                toset = dates.date_today()
461
 
 
462
 
            y = toset.year()
463
 
            m = toset.month()
464
 
            d = int(toset.day())
465
 
            
466
 
            #We have to select the day first. If not, we might ask for
467
 
            #February while still being on 31 -> error !
468
 
            self.cal_widget.select_day(d)
469
 
            self.cal_widget.select_month(int(m)-1,int(y))
470
 
            
471
 
        self.calendar.connect('button-press-event', self.__focus_out)
472
 
        self.sigid = self.cal_widget.connect("day-selected",self.day_selected)
473
 
        self.sigid_month = self.cal_widget.connect("month-changed",self.month_changed)
474
 
 
475
 
    def day_selected(self,widget) :
476
 
        y,m,d = widget.get_date()
477
 
        if self.__opened_date == "due" :
478
 
            self.task.set_due_date(dates.strtodate("%s-%s-%s"%(y,m+1,d)))
479
 
        elif self.__opened_date == "start" :
480
 
            self.task.set_start_date(dates.strtodate("%s-%s-%s"%(y,m+1,d)))
481
 
        elif self.__opened_date == "closed" :
482
 
            self.task.set_closed_date(dates.strtodate("%s-%s-%s"%(y,m+1,d)))
483
 
        if self.close_when_changed :
484
 
            #When we select a day, we connect the mouse release to the
485
 
            #closing of the calendar.
486
 
            self.mouse_sigid = self.cal_widget.connect('event',self.__mouse_release)
487
 
        else :
488
 
            self.close_when_changed = True
489
 
        self.refresh_editor()
490
 
        
491
 
    def __mouse_release(self,widget,event):
492
 
        if event.type == gtk.gdk.BUTTON_RELEASE:
493
 
            self.__close_calendar()
494
 
            self.cal_widget.disconnect(self.mouse_sigid)
495
 
        
496
 
    def month_changed(self,widget) :
497
 
        #This is a ugly hack to close the calendar on the first click
498
 
        self.close_when_changed = False
499
 
        self._mark_today_in_bold()
500
 
 
501
 
    def set_opened_date(self, date):
502
 
        if self.__opened_date == "due" :
503
 
            self.task.set_due_date(date)
504
 
        elif self.__opened_date == "start" :
505
 
            self.task.set_start_date(date)
506
 
        elif self.__opened_date == "closed" :
507
 
            self.task.set_closed_date(date)
508
 
        self.refresh_editor()
509
 
        self.__close_calendar()
510
 
        
511
 
    def nodate_pressed(self,widget) : #pylint: disable-msg=W0613
512
 
        self.set_opened_date(dates.no_date)
513
 
        
514
 
    def set_fuzzydate_now(self, widget) : #pylint: disable-msg=W0613
515
 
        self.set_opened_date(dates.NOW)
516
 
        
517
 
    def set_fuzzydate_soon(self, widget) : #pylint: disable-msg=W0613
518
 
        self.set_opened_date(dates.SOON)
519
 
        
520
 
    def set_fuzzydate_later(self, widget) : #pylint: disable-msg=W0613
521
 
        self.set_opened_date(dates.LATER)
522
 
        
523
 
    def dismiss(self,widget) : #pylint: disable-msg=W0613
524
 
        stat = self.task.get_status()
525
 
        if stat == "Dismiss":
526
 
            self.task.set_status("Active")
527
 
            self.refresh_editor()
528
 
        else:
529
 
            self.task.set_status("Dismiss")
530
 
            self.close(None)
531
 
    
532
 
    def keepnote(self,widget) : #pylint: disable-msg=W0613
533
 
        stat = self.task.get_status()
534
 
        toset = "Note"
535
 
        if stat == "Note" :
536
 
            toset = "Active"
537
 
        self.task.set_status(toset)
538
 
        self.refresh_editor()
539
 
    
540
 
    def change_status(self,widget) : #pylint: disable-msg=W0613
541
 
        stat = self.task.get_status()
542
 
        if stat == "Done":
543
 
            self.task.set_status("Active")
544
 
            self.refresh_editor()
545
 
        else:
546
 
            self.task.set_status("Done")
547
 
            self.close(None)
548
 
    
549
 
    def delete_task(self,widget) :
550
 
        if self.delete :
551
 
            result = self.delete(widget,self.task.get_id())
552
 
        #if the task was deleted, we close the window
553
 
        if result : self.window.destroy()
554
 
 
555
 
    
556
 
    #Take the title as argument and return the subtask ID
557
 
    def new_subtask(self,title=None,tid=None) :
558
 
        if tid:
559
 
            self.task.add_subtask(tid)
560
 
        elif title:
561
 
            subt = self.task.new_subtask()
562
 
            subt.set_title(title)
563
 
            tid = subt.get_id()
564
 
            return tid
565
 
 
566
 
    # Create a new task
567
 
    def new_task(self, *args):
568
 
        task = self.req.new_task(tags=None, newtask=True)
569
 
        task_id = task.get_id()
570
 
        self.open_task(task_id)
571
 
        
572
 
    def insert_subtask(self,widget) : #pylint: disable-msg=W0613
573
 
        self.textview.insert_newtask()
574
 
        self.textview.grab_focus()
575
 
        
576
 
    def inserttag_clicked(self,widget) : #pylint: disable-msg=W0613
577
 
        itera = self.textview.get_insert()
578
 
        if itera.starts_line() :
579
 
            self.textview.insert_text("@",itera)
580
 
        else :
581
 
            self.textview.insert_text(" @",itera)
582
 
        self.textview.grab_focus()
583
 
        
584
 
    def inserttag(self,widget,tag) : #pylint: disable-msg=W0613
585
 
        self.textview.insert_tags([tag])
586
 
        self.textview.grab_focus()
587
 
    
588
 
    def save(self) :
589
 
        self.task.set_title(self.textview.get_title())
590
 
        self.task.set_text(self.textview.get_text()) 
591
 
        self.task.sync()
592
 
        if self.config != None:
593
 
            self.config.write()
594
 
        self.time = time.time()
595
 
    #light_save save the task without refreshing every 30seconds
596
 
    #We will reduce the time when the get_text will be in another thread
597
 
    def light_save(self) :
598
 
        #if self.time is none, we never called any save
599
 
        if self.time:
600
 
            diff = time.time() - self.time
601
 
            tosave = diff > GnomeConfig.SAVETIME
602
 
        else:
603
 
            #we don't want to save a task while opening it
604
 
            tosave = self.textview.get_editable()
605
 
            diff = None
606
 
        if tosave:
607
 
            self.save()
608
 
        
609
 
        
610
 
    #This will bring the Task Editor to front    
611
 
    def present(self):
612
 
        self.window.present()
613
 
    def move(self,x,y):
614
 
        try:
615
 
            xx=int(x)
616
 
            yy=int(y)
617
 
            self.window.move(xx,yy)
618
 
        except:
619
 
            pass
620
 
    def get_position(self):
621
 
        return self.window.get_position()
622
 
        
623
 
    def on_move(self,widget,event):
624
 
        #saving the position
625
 
        if self.config != None:
626
 
            tid = self.task.get_id()
627
 
            if not tid in self.config :
628
 
                self.config[tid] = dict()
629
 
            #print "saving task position %s" %str(self.get_position())
630
 
            self.config[tid]["position"] = self.get_position()
631
 
            self.config[tid]["size"] = self.window.get_size()
632
 
        
633
 
    #We define dummy variable for when close is called from a callback
634
 
    def close(self,window=None,a=None,b=None,c=None) : #pylint: disable-msg=W0613
635
 
        #We should also destroy the whole taskeditor object.
636
 
        self.window.destroy()
637
 
    
638
 
    #The destroy signal is linked to the "close" button. So if we call
639
 
    #destroy in the close function, this will cause the close to be called twice
640
 
    #To solve that, close will just call "destroy" and the destroy signal
641
 
    #Will be linked to this destruction method that will save the task
642
 
    def destruction(self,a=None) :#pylint: disable-msg=W0613
643
 
        #Save should be also called when buffer is modified
644
 
        self.pengine.onTaskClose(self.plugins, self.te_plugin_api)
645
 
        self.p_apis.remove(self.te_plugin_api)
646
 
        tid = self.task.get_id()
647
 
        if self.task.is_new():
648
 
            self.req.delete_task(tid)
649
 
        else:
650
 
            self.save()
651
 
            for i in self.task.get_subtasks():
652
 
                i.set_to_keep()
653
 
        self.closing(tid)
654
 
        
655
 
############# Private functions #################
656
 
        
657
 
    
658
 
    def __focus_out(self,w=None,e=None) : #pylint: disable-msg=W0613
659
 
        #We should only close if the pointer click is out of the calendar !
660
 
        p = self.calendar.window.get_pointer()
661
 
        s = self.calendar.get_size()
662
 
        if  not(0 <= p[0] <= s[0] and 0 <= p[1] <= s[1]) :
663
 
            self.__close_calendar()
664
 
        
665
 
    
666
 
    def __close_calendar(self,widget=None,e=None) : #pylint: disable-msg=W0613
667
 
        self.calendar.hide()
668
 
        self.__opened_date = ''
669
 
        gtk.gdk.pointer_ungrab()
670
 
        self.calendar.grab_remove()
671
 
        if self.sigid :
672
 
            self.cal_widget.disconnect(self.sigid)
673
 
            self.sigid = None
674
 
        if self.sigid_month :
675
 
            self.cal_widget.disconnect(self.sigid_month)
676
 
            self.sigid_month = None
677
 
 
678
 
    
679