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
This commit is contained in:
Florian Müllner 2011-07-28 16:59:03 +02:00
parent 709193d680
commit 0751a90bd9
2 changed files with 427 additions and 57 deletions

View File

@ -387,6 +387,49 @@ StTooltip StLabel {
spacing: 4px; 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 { #legacyTray {
spacing: 14px; spacing: 14px;
padding-left: 14px; padding-left: 14px;

View File

@ -22,11 +22,337 @@ const DISABLE_USER_SWITCH_KEY = 'disable-user-switching';
const DISABLE_LOCK_SCREEN_KEY = 'disable-lock-screen'; const DISABLE_LOCK_SCREEN_KEY = 'disable-lock-screen';
const DISABLE_LOG_OUT_KEY = 'disable-log-out'; 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 // Adapted from gdm/gui/user-switch-applet/applet.c
// //
// Copyright (C) 2004-2005 James M. Cape <jcape@ignore-your.tv>. // Copyright (C) 2004-2005 James M. Cape <jcape@ignore-your.tv>.
// Copyright (C) 2008,2009 Red Hat, Inc. // 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() { function UserMenuButton() {
this._init(); this._init();
} }
@ -46,7 +372,6 @@ UserMenuButton.prototype = {
this._user = this._gdm.get_user(GLib.get_user_name()); this._user = this._gdm.get_user(GLib.get_user_name());
this._presence = new GnomeSession.Presence(); this._presence = new GnomeSession.Presence();
this._presenceItems = {};
this._session = new GnomeSession.SessionManager(); this._session = new GnomeSession.SessionManager();
this._haveShutdown = true; this._haveShutdown = true;
@ -60,13 +385,30 @@ UserMenuButton.prototype = {
box.add(this._iconBox, { y_align: St.Align.MIDDLE, y_fill: false }); box.add(this._iconBox, { y_align: St.Align.MIDDLE, y_fill: false });
let textureCache = St.TextureCache.get_default(); let textureCache = St.TextureCache.get_default();
this._availableIcon = new St.Icon({ icon_name: 'user-available', style_class: 'popup-menu-icon' }); this._offlineIcon = new St.Icon({ icon_name: 'user-offline',
this._busyIcon = new St.Icon({ icon_name: 'user-busy', style_class: 'popup-menu-icon' }); style_class: 'popup-menu-icon' });
this._invisibleIcon = new St.Icon({ icon_name: 'user-invisible', style_class: 'popup-menu-icon' }); this._availableIcon = new St.Icon({ icon_name: 'user-available',
this._idleIcon = new St.Icon({ icon_name: 'user-idle', style_class: 'popup-menu-icon' }); 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._updatePresenceIcon)); this._presence.connect('StatusChanged',
this._presence.getStatus(Lang.bind(this, this._updatePresenceIcon)); 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(); this._name = new St.Label();
box.add(this._name, { y_align: St.Align.MIDDLE, y_fill: false }); box.add(this._name, { y_align: St.Align.MIDDLE, y_fill: false });
@ -110,9 +452,9 @@ UserMenuButton.prototype = {
_updateUserName: function() { _updateUserName: function() {
if (this._user.is_loaded) if (this._user.is_loaded)
this._name.set_text(this._user.get_real_name()); this._name.set_text(this._user.get_real_name());
else else
this._name.set_text(""); this._name.set_text("");
}, },
_updateSwitchUser: function() { _updateSwitchUser: function() {
@ -171,38 +513,43 @@ UserMenuButton.prototype = {
} }
}, },
_updatePresenceIcon: function(presence, status) { _updateSwitch: function(presence, status) {
if (status == GnomeSession.PresenceStatus.AVAILABLE) let active = status == GnomeSession.PresenceStatus.BUSY;
this._iconBox.child = this._availableIcon; this._dontDisturbSwitch.setToggleState(active);
else if (status == GnomeSession.PresenceStatus.BUSY) },
this._iconBox.child = this._busyIcon;
else if (status == GnomeSession.PresenceStatus.INVISIBLE)
this._iconBox.child = this._invisibleIcon;
else
this._iconBox.child = this._idleIcon;
for (let itemStatus in this._presenceItems) _updatePresenceIcon: function(accountMgr, presence, status, message) {
this._presenceItems[itemStatus].setShowDot(itemStatus == status); if (presence == Tp.ConnectionPresenceType.AVAILABLE)
this._iconBox.child = this._availableIcon;
else if (presence == Tp.ConnectionPresenceType.BUSY)
this._iconBox.child = this._busyIcon;
else if (presence == Tp.ConnectionPresenceType.HIDDEN)
this._iconBox.child = this._invisibleIcon;
else if (presence == Tp.ConnectionPresenceType.AWAY)
this._iconBox.child = this._awayIcon;
else if (presence == Tp.ConnectionPresenceType.EXTENDED_AWAY)
this._iconBox.child = this._idleIcon;
else
this._iconBox.child = this._offlineIcon;
}, },
_createSubMenu: function() { _createSubMenu: function() {
let item; let item;
item = new PopupMenu.PopupImageMenuItem(_("Available"), 'user-available'); item = new IMStatusChooserItem();
item.connect('activate', Lang.bind(this, this._setPresenceStatus, GnomeSession.PresenceStatus.AVAILABLE)); item.connect('activate', Lang.bind(this, this._onMyAccountActivate));
this.menu.addMenuItem(item); this.menu.addMenuItem(item);
this._presenceItems[GnomeSession.PresenceStatus.AVAILABLE] = item;
item = new PopupMenu.PopupImageMenuItem(_("Busy"), 'user-busy'); item = new PopupMenu.PopupSwitchMenuItem(_("Do Not Disturb"));
item.connect('activate', Lang.bind(this, this._setPresenceStatus, GnomeSession.PresenceStatus.BUSY)); item.connect('activate', Lang.bind(this, this._updatePresenceStatus));
this.menu.addMenuItem(item); this.menu.addMenuItem(item);
this._presenceItems[GnomeSession.PresenceStatus.BUSY] = item; this._dontDisturbSwitch = item;
item = new PopupMenu.PopupSeparatorMenuItem(); item = new PopupMenu.PopupSeparatorMenuItem();
this.menu.addMenuItem(item); this.menu.addMenuItem(item);
item = new PopupMenu.PopupMenuItem(_("My Account")); item = new PopupMenu.PopupMenuItem(_("Online Accounts"));
item.connect('activate', Lang.bind(this, this._onMyAccountActivate)); item.connect('activate', Lang.bind(this, this._onOnlineAccountsActivate));
this.menu.addMenuItem(item); this.menu.addMenuItem(item);
item = new PopupMenu.PopupMenuItem(_("System Settings")); item = new PopupMenu.PopupMenuItem(_("System Settings"));
@ -238,10 +585,10 @@ UserMenuButton.prototype = {
this._updateSuspendOrPowerOff(); 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._presence.setStatus(status);
this._setIMStatus(status);
}, },
_onMyAccountActivate: function() { _onMyAccountActivate: function() {
@ -250,6 +597,12 @@ UserMenuButton.prototype = {
app.activate(); 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() { _onPreferencesActivate: function() {
Main.overview.hide(); Main.overview.hide();
let app = Shell.AppSystem.get_default().lookup_app('gnome-control-center.desktop'); let app = Shell.AppSystem.get_default().lookup_app('gnome-control-center.desktop');
@ -287,31 +640,5 @@ UserMenuButton.prototype = {
} else { } else {
this._session.ShutdownRemote(); 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);
} }
}; };