[polari/user-list-update: 4/5] userList: Add some useful context to user entries



commit ea5976c6674382aef78290c73a8db85b51e06823
Author: Florian Müllner <fmuellner gnome org>
Date:   Wed Oct 2 16:11:27 2013 +0200

    userList: Add some useful context to user entries
    
    Currently the user list is purely informational by indicating who is
    present in a channel; make it more useful by displaying some basic
    /whois information when selecting a user and offering a context action
    to open a private conversation.

 data/resources/application.css |   14 +++
 src/application.js             |    1 +
 src/userList.js                |  199 ++++++++++++++++++++++++++++++++++++++-
 3 files changed, 208 insertions(+), 6 deletions(-)
---
diff --git a/data/resources/application.css b/data/resources/application.css
index 2f25b16..e07c51d 100644
--- a/data/resources/application.css
+++ b/data/resources/application.css
@@ -24,6 +24,20 @@
     border-width: 0 0 1px 0;
 }
 
+.polari-user-list .list-row {
+    -GtkWidget-focus-padding: 0;
+}
+
+.polari-user-list .list-row.expanded {
+    background-color: @content_view_bg;
+}
+
+.polari-user-list .list-row GtkFrame {
+    box-shadow: inset 0 2px 4px alpha(@polari_dark_bg_color, 0.8);
+    transition: all 250ms ease-out;
+    border-width: 0;
+}
+
 .polari-selection-toolbar,
 .polari-input-area {
     background-color: @polari_dark_bg_color;
diff --git a/src/application.js b/src/application.js
index db959d9..16931e1 100644
--- a/src/application.js
+++ b/src/application.js
@@ -42,6 +42,7 @@ const Application = new Lang.Class({
 
         window._ = Gettext.gettext;
         window.C_ = Gettext.pgettext;
+        window.ngettext = Gettext.ngettext;
 
         Gtk.init(null);
 
diff --git a/src/userList.js b/src/userList.js
index 085ec08..51e43bc 100644
--- a/src/userList.js
+++ b/src/userList.js
@@ -1,4 +1,6 @@
 const Gdk = imports.gi.Gdk;
+const Gio = imports.gi.Gio;
+const GLib = imports.gi.GLib;
 const Gtk = imports.gi.Gtk;
 const Pango = imports.gi.Pango;
 const Tp = imports.gi.TelepathyGLib;
@@ -112,17 +114,187 @@ const UserListRow = new Lang.Class({
         this._createWidget(user);
 
         this.widget.user = user;
+
+        this.widget.connect('unmap', Lang.bind(this, function() {
+            this._revealer.reveal_child = false;
+        }));
+        this.widget.connect('state-flags-changed',
+                            Lang.bind(this, this._updateArrowVisibility));
+
+        this._revealer.connect('notify::reveal-child',
+                               Lang.bind(this, this._onExpandedChanged));
+    },
+
+    get expand() {
+        return this._revealer.reveal_child;
+    },
+
+    set expand(expand) {
+        this._revealer.reveal_child = expand;
     },
 
     _createWidget: function(user) {
         this.widget = new Gtk.ListBoxRow();
-        let box = new Gtk.Box({ margin: 4, spacing: 4 });
-        box.add(new Gtk.Image({ icon_name: 'avatar-default-symbolic' }));
-        box.add(new Gtk.Label({ label: user.alias,
-                                halign: Gtk.Align.START,
-                                ellipsize: Pango.EllipsizeMode.END }));
-        this.widget.add(box);
+
+        let vbox = new Gtk.Box({ orientation: Gtk.Orientation.VERTICAL });
+        this.widget.add(vbox);
+
+        let hbox = new Gtk.Box({ margin: 4, spacing: 4 });
+        this._arrow = new Gtk.Arrow({ arrow_type: Gtk.ArrowType.RIGHT,
+                                      no_show_all: true });
+        hbox.add(new Gtk.Image({ icon_name: 'avatar-default-symbolic' }));
+        hbox.add(new Gtk.Label({ label: user.alias,
+                                 halign: Gtk.Align.START,
+                                 hexpand: true,
+                                 ellipsize: Pango.EllipsizeMode.END }));
+        hbox.add(this._arrow);
+        vbox.add(hbox);
+
+        this._revealer = new Gtk.Revealer({ reveal_child: false });
+        vbox.add(this._revealer);
+
+        let frame = new Gtk.Frame({ hexpand: true });
+        this._revealer.add(frame);
+
+        let box = new Gtk.Box({ orientation: Gtk.Orientation.VERTICAL,
+                                spacing: 6, margin: 6 });
+        frame.add(box);
+
+        this._spinnerBox = new Gtk.Box({ spacing: 6, margin: 12,
+                                         hexpand: true,
+                                         halign: Gtk.Align.CENTER });
+        this._spinner = new Gtk.Spinner();
+        this._spinnerBox.add(this._spinner);
+        this._spinnerBox.add(new Gtk.Label({ label: _("Loading details") }));
+        box.add(this._spinnerBox);
+
+        this._detailsGrid = new Gtk.Grid({ row_spacing: 6, column_spacing: 6,
+                                           hexpand: true });
+        box.add(this._detailsGrid);
+
+        if (user != user.connection.self_contact) {
+            let button = new Gtk.Button({ label: _("Message"),
+                                          margin_top: 12,
+                                          hexpand: true,
+                                          halign: Gtk.Align.END });
+            button.connect('clicked', Lang.bind(this, this._onButtonClicked));
+            user.connection.connect('notify::self-contact', function() {
+                if (user == user.connection.self_contact)
+                    button.destroy();
+            });
+            box.add(button);
+        }
+
         this.widget.show_all();
+    },
+
+    _formatLast: function(seconds) {
+        if (seconds < 60)
+            return ngettext("%d second ago",
+                            "%d seconds ago", seconds).format(seconds);
+
+        let minutes = seconds / 60;
+        if (minutes < 60)
+            return ngettext("%d minute ago",
+                            "%d minutes ago", minutes).format(minutes);
+
+        let hours = minutes / 60;
+        if (hours < 24)
+            return ngettext("%d hour ago",
+                            "%d hours ago", hours).format(hours);
+
+        let days = hours / 24;
+        if (days < 7)
+            return ngettext("%d day ago",
+                            "%d days ago", days).format(days);
+
+        let weeks = days / 7;
+        if (days < 30)
+            return ngettext("%d week ago",
+                            "%d weeks ago", weeks).format(weeks);
+
+        let months = days / 30;
+        return ngettext("%d month ago",
+                        "%d months ago", months).format(months);
+    },
+
+    _onContactInfoReady: function(c, res) {
+        let fn, last;
+        let info = this.widget.user.get_contact_info();
+        for (let i = 0; i < info.length; i++) {
+            if (info[i].field_name == 'fn')
+                fn = info[i].field_value[0];
+            else if (info[i].field_name == 'x-idle-time')
+                last = info[i].field_value[0];
+        }
+
+        if (!fn)
+            fn = this.widget.user.alias;
+
+        let row = 0;
+        let w = new Gtk.Label({ label: fn, ellipsize: Pango.EllipsizeMode.END,
+                                halign: Gtk.Align.START });
+        this._detailsGrid.attach(w, 0, row++, 2, 1);
+
+        if (last) {
+            w = new Gtk.Label({ label: _("Last Activity:"),
+                                valign: Gtk.Align.START });
+            this._detailsGrid.attach(w, 0, row, 1, 1);
+
+            w = new Gtk.Label({ label: this._formatLast(last), wrap: true,
+                                hexpand: true });
+            this._detailsGrid.attach(w, 1, row++, 1, 1);
+        }
+
+        this._detailsGrid.show_all();
+
+        this._spinner.stop();
+        this._spinnerBox.hide();
+    },
+
+    _onButtonClicked: function() {
+        let account = this.widget.user.connection.get_account();
+
+        let app = Gio.Application.get_default();
+        let action = app.lookup_action('message-user');
+        let time = Gtk.get_current_event().get_time();
+        action.activate(GLib.Variant.new('(ssu)',
+                                         [ account.get_object_path(),
+                                           this.widget.user.alias,
+                                           time ]));
+    },
+
+    _updateArrowVisibility: function() {
+        let flags = this.widget.get_state_flags();
+        this._arrow.visible = this.expand ||
+                              flags & Gtk.StateFlags.PRELIGHT ||
+                              flags & Gtk.StateFlags.FOCUSED;
+    },
+
+    _onExpandedChanged: function() {
+        if (this._revealer.reveal_child) {
+            this.widget.get_style_context().add_class('expanded');
+            this._arrow.arrow_type = Gtk.ArrowType.DOWN;
+
+            this._spinnerBox.show();
+            this._spinner.start();
+
+            this._detailsGrid.foreach(function(w) { w.destroy(); });
+
+            this._cancellable = new Gio.Cancellable();
+            this.widget.user.request_contact_info_async(this._cancellable,
+                                                        Lang.bind(this, this._onContactInfoReady));
+        } else {
+            this.widget.get_style_context().remove_class('expanded');
+            this._arrow.arrow_type = Gtk.ArrowType.RIGHT;
+            this._updateArrowVisibility();
+
+            this._spinner.stop();
+
+            if (this._cancellable)
+                this._cancellable.cancel();
+            this._cancellable = null;
+        }
     }
 });
 
@@ -141,8 +313,12 @@ const UserList = new Lang.Class({
         this._list.set_filter_func(Lang.bind(this, this._filterRows));
         this._list.set_sort_func(Lang.bind(this, this._sort));
 
+        this._list.connect('row-activated',
+                           Lang.bind(this, this._onRowActivated));
+
         this._room = room;
         this._rows = {};
+        this._activeRow = null;
 
         room.connect('member-renamed',
                      Lang.bind(this, this._onMemberRenamed));
@@ -195,6 +371,17 @@ const UserList = new Lang.Class({
         delete this._rows[member];
     },
 
+    _setActiveRow: function(row) {
+        if (this._activeRow && this._activeRow != row)
+            this._activeRow.expand = false;
+        this._activeRow = row;
+    },
+
+    _onRowActivated: function(list, row) {
+        this._setActiveRow(this._rows[row.user]);
+        this._activeRow.expand = !this._activeRow.expand;
+    },
+
     _sort: function(row1, row2) {
         return row1.user.alias.localeCompare(row2.user.alias);
     },


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]