Merge branch 'master' into message-tray

Conflicts:
	js/ui/main.js
This commit is contained in:
Dan Winship 2010-01-05 11:25:15 -05:00
commit 09653fbaf6
53 changed files with 4702 additions and 1572 deletions

View File

@ -88,6 +88,21 @@
</locale> </locale>
</schema> </schema>
<schema>
<key>/schemas/desktop/gnome/shell/disabled_extensions</key>
<applyto>/desktop/gnome/shell/disabled_extensions</applyto>
<owner>gnome-shell</owner>
<type>list</type>
<list_type>string</list_type>
<default>[]</default>
<locale name="C">
<short>Uuids of extensions to disable</short>
<long>
GNOME Shell extensions have a uuid property; this key lists extensions which should not be loaded.
</long>
</locale>
</schema>
</schemalist> </schemalist>
</gconfschemafile> </gconfschemafile>

View File

@ -17,6 +17,15 @@
* Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
*/ */
.shell-link {
color: #0000ff;
text-decoration: underline;
}
.shell-link:hover {
color: #0000e0;
}
StScrollBar StScrollBar
{ {
padding: 0px; padding: 0px;
@ -152,17 +161,6 @@ StTooltip {
spacing: 12px; 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 { #searchEntry {
padding: 4px; padding: 4px;
border-bottom: 1px solid #262626; border-bottom: 1px solid #262626;
@ -237,6 +235,29 @@ StTooltip {
height: 16px; 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 */ /* GenericDisplay */
.generic-display-container { .generic-display-container {
@ -352,6 +373,19 @@ StTooltip {
border-radius: 4px; border-radius: 4px;
} }
#LookingGlassDialog .labels {
spacing: 4px;
}
#LookingGlassDialog .notebook-tab {
padding: 2px;
}
#LookingGlassDialog .notebook-tab:selected {
border: 1px solid #88ff66;
padding: 1px;
}
#LookingGlassDialog StLabel #LookingGlassDialog StLabel
{ {
color: #88ff66; color: #88ff66;
@ -368,6 +402,29 @@ StTooltip {
spacing: 4px; 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 */ /* Calendar popup */
#calendarPopup { #calendarPopup {
@ -458,6 +515,11 @@ StTooltip {
spacing: 4px; spacing: 4px;
} }
.switcher-list .thumbnail {
width: 256px;
height: 256px;
}
.switcher-list .outlined-item-box { .switcher-list .outlined-item-box {
padding: 6px; padding: 6px;
border: 2px solid rgba(85,85,85,1.0); border: 2px solid rgba(85,85,85,1.0);

View File

@ -7,6 +7,7 @@ const Shell = imports.gi.Shell;
const Lang = imports.lang; const Lang = imports.lang;
const Signals = imports.signals; const Signals = imports.signals;
const Search = imports.ui.search;
const Main = imports.ui.main; const Main = imports.ui.main;
const THUMBNAIL_ICON_MARGIN = 2; const THUMBNAIL_ICON_MARGIN = 2;
@ -23,6 +24,7 @@ DocInfo.prototype = {
// correctly. See http://bugzilla.gnome.org/show_bug.cgi?id=567094 // correctly. See http://bugzilla.gnome.org/show_bug.cgi?id=567094
this.timestamp = recentInfo.get_modified().getTime() / 1000; this.timestamp = recentInfo.get_modified().getTime() / 1000;
this.name = recentInfo.get_display_name(); this.name = recentInfo.get_display_name();
this._lowerName = this.name.toLowerCase();
this.uri = recentInfo.get_uri(); this.uri = recentInfo.get_uri();
this.mimeType = recentInfo.get_mime_type(); this.mimeType = recentInfo.get_mime_type();
}, },
@ -32,54 +34,27 @@ DocInfo.prototype = {
}, },
launch : function() { launch : function() {
// While using Gio.app_info_launch_default_for_uri() would be Shell.DocSystem.get_default().open(this.recentInfo);
// 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
// '%<something>' 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);
}
}
}, },
exists : function() { matchTerms: function(terms) {
return this.recentInfo.exists(); let mtype = Search.MatchType.NONE;
for (let i = 0; i < terms.length; i++) {
let term = terms[i];
let idx = this._lowerName.indexOf(term);
if (idx == 0) {
if (mtype != Search.MatchType.NONE)
return Search.MatchType.MULTIPLE;
mtype = Search.MatchType.PREFIX;
} else if (idx > 0) {
if (mtype != Search.MatchType.NONE)
return Search.MatchType.MULTIPLE;
mtype = Search.MatchType.SUBSTRING;
} else {
continue;
}
}
return mtype;
} }
}; };
@ -91,50 +66,86 @@ function getDocManager() {
return docManagerInstance; return docManagerInstance;
} }
/**
* DocManager wraps the DocSystem, primarily to expose DocInfo objects
* which conform to the GenericDisplay item API.
*/
function DocManager() { function DocManager() {
this._init(); this._init();
} }
DocManager.prototype = { DocManager.prototype = {
_init: function() { _init: function() {
this._recentManager = Gtk.RecentManager.get_default(); this._docSystem = Shell.DocSystem.get_default();
this._items = {}; this._infosByTimestamp = [];
this._recentManager.connect('changed', Lang.bind(this, function(recentManager) { this._infosByUri = {};
this._reload(); this._docSystem.connect('changed', Lang.bind(this, this._reload));
this.emit('changed');
}));
this._reload(); this._reload();
}, },
_reload: function() { _reload: function() {
let docs = this._recentManager.get_items(); let docs = this._docSystem.get_all();
let newItems = {}; this._infosByTimestamp = [];
this._infosByUri = {};
for (let i = 0; i < docs.length; i++) { for (let i = 0; i < docs.length; i++) {
let recentInfo = docs[i]; let recentInfo = docs[i];
if (!recentInfo.exists())
continue;
let docInfo = new DocInfo(recentInfo); let docInfo = new DocInfo(recentInfo);
this._infosByTimestamp.push(docInfo);
// we use GtkRecentInfo URI as an item Id this._infosByUri[docInfo.uri] = docInfo;
newItems[docInfo.uri] = docInfo;
} }
let deleted = {}; this.emit('changed');
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;
}, },
getItems: function() { getTimestampOrderedInfos: function() {
return this._items; return this._infosByTimestamp;
},
getInfosByUri: function() {
return this._infosByUri;
},
lookupByUri: function(uri) {
return this._infosByUri[uri];
},
queueExistenceCheck: function(count) {
return this._docSystem.queue_existence_check(count);
},
initialSearch: function(terms) {
let multipleMatches = [];
let prefixMatches = [];
let substringMatches = [];
for (let i = 0; i < this._infosByTimestamp.length; i++) {
let item = this._infosByTimestamp[i];
let mtype = item.matchTerms(terms);
if (mtype == Search.MatchType.MULTIPLE)
multipleMatches.push(item.uri);
else if (mtype == Search.MatchType.PREFIX)
prefixMatches.push(item.uri);
else if (mtype == Search.MatchType.SUBSTRING)
substringMatches.push(item.uri);
}
return multipleMatches.concat(prefixMatches.concat(substringMatches));
},
subsearch: function(previousResults, terms) {
let multipleMatches = [];
let prefixMatches = [];
let substringMatches = [];
for (let i = 0; i < previousResults.length; i++) {
let uri = previousResults[i];
let item = this._infosByUri[uri];
let mtype = item.matchTerms(terms);
if (mtype == Search.MatchType.MULTIPLE)
multipleMatches.push(uri);
else if (mtype == Search.MatchType.PREFIX)
prefixMatches.push(uri);
else if (mtype == Search.MatchType.SUBSTRING)
substringMatches.push(uri);
}
return multipleMatches.concat(prefixMatches.concat(substringMatches));
} }
} }

View File

@ -10,6 +10,7 @@ dist_jsui_DATA = \
dnd.js \ dnd.js \
docDisplay.js \ docDisplay.js \
environment.js \ environment.js \
extensionSystem.js \
genericDisplay.js \ genericDisplay.js \
lightbox.js \ lightbox.js \
link.js \ link.js \
@ -20,6 +21,7 @@ dist_jsui_DATA = \
panel.js \ panel.js \
placeDisplay.js \ placeDisplay.js \
runDialog.js \ runDialog.js \
search.js \
shellDBus.js \ shellDBus.js \
sidebar.js \ sidebar.js \
statusMenu.js \ statusMenu.js \

View File

@ -592,7 +592,7 @@ AppIcon.prototype = {
this.actor = new St.BoxLayout({ style_class: "alt-tab-app", this.actor = new St.BoxLayout({ style_class: "alt-tab-app",
vertical: true }); vertical: true });
this._icon = this.app.create_icon_texture(POPUP_APPICON_SIZE); 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._label = new St.Label({ text: this.app.get_name() });
this.actor.add(this._label, { x_fill: false }); this.actor.add(this._label, { x_fill: false });
} }
@ -750,11 +750,14 @@ ThumbnailList.prototype = {
let box = new St.BoxLayout({ style_class: "thumbnail-box", let box = new St.BoxLayout({ style_class: "thumbnail-box",
vertical: true }); vertical: true });
let bin = new St.Bin({ style_class: "thumbnail" });
let clone = new Clutter.Clone ({ source: windowTexture, let clone = new Clutter.Clone ({ source: windowTexture,
reactive: true, reactive: true,
width: width * scale, width: width * scale,
height: height * scale }); height: height * scale });
box.add_actor(clone);
bin.add_actor(clone);
box.add_actor(bin);
let title = windows[i].get_title(); let title = windows[i].get_title();
if (title) { if (title) {

View File

@ -18,6 +18,7 @@ const AppFavorites = imports.ui.appFavorites;
const DND = imports.ui.dnd; const DND = imports.ui.dnd;
const GenericDisplay = imports.ui.genericDisplay; const GenericDisplay = imports.ui.genericDisplay;
const Main = imports.ui.main; const Main = imports.ui.main;
const Search = imports.ui.search;
const Workspaces = imports.ui.workspaces; const Workspaces = imports.ui.workspaces;
const APPICON_SIZE = 48; const APPICON_SIZE = 48;
@ -54,7 +55,7 @@ AppDisplayItem.prototype = {
let windows = app.get_windows(); let windows = app.get_windows();
if (windows.length > 0) { if (windows.length > 0) {
let mostRecentWindow = windows[0]; let mostRecentWindow = windows[0];
Main.overview.activateWindow(mostRecentWindow, Main.currentTime()); Main.overview.activateWindow(mostRecentWindow, global.get_current_time());
} else { } else {
this._appInfo.launch(); this._appInfo.launch();
} }
@ -134,19 +135,12 @@ AppDisplay.prototype = {
this._addApp(app); this._addApp(app);
} }
} else { } else {
// Loop over the toplevel menu items, load the set of desktop file ids let apps = this._appSystem.get_flattened_apps();
// associated with each one. for (let i = 0; i < apps.length; i++) {
let allMenus = this._appSystem.get_menus(); let app = apps[i];
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); this._addApp(app);
} }
} }
}
this._appsStale = false; this._appsStale = false;
return false; return false;
@ -220,31 +214,104 @@ AppDisplay.prototype = {
Signals.addSignalMethods(AppDisplay.prototype); Signals.addSignalMethods(AppDisplay.prototype);
function BaseAppSearchProvider() {
function BaseWellItem(app, isFavorite) { this._init();
this._init(app, isFavorite);
} }
BaseWellItem.prototype = { BaseAppSearchProvider.prototype = {
_init : function(app, isFavorite) { __proto__: Search.SearchProvider.prototype,
_init: function(name) {
Search.SearchProvider.prototype._init.call(this, name);
this._appSys = Shell.AppSystem.get_default();
},
getResultMeta: function(resultId) {
let app = this._appSys.get_app(resultId);
if (!app)
return null;
return { 'id': resultId,
'name': app.get_name(),
'icon': app.create_icon_texture(Search.RESULT_ICON_SIZE)};
},
activateResult: function(id) {
let app = this._appSys.get_app(id);
app.launch();
}
};
function AppSearchProvider() {
this._init();
}
AppSearchProvider.prototype = {
__proto__: BaseAppSearchProvider.prototype,
_init: function() {
BaseAppSearchProvider.prototype._init.call(this, _("APPLICATIONS"));
},
getInitialResultSet: function(terms) {
return this._appSys.initial_search(false, terms);
},
getSubsearchResultSet: function(previousResults, terms) {
return this._appSys.subsearch(false, previousResults, terms);
},
expandSearch: function(terms) {
log("TODO expand search");
}
}
function PrefsSearchProvider() {
this._init();
}
PrefsSearchProvider.prototype = {
__proto__: BaseAppSearchProvider.prototype,
_init: function() {
BaseAppSearchProvider.prototype._init.call(this, _("PREFERENCES"));
},
getInitialResultSet: function(terms) {
return this._appSys.initial_search(true, terms);
},
getSubsearchResultSet: function(previousResults, terms) {
return this._appSys.subsearch(true, previousResults, terms);
},
expandSearch: function(terms) {
let controlCenter = this._appSys.load_from_desktop_file('gnomecc.desktop');
controlCenter.launch();
Main.overview.hide();
}
}
function AppIcon(app) {
this._init(app);
}
AppIcon.prototype = {
_init : function(app) {
this.app = app; this.app = app;
this._glowExtendVertical = 0; this._glowExtendVertical = 0;
this._glowShrinkHorizontal = 0; this._glowShrinkHorizontal = 0;
this.actor = new St.Clickable({ style_class: 'app-well-app', this.actor = new St.Bin({ style_class: 'app-icon',
reactive: true }); x_fill: true,
y_fill: true });
this.actor._delegate = this; this.actor._delegate = this;
this.actor.connect('destroy', Lang.bind(this, this._onDestroy)); 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 }); let box = new St.BoxLayout({ vertical: true });
this.actor.set_child(box); 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); this.icon = this.app.create_icon_texture(APPICON_SIZE);
box.add(this.icon, { expand: true, x_fill: false, y_fill: false }); box.add(this.icon, { expand: true, x_fill: false, y_fill: false });
@ -262,17 +329,9 @@ BaseWellItem.prototype = {
this._glowBox.connect('style-changed', Lang.bind(this, this._onStyleChanged)); this._glowBox.connect('style-changed', Lang.bind(this, this._onStyleChanged));
this._nameBox.add_actor(this._glowBox); this._nameBox.add_actor(this._glowBox);
this._glowBox.lower(this._name); this._glowBox.lower(this._name);
this._appWindowChangedId = this.app.connect('windows-changed', Lang.bind(this, this._rerenderGlow)); this._appWindowChangedId = this.app.connect('windows-changed', Lang.bind(this, this._queueRerenderGlow));
this._rerenderGlow();
box.add(nameBox); 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) { _nameBoxGetPreferredWidth: function (nameBox, forHeight, alloc) {
@ -319,18 +378,24 @@ BaseWellItem.prototype = {
this.app.disconnect(this._appWindowChangedId); this.app.disconnect(this._appWindowChangedId);
}, },
_onMapped: function() { _queueRerenderGlow: function() {
if (!this._queuedGlowRerender) Main.queueDeferredWork(this._workId);
return; },
this._queuedGlowRerender = false;
this._rerenderGlow(); _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() { _rerenderGlow: function() {
if (!this.actor.mapped) {
this._queuedGlowRerender = true;
return;
}
this._glowBox.destroy_children(); this._glowBox.destroy_children();
let glowPath = GLib.filename_to_uri(global.imagedir + 'app-well-glow.png', ''); let glowPath = GLib.filename_to_uri(global.imagedir + 'app-well-glow.png', '');
let windows = this.app.get_windows(); let windows = this.app.get_windows();
@ -340,6 +405,34 @@ BaseWellItem.prototype = {
glow.keep_aspect_ratio = false; glow.keep_aspect_ratio = false;
this._glowBox.add(glow); 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) { _onButtonPress: function(actor, event) {
@ -354,7 +447,7 @@ BaseWellItem.prototype = {
if (this.actor.pressed && this._dragStartX != null) { if (this.actor.pressed && this._dragStartX != null) {
this.actor.fake_release(); this.actor.fake_release();
this._draggable.startDrag(this._dragStartX, this._dragStartY, this._draggable.startDrag(this._dragStartX, this._dragStartY,
Main.currentTime()); global.get_current_time());
} else { } else {
this._dragStartX = null; this._dragStartX = null;
this._dragStartY = null; this._dragStartY = null;
@ -374,19 +467,6 @@ BaseWellItem.prototype = {
return false; 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) { popupMenu: function(activatingButton) {
if (!this._menu) { if (!this._menu) {
this._menu = new AppIconMenu(this); this._menu = new AppIconMenu(this);
@ -410,13 +490,60 @@ BaseWellItem.prototype = {
return false; return false;
}, },
// Default implementations; AppDisplay.RunningWellItem overrides these activateMostRecentWindow: function () {
highlightWindow: function(window) { let mostRecentWindow = this.app.get_windows()[0];
this.emit('highlight-window', window); Main.overview.activateWindow(mostRecentWindow, global.get_current_time());
}, },
activateWindow: function(window) { highlightWindow: function(metaWindow) {
this.emit('activate-window', window); if (!this._getRunning())
return;
Main.overview.getWorkspacesForWindow(metaWindow).setHighlightWindow(metaWindow);
},
activateWindow: function(metaWindow) {
if (metaWindow) {
this._didActivateWindow = true;
Main.overview.activateWindow(metaWindow, global.get_current_time());
} else
Main.overview.hide();
},
_onMenuPoppedUp: function() {
if (this._getRunning()) {
Main.overview.getWorkspacesForWindow(null).setApplicationWindowSelection(this.app.get_id());
this._setWindowSelection = true;
}
},
_onMenuPoppedDown: function() {
if (this._didActivateWindow)
return;
if (!this._setWindowSelection)
return;
Main.overview.getWorkspacesForWindow(null).setApplicationWindowSelection(null);
this._setWindowSelection = false;
},
_getRunning: function() {
return this.app.get_windows().length > 0;
},
_onActivate: function (event) {
let running = this._getRunning();
if (!running) {
this.app.launch();
} else {
let modifiers = Shell.get_event_state(event);
if (modifiers & Clutter.ModifierType.CONTROL_MASK) {
this.app.launch();
} else {
this.activateMostRecentWindow();
}
}
}, },
shellWorkspaceLaunch : function() { shellWorkspaceLaunch : function() {
@ -441,7 +568,7 @@ BaseWellItem.prototype = {
return this.actor; return this.actor;
} }
} }
Signals.addSignalMethods(BaseWellItem.prototype); Signals.addSignalMethods(AppWellIcon.prototype);
function AppIconMenu(source) { function AppIconMenu(source) {
this._init(source); this._init(source);
@ -607,7 +734,7 @@ AppIconMenu.prototype = {
this._redisplay(); this._redisplay();
this._windowContainer.popup(activatingButton, Main.currentTime()); this._windowContainer.popup(activatingButton, global.get_current_time());
this.emit('popup', true); this.emit('popup', true);
@ -741,80 +868,6 @@ AppIconMenu.prototype = {
}; };
Signals.addSignalMethods(AppIconMenu.prototype); Signals.addSignalMethods(AppIconMenu.prototype);
function RunningWellItem(app, isFavorite) {
this._init(app, isFavorite);
}
RunningWellItem.prototype = {
__proto__: BaseWellItem.prototype,
_init: function(app, isFavorite) {
BaseWellItem.prototype._init.call(this, app, isFavorite);
},
_onActivate: function (event) {
let modifiers = Shell.get_event_state(event);
if (modifiers & Clutter.ModifierType.CONTROL_MASK) {
this.app.launch();
} else {
this.activateMostRecentWindow();
}
},
activateMostRecentWindow: function () {
let mostRecentWindow = this.app.get_windows()[0];
Main.overview.activateWindow(mostRecentWindow, Main.currentTime());
},
highlightWindow: function(metaWindow) {
Main.overview.getWorkspacesForWindow(metaWindow).setHighlightWindow(metaWindow);
},
activateWindow: function(metaWindow) {
if (metaWindow) {
this._didActivateWindow = true;
Main.overview.activateWindow(metaWindow, Main.currentTime());
} else
Main.overview.hide();
},
_onMenuPoppedUp: function() {
Main.overview.getWorkspacesForWindow(null).setApplicationWindowSelection(this.app.get_id());
},
_onMenuPoppedDown: function() {
if (this._didActivateWindow)
return;
Main.overview.getWorkspacesForWindow(null).setApplicationWindowSelection(null);
}
};
function InactiveWellItem(app, isFavorite) {
this._init(app, isFavorite);
}
InactiveWellItem.prototype = {
__proto__: BaseWellItem.prototype,
_init : function(app, isFavorite) {
BaseWellItem.prototype._init.call(this, app, isFavorite);
},
_onActivate: function(event) {
this.app.launch();
Main.overview.hide();
return true;
},
_onMenuPoppedUp: function() {
},
_onMenuPoppedDown: function() {
}
};
function WellGrid() { function WellGrid() {
this._init(); this._init();
} }
@ -956,8 +1009,7 @@ AppWell.prototype = {
x_align: Big.BoxAlignment.CENTER }); x_align: Big.BoxAlignment.CENTER });
this.actor._delegate = this; this.actor._delegate = this;
this._pendingRedisplay = false; this._workId = Main.initializeDeferredWork(this.actor, Lang.bind(this, this._redisplay));
this.actor.connect('notify::mapped', Lang.bind(this, this._onMappedNotify));
this._grid = new WellGrid(); this._grid = new WellGrid();
this.actor.append(this._grid.actor, Big.BoxPackFlags.EXPAND); this.actor.append(this._grid.actor, Big.BoxPackFlags.EXPAND);
@ -965,12 +1017,9 @@ AppWell.prototype = {
this._tracker = Shell.WindowTracker.get_default(); this._tracker = Shell.WindowTracker.get_default();
this._appSystem = Shell.AppSystem.get_default(); this._appSystem = Shell.AppSystem.get_default();
this._appSystem.connect('installed-changed', Lang.bind(this, this._redisplay)); this._appSystem.connect('installed-changed', Lang.bind(this, this._queueRedisplay));
AppFavorites.getAppFavorites().connect('changed', Lang.bind(this, this._queueRedisplay));
AppFavorites.getAppFavorites().connect('changed', Lang.bind(this, this._redisplay)); this._tracker.connect('app-running-changed', Lang.bind(this, this._queueRedisplay));
this._tracker.connect('app-running-changed', Lang.bind(this, this._redisplay));
this._redisplay();
}, },
_appIdListToHash: function(apps) { _appIdListToHash: function(apps) {
@ -980,20 +1029,11 @@ AppWell.prototype = {
return ids; return ids;
}, },
_onMappedNotify: function() { _queueRedisplay: function () {
let mapped = this.actor.mapped; Main.queueDeferredWork(this._workId);
if (mapped && this._pendingRedisplay)
this._redisplay();
}, },
_redisplay: function () { _redisplay: function () {
let mapped = this.actor.mapped;
if (!mapped) {
this._pendingRedisplay = true;
return;
}
this._pendingRedisplay = false;
this._grid.removeAll(); this._grid.removeAll();
let favorites = AppFavorites.getAppFavorites().getFavoriteMap(); let favorites = AppFavorites.getAppFavorites().getFavoriteMap();
@ -1007,12 +1047,7 @@ AppWell.prototype = {
let nFavorites = 0; let nFavorites = 0;
for (let id in favorites) { for (let id in favorites) {
let app = favorites[id]; let app = favorites[id];
let display; let display = new AppWellIcon(app);
if (app.get_windows().length > 0) {
display = new RunningWellItem(app, true);
} else {
display = new InactiveWellItem(app, true);
}
this._grid.addItem(display.actor); this._grid.addItem(display.actor);
nFavorites++; nFavorites++;
} }
@ -1021,7 +1056,7 @@ AppWell.prototype = {
let app = running[i]; let app = running[i];
if (app.get_id() in favorites) if (app.get_id() in favorites)
continue; continue;
let display = new RunningWellItem(app, false); let display = new AppWellIcon(app);
this._grid.addItem(display.actor); this._grid.addItem(display.actor);
} }

View File

@ -472,7 +472,7 @@ AppIconMenu.prototype = {
this._redisplay(); this._redisplay();
this._windowContainer.popup(activatingButton, Main.currentTime()); this._windowContainer.popup(activatingButton, global.get_current_time());
this.emit('popup', true); this.emit('popup', true);

View File

@ -191,6 +191,7 @@ Chrome.prototype = {
_windowsRestacked: function() { _windowsRestacked: function() {
let windows = global.get_windows(); let windows = global.get_windows();
let primary = global.get_primary_monitor();
// The chrome layer should be visible unless there is a window // The chrome layer should be visible unless there is a window
// with layer FULLSCREEN, or a window with layer // with layer FULLSCREEN, or a window with layer
@ -208,17 +209,15 @@ Chrome.prototype = {
for (let i = windows.length - 1; i > -1; i--) { for (let i = windows.length - 1; i > -1; i--) {
let layer = windows[i].get_meta_window().get_layer(); let layer = windows[i].get_meta_window().get_layer();
if (layer == Meta.StackLayer.OVERRIDE_REDIRECT) { if (layer == Meta.StackLayer.OVERRIDE_REDIRECT ||
if (windows[i].x <= 0 && layer == Meta.StackLayer.FULLSCREEN) {
windows[i].x + windows[i].width >= global.screen_width && if (windows[i].x <= primary.x &&
windows[i].y <= 0 && windows[i].x + windows[i].width >= primary.x + primary.width &&
windows[i].y + windows[i].height >= global.screen_height) { windows[i].y <= primary.y &&
windows[i].y + windows[i].height >= primary.y + primary.height) {
this._obscuredByFullscreen = true; this._obscuredByFullscreen = true;
break; break;
} }
} else if (layer == Meta.StackLayer.FULLSCREEN) {
this._obscuredByFullscreen = true;
break;
} else } else
break; break;
} }

View File

@ -17,6 +17,10 @@ const DocDisplay = imports.ui.docDisplay;
const PlaceDisplay = imports.ui.placeDisplay; const PlaceDisplay = imports.ui.placeDisplay;
const GenericDisplay = imports.ui.genericDisplay; const GenericDisplay = imports.ui.genericDisplay;
const Main = imports.ui.main; 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_PADDING = 4;
const DEFAULT_SPACING = 4; const DEFAULT_SPACING = 4;
@ -332,6 +336,254 @@ SearchEntry.prototype = {
}; };
Signals.addSignalMethods(SearchEntry.prototype); Signals.addSignalMethods(SearchEntry.prototype);
function SearchResult(provider, metaInfo, terms) {
this._init(provider, metaInfo, terms);
}
SearchResult.prototype = {
_init: function(provider, metaInfo, terms) {
this.provider = provider;
this.metaInfo = metaInfo;
this.actor = new St.Clickable({ style_class: 'dash-search-result',
reactive: true,
x_align: St.Align.START,
x_fill: true,
y_fill: true });
this.actor._delegate = this;
let content = provider.createResultActor(metaInfo, terms);
if (content == null) {
content = new St.BoxLayout({ style_class: 'dash-search-result-content' });
let title = new St.Label({ text: this.metaInfo['name'] });
let icon = this.metaInfo['icon'];
content.add(icon, { y_fill: false });
content.add(title, { expand: true, y_fill: false });
}
this._content = content;
this.actor.set_child(content);
this.actor.connect('clicked', Lang.bind(this, this._onResultClicked));
},
setSelected: function(selected) {
this._content.set_style_pseudo_class(selected ? 'selected' : null);
},
activate: function() {
this.provider.activateResult(this.metaInfo.id);
Main.overview.toggle();
},
_onResultClicked: function(actor, event) {
this.activate();
}
}
function OverflowSearchResults(provider) {
this._init(provider);
}
OverflowSearchResults.prototype = {
__proto__: Search.SearchResultDisplay.prototype,
_init: function(provider) {
Search.SearchResultDisplay.prototype._init.call(this, provider);
this.actor = new St.OverflowBox({ style_class: 'dash-search-section-list-results' });
},
renderResults: function(results, terms) {
for (let i = 0; i < results.length && i < MAX_RENDERED_SEARCH_RESULTS; i++) {
let result = results[i];
let meta = this.provider.getResultMeta(result);
let display = new SearchResult(this.provider, meta, terms);
this.actor.add_actor(display.actor);
}
},
getVisibleCount: function() {
return this.actor.get_n_visible();
},
selectIndex: function(index) {
let nVisible = this.actor.get_n_visible();
let children = this.actor.get_children();
if (this.selectionIndex >= 0) {
let prevActor = children[this.selectionIndex];
prevActor._delegate.setSelected(false);
}
this.selectionIndex = -1;
if (index >= nVisible)
return false;
else if (index < 0)
return false;
let targetActor = children[index];
targetActor._delegate.setSelected(true);
this.selectionIndex = index;
return true;
}
}
function SearchResults(searchSystem) {
this._init(searchSystem);
}
SearchResults.prototype = {
_init: function(searchSystem) {
this._searchSystem = searchSystem;
this.actor = new St.BoxLayout({ name: 'dashSearchResults',
vertical: true });
this._searchingNotice = new St.Label({ style_class: 'dash-search-starting',
text: _("Searching...") });
this.actor.add(this._searchingNotice);
this._selectedProvider = -1;
this._providers = this._searchSystem.getProviders();
this._providerMeta = [];
for (let i = 0; i < this._providers.length; i++) {
let provider = this._providers[i];
let providerBox = new St.BoxLayout({ style_class: 'dash-search-section',
vertical: true });
let titleButton = new St.Button({ style_class: 'dash-search-section-header',
reactive: true,
x_fill: true,
y_fill: true });
titleButton.connect('clicked', Lang.bind(this, function () { this._onHeaderClicked(provider); }));
providerBox.add(titleButton);
let titleBox = new St.BoxLayout();
titleButton.set_child(titleBox);
let title = new St.Label({ text: provider.title });
let count = new St.Label();
titleBox.add(title, { expand: true });
titleBox.add(count);
let resultDisplayBin = new St.Bin({ style_class: 'dash-search-section-results',
x_fill: true,
y_fill: true });
providerBox.add(resultDisplayBin, { expand: true });
let resultDisplay = provider.createResultContainerActor();
if (resultDisplay == null) {
resultDisplay = new OverflowSearchResults(provider);
}
resultDisplayBin.set_child(resultDisplay.actor);
this._providerMeta.push({ actor: providerBox,
resultDisplay: resultDisplay,
count: count });
this.actor.add(providerBox);
}
},
_clearDisplay: function() {
this._selectedProvider = -1;
this._visibleResultsCount = 0;
for (let i = 0; i < this._providerMeta.length; i++) {
let meta = this._providerMeta[i];
meta.resultDisplay.clear();
meta.actor.hide();
}
},
reset: function() {
this._searchSystem.reset();
this._searchingNotice.hide();
this._clearDisplay();
},
startingSearch: function() {
this.reset();
this._searchingNotice.show();
},
_metaForProvider: function(provider) {
return this._providerMeta[this._providers.indexOf(provider)];
},
updateSearch: function (searchString) {
let results = this._searchSystem.updateSearch(searchString);
this._searchingNotice.hide();
this._clearDisplay();
let terms = this._searchSystem.getTerms();
for (let i = 0; i < results.length; i++) {
let [provider, providerResults] = results[i];
let meta = this._metaForProvider(provider);
meta.actor.show();
meta.resultDisplay.renderResults(providerResults, terms);
meta.count.set_text(""+providerResults.length);
}
this.selectDown(false);
return true;
},
_onHeaderClicked: function(provider) {
provider.expandSearch(this._searchSystem.getTerms());
},
_modifyActorSelection: function(resultDisplay, up) {
let success;
let index = resultDisplay.getSelectionIndex();
if (up && index == -1)
index = resultDisplay.getVisibleCount() - 1;
else if (up)
index = index - 1;
else
index = index + 1;
return resultDisplay.selectIndex(index);
},
selectUp: function(recursing) {
for (let i = this._selectedProvider; i >= 0; i--) {
let meta = this._providerMeta[i];
if (!meta.actor.visible)
continue;
let success = this._modifyActorSelection(meta.resultDisplay, true);
if (success) {
this._selectedProvider = i;
return;
}
}
if (this._providerMeta.length > 0 && !recursing) {
this._selectedProvider = this._providerMeta.length - 1;
this.selectUp(true);
}
},
selectDown: function(recursing) {
let current = this._selectedProvider;
if (current == -1)
current = 0;
for (let i = current; i < this._providerMeta.length; i++) {
let meta = this._providerMeta[i];
if (!meta.actor.visible)
continue;
let success = this._modifyActorSelection(meta.resultDisplay, false);
if (success) {
this._selectedProvider = i;
return;
}
}
if (this._providerMeta.length > 0 && !recursing) {
this._selectedProvider = 0;
this.selectDown(true);
}
},
activateSelected: function() {
let current = this._selectedProvider;
if (current < 0)
return;
let meta = this._providerMeta[current];
let resultDisplay = meta.resultDisplay;
let children = resultDisplay.actor.get_children();
let targetActor = children[resultDisplay.getSelectionIndex()];
targetActor._delegate.activate();
}
}
function MoreLink() { function MoreLink() {
this._init(); this._init();
} }
@ -344,15 +596,15 @@ MoreLink.prototype = {
let expander = new St.Bin({ style_class: "more-link-expander" }); let expander = new St.Bin({ style_class: "more-link-expander" });
this.actor.add(expander, { expand: true, y_fill: false }); this.actor.add(expander, { expand: true, y_fill: false });
},
this.actor.connect('button-press-event', Lang.bind(this, function (b, e) { activate: function() {
if (this.pane == null) { if (this.pane == null) {
// Ensure the pane is created; the activated handler will call setPane // Ensure the pane is created; the activated handler will call setPane
this.emit('activated'); this.emit('activated');
} }
this._pane.toggle(); this._pane.toggle();
return true; return true;
}));
}, },
setPane: function (pane) { setPane: function (pane) {
@ -385,7 +637,8 @@ SectionHeader.prototype = {
this.actor = new St.Bin({ style_class: "section-header", this.actor = new St.Bin({ style_class: "section-header",
x_align: St.Align.START, x_align: St.Align.START,
x_fill: true, x_fill: true,
y_fill: true }); y_fill: true,
reactive: !suppressBrowse });
this._innerBox = new St.BoxLayout({ style_class: "section-header-inner" }); this._innerBox = new St.BoxLayout({ style_class: "section-header-inner" });
this.actor.set_child(this._innerBox); this.actor.set_child(this._innerBox);
@ -410,9 +663,14 @@ SectionHeader.prototype = {
if (!suppressBrowse) { if (!suppressBrowse) {
this.moreLink = new MoreLink(); this.moreLink = new MoreLink();
this._innerBox.add(this.moreLink.actor, { x_align: St.Align.END }); 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) { setTitle : function(title) {
this.text.text = title; this.text.text = title;
}, },
@ -500,9 +758,9 @@ Dash.prototype = {
vertical: true, vertical: true,
reactive: true }); reactive: true });
// Size for this one explicitly set from overlay.js // The searchArea just holds the entry
this.searchArea = new Big.Box({ y_align: Big.BoxAlignment.CENTER }); this.searchArea = new St.BoxLayout({ name: "dashSearchArea",
vertical: true });
this.sectionArea = new St.BoxLayout({ name: "dashSections", this.sectionArea = new St.BoxLayout({ name: "dashSections",
vertical: true }); vertical: true });
@ -517,16 +775,35 @@ Dash.prototype = {
this._searchActive = false; this._searchActive = false;
this._searchPending = false; this._searchPending = false;
this._searchEntry = new SearchEntry(); 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._searchTimeoutId = 0;
this._searchEntry.entry.connect('text-changed', Lang.bind(this, function (se, prop) { this._searchEntry.entry.connect('text-changed', Lang.bind(this, function (se, prop) {
let text = this._searchEntry.getText(); 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; let searchPreviouslyActive = this._searchActive;
this._searchActive = text != ''; this._searchActive = text != '';
this._searchPending = this._searchActive && !searchPreviouslyActive; 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._searchActive) {
if (this._searchTimeoutId > 0) { if (this._searchTimeoutId > 0) {
Mainloop.source_remove(this._searchTimeoutId); Mainloop.source_remove(this._searchTimeoutId);
@ -543,24 +820,15 @@ Dash.prototype = {
Mainloop.source_remove(this._searchTimeoutId); Mainloop.source_remove(this._searchTimeoutId);
this._doSearch(); this._doSearch();
} }
// Only one of the displays will have an item selected, so it's ok to this.searchResults.activateSelected();
// call activateSelected() on all of them.
for (var i = 0; i < this._searchSections.length; i++) {
let section = this._searchSections[i];
section.resultArea.display.activateSelected();
}
return true; return true;
})); }));
this._searchEntry.entry.connect('key-press-event', Lang.bind(this, function (se, e) { this._searchEntry.entry.connect('key-press-event', Lang.bind(this, function (se, e) {
let text = this._searchEntry.getText();
let symbol = e.get_key_symbol(); let symbol = e.get_key_symbol();
if (symbol == Clutter.Escape) { if (symbol == Clutter.Escape) {
// Escape will keep clearing things back to the desktop. // 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. // If we have an active search, we remove it.
else if (this._searchActive) if (this._searchActive)
this._searchEntry.reset(); this._searchEntry.reset();
// Next, if we're in one of the "more" modes or showing the details pane, close them // Next, if we're in one of the "more" modes or showing the details pane, close them
else if (this._activePane != null) else if (this._activePane != null)
@ -572,44 +840,14 @@ Dash.prototype = {
} else if (symbol == Clutter.Up) { } else if (symbol == Clutter.Up) {
if (!this._searchActive) if (!this._searchActive)
return true; return true;
// selectUp and selectDown wrap around in their respective displays this.searchResults.selectUp(false);
// 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;
}
}
return true; return true;
} else if (symbol == Clutter.Down) { } else if (symbol == Clutter.Down) {
if (!this._searchActive) if (!this._searchActive)
return true; return true;
for (var i = 0; i < this._searchSections.length; i++) {
let section = this._searchSections[i]; this.searchResults.selectDown(false);
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;
}
}
return true; return true;
} }
return false; return false;
@ -666,102 +904,12 @@ Dash.prototype = {
this._docDisplay.emit('changed'); this._docDisplay.emit('changed');
this.sectionArea.add(this._docsSection.actor, { expand: true }); 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 () { _doSearch: function () {
this._searchTimeoutId = 0; this._searchTimeoutId = 0;
let text = this._searchEntry.getText(); let text = this._searchEntry.getText();
text = text.replace(/^\s+/g, "").replace(/\s+$/g, ""); this.searchResults.updateSearch(text);
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();
return false; return false;
}, },
@ -794,101 +942,6 @@ Dash.prototype = {
} }
})); }));
Main.overview.addPane(pane); 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); Signals.addSignalMethods(Dash.prototype);

View File

@ -10,12 +10,16 @@ const Shell = imports.gi.Shell;
const Signals = imports.signals; const Signals = imports.signals;
const St = imports.gi.St; const St = imports.gi.St;
const Mainloop = imports.mainloop; const Mainloop = imports.mainloop;
const Gettext = imports.gettext.domain('gnome-shell');
const _ = Gettext.gettext;
const DocInfo = imports.misc.docInfo; const DocInfo = imports.misc.docInfo;
const DND = imports.ui.dnd; const DND = imports.ui.dnd;
const GenericDisplay = imports.ui.genericDisplay; const GenericDisplay = imports.ui.genericDisplay;
const Main = imports.ui.main; const Main = imports.ui.main;
const Search = imports.ui.search;
const MAX_DASH_DOCS = 50;
const DASH_DOCS_ICON_SIZE = 16; const DASH_DOCS_ICON_SIZE = 16;
const DEFAULT_SPACING = 4; const DEFAULT_SPACING = 4;
@ -152,7 +156,8 @@ DocDisplay.prototype = {
_refreshCache : function() { _refreshCache : function() {
if (!this._docsStale) if (!this._docsStale)
return true; return true;
this._allItems = this._docManager.getItems(); this._allItems = {};
Lang.copyProperties(this._docManager.getInfosByUri(), this._allItems);
this._docsStale = false; this._docsStale = false;
return false; return false;
}, },
@ -177,13 +182,8 @@ DocDisplay.prototype = {
this._matchedItemKeys = []; this._matchedItemKeys = [];
let docIdsToRemove = []; let docIdsToRemove = [];
for (docId in this._allItems) { 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._matchedItems[docId] = 1;
this._matchedItemKeys.push(docId); this._matchedItemKeys.push(docId);
} else {
docIdsToRemove.push(docId);
}
} }
for (docId in docIdsToRemove) { for (docId in docIdsToRemove) {
@ -275,16 +275,21 @@ DashDocDisplayItem.prototype = {
Main.overview.hide(); Main.overview.hide();
})); }));
this.actor._delegate = this;
this._icon = docInfo.createIcon(DASH_DOCS_ICON_SIZE); this._icon = docInfo.createIcon(DASH_DOCS_ICON_SIZE);
let iconBox = new Big.Box({ y_align: Big.BoxAlignment.CENTER }); let iconBox = new Big.Box({ y_align: Big.BoxAlignment.CENTER });
iconBox.append(this._icon, Big.BoxPackFlags.NONE); iconBox.append(this._icon, Big.BoxPackFlags.NONE);
this.actor.append(iconBox, 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 }); text: docInfo.name });
this.actor.append(name, Big.BoxPackFlags.EXPAND); this.actor.append(name, Big.BoxPackFlags.EXPAND);
let draggable = DND.makeDraggable(this.actor); let draggable = DND.makeDraggable(this.actor);
this.actor._delegate = this; },
getUri: function() {
return this._info.uri;
}, },
getDragActorSource: function() { getDragActorSource: function() {
@ -316,12 +321,14 @@ DashDocDisplay.prototype = {
this.actor.connect('get-preferred-width', Lang.bind(this, this._getPreferredWidth)); 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('get-preferred-height', Lang.bind(this, this._getPreferredHeight));
this.actor.connect('allocate', Lang.bind(this, this._allocate)); 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 = DocInfo.getDocManager();
this._docManager.connect('changed', Lang.bind(this, function(mgr) { this._docManager.connect('changed', Lang.bind(this, this._onDocsChanged));
this._redisplay(); this._pendingDocsChange = true;
})); this._checkDocExistence = false;
this._redisplay();
}, },
_getPreferredWidth: function(actor, forHeight, alloc) { _getPreferredWidth: function(actor, forHeight, alloc) {
@ -355,15 +362,17 @@ DashDocDisplay.prototype = {
let firstColumnChildren = Math.ceil(children.length / 2); let firstColumnChildren = Math.ceil(children.length / 2);
let natural = 0;
for (let i = 0; i < firstColumnChildren; i++) { for (let i = 0; i < firstColumnChildren; i++) {
let child = children[i]; let child = children[i];
let [minSize, naturalSize] = child.get_preferred_height(forWidth); let [minSize, naturalSize] = child.get_preferred_height(-1);
alloc.natural_size += naturalSize; natural += naturalSize;
if (i > 0 && i < children.length - 1) { if (i > 0 && i < children.length - 1) {
alloc.natural_size += DEFAULT_SPACING; natural += DEFAULT_SPACING;
} }
} }
alloc.natural_size = natural;
}, },
_allocate: function(actor, box, flags) { _allocate: function(actor, box, flags) {
@ -418,28 +427,49 @@ DashDocDisplay.prototype = {
i++; i++;
} }
// Everything else didn't fit, just hide it. if (this._checkDocExistence) {
for (; i < children.length; i++) { // Now we know how many docs we are displaying, queue a check to see if any of them
children[i].hide(); // 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() { _redisplay: function() {
// Should be kept alive by the _actorsByUri
this.actor.remove_all(); this.actor.remove_all();
let docs = this._docManager.getTimestampOrderedInfos();
let docs = this._docManager.getItems(); for (let i = 0; i < docs.length && i < MAX_DASH_DOCS; i++) {
let docUrls = []; let doc = docs[i];
for (let url in docs) { let display = this._actorsByUri[doc.uri];
docUrls.push(url); if (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); this.actor.add_actor(display.actor);
} else {
let display = new DashDocDisplayItem(doc);
this.actor.add_actor(display.actor);
this._actorsByUri[doc.uri] = display;
}
}
// 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'); this.emit('changed');
} }
@ -447,3 +477,41 @@ DashDocDisplay.prototype = {
Signals.addSignalMethods(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");
}
};

158
js/ui/extensionSystem.js Normal file
View File

@ -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);
}
}

View File

@ -14,7 +14,6 @@ const Shell = imports.gi.Shell;
const St = imports.gi.St; const St = imports.gi.St;
const DND = imports.ui.dnd; const DND = imports.ui.dnd;
const Link = imports.ui.link;
const Main = imports.ui.main; const Main = imports.ui.main;
const RedisplayFlags = { NONE: 0, const RedisplayFlags = { NONE: 0,

View File

@ -3,78 +3,21 @@
const Clutter = imports.gi.Clutter; const Clutter = imports.gi.Clutter;
const Lang = imports.lang; const Lang = imports.lang;
const Signals = imports.signals; 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) { function Link(props) {
this._init(props); this._init(props);
} }
Link.prototype = { Link.prototype = {
_init : function(props) { _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 // The user can pass in reactive: false to override the above and get
// a non-reactive link (a link to the current page, perhaps) // 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 = new St.Button(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;
} }
}; };

View File

@ -9,7 +9,11 @@ const Shell = imports.gi.Shell;
const Signals = imports.signals; const Signals = imports.signals;
const Lang = imports.lang; const Lang = imports.lang;
const Mainloop = imports.mainloop; 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 Tweener = imports.ui.tweener;
const Main = imports.ui.main; const Main = imports.ui.main;
@ -39,24 +43,21 @@ Notebook.prototype = {
_init: function() { _init: function() {
this.actor = new St.BoxLayout({ vertical: true }); this.actor = new St.BoxLayout({ vertical: true });
this.tabControls = new Big.Box({ orientation: Big.BoxOrientation.HORIZONTAL, this.tabControls = new St.BoxLayout({ style_class: "labels" });
spacing: 4, padding: 2 });
this._selectedIndex = -1; this._selectedIndex = -1;
this._tabs = []; this._tabs = [];
}, },
appendPage: function(name, child) { appendPage: function(name, child) {
let labelOuterBox = new Big.Box({ padding: 2 }); let labelBox = new St.BoxLayout({ style_class: "notebook-tab" });
let labelBox = new St.BoxLayout({ reactive: true }); let label = new St.Button({ label: name });
labelOuterBox.append(labelBox, Big.BoxPackFlags.NONE); label.connect('clicked', Lang.bind(this, function () {
let label = new St.Label({ text: name });
labelBox.connect('button-press-event', Lang.bind(this, function () {
this.selectChild(child); this.selectChild(child);
return true; return true;
})); }));
labelBox.add(label, { expand: 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 }); let scrollview = new St.ScrollView({ x_fill: true, y_fill: true });
scrollview.get_hscroll_bar().hide(); scrollview.get_hscroll_bar().hide();
@ -64,6 +65,7 @@ Notebook.prototype = {
let tabData = { child: child, let tabData = { child: child,
labelBox: labelBox, labelBox: labelBox,
label: label,
scrollView: scrollview, scrollView: scrollview,
_scrollToBottom: false }; _scrollToBottom: false };
this._tabs.push(tabData); this._tabs.push(tabData);
@ -82,8 +84,7 @@ Notebook.prototype = {
if (this._selectedIndex < 0) if (this._selectedIndex < 0)
return; return;
let tabData = this._tabs[this._selectedIndex]; let tabData = this._tabs[this._selectedIndex];
tabData.labelBox.padding = 2; tabData.labelBox.set_style_pseudo_class(null);
tabData.labelBox.border = 0;
tabData.scrollView.hide(); tabData.scrollView.hide();
this._selectedIndex = -1; this._selectedIndex = -1;
}, },
@ -97,8 +98,7 @@ Notebook.prototype = {
return; return;
} }
let tabData = this._tabs[index]; let tabData = this._tabs[index];
tabData.labelBox.padding = 1; tabData.labelBox.set_style_pseudo_class('selected');
tabData.labelBox.border = 1;
tabData.scrollView.show(); tabData.scrollView.show();
this._selectedIndex = index; this._selectedIndex = index;
this.emit('selection', tabData.child); this.emit('selection', tabData.child);
@ -308,6 +308,135 @@ Inspector.prototype = {
Signals.addSignalMethods(Inspector.prototype); Signals.addSignalMethods(Inspector.prototype);
function ErrorLog() {
this._init();
}
ErrorLog.prototype = {
_init: function() {
this.actor = new St.BoxLayout();
this.text = new St.Label();
this.actor.add(this.text);
this.text.clutter_text.line_wrap = true;
this.actor.connect('notify::mapped', Lang.bind(this, this._renderText));
},
_formatTime: function(d){
function pad(n) { return n < 10 ? '0' + n : n };
return d.getUTCFullYear()+'-'
+ pad(d.getUTCMonth()+1)+'-'
+ pad(d.getUTCDate())+'T'
+ pad(d.getUTCHours())+':'
+ pad(d.getUTCMinutes())+':'
+ pad(d.getUTCSeconds())+'Z'
},
_renderText: function() {
if (!this.actor.mapped)
return;
let text = this.text.text;
let stack = Main._getAndClearErrorStack();
for (let i = 0; i < stack.length; i++) {
let logItem = stack[i];
text += logItem.category + " t=" + this._formatTime(new Date(logItem.timestamp)) + " " + logItem.message + "\n";
}
this.text.text = text;
}
}
function Extensions() {
this._init();
}
Extensions.prototype = {
_init: function() {
this.actor = new St.BoxLayout({ vertical: true,
name: 'lookingGlassExtensions' });
this._noExtensions = new St.Label({ style_class: 'lg-extensions-none',
text: _("No extensions installed") });
this._extensionsList = new St.BoxLayout({ vertical: true,
style_class: 'lg-extensions-list' });
this.actor.add(this._extensionsList);
this._loadExtensionList();
},
_loadExtensionList: function() {
let extensions = ExtensionSystem.extensionMeta;
let totalExtensions = 0;
for (let uuid in extensions) {
let extensionDisplay = this._createExtensionDisplay(extensions[uuid]);
this._extensionsList.add(extensionDisplay);
totalExtensions++;
}
if (totalExtensions == 0) {
this._extensionsList.add(this._noExtensions);
}
},
_onViewSource: function (actor) {
let meta = actor._extensionMeta;
let file = Gio.file_new_for_path(meta.path);
let uri = file.get_uri();
Gio.app_info_launch_default_for_uri(uri, global.create_app_launch_context());
Main.lookingGlass.close();
},
_onWebPage: function (actor) {
let meta = actor._extensionMeta;
Gio.app_info_launch_default_for_uri(meta.url, global.create_app_launch_context());
Main.lookingGlass.close();
},
_stateToString: function(extensionState) {
switch (extensionState) {
case ExtensionSystem.ExtensionState.ENABLED:
return _("Enabled");
case ExtensionSystem.ExtensionState.DISABLED:
return _("Disabled");
case ExtensionSystem.ExtensionState.ERROR:
return _("Error");
case ExtensionSystem.ExtensionState.OUT_OF_DATE:
return _("Out of date");
}
return "Unknown"; // Not translated, shouldn't appear
},
_createExtensionDisplay: function(meta) {
let box = new St.BoxLayout({ style_class: 'lg-extension', vertical: true });
let name = new St.Label({ style_class: 'lg-extension-name',
text: meta.name });
box.add(name, { expand: true });
let description = new St.Label({ style_class: 'lg-extension-description',
text: meta.description });
box.add(description, { expand: true });
let metaBox = new St.BoxLayout();
box.add(metaBox);
let stateString = this._stateToString(meta.state);
let state = new St.Label({ style_class: 'lg-extension-state',
text: this._stateToString(meta.state) });
let actionsContainer = new St.Bin({ x_align: St.Align.END });
metaBox.add(actionsContainer);
let actionsBox = new St.BoxLayout({ style_class: 'lg-extension-actions' });
actionsContainer.set_child(actionsBox);
let viewsource = new Link.Link({ label: _("View Source") });
viewsource.actor._extensionMeta = meta;
viewsource.actor.connect('clicked', Lang.bind(this, this._onViewSource));
actionsBox.add(viewsource.actor);
if (meta.url) {
let webpage = new Link.Link({ label: _("Web Page") });
webpage.actor._extensionMeta = meta;
webpage.actor.connect('clicked', Lang.bind(this, this._onWebPage));
actionsBox.add(webpage.actor);
}
return box;
}
};
function LookingGlass() { function LookingGlass() {
this._init(); this._init();
} }
@ -406,6 +535,12 @@ LookingGlass.prototype = {
notebook.selectIndex(0); notebook.selectIndex(0);
})); }));
this._errorLog = new ErrorLog();
notebook.appendPage('Errors', this._errorLog.actor);
this._extensions = new Extensions();
notebook.appendPage('Extensions', this._extensions.actor);
this._entry.clutter_text.connect('activate', Lang.bind(this, function (o, e) { this._entry.clutter_text.connect('activate', Lang.bind(this, function (o, e) {
let text = o.get_text(); let text = o.get_text();
// Ensure we don't get newlines in the command; the history file is // Ensure we don't get newlines in the command; the history file is
@ -421,10 +556,7 @@ LookingGlass.prototype = {
})); }));
this._entry.clutter_text.connect('key-press-event', Lang.bind(this, function(o, e) { this._entry.clutter_text.connect('key-press-event', Lang.bind(this, function(o, e) {
let symbol = e.get_key_symbol(); let symbol = e.get_key_symbol();
if (symbol == Clutter.Escape) { if (symbol == Clutter.Up) {
this.close();
return true;
} else if (symbol == Clutter.Up) {
if (this._historyNavIndex >= this._history.length - 1) if (this._historyNavIndex >= this._history.length - 1)
return true; return true;
this._historyNavIndex++; this._historyNavIndex++;
@ -569,6 +701,16 @@ LookingGlass.prototype = {
this._resizeTo(actor); 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() { open : function() {
if (this._open) if (this._open)
return; return;
@ -576,6 +718,9 @@ LookingGlass.prototype = {
if (!Main.pushModal(this.actor)) if (!Main.pushModal(this.actor))
return; return;
this._keyPressEventId = global.stage.connect('key-press-event',
Lang.bind(this, this._globalKeyPressEvent));
this.actor.show(); this.actor.show();
this.actor.lower(Main.chrome.actor); this.actor.lower(Main.chrome.actor);
this._open = true; this._open = true;
@ -594,6 +739,9 @@ LookingGlass.prototype = {
if (!this._open) if (!this._open)
return; return;
if (this._keyPressEventId)
global.stage.disconnect(this._keyPressEventId);
this._historyNavIndex = -1; this._historyNavIndex = -1;
this._open = false; this._open = false;
Tweener.removeTweens(this.actor); Tweener.removeTweens(this.actor);

View File

@ -14,6 +14,7 @@ const St = imports.gi.St;
const Chrome = imports.ui.chrome; const Chrome = imports.ui.chrome;
const Environment = imports.ui.environment; const Environment = imports.ui.environment;
const ExtensionSystem = imports.ui.extensionSystem;
const MessageTray = imports.ui.messageTray; const MessageTray = imports.ui.messageTray;
const Messaging = imports.ui.messaging; const Messaging = imports.ui.messaging;
const Overview = imports.ui.overview; const Overview = imports.ui.overview;
@ -45,6 +46,8 @@ let recorder = null;
let shellDBusService = null; let shellDBusService = null;
let modalCount = 0; let modalCount = 0;
let modalActorFocusStack = []; let modalActorFocusStack = [];
let _errorLogStack = [];
let _startDate;
function start() { function start() {
// Add a binding for "global" in the global JS namespace; (gjs // Add a binding for "global" in the global JS namespace; (gjs
@ -52,6 +55,12 @@ function start() {
// called "window".) // called "window".)
window.global = Shell.Global.get(); 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"); Gio.DesktopAppInfo.set_desktop_env("GNOME");
global.grab_dbus_service(); global.grab_dbus_service();
@ -116,6 +125,8 @@ function start() {
notificationPopup = new MessageTray.Notification(); notificationPopup = new MessageTray.Notification();
messageTray = new MessageTray.MessageTray(); messageTray = new MessageTray.MessageTray();
_startDate = new Date();
global.screen.connect('toggle-recording', function() { global.screen.connect('toggle-recording', function() {
if (recorder == null) { if (recorder == null) {
recorder = new Shell.Recorder({ stage: global.stage }); recorder = new Shell.Recorder({ stage: global.stage });
@ -130,6 +141,9 @@ function start() {
_relayout(); _relayout();
ExtensionSystem.init();
ExtensionSystem.loadExtensions();
panel.startupAnimation(); panel.startupAnimation();
let display = global.screen.get_display(); let display = global.screen.get_display();
@ -138,9 +152,52 @@ function start() {
global.stage.connect('captured-event', _globalKeyPressHandler); global.stage.connect('captured-event', _globalKeyPressHandler);
_log('info', 'loaded at ' + _startDate);
Mainloop.idle_add(_removeUnusedWorkspaces); 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() { function _relayout() {
let primary = global.get_primary_monitor(); let primary = global.get_primary_monitor();
panel.actor.set_position(primary.x, primary.y); panel.actor.set_position(primary.x, primary.y);
@ -251,7 +308,7 @@ function _findModal(actor) {
*/ */
function pushModal(actor) { function pushModal(actor) {
if (modalCount == 0) { if (modalCount == 0) {
if (!global.begin_modal(currentTime())) { if (!global.begin_modal(global.get_current_time())) {
log("pushModal: invocation of begin_modal failed"); log("pushModal: invocation of begin_modal failed");
return false; return false;
} }
@ -304,7 +361,7 @@ function popModal(actor) {
if (modalCount > 0) if (modalCount > 0)
return; return;
global.end_modal(currentTime()); global.end_modal(global.get_current_time());
global.set_stage_input_mode(Shell.StageInputMode.NORMAL); global.set_stage_input_mode(Shell.StageInputMode.NORMAL);
} }
@ -323,45 +380,6 @@ function getRunDialog() {
return runDialog; 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: * activateWindow:
* @window: the Meta.Window to activate * @window: the Meta.Window to activate
@ -374,7 +392,7 @@ function activateWindow(window, time) {
let windowWorkspaceNum = window.get_workspace().index(); let windowWorkspaceNum = window.get_workspace().index();
if (!time) if (!time)
time = currentTime(); time = global.get_current_time();
if (windowWorkspaceNum != activeWorkspaceNum) { if (windowWorkspaceNum != activeWorkspaceNum) {
let workspace = global.screen.get_workspace_by_index(windowWorkspaceNum); let workspace = global.screen.get_workspace_by_index(windowWorkspaceNum);
@ -383,3 +401,120 @@ function activateWindow(window, time) {
window.activate(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;
});
}
}

View File

@ -12,7 +12,6 @@ const Lang = imports.lang;
const AppDisplay = imports.ui.appDisplay; const AppDisplay = imports.ui.appDisplay;
const DocDisplay = imports.ui.docDisplay; const DocDisplay = imports.ui.docDisplay;
const GenericDisplay = imports.ui.genericDisplay; const GenericDisplay = imports.ui.genericDisplay;
const Link = imports.ui.link;
const Main = imports.ui.main; const Main = imports.ui.main;
const Panel = imports.ui.panel; const Panel = imports.ui.panel;
const Dash = imports.ui.dash; const Dash = imports.ui.dash;
@ -184,6 +183,7 @@ Overview.prototype = {
this._dash.actor.set_size(displayGridColumnWidth, contentHeight); this._dash.actor.set_size(displayGridColumnWidth, contentHeight);
this._dash.searchArea.height = this._workspacesY - contentY; this._dash.searchArea.height = this._workspacesY - contentY;
this._dash.sectionArea.height = this._workspacesHeight; 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 // place the 'Add Workspace' button in the bottom row of the grid
addRemoveButtonSize = Math.floor(displayGridRowHeight * 3/5); addRemoveButtonSize = Math.floor(displayGridRowHeight * 3/5);
@ -248,7 +248,7 @@ Overview.prototype = {
// This allows the user to place the item on any workspace. // This allows the user to place the item on any workspace.
handleDragOver : function(source, actor, x, y, time) { handleDragOver : function(source, actor, x, y, time) {
if (source instanceof GenericDisplay.GenericDisplayItem if (source instanceof GenericDisplay.GenericDisplayItem
|| source instanceof AppDisplay.BaseWellItem) { || source instanceof AppDisplay.AppIcon) {
if (this._activeDisplayPane != null) if (this._activeDisplayPane != null)
this._activeDisplayPane.close(); this._activeDisplayPane.close();
return true; return true;
@ -457,7 +457,7 @@ Overview.prototype = {
}, },
_addNewWorkspace: function() { _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) { _acceptNewWorkspaceDrop: function(source, dropActor, x, y, time) {

View File

@ -15,7 +15,7 @@ const _ = Gettext.gettext;
const DND = imports.ui.dnd; const DND = imports.ui.dnd;
const Main = imports.ui.main; const Main = imports.ui.main;
const GenericDisplay = imports.ui.genericDisplay; const Search = imports.ui.search;
const NAUTILUS_PREFS_DIR = '/apps/nautilus/preferences'; const NAUTILUS_PREFS_DIR = '/apps/nautilus/preferences';
const DESKTOP_IS_HOME_KEY = NAUTILUS_PREFS_DIR + '/desktop_is_home_dir'; const DESKTOP_IS_HOME_KEY = NAUTILUS_PREFS_DIR + '/desktop_is_home_dir';
@ -30,19 +30,78 @@ const PLACES_ICON_SIZE = 16;
* @iconFactory: A JavaScript callback which will create an icon texture given a size parameter * @iconFactory: A JavaScript callback which will create an icon texture given a size parameter
* @launch: A JavaScript callback to launch the entry * @launch: A JavaScript callback to launch the entry
*/ */
function PlaceInfo(name, iconFactory, launch) { function PlaceInfo(id, name, iconFactory, launch) {
this._init(name, iconFactory, launch); this._init(id, name, iconFactory, launch);
} }
PlaceInfo.prototype = { PlaceInfo.prototype = {
_init: function(name, iconFactory, launch) { _init: function(id, name, iconFactory, launch) {
this.id = id;
this.name = name; this.name = name;
this._lowerName = name.toLowerCase();
this.iconFactory = iconFactory; this.iconFactory = iconFactory;
this.launch = launch; this.launch = launch;
this.id = null; },
matchTerms: function(terms) {
let mtype = Search.MatchType.NONE;
for (let i = 0; i < terms.length; i++) {
let term = terms[i];
let idx = this._lowerName.indexOf(term);
if (idx == 0)
return Search.MatchType.PREFIX;
else if (idx > 0)
mtype = Search.MatchType.SUBSTRING;
}
return mtype;
},
isRemovable: function() {
return false;
} }
} }
function PlaceDeviceInfo(mount) {
this._init(mount);
}
PlaceDeviceInfo.prototype = {
__proto__: PlaceInfo.prototype,
_init: function(mount) {
this._mount = mount;
this.name = mount.get_name();
this._lowerName = this.name.toLowerCase();
this.id = "mount:" + mount.get_root().get_uri();
},
iconFactory: function(size) {
let icon = this._mount.get_icon();
return Shell.TextureCache.get_default().load_gicon(icon, size);
},
launch: function() {
Gio.app_info_launch_default_for_uri(this._mount.get_root().get_uri(),
global.create_app_launch_context());
},
isRemovable: function() {
return this._mount.can_unmount();
},
remove: function() {
if (!this.isRemovable())
return;
this._mount.unmount(0, null, Lang.bind(this, this._removeFinish), null);
},
_removeFinish: function(o, res, data) {
this._mount.unmount_finish(res);
}
}
function PlacesManager() { function PlacesManager() {
this._init(); this._init();
} }
@ -52,6 +111,7 @@ PlacesManager.prototype = {
let gconf = Shell.GConf.get_default(); let gconf = Shell.GConf.get_default();
gconf.watch_directory(NAUTILUS_PREFS_DIR); gconf.watch_directory(NAUTILUS_PREFS_DIR);
this._defaultPlaces = [];
this._mounts = []; this._mounts = [];
this._bookmarks = []; this._bookmarks = [];
this._isDesktopHome = false; this._isDesktopHome = false;
@ -60,12 +120,12 @@ PlacesManager.prototype = {
let homeUri = homeFile.get_uri(); let homeUri = homeFile.get_uri();
let homeLabel = Shell.util_get_label_for_uri (homeUri); let homeLabel = Shell.util_get_label_for_uri (homeUri);
let homeIcon = Shell.util_get_icon_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) { function(size) {
return Shell.TextureCache.get_default().load_gicon(homeIcon, size); return Shell.TextureCache.get_default().load_gicon(homeIcon, size);
}, },
function() { 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); let desktopPath = GLib.get_user_special_dir(GLib.UserDirectory.DIRECTORY_DESKTOP);
@ -73,15 +133,15 @@ PlacesManager.prototype = {
let desktopUri = desktopFile.get_uri(); let desktopUri = desktopFile.get_uri();
let desktopLabel = Shell.util_get_label_for_uri (desktopUri); let desktopLabel = Shell.util_get_label_for_uri (desktopUri);
let desktopIcon = Shell.util_get_icon_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) { function(size) {
return Shell.TextureCache.get_default().load_gicon(desktopIcon, size); return Shell.TextureCache.get_default().load_gicon(desktopIcon, size);
}, },
function() { function() {
Gio.app_info_launch_default_for_uri(desktopUri, Main.createAppLaunchContext()); Gio.app_info_launch_default_for_uri(desktopUri, global.create_app_launch_context());
}); });
this._connect = new PlaceInfo(_("Connect to..."), this._connect = new PlaceInfo('special:connect', _("Connect to..."),
function (size) { function (size) {
return Shell.TextureCache.get_default().load_icon_name("applications-internet", size); return Shell.TextureCache.get_default().load_icon_name("applications-internet", size);
}, },
@ -101,7 +161,7 @@ PlacesManager.prototype = {
} }
if (networkApp != null) { if (networkApp != null) {
this._network = new PlaceInfo(networkApp.get_name(), this._network = new PlaceInfo('special:network', networkApp.get_name(),
function(size) { function(size) {
return networkApp.create_icon_texture(size); return networkApp.create_icon_texture(size);
}, },
@ -110,6 +170,16 @@ PlacesManager.prototype = {
}); });
} }
this._defaultPlaces.push(this._home);
if (!this._isDesktopHome)
this._defaultPlaces.push(this._desktopMenu);
if (this._network)
this._defaultPlaces.push(this._network);
this._defaultPlaces.push(this._connect);
/* /*
* Show devices, code more or less ported from nautilus-places-sidebar.c * Show devices, code more or less ported from nautilus-places-sidebar.c
*/ */
@ -238,12 +308,12 @@ PlacesManager.prototype = {
continue; continue;
let icon = Shell.util_get_icon_for_uri(bookmark); let icon = Shell.util_get_icon_for_uri(bookmark);
let item = new PlaceInfo(label, let item = new PlaceInfo('bookmark:' + bookmark, label,
function(size) { function(size) {
return Shell.TextureCache.get_default().load_gicon(icon, size); return Shell.TextureCache.get_default().load_gicon(icon, size);
}, },
function() { 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); this._bookmarks.push(item);
} }
@ -263,17 +333,7 @@ PlacesManager.prototype = {
}, },
_addMount: function(mount) { _addMount: function(mount) {
let mountLabel = mount.get_name(); let devItem = new PlaceDeviceInfo(mount);
let mountIcon = mount.get_icon();
let root = mount.get_root();
let mountUri = root.get_uri();
let devItem = new PlaceInfo(mountLabel,
function(size) {
return Shell.TextureCache.get_default().load_gicon(mountIcon, size);
},
function() {
Gio.app_info_launch_default_for_uri(mountUri, Main.createAppLaunchContext());
});
this._mounts.push(devItem); this._mounts.push(devItem);
}, },
@ -282,16 +342,7 @@ PlacesManager.prototype = {
}, },
getDefaultPlaces: function () { getDefaultPlaces: function () {
let places = [this._home]; return this._defaultPlaces;
if (!this._isDesktopHome)
places.push(this._desktopMenu);
if (this._network)
places.push(this._network);
places.push(this._connect);
return places;
}, },
getBookmarks: function () { getBookmarks: function () {
@ -300,6 +351,28 @@ PlacesManager.prototype = {
getMounts: function () { getMounts: function () {
return this._mounts; return this._mounts;
},
_lookupById: function(sourceArray, id) {
for (let i = 0; i < sourceArray.length; i++) {
let place = sourceArray[i];
if (place.id == id)
return place;
}
return null;
},
lookupPlaceById: function(id) {
let colonIdx = id.indexOf(':');
let type = id.substring(0, colonIdx);
let sourceArray = null;
if (type == 'special')
sourceArray = this._defaultPlaces;
else if (type == 'mount')
sourceArray = this._mounts;
else if (type == 'bookmark')
sourceArray = this._bookmarks;
return this._lookupById(sourceArray, id);
} }
}; };
@ -319,23 +392,37 @@ DashPlaceDisplayItem.prototype = {
this._info = info; this._info = info;
this._icon = info.iconFactory(PLACES_ICON_SIZE); this._icon = info.iconFactory(PLACES_ICON_SIZE);
this.actor = new Big.Box({ orientation: Big.BoxOrientation.HORIZONTAL, this.actor = new Big.Box({ orientation: Big.BoxOrientation.HORIZONTAL,
reactive: true,
spacing: 4 }); spacing: 4 });
this.actor.connect('button-release-event', Lang.bind(this, function (b, e) { let text = new St.Button({ style_class: 'places-item',
this._info.launch(); label: info.name,
Main.overview.hide(); x_align: St.Align.START });
})); text.connect('clicked', Lang.bind(this, this._onClicked));
let text = new St.Label({ style_class: 'places-item', let iconBox = new St.Bin({ child: this._icon, reactive: true });
text: info.name }); iconBox.connect('button-release-event',
let iconBox = new Big.Box({ y_align: Big.BoxAlignment.CENTER }); Lang.bind(this, this._onClicked));
iconBox.append(this._icon, Big.BoxPackFlags.NONE);
this.actor.append(iconBox, Big.BoxPackFlags.NONE); this.actor.append(iconBox, Big.BoxPackFlags.NONE);
this.actor.append(text, Big.BoxPackFlags.EXPAND); 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; this.actor._delegate = this;
let draggable = DND.makeDraggable(this.actor); let draggable = DND.makeDraggable(this.actor);
}, },
_onClicked: function(b) {
this._info.launch();
Main.overview.hide();
},
getDragActorSource: function() { getDragActorSource: function() {
return this._icon; return this._icon;
}, },
@ -421,120 +508,67 @@ DashPlaceDisplay.prototype = {
Signals.addSignalMethods(DashPlaceDisplay.prototype); Signals.addSignalMethods(DashPlaceDisplay.prototype);
function PlaceSearchProvider() {
function PlaceDisplayItem(placeInfo) { this._init();
this._init(placeInfo);
} }
PlaceDisplayItem.prototype = { PlaceSearchProvider.prototype = {
__proto__: GenericDisplay.GenericDisplayItem.prototype, __proto__: Search.SearchProvider.prototype,
_init : function(placeInfo) { _init: function() {
GenericDisplay.GenericDisplayItem.prototype._init.call(this); Search.SearchProvider.prototype._init.call(this, _("PLACES"));
this._info = placeInfo;
this._setItemInfo(placeInfo.name, '');
}, },
//// Public method overrides //// getResultMeta: function(resultId) {
let placeInfo = Main.placesManager.lookupPlaceById(resultId);
// Opens an application represented by this display item. if (!placeInfo)
launch : function() { return null;
this._info.launch(); return { 'id': resultId,
'name': placeInfo.name,
'icon': placeInfo.iconFactory(Search.RESULT_ICON_SIZE) };
}, },
shellWorkspaceLaunch: function() { activateResult: function(id) {
this._info.launch(); let placeInfo = Main.placesManager.lookupPlaceById(id);
placeInfo.launch();
}, },
//// Protected method overrides //// _compareResultMeta: function (idA, idB) {
let infoA = Main.placesManager.lookupPlaceById(idA);
// Returns an icon for the item. let infoB = Main.placesManager.lookupPlaceById(idB);
_createIcon: function() { return infoA.name.localeCompare(infoB.name);
return this._info.iconFactory(GenericDisplay.ITEM_DISPLAY_ICON_SIZE);
}, },
// Returns a preview icon for the item. _searchPlaces: function(places, terms) {
_createPreviewIcon: function() { let multipleResults = [];
return this._info.iconFactory(GenericDisplay.PREVIEW_ICON_SIZE); 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);
},
function PlaceDisplay(flags) { getSubsearchResultSet: function(previousResults, terms) {
this._init(flags); let places = previousResults.map(function (id) { return Main.placesManager.lookupPlaceById(id); });
return this._searchPlaces(places, terms);
}
} }
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);
}
};

View File

@ -2,6 +2,7 @@
const Big = imports.gi.Big; const Big = imports.gi.Big;
const Clutter = imports.gi.Clutter; const Clutter = imports.gi.Clutter;
const Gio = imports.gi.Gio;
const GLib = imports.gi.GLib; const GLib = imports.gi.GLib;
const Lang = imports.lang; const Lang = imports.lang;
const Mainloop = imports.mainloop; const Mainloop = imports.mainloop;
@ -24,6 +25,144 @@ const DIALOG_WIDTH = 320;
const DIALOG_PADDING = 6; const DIALOG_PADDING = 6;
const ICON_SIZE = 24; const ICON_SIZE = 24;
const ICON_BOX_SIZE = 36; 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() { function RunDialog() {
this._init(); this._init();
@ -137,16 +276,55 @@ RunDialog.prototype = {
this.close(); 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) { this._entry.connect('key-press-event', Lang.bind(this, function(o, e) {
let symbol = e.get_key_symbol(); let symbol = e.get_key_symbol();
if (symbol == Clutter.Escape) { if (symbol == Clutter.Escape) {
this.close(); this.close();
return true; 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; 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) { _run : function(command) {
this._commandError = false; this._commandError = false;
let f; let f;

272
js/ui/search.js Normal file
View File

@ -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);

View File

@ -69,6 +69,8 @@ Sidebar.prototype = {
Lang.bind(this, this._expandedChanged)); Lang.bind(this, this._expandedChanged));
this._gconf.connect('changed::sidebar/visible', this._gconf.connect('changed::sidebar/visible',
Lang.bind(this, this._visibleChanged)); Lang.bind(this, this._visibleChanged));
this._adjustPosition();
}, },
addWidget: function(widget) { addWidget: function(widget) {
@ -82,6 +84,14 @@ Sidebar.prototype = {
this.box.append(widgetBox.actor, Big.BoxPackFlags.NONE); this.box.append(widgetBox.actor, Big.BoxPackFlags.NONE);
this._widgets.push(widgetBox); 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() { _visibleChanged: function() {

View File

@ -354,7 +354,6 @@ RecentDocsWidget.prototype = {
for (i = 0; i < docs.length; i++) { for (i = 0; i < docs.length; i++) {
let docInfo = new DocInfo.DocInfo (docs[i]); let docInfo = new DocInfo.DocInfo (docs[i]);
if (docInfo.exists())
items.push(docInfo); items.push(docInfo);
} }

View File

@ -337,8 +337,6 @@ WindowOverlay.prototype = {
button._overlap = 0; button._overlap = 0;
windowClone.actor.connect('destroy', Lang.bind(this, this._onDestroy)); windowClone.actor.connect('destroy', Lang.bind(this, this._onDestroy));
windowClone.actor.connect('notify::allocation',
Lang.bind(this, this._positionItems));
windowClone.actor.connect('enter-event', windowClone.actor.connect('enter-event',
Lang.bind(this, this._onEnter)); Lang.bind(this, this._onEnter));
windowClone.actor.connect('leave-event', windowClone.actor.connect('leave-event',
@ -395,23 +393,30 @@ WindowOverlay.prototype = {
this.title.height + this.title._spacing; 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 button = this.closeButton;
let title = this.title; let title = this.title;
let [x, y] = win.get_transformed_position(); let buttonX = cloneX + cloneWidth - button._overlap;
let [w, h] = win.get_transformed_size(); let buttonY = cloneY - button.height + button._overlap;
let buttonX = x + w - button._overlap;
let buttonY = y - button.height + button._overlap;
button.set_position(Math.floor(buttonX), Math.floor(buttonY)); button.set_position(Math.floor(buttonX), Math.floor(buttonY));
if (!title.fullWidth) if (!title.fullWidth)
title.fullWidth = title.width; 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 titleX = cloneX + (cloneWidth - title.width) / 2;
let titleY = y + h + title._spacing; let titleY = cloneY + cloneHeight + title._spacing;
title.set_position(Math.floor(titleX), Math.floor(titleY)); title.set_position(Math.floor(titleX), Math.floor(titleY));
}, },
@ -496,7 +501,7 @@ WindowOverlay.prototype = {
let closeNode = this.closeButton.get_theme_node(); let closeNode = this.closeButton.get_theme_node();
let [success, len] = closeNode.get_length('-shell-close-overlap', [success, len] = closeNode.get_length('-shell-close-overlap',
false); false);
if (success) if (success)
this.closeButton._overlap = len; this.closeButton._overlap = len;
@ -1034,7 +1039,7 @@ Workspace.prototype = {
time: Overview.ANIMATION_TIME, time: Overview.ANIMATION_TIME,
transition: "easeOutQuad", transition: "easeOutQuad",
onComplete: Lang.bind(this, function() { 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() { _fadeInAllOverlays: function() {
for (let i = 1; i < this._windows.length; i++) { for (let i = 1; i < this._windows.length; i++) {
let clone = this._windows[i]; let clone = this._windows[i];
let overlay = this._windowOverlays[i]; let overlay = this._windowOverlays[i];
if (this._showOnlyWindows != null && !(clone.metaWindow in this._showOnlyWindows)) if (this._showOnlyWindows != null && !(clone.metaWindow in this._showOnlyWindows))
continue; continue;
overlay.fadeIn(); this._fadeInWindowOverlay(clone, overlay);
} }
}, },

View File

@ -6,9 +6,11 @@ de
el el
en_GB en_GB
es es
fi
fr fr
ga ga
gl gl
he
hu hu
it it
ko ko

View File

@ -9,5 +9,4 @@ js/ui/runDialog.js
js/ui/widget.js js/ui/widget.js
src/gdmuser/gdm-user.c src/gdmuser/gdm-user.c
src/shell-global.c src/shell-global.c
src/shell-status-menu.c
src/shell-uri-util.c src/shell-uri-util.c

181
po/es.po
View File

@ -8,8 +8,8 @@ msgstr ""
"Project-Id-Version: gnome-shell master\n" "Project-Id-Version: gnome-shell master\n"
"Report-Msgid-Bugs-To: http://bugzilla.gnome.org/enter_bug.cgi?product=gnome-" "Report-Msgid-Bugs-To: http://bugzilla.gnome.org/enter_bug.cgi?product=gnome-"
"shell&component=general\n" "shell&component=general\n"
"POT-Creation-Date: 2009-10-27 20:37+0000\n" "POT-Creation-Date: 2009-12-18 15:06+0000\n"
"PO-Revision-Date: 2009-10-27 23:32+0100\n" "PO-Revision-Date: 2009-12-19 14:41+0100\n"
"Last-Translator: Jorge González <jorgegonz@svn.gnome.org>\n" "Last-Translator: Jorge González <jorgegonz@svn.gnome.org>\n"
"Language-Team: Español <gnome-es-list@gnome.org>\n" "Language-Team: Español <gnome-es-list@gnome.org>\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
@ -25,76 +25,64 @@ msgstr "GNOME Shell"
msgid "Window management and application launching" msgid "Window management and application launching"
msgstr "Gestión de ventanas e inicio de aplicaciones" msgstr "Gestión de ventanas e inicio de aplicaciones"
#: ../js/ui/appDisplay.js:332 #. **** Applications ****
msgid "Frequent" #: ../js/ui/appDisplay.js:252 ../js/ui/dash.js:852
msgstr "Frecuentes" msgid "APPLICATIONS"
msgstr "APLICACIONES"
#: ../js/ui/appDisplay.js:867 #: ../js/ui/appDisplay.js:276
msgid "Drag here to add favorites" msgid "PREFERENCES"
msgstr "Arrastrar aquí para añadir a los favoritos" msgstr "PREFERENCIAS"
#: ../js/ui/appIcon.js:426 #: ../js/ui/appDisplay.js:707 ../js/ui/appIcon.js:425
msgid "New Window" msgid "New Window"
msgstr "Ventana nueva" msgstr "Ventana nueva"
#: ../js/ui/appIcon.js:430 #: ../js/ui/appDisplay.js:711 ../js/ui/appIcon.js:429
msgid "Remove from Favorites" msgid "Remove from Favorites"
msgstr "Quitar de los favoritos" msgstr "Quitar de los favoritos"
#: ../js/ui/appIcon.js:431 #: ../js/ui/appDisplay.js:712 ../js/ui/appIcon.js:430
msgid "Add to Favorites" msgid "Add to Favorites"
msgstr "Añadir a los favoritos" 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..." msgid "Find..."
msgstr "Buscar…" msgstr "Buscar…"
#: ../js/ui/dash.js:400 #: ../js/ui/dash.js:437
msgid "More" #| msgid "Search"
msgstr "Más" msgid "Searching..."
msgstr "Buscando…"
#: ../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"
#. **** Places **** #. **** Places ****
#. Translators: This is in the sense of locations for documents, #. Translators: This is in the sense of locations for documents,
#. network locations, etc. #. network locations, etc.
#: ../js/ui/dash.js:745 #: ../js/ui/dash.js:872 ../js/ui/placeDisplay.js:471
msgid "PLACES" msgid "PLACES"
msgstr "LUGARES" msgstr "LUGARES"
#. **** Documents **** #. **** Documents ****
#: ../js/ui/dash.js:752 ../js/ui/dash.js:797 #: ../js/ui/dash.js:879
msgid "RECENT DOCUMENTS" msgid "RECENT DOCUMENTS"
msgstr "DOCUMENTOS RECIENTES" 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. #. 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". #. 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" msgid "Activities"
msgstr "Actividades" msgstr "Actividades"
#. Translators: This is a time format. #. Translators: This is a time format.
#: ../js/ui/panel.js:491 #: ../js/ui/panel.js:440
msgid "%a %l:%M %p" msgid "%a %l:%M %p"
msgstr "%a %H:%M" msgstr "%a %H:%M"
#: ../js/ui/places.js:178 #: ../js/ui/placeDisplay.js:99
msgid "Connect to..." msgid "Connect to..."
msgstr "Conectar a…" msgstr "Conectar a…"
@ -120,101 +108,49 @@ msgstr "Aplicaciones"
msgid "Recent Documents" msgid "Recent Documents"
msgstr "Documentos recientes" msgstr "Documentos recientes"
#: ../src/shell-global.c:812 #: ../src/shell-global.c:826
msgid "Less than a minute ago" msgid "Less than a minute ago"
msgstr "Hace menos de un minuto" msgstr "Hace menos de un minuto"
#: ../src/shell-global.c:815 #: ../src/shell-global.c:829
#, c-format #, c-format
msgid "%d minute ago" msgid "%d minute ago"
msgid_plural "%d minutes ago" msgid_plural "%d minutes ago"
msgstr[0] "Hace %d minuto" msgstr[0] "Hace %d minuto"
msgstr[1] "Hace %d minutos" msgstr[1] "Hace %d minutos"
#: ../src/shell-global.c:818 #: ../src/shell-global.c:832
#, c-format #, c-format
msgid "%d hour ago" msgid "%d hour ago"
msgid_plural "%d hours ago" msgid_plural "%d hours ago"
msgstr[0] "Hace %d hora" msgstr[0] "Hace %d hora"
msgstr[1] "Hace %d horas" msgstr[1] "Hace %d horas"
#: ../src/shell-global.c:821 #: ../src/shell-global.c:835
#, c-format #, c-format
msgid "%d day ago" msgid "%d day ago"
msgid_plural "%d days ago" msgid_plural "%d days ago"
msgstr[0] "Hace %d día" msgstr[0] "Hace %d día"
msgstr[1] "Hace %d días" msgstr[1] "Hace %d días"
#: ../src/shell-global.c:824 #: ../src/shell-global.c:838
#, c-format #, c-format
msgid "%d week ago" msgid "%d week ago"
msgid_plural "%d weeks ago" msgid_plural "%d weeks ago"
msgstr[0] "Hace %d semana" msgstr[0] "Hace %d semana"
msgstr[1] "Hace %d semanas" msgstr[1] "Hace %d semanas"
#: ../src/shell-status-menu.c:156 #: ../src/shell-uri-util.c:89
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
msgid "Home Folder" msgid "Home Folder"
msgstr "Carpeta personal" msgstr "Carpeta personal"
#. Translators: this is the same string as the one found in #. Translators: this is the same string as the one found in
#. * nautilus #. * nautilus
#: ../src/shell-uri-util.c:102 #: ../src/shell-uri-util.c:104
msgid "File System" msgid "File System"
msgstr "Sistema de archivos" msgstr "Sistema de archivos"
#: ../src/shell-uri-util.c:248 #: ../src/shell-uri-util.c:250
msgid "Search" msgid "Search"
msgstr "Buscar" msgstr "Buscar"
@ -223,11 +159,58 @@ msgstr "Buscar"
#. * example, "Trash: some-directory". It means that the #. * example, "Trash: some-directory". It means that the
#. * directory called "some-directory" is in the trash. #. * directory called "some-directory" is in the trash.
#. #.
#: ../src/shell-uri-util.c:298 #: ../src/shell-uri-util.c:300
#, c-format #, c-format
msgid "%1$s: %2$s" msgid "%1$s: %2$s"
msgstr "%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" #~ msgid "Browse"
#~ msgstr "Examine" #~ msgstr "Examine"

102
po/fr.po
View File

@ -6,13 +6,13 @@
# #
msgid "" msgid ""
msgstr "" 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-" "Report-Msgid-Bugs-To: http://bugzilla.gnome.org/enter_bug.cgi?product=gnome-"
"shell&component=general\n" "shell&component=general\n"
"POT-Creation-Date: 2009-09-09 21:30+0000\n" "POT-Creation-Date: 2009-11-13 17:44+0000\n"
"PO-Revision-Date: 2009-09-11 21:40+0200\n" "PO-Revision-Date: 2009-12-05 16:43+0100\n"
"Last-Translator: Mathieu Bridon <bochecha@fedoraproject.org>\n" "Last-Translator: Pablo Martin-Gomez <pablo.martin-gomez@laposte.net>\n"
"Language-Team: GNOME French Team\n" "Language-Team: GNOME French Team <gnomefr@traduc.org>\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
@ -26,80 +26,115 @@ msgstr "GNOME Shell"
msgid "Window management and application launching" msgid "Window management and application launching"
msgstr "Gestion des fenêtres et lancement des applications" msgstr "Gestion des fenêtres et lancement des applications"
#. left side #: ../js/ui/appDisplay.js:696
#: ../js/ui/panel.js:269 msgid "Drag here to add favorites"
msgid "Activities" msgstr "Glisser ici pour ajouter aux favoris"
msgstr "Activités"
#. Translators: This is a time format. #: ../js/ui/appIcon.js:425
#: ../js/ui/panel.js:452 msgid "New Window"
msgid "%a %l:%M %p" msgstr "Nouvelle fenêtre"
msgstr "%a %H:%M"
#: ../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..." msgid "Find..."
msgstr "Rechercher..." msgstr "Rechercher..."
#: ../js/ui/dash.js:372
msgid "Browse"
msgstr "Parcourir"
#: ../js/ui/dash.js:508
msgid "(see all)"
msgstr "(tout afficher)"
#. **** Applications **** #. **** 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" msgid "APPLICATIONS"
msgstr "APPLICATIONS" msgstr "APPLICATIONS"
#. **** Places **** #. **** Places ****
#. Translators: This is in the sense of locations for documents, #. Translators: This is in the sense of locations for documents,
#. network locations, etc. #. network locations, etc.
#: ../js/ui/dash.js:720 #: ../js/ui/dash.js:676 ../js/ui/dash.js:733
msgid "PLACES" msgid "PLACES"
msgstr "RACCOURCIS" msgstr "RACCOURCIS"
#. **** Documents **** #. **** 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" msgid "RECENT DOCUMENTS"
msgstr "DOCUMENTS RÉCENTS" msgstr "DOCUMENTS RÉCENTS"
#. **** Search Results **** #. **** 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" msgid "SEARCH RESULTS"
msgstr "RÉSULTATS DE LA RECHERCHE" 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:" msgid "Please enter a command:"
msgstr "Veuillez saisir une commande :" 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" msgid "Less than a minute ago"
msgstr "Il y a moins d'une minute" msgstr "Il y a moins d'une minute"
#: ../src/shell-global.c:802 #: ../src/shell-global.c:824
#, c-format #, c-format
msgid "%d minute ago" msgid "%d minute ago"
msgid_plural "%d minutes ago" msgid_plural "%d minutes ago"
msgstr[0] "Il y a %d minute" msgstr[0] "Il y a %d minute"
msgstr[1] "Il y a %d minutes" msgstr[1] "Il y a %d minutes"
#: ../src/shell-global.c:805 #: ../src/shell-global.c:827
#, c-format #, c-format
msgid "%d hour ago" msgid "%d hour ago"
msgid_plural "%d hours ago" msgid_plural "%d hours ago"
msgstr[0] "Il y a %d heure" msgstr[0] "Il y a %d heure"
msgstr[1] "Il y a %d heures" msgstr[1] "Il y a %d heures"
#: ../src/shell-global.c:808 #: ../src/shell-global.c:830
#, c-format #, c-format
msgid "%d day ago" msgid "%d day ago"
msgid_plural "%d days ago" msgid_plural "%d days ago"
msgstr[0] "Il y a %d jour" msgstr[0] "Il y a %d jour"
msgstr[1] "Il y a %d jours" msgstr[1] "Il y a %d jours"
#: ../src/shell-global.c:811 #: ../src/shell-global.c:833
#, c-format #, c-format
msgid "%d week ago" msgid "%d week ago"
msgid_plural "%d weeks ago" msgid_plural "%d weeks ago"
@ -180,3 +215,6 @@ msgstr "Recherche"
#, c-format #, c-format
msgid "%1$s: %2$s" msgid "%1$s: %2$s"
msgstr "%1$s : %2$s" msgstr "%1$s : %2$s"
#~ msgid "Browse"
#~ msgstr "Parcourir"

240
po/gl.po
View File

@ -2,19 +2,22 @@
# Copyright (C) 2009 gnome-shell's COPYRIGHT HOLDER # Copyright (C) 2009 gnome-shell's COPYRIGHT HOLDER
# This file is distributed under the same license as the gnome-shell package. # This file is distributed under the same license as the gnome-shell package.
# Fran Diéguez <fran.dieguez@mabishu.com>, 2009. # Fran Diéguez <fran.dieguez@mabishu.com>, 2009.
# # Anton Meixome <certima@certima.net>, 2009.
# Antón Méixome <meixome@certima.net>, 2009.
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: gnome-shell master\n" "Project-Id-Version: gnome-shell master\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2009-09-15 23:06+0200\n" "POT-Creation-Date: 2009-12-28 17:07+0100\n"
"PO-Revision-Date: 2009-09-10 22:32+0100\n" "PO-Revision-Date: 2009-12-28 08:11+0100\n"
"Last-Translator: Fran Diéguez <fran.dieguez@glug.es>\n" "Last-Translator: Antón Méixome <meixome@certima.net>\n"
"Language-Team: Galician <gnome@mancomun.org>\n" "Language-Team: Galician Proxecto Trasno <proxecto@trasno.net>\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Language: gl\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Virtaal 0.4.0\n"
#: ../data/gnome-shell.desktop.in.in.h:1 #: ../data/gnome-shell.desktop.in.in.h:1
msgid "GNOME Shell" msgid "GNOME Shell"
@ -22,154 +25,133 @@ msgstr "GNOME Shell"
#: ../data/gnome-shell.desktop.in.in.h:2 #: ../data/gnome-shell.desktop.in.in.h:2
msgid "Window management and application launching" msgid "Window management and application launching"
msgstr "Xestor de xanelas e lanzado de aplicativos" msgstr "Xestor de xanelas e lanzamento de aplicativos"
#. left side #. **** Applications ****
#: ../js/ui/panel.js:269 #: ../js/ui/appDisplay.js:252 ../js/ui/dash.js:858
msgid "Activities" msgid "APPLICATIONS"
msgstr "Actividades" msgstr "APLICATIVOS"
#. Translators: This is a time format. #: ../js/ui/appDisplay.js:276
#: ../js/ui/panel.js:452 msgid "PREFERENCES"
msgid "%a %l:%M %p" msgstr "PREFERENCIAS"
msgstr "%a %l:%M %p"
#: ../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..." msgid "Find..."
msgstr "Buscar..." msgstr "Buscar..."
#: ../js/ui/dash.js:400 #: ../js/ui/dash.js:437
msgid "Browse" msgid "Searching..."
msgstr "Explorar" msgstr "Buscando..."
#: ../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"
#. **** Places **** #. **** Places ****
#. Translators: This is in the sense of locations for documents, #. Translators: This is in the sense of locations for documents,
#. network locations, etc. #. network locations, etc.
#: ../js/ui/dash.js:773 #: ../js/ui/dash.js:878 ../js/ui/placeDisplay.js:519
msgid "PLACES" msgid "PLACES"
msgstr "LUGARES" msgstr "LUGARES"
#. **** Documents **** #. **** Documents ****
#: ../js/ui/dash.js:780 ../js/ui/dash.js:819 #: ../js/ui/dash.js:885
msgid "RECENT DOCUMENTS" msgid "RECENT DOCUMENTS"
msgstr "DOCUMENTOS RECENTES" msgstr "DOCUMENTOS RECENTES"
#. **** Search Results **** #. Button on the left side of the panel.
#: ../js/ui/dash.js:799 ../js/ui/dash.js:931 #. Translators: If there is no suitable word for "Activities" in your language, you can use the word for "Overview".
msgid "SEARCH RESULTS" #: ../js/ui/panel.js:227
msgstr "RESULTADOS DA BÚSQUEDA" msgid "Activities"
msgstr "Actividades"
#: ../js/ui/dash.js:814 #. Translators: This is a time format.
msgid "PREFERENCES" #: ../js/ui/panel.js:440
msgstr "" 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:" msgid "Please enter a command:"
msgstr "Insira unha orde:" msgstr "Insira unha orde:"
#: ../src/shell-global.c:799 #: ../js/ui/runDialog.js:351
msgid "Less than a minute ago" #, c-format
msgstr "Menos de un minuto" 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 #, c-format
msgid "%d minute ago" msgid "%d minute ago"
msgid_plural "%d minutes ago" msgid_plural "%d minutes ago"
msgstr[0] "fai %d minuto" msgstr[0] "hai %d minuto"
msgstr[1] "fai %d minutos" msgstr[1] "hai %d minutos"
#: ../src/shell-global.c:805 #: ../src/shell-global.c:896
#, c-format #, c-format
msgid "%d hour ago" msgid "%d hour ago"
msgid_plural "%d hours ago" msgid_plural "%d hours ago"
msgstr[0] "fai %d hora" msgstr[0] "hai %d hora"
msgstr[1] "fai %d horas" msgstr[1] "hai %d horas"
#: ../src/shell-global.c:808 #: ../src/shell-global.c:899
#, c-format #, c-format
msgid "%d day ago" msgid "%d day ago"
msgid_plural "%d days ago" msgid_plural "%d days ago"
msgstr[0] "fai %d día" msgstr[0] "hai %d día"
msgstr[1] "fai %d días" msgstr[1] "hai %d días"
#: ../src/shell-global.c:811 #: ../src/shell-global.c:902
#, c-format #, c-format
msgid "%d week ago" msgid "%d week ago"
msgid_plural "%d weeks ago" msgid_plural "%d weeks ago"
msgstr[0] "fai %d semana" msgstr[0] "hai %d semana"
msgstr[1] "fai %d semanas" msgstr[1] "hai %d semanas"
#: ../src/shell-status-menu.c:156 #: ../src/shell-uri-util.c:89
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
msgid "Home Folder" msgid "Home Folder"
msgstr "Cartafol persoal" msgstr "Cartafol persoal"
#. Translators: this is the same string as the one found in #. Translators: this is the same string as the one found in
#. * nautilus #. * nautilus
#: ../src/shell-uri-util.c:102 #: ../src/shell-uri-util.c:104
msgid "File System" msgid "File System"
msgstr "Sistema de ficheiros" msgstr "Sistema de ficheiros"
#: ../src/shell-uri-util.c:248 #: ../src/shell-uri-util.c:250
msgid "Search" msgid "Search"
msgstr "Buscar" msgstr "Buscar"
@ -178,11 +160,55 @@ msgstr "Buscar"
#. * example, "Trash: some-directory". It means that the #. * example, "Trash: some-directory". It means that the
#. * directory called "some-directory" is in the trash. #. * directory called "some-directory" is in the trash.
#. #.
#: ../src/shell-uri-util.c:298 #: ../src/shell-uri-util.c:300
#, c-format #, c-format
msgid "%1$s: %2$s" msgid "%1$s: %2$s"
msgstr "%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" #~ msgid "Find apps or documents"
#~ msgstr "Atopar aplicativos ou documentos" #~ msgstr "Atopar aplicativos ou documentos"

215
po/he.po Normal file
View File

@ -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 <lielft@gmail.com>, 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 <lielft@gmail.com>\n"
"Language-Team: Hebrew <he@li.org>\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"

186
po/it.po
View File

@ -7,8 +7,8 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: gnome-shell\n" "Project-Id-Version: gnome-shell\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2009-11-03 22:26+0100\n" "POT-Creation-Date: 2009-12-28 21:58+0100\n"
"PO-Revision-Date: 2009-11-03 22:28+0100\n" "PO-Revision-Date: 2009-12-28 21:59+0100\n"
"Last-Translator: Milo Casagrande <milo@ubuntu.com>\n" "Last-Translator: Milo Casagrande <milo@ubuntu.com>\n"
"Language-Team: Italian <tp@lists.linux.it>\n" "Language-Team: Italian <tp@lists.linux.it>\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
@ -24,85 +24,72 @@ msgstr "GNOME Shell"
msgid "Window management and application launching" msgid "Window management and application launching"
msgstr "Gestione finestre e avvio applicazioni" msgstr "Gestione finestre e avvio applicazioni"
#: ../js/ui/appDisplay.js:332 #. **** Applications ****
msgid "Frequent" #: ../js/ui/appDisplay.js:252 ../js/ui/dash.js:858
msgstr "Frequente" msgid "APPLICATIONS"
msgstr "Applicazioni"
#: ../js/ui/appDisplay.js:867 #: ../js/ui/appDisplay.js:276
msgid "Drag here to add favorites" msgid "PREFERENCES"
msgstr "Trascinare qui per aggiungere ai preferiti" msgstr "Preferenze"
#: ../js/ui/appIcon.js:426 #: ../js/ui/appDisplay.js:707 ../js/ui/appIcon.js:425
msgid "New Window" msgid "New Window"
msgstr "Nuova finestra" msgstr "Nuova finestra"
#: ../js/ui/appIcon.js:430 #: ../js/ui/appDisplay.js:711 ../js/ui/appIcon.js:429
msgid "Remove from Favorites" msgid "Remove from Favorites"
msgstr "Rimuovi dai preferiti" msgstr "Rimuovi dai preferiti"
#: ../js/ui/appIcon.js:431 #: ../js/ui/appDisplay.js:712 ../js/ui/appIcon.js:430
msgid "Add to Favorites" msgid "Add to Favorites"
msgstr "Aggiungi ai preferiti" 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..." msgid "Find..."
msgstr "Trova..." msgstr "Trova..."
#: ../js/ui/dash.js:400 #: ../js/ui/dash.js:437
msgid "More" msgid "Searching..."
msgstr "Altro" msgstr "Ricerca..."
#: ../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"
#. **** Places **** #. **** Places ****
#. Translators: This is in the sense of locations for documents, #. Translators: This is in the sense of locations for documents,
#. network locations, etc. #. network locations, etc.
#: ../js/ui/dash.js:745 #: ../js/ui/dash.js:878 ../js/ui/placeDisplay.js:519
msgid "PLACES" msgid "PLACES"
msgstr "Risorse" msgstr "Risorse"
#. **** Documents **** #. **** Documents ****
#: ../js/ui/dash.js:752 ../js/ui/dash.js:797 #: ../js/ui/dash.js:885
msgid "RECENT DOCUMENTS" msgid "RECENT DOCUMENTS"
msgstr "Documenti recenti" 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. #. 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". #. 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" msgid "Activities"
msgstr "Attività" msgstr "Attività"
# (ndt) proviamo col k, se non funge, sappiamo il perché... # (ndt) proviamo col k, se non funge, sappiamo il perché...
#. Translators: This is a time format. #. Translators: This is a time format.
#: ../js/ui/panel.js:491 #: ../js/ui/panel.js:440
msgid "%a %l:%M %p" msgid "%a %l:%M %p"
msgstr "%a %k.%M" msgstr "%a %k.%M"
#: ../js/ui/places.js:178 #: ../js/ui/placeDisplay.js:144
msgid "Connect to..." msgid "Connect to..."
msgstr "Connetti a..." msgstr "Connetti a..."
#: ../js/ui/runDialog.js:96 #: ../js/ui/runDialog.js:235
msgid "Please enter a command:" msgid "Please enter a command:"
msgstr "Inserire un comando:" msgstr "Inserire un comando:"
#: ../js/ui/runDialog.js:173 #: ../js/ui/runDialog.js:351
#, c-format #, c-format
msgid "Execution of '%s' failed:" msgid "Execution of '%s' failed:"
msgstr "Esecuzione di «%s» non riuscita:" msgstr "Esecuzione di «%s» non riuscita:"
@ -120,102 +107,49 @@ msgstr "Applicazioni"
msgid "Recent Documents" msgid "Recent Documents"
msgstr "Documenti recenti" msgstr "Documenti recenti"
#: ../src/shell-global.c:821 #: ../src/shell-global.c:890
msgid "Less than a minute ago" msgid "Less than a minute ago"
msgstr "Meno di un minuto fa" msgstr "Meno di un minuto fa"
#: ../src/shell-global.c:824 #: ../src/shell-global.c:893
#, c-format #, c-format
msgid "%d minute ago" msgid "%d minute ago"
msgid_plural "%d minutes ago" msgid_plural "%d minutes ago"
msgstr[0] "%d minuto fa" msgstr[0] "%d minuto fa"
msgstr[1] "%d minuti fa" msgstr[1] "%d minuti fa"
#: ../src/shell-global.c:827 #: ../src/shell-global.c:896
#, c-format #, c-format
msgid "%d hour ago" msgid "%d hour ago"
msgid_plural "%d hours ago" msgid_plural "%d hours ago"
msgstr[0] "%d ora fa" msgstr[0] "%d ora fa"
msgstr[1] "%d ore fa" msgstr[1] "%d ore fa"
#: ../src/shell-global.c:830 #: ../src/shell-global.c:899
#, c-format #, c-format
msgid "%d day ago" msgid "%d day ago"
msgid_plural "%d days ago" msgid_plural "%d days ago"
msgstr[0] "%d giorno fa" msgstr[0] "%d giorno fa"
msgstr[1] "%d giorni fa" msgstr[1] "%d giorni fa"
#: ../src/shell-global.c:833 #: ../src/shell-global.c:902
#, c-format #, c-format
msgid "%d week ago" msgid "%d week ago"
msgid_plural "%d weeks ago" msgid_plural "%d weeks ago"
msgstr[0] "%d settimana fa" msgstr[0] "%d settimana fa"
msgstr[1] "%d settimane fa" msgstr[1] "%d settimane fa"
# (ndt) valutare se vada al femminile #: ../src/shell-uri-util.c:89
#: ../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
msgid "Home Folder" msgid "Home Folder"
msgstr "Cartella home" msgstr "Cartella home"
#. Translators: this is the same string as the one found in #. Translators: this is the same string as the one found in
#. * nautilus #. * nautilus
#: ../src/shell-uri-util.c:102 #: ../src/shell-uri-util.c:104
msgid "File System" msgid "File System"
msgstr "File system" msgstr "File system"
#: ../src/shell-uri-util.c:248 #: ../src/shell-uri-util.c:250
msgid "Search" msgid "Search"
msgstr "Cerca" msgstr "Cerca"
@ -225,7 +159,55 @@ msgstr "Cerca"
#. * example, "Trash: some-directory". It means that the #. * example, "Trash: some-directory". It means that the
#. * directory called "some-directory" is in the trash. #. * directory called "some-directory" is in the trash.
#. #.
#: ../src/shell-uri-util.c:298 #: ../src/shell-uri-util.c:300
#, c-format #, c-format
msgid "%1$s: %2$s" msgid "%1$s: %2$s"
msgstr "%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..."

165
po/sl.po
View File

@ -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. # This file is distributed under the same license as the gnome-shell package.
# Matej Urbančič <mateju@svn.gnome.org>, 2009. #
# Matej Urbančič <mateju@svn.gnome.org>, 2009 - 2010.
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: gnome-shell master\n" "Project-Id-Version: gnome-shell master\n"
"Report-Msgid-Bugs-To: http://bugzilla.gnome.org/enter_bug.cgi?product=gnome-shell&component=general\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" "POT-Creation-Date: 2010-01-05 01:21+0000\n"
"PO-Revision-Date: 2009-11-14 10:19+0100\n" "PO-Revision-Date: 2010-01-05 09:09+0100\n"
"Last-Translator: Matej Urbančič <mateju@svn.gnome.org>\n" "Last-Translator: Matej Urbančič <mateju@svn.gnome.org>\n"
"Language-Team: Slovenian GNOME Translation Team <gnome-si@googlegroups.com>\n" "Language-Team: Slovenian GNOME Translation Team <gnome-si@googlegroups.com>\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
@ -27,76 +29,76 @@ msgstr "Gnome lupina"
msgid "Window management and application launching" msgid "Window management and application launching"
msgstr "Upravljanje oken in zaganjanje programov" msgstr "Upravljanje oken in zaganjanje programov"
#: ../js/ui/appDisplay.js:696 #. **** Applications ****
msgid "Drag here to add favorites" #: ../js/ui/appDisplay.js:252
msgstr "S potegom na to mesto se izbor doda med priljubljene" #: ../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 #: ../js/ui/appIcon.js:425
msgid "New Window" msgid "New Window"
msgstr "Novo okno" msgstr "Novo okno"
#: ../js/ui/appDisplay.js:711
#: ../js/ui/appIcon.js:429 #: ../js/ui/appIcon.js:429
msgid "Remove from Favorites" msgid "Remove from Favorites"
msgstr "Odstrani iz priljubljenih" msgstr "Odstrani iz priljubljenih"
#: ../js/ui/appDisplay.js:712
#: ../js/ui/appIcon.js:430 #: ../js/ui/appIcon.js:430
msgid "Add to Favorites" msgid "Add to Favorites"
msgstr "Dodaj med priljubljene" msgstr "Dodaj med priljubljene"
#: ../js/ui/dash.js:237 #: ../js/ui/appDisplay.js:1064
msgid "Find..." msgid "Drag here to add favorites"
msgstr "Poišči ..." msgstr "S potegom na to mesto se izbor doda med priljubljene"
#. **** Applications **** #: ../js/ui/dash.js:240
#: ../js/ui/dash.js:656 msgid "Find..."
#: ../js/ui/dash.js:718 msgstr "Najdi ..."
msgid "APPLICATIONS"
msgstr "Programi" #: ../js/ui/dash.js:437
msgid "Searching..."
msgstr "Iskanje ..."
#. **** Places **** #. **** Places ****
#. Translators: This is in the sense of locations for documents, #. Translators: This is in the sense of locations for documents,
#. network locations, etc. #. network locations, etc.
#: ../js/ui/dash.js:676 #: ../js/ui/dash.js:878
#: ../js/ui/dash.js:733 #: ../js/ui/placeDisplay.js:519
msgid "PLACES" msgid "PLACES"
msgstr "Mesta" msgstr "Mesta"
#. **** Documents **** #. **** Documents ****
#: ../js/ui/dash.js:683 #: ../js/ui/dash.js:885
#: ../js/ui/dash.js:728
msgid "RECENT DOCUMENTS" msgid "RECENT DOCUMENTS"
msgstr "Nedavni dokumenti" 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. #. 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". #. 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" msgid "Activities"
msgstr "Dejavnosti" msgstr "Dejavnosti"
#. Translators: This is a time format. #. Translators: This is a time format.
#: ../js/ui/panel.js:491 #: ../js/ui/panel.js:440
msgid "%a %l:%M %p" msgid "%a %l:%M %p"
msgstr "%a, %H:%M" msgstr "%a, %H:%M"
#: ../js/ui/placeDisplay.js:84 #: ../js/ui/placeDisplay.js:144
msgid "Connect to..." msgid "Connect to..."
msgstr "Povezava z ..." msgstr "Povezava z ..."
#: ../js/ui/runDialog.js:96 #: ../js/ui/runDialog.js:235
msgid "Please enter a command:" msgid "Please enter a command:"
msgstr "Vnos ukaza:" msgstr "Vnos ukaza:"
#: ../js/ui/runDialog.js:173 #: ../js/ui/runDialog.js:351
#, c-format #, c-format
msgid "Execution of '%s' failed:" msgid "Execution of '%s' failed:"
msgstr "Izvajanje '%s' je spodletelo:" msgstr "Izvajanje '%s' je spodletelo:"
@ -114,11 +116,11 @@ msgstr "Programi"
msgid "Recent Documents" msgid "Recent Documents"
msgstr "Nedavni dokumenti" msgstr "Nedavni dokumenti"
#: ../src/shell-global.c:821 #: ../src/shell-global.c:890
msgid "Less than a minute ago" msgid "Less than a minute ago"
msgstr "Pred manj kot eno minuto" msgstr "Pred manj kot eno minuto"
#: ../src/shell-global.c:824 #: ../src/shell-global.c:893
#, c-format #, c-format
msgid "%d minute ago" msgid "%d minute ago"
msgid_plural "%d minutes ago" msgid_plural "%d minutes ago"
@ -127,7 +129,7 @@ msgstr[1] "Pred %d minuto"
msgstr[2] "Pred %d minutama" msgstr[2] "Pred %d minutama"
msgstr[3] "Pred %d minutami" msgstr[3] "Pred %d minutami"
#: ../src/shell-global.c:827 #: ../src/shell-global.c:896
#, c-format #, c-format
msgid "%d hour ago" msgid "%d hour ago"
msgid_plural "%d hours ago" msgid_plural "%d hours ago"
@ -136,7 +138,7 @@ msgstr[1] "Pred %d uro"
msgstr[2] "Pred %d urama" msgstr[2] "Pred %d urama"
msgstr[3] "Pred %d urami" msgstr[3] "Pred %d urami"
#: ../src/shell-global.c:830 #: ../src/shell-global.c:899
#, c-format #, c-format
msgid "%d day ago" msgid "%d day ago"
msgid_plural "%d days ago" msgid_plural "%d days ago"
@ -145,7 +147,7 @@ msgstr[1] "Pred %d dnevom"
msgstr[2] "Pred %d dnevoma" msgstr[2] "Pred %d dnevoma"
msgstr[3] "Pred %d dnevi" msgstr[3] "Pred %d dnevi"
#: ../src/shell-global.c:833 #: ../src/shell-global.c:902
#, c-format #, c-format
msgid "%d week ago" msgid "%d week ago"
msgid_plural "%d weeks ago" msgid_plural "%d weeks ago"
@ -154,80 +156,55 @@ msgstr[1] "Pred %d tednom"
msgstr[2] "Pred %d tednoma" msgstr[2] "Pred %d tednoma"
msgstr[3] "Pred %d tedni" msgstr[3] "Pred %d tedni"
#: ../src/shell-status-menu.c:156 #: ../src/shell-uri-util.c:89
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
msgid "Home Folder" msgid "Home Folder"
msgstr "Domača mapa" msgstr "Domača mapa"
#. Translators: this is the same string as the one found in #. Translators: this is the same string as the one found in
#. * nautilus #. * nautilus
#: ../src/shell-uri-util.c:102 #: ../src/shell-uri-util.c:104
msgid "File System" msgid "File System"
msgstr "Datotečni sistem" msgstr "Datotečni sistem"
#: ../src/shell-uri-util.c:248 #: ../src/shell-uri-util.c:250
msgid "Search" msgid "Search"
msgstr "Iskanje" msgstr "Poišči"
#. Translators: the first string is the name of a gvfs #. Translators: the first string is the name of a gvfs
#. * method, and the second string is a path. For #. * method, and the second string is a path. For
#. * example, "Trash: some-directory". It means that the #. * example, "Trash: some-directory". It means that the
#. * directory called "some-directory" is in the trash. #. * directory called "some-directory" is in the trash.
#. #.
#: ../src/shell-uri-util.c:298 #: ../src/shell-uri-util.c:300
#, c-format #, c-format
msgid "%1$s: %2$s" msgid "%1$s: %2$s"
msgstr "%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" #~ msgid "Frequent"
#~ msgstr "Pogosto" #~ msgstr "Pogosto"
#~ msgid "More" #~ msgid "More"

185
po/sv.po
View File

@ -7,8 +7,8 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: gnome-shell\n" "Project-Id-Version: gnome-shell\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2009-09-18 13:02+0200\n" "POT-Creation-Date: 2009-12-01 23:52+0100\n"
"PO-Revision-Date: 2009-09-18 13:02+0100\n" "PO-Revision-Date: 2009-12-01 23:53+0100\n"
"Last-Translator: Daniel Nylander <po@danielnylander.se>\n" "Last-Translator: Daniel Nylander <po@danielnylander.se>\n"
"Language-Team: Swedish <tp-sv@listor.tp-sv.se>\n" "Language-Team: Swedish <tp-sv@listor.tp-sv.se>\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
@ -24,154 +24,139 @@ msgstr "GNOME-skal"
msgid "Window management and application launching" msgid "Window management and application launching"
msgstr "Fönsterhantering och programstarter" msgstr "Fönsterhantering och programstarter"
#. left side #: ../js/ui/appDisplay.js:580
#: ../js/ui/panel.js:269 #: ../js/ui/appIcon.js:425
msgid "Activities" msgid "New Window"
msgstr "Aktiviteter" msgstr "Nytt fönster"
#. Translators: This is a time format. #: ../js/ui/appDisplay.js:584
#: ../js/ui/panel.js:452 #: ../js/ui/appIcon.js:429
msgid "%a %l:%M %p" msgid "Remove from Favorites"
msgstr "%a %H.%M" 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..." msgid "Find..."
msgstr "Sök..." 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 **** #. **** Applications ****
#: ../js/ui/dash.js:753 #: ../js/ui/dash.js:620
#: ../js/ui/dash.js:809 #: ../js/ui/dash.js:682
msgid "APPLICATIONS" msgid "APPLICATIONS"
msgstr "PROGRAM" msgstr "PROGRAM"
#. **** Places **** #. **** Places ****
#. Translators: This is in the sense of locations for documents, #. Translators: This is in the sense of locations for documents,
#. network locations, etc. #. network locations, etc.
#: ../js/ui/dash.js:773 #: ../js/ui/dash.js:640
#: ../js/ui/dash.js:697
msgid "PLACES" msgid "PLACES"
msgstr "PLATSER" msgstr "PLATSER"
#. **** Documents **** #. **** Documents ****
#: ../js/ui/dash.js:780 #: ../js/ui/dash.js:647
#: ../js/ui/dash.js:819 #: ../js/ui/dash.js:692
msgid "RECENT DOCUMENTS" msgid "RECENT DOCUMENTS"
msgstr "SENASTE DOKUMENT" msgstr "SENASTE DOKUMENT"
#. **** Search Results **** #. **** Search Results ****
#: ../js/ui/dash.js:799 #: ../js/ui/dash.js:672
#: ../js/ui/dash.js:931 #: ../js/ui/dash.js:862
msgid "SEARCH RESULTS" msgid "SEARCH RESULTS"
msgstr "SÖKRESULTAT" msgstr "SÖKRESULTAT"
#: ../js/ui/dash.js:814 #: ../js/ui/dash.js:687
msgid "PREFERENCES" msgid "PREFERENCES"
msgstr "INSTÄLLNINGAR" 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:" msgid "Please enter a command:"
msgstr "Ange ett kommando:" 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" msgid "Less than a minute ago"
msgstr "Mindre än en minut sedan" msgstr "Mindre än en minut sedan"
#: ../src/shell-global.c:802 #: ../src/shell-global.c:829
#, c-format #, c-format
msgid "%d minute ago" msgid "%d minute ago"
msgid_plural "%d minutes ago" msgid_plural "%d minutes ago"
msgstr[0] "%d minut sedan" msgstr[0] "%d minut sedan"
msgstr[1] "%d minuter sedan" msgstr[1] "%d minuter sedan"
#: ../src/shell-global.c:805 #: ../src/shell-global.c:832
#, c-format #, c-format
msgid "%d hour ago" msgid "%d hour ago"
msgid_plural "%d hours ago" msgid_plural "%d hours ago"
msgstr[0] "%d timme sedan" msgstr[0] "%d timme sedan"
msgstr[1] "%d timmar sedan" msgstr[1] "%d timmar sedan"
#: ../src/shell-global.c:808 #: ../src/shell-global.c:835
#, c-format #, c-format
msgid "%d day ago" msgid "%d day ago"
msgid_plural "%d days ago" msgid_plural "%d days ago"
msgstr[0] "%d dag sedan" msgstr[0] "%d dag sedan"
msgstr[1] "%d dagar sedan" msgstr[1] "%d dagar sedan"
#: ../src/shell-global.c:811 #: ../src/shell-global.c:838
#, c-format #, c-format
msgid "%d week ago" msgid "%d week ago"
msgid_plural "%d weeks ago" msgid_plural "%d weeks ago"
msgstr[0] "%d vecka sedan" msgstr[0] "%d vecka sedan"
msgstr[1] "%d veckor sedan" msgstr[1] "%d veckor sedan"
#: ../src/shell-status-menu.c:156 #: ../src/shell-uri-util.c:89
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
msgid "Home Folder" msgid "Home Folder"
msgstr "Hemmapp" msgstr "Hemmapp"
#. Translators: this is the same string as the one found in #. Translators: this is the same string as the one found in
#. * nautilus #. * nautilus
#: ../src/shell-uri-util.c:102 #: ../src/shell-uri-util.c:104
msgid "File System" msgid "File System"
msgstr "Filsystem" msgstr "Filsystem"
#: ../src/shell-uri-util.c:248 #: ../src/shell-uri-util.c:250
msgid "Search" msgid "Search"
msgstr "Sök" msgstr "Sök"
@ -180,11 +165,37 @@ msgstr "Sök"
#. * example, "Trash: some-directory". It means that the #. * example, "Trash: some-directory". It means that the
#. * directory called "some-directory" is in the trash. #. * directory called "some-directory" is in the trash.
#. #.
#: ../src/shell-uri-util.c:298 #: ../src/shell-uri-util.c:300
#, c-format #, c-format
msgid "%1$s: %2$s" msgid "%1$s: %2$s"
msgstr "%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" #~ msgid "Find apps or documents"
#~ msgstr "Hitta program eller dokument" #~ msgstr "Hitta program eller dokument"
#~ msgid "DOCUMENTS" #~ msgid "DOCUMENTS"

View File

@ -78,6 +78,7 @@ st_source_h = \
st/st-entry.h \ st/st-entry.h \
st/st-im-text.h \ st/st-im-text.h \
st/st-label.h \ st/st-label.h \
st/st-overflow-box.h \
st/st-private.h \ st/st-private.h \
st/st-scrollable.h \ st/st-scrollable.h \
st/st-scroll-bar.h \ st/st-scroll-bar.h \
@ -116,6 +117,7 @@ st_source_c = \
st/st-entry.c \ st/st-entry.c \
st/st-im-text.c \ st/st-im-text.c \
st/st-label.c \ st/st-label.c \
st/st-overflow-box.c \
st/st-private.c \ st/st-private.c \
st/st-scrollable.c \ st/st-scrollable.c \
st/st-scroll-bar.c \ st/st-scroll-bar.c \

View File

@ -63,6 +63,8 @@ libgnome_shell_la_SOURCES = \
shell-app-usage.h \ shell-app-usage.h \
shell-arrow.c \ shell-arrow.c \
shell-arrow.h \ shell-arrow.h \
shell-doc-system.c \
shell-doc-system.h \
shell-drawing.c \ shell-drawing.c \
shell-drawing.h \ shell-drawing.h \
shell-embedded-window.c \ shell-embedded-window.c \

87
src/gnome-shell.in Executable file → Normal file
View File

@ -212,6 +212,8 @@ parser.add_option("-w", "--wide", action="store_true",
help="Use widescreen (1280x800) with Xephyr") help="Use widescreen (1280x800) with Xephyr")
parser.add_option("", "--eval-file", metavar="EVAL_FILE", parser.add_option("", "--eval-file", metavar="EVAL_FILE",
help="Evaluate the contents of the given JavaScript 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() options, args = parser.parse_args()
@ -219,6 +221,91 @@ if args:
parser.print_usage() parser.print_usage()
sys.exit(1) sys.exit(1)
if options.create_extension:
import json
print
print '''Name should be a very short (ideally descriptive) string.
Examples are: "Click To Focus", "Adblock", "Shell Window Shrinker".
'''
name = raw_input('Name: ').strip()
print
print '''Description is a single-sentence explanation of what your extension does.
Examples are: "Make windows visible on click", "Block advertisement popups"
"Animate windows shrinking on minimize"
'''
description = raw_input('Description: ').strip()
underifier = re.compile('[^A-Za-z]')
sample_uuid = underifier.sub('_', name)
# TODO use evolution data server
hostname = subprocess.Popen(['hostname'], stdout=subprocess.PIPE).communicate()[0].strip()
sample_uuid = sample_uuid + '@' + hostname
print
print '''Uuid is a globally-unique identifier for your extension.
This should be in the format of an email address (foo.bar@extensions.example.com), but
need not be an actual email address, though it's a good idea to base the uuid on your
email address. For example, if your email address is janedoe@example.com, you might
use an extension title clicktofocus@janedoe.example.com.'''
uuid = raw_input('Uuid [%s]: ' % (sample_uuid, )).strip()
if uuid == '':
uuid = sample_uuid
extension_path = os.path.join(os.path.expanduser('~/.config'), 'gnome-shell', 'extensions', uuid)
if os.path.exists(extension_path):
print "Extension path %r already exists" % (extension_path, )
sys.exit(0)
os.makedirs(extension_path)
meta = { 'name': name,
'description': description,
'uuid': uuid }
f = open(os.path.join(extension_path, 'metadata.json'), 'w')
try:
json.dump(meta, f)
except AttributeError:
# For Python versions older than 2.6, try using the json-py module
f.write(json.write(meta) + '\n')
f.close()
extensionjs_path = os.path.join(extension_path, 'extension.js')
f = open(extensionjs_path, 'w')
f.write('''// Sample extension code, makes clicking on the panel show a message
const St = imports.gi.St;
const Mainloop = imports.mainloop;
const Main = imports.ui.main;
function _showHello() {
let text = new St.Label({ style_class: 'helloworld-label', text: "Hello, world!" });
let monitor = global.get_primary_monitor();
global.stage.add_actor(text);
text.set_position(Math.floor (monitor.width / 2 - text.width / 2), Math.floor(monitor.height / 2 - text.height / 2));
Mainloop.timeout_add(3000, function () { text.destroy(); });
}
// Put your extension initialization code here
function main() {
Main.panel.actor.reactive = true;
Main.panel.actor.connect('button-release-event', _showHello);
}
''')
f.close()
f = open(os.path.join(extension_path, 'stylesheet.css'), 'w')
f.write('''/* Example stylesheet */
.helloworld-label {
font-size: 36px;
font-weight: bold;
color: #ffffff;
background-color: rgba(10,10,10,0.7);
border-radius: 5px;
}
''')
f.close()
subprocess.Popen(['gnome-open', extensionjs_path])
sys.exit(0)
if options.eval_file: if options.eval_file:
import dbus import dbus

View File

@ -48,9 +48,7 @@ struct _ShellAppSystemPrivate {
GHashTable *app_id_to_info; GHashTable *app_id_to_info;
GHashTable *app_id_to_app; GHashTable *app_id_to_app;
GHashTable *cached_menu_contents; /* <char *id, GSList<ShellAppInfo*>> */ GSList *cached_flattened_apps; /* ShellAppInfo */
GSList *cached_app_menus; /* ShellAppMenuEntry */
GSList *cached_settings; /* ShellAppInfo */ GSList *cached_settings; /* ShellAppInfo */
gint app_monitor_id; gint app_monitor_id;
@ -58,7 +56,6 @@ struct _ShellAppSystemPrivate {
guint app_change_timeout_id; guint app_change_timeout_id;
}; };
static void free_appinfo_gslist (gpointer list);
static void shell_app_system_finalize (GObject *object); static void shell_app_system_finalize (GObject *object);
static gboolean on_tree_changed (gpointer user_data); static gboolean on_tree_changed (gpointer user_data);
static void on_tree_changed_cb (GMenuTree *tree, gpointer user_data); static void on_tree_changed_cb (GMenuTree *tree, gpointer user_data);
@ -83,6 +80,10 @@ struct _ShellAppInfo {
*/ */
guint refcount; guint refcount;
char *casefolded_name;
char *name_collation_key;
char *casefolded_description;
GMenuTreeItem *entry; GMenuTreeItem *entry;
GKeyFile *keyfile; GKeyFile *keyfile;
@ -104,6 +105,11 @@ shell_app_info_unref (ShellAppInfo *info)
{ {
if (--info->refcount > 0) if (--info->refcount > 0)
return; return;
g_free (info->casefolded_name);
g_free (info->name_collation_key);
g_free (info->casefolded_description);
switch (info->type) switch (info->type)
{ {
case SHELL_APP_INFO_TYPE_ENTRY: case SHELL_APP_INFO_TYPE_ENTRY:
@ -129,7 +135,7 @@ shell_app_info_new_from_tree_item (GMenuTreeItem *item)
if (!item) if (!item)
return NULL; return NULL;
info = g_slice_alloc (sizeof (ShellAppInfo)); info = g_slice_alloc0 (sizeof (ShellAppInfo));
info->type = SHELL_APP_INFO_TYPE_ENTRY; info->type = SHELL_APP_INFO_TYPE_ENTRY;
info->refcount = 1; info->refcount = 1;
info->entry = gmenu_tree_item_ref (item); info->entry = gmenu_tree_item_ref (item);
@ -141,7 +147,7 @@ shell_app_info_new_from_window (MetaWindow *window)
{ {
ShellAppInfo *info; ShellAppInfo *info;
info = g_slice_alloc (sizeof (ShellAppInfo)); info = g_slice_alloc0 (sizeof (ShellAppInfo));
info->type = SHELL_APP_INFO_TYPE_WINDOW; info->type = SHELL_APP_INFO_TYPE_WINDOW;
info->refcount = 1; info->refcount = 1;
info->window = g_object_ref (window); info->window = g_object_ref (window);
@ -159,7 +165,7 @@ shell_app_info_new_from_keyfile_take_ownership (GKeyFile *keyfile,
{ {
ShellAppInfo *info; ShellAppInfo *info;
info = g_slice_alloc (sizeof (ShellAppInfo)); info = g_slice_alloc0 (sizeof (ShellAppInfo));
info->type = SHELL_APP_INFO_TYPE_DESKTOP_FILE; info->type = SHELL_APP_INFO_TYPE_DESKTOP_FILE;
info->refcount = 1; info->refcount = 1;
info->keyfile = keyfile; info->keyfile = keyfile;
@ -167,29 +173,6 @@ shell_app_info_new_from_keyfile_take_ownership (GKeyFile *keyfile,
return info; 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) static void shell_app_system_class_init(ShellAppSystemClass *klass)
{ {
GObjectClass *gobject_class = (GObjectClass *)klass; GObjectClass *gobject_class = (GObjectClass *)klass;
@ -225,9 +208,6 @@ shell_app_system_init (ShellAppSystem *self)
/* Key is owned by info */ /* Key is owned by info */
priv->app_id_to_app = g_hash_table_new (g_str_hash, g_str_equal); 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 /* For now, we want to pick up Evince, Nautilus, etc. We'll
* handle NODISPLAY semantics at a higher level or investigate them * handle NODISPLAY semantics at a higher level or investigate them
* case by case. * case by case.
@ -257,15 +237,12 @@ shell_app_system_finalize (GObject *object)
gmenu_tree_unref (priv->apps_tree); gmenu_tree_unref (priv->apps_tree);
gmenu_tree_unref (priv->settings_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_info);
g_hash_table_destroy (priv->app_id_to_app); 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_foreach (priv->cached_flattened_apps, (GFunc)shell_app_info_unref, NULL);
g_slist_free (priv->cached_app_menus); g_slist_free (priv->cached_flattened_apps);
priv->cached_app_menus = NULL; priv->cached_flattened_apps = NULL;
g_slist_foreach (priv->cached_settings, (GFunc)shell_app_info_unref, NULL); g_slist_foreach (priv->cached_settings, (GFunc)shell_app_info_unref, NULL);
g_slist_free (priv->cached_settings); g_slist_free (priv->cached_settings);
priv->cached_settings = NULL; priv->cached_settings = NULL;
@ -273,60 +250,10 @@ shell_app_system_finalize (GObject *object)
G_OBJECT_CLASS (shell_app_system_parent_class)->finalize(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 * static GSList *
gather_entries_recurse (ShellAppSystem *monitor, gather_entries_recurse (ShellAppSystem *monitor,
GSList *apps, GSList *apps,
GHashTable *unique,
GMenuTreeDirectory *root) GMenuTreeDirectory *root)
{ {
GSList *contents; GSList *contents;
@ -342,13 +269,17 @@ gather_entries_recurse (ShellAppSystem *monitor,
case GMENU_TREE_ITEM_ENTRY: case GMENU_TREE_ITEM_ENTRY:
{ {
ShellAppInfo *app = shell_app_info_new_from_tree_item (item); ShellAppInfo *app = shell_app_info_new_from_tree_item (item);
if (!g_hash_table_lookup (unique, shell_app_info_get_id (app)))
{
apps = g_slist_prepend (apps, app); apps = g_slist_prepend (apps, app);
g_hash_table_insert (unique, (char*)shell_app_info_get_id (app), app);
}
} }
break; break;
case GMENU_TREE_ITEM_DIRECTORY: case GMENU_TREE_ITEM_DIRECTORY:
{ {
GMenuTreeDirectory *dir = (GMenuTreeDirectory*)item; GMenuTreeDirectory *dir = (GMenuTreeDirectory*)item;
apps = gather_entries_recurse (monitor, apps, dir); apps = gather_entries_recurse (monitor, apps, unique, dir);
} }
break; break;
default: default:
@ -365,6 +296,7 @@ gather_entries_recurse (ShellAppSystem *monitor,
static void static void
reread_entries (ShellAppSystem *self, reread_entries (ShellAppSystem *self,
GSList **cache, GSList **cache,
GHashTable *unique,
GMenuTree *tree) GMenuTree *tree)
{ {
GMenuTreeDirectory *trunk; GMenuTreeDirectory *trunk;
@ -375,23 +307,22 @@ reread_entries (ShellAppSystem *self,
g_slist_free (*cache); g_slist_free (*cache);
*cache = NULL; *cache = NULL;
*cache = gather_entries_recurse (self, *cache, trunk); *cache = gather_entries_recurse (self, *cache, unique, trunk);
gmenu_tree_item_unref (trunk); gmenu_tree_item_unref (trunk);
} }
static void static void
cache_by_id (ShellAppSystem *self, GSList *apps, gboolean ref) cache_by_id (ShellAppSystem *self, GSList *apps)
{ {
GSList *iter; GSList *iter;
for (iter = apps; iter; iter = iter->next) for (iter = apps; iter; iter = iter->next)
{ {
ShellAppInfo *info = iter->data; ShellAppInfo *info = iter->data;
if (ref)
shell_app_info_ref (info); shell_app_info_ref (info);
/* the name is owned by the info itself */ /* 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), g_hash_table_replace (self->priv->app_id_to_info, (char*)shell_app_info_get_id (info),
info); info);
} }
} }
@ -399,22 +330,17 @@ cache_by_id (ShellAppSystem *self, GSList *apps, gboolean ref)
static void static void
reread_menus (ShellAppSystem *self) reread_menus (ShellAppSystem *self)
{ {
GSList *apps; GHashTable *unique = g_hash_table_new (g_str_hash, g_str_equal);
GMenuTreeDirectory *trunk;
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); 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); cache_by_id (self, self->priv->cached_flattened_apps);
gmenu_tree_item_unref (trunk); cache_by_id (self, self->priv->cached_settings);
cache_by_id (self, apps, FALSE);
g_slist_free (apps);
cache_by_id (self, self->priv->cached_settings, TRUE);
} }
static gboolean static gboolean
@ -423,7 +349,6 @@ on_tree_changed (gpointer user_data)
ShellAppSystem *self = SHELL_APP_SYSTEM (user_data); ShellAppSystem *self = SHELL_APP_SYSTEM (user_data);
reread_menus (self); reread_menus (self);
g_hash_table_remove_all (self->priv->cached_menu_contents);
g_signal_emit (self, signals[INSTALLED_CHANGED], 0); g_signal_emit (self, signals[INSTALLED_CHANGED], 0);
@ -469,21 +394,8 @@ shell_app_info_get_type (void)
return gtype; 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 * Traverses a toplevel menu, and returns all items under it. Nested items
* are flattened. This value is computed on initial call and cached thereafter * 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 * Return value: (transfer none) (element-type ShellAppInfo): List of applications
*/ */
GSList * GSList *
shell_app_system_get_applications_for_menu (ShellAppSystem *self, shell_app_system_get_flattened_apps (ShellAppSystem *self)
const char *menu)
{ {
GSList *apps; return self->priv->cached_flattened_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;
} }
/** /**
@ -711,6 +591,253 @@ shell_app_system_lookup_heuristic_basename (ShellAppSystem *system,
return NULL; return NULL;
} }
typedef enum {
MATCH_NONE,
MATCH_MULTIPLE, /* Matches multiple terms */
MATCH_PREFIX, /* Strict prefix */
MATCH_SUBSTRING /* Not prefix, substring */
} ShellAppInfoSearchMatch;
static char *
normalize_and_casefold (const char *str)
{
char *normalized, *result;
if (str == NULL)
return NULL;
normalized = g_utf8_normalize (str, -1, G_NORMALIZE_ALL);
result = g_utf8_casefold (normalized, -1);
g_free (normalized);
return result;
}
static void
shell_app_info_init_search_data (ShellAppInfo *info)
{
const char *name;
const char *comment;
g_assert (info->type == SHELL_APP_INFO_TYPE_ENTRY);
name = gmenu_tree_entry_get_name ((GMenuTreeEntry*)info->entry);
info->casefolded_name = normalize_and_casefold (name);
comment = gmenu_tree_entry_get_comment ((GMenuTreeEntry*)info->entry);
info->casefolded_description = normalize_and_casefold (comment);
}
static ShellAppInfoSearchMatch
shell_app_info_match_terms (ShellAppInfo *info,
GSList *terms)
{
GSList *iter;
ShellAppInfoSearchMatch match;
if (G_UNLIKELY(!info->casefolded_name))
shell_app_info_init_search_data (info);
match = MATCH_NONE;
for (iter = terms; iter; iter = iter->next)
{
const char *term = iter->data;
const char *p;
p = strstr (info->casefolded_name, term);
if (p == info->casefolded_name)
{
if (match != MATCH_NONE)
return MATCH_MULTIPLE;
else
match = MATCH_PREFIX;
}
else if (p != NULL)
match = MATCH_SUBSTRING;
if (!info->casefolded_description)
continue;
p = strstr (info->casefolded_description, term);
if (p != NULL)
match = MATCH_SUBSTRING;
}
return match;
}
static gint
shell_app_info_compare (gconstpointer a,
gconstpointer b,
gpointer data)
{
ShellAppSystem *system = data;
const char *id_a = a;
const char *id_b = b;
ShellAppInfo *info_a = g_hash_table_lookup (system->priv->app_id_to_info, id_a);
ShellAppInfo *info_b = g_hash_table_lookup (system->priv->app_id_to_info, id_b);
if (!info_a->name_collation_key)
info_a->name_collation_key = g_utf8_collate_key (gmenu_tree_entry_get_name ((GMenuTreeEntry*)info_a->entry), -1);
if (!info_b->name_collation_key)
info_b->name_collation_key = g_utf8_collate_key (gmenu_tree_entry_get_name ((GMenuTreeEntry*)info_b->entry), -1);
return strcmp (info_a->name_collation_key, info_b->name_collation_key);
}
static GSList *
sort_and_concat_results (ShellAppSystem *system,
GSList *multiple_matches,
GSList *prefix_matches,
GSList *substring_matches)
{
multiple_matches = g_slist_sort_with_data (multiple_matches, shell_app_info_compare, system);
prefix_matches = g_slist_sort_with_data (prefix_matches, shell_app_info_compare, system);
substring_matches = g_slist_sort_with_data (substring_matches, shell_app_info_compare, system);
return g_slist_concat (multiple_matches, g_slist_concat (prefix_matches, substring_matches));
}
/**
* normalize_terms:
* @terms: (element-type utf8): Input search terms
*
* Returns: (element-type utf8) (transfer full): Unicode-normalized and lowercased terms
*/
static GSList *
normalize_terms (GSList *terms)
{
GSList *normalized_terms = NULL;
GSList *iter;
for (iter = terms; iter; iter = iter->next)
{
const char *term = iter->data;
normalized_terms = g_slist_prepend (normalized_terms, normalize_and_casefold (term));
}
return normalized_terms;
}
static inline void
shell_app_system_do_match (ShellAppSystem *system,
ShellAppInfo *info,
GSList *terms,
GSList **multiple_results,
GSList **prefix_results,
GSList **substring_results)
{
const char *id = shell_app_info_get_id (info);
ShellAppInfoSearchMatch match;
if (shell_app_info_get_is_nodisplay (info))
return;
match = shell_app_info_match_terms (info, terms);
switch (match)
{
case MATCH_NONE:
break;
case MATCH_MULTIPLE:
*multiple_results = g_slist_prepend (*multiple_results, (char *) id);
break;
case MATCH_PREFIX:
*prefix_results = g_slist_prepend (*prefix_results, (char *) id);
break;
case MATCH_SUBSTRING:
*substring_results = g_slist_prepend (*substring_results, (char *) id);
break;
}
}
static GSList *
shell_app_system_initial_search_internal (ShellAppSystem *self,
GSList *terms,
GSList *source)
{
GSList *multiple_results = NULL;
GSList *prefix_results = NULL;
GSList *substring_results = NULL;
GSList *iter;
GSList *normalized_terms = normalize_terms (terms);
for (iter = source; iter; iter = iter->next)
{
ShellAppInfo *info = iter->data;
shell_app_system_do_match (self, info, normalized_terms, &multiple_results, &prefix_results, &substring_results);
}
g_slist_foreach (normalized_terms, (GFunc)g_free, NULL);
g_slist_free (normalized_terms);
return sort_and_concat_results (self, multiple_results, prefix_results, substring_results);
}
/**
* shell_app_system_initial_search:
* @self: A #ShellAppSystem
* @prefs: %TRUE iff we should search preferences instead of apps
* @terms: (element-type utf8): List of terms, logical OR
*
* Search through applications for the given search terms. Note that returned
* strings are only valid until a return to the main loop.
*
* Returns: (transfer container) (element-type utf8): List of application identifiers
*/
GSList *
shell_app_system_initial_search (ShellAppSystem *self,
gboolean prefs,
GSList *terms)
{
return shell_app_system_initial_search_internal (self, terms,
prefs ? self->priv->cached_settings : self->priv->cached_flattened_apps);
}
/**
* shell_app_system_subsearch:
* @self: A #ShellAppSystem
* @prefs: %TRUE iff we should search preferences instead of apps
* @previous_results: (element-type utf8): List of previous results
* @terms: (element-type utf8): List of terms, logical OR
*
* Search through a previous result set; for more information, see
* js/ui/search.js. Note the value of @prefs must be
* the same as passed to shell_app_system_initial_search(). Note that returned
* strings are only valid until a return to the main loop.
*
* Returns: (transfer container) (element-type utf8): List of application identifiers
*/
GSList *
shell_app_system_subsearch (ShellAppSystem *system,
gboolean prefs,
GSList *previous_results,
GSList *terms)
{
GSList *iter;
GSList *multiple_results = NULL;
GSList *prefix_results = NULL;
GSList *substring_results = NULL;
GSList *normalized_terms = normalize_terms (terms);
/* Note prefs is deliberately ignored; both apps and prefs are in app_id_to_app,
* but we have the parameter for consistency and in case in the future
* they're not in the same data structure.
*/
for (iter = previous_results; iter; iter = iter->next)
{
const char *id = iter->data;
ShellAppInfo *info;
info = g_hash_table_lookup (system->priv->app_id_to_info, id);
if (!info)
continue;
shell_app_system_do_match (system, info, normalized_terms, &multiple_results, &prefix_results, &substring_results);
}
g_slist_foreach (normalized_terms, (GFunc)g_free, NULL);
g_slist_free (normalized_terms);
/* Note that a shorter term might have matched as a prefix, but
when extended only as a substring, so we have to redo the
sort rather than reusing the existing ordering */
return sort_and_concat_results (system, multiple_results, prefix_results, substring_results);
}
const char * const char *
shell_app_info_get_id (ShellAppInfo *info) shell_app_info_get_id (ShellAppInfo *info)
{ {

View File

@ -37,18 +37,6 @@ struct _ShellAppSystemClass
GType shell_app_system_get_type (void) G_GNUC_CONST; GType shell_app_system_get_type (void) G_GNUC_CONST;
ShellAppSystem* shell_app_system_get_default(void); 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; typedef struct _ShellAppInfo ShellAppInfo;
#define SHELL_TYPE_APP_INFO (shell_app_info_get_type ()) #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); 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_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__ */ #endif /* __SHELL_APP_SYSTEM_H__ */

367
src/shell-doc-system.c Normal file
View File

@ -0,0 +1,367 @@
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
#include "config.h"
#include "shell-doc-system.h"
#include "shell-global.h"
#include "shell-texture-cache.h"
/**
* SECTION:shell-doc-system
* @short_description: Track recently used documents
*
* Wraps #GtkRecentManager, caching recently used document information, and adds
* APIs for asynchronous queries.
*/
enum {
CHANGED,
DELETED,
LAST_SIGNAL
};
static guint signals[LAST_SIGNAL] = { 0 };
struct _ShellDocSystemPrivate {
GtkRecentManager *manager;
GHashTable *infos_by_uri;
GSList *infos_by_timestamp;
guint idle_recent_changed_id;
GHashTable *deleted_infos;
guint idle_emit_deleted_id;
};
G_DEFINE_TYPE(ShellDocSystem, shell_doc_system, G_TYPE_OBJECT);
/**
* shell_doc_system_get_all:
* @self: A #ShellDocSystem
*
* Returns the currently cached set of recent files. Recent files are read initially
* from the underlying #GtkRecentManager, and updated when it changes.
* This function does not perform I/O.
*
* Returns: (transfer none) (element-type GtkRecentInfo): Cached recent file infos
*/
GSList *
shell_doc_system_get_all (ShellDocSystem *self)
{
return self->priv->infos_by_timestamp;
}
/**
* @self: A #ShellDocSystem
* @uri: Url
*
* Returns: (transfer none): Recent file info corresponding to given @uri
*/
GtkRecentInfo *
shell_doc_system_lookup_by_uri (ShellDocSystem *self,
const char *uri)
{
return g_hash_table_lookup (self->priv->infos_by_uri, uri);
}
static gboolean
shell_doc_system_idle_emit_deleted (gpointer data)
{
ShellDocSystem *self = SHELL_DOC_SYSTEM (data);
GHashTableIter iter;
gpointer key, value;
self->priv->idle_emit_deleted_id = 0;
g_hash_table_iter_init (&iter, self->priv->deleted_infos);
while (g_hash_table_iter_next (&iter, &key, &value))
{
GtkRecentInfo *info = key;
g_signal_emit (self, signals[DELETED], 0, info);
}
g_signal_emit (self, signals[CHANGED], 0);
return FALSE;
}
typedef struct {
ShellDocSystem *self;
GtkRecentInfo *info;
} ShellDocSystemRecentQueryData;
static void
on_recent_file_query_result (GObject *source,
GAsyncResult *result,
gpointer user_data)
{
ShellDocSystemRecentQueryData *data = user_data;
ShellDocSystem *self = data->self;
GError *error = NULL;
GFileInfo *fileinfo;
fileinfo = g_file_query_info_finish (G_FILE (source), result, &error);
if (fileinfo)
g_object_unref (fileinfo);
/* This is a strict error check; we don't want to cause recent files to
* vanish for anything potentially transient.
*/
if (error != NULL && error->domain == G_IO_ERROR && error->code == G_IO_ERROR_NOT_FOUND)
{
self->priv->infos_by_timestamp = g_slist_remove (self->priv->infos_by_timestamp, data->info);
g_hash_table_remove (self->priv->infos_by_uri, gtk_recent_info_get_uri (data->info));
g_hash_table_insert (self->priv->deleted_infos, gtk_recent_info_ref (data->info), NULL);
if (self->priv->idle_emit_deleted_id == 0)
self->priv->idle_emit_deleted_id = g_timeout_add (0, shell_doc_system_idle_emit_deleted, self);
}
g_clear_error (&error);
gtk_recent_info_unref (data->info);
g_free (data);
}
/**
* shell_doc_system_queue_existence_check:
* @self: A #ShellDocSystem
* @n_items: Count of items to check for existence, starting from most recent
*
* Asynchronously start a check of a number of recent file for existence;
* any deleted files will be emitted from the #ShellDocSystem::deleted
* signal. Note that this function ignores non-local files; they
* will simply always appear to exist (until they are removed from
* the recent file list manually).
*
* The intent of this function is to be called after a #ShellDocSystem::changed
* signal has been emitted, and a display has shown a subset of those files.
*/
void
shell_doc_system_queue_existence_check (ShellDocSystem *self,
guint n_items)
{
GSList *iter;
guint i;
for (i = 0, iter = self->priv->infos_by_timestamp; i < n_items && iter; i++, iter = iter->next)
{
GtkRecentInfo *info = iter->data;
const char *uri;
GFile *file;
ShellDocSystemRecentQueryData *data;
if (!gtk_recent_info_is_local (info))
continue;
data = g_new0 (ShellDocSystemRecentQueryData, 1);
data->self = self;
data->info = gtk_recent_info_ref (info);
uri = gtk_recent_info_get_uri (info);
file = g_file_new_for_uri (uri);
g_file_query_info_async (file, "standard::type", G_FILE_QUERY_INFO_NONE,
G_PRIORITY_DEFAULT, NULL, on_recent_file_query_result, data);
g_object_unref (file);
}
}
static int
sort_infos_by_timestamp_descending (gconstpointer a,
gconstpointer b)
{
GtkRecentInfo *info_a = (GtkRecentInfo*)a;
GtkRecentInfo *info_b = (GtkRecentInfo*)b;
time_t modified_a, modified_b;
modified_a = gtk_recent_info_get_modified (info_a);
modified_b = gtk_recent_info_get_modified (info_b);
return modified_b - modified_a;
}
static gboolean
idle_handle_recent_changed (gpointer data)
{
ShellDocSystem *self = SHELL_DOC_SYSTEM (data);
GList *items, *iter;
self->priv->idle_recent_changed_id = 0;
g_hash_table_remove_all (self->priv->deleted_infos);
g_hash_table_remove_all (self->priv->infos_by_uri);
g_slist_free (self->priv->infos_by_timestamp);
self->priv->infos_by_timestamp = NULL;
items = gtk_recent_manager_get_items (self->priv->manager);
for (iter = items; iter; iter = iter->next)
{
GtkRecentInfo *info = iter->data;
const char *uri = gtk_recent_info_get_uri (info);
/* uri is owned by the info */
g_hash_table_insert (self->priv->infos_by_uri, (char*) uri, info);
self->priv->infos_by_timestamp = g_slist_prepend (self->priv->infos_by_timestamp, info);
}
g_list_free (items);
self->priv->infos_by_timestamp = g_slist_sort (self->priv->infos_by_timestamp, sort_infos_by_timestamp_descending);
g_signal_emit (self, signals[CHANGED], 0);
return FALSE;
}
static void
shell_doc_system_on_recent_changed (GtkRecentManager *manager,
ShellDocSystem *self)
{
if (self->priv->idle_recent_changed_id != 0)
return;
self->priv->idle_recent_changed_id = g_timeout_add (0, idle_handle_recent_changed, self);
}
/**
* shell_doc_system_open:
* @system: A #ShellDocSystem
* @info: A #GtkRecentInfo
*
* Launch the default application associated with the mime type of
* @info, using its uri.
*/
void
shell_doc_system_open (ShellDocSystem *system,
GtkRecentInfo *info)
{
GFile *file;
GAppInfo *app_info;
gboolean needs_uri;
file = g_file_new_for_uri (gtk_recent_info_get_uri (info));
needs_uri = g_file_get_path (file) == NULL;
g_object_unref (file);
app_info = g_app_info_get_default_for_type (gtk_recent_info_get_mime_type (info), needs_uri);
if (app_info != NULL)
{
GList *uris;
uris = g_list_prepend (NULL, (gpointer)gtk_recent_info_get_uri (info));
g_app_info_launch_uris (app_info, uris, shell_global_create_app_launch_context (shell_global_get ()), NULL);
g_list_free (uris);
}
else
{
char *app_name;
#if GTK_MINOR_VERSION >= 18
const char *app_exec;
#else
char *app_exec;
#endif
char *app_exec_quoted;
guint count;
time_t time;
app_name = gtk_recent_info_last_application (info);
if (gtk_recent_info_get_application_info (info, app_name, &app_exec, &count, &time))
{
GRegex *regex;
GAppLaunchContext *context;
/* TODO: Change this once better support for creating
GAppInfo is added to GtkRecentInfo, as right now
this relies on the fact that the file uri is
already a part of appExec, so we don't supply any
files to app_info.launch().
The 'command line' passed to
create_from_command_line is allowed to contain
'%<something>' 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;
}

46
src/shell-doc-system.h Normal file
View File

@ -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 <gio/gio.h>
#include <gtk/gtk.h>
#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__ */

View File

@ -105,7 +105,7 @@ function runTestFixedBox() {
G_DEFINE_TYPE(ShellGenericContainer, shell_generic_container, CLUTTER_TYPE_GROUP); G_DEFINE_TYPE(ShellGenericContainer, shell_generic_container, CLUTTER_TYPE_GROUP);
struct _ShellGenericContainerPrivate { struct _ShellGenericContainerPrivate {
gpointer dummy; GHashTable *skip_paint;
}; };
/* Signals */ /* Signals */
@ -182,15 +182,119 @@ shell_generic_container_get_preferred_height (ClutterActor *actor,
shell_generic_container_allocation_unref (alloc); 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 static void
shell_generic_container_class_init (ShellGenericContainerClass *klass) shell_generic_container_class_init (ShellGenericContainerClass *klass)
{ {
GObjectClass *gobject_class = G_OBJECT_CLASS (klass); GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
ClutterActorClass *actor_class = CLUTTER_ACTOR_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_width = shell_generic_container_get_preferred_width;
actor_class->get_preferred_height = shell_generic_container_get_preferred_height; actor_class->get_preferred_height = shell_generic_container_get_preferred_height;
actor_class->allocate = shell_generic_container_allocate; 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] = shell_generic_container_signals[GET_PREFERRED_WIDTH] =
g_signal_new ("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, area->priv = G_TYPE_INSTANCE_GET_PRIVATE (area, SHELL_TYPE_GENERIC_CONTAINER,
ShellGenericContainerPrivate); 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) GType shell_generic_container_allocation_get_type (void)

View File

@ -42,4 +42,8 @@ struct _ShellGenericContainerClass
GType shell_generic_container_get_type (void) G_GNUC_CONST; 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__ */ #endif /* __SHELL_GENERIC_CONTAINER_H__ */

View File

@ -522,6 +522,70 @@ shell_global_display_is_grabbed (ShellGlobal *global)
return meta_display_get_grab_op (display) != META_GRAB_OP_NONE; 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),
"<target_object_script>",
0,
&target_object))
{
char *message;
gjs_log_exception(context,
&message);
g_set_error(error,
G_IO_ERROR,
G_IO_ERROR_FAILED,
"%s", message ? message : "(unknown)");
g_free(message);
return FALSE;
}
if (!JSVAL_IS_OBJECT (target_object))
{
g_error ("shell_global_add_extension_importer: invalid target object");
return FALSE;
}
search_path[0] = (char*)directory;
importer = gjs_define_importer (context, JSVAL_TO_OBJECT (target_object), target_property, (const char **)search_path, FALSE);
return TRUE;
}
/* Code to close all file descriptors before we exec; copied from gspawn.c in GLib. /* Code to close all file descriptors before we exec; copied from gspawn.c in GLib.
* *
* Authors: Padraig O'Briain, Matthias Clasen, Lennart Poettering * Authors: Padraig O'Briain, Matthias Clasen, Lennart Poettering
@ -1064,3 +1128,63 @@ shell_popup_menu (GtkMenu *menu, int button, guint32 time,
gtk_menu_popup (menu, NULL, NULL, shell_popup_menu_position_func, NULL, gtk_menu_popup (menu, NULL, NULL, shell_popup_menu_position_func, NULL,
button, time); 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;
}

View File

@ -43,6 +43,12 @@ ShellGlobal *shell_global_get (void);
MetaScreen *shell_global_get_screen (ShellGlobal *global); 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); void shell_global_grab_dbus_service (ShellGlobal *global);
typedef enum { typedef enum {
@ -82,6 +88,9 @@ ClutterModifierType shell_get_event_state (ClutterEvent *event);
void shell_popup_menu (GtkMenu *menu, int button, guint32 time, void shell_popup_menu (GtkMenu *menu, int button, guint32 time,
int menu_x, int menu_y); 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 G_END_DECLS

View File

@ -98,10 +98,6 @@ static void
st_button_update_label_style (StButton *button) st_button_update_label_style (StButton *button)
{ {
ClutterActor *label; ClutterActor *label;
StThemeNode *theme_node;
ClutterColor color;
const PangoFontDescription *font;
gchar *font_string = NULL;
label = st_bin_get_child ((StBin*) button); label = st_bin_get_child ((StBin*) button);
@ -109,15 +105,7 @@ st_button_update_label_style (StButton *button)
if (!CLUTTER_IS_TEXT (label)) if (!CLUTTER_IS_TEXT (label))
return; return;
theme_node = st_widget_get_theme_node (ST_WIDGET (button)); _st_set_text_from_style ((ClutterText*) label, 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);
} }
static void static void

View File

@ -43,7 +43,7 @@
#include <clutter/clutter.h> #include <clutter/clutter.h>
#include "st-label.h" #include "st-label.h"
#include "st-private.h"
#include "st-widget.h" #include "st-widget.h"
enum enum
@ -110,21 +110,9 @@ st_label_get_property (GObject *gobject,
static void static void
st_label_style_changed (StWidget *self) st_label_style_changed (StWidget *self)
{ {
StLabelPrivate *priv; StLabelPrivate *priv = ST_LABEL(self)->priv;
StThemeNode *theme_node;
ClutterColor color;
const PangoFontDescription *font;
gchar *font_string;
priv = ST_LABEL (self)->priv; _st_set_text_from_style ((ClutterText *)priv->label, st_widget_get_theme_node (self));
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_WIDGET_CLASS (st_label_parent_class)->style_changed (self); ST_WIDGET_CLASS (st_label_parent_class)->style_changed (self);
} }

706
src/st/st-overflow-box.c Normal file
View File

@ -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 <walters@verbum.org>, 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 <stdlib.h>
#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;
}

75
src/st/st-overflow-box.h Normal file
View File

@ -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 <st/st.h> can be included directly.h"
#endif
#ifndef _ST_OVERFLOW_BOX_H
#define _ST_OVERFLOW_BOX_H
#include <st/st-box-layout.h>
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 */

View File

@ -110,3 +110,52 @@ _st_allocate_fill (ClutterActor *child,
*childbox = allocation; *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);
}

View File

@ -55,4 +55,7 @@ void _st_allocate_fill (ClutterActor *child,
gboolean x_fill, gboolean x_fill,
gboolean y_fill); gboolean y_fill);
void _st_set_text_from_style (ClutterText *text,
StThemeNode *theme_node);
#endif /* __ST_PRIVATE_H__ */ #endif /* __ST_PRIVATE_H__ */

View File

@ -1953,7 +1953,10 @@ st_theme_node_get_font (StThemeNode *node)
} }
if (family) 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) if (size_set)
pango_font_description_set_absolute_size (node->font_desc, size); pango_font_description_set_absolute_size (node->font_desc, size);

View File

@ -44,6 +44,8 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <gio/gio.h>
#include "st-theme-node.h" #include "st-theme-node.h"
#include "st-theme-private.h" #include "st-theme-private.h"
@ -68,6 +70,7 @@ struct _StTheme
char *application_stylesheet; char *application_stylesheet;
char *default_stylesheet; char *default_stylesheet;
char *theme_stylesheet; char *theme_stylesheet;
GSList *custom_stylesheets;
GHashTable *stylesheets_by_filename; GHashTable *stylesheets_by_filename;
GHashTable *filenames_by_stylesheet; GHashTable *filenames_by_stylesheet;
@ -193,24 +196,19 @@ convert_rgba_RGBA (char *buf)
} }
static CRStyleSheet * static CRStyleSheet *
parse_stylesheet (const char *filename) parse_stylesheet (const char *filename,
GError **error)
{ {
enum CRStatus status; enum CRStatus status;
char *contents; char *contents;
gsize length; gsize length;
GError *error = NULL;
CRStyleSheet *stylesheet = NULL; CRStyleSheet *stylesheet = NULL;
if (filename == NULL) if (filename == NULL)
return NULL; return NULL;
if (!g_file_get_contents (filename, &contents ,&length, &error)) if (!g_file_get_contents (filename, &contents, &length, error))
{
g_warning("Couldn't read stylesheet: %s", error->message);
g_error_free (error);
return NULL; return NULL;
}
convert_rgba_RGBA (contents); convert_rgba_RGBA (contents);
@ -218,11 +216,14 @@ parse_stylesheet (const char *filename)
length, length,
CR_UTF_8, CR_UTF_8,
&stylesheet); &stylesheet);
g_free (contents);
if (status != CR_OK) if (status != CR_OK)
g_warning ("Error parsing stylesheet '%s'", filename); {
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
g_free (contents); "Error parsing stylesheet '%s'; errcode:%d", filename, status);
return NULL;
}
return stylesheet; return stylesheet;
} }
@ -243,7 +244,8 @@ _st_theme_parse_declaration_list (const char *str)
} }
#else /* LIBCROCO_VERSION_NUMBER >= 602 */ #else /* LIBCROCO_VERSION_NUMBER >= 602 */
static CRStyleSheet * static CRStyleSheet *
parse_stylesheet (const char *filename) parse_stylesheet (const char *filename,
GError **error)
{ {
enum CRStatus status; enum CRStatus status;
CRStyleSheet *stylesheet; CRStyleSheet *stylesheet;
@ -257,7 +259,8 @@ parse_stylesheet (const char *filename)
if (status != CR_OK) 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; return NULL;
} }
@ -272,6 +275,22 @@ _st_theme_parse_declaration_list (const char *str)
} }
#endif /* LIBCROCO_VERSION_NUMBER < 602 */ #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 static void
insert_stylesheet (StTheme *theme, insert_stylesheet (StTheme *theme,
const char *filename, const char *filename,
@ -289,6 +308,42 @@ insert_stylesheet (StTheme *theme,
g_hash_table_insert (theme->filenames_by_stylesheet, stylesheet, filename_copy); 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 * static GObject *
st_theme_constructor (GType type, st_theme_constructor (GType type,
guint n_construct_properties, guint n_construct_properties,
@ -305,9 +360,9 @@ st_theme_constructor (GType type,
construct_properties); construct_properties);
theme = ST_THEME (object); theme = ST_THEME (object);
application_stylesheet = parse_stylesheet (theme->application_stylesheet); application_stylesheet = parse_stylesheet_nofail (theme->application_stylesheet);
theme_stylesheet = parse_stylesheet (theme->theme_stylesheet); theme_stylesheet = parse_stylesheet_nofail (theme->theme_stylesheet);
default_stylesheet = parse_stylesheet (theme->default_stylesheet); default_stylesheet = parse_stylesheet_nofail (theme->default_stylesheet);
theme->cascade = cr_cascade_new (application_stylesheet, theme->cascade = cr_cascade_new (application_stylesheet,
theme_stylesheet, theme_stylesheet,
@ -328,6 +383,10 @@ st_theme_finalize (GObject * object)
{ {
StTheme *theme = ST_THEME (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->stylesheets_by_filename);
g_hash_table_destroy (theme->filenames_by_stylesheet); 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. *Evaluates if a given additional selector matches an style node.
*@param a_add_sel the additional selector to consider. *@param a_add_sel the additional selector to consider.
*@param a_node the style node 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); import_rule->url->stryng->str);
if (filename) if (filename)
import_rule->sheet = parse_stylesheet (filename); import_rule->sheet = parse_stylesheet (filename, NULL);
if (import_rule->sheet) if (import_rule->sheet)
{ {
@ -980,6 +1040,7 @@ _st_theme_get_matched_properties (StTheme *theme,
enum CRStyleOrigin origin = 0; enum CRStyleOrigin origin = 0;
CRStyleSheet *sheet = NULL; CRStyleSheet *sheet = NULL;
GPtrArray *props = g_ptr_array_new (); 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 (theme), NULL);
g_return_val_if_fail (ST_IS_THEME_NODE (node), 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); 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 /* We count on a stable sort here so that later declarations come
* after earlier declarations */ * after earlier declarations */
g_ptr_array_sort (props, compare_declarations); g_ptr_array_sort (props, compare_declarations);

View File

@ -33,6 +33,10 @@ StTheme *st_theme_new (const char *application_stylesheet,
const char *theme_stylesheet, const char *theme_stylesheet,
const char *default_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 G_END_DECLS
#endif /* __ST_THEME_H__ */ #endif /* __ST_THEME_H__ */

View File

@ -155,7 +155,7 @@ if test x$system = xMandrivaLinux ; then
fi fi
SOURCE=$HOME/Source 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 if [ -d $SOURCE ] ; then : ; else
mkdir $SOURCE mkdir $SOURCE

View File

@ -18,7 +18,7 @@
# Only rebuild modules that have changed # Only rebuild modules that have changed
build_policy = 'updated' 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' ] modules = [ 'gnome-shell' ]