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' ]