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