aboutsummaryrefslogtreecommitdiff
path: root/src/dash-entry.vala
diff options
context:
space:
mode:
Diffstat (limited to 'src/dash-entry.vala')
-rw-r--r--src/dash-entry.vala319
1 files changed, 319 insertions, 0 deletions
diff --git a/src/dash-entry.vala b/src/dash-entry.vala
new file mode 100644
index 0000000..9528705
--- /dev/null
+++ b/src/dash-entry.vala
@@ -0,0 +1,319 @@
+/* -*- Mode: Vala; indent-tabs-mode: nil; tab-width: 4 -*-
+ *
+ * Copyright (C) 2012 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: Michael Terry <michael.terry@canonical.com>
+ */
+
+/* Vala's vapi for gtk3 is broken for lookup_color (it forgets the out keyword) */
+[CCode (cheader_filename = "gtk/gtk.h")]
+extern bool gtk_style_context_lookup_color (Gtk.StyleContext ctx, string color_name, out Gdk.RGBA color);
+
+public class DashEntry : Gtk.Entry, Fadable
+{
+ public static string font = "Ubuntu 14";
+ public signal void respond ();
+
+ public string constant_placeholder_text { get; set; }
+ public bool can_respond { get; set; default = true; }
+
+ private bool _did_respond;
+ public bool did_respond
+ {
+ get
+ {
+ return _did_respond;
+ }
+ set
+ {
+ _did_respond = value;
+ if (value)
+ set_state_flags (Gtk.StateFlags.ACTIVE, false);
+ else
+ unset_state_flags (Gtk.StateFlags.ACTIVE);
+ queue_draw ();
+ }
+ }
+
+ private static const string NO_BORDER_CLASS = "unity-greeter-no-border";
+
+ protected FadeTracker fade_tracker { get; protected set; }
+ private Gdk.Window arrow_win;
+ private static Gdk.Pixbuf arrow_pixbuf;
+
+ construct
+ {
+ fade_tracker = new FadeTracker (this);
+
+ notify["can-respond"].connect (queue_draw);
+ button_press_event.connect (button_press_event_cb);
+
+ if (arrow_pixbuf == null)
+ {
+ var filename = Path.build_filename (Config.PKGDATADIR, "arrow_right.png");
+ try
+ {
+ arrow_pixbuf = new Gdk.Pixbuf.from_file (filename);
+ }
+ catch (Error e)
+ {
+ debug ("Internal error loading arrow icon: %s", e.message);
+ }
+ }
+
+ override_font (Pango.FontDescription.from_string (font));
+
+ var style_ctx = get_style_context ();
+
+ try
+ {
+ var padding_provider = new Gtk.CssProvider ();
+ var css = "* {padding-right: %dpx;}".printf (get_arrow_size ());
+ padding_provider.load_from_data (css, -1);
+ style_ctx.add_provider (padding_provider,
+ Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION);
+ }
+ catch (Error e)
+ {
+ debug ("Internal error loading padding style: %s", e.message);
+ }
+
+ // We add the styles and classes we need for normal operation of the
+ // spinner animation. These are always "on" and we just turn them off
+ // right before drawing our parent class's draw function. This is done
+ // opt-out like that rather than just turning the styles on when we
+ // need to draw the spinner because the animation doesn't work right
+ // otherwise. See the draw() function for how we turn it off.
+ var no_border_provider = new Gtk.CssProvider ();
+ try
+ {
+ var css = ".%s {border: 0px;}".printf (NO_BORDER_CLASS);
+ no_border_provider.load_from_data (css, -1);
+ }
+ catch (Error e)
+ {
+ debug ("Internal error loading spinner style: %s", e.message);
+ }
+ style_ctx.add_provider (no_border_provider,
+ Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION);
+ style_ctx.add_class (NO_BORDER_CLASS);
+ style_ctx.add_class (Gtk.STYLE_CLASS_SPINNER);
+ }
+
+ public override bool draw (Cairo.Context c)
+ {
+ var style_ctx = get_style_context ();
+
+ // See construct method for explanation of why we remove classes
+ style_ctx.save ();
+ style_ctx.remove_class (Gtk.STYLE_CLASS_SPINNER);
+ style_ctx.remove_class (NO_BORDER_CLASS);
+ c.save ();
+ c.push_group ();
+ base.draw (c);
+ c.pop_group_to_source ();
+ c.paint_with_alpha (fade_tracker.alpha);
+ c.restore ();
+ style_ctx.restore ();
+
+ /* Now draw the prompt text */
+ if (get_text_length () == 0 && constant_placeholder_text.length > 0)
+ draw_prompt_text (c);
+
+ /* Draw activity spinner if we need to */
+ if (did_respond)
+ draw_spinner (c);
+ else if (can_respond && get_text_length () > 0)
+ draw_arrow (c);
+
+ return false;
+ }
+
+ private void draw_spinner (Cairo.Context c)
+ {
+ c.save ();
+
+ var style_ctx = get_style_context ();
+ var arrow_size = get_arrow_size ();
+ Gtk.cairo_transform_to_window (c, this, arrow_win);
+ style_ctx.render_activity (c, 0, 0, arrow_size, arrow_size);
+
+ c.restore ();
+ }
+
+ private void draw_arrow (Cairo.Context c)
+ {
+ if (arrow_pixbuf == null)
+ return;
+
+ c.save ();
+
+ var arrow_size = get_arrow_size ();
+ Gtk.cairo_transform_to_window (c, this, arrow_win);
+ c.translate (arrow_size - arrow_pixbuf.get_width () - 1, 0); // right align
+ Gdk.cairo_set_source_pixbuf (c, arrow_pixbuf, 0, 0);
+
+ c.paint ();
+ c.restore ();
+ }
+
+ private void draw_prompt_text (Cairo.Context c)
+ {
+ c.save ();
+
+ /* Position text */
+ int x, y;
+ get_layout_offsets (out x, out y);
+ c.move_to (x, y);
+
+ /* Set foreground color */
+ var fg = Gdk.RGBA ();
+ var context = get_style_context ();
+ if (!gtk_style_context_lookup_color (context, "placeholder_text_color", out fg))
+ fg.parse ("#888");
+ c.set_source_rgba (fg.red, fg.green, fg.blue, fg.alpha);
+
+ /* Draw text */
+ var layout = create_pango_layout (constant_placeholder_text);
+ layout.set_font_description (Pango.FontDescription.from_string ("Ubuntu 13"));
+ Pango.cairo_show_layout (c, layout);
+
+ c.restore ();
+ }
+
+ public override void activate ()
+ {
+ base.activate ();
+ if (can_respond)
+ {
+ did_respond = true;
+ respond ();
+ }
+ else
+ {
+ get_toplevel ().child_focus (Gtk.DirectionType.TAB_FORWARD);
+ }
+ }
+
+ public bool button_press_event_cb (Gdk.EventButton event)
+ {
+ if (event.window == arrow_win && get_text_length () > 0)
+ {
+ activate ();
+ return true;
+ }
+ else
+ return false;
+ }
+
+ private int get_arrow_size ()
+ {
+ // height is larger than width for the arrow, so we measure using that
+ if (arrow_pixbuf != null)
+ return arrow_pixbuf.get_height ();
+ else
+ return 20; // Shouldn't happen
+ }
+
+ private void get_arrow_location (out int x, out int y)
+ {
+ var arrow_size = get_arrow_size ();
+
+ Gtk.Allocation allocation;
+ get_allocation (out allocation);
+
+ // height is larger than width for the arrow, so we measure using that
+ var margin = (allocation.height - arrow_size) / 2;
+
+ x = allocation.x + allocation.width - margin - arrow_size;
+ y = allocation.y + margin;
+ }
+
+ public override void size_allocate (Gtk.Allocation allocation)
+ {
+ base.size_allocate (allocation);
+
+ if (arrow_win == null)
+ return;
+
+ int arrow_x, arrow_y;
+ get_arrow_location (out arrow_x, out arrow_y);
+ var arrow_size = get_arrow_size ();
+
+ arrow_win.move_resize (arrow_x, arrow_y, arrow_size, arrow_size);
+ }
+
+ public override void realize ()
+ {
+ base.realize ();
+
+ var cursor = new Gdk.Cursor (Gdk.CursorType.LEFT_PTR);
+ var attrs = Gdk.WindowAttr ();
+ attrs.x = 0;
+ attrs.y = 0;
+ attrs.width = 1;
+ attrs.height = 1;
+ attrs.cursor = cursor;
+ attrs.wclass = Gdk.WindowWindowClass.INPUT_ONLY;
+ attrs.window_type = Gdk.WindowType.CHILD;
+ attrs.event_mask = get_events () |
+ Gdk.EventMask.BUTTON_PRESS_MASK;
+
+ arrow_win = new Gdk.Window (get_window (), attrs,
+ Gdk.WindowAttributesType.X |
+ Gdk.WindowAttributesType.Y |
+ Gdk.WindowAttributesType.CURSOR);
+ arrow_win.ref ();
+ arrow_win.set_user_data (this);
+ }
+
+ public override void unrealize ()
+ {
+ if (arrow_win != null)
+ {
+ arrow_win.destroy ();
+ arrow_win = null;
+ }
+ base.unrealize ();
+ }
+
+ public override void map ()
+ {
+ base.map ();
+ if (arrow_win != null)
+ arrow_win.show ();
+ }
+
+ public override void unmap ()
+ {
+ if (arrow_win != null)
+ arrow_win.hide ();
+ base.unmap ();
+ }
+
+ public override bool key_press_event (Gdk.EventKey event)
+ {
+ // This is a workaroud for bug https://launchpad.net/bugs/944159
+ // The problem is that orca seems to not notice that it's in a password
+ // field on startup. We just need to kick orca in the pants.
+ if (UnityGreeter.singleton.orca_needs_kick)
+ {
+ Signal.emit_by_name (get_accessible (), "focus-event", true);
+ UnityGreeter.singleton.orca_needs_kick = false;
+ }
+
+ return base.key_press_event (event);
+ }
+}