~toineo/unity-lens-applications/calculator

« back to all changes in this revision

Viewing changes to src/runner.vala

  • Committer: Antoine Voizard
  • Date: 2011-08-12 07:50:17 UTC
  • mfrom: (212.1.7 unity-lens-applications)
  • Revision ID: tux.toineo@gmail.com-20110812075017-cizb0bkzd006ip0g
Remerged against njpatel's and didrocks' work

Show diffs side-by-side

added added

removed removed

Lines of Context:
21
21
using Gee;
22
22
 
23
23
 
24
 
namespace Unity.ApplicationsPlace {
 
24
namespace Unity.ApplicationsLens {
25
25
 
26
26
  private class AboutEntry {
27
27
    public string name;
39
39
  public class Runner: GLib.Object
40
40
  {  
41
41
  
42
 
    private Unity.ApplicationsPlace.Daemon daemon;    
43
 
    private const string BUS_NAME_PREFIX = "com.canonical.Unity.ApplicationsPlace.Runner";
 
42
    private Unity.ApplicationsLens.Daemon daemon;    
 
43
    private const string BUS_NAME_PREFIX = "com.canonical.Unity.ApplicationsLens.Runner";
44
44
    
45
45
    /* for now, load the same keys as gnome-panel */
46
46
    private const string HISTORY_KEY = "history";
47
47
    private const int    MAX_HISTORY = 10;
48
48
    
49
 
    public Unity.PlaceEntryInfo place_entry;
 
49
    public Unity.Lens lens;
 
50
    public Unity.Scope scope;
50
51
 
51
52
    private bool all_models_synced;
52
53
 
53
54
    /* We remember the previous search so we can figure out if we should do
54
55
     * incremental filtering of the result models */
55
 
    private PlaceSearch? previous_search;    
 
56
    private LensSearch? previous_search;    
56
57
    
57
58
    private Gee.HashMap<string,AboutEntry> about_entries;
58
59
    private Gee.List<string> executables;
62
63
    
63
64
    private EquationParser parser;
64
65
    
65
 
    public Runner (Unity.ApplicationsPlace.Daemon daemon)
 
66
    public Runner (Unity.ApplicationsLens.Daemon daemon)
66
67
    {
67
 
      var sections_model = new Dee.SharedModel(BUS_NAME_PREFIX + "SectionsModel");
68
 
      sections_model.set_schema ("s", "s");
69
 
 
70
 
      var groups_model = new Dee.SharedModel(BUS_NAME_PREFIX + "GroupsModel");
71
 
      groups_model.set_schema ("s", "s", "s");
72
 
 
73
 
      var results_model = new Dee.SharedModel(BUS_NAME_PREFIX + "ResultsModel");
74
 
      results_model.set_schema ("s", "s", "u", "s", "s", "s");
75
 
    
76
 
      place_entry = new PlaceEntryInfo ("/com/canonical/unity/applicationsplace/runner");
77
 
      place_entry.sections_model = sections_model;
78
 
      place_entry.entry_renderer_info.groups_model = groups_model;
79
 
      place_entry.entry_renderer_info.results_model = results_model;
80
 
 
 
68
      /* First create scope */
 
69
      scope = new Unity.Scope ("/com/canonical/unity/scope/commands");
 
70
      scope.search_in_global = false;
 
71
            
 
72
      /* Listen for changes to the lens scope search */
 
73
      scope.notify["active-search"].connect (
 
74
        (obj, pspec) => {
 
75
          var search = scope.active_search;
 
76
 
 
77
          if (!Utils.search_has_really_changed (previous_search, search))
 
78
            return;
 
79
 
 
80
          update_search.begin(search);
 
81
          previous_search = search;
 
82
        }
 
83
      );
 
84
 
 
85
      /* Re-do the search if the filters changed */
 
86
      scope.filters_changed.connect (
 
87
        () => {
 
88
          var search = scope.active_search;
 
89
          if (search.search_string == null)
 
90
            return;
 
91
 
 
92
          update_search.begin(search);
 
93
          previous_search = search;
 
94
          
 
95
        }
 
96
      );
 
97
 
 
98
      // Listen for when the lens is hidden/shown by the Unity Shell
 
99
      scope.notify["active"].connect(
 
100
              (obj, pspec) => {
 
101
                if (scope.active)
 
102
                {
 
103
            var search = scope.active_search;
 
104
            update_search.begin(search);
 
105
            previous_search = search;
 
106
          }
 
107
              }
 
108
      );
 
109
 
 
110
      scope.activate_uri.connect (daemon.activate);
 
111
 
 
112
       /* Now create Lens */
 
113
      lens = new Unity.Lens ("/com/canonical/unity/lens/commands", "commands");
 
114
      lens.search_hint = _("Run a command");
 
115
      lens.visible = false;
 
116
      lens.search_in_global = false;
 
117
      populate_categories ();
 
118
      //populate_filters();
 
119
      lens.add_local_scope (scope);
 
120
      lens.export ();
81
121
      previous_search = null;
82
122
      
83
123
      /* create the private about entries */
86
126
      
87
127
      executables = new Gee.ArrayList<string> ();
88
128
      find_system_executables.begin ();
89
 
      
 
129
 
90
130
      this.gp_settings = new Settings ("com.canonical.Unity.Runner");
91
131
      history = new Gee.ArrayList<string> ();
92
132
      load_history ();
93
133
      
94
134
      parser = new EquationParser ();
95
135
      
96
 
      /* Listen for changes to the place entry search */
97
 
      place_entry.notify["active-search"].connect (
98
 
        (obj, pspec) => {
99
 
          if (!all_models_synced)
100
 
            return;
101
 
 
102
 
          var search = place_entry.active_search;
103
 
          
104
 
          if (!Utils.search_has_really_changed (previous_search, search))
105
 
            return;
106
 
          
107
 
          update_search.begin(search);
108
 
          previous_search = search;
109
 
        }
110
 
      );
111
 
      
112
 
      // Listen for when the place is hidden/shown by the Unity Shell
113
 
      place_entry.notify["active"].connect(
114
 
              (obj, pspec) => {
115
 
                if (place_entry.active)
116
 
                {
117
 
            var search = place_entry.active_search;
118
 
            update_search.begin(search);
119
 
            previous_search = search;
120
 
          }
121
 
              }
122
 
      );
123
 
 
124
 
      /* We should not start manipulating any of our models before they are
125
 
       * all synchronized. When they are we set all_models_synced = true */
126
 
      sections_model.notify["synchronized"].connect (check_models_synced);
127
 
      groups_model.notify["synchronized"].connect (check_models_synced);
128
 
      results_model.notify["synchronized"].connect (check_models_synced);
129
 
      all_models_synced = false;
130
 
 
131
136
      this.daemon = daemon;
132
137
    
133
138
    }
134
139
 
135
 
    private async void update_search (PlaceSearch? search)
136
 
    {
137
 
      var model = place_entry.entry_renderer_info.results_model;
 
140
    private void populate_categories ()
 
141
    {
 
142
      Unity.Category[] categories = {};
 
143
 
 
144
      var cat = new Unity.Category (_("Results"),
 
145
                                    ICON_PATH + "category-installed.svg");
 
146
      categories += cat;
 
147
 
 
148
      cat = new Unity.Category (_("History"),
 
149
                                ICON_PATH + "category-available.svg");
 
150
 
 
151
      categories += cat;
 
152
 
 
153
      lens.categories = categories;
 
154
    }
 
155
 
 
156
    private async void update_search (LensSearch? search)
 
157
    {
 
158
      var model = scope.results_model;
138
159
      model.clear ();
139
160
 
140
 
      var search_string = search.get_search_string ();
 
161
      var search_string = search.search_string;
141
162
      bool has_search = !Utils.search_is_invalid (search);
142
 
              
 
163
      
143
164
      /* Prevent concurrent searches and concurrent updates of our models,
144
165
       * by preventing any notify signals from propagating to us.
145
 
       * Important: Remember to thaw the notifys with release_entrylock()! */
146
 
      place_entry.freeze_notify ();
 
166
       * Important: Remember to thaw the notifys with release_scopelock()! */
 
167
      scope.freeze_notify ();
147
168
 
148
169
      if (!has_search)
149
170
      {
150
 
        display_history (ref model); 
151
 
        release_entrylock ();
 
171
        display_history (ref model);
 
172
        release_scopelock ();
152
173
        search.finished ();
153
174
        return;
154
175
      }
183
204
               dirs_match.size, executables_match.size, timer.elapsed ()*1000, search_string);      
184
205
      }
185
206
      
186
 
      release_entrylock ();
 
207
      release_scopelock ();
187
208
      search.finished ();
188
209
    }
189
210
    
190
 
    /* TODO : once computed, let's do something with the result (i.e copy it to the input line,
191
 
     * or copy it to the system buffer) */
192
 
    private bool process_computation (ref string search_string, ref Dee.Model model, out double result)
 
211
    private bool process_computation (ref string search_string, ref Dee.SharedModel model, out double result)
193
212
    {
194
213
      if (search_string.has_prefix ("="))
195
214
      {
222
241
          {
223
242
            result_string = _("Unable to compute");
224
243
            debug ("Unable to calculate that expression : " + search_string);
225
 
          }              
 
244
          }
226
245
        } 
227
246
        else // Empty string case
228
247
        {
235
254
                      result_string,
236
255
                      null);
237
256
        
238
 
        return true;      
 
257
        return true;
239
258
      }
240
259
      
241
260
      else return false;
267
286
      }
268
287
    }
269
288
    
270
 
    private void process_command (ref string search_string, ref Dee.Model model, ref Gee.ArrayList<string> dirs_match, ref Gee.ArrayList<string> executables_match)
271
 
    {      
 
289
    private void process_command (ref string search_string, ref Dee.SharedModel model, ref Gee.ArrayList<string> dirs_match, ref Gee.ArrayList<string> executables_match)
 
290
    {
272
291
      string uri;
273
292
      Icon icon;
274
293
      string mimetype;
275
294
      string display_name;
276
 
      var group_id = RunnerGroup.HISTORY; 
 
295
      var category_id = RunnerCategory.HISTORY;
277
296
      
278
 
      display_history (ref model);      
 
297
      display_history (ref model);
279
298
      
280
299
      /* manual seek with directory and executables result */
281
300
      if (search_string.has_prefix ("/") || search_string.has_prefix ("~"))
325
344
        catch (Error err) {
326
345
          warning("Error listing directory executables: %s\n", err.message);
327
346
        }
 
347
 
328
348
      }
329
349
      /* complete again system executables */
330
350
      else
341
361
      executables_match.sort ();
342
362
      dirs_match.sort ();
343
363
      
344
 
      group_id = RunnerGroup.RESULTS;
 
364
      category_id = RunnerCategory.RESULTS;
345
365
 
346
366
      // populate results
347
 
      // 1. enable launching the exact search string
348
 
      display_name = get_icon_uri_and_mimetype (search_string, out icon, out uri, out mimetype);
349
 
      model.append (uri.strip (), icon.to_string (),
350
 
                    group_id, mimetype,
351
 
                    display_name,
352
 
                    null);
 
367
      // 1. enable launching the exact search string if no other result
 
368
      if ((executables_match.size == 0) && (dirs_match.size == 0))
 
369
      {
 
370
        display_name = get_icon_uri_and_mimetype (search_string, out icon, out uri, out mimetype);
 
371
        model.append (uri.strip (), icon.to_string (),
 
372
                      category_id, mimetype,
 
373
                      display_name,
 
374
                      null);
 
375
      }
353
376
      
354
377
      // 2. add possible directories (we don't store them)
355
378
      mimetype = "inode/directory";
358
381
      {
359
382
        uri = @"unity-runner://$(dir)";
360
383
        model.append (uri, icon.to_string (),
361
 
                      group_id, mimetype,
 
384
                      category_id, mimetype,
362
385
                      dir,
363
386
                      null);        
364
387
      }
371
394
        display_name = get_icon_uri_and_mimetype (final_exec, out icon, out uri, out mimetype);   
372
395
        
373
396
        model.append (uri, icon.to_string (),
374
 
                      group_id, mimetype,
 
397
                      category_id, mimetype,
375
398
                      display_name,
376
399
                      null);
377
400
      }
378
401
    }
379
402
    
380
 
    private bool process_easter (ref string search_string, ref Dee.Model model)
 
403
    private bool process_easter (ref string search_string, ref Dee.SharedModel model)
381
404
    {
 
405
      /* no easter egg in unity */
382
406
      if (search_string == "free the fish")
383
407
      {
384
408
        string uri = "no-easter-egg";
404
428
      return true;
405
429
    }
406
430
    
407
 
    private void display_history (ref Dee.Model model)
 
431
    private void display_history (ref Dee.SharedModel model)
408
432
    {
409
433
      string uri;
410
434
      Icon icon;
411
435
      string mimetype;
412
436
      string display_name;
413
 
      var group_id = RunnerGroup.HISTORY; 
 
437
      var category_id = RunnerCategory.HISTORY;
414
438
      
415
439
      foreach (var command in this.history)
416
440
      {          
417
441
        display_name = get_icon_uri_and_mimetype (command, out icon, out uri, out mimetype);   
418
442
        model.append (uri, icon.to_string (),
419
 
                      group_id, mimetype,
 
443
                      category_id, mimetype,
420
444
                      display_name,
421
445
                      null);
422
 
      }  
 
446
      }
423
447
    }
424
448
    
425
 
    private void release_entrylock ()
 
449
    private void release_scopelock ()
426
450
    {
427
451
       /* Allow new searches once we enter an idle again.
428
452
       * We don't do it directly from here as that could mean we start
429
453
       * changing the model even before we had flushed out current changes
430
454
       */
431
455
      Idle.add (() => {
432
 
        place_entry.thaw_notify ();
 
456
        scope.thaw_notify ();
433
457
        return false;
434
458
      });       
435
459
    }
512
536
      return exec_string;
513
537
      
514
538
    }
515
 
 
516
 
    
517
 
    /* The check_models_synced() method acts like a latch - once all models
518
 
     * have reported themselves to be synchronized we set
519
 
     * all_models_synced = true and tell the searches to re-check their state
520
 
     * as they should refuse to run when all_models_synced == false */
521
 
    private void check_models_synced (Object obj, ParamSpec pspec)
522
 
    {
523
 
      if ((place_entry.sections_model as Dee.SharedModel).synchronized &&
524
 
          (place_entry.entry_renderer_info.groups_model as Dee.SharedModel).synchronized &&
525
 
          (place_entry.entry_renderer_info.results_model as Dee.SharedModel).synchronized) {
526
 
        if (all_models_synced == false)
527
 
          {
528
 
            all_models_synced = true;
529
 
            
530
 
            populate_groups (place_entry.entry_renderer_info.groups_model);
531
 
            
532
 
            /* Emitting notify here will make us recheck if the search results
533
 
             * need update. In the negative case this is a noop */
534
 
            place_entry.notify_property ("active-search");
535
 
          }
536
 
      }
537
 
    }
538
 
    
539
 
    private void populate_groups (Dee.Model groups)
540
 
    {
541
 
      if (groups.get_n_rows() != 0)
542
 
        {
543
 
          debug ("The groups model already populated. We probably cloned it off Unity. Rebuilding.");
544
 
          groups.clear ();
545
 
        }
546
 
 
547
 
      // TODO: needs asset
548
 
      groups.append ("UnityDefaultRenderer",
549
 
                     _("Results"),
550
 
                     ICON_PATH + "group-installed.svg");
551
 
      groups.append ("UnityDefaultRenderer",
552
 
                     _("History"),
553
 
                     ICON_PATH + "group-available.svg");
554
 
 
555
 
      /* expand the history */
556
 
      place_entry.entry_renderer_info.set_hint ("ExpandedGroups",
557
 
                                                 @"$((uint)RunnerGroup.HISTORY)");
558
 
    }
559
 
    
 
539
 
 
540
 
560
541
    public void add_history (string last_command)
561
542
    {
562
543
 
577
558
        i++;
578
559
      }
579
560
      this.history = new_history;
580
 
      
 
561
 
581
562
      // store in gsettings
582
563
      try
583
564
      {
589
570
      }
590
571
      
591
572
      // force a search to refresh history order (TODO: be more clever in the future)
592
 
      update_search.begin(place_entry.active_search);
 
573
      update_search.begin(scope.active_search);
593
574
    }
594
575
    
595
576
    private void load_history ()