More actions to make valaunch gtk+3.0 ready
[valaunch:valaunch.git] / src / valaunch-window.vala
1 /*
2  * Copyright (C) 2005 Imendio AB
3  * Copyright (C) 2010 Jörn Magens
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License as
7  * published by the Free Software Foundation; either version 2 of the
8  * License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public
16  * License along with this library; if not, write to the
17  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18  * Boston, MA 02111-1307, USA.
19  */
20
21 private class Valaunch.Window : Gtk.Window {
22         private const int SEARCH_TIMEOUT_BASE = 50;  // miliseconds
23         private const int SEARCH_TIMEOUT_MAX = 700;  // miliseconds
24         private const int SEARCH_TIMEOUT_STEP = 200; // miliseconds
25         private const int SEARCH_RESET_TIMEOUT = 3;  // seconds
26         
27         private const string START_SEARCHING = _("Type to start searching");
28         
29         private Focus _focus;
30         
31         private ModuleManager manager;
32
33         public enum Focus {
34                 BOX1,
35                 BOX2,
36                 BOX3
37         }
38         
39         private enum Column {
40                 DISPLAYABLE,
41                 PIXBUF,
42                 NAME,
43                 NUM_COLS
44         }
45         
46         public new Focus focus {
47                 get {
48                         return _focus;
49                 }
50                 set {
51                         Gtk.ListStore store;
52                         if(this._focus == value) {
53                                 return;
54                         }
55                         if(value == Focus.BOX2) {
56                                 if(this.current_action == null) {
57                                         return;
58                                 }
59                         }
60                         store =(Gtk.ListStore)this.result_treeview.get_model();
61                         store.clear();
62                         if(value == Focus.BOX2) {
63                                 /* Save the search string so that we can reuse it when tabbing
64                                  * back to the icon box.
65                                  */
66                                 this.last_item_search_str = this.search_string.str;
67                                 this.search_string.truncate(0);
68                         }
69                         else {
70                                 /* Reset the search string. */
71                                 this.search_string.truncate(0);
72                                 if(this.last_item_search_str != null) {
73                                         this.search_string.append(this.last_item_search_str);
74                                 }
75                         }
76                         this.item_icon_box.focused = (value == Focus.BOX1);
77                         this.action_icon_box.focused = (value == Focus.BOX2);
78                         this._focus = value;
79                         queue_search();
80                 }
81         }
82
83         private StringBuilder search_string;
84         private string last_item_search_str;
85
86         private unowned Valaunch.Item current_item;
87         private unowned Valaunch.Action current_action;
88
89         public new Valaunch.Frame frame;
90
91         private Gtk.TreeView result_treeview;
92         private Gtk.Alignment instruction;
93         private Gtk.Label instr_label;
94         private Gtk.ScrolledWindow result_sw;
95         private Gtk.HBox result_hbox;
96
97         private Valaunch.IconBox item_icon_box;
98         private Valaunch.IconBox action_icon_box;
99
100         private Gdk.Pixbuf empty_pixbuf;
101         private Gdk.Pixbuf unknown_pixbuf;
102
103         private uint search_timeout_id;
104
105         private bool reset_search;
106         private uint search_reset_timeout_id;
107
108         private bool delayed_activation;
109         private bool can_rgba;
110
111         private bool _transparent;
112         public bool transparent { 
113                 get {
114                         return _transparent;
115                 }
116                 set {
117                         if(this._transparent == value) 
118                                 return;
119
120                         if(value && !this.can_rgba) {
121                                 warning("Cannot paint window transparent(no rgba)");
122                                 return;
123                         }
124                         this._transparent = value;
125                         this.action_icon_box.transparent = value;
126                         this.item_icon_box.transparent   = value;
127                         if(value) {
128                                 this.frame.fill = true;
129                                 this.instr_label.modify_fg(Gtk.StateType.NORMAL, this.instr_label.style.white);
130                                 this.result_sw.set_shadow_type(Gtk.ShadowType.NONE);
131                         }
132                         else {
133                                 this.instr_label.modify_fg(Gtk.StateType.NORMAL, this.instr_label.style.fg[Gtk.StateType.NORMAL]);
134                                 this.frame.fill = false;
135                                 this.result_sw.set_shadow_type(Gtk.ShadowType.IN);
136                         }
137                         
138                         if(gtk_widget_is_drawable(this)) //this.is_drawable())
139                                 this.queue_draw();
140                 }
141         }
142
143         public Window(Valaunch.ModuleManager _manager, bool _transparent) {
144                 GLib.Object(type : Gtk.WindowType.POPUP);
145                 this.manager = _manager;
146
147                 Gdk.Color color = Gdk.Color();
148                 Gtk.CellRenderer cell;
149                 Gtk.TreeSelection selection;
150                 int xpad, ypad;
151                 string tmp;
152         
153                 this.set_app_paintable(true);
154                 this.set_keep_above(false);
155                 
156                 set_colormap();
157                 
158                 this.focus = Focus.BOX1;
159                 
160                 this.search_timeout_id = 0;
161                 this.search_reset_timeout_id = 0;
162                 this.reset_search = false;
163                 this.delayed_activation = false;
164                 
165                 this.search_string = new StringBuilder();
166                 
167                 this.empty_pixbuf = new Gdk.Pixbuf(Gdk.Colorspace.RGB, true, 8, ICON_SIZE, ICON_SIZE);
168                 this.empty_pixbuf.fill(0x00000000);
169                 
170                 this.unknown_pixbuf = new Gdk.Pixbuf(Gdk.Colorspace.RGB, true, 8, ICON_SIZE, ICON_SIZE);
171                 
172                 this.unknown_pixbuf.fill(0x00000000);
173                 
174                 this.frame = new Valaunch.Frame();
175                 
176                 color.red = color.green = color.blue = 0;
177                 
178                 this.frame.fill_color = color;
179                 this.frame.fill_alpha = (uint16)(uint16.MAX * 0.85);
180                 
181                 this.add(this.frame);
182                 this.frame.show();
183                 
184                 Gtk.VBox vbox = new Gtk.VBox(false, 6);
185                 vbox.set_border_width(18);
186                 vbox.show();
187                 
188                 this.frame.add(vbox);
189                 
190                 this.result_hbox = new Gtk.HBox(false, 12);
191                 this.result_hbox.show();
192                 vbox.pack_start(this.result_hbox, false, false, 0);
193
194                 this.item_icon_box = new IconBox(null, this.empty_pixbuf);
195                 this.item_icon_box.focused = true;
196                 
197                 this.item_icon_box.show();
198                 this.result_hbox.pack_start(this.item_icon_box, true, true, 0);
199
200                 this.action_icon_box = new IconBox(null, this.empty_pixbuf);
201                 this.action_icon_box.focused = false;
202                 this.action_icon_box.show();
203                 this.result_hbox.pack_start(this.action_icon_box, true, true, 0);
204
205                 this.instruction = new Gtk.Alignment(0.5f, 0.5f, 1, 1);
206                 this.instruction.set_padding( 8, 0, 0, 0);
207                 tmp = ("<i>%s</i>").printf(_("Type to start searching"));
208                 var label = new Gtk.Label(tmp);
209                 label.set_use_markup(true);
210                 label.show();
211                 this.instruction.show();
212                 this.instruction.add(label);
213                 vbox.pack_start(this.instruction, false, false, 0);
214                 this.instr_label = label;
215                 
216                 this.result_sw = new Gtk.ScrolledWindow(null, null);
217
218                 
219                 /* Note: kind of arbitrary value here, seems to show exactly 4 lines, at
220                  * least for clearlooks.
221                  */
222                 this.result_sw.set_size_request(-1,(ICON_SIZE + 4) * 4 + 2);
223
224                 this.result_sw.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC);
225
226                 this.result_sw.set_shadow_type(Gtk.ShadowType.IN);
227         
228                 vbox.pack_start(this.result_sw, true, true, 0);
229
230                 this.result_treeview = new Gtk.TreeView();
231                 this.result_treeview.set_enable_search(false);
232                 this.result_treeview.set_headers_visible(false);
233                 this.result_treeview.show();
234
235                 this.result_sw.add(this.result_treeview);
236
237                 var store = new Gtk.ListStore(Column.NUM_COLS, typeof(Valaunch.IDisplayable), typeof(Gdk.Pixbuf), typeof(string));
238                 this.result_treeview.set_model(store);
239                 var column = new Gtk.TreeViewColumn();
240
241                 cell = new Gtk.CellRendererPixbuf();
242         
243                 column.pack_start(cell, false);
244         
245                 cell.cell_background = "white";
246                 
247                 xpad = (int)cell.xpad;
248                 ypad = (int)cell.ypad;
249                 
250                 cell.set_fixed_size(-1, ICON_SIZE - ypad);
251                 
252                 column.add_attribute(cell, "pixbuf", Column.PIXBUF);
253                 
254                 var celltext =  new Gtk.CellRendererText();
255                 celltext.ellipsize = Pango.EllipsizeMode.END;
256                 column.pack_start(celltext, true);
257                 
258                 column.add_attribute(celltext,
259                                                         "markup",
260                                                         Column.NAME);
261                 
262                 this.result_treeview.append_column(column);
263                 
264                 selection = this.result_treeview.get_selection();
265                 
266                 this.transparent = true;
267                 
268                 selection.changed.connect(result_row_selected_cb);
269                 this.key_press_event.connect(key_press_event_cb);
270                 this.screen_changed.connect(screen_changed_cb);
271                 this.realize.connect(realize_cb);
272                 this.expose_event.connect(expose_cb);
273
274                 this.transparent = _transparent;
275         }
276         
277         ~Window() {
278                 if(this.search_timeout_id != 0)
279                         Source.remove(this.search_timeout_id);
280                 
281                 if(this.search_reset_timeout_id != 0)
282                         Source.remove(this.search_reset_timeout_id);
283                 
284         }
285
286         private void realize_cb(Gtk.Widget widget) {
287                 this.set_position();
288         }
289
290         private void screen_changed_cb(Gtk.Widget widget, Gdk.Screen old_screen) {
291                 this.set_colormap();
292         }
293 //Cairo.Surface sr_active;
294 //int surface_height = 0;
295         private bool expose_cb(Gdk.EventExpose event) {
296                 Cairo.Context cr;
297                 
298                 if(!this.transparent) {
299                         Gtk.paint_box(this.style,
300                                       this.get_window(),
301                                       Gtk.StateType.NORMAL, 
302                                       Gtk.ShadowType.IN,
303                                       event.area,
304                                       this,
305                                       "base",
306                                       0, 0, -1, -1);
307                         return false;
308                 }
309                 
310                 cr = Gdk.cairo_create(this.get_window());
311                 
312                 
313                 cr.rectangle(event.area.x,
314                                          event.area.y,
315                                          event.area.width,
316                                          event.area.height);
317                 cr.clip();
318                 
319                 /* fully transparent */
320                 cr.set_source_rgba(1.0, 1.0, 1.0, 0.0);
321                 
322                 cr.set_operator(Cairo.Operator.SOURCE);
323                 cr.paint();
324
325 //              if (sr_active == null || surface_height != event.area.height) {
326 //                      surface_height = event.area.height;
327 //                      sr_active = new Cairo.Surface.similar (cr.get_target(), cr.get_target().get_content() , event.area.width, event.area.height);
328 //                      Cairo.Context c2 = new Cairo.Context (sr_active);
329 ////                    c2.SetRoundedRectanglePath (0, 0, event.area.width, event.area.height, parent.WindowRadius*.6);
330 //                      cr.move_to (10, 0);
331 //                      cr.arc (event.area.width-10, 10, 10, Math.PI*1.5, Math.PI*2);
332 //                      cr.arc (event.area.width-10, event.area.height-10, 10, 0, Math.PI*0.5);
333 //                      cr.arc (10, event.area.height-10, 10, Math.PI*0.5, Math.PI);
334 //                      cr.arc (10, 10, 10, Math.PI, Math.PI*1.5);
335 //                      
336 //                      Cairo.Pattern lg = new Cairo.Pattern.linear (0, 0, 0, event.area.height);
337 //                      lg.add_color_stop_rgba (0, 1.0, 1.0, 1.0, 0.0);
338 //                      lg.add_color_stop_rgba (0.4, 1.0, 1.0, 1.0, 0.0);
339 //                      lg.add_color_stop_rgba (1, 1.0, 1.0, 1.0, 0.3);
340 //                      c2.set_source(lg);
341 //                      c2.fill ();
342 //                      lg.Destroy ();
343 //                      (c2 as IDisposable).Dispose ();
344 //              }
345                 
346                 
347 //              if (!focused)
348 //                      return;
349 //              cr.set_source_surface(sr_active, event.area.x,  event.area.y);
350 //              cr.paint ();
351                         
352 //              //---
353 //              cr.move_to (event.area.x + event.area.width - 25, event.area.y + 12);
354 //              cr.line_to (event.area.x + event.area.width - 15,  event.area.y + 12);
355 //              cr.line_to (event.area.x + event.area.width - 20, event.area.y + 17);
356 //              cr.set_source_rgba(1.0, 1.0, 1.0, 1.0);
357 //              cr.fill ();
358 //              //---
359                 return false;
360         }
361
362 //      public static void SetRoundedRectanglePath(this Context cr, double x, double y, 
363 //                                                     double width, double height, double radius)
364 //              {
365 //                      cr.MoveTo (x+radius, y);
366 //                      cr.Arc (x+width-radius, y+radius, radius, Math.PI*1.5, Math.PI*2);
367 //                      cr.Arc (x+width-radius, y+height-radius, radius, 0, Math.PI*.5);
368 //                      cr.Arc (x+radius, y+height-radius, radius, Math.PI*.5, Math.PI);
369 //                      cr.Arc (x+radius, y+radius, radius, Math.PI, Math.PI*1.5);
370 //              }
371         // make current item point to the current item
372         private void set_item(Valaunch.Item? item, string? match) {
373                 if(this.current_item == item)
374                         return;
375                 
376                 if(this.current_item != null)
377                         this.current_item = null;
378                 
379                 if(item != null) {
380                         string markup = null;
381                         Valaunch.Collection<Valaunch.Action> actions = null;
382                         
383                         this.current_item = item;
384                         
385                         if(match != null) 
386                                 markup = string_markup_substring(item.display_name, match, "u");
387                         
388                         if(markup == null) 
389                                 markup = Markup.escape_text(item.display_name, -1);
390                         
391                         this.item_icon_box.caption = markup;
392                         this.item_icon_box.pixbuf = (item.pixbuf != null) ? item.pixbuf : this.unknown_pixbuf;
393                         
394                         actions = item.get_actions(null);
395                         
396                         if(actions == null) {
397                                 this.manager.set_actions(item);
398                                 actions = item.get_actions(null);
399                         }
400                         if(actions != null && actions.get_size() > 0) {
401                                 this.set_action(actions[0], null);
402                         }
403                         else {
404                                 this.set_action(null, null);
405                         }
406                 }
407                 else {
408                         this.item_icon_box.caption = null;
409                         this.item_icon_box.pixbuf = this.empty_pixbuf;
410                         this.set_action(null, null);
411                 }
412         }
413
414         // This function starts an action if delayed_activation has been set before
415         // (via return) or it sets up the action's name and pixbuf' displayed in the 
416         // action icon box.
417         private void set_action(Valaunch.Action? action, string? match) {
418                 if(action != null && this.delayed_activation) {
419                         /* If the user hit enter before getting any
420                          * hits, we just activate the first hit that is
421                          * found.
422                          */
423                         if(this.search_timeout_id != 0) {
424                                 Source.remove(this.search_timeout_id);
425                                 this.search_timeout_id = 0;
426                         }
427                         action.activate();
428                         hide_search_results();
429                         this.hide();
430                         this.delayed_activation = false;
431                         reset_result();
432                         return;
433                 }
434
435                 if(this.current_action == action) {
436                         return;
437                 }
438
439                 if(this.current_action != null) {
440                         this.current_action = null;
441                 }
442
443                 if(action != null) {
444                         string markup = null;
445                         this.current_action = action;
446                         
447                         if(match != null) 
448                                 markup = string_markup_substring(action.display_name, match, "u");
449                         
450                         if(markup == null) 
451                                 markup = Markup.escape_text(action.display_name, -1);
452                         
453                         this.action_icon_box.caption = markup;
454                         this.action_icon_box.pixbuf = action.pixbuf != null ? action.pixbuf : this.unknown_pixbuf;
455                 }
456                 else {
457                         this.action_icon_box.caption = null;
458                         this.action_icon_box.pixbuf = this.empty_pixbuf;
459                 }
460         }
461
462         private void queue_search() {
463                 unowned Valaunch.IDisplayable current;
464                 unowned Valaunch.IconBox icon_box;
465                 int timeout;
466                 
467                 if(this.focus == Valaunch.Window.Focus.BOX1) {
468                         current  = (Valaunch.IDisplayable)this.current_item;
469                         icon_box = (Valaunch.IconBox)this.item_icon_box;
470                 }
471                 else { // ACTION
472                         current  = (Valaunch.IDisplayable)this.current_action;
473                         icon_box = (Valaunch.IconBox)this.action_icon_box;
474                 }
475                 
476                 /* If we already have a match, try matching further on it. */
477                 if(current != null) {
478                         if(string_has_substring(current.display_name, this.search_string.str)) {
479                                 string markup;
480                                 markup = string_markup_substring(current.display_name, this.search_string.str, "u");
481                                 icon_box.caption = markup != null ? markup : current.display_name;
482                         }
483                         else {
484                                 if(this.focus == Focus.BOX1) {
485                                         set_item(null, null);
486                                         set_action(null, null); //JM
487                                 }
488                                 else {
489                                         set_action(null, null);
490                                 }
491                                 
492                                 icon_box.caption = this.search_string.str;
493                         }
494                 }
495                 else {
496                         icon_box.caption = this.search_string.str;
497                 }
498                 
499                 if(this.search_timeout_id != 0)
500                         Source.remove(this.search_timeout_id);
501                 
502                 timeout = SEARCH_TIMEOUT_BASE + SEARCH_TIMEOUT_MAX - SEARCH_TIMEOUT_STEP * (int)this.search_string.str.length;
503                 timeout = timeout.clamp(SEARCH_TIMEOUT_BASE, SEARCH_TIMEOUT_MAX);
504                 if(this.focus == Valaunch.Window.Focus.BOX2)
505                         timeout = 10;
506                 this.search_timeout_id = Timeout.add(timeout, search_timeout_cb);
507         }
508
509         private bool search_timeout_cb() {
510                 this.search_timeout_id = 0;
511                 perform_search(this.search_string.str);
512                 return false;
513         }
514
515         private bool search_reset_timeout_cb() {
516                 this.search_reset_timeout_id = 0;
517                 this.reset_search = true;
518                 return false;
519         }
520
521         private void perform_search(string? str) {
522                 Valaunch.Collection<Valaunch.IDisplayable> list;
523                 Gtk.ListStore store;
524                 Gtk.TreeSelection selection;
525                 bool selected = false;
526                 this.reset_result();
527                 if(str == "") { // != null && str.length == 0
528                         string tmp;
529                         str = null;
530                         tmp = "<i>%s</i>".printf(START_SEARCHING);
531                         this.instr_label.set_markup(tmp);
532                 }
533                 if(str == null && this.focus == Focus.BOX1) {
534                         this.hide_search_results();
535                         return;
536                 }
537                 
538                 if(this.focus == Focus.BOX1) {  // left side !?!?
539                         list = this.manager.query(str);
540                 } 
541                 else {
542                         list = this.current_item.get_actions(str);
543                 }
544                 if(list == null) {
545                         string tmp;
546                         hide_search_results();
547                         tmp = ("<b>%s</b>").printf(_("No match"));
548                         this.instr_label.set_markup(tmp);
549                         return;
550                 }
551                 store = (Gtk.ListStore)this.result_treeview.get_model();
552                 selection = this.result_treeview.get_selection();
553                 foreach(Valaunch.IDisplayable object in list) {
554                         string markup;
555                         Gdk.Pixbuf    pixbuf;
556                         Gtk.TreeIter  iter;
557                         
558                         /* Comment out for now, it looks a bit bad. */
559                         markup = string_markup_substring(object.display_name, str, "u");
560                         pixbuf = object.pixbuf;
561                         if(pixbuf == null) 
562                                 pixbuf = this.unknown_pixbuf;
563                         
564                         store.append(out iter);
565                         store.set(iter,
566                                   Column.DISPLAYABLE, object,
567                                   Column.PIXBUF, pixbuf,
568                                   Column.NAME,   markup != null ? markup : object.display_name,
569                                   -1);
570                         
571                         if(!selected) {
572                                 selection.select_iter(iter);
573                                 selected = true;
574                         }
575                 }
576                 /* Don't show the results again if we already activated. */
577                 if(!this.delayed_activation) {
578                         show_search_results();
579                 }
580         }
581
582         private void show_search_results() {
583                 Gtk.Requisition req = Gtk.Requisition();
584                 
585                 this.result_sw.show();
586                 this.instruction.hide();
587                 
588                 /* Resize the window to fit the new content. */
589                 this.get_size_request(out req.width, out req.height);
590                 this.resize(req.width > 0 ? req.width : 1, req.height > 0 ? req.height : 1);
591         }
592
593         private void hide_search_results() {
594                 Gtk.Requisition req = {};
595                 
596                 this.result_sw.hide();
597                 this.instruction.show();
598                 
599                 /* Resize the window to fit the new content. */
600                 this.get_size_request(out req.width, out req.height);
601                 this.resize(req.width > 0 ? req.width : 1, req.height > 0 ? req.height : 1);
602         }
603
604         private void result_row_selected_cb(Gtk.TreeSelection selection) {
605                 Gtk.TreeModel model;
606                 Gtk.TreeIter iter;
607                 //print("result_row_selected_cb\n");
608                 if(selection.get_selected(out model, out iter)) {
609                         Valaunch.IDisplayable object = null;;
610                         model.get(iter,
611                                   Column.DISPLAYABLE, ref object,
612                                  -1);
613                         if(object == null) {
614                                 print("warning cannot set item\n");
615                                 return;
616                         }
617                         if(this.focus == Focus.BOX1) {
618                                 this.set_item(((Valaunch.Item)object), this.search_string.str);
619                         }
620                         else {
621                                 this.set_action(((Valaunch.Action)object), this.search_string.str);
622                         }
623                 }
624         }
625
626         private void reset_result() {
627                 Gtk.ListStore store;
628                 
629                 this.reset_search = false;
630                 store =(Gtk.ListStore)this.result_treeview.get_model();
631                 store.clear();
632                 
633                 if(this.focus == Focus.BOX1) {
634                         this.set_item(null, null);
635                         this.set_action(null, null); //JM
636                         this.item_icon_box.caption = this.search_string.str;
637                         this.action_icon_box.caption = this.search_string.str; //JM
638                         this.instr_label.set_markup("<i>%s</i>".printf(START_SEARCHING));
639                 }
640                 else {
641                         this.set_action(null, null);
642                         this.action_icon_box.caption = this.search_string.str;
643                 }
644         }
645
646         private void string_utf8_remove_last_char(StringBuilder stri) {
647                 char* ptr;
648                 long  utf8_len = stri.str.length;
649                 if(utf8_len == 0) {
650                         return;
651                 }
652                 utf8_len--;
653                 ptr = stri.str.offset(utf8_len);
654                 stri.truncate(stri.len - ((string)ptr).length);
655         }
656         
657         private const uint Key_ISO_Enter = 0xFE34;
658         private const uint Key_Escape = 0xFF1B;
659         private const uint Key_BackSpace = 0xFF08;
660         private const uint Key_Tab = 0xFF09;
661         private const uint Key_Up = 0xFF52;
662         private const uint Key_Down = 0xFF54;
663         private const uint Key_Return = 0xFF0D;
664         
665         private bool key_press_event_cb(Gtk.Widget widget, Gdk.EventKey event) {
666                 this.delayed_activation = false;
667                 if((event.state & Gdk.ModifierType.CONTROL_MASK) == Gdk.ModifierType.CONTROL_MASK) {
668                         return true;
669                 }
670                 switch(event.keyval) {
671                         case Key_Escape:
672                                 if(this.search_timeout_id != 0) {
673                                         Source.remove(this.search_timeout_id);
674                                         this.search_timeout_id = 0;
675                                 }
676                                 hide_search_results();
677                                 /* If text, clear it out, otherwise close the window. */
678                                 if(this.search_string.len > 0) {
679                                         this.search_string.truncate(0);
680                                         reset_result();
681                                 }
682                                 else {
683                                         this.hide();
684                                 }
685                                 return true;
686                         case Key_BackSpace:
687                                 if(this.search_string.len == 0) {
688                                         return true;
689                                 }
690                                 string_utf8_remove_last_char(this.search_string);
691                                 queue_search();
692                                 start_reset_timeout();
693                                 return true;
694                         case Key_Tab:
695                                 if(this.focus == Focus.BOX1) {
696                                         if(this.current_action != null) {
697                                                 this.focus = Focus.BOX2;
698                                         }
699                                 }
700                                 else {
701                                         this.focus = Focus.BOX1;
702                                 }
703                                 start_reset_timeout();
704                                 return true;
705                         case Key_Up:
706                         case Key_Down: {
707                                 Gtk.TreeSelection selection;
708                                 Gtk.TreeModel model;
709                                 Gtk.TreeIter iter;
710                                 selection = this.result_treeview.get_selection();
711                                 if(selection.get_selected(out model, out iter)) {
712                                         Gtk.TreePath path;
713                                         path = model.get_path(iter);
714                                         if(event.keyval == Key_Up) {
715                                                 path.prev();
716                                         }
717                                         else {
718                                                 path.next();
719                                         }
720                                         selection.select_path(path);
721                                         this.result_treeview.scroll_to_cell(path, null, false, 0.0f, 0.0f);
722                                 }
723                                 start_reset_timeout();
724                                 return true;
725                         }
726                         case Key_Return:
727                         case Key_ISO_Enter:
728                                 if(this.current_action != null) {
729                                         this.current_action.activate();
730                                         hide_search_results();
731                                         this.hide();
732                                 }
733                                 else {
734                                         this.delayed_activation = true;
735                                 }
736                                 return true;
737                         
738                         default:
739                                 if(event.length > 0) {
740                                         if(this.reset_search) {
741                                                 this.search_string.truncate(0);
742                                                 reset_result();
743                                         }
744                                         
745                                         this.search_string.append_unichar(Gdk.keyval_to_unicode(event.keyval));
746                                 }
747                                 else {
748                                         return true;
749                                 }
750                                 break;
751                 }
752                 queue_search();
753                 start_reset_timeout();
754                 return true;
755         }
756
757         private bool grab() {
758                 unowned Gdk.Window gdk_window;
759                 uint32 time;
760                 gdk_window = this.get_window();
761                 time = Gdk.x11_get_server_time(gdk_window);
762                 if((Gdk.pointer_grab(gdk_window, true, Gdk.EventMask.BUTTON_PRESS_MASK   |
763                                                        Gdk.EventMask.BUTTON_RELEASE_MASK |
764                                                        Gdk.EventMask.POINTER_MOTION_MASK,
765                                                        null, null, time) == Gdk.GrabStatus.SUCCESS)) {
766                         if(Gdk.keyboard_grab(gdk_window, true, time) == Gdk.GrabStatus.SUCCESS) {
767                                 return true;
768                         }
769                         else {
770                                 Gdk.pointer_ungrab(time);
771                                 return false;
772                         }
773                 }
774                 return false;
775         }
776
777         public new void present() {
778                 int i = 0;
779                 if(!this.get_is_visible()) {
780                         this.search_string.truncate(0);
781                         this.last_item_search_str = null;
782                         
783                         this.focus = Focus.BOX1;
784                         
785                         reset_result();
786                         
787                         if(gtk_widget_get_realized(this)) { // this.is_realized()) {
788                                 set_position();
789                         }
790                         base.present();
791                 }
792
793                 /* This is a really ugly hack. There seems to be a problem in recent
794                  * metacity versions that grab the keyboard when invoking a keybinding
795                  * command, which makes our grabbing fail. So we retry for a while...
796                  */
797                 while(i < 100) {
798                         if(this.grab()) {
799                                 break;
800                         }
801                         i++;
802                         Thread.usleep(100);
803                 }
804         }
805
806         private bool get_is_visible() {
807                 bool visible = this.visible;
808                 if(this.get_window() != null) {
809                         bool iconified;
810                         iconified = (this.get_window().get_state() & Gdk.WindowState.ICONIFIED) == Gdk.WindowState.ICONIFIED;
811                         visible = visible && !iconified;
812                 }
813                 return visible;
814         }
815
816         public new void set_position() {
817                 Gdk.Display display;
818                 Gdk.Screen screen;
819                 Gdk.Rectangle moni_geo;
820                 int screen_xorg, screen_yorg;
821                 int screen_width, screen_height;
822                 int x, y;
823                 int px, py;
824                 int width, height;
825                 int moni_n;
826                 
827                 this.get_size(out width, out height);
828                 
829                 display = this.get_display();
830                 display.get_pointer(out screen, out px, out py, null);
831                 moni_n = screen.get_monitor_at_point(px, py);
832                 screen.get_monitor_geometry(moni_n, out moni_geo);
833                 
834                 screen_xorg = moni_geo.x;
835                 screen_yorg = moni_geo.y;
836                 screen_width = moni_geo.width;
837                 screen_height = moni_geo.height;
838                 
839                 x = screen_width / 2 - width / 2 + screen_xorg;
840                 y = screen_height / 3 - height / 2 + screen_yorg;
841                 this.move(x, y);
842         }
843
844         public new void set_colormap() {
845                 Gdk.Screen screen;
846                 Gdk.Colormap colormap;
847                 screen = this.get_screen();
848                 colormap = screen.get_rgba_colormap();
849                 
850                 if(colormap != null) {
851                         this.can_rgba = true;
852                 }
853                 else {
854                         colormap = screen.get_rgb_colormap();
855                         print("No Alpha support \n");
856                 }
857                 base.set_colormap(colormap);
858         }
859
860         private void start_reset_timeout() {
861                 if(this.search_reset_timeout_id != 0) 
862                         Source.remove(this.search_reset_timeout_id);
863                 
864                 this.search_reset_timeout_id = Timeout.add_seconds(SEARCH_RESET_TIMEOUT, search_reset_timeout_cb);
865         }
866 }
867