diff options
Diffstat (limited to '')
-rw-r--r-- | js/ui/userWidget.js | 250 |
1 files changed, 250 insertions, 0 deletions
diff --git a/js/ui/userWidget.js b/js/ui/userWidget.js new file mode 100644 index 0000000..5540ea6 --- /dev/null +++ b/js/ui/userWidget.js @@ -0,0 +1,250 @@ +// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- +// +// A widget showing the user avatar and name +/* exported UserWidget */ + +const { Clutter, GLib, GObject, St } = imports.gi; + +const Params = imports.misc.params; + +var AVATAR_ICON_SIZE = 64; + +// 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. + +var Avatar = GObject.registerClass( +class Avatar extends St.Bin { + _init(user, params) { + let themeContext = St.ThemeContext.get_for_stage(global.stage); + params = Params.parse(params, { + styleClass: 'user-icon', + reactive: false, + iconSize: AVATAR_ICON_SIZE, + }); + + super._init({ + style_class: params.styleClass, + reactive: params.reactive, + width: params.iconSize * themeContext.scaleFactor, + height: params.iconSize * themeContext.scaleFactor, + }); + + this._iconSize = params.iconSize; + this._user = user; + + this.bind_property('reactive', this, 'track-hover', + GObject.BindingFlags.SYNC_CREATE); + this.bind_property('reactive', this, 'can-focus', + GObject.BindingFlags.SYNC_CREATE); + + // Monitor the scaling factor to make sure we recreate the avatar when needed. + this._scaleFactorChangeId = + themeContext.connect('notify::scale-factor', this.update.bind(this)); + + this.connect('destroy', this._onDestroy.bind(this)); + } + + vfunc_style_changed() { + super.vfunc_style_changed(); + + let node = this.get_theme_node(); + let [found, iconSize] = node.lookup_length('icon-size', false); + + if (!found) + return; + + let themeContext = St.ThemeContext.get_for_stage(global.stage); + + // node.lookup_length() returns a scaled value, but we + // need unscaled + this._iconSize = iconSize / themeContext.scaleFactor; + this.update(); + } + + _onDestroy() { + if (this._scaleFactorChangeId) { + let themeContext = St.ThemeContext.get_for_stage(global.stage); + themeContext.disconnect(this._scaleFactorChangeId); + delete this._scaleFactorChangeId; + } + } + + setSensitive(sensitive) { + this.reactive = sensitive; + } + + update() { + let iconFile = null; + if (this._user) { + iconFile = this._user.get_icon_file(); + if (iconFile && !GLib.file_test(iconFile, GLib.FileTest.EXISTS)) + iconFile = null; + } + + let { scaleFactor } = St.ThemeContext.get_for_stage(global.stage); + this.set_size( + this._iconSize * scaleFactor, + this._iconSize * scaleFactor); + + if (iconFile) { + this.child = null; + this.style = ` + background-image: url("${iconFile}"); + background-size: cover;`; + } else { + this.style = null; + this.child = new St.Icon({ + icon_name: 'avatar-default-symbolic', + icon_size: this._iconSize, + }); + } + } +}); + +var UserWidgetLabel = GObject.registerClass( +class UserWidgetLabel extends St.Widget { + _init(user) { + super._init({ layout_manager: new Clutter.BinLayout() }); + + this._user = user; + + this._realNameLabel = new St.Label({ style_class: 'user-widget-label', + y_align: Clutter.ActorAlign.CENTER }); + this.add_child(this._realNameLabel); + + this._userNameLabel = new St.Label({ style_class: 'user-widget-label', + y_align: Clutter.ActorAlign.CENTER }); + this.add_child(this._userNameLabel); + + this._currentLabel = null; + + this._userLoadedId = this._user.connect('notify::is-loaded', this._updateUser.bind(this)); + this._userChangedId = this._user.connect('changed', this._updateUser.bind(this)); + this._updateUser(); + + // We can't override the destroy vfunc because that might be called during + // object finalization, and we can't call any JS inside a GC finalize callback, + // so we use a signal, that will be disconnected by GObject the first time + // the actor is destroyed (which is guaranteed to be as part of a normal + // destroy() call from JS, possibly from some ancestor) + this.connect('destroy', this._onDestroy.bind(this)); + } + + _onDestroy() { + if (this._userLoadedId != 0) { + this._user.disconnect(this._userLoadedId); + this._userLoadedId = 0; + } + + if (this._userChangedId != 0) { + this._user.disconnect(this._userChangedId); + this._userChangedId = 0; + } + } + + vfunc_allocate(box) { + this.set_allocation(box); + + let availWidth = box.x2 - box.x1; + let availHeight = box.y2 - box.y1; + + let [, , natRealNameWidth] = this._realNameLabel.get_preferred_size(); + + let childBox = new Clutter.ActorBox(); + + let hiddenLabel; + if (natRealNameWidth <= availWidth) { + this._currentLabel = this._realNameLabel; + hiddenLabel = this._userNameLabel; + } else { + this._currentLabel = this._userNameLabel; + hiddenLabel = this._realNameLabel; + } + this.label_actor = this._currentLabel; + + hiddenLabel.allocate(childBox); + + childBox.set_size(availWidth, availHeight); + + this._currentLabel.allocate(childBox); + } + + vfunc_paint(paintContext) { + this._currentLabel.paint(paintContext); + } + + _updateUser() { + if (this._user.is_loaded) { + this._realNameLabel.text = this._user.get_real_name(); + this._userNameLabel.text = this._user.get_user_name(); + } else { + this._realNameLabel.text = ''; + this._userNameLabel.text = ''; + } + } +}); + +var UserWidget = GObject.registerClass( +class UserWidget extends St.BoxLayout { + _init(user, orientation = Clutter.Orientation.HORIZONTAL) { + // If user is null, that implies a username-based login authorization. + this._user = user; + + let vertical = orientation == Clutter.Orientation.VERTICAL; + let xAlign = vertical ? Clutter.ActorAlign.CENTER : Clutter.ActorAlign.START; + let styleClass = vertical ? 'user-widget vertical' : 'user-widget horizontal'; + + super._init({ + styleClass, + vertical, + xAlign, + }); + + this.connect('destroy', this._onDestroy.bind(this)); + + this._avatar = new Avatar(user); + this._avatar.x_align = Clutter.ActorAlign.CENTER; + this.add_child(this._avatar); + + this._userLoadedId = 0; + this._userChangedId = 0; + if (user) { + this._label = new UserWidgetLabel(user); + this.add_child(this._label); + + this._label.bind_property('label-actor', this, 'label-actor', + GObject.BindingFlags.SYNC_CREATE); + + this._userLoadedId = this._user.connect('notify::is-loaded', this._updateUser.bind(this)); + this._userChangedId = this._user.connect('changed', this._updateUser.bind(this)); + } else { + this._label = new St.Label({ + style_class: 'user-widget-label', + text: 'Empty User', + opacity: 0, + }); + this.add_child(this._label); + + } + + this._updateUser(); + } + + _onDestroy() { + if (this._userLoadedId != 0) { + this._user.disconnect(this._userLoadedId); + this._userLoadedId = 0; + } + + if (this._userChangedId != 0) { + this._user.disconnect(this._userChangedId); + this._userChangedId = 0; + } + } + + _updateUser() { + this._avatar.update(); + } +}); |