diff --git a/data/gnome-shell.schemas b/data/gnome-shell.schemas
index 9ace3bcd5..4e1f49620 100644
--- a/data/gnome-shell.schemas
+++ b/data/gnome-shell.schemas
@@ -88,6 +88,21 @@
+
+ /schemas/desktop/gnome/shell/disabled_extensions
+ /desktop/gnome/shell/disabled_extensions
+ gnome-shell
+ list
+ string
+ []
+
+ Uuids of extensions to disable
+
+ GNOME Shell extensions have a uuid property; this key lists extensions which should not be loaded.
+
+
+
+
diff --git a/data/theme/gnome-shell.css b/data/theme/gnome-shell.css
index efcc65442..e82b2e649 100644
--- a/data/theme/gnome-shell.css
+++ b/data/theme/gnome-shell.css
@@ -17,6 +17,15 @@
* Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
*/
+.shell-link {
+ color: #0000ff;
+ text-decoration: underline;
+}
+
+.shell-link:hover {
+ color: #0000e0;
+}
+
StScrollBar
{
padding: 0px;
@@ -152,17 +161,6 @@ StTooltip {
spacing: 12px;
}
-.dash-search-section-header {
- padding: 6px 0px;
- spacing: 4px;
- font-size: 12px;
- color: #bbbbbb;
-}
-
-.dash-search-section-title, dash-search-section-count {
- font-weight: bold;
-}
-
#searchEntry {
padding: 4px;
border-bottom: 1px solid #262626;
@@ -237,6 +235,29 @@ StTooltip {
height: 16px;
}
+.dash-search-section-header {
+ padding: 6px 0px;
+ spacing: 4px;
+}
+
+.dash-search-section-results {
+ color: #ffffff;
+ padding-left: 4px;
+}
+
+.dash-search-section-list-results {
+ spacing: 4px;
+}
+
+.dash-search-result-content {
+ padding: 2px;
+}
+
+.dash-search-result-content:selected {
+ padding: 1px;
+ border: 1px solid #262626;
+}
+
/* GenericDisplay */
.generic-display-container {
@@ -352,6 +373,19 @@ StTooltip {
border-radius: 4px;
}
+#LookingGlassDialog .labels {
+ spacing: 4px;
+}
+
+#LookingGlassDialog .notebook-tab {
+ padding: 2px;
+}
+
+#LookingGlassDialog .notebook-tab:selected {
+ border: 1px solid #88ff66;
+ padding: 1px;
+}
+
#LookingGlassDialog StLabel
{
color: #88ff66;
@@ -368,6 +402,29 @@ StTooltip {
spacing: 4px;
}
+#lookingGlassExtensions {
+ padding: 4px;
+}
+
+.lg-extension-list {
+ padding: 4px;
+ spacing: 6px;
+}
+
+.lg-extension {
+ border: 1px solid #6f6f6f;
+ border-radius: 4px;
+ padding: 4px;
+}
+
+.lg-extension-name {
+ font-weight: bold;
+}
+
+.lg-extension-actions {
+ spacing: 6px;
+}
+
/* Calendar popup */
#calendarPopup {
@@ -458,6 +515,11 @@ StTooltip {
spacing: 4px;
}
+.switcher-list .thumbnail {
+ width: 256px;
+ height: 256px;
+}
+
.switcher-list .outlined-item-box {
padding: 6px;
border: 2px solid rgba(85,85,85,1.0);
diff --git a/js/misc/docInfo.js b/js/misc/docInfo.js
index 8123e281b..559e9ecef 100644
--- a/js/misc/docInfo.js
+++ b/js/misc/docInfo.js
@@ -7,6 +7,7 @@ const Shell = imports.gi.Shell;
const Lang = imports.lang;
const Signals = imports.signals;
+const Search = imports.ui.search;
const Main = imports.ui.main;
const THUMBNAIL_ICON_MARGIN = 2;
@@ -23,6 +24,7 @@ DocInfo.prototype = {
// correctly. See http://bugzilla.gnome.org/show_bug.cgi?id=567094
this.timestamp = recentInfo.get_modified().getTime() / 1000;
this.name = recentInfo.get_display_name();
+ this._lowerName = this.name.toLowerCase();
this.uri = recentInfo.get_uri();
this.mimeType = recentInfo.get_mime_type();
},
@@ -32,54 +34,27 @@ DocInfo.prototype = {
},
launch : function() {
- // While using Gio.app_info_launch_default_for_uri() would be
- // shorter in terms of lines of code, we are not doing so
- // because that would duplicate the work of retrieving the
- // mime type.
- let needsUri = Gio.file_new_for_uri(this.uri).get_path() == null;
- let appInfo = Gio.app_info_get_default_for_type(this.mimeType, needsUri);
-
- if (appInfo != null) {
- appInfo.launch_uris([this.uri], Main.createAppLaunchContext());
- } else {
- log("Failed to get default application info for mime type " + this.mimeType +
- ". Will try to use the last application that registered the document.");
- let appName = this.recentInfo.last_application();
- let [success, appExec, count, time] = this.recentInfo.get_application_info(appName);
- if (success) {
- log("Will open a document with the following command: " + appExec);
- // TODO: Change this once better support for creating
- // GAppInfo is added to GtkRecentInfo, as right now
- // this relies on the fact that the file uri is
- // already a part of appExec, so we don't supply any
- // files to appInfo.launch().
-
- // The 'command line' passed to
- // create_from_command_line is allowed to contain
- // '%' macros that are expanded to file
- // name / icon name, etc, so we need to escape % as %%
- appExec = appExec.replace(/%/g, "%%");
-
- let appInfo = Gio.app_info_create_from_commandline(appExec, null, 0, null);
-
- // The point of passing an app launch context to
- // launch() is mostly to get startup notification and
- // associated benefits like the app appearing on the
- // right desktop; but it doesn't really work for now
- // because with the way we create the appInfo we
- // aren't reading the application's desktop file, and
- // thus don't find the StartupNotify=true in it. So,
- // despite passing the app launch context, no startup
- // notification occurs.
- appInfo.launch([], Main.createAppLaunchContext());
- } else {
- log("Failed to get application info for " + this.uri);
- }
- }
+ Shell.DocSystem.get_default().open(this.recentInfo);
},
- exists : function() {
- return this.recentInfo.exists();
+ matchTerms: function(terms) {
+ let mtype = Search.MatchType.NONE;
+ for (let i = 0; i < terms.length; i++) {
+ let term = terms[i];
+ let idx = this._lowerName.indexOf(term);
+ if (idx == 0) {
+ if (mtype != Search.MatchType.NONE)
+ return Search.MatchType.MULTIPLE;
+ mtype = Search.MatchType.PREFIX;
+ } else if (idx > 0) {
+ if (mtype != Search.MatchType.NONE)
+ return Search.MatchType.MULTIPLE;
+ mtype = Search.MatchType.SUBSTRING;
+ } else {
+ continue;
+ }
+ }
+ return mtype;
}
};
@@ -91,50 +66,86 @@ function getDocManager() {
return docManagerInstance;
}
+/**
+ * DocManager wraps the DocSystem, primarily to expose DocInfo objects
+ * which conform to the GenericDisplay item API.
+ */
function DocManager() {
this._init();
}
DocManager.prototype = {
_init: function() {
- this._recentManager = Gtk.RecentManager.get_default();
- this._items = {};
- this._recentManager.connect('changed', Lang.bind(this, function(recentManager) {
- this._reload();
- this.emit('changed');
- }));
+ this._docSystem = Shell.DocSystem.get_default();
+ this._infosByTimestamp = [];
+ this._infosByUri = {};
+ this._docSystem.connect('changed', Lang.bind(this, this._reload));
this._reload();
},
_reload: function() {
- let docs = this._recentManager.get_items();
- let newItems = {};
+ let docs = this._docSystem.get_all();
+ this._infosByTimestamp = [];
+ this._infosByUri = {};
for (let i = 0; i < docs.length; i++) {
let recentInfo = docs[i];
- if (!recentInfo.exists())
- continue;
let docInfo = new DocInfo(recentInfo);
-
- // we use GtkRecentInfo URI as an item Id
- newItems[docInfo.uri] = docInfo;
+ this._infosByTimestamp.push(docInfo);
+ this._infosByUri[docInfo.uri] = docInfo;
}
- let deleted = {};
- for (var uri in this._items) {
- if (!(uri in newItems))
- deleted[uri] = this._items[uri];
- }
- /* If we'd cached any thumbnail references that no longer exist,
- dump them here */
- let texCache = Shell.TextureCache.get_default();
- for (var uri in deleted) {
- texCache.evict_recent_thumbnail(this._items[uri].recentInfo);
- }
- this._items = newItems;
+ this.emit('changed');
},
- getItems: function() {
- return this._items;
+ getTimestampOrderedInfos: function() {
+ return this._infosByTimestamp;
+ },
+
+ getInfosByUri: function() {
+ return this._infosByUri;
+ },
+
+ lookupByUri: function(uri) {
+ return this._infosByUri[uri];
+ },
+
+ queueExistenceCheck: function(count) {
+ return this._docSystem.queue_existence_check(count);
+ },
+
+ initialSearch: function(terms) {
+ let multipleMatches = [];
+ let prefixMatches = [];
+ let substringMatches = [];
+ for (let i = 0; i < this._infosByTimestamp.length; i++) {
+ let item = this._infosByTimestamp[i];
+ let mtype = item.matchTerms(terms);
+ if (mtype == Search.MatchType.MULTIPLE)
+ multipleMatches.push(item.uri);
+ else if (mtype == Search.MatchType.PREFIX)
+ prefixMatches.push(item.uri);
+ else if (mtype == Search.MatchType.SUBSTRING)
+ substringMatches.push(item.uri);
+ }
+ return multipleMatches.concat(prefixMatches.concat(substringMatches));
+ },
+
+ subsearch: function(previousResults, terms) {
+ let multipleMatches = [];
+ let prefixMatches = [];
+ let substringMatches = [];
+ for (let i = 0; i < previousResults.length; i++) {
+ let uri = previousResults[i];
+ let item = this._infosByUri[uri];
+ let mtype = item.matchTerms(terms);
+ if (mtype == Search.MatchType.MULTIPLE)
+ multipleMatches.push(uri);
+ else if (mtype == Search.MatchType.PREFIX)
+ prefixMatches.push(uri);
+ else if (mtype == Search.MatchType.SUBSTRING)
+ substringMatches.push(uri);
+ }
+ return multipleMatches.concat(prefixMatches.concat(substringMatches));
}
}
diff --git a/js/ui/Makefile.am b/js/ui/Makefile.am
index 270c0ebf6..ca38169f6 100644
--- a/js/ui/Makefile.am
+++ b/js/ui/Makefile.am
@@ -10,6 +10,7 @@ dist_jsui_DATA = \
dnd.js \
docDisplay.js \
environment.js \
+ extensionSystem.js \
genericDisplay.js \
lightbox.js \
link.js \
@@ -20,6 +21,7 @@ dist_jsui_DATA = \
panel.js \
placeDisplay.js \
runDialog.js \
+ search.js \
shellDBus.js \
sidebar.js \
statusMenu.js \
diff --git a/js/ui/altTab.js b/js/ui/altTab.js
index 3ed39f8c5..f211a8b3c 100644
--- a/js/ui/altTab.js
+++ b/js/ui/altTab.js
@@ -459,7 +459,7 @@ SwitcherList.prototype = {
this._separator = box;
this._list.add_actor(box);
},
-
+
highlight: function(index, justOutline) {
if (this._highlighted != -1)
this._items[this._highlighted].style_class = 'item-box';
@@ -477,7 +477,7 @@ SwitcherList.prototype = {
_itemActivated: function(n) {
this.emit('item-activated', n);
},
-
+
_itemEntered: function(n) {
this.emit('item-entered', n);
},
@@ -592,7 +592,7 @@ AppIcon.prototype = {
this.actor = new St.BoxLayout({ style_class: "alt-tab-app",
vertical: true });
this._icon = this.app.create_icon_texture(POPUP_APPICON_SIZE);
- this.actor.add(this._icon, { x_fill: false, y_fill: false });
+ this.actor.add(this._icon, { x_fill: false, y_fill: false } );
this._label = new St.Label({ text: this.app.get_name() });
this.actor.add(this._label, { x_fill: false });
}
@@ -750,11 +750,14 @@ ThumbnailList.prototype = {
let box = new St.BoxLayout({ style_class: "thumbnail-box",
vertical: true });
+ let bin = new St.Bin({ style_class: "thumbnail" });
let clone = new Clutter.Clone ({ source: windowTexture,
reactive: true,
width: width * scale,
height: height * scale });
- box.add_actor(clone);
+
+ bin.add_actor(clone);
+ box.add_actor(bin);
let title = windows[i].get_title();
if (title) {
diff --git a/js/ui/appDisplay.js b/js/ui/appDisplay.js
index 8bf359d08..fbe096a1b 100644
--- a/js/ui/appDisplay.js
+++ b/js/ui/appDisplay.js
@@ -18,6 +18,7 @@ const AppFavorites = imports.ui.appFavorites;
const DND = imports.ui.dnd;
const GenericDisplay = imports.ui.genericDisplay;
const Main = imports.ui.main;
+const Search = imports.ui.search;
const Workspaces = imports.ui.workspaces;
const APPICON_SIZE = 48;
@@ -54,7 +55,7 @@ AppDisplayItem.prototype = {
let windows = app.get_windows();
if (windows.length > 0) {
let mostRecentWindow = windows[0];
- Main.overview.activateWindow(mostRecentWindow, Main.currentTime());
+ Main.overview.activateWindow(mostRecentWindow, global.get_current_time());
} else {
this._appInfo.launch();
}
@@ -134,17 +135,10 @@ AppDisplay.prototype = {
this._addApp(app);
}
} else {
- // Loop over the toplevel menu items, load the set of desktop file ids
- // associated with each one.
- let allMenus = this._appSystem.get_menus();
- for (let i = 0; i < allMenus.length; i++) {
- let menu = allMenus[i];
- let menuApps = this._appSystem.get_applications_for_menu(menu.id);
-
- for (let j = 0; j < menuApps.length; j++) {
- let app = menuApps[j];
- this._addApp(app);
- }
+ let apps = this._appSystem.get_flattened_apps();
+ for (let i = 0; i < apps.length; i++) {
+ let app = apps[i];
+ this._addApp(app);
}
}
@@ -220,31 +214,104 @@ AppDisplay.prototype = {
Signals.addSignalMethods(AppDisplay.prototype);
-
-function BaseWellItem(app, isFavorite) {
- this._init(app, isFavorite);
+function BaseAppSearchProvider() {
+ this._init();
}
-BaseWellItem.prototype = {
- _init : function(app, isFavorite) {
+BaseAppSearchProvider.prototype = {
+ __proto__: Search.SearchProvider.prototype,
+
+ _init: function(name) {
+ Search.SearchProvider.prototype._init.call(this, name);
+ this._appSys = Shell.AppSystem.get_default();
+ },
+
+ getResultMeta: function(resultId) {
+ let app = this._appSys.get_app(resultId);
+ if (!app)
+ return null;
+ return { 'id': resultId,
+ 'name': app.get_name(),
+ 'icon': app.create_icon_texture(Search.RESULT_ICON_SIZE)};
+ },
+
+ activateResult: function(id) {
+ let app = this._appSys.get_app(id);
+ app.launch();
+ }
+};
+
+function AppSearchProvider() {
+ this._init();
+}
+
+AppSearchProvider.prototype = {
+ __proto__: BaseAppSearchProvider.prototype,
+
+ _init: function() {
+ BaseAppSearchProvider.prototype._init.call(this, _("APPLICATIONS"));
+ },
+
+ getInitialResultSet: function(terms) {
+ return this._appSys.initial_search(false, terms);
+ },
+
+ getSubsearchResultSet: function(previousResults, terms) {
+ return this._appSys.subsearch(false, previousResults, terms);
+ },
+
+ expandSearch: function(terms) {
+ log("TODO expand search");
+ }
+}
+
+function PrefsSearchProvider() {
+ this._init();
+}
+
+PrefsSearchProvider.prototype = {
+ __proto__: BaseAppSearchProvider.prototype,
+
+ _init: function() {
+ BaseAppSearchProvider.prototype._init.call(this, _("PREFERENCES"));
+ },
+
+ getInitialResultSet: function(terms) {
+ return this._appSys.initial_search(true, terms);
+ },
+
+ getSubsearchResultSet: function(previousResults, terms) {
+ return this._appSys.subsearch(true, previousResults, terms);
+ },
+
+ expandSearch: function(terms) {
+ let controlCenter = this._appSys.load_from_desktop_file('gnomecc.desktop');
+ controlCenter.launch();
+ Main.overview.hide();
+ }
+}
+
+function AppIcon(app) {
+ this._init(app);
+}
+
+AppIcon.prototype = {
+ _init : function(app) {
this.app = app;
this._glowExtendVertical = 0;
this._glowShrinkHorizontal = 0;
- this.actor = new St.Clickable({ style_class: 'app-well-app',
- reactive: true });
+ this.actor = new St.Bin({ style_class: 'app-icon',
+ x_fill: true,
+ y_fill: true });
this.actor._delegate = this;
this.actor.connect('destroy', Lang.bind(this, this._onDestroy));
- this.actor.connect('notify::mapped', Lang.bind(this, this._onMapped));
+ this._workId = Main.initializeDeferredWork(this.actor, Lang.bind(this, this._rerenderGlow));
let box = new St.BoxLayout({ vertical: true });
this.actor.set_child(box);
- this.actor.connect('clicked', Lang.bind(this, this._onClicked));
-
- this._menu = null;
-
this.icon = this.app.create_icon_texture(APPICON_SIZE);
box.add(this.icon, { expand: true, x_fill: false, y_fill: false });
@@ -262,17 +329,9 @@ BaseWellItem.prototype = {
this._glowBox.connect('style-changed', Lang.bind(this, this._onStyleChanged));
this._nameBox.add_actor(this._glowBox);
this._glowBox.lower(this._name);
- this._appWindowChangedId = this.app.connect('windows-changed', Lang.bind(this, this._rerenderGlow));
- this._rerenderGlow();
+ this._appWindowChangedId = this.app.connect('windows-changed', Lang.bind(this, this._queueRerenderGlow));
box.add(nameBox);
-
- this._draggable = DND.makeDraggable(this.actor, true);
- this._dragStartX = null;
- this._dragStartY = null;
-
- this.actor.connect('button-press-event', Lang.bind(this, this._onButtonPress));
- this.actor.connect('notify::hover', Lang.bind(this, this._onHoverChange));
},
_nameBoxGetPreferredWidth: function (nameBox, forHeight, alloc) {
@@ -319,18 +378,24 @@ BaseWellItem.prototype = {
this.app.disconnect(this._appWindowChangedId);
},
- _onMapped: function() {
- if (!this._queuedGlowRerender)
- return;
- this._queuedGlowRerender = false;
- this._rerenderGlow();
+ _queueRerenderGlow: function() {
+ Main.queueDeferredWork(this._workId);
+ },
+
+ _onStyleChanged: function() {
+ let themeNode = this._glowBox.get_theme_node();
+
+ let success, len;
+ [success, len] = themeNode.get_length('-shell-glow-extend-vertical', false);
+ if (success)
+ this._glowExtendVertical = len;
+ [success, len] = themeNode.get_length('-shell-glow-shrink-horizontal', false);
+ if (success)
+ this._glowShrinkHorizontal = len;
+ this.actor.queue_relayout();
},
_rerenderGlow: function() {
- if (!this.actor.mapped) {
- this._queuedGlowRerender = true;
- return;
- }
this._glowBox.destroy_children();
let glowPath = GLib.filename_to_uri(global.imagedir + 'app-well-glow.png', '');
let windows = this.app.get_windows();
@@ -340,6 +405,34 @@ BaseWellItem.prototype = {
glow.keep_aspect_ratio = false;
this._glowBox.add(glow);
}
+ }
+}
+
+function AppWellIcon(app) {
+ this._init(app);
+}
+
+AppWellIcon.prototype = {
+ _init : function(app) {
+ this.app = app;
+ this.actor = new St.Clickable({ style_class: 'app-well-app',
+ reactive: true,
+ x_fill: true,
+ y_fill: true });
+ this.actor._delegate = this;
+
+ this._icon = new AppIcon(app);
+ this.actor.set_child(this._icon.actor);
+
+ this.actor.connect('clicked', Lang.bind(this, this._onClicked));
+ this._menu = null;
+
+ this._draggable = DND.makeDraggable(this.actor, true);
+ this._dragStartX = null;
+ this._dragStartY = null;
+
+ this.actor.connect('button-press-event', Lang.bind(this, this._onButtonPress));
+ this.actor.connect('notify::hover', Lang.bind(this, this._onHoverChange));
},
_onButtonPress: function(actor, event) {
@@ -354,7 +447,7 @@ BaseWellItem.prototype = {
if (this.actor.pressed && this._dragStartX != null) {
this.actor.fake_release();
this._draggable.startDrag(this._dragStartX, this._dragStartY,
- Main.currentTime());
+ global.get_current_time());
} else {
this._dragStartX = null;
this._dragStartY = null;
@@ -374,19 +467,6 @@ BaseWellItem.prototype = {
return false;
},
- _onStyleChanged: function() {
- let themeNode = this._glowBox.get_theme_node();
-
- let success, len;
- [success, len] = themeNode.get_length('-shell-glow-extend-vertical', false);
- if (success)
- this._glowExtendVertical = len;
- [success, len] = themeNode.get_length('-shell-glow-shrink-horizontal', false);
- if (success)
- this._glowShrinkHorizontal = len;
- this.actor.queue_relayout();
- },
-
popupMenu: function(activatingButton) {
if (!this._menu) {
this._menu = new AppIconMenu(this);
@@ -410,13 +490,60 @@ BaseWellItem.prototype = {
return false;
},
- // Default implementations; AppDisplay.RunningWellItem overrides these
- highlightWindow: function(window) {
- this.emit('highlight-window', window);
+ activateMostRecentWindow: function () {
+ let mostRecentWindow = this.app.get_windows()[0];
+ Main.overview.activateWindow(mostRecentWindow, global.get_current_time());
},
- activateWindow: function(window) {
- this.emit('activate-window', window);
+ highlightWindow: function(metaWindow) {
+ if (!this._getRunning())
+ return;
+ Main.overview.getWorkspacesForWindow(metaWindow).setHighlightWindow(metaWindow);
+ },
+
+ activateWindow: function(metaWindow) {
+ if (metaWindow) {
+ this._didActivateWindow = true;
+ Main.overview.activateWindow(metaWindow, global.get_current_time());
+ } else
+ Main.overview.hide();
+ },
+
+ _onMenuPoppedUp: function() {
+ if (this._getRunning()) {
+ Main.overview.getWorkspacesForWindow(null).setApplicationWindowSelection(this.app.get_id());
+ this._setWindowSelection = true;
+ }
+ },
+
+ _onMenuPoppedDown: function() {
+ if (this._didActivateWindow)
+ return;
+ if (!this._setWindowSelection)
+ return;
+
+ Main.overview.getWorkspacesForWindow(null).setApplicationWindowSelection(null);
+ this._setWindowSelection = false;
+ },
+
+ _getRunning: function() {
+ return this.app.get_windows().length > 0;
+ },
+
+ _onActivate: function (event) {
+ let running = this._getRunning();
+
+ if (!running) {
+ this.app.launch();
+ } else {
+ let modifiers = Shell.get_event_state(event);
+
+ if (modifiers & Clutter.ModifierType.CONTROL_MASK) {
+ this.app.launch();
+ } else {
+ this.activateMostRecentWindow();
+ }
+ }
},
shellWorkspaceLaunch : function() {
@@ -441,7 +568,7 @@ BaseWellItem.prototype = {
return this.actor;
}
}
-Signals.addSignalMethods(BaseWellItem.prototype);
+Signals.addSignalMethods(AppWellIcon.prototype);
function AppIconMenu(source) {
this._init(source);
@@ -607,7 +734,7 @@ AppIconMenu.prototype = {
this._redisplay();
- this._windowContainer.popup(activatingButton, Main.currentTime());
+ this._windowContainer.popup(activatingButton, global.get_current_time());
this.emit('popup', true);
@@ -741,80 +868,6 @@ AppIconMenu.prototype = {
};
Signals.addSignalMethods(AppIconMenu.prototype);
-function RunningWellItem(app, isFavorite) {
- this._init(app, isFavorite);
-}
-
-RunningWellItem.prototype = {
- __proto__: BaseWellItem.prototype,
-
- _init: function(app, isFavorite) {
- BaseWellItem.prototype._init.call(this, app, isFavorite);
- },
-
- _onActivate: function (event) {
- let modifiers = Shell.get_event_state(event);
-
- if (modifiers & Clutter.ModifierType.CONTROL_MASK) {
- this.app.launch();
- } else {
- this.activateMostRecentWindow();
- }
- },
-
- activateMostRecentWindow: function () {
- let mostRecentWindow = this.app.get_windows()[0];
- Main.overview.activateWindow(mostRecentWindow, Main.currentTime());
- },
-
- highlightWindow: function(metaWindow) {
- Main.overview.getWorkspacesForWindow(metaWindow).setHighlightWindow(metaWindow);
- },
-
- activateWindow: function(metaWindow) {
- if (metaWindow) {
- this._didActivateWindow = true;
- Main.overview.activateWindow(metaWindow, Main.currentTime());
- } else
- Main.overview.hide();
- },
-
- _onMenuPoppedUp: function() {
- Main.overview.getWorkspacesForWindow(null).setApplicationWindowSelection(this.app.get_id());
- },
-
- _onMenuPoppedDown: function() {
- if (this._didActivateWindow)
- return;
-
- Main.overview.getWorkspacesForWindow(null).setApplicationWindowSelection(null);
- }
-};
-
-function InactiveWellItem(app, isFavorite) {
- this._init(app, isFavorite);
-}
-
-InactiveWellItem.prototype = {
- __proto__: BaseWellItem.prototype,
-
- _init : function(app, isFavorite) {
- BaseWellItem.prototype._init.call(this, app, isFavorite);
- },
-
- _onActivate: function(event) {
- this.app.launch();
- Main.overview.hide();
- return true;
- },
-
- _onMenuPoppedUp: function() {
- },
-
- _onMenuPoppedDown: function() {
- }
-};
-
function WellGrid() {
this._init();
}
@@ -956,8 +1009,7 @@ AppWell.prototype = {
x_align: Big.BoxAlignment.CENTER });
this.actor._delegate = this;
- this._pendingRedisplay = false;
- this.actor.connect('notify::mapped', Lang.bind(this, this._onMappedNotify));
+ this._workId = Main.initializeDeferredWork(this.actor, Lang.bind(this, this._redisplay));
this._grid = new WellGrid();
this.actor.append(this._grid.actor, Big.BoxPackFlags.EXPAND);
@@ -965,12 +1017,9 @@ AppWell.prototype = {
this._tracker = Shell.WindowTracker.get_default();
this._appSystem = Shell.AppSystem.get_default();
- this._appSystem.connect('installed-changed', Lang.bind(this, this._redisplay));
-
- AppFavorites.getAppFavorites().connect('changed', Lang.bind(this, this._redisplay));
- this._tracker.connect('app-running-changed', Lang.bind(this, this._redisplay));
-
- this._redisplay();
+ this._appSystem.connect('installed-changed', Lang.bind(this, this._queueRedisplay));
+ AppFavorites.getAppFavorites().connect('changed', Lang.bind(this, this._queueRedisplay));
+ this._tracker.connect('app-running-changed', Lang.bind(this, this._queueRedisplay));
},
_appIdListToHash: function(apps) {
@@ -980,20 +1029,11 @@ AppWell.prototype = {
return ids;
},
- _onMappedNotify: function() {
- let mapped = this.actor.mapped;
- if (mapped && this._pendingRedisplay)
- this._redisplay();
+ _queueRedisplay: function () {
+ Main.queueDeferredWork(this._workId);
},
_redisplay: function () {
- let mapped = this.actor.mapped;
- if (!mapped) {
- this._pendingRedisplay = true;
- return;
- }
- this._pendingRedisplay = false;
-
this._grid.removeAll();
let favorites = AppFavorites.getAppFavorites().getFavoriteMap();
@@ -1007,12 +1047,7 @@ AppWell.prototype = {
let nFavorites = 0;
for (let id in favorites) {
let app = favorites[id];
- let display;
- if (app.get_windows().length > 0) {
- display = new RunningWellItem(app, true);
- } else {
- display = new InactiveWellItem(app, true);
- }
+ let display = new AppWellIcon(app);
this._grid.addItem(display.actor);
nFavorites++;
}
@@ -1021,7 +1056,7 @@ AppWell.prototype = {
let app = running[i];
if (app.get_id() in favorites)
continue;
- let display = new RunningWellItem(app, false);
+ let display = new AppWellIcon(app);
this._grid.addItem(display.actor);
}
diff --git a/js/ui/appIcon.js b/js/ui/appIcon.js
index 5202a1da6..27182fede 100644
--- a/js/ui/appIcon.js
+++ b/js/ui/appIcon.js
@@ -472,7 +472,7 @@ AppIconMenu.prototype = {
this._redisplay();
- this._windowContainer.popup(activatingButton, Main.currentTime());
+ this._windowContainer.popup(activatingButton, global.get_current_time());
this.emit('popup', true);
diff --git a/js/ui/chrome.js b/js/ui/chrome.js
index daa4c4dd1..2e57456cf 100644
--- a/js/ui/chrome.js
+++ b/js/ui/chrome.js
@@ -191,6 +191,7 @@ Chrome.prototype = {
_windowsRestacked: function() {
let windows = global.get_windows();
+ let primary = global.get_primary_monitor();
// The chrome layer should be visible unless there is a window
// with layer FULLSCREEN, or a window with layer
@@ -208,17 +209,15 @@ Chrome.prototype = {
for (let i = windows.length - 1; i > -1; i--) {
let layer = windows[i].get_meta_window().get_layer();
- if (layer == Meta.StackLayer.OVERRIDE_REDIRECT) {
- if (windows[i].x <= 0 &&
- windows[i].x + windows[i].width >= global.screen_width &&
- windows[i].y <= 0 &&
- windows[i].y + windows[i].height >= global.screen_height) {
+ if (layer == Meta.StackLayer.OVERRIDE_REDIRECT ||
+ layer == Meta.StackLayer.FULLSCREEN) {
+ if (windows[i].x <= primary.x &&
+ windows[i].x + windows[i].width >= primary.x + primary.width &&
+ windows[i].y <= primary.y &&
+ windows[i].y + windows[i].height >= primary.y + primary.height) {
this._obscuredByFullscreen = true;
break;
}
- } else if (layer == Meta.StackLayer.FULLSCREEN) {
- this._obscuredByFullscreen = true;
- break;
} else
break;
}
diff --git a/js/ui/dash.js b/js/ui/dash.js
index 1fc2184d6..e50c8538c 100644
--- a/js/ui/dash.js
+++ b/js/ui/dash.js
@@ -17,6 +17,10 @@ const DocDisplay = imports.ui.docDisplay;
const PlaceDisplay = imports.ui.placeDisplay;
const GenericDisplay = imports.ui.genericDisplay;
const Main = imports.ui.main;
+const Search = imports.ui.search;
+
+// 25 search results (per result type) should be enough for everyone
+const MAX_RENDERED_SEARCH_RESULTS = 25;
const DEFAULT_PADDING = 4;
const DEFAULT_SPACING = 4;
@@ -332,6 +336,254 @@ SearchEntry.prototype = {
};
Signals.addSignalMethods(SearchEntry.prototype);
+function SearchResult(provider, metaInfo, terms) {
+ this._init(provider, metaInfo, terms);
+}
+
+SearchResult.prototype = {
+ _init: function(provider, metaInfo, terms) {
+ this.provider = provider;
+ this.metaInfo = metaInfo;
+ this.actor = new St.Clickable({ style_class: 'dash-search-result',
+ reactive: true,
+ x_align: St.Align.START,
+ x_fill: true,
+ y_fill: true });
+ this.actor._delegate = this;
+
+ let content = provider.createResultActor(metaInfo, terms);
+ if (content == null) {
+ content = new St.BoxLayout({ style_class: 'dash-search-result-content' });
+ let title = new St.Label({ text: this.metaInfo['name'] });
+ let icon = this.metaInfo['icon'];
+ content.add(icon, { y_fill: false });
+ content.add(title, { expand: true, y_fill: false });
+ }
+ this._content = content;
+ this.actor.set_child(content);
+
+ this.actor.connect('clicked', Lang.bind(this, this._onResultClicked));
+ },
+
+ setSelected: function(selected) {
+ this._content.set_style_pseudo_class(selected ? 'selected' : null);
+ },
+
+ activate: function() {
+ this.provider.activateResult(this.metaInfo.id);
+ Main.overview.toggle();
+ },
+
+ _onResultClicked: function(actor, event) {
+ this.activate();
+ }
+}
+
+function OverflowSearchResults(provider) {
+ this._init(provider);
+}
+
+OverflowSearchResults.prototype = {
+ __proto__: Search.SearchResultDisplay.prototype,
+
+ _init: function(provider) {
+ Search.SearchResultDisplay.prototype._init.call(this, provider);
+ this.actor = new St.OverflowBox({ style_class: 'dash-search-section-list-results' });
+ },
+
+ renderResults: function(results, terms) {
+ for (let i = 0; i < results.length && i < MAX_RENDERED_SEARCH_RESULTS; i++) {
+ let result = results[i];
+ let meta = this.provider.getResultMeta(result);
+ let display = new SearchResult(this.provider, meta, terms);
+ this.actor.add_actor(display.actor);
+ }
+ },
+
+ getVisibleCount: function() {
+ return this.actor.get_n_visible();
+ },
+
+ selectIndex: function(index) {
+ let nVisible = this.actor.get_n_visible();
+ let children = this.actor.get_children();
+ if (this.selectionIndex >= 0) {
+ let prevActor = children[this.selectionIndex];
+ prevActor._delegate.setSelected(false);
+ }
+ this.selectionIndex = -1;
+ if (index >= nVisible)
+ return false;
+ else if (index < 0)
+ return false;
+ let targetActor = children[index];
+ targetActor._delegate.setSelected(true);
+ this.selectionIndex = index;
+ return true;
+ }
+}
+
+function SearchResults(searchSystem) {
+ this._init(searchSystem);
+}
+
+SearchResults.prototype = {
+ _init: function(searchSystem) {
+ this._searchSystem = searchSystem;
+
+ this.actor = new St.BoxLayout({ name: 'dashSearchResults',
+ vertical: true });
+ this._searchingNotice = new St.Label({ style_class: 'dash-search-starting',
+ text: _("Searching...") });
+ this.actor.add(this._searchingNotice);
+ this._selectedProvider = -1;
+ this._providers = this._searchSystem.getProviders();
+ this._providerMeta = [];
+ for (let i = 0; i < this._providers.length; i++) {
+ let provider = this._providers[i];
+ let providerBox = new St.BoxLayout({ style_class: 'dash-search-section',
+ vertical: true });
+ let titleButton = new St.Button({ style_class: 'dash-search-section-header',
+ reactive: true,
+ x_fill: true,
+ y_fill: true });
+ titleButton.connect('clicked', Lang.bind(this, function () { this._onHeaderClicked(provider); }));
+ providerBox.add(titleButton);
+ let titleBox = new St.BoxLayout();
+ titleButton.set_child(titleBox);
+ let title = new St.Label({ text: provider.title });
+ let count = new St.Label();
+ titleBox.add(title, { expand: true });
+ titleBox.add(count);
+
+ let resultDisplayBin = new St.Bin({ style_class: 'dash-search-section-results',
+ x_fill: true,
+ y_fill: true });
+ providerBox.add(resultDisplayBin, { expand: true });
+ let resultDisplay = provider.createResultContainerActor();
+ if (resultDisplay == null) {
+ resultDisplay = new OverflowSearchResults(provider);
+ }
+ resultDisplayBin.set_child(resultDisplay.actor);
+
+ this._providerMeta.push({ actor: providerBox,
+ resultDisplay: resultDisplay,
+ count: count });
+ this.actor.add(providerBox);
+ }
+ },
+
+ _clearDisplay: function() {
+ this._selectedProvider = -1;
+ this._visibleResultsCount = 0;
+ for (let i = 0; i < this._providerMeta.length; i++) {
+ let meta = this._providerMeta[i];
+ meta.resultDisplay.clear();
+ meta.actor.hide();
+ }
+ },
+
+ reset: function() {
+ this._searchSystem.reset();
+ this._searchingNotice.hide();
+ this._clearDisplay();
+ },
+
+ startingSearch: function() {
+ this.reset();
+ this._searchingNotice.show();
+ },
+
+ _metaForProvider: function(provider) {
+ return this._providerMeta[this._providers.indexOf(provider)];
+ },
+
+ updateSearch: function (searchString) {
+ let results = this._searchSystem.updateSearch(searchString);
+
+ this._searchingNotice.hide();
+ this._clearDisplay();
+
+ let terms = this._searchSystem.getTerms();
+
+ for (let i = 0; i < results.length; i++) {
+ let [provider, providerResults] = results[i];
+ let meta = this._metaForProvider(provider);
+ meta.actor.show();
+ meta.resultDisplay.renderResults(providerResults, terms);
+ meta.count.set_text(""+providerResults.length);
+ }
+
+ this.selectDown(false);
+
+ return true;
+ },
+
+ _onHeaderClicked: function(provider) {
+ provider.expandSearch(this._searchSystem.getTerms());
+ },
+
+ _modifyActorSelection: function(resultDisplay, up) {
+ let success;
+ let index = resultDisplay.getSelectionIndex();
+ if (up && index == -1)
+ index = resultDisplay.getVisibleCount() - 1;
+ else if (up)
+ index = index - 1;
+ else
+ index = index + 1;
+ return resultDisplay.selectIndex(index);
+ },
+
+ selectUp: function(recursing) {
+ for (let i = this._selectedProvider; i >= 0; i--) {
+ let meta = this._providerMeta[i];
+ if (!meta.actor.visible)
+ continue;
+ let success = this._modifyActorSelection(meta.resultDisplay, true);
+ if (success) {
+ this._selectedProvider = i;
+ return;
+ }
+ }
+ if (this._providerMeta.length > 0 && !recursing) {
+ this._selectedProvider = this._providerMeta.length - 1;
+ this.selectUp(true);
+ }
+ },
+
+ selectDown: function(recursing) {
+ let current = this._selectedProvider;
+ if (current == -1)
+ current = 0;
+ for (let i = current; i < this._providerMeta.length; i++) {
+ let meta = this._providerMeta[i];
+ if (!meta.actor.visible)
+ continue;
+ let success = this._modifyActorSelection(meta.resultDisplay, false);
+ if (success) {
+ this._selectedProvider = i;
+ return;
+ }
+ }
+ if (this._providerMeta.length > 0 && !recursing) {
+ this._selectedProvider = 0;
+ this.selectDown(true);
+ }
+ },
+
+ activateSelected: function() {
+ let current = this._selectedProvider;
+ if (current < 0)
+ return;
+ let meta = this._providerMeta[current];
+ let resultDisplay = meta.resultDisplay;
+ let children = resultDisplay.actor.get_children();
+ let targetActor = children[resultDisplay.getSelectionIndex()];
+ targetActor._delegate.activate();
+ }
+}
+
function MoreLink() {
this._init();
}
@@ -344,15 +596,15 @@ MoreLink.prototype = {
let expander = new St.Bin({ style_class: "more-link-expander" });
this.actor.add(expander, { expand: true, y_fill: false });
+ },
- this.actor.connect('button-press-event', Lang.bind(this, function (b, e) {
- if (this.pane == null) {
- // Ensure the pane is created; the activated handler will call setPane
- this.emit('activated');
- }
- this._pane.toggle();
- return true;
- }));
+ activate: function() {
+ if (this.pane == null) {
+ // Ensure the pane is created; the activated handler will call setPane
+ this.emit('activated');
+ }
+ this._pane.toggle();
+ return true;
},
setPane: function (pane) {
@@ -385,7 +637,8 @@ SectionHeader.prototype = {
this.actor = new St.Bin({ style_class: "section-header",
x_align: St.Align.START,
x_fill: true,
- y_fill: true });
+ y_fill: true,
+ reactive: !suppressBrowse });
this._innerBox = new St.BoxLayout({ style_class: "section-header-inner" });
this.actor.set_child(this._innerBox);
@@ -410,9 +663,14 @@ SectionHeader.prototype = {
if (!suppressBrowse) {
this.moreLink = new MoreLink();
this._innerBox.add(this.moreLink.actor, { x_align: St.Align.END });
+ this.actor.connect('button-press-event', Lang.bind(this, this._onButtonPress));
}
},
+ _onButtonPress: function() {
+ this.moreLink.activate();
+ },
+
setTitle : function(title) {
this.text.text = title;
},
@@ -500,9 +758,9 @@ Dash.prototype = {
vertical: true,
reactive: true });
- // Size for this one explicitly set from overlay.js
- this.searchArea = new Big.Box({ y_align: Big.BoxAlignment.CENTER });
-
+ // The searchArea just holds the entry
+ this.searchArea = new St.BoxLayout({ name: "dashSearchArea",
+ vertical: true });
this.sectionArea = new St.BoxLayout({ name: "dashSections",
vertical: true });
@@ -517,16 +775,35 @@ Dash.prototype = {
this._searchActive = false;
this._searchPending = false;
this._searchEntry = new SearchEntry();
- this.searchArea.append(this._searchEntry.actor, Big.BoxPackFlags.EXPAND);
+ this.searchArea.add(this._searchEntry.actor, { y_fill: false, expand: true });
+
+ this._searchSystem = new Search.SearchSystem();
+ this._searchSystem.registerProvider(new AppDisplay.AppSearchProvider());
+ this._searchSystem.registerProvider(new AppDisplay.PrefsSearchProvider());
+ this._searchSystem.registerProvider(new PlaceDisplay.PlaceSearchProvider());
+ this._searchSystem.registerProvider(new DocDisplay.DocSearchProvider());
+
+ this.searchResults = new SearchResults(this._searchSystem);
+ this.actor.add(this.searchResults.actor);
+ this.searchResults.actor.hide();
this._searchTimeoutId = 0;
this._searchEntry.entry.connect('text-changed', Lang.bind(this, function (se, prop) {
let text = this._searchEntry.getText();
- text = text.replace(/^\s+/g, "").replace(/\s+$/g, "")
+ text = text.replace(/^\s+/g, "").replace(/\s+$/g, "");
let searchPreviouslyActive = this._searchActive;
this._searchActive = text != '';
this._searchPending = this._searchActive && !searchPreviouslyActive;
- this._updateDashActors();
+ if (this._searchPending) {
+ this.searchResults.startingSearch();
+ }
+ if (this._searchActive) {
+ this.searchResults.actor.show();
+ this.sectionArea.hide();
+ } else {
+ this.searchResults.actor.hide();
+ this.sectionArea.show();
+ }
if (!this._searchActive) {
if (this._searchTimeoutId > 0) {
Mainloop.source_remove(this._searchTimeoutId);
@@ -543,24 +820,15 @@ Dash.prototype = {
Mainloop.source_remove(this._searchTimeoutId);
this._doSearch();
}
- // Only one of the displays will have an item selected, so it's ok to
- // call activateSelected() on all of them.
- for (var i = 0; i < this._searchSections.length; i++) {
- let section = this._searchSections[i];
- section.resultArea.display.activateSelected();
- }
+ this.searchResults.activateSelected();
return true;
}));
this._searchEntry.entry.connect('key-press-event', Lang.bind(this, function (se, e) {
- let text = this._searchEntry.getText();
let symbol = e.get_key_symbol();
if (symbol == Clutter.Escape) {
// Escape will keep clearing things back to the desktop.
- // If we are showing a particular section of search, go back to all sections.
- if (this._searchResultsSingleShownSection != null)
- this._showAllSearchSections();
// If we have an active search, we remove it.
- else if (this._searchActive)
+ if (this._searchActive)
this._searchEntry.reset();
// Next, if we're in one of the "more" modes or showing the details pane, close them
else if (this._activePane != null)
@@ -572,44 +840,14 @@ Dash.prototype = {
} else if (symbol == Clutter.Up) {
if (!this._searchActive)
return true;
- // selectUp and selectDown wrap around in their respective displays
- // too, but there doesn't seem to be any flickering if we first select
- // something in one display, but then unset the selection, and move
- // it to the other display, so it's ok to do that.
- for (var i = 0; i < this._searchSections.length; i++) {
- let section = this._searchSections[i];
- if (section.resultArea.display.hasSelected() && !section.resultArea.display.selectUp()) {
- if (this._searchResultsSingleShownSection != section.type) {
- // We need to move the selection to the next section above this section that has items,
- // wrapping around at the bottom, if necessary.
- let newSectionIndex = this._findAnotherSectionWithItems(i, -1);
- if (newSectionIndex >= 0) {
- this._searchSections[newSectionIndex].resultArea.display.selectLastItem();
- section.resultArea.display.unsetSelected();
- }
- }
- break;
- }
- }
+ this.searchResults.selectUp(false);
+
return true;
} else if (symbol == Clutter.Down) {
if (!this._searchActive)
return true;
- for (var i = 0; i < this._searchSections.length; i++) {
- let section = this._searchSections[i];
- if (section.resultArea.display.hasSelected() && !section.resultArea.display.selectDown()) {
- if (this._searchResultsSingleShownSection != section.type) {
- // We need to move the selection to the next section below this section that has items,
- // wrapping around at the top, if necessary.
- let newSectionIndex = this._findAnotherSectionWithItems(i, 1);
- if (newSectionIndex >= 0) {
- this._searchSections[newSectionIndex].resultArea.display.selectFirstItem();
- section.resultArea.display.unsetSelected();
- }
- }
- break;
- }
- }
+
+ this.searchResults.selectDown(false);
return true;
}
return false;
@@ -666,102 +904,12 @@ Dash.prototype = {
this._docDisplay.emit('changed');
this.sectionArea.add(this._docsSection.actor, { expand: true });
-
- /***** Search Results *****/
-
- this._searchResultsSection = new Section(_("SEARCH RESULTS"), true);
-
- this._searchResultsSingleShownSection = null;
-
- this._searchResultsSection.header.connect('back-link-activated', Lang.bind(this, function () {
- this._showAllSearchSections();
- }));
-
- this._searchSections = [
- { type: APPS,
- title: _("APPLICATIONS"),
- header: null,
- resultArea: null
- },
- { type: PREFS,
- title: _("PREFERENCES"),
- header: null,
- resultArea: null
- },
- { type: DOCS,
- title: _("RECENT DOCUMENTS"),
- header: null,
- resultArea: null
- },
- { type: PLACES,
- title: _("PLACES"),
- header: null,
- resultArea: null
- }
- ];
-
- for (var i = 0; i < this._searchSections.length; i++) {
- let section = this._searchSections[i];
- section.header = new SearchSectionHeader(section.title,
- Lang.bind(this,
- function () {
- this._showSingleSearchSection(section.type);
- }));
- this._searchResultsSection.content.add(section.header.actor);
- section.resultArea = new ResultArea(section.type, GenericDisplay.GenericDisplayFlags.DISABLE_VSCROLLING);
- this._searchResultsSection.content.add(section.resultArea.actor, { expand: true });
- createPaneForDetails(this, section.resultArea.display);
- }
-
- this.sectionArea.add(this._searchResultsSection.actor, { expand: true });
- this._searchResultsSection.actor.hide();
},
_doSearch: function () {
this._searchTimeoutId = 0;
let text = this._searchEntry.getText();
- text = text.replace(/^\s+/g, "").replace(/\s+$/g, "");
-
- let selectionSet = false;
-
- for (var i = 0; i < this._searchSections.length; i++) {
- let section = this._searchSections[i];
- section.resultArea.display.setSearch(text);
- let itemCount = section.resultArea.display.getMatchedItemsCount();
- let itemCountText = itemCount + "";
- section.header.countText.text = itemCountText;
-
- if (this._searchResultsSingleShownSection == section.type) {
- this._searchResultsSection.header.setCountText(itemCountText);
- if (itemCount == 0) {
- section.resultArea.actor.hide();
- } else {
- section.resultArea.actor.show();
- }
- } else if (this._searchResultsSingleShownSection == null) {
- // Don't show the section if it has no results
- if (itemCount == 0) {
- section.header.actor.hide();
- section.resultArea.actor.hide();
- } else {
- section.header.actor.show();
- section.resultArea.actor.show();
- }
- }
-
- // Refresh the selection when a new search is applied.
- section.resultArea.display.unsetSelected();
- if (!selectionSet && section.resultArea.display.hasItems() &&
- (this._searchResultsSingleShownSection == null || this._searchResultsSingleShownSection == section.type)) {
- section.resultArea.display.selectFirstItem();
- selectionSet = true;
- }
- }
-
- // Here work around a bug that I never quite tracked down
- // the root cause of; it appeared that the search results
- // section was getting a 0 height allocation.
- this._searchResultsSection.content.queue_relayout();
+ this.searchResults.updateSearch(text);
return false;
},
@@ -794,101 +942,6 @@ Dash.prototype = {
}
}));
Main.overview.addPane(pane);
- },
-
- _updateDashActors: function() {
- if (this._searchPending) {
- this._searchResultsSection.actor.show();
- // We initially hide all sections when we start a search. When the search timeout
- // first runs, the sections that have matching results are shown. As the search
- // is refined, only the sections that have matching results will be shown.
- for (let i = 0; i < this._searchSections.length; i++) {
- let section = this._searchSections[i];
- section.header.actor.hide();
- section.resultArea.actor.hide();
- }
- this._appsSection.actor.hide();
- this._placesSection.actor.hide();
- this._docsSection.actor.hide();
- } else if (!this._searchActive) {
- this._showAllSearchSections();
- this._searchResultsSection.actor.hide();
- this._appsSection.actor.show();
- this._placesSection.actor.show();
- this._docsSection.actor.show();
- }
- },
-
- _showSingleSearchSection: function(type) {
- // We currently don't allow going from showing one section to showing another section.
- if (this._searchResultsSingleShownSection != null) {
- throw new Error("We were already showing a single search section: '" + this._searchResultsSingleShownSection
- + "' when _showSingleSearchSection() was called for '" + type + "'");
- }
- for (var i = 0; i < this._searchSections.length; i++) {
- let section = this._searchSections[i];
- if (section.type == type) {
- // This will be the only section shown.
- section.resultArea.display.selectFirstItem();
- let itemCount = section.resultArea.display.getMatchedItemsCount();
- let itemCountText = itemCount + "";
- section.header.actor.hide();
- this._searchResultsSection.header.setTitle(section.title);
- this._searchResultsSection.header.setBackLinkVisible(true);
- this._searchResultsSection.header.setCountText(itemCountText);
- } else {
- // We need to hide this section.
- section.header.actor.hide();
- section.resultArea.actor.hide();
- section.resultArea.display.unsetSelected();
- }
- }
- this._searchResultsSingleShownSection = type;
- },
-
- _showAllSearchSections: function() {
- if (this._searchResultsSingleShownSection != null) {
- let selectionSet = false;
- for (var i = 0; i < this._searchSections.length; i++) {
- let section = this._searchSections[i];
- if (section.type == this._searchResultsSingleShownSection) {
- // This will no longer be the only section shown.
- let itemCount = section.resultArea.display.getMatchedItemsCount();
- if (itemCount != 0) {
- section.header.actor.show();
- section.resultArea.display.selectFirstItem();
- selectionSet = true;
- }
- this._searchResultsSection.header.setTitle(_("SEARCH RESULTS"));
- this._searchResultsSection.header.setBackLinkVisible(false);
- this._searchResultsSection.header.setCountText("");
- } else {
- // We need to restore this section.
- let itemCount = section.resultArea.display.getMatchedItemsCount();
- if (itemCount != 0) {
- section.header.actor.show();
- section.resultArea.actor.show();
- // This ensures that some other section will have the selection if the
- // single section that was being displayed did not have any items.
- if (!selectionSet) {
- section.resultArea.display.selectFirstItem();
- selectionSet = true;
- }
- }
- }
- }
- this._searchResultsSingleShownSection = null;
- }
- },
-
- _findAnotherSectionWithItems: function(index, increment) {
- let pos = _getIndexWrapped(index, increment, this._searchSections.length);
- while (pos != index) {
- if (this._searchSections[pos].resultArea.display.hasItems())
- return pos;
- pos = _getIndexWrapped(pos, increment, this._searchSections.length);
- }
- return -1;
}
};
Signals.addSignalMethods(Dash.prototype);
diff --git a/js/ui/docDisplay.js b/js/ui/docDisplay.js
index 2b67f50ba..ee4fa7ee5 100644
--- a/js/ui/docDisplay.js
+++ b/js/ui/docDisplay.js
@@ -10,12 +10,16 @@ const Shell = imports.gi.Shell;
const Signals = imports.signals;
const St = imports.gi.St;
const Mainloop = imports.mainloop;
+const Gettext = imports.gettext.domain('gnome-shell');
+const _ = Gettext.gettext;
const DocInfo = imports.misc.docInfo;
const DND = imports.ui.dnd;
const GenericDisplay = imports.ui.genericDisplay;
const Main = imports.ui.main;
+const Search = imports.ui.search;
+const MAX_DASH_DOCS = 50;
const DASH_DOCS_ICON_SIZE = 16;
const DEFAULT_SPACING = 4;
@@ -152,7 +156,8 @@ DocDisplay.prototype = {
_refreshCache : function() {
if (!this._docsStale)
return true;
- this._allItems = this._docManager.getItems();
+ this._allItems = {};
+ Lang.copyProperties(this._docManager.getInfosByUri(), this._allItems);
this._docsStale = false;
return false;
},
@@ -177,13 +182,8 @@ DocDisplay.prototype = {
this._matchedItemKeys = [];
let docIdsToRemove = [];
for (docId in this._allItems) {
- // this._allItems[docId].exists() checks if the resource still exists
- if (this._allItems[docId].exists()) {
- this._matchedItems[docId] = 1;
- this._matchedItemKeys.push(docId);
- } else {
- docIdsToRemove.push(docId);
- }
+ this._matchedItems[docId] = 1;
+ this._matchedItemKeys.push(docId);
}
for (docId in docIdsToRemove) {
@@ -275,16 +275,21 @@ DashDocDisplayItem.prototype = {
Main.overview.hide();
}));
+ this.actor._delegate = this;
+
this._icon = docInfo.createIcon(DASH_DOCS_ICON_SIZE);
let iconBox = new Big.Box({ y_align: Big.BoxAlignment.CENTER });
iconBox.append(this._icon, Big.BoxPackFlags.NONE);
this.actor.append(iconBox, Big.BoxPackFlags.NONE);
- let name = new St.Label({ style_class: "dash-recent-docs-item",
+ let name = new St.Label({ style_class: 'dash-recent-docs-item',
text: docInfo.name });
this.actor.append(name, Big.BoxPackFlags.EXPAND);
let draggable = DND.makeDraggable(this.actor);
- this.actor._delegate = this;
+ },
+
+ getUri: function() {
+ return this._info.uri;
},
getDragActorSource: function() {
@@ -316,12 +321,14 @@ DashDocDisplay.prototype = {
this.actor.connect('get-preferred-width', Lang.bind(this, this._getPreferredWidth));
this.actor.connect('get-preferred-height', Lang.bind(this, this._getPreferredHeight));
this.actor.connect('allocate', Lang.bind(this, this._allocate));
+ this._workId = Main.initializeDeferredWork(this.actor, Lang.bind(this, this._redisplay));
+
+ this._actorsByUri = {};
this._docManager = DocInfo.getDocManager();
- this._docManager.connect('changed', Lang.bind(this, function(mgr) {
- this._redisplay();
- }));
- this._redisplay();
+ this._docManager.connect('changed', Lang.bind(this, this._onDocsChanged));
+ this._pendingDocsChange = true;
+ this._checkDocExistence = false;
},
_getPreferredWidth: function(actor, forHeight, alloc) {
@@ -355,15 +362,17 @@ DashDocDisplay.prototype = {
let firstColumnChildren = Math.ceil(children.length / 2);
+ let natural = 0;
for (let i = 0; i < firstColumnChildren; i++) {
let child = children[i];
- let [minSize, naturalSize] = child.get_preferred_height(forWidth);
- alloc.natural_size += naturalSize;
+ let [minSize, naturalSize] = child.get_preferred_height(-1);
+ natural += naturalSize;
if (i > 0 && i < children.length - 1) {
- alloc.natural_size += DEFAULT_SPACING;
+ natural += DEFAULT_SPACING;
}
}
+ alloc.natural_size = natural;
},
_allocate: function(actor, box, flags) {
@@ -418,28 +427,49 @@ DashDocDisplay.prototype = {
i++;
}
- // Everything else didn't fit, just hide it.
- for (; i < children.length; i++) {
- children[i].hide();
+ if (this._checkDocExistence) {
+ // Now we know how many docs we are displaying, queue a check to see if any of them
+ // have been deleted. If they are deleted, then we'll get a 'changed' signal; since
+ // we'll now be displaying items we weren't previously, we'll check again to see
+ // if they were deleted, and so forth and so on.
+ // TODO: We should change this to ask for as many as we can fit in the given space:
+ // https://bugzilla.gnome.org/show_bug.cgi?id=603522#c23
+ this._docManager.queueExistenceCheck(i);
+ this._checkDocExistence = false;
}
+
+ let skipPaint = [];
+ for (; i < children.length; i++)
+ this.actor.set_skip_paint(children[i], true);
+ },
+
+ _onDocsChanged: function() {
+ this._checkDocExistence = true;
+ Main.queueDeferredWork(this._workId);
},
_redisplay: function() {
+ // Should be kept alive by the _actorsByUri
this.actor.remove_all();
-
- let docs = this._docManager.getItems();
- let docUrls = [];
- for (let url in docs) {
- docUrls.push(url);
+ let docs = this._docManager.getTimestampOrderedInfos();
+ for (let i = 0; i < docs.length && i < MAX_DASH_DOCS; i++) {
+ let doc = docs[i];
+ let display = this._actorsByUri[doc.uri];
+ if (display) {
+ this.actor.add_actor(display.actor);
+ } else {
+ let display = new DashDocDisplayItem(doc);
+ this.actor.add_actor(display.actor);
+ this._actorsByUri[doc.uri] = display;
+ }
}
- docUrls.sort(function (urlA, urlB) { return docs[urlB].timestamp - docs[urlA].timestamp; });
- let textureCache = Shell.TextureCache.get_default();
-
- for (let i = 0; i < docUrls.length; i++) {
- let url = docUrls[i];
- let docInfo = docs[url];
- let display = new DashDocDisplayItem(docInfo);
- this.actor.add_actor(display.actor);
+ // Any unparented actors must have been deleted
+ for (let uri in this._actorsByUri) {
+ let display = this._actorsByUri[uri];
+ if (display.actor.get_parent() == null) {
+ display.actor.destroy();
+ delete this._actorsByUri[uri];
+ }
}
this.emit('changed');
}
@@ -447,3 +477,41 @@ DashDocDisplay.prototype = {
Signals.addSignalMethods(DashDocDisplay.prototype);
+function DocSearchProvider() {
+ this._init();
+}
+
+DocSearchProvider.prototype = {
+ __proto__: Search.SearchProvider.prototype,
+
+ _init: function(name) {
+ Search.SearchProvider.prototype._init.call(this, _("DOCUMENTS"));
+ this._docManager = DocInfo.getDocManager();
+ },
+
+ getResultMeta: function(resultId) {
+ let docInfo = this._docManager.lookupByUri(resultId);
+ if (!docInfo)
+ return null;
+ return { 'id': resultId,
+ 'name': docInfo.name,
+ 'icon': docInfo.createIcon(Search.RESULT_ICON_SIZE)};
+ },
+
+ activateResult: function(id) {
+ let docInfo = this._docManager.lookupByUri(id);
+ docInfo.launch();
+ },
+
+ getInitialResultSet: function(terms) {
+ return this._docManager.initialSearch(terms);
+ },
+
+ getSubsearchResultSet: function(previousResults, terms) {
+ return this._docManager.subsearch(previousResults, terms);
+ },
+
+ expandSearch: function(terms) {
+ log("TODO expand docs search");
+ }
+};
diff --git a/js/ui/extensionSystem.js b/js/ui/extensionSystem.js
new file mode 100644
index 000000000..6e28623a5
--- /dev/null
+++ b/js/ui/extensionSystem.js
@@ -0,0 +1,158 @@
+/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
+
+const GLib = imports.gi.GLib;
+const Gio = imports.gi.Gio;
+const St = imports.gi.St;
+const Shell = imports.gi.Shell;
+
+const ExtensionState = {
+ ENABLED: 1,
+ DISABLED: 2,
+ ERROR: 3,
+ OUT_OF_DATE: 4
+};
+
+const ExtensionType = {
+ SYSTEM: 1,
+ PER_USER: 2
+};
+
+// Maps uuid -> metadata object
+const extensionMeta = {};
+// Maps uuid -> importer object (extension directory tree)
+const extensions = {};
+// Array of uuids
+var disabledExtensions;
+// GFile for user extensions
+var userExtensionsDir = null;
+
+function loadExtension(dir, enabled, type) {
+ let info;
+ let baseErrorString = 'While loading extension from "' + dir.get_parse_name() + '": ';
+
+ let metadataFile = dir.get_child('metadata.json');
+ if (!metadataFile.query_exists(null)) {
+ global.logError(baseErrorString + 'Missing metadata.json');
+ return;
+ }
+
+ let [success, metadataContents, len, etag] = metadataFile.load_contents(null);
+ let meta;
+ try {
+ meta = JSON.parse(metadataContents);
+ } catch (e) {
+ global.logError(baseErrorString + 'Failed to parse metadata.json: ' + e);
+ return;
+ }
+ let requiredProperties = ['uuid', 'name', 'description'];
+ for (let i = 0; i < requiredProperties; i++) {
+ let prop = requiredProperties[i];
+ if (!meta[prop]) {
+ global.logError(baseErrorString + 'missing "' + prop + '" property in metadata.json');
+ return;
+ }
+ }
+ // Encourage people to add this
+ if (!meta['url']) {
+ global.log(baseErrorString + 'Warning: Missing "url" property in metadata.json');
+ }
+
+ let base = dir.get_basename();
+ if (base != meta.uuid) {
+ global.logError(baseErrorString + 'uuid "' + meta.uuid + '" from metadata.json does not match directory name "' + base + '"');
+ return;
+ }
+
+ extensionMeta[meta.uuid] = meta;
+ extensionMeta[meta.uuid].type = type;
+ extensionMeta[meta.uuid].path = dir.get_path();
+ if (!enabled) {
+ extensionMeta[meta.uuid].state = ExtensionState.DISABLED;
+ return;
+ }
+
+ // Default to error, we set success as the last step
+ extensionMeta[meta.uuid].state = ExtensionState.ERROR;
+
+ let extensionJs = dir.get_child('extension.js');
+ if (!extensionJs.query_exists(null)) {
+ global.logError(baseErrorString + 'Missing extension.js');
+ return;
+ }
+ let stylesheetPath = null;
+ let themeContext = St.ThemeContext.get_for_stage(global.stage);
+ let theme = themeContext.get_theme();
+ let stylesheetFile = dir.get_child('stylesheet.css');
+ if (stylesheetFile.query_exists(null)) {
+ try {
+ theme.load_stylesheet(stylesheetFile.get_path());
+ } catch (e) {
+ global.logError(baseErrorString + 'Stylesheet parse error: ' + e);
+ return;
+ }
+ }
+
+ let extensionModule;
+ try {
+ global.add_extension_importer('imports.ui.extensionSystem.extensions', meta.uuid, dir.get_path());
+ extensionModule = extensions[meta.uuid].extension;
+ } catch (e) {
+ if (stylesheetPath != null)
+ theme.unload_stylesheet(stylesheetPath);
+ global.logError(baseErrorString + e);
+ return;
+ }
+ if (!extensionModule.main) {
+ global.logError(baseErrorString + 'missing \'main\' function');
+ return;
+ }
+ try {
+ extensionModule.main();
+ } catch (e) {
+ if (stylesheetPath != null)
+ theme.unload_stylesheet(stylesheetPath);
+ global.logError(baseErrorString + 'Failed to evaluate main function:' + e);
+ return;
+ }
+ extensionMeta[meta.uuid].state = ExtensionState.ENABLED;
+ global.log('Loaded extension ' + meta.uuid);
+}
+
+function init() {
+ let userConfigPath = GLib.get_user_config_dir();
+ let userExtensionsPath = GLib.build_filenamev([userConfigPath, 'gnome-shell', 'extensions']);
+ userExtensionsDir = Gio.file_new_for_path(userExtensionsPath);
+ try {
+ userExtensionsDir.make_directory_with_parents(null);
+ } catch (e) {
+ global.logError(""+e);
+ }
+
+ disabledExtensions = Shell.GConf.get_default().get_string_list('disabled_extensions');
+}
+
+function _loadExtensionsIn(dir, type) {
+ let fileEnum = dir.enumerate_children('standard::*', Gio.FileQueryInfoFlags.NONE, null);
+ let file, info;
+ while ((info = fileEnum.next_file(null)) != null) {
+ let fileType = info.get_file_type();
+ if (fileType != Gio.FileType.DIRECTORY)
+ continue;
+ let name = info.get_name();
+ let enabled = disabledExtensions.indexOf(name) < 0;
+ let child = dir.get_child(name);
+ loadExtension(child, enabled, type);
+ }
+ fileEnum.close(null);
+}
+
+function loadExtensions() {
+ _loadExtensionsIn(userExtensionsDir, ExtensionType.PER_USER);
+ let systemDataDirs = GLib.get_system_data_dirs();
+ for (let i = 0; i < systemDataDirs.length; i++) {
+ let dirPath = systemDataDirs[i] + '/gnome-shell/extensions';
+ let dir = Gio.file_new_for_path(dirPath);
+ if (dir.query_exists(null))
+ _loadExtensionsIn(dir, ExtensionType.SYSTEM);
+ }
+}
diff --git a/js/ui/genericDisplay.js b/js/ui/genericDisplay.js
index 939d98936..aaa018906 100644
--- a/js/ui/genericDisplay.js
+++ b/js/ui/genericDisplay.js
@@ -14,7 +14,6 @@ const Shell = imports.gi.Shell;
const St = imports.gi.St;
const DND = imports.ui.dnd;
-const Link = imports.ui.link;
const Main = imports.ui.main;
const RedisplayFlags = { NONE: 0,
diff --git a/js/ui/link.js b/js/ui/link.js
index fb8ecd85f..414df3827 100644
--- a/js/ui/link.js
+++ b/js/ui/link.js
@@ -3,78 +3,21 @@
const Clutter = imports.gi.Clutter;
const Lang = imports.lang;
const Signals = imports.signals;
+const St = imports.gi.St;
-// Link is a clickable link. Right now it just handles properly capturing
-// press and release events and short-circuiting the button handling in
-// ClutterText, but more features like different colors for hover/pressed states
-// or a different mouse cursor could be implemented.
-//
-// The properties passed in are forwarded to the Clutter.Text() constructor,
-// so can include, 'text', 'font_name', etc.
function Link(props) {
this._init(props);
}
Link.prototype = {
_init : function(props) {
- let realProps = { reactive: true };
+ let realProps = { reactive: 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)
- Lang.copyProperties(props, realProps);
+ Lang.copyProperties(props, realProps);
- this.actor = new Clutter.Text(realProps);
- this.actor._delegate = this;
- this.actor.connect('button-press-event', Lang.bind(this, this._onButtonPress));
- this.actor.connect('button-release-event', Lang.bind(this, this._onButtonRelease));
- this.actor.connect('enter-event', Lang.bind(this, this._onEnter));
- this.actor.connect('leave-event', Lang.bind(this, this._onLeave));
-
- this._buttonDown = false;
- this._havePointer = false;
- },
-
- // Update the text of the link
- setText : function(text) {
- this.actor.text = text;
- },
-
- // We want to react on buttonDown, but if we override button-release-event for
- // ClutterText, but not button-press-event, we get a stuck grab. Tracking
- // buttonDown and doing the grab isn't really necessary, but doing it makes
- // the behavior perfectly correct if the user clicks on one actor, drags
- // to another and releases - that should not trigger either actor.
- _onButtonPress : function(actor, event) {
- this._buttonDown = true;
- this._havePointer = true; // Hack to work around poor enter/leave tracking in Clutter
- Clutter.grab_pointer(actor);
-
- return true;
- },
-
- _onButtonRelease : function(actor, event) {
- if (this._buttonDown) {
- this._buttonDown = false;
- Clutter.ungrab_pointer(actor);
-
- if (this._havePointer)
- this.emit('clicked');
- }
-
- return true;
- },
-
- _onEnter : function(actor, event) {
- if (event.get_source() == actor)
- this._havePointer = true;
-
- return false;
- },
-
- _onLeave : function(actor, event) {
- if (event.get_source() == actor)
- this._havePointer = false;
-
- return false;
+ this.actor = new St.Button(realProps);
}
};
diff --git a/js/ui/lookingGlass.js b/js/ui/lookingGlass.js
index afe99b864..783d3ef0f 100644
--- a/js/ui/lookingGlass.js
+++ b/js/ui/lookingGlass.js
@@ -9,7 +9,11 @@ const Shell = imports.gi.Shell;
const Signals = imports.signals;
const Lang = imports.lang;
const Mainloop = imports.mainloop;
+const Gettext = imports.gettext.domain('gnome-shell');
+const _ = Gettext.gettext;
+const ExtensionSystem = imports.ui.extensionSystem;
+const Link = imports.ui.link;
const Tweener = imports.ui.tweener;
const Main = imports.ui.main;
@@ -39,24 +43,21 @@ Notebook.prototype = {
_init: function() {
this.actor = new St.BoxLayout({ vertical: true });
- this.tabControls = new Big.Box({ orientation: Big.BoxOrientation.HORIZONTAL,
- spacing: 4, padding: 2 });
+ this.tabControls = new St.BoxLayout({ style_class: "labels" });
this._selectedIndex = -1;
this._tabs = [];
},
appendPage: function(name, child) {
- let labelOuterBox = new Big.Box({ padding: 2 });
- let labelBox = new St.BoxLayout({ reactive: true });
- labelOuterBox.append(labelBox, Big.BoxPackFlags.NONE);
- let label = new St.Label({ text: name });
- labelBox.connect('button-press-event', Lang.bind(this, function () {
+ let labelBox = new St.BoxLayout({ style_class: "notebook-tab" });
+ 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.append(labelOuterBox, Big.BoxPackFlags.NONE);
+ this.tabControls.add(labelBox);
let scrollview = new St.ScrollView({ x_fill: true, y_fill: true });
scrollview.get_hscroll_bar().hide();
@@ -64,6 +65,7 @@ Notebook.prototype = {
let tabData = { child: child,
labelBox: labelBox,
+ label: label,
scrollView: scrollview,
_scrollToBottom: false };
this._tabs.push(tabData);
@@ -82,8 +84,7 @@ Notebook.prototype = {
if (this._selectedIndex < 0)
return;
let tabData = this._tabs[this._selectedIndex];
- tabData.labelBox.padding = 2;
- tabData.labelBox.border = 0;
+ tabData.labelBox.set_style_pseudo_class(null);
tabData.scrollView.hide();
this._selectedIndex = -1;
},
@@ -97,8 +98,7 @@ Notebook.prototype = {
return;
}
let tabData = this._tabs[index];
- tabData.labelBox.padding = 1;
- tabData.labelBox.border = 1;
+ tabData.labelBox.set_style_pseudo_class('selected');
tabData.scrollView.show();
this._selectedIndex = index;
this.emit('selection', tabData.child);
@@ -308,6 +308,135 @@ Inspector.prototype = {
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);
+ 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 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._extensionsList = new St.BoxLayout({ vertical: true,
+ style_class: 'lg-extensions-list' });
+ this.actor.add(this._extensionsList);
+ this._loadExtensionList();
+ },
+
+ _loadExtensionList: function() {
+ let extensions = ExtensionSystem.extensionMeta;
+ let totalExtensions = 0;
+ for (let uuid in extensions) {
+ let extensionDisplay = this._createExtensionDisplay(extensions[uuid]);
+ this._extensionsList.add(extensionDisplay);
+ totalExtensions++;
+ }
+ if (totalExtensions == 0) {
+ this._extensionsList.add(this._noExtensions);
+ }
+ },
+
+ _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();
+ },
+
+ _stateToString: function(extensionState) {
+ switch (extensionState) {
+ case ExtensionSystem.ExtensionState.ENABLED:
+ return _("Enabled");
+ case ExtensionSystem.ExtensionState.DISABLED:
+ return _("Disabled");
+ case ExtensionSystem.ExtensionState.ERROR:
+ return _("Error");
+ case ExtensionSystem.ExtensionState.OUT_OF_DATE:
+ return _("Out of date");
+ }
+ 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 });
+ box.add(description, { expand: true });
+
+ let metaBox = new St.BoxLayout();
+ box.add(metaBox);
+ let stateString = this._stateToString(meta.state);
+ let state = new St.Label({ style_class: 'lg-extension-state',
+ text: this._stateToString(meta.state) });
+
+ let actionsContainer = new St.Bin({ x_align: St.Align.END });
+ metaBox.add(actionsContainer);
+ let actionsBox = new St.BoxLayout({ style_class: 'lg-extension-actions' });
+ actionsContainer.set_child(actionsBox);
+
+ let viewsource = new Link.Link({ label: _("View Source") });
+ viewsource.actor._extensionMeta = meta;
+ viewsource.actor.connect('clicked', Lang.bind(this, this._onViewSource));
+ actionsBox.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));
+ actionsBox.add(webpage.actor);
+ }
+
+ return box;
+ }
+};
+
function LookingGlass() {
this._init();
}
@@ -406,6 +535,12 @@ LookingGlass.prototype = {
notebook.selectIndex(0);
}));
+ this._errorLog = new ErrorLog();
+ notebook.appendPage('Errors', this._errorLog.actor);
+
+ this._extensions = new Extensions();
+ notebook.appendPage('Extensions', this._extensions.actor);
+
this._entry.clutter_text.connect('activate', Lang.bind(this, function (o, e) {
let text = o.get_text();
// Ensure we don't get newlines in the command; the history file is
@@ -421,10 +556,7 @@ LookingGlass.prototype = {
}));
this._entry.clutter_text.connect('key-press-event', Lang.bind(this, function(o, e) {
let symbol = e.get_key_symbol();
- if (symbol == Clutter.Escape) {
- this.close();
- return true;
- } else if (symbol == Clutter.Up) {
+ if (symbol == Clutter.Up) {
if (this._historyNavIndex >= this._history.length - 1)
return true;
this._historyNavIndex++;
@@ -569,6 +701,16 @@ LookingGlass.prototype = {
this._resizeTo(actor);
},
+ // 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();
+ return true;
+ }
+ return false;
+ },
+
open : function() {
if (this._open)
return;
@@ -576,6 +718,9 @@ LookingGlass.prototype = {
if (!Main.pushModal(this.actor))
return;
+ this._keyPressEventId = global.stage.connect('key-press-event',
+ Lang.bind(this, this._globalKeyPressEvent));
+
this.actor.show();
this.actor.lower(Main.chrome.actor);
this._open = true;
@@ -594,6 +739,9 @@ LookingGlass.prototype = {
if (!this._open)
return;
+ if (this._keyPressEventId)
+ global.stage.disconnect(this._keyPressEventId);
+
this._historyNavIndex = -1;
this._open = false;
Tweener.removeTweens(this.actor);
diff --git a/js/ui/main.js b/js/ui/main.js
index 10ea3a546..502511e66 100644
--- a/js/ui/main.js
+++ b/js/ui/main.js
@@ -14,6 +14,7 @@ const St = imports.gi.St;
const Chrome = imports.ui.chrome;
const Environment = imports.ui.environment;
+const ExtensionSystem = imports.ui.extensionSystem;
const MessageTray = imports.ui.messageTray;
const Messaging = imports.ui.messaging;
const Overview = imports.ui.overview;
@@ -45,6 +46,8 @@ let recorder = null;
let shellDBusService = null;
let modalCount = 0;
let modalActorFocusStack = [];
+let _errorLogStack = [];
+let _startDate;
function start() {
// Add a binding for "global" in the global JS namespace; (gjs
@@ -52,6 +55,12 @@ function start() {
// called "window".)
window.global = Shell.Global.get();
+ // Now monkey patch utility functions into the global proxy;
+ // This is easier and faster than indirecting down into global
+ // if we want to call back up into JS.
+ global.logError = _logError;
+ global.log = _logDebug;
+
Gio.DesktopAppInfo.set_desktop_env("GNOME");
global.grab_dbus_service();
@@ -116,6 +125,8 @@ function start() {
notificationPopup = new MessageTray.Notification();
messageTray = new MessageTray.MessageTray();
+ _startDate = new Date();
+
global.screen.connect('toggle-recording', function() {
if (recorder == null) {
recorder = new Shell.Recorder({ stage: global.stage });
@@ -130,6 +141,9 @@ function start() {
_relayout();
+ ExtensionSystem.init();
+ ExtensionSystem.loadExtensions();
+
panel.startupAnimation();
let display = global.screen.get_display();
@@ -138,9 +152,52 @@ function start() {
global.stage.connect('captured-event', _globalKeyPressHandler);
+ _log('info', 'loaded at ' + _startDate);
+
Mainloop.idle_add(_removeUnusedWorkspaces);
}
+/**
+ * _log:
+ * @category: string message type ('info', 'error')
+ * @msg: A message string
+ * ...: Any further arguments are converted into JSON notation,
+ * and appended to the log message, separated by spaces.
+ *
+ * Log a message into the LookingGlass error
+ * stream. This is primarily intended for use by the
+ * extension system as well as debugging.
+ */
+function _log(category, msg) {
+ let text = msg;
+ if (arguments.length > 2) {
+ text += ': ';
+ for (let i = 2; i < arguments.length; i++) {
+ text += JSON.stringify(arguments[i]);
+ if (i < arguments.length - 1)
+ text += " ";
+ }
+ }
+ _errorLogStack.push({timestamp: new Date().getTime(),
+ category: category,
+ message: text });
+}
+
+function _logError(msg) {
+ return _log('error', msg);
+}
+
+function _logDebug(msg) {
+ return _log('debug', msg);
+}
+
+// Used by the error display in lookingGlass.js
+function _getAndClearErrorStack() {
+ let errors = _errorLogStack;
+ _errorLogStack = [];
+ return errors;
+}
+
function _relayout() {
let primary = global.get_primary_monitor();
panel.actor.set_position(primary.x, primary.y);
@@ -251,7 +308,7 @@ function _findModal(actor) {
*/
function pushModal(actor) {
if (modalCount == 0) {
- if (!global.begin_modal(currentTime())) {
+ if (!global.begin_modal(global.get_current_time())) {
log("pushModal: invocation of begin_modal failed");
return false;
}
@@ -304,7 +361,7 @@ function popModal(actor) {
if (modalCount > 0)
return;
- global.end_modal(currentTime());
+ global.end_modal(global.get_current_time());
global.set_stage_input_mode(Shell.StageInputMode.NORMAL);
}
@@ -323,45 +380,6 @@ function getRunDialog() {
return runDialog;
}
-function createAppLaunchContext() {
- let context = new Gdk.AppLaunchContext();
- context.set_timestamp(currentTime());
-
- // Make sure that the app is opened on the current workspace even if
- // the user switches before it starts
- context.set_desktop(global.screen.get_active_workspace_index());
-
- return context;
-}
-
-/**
- * currentTime:
- *
- * Gets the current X server time from the current Clutter, Gdk, or X
- * event. If called from outside an event handler, this may return
- * %Clutter.CURRENT_TIME (aka 0), or it may return a slightly
- * out-of-date timestamp.
- */
-function currentTime() {
- // meta_display_get_current_time() will return the correct time
- // when handling an X or Gdk event, but will return CurrentTime
- // from some Clutter event callbacks.
- //
- // clutter_get_current_event_time() will return the correct time
- // from a Clutter event callback, but may return an out-of-date
- // timestamp if called at other times.
- //
- // So we try meta_display_get_current_time() first, since we
- // can recognize a "wrong" answer from that, and then fall back
- // to clutter_get_current_event_time().
-
- let time = global.screen.get_display().get_current_time();
- if (time != Clutter.CURRENT_TIME)
- return time;
-
- return Clutter.get_current_event_time();
-}
-
/**
* activateWindow:
* @window: the Meta.Window to activate
@@ -374,7 +392,7 @@ function activateWindow(window, time) {
let windowWorkspaceNum = window.get_workspace().index();
if (!time)
- time = currentTime();
+ time = global.get_current_time();
if (windowWorkspaceNum != activeWorkspaceNum) {
let workspace = global.screen.get_workspace_by_index(windowWorkspaceNum);
@@ -383,3 +401,120 @@ function activateWindow(window, time) {
window.activate(time);
}
}
+
+// TODO - replace this timeout with some system to guess when the user might
+// be e.g. just reading the screen and not likely to interact.
+const DEFERRED_TIMEOUT_SECONDS = 20;
+var _deferredWorkData = {};
+// Work scheduled for some point in the future
+var _deferredWorkQueue = [];
+// Work we need to process before the next redraw
+var _beforeRedrawQueue = [];
+// Counter to assign work ids
+var _deferredWorkSequence = 0;
+var _deferredTimeoutId = 0;
+
+function _runDeferredWork(workId) {
+ if (!_deferredWorkData[workId])
+ return;
+ let index = _deferredWorkQueue.indexOf(workId);
+ if (index < 0)
+ return;
+
+ _deferredWorkQueue.splice(index, 1);
+ _deferredWorkData[workId].callback();
+ if (_deferredWorkQueue.length == 0 && _deferredTimeoutId > 0) {
+ Mainloop.source_remove(_deferredTimeoutId);
+ _deferredTimeoutId = 0;
+ }
+}
+
+function _runAllDeferredWork() {
+ while (_deferredWorkQueue.length > 0)
+ _runDeferredWork(_deferredWorkQueue[0]);
+}
+
+function _runBeforeRedrawQueue() {
+ for (let i = 0; i < _beforeRedrawQueue.length; i++) {
+ let workId = _beforeRedrawQueue[i];
+ _runDeferredWork(workId);
+ }
+ _beforeRedrawQueue = [];
+}
+
+function _queueBeforeRedraw(workId) {
+ _beforeRedrawQueue.push(workId);
+ if (_beforeRedrawQueue.length == 1) {
+ Meta.later_add(Meta.LaterType.BEFORE_REDRAW, function () {
+ _runBeforeRedrawQueue();
+ return false;
+ }, null);
+ }
+}
+
+/**
+ * initializeDeferredWork:
+ * @actor: A #ClutterActor
+ * @callback: Function to invoke to perform work
+ *
+ * This function sets up a callback to be invoked when either the
+ * given actor is mapped, or after some period of time when the machine
+ * is idle. This is useful if your actor isn't always visible on the
+ * screen (for example, all actors in the overview), and you don't want
+ * to consume resources updating if the actor isn't actually going to be
+ * displaying to the user.
+ *
+ * Note that queueDeferredWork is called by default immediately on
+ * initialization as well, under the assumption that new actors
+ * will need it.
+ *
+ * Returns: A string work identifer
+ */
+function initializeDeferredWork(actor, callback, props) {
+ // Turn into a string so we can use as an object property
+ let workId = "" + (++_deferredWorkSequence);
+ _deferredWorkData[workId] = { 'actor': actor,
+ 'callback': callback };
+ actor.connect('notify::mapped', function () {
+ if (!(actor.mapped && _deferredWorkQueue.indexOf(workId) >= 0))
+ return;
+ _queueBeforeRedraw(workId);
+ });
+ actor.connect('destroy', function() {
+ let index = _deferredWorkQueue.indexOf(workId);
+ if (index >= 0)
+ _deferredWorkQueue.splice(index, 1);
+ delete _deferredWorkData[workId];
+ });
+ queueDeferredWork(workId);
+ return workId;
+}
+
+/**
+ * queueDeferredWork:
+ * @workId: work identifier
+ *
+ * Ensure that the work identified by @workId will be
+ * run on map or timeout. You should call this function
+ * for example when data being displayed by the actor has
+ * changed.
+ */
+function queueDeferredWork(workId) {
+ let data = _deferredWorkData[workId];
+ if (!data) {
+ global.logError("invalid work id ", workId);
+ return;
+ }
+ if (_deferredWorkQueue.indexOf(workId) < 0)
+ _deferredWorkQueue.push(workId);
+ if (data.actor.mapped) {
+ _queueBeforeRedraw(workId);
+ return;
+ } else if (_deferredTimeoutId == 0) {
+ _deferredTimeoutId = Mainloop.timeout_add_seconds(DEFERRED_TIMEOUT_SECONDS, function () {
+ _runAllDeferredWork();
+ _deferredTimeoutId = 0;
+ return false;
+ });
+ }
+}
diff --git a/js/ui/overview.js b/js/ui/overview.js
index e217bfe1a..43ff6d75d 100644
--- a/js/ui/overview.js
+++ b/js/ui/overview.js
@@ -12,7 +12,6 @@ const Lang = imports.lang;
const AppDisplay = imports.ui.appDisplay;
const DocDisplay = imports.ui.docDisplay;
const GenericDisplay = imports.ui.genericDisplay;
-const Link = imports.ui.link;
const Main = imports.ui.main;
const Panel = imports.ui.panel;
const Dash = imports.ui.dash;
@@ -184,6 +183,7 @@ Overview.prototype = {
this._dash.actor.set_size(displayGridColumnWidth, contentHeight);
this._dash.searchArea.height = this._workspacesY - contentY;
this._dash.sectionArea.height = this._workspacesHeight;
+ this._dash.searchResults.actor.height = this._workspacesHeight;
// place the 'Add Workspace' button in the bottom row of the grid
addRemoveButtonSize = Math.floor(displayGridRowHeight * 3/5);
@@ -248,7 +248,7 @@ Overview.prototype = {
// This allows the user to place the item on any workspace.
handleDragOver : function(source, actor, x, y, time) {
if (source instanceof GenericDisplay.GenericDisplayItem
- || source instanceof AppDisplay.BaseWellItem) {
+ || source instanceof AppDisplay.AppIcon) {
if (this._activeDisplayPane != null)
this._activeDisplayPane.close();
return true;
@@ -457,7 +457,7 @@ Overview.prototype = {
},
_addNewWorkspace: function() {
- global.screen.append_new_workspace(false, Main.currentTime());
+ global.screen.append_new_workspace(false, global.get_current_time());
},
_acceptNewWorkspaceDrop: function(source, dropActor, x, y, time) {
diff --git a/js/ui/placeDisplay.js b/js/ui/placeDisplay.js
index f408fc978..160a029df 100644
--- a/js/ui/placeDisplay.js
+++ b/js/ui/placeDisplay.js
@@ -15,7 +15,7 @@ const _ = Gettext.gettext;
const DND = imports.ui.dnd;
const Main = imports.ui.main;
-const GenericDisplay = imports.ui.genericDisplay;
+const Search = imports.ui.search;
const NAUTILUS_PREFS_DIR = '/apps/nautilus/preferences';
const DESKTOP_IS_HOME_KEY = NAUTILUS_PREFS_DIR + '/desktop_is_home_dir';
@@ -30,19 +30,78 @@ const PLACES_ICON_SIZE = 16;
* @iconFactory: A JavaScript callback which will create an icon texture given a size parameter
* @launch: A JavaScript callback to launch the entry
*/
-function PlaceInfo(name, iconFactory, launch) {
- this._init(name, iconFactory, launch);
+function PlaceInfo(id, name, iconFactory, launch) {
+ this._init(id, name, iconFactory, launch);
}
PlaceInfo.prototype = {
- _init: function(name, iconFactory, launch) {
+ _init: function(id, name, iconFactory, launch) {
+ this.id = id;
this.name = name;
+ this._lowerName = name.toLowerCase();
this.iconFactory = iconFactory;
this.launch = launch;
- this.id = null;
+ },
+
+ matchTerms: function(terms) {
+ let mtype = Search.MatchType.NONE;
+ for (let i = 0; i < terms.length; i++) {
+ let term = terms[i];
+ let idx = this._lowerName.indexOf(term);
+ if (idx == 0)
+ return Search.MatchType.PREFIX;
+ else if (idx > 0)
+ mtype = Search.MatchType.SUBSTRING;
+ }
+ return mtype;
+ },
+
+ isRemovable: function() {
+ return false;
}
}
+function PlaceDeviceInfo(mount) {
+ this._init(mount);
+}
+
+PlaceDeviceInfo.prototype = {
+ __proto__: PlaceInfo.prototype,
+
+ _init: function(mount) {
+ this._mount = mount;
+ this.name = mount.get_name();
+ this._lowerName = this.name.toLowerCase();
+ this.id = "mount:" + mount.get_root().get_uri();
+ },
+
+ iconFactory: function(size) {
+ let icon = this._mount.get_icon();
+ return Shell.TextureCache.get_default().load_gicon(icon, size);
+ },
+
+ launch: function() {
+ Gio.app_info_launch_default_for_uri(this._mount.get_root().get_uri(),
+ global.create_app_launch_context());
+ },
+
+ isRemovable: function() {
+ return this._mount.can_unmount();
+ },
+
+ remove: function() {
+ if (!this.isRemovable())
+ return;
+
+ this._mount.unmount(0, null, Lang.bind(this, this._removeFinish), null);
+ },
+
+ _removeFinish: function(o, res, data) {
+ this._mount.unmount_finish(res);
+ }
+}
+
+
function PlacesManager() {
this._init();
}
@@ -52,6 +111,7 @@ PlacesManager.prototype = {
let gconf = Shell.GConf.get_default();
gconf.watch_directory(NAUTILUS_PREFS_DIR);
+ this._defaultPlaces = [];
this._mounts = [];
this._bookmarks = [];
this._isDesktopHome = false;
@@ -60,12 +120,12 @@ PlacesManager.prototype = {
let homeUri = homeFile.get_uri();
let homeLabel = Shell.util_get_label_for_uri (homeUri);
let homeIcon = Shell.util_get_icon_for_uri (homeUri);
- this._home = new PlaceInfo(homeLabel,
+ this._home = new PlaceInfo('special:home', homeLabel,
function(size) {
return Shell.TextureCache.get_default().load_gicon(homeIcon, size);
},
function() {
- Gio.app_info_launch_default_for_uri(homeUri, Main.createAppLaunchContext());
+ Gio.app_info_launch_default_for_uri(homeUri, global.create_app_launch_context());
});
let desktopPath = GLib.get_user_special_dir(GLib.UserDirectory.DIRECTORY_DESKTOP);
@@ -73,15 +133,15 @@ PlacesManager.prototype = {
let desktopUri = desktopFile.get_uri();
let desktopLabel = Shell.util_get_label_for_uri (desktopUri);
let desktopIcon = Shell.util_get_icon_for_uri (desktopUri);
- this._desktopMenu = new PlaceInfo(desktopLabel,
+ this._desktopMenu = new PlaceInfo('special:desktop', desktopLabel,
function(size) {
return Shell.TextureCache.get_default().load_gicon(desktopIcon, size);
},
function() {
- Gio.app_info_launch_default_for_uri(desktopUri, Main.createAppLaunchContext());
+ Gio.app_info_launch_default_for_uri(desktopUri, global.create_app_launch_context());
});
- this._connect = new PlaceInfo(_("Connect to..."),
+ this._connect = new PlaceInfo('special:connect', _("Connect to..."),
function (size) {
return Shell.TextureCache.get_default().load_icon_name("applications-internet", size);
},
@@ -101,7 +161,7 @@ PlacesManager.prototype = {
}
if (networkApp != null) {
- this._network = new PlaceInfo(networkApp.get_name(),
+ this._network = new PlaceInfo('special:network', networkApp.get_name(),
function(size) {
return networkApp.create_icon_texture(size);
},
@@ -110,6 +170,16 @@ PlacesManager.prototype = {
});
}
+ this._defaultPlaces.push(this._home);
+
+ if (!this._isDesktopHome)
+ this._defaultPlaces.push(this._desktopMenu);
+
+ if (this._network)
+ this._defaultPlaces.push(this._network);
+
+ this._defaultPlaces.push(this._connect);
+
/*
* Show devices, code more or less ported from nautilus-places-sidebar.c
*/
@@ -238,12 +308,12 @@ PlacesManager.prototype = {
continue;
let icon = Shell.util_get_icon_for_uri(bookmark);
- let item = new PlaceInfo(label,
+ let item = new PlaceInfo('bookmark:' + bookmark, label,
function(size) {
return Shell.TextureCache.get_default().load_gicon(icon, size);
},
function() {
- Gio.app_info_launch_default_for_uri(bookmark, Main.createAppLaunchContext());
+ Gio.app_info_launch_default_for_uri(bookmark, global.create_app_launch_context());
});
this._bookmarks.push(item);
}
@@ -263,17 +333,7 @@ PlacesManager.prototype = {
},
_addMount: function(mount) {
- let mountLabel = mount.get_name();
- let mountIcon = mount.get_icon();
- let root = mount.get_root();
- let mountUri = root.get_uri();
- let devItem = new PlaceInfo(mountLabel,
- function(size) {
- return Shell.TextureCache.get_default().load_gicon(mountIcon, size);
- },
- function() {
- Gio.app_info_launch_default_for_uri(mountUri, Main.createAppLaunchContext());
- });
+ let devItem = new PlaceDeviceInfo(mount);
this._mounts.push(devItem);
},
@@ -282,16 +342,7 @@ PlacesManager.prototype = {
},
getDefaultPlaces: function () {
- let places = [this._home];
-
- if (!this._isDesktopHome)
- places.push(this._desktopMenu);
-
- if (this._network)
- places.push(this._network);
-
- places.push(this._connect);
- return places;
+ return this._defaultPlaces;
},
getBookmarks: function () {
@@ -300,6 +351,28 @@ PlacesManager.prototype = {
getMounts: function () {
return this._mounts;
+ },
+
+ _lookupById: function(sourceArray, id) {
+ for (let i = 0; i < sourceArray.length; i++) {
+ let place = sourceArray[i];
+ if (place.id == id)
+ return place;
+ }
+ return null;
+ },
+
+ lookupPlaceById: function(id) {
+ let colonIdx = id.indexOf(':');
+ let type = id.substring(0, colonIdx);
+ let sourceArray = null;
+ if (type == 'special')
+ sourceArray = this._defaultPlaces;
+ else if (type == 'mount')
+ sourceArray = this._mounts;
+ else if (type == 'bookmark')
+ sourceArray = this._bookmarks;
+ return this._lookupById(sourceArray, id);
}
};
@@ -319,23 +392,37 @@ DashPlaceDisplayItem.prototype = {
this._info = info;
this._icon = info.iconFactory(PLACES_ICON_SIZE);
this.actor = new Big.Box({ orientation: Big.BoxOrientation.HORIZONTAL,
- reactive: true,
spacing: 4 });
- this.actor.connect('button-release-event', Lang.bind(this, function (b, e) {
- this._info.launch();
- Main.overview.hide();
- }));
- let text = new St.Label({ style_class: 'places-item',
- text: info.name });
- let iconBox = new Big.Box({ y_align: Big.BoxAlignment.CENTER });
- iconBox.append(this._icon, Big.BoxPackFlags.NONE);
+ let text = new St.Button({ style_class: 'places-item',
+ label: info.name,
+ x_align: St.Align.START });
+ text.connect('clicked', Lang.bind(this, this._onClicked));
+ let iconBox = new St.Bin({ child: this._icon, reactive: true });
+ iconBox.connect('button-release-event',
+ Lang.bind(this, this._onClicked));
this.actor.append(iconBox, Big.BoxPackFlags.NONE);
this.actor.append(text, Big.BoxPackFlags.EXPAND);
+ if (info.isRemovable()) {
+ let removeIcon = Shell.TextureCache.get_default().load_icon_name ('media-eject', PLACES_ICON_SIZE);
+ let removeIconBox = new St.Button({ child: removeIcon,
+ reactive: true });
+ this.actor.append(removeIconBox, Big.BoxPackFlags.NONE);
+ removeIconBox.connect('clicked',
+ Lang.bind(this, function() {
+ this._info.remove();
+ }));
+ }
+
this.actor._delegate = this;
let draggable = DND.makeDraggable(this.actor);
},
+ _onClicked: function(b) {
+ this._info.launch();
+ Main.overview.hide();
+ },
+
getDragActorSource: function() {
return this._icon;
},
@@ -421,120 +508,67 @@ DashPlaceDisplay.prototype = {
Signals.addSignalMethods(DashPlaceDisplay.prototype);
-
-function PlaceDisplayItem(placeInfo) {
- this._init(placeInfo);
+function PlaceSearchProvider() {
+ this._init();
}
-PlaceDisplayItem.prototype = {
- __proto__: GenericDisplay.GenericDisplayItem.prototype,
+PlaceSearchProvider.prototype = {
+ __proto__: Search.SearchProvider.prototype,
- _init : function(placeInfo) {
- GenericDisplay.GenericDisplayItem.prototype._init.call(this);
- this._info = placeInfo;
-
- this._setItemInfo(placeInfo.name, '');
+ _init: function() {
+ Search.SearchProvider.prototype._init.call(this, _("PLACES"));
},
- //// Public method overrides ////
-
- // Opens an application represented by this display item.
- launch : function() {
- this._info.launch();
+ getResultMeta: function(resultId) {
+ let placeInfo = Main.placesManager.lookupPlaceById(resultId);
+ if (!placeInfo)
+ return null;
+ return { 'id': resultId,
+ 'name': placeInfo.name,
+ 'icon': placeInfo.iconFactory(Search.RESULT_ICON_SIZE) };
},
- shellWorkspaceLaunch: function() {
- this._info.launch();
+ activateResult: function(id) {
+ let placeInfo = Main.placesManager.lookupPlaceById(id);
+ placeInfo.launch();
},
- //// Protected method overrides ////
-
- // Returns an icon for the item.
- _createIcon: function() {
- return this._info.iconFactory(GenericDisplay.ITEM_DISPLAY_ICON_SIZE);
+ _compareResultMeta: function (idA, idB) {
+ let infoA = Main.placesManager.lookupPlaceById(idA);
+ let infoB = Main.placesManager.lookupPlaceById(idB);
+ return infoA.name.localeCompare(infoB.name);
},
- // Returns a preview icon for the item.
- _createPreviewIcon: function() {
- return this._info.iconFactory(GenericDisplay.PREVIEW_ICON_SIZE);
+ _searchPlaces: function(places, terms) {
+ let multipleResults = [];
+ let prefixResults = [];
+ let substringResults = [];
+
+ terms = terms.map(String.toLowerCase);
+
+ for (let i = 0; i < places.length; i++) {
+ let place = places[i];
+ let mtype = place.matchTerms(terms);
+ if (mtype == Search.MatchType.MULTIPLE)
+ multipleResults.push(place.id);
+ else if (mtype == Search.MatchType.PREFIX)
+ prefixResults.push(place.id);
+ else if (mtype == Search.MatchType.SUBSTRING)
+ substringResults.push(place.id);
+ }
+ multipleResults.sort(this._compareResultMeta);
+ prefixResults.sort(this._compareResultMeta);
+ substringResults.sort(this._compareResultMeta);
+ return multipleResults.concat(prefixResults.concat(substringResults));
+ },
+
+ getInitialResultSet: function(terms) {
+ let places = Main.placesManager.getAllPlaces();
+ return this._searchPlaces(places, terms);
+ },
+
+ getSubsearchResultSet: function(previousResults, terms) {
+ let places = previousResults.map(function (id) { return Main.placesManager.lookupPlaceById(id); });
+ return this._searchPlaces(places, terms);
}
-
-};
-
-function PlaceDisplay(flags) {
- this._init(flags);
}
-
-PlaceDisplay.prototype = {
- __proto__: GenericDisplay.GenericDisplay.prototype,
-
- _init: function(flags) {
- GenericDisplay.GenericDisplay.prototype._init.call(this, flags);
- this._stale = true;
- Main.placesManager.connect('places-updated', Lang.bind(this, function (e) {
- this._stale = true;
- }));
- },
-
- //// Protected method overrides ////
- _refreshCache: function () {
- if (!this._stale)
- return true;
- this._allItems = {};
- let array = Main.placesManager.getAllPlaces();
- for (let i = 0; i < array.length; i ++) {
- // We are using an array id as placeInfo id because placeInfo doesn't have any
- // other information piece that can be used as a unique id. There are different
- // types of placeInfo, such as devices and directories that would result in differently
- // structured ids. Also the home directory can show up in both the default places and in
- // bookmarks which means its URI can't be used as a unique id. (This does mean it can
- // appear twice in search results, though that doesn't happen at the moment because we
- // name it "Home Folder" in default places and it's named with the user's system name
- // if it appears as a bookmark.)
- let placeInfo = array[i];
- placeInfo.id = i;
- this._allItems[i] = placeInfo;
- }
- this._stale = false;
- return false;
- },
-
- // Sets the list of the displayed items.
- _setDefaultList: function() {
- this._matchedItems = {};
- this._matchedItemKeys = [];
- for (id in this._allItems) {
- this._matchedItems[id] = 1;
- this._matchedItemKeys.push(id);
- }
- this._matchedItemKeys.sort(Lang.bind(this, this._compareItems));
- },
-
- // Checks if the item info can be a match for the search string by checking
- // the name of the place. Item info is expected to be PlaceInfo.
- // Returns a boolean flag indicating if itemInfo is a match.
- _isInfoMatching: function(itemInfo, search) {
- if (search == null || search == '')
- return true;
-
- let name = itemInfo.name.toLowerCase();
- if (name.indexOf(search) >= 0)
- return true;
-
- return false;
- },
-
- // Compares items associated with the item ids based on the alphabetical order
- // of the item names.
- // Returns an integer value indicating the result of the comparison.
- _compareItems: function(itemIdA, itemIdB) {
- let placeA = this._allItems[itemIdA];
- let placeB = this._allItems[itemIdB];
- return placeA.name.localeCompare(placeB.name);
- },
-
- // Creates a PlaceDisplayItem based on itemInfo, which is expected to be a PlaceInfo object.
- _createDisplayItem: function(itemInfo) {
- return new PlaceDisplayItem(itemInfo);
- }
-};
diff --git a/js/ui/runDialog.js b/js/ui/runDialog.js
index 487aeb11b..6f98738cd 100644
--- a/js/ui/runDialog.js
+++ b/js/ui/runDialog.js
@@ -2,6 +2,7 @@
const Big = imports.gi.Big;
const Clutter = imports.gi.Clutter;
+const Gio = imports.gi.Gio;
const GLib = imports.gi.GLib;
const Lang = imports.lang;
const Mainloop = imports.mainloop;
@@ -24,6 +25,144 @@ const DIALOG_WIDTH = 320;
const DIALOG_PADDING = 6;
const ICON_SIZE = 24;
const ICON_BOX_SIZE = 36;
+const MAX_FILE_DELETED_BEFORE_INVALID = 10;
+
+function CommandCompleter() {
+ this._init();
+}
+
+CommandCompleter.prototype = {
+ _init : function() {
+ this._changedCount = 0;
+ this._paths = GLib.getenv('PATH').split(':');
+ this._valid = false;
+ this._updateInProgress = false;
+ this._childs = new Array(this._paths.length);
+ this._monitors = new Array(this._paths.length);
+ for (let i = 0; i < this._paths.length; i++) {
+ this._childs[i] = [];
+ let file = Gio.file_new_for_path(this._paths[i]);
+ let info = file.query_info(Gio.FILE_ATTRIBUTE_STANDARD_TYPE, Gio.FileQueryInfoFlags.NONE, null);
+
+ if (info.get_attribute_uint32(Gio.FILE_ATTRIBUTE_STANDARD_TYPE) != Gio.FileType.DIRECTORY)
+ continue;
+
+ this._paths[i] = file.get_path();
+ this._monitors[i] = file.monitor_directory(Gio.FileMonitorFlags.NONE, null);
+ if (this._monitors[i] != null) {
+ this._monitors[i].connect("changed", Lang.bind(this, this._onChanged));
+ }
+ }
+ this._update(0);
+ },
+
+ _onGetEnumerateComplete : function(obj, res) {
+ this._enumerator = obj.enumerate_children_finish(res);
+ this._enumerator.next_files_async(100, GLib.PRIORITY_LOW, null, Lang.bind(this, this._onNextFileComplete), null);
+ },
+
+ _onNextFileComplete : function(obj, res) {
+ let files = obj.next_files_finish(res);
+ for (let i = 0; i < files.length; i++) {
+ this._childs[this._i].push(files[i].get_name());
+ }
+ if (files.length) {
+ this._enumerator.next_files_async(100, GLib.PRIORITY_LOW, null, Lang.bind(this, this._onNextFileComplete), null);
+ } else {
+ this._enumerator.close(null);
+ this._enumerator = null;
+ this._update(this._i + 1);
+ }
+ },
+
+ update : function() {
+ if (this._valid)
+ return;
+ this._update(0);
+ },
+
+ _update : function(i) {
+ if (i == 0 && this._updateInProgress)
+ return;
+ this._updateInProgress = true;
+ this._changedCount = 0;
+ this._i = i;
+ if (i >= this._paths.length) {
+ this._valid = true;
+ this._updateInProgress = false;
+ return;
+ }
+ let file = Gio.file_new_for_path(this._paths[i]);
+ this._childs[this._i] = [];
+ file.enumerate_children_async(Gio.FILE_ATTRIBUTE_STANDARD_NAME, Gio.FileQueryInfoFlags.NONE, GLib.PRIORITY_LOW, null, Lang.bind(this, this._onGetEnumerateComplete), null);
+ },
+
+ _onChanged : function(m, f, of, type) {
+ if (!this._valid)
+ return;
+ let path = f.get_parent().get_path();
+ let k = undefined;
+ for (let i = 0; i < this._paths.length; i++) {
+ if (this._paths[i] == path)
+ k = i;
+ }
+ if (k === undefined) {
+ return;
+ }
+ if (type == Gio.FileMonitorEvent.CREATED) {
+ this._childs[k].push(f.get_basename());
+ }
+ if (type == Gio.FileMonitorEvent.DELETED) {
+ this._changedCount++;
+ if (this._changedCount > MAX_FILE_DELETED_BEFORE_INVALID) {
+ this._valid = false;
+ }
+ let name = f.get_basename();
+ this._childs[k] = this._childs[k].filter(function(e) {
+ return e != name;
+ });
+ }
+ if (type == Gio.FileMonitorEvent.UNMOUNTED) {
+ this._childs[k] = [];
+ }
+ },
+
+ getCompletion: function(text) {
+ let common = "";
+ let notInit = true;
+ if (!this._valid) {
+ this._update(0);
+ return common;
+ }
+ function _getCommon(s1, s2) {
+ let k = 0;
+ for (; k < s1.length && k < s2.length; k++) {
+ if (s1[k] != s2[k])
+ break;
+ }
+ if (k == 0)
+ return "";
+ return s1.substr(0, k);
+ }
+ function _hasPrefix(s1, prefix) {
+ return s1.indexOf(prefix) == 0;
+ }
+ for (let i = 0; i < this._childs.length; i++) {
+ for (let k = 0; k < this._childs[i].length; k++) {
+ if (!_hasPrefix(this._childs[i][k], text))
+ continue;
+ if (notInit) {
+ common = this._childs[i][k];
+ notInit = false;
+ }
+ common = _getCommon(common, this._childs[i][k]);
+ }
+ }
+ if (common.length)
+ return common.substr(text.length);
+ return common;
+ }
+};
function RunDialog() {
this._init();
@@ -137,16 +276,55 @@ RunDialog.prototype = {
this.close();
}));
+ this._pathCompleter = new Gio.FilenameCompleter();
+ this._commandCompleter = new CommandCompleter();
+ this._group.connect('notify::visible', Lang.bind(this._commandCompleter, this._commandCompleter.update));
this._entry.connect('key-press-event', Lang.bind(this, function(o, e) {
let symbol = e.get_key_symbol();
if (symbol == Clutter.Escape) {
this.close();
return true;
}
+ if (symbol == Clutter.slash) {
+ // Need preload data before get completion. GFilenameCompleter load content of parent directory.
+ // Parent directory for /usr/include/ is /usr/. So need to add fake name('a').
+ let text = o.get_text().concat('/a');
+ let prefix;
+ if (text.lastIndexOf(' ') == -1)
+ prefix = text;
+ else
+ prefix = text.substr(text.lastIndexOf(' ') + 1);
+ this._getCompletion(prefix);
+ return false;
+ }
+ if (symbol == Clutter.Tab) {
+ let text = o.get_text();
+ let prefix;
+ if (text.lastIndexOf(' ') == -1)
+ prefix = text;
+ else
+ prefix = text.substr(text.lastIndexOf(' ') + 1);
+ let postfix = this._getCompletion(prefix);
+ if (postfix != null && postfix.length > 0) {
+ o.insert_text(postfix, -1);
+ o.set_cursor_position(text.length + postfix.length);
+ if (postfix[postfix.length - 1] == '/')
+ this._getCompletion(text + postfix + 'a');
+ }
+ return true;
+ }
return false;
}));
},
+ _getCompletion : function(text) {
+ if (text.indexOf('/') != -1) {
+ return this._pathCompleter.get_completion_suffix(text);
+ } else {
+ return this._commandCompleter.getCompletion(text);
+ }
+ },
+
_run : function(command) {
this._commandError = false;
let f;
diff --git a/js/ui/search.js b/js/ui/search.js
new file mode 100644
index 000000000..2b738523c
--- /dev/null
+++ b/js/ui/search.js
@@ -0,0 +1,272 @@
+/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
+
+const Signals = imports.signals;
+const St = imports.gi.St;
+
+const RESULT_ICON_SIZE = 24;
+
+// Not currently referenced by the search API, but
+// this enumeration can be useful for provider
+// implementations.
+const MatchType = {
+ NONE: 0,
+ MULTIPLE: 1,
+ PREFIX: 2,
+ SUBSTRING: 3
+};
+
+function SearchResultDisplay(provider) {
+ this._init(provider);
+}
+
+SearchResultDisplay.prototype = {
+ _init: function(provider) {
+ this.provider = provider;
+ this.actor = null;
+ this.selectionIndex = -1;
+ },
+
+ /**
+ * renderResults:
+ * @results: List of identifier strings
+ * @terms: List of search term strings
+ *
+ * Display the given search matches which resulted
+ * from the given terms. It's expected that not
+ * all results will fit in the space for the container
+ * actor; in this case, show as many as makes sense
+ * for your result type.
+ *
+ * The terms are useful for search match highlighting.
+ */
+ renderResults: function(results, terms) {
+ throw new Error("not implemented");
+ },
+
+ /**
+ * clear:
+ * Remove all results from this display and reset the selection index.
+ */
+ clear: function() {
+ this.actor.get_children().forEach(function (actor) { actor.destroy(); });
+ this.selectionIndex = -1;
+ },
+
+ /**
+ * getSelectionIndex:
+ *
+ * Returns the index of the selected actor, or -1 if none.
+ */
+ getSelectionIndex: function() {
+ return this.selectionIndex;
+ },
+
+ /**
+ * getVisibleResultCount:
+ *
+ * Returns: The number of actors visible.
+ */
+ getVisibleResultCount: function() {
+ throw new Error("not implemented");
+ },
+
+ /**
+ * selectIndex:
+ * @index: Integer index
+ *
+ * Move selection to the given index.
+ * Return true if successful, false if no more results
+ * available.
+ */
+ selectIndex: function() {
+ throw new Error("not implemented");
+ }
+};
+
+/**
+ * SearchProvider:
+ *
+ * Subclass this object to add a new result type
+ * to the search system, then call registerProvider()
+ * in SearchSystem with an instance.
+ */
+function SearchProvider(title) {
+ this._init(title);
+}
+
+SearchProvider.prototype = {
+ _init: function(title) {
+ this.title = title;
+ },
+
+ /**
+ * getInitialResultSet:
+ * @terms: Array of search terms, treated as logical OR
+ *
+ * Called when the user first begins a search (most likely
+ * therefore a single term of length one or two), or when
+ * a new term is added.
+ *
+ * Should return an array of result identifier strings representing
+ * items which match the given search terms. This
+ * is expected to be a substring match on the metadata for a given
+ * item. Ordering of returned results is up to the discretion of the provider,
+ * but you should follow these heruistics:
+ *
+ * * Put items which match multiple search terms before single matches
+ * * Put items which match on a prefix before non-prefix substring matches
+ *
+ * This function should be fast; do not perform unindexed full-text searches
+ * or network queries.
+ */
+ getInitialResultSet: function(terms) {
+ throw new Error("not implemented");
+ },
+
+ /**
+ * getSubsearchResultSet:
+ * @previousResults: Array of item identifiers
+ * @newTerms: Updated search terms
+ *
+ * Called when a search is performed which is a "subsearch" of
+ * the previous search; i.e. when every search term has exactly
+ * one corresponding term in the previous search which is a prefix
+ * of the new term.
+ *
+ * This allows search providers to only search through the previous
+ * result set, rather than possibly performing a full re-query.
+ */
+ getSubsearchResultSet: function(previousResults, newTerms) {
+ throw new Error("not implemented");
+ },
+
+ /**
+ * getResultInfo:
+ * @id: Result identifier string
+ *
+ * Return an object with 'id', 'name', (both strings) and 'icon' (Clutter.Texture)
+ * properties which describe the given search result.
+ */
+ getResultMeta: function(id) {
+ throw new Error("not implemented");
+ },
+
+ /**
+ * createResultContainer:
+ *
+ * Search providers may optionally override this to render their
+ * results in a custom fashion. The default implementation
+ * will create a vertical list.
+ *
+ * Returns: An instance of SearchResultDisplay.
+ */
+ createResultContainerActor: function() {
+ return null;
+ },
+
+ /**
+ * createResultActor:
+ * @resultMeta: Object with result metadata
+ * @terms: Array of search terms, should be used for highlighting
+ *
+ * Search providers may optionally override this to render a
+ * particular serch result in a custom fashion. The default
+ * implementation will show the icon next to the name.
+ *
+ * The actor should be an instance of St.Widget, with the style class
+ * 'dash-search-result-content'.
+ */
+ createResultActor: function(resultMeta, terms) {
+ return null;
+ },
+
+ /**
+ * activateResult:
+ * @id: Result identifier string
+ *
+ * Called when the user chooses a given result.
+ */
+ activateResult: function(id) {
+ throw new Error("not implemented");
+ },
+
+ /**
+ * expandSearch:
+ *
+ * Called when the user clicks on the header for this
+ * search section. Should typically launch an external program
+ * displaying search results for that item type.
+ */
+ expandSearch: function(terms) {
+ throw new Error("not implemented");
+ }
+}
+Signals.addSignalMethods(SearchProvider.prototype);
+
+function SearchSystem() {
+ this._init();
+}
+
+SearchSystem.prototype = {
+ _init: function() {
+ this._providers = [];
+ this.reset();
+ },
+
+ registerProvider: function (provider) {
+ this._providers.push(provider);
+ },
+
+ getProviders: function() {
+ return this._providers;
+ },
+
+ getTerms: function() {
+ return this._previousTerms;
+ },
+
+ reset: function() {
+ this._previousTerms = [];
+ this._previousResults = [];
+ },
+
+ updateSearch: function(searchString) {
+ searchString = searchString.replace(/^\s+/g, "").replace(/\s+$/g, "");
+ if (searchString == '')
+ return null;
+
+ let terms = searchString.split(/\s+/);
+ let isSubSearch = terms.length == this._previousTerms.length;
+ if (isSubSearch) {
+ for (let i = 0; i < terms.length; i++) {
+ if (terms[i].indexOf(this._previousTerms[i]) != 0) {
+ isSubSearch = false;
+ break;
+ }
+ }
+ }
+
+ let results = [];
+ if (isSubSearch) {
+ for (let i = 0; i < this._previousResults.length; i++) {
+ let [provider, previousResults] = this._previousResults[i];
+ let providerResults = provider.getSubsearchResultSet(previousResults, terms);
+ if (providerResults.length > 0)
+ results.push([provider, providerResults]);
+ }
+ } else {
+ for (let i = 0; i < this._providers.length; i++) {
+ let provider = this._providers[i];
+ let providerResults = provider.getInitialResultSet(terms);
+ if (providerResults.length > 0)
+ results.push([provider, providerResults]);
+ }
+ }
+
+ this._previousTerms = terms;
+ this._previousResults = results;
+
+ return results;
+ }
+}
+Signals.addSignalMethods(SearchSystem.prototype);
diff --git a/js/ui/sidebar.js b/js/ui/sidebar.js
index 9e2ebd396..5389f251a 100644
--- a/js/ui/sidebar.js
+++ b/js/ui/sidebar.js
@@ -69,6 +69,8 @@ Sidebar.prototype = {
Lang.bind(this, this._expandedChanged));
this._gconf.connect('changed::sidebar/visible',
Lang.bind(this, this._visibleChanged));
+
+ this._adjustPosition();
},
addWidget: function(widget) {
@@ -82,6 +84,14 @@ Sidebar.prototype = {
this.box.append(widgetBox.actor, Big.BoxPackFlags.NONE);
this._widgets.push(widgetBox);
+ this._adjustPosition();
+ },
+
+ _adjustPosition: function() {
+ let primary=global.get_primary_monitor();
+
+ this.actor.y = Math.max(primary.y + Panel.PANEL_HEIGHT,primary.height/2 - this.actor.height/2);
+ this.actor.x = primary.x;
},
_visibleChanged: function() {
diff --git a/js/ui/widget.js b/js/ui/widget.js
index 341833d82..b7e8ca335 100644
--- a/js/ui/widget.js
+++ b/js/ui/widget.js
@@ -354,8 +354,7 @@ RecentDocsWidget.prototype = {
for (i = 0; i < docs.length; i++) {
let docInfo = new DocInfo.DocInfo (docs[i]);
- if (docInfo.exists())
- items.push(docInfo);
+ items.push(docInfo);
}
items.sort(function (a,b) { return b.timestamp - a.timestamp; });
diff --git a/js/ui/workspaces.js b/js/ui/workspaces.js
index 58e982fc0..39f5ae317 100644
--- a/js/ui/workspaces.js
+++ b/js/ui/workspaces.js
@@ -337,8 +337,6 @@ WindowOverlay.prototype = {
button._overlap = 0;
windowClone.actor.connect('destroy', Lang.bind(this, this._onDestroy));
- windowClone.actor.connect('notify::allocation',
- Lang.bind(this, this._positionItems));
windowClone.actor.connect('enter-event',
Lang.bind(this, this._onEnter));
windowClone.actor.connect('leave-event',
@@ -395,23 +393,30 @@ WindowOverlay.prototype = {
this.title.height + this.title._spacing;
},
- _positionItems: function(win) {
+ /**
+ * @cloneX: x position of windowClone
+ * @cloneY: y position of windowClone
+ * @cloneWidth: width of windowClone
+ * @cloneHeight height of windowClone
+ */
+ // These parameters are not the values retrieved with
+ // get_transformed_position() and get_transformed_size(),
+ // as windowClone might be moving.
+ // See Workspace._fadeInWindowOverlay
+ updatePositions: function(cloneX, cloneY, cloneWidth, cloneHeight) {
let button = this.closeButton;
let title = this.title;
- let [x, y] = win.get_transformed_position();
- let [w, h] = win.get_transformed_size();
-
- let buttonX = x + w - button._overlap;
- let buttonY = y - button.height + button._overlap;
+ let buttonX = cloneX + cloneWidth - button._overlap;
+ let buttonY = cloneY - button.height + button._overlap;
button.set_position(Math.floor(buttonX), Math.floor(buttonY));
if (!title.fullWidth)
title.fullWidth = title.width;
- title.width = Math.min(title.fullWidth, w);
+ title.width = Math.min(title.fullWidth, cloneWidth);
- let titleX = x + (w - title.width) / 2;
- let titleY = y + h + title._spacing;
+ let titleX = cloneX + (cloneWidth - title.width) / 2;
+ let titleY = cloneY + cloneHeight + title._spacing;
title.set_position(Math.floor(titleX), Math.floor(titleY));
},
@@ -496,8 +501,8 @@ WindowOverlay.prototype = {
let closeNode = this.closeButton.get_theme_node();
- let [success, len] = closeNode.get_length('-shell-close-overlap',
- false);
+ [success, len] = closeNode.get_length('-shell-close-overlap',
+ false);
if (success)
this.closeButton._overlap = len;
@@ -1034,7 +1039,7 @@ Workspace.prototype = {
time: Overview.ANIMATION_TIME,
transition: "easeOutQuad",
onComplete: Lang.bind(this, function() {
- overlay.fadeIn();
+ this._fadeInWindowOverlay(clone, overlay);
})
});
}
@@ -1058,13 +1063,31 @@ Workspace.prototype = {
}
},
+ _fadeInWindowOverlay: function(clone, overlay) {
+ // This is a little messy and complicated because when we
+ // start the fade-in we may not have done the final positioning
+ // of the workspaces. (Tweener doesn't necessarily finish
+ // all animations before calling onComplete callbacks.)
+ // So we need to manually compute where the window will
+ // be after the workspace animation finishes.
+ let [cloneX, cloneY] = clone.actor.get_position();
+ let [cloneWidth, cloneHeight] = clone.actor.get_size();
+ cloneX = this.gridX + this.scale * cloneX;
+ cloneY = this.gridY + this.scale * cloneY;
+ cloneWidth = this.scale * clone.actor.scale_x * cloneWidth;
+ cloneHeight = this.scale * clone.actor.scale_y * cloneHeight;
+
+ overlay.updatePositions(cloneX, cloneY, cloneWidth, cloneHeight);
+ overlay.fadeIn();
+ },
+
_fadeInAllOverlays: function() {
for (let i = 1; i < this._windows.length; i++) {
let clone = this._windows[i];
let overlay = this._windowOverlays[i];
if (this._showOnlyWindows != null && !(clone.metaWindow in this._showOnlyWindows))
continue;
- overlay.fadeIn();
+ this._fadeInWindowOverlay(clone, overlay);
}
},
diff --git a/po/LINGUAS b/po/LINGUAS
index 04179aedf..bfdfb8d67 100644
--- a/po/LINGUAS
+++ b/po/LINGUAS
@@ -6,9 +6,11 @@ de
el
en_GB
es
+fi
fr
ga
gl
+he
hu
it
ko
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 6e72d3962..9a495c38f 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -9,5 +9,4 @@ js/ui/runDialog.js
js/ui/widget.js
src/gdmuser/gdm-user.c
src/shell-global.c
-src/shell-status-menu.c
src/shell-uri-util.c
diff --git a/po/es.po b/po/es.po
index f028abdb7..b40e95bd8 100644
--- a/po/es.po
+++ b/po/es.po
@@ -8,8 +8,8 @@ msgstr ""
"Project-Id-Version: gnome-shell master\n"
"Report-Msgid-Bugs-To: http://bugzilla.gnome.org/enter_bug.cgi?product=gnome-"
"shell&component=general\n"
-"POT-Creation-Date: 2009-10-27 20:37+0000\n"
-"PO-Revision-Date: 2009-10-27 23:32+0100\n"
+"POT-Creation-Date: 2009-12-18 15:06+0000\n"
+"PO-Revision-Date: 2009-12-19 14:41+0100\n"
"Last-Translator: Jorge González \n"
"Language-Team: Español \n"
"MIME-Version: 1.0\n"
@@ -25,76 +25,64 @@ msgstr "GNOME Shell"
msgid "Window management and application launching"
msgstr "Gestión de ventanas e inicio de aplicaciones"
-#: ../js/ui/appDisplay.js:332
-msgid "Frequent"
-msgstr "Frecuentes"
+#. **** Applications ****
+#: ../js/ui/appDisplay.js:252 ../js/ui/dash.js:852
+msgid "APPLICATIONS"
+msgstr "APLICACIONES"
-#: ../js/ui/appDisplay.js:867
-msgid "Drag here to add favorites"
-msgstr "Arrastrar aquí para añadir a los favoritos"
+#: ../js/ui/appDisplay.js:276
+msgid "PREFERENCES"
+msgstr "PREFERENCIAS"
-#: ../js/ui/appIcon.js:426
+#: ../js/ui/appDisplay.js:707 ../js/ui/appIcon.js:425
msgid "New Window"
msgstr "Ventana nueva"
-#: ../js/ui/appIcon.js:430
+#: ../js/ui/appDisplay.js:711 ../js/ui/appIcon.js:429
msgid "Remove from Favorites"
msgstr "Quitar de los favoritos"
-#: ../js/ui/appIcon.js:431
+#: ../js/ui/appDisplay.js:712 ../js/ui/appIcon.js:430
msgid "Add to Favorites"
msgstr "Añadir a los favoritos"
-#: ../js/ui/dash.js:283
+#: ../js/ui/appDisplay.js:1064
+msgid "Drag here to add favorites"
+msgstr "Arrastrar aquí para añadir a los favoritos"
+
+#: ../js/ui/dash.js:240
msgid "Find..."
msgstr "Buscar…"
-#: ../js/ui/dash.js:400
-msgid "More"
-msgstr "Más"
-
-#: ../js/ui/dash.js:543
-msgid "(see all)"
-msgstr "(ver todo)"
-
-#. **** Applications ****
-#: ../js/ui/dash.js:725 ../js/ui/dash.js:787
-msgid "APPLICATIONS"
-msgstr "APLICACIONES"
+#: ../js/ui/dash.js:437
+#| msgid "Search"
+msgid "Searching..."
+msgstr "Buscando…"
#. **** Places ****
#. Translators: This is in the sense of locations for documents,
#. network locations, etc.
-#: ../js/ui/dash.js:745
+#: ../js/ui/dash.js:872 ../js/ui/placeDisplay.js:471
msgid "PLACES"
msgstr "LUGARES"
#. **** Documents ****
-#: ../js/ui/dash.js:752 ../js/ui/dash.js:797
+#: ../js/ui/dash.js:879
msgid "RECENT DOCUMENTS"
msgstr "DOCUMENTOS RECIENTES"
-#. **** Search Results ****
-#: ../js/ui/dash.js:777 ../js/ui/dash.js:961
-msgid "SEARCH RESULTS"
-msgstr "RESULTADOS DE LA BÚSQUEDA"
-
-#: ../js/ui/dash.js:792
-msgid "PREFERENCES"
-msgstr "PREFERENCIAS"
-
#. Button on the left side of the panel.
#. Translators: If there is no suitable word for "Activities" in your language, you can use the word for "Overview".
-#: ../js/ui/panel.js:274
+#: ../js/ui/panel.js:227
msgid "Activities"
msgstr "Actividades"
#. Translators: This is a time format.
-#: ../js/ui/panel.js:491
+#: ../js/ui/panel.js:440
msgid "%a %l:%M %p"
msgstr "%a %H:%M"
-#: ../js/ui/places.js:178
+#: ../js/ui/placeDisplay.js:99
msgid "Connect to..."
msgstr "Conectar a…"
@@ -120,101 +108,49 @@ msgstr "Aplicaciones"
msgid "Recent Documents"
msgstr "Documentos recientes"
-#: ../src/shell-global.c:812
+#: ../src/shell-global.c:826
msgid "Less than a minute ago"
msgstr "Hace menos de un minuto"
-#: ../src/shell-global.c:815
+#: ../src/shell-global.c:829
#, c-format
msgid "%d minute ago"
msgid_plural "%d minutes ago"
msgstr[0] "Hace %d minuto"
msgstr[1] "Hace %d minutos"
-#: ../src/shell-global.c:818
+#: ../src/shell-global.c:832
#, c-format
msgid "%d hour ago"
msgid_plural "%d hours ago"
msgstr[0] "Hace %d hora"
msgstr[1] "Hace %d horas"
-#: ../src/shell-global.c:821
+#: ../src/shell-global.c:835
#, c-format
msgid "%d day ago"
msgid_plural "%d days ago"
msgstr[0] "Hace %d día"
msgstr[1] "Hace %d días"
-#: ../src/shell-global.c:824
+#: ../src/shell-global.c:838
#, c-format
msgid "%d week ago"
msgid_plural "%d weeks ago"
msgstr[0] "Hace %d semana"
msgstr[1] "Hace %d semanas"
-#: ../src/shell-status-menu.c:156
-msgid "Unknown"
-msgstr "Desconocido"
-
-#: ../src/shell-status-menu.c:212
-#, c-format
-msgid "Can't lock screen: %s"
-msgstr "No se puede bloquear la pantalla: %s"
-
-#: ../src/shell-status-menu.c:227
-#, c-format
-msgid "Can't temporarily set screensaver to blank screen: %s"
-msgstr ""
-"No se puede establecer temporalmente el salvapantallas a oscurecer pantalla: "
-"%s"
-
-#: ../src/shell-status-menu.c:351
-#, c-format
-msgid "Can't logout: %s"
-msgstr "No se puede salir de la sesión: %s"
-
-#: ../src/shell-status-menu.c:492
-msgid "Account Information..."
-msgstr "Información de la cuenta…"
-
-#: ../src/shell-status-menu.c:502
-msgid "Sidebar"
-msgstr "Barra lateral"
-
-#: ../src/shell-status-menu.c:510
-msgid "System Preferences..."
-msgstr "Preferencias del sistema…"
-
-#: ../src/shell-status-menu.c:525
-msgid "Lock Screen"
-msgstr "Bloquear la pantalla"
-
-#: ../src/shell-status-menu.c:535
-msgid "Switch User"
-msgstr "Cambiar de usuario"
-
-#. Only show switch user if there are other users
-#. Log Out
-#: ../src/shell-status-menu.c:546
-msgid "Log Out..."
-msgstr "Salir…"
-
-#. Shut down
-#: ../src/shell-status-menu.c:557
-msgid "Shut Down..."
-msgstr "Apagar…"
-
-#: ../src/shell-uri-util.c:87
+#: ../src/shell-uri-util.c:89
msgid "Home Folder"
msgstr "Carpeta personal"
#. Translators: this is the same string as the one found in
#. * nautilus
-#: ../src/shell-uri-util.c:102
+#: ../src/shell-uri-util.c:104
msgid "File System"
msgstr "Sistema de archivos"
-#: ../src/shell-uri-util.c:248
+#: ../src/shell-uri-util.c:250
msgid "Search"
msgstr "Buscar"
@@ -223,11 +159,58 @@ msgstr "Buscar"
#. * example, "Trash: some-directory". It means that the
#. * directory called "some-directory" is in the trash.
#.
-#: ../src/shell-uri-util.c:298
+#: ../src/shell-uri-util.c:300
#, c-format
msgid "%1$s: %2$s"
msgstr "%1$s: %2$s"
+#~ msgid "Frequent"
+#~ msgstr "Frecuentes"
+
+#~ msgid "More"
+#~ msgstr "Más"
+
+#~ msgid "(see all)"
+#~ msgstr "(ver todo)"
+
+#~ msgid "SEARCH RESULTS"
+#~ msgstr "RESULTADOS DE LA BÚSQUEDA"
+
+#~ msgid "Unknown"
+#~ msgstr "Desconocido"
+
+#~ msgid "Can't lock screen: %s"
+#~ msgstr "No se puede bloquear la pantalla: %s"
+
+#~ msgid "Can't temporarily set screensaver to blank screen: %s"
+#~ msgstr ""
+#~ "No se puede establecer temporalmente el salvapantallas a oscurecer "
+#~ "pantalla: %s"
+
+#~ msgid "Can't logout: %s"
+#~ msgstr "No se puede salir de la sesión: %s"
+
+#~ msgid "Account Information..."
+#~ msgstr "Información de la cuenta…"
+
+#~ msgid "Sidebar"
+#~ msgstr "Barra lateral"
+
+#~ msgid "System Preferences..."
+#~ msgstr "Preferencias del sistema…"
+
+#~ msgid "Lock Screen"
+#~ msgstr "Bloquear la pantalla"
+
+#~ msgid "Switch User"
+#~ msgstr "Cambiar de usuario"
+
+#~ msgid "Log Out..."
+#~ msgstr "Salir…"
+
+#~ msgid "Shut Down..."
+#~ msgstr "Apagar…"
+
#~ msgid "Browse"
#~ msgstr "Examine"
diff --git a/po/fr.po b/po/fr.po
index 4907b02c1..53809c2a4 100644
--- a/po/fr.po
+++ b/po/fr.po
@@ -6,13 +6,13 @@
#
msgid ""
msgstr ""
-"Project-Id-Version: HEAD\n"
+"Project-Id-Version: gnome-shell master fr\n"
"Report-Msgid-Bugs-To: http://bugzilla.gnome.org/enter_bug.cgi?product=gnome-"
"shell&component=general\n"
-"POT-Creation-Date: 2009-09-09 21:30+0000\n"
-"PO-Revision-Date: 2009-09-11 21:40+0200\n"
-"Last-Translator: Mathieu Bridon \n"
-"Language-Team: GNOME French Team\n"
+"POT-Creation-Date: 2009-11-13 17:44+0000\n"
+"PO-Revision-Date: 2009-12-05 16:43+0100\n"
+"Last-Translator: Pablo Martin-Gomez \n"
+"Language-Team: GNOME French Team \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
@@ -26,80 +26,115 @@ msgstr "GNOME Shell"
msgid "Window management and application launching"
msgstr "Gestion des fenêtres et lancement des applications"
-#. left side
-#: ../js/ui/panel.js:269
-msgid "Activities"
-msgstr "Activités"
+#: ../js/ui/appDisplay.js:696
+msgid "Drag here to add favorites"
+msgstr "Glisser ici pour ajouter aux favoris"
-#. Translators: This is a time format.
-#: ../js/ui/panel.js:452
-msgid "%a %l:%M %p"
-msgstr "%a %H:%M"
+#: ../js/ui/appIcon.js:425
+msgid "New Window"
+msgstr "Nouvelle fenêtre"
-#: ../js/ui/dash.js:255
+#: ../js/ui/appIcon.js:429
+msgid "Remove from Favorites"
+msgstr "Enlever des favoris"
+
+#: ../js/ui/appIcon.js:430
+msgid "Add to Favorites"
+msgstr "Ajouter aux favoris"
+
+#: ../js/ui/dash.js:237
msgid "Find..."
msgstr "Rechercher..."
-#: ../js/ui/dash.js:372
-msgid "Browse"
-msgstr "Parcourir"
-
-#: ../js/ui/dash.js:508
-msgid "(see all)"
-msgstr "(tout afficher)"
-
#. **** Applications ****
-#: ../js/ui/dash.js:700 ../js/ui/dash.js:756 ../js/ui/dash.js:887
+#: ../js/ui/dash.js:656 ../js/ui/dash.js:718
msgid "APPLICATIONS"
msgstr "APPLICATIONS"
#. **** Places ****
#. Translators: This is in the sense of locations for documents,
#. network locations, etc.
-#: ../js/ui/dash.js:720
+#: ../js/ui/dash.js:676 ../js/ui/dash.js:733
msgid "PLACES"
msgstr "RACCOURCIS"
#. **** Documents ****
-#: ../js/ui/dash.js:727 ../js/ui/dash.js:768 ../js/ui/dash.js:861
+#: ../js/ui/dash.js:683 ../js/ui/dash.js:728
msgid "RECENT DOCUMENTS"
msgstr "DOCUMENTS RÉCENTS"
#. **** Search Results ****
-#: ../js/ui/dash.js:746 ../js/ui/dash.js:850 ../js/ui/dash.js:876
+#: ../js/ui/dash.js:708 ../js/ui/dash.js:898
msgid "SEARCH RESULTS"
msgstr "RÉSULTATS DE LA RECHERCHE"
-#: ../js/ui/runDialog.js:90
+#: ../js/ui/dash.js:723
+msgid "PREFERENCES"
+msgstr "PRÉFÉRENCES"
+
+#. Button on the left side of the panel.
+#. Translators: If there is no suitable word for "Activities" in your language, you can use the word for "Overview".
+#: ../js/ui/panel.js:274
+msgid "Activities"
+msgstr "Activités"
+
+#. Translators: This is a time format.
+#: ../js/ui/panel.js:491
+msgid "%a %l:%M %p"
+msgstr "%a %H:%M"
+
+#: ../js/ui/placeDisplay.js:84
+msgid "Connect to..."
+msgstr "Connexion à..."
+
+#: ../js/ui/runDialog.js:96
msgid "Please enter a command:"
msgstr "Veuillez saisir une commande :"
-#: ../src/shell-global.c:799
+#: ../js/ui/runDialog.js:173
+#, c-format
+msgid "Execution of '%s' failed:"
+msgstr "Exécution de « %s » impossible :"
+
+#. Translators: This is a time format.
+#: ../js/ui/widget.js:163
+msgid "%H:%M"
+msgstr "%H:%M"
+
+#: ../js/ui/widget.js:317
+msgid "Applications"
+msgstr "Applications"
+
+#: ../js/ui/widget.js:339
+msgid "Recent Documents"
+msgstr "Documents récents"
+
+#: ../src/shell-global.c:821
msgid "Less than a minute ago"
msgstr "Il y a moins d'une minute"
-#: ../src/shell-global.c:802
+#: ../src/shell-global.c:824
#, c-format
msgid "%d minute ago"
msgid_plural "%d minutes ago"
msgstr[0] "Il y a %d minute"
msgstr[1] "Il y a %d minutes"
-#: ../src/shell-global.c:805
+#: ../src/shell-global.c:827
#, c-format
msgid "%d hour ago"
msgid_plural "%d hours ago"
msgstr[0] "Il y a %d heure"
msgstr[1] "Il y a %d heures"
-#: ../src/shell-global.c:808
+#: ../src/shell-global.c:830
#, c-format
msgid "%d day ago"
msgid_plural "%d days ago"
msgstr[0] "Il y a %d jour"
msgstr[1] "Il y a %d jours"
-#: ../src/shell-global.c:811
+#: ../src/shell-global.c:833
#, c-format
msgid "%d week ago"
msgid_plural "%d weeks ago"
@@ -180,3 +215,6 @@ msgstr "Recherche"
#, c-format
msgid "%1$s: %2$s"
msgstr "%1$s : %2$s"
+
+#~ msgid "Browse"
+#~ msgstr "Parcourir"
diff --git a/po/gl.po b/po/gl.po
index b792402cd..1c73af893 100644
--- a/po/gl.po
+++ b/po/gl.po
@@ -2,19 +2,22 @@
# Copyright (C) 2009 gnome-shell's COPYRIGHT HOLDER
# This file is distributed under the same license as the gnome-shell package.
# Fran Diéguez , 2009.
-#
+# Anton Meixome , 2009.
+# Antón Méixome , 2009.
msgid ""
msgstr ""
"Project-Id-Version: gnome-shell master\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2009-09-15 23:06+0200\n"
-"PO-Revision-Date: 2009-09-10 22:32+0100\n"
-"Last-Translator: Fran Diéguez \n"
-"Language-Team: Galician \n"
+"POT-Creation-Date: 2009-12-28 17:07+0100\n"
+"PO-Revision-Date: 2009-12-28 08:11+0100\n"
+"Last-Translator: Antón Méixome \n"
+"Language-Team: Galician Proxecto Trasno \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
+"Language: gl\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: Virtaal 0.4.0\n"
#: ../data/gnome-shell.desktop.in.in.h:1
msgid "GNOME Shell"
@@ -22,154 +25,133 @@ msgstr "GNOME Shell"
#: ../data/gnome-shell.desktop.in.in.h:2
msgid "Window management and application launching"
-msgstr "Xestor de xanelas e lanzado de aplicativos"
+msgstr "Xestor de xanelas e lanzamento de aplicativos"
-#. left side
-#: ../js/ui/panel.js:269
-msgid "Activities"
-msgstr "Actividades"
+#. **** Applications ****
+#: ../js/ui/appDisplay.js:252 ../js/ui/dash.js:858
+msgid "APPLICATIONS"
+msgstr "APLICATIVOS"
-#. Translators: This is a time format.
-#: ../js/ui/panel.js:452
-msgid "%a %l:%M %p"
-msgstr "%a %l:%M %p"
+#: ../js/ui/appDisplay.js:276
+msgid "PREFERENCES"
+msgstr "PREFERENCIAS"
-#: ../js/ui/dash.js:283
+#: ../js/ui/appDisplay.js:707 ../js/ui/appIcon.js:425
+msgid "New Window"
+msgstr "Xanela nova"
+
+#: ../js/ui/appDisplay.js:711 ../js/ui/appIcon.js:429
+msgid "Remove from Favorites"
+msgstr "Eliminar de Favoritos"
+
+#: ../js/ui/appDisplay.js:712 ../js/ui/appIcon.js:430
+msgid "Add to Favorites"
+msgstr "Engadir a Favoritos"
+
+#: ../js/ui/appDisplay.js:1064
+msgid "Drag here to add favorites"
+msgstr "Arrastra aquí para engadir favoritos"
+
+#: ../js/ui/dash.js:240
msgid "Find..."
msgstr "Buscar..."
-#: ../js/ui/dash.js:400
-msgid "Browse"
-msgstr "Explorar"
-
-#: ../js/ui/dash.js:536
-msgid "(see all)"
-msgstr "(ver todos)"
-
-#. **** Applications ****
-#: ../js/ui/dash.js:753 ../js/ui/dash.js:809
-msgid "APPLICATIONS"
-msgstr "APLICATIVOS"
+#: ../js/ui/dash.js:437
+msgid "Searching..."
+msgstr "Buscando..."
#. **** Places ****
#. Translators: This is in the sense of locations for documents,
#. network locations, etc.
-#: ../js/ui/dash.js:773
+#: ../js/ui/dash.js:878 ../js/ui/placeDisplay.js:519
msgid "PLACES"
msgstr "LUGARES"
#. **** Documents ****
-#: ../js/ui/dash.js:780 ../js/ui/dash.js:819
+#: ../js/ui/dash.js:885
msgid "RECENT DOCUMENTS"
msgstr "DOCUMENTOS RECENTES"
-#. **** Search Results ****
-#: ../js/ui/dash.js:799 ../js/ui/dash.js:931
-msgid "SEARCH RESULTS"
-msgstr "RESULTADOS DA BÚSQUEDA"
+#. Button on the left side of the panel.
+#. Translators: If there is no suitable word for "Activities" in your language, you can use the word for "Overview".
+#: ../js/ui/panel.js:227
+msgid "Activities"
+msgstr "Actividades"
-#: ../js/ui/dash.js:814
-msgid "PREFERENCES"
-msgstr ""
+#. Translators: This is a time format.
+#: ../js/ui/panel.js:440
+msgid "%a %l:%M %p"
+msgstr "%a %l:%M %p"
-#: ../js/ui/runDialog.js:96
+#: ../js/ui/placeDisplay.js:144
+msgid "Connect to..."
+msgstr "Conectar con..."
+
+#: ../js/ui/runDialog.js:235
msgid "Please enter a command:"
msgstr "Insira unha orde:"
-#: ../src/shell-global.c:799
-msgid "Less than a minute ago"
-msgstr "Menos de un minuto"
+#: ../js/ui/runDialog.js:351
+#, c-format
+msgid "Execution of '%s' failed:"
+msgstr "Fallou a execución de %s"
-#: ../src/shell-global.c:802
+#. Translators: This is a time format.
+#: ../js/ui/widget.js:163
+msgid "%H:%M"
+msgstr "%H:%M"
+
+#: ../js/ui/widget.js:317
+msgid "Applications"
+msgstr "Aplicativos"
+
+#: ../js/ui/widget.js:339
+msgid "Recent Documents"
+msgstr "Documentos recentes"
+
+#: ../src/shell-global.c:890
+msgid "Less than a minute ago"
+msgstr "Hai menos dun minuto"
+
+#: ../src/shell-global.c:893
#, c-format
msgid "%d minute ago"
msgid_plural "%d minutes ago"
-msgstr[0] "fai %d minuto"
-msgstr[1] "fai %d minutos"
+msgstr[0] "hai %d minuto"
+msgstr[1] "hai %d minutos"
-#: ../src/shell-global.c:805
+#: ../src/shell-global.c:896
#, c-format
msgid "%d hour ago"
msgid_plural "%d hours ago"
-msgstr[0] "fai %d hora"
-msgstr[1] "fai %d horas"
+msgstr[0] "hai %d hora"
+msgstr[1] "hai %d horas"
-#: ../src/shell-global.c:808
+#: ../src/shell-global.c:899
#, c-format
msgid "%d day ago"
msgid_plural "%d days ago"
-msgstr[0] "fai %d día"
-msgstr[1] "fai %d días"
+msgstr[0] "hai %d día"
+msgstr[1] "hai %d días"
-#: ../src/shell-global.c:811
+#: ../src/shell-global.c:902
#, c-format
msgid "%d week ago"
msgid_plural "%d weeks ago"
-msgstr[0] "fai %d semana"
-msgstr[1] "fai %d semanas"
+msgstr[0] "hai %d semana"
+msgstr[1] "hai %d semanas"
-#: ../src/shell-status-menu.c:156
-msgid "Unknown"
-msgstr "Descoñecido"
-
-#: ../src/shell-status-menu.c:212
-#, c-format
-msgid "Can't lock screen: %s"
-msgstr "Non foi posíbel bloquear a pantalla: %s"
-
-#: ../src/shell-status-menu.c:227
-#, c-format
-msgid "Can't temporarily set screensaver to blank screen: %s"
-msgstr ""
-"Non foi posíbel establecer o salvapantallas a unha pantalla en branco: %s"
-
-#: ../src/shell-status-menu.c:351
-#, c-format
-msgid "Can't logout: %s"
-msgstr "Non foi posíbel pechar a sesión: %s"
-
-#: ../src/shell-status-menu.c:492
-msgid "Account Information..."
-msgstr "Información da conta..."
-
-#: ../src/shell-status-menu.c:502
-msgid "Sidebar"
-msgstr "Barra lateral"
-
-#: ../src/shell-status-menu.c:510
-msgid "System Preferences..."
-msgstr "Preferenzas do sistema..."
-
-#: ../src/shell-status-menu.c:525
-msgid "Lock Screen"
-msgstr "Bloquear pantalla"
-
-#: ../src/shell-status-menu.c:535
-msgid "Switch User"
-msgstr "Cambiar de usuario"
-
-#. Only show switch user if there are other users
-#. Log Out
-#: ../src/shell-status-menu.c:546
-msgid "Log Out..."
-msgstr "Pechar sesión..."
-
-#. Shut down
-#: ../src/shell-status-menu.c:557
-msgid "Shut Down..."
-msgstr "Apagar..."
-
-#: ../src/shell-uri-util.c:87
+#: ../src/shell-uri-util.c:89
msgid "Home Folder"
msgstr "Cartafol persoal"
#. Translators: this is the same string as the one found in
#. * nautilus
-#: ../src/shell-uri-util.c:102
+#: ../src/shell-uri-util.c:104
msgid "File System"
msgstr "Sistema de ficheiros"
-#: ../src/shell-uri-util.c:248
+#: ../src/shell-uri-util.c:250
msgid "Search"
msgstr "Buscar"
@@ -178,11 +160,55 @@ msgstr "Buscar"
#. * example, "Trash: some-directory". It means that the
#. * directory called "some-directory" is in the trash.
#.
-#: ../src/shell-uri-util.c:298
+#: ../src/shell-uri-util.c:300
#, c-format
msgid "%1$s: %2$s"
msgstr "%1$s: %2$s"
+#~ msgid "SEARCH RESULTS"
+#~ msgstr "RESULTADOS DA BUSCA"
+
+#~ msgid "Unknown"
+#~ msgstr "Descoñecido"
+
+#~ msgid "Can't lock screen: %s"
+#~ msgstr "Non foi posíbel bloquear a pantalla: %s"
+
+#~ msgid "Can't temporarily set screensaver to blank screen: %s"
+#~ msgstr ""
+#~ "Non foi posíbel estabelecer temporalmente o salvapantallas a unha "
+#~ "pantalla en branco: %s"
+
+#~ msgid "Can't logout: %s"
+#~ msgstr "Non foi posíbel pechar a sesión: %s"
+
+#~ msgid "Account Information..."
+#~ msgstr "Información da conta..."
+
+#~ msgid "Sidebar"
+#~ msgstr "Barra lateral"
+
+#~ msgid "System Preferences..."
+#~ msgstr "Preferencias do sistema..."
+
+#~ msgid "Lock Screen"
+#~ msgstr "Bloquear pantalla"
+
+#~ msgid "Switch User"
+#~ msgstr "Cambiar de usuario"
+
+#~ msgid "Log Out..."
+#~ msgstr "Saír da sesión..."
+
+#~ msgid "Shut Down..."
+#~ msgstr "Apagar..."
+
+#~ msgid "Browse"
+#~ msgstr "Explorar"
+
+#~ msgid "(see all)"
+#~ msgstr "(ver todos)"
+
#~ msgid "Find apps or documents"
#~ msgstr "Atopar aplicativos ou documentos"
diff --git a/po/he.po b/po/he.po
new file mode 100644
index 000000000..0e9b9ed24
--- /dev/null
+++ b/po/he.po
@@ -0,0 +1,215 @@
+# Hebrew translation for gnome-shell.
+# Copyright (C) 2009 gnome-shell's COPYRIGHT HOLDER
+# This file is distributed under the same license as the gnome-shell package.
+# liel , 2009.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: gnome-shell master\n"
+"Report-Msgid-Bugs-To: http://bugzilla.gnome.org/enter_bug.cgi?product=gnome-"
+"shell&component=general\n"
+"POT-Creation-Date: 2009-11-13 17:44+0000\n"
+"PO-Revision-Date: 2009-11-28 17:33+0200\n"
+"Last-Translator: Liel Fridman \n"
+"Language-Team: Hebrew \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#: ../data/gnome-shell.desktop.in.in.h:1
+msgid "GNOME Shell"
+msgstr "מעטפת GNOME"
+
+#: ../data/gnome-shell.desktop.in.in.h:2
+msgid "Window management and application launching"
+msgstr "ניהול חלונות והרצת יישומים"
+
+#: ../js/ui/appDisplay.js:696
+msgid "Drag here to add favorites"
+msgstr "יש לגרור פריטים לכאן כדי להוסיף מועדפים"
+
+#: ../js/ui/appIcon.js:425
+msgid "New Window"
+msgstr "חלון חדש"
+
+#: ../js/ui/appIcon.js:429
+msgid "Remove from Favorites"
+msgstr "הסר מהמועדפים"
+
+#: ../js/ui/appIcon.js:430
+msgid "Add to Favorites"
+msgstr "הוסף למועדפים"
+
+#: ../js/ui/dash.js:237
+msgid "Find..."
+msgstr "חפש..."
+
+#. **** Applications ****
+#: ../js/ui/dash.js:656 ../js/ui/dash.js:718
+msgid "APPLICATIONS"
+msgstr "יישומים"
+
+#. **** Places ****
+#. Translators: This is in the sense of locations for documents,
+#. network locations, etc.
+#: ../js/ui/dash.js:676 ../js/ui/dash.js:733
+msgid "PLACES"
+msgstr "מקומות"
+
+#. **** Documents ****
+#: ../js/ui/dash.js:683 ../js/ui/dash.js:728
+msgid "RECENT DOCUMENTS"
+msgstr "מסמכים אחרונים"
+
+#. **** Search Results ****
+#: ../js/ui/dash.js:708 ../js/ui/dash.js:898
+msgid "SEARCH RESULTS"
+msgstr "תוצאות חיפוש"
+
+#: ../js/ui/dash.js:723
+msgid "PREFERENCES"
+msgstr "העדפות"
+
+#. Button on the left side of the panel.
+#. Translators: If there is no suitable word for "Activities" in your language, you can use the word for "Overview".
+#: ../js/ui/panel.js:274
+msgid "Activities"
+msgstr "פעילויות"
+
+#. Translators: This is a time format.
+#: ../js/ui/panel.js:491
+msgid "%a %l:%M %p"
+msgstr "%a %l:%M %p"
+
+#: ../js/ui/placeDisplay.js:84
+msgid "Connect to..."
+msgstr "התחבר אל..."
+
+#: ../js/ui/runDialog.js:96
+msgid "Please enter a command:"
+msgstr "נא להזין פקודה:"
+
+#: ../js/ui/runDialog.js:173
+#, c-format
+msgid "Execution of '%s' failed:"
+msgstr "ההרצה של '%s' נכשלה:"
+
+#. Translators: This is a time format.
+#: ../js/ui/widget.js:163
+msgid "%H:%M"
+msgstr "%H:%M"
+
+#: ../js/ui/widget.js:317
+msgid "Applications"
+msgstr "יישומים"
+
+#: ../js/ui/widget.js:339
+msgid "Recent Documents"
+msgstr "מסמכים אחרונים"
+
+#: ../src/shell-global.c:821
+msgid "Less than a minute ago"
+msgstr "לפני פחות מדקה"
+
+#: ../src/shell-global.c:824
+#, c-format
+msgid "%d minute ago"
+msgid_plural "%d minutes ago"
+msgstr[0] "לפני דקה"
+msgstr[1] "לפני %d דקות"
+
+#: ../src/shell-global.c:827
+#, c-format
+msgid "%d hour ago"
+msgid_plural "%d hours ago"
+msgstr[0] "לפני שעה"
+msgstr[1] "לפני %d שעות"
+
+#: ../src/shell-global.c:830
+#, c-format
+msgid "%d day ago"
+msgid_plural "%d days ago"
+msgstr[0] "לפני יום"
+msgstr[1] "לפני %d ימים"
+
+#: ../src/shell-global.c:833
+#, c-format
+msgid "%d week ago"
+msgid_plural "%d weeks ago"
+msgstr[0] "לפני שבוע"
+msgstr[1] "לפני %d שבועות"
+
+#: ../src/shell-status-menu.c:156
+msgid "Unknown"
+msgstr "לא ידוע"
+
+#: ../src/shell-status-menu.c:212
+#, c-format
+msgid "Can't lock screen: %s"
+msgstr "לא ניתן לנעול את המסך: %s"
+
+#: ../src/shell-status-menu.c:227
+#, c-format
+msgid "Can't temporarily set screensaver to blank screen: %s"
+msgstr "לא ניתן זמנית לקבוע שומר מסך כמסך שחור: %s"
+
+#: ../src/shell-status-menu.c:351
+#, c-format
+msgid "Can't logout: %s"
+msgstr "לא ניתן להתנתק: %s"
+
+#: ../src/shell-status-menu.c:492
+msgid "Account Information..."
+msgstr "מידע על המשתמש..."
+
+#: ../src/shell-status-menu.c:502
+msgid "Sidebar"
+msgstr "סרגל צד"
+
+#: ../src/shell-status-menu.c:510
+msgid "System Preferences..."
+msgstr "העדפות מערכת..."
+
+#: ../src/shell-status-menu.c:525
+msgid "Lock Screen"
+msgstr "נעילת המסך"
+
+#: ../src/shell-status-menu.c:535
+msgid "Switch User"
+msgstr "החלף משתמש"
+
+#. Only show switch user if there are other users
+#. Log Out
+#: ../src/shell-status-menu.c:546
+msgid "Log Out..."
+msgstr "ניתוק..."
+
+#. Shut down
+#: ../src/shell-status-menu.c:557
+msgid "Shut Down..."
+msgstr "כיבוי..."
+
+#: ../src/shell-uri-util.c:87
+msgid "Home Folder"
+msgstr "תיקיית הבית"
+
+#. Translators: this is the same string as the one found in
+#. * nautilus
+#: ../src/shell-uri-util.c:102
+msgid "File System"
+msgstr "מערכת הקבצים"
+
+#: ../src/shell-uri-util.c:248
+msgid "Search"
+msgstr "חפש"
+
+#. Translators: the first string is the name of a gvfs
+#. * method, and the second string is a path. For
+#. * example, "Trash: some-directory". It means that the
+#. * directory called "some-directory" is in the trash.
+#.
+#: ../src/shell-uri-util.c:298
+#, c-format
+msgid "%1$s: %2$s"
+msgstr "%1$s: %2$s"
diff --git a/po/it.po b/po/it.po
index ff145d7b7..c5e91d139 100644
--- a/po/it.po
+++ b/po/it.po
@@ -7,8 +7,8 @@ msgid ""
msgstr ""
"Project-Id-Version: gnome-shell\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2009-11-03 22:26+0100\n"
-"PO-Revision-Date: 2009-11-03 22:28+0100\n"
+"POT-Creation-Date: 2009-12-28 21:58+0100\n"
+"PO-Revision-Date: 2009-12-28 21:59+0100\n"
"Last-Translator: Milo Casagrande \n"
"Language-Team: Italian \n"
"MIME-Version: 1.0\n"
@@ -24,85 +24,72 @@ msgstr "GNOME Shell"
msgid "Window management and application launching"
msgstr "Gestione finestre e avvio applicazioni"
-#: ../js/ui/appDisplay.js:332
-msgid "Frequent"
-msgstr "Frequente"
+#. **** Applications ****
+#: ../js/ui/appDisplay.js:252 ../js/ui/dash.js:858
+msgid "APPLICATIONS"
+msgstr "Applicazioni"
-#: ../js/ui/appDisplay.js:867
-msgid "Drag here to add favorites"
-msgstr "Trascinare qui per aggiungere ai preferiti"
+#: ../js/ui/appDisplay.js:276
+msgid "PREFERENCES"
+msgstr "Preferenze"
-#: ../js/ui/appIcon.js:426
+#: ../js/ui/appDisplay.js:707 ../js/ui/appIcon.js:425
msgid "New Window"
msgstr "Nuova finestra"
-#: ../js/ui/appIcon.js:430
+#: ../js/ui/appDisplay.js:711 ../js/ui/appIcon.js:429
msgid "Remove from Favorites"
msgstr "Rimuovi dai preferiti"
-#: ../js/ui/appIcon.js:431
+#: ../js/ui/appDisplay.js:712 ../js/ui/appIcon.js:430
msgid "Add to Favorites"
msgstr "Aggiungi ai preferiti"
-#: ../js/ui/dash.js:283
+#: ../js/ui/appDisplay.js:1064
+msgid "Drag here to add favorites"
+msgstr "Trascinare qui per aggiungere ai preferiti"
+
+#: ../js/ui/dash.js:240
msgid "Find..."
msgstr "Trova..."
-#: ../js/ui/dash.js:400
-msgid "More"
-msgstr "Altro"
-
-#: ../js/ui/dash.js:543
-msgid "(see all)"
-msgstr "(vedi tutto)"
-
-#. **** Applications ****
-#: ../js/ui/dash.js:725 ../js/ui/dash.js:787
-msgid "APPLICATIONS"
-msgstr "Applicazioni"
+#: ../js/ui/dash.js:437
+msgid "Searching..."
+msgstr "Ricerca..."
#. **** Places ****
#. Translators: This is in the sense of locations for documents,
#. network locations, etc.
-#: ../js/ui/dash.js:745
+#: ../js/ui/dash.js:878 ../js/ui/placeDisplay.js:519
msgid "PLACES"
msgstr "Risorse"
#. **** Documents ****
-#: ../js/ui/dash.js:752 ../js/ui/dash.js:797
+#: ../js/ui/dash.js:885
msgid "RECENT DOCUMENTS"
msgstr "Documenti recenti"
-#. **** Search Results ****
-#: ../js/ui/dash.js:777 ../js/ui/dash.js:961
-msgid "SEARCH RESULTS"
-msgstr "Risultati ricerca"
-
-#: ../js/ui/dash.js:792
-msgid "PREFERENCES"
-msgstr "Preferenze"
-
#. Button on the left side of the panel.
#. Translators: If there is no suitable word for "Activities" in your language, you can use the word for "Overview".
-#: ../js/ui/panel.js:274
+#: ../js/ui/panel.js:227
msgid "Activities"
msgstr "Attività"
# (ndt) proviamo col k, se non funge, sappiamo il perché...
#. Translators: This is a time format.
-#: ../js/ui/panel.js:491
+#: ../js/ui/panel.js:440
msgid "%a %l:%M %p"
msgstr "%a %k.%M"
-#: ../js/ui/places.js:178
+#: ../js/ui/placeDisplay.js:144
msgid "Connect to..."
msgstr "Connetti a..."
-#: ../js/ui/runDialog.js:96
+#: ../js/ui/runDialog.js:235
msgid "Please enter a command:"
msgstr "Inserire un comando:"
-#: ../js/ui/runDialog.js:173
+#: ../js/ui/runDialog.js:351
#, c-format
msgid "Execution of '%s' failed:"
msgstr "Esecuzione di «%s» non riuscita:"
@@ -120,102 +107,49 @@ msgstr "Applicazioni"
msgid "Recent Documents"
msgstr "Documenti recenti"
-#: ../src/shell-global.c:821
+#: ../src/shell-global.c:890
msgid "Less than a minute ago"
msgstr "Meno di un minuto fa"
-#: ../src/shell-global.c:824
+#: ../src/shell-global.c:893
#, c-format
msgid "%d minute ago"
msgid_plural "%d minutes ago"
msgstr[0] "%d minuto fa"
msgstr[1] "%d minuti fa"
-#: ../src/shell-global.c:827
+#: ../src/shell-global.c:896
#, c-format
msgid "%d hour ago"
msgid_plural "%d hours ago"
msgstr[0] "%d ora fa"
msgstr[1] "%d ore fa"
-#: ../src/shell-global.c:830
+#: ../src/shell-global.c:899
#, c-format
msgid "%d day ago"
msgid_plural "%d days ago"
msgstr[0] "%d giorno fa"
msgstr[1] "%d giorni fa"
-#: ../src/shell-global.c:833
+#: ../src/shell-global.c:902
#, c-format
msgid "%d week ago"
msgid_plural "%d weeks ago"
msgstr[0] "%d settimana fa"
msgstr[1] "%d settimane fa"
-# (ndt) valutare se vada al femminile
-#: ../src/shell-status-menu.c:156
-msgid "Unknown"
-msgstr "Sconosciuto"
-
-#: ../src/shell-status-menu.c:212
-#, c-format
-msgid "Can't lock screen: %s"
-msgstr "Impossibile bloccare lo schermo: %s"
-
-#: ../src/shell-status-menu.c:227
-#, c-format
-msgid "Can't temporarily set screensaver to blank screen: %s"
-msgstr ""
-"Impossibile impostare temporaneamente il salva schermo a schermo nero: %s "
-
-#: ../src/shell-status-menu.c:351
-#, c-format
-msgid "Can't logout: %s"
-msgstr "Impossibile terminare la sessione: %s"
-
-#: ../src/shell-status-menu.c:492
-msgid "Account Information..."
-msgstr "Informazioni account..."
-
-#: ../src/shell-status-menu.c:502
-msgid "Sidebar"
-msgstr "Barra laterale"
-
-#: ../src/shell-status-menu.c:510
-msgid "System Preferences..."
-msgstr "Preferenze di sistema..."
-
-#: ../src/shell-status-menu.c:525
-msgid "Lock Screen"
-msgstr "Blocca schermo"
-
-#: ../src/shell-status-menu.c:535
-msgid "Switch User"
-msgstr "Cambia utente"
-
-#. Only show switch user if there are other users
-#. Log Out
-#: ../src/shell-status-menu.c:546
-msgid "Log Out..."
-msgstr "Termina sessione..."
-
-# (ndt) da valutare... pare che ora anche Windows usi 'Arresta...'...
-#. Shut down
-#: ../src/shell-status-menu.c:557
-msgid "Shut Down..."
-msgstr "Spegni..."
-
-#: ../src/shell-uri-util.c:87
+#: ../src/shell-uri-util.c:89
msgid "Home Folder"
msgstr "Cartella home"
#. Translators: this is the same string as the one found in
#. * nautilus
-#: ../src/shell-uri-util.c:102
+#: ../src/shell-uri-util.c:104
msgid "File System"
msgstr "File system"
-#: ../src/shell-uri-util.c:248
+#: ../src/shell-uri-util.c:250
msgid "Search"
msgstr "Cerca"
@@ -225,7 +159,55 @@ msgstr "Cerca"
#. * example, "Trash: some-directory". It means that the
#. * directory called "some-directory" is in the trash.
#.
-#: ../src/shell-uri-util.c:298
+#: ../src/shell-uri-util.c:300
#, c-format
msgid "%1$s: %2$s"
msgstr "%1$s: %2$s"
+
+#~ msgid "Frequent"
+#~ msgstr "Frequente"
+
+#~ msgid "More"
+#~ msgstr "Altro"
+
+#~ msgid "(see all)"
+#~ msgstr "(vedi tutto)"
+
+#~ msgid "SEARCH RESULTS"
+#~ msgstr "Risultati ricerca"
+
+# (ndt) valutare se vada al femminile
+#~ msgid "Unknown"
+#~ msgstr "Sconosciuto"
+
+#~ msgid "Can't lock screen: %s"
+#~ msgstr "Impossibile bloccare lo schermo: %s"
+
+#~ msgid "Can't temporarily set screensaver to blank screen: %s"
+#~ msgstr ""
+#~ "Impossibile impostare temporaneamente il salva schermo a schermo nero: %s "
+
+#~ msgid "Can't logout: %s"
+#~ msgstr "Impossibile terminare la sessione: %s"
+
+#~ msgid "Account Information..."
+#~ msgstr "Informazioni account..."
+
+#~ msgid "Sidebar"
+#~ msgstr "Barra laterale"
+
+#~ msgid "System Preferences..."
+#~ msgstr "Preferenze di sistema..."
+
+#~ msgid "Lock Screen"
+#~ msgstr "Blocca schermo"
+
+#~ msgid "Switch User"
+#~ msgstr "Cambia utente"
+
+#~ msgid "Log Out..."
+#~ msgstr "Termina sessione..."
+
+# (ndt) da valutare... pare che ora anche Windows usi 'Arresta...'...
+#~ msgid "Shut Down..."
+#~ msgstr "Spegni..."
diff --git a/po/sl.po b/po/sl.po
index e55ddadff..3a09dba6d 100644
--- a/po/sl.po
+++ b/po/sl.po
@@ -1,13 +1,15 @@
-# Slovenian translation for gnome-shell.
+# Slovenian translation of gnome-shell package.
+# Copyright (C) 2006 Free Software Foundation, Inc.
# This file is distributed under the same license as the gnome-shell package.
-# Matej Urbančič , 2009.
+#
+# Matej Urbančič , 2009 - 2010.
#
msgid ""
msgstr ""
"Project-Id-Version: gnome-shell master\n"
"Report-Msgid-Bugs-To: http://bugzilla.gnome.org/enter_bug.cgi?product=gnome-shell&component=general\n"
-"POT-Creation-Date: 2009-11-12 21:33+0000\n"
-"PO-Revision-Date: 2009-11-14 10:19+0100\n"
+"POT-Creation-Date: 2010-01-05 01:21+0000\n"
+"PO-Revision-Date: 2010-01-05 09:09+0100\n"
"Last-Translator: Matej Urbančič \n"
"Language-Team: Slovenian GNOME Translation Team \n"
"MIME-Version: 1.0\n"
@@ -27,76 +29,76 @@ msgstr "Gnome lupina"
msgid "Window management and application launching"
msgstr "Upravljanje oken in zaganjanje programov"
-#: ../js/ui/appDisplay.js:696
-msgid "Drag here to add favorites"
-msgstr "S potegom na to mesto se izbor doda med priljubljene"
+#. **** Applications ****
+#: ../js/ui/appDisplay.js:252
+#: ../js/ui/dash.js:858
+msgid "APPLICATIONS"
+msgstr "Programi"
+#: ../js/ui/appDisplay.js:276
+msgid "PREFERENCES"
+msgstr "Možnosti"
+
+#: ../js/ui/appDisplay.js:707
#: ../js/ui/appIcon.js:425
msgid "New Window"
msgstr "Novo okno"
+#: ../js/ui/appDisplay.js:711
#: ../js/ui/appIcon.js:429
msgid "Remove from Favorites"
msgstr "Odstrani iz priljubljenih"
+#: ../js/ui/appDisplay.js:712
#: ../js/ui/appIcon.js:430
msgid "Add to Favorites"
msgstr "Dodaj med priljubljene"
-#: ../js/ui/dash.js:237
-msgid "Find..."
-msgstr "Poišči ..."
+#: ../js/ui/appDisplay.js:1064
+msgid "Drag here to add favorites"
+msgstr "S potegom na to mesto se izbor doda med priljubljene"
-#. **** Applications ****
-#: ../js/ui/dash.js:656
-#: ../js/ui/dash.js:718
-msgid "APPLICATIONS"
-msgstr "Programi"
+#: ../js/ui/dash.js:240
+msgid "Find..."
+msgstr "Najdi ..."
+
+#: ../js/ui/dash.js:437
+msgid "Searching..."
+msgstr "Iskanje ..."
#. **** Places ****
#. Translators: This is in the sense of locations for documents,
#. network locations, etc.
-#: ../js/ui/dash.js:676
-#: ../js/ui/dash.js:733
+#: ../js/ui/dash.js:878
+#: ../js/ui/placeDisplay.js:519
msgid "PLACES"
msgstr "Mesta"
#. **** Documents ****
-#: ../js/ui/dash.js:683
-#: ../js/ui/dash.js:728
+#: ../js/ui/dash.js:885
msgid "RECENT DOCUMENTS"
msgstr "Nedavni dokumenti"
-#. **** Search Results ****
-#: ../js/ui/dash.js:708
-#: ../js/ui/dash.js:898
-msgid "SEARCH RESULTS"
-msgstr "Rezultati iskanja"
-
-#: ../js/ui/dash.js:723
-msgid "PREFERENCES"
-msgstr "Lastnosti"
-
#. Button on the left side of the panel.
#. Translators: If there is no suitable word for "Activities" in your language, you can use the word for "Overview".
-#: ../js/ui/panel.js:274
+#: ../js/ui/panel.js:227
msgid "Activities"
msgstr "Dejavnosti"
#. Translators: This is a time format.
-#: ../js/ui/panel.js:491
+#: ../js/ui/panel.js:440
msgid "%a %l:%M %p"
msgstr "%a, %H:%M"
-#: ../js/ui/placeDisplay.js:84
+#: ../js/ui/placeDisplay.js:144
msgid "Connect to..."
msgstr "Povezava z ..."
-#: ../js/ui/runDialog.js:96
+#: ../js/ui/runDialog.js:235
msgid "Please enter a command:"
msgstr "Vnos ukaza:"
-#: ../js/ui/runDialog.js:173
+#: ../js/ui/runDialog.js:351
#, c-format
msgid "Execution of '%s' failed:"
msgstr "Izvajanje '%s' je spodletelo:"
@@ -114,11 +116,11 @@ msgstr "Programi"
msgid "Recent Documents"
msgstr "Nedavni dokumenti"
-#: ../src/shell-global.c:821
+#: ../src/shell-global.c:890
msgid "Less than a minute ago"
msgstr "Pred manj kot eno minuto"
-#: ../src/shell-global.c:824
+#: ../src/shell-global.c:893
#, c-format
msgid "%d minute ago"
msgid_plural "%d minutes ago"
@@ -127,7 +129,7 @@ msgstr[1] "Pred %d minuto"
msgstr[2] "Pred %d minutama"
msgstr[3] "Pred %d minutami"
-#: ../src/shell-global.c:827
+#: ../src/shell-global.c:896
#, c-format
msgid "%d hour ago"
msgid_plural "%d hours ago"
@@ -136,7 +138,7 @@ msgstr[1] "Pred %d uro"
msgstr[2] "Pred %d urama"
msgstr[3] "Pred %d urami"
-#: ../src/shell-global.c:830
+#: ../src/shell-global.c:899
#, c-format
msgid "%d day ago"
msgid_plural "%d days ago"
@@ -145,7 +147,7 @@ msgstr[1] "Pred %d dnevom"
msgstr[2] "Pred %d dnevoma"
msgstr[3] "Pred %d dnevi"
-#: ../src/shell-global.c:833
+#: ../src/shell-global.c:902
#, c-format
msgid "%d week ago"
msgid_plural "%d weeks ago"
@@ -154,80 +156,55 @@ msgstr[1] "Pred %d tednom"
msgstr[2] "Pred %d tednoma"
msgstr[3] "Pred %d tedni"
-#: ../src/shell-status-menu.c:156
-msgid "Unknown"
-msgstr "Neznano"
-
-#: ../src/shell-status-menu.c:212
-#, c-format
-msgid "Can't lock screen: %s"
-msgstr "Ni mogoče zakleniti zaslona: %s"
-
-#: ../src/shell-status-menu.c:227
-#, c-format
-msgid "Can't temporarily set screensaver to blank screen: %s"
-msgstr "Ni mogoče začasno nastaviti črnega zaslona za ohranjevalnik zaslona: %s"
-
-#: ../src/shell-status-menu.c:351
-#, c-format
-msgid "Can't logout: %s"
-msgstr "Ni se mogoče odjaviti: %s"
-
-#: ../src/shell-status-menu.c:492
-msgid "Account Information..."
-msgstr "Podrobnosti računa ..."
-
-#: ../src/shell-status-menu.c:502
-msgid "Sidebar"
-msgstr "Stranska vrstica"
-
-#: ../src/shell-status-menu.c:510
-msgid "System Preferences..."
-msgstr "Sistemske lastnosti ..."
-
-#: ../src/shell-status-menu.c:525
-msgid "Lock Screen"
-msgstr "Zakleni zaslon"
-
-#: ../src/shell-status-menu.c:535
-msgid "Switch User"
-msgstr "Preklop uporabnika"
-
-#. Only show switch user if there are other users
-#. Log Out
-#: ../src/shell-status-menu.c:546
-msgid "Log Out..."
-msgstr "Odjava ..."
-
-#. Shut down
-#: ../src/shell-status-menu.c:557
-msgid "Shut Down..."
-msgstr "Izklopi ..."
-
-#: ../src/shell-uri-util.c:87
+#: ../src/shell-uri-util.c:89
msgid "Home Folder"
msgstr "Domača mapa"
#. Translators: this is the same string as the one found in
#. * nautilus
-#: ../src/shell-uri-util.c:102
+#: ../src/shell-uri-util.c:104
msgid "File System"
msgstr "Datotečni sistem"
-#: ../src/shell-uri-util.c:248
+#: ../src/shell-uri-util.c:250
msgid "Search"
-msgstr "Iskanje"
+msgstr "Poišči"
#. Translators: the first string is the name of a gvfs
#. * method, and the second string is a path. For
#. * example, "Trash: some-directory". It means that the
#. * directory called "some-directory" is in the trash.
#.
-#: ../src/shell-uri-util.c:298
+#: ../src/shell-uri-util.c:300
#, c-format
msgid "%1$s: %2$s"
msgstr "%1$s: %2$s"
+#~ msgid "SEARCH RESULTS"
+#~ msgstr "Rezultati iskanja"
+#~ msgid "Unknown"
+#~ msgstr "Neznano"
+#~ msgid "Can't lock screen: %s"
+#~ msgstr "Ni mogoče zakleniti zaslona: %s"
+#~ msgid "Can't temporarily set screensaver to blank screen: %s"
+#~ msgstr ""
+#~ "Ni mogoče začasno nastaviti črnega zaslona za ohranjevalnik zaslona: %s"
+#~ msgid "Can't logout: %s"
+#~ msgstr "Ni se mogoče odjaviti: %s"
+#~ msgid "Account Information..."
+#~ msgstr "Podrobnosti računa ..."
+#~ msgid "Sidebar"
+#~ msgstr "Stranska vrstica"
+#~ msgid "System Preferences..."
+#~ msgstr "Sistemske možnosti ..."
+#~ msgid "Lock Screen"
+#~ msgstr "Zakleni zaslon"
+#~ msgid "Switch User"
+#~ msgstr "Preklop uporabnika"
+#~ msgid "Log Out..."
+#~ msgstr "Odjava ..."
+#~ msgid "Shut Down..."
+#~ msgstr "Izklopi ..."
#~ msgid "Frequent"
#~ msgstr "Pogosto"
#~ msgid "More"
diff --git a/po/sv.po b/po/sv.po
index e115dc261..7f3d2c5f2 100644
--- a/po/sv.po
+++ b/po/sv.po
@@ -7,8 +7,8 @@ msgid ""
msgstr ""
"Project-Id-Version: gnome-shell\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2009-09-18 13:02+0200\n"
-"PO-Revision-Date: 2009-09-18 13:02+0100\n"
+"POT-Creation-Date: 2009-12-01 23:52+0100\n"
+"PO-Revision-Date: 2009-12-01 23:53+0100\n"
"Last-Translator: Daniel Nylander \n"
"Language-Team: Swedish \n"
"MIME-Version: 1.0\n"
@@ -24,154 +24,139 @@ msgstr "GNOME-skal"
msgid "Window management and application launching"
msgstr "Fönsterhantering och programstarter"
-#. left side
-#: ../js/ui/panel.js:269
-msgid "Activities"
-msgstr "Aktiviteter"
+#: ../js/ui/appDisplay.js:580
+#: ../js/ui/appIcon.js:425
+msgid "New Window"
+msgstr "Nytt fönster"
-#. Translators: This is a time format.
-#: ../js/ui/panel.js:452
-msgid "%a %l:%M %p"
-msgstr "%a %H.%M"
+#: ../js/ui/appDisplay.js:584
+#: ../js/ui/appIcon.js:429
+msgid "Remove from Favorites"
+msgstr "Ta bort från favoriter"
-#: ../js/ui/dash.js:283
+#: ../js/ui/appDisplay.js:585
+#: ../js/ui/appIcon.js:430
+msgid "Add to Favorites"
+msgstr "Lägg till som favorit"
+
+#: ../js/ui/appDisplay.js:1029
+msgid "Drag here to add favorites"
+msgstr "Dra hit för att lägga till favorit"
+
+#: ../js/ui/dash.js:236
msgid "Find..."
msgstr "Sök..."
-#: ../js/ui/dash.js:400
-msgid "Browse"
-msgstr "Bläddra"
-
-#: ../js/ui/dash.js:536
-msgid "(see all)"
-msgstr "(se alla)"
-
#. **** Applications ****
-#: ../js/ui/dash.js:753
-#: ../js/ui/dash.js:809
+#: ../js/ui/dash.js:620
+#: ../js/ui/dash.js:682
msgid "APPLICATIONS"
msgstr "PROGRAM"
#. **** Places ****
#. Translators: This is in the sense of locations for documents,
#. network locations, etc.
-#: ../js/ui/dash.js:773
+#: ../js/ui/dash.js:640
+#: ../js/ui/dash.js:697
msgid "PLACES"
msgstr "PLATSER"
#. **** Documents ****
-#: ../js/ui/dash.js:780
-#: ../js/ui/dash.js:819
+#: ../js/ui/dash.js:647
+#: ../js/ui/dash.js:692
msgid "RECENT DOCUMENTS"
msgstr "SENASTE DOKUMENT"
#. **** Search Results ****
-#: ../js/ui/dash.js:799
-#: ../js/ui/dash.js:931
+#: ../js/ui/dash.js:672
+#: ../js/ui/dash.js:862
msgid "SEARCH RESULTS"
msgstr "SÖKRESULTAT"
-#: ../js/ui/dash.js:814
+#: ../js/ui/dash.js:687
msgid "PREFERENCES"
msgstr "INSTÄLLNINGAR"
-#: ../js/ui/runDialog.js:101
+#. Button on the left side of the panel.
+#. Translators: If there is no suitable word for "Activities" in your language, you can use the word for "Overview".
+#: ../js/ui/panel.js:227
+msgid "Activities"
+msgstr "Aktiviteter"
+
+#. Translators: This is a time format.
+#: ../js/ui/panel.js:440
+msgid "%a %l:%M %p"
+msgstr "%a %H.%M"
+
+#: ../js/ui/placeDisplay.js:84
+msgid "Connect to..."
+msgstr "Anslut till..."
+
+#: ../js/ui/runDialog.js:96
msgid "Please enter a command:"
msgstr "Ange ett kommando:"
-#: ../src/shell-global.c:799
+#: ../js/ui/runDialog.js:173
+#, c-format
+msgid "Execution of '%s' failed:"
+msgstr "Körning av \"%s\" misslyckades:"
+
+#. Translators: This is a time format.
+#: ../js/ui/widget.js:163
+msgid "%H:%M"
+msgstr "%H.%M"
+
+#: ../js/ui/widget.js:317
+msgid "Applications"
+msgstr "Program"
+
+#: ../js/ui/widget.js:339
+msgid "Recent Documents"
+msgstr "Senaste dokument"
+
+#: ../src/shell-global.c:826
msgid "Less than a minute ago"
msgstr "Mindre än en minut sedan"
-#: ../src/shell-global.c:802
+#: ../src/shell-global.c:829
#, c-format
msgid "%d minute ago"
msgid_plural "%d minutes ago"
msgstr[0] "%d minut sedan"
msgstr[1] "%d minuter sedan"
-#: ../src/shell-global.c:805
+#: ../src/shell-global.c:832
#, c-format
msgid "%d hour ago"
msgid_plural "%d hours ago"
msgstr[0] "%d timme sedan"
msgstr[1] "%d timmar sedan"
-#: ../src/shell-global.c:808
+#: ../src/shell-global.c:835
#, c-format
msgid "%d day ago"
msgid_plural "%d days ago"
msgstr[0] "%d dag sedan"
msgstr[1] "%d dagar sedan"
-#: ../src/shell-global.c:811
+#: ../src/shell-global.c:838
#, c-format
msgid "%d week ago"
msgid_plural "%d weeks ago"
msgstr[0] "%d vecka sedan"
msgstr[1] "%d veckor sedan"
-#: ../src/shell-status-menu.c:156
-msgid "Unknown"
-msgstr "Okänt"
-
-#: ../src/shell-status-menu.c:212
-#, c-format
-msgid "Can't lock screen: %s"
-msgstr "Kan inte låsa skärmen: %s"
-
-#: ../src/shell-status-menu.c:227
-#, c-format
-msgid "Can't temporarily set screensaver to blank screen: %s"
-msgstr "Kan inte temporärt ställa in skärmsläckaren till blank skärm: %s"
-
-#: ../src/shell-status-menu.c:351
-#, c-format
-msgid "Can't logout: %s"
-msgstr "Kan inte logga ut: %s"
-
-#: ../src/shell-status-menu.c:492
-msgid "Account Information..."
-msgstr "Kontoinformation..."
-
-#: ../src/shell-status-menu.c:502
-msgid "Sidebar"
-msgstr "Sidopanel"
-
-#: ../src/shell-status-menu.c:510
-msgid "System Preferences..."
-msgstr "Systeminställningar..."
-
-#: ../src/shell-status-menu.c:525
-msgid "Lock Screen"
-msgstr "Lås skärmen"
-
-#: ../src/shell-status-menu.c:535
-msgid "Switch User"
-msgstr "Växla användare"
-
-#. Only show switch user if there are other users
-#. Log Out
-#: ../src/shell-status-menu.c:546
-msgid "Log Out..."
-msgstr "Logga ut..."
-
-#. Shut down
-#: ../src/shell-status-menu.c:557
-msgid "Shut Down..."
-msgstr "Stäng av..."
-
-#: ../src/shell-uri-util.c:87
+#: ../src/shell-uri-util.c:89
msgid "Home Folder"
msgstr "Hemmapp"
#. Translators: this is the same string as the one found in
#. * nautilus
-#: ../src/shell-uri-util.c:102
+#: ../src/shell-uri-util.c:104
msgid "File System"
msgstr "Filsystem"
-#: ../src/shell-uri-util.c:248
+#: ../src/shell-uri-util.c:250
msgid "Search"
msgstr "Sök"
@@ -180,11 +165,37 @@ msgstr "Sök"
#. * example, "Trash: some-directory". It means that the
#. * directory called "some-directory" is in the trash.
#.
-#: ../src/shell-uri-util.c:298
+#: ../src/shell-uri-util.c:300
#, c-format
msgid "%1$s: %2$s"
msgstr "%1$s: %2$s"
+#~ msgid "(see all)"
+#~ msgstr "(se alla)"
+#~ msgid "Unknown"
+#~ msgstr "Okänt"
+#~ msgid "Can't lock screen: %s"
+#~ msgstr "Kan inte låsa skärmen: %s"
+#~ msgid "Can't temporarily set screensaver to blank screen: %s"
+#~ msgstr "Kan inte temporärt ställa in skärmsläckaren till blank skärm: %s"
+#~ msgid "Can't logout: %s"
+#~ msgstr "Kan inte logga ut: %s"
+#~ msgid "Account Information..."
+#~ msgstr "Kontoinformation..."
+#~ msgid "Sidebar"
+#~ msgstr "Sidopanel"
+#~ msgid "System Preferences..."
+#~ msgstr "Systeminställningar..."
+#~ msgid "Lock Screen"
+#~ msgstr "Lås skärmen"
+#~ msgid "Switch User"
+#~ msgstr "Växla användare"
+#~ msgid "Log Out..."
+#~ msgstr "Logga ut..."
+#~ msgid "Shut Down..."
+#~ msgstr "Stäng av..."
+#~ msgid "Browse"
+#~ msgstr "Bläddra"
#~ msgid "Find apps or documents"
#~ msgstr "Hitta program eller dokument"
#~ msgid "DOCUMENTS"
diff --git a/src/Makefile-st.am b/src/Makefile-st.am
index a6a85f12e..bd7d15bbb 100644
--- a/src/Makefile-st.am
+++ b/src/Makefile-st.am
@@ -78,6 +78,7 @@ st_source_h = \
st/st-entry.h \
st/st-im-text.h \
st/st-label.h \
+ st/st-overflow-box.h \
st/st-private.h \
st/st-scrollable.h \
st/st-scroll-bar.h \
@@ -116,6 +117,7 @@ st_source_c = \
st/st-entry.c \
st/st-im-text.c \
st/st-label.c \
+ st/st-overflow-box.c \
st/st-private.c \
st/st-scrollable.c \
st/st-scroll-bar.c \
diff --git a/src/Makefile.am b/src/Makefile.am
index 29c36cb80..0b9ea5e9d 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -63,6 +63,8 @@ libgnome_shell_la_SOURCES = \
shell-app-usage.h \
shell-arrow.c \
shell-arrow.h \
+ shell-doc-system.c \
+ shell-doc-system.h \
shell-drawing.c \
shell-drawing.h \
shell-embedded-window.c \
diff --git a/src/gnome-shell.in b/src/gnome-shell.in
old mode 100755
new mode 100644
index b009ec495..41354ad0a
--- a/src/gnome-shell.in
+++ b/src/gnome-shell.in
@@ -212,6 +212,8 @@ parser.add_option("-w", "--wide", action="store_true",
help="Use widescreen (1280x800) with Xephyr")
parser.add_option("", "--eval-file", metavar="EVAL_FILE",
help="Evaluate the contents of the given JavaScript file")
+parser.add_option("", "--create-extension", action="store_true",
+ help="Create a new GNOME Shell extension")
options, args = parser.parse_args()
@@ -219,6 +221,91 @@ if args:
parser.print_usage()
sys.exit(1)
+if options.create_extension:
+ import json
+
+ print
+ print '''Name should be a very short (ideally descriptive) string.
+Examples are: "Click To Focus", "Adblock", "Shell Window Shrinker".
+'''
+ name = raw_input('Name: ').strip()
+ print
+ print '''Description is a single-sentence explanation of what your extension does.
+Examples are: "Make windows visible on click", "Block advertisement popups"
+ "Animate windows shrinking on minimize"
+'''
+ description = raw_input('Description: ').strip()
+ underifier = re.compile('[^A-Za-z]')
+ sample_uuid = underifier.sub('_', name)
+ # TODO use evolution data server
+ hostname = subprocess.Popen(['hostname'], stdout=subprocess.PIPE).communicate()[0].strip()
+ sample_uuid = sample_uuid + '@' + hostname
+
+ print
+ print '''Uuid is a globally-unique identifier for your extension.
+This should be in the format of an email address (foo.bar@extensions.example.com), but
+need not be an actual email address, though it's a good idea to base the uuid on your
+email address. For example, if your email address is janedoe@example.com, you might
+use an extension title clicktofocus@janedoe.example.com.'''
+ uuid = raw_input('Uuid [%s]: ' % (sample_uuid, )).strip()
+ if uuid == '':
+ uuid = sample_uuid
+
+ extension_path = os.path.join(os.path.expanduser('~/.config'), 'gnome-shell', 'extensions', uuid)
+ if os.path.exists(extension_path):
+ print "Extension path %r already exists" % (extension_path, )
+ sys.exit(0)
+ os.makedirs(extension_path)
+ meta = { 'name': name,
+ 'description': description,
+ 'uuid': uuid }
+ f = open(os.path.join(extension_path, 'metadata.json'), 'w')
+ try:
+ json.dump(meta, f)
+ except AttributeError:
+ # For Python versions older than 2.6, try using the json-py module
+ f.write(json.write(meta) + '\n')
+ f.close()
+
+ extensionjs_path = os.path.join(extension_path, 'extension.js')
+ f = open(extensionjs_path, 'w')
+ f.write('''// Sample extension code, makes clicking on the panel show a message
+const St = imports.gi.St;
+const Mainloop = imports.mainloop;
+
+const Main = imports.ui.main;
+
+function _showHello() {
+ let text = new St.Label({ style_class: 'helloworld-label', text: "Hello, world!" });
+ let monitor = global.get_primary_monitor();
+ global.stage.add_actor(text);
+ text.set_position(Math.floor (monitor.width / 2 - text.width / 2), Math.floor(monitor.height / 2 - text.height / 2));
+ Mainloop.timeout_add(3000, function () { text.destroy(); });
+}
+
+// Put your extension initialization code here
+function main() {
+ Main.panel.actor.reactive = true;
+ Main.panel.actor.connect('button-release-event', _showHello);
+}
+''')
+ f.close()
+
+ f = open(os.path.join(extension_path, 'stylesheet.css'), 'w')
+ f.write('''/* Example stylesheet */
+.helloworld-label {
+ font-size: 36px;
+ font-weight: bold;
+ color: #ffffff;
+ background-color: rgba(10,10,10,0.7);
+ border-radius: 5px;
+}
+''')
+ f.close()
+
+ subprocess.Popen(['gnome-open', extensionjs_path])
+ sys.exit(0)
+
if options.eval_file:
import dbus
diff --git a/src/shell-app-system.c b/src/shell-app-system.c
index 0f61576d5..ab609ec1f 100644
--- a/src/shell-app-system.c
+++ b/src/shell-app-system.c
@@ -48,9 +48,7 @@ struct _ShellAppSystemPrivate {
GHashTable *app_id_to_info;
GHashTable *app_id_to_app;
- GHashTable *cached_menu_contents; /* > */
- GSList *cached_app_menus; /* ShellAppMenuEntry */
-
+ GSList *cached_flattened_apps; /* ShellAppInfo */
GSList *cached_settings; /* ShellAppInfo */
gint app_monitor_id;
@@ -58,7 +56,6 @@ struct _ShellAppSystemPrivate {
guint app_change_timeout_id;
};
-static void free_appinfo_gslist (gpointer list);
static void shell_app_system_finalize (GObject *object);
static gboolean on_tree_changed (gpointer user_data);
static void on_tree_changed_cb (GMenuTree *tree, gpointer user_data);
@@ -83,6 +80,10 @@ struct _ShellAppInfo {
*/
guint refcount;
+ char *casefolded_name;
+ char *name_collation_key;
+ char *casefolded_description;
+
GMenuTreeItem *entry;
GKeyFile *keyfile;
@@ -104,6 +105,11 @@ shell_app_info_unref (ShellAppInfo *info)
{
if (--info->refcount > 0)
return;
+
+ g_free (info->casefolded_name);
+ g_free (info->name_collation_key);
+ g_free (info->casefolded_description);
+
switch (info->type)
{
case SHELL_APP_INFO_TYPE_ENTRY:
@@ -129,7 +135,7 @@ shell_app_info_new_from_tree_item (GMenuTreeItem *item)
if (!item)
return NULL;
- info = g_slice_alloc (sizeof (ShellAppInfo));
+ info = g_slice_alloc0 (sizeof (ShellAppInfo));
info->type = SHELL_APP_INFO_TYPE_ENTRY;
info->refcount = 1;
info->entry = gmenu_tree_item_ref (item);
@@ -141,7 +147,7 @@ shell_app_info_new_from_window (MetaWindow *window)
{
ShellAppInfo *info;
- info = g_slice_alloc (sizeof (ShellAppInfo));
+ info = g_slice_alloc0 (sizeof (ShellAppInfo));
info->type = SHELL_APP_INFO_TYPE_WINDOW;
info->refcount = 1;
info->window = g_object_ref (window);
@@ -159,7 +165,7 @@ shell_app_info_new_from_keyfile_take_ownership (GKeyFile *keyfile,
{
ShellAppInfo *info;
- info = g_slice_alloc (sizeof (ShellAppInfo));
+ info = g_slice_alloc0 (sizeof (ShellAppInfo));
info->type = SHELL_APP_INFO_TYPE_DESKTOP_FILE;
info->refcount = 1;
info->keyfile = keyfile;
@@ -167,29 +173,6 @@ shell_app_info_new_from_keyfile_take_ownership (GKeyFile *keyfile,
return info;
}
-static gpointer
-shell_app_menu_entry_copy (gpointer entryp)
-{
- ShellAppMenuEntry *entry;
- ShellAppMenuEntry *copy;
- entry = entryp;
- copy = g_new0 (ShellAppMenuEntry, 1);
- copy->name = g_strdup (entry->name);
- copy->id = g_strdup (entry->id);
- copy->icon = g_strdup (entry->icon);
- return copy;
-}
-
-static void
-shell_app_menu_entry_free (gpointer entryp)
-{
- ShellAppMenuEntry *entry = entryp;
- g_free (entry->name);
- g_free (entry->id);
- g_free (entry->icon);
- g_free (entry);
-}
-
static void shell_app_system_class_init(ShellAppSystemClass *klass)
{
GObjectClass *gobject_class = (GObjectClass *)klass;
@@ -225,9 +208,6 @@ shell_app_system_init (ShellAppSystem *self)
/* Key is owned by info */
priv->app_id_to_app = g_hash_table_new (g_str_hash, g_str_equal);
- priv->cached_menu_contents = g_hash_table_new_full (g_str_hash, g_str_equal,
- g_free, free_appinfo_gslist);
-
/* For now, we want to pick up Evince, Nautilus, etc. We'll
* handle NODISPLAY semantics at a higher level or investigate them
* case by case.
@@ -257,15 +237,12 @@ shell_app_system_finalize (GObject *object)
gmenu_tree_unref (priv->apps_tree);
gmenu_tree_unref (priv->settings_tree);
- g_hash_table_destroy (priv->cached_menu_contents);
-
g_hash_table_destroy (priv->app_id_to_info);
g_hash_table_destroy (priv->app_id_to_app);
- g_slist_foreach (priv->cached_app_menus, (GFunc)shell_app_menu_entry_free, NULL);
- g_slist_free (priv->cached_app_menus);
- priv->cached_app_menus = NULL;
-
+ g_slist_foreach (priv->cached_flattened_apps, (GFunc)shell_app_info_unref, NULL);
+ g_slist_free (priv->cached_flattened_apps);
+ priv->cached_flattened_apps = NULL;
g_slist_foreach (priv->cached_settings, (GFunc)shell_app_info_unref, NULL);
g_slist_free (priv->cached_settings);
priv->cached_settings = NULL;
@@ -273,60 +250,10 @@ shell_app_system_finalize (GObject *object)
G_OBJECT_CLASS (shell_app_system_parent_class)->finalize(object);
}
-static void
-free_appinfo_gslist (gpointer listp)
-{
- GSList *list = listp;
- g_slist_foreach (list, (GFunc) shell_app_info_unref, NULL);
- g_slist_free (list);
-}
-
-static void
-reread_directories (ShellAppSystem *self, GSList **cache, GMenuTree *tree)
-{
- GMenuTreeDirectory *trunk;
- GSList *entries;
- GSList *iter;
-
- trunk = gmenu_tree_get_root_directory (tree);
- entries = gmenu_tree_directory_get_contents (trunk);
-
- g_slist_foreach (*cache, (GFunc)shell_app_menu_entry_free, NULL);
- g_slist_free (*cache);
- *cache = NULL;
-
- for (iter = entries; iter; iter = iter->next)
- {
- GMenuTreeItem *item = iter->data;
-
- switch (gmenu_tree_item_get_type (item))
- {
- case GMENU_TREE_ITEM_DIRECTORY:
- {
- GMenuTreeDirectory *dir = iter->data;
- ShellAppMenuEntry *shell_entry = g_new0 (ShellAppMenuEntry, 1);
- shell_entry->name = g_strdup (gmenu_tree_directory_get_name (dir));
- shell_entry->id = g_strdup (gmenu_tree_directory_get_menu_id (dir));
- shell_entry->icon = g_strdup (gmenu_tree_directory_get_icon (dir));
-
- *cache = g_slist_prepend (*cache, shell_entry);
- }
- break;
- default:
- break;
- }
-
- gmenu_tree_item_unref (item);
- }
- *cache = g_slist_reverse (*cache);
-
- g_slist_free (entries);
- gmenu_tree_item_unref (trunk);
-}
-
static GSList *
gather_entries_recurse (ShellAppSystem *monitor,
GSList *apps,
+ GHashTable *unique,
GMenuTreeDirectory *root)
{
GSList *contents;
@@ -342,13 +269,17 @@ gather_entries_recurse (ShellAppSystem *monitor,
case GMENU_TREE_ITEM_ENTRY:
{
ShellAppInfo *app = shell_app_info_new_from_tree_item (item);
- apps = g_slist_prepend (apps, app);
+ if (!g_hash_table_lookup (unique, shell_app_info_get_id (app)))
+ {
+ apps = g_slist_prepend (apps, app);
+ g_hash_table_insert (unique, (char*)shell_app_info_get_id (app), app);
+ }
}
break;
case GMENU_TREE_ITEM_DIRECTORY:
{
GMenuTreeDirectory *dir = (GMenuTreeDirectory*)item;
- apps = gather_entries_recurse (monitor, apps, dir);
+ apps = gather_entries_recurse (monitor, apps, unique, dir);
}
break;
default:
@@ -365,6 +296,7 @@ gather_entries_recurse (ShellAppSystem *monitor,
static void
reread_entries (ShellAppSystem *self,
GSList **cache,
+ GHashTable *unique,
GMenuTree *tree)
{
GMenuTreeDirectory *trunk;
@@ -375,46 +307,40 @@ reread_entries (ShellAppSystem *self,
g_slist_free (*cache);
*cache = NULL;
- *cache = gather_entries_recurse (self, *cache, trunk);
+ *cache = gather_entries_recurse (self, *cache, unique, trunk);
gmenu_tree_item_unref (trunk);
}
static void
-cache_by_id (ShellAppSystem *self, GSList *apps, gboolean ref)
+cache_by_id (ShellAppSystem *self, GSList *apps)
{
GSList *iter;
for (iter = apps; iter; iter = iter->next)
{
ShellAppInfo *info = iter->data;
- if (ref)
- shell_app_info_ref (info);
+ shell_app_info_ref (info);
/* the name is owned by the info itself */
- g_hash_table_insert (self->priv->app_id_to_info, (char*)shell_app_info_get_id (info),
- info);
+ g_hash_table_replace (self->priv->app_id_to_info, (char*)shell_app_info_get_id (info),
+ info);
}
}
static void
reread_menus (ShellAppSystem *self)
{
- GSList *apps;
- GMenuTreeDirectory *trunk;
+ GHashTable *unique = g_hash_table_new (g_str_hash, g_str_equal);
- reread_directories (self, &(self->priv->cached_app_menus), self->priv->apps_tree);
+ reread_entries (self, &(self->priv->cached_flattened_apps), unique, self->priv->apps_tree);
+ g_hash_table_remove_all (unique);
+ reread_entries (self, &(self->priv->cached_settings), unique, self->priv->settings_tree);
+ g_hash_table_destroy (unique);
- reread_entries (self, &(self->priv->cached_settings), self->priv->settings_tree);
-
- /* Now loop over applications.menu and settings.menu, inserting each by desktop file
- * ID into a hash */
g_hash_table_remove_all (self->priv->app_id_to_info);
- trunk = gmenu_tree_get_root_directory (self->priv->apps_tree);
- apps = gather_entries_recurse (self, NULL, trunk);
- gmenu_tree_item_unref (trunk);
- cache_by_id (self, apps, FALSE);
- g_slist_free (apps);
- cache_by_id (self, self->priv->cached_settings, TRUE);
+
+ cache_by_id (self, self->priv->cached_flattened_apps);
+ cache_by_id (self, self->priv->cached_settings);
}
static gboolean
@@ -423,7 +349,6 @@ on_tree_changed (gpointer user_data)
ShellAppSystem *self = SHELL_APP_SYSTEM (user_data);
reread_menus (self);
- g_hash_table_remove_all (self->priv->cached_menu_contents);
g_signal_emit (self, signals[INSTALLED_CHANGED], 0);
@@ -469,21 +394,8 @@ shell_app_info_get_type (void)
return gtype;
}
-GType
-shell_app_menu_entry_get_type (void)
-{
- static GType gtype = G_TYPE_INVALID;
- if (gtype == G_TYPE_INVALID)
- {
- gtype = g_boxed_type_register_static ("ShellAppMenuEntry",
- shell_app_menu_entry_copy,
- shell_app_menu_entry_free);
- }
- return gtype;
-}
-
/**
- * shell_app_system_get_applications_for_menu:
+ * shell_app_system_get_flattened_apps:
*
* Traverses a toplevel menu, and returns all items under it. Nested items
* are flattened. This value is computed on initial call and cached thereafter
@@ -492,41 +404,9 @@ shell_app_menu_entry_get_type (void)
* Return value: (transfer none) (element-type ShellAppInfo): List of applications
*/
GSList *
-shell_app_system_get_applications_for_menu (ShellAppSystem *self,
- const char *menu)
+shell_app_system_get_flattened_apps (ShellAppSystem *self)
{
- GSList *apps;
-
- apps = g_hash_table_lookup (self->priv->cached_menu_contents, menu);
- if (!apps)
- {
- char *path;
- GMenuTreeDirectory *menu_entry;
- path = g_strdup_printf ("/%s", menu);
- menu_entry = gmenu_tree_get_directory_from_path (self->priv->apps_tree, path);
- g_free (path);
- g_assert (menu_entry != NULL);
-
- apps = gather_entries_recurse (self, NULL, menu_entry);
- g_hash_table_insert (self->priv->cached_menu_contents, g_strdup (menu), apps);
-
- gmenu_tree_item_unref (menu_entry);
- }
-
- return apps;
-}
-
-/**
- * shell_app_system_get_menus:
- *
- * Returns a list of toplevel #ShellAppMenuEntry items
- *
- * Return value: (transfer none) (element-type AppMenuEntry): List of toplevel menus
- */
-GSList *
-shell_app_system_get_menus (ShellAppSystem *monitor)
-{
- return monitor->priv->cached_app_menus;
+ return self->priv->cached_flattened_apps;
}
/**
@@ -711,6 +591,253 @@ shell_app_system_lookup_heuristic_basename (ShellAppSystem *system,
return NULL;
}
+typedef enum {
+ MATCH_NONE,
+ MATCH_MULTIPLE, /* Matches multiple terms */
+ MATCH_PREFIX, /* Strict prefix */
+ MATCH_SUBSTRING /* Not prefix, substring */
+} ShellAppInfoSearchMatch;
+
+static char *
+normalize_and_casefold (const char *str)
+{
+ char *normalized, *result;
+
+ if (str == NULL)
+ return NULL;
+
+ normalized = g_utf8_normalize (str, -1, G_NORMALIZE_ALL);
+ result = g_utf8_casefold (normalized, -1);
+ g_free (normalized);
+ return result;
+}
+
+static void
+shell_app_info_init_search_data (ShellAppInfo *info)
+{
+ const char *name;
+ const char *comment;
+
+ g_assert (info->type == SHELL_APP_INFO_TYPE_ENTRY);
+
+ name = gmenu_tree_entry_get_name ((GMenuTreeEntry*)info->entry);
+ info->casefolded_name = normalize_and_casefold (name);
+
+ comment = gmenu_tree_entry_get_comment ((GMenuTreeEntry*)info->entry);
+ info->casefolded_description = normalize_and_casefold (comment);
+}
+
+static ShellAppInfoSearchMatch
+shell_app_info_match_terms (ShellAppInfo *info,
+ GSList *terms)
+{
+ GSList *iter;
+ ShellAppInfoSearchMatch match;
+
+ if (G_UNLIKELY(!info->casefolded_name))
+ shell_app_info_init_search_data (info);
+
+ match = MATCH_NONE;
+ for (iter = terms; iter; iter = iter->next)
+ {
+ const char *term = iter->data;
+ const char *p;
+
+ p = strstr (info->casefolded_name, term);
+ if (p == info->casefolded_name)
+ {
+ if (match != MATCH_NONE)
+ return MATCH_MULTIPLE;
+ else
+ match = MATCH_PREFIX;
+ }
+ else if (p != NULL)
+ match = MATCH_SUBSTRING;
+
+ if (!info->casefolded_description)
+ continue;
+ p = strstr (info->casefolded_description, term);
+ if (p != NULL)
+ match = MATCH_SUBSTRING;
+ }
+ return match;
+}
+
+static gint
+shell_app_info_compare (gconstpointer a,
+ gconstpointer b,
+ gpointer data)
+{
+ ShellAppSystem *system = data;
+ const char *id_a = a;
+ const char *id_b = b;
+ ShellAppInfo *info_a = g_hash_table_lookup (system->priv->app_id_to_info, id_a);
+ ShellAppInfo *info_b = g_hash_table_lookup (system->priv->app_id_to_info, id_b);
+
+ if (!info_a->name_collation_key)
+ info_a->name_collation_key = g_utf8_collate_key (gmenu_tree_entry_get_name ((GMenuTreeEntry*)info_a->entry), -1);
+ if (!info_b->name_collation_key)
+ info_b->name_collation_key = g_utf8_collate_key (gmenu_tree_entry_get_name ((GMenuTreeEntry*)info_b->entry), -1);
+
+ return strcmp (info_a->name_collation_key, info_b->name_collation_key);
+}
+
+static GSList *
+sort_and_concat_results (ShellAppSystem *system,
+ GSList *multiple_matches,
+ GSList *prefix_matches,
+ GSList *substring_matches)
+{
+ multiple_matches = g_slist_sort_with_data (multiple_matches, shell_app_info_compare, system);
+ prefix_matches = g_slist_sort_with_data (prefix_matches, shell_app_info_compare, system);
+ substring_matches = g_slist_sort_with_data (substring_matches, shell_app_info_compare, system);
+ return g_slist_concat (multiple_matches, g_slist_concat (prefix_matches, substring_matches));
+}
+
+/**
+ * normalize_terms:
+ * @terms: (element-type utf8): Input search terms
+ *
+ * Returns: (element-type utf8) (transfer full): Unicode-normalized and lowercased terms
+ */
+static GSList *
+normalize_terms (GSList *terms)
+{
+ GSList *normalized_terms = NULL;
+ GSList *iter;
+ for (iter = terms; iter; iter = iter->next)
+ {
+ const char *term = iter->data;
+ normalized_terms = g_slist_prepend (normalized_terms, normalize_and_casefold (term));
+ }
+ return normalized_terms;
+}
+
+static inline void
+shell_app_system_do_match (ShellAppSystem *system,
+ ShellAppInfo *info,
+ GSList *terms,
+ GSList **multiple_results,
+ GSList **prefix_results,
+ GSList **substring_results)
+{
+ const char *id = shell_app_info_get_id (info);
+ ShellAppInfoSearchMatch match;
+
+ if (shell_app_info_get_is_nodisplay (info))
+ return;
+
+ match = shell_app_info_match_terms (info, terms);
+ switch (match)
+ {
+ case MATCH_NONE:
+ break;
+ case MATCH_MULTIPLE:
+ *multiple_results = g_slist_prepend (*multiple_results, (char *) id);
+ break;
+ case MATCH_PREFIX:
+ *prefix_results = g_slist_prepend (*prefix_results, (char *) id);
+ break;
+ case MATCH_SUBSTRING:
+ *substring_results = g_slist_prepend (*substring_results, (char *) id);
+ break;
+ }
+}
+
+static GSList *
+shell_app_system_initial_search_internal (ShellAppSystem *self,
+ GSList *terms,
+ GSList *source)
+{
+ GSList *multiple_results = NULL;
+ GSList *prefix_results = NULL;
+ GSList *substring_results = NULL;
+ GSList *iter;
+ GSList *normalized_terms = normalize_terms (terms);
+
+ for (iter = source; iter; iter = iter->next)
+ {
+ ShellAppInfo *info = iter->data;
+
+ shell_app_system_do_match (self, info, normalized_terms, &multiple_results, &prefix_results, &substring_results);
+ }
+ g_slist_foreach (normalized_terms, (GFunc)g_free, NULL);
+ g_slist_free (normalized_terms);
+
+ return sort_and_concat_results (self, multiple_results, prefix_results, substring_results);
+}
+
+/**
+ * shell_app_system_initial_search:
+ * @self: A #ShellAppSystem
+ * @prefs: %TRUE iff we should search preferences instead of apps
+ * @terms: (element-type utf8): List of terms, logical OR
+ *
+ * Search through applications for the given search terms. Note that returned
+ * strings are only valid until a return to the main loop.
+ *
+ * Returns: (transfer container) (element-type utf8): List of application identifiers
+ */
+GSList *
+shell_app_system_initial_search (ShellAppSystem *self,
+ gboolean prefs,
+ GSList *terms)
+{
+ return shell_app_system_initial_search_internal (self, terms,
+ prefs ? self->priv->cached_settings : self->priv->cached_flattened_apps);
+}
+
+/**
+ * shell_app_system_subsearch:
+ * @self: A #ShellAppSystem
+ * @prefs: %TRUE iff we should search preferences instead of apps
+ * @previous_results: (element-type utf8): List of previous results
+ * @terms: (element-type utf8): List of terms, logical OR
+ *
+ * Search through a previous result set; for more information, see
+ * js/ui/search.js. Note the value of @prefs must be
+ * the same as passed to shell_app_system_initial_search(). Note that returned
+ * strings are only valid until a return to the main loop.
+ *
+ * Returns: (transfer container) (element-type utf8): List of application identifiers
+ */
+GSList *
+shell_app_system_subsearch (ShellAppSystem *system,
+ gboolean prefs,
+ GSList *previous_results,
+ GSList *terms)
+{
+ GSList *iter;
+ GSList *multiple_results = NULL;
+ GSList *prefix_results = NULL;
+ GSList *substring_results = NULL;
+ GSList *normalized_terms = normalize_terms (terms);
+
+ /* Note prefs is deliberately ignored; both apps and prefs are in app_id_to_app,
+ * but we have the parameter for consistency and in case in the future
+ * they're not in the same data structure.
+ */
+
+ for (iter = previous_results; iter; iter = iter->next)
+ {
+ const char *id = iter->data;
+ ShellAppInfo *info;
+
+ info = g_hash_table_lookup (system->priv->app_id_to_info, id);
+ if (!info)
+ continue;
+
+ shell_app_system_do_match (system, info, normalized_terms, &multiple_results, &prefix_results, &substring_results);
+ }
+ g_slist_foreach (normalized_terms, (GFunc)g_free, NULL);
+ g_slist_free (normalized_terms);
+
+ /* Note that a shorter term might have matched as a prefix, but
+ when extended only as a substring, so we have to redo the
+ sort rather than reusing the existing ordering */
+ return sort_and_concat_results (system, multiple_results, prefix_results, substring_results);
+}
+
const char *
shell_app_info_get_id (ShellAppInfo *info)
{
diff --git a/src/shell-app-system.h b/src/shell-app-system.h
index 3e3bee111..d6c75817c 100644
--- a/src/shell-app-system.h
+++ b/src/shell-app-system.h
@@ -37,18 +37,6 @@ struct _ShellAppSystemClass
GType shell_app_system_get_type (void) G_GNUC_CONST;
ShellAppSystem* shell_app_system_get_default(void);
-GSList *shell_app_system_get_applications_for_menu (ShellAppSystem *system, const char *menu);
-
-typedef struct _ShellAppMenuEntry ShellAppMenuEntry;
-
-struct _ShellAppMenuEntry {
- char *name;
- char *id;
- char *icon;
-};
-
-GType shell_app_menu_entry_get_type (void);
-
typedef struct _ShellAppInfo ShellAppInfo;
#define SHELL_TYPE_APP_INFO (shell_app_info_get_type ())
@@ -85,8 +73,17 @@ ShellApp *shell_app_system_lookup_heuristic_basename (ShellAppSystem *system, co
ShellAppInfo *shell_app_system_create_from_window (ShellAppSystem *system, MetaWindow *window);
-GSList *shell_app_system_get_menus (ShellAppSystem *system);
+GSList *shell_app_system_get_flattened_apps (ShellAppSystem *system);
GSList *shell_app_system_get_all_settings (ShellAppSystem *system);
+GSList *shell_app_system_initial_search (ShellAppSystem *system,
+ gboolean prefs,
+ GSList *terms);
+
+GSList *shell_app_system_subsearch (ShellAppSystem *system,
+ gboolean prefs,
+ GSList *previous_results,
+ GSList *terms);
+
#endif /* __SHELL_APP_SYSTEM_H__ */
diff --git a/src/shell-doc-system.c b/src/shell-doc-system.c
new file mode 100644
index 000000000..ecb99736e
--- /dev/null
+++ b/src/shell-doc-system.c
@@ -0,0 +1,367 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+#include "config.h"
+
+#include "shell-doc-system.h"
+
+#include "shell-global.h"
+#include "shell-texture-cache.h"
+
+
+/**
+ * SECTION:shell-doc-system
+ * @short_description: Track recently used documents
+ *
+ * Wraps #GtkRecentManager, caching recently used document information, and adds
+ * APIs for asynchronous queries.
+ */
+enum {
+ CHANGED,
+ DELETED,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+struct _ShellDocSystemPrivate {
+ GtkRecentManager *manager;
+ GHashTable *infos_by_uri;
+ GSList *infos_by_timestamp;
+
+ guint idle_recent_changed_id;
+
+ GHashTable *deleted_infos;
+ guint idle_emit_deleted_id;
+};
+
+G_DEFINE_TYPE(ShellDocSystem, shell_doc_system, G_TYPE_OBJECT);
+
+/**
+ * shell_doc_system_get_all:
+ * @self: A #ShellDocSystem
+ *
+ * Returns the currently cached set of recent files. Recent files are read initially
+ * from the underlying #GtkRecentManager, and updated when it changes.
+ * This function does not perform I/O.
+ *
+ * Returns: (transfer none) (element-type GtkRecentInfo): Cached recent file infos
+ */
+GSList *
+shell_doc_system_get_all (ShellDocSystem *self)
+{
+ return self->priv->infos_by_timestamp;
+}
+
+/**
+ * @self: A #ShellDocSystem
+ * @uri: Url
+ *
+ * Returns: (transfer none): Recent file info corresponding to given @uri
+ */
+GtkRecentInfo *
+shell_doc_system_lookup_by_uri (ShellDocSystem *self,
+ const char *uri)
+{
+ return g_hash_table_lookup (self->priv->infos_by_uri, uri);
+}
+
+static gboolean
+shell_doc_system_idle_emit_deleted (gpointer data)
+{
+ ShellDocSystem *self = SHELL_DOC_SYSTEM (data);
+ GHashTableIter iter;
+ gpointer key, value;
+
+ self->priv->idle_emit_deleted_id = 0;
+
+ g_hash_table_iter_init (&iter, self->priv->deleted_infos);
+
+ while (g_hash_table_iter_next (&iter, &key, &value))
+ {
+ GtkRecentInfo *info = key;
+ g_signal_emit (self, signals[DELETED], 0, info);
+ }
+
+ g_signal_emit (self, signals[CHANGED], 0);
+
+ return FALSE;
+}
+
+typedef struct {
+ ShellDocSystem *self;
+ GtkRecentInfo *info;
+} ShellDocSystemRecentQueryData;
+
+static void
+on_recent_file_query_result (GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ ShellDocSystemRecentQueryData *data = user_data;
+ ShellDocSystem *self = data->self;
+ GError *error = NULL;
+ GFileInfo *fileinfo;
+
+ fileinfo = g_file_query_info_finish (G_FILE (source), result, &error);
+ if (fileinfo)
+ g_object_unref (fileinfo);
+ /* This is a strict error check; we don't want to cause recent files to
+ * vanish for anything potentially transient.
+ */
+ if (error != NULL && error->domain == G_IO_ERROR && error->code == G_IO_ERROR_NOT_FOUND)
+ {
+ self->priv->infos_by_timestamp = g_slist_remove (self->priv->infos_by_timestamp, data->info);
+ g_hash_table_remove (self->priv->infos_by_uri, gtk_recent_info_get_uri (data->info));
+
+ g_hash_table_insert (self->priv->deleted_infos, gtk_recent_info_ref (data->info), NULL);
+
+ if (self->priv->idle_emit_deleted_id == 0)
+ self->priv->idle_emit_deleted_id = g_timeout_add (0, shell_doc_system_idle_emit_deleted, self);
+ }
+ g_clear_error (&error);
+
+ gtk_recent_info_unref (data->info);
+ g_free (data);
+}
+
+/**
+ * shell_doc_system_queue_existence_check:
+ * @self: A #ShellDocSystem
+ * @n_items: Count of items to check for existence, starting from most recent
+ *
+ * Asynchronously start a check of a number of recent file for existence;
+ * any deleted files will be emitted from the #ShellDocSystem::deleted
+ * signal. Note that this function ignores non-local files; they
+ * will simply always appear to exist (until they are removed from
+ * the recent file list manually).
+ *
+ * The intent of this function is to be called after a #ShellDocSystem::changed
+ * signal has been emitted, and a display has shown a subset of those files.
+ */
+void
+shell_doc_system_queue_existence_check (ShellDocSystem *self,
+ guint n_items)
+{
+ GSList *iter;
+ guint i;
+
+ for (i = 0, iter = self->priv->infos_by_timestamp; i < n_items && iter; i++, iter = iter->next)
+ {
+ GtkRecentInfo *info = iter->data;
+ const char *uri;
+ GFile *file;
+ ShellDocSystemRecentQueryData *data;
+
+ if (!gtk_recent_info_is_local (info))
+ continue;
+
+ data = g_new0 (ShellDocSystemRecentQueryData, 1);
+ data->self = self;
+ data->info = gtk_recent_info_ref (info);
+
+ uri = gtk_recent_info_get_uri (info);
+ file = g_file_new_for_uri (uri);
+
+ g_file_query_info_async (file, "standard::type", G_FILE_QUERY_INFO_NONE,
+ G_PRIORITY_DEFAULT, NULL, on_recent_file_query_result, data);
+ g_object_unref (file);
+ }
+}
+
+static int
+sort_infos_by_timestamp_descending (gconstpointer a,
+ gconstpointer b)
+{
+ GtkRecentInfo *info_a = (GtkRecentInfo*)a;
+ GtkRecentInfo *info_b = (GtkRecentInfo*)b;
+ time_t modified_a, modified_b;
+
+ modified_a = gtk_recent_info_get_modified (info_a);
+ modified_b = gtk_recent_info_get_modified (info_b);
+
+ return modified_b - modified_a;
+}
+
+static gboolean
+idle_handle_recent_changed (gpointer data)
+{
+ ShellDocSystem *self = SHELL_DOC_SYSTEM (data);
+ GList *items, *iter;
+
+ self->priv->idle_recent_changed_id = 0;
+
+ g_hash_table_remove_all (self->priv->deleted_infos);
+ g_hash_table_remove_all (self->priv->infos_by_uri);
+ g_slist_free (self->priv->infos_by_timestamp);
+ self->priv->infos_by_timestamp = NULL;
+
+ items = gtk_recent_manager_get_items (self->priv->manager);
+ for (iter = items; iter; iter = iter->next)
+ {
+ GtkRecentInfo *info = iter->data;
+ const char *uri = gtk_recent_info_get_uri (info);
+
+ /* uri is owned by the info */
+ g_hash_table_insert (self->priv->infos_by_uri, (char*) uri, info);
+
+ self->priv->infos_by_timestamp = g_slist_prepend (self->priv->infos_by_timestamp, info);
+ }
+ g_list_free (items);
+
+ self->priv->infos_by_timestamp = g_slist_sort (self->priv->infos_by_timestamp, sort_infos_by_timestamp_descending);
+
+ g_signal_emit (self, signals[CHANGED], 0);
+
+ return FALSE;
+}
+
+static void
+shell_doc_system_on_recent_changed (GtkRecentManager *manager,
+ ShellDocSystem *self)
+{
+ if (self->priv->idle_recent_changed_id != 0)
+ return;
+ self->priv->idle_recent_changed_id = g_timeout_add (0, idle_handle_recent_changed, self);
+}
+
+/**
+ * shell_doc_system_open:
+ * @system: A #ShellDocSystem
+ * @info: A #GtkRecentInfo
+ *
+ * Launch the default application associated with the mime type of
+ * @info, using its uri.
+ */
+void
+shell_doc_system_open (ShellDocSystem *system,
+ GtkRecentInfo *info)
+{
+ GFile *file;
+ GAppInfo *app_info;
+ gboolean needs_uri;
+
+ file = g_file_new_for_uri (gtk_recent_info_get_uri (info));
+ needs_uri = g_file_get_path (file) == NULL;
+ g_object_unref (file);
+
+ app_info = g_app_info_get_default_for_type (gtk_recent_info_get_mime_type (info), needs_uri);
+ if (app_info != NULL)
+ {
+ GList *uris;
+ uris = g_list_prepend (NULL, (gpointer)gtk_recent_info_get_uri (info));
+ g_app_info_launch_uris (app_info, uris, shell_global_create_app_launch_context (shell_global_get ()), NULL);
+ g_list_free (uris);
+ }
+ else
+ {
+ char *app_name;
+#if GTK_MINOR_VERSION >= 18
+ const char *app_exec;
+#else
+ char *app_exec;
+#endif
+ char *app_exec_quoted;
+ guint count;
+ time_t time;
+
+ app_name = gtk_recent_info_last_application (info);
+ if (gtk_recent_info_get_application_info (info, app_name, &app_exec, &count, &time))
+ {
+ GRegex *regex;
+ GAppLaunchContext *context;
+
+ /* TODO: Change this once better support for creating
+ GAppInfo is added to GtkRecentInfo, as right now
+ this relies on the fact that the file uri is
+ already a part of appExec, so we don't supply any
+ files to app_info.launch().
+
+ The 'command line' passed to
+ create_from_command_line is allowed to contain
+ '%' macros that are expanded to file
+ name / icon name, etc, so we need to escape % as %%
+ */
+
+ regex = g_regex_new ("%", 0, 0, NULL);
+ app_exec_quoted = g_regex_replace (regex, app_exec, -1, 0, "%%", 0, NULL);
+ g_regex_unref (regex);
+
+ app_info = g_app_info_create_from_commandline (app_exec, NULL, 0, NULL);
+
+ /* The point of passing an app launch context to
+ launch() is mostly to get startup notification and
+ associated benefits like the app appearing on the
+ right desktop; but it doesn't really work for now
+ because with the way we create the appInfo we
+ aren't reading the application's desktop file, and
+ thus don't find the StartupNotify=true in it. So,
+ despite passing the app launch context, no startup
+ notification occurs.
+ */
+ context = shell_global_create_app_launch_context (shell_global_get ());
+ g_app_info_launch (app_info, NULL, context, NULL);
+ g_object_unref (context);
+ }
+
+ g_free (app_name);
+ }
+}
+
+static void
+shell_doc_system_class_init(ShellDocSystemClass *klass)
+{
+ GObjectClass *gobject_class = (GObjectClass *)klass;
+
+ signals[CHANGED] =
+ g_signal_new ("changed",
+ SHELL_TYPE_DOC_SYSTEM,
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ signals[DELETED] =
+ g_signal_new ("deleted",
+ SHELL_TYPE_DOC_SYSTEM,
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__BOXED,
+ G_TYPE_NONE, 1, GTK_TYPE_RECENT_INFO);
+
+ g_type_class_add_private (gobject_class, sizeof (ShellDocSystemPrivate));
+}
+
+static void
+shell_doc_system_init (ShellDocSystem *self)
+{
+ ShellDocSystemPrivate *priv;
+
+ self->priv = priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ SHELL_TYPE_DOC_SYSTEM,
+ ShellDocSystemPrivate);
+ self->priv->manager = gtk_recent_manager_get_default ();
+
+ self->priv->deleted_infos = g_hash_table_new_full (NULL, NULL, (GDestroyNotify)gtk_recent_info_unref, NULL);
+ self->priv->infos_by_uri = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, (GDestroyNotify)gtk_recent_info_unref);
+
+ g_signal_connect (self->priv->manager, "changed", G_CALLBACK(shell_doc_system_on_recent_changed), self);
+ shell_doc_system_on_recent_changed (self->priv->manager, self);
+}
+
+/**
+ * shell_doc_system_get_default:
+ *
+ * Return Value: (transfer none): The global #ShellDocSystem singleton
+ */
+ShellDocSystem *
+shell_doc_system_get_default ()
+{
+ static ShellDocSystem *instance = NULL;
+
+ if (instance == NULL)
+ instance = g_object_new (SHELL_TYPE_DOC_SYSTEM, NULL);
+
+ return instance;
+}
diff --git a/src/shell-doc-system.h b/src/shell-doc-system.h
new file mode 100644
index 000000000..0f2ad6037
--- /dev/null
+++ b/src/shell-doc-system.h
@@ -0,0 +1,46 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+#ifndef __SHELL_DOC_SYSTEM_H__
+#define __SHELL_DOC_SYSTEM_H__
+
+#include
+#include
+
+#define SHELL_TYPE_DOC_SYSTEM (shell_doc_system_get_type ())
+#define SHELL_DOC_SYSTEM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SHELL_TYPE_DOC_SYSTEM, ShellDocSystem))
+#define SHELL_DOC_SYSTEM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SHELL_TYPE_DOC_SYSTEM, ShellDocSystemClass))
+#define SHELL_IS_DOC_SYSTEM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SHELL_TYPE_DOC_SYSTEM))
+#define SHELL_IS_DOC_SYSTEM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SHELL_TYPE_DOC_SYSTEM))
+#define SHELL_DOC_SYSTEM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), SHELL_TYPE_DOC_SYSTEM, ShellDocSystemClass))
+
+typedef struct _ShellDocSystem ShellDocSystem;
+typedef struct _ShellDocSystemClass ShellDocSystemClass;
+typedef struct _ShellDocSystemPrivate ShellDocSystemPrivate;
+
+struct _ShellDocSystem
+{
+ GObject parent;
+
+ ShellDocSystemPrivate *priv;
+};
+
+struct _ShellDocSystemClass
+{
+ GObjectClass parent_class;
+};
+
+GType shell_doc_system_get_type (void) G_GNUC_CONST;
+
+ShellDocSystem* shell_doc_system_get_default (void);
+
+GSList *shell_doc_system_get_all (ShellDocSystem *system);
+
+GtkRecentInfo *shell_doc_system_lookup_by_uri (ShellDocSystem *system,
+ const char *uri);
+
+void shell_doc_system_queue_existence_check (ShellDocSystem *system,
+ guint n_recent);
+
+void shell_doc_system_open (ShellDocSystem *system,
+ GtkRecentInfo *info);
+
+#endif /* __SHELL_DOC_SYSTEM_H__ */
diff --git a/src/shell-generic-container.c b/src/shell-generic-container.c
index 68b3afde2..b6cdeecc0 100644
--- a/src/shell-generic-container.c
+++ b/src/shell-generic-container.c
@@ -105,7 +105,7 @@ function runTestFixedBox() {
G_DEFINE_TYPE(ShellGenericContainer, shell_generic_container, CLUTTER_TYPE_GROUP);
struct _ShellGenericContainerPrivate {
- gpointer dummy;
+ GHashTable *skip_paint;
};
/* Signals */
@@ -182,15 +182,119 @@ shell_generic_container_get_preferred_height (ClutterActor *actor,
shell_generic_container_allocation_unref (alloc);
}
+static void
+shell_generic_container_paint (ClutterActor *actor)
+{
+ ShellGenericContainer *self = (ShellGenericContainer*) actor;
+ GList *iter, *children;
+
+ children = clutter_container_get_children ((ClutterContainer*) actor);
+
+ for (iter = children; iter; iter = iter->next)
+ {
+ ClutterActor *child = iter->data;
+
+ if (g_hash_table_lookup (self->priv->skip_paint, child))
+ continue;
+
+ clutter_actor_paint (child);
+ }
+
+ g_list_free (children);
+}
+
+static void
+shell_generic_container_pick (ClutterActor *actor,
+ const ClutterColor *color)
+{
+ ShellGenericContainer *self = (ShellGenericContainer*) actor;
+ GList *iter, *children;
+
+ (CLUTTER_ACTOR_CLASS (g_type_class_peek (clutter_actor_get_type ())))->pick (actor, color);
+
+ children = clutter_container_get_children ((ClutterContainer*) actor);
+
+ for (iter = children; iter; iter = iter->next)
+ {
+ ClutterActor *child = iter->data;
+
+ if (g_hash_table_lookup (self->priv->skip_paint, child))
+ continue;
+
+ clutter_actor_paint (child);
+ }
+
+ g_list_free (children);
+}
+
+static void
+on_skip_paint_weakref (gpointer user_data,
+ GObject *location)
+{
+ ShellGenericContainer *self = SHELL_GENERIC_CONTAINER (user_data);
+
+ g_hash_table_remove (self->priv->skip_paint, location);
+}
+
+/**
+ * shell_generic_container_set_skip_paint:
+ * @container: A #ShellGenericContainer
+ * @child: Child #ClutterActor
+ * @skip %TRUE if we should skip painting
+ *
+ * Set whether or not we should skip painting @actor. Workaround for
+ * lack of gjs ability to override _paint vfunc.
+ */
+void
+shell_generic_container_set_skip_paint (ShellGenericContainer *self,
+ ClutterActor *child,
+ gboolean skip)
+{
+ gboolean currently_skipping;
+
+ currently_skipping = g_hash_table_lookup (self->priv->skip_paint, child) != NULL;
+ if (!!skip == currently_skipping)
+ return;
+
+ if (!skip)
+ {
+ g_object_weak_unref ((GObject*) child, on_skip_paint_weakref, self);
+ g_hash_table_remove (self->priv->skip_paint, child);
+ }
+ else
+ {
+ g_object_weak_ref ((GObject*) child, on_skip_paint_weakref, self);
+ g_hash_table_insert (self->priv->skip_paint, child, child);
+ }
+}
+
+static void
+shell_generic_container_dispose (GObject *object)
+{
+ ShellGenericContainer *self = (ShellGenericContainer*) object;
+
+ if (self->priv->skip_paint != NULL)
+ {
+ g_hash_table_destroy (self->priv->skip_paint);
+ self->priv->skip_paint = NULL;
+ }
+
+ G_OBJECT_CLASS (shell_generic_container_parent_class)->dispose (object);
+}
+
static void
shell_generic_container_class_init (ShellGenericContainerClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass);
+ gobject_class->dispose = shell_generic_container_dispose;
+
actor_class->get_preferred_width = shell_generic_container_get_preferred_width;
actor_class->get_preferred_height = shell_generic_container_get_preferred_height;
actor_class->allocate = shell_generic_container_allocate;
+ actor_class->paint = shell_generic_container_paint;
+ actor_class->pick = shell_generic_container_pick;
shell_generic_container_signals[GET_PREFERRED_WIDTH] =
g_signal_new ("get-preferred-width",
@@ -227,6 +331,7 @@ shell_generic_container_init (ShellGenericContainer *area)
{
area->priv = G_TYPE_INSTANCE_GET_PRIVATE (area, SHELL_TYPE_GENERIC_CONTAINER,
ShellGenericContainerPrivate);
+ area->priv->skip_paint = g_hash_table_new_full (NULL, NULL, (GDestroyNotify)g_object_unref, NULL);
}
GType shell_generic_container_allocation_get_type (void)
diff --git a/src/shell-generic-container.h b/src/shell-generic-container.h
index 53e0907d5..009df5516 100644
--- a/src/shell-generic-container.h
+++ b/src/shell-generic-container.h
@@ -42,4 +42,8 @@ struct _ShellGenericContainerClass
GType shell_generic_container_get_type (void) G_GNUC_CONST;
+void shell_generic_container_set_skip_paint (ShellGenericContainer *container,
+ ClutterActor *actor,
+ gboolean skip);
+
#endif /* __SHELL_GENERIC_CONTAINER_H__ */
diff --git a/src/shell-global.c b/src/shell-global.c
index a36753e9a..7fd37c998 100644
--- a/src/shell-global.c
+++ b/src/shell-global.c
@@ -522,6 +522,70 @@ shell_global_display_is_grabbed (ShellGlobal *global)
return meta_display_get_grab_op (display) != META_GRAB_OP_NONE;
}
+/* Defining this here for now, see
+ * https://bugzilla.gnome.org/show_bug.cgi?id=604075
+ * for upstreaming status.
+ */
+JSContext * gjs_context_get_context (GjsContext *context);
+
+/**
+ * shell_global_add_extension_importer:
+ * @target_object_script: JavaScript code evaluating to a target object
+ * @target_property: Name of property to use for importer
+ * @directory: Source directory:
+ * @error: A #GError
+ *
+ * This function sets a property named @target_property on the object
+ * resulting from the evaluation of @target_object_script code, which
+ * acts as a GJS importer for directory @directory.
+ *
+ * Returns: %TRUE on success
+ */
+gboolean
+shell_global_add_extension_importer (ShellGlobal *global,
+ const char *target_object_script,
+ const char *target_property,
+ const char *directory,
+ GError **error)
+{
+ jsval target_object;
+ JSObject *importer;
+ JSContext *context = gjs_context_get_context (global->js_context);
+ char *search_path[2] = { 0, 0 };
+
+ // This is a bit of a hack; ideally we'd be able to pass our target
+ // object directly into this function, but introspection doesn't
+ // support that at the moment. Instead evaluate a string to get it.
+ if (!JS_EvaluateScript(context,
+ JS_GetGlobalObject(context),
+ target_object_script,
+ strlen (target_object_script),
+ "",
+ 0,
+ &target_object))
+ {
+ char *message;
+ gjs_log_exception(context,
+ &message);
+ g_set_error(error,
+ G_IO_ERROR,
+ G_IO_ERROR_FAILED,
+ "%s", message ? message : "(unknown)");
+ g_free(message);
+ return FALSE;
+ }
+
+ if (!JSVAL_IS_OBJECT (target_object))
+ {
+ g_error ("shell_global_add_extension_importer: invalid target object");
+ return FALSE;
+ }
+
+ search_path[0] = (char*)directory;
+ importer = gjs_define_importer (context, JSVAL_TO_OBJECT (target_object), target_property, (const char **)search_path, FALSE);
+ return TRUE;
+}
+
/* Code to close all file descriptors before we exec; copied from gspawn.c in GLib.
*
* Authors: Padraig O'Briain, Matthias Clasen, Lennart Poettering
@@ -1064,3 +1128,63 @@ shell_popup_menu (GtkMenu *menu, int button, guint32 time,
gtk_menu_popup (menu, NULL, NULL, shell_popup_menu_position_func, NULL,
button, time);
}
+
+/**
+ * shell_global_get_current_time:
+ * @global: A #ShellGlobal
+ *
+ * Returns: the current X server time from the current Clutter, Gdk, or X
+ * event. If called from outside an event handler, this may return
+ * %Clutter.CURRENT_TIME (aka 0), or it may return a slightly
+ * out-of-date timestamp.
+ */
+guint32
+shell_global_get_current_time (ShellGlobal *global)
+{
+ guint32 time;
+ MetaDisplay *display;
+
+ /* meta_display_get_current_time() will return the correct time
+ when handling an X or Gdk event, but will return CurrentTime
+ from some Clutter event callbacks.
+
+ clutter_get_current_event_time() will return the correct time
+ from a Clutter event callback, but may return an out-of-date
+ timestamp if called at other times.
+
+ So we try meta_display_get_current_time() first, since we
+ can recognize a "wrong" answer from that, and then fall back
+ to clutter_get_current_event_time().
+ */
+
+ display = meta_screen_get_display (shell_global_get_screen (global));
+ time = meta_display_get_current_time (display);
+ if (time != CLUTTER_CURRENT_TIME)
+ return time;
+
+ return clutter_get_current_event_time ();
+}
+
+/**
+ * shell_global_get_app_launch_context:
+ * @global: A #ShellGlobal
+ *
+ * Create a #GAppLaunchContext set up with the correct timestamp, and
+ * targeted to activate on the current workspace.
+ *
+ * Return value: A new #GAppLaunchContext
+ */
+GAppLaunchContext *
+shell_global_create_app_launch_context (ShellGlobal *global)
+{
+ GdkAppLaunchContext *context;
+
+ context = gdk_app_launch_context_new ();
+ gdk_app_launch_context_set_timestamp (context, shell_global_get_current_time (global));
+
+ // Make sure that the app is opened on the current workspace even if
+ // the user switches before it starts
+ gdk_app_launch_context_set_desktop (context, meta_screen_get_active_workspace_index (shell_global_get_screen (global)));
+
+ return (GAppLaunchContext *)context;
+}
diff --git a/src/shell-global.h b/src/shell-global.h
index 60b9cb552..12ebf5033 100644
--- a/src/shell-global.h
+++ b/src/shell-global.h
@@ -43,6 +43,12 @@ ShellGlobal *shell_global_get (void);
MetaScreen *shell_global_get_screen (ShellGlobal *global);
+gboolean shell_global_add_extension_importer (ShellGlobal *global,
+ const char *target_object_script,
+ const char *target_property,
+ const char *directory,
+ GError **error);
+
void shell_global_grab_dbus_service (ShellGlobal *global);
typedef enum {
@@ -82,6 +88,9 @@ ClutterModifierType shell_get_event_state (ClutterEvent *event);
void shell_popup_menu (GtkMenu *menu, int button, guint32 time,
int menu_x, int menu_y);
+guint32 shell_global_get_current_time (ShellGlobal *global);
+
+GAppLaunchContext *shell_global_create_app_launch_context (ShellGlobal *global);
G_END_DECLS
diff --git a/src/st/st-button.c b/src/st/st-button.c
index e1602e086..52032f0f3 100644
--- a/src/st/st-button.c
+++ b/src/st/st-button.c
@@ -98,10 +98,6 @@ static void
st_button_update_label_style (StButton *button)
{
ClutterActor *label;
- StThemeNode *theme_node;
- ClutterColor color;
- const PangoFontDescription *font;
- gchar *font_string = NULL;
label = st_bin_get_child ((StBin*) button);
@@ -109,15 +105,7 @@ st_button_update_label_style (StButton *button)
if (!CLUTTER_IS_TEXT (label))
return;
- theme_node = st_widget_get_theme_node (ST_WIDGET (button));
-
- st_theme_node_get_foreground_color (theme_node, &color);
- clutter_text_set_color (CLUTTER_TEXT (label), &color);
-
- font = st_theme_node_get_font (theme_node);
- font_string = pango_font_description_to_string (font);
- clutter_text_set_font_name (CLUTTER_TEXT (label), font_string);
- g_free (font_string);
+ _st_set_text_from_style ((ClutterText*) label, st_widget_get_theme_node (ST_WIDGET (button)));
}
static void
diff --git a/src/st/st-label.c b/src/st/st-label.c
index db9b0c154..1a304a4bf 100644
--- a/src/st/st-label.c
+++ b/src/st/st-label.c
@@ -43,7 +43,7 @@
#include
#include "st-label.h"
-
+#include "st-private.h"
#include "st-widget.h"
enum
@@ -110,21 +110,9 @@ st_label_get_property (GObject *gobject,
static void
st_label_style_changed (StWidget *self)
{
- StLabelPrivate *priv;
- StThemeNode *theme_node;
- ClutterColor color;
- const PangoFontDescription *font;
- gchar *font_string;
+ StLabelPrivate *priv = ST_LABEL(self)->priv;
- priv = ST_LABEL (self)->priv;
- theme_node = st_widget_get_theme_node (self);
- st_theme_node_get_foreground_color (theme_node, &color);
- clutter_text_set_color (CLUTTER_TEXT (priv->label), &color);
-
- font = st_theme_node_get_font (theme_node);
- font_string = pango_font_description_to_string (font);
- clutter_text_set_font_name (CLUTTER_TEXT (priv->label), font_string);
- g_free (font_string);
+ _st_set_text_from_style ((ClutterText *)priv->label, st_widget_get_theme_node (self));
ST_WIDGET_CLASS (st_label_parent_class)->style_changed (self);
}
diff --git a/src/st/st-overflow-box.c b/src/st/st-overflow-box.c
new file mode 100644
index 000000000..624939831
--- /dev/null
+++ b/src/st/st-overflow-box.c
@@ -0,0 +1,706 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * Portions derived from st-box-layout.c, which is
+ * Copyright 2009 Intel Corporation.
+ * Modified into -overflow-box, by Colin Walters , which is
+ * Copyright 2009 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+/**
+ * SECTION:st-overflow-box
+ * @short_description: A vertical box which paints as many actors as it can fit
+ *
+ * This is a "flexible" box which will paint as many actors as it can within
+ * its given allocation; its minimum height request will be the sum of the
+ * mimimum size for the #StOverflowBox:min-children property, which is
+ * by default 0.
+ *
+ * Every child will be allocated the full width of the box, and always be
+ * given its preferred height. Even if not actually painted, every child
+ * is counted for overall preferred width/height.
+ */
+
+#include
+
+#include "st-overflow-box.h"
+
+#include "st-private.h"
+#include "st-box-layout-child.h"
+
+static void st_overflow_box_container_iface_init (ClutterContainerIface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (StOverflowBox, st_overflow_box, ST_TYPE_WIDGET,
+ G_IMPLEMENT_INTERFACE (CLUTTER_TYPE_CONTAINER,
+ st_overflow_box_container_iface_init));
+
+#define OVERFLOW_BOX_LAYOUT_PRIVATE(o) \
+ (G_TYPE_INSTANCE_GET_PRIVATE ((o), ST_TYPE_OVERFLOW_BOX, StOverflowBoxPrivate))
+
+enum {
+ PROP_0,
+
+ PROP_MIN_CHILDREN
+};
+
+struct _StOverflowBoxPrivate
+{
+ GList *children;
+ guint min_children;
+ guint n_visible;
+
+ guint spacing;
+};
+
+/*
+ * ClutterContainer Implementation
+ */
+static void
+st_overflow_box_add_actor (ClutterContainer *container,
+ ClutterActor *actor)
+{
+ StOverflowBoxPrivate *priv = ST_OVERFLOW_BOX (container)->priv;
+
+ clutter_actor_set_parent (actor, CLUTTER_ACTOR (container));
+
+ priv->children = g_list_append (priv->children, actor);
+
+ clutter_actor_queue_relayout (CLUTTER_ACTOR (container));
+
+ g_signal_emit_by_name (container, "actor-added", actor);
+}
+
+static void
+st_overflow_box_remove_actor (ClutterContainer *container,
+ ClutterActor *actor)
+{
+ StOverflowBoxPrivate *priv = ST_OVERFLOW_BOX (container)->priv;
+
+ GList *item = NULL;
+
+ item = g_list_find (priv->children, actor);
+
+ if (item == NULL)
+ {
+ g_warning ("Actor of type '%s' is not a child of container of type '%s'",
+ g_type_name (G_OBJECT_TYPE (actor)),
+ g_type_name (G_OBJECT_TYPE (container)));
+ return;
+ }
+
+ g_object_ref (actor);
+
+ priv->children = g_list_delete_link (priv->children, item);
+ clutter_actor_unparent (actor);
+
+ g_signal_emit_by_name (container, "actor-removed", actor);
+
+ g_object_unref (actor);
+
+ clutter_actor_queue_relayout ((ClutterActor*) container);
+}
+
+static void
+st_overflow_box_foreach (ClutterContainer *container,
+ ClutterCallback callback,
+ gpointer callback_data)
+{
+ StOverflowBoxPrivate *priv = ST_OVERFLOW_BOX (container)->priv;
+
+ g_list_foreach (priv->children, (GFunc) callback, callback_data);
+}
+
+static void
+st_overflow_box_lower (ClutterContainer *container,
+ ClutterActor *actor,
+ ClutterActor *sibling)
+{
+ StOverflowBoxPrivate *priv = ST_OVERFLOW_BOX (container)->priv;
+
+ /* copied from clutter/clutter/clutter-group.c */
+
+ priv->children = g_list_remove (priv->children, actor);
+
+ /* Push to bottom */
+ if (!sibling)
+ {
+ GList *last_item;
+
+ last_item = g_list_first (priv->children);
+
+ if (last_item)
+ sibling = last_item->data;
+
+ priv->children = g_list_prepend (priv->children, actor);
+ }
+ else
+ {
+ gint pos;
+
+ pos = g_list_index (priv->children, sibling);
+
+ priv->children = g_list_insert (priv->children, actor, pos);
+ }
+
+ /* See comment in group_raise for this */
+ if (sibling &&
+ clutter_actor_get_depth (sibling) != clutter_actor_get_depth (actor))
+ {
+ clutter_actor_set_depth (actor, clutter_actor_get_depth (sibling));
+ }
+
+ if (CLUTTER_ACTOR_IS_VISIBLE (container))
+ clutter_actor_queue_redraw (CLUTTER_ACTOR (container));
+}
+
+static void
+st_overflow_box_raise (ClutterContainer *container,
+ ClutterActor *actor,
+ ClutterActor *sibling)
+{
+ StOverflowBoxPrivate *priv = ST_OVERFLOW_BOX (container)->priv;
+
+ priv->children = g_list_remove (priv->children, actor);
+
+ /* copied from clutter/clutter/clutter-group.c */
+
+ /* Raise at the top */
+ if (!sibling)
+ {
+ GList *last_item;
+
+ last_item = g_list_last (priv->children);
+
+ if (last_item)
+ sibling = last_item->data;
+
+ priv->children = g_list_append (priv->children, actor);
+ }
+ else
+ {
+ gint pos;
+
+ pos = g_list_index (priv->children, sibling) + 1;
+
+ priv->children = g_list_insert (priv->children, actor, pos);
+ }
+
+ /* set Z ordering a value below, this will then call sort
+ * as values are equal ordering shouldn't change but Z
+ * values will be correct.
+ *
+ * FIXME: optimise
+ */
+ if (sibling &&
+ clutter_actor_get_depth (sibling) != clutter_actor_get_depth (actor))
+ {
+ clutter_actor_set_depth (actor, clutter_actor_get_depth (sibling));
+ }
+
+ if (CLUTTER_ACTOR_IS_VISIBLE (container))
+ clutter_actor_queue_redraw (CLUTTER_ACTOR (container));
+}
+
+static void
+st_overflow_box_sort_depth_order (ClutterContainer *container)
+{
+ /* XXX: not yet implemented */
+ g_warning ("%s() not yet implemented", __FUNCTION__);
+}
+
+static void
+st_overflow_box_container_iface_init (ClutterContainerIface *iface)
+{
+ iface->add = st_overflow_box_add_actor;
+ iface->remove = st_overflow_box_remove_actor;
+ iface->foreach = st_overflow_box_foreach;
+ iface->lower = st_overflow_box_lower;
+ iface->raise = st_overflow_box_raise;
+ iface->sort_depth_order = st_overflow_box_sort_depth_order;
+}
+
+
+static void
+st_overflow_box_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ StOverflowBoxPrivate *priv = ST_OVERFLOW_BOX (object)->priv;
+
+ switch (property_id)
+ {
+ case PROP_MIN_CHILDREN:
+ g_value_set_uint (value, priv->min_children);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+}
+
+static void
+st_overflow_box_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ StOverflowBox *box = ST_OVERFLOW_BOX (object);
+
+ switch (property_id)
+ {
+ case PROP_MIN_CHILDREN:
+ st_overflow_box_set_min_children (box, g_value_get_uint (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+}
+
+static void
+st_overflow_box_dispose (GObject *object)
+{
+ StOverflowBoxPrivate *priv = ST_OVERFLOW_BOX (object)->priv;
+
+ while (priv->children)
+ clutter_actor_destroy (priv->children->data);
+
+ G_OBJECT_CLASS (st_overflow_box_parent_class)->dispose (object);
+}
+
+static void
+get_content_preferred_width (StOverflowBox *self,
+ gfloat for_height,
+ gfloat *min_width_p,
+ gfloat *natural_width_p)
+{
+ StOverflowBoxPrivate *priv = self->priv;
+ gint n_children = 0;
+ gint n_fixed = 0;
+ gfloat min_width, natural_width;
+ GList *l;
+
+ min_width = 0;
+ natural_width = 0;
+
+ for (l = priv->children; l; l = g_list_next (l))
+ {
+ ClutterActor *child = l->data;
+ gfloat child_min = 0, child_nat = 0;
+
+ if (!CLUTTER_ACTOR_IS_VISIBLE (child))
+ continue;
+
+ n_children++;
+
+ if (clutter_actor_get_fixed_position_set (child))
+ {
+ n_fixed++;
+ continue;
+ }
+
+ clutter_actor_get_preferred_width (child,
+ -1,
+ &child_min,
+ &child_nat);
+
+ min_width = MAX (child_min, min_width);
+ natural_width = MAX (child_nat, natural_width);
+ }
+
+ if ((n_children - n_fixed) > 1)
+ {
+ min_width += priv->spacing * (n_children - n_fixed - 1);
+ natural_width += priv->spacing * (n_children - n_fixed - 1);
+ }
+
+ if (min_width_p)
+ *min_width_p = min_width;
+
+ if (natural_width_p)
+ *natural_width_p = natural_width;
+}
+
+static void
+st_overflow_box_get_preferred_width (ClutterActor *actor,
+ gfloat for_height,
+ gfloat *min_width_p,
+ gfloat *natural_width_p)
+{
+ StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (actor));
+
+ st_theme_node_adjust_for_height (theme_node, &for_height);
+
+ get_content_preferred_width (ST_OVERFLOW_BOX (actor), for_height,
+ min_width_p, natural_width_p);
+
+ st_theme_node_adjust_preferred_width (theme_node,
+ min_width_p, natural_width_p);
+}
+
+static void
+get_content_preferred_height (StOverflowBox *self,
+ gfloat for_width,
+ gfloat *min_height_p,
+ gfloat *natural_height_p)
+{
+ StOverflowBoxPrivate *priv = self->priv;
+ gint n_min_children = 0;
+ gint n_children = 0;
+ gint n_fixed = 0;
+ gfloat min_height, natural_height;
+ GList *l;
+
+ min_height = 0;
+ natural_height = 0;
+
+ for (l = priv->children; l; l = g_list_next (l))
+ {
+ ClutterActor *child = l->data;
+ gfloat child_min = 0, child_nat = 0;
+
+ if (!CLUTTER_ACTOR_IS_VISIBLE (child))
+ continue;
+
+ n_children++;
+
+ if (clutter_actor_get_fixed_position_set (child))
+ {
+ n_fixed++;
+ continue;
+ }
+
+ clutter_actor_get_preferred_height (child,
+ for_width,
+ &child_min,
+ &child_nat);
+
+ if (n_children < priv->min_children)
+ {
+ n_min_children++;
+ min_height += child_min;
+ }
+ natural_height += child_nat;
+ }
+
+ min_height += priv->spacing * MAX(0, n_min_children - 1);
+ natural_height += priv->spacing * MAX(0, n_children - n_fixed - 1);
+
+ if (min_height_p)
+ *min_height_p = min_height;
+
+ if (natural_height_p)
+ *natural_height_p = natural_height;
+}
+
+static void
+st_overflow_box_get_preferred_height (ClutterActor *actor,
+ gfloat for_width,
+ gfloat *min_height_p,
+ gfloat *natural_height_p)
+{
+ StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (actor));
+
+ st_theme_node_adjust_for_width (theme_node, &for_width);
+
+ get_content_preferred_height (ST_OVERFLOW_BOX (actor), for_width,
+ min_height_p, natural_height_p);
+
+ st_theme_node_adjust_preferred_height (theme_node,
+ min_height_p, natural_height_p);
+}
+
+static void
+st_overflow_box_allocate (ClutterActor *actor,
+ const ClutterActorBox *box,
+ ClutterAllocationFlags flags)
+{
+ StOverflowBoxPrivate *priv = ST_OVERFLOW_BOX (actor)->priv;
+ StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (actor));
+ ClutterActorBox content_box;
+ gfloat position;
+ float avail_width, avail_height;
+ GList *l;
+ int i;
+ gboolean done_non_fixed;
+
+ CLUTTER_ACTOR_CLASS (st_overflow_box_parent_class)->allocate (actor, box,
+ flags);
+
+ if (priv->children == NULL)
+ return;
+
+ st_theme_node_get_content_box (theme_node, box, &content_box);
+
+ avail_width = content_box.x2 - content_box.x1;
+ avail_height = content_box.y2 - content_box.y1;
+
+ position = content_box.y1;
+ priv->n_visible = 0;
+
+ done_non_fixed = FALSE;
+ for (l = priv->children, i = 0; l; l = l->next, i++)
+ {
+ ClutterActor *child = (ClutterActor*) l->data;
+ ClutterActorBox child_box;
+ gfloat child_min, child_nat;
+ gboolean fixed;
+
+ if (!CLUTTER_ACTOR_IS_VISIBLE (child))
+ continue;
+
+ fixed = clutter_actor_get_fixed_position_set (child);
+ if (fixed)
+ {
+ clutter_actor_allocate_preferred_size (child, flags);
+ continue;
+ }
+ else if (done_non_fixed)
+ continue;
+
+ clutter_actor_get_preferred_height (child, avail_width,
+ &child_min, &child_nat);
+
+ if (position + child_nat > content_box.y2)
+ {
+ done_non_fixed = TRUE; /* Continue iterating on non fixed */
+ continue;
+ }
+
+ priv->n_visible++;
+ child_box.y1 = (int)(0.5 + position);
+ child_box.y2 = child_box.y1 + (int)(0.5 + child_nat);
+ child_box.x1 = content_box.x1;
+ child_box.x2 = content_box.x2;
+
+ position += child_nat + priv->spacing;
+
+ clutter_actor_allocate (child, &child_box, flags);
+ }
+}
+
+static void
+st_overflow_box_internal_paint (StOverflowBox *box)
+{
+ StOverflowBoxPrivate *priv = box->priv;
+ GList *l;
+ int i;
+
+ i = 0;
+ for (l = priv->children; i < priv->n_visible && l; l = l->next)
+ {
+ ClutterActor *child = (ClutterActor*) l->data;
+
+ if (!CLUTTER_ACTOR_IS_VISIBLE (child))
+ continue;
+ if (!clutter_actor_get_fixed_position_set (child))
+ i++;
+
+ clutter_actor_paint (child);
+ }
+
+ for (;l; l = l->next)
+ {
+ ClutterActor *child = (ClutterActor*) l->data;
+
+ if (!CLUTTER_ACTOR_IS_VISIBLE (child))
+ continue;
+
+ if (clutter_actor_get_fixed_position_set (child))
+ clutter_actor_paint (child);
+ }
+}
+
+static void
+st_overflow_box_paint (ClutterActor *actor)
+{
+ CLUTTER_ACTOR_CLASS (st_overflow_box_parent_class)->paint (actor);
+
+ st_overflow_box_internal_paint (ST_OVERFLOW_BOX (actor));
+}
+
+static void
+st_overflow_box_pick (ClutterActor *actor,
+ const ClutterColor *color)
+{
+ CLUTTER_ACTOR_CLASS (st_overflow_box_parent_class)->pick (actor, color);
+
+ st_overflow_box_internal_paint (ST_OVERFLOW_BOX (actor));
+}
+
+static void
+st_overflow_box_style_changed (StWidget *self)
+{
+ StOverflowBoxPrivate *priv = ST_OVERFLOW_BOX (self)->priv;
+ StThemeNode *theme_node = st_widget_get_theme_node (self);
+ int old_spacing = priv->spacing;
+ double spacing = 0;
+
+ st_theme_node_get_length (theme_node, "spacing", FALSE, &spacing);
+ priv->spacing = (int)(spacing + 0.5);
+ if (priv->spacing != old_spacing)
+ clutter_actor_queue_relayout (CLUTTER_ACTOR (self));
+
+ ST_WIDGET_CLASS (st_overflow_box_parent_class)->style_changed (self);
+}
+
+static void
+st_overflow_box_class_init (StOverflowBoxClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass);
+ StWidgetClass *widget_class = ST_WIDGET_CLASS (klass);
+ GParamSpec *pspec;
+
+ g_type_class_add_private (klass, sizeof (StOverflowBoxPrivate));
+
+ object_class->get_property = st_overflow_box_get_property;
+ object_class->set_property = st_overflow_box_set_property;
+ object_class->dispose = st_overflow_box_dispose;
+
+ actor_class->allocate = st_overflow_box_allocate;
+ actor_class->get_preferred_width = st_overflow_box_get_preferred_width;
+ actor_class->get_preferred_height = st_overflow_box_get_preferred_height;
+ actor_class->paint = st_overflow_box_paint;
+ actor_class->pick = st_overflow_box_pick;
+
+ widget_class->style_changed = st_overflow_box_style_changed;
+
+ pspec = g_param_spec_uint ("min-children",
+ "Min Children",
+ "The actor will request a minimum size large enough to include this many children",
+ 0, G_MAXUINT, 0,
+ ST_PARAM_READWRITE);
+ g_object_class_install_property (object_class, PROP_MIN_CHILDREN, pspec);
+}
+
+static void
+st_overflow_box_init (StOverflowBox *self)
+{
+ self->priv = OVERFLOW_BOX_LAYOUT_PRIVATE (self);
+}
+
+/**
+ * st_overflow_box_get_min_children:
+ * @box: A #StOverflowBox
+ *
+ * Get the value of the #StOverflowBox::pack-start property.
+ *
+ * Returns: #TRUE if pack-start is enabled
+ */
+gboolean
+st_overflow_box_get_min_children (StOverflowBox *box)
+{
+ g_return_val_if_fail (ST_IS_OVERFLOW_BOX (box), FALSE);
+
+ return box->priv->min_children;
+}
+
+/**
+ * st_box_layout_set_min_children:
+ * @box: A #StOverflowBox
+ * @min_children: Minimum children value
+ *
+ * Set the minimum number of children to be visible.
+ */
+void
+st_overflow_box_set_min_children (StOverflowBox *box,
+ guint min_children)
+{
+ g_return_if_fail (ST_IS_OVERFLOW_BOX (box));
+
+ if (box->priv->min_children != min_children)
+ {
+ box->priv->min_children = min_children;
+ clutter_actor_queue_relayout ((ClutterActor*) box);
+
+ g_object_notify (G_OBJECT (box), "min-children");
+ }
+}
+
+
+static void
+st_overflow_box_internal_remove_all (StOverflowBox *self,
+ gboolean destroy)
+{
+ StOverflowBoxPrivate *priv = ST_OVERFLOW_BOX (self)->priv;
+ ClutterActor *child;
+
+ while (priv->children)
+ {
+ child = priv->children->data;
+
+ g_object_ref (child);
+ priv->children = g_list_delete_link (priv->children, priv->children);
+ clutter_actor_unparent (child);
+ g_signal_emit_by_name (self, "actor-removed", child);
+ if (destroy)
+ clutter_actor_destroy (child);
+ g_object_unref (child);
+ }
+
+ clutter_actor_queue_relayout ((ClutterActor*) self);
+}
+
+/**
+ * st_overflow_box_remove_all:
+ * @self:
+ *
+ * Efficiently unparent all children currently in this box.
+ */
+void
+st_overflow_box_remove_all (StOverflowBox *self)
+{
+ st_overflow_box_internal_remove_all (self, FALSE);
+}
+
+/**
+ * st_overflow_box_destroy_children:
+ * @self:
+ *
+ * Efficiently unparent and destroy all children currently in this box.
+ */
+void
+st_overflow_box_destroy_children (StOverflowBox *self)
+{
+ st_overflow_box_internal_remove_all (self, TRUE);
+}
+
+/**
+ * st_overflow_box_get_n_children:
+ * @self: a #StOverflowBox
+ *
+ * Returns the number of children in this box.
+ */
+guint
+st_overflow_box_get_n_children (StOverflowBox *self)
+{
+ return g_list_length (self->priv->children);
+}
+
+/**
+ * st_overflow_box_get_n_visible:
+ * @self: a #StOverflowBox
+ *
+ * Returns the number of children we will paint. Only valid
+ * after the actor has been allocated.
+ */
+guint
+st_overflow_box_get_n_visible (StOverflowBox *self)
+{
+ return self->priv->n_visible;
+}
diff --git a/src/st/st-overflow-box.h b/src/st/st-overflow-box.h
new file mode 100644
index 000000000..aad595345
--- /dev/null
+++ b/src/st/st-overflow-box.h
@@ -0,0 +1,75 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-overflow-box.h: box which hides actors that don't fit
+ *
+ * Copyright 2009 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#if !defined(ST_H_INSIDE) && !defined(ST_COMPILATION)
+#error "Only can be included directly.h"
+#endif
+
+#ifndef _ST_OVERFLOW_BOX_H
+#define _ST_OVERFLOW_BOX_H
+
+#include
+
+G_BEGIN_DECLS
+
+#define ST_TYPE_OVERFLOW_BOX st_overflow_box_get_type()
+
+#define ST_OVERFLOW_BOX(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), ST_TYPE_OVERFLOW_BOX, StOverflowBox))
+#define ST_OVERFLOW_BOX_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), ST_TYPE_OVERFLOW_BOX, StOverflowBoxClass))
+#define ST_IS_OVERFLOW_BOX(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), ST_TYPE_OVERFLOW_BOX))
+#define ST_IS_OVERFLOW_BOX_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), ST_TYPE_OVERFLOW_BOX))
+#define ST_OVERFLOW_BOX_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), ST_TYPE_OVERFLOW_BOX, StOverflowBoxClass))
+
+typedef struct _StOverflowBox StOverflowBox;
+typedef struct _StOverflowBoxClass StOverflowBoxClass;
+typedef struct _StOverflowBoxPrivate StOverflowBoxPrivate;
+
+/**
+ * StOverflowBox:
+ *
+ * The contents of this structure are private and should only be accessed
+ * through the public API.
+ */
+struct _StOverflowBox
+{
+ /*< private >*/
+ StWidget parent;
+
+ StOverflowBoxPrivate *priv;
+};
+
+struct _StOverflowBoxClass
+{
+ StWidgetClass parent_class;
+};
+
+GType st_overflow_box_get_type (void);
+
+void st_overflow_box_set_min_children (StOverflowBox *self, guint min_children);
+void st_overflow_box_remove_all (StOverflowBox *box);
+void st_overflow_box_destroy_children (StOverflowBox *box);
+guint st_overflow_box_get_n_children (StOverflowBox *box);
+guint st_overflow_box_get_n_visible (StOverflowBox *box);
+gboolean st_overflow_box_get_min_children (StOverflowBox *box);
+
+G_END_DECLS
+
+#endif /* _ST_OVERFLOW_BOX_H */
diff --git a/src/st/st-private.c b/src/st/st-private.c
index 7332e122a..182e36de7 100644
--- a/src/st/st-private.c
+++ b/src/st/st-private.c
@@ -110,3 +110,52 @@ _st_allocate_fill (ClutterActor *child,
*childbox = allocation;
}
+
+/**
+ * _st_set_text_from_style:
+ * @text: Target #ClutterText
+ * @theme_node: Source #StThemeNode
+ *
+ * Set various GObject properties of the @text object using
+ * CSS information from @theme_node.
+ */
+void
+_st_set_text_from_style (ClutterText *text,
+ StThemeNode *theme_node)
+{
+
+ ClutterColor color;
+ StTextDecoration decoration;
+ PangoAttrList *attribs;
+ const PangoFontDescription *font;
+ gchar *font_string;
+
+ st_theme_node_get_foreground_color (theme_node, &color);
+ clutter_text_set_color (text, &color);
+
+ font = st_theme_node_get_font (theme_node);
+ font_string = pango_font_description_to_string (font);
+ clutter_text_set_font_name (text, font_string);
+ g_free (font_string);
+
+ attribs = pango_attr_list_new ();
+
+ decoration = st_theme_node_get_text_decoration (theme_node);
+ if (decoration & ST_TEXT_DECORATION_UNDERLINE)
+ {
+ PangoAttribute *underline = pango_attr_underline_new (PANGO_UNDERLINE_SINGLE);
+ pango_attr_list_insert (attribs, underline);
+ }
+ if (decoration & ST_TEXT_DECORATION_LINE_THROUGH)
+ {
+ PangoAttribute *strikethrough = pango_attr_strikethrough_new (TRUE);
+ pango_attr_list_insert (attribs, strikethrough);
+ }
+ /* Pango doesn't have an equivalent attribute for _OVERLINE, and we deliberately
+ * skip BLINK (for now...)
+ */
+
+ clutter_text_set_attributes (text, attribs);
+
+ pango_attr_list_unref (attribs);
+}
diff --git a/src/st/st-private.h b/src/st/st-private.h
index 34ef85e74..42ac82423 100644
--- a/src/st/st-private.h
+++ b/src/st/st-private.h
@@ -55,4 +55,7 @@ void _st_allocate_fill (ClutterActor *child,
gboolean x_fill,
gboolean y_fill);
+void _st_set_text_from_style (ClutterText *text,
+ StThemeNode *theme_node);
+
#endif /* __ST_PRIVATE_H__ */
diff --git a/src/st/st-theme-node.c b/src/st/st-theme-node.c
index 29acbd8e0..fb14c68f5 100644
--- a/src/st/st-theme-node.c
+++ b/src/st/st-theme-node.c
@@ -1953,7 +1953,10 @@ st_theme_node_get_font (StThemeNode *node)
}
if (family)
- pango_font_description_set_family (node->font_desc, family);
+ {
+ pango_font_description_set_family (node->font_desc, family);
+ g_free (family);
+ }
if (size_set)
pango_font_description_set_absolute_size (node->font_desc, size);
diff --git a/src/st/st-theme.c b/src/st/st-theme.c
index 944783210..4134b758b 100644
--- a/src/st/st-theme.c
+++ b/src/st/st-theme.c
@@ -44,6 +44,8 @@
#include
#include
+#include
+
#include "st-theme-node.h"
#include "st-theme-private.h"
@@ -68,6 +70,7 @@ struct _StTheme
char *application_stylesheet;
char *default_stylesheet;
char *theme_stylesheet;
+ GSList *custom_stylesheets;
GHashTable *stylesheets_by_filename;
GHashTable *filenames_by_stylesheet;
@@ -193,24 +196,19 @@ convert_rgba_RGBA (char *buf)
}
static CRStyleSheet *
-parse_stylesheet (const char *filename)
+parse_stylesheet (const char *filename,
+ GError **error)
{
enum CRStatus status;
char *contents;
gsize length;
- GError *error = NULL;
CRStyleSheet *stylesheet = NULL;
if (filename == NULL)
return NULL;
- if (!g_file_get_contents (filename, &contents ,&length, &error))
- {
- g_warning("Couldn't read stylesheet: %s", error->message);
- g_error_free (error);
-
- return NULL;
- }
+ if (!g_file_get_contents (filename, &contents, &length, error))
+ return NULL;
convert_rgba_RGBA (contents);
@@ -218,11 +216,14 @@ parse_stylesheet (const char *filename)
length,
CR_UTF_8,
&stylesheet);
+ g_free (contents);
if (status != CR_OK)
- g_warning ("Error parsing stylesheet '%s'", filename);
-
- g_free (contents);
+ {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "Error parsing stylesheet '%s'; errcode:%d", filename, status);
+ return NULL;
+ }
return stylesheet;
}
@@ -243,7 +244,8 @@ _st_theme_parse_declaration_list (const char *str)
}
#else /* LIBCROCO_VERSION_NUMBER >= 602 */
static CRStyleSheet *
-parse_stylesheet (const char *filename)
+parse_stylesheet (const char *filename,
+ GError **error)
{
enum CRStatus status;
CRStyleSheet *stylesheet;
@@ -257,7 +259,8 @@ parse_stylesheet (const char *filename)
if (status != CR_OK)
{
- g_warning ("Error parsing stylesheet '%s'", filename);
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "Error parsing stylesheet '%s'; errcode:%d", filename, status);
return NULL;
}
@@ -272,6 +275,22 @@ _st_theme_parse_declaration_list (const char *str)
}
#endif /* LIBCROCO_VERSION_NUMBER < 602 */
+/* Just g_warning for now until we have something nicer to do */
+static CRStyleSheet *
+parse_stylesheet_nofail (const char *filename)
+{
+ GError *error = NULL;
+ CRStyleSheet *result;
+
+ result = parse_stylesheet (filename, &error);
+ if (error)
+ {
+ g_warning ("%s", error->message);
+ g_clear_error (&error);
+ }
+ return result;
+}
+
static void
insert_stylesheet (StTheme *theme,
const char *filename,
@@ -289,6 +308,42 @@ insert_stylesheet (StTheme *theme,
g_hash_table_insert (theme->filenames_by_stylesheet, stylesheet, filename_copy);
}
+gboolean
+st_theme_load_stylesheet (StTheme *theme,
+ const char *path,
+ GError **error)
+{
+ CRStyleSheet *stylesheet;
+
+ stylesheet = parse_stylesheet (path, error);
+ if (!stylesheet)
+ return FALSE;
+
+ insert_stylesheet (theme, path, stylesheet);
+ theme->custom_stylesheets = g_slist_prepend (theme->custom_stylesheets, stylesheet);
+
+ return TRUE;
+}
+
+void
+st_theme_unload_stylesheet (StTheme *theme,
+ const char *path)
+{
+ CRStyleSheet *stylesheet;
+
+ stylesheet = g_hash_table_lookup (theme->stylesheets_by_filename, path);
+ if (!stylesheet)
+ return;
+
+ if (!g_slist_find (theme->custom_stylesheets, stylesheet))
+ return;
+
+ theme->custom_stylesheets = g_slist_remove (theme->custom_stylesheets, stylesheet);
+ g_hash_table_remove (theme->stylesheets_by_filename, path);
+ g_hash_table_remove (theme->filenames_by_stylesheet, stylesheet);
+ cr_stylesheet_unref (stylesheet);
+}
+
static GObject *
st_theme_constructor (GType type,
guint n_construct_properties,
@@ -305,9 +360,9 @@ st_theme_constructor (GType type,
construct_properties);
theme = ST_THEME (object);
- application_stylesheet = parse_stylesheet (theme->application_stylesheet);
- theme_stylesheet = parse_stylesheet (theme->theme_stylesheet);
- default_stylesheet = parse_stylesheet (theme->default_stylesheet);
+ application_stylesheet = parse_stylesheet_nofail (theme->application_stylesheet);
+ theme_stylesheet = parse_stylesheet_nofail (theme->theme_stylesheet);
+ default_stylesheet = parse_stylesheet_nofail (theme->default_stylesheet);
theme->cascade = cr_cascade_new (application_stylesheet,
theme_stylesheet,
@@ -328,6 +383,10 @@ st_theme_finalize (GObject * object)
{
StTheme *theme = ST_THEME (object);
+ g_slist_foreach (theme->custom_stylesheets, (GFunc) cr_stylesheet_unref, NULL);
+ g_slist_free (theme->custom_stylesheets);
+ theme->custom_stylesheets = NULL;
+
g_hash_table_destroy (theme->stylesheets_by_filename);
g_hash_table_destroy (theme->filenames_by_stylesheet);
@@ -559,6 +618,7 @@ id_add_sel_matches_style (CRAdditionalSel *a_add_sel,
}
/**
+ *additional_selector_matches_style:
*Evaluates if a given additional selector matches an style node.
*@param a_add_sel the additional selector to consider.
*@param a_node the style node to consider.
@@ -862,7 +922,7 @@ add_matched_properties (StTheme *a_this,
import_rule->url->stryng->str);
if (filename)
- import_rule->sheet = parse_stylesheet (filename);
+ import_rule->sheet = parse_stylesheet (filename, NULL);
if (import_rule->sheet)
{
@@ -980,6 +1040,7 @@ _st_theme_get_matched_properties (StTheme *theme,
enum CRStyleOrigin origin = 0;
CRStyleSheet *sheet = NULL;
GPtrArray *props = g_ptr_array_new ();
+ GSList *iter;
g_return_val_if_fail (ST_IS_THEME (theme), NULL);
g_return_val_if_fail (ST_IS_THEME_NODE (node), NULL);
@@ -993,6 +1054,9 @@ _st_theme_get_matched_properties (StTheme *theme,
add_matched_properties (theme, sheet, node, props);
}
+ for (iter = theme->custom_stylesheets; iter; iter = iter->next)
+ add_matched_properties (theme, iter->data, node, props);
+
/* We count on a stable sort here so that later declarations come
* after earlier declarations */
g_ptr_array_sort (props, compare_declarations);
diff --git a/src/st/st-theme.h b/src/st/st-theme.h
index dcf567a00..1a55b29fb 100644
--- a/src/st/st-theme.h
+++ b/src/st/st-theme.h
@@ -33,6 +33,10 @@ StTheme *st_theme_new (const char *application_stylesheet,
const char *theme_stylesheet,
const char *default_stylesheet);
+gboolean st_theme_load_stylesheet (StTheme *theme, const char *path, GError **error);
+
+void st_theme_unload_stylesheet (StTheme *theme, const char *path);
+
G_END_DECLS
#endif /* __ST_THEME_H__ */
diff --git a/tools/build/gnome-shell-build-setup.sh b/tools/build/gnome-shell-build-setup.sh
index 223500353..e254de613 100755
--- a/tools/build/gnome-shell-build-setup.sh
+++ b/tools/build/gnome-shell-build-setup.sh
@@ -155,7 +155,7 @@ if test x$system = xMandrivaLinux ; then
fi
SOURCE=$HOME/Source
-BASEURL=http://git.gnome.org/cgit/gnome-shell/plain/tools/build
+BASEURL=http://git.gnome.org/browse/gnome-shell/plain/tools/build
if [ -d $SOURCE ] ; then : ; else
mkdir $SOURCE
diff --git a/tools/build/jhbuildrc-gnome-shell b/tools/build/jhbuildrc-gnome-shell
index 0202977e6..97c13668b 100644
--- a/tools/build/jhbuildrc-gnome-shell
+++ b/tools/build/jhbuildrc-gnome-shell
@@ -18,7 +18,7 @@
# Only rebuild modules that have changed
build_policy = 'updated'
-moduleset = 'http://git.gnome.org/cgit/gnome-shell/plain/tools/build/gnome-shell.modules'
+moduleset = 'http://git.gnome.org/browse/gnome-shell/plain/tools/build/gnome-shell.modules'
modules = [ 'gnome-shell' ]