~ubuntu-branches/ubuntu/oneiric/unity-greeter/oneiric

« back to all changes in this revision

Viewing changes to .pc/disable-fuse.patch/src/user-list.vala

  • Committer: Package Import Robot
  • Author(s): Robert Ancell
  • Date: 2011-09-14 16:30:31 UTC
  • mfrom: (1.1.4 upstream)
  • Revision ID: package-import@ubuntu.com-20110914163031-xf652szb06rgus03
Tags: 0.0.7-0ubuntu1
* New upstream release: (LP: #843621)
  - Show caps-lock warning in password field
  - Use constant time animation
  - Render background images in a background thread
  - Load indicators in a thread
  - Add timing to logs
  - Add more logging messages
  - Fade out long names (LP: #844047)
  - Wait until background is loaded before showing the main window - stops
    a white flash being seen on startup. (LP: #836479)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
/*
2
 
 * Copyright (C) 2011 Canonical Ltd
3
 
 *
4
 
 * This program is free software: you can redistribute it and/or modify
5
 
 * it under the terms of the GNU General Public License version 3 as
6
 
 * published by the Free Software Foundation.
7
 
 *
8
 
 * This program is distributed in the hope that it will be useful,
9
 
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10
 
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
 
 * GNU General Public License for more details.
12
 
 *
13
 
 * You should have received a copy of the GNU General Public License
14
 
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
15
 
 *
16
 
 * Authored by: Robert Ancell <robert.ancell@canonical.com>
17
 
 */
18
 
 
19
 
private class UserEntry
20
 
{
21
 
    public int index;
22
 
 
23
 
    public string? name;
24
 
 
25
 
    /* Label to display */
26
 
    public string label;
27
 
 
28
 
    public string? background_filename;
29
 
    public Cairo.Pattern background_pattern;
30
 
 
31
 
    public double y;
32
 
    public double target_y;
33
 
    public double direction;
34
 
    public double width;
35
 
 
36
 
    public bool is_active;
37
 
 
38
 
    public bool moving
39
 
    {
40
 
        get { return y != target_y; }
41
 
    }
42
 
}
43
 
 
44
 
private class SessionMenuItem : Gtk.RadioMenuItem
45
 
{
46
 
    public string session_name;
47
 
}
48
 
 
49
 
public class UserList : Gtk.Container
50
 
{
51
 
    public string default_background = "#000000";
52
 
 
53
 
    private int grid_size = 42;
54
 
    private int grid_x_offset;
55
 
    private int grid_y_offset;
56
 
    private int box_width = 7;
57
 
 
58
 
    private List<UserEntry> entries = null;
59
 
    private UserEntry? selected_entry = null;
60
 
    private UserEntry? old_selected_entry = null;
61
 
 
62
 
    private uint scroll_animate_timer = 0;
63
 
 
64
 
    private uint background_animate_timer = 0;
65
 
    private double background_alpha;
66
 
 
67
 
    private List<Gtk.Widget> children;
68
 
 
69
 
    private Gtk.MenuBar menubar;
70
 
    private GLib.List<Indicator.Object> indicators = null;
71
 
    
72
 
    private string? error;
73
 
    private string? message;
74
 
 
75
 
    private Cairo.FontFace font_face;
76
 
    private Cairo.ImageSurface logo_surface;
77
 
    private Cairo.Pattern logo_pattern;
78
 
 
79
 
    private Gtk.Entry prompt_entry;
80
 
    private Gtk.Button login_button;
81
 
    private Gtk.Button options_button;
82
 
    private Gtk.Menu options_menu;
83
 
    unowned GLib.SList<SessionMenuItem> session_group = null;
84
 
 
85
 
    private bool complete = false;
86
 
 
87
 
    public signal void user_selected (string? username);
88
 
    public signal void respond_to_prompt (string text);
89
 
    public signal void start_session ();
90
 
    
91
 
    public string? selected
92
 
    {
93
 
        get { if (selected_entry == null) return null; return selected_entry.name; }
94
 
    }
95
 
 
96
 
    public string? session
97
 
    {
98
 
        get
99
 
        {
100
 
            foreach (var item in session_group)
101
 
            {
102
 
                if (item.active)
103
 
                    return item.session_name;
104
 
            }
105
 
            return null;
106
 
        }
107
 
        set
108
 
        {
109
 
            foreach (var item in session_group)
110
 
            {
111
 
                if (item.session_name == value)
112
 
                {
113
 
                    item.active = true;
114
 
                    return;
115
 
                }
116
 
            }
117
 
        }
118
 
    }
119
 
 
120
 
    public UserList ()
121
 
    {
122
 
        can_focus = false;
123
 
        background_alpha = 1.0;
124
 
 
125
 
        FreeType.Library library;
126
 
        FreeType.init (out library);
127
 
        FreeType.Face face;
128
 
        FreeType.new_face (library, "/usr/share/fonts/truetype/ubuntu-font-family/Ubuntu-R.ttf", 0, out face);
129
 
        font_face = Cairo.ft_font_face_create_for_ft_face (face, 0);
130
 
 
131
 
        logo_surface = new Cairo.ImageSurface.from_png (Path.build_filename (Config.PKGDATADIR, "logo.png", null));
132
 
        logo_pattern = new Cairo.Pattern.for_surface (logo_surface);
133
 
 
134
 
        menubar = new Gtk.MenuBar ();
135
 
 
136
 
        menubar.draw.connect_after (menubar_draw_cb);
137
 
        menubar.pack_direction = Gtk.PackDirection.RTL;
138
 
        menubar.show ();
139
 
        add (menubar);
140
 
 
141
 
        var i = new Gtk.MenuItem.with_label (Posix.utsname ().nodename);
142
 
        i.right_justified = true;
143
 
        i.show ();
144
 
        menubar.append (i);
145
 
 
146
 
        prompt_entry = new Gtk.Entry ();
147
 
        prompt_entry.invisible_char = '✻';
148
 
        prompt_entry.has_frame = false;
149
 
        var b = Gtk.Border ();
150
 
        b.left = 15;
151
 
        b.right = 15;
152
 
        b.top = 15;
153
 
        b.bottom = 15;
154
 
        prompt_entry.set_inner_border (b);
155
 
        prompt_entry.activate.connect (prompt_entry_activate_cb);
156
 
        add (prompt_entry);
157
 
 
158
 
        login_button = new Gtk.Button ();
159
 
        var label = new Gtk.Label ("<span font_size=\"large\">" + _("Login") + "</span>");
160
 
        label.use_markup = true;
161
 
        label.show ();
162
 
        login_button.add (label);
163
 
        login_button.clicked.connect (login_button_clicked_cb);
164
 
        add (login_button);
165
 
 
166
 
        options_button = new Gtk.Button ();
167
 
        var image = new Gtk.Image.from_file (Path.build_filename (Config.PKGDATADIR, "cog.png", null));
168
 
        image.show ();
169
 
        options_button.relief = Gtk.ReliefStyle.NONE;
170
 
        options_button.add (image);
171
 
        options_button.clicked.connect (options_button_clicked_cb);
172
 
        add (options_button);
173
 
 
174
 
        options_menu = new Gtk.Menu ();
175
 
 
176
 
        setup_indicators ();
177
 
    }
178
 
 
179
 
    private Cairo.Context menubar_cairo_context;
180
 
 
181
 
    private void draw_child_cb (Gtk.Widget child)
182
 
    {
183
 
        menubar.propagate_draw (child, menubar_cairo_context);
184
 
    }
185
 
 
186
 
    private bool menubar_draw_cb (Cairo.Context c)
187
 
    {
188
 
        draw_background (c);
189
 
 
190
 
        menubar_cairo_context = c;
191
 
        menubar.forall (draw_child_cb);
192
 
 
193
 
        return false;
194
 
    }
195
 
 
196
 
    async void greeter_set_env (string key, string val)
197
 
    {
198
 
        GLib.Environment.set_variable (key, val, true);
199
 
 
200
 
        /* And also set it in the DBus activation environment so that any
201
 
         * indicator services pick it up. */
202
 
        try
203
 
        {
204
 
            var proxy = new GLib.DBusProxy.for_bus_sync (GLib.BusType.SESSION,
205
 
                                                         GLib.DBusProxyFlags.NONE, null, 
206
 
                                                         "org.freedesktop.DBus",
207
 
                                                         "/org/freedesktop/DBus",
208
 
                                                         "org.freedesktop.DBus",
209
 
                                                         null);
210
 
 
211
 
            var builder = new GLib.VariantBuilder (GLib.VariantType.ARRAY);
212
 
            builder.add ("{ss}", key, val);
213
 
 
214
 
            yield proxy.call ("UpdateActivationEnvironment",
215
 
                              new GLib.Variant ("(a{ss})", builder),
216
 
                              GLib.DBusCallFlags.NONE, -1, null);
217
 
        }
218
 
        catch (Error e)
219
 
        {
220
 
            warning ("Could not get set environment for indicators: %s", e.message);
221
 
            return;
222
 
        }
223
 
    }
224
 
 
225
 
    async void setup_indicators ()
226
 
    {
227
 
        greeter_set_env ("INDICATOR_GREETER_MODE", "1"); // reduced functionality
228
 
        greeter_set_env ("GIO_USE_VFS", "local"); // no gvfsd
229
 
        greeter_set_env ("RUNNING_UNDER_GDM", "1"); // for gnome-settings-daemon
230
 
 
231
 
        load_indicator ("/usr/lib/indicators3/6/libsession.so");
232
 
        load_indicator ("/usr/lib/indicators3/6/libdatetime.so");
233
 
        load_indicator ("/usr/lib/indicators3/6/libpower.so");
234
 
        load_indicator ("/usr/lib/indicators3/6/libsoundmenu.so");
235
 
    }
236
 
 
237
 
    public void show_prompt (string text, bool secret = false)
238
 
    {
239
 
        login_button.hide ();
240
 
        message = text;
241
 
        prompt_entry.text = "";
242
 
        prompt_entry.show ();
243
 
        prompt_entry.visibility = !secret;
244
 
        prompt_entry.grab_focus ();
245
 
        queue_draw ();
246
 
    }
247
 
 
248
 
    public void show_authenticated ()
249
 
    {
250
 
        prompt_entry.hide ();
251
 
        message = "";
252
 
        login_button.show ();
253
 
        login_button.grab_focus ();
254
 
        queue_draw ();
255
 
    }
256
 
 
257
 
    public void login_complete ()
258
 
    {
259
 
        complete = true;
260
 
        sensitive = false;
261
 
 
262
 
        error = null;
263
 
        message = _("Logging in...");
264
 
 
265
 
        login_button.hide ();
266
 
        prompt_entry.hide ();
267
 
 
268
 
        queue_draw ();
269
 
    }
270
 
 
271
 
    public void set_error (string? text)
272
 
    {
273
 
        error = text;
274
 
        queue_draw ();
275
 
    }
276
 
    
277
 
    private void load_indicator (string filename)
278
 
    {
279
 
        var io = new Indicator.Object.from_file (filename);
280
 
        indicators.append (io);
281
 
        io.entry_added.connect (indicator_added_cb);
282
 
        io.entry_removed.connect (indicator_removed_cb);
283
 
        foreach (var entry in io.get_entries ())
284
 
            indicator_added_cb (entry);
285
 
    }
286
 
 
287
 
    private void indicator_added_cb (Indicator.ObjectEntry entry)
288
 
    {
289
 
        var menuitem = new Gtk.MenuItem ();
290
 
        menuitem.show ();
291
 
 
292
 
        var hbox = new Gtk.HBox (false, 3);
293
 
        menuitem.add (hbox);
294
 
        hbox.show ();
295
 
 
296
 
        if (entry.image != null)
297
 
            hbox.pack_start (entry.image, false, false, 0);
298
 
        if (entry.label != null)
299
 
            hbox.pack_start (entry.label, false, false, 0);
300
 
        if (entry.menu != null)
301
 
            menuitem.submenu = entry.menu;
302
 
 
303
 
        menubar.insert (menuitem, (int) menubar.get_children ().length () - 1);
304
 
    }
305
 
 
306
 
    private void indicator_removed_cb (Indicator.ObjectEntry entry)
307
 
    {
308
 
        // FIXME
309
 
    }
310
 
 
311
 
    /* Number of entries in the list */
312
 
    private uint n_entries
313
 
    {
314
 
        get { return entries.length (); }
315
 
    }
316
 
 
317
 
    /* Half above the line, rounding down */
318
 
    private uint n_above
319
 
    {
320
 
        get { return (n_entries - 1) / 2; }
321
 
    }
322
 
 
323
 
    /* Half below the line, rounding up */
324
 
    private uint n_below
325
 
    {
326
 
        get { return n_entries / 2; }
327
 
    }
328
 
 
329
 
    /* Box in the middle taking up three rows */
330
 
    private int box_height = 3;
331
 
 
332
 
    /* Total height of the box and entries */
333
 
    private new int height
334
 
    {
335
 
        get { return ((int) n_entries - 1) + box_height; }
336
 
    }
337
 
    
338
 
    public void add_session (string name, string label)
339
 
    {
340
 
        var item = new SessionMenuItem ();
341
 
        item.set_group (session_group);
342
 
        item.session_name = name;
343
 
        item.label = label;
344
 
        item.show ();
345
 
        options_menu.append (item);
346
 
        session_group = (GLib.SList<SessionMenuItem>) item.get_group ();
347
 
    }
348
 
 
349
 
    public void add_entry (string? name, string label, string? background = null, bool is_active = false)
350
 
    {
351
 
        var e = new UserEntry ();
352
 
        e.name = name;
353
 
        e.label = label;
354
 
        e.background_filename = background;
355
 
        e.is_active = is_active;
356
 
        e.width = 5;
357
 
        e.index = (int) n_entries;
358
 
        entries.append (e);
359
 
 
360
 
        if (selected_entry == null)
361
 
            select_entry (e);
362
 
 
363
 
        foreach (var entry in entries)
364
 
        {
365
 
            update_entry_location (entry);
366
 
            entry.y = entry.target_y;
367
 
        }
368
 
 
369
 
        queue_draw ();
370
 
    }
371
 
 
372
 
    private void prompt_entry_activate_cb ()
373
 
    {
374
 
        respond_to_prompt (prompt_entry.text);
375
 
        prompt_entry.text = "";
376
 
    }
377
 
    
378
 
    private void login_button_clicked_cb ()
379
 
    {
380
 
        debug ("login %s", selected_entry.name);
381
 
        start_session ();
382
 
    }
383
 
    
384
 
    private void options_menu_position_cb (Gtk.Menu menu, out int x, out int y, out bool push_in)
385
 
    {
386
 
        Gtk.Allocation button_allocation;
387
 
        options_button.get_allocation (out button_allocation);
388
 
 
389
 
        get_window ().get_origin (out x, out y);
390
 
        x += button_allocation.x;
391
 
        y += button_allocation.y + button_allocation.height;
392
 
        push_in = true;
393
 
    }
394
 
 
395
 
    private void options_button_clicked_cb ()
396
 
    {
397
 
        options_menu.popup (null, null, options_menu_position_cb, 0, Gtk.get_current_event_time ());
398
 
    }
399
 
 
400
 
    private void update_entry_location (UserEntry entry)
401
 
    {
402
 
        /* Get the number of steps below the selected item */
403
 
        var offset = entry.index - selected_entry.index;
404
 
 
405
 
        /* If above then place before box */
406
 
        if (offset > (int) n_below)
407
 
            offset -= (int) n_entries;
408
 
        /* If below then place after box */
409
 
        else if (offset < - (int) n_above)
410
 
            offset += (int) n_entries;
411
 
 
412
 
        entry.target_y = offset;
413
 
 
414
 
        /* Move towards new location */
415
 
        if (entry.target_y >= entry.y)
416
 
            entry.direction = 1.0;
417
 
        else
418
 
            entry.direction = -1.0;
419
 
    }
420
 
 
421
 
    private bool scroll_animate_cb ()
422
 
    {
423
 
        var animating = false;
424
 
        foreach (var entry in entries)
425
 
        {
426
 
            if (entry.y != entry.target_y)
427
 
            {
428
 
                var speed = 0.1;
429
 
 
430
 
                var step = entry.target_y - entry.y;
431
 
                if (entry.direction < 0)
432
 
                    step = -step;
433
 
                if (step < 0)
434
 
                    step += height;
435
 
 
436
 
                /* If close enough finish moving */
437
 
                if (step <= speed)
438
 
                    entry.y = entry.target_y;
439
 
                else
440
 
                {
441
 
                    entry.y += speed * entry.direction;
442
 
 
443
 
                    /* Wrap around */
444
 
                    if (entry.direction < 0 && entry.y < - (int) n_above)
445
 
                        entry.y += n_entries;
446
 
                    if (entry.direction > 0 && entry.y >= n_below)
447
 
                        entry.y -= n_entries;
448
 
 
449
 
                }
450
 
            }
451
 
 
452
 
            if (entry.moving)
453
 
                animating = true;
454
 
        }
455
 
 
456
 
        // FIXME: Should just redraw box
457
 
        queue_draw ();
458
 
 
459
 
        /* Stop when we get there */
460
 
        if (!animating)
461
 
        {
462
 
            scroll_animate_timer = 0;
463
 
            debug ("stop scroll animation");
464
 
            return false;
465
 
        }
466
 
        else
467
 
            return true;
468
 
    }
469
 
 
470
 
    private bool background_animate_cb ()
471
 
    {
472
 
        background_alpha += 0.05;
473
 
        if (background_alpha > 1.0)
474
 
            background_alpha = 1.0;
475
 
 
476
 
        queue_draw ();
477
 
 
478
 
        /* Stop when we get there */
479
 
        if (background_alpha == 1.0)
480
 
        {
481
 
            background_animate_timer = 0;
482
 
            debug ("stop background animation");
483
 
            return false;
484
 
        }
485
 
        else
486
 
            return true;
487
 
    }
488
 
 
489
 
    private void select_entry (UserEntry entry)
490
 
    {
491
 
        debug ("select %s", entry.name);
492
 
 
493
 
        /* Roll everything in the same direction */
494
 
        var direction = 1.0;
495
 
        if (selected_entry != null && entry.y > selected_entry.y)
496
 
           direction = -1.0;
497
 
 
498
 
        old_selected_entry = selected_entry;
499
 
        selected_entry = entry;
500
 
 
501
 
        prompt_entry.hide ();
502
 
        login_button.hide ();
503
 
 
504
 
        user_selected (selected_entry.name);
505
 
 
506
 
        if (old_selected_entry != null)
507
 
        {
508
 
            background_alpha = 0.0;
509
 
            if (background_animate_timer == 0)
510
 
            {
511
 
                debug ("start background animation");
512
 
                background_animate_timer = Timeout.add (10, background_animate_cb);
513
 
            }
514
 
        }
515
 
 
516
 
        var animate = false;
517
 
        foreach (var e in entries)
518
 
        {
519
 
            update_entry_location (e);
520
 
            e.direction = direction;
521
 
 
522
 
            /* Move straight there if haven't selected anything previously */
523
 
            if (old_selected_entry == null)
524
 
                e.y = e.target_y;
525
 
 
526
 
            if (e.moving)
527
 
                animate = true;
528
 
        }
529
 
 
530
 
        if (animate && scroll_animate_timer == 0)
531
 
        {
532
 
            debug ("start scroll animation");
533
 
            scroll_animate_timer = Timeout.add (10, scroll_animate_cb);
534
 
        }
535
 
    }
536
 
 
537
 
    private UserEntry? get_entry_by_index (int index)
538
 
    {
539
 
        foreach (var entry in entries)
540
 
        {
541
 
            if (entry.index == index)
542
 
                return entry;
543
 
        }
544
 
 
545
 
        return null;
546
 
    }
547
 
 
548
 
    private void select_prev_entry ()
549
 
    {
550
 
        var index = selected_entry.index - 1;
551
 
        if (index < 0)
552
 
            index += (int) n_entries;
553
 
        select_entry (get_entry_by_index (index));
554
 
    }
555
 
 
556
 
    private void select_next_entry ()
557
 
    {
558
 
        var index = selected_entry.index + 1;
559
 
        if (index >= (int) n_entries)
560
 
            index -= (int) n_entries;
561
 
        select_entry (get_entry_by_index (index));
562
 
    }
563
 
 
564
 
    private void get_selected_location (out int x, out int y)
565
 
    {
566
 
        x = grid_x_offset + grid_size;
567
 
        y = grid_y_offset + (get_allocated_height () - box_height * grid_size) / 2;
568
 
    }
569
 
    
570
 
    public override void add (Gtk.Widget widget)
571
 
    {
572
 
        children.append (widget);
573
 
        if (get_realized ())
574
 
            widget.set_parent_window (get_window ());
575
 
        widget.set_parent (this);
576
 
    }
577
 
 
578
 
    public override void remove (Gtk.Widget widget)
579
 
    {
580
 
        widget.unparent ();
581
 
        children.remove (widget);
582
 
    }
583
 
 
584
 
    public override void forall_internal (bool include_internal, Gtk.Callback callback)
585
 
    {
586
 
        foreach (var child in children)
587
 
            callback (child);
588
 
    }
589
 
 
590
 
    public override void realize ()
591
 
    {
592
 
        set_realized (true);
593
 
 
594
 
        Gtk.Allocation allocation;
595
 
        get_allocation (out allocation);
596
 
 
597
 
        var attributes = Gdk.WindowAttr ();
598
 
        attributes.window_type = Gdk.WindowType.CHILD;
599
 
        attributes.x = allocation.x;
600
 
        attributes.y = allocation.y;
601
 
        attributes.width = allocation.width;
602
 
        attributes.height = allocation.height;
603
 
        attributes.wclass = Gdk.WindowWindowClass.OUTPUT;
604
 
        attributes.visual = get_visual ();
605
 
        attributes.event_mask = Gdk.EventMask.EXPOSURE_MASK | Gdk.EventMask.BUTTON_PRESS_MASK | Gdk.EventMask.BUTTON_RELEASE_MASK | Gdk.EventMask.KEY_PRESS_MASK;
606
 
        int attributes_mask = Gdk.WindowAttributesType.X | Gdk.WindowAttributesType.Y | Gdk.WindowAttributesType.VISUAL;
607
 
 
608
 
        var window = new Gdk.Window (get_parent_window (), attributes, attributes_mask);
609
 
        set_window (window);
610
 
        window.set_user_data (this);
611
 
 
612
 
        foreach (var child in children)
613
 
            child.set_parent_window (get_window ());
614
 
    }
615
 
 
616
 
    public override void map ()
617
 
    {
618
 
        set_mapped (true);
619
 
 
620
 
        foreach (var child in children)
621
 
            if (child.visible && !child.get_mapped ())
622
 
                child.map ();
623
 
 
624
 
        get_window ().show ();
625
 
    }
626
 
 
627
 
    public override void size_allocate (Gtk.Allocation allocation)
628
 
    {
629
 
        var resized = allocation.height != get_allocated_height () || allocation.width != get_allocated_width ();
630
 
 
631
 
        set_allocation (allocation);
632
 
        
633
 
        if (get_realized ())
634
 
            get_window ().move_resize (allocation.x, allocation.y, allocation.width, allocation.height);
635
 
 
636
 
        grid_x_offset = (int) (allocation.width % grid_size) / 2;
637
 
        grid_y_offset = (int) (allocation.height % grid_size) / 2;
638
 
 
639
 
        Gtk.Requisition natural_size;
640
 
        menubar.get_preferred_size (null, out natural_size);
641
 
        var child_allocation = Gtk.Allocation ();
642
 
        natural_size.height = 32;
643
 
        natural_size.width = get_allocated_width ();
644
 
        child_allocation.x = 0;
645
 
        child_allocation.y = 0;
646
 
        child_allocation.width = natural_size.width;
647
 
        child_allocation.height = natural_size.height;
648
 
        menubar.size_allocate (child_allocation);
649
 
 
650
 
        /* Put prompt entry and login button inside login box */
651
 
        int base_x, base_y;
652
 
        get_selected_location (out base_x, out base_y);
653
 
        child_allocation.x = base_x + grid_size / 2;
654
 
        child_allocation.y = base_y + grid_size * 2 - grid_size / 2;
655
 
        child_allocation.width = grid_size * (box_width - 1);
656
 
        child_allocation.height = grid_size;
657
 
        prompt_entry.size_allocate (child_allocation);
658
 
        login_button.size_allocate (child_allocation);
659
 
 
660
 
        child_allocation.x = base_x + box_width * grid_size - grid_size - grid_size / 4;
661
 
        child_allocation.y = base_y + grid_size / 4;
662
 
        child_allocation.width = grid_size;
663
 
        child_allocation.height = grid_size;
664
 
        options_button.size_allocate (child_allocation);
665
 
        options_button.show ();
666
 
 
667
 
        /* Regenerate backgrounds */
668
 
        if (resized)
669
 
        {
670
 
            foreach (var entry in entries)
671
 
                entry.background_pattern = null;
672
 
        }
673
 
    }
674
 
 
675
 
    private void draw_entries (Cairo.Context c)
676
 
    {
677
 
        foreach (var entry in entries)
678
 
        {
679
 
            c.save ();
680
 
            c.translate (0, entry.y * grid_size);
681
 
 
682
 
            if (entry.is_active)
683
 
            {
684
 
                c.move_to (8, grid_size / 2 + 0.5 - 4);
685
 
                c.rel_line_to (5, 4);
686
 
                c.rel_line_to (-5, 4);
687
 
                c.close_path ();
688
 
                c.set_source_rgba (1.0, 1.0, 1.0, 0.5);
689
 
                c.fill ();
690
 
            }
691
 
 
692
 
            c.set_font_size (0.5 * grid_size);
693
 
            Cairo.TextExtents extents;
694
 
            c.text_extents (entry.label, out extents);
695
 
            c.move_to (grid_size / 2, grid_size - (grid_size - (extents.height)) / 2);
696
 
            c.set_source_rgba (1.0, 1.0, 1.0, 0.5);
697
 
            c.show_text (entry.label);
698
 
 
699
 
            c.restore ();
700
 
        }
701
 
    }
702
 
 
703
 
    private Cairo.Pattern make_background (string? filename)
704
 
    {
705
 
        if (filename == null)
706
 
            filename = default_background;
707
 
 
708
 
        debug ("making background %s at %dx%d", filename, get_allocated_width (), get_allocated_height ());
709
 
 
710
 
        Gdk.Color color;
711
 
        if (Gdk.Color.parse (filename, out color))
712
 
        {
713
 
            return new Cairo.Pattern.rgb (color.red / 65535.0, color.green / 65535.0, color.blue / 65535.0);
714
 
        }
715
 
 
716
 
        Gdk.Pixbuf orig_image;
717
 
        try
718
 
        {
719
 
            orig_image = new Gdk.Pixbuf.from_file (filename);
720
 
        }
721
 
        catch (Error e)
722
 
        {
723
 
            debug ("Error loading background: %s", e.message);
724
 
            return new Cairo.Pattern.rgb (0, 0, 0);
725
 
        }
726
 
 
727
 
        var target_aspect = (double) get_allocated_width () / get_allocated_height ();
728
 
        var aspect = (double) orig_image.width / orig_image.height;
729
 
        double scale, offset_x = 0, offset_y = 0;
730
 
        if (aspect > target_aspect)
731
 
        {
732
 
            /* Fit height and trim sides */
733
 
            scale = (double) get_allocated_height () / orig_image.height;
734
 
            offset_x = (orig_image.width * scale - get_allocated_width ()) / 2;
735
 
        }
736
 
        else
737
 
        {
738
 
            /* Fit width and trim top and bottom */
739
 
            scale = (double) get_allocated_width () / orig_image.width;
740
 
            offset_y = (orig_image.height * scale - get_allocated_height ()) / 2;
741
 
        }
742
 
 
743
 
        var image = new Gdk.Pixbuf (orig_image.colorspace, orig_image.has_alpha, orig_image.bits_per_sample, get_allocated_width (), get_allocated_height ());
744
 
        orig_image.scale (image, 0, 0, get_allocated_width (), get_allocated_height (), -offset_x, -offset_y, scale, scale, Gdk.InterpType.BILINEAR);
745
 
 
746
 
        /* Overlay grid */
747
 
        var overlay_surface = new Cairo.ImageSurface (Cairo.Format.ARGB32, grid_size, grid_size);
748
 
        var oc = new Cairo.Context (overlay_surface);
749
 
        oc.rectangle (0, 0, 1, 1);
750
 
        oc.rectangle (grid_size - 1, 0, 1, 1);
751
 
        oc.rectangle (0, grid_size - 1, 1, 1);
752
 
        oc.rectangle (grid_size - 1, grid_size - 1, 1, 1);
753
 
        oc.set_source_rgba (1.0, 1.0, 1.0, 0.25);
754
 
        oc.fill ();
755
 
        var overlay = new Cairo.Pattern.for_surface (overlay_surface);
756
 
        var matrix = Cairo.Matrix.identity ();
757
 
        matrix.translate (-grid_x_offset, -grid_y_offset);
758
 
        overlay.set_matrix (matrix);
759
 
        overlay.set_extend (Cairo.Extend.REPEAT);
760
 
 
761
 
        /* Create background */
762
 
        var surface = new Cairo.ImageSurface (Cairo.Format.RGB24, get_allocated_width (), get_allocated_height ());
763
 
        var bc = new Cairo.Context (surface);
764
 
        Gdk.cairo_set_source_pixbuf (bc, image, 0, 0);
765
 
        bc.paint ();
766
 
 
767
 
        /* Draw overlay */
768
 
        bc.set_source (overlay);
769
 
        bc.rectangle (grid_size - 1, grid_size - 1, get_allocated_width () - grid_size * 2 + 2, get_allocated_height () - grid_size * 2 + 2);
770
 
        bc.fill ();
771
 
        
772
 
        /* Mask out dots under logo */
773
 
        bc.rectangle (0, get_allocated_height () - logo_surface.get_height (), logo_surface.get_width (), logo_surface.get_height ());
774
 
        Gdk.cairo_set_source_pixbuf (bc, image, 0, 0);
775
 
        bc.fill ();
776
 
 
777
 
        var pattern = new Cairo.Pattern.for_surface (surface);
778
 
        pattern.set_extend (Cairo.Extend.REPEAT);
779
 
 
780
 
        return pattern;
781
 
    }
782
 
 
783
 
    public Cairo.Pattern get_background ()
784
 
    {
785
 
        return get_background_for_user (selected_entry);
786
 
    }
787
 
 
788
 
    private Cairo.Pattern get_background_for_user (UserEntry? entry)
789
 
    {
790
 
        if (entry == null)
791
 
            return new Cairo.Pattern.rgb (0, 0, 0);
792
 
 
793
 
        if (entry.background_pattern == null)
794
 
            entry.background_pattern = make_background (entry.background_filename);
795
 
        return entry.background_pattern;
796
 
    }
797
 
    
798
 
    private void draw_background (Cairo.Context c)
799
 
    {
800
 
        if (background_alpha == 1.0)
801
 
        {
802
 
            c.set_source (get_background_for_user (selected_entry));
803
 
            c.paint ();
804
 
        }
805
 
        else
806
 
        {
807
 
            /* Draw old background */
808
 
            c.set_source (get_background_for_user (old_selected_entry));
809
 
            c.paint ();
810
 
 
811
 
            /* Draw new background */
812
 
            c.set_source (get_background_for_user (selected_entry));
813
 
            c.paint_with_alpha (background_alpha);
814
 
        }
815
 
    }
816
 
 
817
 
    public override bool draw (Cairo.Context c)
818
 
    {
819
 
        draw_background (c);
820
 
 
821
 
        c.set_font_face (font_face);
822
 
 
823
 
        /* Logo */
824
 
        c.save ();
825
 
        c.translate (0, get_allocated_height () - logo_surface.get_height ());
826
 
        c.set_source (logo_pattern);
827
 
        c.rectangle (0, 0, logo_surface.get_width (), logo_surface.get_height ());
828
 
        c.fill ();
829
 
        c.restore ();
830
 
 
831
 
        int base_x, base_y;
832
 
        get_selected_location (out base_x, out base_y);
833
 
 
834
 
        c.save ();
835
 
        c.translate (base_x, base_y);
836
 
 
837
 
        /* Draw entries above the box */
838
 
        c.save ();
839
 
        c.rectangle (0, -n_above * grid_size, box_width * grid_size, n_above * grid_size);
840
 
        c.clip ();
841
 
        draw_entries (c);
842
 
        c.translate (0, -n_entries * grid_size);
843
 
        draw_entries (c);
844
 
        c.restore ();
845
 
 
846
 
        /* Draw entries below the box */
847
 
        c.save ();
848
 
        c.rectangle (0, box_height * grid_size, box_width * grid_size, n_below * grid_size);
849
 
        c.clip ();
850
 
        c.translate (0, (box_height - 1) * grid_size);
851
 
        draw_entries (c);
852
 
        c.translate (0, n_entries * grid_size);
853
 
        draw_entries (c);
854
 
        c.restore ();
855
 
 
856
 
        /* Draw box */
857
 
        var border = 5;
858
 
        var box_w = box_width * grid_size - border * 2;
859
 
        var box_h = box_height * grid_size - border * 2;
860
 
        var box_r = 0.2 * grid_size;
861
 
        cairo_rounded_rectangle (c,
862
 
                                 border + 0.5, border + 0.5,
863
 
                                 box_w - 1, box_h - 1,
864
 
                                 box_r);
865
 
        c.set_source_rgba (0.0, 0.0, 0.0, 0.4);
866
 
        c.fill_preserve ();
867
 
        c.set_line_width (1.0);
868
 
        c.set_source_rgba (1.0, 1.0, 1.0, 0.25);
869
 
        c.stroke ();
870
 
 
871
 
        cairo_rounded_rectangle (c,
872
 
                                 border + 0.5 + 2, border + 0.5 + 2,
873
 
                                 box_w - 5, box_h - 5,
874
 
                                 box_r - 2);
875
 
        c.stroke ();
876
 
 
877
 
        /* Selected item */
878
 
        if (selected_entry != null)
879
 
        {
880
 
            Cairo.TextExtents extents;
881
 
            c.set_font_size (0.5 * grid_size);
882
 
            c.text_extents (selected_entry.label, out extents);
883
 
            var text_y = grid_size - (grid_size - border - extents.height) / 4;
884
 
 
885
 
            if (selected_entry.is_active)
886
 
            {
887
 
                c.move_to (8, text_y - extents.height / 2 + 0.5 - 4);
888
 
                c.rel_line_to (5, 4);
889
 
                c.rel_line_to (-5, 4);
890
 
                c.close_path ();
891
 
                c.set_source_rgb (1.0, 1.0, 1.0);
892
 
                c.fill ();
893
 
            }
894
 
            c.move_to (grid_size / 2, text_y);
895
 
            c.set_source_rgb (1.0, 1.0, 1.0);
896
 
            c.show_text (selected_entry.label);
897
 
        }
898
 
 
899
 
        if (error != null || message != null)
900
 
        {
901
 
            Cairo.TextExtents extents;
902
 
 
903
 
            string text;
904
 
            if (error == null)
905
 
                text = message;
906
 
            else
907
 
                text = error;
908
 
 
909
 
            c.set_font_size (0.3 * grid_size);
910
 
            c.text_extents (text, out extents);
911
 
            c.move_to (grid_size / 2, grid_size * 1.25);
912
 
            if (error != null)
913
 
                c.set_source_rgb (1.0, 0.0, 0.0);
914
 
            else
915
 
                c.set_source_rgb (1.0, 1.0, 1.0);
916
 
            c.show_text (text);
917
 
        }
918
 
 
919
 
        c.restore ();
920
 
 
921
 
        foreach (var child in children)
922
 
            propagate_draw (child, c);
923
 
 
924
 
        return false;
925
 
    }
926
 
 
927
 
    private void cairo_rounded_rectangle (Cairo.Context c, double x, double y, double width, double height, double radius)
928
 
    {
929
 
        var w = width - radius * 2;
930
 
        var h = height - radius * 2;
931
 
        var kappa = 0.5522847498 * radius;
932
 
        c.move_to (x + radius, y);
933
 
        c.rel_line_to (w, 0);
934
 
        c.rel_curve_to (kappa, 0, radius, radius - kappa, radius, radius);
935
 
        c.rel_line_to (0, h);
936
 
        c.rel_curve_to (0, kappa, kappa - radius, radius, -radius, radius);
937
 
        c.rel_line_to (-w, 0);
938
 
        c.rel_curve_to (-kappa, 0, -radius, kappa - radius, -radius, -radius);
939
 
        c.rel_line_to (0, -h);
940
 
        c.rel_curve_to (0, -kappa, radius - kappa, -radius, radius, -radius);
941
 
    }
942
 
 
943
 
    // FIXME: Don't seem to be defined in Vala
944
 
    private const uint KEY_Up = 0xff52;
945
 
    private const uint KEY_Down = 0xff54;
946
 
 
947
 
    public override bool key_press_event (Gdk.EventKey event)
948
 
    {
949
 
        switch (event.keyval)
950
 
        {
951
 
        case KEY_Up:
952
 
            select_prev_entry ();
953
 
            break;
954
 
        case KEY_Down:
955
 
            select_next_entry ();
956
 
            break;
957
 
        default:
958
 
            return false;
959
 
        }
960
 
 
961
 
        return true;
962
 
    }
963
 
 
964
 
    public override bool button_release_event (Gdk.EventButton event)
965
 
    {
966
 
        int base_x, base_y;
967
 
        get_selected_location (out base_x, out base_y);
968
 
        foreach (var entry in entries)
969
 
        {
970
 
            var y = entry.y;
971
 
            if (y > 0)
972
 
                y += box_height - 1;
973
 
 
974
 
            if (event.x >= base_x &&
975
 
                event.x <= base_x + entry.width * grid_size &&
976
 
                event.y >= base_y + y * grid_size &&
977
 
                event.y <= base_y + (y + 1) * grid_size)
978
 
            {
979
 
                select_entry (entry);
980
 
                return true;
981
 
            }
982
 
        }
983
 
 
984
 
        return false;
985
 
    }
986
 
}