[gnome-shell] user-menu: Implement new mockups



commit 0751a90bd93d89782f1a711b360fd54dad0651f6
Author: Florian MÃllner <fmuellner gnome org>
Date:   Thu Jul 28 16:59:03 2011 +0200

    user-menu: Implement new mockups
    
    The current user status menu allow to set the session status,
    which also influences the IM status when signed in with
    mission-control. However, the way it is presented to the user
    makes it hard to figure out how the statuses interact or that
    there are two distinct status in the first place.
    Therefore, use a separate control for each status, and update the
    overall look to match gnome-contacts.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=652837

 data/theme/gnome-shell.css |   43 +++++
 js/ui/userMenu.js          |  437 ++++++++++++++++++++++++++++++++++++++------
 2 files changed, 425 insertions(+), 55 deletions(-)
---
diff --git a/data/theme/gnome-shell.css b/data/theme/gnome-shell.css
index fa2b0b7..313d0b2 100644
--- a/data/theme/gnome-shell.css
+++ b/data/theme/gnome-shell.css
@@ -387,6 +387,49 @@ StTooltip StLabel {
     spacing: 4px;
 }
 
+.status-chooser {
+    spacing: .4em;
+}
+
+.status-chooser .popup-menu-item,
+.status-chooser-combo .popup-menu-item {
+    padding: .4em;
+}
+
+.status-chooser-user-icon {
+    border: 2px solid #8b8b8b;
+    border-radius: 5px;
+    width: 48pt;
+    height: 48pt;
+}
+
+.status-chooser-user-icon:hover {
+    border: 2px solid #bbbbbb;
+}
+
+.status-chooser-user-name {
+    font-weight: bold;
+    font-size: 1.3em;
+}
+
+.status-chooser-combo {
+    border: 1px solid transparent;
+}
+
+.status-chooser-combo.popup-combo-menu {
+    background-color: rgba(0,0,0,0.7);
+    padding: .4em 0em;
+    border-radius: 4px;
+    border: 1px solid #5f5f5f;
+    color: #ffffff;
+    font-size: 10.5pt;
+}
+
+.status-chooser-status-item,
+.status-chooser-combo > .popup-combobox-item {
+    spacing: .4em;
+}
+
 #legacyTray {
     spacing: 14px;
     padding-left: 14px;
diff --git a/js/ui/userMenu.js b/js/ui/userMenu.js
index 0d53b54..b9f9f16 100644
--- a/js/ui/userMenu.js
+++ b/js/ui/userMenu.js
@@ -22,11 +22,337 @@ const DISABLE_USER_SWITCH_KEY = 'disable-user-switching';
 const DISABLE_LOCK_SCREEN_KEY = 'disable-lock-screen';
 const DISABLE_LOG_OUT_KEY = 'disable-log-out';
 
+const WRAP_WIDTH = 150;
+const DIALOG_ICON_SIZE = 64;
+
+const IMStatus = {
+    AVAILABLE: 0,
+    BUSY: 1,
+    HIDDEN: 2,
+    AWAY: 3,
+    IDLE: 4,
+    OFFLINE: 5,
+    LAST: 6
+};
+
 // Adapted from gdm/gui/user-switch-applet/applet.c
 //
 // Copyright (C) 2004-2005 James M. Cape <jcape ignore-your tv>.
 // Copyright (C) 2008,2009 Red Hat, Inc.
 
+
+function IMStatusItem(label, iconName) {
+    this._init(label, iconName);
+}
+
+IMStatusItem.prototype = {
+    __proto__: PopupMenu.PopupBaseMenuItem.prototype,
+
+    _init: function(label, iconName) {
+        PopupMenu.PopupBaseMenuItem.prototype._init.call(this);
+
+        this.actor.add_style_class_name('status-chooser-status-item');
+
+        this._icon = new St.Icon({ style_class: 'popup-menu-icon' });
+        this.addActor(this._icon);
+
+        if (iconName)
+            this._icon.icon_name = iconName;
+
+        this.label = new St.Label({ text: label });
+        this.addActor(this.label);
+    }
+};
+
+function IMUserNameItem() {
+    this._init();
+}
+
+IMUserNameItem.prototype = {
+    __proto__: PopupMenu.PopupBaseMenuItem.prototype,
+
+    _init: function() {
+        PopupMenu.PopupBaseMenuItem.prototype._init.call(this,
+                                                         { reactive: false,
+                                                           style_class: 'status-chooser-user-name' });
+
+        this._wrapper = new Shell.GenericContainer();
+        this._wrapper.connect('get-preferred-width',
+                              Lang.bind(this, this._wrapperGetPreferredWidth));
+        this._wrapper.connect('get-preferred-height',
+                              Lang.bind(this, this._wrapperGetPreferredHeight));
+        this._wrapper.connect('allocate',
+                              Lang.bind(this, this._wrapperAllocate));
+        this.addActor(this._wrapper, { expand: true, span: -1 });
+
+        this.label = new St.Label();
+        this.label.clutter_text.set_line_wrap(true);
+        this._wrapper.add_actor(this.label);
+    },
+
+    _wrapperGetPreferredWidth: function(actor, forHeight, alloc) {
+        [alloc.min_size, alloc.natural_size] = this.label.get_preferred_width(-1);
+        if (alloc.natural_size > WRAP_WIDTH)
+            alloc.natural_size = WRAP_WIDTH;
+    },
+
+    _wrapperGetPreferredHeight: function(actor, forWidth, alloc) {
+        let minWidth, natWidth;
+        [alloc.min_size, alloc.natural_size] = this.label.get_preferred_height(forWidth);
+        [minWidth, natWidth] = this.label.get_preferred_width(-1);
+        if (natWidth > WRAP_WIDTH) {
+            alloc.min_size *= 2;
+            alloc.natural_size *= 2;
+        }
+    },
+
+    _wrapperAllocate: function(actor, box, flags) {
+        let availWidth = box.x2 - box.x1;
+        let availHeight = box.y2 - box.y1;
+        this.label.allocate(box, flags);
+    }
+};
+
+function IMStatusChooserItem() {
+    this._init();
+}
+
+IMStatusChooserItem.prototype = {
+    __proto__: PopupMenu.PopupBaseMenuItem.prototype,
+
+    _init: function() {
+        PopupMenu.PopupBaseMenuItem.prototype._init.call (this,
+                                                          { reactive: false,
+                                                            style_class: 'status-chooser' });
+
+        this._iconBin = new St.Button({ style_class: 'status-chooser-user-icon' });
+        this.addActor(this._iconBin);
+
+        this._iconBin.connect('clicked', Lang.bind(this,
+            function() {
+                this.activate();
+            }));
+
+        this._section = new PopupMenu.PopupMenuSection();
+        this.addActor(this._section.actor);
+
+        this._name = new IMUserNameItem();
+        this._section.addMenuItem(this._name);
+
+        this._combo = new PopupMenu.PopupComboBoxMenuItem({ style_class: 'status-chooser-combo' });
+        this._section.addMenuItem(this._combo);
+
+        let item;
+
+        item = new IMStatusItem(_("Available"), 'user-available');
+        this._combo.addMenuItem(item, IMStatus.AVAILABLE);
+
+        item = new IMStatusItem(_("Busy"), 'user-busy');
+        this._combo.addMenuItem(item, IMStatus.BUSY);
+
+        item = new IMStatusItem(_("Hidden"), 'user-invisible');
+        this._combo.addMenuItem(item, IMStatus.HIDDEN);
+
+        item = new IMStatusItem(_("Away"), 'user-away');
+        this._combo.addMenuItem(item, IMStatus.AWAY);
+
+        item = new IMStatusItem(_("Idle"), 'user-idle');
+        this._combo.addMenuItem(item, IMStatus.IDLE);
+
+        item = new IMStatusItem(_("Unavailable"), 'user-offline');
+        this._combo.addMenuItem(item, IMStatus.OFFLINE);
+
+        this._combo.connect('active-item-changed',
+                            Lang.bind(this, this._changeIMStatus));
+
+        this._presence = new GnomeSession.Presence();
+        this._presence.getStatus(Lang.bind(this, this._sessionStatusChanged));
+        this._presence.connect('StatusChanged',
+                               Lang.bind(this, this._sessionStatusChanged));
+
+        this._previousPresence = undefined;
+
+        this._accountMgr = Tp.AccountManager.dup()
+        this._accountMgr.connect('most-available-presence-changed',
+                                 Lang.bind(this, this._IMStatusChanged));
+        this._accountMgr.prepare_async(null, Lang.bind(this,
+            function(mgr) {
+                let [presence, s, msg] = mgr.get_most_available_presence();
+
+                this._previousPresence = presence;
+                this._IMStatusChanged(mgr, presence, s, msg);
+            }));
+
+        this._gdm = Gdm.UserManager.ref_default();
+        this._gdm.queue_load();
+
+        this._user = this._gdm.get_user(GLib.get_user_name());
+
+        this._userLoadedId = this._user.connect('notify::is-loaded',
+                                                Lang.bind(this,
+                                                          this._updateUser));
+        this._userChangedId = this._user.connect('changed',
+                                                 Lang.bind(this,
+                                                           this._updateUser));
+    },
+
+    // Override getColumnWidths()/setColumnWidths() to make the item
+    // independent from the overall column layout of the menu
+    getColumnWidths: function() {
+        return [];
+    },
+
+    setColumnWidths: function(widths) {
+        this._columnWidths = PopupMenu.PopupBaseMenuItem.prototype.getColumnWidths.call(this);
+        let sectionWidths = this._section.getColumnWidths();
+        this._section.setColumnWidths(sectionWidths);
+    },
+
+    _updateUser: function() {
+        let iconFile = null;
+        if (this._user.is_loaded) {
+            this._name.label.set_text(this._user.get_real_name());
+            iconFile = this._user.get_icon_file();
+            if (!GLib.file_test(iconFile, GLib.FileTest.EXISTS))
+                iconFile = null;
+        } else {
+            this._name.label.set_text("");
+        }
+
+        if (iconFile)
+            this._setIconFromFile(iconFile);
+        else
+            this._setIconFromName('avatar-default');
+    },
+
+    _setIconFromFile: function(iconFile) {
+        this._iconBin.set_style('background-image: url("' + iconFile + '");');
+    },
+
+    _setIconFromName: function(iconName) {
+        this._iconBin.set_style(null);
+
+        if (iconName != null) {
+            let textureCache = St.TextureCache.get_default();
+            let icon = textureCache.load_icon_name(this._iconBin.get_theme_node(),
+                                                   iconName,
+                                                   St.IconType.SYMBOLIC,
+                                                   DIALOG_ICON_SIZE);
+
+            this._iconBin.child = icon;
+            this._iconBin.show();
+        } else {
+            this._iconBin.child = null;
+            this._iconBin.hide();
+        }
+    },
+
+    _statusForPresence: function(presence) {
+        switch(presence) {
+            case Tp.ConnectionPresenceType.AVAILABLE:
+                return _("Available");
+            case Tp.ConnectionPresenceType.BUSY:
+                return _("Busy");
+            case Tp.ConnectionPresenceType.OFFLINE:
+                return _("Unavailable");
+            case Tp.ConnectionPresenceType.HIDDEN:
+                return _("Hidden");
+            case Tp.ConnectionPresenceType.AWAY:
+                return _("Away");
+            case Tp.ConnectionPresenceType.EXTENDED_AWAY:
+                return _("Idle");
+            default:
+                return _("Unknown");
+        }
+    },
+
+    _IMStatusChanged: function(accountMgr, presence, status, message) {
+        if (presence == Tp.ConnectionPresenceType.AVAILABLE)
+            this._presence.setStatus(GnomeSession.PresenceStatus.AVAILABLE);
+
+        if (!this._expectedPresence || presence != this._expectedPresence)
+            this._previousPresence = presence;
+        else
+            this._expectedPresence = undefined;
+
+        let activatedItem;
+
+        if (presence == Tp.ConnectionPresenceType.AVAILABLE)
+            activatedItem = IMStatus.AVAILABLE;
+        else if (presence == Tp.ConnectionPresenceType.BUSY)
+            activatedItem = IMStatus.BUSY;
+        else if (presence == Tp.ConnectionPresenceType.HIDDEN)
+            activatedItem = IMStatus.HIDDEN;
+        else if (presence == Tp.ConnectionPresenceType.AWAY)
+            activatedItem = IMStatus.AWAY;
+        else if (presence == Tp.ConnectionPresenceType.EXTENDED_AWAY)
+            activatedItem = IMStatus.IDLE;
+        else
+            activatedItem = IMStatus.OFFLINE;
+
+        this._combo.setActiveItem(activatedItem);
+        for (let i = 0; i < IMStatus.LAST; i++) {
+            if (i == IMStatus.AVAILABLE || i == IMStatus.OFFLINE)
+                continue;   // always visible
+
+            this._combo.setItemVisible(i, i == activatedItem);
+        }
+    },
+
+    _changeIMStatus: function(menuItem, id) {
+        let [presence, s, msg] = this._accountMgr.get_most_available_presence();
+        let newPresence, status;
+
+        if (id == IMStatus.AVAILABLE) {
+            newPresence = Tp.ConnectionPresenceType.AVAILABLE;
+        } else if (id == IMStatus.OFFLINE) {
+            newPresence = Tp.ConnectionPresenceType.OFFLINE;
+        } else
+            return;
+
+        status = this._statusForPresence(newPresence);
+        msg = msg ? msg : "";
+        this._accountMgr.set_all_requested_presences(newPresence, status, msg);
+    },
+
+    _sessionStatusChanged: function(sessionPresence, sessionStatus) {
+        let [presence, s, msg] = this._accountMgr.get_most_available_presence();
+        let newPresence, status;
+
+        if (sessionStatus == GnomeSession.PresenceStatus.AVAILABLE) {
+            newPresence = this._previousPresence;
+        } else if (sessionStatus == GnomeSession.PresenceStatus.BUSY) {
+            // Only change presence if the current one is "more present" than
+            // busy, or if coming back from idle
+            if (presence == Tp.ConnectionPresenceType.AVAILABLE ||
+                presence == Tp.ConnectionPresenceType.EXTENDED_AWAY) {
+                newPresence = Tp.ConnectionPresenceType.BUSY;
+            } else {
+                return;
+            }
+        } else if (sessionStatus == GnomeSession.PresenceStatus.IDLE) {
+            // Only change presence if the current one is "more present" than
+            // idle
+            if (presence != Tp.ConnectionPresenceType.OFFLINE)
+                newPresence = Tp.ConnectionPresenceType.EXTENDED_AWAY;
+            else
+                return;
+        } else {
+            return;
+        }
+
+        if (newPresence == undefined)
+            return;
+
+        status = this._statusForPresence(newPresence);
+        msg = msg ? msg : "";
+
+        this._expectedPresence = newPresence;
+        this._accountMgr.set_all_requested_presences(newPresence, status, msg);
+    }
+};
+
+
 function UserMenuButton() {
     this._init();
 }
@@ -46,7 +372,6 @@ UserMenuButton.prototype = {
 
         this._user = this._gdm.get_user(GLib.get_user_name());
         this._presence = new GnomeSession.Presence();
-        this._presenceItems = {};
         this._session = new GnomeSession.SessionManager();
         this._haveShutdown = true;
 
@@ -60,13 +385,30 @@ UserMenuButton.prototype = {
         box.add(this._iconBox, { y_align: St.Align.MIDDLE, y_fill: false });
 
         let textureCache = St.TextureCache.get_default();
-        this._availableIcon = new St.Icon({ icon_name: 'user-available', style_class: 'popup-menu-icon' });
-        this._busyIcon = new St.Icon({ icon_name: 'user-busy', style_class: 'popup-menu-icon' });
-        this._invisibleIcon = new St.Icon({ icon_name: 'user-invisible', style_class: 'popup-menu-icon' });
-        this._idleIcon = new St.Icon({ icon_name: 'user-idle', style_class: 'popup-menu-icon' });
-
-        this._presence.connect('StatusChanged', Lang.bind(this, this._updatePresenceIcon));
-        this._presence.getStatus(Lang.bind(this, this._updatePresenceIcon));
+        this._offlineIcon = new St.Icon({ icon_name: 'user-offline',
+                                          style_class: 'popup-menu-icon' });
+        this._availableIcon = new St.Icon({ icon_name: 'user-available',
+                                            style_class: 'popup-menu-icon' });
+        this._busyIcon = new St.Icon({ icon_name: 'user-busy',
+                                       style_class: 'popup-menu-icon' });
+        this._invisibleIcon = new St.Icon({ icon_name: 'user-invisible',
+                                            style_class: 'popup-menu-icon' });
+        this._awayIcon = new St.Icon({ icon_name: 'user-away',
+                                       style_class: 'popup-menu-icon' });
+        this._idleIcon = new St.Icon({ icon_name: 'user-idle',
+                                       style_class: 'popup-menu-icon' });
+
+        this._presence.connect('StatusChanged',
+                               Lang.bind(this, this._updateSwitch));
+        this._presence.getStatus(Lang.bind(this, this._updateSwitch));
+
+        this._account_mgr.connect('most-available-presence-changed',
+                                  Lang.bind(this, this._updatePresenceIcon));
+        this._account_mgr.prepare_async(null, Lang.bind(this,
+            function(mgr) {
+                let [presence, s, msg] = mgr.get_most_available_presence();
+                this._updatePresenceIcon(mgr, presence, s, msg);
+            }));
 
         this._name = new St.Label();
         box.add(this._name, { y_align: St.Align.MIDDLE, y_fill: false });
@@ -110,9 +452,9 @@ UserMenuButton.prototype = {
 
     _updateUserName: function() {
         if (this._user.is_loaded)
-          this._name.set_text(this._user.get_real_name());
+            this._name.set_text(this._user.get_real_name());
         else
-          this._name.set_text("");
+            this._name.set_text("");
     },
 
     _updateSwitchUser: function() {
@@ -171,38 +513,43 @@ UserMenuButton.prototype = {
         }
     },
 
-    _updatePresenceIcon: function(presence, status) {
-        if (status == GnomeSession.PresenceStatus.AVAILABLE)
+    _updateSwitch: function(presence, status) {
+        let active = status == GnomeSession.PresenceStatus.BUSY;
+        this._dontDisturbSwitch.setToggleState(active);
+    },
+
+    _updatePresenceIcon: function(accountMgr, presence, status, message) {
+        if (presence == Tp.ConnectionPresenceType.AVAILABLE)
             this._iconBox.child = this._availableIcon;
-        else if (status == GnomeSession.PresenceStatus.BUSY)
+        else if (presence == Tp.ConnectionPresenceType.BUSY)
             this._iconBox.child = this._busyIcon;
-        else if (status == GnomeSession.PresenceStatus.INVISIBLE)
+        else if (presence == Tp.ConnectionPresenceType.HIDDEN)
             this._iconBox.child = this._invisibleIcon;
-        else
+        else if (presence == Tp.ConnectionPresenceType.AWAY)
+            this._iconBox.child = this._awayIcon;
+        else if (presence == Tp.ConnectionPresenceType.EXTENDED_AWAY)
             this._iconBox.child = this._idleIcon;
-
-        for (let itemStatus in this._presenceItems)
-            this._presenceItems[itemStatus].setShowDot(itemStatus == status);
+        else
+            this._iconBox.child = this._offlineIcon;
     },
 
     _createSubMenu: function() {
         let item;
 
-        item = new PopupMenu.PopupImageMenuItem(_("Available"), 'user-available');
-        item.connect('activate', Lang.bind(this, this._setPresenceStatus, GnomeSession.PresenceStatus.AVAILABLE));
+        item = new IMStatusChooserItem();
+        item.connect('activate', Lang.bind(this, this._onMyAccountActivate));
         this.menu.addMenuItem(item);
-        this._presenceItems[GnomeSession.PresenceStatus.AVAILABLE] = item;
 
-        item = new PopupMenu.PopupImageMenuItem(_("Busy"), 'user-busy');
-        item.connect('activate', Lang.bind(this, this._setPresenceStatus, GnomeSession.PresenceStatus.BUSY));
+        item = new PopupMenu.PopupSwitchMenuItem(_("Do Not Disturb"));
+        item.connect('activate', Lang.bind(this, this._updatePresenceStatus));
         this.menu.addMenuItem(item);
-        this._presenceItems[GnomeSession.PresenceStatus.BUSY] = item;
+        this._dontDisturbSwitch = item;
 
         item = new PopupMenu.PopupSeparatorMenuItem();
         this.menu.addMenuItem(item);
 
-        item = new PopupMenu.PopupMenuItem(_("My Account"));
-        item.connect('activate', Lang.bind(this, this._onMyAccountActivate));
+        item = new PopupMenu.PopupMenuItem(_("Online Accounts"));
+        item.connect('activate', Lang.bind(this, this._onOnlineAccountsActivate));
         this.menu.addMenuItem(item);
 
         item = new PopupMenu.PopupMenuItem(_("System Settings"));
@@ -238,10 +585,10 @@ UserMenuButton.prototype = {
         this._updateSuspendOrPowerOff();
     },
 
-    _setPresenceStatus: function(item, event, status) {
+    _updatePresenceStatus: function(item, event) {
+        let status = item.state ? GnomeSession.PresenceStatus.BUSY
+                                : GnomeSession.PresenceStatus.AVAILABLE;
         this._presence.setStatus(status);
-
-        this._setIMStatus(status);
     },
 
     _onMyAccountActivate: function() {
@@ -250,6 +597,12 @@ UserMenuButton.prototype = {
         app.activate();
     },
 
+    _onOnlineAccountsActivate: function() {
+        Main.overview.hide();
+        let app = Shell.AppSystem.get_default().lookup_setting('gnome-online-accounts-panel.desktop');
+        app.activate(-1);
+    },
+
     _onPreferencesActivate: function() {
         Main.overview.hide();
         let app = Shell.AppSystem.get_default().lookup_app('gnome-control-center.desktop');
@@ -287,31 +640,5 @@ UserMenuButton.prototype = {
         } else {
             this._session.ShutdownRemote();
         }
-    },
-
-    _setIMStatus: function(session_status) {
-        let [presence_type, presence_status, msg] = this._account_mgr.get_most_available_presence();
-        let type, status;
-
-        // We change the IM presence only if there are connected accounts
-        if (presence_type == Tp.ConnectionPresenceType.UNSET ||
-            presence_type == Tp.ConnectionPresenceType.OFFLINE ||
-            presence_type == Tp.ConnectionPresenceType.UNKNOWN ||
-            presence_type == Tp.ConnectionPresenceType.ERROR)
-          return;
-
-        if (session_status == GnomeSession.PresenceStatus.AVAILABLE) {
-            type = Tp.ConnectionPresenceType.AVAILABLE;
-            status = "available";
-        }
-        else if (session_status == GnomeSession.PresenceStatus.BUSY) {
-            type = Tp.ConnectionPresenceType.BUSY;
-            status = "busy";
-        }
-        else {
-          return;
-        }
-
-        this._account_mgr.set_all_requested_presences(type, status, msg);
     }
 };



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