1/* -*- Mode: Vala; indent-tabs-mode: nil; tab-width: 4 -*- 2 * 3 * Copyright (C) 2012 Canonical Ltd 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 version 3 as 7 * published by the Free Software Foundation. 8 * 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. 13 * 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/>. 16 * 17 * Authors: Michael Terry <michael.terry@canonical.com> 18 */ 19 20/* Vala's vapi for gtk3 is broken for lookup_color (it forgets the out keyword) */ 21[CCode (cheader_filename = "gtk/gtk.h")] 22extern bool gtk_style_context_lookup_color (Gtk.StyleContext ctx, string color_name, out Gdk.RGBA color); 23 24public class DashEntry : Gtk.Entry, Fadable 25{ 26 public static string font = "Ubuntu 14"; 27 public signal void respond (); 28 29 public string constant_placeholder_text { get; set; } 30 public bool can_respond { get; set; default = true; } 31 32 private bool _did_respond; 33 public bool did_respond 34 { 35 get 36 { 37 return _did_respond; 38 } 39 set 40 { 41 _did_respond = value; 42 if (value) 43 set_state_flags (Gtk.StateFlags.ACTIVE, false); 44 else 45 unset_state_flags (Gtk.StateFlags.ACTIVE); 46 queue_draw (); 47 } 48 } 49 50 protected FadeTracker fade_tracker { get; protected set; } 51 private Gdk.Window arrow_win; 52 private static Gdk.Pixbuf arrow_pixbuf; 53 54 construct 55 { 56 fade_tracker = new FadeTracker (this); 57 58 notify["can-respond"].connect (queue_draw); 59 button_press_event.connect (button_press_event_cb); 60 61 if (arrow_pixbuf == null) 62 { 63 var filename = Path.build_filename (Config.PKGDATADIR, "arrow_right.png"); 64 try 65 { 66 arrow_pixbuf = new Gdk.Pixbuf.from_file (filename); 67 } 68 catch (Error e) 69 { 70 debug ("Internal error loading arrow icon: %s", e.message); 71 } 72 } 73 74 override_font (Pango.FontDescription.from_string (font)); 75 76 var style_ctx = get_style_context (); 77 78 try 79 { 80 var padding_provider = new Gtk.CssProvider (); 81 var css = "* {padding-right: %dpx;}".printf (get_arrow_size ()); 82 padding_provider.load_from_data (css, -1); 83 style_ctx.add_provider (padding_provider, 84 Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION); 85 } 86 catch (Error e) 87 { 88 debug ("Internal error loading padding style: %s", e.message); 89 } 90 } 91 92 public override bool draw (Cairo.Context c) 93 { 94 var style_ctx = get_style_context (); 95 96 // See construct method for explanation of why we remove classes 97 style_ctx.save (); 98 c.save (); 99 c.push_group (); 100 base.draw (c); 101 c.pop_group_to_source (); 102 c.paint_with_alpha (fade_tracker.alpha); 103 c.restore (); 104 style_ctx.restore (); 105 106 /* Now draw the prompt text */ 107 if (get_text_length () == 0 && constant_placeholder_text.length > 0) 108 draw_prompt_text (c); 109 110 /* Draw activity spinner if we need to */ 111 if (did_respond) 112 draw_spinner (c); 113 else if (can_respond && get_text_length () > 0) 114 draw_arrow (c); 115 116 return false; 117 } 118 119 private void draw_spinner (Cairo.Context c) 120 { 121 c.save (); 122 123 var style_ctx = get_style_context (); 124 var arrow_size = get_arrow_size (); 125 Gtk.cairo_transform_to_window (c, this, arrow_win); 126 style_ctx.render_activity (c, 0, 0, arrow_size, arrow_size); 127 128 c.restore (); 129 } 130 131 private void draw_arrow (Cairo.Context c) 132 { 133 if (arrow_pixbuf == null) 134 return; 135 136 c.save (); 137 138 var arrow_size = get_arrow_size (); 139 Gtk.cairo_transform_to_window (c, this, arrow_win); 140 c.translate (arrow_size - arrow_pixbuf.get_width () - 1, 0); // right align 141 Gdk.cairo_set_source_pixbuf (c, arrow_pixbuf, 0, 0); 142 143 c.paint (); 144 c.restore (); 145 } 146 147 private void draw_prompt_text (Cairo.Context c) 148 { 149 c.save (); 150 151 /* Position text */ 152 int layout_width, layout_height; 153 Gdk.Rectangle rect; 154 155 var layout = create_pango_layout (constant_placeholder_text); 156 layout.set_font_description (Pango.FontDescription.from_string ("Ubuntu 13")); 157 layout.get_pixel_size (out layout_width, out layout_height); 158 159 get_text_area (out rect); 160 161 c.move_to (rect.x, rect.y + (rect.height / 2) - (layout_height / 2)); 162 163 /* Set foreground color */ 164 var fg = Gdk.RGBA (); 165 var context = get_style_context (); 166 if (!gtk_style_context_lookup_color (context, "placeholder_text_color", out fg)) 167 fg.parse ("#888"); 168 c.set_source_rgba (fg.red, fg.green, fg.blue, fg.alpha); 169 170 /* Draw text */ 171 Pango.cairo_show_layout (c, layout); 172 173 c.restore (); 174 } 175 176 public override void activate () 177 { 178 base.activate (); 179 if (can_respond) 180 { 181 did_respond = true; 182 respond (); 183 } 184 else 185 { 186 get_toplevel ().child_focus (Gtk.DirectionType.TAB_FORWARD); 187 } 188 } 189 190 public bool button_press_event_cb (Gdk.EventButton event) 191 { 192 if (event.window == arrow_win && get_text_length () > 0) 193 { 194 activate (); 195 return true; 196 } 197 else 198 return false; 199 } 200 201 private int get_arrow_size () 202 { 203 // height is larger than width for the arrow, so we measure using that 204 if (arrow_pixbuf != null) 205 return arrow_pixbuf.get_height (); 206 else 207 return 20; // Shouldn't happen 208 } 209 210 private void get_arrow_location (out int x, out int y) 211 { 212 var arrow_size = get_arrow_size (); 213 214 Gtk.Allocation allocation; 215 get_allocation (out allocation); 216 217 // height is larger than width for the arrow, so we measure using that 218 var margin = (allocation.height - arrow_size) / 2; 219 220 x = allocation.x + allocation.width - margin - arrow_size; 221 y = allocation.y + margin; 222 } 223 224 public override void size_allocate (Gtk.Allocation allocation) 225 { 226 base.size_allocate (allocation); 227 228 if (arrow_win == null) 229 return; 230 231 int arrow_x, arrow_y; 232 get_arrow_location (out arrow_x, out arrow_y); 233 var arrow_size = get_arrow_size (); 234 235 arrow_win.move_resize (arrow_x, arrow_y, arrow_size, arrow_size); 236 } 237 238 public override void realize () 239 { 240 base.realize (); 241 242 var cursor = new Gdk.Cursor.for_display (get_display (), Gdk.CursorType.LEFT_PTR); 243 var attrs = Gdk.WindowAttr (); 244 attrs.x = 0; 245 attrs.y = 0; 246 attrs.width = 1; 247 attrs.height = 1; 248 attrs.cursor = cursor; 249 attrs.wclass = Gdk.WindowWindowClass.INPUT_ONLY; 250 attrs.window_type = Gdk.WindowType.CHILD; 251 attrs.event_mask = get_events () | 252 Gdk.EventMask.BUTTON_PRESS_MASK; 253 254 arrow_win = new Gdk.Window (get_window (), attrs, 255 Gdk.WindowAttributesType.X | 256 Gdk.WindowAttributesType.Y | 257 Gdk.WindowAttributesType.CURSOR); 258 arrow_win.ref (); 259 arrow_win.set_user_data (this); 260 } 261 262 public override void unrealize () 263 { 264 if (arrow_win != null) 265 { 266 arrow_win.destroy (); 267 arrow_win = null; 268 } 269 base.unrealize (); 270 } 271 272 public override void map () 273 { 274 base.map (); 275 if (arrow_win != null) 276 arrow_win.show (); 277 } 278 279 public override void unmap () 280 { 281 if (arrow_win != null) 282 arrow_win.hide (); 283 base.unmap (); 284 } 285 286 public override bool key_press_event (Gdk.EventKey event) 287 { 288 // This is a workaroud for bug https://launchpad.net/bugs/944159 289 // The problem is that orca seems to not notice that it's in a password 290 // field on startup. We just need to kick orca in the pants. 291 if (SlickGreeter.singleton.orca_needs_kick) 292 { 293 Signal.emit_by_name (get_accessible (), "focus-event", true); 294 SlickGreeter.singleton.orca_needs_kick = false; 295 } 296 297 return base.key_press_event (event); 298 } 299} 300