diff options
Diffstat (limited to 'src/shutdown-dialog.vala')
-rw-r--r-- | src/shutdown-dialog.vala | 622 |
1 files changed, 622 insertions, 0 deletions
diff --git a/src/shutdown-dialog.vala b/src/shutdown-dialog.vala new file mode 100644 index 0000000..73e1bf4 --- /dev/null +++ b/src/shutdown-dialog.vala @@ -0,0 +1,622 @@ +/* -*- Mode: Vala; indent-tabs-mode: nil; tab-width: 4 -*- + * + * Copyright (C) 2013 Canonical Ltd + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * Authors: Robert Ancell <robert.ancell@canonical.com> + * Marco Trevisan <marco.trevisan@canonical.com> + */ + +public enum ShutdownDialogType +{ + LOGOUT, + SHUTDOWN, + RESTART +} + +public class ShutdownDialog : Gtk.Fixed +{ + public signal void closed (); + + private Cairo.ImageSurface? bg_surface = null; + private Cairo.ImageSurface? corner_surface = null; + private Cairo.ImageSurface? left_surface = null; + private Cairo.ImageSurface? top_surface = null; + private Cairo.Pattern? corner_pattern = null; + private Cairo.Pattern? left_pattern = null; + private Cairo.Pattern? top_pattern = null; + + private const int BORDER_SIZE = 30; + private const int BORDER_INTERNAL_SIZE = 10; + private const int BORDER_EXTERNAL_SIZE = BORDER_SIZE - BORDER_INTERNAL_SIZE; + private const int CLOSE_OFFSET = 3; + private const int BUTTON_TEXT_SPACE = 9; + private const int BLUR_RADIUS = 8; + + private Monitor monitor; + private weak Background background; + private Gdk.RGBA avg_color; + + private Gtk.Box vbox; + private DialogButton close_button; + private Gtk.Box button_box; + private Gtk.EventBox monitor_events; + private Gtk.EventBox vbox_events; + + private AnimateTimer animation; + private bool closing = false; + + + public ShutdownDialog (ShutdownDialogType type, Background bg) + { + background = bg; + background.notify["alpha"].connect (rebuild_background); + background.notify["average-color"].connect (update_background_color); + update_background_color (); + + // This event box covers the monitor size, and closes the dialog on click. + monitor_events = new Gtk.EventBox (); + monitor_events.visible = true; + monitor_events.set_visible_window (false); + monitor_events.events |= Gdk.EventMask.BUTTON_PRESS_MASK; + monitor_events.button_press_event.connect (() => { + close (); + return true; + }); + add (monitor_events); + + vbox = new Gtk.Box (Gtk.Orientation.VERTICAL, 10); + vbox.visible = true; + + vbox.margin = BORDER_INTERNAL_SIZE; + vbox.margin_top += 9; + vbox.margin_left += 20; + vbox.margin_right += 20; + vbox.margin_bottom += 2; + + // This event box consumes the click events inside the vbox + vbox_events = new Gtk.EventBox(); + vbox_events.visible = true; + vbox_events.set_visible_window (false); + vbox_events.events |= Gdk.EventMask.BUTTON_PRESS_MASK; + vbox_events.button_press_event.connect (() => { return true; }); + vbox_events.add (vbox); + monitor_events.add (vbox_events); + + string text; + + if (type == ShutdownDialogType.SHUTDOWN) + { + text = _("Goodbye. Would you like to…"); + } + else + { + var title_label = new Gtk.Label (_("Shut Down")); + title_label.visible = true; + title_label.override_font (Pango.FontDescription.from_string ("Ubuntu Light 15")); + title_label.override_color (Gtk.StateFlags.NORMAL, { 1.0f, 1.0f, 1.0f, 1.0f }); + title_label.set_alignment (0.0f, 0.5f); + vbox.pack_start (title_label, false, false, 0); + + text = _("Are you sure you want to shut down the computer?"); + } + + var have_open_sessions = false; + try + { + var b = Bus.get_sync (BusType.SYSTEM); + var result = b.call_sync ("org.freedesktop.DisplayManager", + "/org/freedesktop/DisplayManager", + "org.freedesktop.DBus.Properties", + "Get", + new Variant ("(ss)", "org.freedesktop.DisplayManager", "Sessions"), + new VariantType ("(v)"), + DBusCallFlags.NONE, + -1, + null); + Variant value; + result.get ("(v)", out value); + have_open_sessions = value.n_children () > 0; + } + catch (Error e) + { + warning ("Failed to check sessions from logind: %s", e.message); + } + if (have_open_sessions) + text = "%s\n\n%s".printf (_("Other users are currently logged in to this computer, shutting down now will also close these other sessions."), text); + + var label = new Gtk.Label (text); + label.set_line_wrap (true); + label.override_font (Pango.FontDescription.from_string ("Ubuntu Light 12")); + label.override_color (Gtk.StateFlags.NORMAL, { 1.0f, 1.0f, 1.0f, 1.0f }); + label.set_alignment (0.0f, 0.5f); + label.visible = true; + vbox.pack_start (label, false, false, 0); + + button_box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 20); + button_box.visible = true; + vbox.pack_start (button_box, false, false, 0); + + if (type == ShutdownDialogType.SHUTDOWN) + { + if (LightDM.get_can_suspend ()) + { + var button = add_button (_("Suspend"), Path.build_filename (Config.PKGDATADIR, "suspend.png"), Path.build_filename (Config.PKGDATADIR, "suspend_highlight.png")); + button.clicked.connect (() => + { + try + { + LightDM.suspend (); + close (); + } + catch (Error e) + { + warning ("Failed to suspend: %s", e.message); + } + }); + } + + if (LightDM.get_can_hibernate ()) + { + var button = add_button (_("Hibernate"), Path.build_filename (Config.PKGDATADIR, "hibernate.png"), Path.build_filename (Config.PKGDATADIR, "hibernate_highlight.png")); + button.clicked.connect (() => + { + try + { + LightDM.hibernate (); + close (); + } + catch (Error e) + { + warning ("Failed to hibernate: %s", e.message); + } + }); + } + } + + if (LightDM.get_can_restart ()) + { + var button = add_button (_("Restart"), Path.build_filename (Config.PKGDATADIR, "restart.png"), Path.build_filename (Config.PKGDATADIR, "restart_highlight.png")); + button.clicked.connect (() => + { + try + { + LightDM.restart (); + close (); + } + catch (Error e) + { + warning ("Failed to restart: %s", e.message); + } + }); + } + + if (LightDM.get_can_shutdown ()) + { + var button = add_button (_("Shut Down"), Path.build_filename (Config.PKGDATADIR, "shutdown.png"), Path.build_filename (Config.PKGDATADIR, "shutdown_highlight.png")); + button.clicked.connect (() => + { + try + { + LightDM.shutdown (); + close (); + } + catch (Error e) + { + warning ("Failed to shutdown: %s", e.message); + } + }); + + if (type != ShutdownDialogType.SHUTDOWN) + show.connect(() => { button.grab_focus (); }); + } + + close_button = new DialogButton (Path.build_filename (Config.PKGDATADIR, "dialog_close.png"), Path.build_filename (Config.PKGDATADIR, "dialog_close_highlight.png"), Path.build_filename (Config.PKGDATADIR, "dialog_close_press.png")); + close_button.can_focus = false; + close_button.clicked.connect (() => { close (); }); + close_button.visible = true; + add (close_button); + + animation = new AnimateTimer ((x) => { return x; }, AnimateTimer.INSTANT); + animation.animate.connect (() => { queue_draw (); }); + show.connect (() => { animation.reset(); }); + } + + public void close () + { + var start_value = 1.0f - animation.progress; + animation = new AnimateTimer ((x) => { return start_value + x; }, AnimateTimer.INSTANT); + animation.animate.connect ((p) => + { + queue_draw (); + + if (p >= 1.0f) + { + animation.stop (); + closed (); + } + }); + + closing = true; + animation.reset(); + } + + private void rebuild_background () + { + bg_surface = null; + queue_draw (); + } + + private void update_background_color () + { + // Apply the same color corrections we do in Unity + // For reference, see unity's unity-shared/BGHash.cpp + double hue, saturation, value; + const double COLOR_ALPHA = 0.72f; + + Gdk.RGBA color = background.average_color; + Gtk.RGB.to_hsv (color.red, color.green, color.blue, + out hue, out saturation, out value); + + if (saturation < 0.08) + { + // Got a grayscale image + avg_color = {0.18f, 0.20f, 0.21f, COLOR_ALPHA }; + } + else + { + const Gdk.RGBA[] cmp_colors = + { + {84/255.0f, 14/255.0f, 68/255.0f, 1.0f}, + {110/255.0f, 11/255.0f, 42/255.0f, 1.0f}, + {132/255.0f, 22/255.0f, 23/255.0f, 1.0f}, + {132/255.0f, 55/255.0f, 27/255.0f, 1.0f}, + {134/255.0f, 77/255.0f, 32/255.0f, 1.0f}, + {133/255.0f, 127/255.0f, 49/255.0f, 1.0f}, + {29/255.0f, 99/255.0f, 49/255.0f, 1.0f}, + {17/255.0f, 88/255.0f, 46/255.0f, 1.0f}, + {14/255.0f, 89/255.0f, 85/255.0f, 1.0f}, + {25/255.0f, 43/255.0f, 89/255.0f, 1.0f}, + {27/255.0f, 19/255.0f, 76/255.0f, 1.0f}, + {2/255.0f, 192/255.0f, 212/255.0f, 1.0f} + }; + + avg_color = {0, 0, 0, 1}; + double closest_diff = 200.0f; + + foreach (var c in cmp_colors) + { + double cmp_hue, cmp_sat, cmp_value; + Gtk.RGB.to_hsv (c.red, c.green, c.blue, + out cmp_hue, out cmp_sat, out cmp_value); + double color_diff = Math.fabs (hue - cmp_hue); + + if (color_diff < closest_diff) + { + avg_color = c; + closest_diff = color_diff; + } + } + + double new_hue, new_saturation, new_value; + Gtk.RGB.to_hsv (avg_color.red, avg_color.green, avg_color.blue, + out new_hue, out new_saturation, out new_value); + + saturation = double.min (saturation, new_saturation); + saturation *= (2.0f - saturation); + value = double.min (double.min (value, new_value), 0.26f); + Gtk.HSV.to_rgb (hue, saturation, value, + out avg_color.red, out avg_color.green, out avg_color.blue); + avg_color.alpha = COLOR_ALPHA; + } + + rebuild_background (); + } + + public void set_active_monitor (Monitor m) + { + if (m == this.monitor || m.equals (this.monitor)) + return; + + monitor = m; + rebuild_background (); + set_size_request (monitor.width, monitor.height); + } + + public void focus_next () + { + (get_toplevel () as Gtk.Window).move_focus (Gtk.DirectionType.TAB_FORWARD); + } + + public void focus_prev () + { + (get_toplevel () as Gtk.Window).move_focus (Gtk.DirectionType.TAB_BACKWARD); + } + + public void cancel () + { + var widget = (get_toplevel () as Gtk.Window).get_focus (); + if (widget is DialogButton) + (get_toplevel () as Gtk.Window).set_focus (null); + else + close (); + } + + public override void size_allocate (Gtk.Allocation allocation) + { + base.size_allocate (allocation); + monitor_events.size_allocate (allocation); + + var content_allocation = Gtk.Allocation (); + int minimum_width, natural_width, minimum_height, natural_height; + vbox_events.get_preferred_width (out minimum_width, out natural_width); + vbox_events.get_preferred_height_for_width (minimum_width, out minimum_height, out natural_height); + content_allocation.x = allocation.x + (allocation.width - minimum_width) / 2; + content_allocation.y = allocation.y + (allocation.height - minimum_height) / 2; + content_allocation.width = minimum_width; + content_allocation.height = minimum_height; + vbox_events.size_allocate (content_allocation); + + var a = Gtk.Allocation (); + close_button.get_preferred_width (out minimum_width, out natural_width); + close_button.get_preferred_height (out minimum_height, out natural_height); + a.x = content_allocation.x - BORDER_EXTERNAL_SIZE + CLOSE_OFFSET; + a.y = content_allocation.y - BORDER_EXTERNAL_SIZE + CLOSE_OFFSET; + a.width = minimum_width; + a.height = minimum_height; + close_button.size_allocate (a); + } + + public override bool draw (Cairo.Context c) + { + if (corner_surface == null) + { + corner_surface = new Cairo.ImageSurface.from_png (Path.build_filename (Config.PKGDATADIR, "switcher_corner.png")); + left_surface = new Cairo.ImageSurface.from_png (Path.build_filename (Config.PKGDATADIR, "switcher_left.png")); + top_surface = new Cairo.ImageSurface.from_png (Path.build_filename (Config.PKGDATADIR, "switcher_top.png")); + corner_pattern = new Cairo.Pattern.for_surface (corner_surface); + left_pattern = new Cairo.Pattern.for_surface (left_surface); + left_pattern.set_extend (Cairo.Extend.REPEAT); + top_pattern = new Cairo.Pattern.for_surface (top_surface); + top_pattern.set_extend (Cairo.Extend.REPEAT); + } + + int width = vbox_events.get_allocated_width (); + int height = vbox_events.get_allocated_height (); + int x = (get_allocated_width () - width) / 2; + int y = (get_allocated_height () - height) / 2; + + if (animation.is_running) + c.push_group (); + + /* Darken background */ + c.set_source_rgba (0, 0, 0, 0.25); + c.paint (); + + if (bg_surface == null || animation.is_running) + { + /* Create a new blurred surface of the current surface */ + bg_surface = new Cairo.ImageSurface (Cairo.Format.ARGB32, width, height); + var bg_cr = new Cairo.Context (bg_surface); + + bg_cr.set_source_surface (c.get_target (), -x - monitor.x, -y - monitor.y); + bg_cr.rectangle (0, 0, width, height); + bg_cr.fill (); + + CairoUtils.ExponentialBlur.surface (bg_surface, BLUR_RADIUS); + } + + /* Background */ + c.save (); + c.translate (x, y); + + CairoUtils.rounded_rectangle (c, 0, 0, width, height, 4); + c.set_source_surface (bg_surface, 0, 0); + c.fill_preserve (); + c.set_source_rgba (avg_color.red, avg_color.green, avg_color.blue, avg_color.alpha); + c.fill (); + + c.restore(); + + /* Draw borders */ + x -= BORDER_EXTERNAL_SIZE; + y -= BORDER_EXTERNAL_SIZE; + width += BORDER_EXTERNAL_SIZE * 2; + height += BORDER_EXTERNAL_SIZE * 2; + + c.save (); + c.translate (x, y); + + /* Top left */ + var m = Cairo.Matrix.identity (); + corner_pattern.set_matrix (m); + c.set_source (corner_pattern); + c.rectangle (0, 0, BORDER_SIZE, BORDER_SIZE); + c.fill (); + + /* Top right */ + m = Cairo.Matrix.identity (); + m.translate (width, 0); + m.scale (-1, 1); + corner_pattern.set_matrix (m); + c.set_source (corner_pattern); + c.rectangle (width - BORDER_SIZE, 0, BORDER_SIZE, BORDER_SIZE); + c.fill (); + + /* Bottom left */ + m = Cairo.Matrix.identity (); + m.translate (0, height); + m.scale (1, -1); + corner_pattern.set_matrix (m); + c.set_source (corner_pattern); + c.rectangle (0, height - BORDER_SIZE, BORDER_SIZE, BORDER_SIZE); + c.fill (); + + /* Bottom right */ + m = Cairo.Matrix.identity (); + m.translate (width, height); + m.scale (-1, -1); + corner_pattern.set_matrix (m); + c.set_source (corner_pattern); + c.rectangle (width - BORDER_SIZE, height - BORDER_SIZE, BORDER_SIZE, BORDER_SIZE); + c.fill (); + + /* Left */ + m = Cairo.Matrix.identity (); + left_pattern.set_matrix (m); + c.set_source (left_pattern); + c.rectangle (0, BORDER_SIZE, BORDER_SIZE, height - BORDER_SIZE * 2); + c.fill (); + + /* Right */ + m = Cairo.Matrix.identity (); + m.translate (width, 0); + m.scale (-1, 1); + left_pattern.set_matrix (m); + c.set_source (left_pattern); + c.rectangle (width - BORDER_SIZE, BORDER_SIZE, BORDER_SIZE, height - BORDER_SIZE * 2); + c.fill (); + + /* Top */ + m = Cairo.Matrix.identity (); + top_pattern.set_matrix (m); + c.set_source (top_pattern); + c.rectangle (BORDER_SIZE, 0, width - BORDER_SIZE * 2, BORDER_SIZE); + c.fill (); + + /* Bottom */ + m = Cairo.Matrix.identity (); + m.translate (0, height); + m.scale (1, -1); + top_pattern.set_matrix (m); + c.set_source (top_pattern); + c.rectangle (BORDER_SIZE, height - BORDER_SIZE, width - BORDER_SIZE * 2, BORDER_SIZE); + c.fill (); + + c.restore (); + + var ret = base.draw (c); + + if (animation.is_running) + { + c.pop_group_to_source (); + c.paint_with_alpha (closing ? 1.0f - animation.progress : animation.progress); + } + + return ret; + } + + private DialogButton add_button (string text, string inactive_filename, string active_filename) + { + var b = new Gtk.Box (Gtk.Orientation.VERTICAL, BUTTON_TEXT_SPACE); + b.visible = true; + button_box.pack_start (b, false, false, 0); + + var label = new Gtk.Label (text); + var button = new DialogButton (inactive_filename, active_filename, null, label); + button.visible = true; + + b.pack_start (button, false, false, 0); + b.pack_start (label, false, false, 0); + + return button; + } +} + +private class DialogButton : Gtk.Button +{ + private string inactive_filename; + private string focused_filename; + private string? active_filename; + private Gtk.Image i; + private Gtk.Label? l; + + public DialogButton (string inactive_filename, string focused_filename, string? active_filename, Gtk.Label? label = null) + { + this.inactive_filename = inactive_filename; + this.focused_filename = focused_filename; + this.active_filename = active_filename; + relief = Gtk.ReliefStyle.NONE; + focus_on_click = false; + i = new Gtk.Image.from_file (inactive_filename); + i.visible = true; + add (i); + + l = label; + + if (l != null) + { + l.visible = true; + l.override_font (Pango.FontDescription.from_string ("Ubuntu Light 12")); + l.override_color (Gtk.StateFlags.NORMAL, { 1.0f, 1.0f, 1.0f, 0.0f }); + l.override_color (Gtk.StateFlags.FOCUSED, { 1.0f, 1.0f, 1.0f, 1.0f }); + l.override_color (Gtk.StateFlags.ACTIVE, { 1.0f, 1.0f, 1.0f, 1.0f }); + this.get_accessible ().set_name (l.get_text ()); + } + + UnityGreeter.add_style_class (this); + try + { + // Remove the default GtkButton paddings and border + var style = new Gtk.CssProvider (); + style.load_from_data ("* {padding: 0px 0px 0px 0px; border: 0px; }", -1); + get_style_context ().add_provider (style, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION); + } + catch (Error e) + { + debug ("Internal error loading session chooser style: %s", e.message); + } + } + + public override bool enter_notify_event (Gdk.EventCrossing event) + { + grab_focus (); + return base.enter_notify_event (event); + } + + public override bool leave_notify_event (Gdk.EventCrossing event) + { + (get_toplevel () as Gtk.Window).set_focus (null); + return base.leave_notify_event (event); + } + + public override bool draw (Cairo.Context c) + { + i.draw (c); + return true; + } + + public override void state_flags_changed (Gtk.StateFlags previous_state) + { + var new_flags = get_state_flags (); + + if ((new_flags & Gtk.StateFlags.PRELIGHT) != 0 && !can_focus || + (new_flags & Gtk.StateFlags.FOCUSED) != 0) + { + if ((new_flags & Gtk.StateFlags.ACTIVE) != 0 && active_filename != null) + i.set_from_file (active_filename); + else + i.set_from_file (focused_filename); + } + else + { + i.set_from_file (inactive_filename); + } + + if (l != null) + l.set_state_flags (new_flags, true); + + base.state_flags_changed (previous_state); + } +} |