From 016ad6955099448404731add33691ca69e3176bc Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Sat, 15 May 2010 12:43:56 -0400 Subject: [PATCH] [lookingGlass] Remove Properties tab, replace with popup object inspector Make inspecting objects more dynamic by turning them into links which pops up a dialog, rather than the more clunky tab interaction. --- data/theme/gnome-shell.css | 53 +++++++++- js/ui/link.js | 1 + js/ui/lookingGlass.js | 205 ++++++++++++++++++++++++++++++------- 3 files changed, 218 insertions(+), 41 deletions(-) diff --git a/data/theme/gnome-shell.css b/data/theme/gnome-shell.css index b00de1a5a..266cfa703 100644 --- a/data/theme/gnome-shell.css +++ b/data/theme/gnome-shell.css @@ -636,7 +636,7 @@ StTooltip { background-color: rgba(0,0,0,0.85); spacing: 4px; padding: 4px; - border: 1px solid rgba(0,0,172,0.85); + border: 2px solid grey; border-radius: 4px; color: #88ff66; @@ -656,21 +656,58 @@ StTooltip { padding: 2px; } +#LookingGlassDialog .notebook-tab:hover { + color: #00ff00; +} + #LookingGlassDialog .notebook-tab:selected { border: 1px solid #88ff66; - padding: 1px; + border-radius: 4px; + padding: 5px; } -#LookingGlassDialog StLabel +#LookingGlassDialog .lg-inspector-title { + font-weight: bold; + padding-bottom: 8px; +} + +.lg-dialog StLabel { color: #88ff66; } -#LookingGlassDialog StEntry +.lg-dialog StEntry { color: #88ff66; } +.lg-obj-inspector-title +{ + spacing: 4px; +} + +.lg-obj-inspector-button +{ + border: 1px solid #88ff66; + padding: 4px; + border-radius: 4px; +} + +.lg-obj-inspector-button:hover +{ + border: 1px solid #00ff00; +} + +.lg-dialog .shell-link +{ + color: #88ff66; +} + +.lg-dialog .shell-link:hover +{ + color: #00ff00; +} + #LookingGlassDialog StBoxLayout#EvalBox { padding: 4px; @@ -705,6 +742,14 @@ StTooltip { spacing: 6px; } +#LookingGlassPropertyInspector { + background: rgba(0, 0, 0, 0.9); + border: 2px solid grey; + border-radius: 4px; + padding: 6px; + color: #88ff66; +} + /* Calendar popup */ #calendarPopup { diff --git a/js/ui/link.js b/js/ui/link.js index 414df3827..7b694dff2 100644 --- a/js/ui/link.js +++ b/js/ui/link.js @@ -12,6 +12,7 @@ function Link(props) { Link.prototype = { _init : function(props) { let realProps = { reactive: true, + track_hover: true, style_class: 'shell-link' }; // The user can pass in reactive: false to override the above and get // a non-reactive link (a link to the current page, perhaps) diff --git a/js/ui/lookingGlass.js b/js/ui/lookingGlass.js index 62b743732..87c73fa55 100644 --- a/js/ui/lookingGlass.js +++ b/js/ui/lookingGlass.js @@ -1,6 +1,7 @@ /* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */ const Clutter = imports.gi.Clutter; +const GLib = imports.gi.GLib; const Gio = imports.gi.Gio; const Pango = imports.gi.Pango; const St = imports.gi.St; @@ -49,7 +50,9 @@ Notebook.prototype = { }, appendPage: function(name, child) { - let labelBox = new St.BoxLayout({ style_class: 'notebook-tab' }); + let labelBox = new St.BoxLayout({ style_class: 'notebook-tab', + reactive: true, + track_hover: true }); let label = new St.Button({ label: name }); label.connect('clicked', Lang.bind(this, function () { this.selectChild(child); @@ -138,6 +141,40 @@ Notebook.prototype = { }; Signals.addSignalMethods(Notebook.prototype); +function objectToString(o) { + if (typeof(o) == typeof(objectToString)) { + // special case this since the default is way, way too verbose + return ""; + } else { + return "" + o; + } +} + +function ObjLink(o, title) { + this._init(o, title); +} + +ObjLink.prototype = { + __proto__: Link.Link, + + _init: function(o, title) { + let text; + if (title) + text = title; + else + text = objectToString(o); + text = GLib.markup_escape_text(text, -1); + this._obj = o; + Link.Link.prototype._init.call(this, { label: text }); + this.actor.get_child().single_line_mode = true; + this.actor.connect('clicked', Lang.bind(this, this._onClicked)); + }, + + _onClicked: function (link) { + Main.lookingGlass.inspectObject(this._obj, this.actor); + } +} + function Result(command, o, index) { this._init(command, o, index); } @@ -150,15 +187,16 @@ Result.prototype = { this.actor = new St.BoxLayout({ vertical: true }); let cmdTxt = new St.Label({ text: command }); - cmdTxt.ellipsize = Pango.EllipsizeMode.END; - + cmdTxt.clutter_text.ellipsize = Pango.EllipsizeMode.END; this.actor.add(cmdTxt); - let resultTxt = new St.Label({ text: 'r(' + index + ') = ' + o }); - resultTxt.ellipsize = Pango.EllipsizeMode.END; - - this.actor.add(resultTxt); - let line = new Clutter.Rectangle({ name: 'Separator', - height: 1 }); + let box = new St.BoxLayout({}); + this.actor.add(box); + let resultTxt = new St.Label({ text: 'r(' + index + ') = ' }); + resultTxt.clutter_text.ellipsize = Pango.EllipsizeMode.END; + box.add(resultTxt); + let objLink = new ObjLink(o); + box.add(objLink.actor); + let line = new Clutter.Rectangle({ name: 'Separator' }); let padBin = new St.Bin({ name: 'Separator', x_fill: true, y_fill: true }); padBin.add_actor(line); this.actor.add(padBin); @@ -188,9 +226,8 @@ WindowList.prototype = { metaWindow.connect('unmanaged', Lang.bind(this, this._updateWindowList)); let box = new St.BoxLayout({ vertical: true }); this.actor.add(box); - let label = new Link.Link({ label: metaWindow.title, x_align: St.Align.START }); - label.actor.connect('clicked', Lang.bind(this, function () { this.emit('selected', metaWindow); })); - box.add(label.actor); + let windowLink = new ObjLink(metaWindow, metaWindow.title); + box.add(windowLink.actor, { x_align: St.Align.START, x_fill: false }); let propsBox = new St.BoxLayout({ vertical: true, style: 'padding-left: 6px;' }); box.add(propsBox); propsBox.add(new St.Label({ text: 'wmclass: ' + metaWindow.get_wm_class() })); @@ -199,8 +236,10 @@ WindowList.prototype = { let icon = app.create_icon_texture(22); let propBox = new St.BoxLayout({ style: 'spacing: 6px; ' }); propsBox.add(propBox); - propBox.add(new St.Label({ text: 'app: ' + app.get_id() }), { y_align: St.Align.MIDDLE }); - propBox.add(icon, { y_align: St.Align.MIDDLE }); + propBox.add(new St.Label({ text: 'app: ' }), { y_fill: false }); + let appLink = new ObjLink(app, app.get_id()); + propBox.add(appLink.actor, { y_fill: false }); + propBox.add(icon, { y_fill: false }); } else { propsBox.add(new St.Label({ text: '' })); } @@ -209,36 +248,109 @@ WindowList.prototype = { }; Signals.addSignalMethods(WindowList.prototype); -function PropertyInspector() { +function ObjInspector() { this._init(); } -PropertyInspector.prototype = { +ObjInspector.prototype = { _init : function () { - this._target = null; + this._obj = null; + this._previousObj = null; this._parentList = []; - this.actor = new St.BoxLayout({ name: 'PropertyInspector', vertical: true }); + this.actor = new St.ScrollView({ x_fill: true, y_fill: true }); + this.actor.get_hscroll_bar().hide(); + this._container = new St.BoxLayout({ name: 'LookingGlassPropertyInspector', + style_class: 'lg-dialog', + vertical: true }); + this.actor.add_actor(this._container); }, - setTarget: function(actor) { - this.target = actor; + selectObject: function(obj, skipPrevious) { + if (!skipPrevious) + this._previousObj = this._obj; + else + this._previousObj = null; + this._obj = obj; - this.actor.get_children().forEach(function (child) { child.destroy(); }); + this._container.get_children().forEach(function (child) { child.destroy(); }); - for (let propName in actor) { - let valueStr; - try { - valueStr = '' + actor[propName]; - } catch (e) { - valueStr = ''; - } - let propText = propName + ': ' + valueStr; - let propDisplay = new St.Label({ reactive: true, - text: propText }); - this.actor.add_actor(propDisplay); + let hbox = new St.BoxLayout({ style_class: 'lg-obj-inspector-title' }); + this._container.add_actor(hbox); + let label = new St.Label({ text: 'Inspecting: %s: %s'.format(typeof(obj), + objectToString(obj)) }); + label.single_line_mode = true; + hbox.add(label, { expand: true, y_fill: false }); + let button = new St.Button({ label: 'Insert', style_class: 'lg-obj-inspector-button' }); + button.connect('clicked', Lang.bind(this, this._onInsert)); + hbox.add(button); + + if (this._previousObj != null) { + button = new St.Button({ label: 'Back', style_class: 'lg-obj-inspector-button' }); + button.connect('clicked', Lang.bind(this, this._onBack)); + hbox.add(button); } + + button = new St.Button({ style_class: 'window-close' }); + button.connect('clicked', Lang.bind(this, this.close)); + hbox.add(button); + if (typeof(obj) == typeof({})) { + for (let propName in obj) { + let valueStr; + let link; + try { + let prop = obj[propName]; + link = new ObjLink(prop).actor; + } catch (e) { + link = new St.Label({ text: '' }); + } + let hbox = new St.BoxLayout(); + let propText = propName + ": " + valueStr; + hbox.add(new St.Label({ text: propName + ': ' })); + hbox.add(link); + this._container.add_actor(hbox); + } + } + }, + + open: function(sourceActor) { + if (this._open) + return; + this._previousObj = null; + this._open = true; + this.actor.show(); + if (sourceActor) { + this.actor.set_scale(0, 0); + let [sourceX, sourceY] = sourceActor.get_transformed_position(); + let [sourceWidth, sourceHeight] = sourceActor.get_transformed_size(); + this.actor.move_anchor_point(Math.floor(sourceX + sourceWidth / 2), + Math.floor(sourceY + sourceHeight / 2)); + Tweener.addTween(this.actor, { scale_x: 1, scale_y: 1, + transition: "easeOutQuad", + time: 0.2 }); + } else { + this.actor.set_scale(1, 1); + } + }, + + close: function() { + if (!this._open) + return; + this._open = false; + this.actor.hide(); + this._previousObj = null; + this._obj = null; + }, + + _onInsert: function() { + let obj = this._obj; + this.close(); + Main.lookingGlass.insertObject(obj); + }, + + _onBack: function() { + this.selectObject(this._previousObj, true); } }; @@ -462,6 +574,7 @@ LookingGlass.prototype = { this._maxItems = 150; this.actor = new St.BoxLayout({ name: 'LookingGlassDialog', + style_class: 'lg-dialog', vertical: true, visible: false }); @@ -473,6 +586,10 @@ LookingGlass.prototype = { Main.uiGroup.add_actor(this.actor); + this._objInspector = new ObjInspector(); + Main.uiGroup.add_actor(this._objInspector.actor); + this._objInspector.actor.hide(); + let toolbar = new St.BoxLayout({ name: 'Toolbar' }); this.actor.add_actor(toolbar); let inspectIcon = St.TextureCache.get_default().load_gicon(new Gio.ThemedIcon({ name: 'gtk-color-picker' }), @@ -521,9 +638,6 @@ LookingGlass.prototype = { })); entryArea.add(this._entry, { expand: true }); - this._propInspector = new PropertyInspector(); - notebook.appendPage('Properties', this._propInspector.actor); - this._windowList = new WindowList(); this._windowList.connect('selected', Lang.bind(this, function(list, window) { notebook.selectIndex(0); @@ -618,7 +732,6 @@ LookingGlass.prototype = { let result = new Result('>>> ' + command, obj, index); this._results.push(result); this._resultsArea.add(result.actor); - this._propInspector.setTarget(obj); if (this._borderPaintTarget != null) { this._borderPaintTarget.disconnect(this._borderPaintId); this._borderPaintTarget = null; @@ -686,6 +799,9 @@ LookingGlass.prototype = { this.actor.y = this._hiddenY; this.actor.width = myWidth; this.actor.height = myHeight; + this._objInspector.actor.set_size(Math.floor(myWidth * 0.8), Math.floor(myHeight * 0.8)); + this._objInspector.actor.set_position(this.actor.x + Math.floor(myWidth * 0.1), + this._targetY + Math.floor(myHeight * 0.1)); }, slaveTo: function(actor) { @@ -696,11 +812,24 @@ LookingGlass.prototype = { this._resizeTo(actor); }, + insertObject: function(obj) { + this._pushResult('', obj); + }, + + inspectObject: function(obj, sourceActor) { + this._objInspector.open(sourceActor); + this._objInspector.selectObject(obj); + }, + // Handle key events which are relevant for all tabs of the LookingGlass _globalKeyPressEvent : function(actor, event) { let symbol = event.get_key_symbol(); if (symbol == Clutter.Escape) { - this.close(); + if (this._objInspector.actor.visible) { + this._objInspector.close(); + } else { + this.close(); + } return true; } return false; @@ -737,6 +866,8 @@ LookingGlass.prototype = { if (this._keyPressEventId) global.stage.disconnect(this._keyPressEventId); + this._objInspector.actor.hide(); + this._historyNavIndex = -1; this._open = false; Tweener.removeTweens(this.actor);