~saurabhanandiit/gtg/exportFixed

« back to all changes in this revision

Viewing changes to GTG/gtk/browser/browser.py

Merge of my work on liblarch newbase and all the backends ported to liblarch
(which mainly means porting the datastore).
One failing test, will check it.

Show diffs side-by-side

added added

removed removed

Lines of Context:
33
33
 
34
34
#our own imports
35
35
import GTG
 
36
from GTG.backends.backendsignals import BackendSignals
 
37
from GTG.gtk.browser.custominfobar import CustomInfoBar
36
38
from GTG.core                       import CoreConfig
37
39
from GTG                         import _, info, ngettext
38
40
from GTG.core.task               import Task
42
44
from GTG.tools.dates             import no_date,\
43
45
                                        get_canonical_date
44
46
from GTG.tools.logger            import Log
 
47
from GTG.tools.tags              import extract_tags_from_text
45
48
#from GTG.tools                   import clipboard
46
49
 
47
50
 
69
72
        print "%s : %s" %(self.st,time.time() - self.start)
70
73
 
71
74
 
72
 
class TaskBrowser:
 
75
class TaskBrowser(gobject.GObject):
73
76
    """ The UI for browsing open and closed tasks, and listing tags in a tree """
74
77
 
 
78
    __string_signal__ = (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, (str, ))
 
79
    __none_signal__ = (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, tuple())
 
80
    __gsignals__ = {'task-added-via-quick-add' : __string_signal__, \
 
81
                    'visibility-toggled': __none_signal__,
 
82
                   }
 
83
 
75
84
    def __init__(self, requester, vmanager, config):
 
85
        gobject.GObject.__init__(self)
76
86
        # Object prime variables
77
87
        self.priv   = {}
78
88
        self.req    = requester
89
99
 
90
100
 
91
101
        ### YOU CAN DEFINE YOUR INTERNAL MECHANICS VARIABLES BELOW
92
 
        
 
102
 
93
103
        # Setup default values for view
94
104
        self._init_browser_config()
95
105
 
139
149
        #Expand all the tasks in the taskview
140
150
        r = self.vtree_panes['active'].expand_all()
141
151
        self.on_select_tag()
142
 
        self.window.show()
 
152
        self.browser_shown = False
143
153
 
144
154
### INIT HELPER FUNCTIONS #####################################################
145
155
#
153
163
        self.priv['selected_rows']            = None
154
164
        self.priv['workview']                 = False
155
165
        self.priv['filter_cbs']               = []
156
 
        self.priv['quick_add_cbs']            = []
157
166
 
158
167
    def _init_icon_theme(self):
159
168
        icon_dirs = CoreConfig().get_icons_directories()
192
201
        self.sidebar_notebook   = self.builder.get_object("sidebar_notebook")
193
202
        self.main_notebook      = self.builder.get_object("main_notebook")
194
203
        self.accessory_notebook = self.builder.get_object("accessory_notebook")
 
204
        self.vbox_toolbars      = self.builder.get_object("vbox_toolbars")
195
205
        
196
206
        self.closed_pane        = None
197
207
 
306
316
                self.on_nonworkviewtag_toggled,
307
317
            "on_preferences_activate":
308
318
                self.open_preferences,
 
319
            "on_edit_backends_activate":
 
320
                self.open_edit_backends,
309
321
        }
310
322
        self.builder.connect_signals(SIGNAL_CONNECTIONS_DIC)
311
323
 
331
343
        # Connect requester signals to TreeModels
332
344
        self.req.connect("task-added", self.on_task_added) 
333
345
        self.req.connect("task-deleted", self.on_task_deleted)
334
 
 
335
 
        
 
346
        #this causes changed be shouwn only on save
 
347
        #tree = self.task_tree_model.get_tree()
 
348
        #tree.connect("task-added-inview", self.on_task_added) 
 
349
        #tree.connect("task-deleted-inview", self.on_task_deleted)
 
350
        b_signals = BackendSignals()
 
351
        b_signals.connect(b_signals.BACKEND_FAILED, self.on_backend_failed)
 
352
        b_signals.connect(b_signals.BACKEND_STATE_TOGGLED, \
 
353
                          self.remove_backend_infobar)
 
354
        b_signals.connect(b_signals.INTERACTION_REQUESTED, \
 
355
                          self.on_backend_needing_interaction)
336
356
        # Selection changes
337
357
        self.selection = self.vtree_panes['active'].get_selection()
338
358
        self.selection.connect("changed", self.on_task_cursor_changed)
383
403
        self.tag_list = self.req.get_tag_tree().get_all_nodes()
384
404
        for i in self.tag_list:
385
405
            self.tag_list_model.append([i[1:]])
386
 
               
 
406
 
387
407
    def _init_tag_completion(self):
388
408
        #Initialize tag completion.
389
409
        self.tag_completion = gtk.EntryCompletion()
396
416
 
397
417
### HELPER FUNCTIONS ########################################################
398
418
 
399
 
    def open_preferences(self,widget):
 
419
    def open_preferences(self, widget):
400
420
        self.vmanager.open_preferences(self.priv)
401
421
        
 
422
    def open_edit_backends(self, widget):
 
423
        self.vmanager.open_edit_backends()
 
424
 
402
425
    def quit(self,widget=None):
403
426
        self.vmanager.close_browser()
404
427
        
494
517
                    col_id,\
495
518
                    self.priv["tasklist"]["sort_order"])
496
519
            except:
497
 
                print "Invalid configuration for sorting columns"
 
520
                Log.error("Invalid configuration for sorting columns")
498
521
 
499
522
        if "view" in self.config["browser"]:
500
523
            view = self.config["browser"]["view"]
510
533
            for t in odic:
511
534
                ted = self.vmanager.open_task(t)
512
535
 
513
 
    
514
536
    def _start_gtg_maximized(self):
515
537
        #This is needed as a hook point to let the Notification are plugin
516
538
        #start gtg minimized
746
768
            self.show_closed_pane()
747
769
        else:
748
770
            self.hide_closed_pane()
 
771
 
 
772
    def __create_closed_tree(self):
 
773
        closedtree = self.req.get_tasks_tree(name='closed')
 
774
        closedtree.apply_filter('closed')
 
775
        return closedtree
749
776
            
750
777
    def show_closed_pane(self):
751
 
        # The done/dismissed taks treeview
 
778
        # The done/dismissed tasks treeview
752
779
        if not self.vtree_panes.has_key('closed'):
753
 
            closedtree = self.req.get_tasks_tree(name='closed')
754
780
            self.vtree_panes['closed'] = \
755
 
                        self.tv_factory.closed_tasks_treeview(closedtree)
756
 
            closedtree.apply_filter('closed')
 
781
                        self.tv_factory.closed_tasks_treeview(\
 
782
                                                self.__create_closed_tree())
757
783
                    # Closed tasks TreeView
758
784
            self.vtree_panes['closed'].connect('row-activated',\
759
785
                self.on_edit_done_task)
812
838
            self.config['browser']["collapsed_tasks"].append(tid)
813
839
 
814
840
    def on_quickadd_activate(self, widget):
815
 
        text = self.quickadd_entry.get_text()
 
841
        text = unicode(self.quickadd_entry.get_text())
 
842
        due_date = no_date
 
843
        defer_date = no_date
816
844
        if text:
817
845
            tags, notagonly = self.get_selected_tags()
818
846
            task = self.req.new_task(newtask=True)
819
847
            task.set_complex_title(text,tags=tags)
820
848
            self.quickadd_entry.set_text('')
821
 
            for f in self.priv['quick_add_cbs']:
822
 
                f(task)
 
849
            gobject.idle_add(self.emit, "task-added-via-quick-add",
 
850
                             task.get_id())
823
851
 
824
852
    def on_tag_treeview_button_press_event(self, treeview, event):
 
853
        Log.debug("Received button event #%d at %d,%d" %(event.button, event.x, event.y))
825
854
        if event.button == 3:
826
855
            x = int(event.x)
827
856
            y = int(event.y)
888
917
            self.reset_cursor()
889
918
 
890
919
    def on_task_treeview_button_press_event(self, treeview, event):
 
920
        """Pop up context menu on right mouse click in the main task tree view"""
 
921
        Log.debug("Received button event #%d at %d,%d" %(event.button, event.x, event.y))
891
922
        if event.button == 3:
892
923
            x = int(event.x)
893
924
            y = int(event.y)
894
925
            time = event.time
895
926
            pthinfo = treeview.get_path_at_pos(x, y)
896
927
            if pthinfo is not None:
897
 
                if treeview.get_selection().count_selected_rows() <= 0:
898
 
                    path, col, cellx, celly = pthinfo
 
928
                path, col, cellx, celly = pthinfo
 
929
                selection = treeview.get_selection()
 
930
                if selection.count_selected_rows() <= 0:
899
931
                    treeview.set_cursor(path, col, 0)
 
932
                else:
 
933
                    selection.select_path(path)
900
934
                treeview.grab_focus()
901
935
                self.taskpopup.popup(None, None, None, event.button, time)
902
936
            return 1
956
990
        if not tid:
957
991
            #tid_to_delete is a [project,task] tuple
958
992
            tids_todelete = self.get_selected_tasks()
 
993
            if not tids_todelete:
 
994
                return
959
995
        else:
960
996
            tids_todelete = [tid]
961
997
        Log.debug("going to delete %s" % tids_todelete)
1066
1102
                task.set_status(Task.STA_DISMISSED)
1067
1103
 
1068
1104
    def on_select_tag(self, widget=None, row=None, col=None):
1069
 
        #When you clic on a tag, you want to unselect the tasks
 
1105
        #When you click on a tag, you want to unselect the tasks
1070
1106
        taglist, notag = self.get_selected_tags()
1071
1107
        if notag:
1072
1108
            newtag = ["notag"]
1075
1111
                newtag = [taglist[0]]
1076
1112
            else:
1077
1113
                newtag = ['no_disabled_tag']
 
1114
 
1078
1115
        #FIXME:handle multiple tags case
1079
1116
        #We apply filters for every visible ViewTree
1080
1117
        for t in self.vtree_panes:
1308
1345
 
1309
1346
    def hide(self):
1310
1347
        """ Hides the task browser """
 
1348
        self.browser_shown = False
1311
1349
        self.window.hide()
 
1350
        gobject.idle_add(self.emit, "visibility-toggled")
1312
1351
 
1313
1352
    def show(self):
1314
1353
        """ Unhides the TaskBrowser """
 
1354
        self.browser_shown = True
1315
1355
        self.window.present()
1316
1356
        #redraws the GDK window, bringing it to front
1317
1357
        self.window.show()
 
1358
        gobject.idle_add(self.emit, "visibility-toggled")
1318
1359
 
1319
1360
    def iconify(self):
1320
1361
        """ Minimizes the TaskBrowser """
1328
1369
        """ Returns true if window is the currently active window """
1329
1370
        return self.window.get_property("is-active")
1330
1371
 
 
1372
    def get_builder(self):
 
1373
        return self.builder
 
1374
 
 
1375
    def get_window(self):
 
1376
        return self.window
 
1377
 
 
1378
    def get_active_tree(self):
 
1379
        '''
 
1380
        Returns the browser tree with all the filters applied. The tasks in
 
1381
        the tree are the same as the ones shown in the browser current view
 
1382
        '''
 
1383
        return self.activetree
 
1384
 
 
1385
    def get_closed_tree(self):
 
1386
        '''
 
1387
        Returns the browser tree with all the filters applied. The tasks in
 
1388
        the tree are the same as the ones shown in the browser current closed
 
1389
        view.
 
1390
        '''
 
1391
        return self.__create_closed_tree()
 
1392
 
 
1393
    def is_shown(self):
 
1394
        return self.browser_shown
 
1395
 
 
1396
## BACKENDS RELATED METHODS ##################################################
 
1397
 
 
1398
    def on_backend_failed(self, sender, backend_id, error_code):
 
1399
        '''
 
1400
        Signal callback.
 
1401
        When a backend fails to work, loads a gtk.Infobar to alert the user
 
1402
 
 
1403
        @param sender: not used, only here for signal compatibility
 
1404
        @param backend_id: the id of the failing backend 
 
1405
        @param error_code: a backend error code, as specified in BackendsSignals
 
1406
        '''
 
1407
        infobar = self._new_infobar(backend_id)
 
1408
        infobar.set_error_code(error_code)
 
1409
 
 
1410
    def on_backend_needing_interaction(self, sender, backend_id, description, \
 
1411
                                       interaction_type, callback):
 
1412
        '''
 
1413
        Signal callback.
 
1414
        When a backend needs some kind of feedback from the user,
 
1415
        loads a gtk.Infobar to alert the user.
 
1416
        This is used, for example, to request confirmation after authenticating
 
1417
        via OAuth.
 
1418
 
 
1419
        @param sender: not used, only here for signal compatibility
 
1420
        @param backend_id: the id of the failing backend 
 
1421
        @param description: a string describing the interaction needed
 
1422
        @param interaction_type: a string describing the type of interaction
 
1423
                                 (yes/no, only confirm, ok/cancel...)
 
1424
        @param callback: the function to call when the user provides the
 
1425
                         feedback
 
1426
        '''
 
1427
        infobar = self._new_infobar(backend_id)
 
1428
        infobar.set_interaction_request(description, interaction_type, callback)
 
1429
 
 
1430
 
 
1431
    def __remove_backend_infobar(self, child, backend_id):
 
1432
        '''
 
1433
        Helper function to remove an gtk.Infobar related to a backend
 
1434
 
 
1435
        @param child: a gtk.Infobar
 
1436
        @param backend_id: the id of the backend which gtk.Infobar should be
 
1437
                            removed.
 
1438
        '''
 
1439
        if isinstance(child, CustomInfoBar) and\
 
1440
            child.get_backend_id() == backend_id:
 
1441
            if self.vbox_toolbars:
 
1442
                self.vbox_toolbars.remove(child)
 
1443
 
 
1444
    def remove_backend_infobar(self, sender, backend_id):
 
1445
        '''
 
1446
        Signal callback.
 
1447
        Deletes the gtk.Infobars related to a backend
 
1448
 
 
1449
        @param sender: not used, only here for signal compatibility
 
1450
        @param backend_id: the id of the backend which gtk.Infobar should be
 
1451
                            removed.
 
1452
        '''
 
1453
        backend = self.req.get_backend(backend_id)
 
1454
        if not backend or (backend and backend.is_enabled()):
 
1455
            #remove old infobar related to backend_id, if any
 
1456
            if self.vbox_toolbars:
 
1457
                self.vbox_toolbars.foreach(self.__remove_backend_infobar, \
 
1458
                                       backend_id)
 
1459
 
 
1460
    def _new_infobar(self, backend_id):
 
1461
        '''
 
1462
        Helper function to create a new infobar for a backend
 
1463
        
 
1464
        @param backend_id: the backend for which we're creating the infobar
 
1465
        @returns gtk.Infobar: the created infobar
 
1466
        '''
 
1467
        #remove old infobar related to backend_id, if any
 
1468
        if not self.vbox_toolbars:
 
1469
            return
 
1470
        self.vbox_toolbars.foreach(self.__remove_backend_infobar, backend_id)
 
1471
        #add a new one
 
1472
        infobar = CustomInfoBar(self.req, self, self.vmanager, backend_id)
 
1473
        self.vbox_toolbars.pack_start(infobar, True)
 
1474
        return infobar