d6b6f814d3
All classes that have at least one other derived class (and thus benefit from the framework) have been now ported. These includes NMDevice, SearchProvider, AltTab.SwitcherList, and some other stuff around. https://bugzilla.gnome.org/show_bug.cgi?id=664436
1232 lines
46 KiB
JavaScript
1232 lines
46 KiB
JavaScript
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
|
|
|
const Clutter = imports.gi.Clutter;
|
|
const Cogl = imports.gi.Cogl;
|
|
const GLib = imports.gi.GLib;
|
|
const Gio = imports.gi.Gio;
|
|
const Gtk = imports.gi.Gtk;
|
|
const Meta = imports.gi.Meta;
|
|
const Pango = imports.gi.Pango;
|
|
const St = imports.gi.St;
|
|
const Shell = imports.gi.Shell;
|
|
const Signals = imports.signals;
|
|
const Lang = imports.lang;
|
|
const Mainloop = imports.mainloop;
|
|
|
|
const History = imports.misc.history;
|
|
const ExtensionSystem = imports.ui.extensionSystem;
|
|
const Link = imports.ui.link;
|
|
const ShellEntry = imports.ui.shellEntry;
|
|
const Tweener = imports.ui.tweener;
|
|
const Main = imports.ui.main;
|
|
const JsParse = imports.misc.jsParse;
|
|
|
|
/* Imports...feel free to add here as needed */
|
|
var commandHeader = 'const Clutter = imports.gi.Clutter; ' +
|
|
'const GLib = imports.gi.GLib; ' +
|
|
'const Gtk = imports.gi.Gtk; ' +
|
|
'const Mainloop = imports.mainloop; ' +
|
|
'const Meta = imports.gi.Meta; ' +
|
|
'const Shell = imports.gi.Shell; ' +
|
|
'const Tp = imports.gi.TelepathyGLib; ' +
|
|
'const Main = imports.ui.main; ' +
|
|
'const Lang = imports.lang; ' +
|
|
'const Tweener = imports.ui.tweener; ' +
|
|
/* Utility functions...we should probably be able to use these
|
|
* in the shell core code too. */
|
|
'const stage = global.stage; ' +
|
|
'const color = function(pixel) { let c= new Clutter.Color(); c.from_pixel(pixel); return c; }; ' +
|
|
/* Special lookingGlass functions */
|
|
'const it = Main.lookingGlass.getIt(); ' +
|
|
'const r = Lang.bind(Main.lookingGlass, Main.lookingGlass.getResult); ';
|
|
|
|
const HISTORY_KEY = 'looking-glass-history';
|
|
// Time between tabs for them to count as a double-tab event
|
|
const AUTO_COMPLETE_DOUBLE_TAB_DELAY = 500;
|
|
const AUTO_COMPLETE_SHOW_COMPLETION_ANIMATION_DURATION = 0.2;
|
|
const AUTO_COMPLETE_GLOBAL_KEYWORDS = _getAutoCompleteGlobalKeywords();
|
|
|
|
function _getAutoCompleteGlobalKeywords() {
|
|
const keywords = ['true', 'false', 'null', 'new'];
|
|
// Don't add the private properties of window (i.e., ones starting with '_')
|
|
const windowProperties = Object.getOwnPropertyNames(window).filter(function(a){ return a.charAt(0) != '_' });
|
|
const headerProperties = JsParse.getDeclaredConstants(commandHeader);
|
|
|
|
return keywords.concat(windowProperties).concat(headerProperties);
|
|
}
|
|
|
|
function AutoComplete(entry) {
|
|
this._init(entry);
|
|
}
|
|
|
|
AutoComplete.prototype = {
|
|
_init: function(entry) {
|
|
this._entry = entry;
|
|
this._entry.connect('key-press-event', Lang.bind(this, this._entryKeyPressEvent));
|
|
this._lastTabTime = global.get_current_time();
|
|
},
|
|
|
|
_processCompletionRequest: function(event) {
|
|
if (event.completions.length == 0) {
|
|
return;
|
|
}
|
|
// Unique match = go ahead and complete; multiple matches + single tab = complete the common starting string;
|
|
// multiple matches + double tab = emit a suggest event with all possible options
|
|
if (event.completions.length == 1) {
|
|
this.additionalCompletionText(event.completions[0], event.attrHead);
|
|
this.emit('completion', { completion: event.completions[0], type: 'whole-word' });
|
|
} else if (event.completions.length > 1 && event.tabType === 'single') {
|
|
let commonPrefix = JsParse.getCommonPrefix(event.completions);
|
|
|
|
if (commonPrefix.length > 0) {
|
|
this.additionalCompletionText(commonPrefix, event.attrHead);
|
|
this.emit('completion', { completion: commonPrefix, type: 'prefix' });
|
|
this.emit('suggest', { completions: event.completions});
|
|
}
|
|
} else if (event.completions.length > 1 && event.tabType === 'double') {
|
|
this.emit('suggest', { completions: event.completions});
|
|
}
|
|
},
|
|
|
|
_entryKeyPressEvent: function(actor, event) {
|
|
let cursorPos = this._entry.clutter_text.get_cursor_position();
|
|
let text = this._entry.get_text();
|
|
if (cursorPos != -1) {
|
|
text = text.slice(0, cursorPos);
|
|
}
|
|
if (event.get_key_symbol() == Clutter.Tab) {
|
|
let [completions, attrHead] = JsParse.getCompletions(text, commandHeader, AUTO_COMPLETE_GLOBAL_KEYWORDS);
|
|
let currTime = global.get_current_time();
|
|
if ((currTime - this._lastTabTime) < AUTO_COMPLETE_DOUBLE_TAB_DELAY) {
|
|
this._processCompletionRequest({ tabType: 'double',
|
|
completions: completions,
|
|
attrHead: attrHead });
|
|
} else {
|
|
this._processCompletionRequest({ tabType: 'single',
|
|
completions: completions,
|
|
attrHead: attrHead });
|
|
}
|
|
this._lastTabTime = currTime;
|
|
}
|
|
},
|
|
|
|
// Insert characters of text not already included in head at cursor position. i.e., if text="abc" and head="a",
|
|
// the string "bc" will be appended to this._entry
|
|
additionalCompletionText: function(text, head) {
|
|
let additionalCompletionText = text.slice(head.length);
|
|
let cursorPos = this._entry.clutter_text.get_cursor_position();
|
|
|
|
this._entry.clutter_text.insert_text(additionalCompletionText, cursorPos);
|
|
}
|
|
};
|
|
Signals.addSignalMethods(AutoComplete.prototype);
|
|
|
|
|
|
function Notebook() {
|
|
this._init();
|
|
}
|
|
|
|
Notebook.prototype = {
|
|
_init: function() {
|
|
this.actor = new St.BoxLayout({ vertical: true });
|
|
|
|
this.tabControls = new St.BoxLayout({ style_class: 'labels' });
|
|
|
|
this._selectedIndex = -1;
|
|
this._tabs = [];
|
|
},
|
|
|
|
appendPage: function(name, child) {
|
|
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);
|
|
return true;
|
|
}));
|
|
labelBox.add(label, { expand: true });
|
|
this.tabControls.add(labelBox);
|
|
|
|
let scrollview = new St.ScrollView({ x_fill: true, y_fill: true });
|
|
scrollview.get_hscroll_bar().hide();
|
|
scrollview.add_actor(child);
|
|
|
|
let tabData = { child: child,
|
|
labelBox: labelBox,
|
|
label: label,
|
|
scrollView: scrollview,
|
|
_scrollToBottom: false };
|
|
this._tabs.push(tabData);
|
|
scrollview.hide();
|
|
this.actor.add(scrollview, { expand: true });
|
|
|
|
let vAdjust = scrollview.vscroll.adjustment;
|
|
vAdjust.connect('changed', Lang.bind(this, function () { this._onAdjustScopeChanged(tabData); }));
|
|
vAdjust.connect('notify::value', Lang.bind(this, function() { this._onAdjustValueChanged(tabData); }));
|
|
|
|
if (this._selectedIndex == -1)
|
|
this.selectIndex(0);
|
|
},
|
|
|
|
_unselect: function() {
|
|
if (this._selectedIndex < 0)
|
|
return;
|
|
let tabData = this._tabs[this._selectedIndex];
|
|
tabData.labelBox.remove_style_pseudo_class('selected');
|
|
tabData.scrollView.hide();
|
|
this._selectedIndex = -1;
|
|
},
|
|
|
|
selectIndex: function(index) {
|
|
if (index == this._selectedIndex)
|
|
return;
|
|
if (index < 0) {
|
|
this._unselect();
|
|
this.emit('selection', null);
|
|
return;
|
|
}
|
|
|
|
// Focus the new tab before unmapping the old one
|
|
let tabData = this._tabs[index];
|
|
if (!tabData.scrollView.navigate_focus(null, Gtk.DirectionType.TAB_FORWARD, false))
|
|
this.actor.grab_key_focus();
|
|
|
|
this._unselect();
|
|
|
|
tabData.labelBox.add_style_pseudo_class('selected');
|
|
tabData.scrollView.show();
|
|
this._selectedIndex = index;
|
|
this.emit('selection', tabData.child);
|
|
},
|
|
|
|
selectChild: function(child) {
|
|
if (child == null)
|
|
this.selectIndex(-1);
|
|
else {
|
|
for (let i = 0; i < this._tabs.length; i++) {
|
|
let tabData = this._tabs[i];
|
|
if (tabData.child == child) {
|
|
this.selectIndex(i);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
scrollToBottom: function(index) {
|
|
let tabData = this._tabs[index];
|
|
tabData._scrollToBottom = true;
|
|
|
|
},
|
|
|
|
_onAdjustValueChanged: function (tabData) {
|
|
let vAdjust = tabData.scrollView.vscroll.adjustment;
|
|
if (vAdjust.value < (vAdjust.upper - vAdjust.lower - 0.5))
|
|
tabData._scrolltoBottom = false;
|
|
},
|
|
|
|
_onAdjustScopeChanged: function (tabData) {
|
|
if (!tabData._scrollToBottom)
|
|
return;
|
|
let vAdjust = tabData.scrollView.vscroll.adjustment;
|
|
vAdjust.value = vAdjust.upper - vAdjust.page_size;
|
|
},
|
|
|
|
nextTab: function() {
|
|
let nextIndex = this._selectedIndex;
|
|
if (nextIndex < this._tabs.length - 1) {
|
|
++nextIndex;
|
|
}
|
|
|
|
this.selectIndex(nextIndex);
|
|
},
|
|
|
|
prevTab: function() {
|
|
let prevIndex = this._selectedIndex;
|
|
if (prevIndex > 0) {
|
|
--prevIndex;
|
|
}
|
|
|
|
this.selectIndex(prevIndex);
|
|
}
|
|
};
|
|
Signals.addSignalMethods(Notebook.prototype);
|
|
|
|
function objectToString(o) {
|
|
if (typeof(o) == typeof(objectToString)) {
|
|
// special case this since the default is way, way too verbose
|
|
return '<js function>';
|
|
} else {
|
|
return '' + o;
|
|
}
|
|
}
|
|
|
|
const ObjLink = new Lang.Class({
|
|
Name: 'ObjLink',
|
|
Extends: 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;
|
|
|
|
this.parent({ 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);
|
|
}
|
|
|
|
Result.prototype = {
|
|
_init : function(command, o, index) {
|
|
this.index = index;
|
|
this.o = o;
|
|
|
|
this.actor = new St.BoxLayout({ vertical: true });
|
|
|
|
let cmdTxt = new St.Label({ text: command });
|
|
cmdTxt.clutter_text.ellipsize = Pango.EllipsizeMode.END;
|
|
this.actor.add(cmdTxt);
|
|
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);
|
|
}
|
|
};
|
|
|
|
function WindowList() {
|
|
this._init();
|
|
}
|
|
|
|
WindowList.prototype = {
|
|
_init : function () {
|
|
this.actor = new St.BoxLayout({ name: 'Windows', vertical: true, style: 'spacing: 8px' });
|
|
let tracker = Shell.WindowTracker.get_default();
|
|
this._updateId = Main.initializeDeferredWork(this.actor, Lang.bind(this, this._updateWindowList));
|
|
global.display.connect('window-created', Lang.bind(this, this._updateWindowList));
|
|
tracker.connect('tracked-windows-changed', Lang.bind(this, this._updateWindowList));
|
|
},
|
|
|
|
_updateWindowList: function() {
|
|
this.actor.get_children().forEach(function (actor) { actor.destroy(); });
|
|
let windows = global.get_window_actors();
|
|
let tracker = Shell.WindowTracker.get_default();
|
|
for (let i = 0; i < windows.length; i++) {
|
|
let metaWindow = windows[i].metaWindow;
|
|
// Avoid multiple connections
|
|
if (!metaWindow._lookingGlassManaged) {
|
|
metaWindow.connect('unmanaged', Lang.bind(this, this._updateWindowList));
|
|
metaWindow._lookingGlassManaged = true;
|
|
}
|
|
let box = new St.BoxLayout({ vertical: true });
|
|
this.actor.add(box);
|
|
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() }));
|
|
let app = tracker.get_window_app(metaWindow);
|
|
if (app != null && !app.is_window_backed()) {
|
|
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: ' }), { 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: '<untracked>' }));
|
|
}
|
|
}
|
|
}
|
|
};
|
|
Signals.addSignalMethods(WindowList.prototype);
|
|
|
|
function ObjInspector() {
|
|
this._init();
|
|
}
|
|
|
|
ObjInspector.prototype = {
|
|
_init : function () {
|
|
this._obj = null;
|
|
this._previousObj = null;
|
|
|
|
this._parentList = [];
|
|
|
|
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);
|
|
},
|
|
|
|
selectObject: function(obj, skipPrevious) {
|
|
if (!skipPrevious)
|
|
this._previousObj = this._obj;
|
|
else
|
|
this._previousObj = null;
|
|
this._obj = obj;
|
|
|
|
this._container.get_children().forEach(function (child) { child.destroy(); });
|
|
|
|
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: '<error>' });
|
|
}
|
|
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);
|
|
}
|
|
};
|
|
|
|
function addBorderPaintHook(actor) {
|
|
let signalId = actor.connect_after('paint',
|
|
function () {
|
|
let color = new Cogl.Color();
|
|
color.init_from_4ub(0xff, 0, 0, 0xc4);
|
|
Cogl.set_source_color(color);
|
|
|
|
let geom = actor.get_allocation_geometry();
|
|
let width = 2;
|
|
|
|
// clockwise order
|
|
Cogl.rectangle(0, 0, geom.width, width);
|
|
Cogl.rectangle(geom.width - width, width,
|
|
geom.width, geom.height);
|
|
Cogl.rectangle(0, geom.height,
|
|
geom.width - width, geom.height - width);
|
|
Cogl.rectangle(0, geom.height - width,
|
|
width, width);
|
|
});
|
|
|
|
actor.queue_redraw();
|
|
return signalId;
|
|
}
|
|
|
|
function Inspector() {
|
|
this._init();
|
|
}
|
|
|
|
Inspector.prototype = {
|
|
_init: function() {
|
|
let container = new Shell.GenericContainer({ width: 0,
|
|
height: 0 });
|
|
container.connect('allocate', Lang.bind(this, this._allocate));
|
|
Main.uiGroup.add_actor(container);
|
|
|
|
let eventHandler = new St.BoxLayout({ name: 'LookingGlassDialog',
|
|
vertical: false,
|
|
reactive: true });
|
|
this._eventHandler = eventHandler;
|
|
container.add_actor(eventHandler);
|
|
this._displayText = new St.Label();
|
|
eventHandler.add(this._displayText, { expand: true });
|
|
|
|
this._borderPaintTarget = null;
|
|
this._borderPaintId = null;
|
|
eventHandler.connect('destroy', Lang.bind(this, this._onDestroy));
|
|
eventHandler.connect('key-press-event', Lang.bind(this, this._onKeyPressEvent));
|
|
eventHandler.connect('button-press-event', Lang.bind(this, this._onButtonPressEvent));
|
|
eventHandler.connect('scroll-event', Lang.bind(this, this._onScrollEvent));
|
|
eventHandler.connect('motion-event', Lang.bind(this, this._onMotionEvent));
|
|
Clutter.grab_pointer(eventHandler);
|
|
Clutter.grab_keyboard(eventHandler);
|
|
|
|
// this._target is the actor currently shown by the inspector.
|
|
// this._pointerTarget is the actor directly under the pointer.
|
|
// Normally these are the same, but if you use the scroll wheel
|
|
// to drill down, they'll diverge until you either scroll back
|
|
// out, or move the pointer outside of _pointerTarget.
|
|
this._target = null;
|
|
this._pointerTarget = null;
|
|
},
|
|
|
|
_allocate: function(actor, box, flags) {
|
|
if (!this._eventHandler)
|
|
return;
|
|
|
|
let primary = Main.layoutManager.primaryMonitor;
|
|
|
|
let [minWidth, minHeight, natWidth, natHeight] =
|
|
this._eventHandler.get_preferred_size();
|
|
|
|
let childBox = new Clutter.ActorBox();
|
|
childBox.x1 = primary.x + Math.floor((primary.width - natWidth) / 2);
|
|
childBox.x2 = childBox.x1 + natWidth;
|
|
childBox.y1 = primary.y + Math.floor((primary.height - natHeight) / 2);
|
|
childBox.y2 = childBox.y1 + natHeight;
|
|
this._eventHandler.allocate(childBox, flags);
|
|
},
|
|
|
|
_close: function() {
|
|
Clutter.ungrab_pointer(this._eventHandler);
|
|
Clutter.ungrab_keyboard(this._eventHandler);
|
|
this._eventHandler.destroy();
|
|
this._eventHandler = null;
|
|
this.emit('closed');
|
|
},
|
|
|
|
_onDestroy: function() {
|
|
if (this._borderPaintTarget != null)
|
|
this._borderPaintTarget.disconnect(this._borderPaintId);
|
|
},
|
|
|
|
_onKeyPressEvent: function (actor, event) {
|
|
if (event.get_key_symbol() == Clutter.Escape)
|
|
this._close();
|
|
return true;
|
|
},
|
|
|
|
_onButtonPressEvent: function (actor, event) {
|
|
if (this._target) {
|
|
let [stageX, stageY] = event.get_coords();
|
|
this.emit('target', this._target, stageX, stageY);
|
|
}
|
|
this._close();
|
|
return true;
|
|
},
|
|
|
|
_onScrollEvent: function (actor, event) {
|
|
switch (event.get_scroll_direction()) {
|
|
case Clutter.ScrollDirection.UP:
|
|
// select parent
|
|
let parent = this._target.get_parent();
|
|
if (parent != null) {
|
|
this._target = parent;
|
|
this._update(event);
|
|
}
|
|
break;
|
|
|
|
case Clutter.ScrollDirection.DOWN:
|
|
// select child
|
|
if (this._target != this._pointerTarget) {
|
|
let child = this._pointerTarget;
|
|
while (child) {
|
|
let parent = child.get_parent();
|
|
if (parent == this._target)
|
|
break;
|
|
child = parent;
|
|
}
|
|
if (child) {
|
|
this._target = child;
|
|
this._update(event);
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
return true;
|
|
},
|
|
|
|
_onMotionEvent: function (actor, event) {
|
|
this._update(event);
|
|
return true;
|
|
},
|
|
|
|
_update: function(event) {
|
|
let [stageX, stageY] = event.get_coords();
|
|
let target = global.stage.get_actor_at_pos(Clutter.PickMode.ALL,
|
|
stageX,
|
|
stageY);
|
|
|
|
if (target != this._pointerTarget)
|
|
this._target = target;
|
|
this._pointerTarget = target;
|
|
|
|
let position = '[inspect x: ' + stageX + ' y: ' + stageY + ']';
|
|
this._displayText.text = '';
|
|
this._displayText.text = position + ' ' + this._target;
|
|
|
|
if (this._borderPaintTarget != this._target) {
|
|
if (this._borderPaintTarget != null)
|
|
this._borderPaintTarget.disconnect(this._borderPaintId);
|
|
this._borderPaintTarget = this._target;
|
|
this._borderPaintId = addBorderPaintHook(this._target);
|
|
}
|
|
}
|
|
};
|
|
|
|
Signals.addSignalMethods(Inspector.prototype);
|
|
|
|
function ErrorLog() {
|
|
this._init();
|
|
}
|
|
|
|
ErrorLog.prototype = {
|
|
_init: function() {
|
|
this.actor = new St.BoxLayout();
|
|
this.text = new St.Label();
|
|
this.actor.add(this.text);
|
|
// We need to override StLabel's default ellipsization when
|
|
// using line_wrap; otherwise ClutterText's layout is going
|
|
// to constrain both the width and height, which prevents
|
|
// scrolling.
|
|
this.text.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
|
|
this.text.clutter_text.line_wrap = true;
|
|
this.actor.connect('notify::mapped', Lang.bind(this, this._renderText));
|
|
},
|
|
|
|
_formatTime: function(d){
|
|
function pad(n) { return n < 10 ? '0' + n : n; }
|
|
return d.getUTCFullYear()+'-'
|
|
+ pad(d.getUTCMonth()+1)+'-'
|
|
+ pad(d.getUTCDate())+'T'
|
|
+ pad(d.getUTCHours())+':'
|
|
+ pad(d.getUTCMinutes())+':'
|
|
+ pad(d.getUTCSeconds())+'Z';
|
|
},
|
|
|
|
_renderText: function() {
|
|
if (!this.actor.mapped)
|
|
return;
|
|
let text = this.text.text;
|
|
let stack = Main._getAndClearErrorStack();
|
|
for (let i = 0; i < stack.length; i++) {
|
|
let logItem = stack[i];
|
|
text += logItem.category + ' t=' + this._formatTime(new Date(logItem.timestamp)) + ' ' + logItem.message + '\n';
|
|
}
|
|
this.text.text = text;
|
|
}
|
|
};
|
|
|
|
function Memory() {
|
|
this._init();
|
|
}
|
|
|
|
Memory.prototype = {
|
|
_init: function() {
|
|
this.actor = new St.BoxLayout({ vertical: true });
|
|
this._glibc_uordblks = new St.Label();
|
|
this.actor.add(this._glibc_uordblks);
|
|
|
|
this._js_bytes = new St.Label();
|
|
this.actor.add(this._js_bytes);
|
|
|
|
this._gjs_boxed = new St.Label();
|
|
this.actor.add(this._gjs_boxed);
|
|
|
|
this._gjs_gobject = new St.Label();
|
|
this.actor.add(this._gjs_gobject);
|
|
|
|
this._gjs_function = new St.Label();
|
|
this.actor.add(this._gjs_function);
|
|
|
|
this._gjs_closure = new St.Label();
|
|
this.actor.add(this._gjs_closure);
|
|
|
|
this._last_gc_seconds_ago = new St.Label();
|
|
this.actor.add(this._last_gc_seconds_ago);
|
|
|
|
this._gcbutton = new St.Button({ label: 'Full GC',
|
|
style_class: 'lg-obj-inspector-button' });
|
|
this._gcbutton.connect('clicked', Lang.bind(this, function () { global.gc(); this._renderText(); }));
|
|
this.actor.add(this._gcbutton, { x_align: St.Align.START,
|
|
x_fill: false });
|
|
|
|
this.actor.connect('notify::mapped', Lang.bind(this, this._renderText));
|
|
},
|
|
|
|
_renderText: function() {
|
|
if (!this.actor.mapped)
|
|
return;
|
|
let memInfo = global.get_memory_info();
|
|
this._glibc_uordblks.text = 'glibc_uordblks: ' + memInfo.glibc_uordblks;
|
|
this._js_bytes.text = 'js bytes: ' + memInfo.js_bytes;
|
|
this._gjs_boxed.text = 'gjs_boxed: ' + memInfo.gjs_boxed;
|
|
this._gjs_gobject.text = 'gjs_gobject: ' + memInfo.gjs_gobject;
|
|
this._gjs_function.text = 'gjs_function: ' + memInfo.gjs_function;
|
|
this._gjs_closure.text = 'gjs_closure: ' + memInfo.gjs_closure;
|
|
this._last_gc_seconds_ago.text = 'last_gc_seconds_ago: ' + memInfo.last_gc_seconds_ago;
|
|
}
|
|
};
|
|
|
|
function Extensions() {
|
|
this._init();
|
|
}
|
|
|
|
Extensions.prototype = {
|
|
_init: function() {
|
|
this.actor = new St.BoxLayout({ vertical: true,
|
|
name: 'lookingGlassExtensions' });
|
|
this._noExtensions = new St.Label({ style_class: 'lg-extensions-none',
|
|
text: _("No extensions installed") });
|
|
this._numExtensions = 0;
|
|
this._extensionsList = new St.BoxLayout({ vertical: true,
|
|
style_class: 'lg-extensions-list' });
|
|
this._extensionsList.add(this._noExtensions);
|
|
this.actor.add(this._extensionsList);
|
|
|
|
for (let uuid in ExtensionSystem.extensionMeta)
|
|
this._loadExtension(null, uuid);
|
|
|
|
ExtensionSystem.connect('extension-loaded',
|
|
Lang.bind(this, this._loadExtension));
|
|
},
|
|
|
|
_loadExtension: function(o, uuid) {
|
|
let extension = ExtensionSystem.extensionMeta[uuid];
|
|
// There can be cases where we create dummy extension metadata
|
|
// that's not really a proper extension. Don't bother with these.
|
|
if (!extension.name)
|
|
return;
|
|
|
|
let extensionDisplay = this._createExtensionDisplay(extension);
|
|
if (this._numExtensions == 0)
|
|
this._extensionsList.remove_actor(this._noExtensions);
|
|
|
|
this._numExtensions ++;
|
|
this._extensionsList.add(extensionDisplay);
|
|
},
|
|
|
|
_onViewSource: function (actor) {
|
|
let meta = actor._extensionMeta;
|
|
let file = Gio.file_new_for_path(meta.path);
|
|
let uri = file.get_uri();
|
|
Gio.app_info_launch_default_for_uri(uri, global.create_app_launch_context());
|
|
Main.lookingGlass.close();
|
|
},
|
|
|
|
_onWebPage: function (actor) {
|
|
let meta = actor._extensionMeta;
|
|
Gio.app_info_launch_default_for_uri(meta.url, global.create_app_launch_context());
|
|
Main.lookingGlass.close();
|
|
},
|
|
|
|
_onViewErrors: function (actor) {
|
|
let meta = actor._extensionMeta;
|
|
let shouldShow = !actor._isShowing;
|
|
|
|
if (shouldShow) {
|
|
let errors = ExtensionSystem.errors[meta.uuid];
|
|
let errorDisplay = new St.BoxLayout({ vertical: true });
|
|
if (errors && errors.length) {
|
|
for (let i = 0; i < errors.length; i ++)
|
|
errorDisplay.add(new St.Label({ text: errors[i] }));
|
|
} else {
|
|
/* Translators: argument is an extension UUID. */
|
|
let message = _("%s has not emitted any errors.").format(meta.uuid);
|
|
errorDisplay.add(new St.Label({ text: message }));
|
|
}
|
|
|
|
actor._errorDisplay = errorDisplay;
|
|
actor._parentBox.add(errorDisplay);
|
|
actor.label = _("Hide Errors");
|
|
} else {
|
|
actor._errorDisplay.destroy();
|
|
actor._errorDisplay = null;
|
|
actor.label = _("Show Errors");
|
|
}
|
|
|
|
actor._isShowing = shouldShow;
|
|
},
|
|
|
|
_stateToString: function(extensionState) {
|
|
switch (extensionState) {
|
|
case ExtensionSystem.ExtensionState.ENABLED:
|
|
return _("Enabled");
|
|
case ExtensionSystem.ExtensionState.DISABLED:
|
|
case ExtensionSystem.ExtensionState.INITIALIZED:
|
|
return _("Disabled");
|
|
case ExtensionSystem.ExtensionState.ERROR:
|
|
return _("Error");
|
|
case ExtensionSystem.ExtensionState.OUT_OF_DATE:
|
|
return _("Out of date");
|
|
case ExtensionSystem.ExtensionState.DOWNLOADING:
|
|
return _("Downloading");
|
|
}
|
|
return 'Unknown'; // Not translated, shouldn't appear
|
|
},
|
|
|
|
_createExtensionDisplay: function(meta) {
|
|
let box = new St.BoxLayout({ style_class: 'lg-extension', vertical: true });
|
|
let name = new St.Label({ style_class: 'lg-extension-name',
|
|
text: meta.name });
|
|
box.add(name, { expand: true });
|
|
let description = new St.Label({ style_class: 'lg-extension-description',
|
|
text: meta.description || 'No description' });
|
|
box.add(description, { expand: true });
|
|
|
|
let metaBox = new St.BoxLayout({ style_class: 'lg-extension-meta' });
|
|
box.add(metaBox);
|
|
let stateString = this._stateToString(meta.state);
|
|
let state = new St.Label({ style_class: 'lg-extension-state',
|
|
text: this._stateToString(meta.state) });
|
|
metaBox.add(state);
|
|
|
|
let viewsource = new Link.Link({ label: _("View Source") });
|
|
viewsource.actor._extensionMeta = meta;
|
|
viewsource.actor.connect('clicked', Lang.bind(this, this._onViewSource));
|
|
metaBox.add(viewsource.actor);
|
|
|
|
if (meta.url) {
|
|
let webpage = new Link.Link({ label: _("Web Page") });
|
|
webpage.actor._extensionMeta = meta;
|
|
webpage.actor.connect('clicked', Lang.bind(this, this._onWebPage));
|
|
metaBox.add(webpage.actor);
|
|
}
|
|
|
|
let viewerrors = new Link.Link({ label: _("Show Errors") });
|
|
viewerrors.actor._extensionMeta = meta;
|
|
viewerrors.actor._parentBox = box;
|
|
viewerrors.actor._isShowing = false;
|
|
viewerrors.actor.connect('clicked', Lang.bind(this, this._onViewErrors));
|
|
metaBox.add(viewerrors.actor);
|
|
|
|
return box;
|
|
}
|
|
};
|
|
|
|
function LookingGlass() {
|
|
this._init();
|
|
}
|
|
|
|
LookingGlass.prototype = {
|
|
_init : function() {
|
|
this._borderPaintTarget = null;
|
|
this._borderPaintId = 0;
|
|
this._borderDestroyId = 0;
|
|
|
|
this._open = false;
|
|
|
|
this._offset = 0;
|
|
this._results = [];
|
|
|
|
// Sort of magic, but...eh.
|
|
this._maxItems = 150;
|
|
|
|
this.actor = new St.BoxLayout({ name: 'LookingGlassDialog',
|
|
style_class: 'lg-dialog',
|
|
vertical: true,
|
|
visible: false,
|
|
reactive: true });
|
|
this.actor.connect('key-press-event', Lang.bind(this, this._globalKeyPressEvent));
|
|
|
|
this._interfaceSettings = new Gio.Settings({ schema: 'org.gnome.desktop.interface' });
|
|
this._interfaceSettings.connect('changed::monospace-font-name',
|
|
Lang.bind(this, this._updateFont));
|
|
this._updateFont();
|
|
|
|
// We want it to appear to slide out from underneath the panel
|
|
Main.layoutManager.panelBox.add_actor(this.actor);
|
|
this.actor.lower_bottom();
|
|
Main.layoutManager.panelBox.connect('allocation-changed',
|
|
Lang.bind(this, this._queueResize));
|
|
Main.layoutManager.keyboardBox.connect('allocation-changed',
|
|
Lang.bind(this, this._queueResize));
|
|
|
|
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 = new St.Icon({ icon_name: 'gtk-color-picker',
|
|
icon_type: St.IconType.FULLCOLOR,
|
|
icon_size: 24 });
|
|
toolbar.add_actor(inspectIcon);
|
|
inspectIcon.reactive = true;
|
|
inspectIcon.connect('button-press-event', Lang.bind(this, function () {
|
|
let inspector = new Inspector();
|
|
inspector.connect('target', Lang.bind(this, function(i, target, stageX, stageY) {
|
|
this._pushResult('<inspect x:' + stageX + ' y:' + stageY + '>',
|
|
target);
|
|
}));
|
|
inspector.connect('closed', Lang.bind(this, function() {
|
|
this.actor.show();
|
|
global.stage.set_key_focus(this._entry);
|
|
}));
|
|
this.actor.hide();
|
|
return true;
|
|
}));
|
|
|
|
let notebook = new Notebook();
|
|
this._notebook = notebook;
|
|
this.actor.add(notebook.actor, { expand: true });
|
|
|
|
let emptyBox = new St.Bin();
|
|
toolbar.add(emptyBox, { expand: true });
|
|
toolbar.add_actor(notebook.tabControls);
|
|
|
|
this._evalBox = new St.BoxLayout({ name: 'EvalBox', vertical: true });
|
|
notebook.appendPage('Evaluator', this._evalBox);
|
|
|
|
this._resultsArea = new St.BoxLayout({ name: 'ResultsArea', vertical: true });
|
|
this._evalBox.add(this._resultsArea, { expand: true });
|
|
|
|
this._entryArea = new St.BoxLayout({ name: 'EntryArea' });
|
|
this._evalBox.add_actor(this._entryArea);
|
|
|
|
let label = new St.Label({ text: 'js>>> ' });
|
|
this._entryArea.add(label);
|
|
|
|
this._entry = new St.Entry({ can_focus: true });
|
|
ShellEntry.addContextMenu(this._entry);
|
|
this._entryArea.add(this._entry, { expand: true });
|
|
|
|
this._windowList = new WindowList();
|
|
this._windowList.connect('selected', Lang.bind(this, function(list, window) {
|
|
notebook.selectIndex(0);
|
|
this._pushResult('<window selection>', window);
|
|
}));
|
|
notebook.appendPage('Windows', this._windowList.actor);
|
|
|
|
this._errorLog = new ErrorLog();
|
|
notebook.appendPage('Errors', this._errorLog.actor);
|
|
|
|
this._memory = new Memory();
|
|
notebook.appendPage('Memory', this._memory.actor);
|
|
|
|
this._extensions = new Extensions();
|
|
notebook.appendPage('Extensions', this._extensions.actor);
|
|
|
|
this._entry.clutter_text.connect('activate', Lang.bind(this, function (o, e) {
|
|
// Hide any completions we are currently showing
|
|
this._hideCompletions();
|
|
|
|
let text = o.get_text();
|
|
// Ensure we don't get newlines in the command; the history file is
|
|
// newline-separated.
|
|
text.replace('\n', ' ');
|
|
// Strip leading and trailing whitespace
|
|
text = text.replace(/^\s+/g, '').replace(/\s+$/g, '');
|
|
if (text == '')
|
|
return true;
|
|
this._evaluate(text);
|
|
return true;
|
|
}));
|
|
|
|
this._history = new History.HistoryManager({ gsettingsKey: HISTORY_KEY,
|
|
entry: this._entry.clutter_text });
|
|
|
|
this._autoComplete = new AutoComplete(this._entry);
|
|
this._autoComplete.connect('suggest', Lang.bind(this, function(a,e) {
|
|
this._showCompletions(e.completions);
|
|
}));
|
|
// If a completion is completed unambiguously, the currently-displayed completion
|
|
// suggestions become irrelevant.
|
|
this._autoComplete.connect('completion', Lang.bind(this, function(a,e) {
|
|
if (e.type == 'whole-word')
|
|
this._hideCompletions();
|
|
}));
|
|
|
|
this._resize();
|
|
},
|
|
|
|
_updateFont: function() {
|
|
let fontName = this._interfaceSettings.get_string('monospace-font-name');
|
|
// This is mishandled by the scanner - should by Pango.FontDescription_from_string(fontName);
|
|
// https://bugzilla.gnome.org/show_bug.cgi?id=595889
|
|
let fontDesc = Pango.font_description_from_string(fontName);
|
|
// We ignore everything but size and style; you'd be crazy to set your system-wide
|
|
// monospace font to be bold/oblique/etc. Could easily be added here.
|
|
this.actor.style =
|
|
'font-size: ' + fontDesc.get_size() / 1024. + (fontDesc.get_size_is_absolute() ? 'px' : 'pt') + ';'
|
|
+ 'font-family: "' + fontDesc.get_family() + '";';
|
|
},
|
|
|
|
_pushResult: function(command, obj) {
|
|
let index = this._results.length + this._offset;
|
|
let result = new Result('>>> ' + command, obj, index);
|
|
this._results.push(result);
|
|
this._resultsArea.add(result.actor);
|
|
if (this._borderPaintTarget != null) {
|
|
this._borderPaintTarget.disconnect(this._borderPaintId);
|
|
this._borderPaintTarget = null;
|
|
}
|
|
if (obj instanceof Clutter.Actor) {
|
|
this._borderPaintTarget = obj;
|
|
this._borderPaintId = addBorderPaintHook(obj);
|
|
this._borderDestroyId = obj.connect('destroy', Lang.bind(this, function () {
|
|
this._borderDestroyId = 0;
|
|
this._borderPaintTarget = null;
|
|
}));
|
|
}
|
|
let children = this._resultsArea.get_children();
|
|
if (children.length > this._maxItems) {
|
|
this._results.shift();
|
|
children[0].destroy();
|
|
this._offset++;
|
|
}
|
|
this._it = obj;
|
|
|
|
// Scroll to bottom
|
|
this._notebook.scrollToBottom(0);
|
|
},
|
|
|
|
_showCompletions: function(completions) {
|
|
if (!this._completionActor) {
|
|
let actor = new St.BoxLayout({ vertical: true });
|
|
|
|
this._completionText = new St.Label({ name: 'LookingGlassAutoCompletionText', style_class: 'lg-completions-text' });
|
|
this._completionText.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
|
|
this._completionText.clutter_text.line_wrap = true;
|
|
actor.add(this._completionText);
|
|
|
|
let line = new Clutter.Rectangle();
|
|
let padBin = new St.Bin({ x_fill: true, y_fill: true });
|
|
padBin.add_actor(line);
|
|
actor.add(padBin);
|
|
|
|
this._completionActor = actor;
|
|
this._evalBox.insert_before(this._completionActor, this._entryArea);
|
|
}
|
|
|
|
this._completionText.set_text(completions.join(', '));
|
|
|
|
// Setting the height to -1 allows us to get its actual preferred height rather than
|
|
// whatever was last given in set_height by Tweener.
|
|
this._completionActor.set_height(-1);
|
|
let [minHeight, naturalHeight] = this._completionText.get_preferred_height(this._resultsArea.get_width());
|
|
|
|
// Don't reanimate if we are already visible
|
|
if (this._completionActor.visible) {
|
|
this._completionActor.height = naturalHeight;
|
|
} else {
|
|
this._completionActor.show();
|
|
Tweener.removeTweens(this._completionActor);
|
|
Tweener.addTween(this._completionActor, { time: AUTO_COMPLETE_SHOW_COMPLETION_ANIMATION_DURATION / St.get_slow_down_factor(),
|
|
transition: 'easeOutQuad',
|
|
height: naturalHeight,
|
|
opacity: 255
|
|
});
|
|
}
|
|
},
|
|
|
|
_hideCompletions: function() {
|
|
if (this._completionActor) {
|
|
Tweener.removeTweens(this._completionActor);
|
|
Tweener.addTween(this._completionActor, { time: AUTO_COMPLETE_SHOW_COMPLETION_ANIMATION_DURATION / St.get_slow_down_factor(),
|
|
transition: 'easeOutQuad',
|
|
height: 0,
|
|
opacity: 0,
|
|
onComplete: Lang.bind(this, function () {
|
|
this._completionActor.hide();
|
|
})
|
|
});
|
|
}
|
|
},
|
|
|
|
_evaluate : function(command) {
|
|
this._history.addItem(command);
|
|
|
|
let fullCmd = commandHeader + command;
|
|
|
|
let resultObj;
|
|
try {
|
|
resultObj = eval(fullCmd);
|
|
} catch (e) {
|
|
resultObj = '<exception ' + e + '>';
|
|
}
|
|
|
|
this._pushResult(command, resultObj);
|
|
this._entry.text = '';
|
|
},
|
|
|
|
getIt: function () {
|
|
return this._it;
|
|
},
|
|
|
|
getResult: function(idx) {
|
|
return this._results[idx - this._offset].o;
|
|
},
|
|
|
|
toggle: function() {
|
|
if (this._open)
|
|
this.close();
|
|
else
|
|
this.open();
|
|
},
|
|
|
|
_queueResize: function() {
|
|
Meta.later_add(Meta.LaterType.BEFORE_REDRAW,
|
|
Lang.bind(this, function () { this._resize(); }));
|
|
},
|
|
|
|
_resize: function() {
|
|
let primary = Main.layoutManager.primaryMonitor;
|
|
let myWidth = primary.width * 0.7;
|
|
let availableHeight = primary.height - Main.layoutManager.keyboardBox.height;
|
|
let myHeight = Math.min(primary.height * 0.7, availableHeight * 0.9);
|
|
this.actor.x = (primary.width - myWidth) / 2;
|
|
this._hiddenY = this.actor.get_parent().height - myHeight - 4; // -4 to hide the top corners
|
|
this._targetY = this._hiddenY + myHeight;
|
|
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));
|
|
},
|
|
|
|
insertObject: function(obj) {
|
|
this._pushResult('<insert>', 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();
|
|
let modifierState = Shell.get_event_state(event);
|
|
if (symbol == Clutter.Escape) {
|
|
if (this._objInspector.actor.visible) {
|
|
this._objInspector.close();
|
|
} else {
|
|
this.close();
|
|
}
|
|
return true;
|
|
}
|
|
// Ctrl+PgUp and Ctrl+PgDown switches tabs in the notebook view
|
|
if (modifierState & Clutter.ModifierType.CONTROL_MASK) {
|
|
if (symbol == Clutter.KEY_Page_Up) {
|
|
this._notebook.prevTab();
|
|
} else if (symbol == Clutter.KEY_Page_Down) {
|
|
this._notebook.nextTab();
|
|
}
|
|
}
|
|
return false;
|
|
},
|
|
|
|
open : function() {
|
|
if (this._open)
|
|
return;
|
|
|
|
if (!Main.pushModal(this._entry))
|
|
return;
|
|
|
|
this._notebook.selectIndex(0);
|
|
this.actor.show();
|
|
this._open = true;
|
|
this._history.lastItem();
|
|
|
|
Tweener.removeTweens(this.actor);
|
|
|
|
// We inverse compensate for the slow-down so you can change the factor
|
|
// through LookingGlass without long waits.
|
|
Tweener.addTween(this.actor, { time: 0.5 / St.get_slow_down_factor(),
|
|
transition: 'easeOutQuad',
|
|
y: this._targetY
|
|
});
|
|
},
|
|
|
|
close : function() {
|
|
if (!this._open)
|
|
return;
|
|
|
|
this._objInspector.actor.hide();
|
|
|
|
this._open = false;
|
|
Tweener.removeTweens(this.actor);
|
|
|
|
if (this._borderPaintTarget != null) {
|
|
this._borderPaintTarget.disconnect(this._borderPaintId);
|
|
this._borderPaintTarget.disconnect(this._borderDestroyId);
|
|
this._borderPaintTarget = null;
|
|
}
|
|
|
|
Main.popModal(this._entry);
|
|
|
|
Tweener.addTween(this.actor, { time: 0.5 / St.get_slow_down_factor(),
|
|
transition: 'easeOutQuad',
|
|
y: this._hiddenY,
|
|
onComplete: Lang.bind(this, function () {
|
|
this.actor.hide();
|
|
})
|
|
});
|
|
}
|
|
};
|
|
Signals.addSignalMethods(LookingGlass.prototype);
|