1/*
2 * Copyright (C) 2010 Michal Hruby <michal.mhr@gmail.com>
3 * Copyright (C) 2010 Alberto Aldegheri <albyrock87+dev@gmail.com>
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program 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
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17 *
18 * Authored by Alberto Aldegheri <albyrock87+dev@gmail.com>
19 *
20 */
21
22namespace Synapse.Gui
23{
24  public class ViewDoish : Synapse.Gui.View
25  {
26    construct {
27
28    }
29
30    static construct
31    {
32      /* Override here style properties */
33      var icon_size = new GLib.ParamSpecInt ("icon-size",
34                                             "Icon Size",
35                                             "The size of focused icon in supported themes",
36                                             32, 256, 128,
37                                             GLib.ParamFlags.READWRITE);
38      var title_max = new GLib.ParamSpecString ("title-size",
39                                                "Title Font Size",
40                                                "The standard size the match title in Pango absolute sizes (string)",
41                                                "medium",
42                                                GLib.ParamFlags.READWRITE);
43      var title_min = new GLib.ParamSpecString ("title-min-size",
44                                                "Title minimum Font Size",
45                                                "The minimum size the match title in Pango absolute sizes (string)",
46                                                "small",
47                                                GLib.ParamFlags.READWRITE);
48      var descr_max = new GLib.ParamSpecString ("description-size",
49                                                "Description Font Size",
50                                                "The standard size the match description in Pango absolute sizes (string)",
51                                                "small",
52                                                GLib.ParamFlags.READWRITE);
53      var descr_min = new GLib.ParamSpecString ("description-min-size",
54                                                "Description minimum Font Size",
55                                                "The minimum size the match description in Pango absolute sizes (string)",
56                                                "small",
57                                                GLib.ParamFlags.READWRITE);
58      install_style_property (icon_size);
59      install_style_property (title_max);
60      install_style_property (title_min);
61      install_style_property (descr_max);
62      install_style_property (descr_min);
63    }
64
65    public override void style_updated ()
66    {
67      base.style_updated ();
68
69      int spacing, icon_size;
70      string tmax, tmin, dmax, dmin;
71	  style_get ("pane-spacing", out spacing, "icon-size", out icon_size,
72        "title-size", out tmax, "title-min-size", out tmin, "description-size", out dmax,
73        "description-min-size", out dmin);
74
75      sp1.set_size_request (spacing, -1);
76      sp2.set_size_request (spacing, -1);
77
78      source_icon.set_pixel_size (icon_size);
79      action_icon.set_pixel_size (icon_size);
80      target_icon.set_pixel_size (icon_size);
81      spacer.set_size_request (1, SHADOW_SIZE + BORDER_RADIUS);
82      source_label.size = SmartLabel.string_to_size (tmax);
83      source_label.min_size = SmartLabel.string_to_size (tmin);
84      action_label.size = SmartLabel.string_to_size (tmax);
85      action_label.min_size = SmartLabel.string_to_size (tmin);
86      target_label.size = SmartLabel.string_to_size (tmax);
87      target_label.min_size = SmartLabel.string_to_size (tmin);
88      description_label.size = SmartLabel.string_to_size (dmax);
89      description_label.min_size = SmartLabel.string_to_size (dmin);
90    }
91
92    private NamedIcon source_icon;
93    private NamedIcon action_icon;
94    private NamedIcon target_icon;
95
96    private SmartLabel source_label;
97    private SmartLabel action_label;
98    private SmartLabel target_label;
99    private SmartLabel description_label;
100
101    private SelectionContainer results_container;
102
103    private ResultBox results_sources;
104    private ResultBox results_actions;
105    private ResultBox results_targets;
106
107    private MenuThrobber menuthrobber;
108
109    private Gtk.Box target_container;
110    private Gtk.Box container;
111
112    private Gtk.Box spane; //source and action panes
113    private Gtk.Box apane;
114    private Gtk.Box tpane;
115
116    private Gtk.Label sp1;
117    private Gtk.Label sp2;
118
119    protected override void build_ui ()
120    {
121      /* Icons */
122      source_icon = new NamedIcon ();
123      action_icon = new NamedIcon ();
124      target_icon = new NamedIcon ();
125      source_icon.set_icon_name ("search", Gtk.IconSize.DND);
126      action_icon.clear ();
127      target_icon.set_icon_name ("");
128
129      source_icon.set_pixel_size (128);
130      source_icon.xpad = 15;
131      action_icon.set_pixel_size (128);
132      action_icon.xpad = 15;
133      target_icon.set_pixel_size (128);
134      target_icon.xpad = 15;
135
136      /* Labels */
137      source_label = new SmartLabel ();
138      source_label.set_ellipsize (Pango.EllipsizeMode.END);
139      source_label.size = SmartLabel.Size.MEDIUM;
140      source_label.min_size = SmartLabel.Size.SMALL;
141      source_label.set_state_flags (Gtk.StateFlags.SELECTED, false);
142      source_label.xalign = 0.5f;
143      action_label = new SmartLabel ();
144      action_label.set_ellipsize (Pango.EllipsizeMode.END);
145      action_label.size = SmartLabel.Size.MEDIUM;
146      action_label.min_size = SmartLabel.Size.SMALL;
147      action_label.set_state_flags (Gtk.StateFlags.SELECTED, false);
148      action_label.xalign = 0.5f;
149      target_label = new SmartLabel ();
150      target_label.set_ellipsize (Pango.EllipsizeMode.END);
151      target_label.size = SmartLabel.Size.MEDIUM;
152      target_label.min_size = SmartLabel.Size.SMALL;
153      target_label.set_state_flags (Gtk.StateFlags.SELECTED, false);
154      target_label.xalign = 0.5f;
155      description_label = new SmartLabel ();
156      description_label.size = SmartLabel.Size.SMALL;
157      description_label.set_animation_enabled (true);
158      description_label.set_state_flags (Gtk.StateFlags.SELECTED, false);
159      description_label.xalign = 0.5f;
160
161      /* Categories - Throbber and menu */ //#0C71D6
162      var categories_hbox = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 0);
163
164      menuthrobber = new MenuThrobber ();
165      menuthrobber.set_state_flags (Gtk.StateFlags.SELECTED, false);
166      menu = (MenuButton) menuthrobber;
167      menuthrobber.set_size_request (14, 14);
168
169      categories_hbox.pack_start (flag_selector);
170      categories_hbox.pack_start (menuthrobber, false);
171
172      flag_selector.selected_markup = "<span size=\"small\"><b>%s</b></span>";
173      flag_selector.unselected_markup = "<span size=\"x-small\">%s</span>";
174      flag_selector.set_state_flags (Gtk.StateFlags.SELECTED, false);
175
176      var hbox_panes = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 0);
177
178      /* PANES */
179      sp1 = new Gtk.Label (null);
180      sp2 = new Gtk.Label (null);
181
182      /* Source Pane */
183      spane = new Gtk.Box (Gtk.Orientation.VERTICAL, 0);
184      spane.border_width = 5;
185      var sensitive = new SensitiveWidget (source_icon);
186      this.make_draggable (sensitive);
187      spane.pack_start (sensitive, false);
188      spane.pack_start (source_label, false);
189
190      /* Action Pane */
191      apane = new Gtk.Box (Gtk.Orientation.VERTICAL, 0);
192      apane.border_width = 5;
193      apane.pack_start (action_icon, false);
194      apane.pack_start (action_label, false);
195
196      hbox_panes.pack_start (spane, false);
197      hbox_panes.pack_start (sp1, true);
198      hbox_panes.pack_start (apane, true);
199
200      /* Target Pane */
201      tpane = new Gtk.Box (Gtk.Orientation.VERTICAL, 0);
202      tpane.border_width = 5;
203      sensitive = new SensitiveWidget (target_icon);
204      this.make_draggable (sensitive);
205      tpane.pack_start (sensitive, false);
206      tpane.pack_start (target_label, false);
207
208      target_container = new Gtk.Box (Gtk.Orientation.VERTICAL, 0);
209      var hb = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 0);
210      hb.pack_start (sp2, false);
211      hb.pack_start (tpane, false, false);
212      target_container.pack_start (new CloneWidget (categories_hbox), false);
213      target_container.pack_start (hb, false, true, 5);
214      target_container.pack_start (new Gtk.Label (null), true);
215
216      /* list */
217      this.prepare_results_container (out results_container, out results_sources,
218                                      out results_actions, out results_targets, Gtk.StateFlags.SELECTED);
219
220      container = new Gtk.Box (Gtk.Orientation.VERTICAL, 0);
221      container.pack_start (categories_hbox, false);
222      container.pack_start (hbox_panes, false, true, 5);
223      container.pack_start (description_label, false);
224      container.pack_start (spacer, false);
225      container.pack_start (results_container, false);
226
227      var main_container = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 0);
228      main_container.pack_start (container, false);
229      main_container.pack_start (target_container, false);
230
231      main_container.show_all ();
232      results_container.hide ();
233
234      this.add (main_container);
235    }
236
237    public override bool is_list_visible ()
238    {
239      return results_container.visible;
240    }
241
242    public override void set_list_visible (bool visible)
243    {
244      results_container.visible = visible;
245    }
246
247    public override void set_throbber_visible (bool visible)
248    {
249      menuthrobber.active = visible;
250    }
251
252    public override void update_searching_for ()
253    {
254      update_labels ();
255      results_container.select_child (model.searching_for);
256      queue_draw ();
257    }
258
259    public override void update_selected_category ()
260    {
261      flag_selector.selected = model.selected_category;
262    }
263
264    protected override void paint_background (Cairo.Context ctx)
265    {
266      bool comp = this.is_composited ();
267      double r = 0, b = 0, g = 0;
268
269      Gtk.Allocation spacer_allocation, flag_selector_allocation,
270        spane_allocation, apane_allocation;
271      spacer.get_allocation (out spacer_allocation);
272      flag_selector.get_allocation (out flag_selector_allocation);
273      spane.get_allocation (out spane_allocation);
274      apane.get_allocation (out apane_allocation);
275
276      if (is_list_visible () || (!comp))
277      {
278        if (comp && is_list_visible ())
279        {
280          Gtk.Allocation results_container_allocation;
281          results_container.get_allocation (out results_container_allocation);
282          ctx.translate (0.5, 0.5);
283          ctx.set_operator (Cairo.Operator.OVER);
284          Utils.cairo_make_shadow_for_rect (ctx, results_container_allocation.x,
285                                                 results_container_allocation.y,
286                                                 results_container_allocation.width - 1,
287                                                 results_container_allocation.height - 1,
288                                                 0, r, g, b, SHADOW_SIZE);
289          ctx.translate (-0.5, -0.5);
290        }
291        ctx.set_operator (Cairo.Operator.SOURCE);
292        ch.set_source_rgba (ctx, 1.0, StyleType.BASE, Gtk.StateFlags.NORMAL);
293        ctx.rectangle (spacer_allocation.x, spacer_allocation.y + BORDER_RADIUS, spacer_allocation.width, SHADOW_SIZE);
294        ctx.fill ();
295      }
296
297      int width = this.get_allocated_width ();
298      int height = spacer_allocation.y + BORDER_RADIUS + SHADOW_SIZE;
299
300      // pattern
301      Cairo.Pattern pat = new Cairo.Pattern.linear(0, 0, 0, height);
302      r = g = b = 0.12;
303      ch.get_color_colorized (ref r, ref g, ref b, StyleType.BG, Gtk.StateFlags.SELECTED);
304      pat.add_color_stop_rgba (0.0, r, g, b, 0.95);
305      r = g = b = 0.4;
306      ch.get_color_colorized (ref r, ref g, ref b, StyleType.BG, Gtk.StateFlags.SELECTED);
307      pat.add_color_stop_rgba (1.0, r, g, b, 1.0);
308
309      r = g = b = 0.0;
310
311      if (target_container.visible)
312      {
313        Gtk.Allocation target_container_allocation, tpane_allocation;
314        target_container.get_allocation (out target_container_allocation);
315        tpane.get_allocation (out tpane_allocation);
316
317        width -= target_container_allocation.width;
318        // draw background
319        ctx.save ();
320        ctx.translate (0.5, 0.5);
321        ctx.set_operator (Cairo.Operator.OVER);
322        Utils.cairo_make_shadow_for_rect (ctx, target_container_allocation.x - BORDER_RADIUS,
323                                               tpane_allocation.y,
324                                               target_container_allocation.width - 1 + BORDER_RADIUS,
325                                               tpane_allocation.height - 1, 15, r, g, b, SHADOW_SIZE);
326        ctx.translate (-0.5, -0.5);
327        Utils.cairo_rounded_rect (ctx, target_container_allocation.x - BORDER_RADIUS,
328                                       tpane_allocation.y,
329                                       target_container_allocation.width + BORDER_RADIUS,
330                                       tpane_allocation.height, 15);
331        ctx.set_operator (Cairo.Operator.SOURCE);
332        ctx.set_source (pat);
333        ctx.clip ();
334        ctx.paint ();
335        if (model.searching_for == SearchingFor.TARGETS)
336        {
337          ctx.set_operator (Cairo.Operator.OVER);
338          Utils.cairo_rounded_rect (ctx, tpane_allocation.x,
339                                         tpane_allocation.y,
340                                         tpane_allocation.width,
341                                         tpane_allocation.height,
342                                         15);
343          ch.set_source_rgba (ctx, 0.3,
344                              StyleType.FG, Gtk.StateFlags.SELECTED);
345          ctx.clip ();
346          ctx.paint ();
347        }
348        ctx.restore ();
349      }
350
351      int delta = flag_selector_allocation.y - BORDER_RADIUS;
352      if (!comp) delta = 0;
353
354      ctx.save ();
355      ctx.translate (SHADOW_SIZE, delta);
356      width -= SHADOW_SIZE * 2;
357      height -= SHADOW_SIZE + delta;
358      // shadow
359      ctx.translate (0.5, 0.5);
360      ctx.set_operator (Cairo.Operator.OVER);
361      Utils.cairo_make_shadow_for_rect (ctx, 0, 0, width - 1, height - 1, BORDER_RADIUS, r, g, b, SHADOW_SIZE);
362      ctx.translate (-0.5, -0.5);
363
364      Utils.cairo_rounded_rect (ctx, 0, 0, width, height, BORDER_RADIUS);
365      ctx.set_source (pat);
366      ctx.set_operator (Cairo.Operator.SOURCE);
367      ctx.clip ();
368      ctx.paint ();
369
370      // reflection
371      ctx.set_operator (Cairo.Operator.OVER);
372      ctx.new_path ();
373      ctx.move_to (0, 0);
374      ctx.rel_line_to (0.0, height / 3.0);
375      ctx.rel_curve_to (width / 4.0, -height / 10.0, width / 4.0 * 3.0, -height / 10.0, width, 0.0);
376      ctx.rel_line_to (0.0, -height / 3.0);
377      ctx.close_path ();
378      pat = new Cairo.Pattern.linear (0, height / 10.0, 0, height / 3.0);
379      pat.add_color_stop_rgba (0.0, 1.0, 1.0, 1.0, 0.0);
380      pat.add_color_stop_rgba (0.7, 1.0, 1.0, 1.0, 0.4);
381      pat.add_color_stop_rgba (1.0, 1.0, 1.0, 1.0, 0.9);
382      ctx.set_source (pat);
383      ctx.clip ();
384      ctx.paint ();
385
386      ctx.restore ();
387
388      // icon bgs
389      ctx.set_operator (Cairo.Operator.OVER);
390      ctx.save ();
391      Utils.cairo_rounded_rect (ctx, spane_allocation.x,
392                                     spane_allocation.y,
393                                     spane_allocation.width,
394                                     spane_allocation.height,
395                                     15);
396      ch.set_source_rgba (ctx, model.searching_for == SearchingFor.SOURCES ? 0.3 : 0.08,
397                          StyleType.FG, Gtk.StateFlags.SELECTED);
398      ctx.clip ();
399      ctx.paint ();
400      ctx.restore ();
401
402      ctx.save ();
403      Utils.cairo_rounded_rect (ctx, apane_allocation.x,
404                                     apane_allocation.y,
405                                     apane_allocation.width,
406                                     apane_allocation.height,
407                                     15);
408      ch.set_source_rgba (ctx, model.searching_for == SearchingFor.ACTIONS ? 0.3 : 0.08,
409                          StyleType.FG, Gtk.StateFlags.SELECTED);
410      ctx.clip ();
411      ctx.paint ();
412      ctx.restore ();
413    }
414
415    private void update_labels ()
416    {
417      var focus = model.get_actual_focus ();
418      if (focus.value == null)
419      {
420        if (controller.is_in_initial_state ())
421        {
422          source_label.set_text (IController.TYPE_TO_SEARCH);
423          description_label.set_text (IController.DOWN_TO_SEE_RECENT);
424        }
425        else if (controller.searched_for_recent ())
426        {
427          description_label.set_text (IController.NO_RECENT_ACTIVITIES);
428        }
429        else
430        {
431          if (this.menuthrobber.active)
432            description_label.set_text (IController.SEARCHING);
433          else
434            description_label.set_text (IController.NO_RESULTS);
435        }
436      }
437      else
438      {
439        description_label.set_text (Utils.get_printable_description (focus.value));
440      }
441      if (model.searching_for == SearchingFor.TARGETS)
442      {
443        flag_selector.sensitive = false;
444      }
445      else
446      {
447        flag_selector.sensitive = true;
448      }
449      target_container.visible = model.needs_target ();
450    }
451
452    public override void update_focused_source (Entry<int, Match> m)
453    {
454      if (controller.is_in_initial_state ()) source_icon.set_icon_name ("search");
455      else if (m.value == null) source_icon.set_icon_name ("");
456      else
457      {
458        if (m.value.has_thumbnail)
459          source_icon.set_icon_name (m.value.thumbnail_path);
460        else
461          source_icon.set_icon_name (m.value.icon_name);
462        results_sources.move_selection_to_index (m.key);
463      }
464      source_label.set_markup (Utils.markup_string_with_search (m.value == null ? "" : m.value.title, this.model.query[SearchingFor.SOURCES], ""));
465      if (model.searching_for == SearchingFor.SOURCES) update_labels ();
466    }
467
468    public override void update_focused_action (Entry<int, Match> m)
469    {
470      if (controller.is_in_initial_state () ||
471          model.focus[SearchingFor.SOURCES].value == null)
472      {
473        action_icon.clear ();
474      }
475      else if (m.value == null)
476      {
477        action_icon.set_icon_name ("");
478      }
479      else
480      {
481        action_icon.set_icon_name (m.value.icon_name);
482        results_actions.move_selection_to_index (m.key);
483      }
484      action_label.set_markup (Utils.markup_string_with_search (m.value == null ? "" : m.value.title, this.model.query[SearchingFor.ACTIONS], ""));
485      if (model.searching_for == SearchingFor.ACTIONS) update_labels ();
486    }
487
488    public override void update_focused_target (Entry<int, Match> m)
489    {
490      if (m.value == null) target_icon.set_icon_name ("");
491      else
492      {
493        if (m.value is UnknownMatch)
494          target_icon.set_icon_name ("text-plain");
495        else
496        {
497          if (m.value.has_thumbnail)
498            target_icon.set_icon_name (m.value.thumbnail_path);
499          else
500            target_icon.set_icon_name (m.value.icon_name);
501        }
502        results_targets.move_selection_to_index (m.key);
503      }
504      target_label.set_markup (Utils.markup_string_with_search (m.value == null ? "" : m.value.title, this.model.query[SearchingFor.TARGETS], ""));
505      if (model.searching_for == SearchingFor.TARGETS) update_labels ();
506    }
507
508    public override void update_sources (Gee.List<Match>? list = null)
509    {
510      results_sources.update_matches (list);
511    }
512    public override void update_actions (Gee.List<Match>? list = null)
513    {
514      results_actions.update_matches (list);
515    }
516    public override void update_targets (Gee.List<Match>? list = null)
517    {
518      results_targets.update_matches (list);
519    }
520  }
521}
522