1
/* -*- Mode: vala; indent-tabs-mode: nil; c-basic-offset: 2; tab-width: 2 -*- */
3
* Copyright (C) 2009 Canonical Ltd
5
* This program is free software: you can redistribute it and/or modify
6
* it under the terms of the GNU General Public License version 3 as
7
* published by the Free Software Foundation.
9
* This program is distributed in the hope that it will be useful,
10
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
* GNU General Public License for more details.
14
* You should have received a copy of the GNU General Public License
15
* along with this program. If not, see <http://www.gnu.org/licenses/>.
17
* Authored by Gordon Allott <gord.allott@canonical.com>
21
namespace Unity.Quicklauncher
23
const string THROBBER_FILE = Unity.PKGDATADIR
24
+ "/quicklauncher_spinner.png";
25
const string FOCUSED_FILE = Unity.PKGDATADIR
26
+ "/quicklauncher_focused_indicator.svg";
27
const string RUNNING_FILE = Unity.PKGDATADIR
28
+ "/quicklauncher_running_indicator.svg";
29
const string HONEYCOMB_MASK_FILE = Unity.PKGDATADIR
30
+ "/honeycomb-mask.png";
32
public class ApplicationView : Ctk.Bin
35
public Launcher.Application app;
36
private Ctk.Image icon;
37
private Ctk.EffectGlow icon_glow_effect;
38
private Ctk.EffectDropShadow icon_dropshadow_effect;
39
private bool _is_sticky;
40
private Clutter.Group container;
41
private Ctk.Image throbber;
42
private Ctk.Image focused_indicator;
43
private Ctk.Image running_indicator;
44
private Gdk.Pixbuf honeycomb_mask;
45
public Unity.TooltipManager.Tooltip tooltip;
47
private uint32 last_pressed_time;
50
private bool is_starting {
64
private Clutter.Animation anim_throbber;
66
private void throbber_start ()
68
if (anim_throbber != null)
69
anim_throbber.completed ();
71
this.throbber.opacity = 255;
72
this.throbber.set_z_rotation_from_gravity (0.0f, Clutter.Gravity.CENTER);
73
this.anim_throbber = this.throbber.animate (
74
Clutter.AnimationMode.LINEAR, 1200,
75
"rotation-angle-z", 360.0f
78
this.anim_throbber.loop = true;
79
GLib.Timeout.add_seconds (15, on_launch_timeout);
82
private void throbber_fadeout ()
84
if (this.throbber.opacity == 0)
87
if (anim_throbber != null)
88
anim_throbber.completed ();
91
this.throbber.animate (Clutter.AnimationMode.EASE_IN_QUAD,
95
this.anim_throbber.loop = false;
98
private void throbber_hide ()
100
if (this.throbber.opacity > 0)
104
/* animation support */
105
private Clutter.Animation _anim;
106
public Clutter.Animation anim {
107
get { return _anim; }
110
assert (_anim is Clutter.Animation);
117
private Clutter.Animation hover_anim;
119
/* if we are not sticky anymore and we are not running, request remove */
120
public bool is_sticky {
121
get { return _is_sticky; }
123
if (value == false && !is_running)
124
this.request_remove (this);
129
public bool is_running {
130
get { return app.running; }
133
public bool is_hovering = false;
136
* signal is called when the application is not marked as sticky and
139
public signal void request_remove (ApplicationView app);
140
public signal void request_attention (ApplicationView app);
141
public signal void clicked (ApplicationView app);
143
public ApplicationView (Launcher.Application app)
145
/* This is a 'view' for a launcher application object
146
* it will (hopefully) be able to launch, track when this application
147
* opens and closes and be able to get desktop file info
150
generate_view_from_app ();
151
this.app.opened.connect(this.on_app_opened);
152
this.app.closed.connect(this.on_app_closed);
154
this.tooltip = Unity.TooltipManager.get_default().create (this.app.name);
156
this.app.notify["running"].connect (this.notify_on_is_running);
157
this.app.notify["focused"].connect (this.notify_on_is_focused);
158
notify_on_is_running ();
159
notify_on_is_focused ();
164
this.container = new Clutter.Group ();
165
add_actor (this.container);
167
this.icon = new Ctk.Image (42);
168
this.container.add_actor(this.icon);
172
button_press_event.connect (this.on_pressed);
173
button_release_event.connect (this.on_released);
174
/* hook up glow for enter/leave events */
175
enter_event.connect(this.on_mouse_enter);
176
leave_event.connect(this.on_mouse_leave);
178
/* hook up tooltip for enter/leave events */
179
this.icon.enter_event.connect (this.on_enter);
180
this.icon.leave_event.connect (this.on_leave);
181
this.icon.set_reactive (true);
183
this.clicked.connect (this.on_clicked);
185
icon_dropshadow_effect = new Ctk.EffectDropShadow(3, 5, 5);
186
this.icon.add_effect(icon_dropshadow_effect);
187
this.icon.queue_relayout();
192
private void load_textures ()
194
this.throbber = new Ctk.Image.from_filename (20, THROBBER_FILE);
195
this.throbber.set_z_rotation_from_gravity (0.0f, Clutter.Gravity.CENTER);
196
this.container.add_actor (this.throbber);
198
this.focused_indicator = new Ctk.Image.from_filename (8, FOCUSED_FILE);
199
this.container.add_actor (this.focused_indicator);
201
this.running_indicator = new Ctk.Image.from_filename (8, RUNNING_FILE);
202
this.container.add_actor (this.running_indicator);
204
this.throbber.set_opacity (0);
205
this.focused_indicator.set_opacity (0);
206
this.running_indicator.set_opacity (0);
208
this.honeycomb_mask = new Gdk.Pixbuf.from_file(HONEYCOMB_MASK_FILE);
214
* re-layouts the various indicators and throbbers in out view
216
private void relayout ()
218
this.throbber.set_position (this.container.width - this.throbber.width,
219
this.container.height - this.throbber.height);
221
float mid_point_y = this.container.height / 2.0f;
222
float focus_halfy = this.focused_indicator.height / 2.0f;
223
float focus_halfx = this.container.width - this.focused_indicator.width;
225
this.focused_indicator.set_position(focus_halfx + 12,
226
mid_point_y - focus_halfy);
227
this.running_indicator.set_position (0, mid_point_y - focus_halfy);
229
this.icon.set_position (6, 0);
233
* taken from the prototype code and shamelessly stolen from
234
* netbook launcher. needs to be improved at some point to deal
235
* with all cases, it will miss some apps at the moment
237
static Gdk.Pixbuf make_icon(string? icon_name)
240
* This code somehow manages to miss a lot of icon names
241
* (non found icons are replaced with stock missing image icons)
242
* which is a little strange as I ported this code fron netbook launcher
243
* pixbuf-cache.c i think, If anyone else has a better idea for this then
244
* please give it a go. otherwise i will revisit this code the last week
245
* of the month sprint
248
Gtk.IconTheme theme = Gtk.IconTheme.get_default ();
250
if (icon_name == null)
252
pixbuf = theme.load_icon(Gtk.STOCK_MISSING_IMAGE, 42, 0);
256
if (icon_name.has_prefix("file://"))
258
string filename = "";
259
/* this try/catch sort of isn't needed... but it makes valac stop
260
* printing warning messages
264
filename = Filename.from_uri(icon_name);
266
catch (GLib.ConvertError e)
271
pixbuf = new Gdk.Pixbuf.from_file_at_scale(icon_name,
273
if (pixbuf is Gdk.Pixbuf)
278
if (Path.is_absolute(icon_name))
280
if (FileUtils.test(icon_name, FileTest.EXISTS))
282
pixbuf = new Gdk.Pixbuf.from_file_at_scale(icon_name,
285
if (pixbuf is Gdk.Pixbuf)
290
if (FileUtils.test ("/usr/share/pixmaps/" + icon_name,
291
FileTest.IS_REGULAR))
293
pixbuf = new Gdk.Pixbuf.from_file_at_scale (
294
"/usr/share/pixmaps/" + icon_name, 42, 42, true
297
if (pixbuf is Gdk.Pixbuf)
301
Gtk.IconInfo info = theme.lookup_icon(icon_name, 42, 0);
304
string filename = info.get_filename();
305
if (FileUtils.test(filename, FileTest.EXISTS))
307
pixbuf = new Gdk.Pixbuf.from_file_at_scale(filename,
310
if (pixbuf is Gdk.Pixbuf)
317
pixbuf = theme.load_icon(icon_name, 42, Gtk.IconLookupFlags.FORCE_SVG);
321
warning ("could not load icon for %s - %s", icon_name, e.message);
322
pixbuf = theme.load_icon(Gtk.STOCK_MISSING_IMAGE, 42, 0);
330
* goes though our launcher application and generates icons and such
332
private void generate_view_from_app ()
334
var pixbuf = make_icon (app.icon_name);
335
this.icon.set_from_pixbuf (pixbuf);
338
private void on_app_opened (Wnck.Application app)
340
this.is_starting = false;
343
private void on_app_closed (Wnck.Application app)
345
this.is_starting = false;
346
if (!this.is_running && !this.is_sticky)
347
this.request_remove (this);
350
private bool on_enter (Clutter.Event event)
354
get_transformed_position (out xx, out xy);
356
/* TODO: 30 is the default tooltip height */
357
xy += (height - 30) / 2;
358
Unity.TooltipManager.get_default().show (this.tooltip, (int) xx, (int) xy);
362
private void on_clicked (ApplicationView appview)
364
/* hide the tooltip, to prevent it from stealing focus when
365
the app starts and is focused */
366
Unity.TooltipManager.get_default().hide (this.tooltip);
375
// we only want to switch to the application, not launch it
380
this.is_starting = true;
387
critical ("could not launch application %s: %s",
390
this.is_starting = false;
395
private bool on_leave (Clutter.Event event)
397
Unity.TooltipManager.get_default().hide (this.tooltip);
401
private bool on_pressed(Clutter.Event src)
403
var bevent = src.button;
404
if (bevent.button == 1)
406
last_pressed_time = bevent.time;
411
private bool on_released (Clutter.Event src)
413
var bevent = src.button;
414
if (bevent.button == 1 &&
415
(bevent.time - last_pressed_time) < 500)
422
private bool on_mouse_enter(Clutter.Event src)
424
this.is_hovering = true;
425
icon_glow_effect = new Ctk.EffectGlow();
426
Clutter.Color c = Clutter.Color() {
432
icon_glow_effect.set_background_texture(honeycomb_mask);
433
icon_glow_effect.set_color(c);
434
icon_glow_effect.set_factor(1.0f);
435
this.icon.add_effect(icon_glow_effect);
436
this.icon.queue_relayout();
438
this.hover_anim = icon_glow_effect.animate (
439
Clutter.AnimationMode.EASE_IN_OUT_SINE, 600, "factor", 0.0f);
440
this.hover_anim.completed.connect (on_hover_anim_completed);
445
private bool on_launch_timeout ()
447
this.is_starting = false;
452
private void on_hover_anim_completed ()
456
Idle.add (do_new_anim);
460
private bool do_new_anim ()
463
if (icon_glow_effect.get_factor() <= 0.0f)
466
this.hover_anim = icon_glow_effect.animate(
467
Clutter.AnimationMode.EASE_IN_OUT_CIRC, 600, "factor", fadeto);
468
this.hover_anim.completed.connect (on_hover_anim_completed);
473
private bool on_mouse_leave(Clutter.Event src)
475
if (this.is_hovering)
477
this.is_hovering = false;
478
this.hover_anim.completed ();
479
this.icon.remove_effect(this.icon_glow_effect);
480
this.icon.queue_relayout();
481
print ("stopped animation");
489
* if the application is not running anymore and we are not sticky, we
490
* request to be removed
492
private void notify_on_is_running ()
494
this.is_starting = false;
496
/* we need to show the running indicator when we are running */
499
this.running_indicator.set_opacity (255);
503
this.running_indicator.set_opacity (0);
504
this.focused_indicator.set_opacity (0);
507
if (!this.is_running && !this.is_sticky)
509
this.request_remove (this);
513
private void notify_on_is_focused ()
516
this.focused_indicator.set_opacity (255);
517
this.request_attention (this);
520
this.focused_indicator.set_opacity (0);
522
this.is_starting = false;