From 7b0526dc5c6c11924f0598ecb8143ca8381f7789 Mon Sep 17 00:00:00 2001 From: Florian Scandella Date: Sun, 29 Nov 2009 14:27:56 +0100 Subject: [PATCH 01/44] [chrome] only hide chrome if fullscreen window is on the same monitor https://bugzilla.gnome.org/show_bug.cgi?id=599926 --- js/ui/chrome.js | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) 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; } From 368d484dee02be8536e36a88da2c88c23f3d7bc9 Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Mon, 30 Nov 2009 13:47:58 -0500 Subject: [PATCH 02/44] Avoid variable redeclaration https://bugzilla.gnome.org/show_bug.cgi?id=603327 --- js/ui/workspaces.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/ui/workspaces.js b/js/ui/workspaces.js index 58e982fc0..f523e8a58 100644 --- a/js/ui/workspaces.js +++ b/js/ui/workspaces.js @@ -496,8 +496,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; From b6cc9c7ff625dccf79d58e86a8cb77c82d2ebddd Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Sat, 24 Oct 2009 13:10:15 -0400 Subject: [PATCH 03/44] Add functions to dynamically load/unload stylesheets For implementing extensions, we want the ability to add a stylesheet dynamically, and unload it as well. https://bugzilla.gnome.org/show_bug.cgi?id=599661 --- src/st/st-theme.c | 100 +++++++++++++++++++++++++++++++++++++--------- src/st/st-theme.h | 4 ++ 2 files changed, 86 insertions(+), 18 deletions(-) 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__ */ From a4405be71c432069dcf90f498844d8780fd2abd2 Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Sat, 24 Oct 2009 13:36:52 -0400 Subject: [PATCH 04/44] Add global.log, global.logError functions These log to a display in lookingGlass; rough equivalent of the Firebug "console.log". https://bugzilla.gnome.org/show_bug.cgi?id=599661 --- js/ui/lookingGlass.js | 39 +++++++++++++++++++++++++++++++ js/ui/main.js | 53 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+) diff --git a/js/ui/lookingGlass.js b/js/ui/lookingGlass.js index afe99b864..5aad6e427 100644 --- a/js/ui/lookingGlass.js +++ b/js/ui/lookingGlass.js @@ -308,6 +308,42 @@ 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.text.connect('notify::mapped', Lang.bind(this, this._onMappedNotify)); + }, + + _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' + }, + + _onMappedNotify: function() { + if (!(this.actor.mapped && Main._errorLogStack.length > 0)) + 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 LookingGlass() { this._init(); } @@ -406,6 +442,9 @@ LookingGlass.prototype = { notebook.selectIndex(0); })); + this._errorLog = new ErrorLog(); + notebook.appendPage('Errors', this._errorLog.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 diff --git a/js/ui/main.js b/js/ui/main.js index 5a0ad4cc9..ed24fc373 100644 --- a/js/ui/main.js +++ b/js/ui/main.js @@ -38,6 +38,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 @@ -45,6 +47,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(); @@ -105,6 +113,8 @@ function start() { sidebar = new Sidebar.Sidebar(); wm = new WindowManager.WindowManager(); + _startDate = new Date(); + global.screen.connect('toggle-recording', function() { if (recorder == null) { recorder = new Shell.Recorder({ stage: global.stage }); @@ -127,9 +137,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); From 34003a1f6b9c7c5673782818b6083b2172f460ba Mon Sep 17 00:00:00 2001 From: Daniel Nylander Date: Tue, 1 Dec 2009 23:54:05 +0100 Subject: [PATCH 05/44] Updated Swedish translation --- po/sv.po | 185 +++++++++++++++++++++++++++++-------------------------- 1 file changed, 98 insertions(+), 87 deletions(-) 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" From 20294f2c9204087880e8d379e374789a31793f6a Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Sat, 5 Dec 2009 16:44:19 +0100 Subject: [PATCH 06/44] Updated French translation Contributed by Pablo Martin-Gomez --- po/fr.po | 102 ++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 70 insertions(+), 32 deletions(-) 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" From 478dba85028763daed0078dd5e04a5b06433abee Mon Sep 17 00:00:00 2001 From: Andre Klapper Date: Fri, 11 Dec 2009 18:50:44 +0100 Subject: [PATCH 07/44] Remove non-existing src/shell-status-menu.c from POTFILES.in --- po/POTFILES.in | 1 - 1 file changed, 1 deletion(-) 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 From b03fa1ebf721579908f9f97b2c2e86916266513c Mon Sep 17 00:00:00 2001 From: Andre Klapper Date: Fri, 11 Dec 2009 18:52:17 +0100 Subject: [PATCH 08/44] Add missing "fi" to po/LINGUAS --- po/LINGUAS | 1 + 1 file changed, 1 insertion(+) diff --git a/po/LINGUAS b/po/LINGUAS index 04179aedf..e74cde6d6 100644 --- a/po/LINGUAS +++ b/po/LINGUAS @@ -6,6 +6,7 @@ de el en_GB es +fi fr ga gl From d624db18c51ecb50b7ef7905b932272e2c3a70b6 Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Thu, 3 Dec 2009 12:19:38 -0500 Subject: [PATCH 09/44] Add deferred work system Previously we had various things watching for notify::mapped so we could be more lazy about updating non-visible actors, but it was fairly ad-hoc. The deferred work system unifies these callbacks, and also adds a timeout so that we don't delay changes arbitrarily; this way we avoid a storm of work if you stay out of the overview for a while, then go in. https://bugzilla.gnome.org/show_bug.cgi?id=603522 --- js/ui/appDisplay.js | 41 ++++----------- js/ui/lookingGlass.js | 6 +-- js/ui/main.js | 117 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 130 insertions(+), 34 deletions(-) diff --git a/js/ui/appDisplay.js b/js/ui/appDisplay.js index 8bf359d08..5fad513f6 100644 --- a/js/ui/appDisplay.js +++ b/js/ui/appDisplay.js @@ -236,7 +236,7 @@ BaseWellItem.prototype = { reactive: 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); @@ -262,8 +262,7 @@ 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); @@ -319,18 +318,11 @@ BaseWellItem.prototype = { this.app.disconnect(this._appWindowChangedId); }, - _onMapped: function() { - if (!this._queuedGlowRerender) - return; - this._queuedGlowRerender = false; - this._rerenderGlow(); + _queueRerenderGlow: function() { + Main.queueDeferredWork(this._workId); }, _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(); @@ -956,8 +948,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 +956,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 +968,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(); diff --git a/js/ui/lookingGlass.js b/js/ui/lookingGlass.js index 5aad6e427..358362432 100644 --- a/js/ui/lookingGlass.js +++ b/js/ui/lookingGlass.js @@ -318,7 +318,7 @@ ErrorLog.prototype = { this.text = new St.Label(); this.actor.add(this.text); this.text.clutter_text.line_wrap = true; - this.text.connect('notify::mapped', Lang.bind(this, this._onMappedNotify)); + this.actor.connect('notify::mapped', Lang.bind(this, this._renderText)); }, _formatTime: function(d){ @@ -331,8 +331,8 @@ ErrorLog.prototype = { + pad(d.getUTCSeconds())+'Z' }, - _onMappedNotify: function() { - if (!(this.actor.mapped && Main._errorLogStack.length > 0)) + _renderText: function() { + if (!this.actor.mapped) return; let text = this.text.text; let stack = Main._getAndClearErrorStack(); diff --git a/js/ui/main.js b/js/ui/main.js index ed24fc373..18ce34b35 100644 --- a/js/ui/main.js +++ b/js/ui/main.js @@ -425,3 +425,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; + }); + } +} From 81b7c0170d525a4a7a923c22253312d88729c0c0 Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Thu, 26 Nov 2009 12:13:16 -0500 Subject: [PATCH 10/44] Add set_skip_paint method Ideally we'd be able to override _paint, but given that we can't at the moment, this method gives a way to implement containers which don't happen to paint all of their children. https://bugzilla.gnome.org/show_bug.cgi?id=603522 --- src/shell-generic-container.c | 107 +++++++++++++++++++++++++++++++++- src/shell-generic-container.h | 4 ++ 2 files changed, 110 insertions(+), 1 deletion(-) 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__ */ From 42757a0c8713a0411e3e7cc4f61630a1a74f94fe Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Thu, 3 Dec 2009 15:59:52 -0500 Subject: [PATCH 11/44] Move Main.currentTime() and Main.createAppLaunchContext() into ShellGlobal Primarily motivated by wanting these functions accessible from C. https://bugzilla.gnome.org/show_bug.cgi?id=603522 --- js/ui/appDisplay.js | 10 ++++---- js/ui/appIcon.js | 2 +- js/ui/main.js | 45 +++----------------------------- js/ui/overview.js | 2 +- js/ui/placeDisplay.js | 8 +++--- src/shell-global.c | 60 +++++++++++++++++++++++++++++++++++++++++++ src/shell-global.h | 3 +++ 7 files changed, 77 insertions(+), 53 deletions(-) diff --git a/js/ui/appDisplay.js b/js/ui/appDisplay.js index 5fad513f6..6a88e8c40 100644 --- a/js/ui/appDisplay.js +++ b/js/ui/appDisplay.js @@ -54,7 +54,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(); } @@ -346,7 +346,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; @@ -599,7 +599,7 @@ AppIconMenu.prototype = { this._redisplay(); - this._windowContainer.popup(activatingButton, Main.currentTime()); + this._windowContainer.popup(activatingButton, global.get_current_time()); this.emit('popup', true); @@ -756,7 +756,7 @@ RunningWellItem.prototype = { activateMostRecentWindow: function () { let mostRecentWindow = this.app.get_windows()[0]; - Main.overview.activateWindow(mostRecentWindow, Main.currentTime()); + Main.overview.activateWindow(mostRecentWindow, global.get_current_time()); }, highlightWindow: function(metaWindow) { @@ -766,7 +766,7 @@ RunningWellItem.prototype = { activateWindow: function(metaWindow) { if (metaWindow) { this._didActivateWindow = true; - Main.overview.activateWindow(metaWindow, Main.currentTime()); + Main.overview.activateWindow(metaWindow, global.get_current_time()); } else Main.overview.hide(); }, 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/main.js b/js/ui/main.js index 18ce34b35..d481cc78e 100644 --- a/js/ui/main.js +++ b/js/ui/main.js @@ -293,7 +293,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; } @@ -346,7 +346,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); } @@ -365,45 +365,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 @@ -416,7 +377,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); diff --git a/js/ui/overview.js b/js/ui/overview.js index e217bfe1a..1b13a5d09 100644 --- a/js/ui/overview.js +++ b/js/ui/overview.js @@ -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..82a36745d 100644 --- a/js/ui/placeDisplay.js +++ b/js/ui/placeDisplay.js @@ -65,7 +65,7 @@ PlacesManager.prototype = { 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); @@ -78,7 +78,7 @@ PlacesManager.prototype = { 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..."), @@ -243,7 +243,7 @@ PlacesManager.prototype = { 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); } @@ -272,7 +272,7 @@ PlacesManager.prototype = { return Shell.TextureCache.get_default().load_gicon(mountIcon, size); }, function() { - Gio.app_info_launch_default_for_uri(mountUri, Main.createAppLaunchContext()); + Gio.app_info_launch_default_for_uri(mountUri, global.create_app_launch_context()); }); this._mounts.push(devItem); }, diff --git a/src/shell-global.c b/src/shell-global.c index a36753e9a..542f28dc1 100644 --- a/src/shell-global.c +++ b/src/shell-global.c @@ -1064,3 +1064,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..ef70b6afd 100644 --- a/src/shell-global.h +++ b/src/shell-global.h @@ -82,6 +82,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 From a442dfea1471c95e23fb8bd4b20ad39b146c243f Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Sun, 29 Nov 2009 17:35:35 -0500 Subject: [PATCH 12/44] Add class for handling recent docs Push some of the JS docInfo down into C; crucially, this lets us use the GIO async API. https://bugzilla.gnome.org/show_bug.cgi?id=603522 --- src/Makefile.am | 2 + src/shell-doc-system.c | 362 +++++++++++++++++++++++++++++++++++++++++ src/shell-doc-system.h | 46 ++++++ 3 files changed, 410 insertions(+) create mode 100644 src/shell-doc-system.c create mode 100644 src/shell-doc-system.h 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/shell-doc-system.c b/src/shell-doc-system.c new file mode 100644 index 000000000..c58b34b1b --- /dev/null +++ b/src/shell-doc-system.c @@ -0,0 +1,362 @@ +/* -*- 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; + char *app_exec, *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__ */ From 949f67469c1d0f680326d16f9ceed8897f33b349 Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Sun, 29 Nov 2009 17:43:25 -0500 Subject: [PATCH 13/44] Rebase recent documents on top of ShellDocSystem Rather of calling .exists() synchronously for all documents, use ShellDocSystem's async API to only stat docs we're showing. Use the doc opening functionality in ShellDocSystem. Also, more intelligently do redisplay(); don't recreate actors for recent docs if we already have one for that URI. We may need more intelligent caching here; if e.g. just the associated application changes, we should probably reread the info. https://bugzilla.gnome.org/show_bug.cgi?id=603522 --- js/misc/docInfo.js | 100 +++++++++++++------------------------------- js/ui/docDisplay.js | 83 ++++++++++++++++++++++++------------ js/ui/widget.js | 3 +- 3 files changed, 87 insertions(+), 99 deletions(-) diff --git a/js/misc/docInfo.js b/js/misc/docInfo.js index 8123e281b..e60d7ca61 100644 --- a/js/misc/docInfo.js +++ b/js/misc/docInfo.js @@ -32,50 +32,7 @@ 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() { @@ -91,50 +48,51 @@ 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); } } diff --git a/js/ui/docDisplay.js b/js/ui/docDisplay.js index 2b67f50ba..6afcf4fa9 100644 --- a/js/ui/docDisplay.js +++ b/js/ui/docDisplay.js @@ -152,7 +152,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; }, @@ -275,16 +276,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 +322,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 +363,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 +428,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++) { + 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'); } 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; }); From a8d18ac18a8177b971896be7c5c666a0e720f535 Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Mon, 7 Dec 2009 21:48:31 -0500 Subject: [PATCH 14/44] Limit dash docs to 50 We has poor performance if we try to allocate hundreds of actors; a bit more ideally, we'd keep pulling as much as we can, but really no one should have more than 50 in the list as is now except on unreasonably large screen sizes. In the future we should change this to be pull-on-demand thing in a chunked fashion. https://bugzilla.gnome.org/show_bug.cgi?id=603522 --- js/ui/docDisplay.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/js/ui/docDisplay.js b/js/ui/docDisplay.js index 6afcf4fa9..9ba6c4c65 100644 --- a/js/ui/docDisplay.js +++ b/js/ui/docDisplay.js @@ -16,6 +16,7 @@ const DND = imports.ui.dnd; const GenericDisplay = imports.ui.genericDisplay; const Main = imports.ui.main; +const MAX_DASH_DOCS = 50; const DASH_DOCS_ICON_SIZE = 16; const DEFAULT_SPACING = 4; @@ -453,7 +454,7 @@ DashDocDisplay.prototype = { // Should be kept alive by the _actorsByUri this.actor.remove_all(); let docs = this._docManager.getTimestampOrderedInfos(); - for (let i = 0; i < docs.length; i++) { + for (let i = 0; i < docs.length && i < MAX_DASH_DOCS; i++) { let doc = docs[i]; let display = this._actorsByUri[doc.uri]; if (display) { From 5a3c3efbeb639d0d7e224b5dd5cb7e8c4c72131e Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Tue, 15 Dec 2009 16:29:21 -0500 Subject: [PATCH 15/44] [ShellDocSystem] Check GTK+ version for constness of argument --- src/shell-doc-system.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/shell-doc-system.c b/src/shell-doc-system.c index c58b34b1b..ecb99736e 100644 --- a/src/shell-doc-system.c +++ b/src/shell-doc-system.c @@ -255,7 +255,12 @@ shell_doc_system_open (ShellDocSystem *system, else { char *app_name; - char *app_exec, *app_exec_quoted; +#if GTK_MINOR_VERSION >= 18 + const char *app_exec; +#else + char *app_exec; +#endif + char *app_exec_quoted; guint count; time_t time; From c2fe9e57b1f3ad7307f3611ed57f6268fccd8b48 Mon Sep 17 00:00:00 2001 From: Liel Fridman Date: Fri, 18 Dec 2009 00:36:50 +0200 Subject: [PATCH 16/44] Added Hebrew translation --- po/LINGUAS | 1 + po/he.po | 215 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 216 insertions(+) create mode 100644 po/he.po diff --git a/po/LINGUAS b/po/LINGUAS index e74cde6d6..bfdfb8d67 100644 --- a/po/LINGUAS +++ b/po/LINGUAS @@ -10,6 +10,7 @@ fi fr ga gl +he hu it ko 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" From 14df7cd62c04df33b70f93a4df7a289db076603e Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Sun, 29 Nov 2009 17:25:49 -0500 Subject: [PATCH 17/44] [StOverflowBox] Vertical box which skips painting underallocated children https://bugzilla.gnome.org/show_bug.cgi?id=603523 --- src/Makefile-st.am | 2 + src/st/st-overflow-box.c | 706 +++++++++++++++++++++++++++++++++++++++ src/st/st-overflow-box.h | 75 +++++ 3 files changed, 783 insertions(+) create mode 100644 src/st/st-overflow-box.c create mode 100644 src/st/st-overflow-box.h 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/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 */ From f5f92b2e7993a023ea0f868ff510109a5a26ed98 Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Tue, 8 Dec 2009 12:51:05 -0500 Subject: [PATCH 18/44] [appDisplay] Unify Inactive/RunningWellItem, split into AppIcon, AppWellIcon The distinction between the inactive and running was silly; just have one class which can handle both running states. However for a future search patch, we do want a separation between an icon which just has icon + name + glow, and a well icon which does the menu integration. https://bugzilla.gnome.org/show_bug.cgi?id=603523 --- js/ui/appDisplay.js | 220 +++++++++++++++++++++----------------------- js/ui/overview.js | 2 +- 2 files changed, 104 insertions(+), 118 deletions(-) diff --git a/js/ui/appDisplay.js b/js/ui/appDisplay.js index 6a88e8c40..651ab3ac4 100644 --- a/js/ui/appDisplay.js +++ b/js/ui/appDisplay.js @@ -221,19 +221,20 @@ AppDisplay.prototype = { Signals.addSignalMethods(AppDisplay.prototype); -function BaseWellItem(app, isFavorite) { - this._init(app, isFavorite); +function AppIcon(app) { + this._init(app); } -BaseWellItem.prototype = { - _init : function(app, isFavorite) { +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._workId = Main.initializeDeferredWork(this.actor, Lang.bind(this, this._rerenderGlow)); @@ -241,10 +242,6 @@ BaseWellItem.prototype = { 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 }); @@ -265,13 +262,6 @@ BaseWellItem.prototype = { 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) { @@ -322,6 +312,19 @@ BaseWellItem.prototype = { 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() { this._glowBox.destroy_children(); let glowPath = GLib.filename_to_uri(global.imagedir + 'app-well-glow.png', ''); @@ -332,6 +335,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) { @@ -366,19 +397,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); @@ -402,13 +420,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, Main.currentTime()); }, - 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, Main.currentTime()); + } 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() { @@ -433,7 +498,7 @@ BaseWellItem.prototype = { return this.actor; } } -Signals.addSignalMethods(BaseWellItem.prototype); +Signals.addSignalMethods(AppWellIcon.prototype); function AppIconMenu(source) { this._init(source); @@ -733,80 +798,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, global.get_current_time()); - }, - - highlightWindow: function(metaWindow) { - 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() { - 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(); } @@ -986,12 +977,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++; } @@ -1000,7 +986,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/overview.js b/js/ui/overview.js index 1b13a5d09..74859a724 100644 --- a/js/ui/overview.js +++ b/js/ui/overview.js @@ -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; From b7646d18ae34a38dd3103421cfbff053e907cfa1 Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Sun, 29 Nov 2009 17:45:30 -0500 Subject: [PATCH 19/44] Add search.js, rebase search system on top The high level goal is to separate the concern of searching for things with display of those things; for example in newer mockups, applications are displayed exactly the same as they look in the AppWell. Another goal was optimizing for speed; for example, application search was pushed mostly down into C, and we avoid lowercasing and normalizing every item over and over. https://bugzilla.gnome.org/show_bug.cgi?id=603523 --- data/theme/gnome-shell.css | 34 ++- js/misc/docInfo.js | 57 +++- js/ui/appDisplay.js | 92 ++++++- js/ui/dash.js | 521 ++++++++++++++++++++----------------- js/ui/docDisplay.js | 50 +++- js/ui/overview.js | 1 + js/ui/placeDisplay.js | 236 ++++++++--------- js/ui/search.js | 272 +++++++++++++++++++ src/shell-app-system.c | 443 +++++++++++++++++++------------ src/shell-app-system.h | 23 +- 10 files changed, 1163 insertions(+), 566 deletions(-) create mode 100644 js/ui/search.js diff --git a/data/theme/gnome-shell.css b/data/theme/gnome-shell.css index 8b4263064..7fc91ffe5 100644 --- a/data/theme/gnome-shell.css +++ b/data/theme/gnome-shell.css @@ -152,17 +152,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 +226,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 { diff --git a/js/misc/docInfo.js b/js/misc/docInfo.js index e60d7ca61..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(); }, @@ -35,8 +37,24 @@ DocInfo.prototype = { 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; } }; @@ -93,6 +111,41 @@ DocManager.prototype = { 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/appDisplay.js b/js/ui/appDisplay.js index 651ab3ac4..e7c372a78 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; @@ -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,6 +214,82 @@ AppDisplay.prototype = { Signals.addSignalMethods(AppDisplay.prototype); +function BaseAppSearchProvider() { + this._init(); +} + +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); diff --git a/js/ui/dash.js b/js/ui/dash.js index 1fc2184d6..3fe7e95b5 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(); + + 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() { + 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) { + this._selectedProvider = this._providerMeta.length - 1; + this.selectUp(); + } + }, + + selectDown: function() { + 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) { + this._selectedProvider = 0; + this.selectDown(); + } + }, + + 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(); } @@ -500,9 +752,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 +769,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 +814,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 +834,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(); + 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(); return true; } return false; @@ -666,102 +898,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 +936,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 9ba6c4c65..ee4fa7ee5 100644 --- a/js/ui/docDisplay.js +++ b/js/ui/docDisplay.js @@ -10,11 +10,14 @@ 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; @@ -179,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) { @@ -479,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/overview.js b/js/ui/overview.js index 74859a724..a9697fff0 100644 --- a/js/ui/overview.js +++ b/js/ui/overview.js @@ -184,6 +184,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); diff --git a/js/ui/placeDisplay.js b/js/ui/placeDisplay.js index 82a36745d..868ad11a5 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,16 +30,30 @@ 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; } } @@ -52,6 +66,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,7 +75,7 @@ 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); }, @@ -73,7 +88,7 @@ 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); }, @@ -81,7 +96,7 @@ PlacesManager.prototype = { 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 +116,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 +125,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,7 +263,7 @@ 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); }, @@ -267,7 +292,8 @@ PlacesManager.prototype = { let mountIcon = mount.get_icon(); let root = mount.get_root(); let mountUri = root.get_uri(); - let devItem = new PlaceInfo(mountLabel, + let devItem = new PlaceInfo('mount:' + mountUri, + mountLabel, function(size) { return Shell.TextureCache.get_default().load_gicon(mountIcon, size); }, @@ -282,16 +308,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 +317,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); } }; @@ -421,120 +460,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/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/src/shell-app-system.c b/src/shell-app-system.c index 0f61576d5..ffe3ccc2a 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,249 @@ 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); + info->name_collation_key = g_utf8_collate_key (name, -1); + + 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); + + 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__ */ From 6b1c3d323b8014213de27097977d5e3648e46738 Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Fri, 18 Dec 2009 10:25:06 -0500 Subject: [PATCH 20/44] [js/ui/Makefile.am] Install search.js --- js/ui/Makefile.am | 1 + 1 file changed, 1 insertion(+) diff --git a/js/ui/Makefile.am b/js/ui/Makefile.am index 350885ef8..c58d37b24 100644 --- a/js/ui/Makefile.am +++ b/js/ui/Makefile.am @@ -19,6 +19,7 @@ dist_jsui_DATA = \ panel.js \ placeDisplay.js \ runDialog.js \ + search.js \ shellDBus.js \ sidebar.js \ statusMenu.js \ From 4394bc3e4039566cce543e416ae027281c1d7557 Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Fri, 18 Dec 2009 10:39:02 -0500 Subject: [PATCH 21/44] [dash] Avoid infinite recursion in keynav --- js/ui/dash.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/js/ui/dash.js b/js/ui/dash.js index 3fe7e95b5..f70be4dfc 100644 --- a/js/ui/dash.js +++ b/js/ui/dash.js @@ -514,7 +514,7 @@ SearchResults.prototype = { meta.count.set_text(""+providerResults.length); } - this.selectDown(); + this.selectDown(false); return true; }, @@ -535,7 +535,7 @@ SearchResults.prototype = { return resultDisplay.selectIndex(index); }, - selectUp: function() { + selectUp: function(recursing) { for (let i = this._selectedProvider; i >= 0; i--) { let meta = this._providerMeta[i]; if (!meta.actor.visible) @@ -546,13 +546,13 @@ SearchResults.prototype = { return; } } - if (this._providerMeta.length > 0) { + if (this._providerMeta.length > 0 && !recursing) { this._selectedProvider = this._providerMeta.length - 1; - this.selectUp(); + this.selectUp(true); } }, - selectDown: function() { + selectDown: function(recursing) { let current = this._selectedProvider; if (current == -1) current = 0; @@ -566,9 +566,9 @@ SearchResults.prototype = { return; } } - if (this._providerMeta.length > 0) { + if (this._providerMeta.length > 0 && !recursing) { this._selectedProvider = 0; - this.selectDown(); + this.selectDown(true); } }, @@ -834,14 +834,14 @@ Dash.prototype = { } else if (symbol == Clutter.Up) { if (!this._searchActive) return true; - this.searchResults.selectUp(); + this.searchResults.selectUp(false); return true; } else if (symbol == Clutter.Down) { if (!this._searchActive) return true; - this.searchResults.selectDown(); + this.searchResults.selectDown(false); return true; } return false; From aa9d3515a1afd124668e7a749d9602b3a547e924 Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Sun, 25 Oct 2009 18:53:10 -0400 Subject: [PATCH 22/44] Add extensionSystem Consumer documentation will live at http://live.gnome.org/GnomeShell/Extensions In terms of implementation; basically we load extensions from the well-known directories. Add a GConf key to disable extensions by uuid. There is a new option --create-extension for the gnome-shell script which takes a bit of interactive input, sets up some sample files, and launches gedit. No extensions UI in this patch; that will come later. https://bugzilla.gnome.org/show_bug.cgi?id=599661 --- data/gnome-shell.schemas | 15 ++++ js/ui/extensionSystem.js | 158 +++++++++++++++++++++++++++++++++++++++ js/ui/main.js | 4 + src/gnome-shell.in | 83 ++++++++++++++++++++ src/shell-global.c | 67 +++++++++++++++++ src/shell-global.h | 6 ++ 6 files changed, 333 insertions(+) create mode 100644 js/ui/extensionSystem.js mode change 100755 => 100644 src/gnome-shell.in 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/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/main.js b/js/ui/main.js index d481cc78e..7361ac301 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 Overview = imports.ui.overview; const Panel = imports.ui.panel; const PlaceDisplay = imports.ui.placeDisplay; @@ -129,6 +130,9 @@ function start() { _relayout(); + ExtensionSystem.init(); + ExtensionSystem.loadExtensions(); + panel.startupAnimation(); let display = global.screen.get_display(); diff --git a/src/gnome-shell.in b/src/gnome-shell.in old mode 100755 new mode 100644 index b009ec495..62c86d604 --- 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,87 @@ 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') + json.dump(meta, f) + 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(['gedit', extensionjs_path]) + sys.exit(0) + if options.eval_file: import dbus diff --git a/src/shell-global.c b/src/shell-global.c index 542f28dc1..ac68bc5d7 100644 --- a/src/shell-global.c +++ b/src/shell-global.c @@ -522,6 +522,73 @@ 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_set_error(error, + G_IO_ERROR, + G_IO_ERROR_FAILED, + "Invalid 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 diff --git a/src/shell-global.h b/src/shell-global.h index ef70b6afd..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 { From c4a49b4de203e70c1fc461925d563966f5677f28 Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Tue, 27 Oct 2009 13:27:00 -0400 Subject: [PATCH 23/44] Port more to CSS https://bugzilla.gnome.org/show_bug.cgi?id=599661 --- data/theme/gnome-shell.css | 13 +++++++++++++ js/ui/lookingGlass.js | 20 ++++++++------------ 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/data/theme/gnome-shell.css b/data/theme/gnome-shell.css index 7fc91ffe5..3c2a6dc48 100644 --- a/data/theme/gnome-shell.css +++ b/data/theme/gnome-shell.css @@ -364,6 +364,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; diff --git a/js/ui/lookingGlass.js b/js/ui/lookingGlass.js index 358362432..469c86dbc 100644 --- a/js/ui/lookingGlass.js +++ b/js/ui/lookingGlass.js @@ -39,24 +39,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 +61,7 @@ Notebook.prototype = { let tabData = { child: child, labelBox: labelBox, + label: label, scrollView: scrollview, _scrollToBottom: false }; this._tabs.push(tabData); @@ -82,8 +80,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 +94,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); From 55fbb9d0af6d8f533876d6e8cf07c0fbf665f996 Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Wed, 2 Dec 2009 14:42:26 -0500 Subject: [PATCH 24/44] [St] Implement text-decoration: [underline|strikethrough] Move CSS handling of StLabel and StButton for their underlying ClutterText objects into st_private, and implement support for the underline and strikethrough St text-decoration properties. Overline isn't implemented for lack of a corresponding Pango attribute, and blink, well... https://bugzilla.gnome.org/show_bug.cgi?id=599661 --- src/st/st-button.c | 14 +------------ src/st/st-label.c | 18 +++-------------- src/st/st-private.c | 49 +++++++++++++++++++++++++++++++++++++++++++++ src/st/st-private.h | 3 +++ 4 files changed, 56 insertions(+), 28 deletions(-) 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-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__ */ From c0ff0066e6eb2c9f2f0251e3e2277f596557e261 Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Wed, 2 Dec 2009 12:13:24 -0500 Subject: [PATCH 25/44] Make link.js into a St.Button, delete unused link imports It's actually totally unused at the moment, but a future patch will use it. https://bugzilla.gnome.org/show_bug.cgi?id=599661 --- data/theme/gnome-shell.css | 9 +++++ js/ui/genericDisplay.js | 1 - js/ui/link.js | 67 +++----------------------------------- js/ui/overview.js | 1 - 4 files changed, 14 insertions(+), 64 deletions(-) diff --git a/data/theme/gnome-shell.css b/data/theme/gnome-shell.css index 3c2a6dc48..62684f574 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; 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/overview.js b/js/ui/overview.js index a9697fff0..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; From 2cb4dfb4a4d2b3ab4afcc133883c8d16b20b286f Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Wed, 28 Oct 2009 18:05:34 -0400 Subject: [PATCH 26/44] [lookingGlass] Add primitive extensions list In the future this should be more like the Firefox Addons UI probably with enable/disable/update checking, but stub it out here for now. https://bugzilla.gnome.org/show_bug.cgi?id=599661 --- data/theme/gnome-shell.css | 23 +++++++++ js/ui/lookingGlass.js | 100 +++++++++++++++++++++++++++++++++++++ src/shell-global.c | 5 +- 3 files changed, 124 insertions(+), 4 deletions(-) diff --git a/data/theme/gnome-shell.css b/data/theme/gnome-shell.css index 62684f574..ce39715e7 100644 --- a/data/theme/gnome-shell.css +++ b/data/theme/gnome-shell.css @@ -402,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 { diff --git a/js/ui/lookingGlass.js b/js/ui/lookingGlass.js index 469c86dbc..f66321dc5 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; @@ -340,6 +344,99 @@ ErrorLog.prototype = { } } +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(); } @@ -441,6 +538,9 @@ LookingGlass.prototype = { 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 diff --git a/src/shell-global.c b/src/shell-global.c index ac68bc5d7..7fd37c998 100644 --- a/src/shell-global.c +++ b/src/shell-global.c @@ -577,10 +577,7 @@ shell_global_add_extension_importer (ShellGlobal *global, if (!JSVAL_IS_OBJECT (target_object)) { - g_set_error(error, - G_IO_ERROR, - G_IO_ERROR_FAILED, - "Invalid object"); + g_error ("shell_global_add_extension_importer: invalid target object"); return FALSE; } From d1edefdc39c442c65856badd7e28dd52f45aee35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=BCllner?= Date: Sun, 22 Nov 2009 04:51:28 +0100 Subject: [PATCH 27/44] Reposition items at start of fade in We need to set the positions after the animation ends (and fadeIn starts), not before when allocation is changed. As the window may still be in motion when the overlay is repositioned, it is safer to pass the calculated values as parameters. https://bugzilla.gnome.org/show_bug.cgi?id=602598 --- js/ui/workspaces.js | 49 +++++++++++++++++++++++++++++++++------------ 1 file changed, 36 insertions(+), 13 deletions(-) diff --git a/js/ui/workspaces.js b/js/ui/workspaces.js index f523e8a58..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)); }, @@ -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); } }, From 1cd9a15ac0fe99b9c346b19b43d79c94f70836e9 Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Fri, 18 Dec 2009 11:28:39 -0500 Subject: [PATCH 28/44] [js/ui/Makefile.am] Install extensionSystem.js --- js/ui/Makefile.am | 1 + 1 file changed, 1 insertion(+) diff --git a/js/ui/Makefile.am b/js/ui/Makefile.am index c58d37b24..5ef241b57 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 \ From f4c05deb2d3b3b52d64e6945b7843fb8d685069d Mon Sep 17 00:00:00 2001 From: Maxim Ermilov Date: Fri, 18 Dec 2009 01:12:46 +0300 Subject: [PATCH 29/44] Completion in alt+F2 Bind for command's completion and path's completion. https://bugzilla.gnome.org/show_bug.cgi?id=597677 --- js/ui/runDialog.js | 178 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 178 insertions(+) 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; From 288eae91e2d7e5547e4d5a3ad305d4a4b8c7b0ef Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Fri, 18 Dec 2009 12:29:25 -0500 Subject: [PATCH 30/44] [ShellAppSystem] Initialize collation keys for info on-demand In case of duplicate infos structures with the same id, the info structures we get from looking up the id in app_id_to_info aren't necessarily the same as those we used to match, so we can't rely on matching to implicitly initialize info->casefolded_name. Since the name collation key isn't used in matching results, just in sorting, init it on-demand in the sorting which is also more efficient. --- src/shell-app-system.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/shell-app-system.c b/src/shell-app-system.c index ffe3ccc2a..ab609ec1f 100644 --- a/src/shell-app-system.c +++ b/src/shell-app-system.c @@ -622,7 +622,6 @@ shell_app_info_init_search_data (ShellAppInfo *info) name = gmenu_tree_entry_get_name ((GMenuTreeEntry*)info->entry); info->casefolded_name = normalize_and_casefold (name); - info->name_collation_key = g_utf8_collate_key (name, -1); comment = gmenu_tree_entry_get_comment ((GMenuTreeEntry*)info->entry); info->casefolded_description = normalize_and_casefold (comment); @@ -675,6 +674,11 @@ shell_app_info_compare (gconstpointer a, 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); } From 45f4292259672bd21495d9ed45d765b1f0635092 Mon Sep 17 00:00:00 2001 From: Siegfried-Angel Gevatter Pujals Date: Fri, 18 Dec 2009 20:22:51 +0100 Subject: [PATCH 31/44] Make "gnome-shell --create-extension" compatible with Python 2.5 Fallback to using the json-py module on systems with a Python version older than 2.6 (which introduced native JSON support). --- src/gnome-shell.in | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/gnome-shell.in b/src/gnome-shell.in index 62c86d604..2142c8f02 100644 --- a/src/gnome-shell.in +++ b/src/gnome-shell.in @@ -260,7 +260,11 @@ use an extension title clicktofocus@janedoe.example.com.''' 'description': description, 'uuid': uuid } f = open(os.path.join(extension_path, 'metadata.json'), 'w') - json.dump(meta, f) + 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') From e326202477693b2e21429af06ff464f918d43f54 Mon Sep 17 00:00:00 2001 From: Siegfried-Angel Gevatter Pujals Date: Fri, 18 Dec 2009 20:26:41 +0100 Subject: [PATCH 32/44] Use the preferred text editor when opening an extension's .js file Change "./src/gnome-shell --create-extension" to use "gnome-open" when opening the newly created .js file, so that it is launched with the user's preferred text editor, instead of hardcoding gedit. --- src/gnome-shell.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gnome-shell.in b/src/gnome-shell.in index 2142c8f02..41354ad0a 100644 --- a/src/gnome-shell.in +++ b/src/gnome-shell.in @@ -303,7 +303,7 @@ function main() { ''') f.close() - subprocess.Popen(['gedit', extensionjs_path]) + subprocess.Popen(['gnome-open', extensionjs_path]) sys.exit(0) if options.eval_file: From 1c4c3afb270bbfcfb76b93f50cb252dca1c4f3e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=BCllner?= Date: Fri, 27 Nov 2009 12:20:02 +0100 Subject: [PATCH 33/44] Add eject buttons in places menu for places which support it https://bugzilla.gnome.org/show_bug.cgi?id=602976 --- js/ui/placeDisplay.js | 90 +++++++++++++++++++++++++++++++++---------- 1 file changed, 69 insertions(+), 21 deletions(-) diff --git a/js/ui/placeDisplay.js b/js/ui/placeDisplay.js index 868ad11a5..160a029df 100644 --- a/js/ui/placeDisplay.js +++ b/js/ui/placeDisplay.js @@ -54,9 +54,54 @@ PlaceInfo.prototype = { 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(); } @@ -288,18 +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('mount:' + mountUri, - mountLabel, - function(size) { - return Shell.TextureCache.get_default().load_gicon(mountIcon, size); - }, - function() { - Gio.app_info_launch_default_for_uri(mountUri, global.create_app_launch_context()); - }); + let devItem = new PlaceDeviceInfo(mount); this._mounts.push(devItem); }, @@ -358,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; }, From 44712c274e67f6cfd1ce6d35a300e4ea0c4313b2 Mon Sep 17 00:00:00 2001 From: Siegfried-Angel Gevatter Pujals Date: Fri, 18 Dec 2009 21:22:58 +0100 Subject: [PATCH 34/44] Change event handling so Escape always works Until now LookingGlass could only be closed using the Escape key when the Evaluator class was active, as the relevant code was associated to the text input field; this moves it into a global event handler instead which works everywhere in LG. --- js/ui/lookingGlass.js | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/js/ui/lookingGlass.js b/js/ui/lookingGlass.js index f66321dc5..783d3ef0f 100644 --- a/js/ui/lookingGlass.js +++ b/js/ui/lookingGlass.js @@ -556,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++; @@ -704,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; @@ -711,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; @@ -729,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); From 44cc42484fc62d73739e4686af5768fe8a2270ae Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Fri, 18 Dec 2009 17:35:06 -0500 Subject: [PATCH 35/44] [appDisplay] Switch to using global.get_current_time() Main.currentTime() moved into ShellGlobal. --- js/ui/appDisplay.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/ui/appDisplay.js b/js/ui/appDisplay.js index e7c372a78..fbe096a1b 100644 --- a/js/ui/appDisplay.js +++ b/js/ui/appDisplay.js @@ -492,7 +492,7 @@ AppWellIcon.prototype = { activateMostRecentWindow: function () { let mostRecentWindow = this.app.get_windows()[0]; - Main.overview.activateWindow(mostRecentWindow, Main.currentTime()); + Main.overview.activateWindow(mostRecentWindow, global.get_current_time()); }, highlightWindow: function(metaWindow) { @@ -504,7 +504,7 @@ AppWellIcon.prototype = { activateWindow: function(metaWindow) { if (metaWindow) { this._didActivateWindow = true; - Main.overview.activateWindow(metaWindow, Main.currentTime()); + Main.overview.activateWindow(metaWindow, global.get_current_time()); } else Main.overview.hide(); }, From 2435c603ec0a62ccccfb403c30c471200347d8f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Gonz=C3=A1lez?= Date: Sat, 19 Dec 2009 14:55:33 +0100 Subject: [PATCH 36/44] Updated Spanish translation --- po/es.po | 181 +++++++++++++++++++++++++------------------------------ 1 file changed, 82 insertions(+), 99 deletions(-) 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" From 6e1a791273d8adae0369b193d7aa7059638f7d1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=BCllner?= Date: Sun, 15 Nov 2009 02:11:20 +0100 Subject: [PATCH 37/44] Allow clicking on any part of section header to browse This is more intuitive, though we need to make the whole thing feel more like a button. https://bugzilla.gnome.org/show_bug.cgi?id=596985 --- js/ui/dash.js | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/js/ui/dash.js b/js/ui/dash.js index f70be4dfc..e50c8538c 100644 --- a/js/ui/dash.js +++ b/js/ui/dash.js @@ -596,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) { @@ -637,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); @@ -662,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; }, From 0e5c83bd2a83e6496ec8e95b51e1f9a62dd6f4ac Mon Sep 17 00:00:00 2001 From: "Owen W. Taylor" Date: Sun, 20 Dec 2009 11:28:59 -0500 Subject: [PATCH 38/44] Fix URLs pointing to git.gnome.org/cgit git.gnome.org/cgit was relocated to git.gnome.org/browse; adjust URLs in our build script to match. --- tools/build/gnome-shell-build-setup.sh | 2 +- tools/build/jhbuildrc-gnome-shell | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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' ] From 350e9583833dac9d1acac0fd2a2edd68b39982b6 Mon Sep 17 00:00:00 2001 From: Florian Scandella Date: Wed, 2 Dec 2009 18:21:30 +0100 Subject: [PATCH 39/44] position sidebar centerd on the left side of the primary monitor https://bugzilla.gnome.org/show_bug.cgi?id=604177 --- js/ui/sidebar.js | 10 ++++++++++ 1 file changed, 10 insertions(+) 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() { From 554549338a2f869b162a014e96091ff50ebce194 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ant=C3=B3n=20M=C3=A9ixome?= Date: Mon, 28 Dec 2009 17:07:26 +0100 Subject: [PATCH 40/44] Updated Galician Translation --- po/gl.po | 240 ++++++++++++++++++++++++++++++------------------------- 1 file changed, 133 insertions(+), 107 deletions(-) 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" From edce0e8feb8478f64c72282e6800f7ed9a9282b7 Mon Sep 17 00:00:00 2001 From: Milo Casagrande Date: Mon, 28 Dec 2009 21:59:19 +0100 Subject: [PATCH 41/44] Updated Italian translation --- po/it.po | 186 +++++++++++++++++++++++++------------------------------ 1 file changed, 84 insertions(+), 102 deletions(-) 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..." From 767fe0ebc246704168244cd23848f987fd7fb88e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Mon, 4 Jan 2010 13:48:27 -0500 Subject: [PATCH 42/44] [St] Don't leak the font family name A copy of the string is made by pango_font_description_set_family() and we don't need the string anymore, so we should free it https://bugzilla.gnome.org/show_bug.cgi?id=605035 --- src/st/st-theme-node.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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); From 9423cc7fae8c1f326b0452762ed9638f860fe115 Mon Sep 17 00:00:00 2001 From: magcius Date: Fri, 18 Dec 2009 18:50:40 -0500 Subject: [PATCH 43/44] Fix application thumbnail sizing when the height is greater than the width of a window. This bug happened because the thumbnail box was only maxed, not set to 256x256. Standard window thumbnails would max out only the width to 256. Having a window with its height greater than its width meant that the height would max out, but not the width, causing thumbnails to look uneven. https://bugzilla.gnome.org/show_bug.cgi?id=604963 --- data/theme/gnome-shell.css | 5 +++++ js/ui/altTab.js | 11 +++++++---- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/data/theme/gnome-shell.css b/data/theme/gnome-shell.css index ce39715e7..91f08affa 100644 --- a/data/theme/gnome-shell.css +++ b/data/theme/gnome-shell.css @@ -493,6 +493,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/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) { From a2cae50e0edfc5e1a22f5f02513b7781c7a38975 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Urban=C4=8Di=C4=8D?= Date: Tue, 5 Jan 2010 16:56:33 +0100 Subject: [PATCH 44/44] Updated Slovenian translation --- po/sl.po | 165 ++++++++++++++++++++++++------------------------------- 1 file changed, 71 insertions(+), 94 deletions(-) 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"