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>
</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>
</gconfschemafile>

View File

@ -17,6 +17,15 @@
* Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
*/
.shell-link {
color: #0000ff;
text-decoration: underline;
}
.shell-link:hover {
color: #0000e0;
}
StScrollBar
{
padding: 0px;
@ -152,17 +161,6 @@ StTooltip {
spacing: 12px;
}
.dash-search-section-header {
padding: 6px 0px;
spacing: 4px;
font-size: 12px;
color: #bbbbbb;
}
.dash-search-section-title, dash-search-section-count {
font-weight: bold;
}
#searchEntry {
padding: 4px;
border-bottom: 1px solid #262626;
@ -237,6 +235,29 @@ StTooltip {
height: 16px;
}
.dash-search-section-header {
padding: 6px 0px;
spacing: 4px;
}
.dash-search-section-results {
color: #ffffff;
padding-left: 4px;
}
.dash-search-section-list-results {
spacing: 4px;
}
.dash-search-result-content {
padding: 2px;
}
.dash-search-result-content:selected {
padding: 1px;
border: 1px solid #262626;
}
/* GenericDisplay */
.generic-display-container {
@ -352,6 +373,19 @@ StTooltip {
border-radius: 4px;
}
#LookingGlassDialog .labels {
spacing: 4px;
}
#LookingGlassDialog .notebook-tab {
padding: 2px;
}
#LookingGlassDialog .notebook-tab:selected {
border: 1px solid #88ff66;
padding: 1px;
}
#LookingGlassDialog StLabel
{
color: #88ff66;
@ -368,6 +402,29 @@ StTooltip {
spacing: 4px;
}
#lookingGlassExtensions {
padding: 4px;
}
.lg-extension-list {
padding: 4px;
spacing: 6px;
}
.lg-extension {
border: 1px solid #6f6f6f;
border-radius: 4px;
padding: 4px;
}
.lg-extension-name {
font-weight: bold;
}
.lg-extension-actions {
spacing: 6px;
}
/* Calendar popup */
#calendarPopup {
@ -458,6 +515,11 @@ StTooltip {
spacing: 4px;
}
.switcher-list .thumbnail {
width: 256px;
height: 256px;
}
.switcher-list .outlined-item-box {
padding: 6px;
border: 2px solid rgba(85,85,85,1.0);

View File

@ -7,6 +7,7 @@ const Shell = imports.gi.Shell;
const Lang = imports.lang;
const Signals = imports.signals;
const Search = imports.ui.search;
const Main = imports.ui.main;
const THUMBNAIL_ICON_MARGIN = 2;
@ -23,6 +24,7 @@ DocInfo.prototype = {
// correctly. See http://bugzilla.gnome.org/show_bug.cgi?id=567094
this.timestamp = recentInfo.get_modified().getTime() / 1000;
this.name = recentInfo.get_display_name();
this._lowerName = this.name.toLowerCase();
this.uri = recentInfo.get_uri();
this.mimeType = recentInfo.get_mime_type();
},
@ -32,54 +34,27 @@ DocInfo.prototype = {
},
launch : function() {
// While using Gio.app_info_launch_default_for_uri() would be
// shorter in terms of lines of code, we are not doing so
// because that would duplicate the work of retrieving the
// mime type.
let needsUri = Gio.file_new_for_uri(this.uri).get_path() == null;
let appInfo = Gio.app_info_get_default_for_type(this.mimeType, needsUri);
if (appInfo != null) {
appInfo.launch_uris([this.uri], Main.createAppLaunchContext());
} else {
log("Failed to get default application info for mime type " + this.mimeType +
". Will try to use the last application that registered the document.");
let appName = this.recentInfo.last_application();
let [success, appExec, count, time] = this.recentInfo.get_application_info(appName);
if (success) {
log("Will open a document with the following command: " + appExec);
// TODO: Change this once better support for creating
// GAppInfo is added to GtkRecentInfo, as right now
// this relies on the fact that the file uri is
// already a part of appExec, so we don't supply any
// files to appInfo.launch().
// The 'command line' passed to
// create_from_command_line is allowed to contain
// '%<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);
}
}
Shell.DocSystem.get_default().open(this.recentInfo);
},
exists : function() {
return this.recentInfo.exists();
matchTerms: function(terms) {
let mtype = Search.MatchType.NONE;
for (let i = 0; i < terms.length; i++) {
let term = terms[i];
let idx = this._lowerName.indexOf(term);
if (idx == 0) {
if (mtype != Search.MatchType.NONE)
return Search.MatchType.MULTIPLE;
mtype = Search.MatchType.PREFIX;
} else if (idx > 0) {
if (mtype != Search.MatchType.NONE)
return Search.MatchType.MULTIPLE;
mtype = Search.MatchType.SUBSTRING;
} else {
continue;
}
}
return mtype;
}
};
@ -91,50 +66,86 @@ function getDocManager() {
return docManagerInstance;
}
/**
* DocManager wraps the DocSystem, primarily to expose DocInfo objects
* which conform to the GenericDisplay item API.
*/
function DocManager() {
this._init();
}
DocManager.prototype = {
_init: function() {
this._recentManager = Gtk.RecentManager.get_default();
this._items = {};
this._recentManager.connect('changed', Lang.bind(this, function(recentManager) {
this._reload();
this.emit('changed');
}));
this._docSystem = Shell.DocSystem.get_default();
this._infosByTimestamp = [];
this._infosByUri = {};
this._docSystem.connect('changed', Lang.bind(this, this._reload));
this._reload();
},
_reload: function() {
let docs = this._recentManager.get_items();
let newItems = {};
let docs = this._docSystem.get_all();
this._infosByTimestamp = [];
this._infosByUri = {};
for (let i = 0; i < docs.length; i++) {
let recentInfo = docs[i];
if (!recentInfo.exists())
continue;
let docInfo = new DocInfo(recentInfo);
// we use GtkRecentInfo URI as an item Id
newItems[docInfo.uri] = docInfo;
this._infosByTimestamp.push(docInfo);
this._infosByUri[docInfo.uri] = docInfo;
}
let deleted = {};
for (var uri in this._items) {
if (!(uri in newItems))
deleted[uri] = this._items[uri];
}
/* If we'd cached any thumbnail references that no longer exist,
dump them here */
let texCache = Shell.TextureCache.get_default();
for (var uri in deleted) {
texCache.evict_recent_thumbnail(this._items[uri].recentInfo);
}
this._items = newItems;
this.emit('changed');
},
getItems: function() {
return this._items;
getTimestampOrderedInfos: function() {
return this._infosByTimestamp;
},
getInfosByUri: function() {
return this._infosByUri;
},
lookupByUri: function(uri) {
return this._infosByUri[uri];
},
queueExistenceCheck: function(count) {
return this._docSystem.queue_existence_check(count);
},
initialSearch: function(terms) {
let multipleMatches = [];
let prefixMatches = [];
let substringMatches = [];
for (let i = 0; i < this._infosByTimestamp.length; i++) {
let item = this._infosByTimestamp[i];
let mtype = item.matchTerms(terms);
if (mtype == Search.MatchType.MULTIPLE)
multipleMatches.push(item.uri);
else if (mtype == Search.MatchType.PREFIX)
prefixMatches.push(item.uri);
else if (mtype == Search.MatchType.SUBSTRING)
substringMatches.push(item.uri);
}
return multipleMatches.concat(prefixMatches.concat(substringMatches));
},
subsearch: function(previousResults, terms) {
let multipleMatches = [];
let prefixMatches = [];
let substringMatches = [];
for (let i = 0; i < previousResults.length; i++) {
let uri = previousResults[i];
let item = this._infosByUri[uri];
let mtype = item.matchTerms(terms);
if (mtype == Search.MatchType.MULTIPLE)
multipleMatches.push(uri);
else if (mtype == Search.MatchType.PREFIX)
prefixMatches.push(uri);
else if (mtype == Search.MatchType.SUBSTRING)
substringMatches.push(uri);
}
return multipleMatches.concat(prefixMatches.concat(substringMatches));
}
}

View File

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

View File

@ -459,7 +459,7 @@ SwitcherList.prototype = {
this._separator = box;
this._list.add_actor(box);
},
highlight: function(index, justOutline) {
if (this._highlighted != -1)
this._items[this._highlighted].style_class = 'item-box';
@ -477,7 +477,7 @@ SwitcherList.prototype = {
_itemActivated: function(n) {
this.emit('item-activated', n);
},
_itemEntered: function(n) {
this.emit('item-entered', n);
},
@ -592,7 +592,7 @@ AppIcon.prototype = {
this.actor = new St.BoxLayout({ style_class: "alt-tab-app",
vertical: true });
this._icon = this.app.create_icon_texture(POPUP_APPICON_SIZE);
this.actor.add(this._icon, { x_fill: false, y_fill: false });
this.actor.add(this._icon, { x_fill: false, y_fill: false } );
this._label = new St.Label({ text: this.app.get_name() });
this.actor.add(this._label, { x_fill: false });
}
@ -750,11 +750,14 @@ ThumbnailList.prototype = {
let box = new St.BoxLayout({ style_class: "thumbnail-box",
vertical: true });
let bin = new St.Bin({ style_class: "thumbnail" });
let clone = new Clutter.Clone ({ source: windowTexture,
reactive: true,
width: width * scale,
height: height * scale });
box.add_actor(clone);
bin.add_actor(clone);
box.add_actor(bin);
let title = windows[i].get_title();
if (title) {

View File

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

View File

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

View File

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

View File

@ -17,6 +17,10 @@ const DocDisplay = imports.ui.docDisplay;
const PlaceDisplay = imports.ui.placeDisplay;
const GenericDisplay = imports.ui.genericDisplay;
const Main = imports.ui.main;
const Search = imports.ui.search;
// 25 search results (per result type) should be enough for everyone
const MAX_RENDERED_SEARCH_RESULTS = 25;
const DEFAULT_PADDING = 4;
const DEFAULT_SPACING = 4;
@ -332,6 +336,254 @@ SearchEntry.prototype = {
};
Signals.addSignalMethods(SearchEntry.prototype);
function SearchResult(provider, metaInfo, terms) {
this._init(provider, metaInfo, terms);
}
SearchResult.prototype = {
_init: function(provider, metaInfo, terms) {
this.provider = provider;
this.metaInfo = metaInfo;
this.actor = new St.Clickable({ style_class: 'dash-search-result',
reactive: true,
x_align: St.Align.START,
x_fill: true,
y_fill: true });
this.actor._delegate = this;
let content = provider.createResultActor(metaInfo, terms);
if (content == null) {
content = new St.BoxLayout({ style_class: 'dash-search-result-content' });
let title = new St.Label({ text: this.metaInfo['name'] });
let icon = this.metaInfo['icon'];
content.add(icon, { y_fill: false });
content.add(title, { expand: true, y_fill: false });
}
this._content = content;
this.actor.set_child(content);
this.actor.connect('clicked', Lang.bind(this, this._onResultClicked));
},
setSelected: function(selected) {
this._content.set_style_pseudo_class(selected ? 'selected' : null);
},
activate: function() {
this.provider.activateResult(this.metaInfo.id);
Main.overview.toggle();
},
_onResultClicked: function(actor, event) {
this.activate();
}
}
function OverflowSearchResults(provider) {
this._init(provider);
}
OverflowSearchResults.prototype = {
__proto__: Search.SearchResultDisplay.prototype,
_init: function(provider) {
Search.SearchResultDisplay.prototype._init.call(this, provider);
this.actor = new St.OverflowBox({ style_class: 'dash-search-section-list-results' });
},
renderResults: function(results, terms) {
for (let i = 0; i < results.length && i < MAX_RENDERED_SEARCH_RESULTS; i++) {
let result = results[i];
let meta = this.provider.getResultMeta(result);
let display = new SearchResult(this.provider, meta, terms);
this.actor.add_actor(display.actor);
}
},
getVisibleCount: function() {
return this.actor.get_n_visible();
},
selectIndex: function(index) {
let nVisible = this.actor.get_n_visible();
let children = this.actor.get_children();
if (this.selectionIndex >= 0) {
let prevActor = children[this.selectionIndex];
prevActor._delegate.setSelected(false);
}
this.selectionIndex = -1;
if (index >= nVisible)
return false;
else if (index < 0)
return false;
let targetActor = children[index];
targetActor._delegate.setSelected(true);
this.selectionIndex = index;
return true;
}
}
function SearchResults(searchSystem) {
this._init(searchSystem);
}
SearchResults.prototype = {
_init: function(searchSystem) {
this._searchSystem = searchSystem;
this.actor = new St.BoxLayout({ name: 'dashSearchResults',
vertical: true });
this._searchingNotice = new St.Label({ style_class: 'dash-search-starting',
text: _("Searching...") });
this.actor.add(this._searchingNotice);
this._selectedProvider = -1;
this._providers = this._searchSystem.getProviders();
this._providerMeta = [];
for (let i = 0; i < this._providers.length; i++) {
let provider = this._providers[i];
let providerBox = new St.BoxLayout({ style_class: 'dash-search-section',
vertical: true });
let titleButton = new St.Button({ style_class: 'dash-search-section-header',
reactive: true,
x_fill: true,
y_fill: true });
titleButton.connect('clicked', Lang.bind(this, function () { this._onHeaderClicked(provider); }));
providerBox.add(titleButton);
let titleBox = new St.BoxLayout();
titleButton.set_child(titleBox);
let title = new St.Label({ text: provider.title });
let count = new St.Label();
titleBox.add(title, { expand: true });
titleBox.add(count);
let resultDisplayBin = new St.Bin({ style_class: 'dash-search-section-results',
x_fill: true,
y_fill: true });
providerBox.add(resultDisplayBin, { expand: true });
let resultDisplay = provider.createResultContainerActor();
if (resultDisplay == null) {
resultDisplay = new OverflowSearchResults(provider);
}
resultDisplayBin.set_child(resultDisplay.actor);
this._providerMeta.push({ actor: providerBox,
resultDisplay: resultDisplay,
count: count });
this.actor.add(providerBox);
}
},
_clearDisplay: function() {
this._selectedProvider = -1;
this._visibleResultsCount = 0;
for (let i = 0; i < this._providerMeta.length; i++) {
let meta = this._providerMeta[i];
meta.resultDisplay.clear();
meta.actor.hide();
}
},
reset: function() {
this._searchSystem.reset();
this._searchingNotice.hide();
this._clearDisplay();
},
startingSearch: function() {
this.reset();
this._searchingNotice.show();
},
_metaForProvider: function(provider) {
return this._providerMeta[this._providers.indexOf(provider)];
},
updateSearch: function (searchString) {
let results = this._searchSystem.updateSearch(searchString);
this._searchingNotice.hide();
this._clearDisplay();
let terms = this._searchSystem.getTerms();
for (let i = 0; i < results.length; i++) {
let [provider, providerResults] = results[i];
let meta = this._metaForProvider(provider);
meta.actor.show();
meta.resultDisplay.renderResults(providerResults, terms);
meta.count.set_text(""+providerResults.length);
}
this.selectDown(false);
return true;
},
_onHeaderClicked: function(provider) {
provider.expandSearch(this._searchSystem.getTerms());
},
_modifyActorSelection: function(resultDisplay, up) {
let success;
let index = resultDisplay.getSelectionIndex();
if (up && index == -1)
index = resultDisplay.getVisibleCount() - 1;
else if (up)
index = index - 1;
else
index = index + 1;
return resultDisplay.selectIndex(index);
},
selectUp: function(recursing) {
for (let i = this._selectedProvider; i >= 0; i--) {
let meta = this._providerMeta[i];
if (!meta.actor.visible)
continue;
let success = this._modifyActorSelection(meta.resultDisplay, true);
if (success) {
this._selectedProvider = i;
return;
}
}
if (this._providerMeta.length > 0 && !recursing) {
this._selectedProvider = this._providerMeta.length - 1;
this.selectUp(true);
}
},
selectDown: function(recursing) {
let current = this._selectedProvider;
if (current == -1)
current = 0;
for (let i = current; i < this._providerMeta.length; i++) {
let meta = this._providerMeta[i];
if (!meta.actor.visible)
continue;
let success = this._modifyActorSelection(meta.resultDisplay, false);
if (success) {
this._selectedProvider = i;
return;
}
}
if (this._providerMeta.length > 0 && !recursing) {
this._selectedProvider = 0;
this.selectDown(true);
}
},
activateSelected: function() {
let current = this._selectedProvider;
if (current < 0)
return;
let meta = this._providerMeta[current];
let resultDisplay = meta.resultDisplay;
let children = resultDisplay.actor.get_children();
let targetActor = children[resultDisplay.getSelectionIndex()];
targetActor._delegate.activate();
}
}
function MoreLink() {
this._init();
}
@ -344,15 +596,15 @@ MoreLink.prototype = {
let expander = new St.Bin({ style_class: "more-link-expander" });
this.actor.add(expander, { expand: true, y_fill: false });
},
this.actor.connect('button-press-event', Lang.bind(this, function (b, e) {
if (this.pane == null) {
// Ensure the pane is created; the activated handler will call setPane
this.emit('activated');
}
this._pane.toggle();
return true;
}));
activate: function() {
if (this.pane == null) {
// Ensure the pane is created; the activated handler will call setPane
this.emit('activated');
}
this._pane.toggle();
return true;
},
setPane: function (pane) {
@ -385,7 +637,8 @@ SectionHeader.prototype = {
this.actor = new St.Bin({ style_class: "section-header",
x_align: St.Align.START,
x_fill: true,
y_fill: true });
y_fill: true,
reactive: !suppressBrowse });
this._innerBox = new St.BoxLayout({ style_class: "section-header-inner" });
this.actor.set_child(this._innerBox);
@ -410,9 +663,14 @@ SectionHeader.prototype = {
if (!suppressBrowse) {
this.moreLink = new MoreLink();
this._innerBox.add(this.moreLink.actor, { x_align: St.Align.END });
this.actor.connect('button-press-event', Lang.bind(this, this._onButtonPress));
}
},
_onButtonPress: function() {
this.moreLink.activate();
},
setTitle : function(title) {
this.text.text = title;
},
@ -500,9 +758,9 @@ Dash.prototype = {
vertical: true,
reactive: true });
// Size for this one explicitly set from overlay.js
this.searchArea = new Big.Box({ y_align: Big.BoxAlignment.CENTER });
// The searchArea just holds the entry
this.searchArea = new St.BoxLayout({ name: "dashSearchArea",
vertical: true });
this.sectionArea = new St.BoxLayout({ name: "dashSections",
vertical: true });
@ -517,16 +775,35 @@ Dash.prototype = {
this._searchActive = false;
this._searchPending = false;
this._searchEntry = new SearchEntry();
this.searchArea.append(this._searchEntry.actor, Big.BoxPackFlags.EXPAND);
this.searchArea.add(this._searchEntry.actor, { y_fill: false, expand: true });
this._searchSystem = new Search.SearchSystem();
this._searchSystem.registerProvider(new AppDisplay.AppSearchProvider());
this._searchSystem.registerProvider(new AppDisplay.PrefsSearchProvider());
this._searchSystem.registerProvider(new PlaceDisplay.PlaceSearchProvider());
this._searchSystem.registerProvider(new DocDisplay.DocSearchProvider());
this.searchResults = new SearchResults(this._searchSystem);
this.actor.add(this.searchResults.actor);
this.searchResults.actor.hide();
this._searchTimeoutId = 0;
this._searchEntry.entry.connect('text-changed', Lang.bind(this, function (se, prop) {
let text = this._searchEntry.getText();
text = text.replace(/^\s+/g, "").replace(/\s+$/g, "")
text = text.replace(/^\s+/g, "").replace(/\s+$/g, "");
let searchPreviouslyActive = this._searchActive;
this._searchActive = text != '';
this._searchPending = this._searchActive && !searchPreviouslyActive;
this._updateDashActors();
if (this._searchPending) {
this.searchResults.startingSearch();
}
if (this._searchActive) {
this.searchResults.actor.show();
this.sectionArea.hide();
} else {
this.searchResults.actor.hide();
this.sectionArea.show();
}
if (!this._searchActive) {
if (this._searchTimeoutId > 0) {
Mainloop.source_remove(this._searchTimeoutId);
@ -543,24 +820,15 @@ Dash.prototype = {
Mainloop.source_remove(this._searchTimeoutId);
this._doSearch();
}
// Only one of the displays will have an item selected, so it's ok to
// call activateSelected() on all of them.
for (var i = 0; i < this._searchSections.length; i++) {
let section = this._searchSections[i];
section.resultArea.display.activateSelected();
}
this.searchResults.activateSelected();
return true;
}));
this._searchEntry.entry.connect('key-press-event', Lang.bind(this, function (se, e) {
let text = this._searchEntry.getText();
let symbol = e.get_key_symbol();
if (symbol == Clutter.Escape) {
// Escape will keep clearing things back to the desktop.
// If we are showing a particular section of search, go back to all sections.
if (this._searchResultsSingleShownSection != null)
this._showAllSearchSections();
// If we have an active search, we remove it.
else if (this._searchActive)
if (this._searchActive)
this._searchEntry.reset();
// Next, if we're in one of the "more" modes or showing the details pane, close them
else if (this._activePane != null)
@ -572,44 +840,14 @@ Dash.prototype = {
} else if (symbol == Clutter.Up) {
if (!this._searchActive)
return true;
// selectUp and selectDown wrap around in their respective displays
// too, but there doesn't seem to be any flickering if we first select
// something in one display, but then unset the selection, and move
// it to the other display, so it's ok to do that.
for (var i = 0; i < this._searchSections.length; i++) {
let section = this._searchSections[i];
if (section.resultArea.display.hasSelected() && !section.resultArea.display.selectUp()) {
if (this._searchResultsSingleShownSection != section.type) {
// We need to move the selection to the next section above this section that has items,
// wrapping around at the bottom, if necessary.
let newSectionIndex = this._findAnotherSectionWithItems(i, -1);
if (newSectionIndex >= 0) {
this._searchSections[newSectionIndex].resultArea.display.selectLastItem();
section.resultArea.display.unsetSelected();
}
}
break;
}
}
this.searchResults.selectUp(false);
return true;
} else if (symbol == Clutter.Down) {
if (!this._searchActive)
return true;
for (var i = 0; i < this._searchSections.length; i++) {
let section = this._searchSections[i];
if (section.resultArea.display.hasSelected() && !section.resultArea.display.selectDown()) {
if (this._searchResultsSingleShownSection != section.type) {
// We need to move the selection to the next section below this section that has items,
// wrapping around at the top, if necessary.
let newSectionIndex = this._findAnotherSectionWithItems(i, 1);
if (newSectionIndex >= 0) {
this._searchSections[newSectionIndex].resultArea.display.selectFirstItem();
section.resultArea.display.unsetSelected();
}
}
break;
}
}
this.searchResults.selectDown(false);
return true;
}
return false;
@ -666,102 +904,12 @@ Dash.prototype = {
this._docDisplay.emit('changed');
this.sectionArea.add(this._docsSection.actor, { expand: true });
/***** Search Results *****/
this._searchResultsSection = new Section(_("SEARCH RESULTS"), true);
this._searchResultsSingleShownSection = null;
this._searchResultsSection.header.connect('back-link-activated', Lang.bind(this, function () {
this._showAllSearchSections();
}));
this._searchSections = [
{ type: APPS,
title: _("APPLICATIONS"),
header: null,
resultArea: null
},
{ type: PREFS,
title: _("PREFERENCES"),
header: null,
resultArea: null
},
{ type: DOCS,
title: _("RECENT DOCUMENTS"),
header: null,
resultArea: null
},
{ type: PLACES,
title: _("PLACES"),
header: null,
resultArea: null
}
];
for (var i = 0; i < this._searchSections.length; i++) {
let section = this._searchSections[i];
section.header = new SearchSectionHeader(section.title,
Lang.bind(this,
function () {
this._showSingleSearchSection(section.type);
}));
this._searchResultsSection.content.add(section.header.actor);
section.resultArea = new ResultArea(section.type, GenericDisplay.GenericDisplayFlags.DISABLE_VSCROLLING);
this._searchResultsSection.content.add(section.resultArea.actor, { expand: true });
createPaneForDetails(this, section.resultArea.display);
}
this.sectionArea.add(this._searchResultsSection.actor, { expand: true });
this._searchResultsSection.actor.hide();
},
_doSearch: function () {
this._searchTimeoutId = 0;
let text = this._searchEntry.getText();
text = text.replace(/^\s+/g, "").replace(/\s+$/g, "");
let selectionSet = false;
for (var i = 0; i < this._searchSections.length; i++) {
let section = this._searchSections[i];
section.resultArea.display.setSearch(text);
let itemCount = section.resultArea.display.getMatchedItemsCount();
let itemCountText = itemCount + "";
section.header.countText.text = itemCountText;
if (this._searchResultsSingleShownSection == section.type) {
this._searchResultsSection.header.setCountText(itemCountText);
if (itemCount == 0) {
section.resultArea.actor.hide();
} else {
section.resultArea.actor.show();
}
} else if (this._searchResultsSingleShownSection == null) {
// Don't show the section if it has no results
if (itemCount == 0) {
section.header.actor.hide();
section.resultArea.actor.hide();
} else {
section.header.actor.show();
section.resultArea.actor.show();
}
}
// Refresh the selection when a new search is applied.
section.resultArea.display.unsetSelected();
if (!selectionSet && section.resultArea.display.hasItems() &&
(this._searchResultsSingleShownSection == null || this._searchResultsSingleShownSection == section.type)) {
section.resultArea.display.selectFirstItem();
selectionSet = true;
}
}
// Here work around a bug that I never quite tracked down
// the root cause of; it appeared that the search results
// section was getting a 0 height allocation.
this._searchResultsSection.content.queue_relayout();
this.searchResults.updateSearch(text);
return false;
},
@ -794,101 +942,6 @@ Dash.prototype = {
}
}));
Main.overview.addPane(pane);
},
_updateDashActors: function() {
if (this._searchPending) {
this._searchResultsSection.actor.show();
// We initially hide all sections when we start a search. When the search timeout
// first runs, the sections that have matching results are shown. As the search
// is refined, only the sections that have matching results will be shown.
for (let i = 0; i < this._searchSections.length; i++) {
let section = this._searchSections[i];
section.header.actor.hide();
section.resultArea.actor.hide();
}
this._appsSection.actor.hide();
this._placesSection.actor.hide();
this._docsSection.actor.hide();
} else if (!this._searchActive) {
this._showAllSearchSections();
this._searchResultsSection.actor.hide();
this._appsSection.actor.show();
this._placesSection.actor.show();
this._docsSection.actor.show();
}
},
_showSingleSearchSection: function(type) {
// We currently don't allow going from showing one section to showing another section.
if (this._searchResultsSingleShownSection != null) {
throw new Error("We were already showing a single search section: '" + this._searchResultsSingleShownSection
+ "' when _showSingleSearchSection() was called for '" + type + "'");
}
for (var i = 0; i < this._searchSections.length; i++) {
let section = this._searchSections[i];
if (section.type == type) {
// This will be the only section shown.
section.resultArea.display.selectFirstItem();
let itemCount = section.resultArea.display.getMatchedItemsCount();
let itemCountText = itemCount + "";
section.header.actor.hide();
this._searchResultsSection.header.setTitle(section.title);
this._searchResultsSection.header.setBackLinkVisible(true);
this._searchResultsSection.header.setCountText(itemCountText);
} else {
// We need to hide this section.
section.header.actor.hide();
section.resultArea.actor.hide();
section.resultArea.display.unsetSelected();
}
}
this._searchResultsSingleShownSection = type;
},
_showAllSearchSections: function() {
if (this._searchResultsSingleShownSection != null) {
let selectionSet = false;
for (var i = 0; i < this._searchSections.length; i++) {
let section = this._searchSections[i];
if (section.type == this._searchResultsSingleShownSection) {
// This will no longer be the only section shown.
let itemCount = section.resultArea.display.getMatchedItemsCount();
if (itemCount != 0) {
section.header.actor.show();
section.resultArea.display.selectFirstItem();
selectionSet = true;
}
this._searchResultsSection.header.setTitle(_("SEARCH RESULTS"));
this._searchResultsSection.header.setBackLinkVisible(false);
this._searchResultsSection.header.setCountText("");
} else {
// We need to restore this section.
let itemCount = section.resultArea.display.getMatchedItemsCount();
if (itemCount != 0) {
section.header.actor.show();
section.resultArea.actor.show();
// This ensures that some other section will have the selection if the
// single section that was being displayed did not have any items.
if (!selectionSet) {
section.resultArea.display.selectFirstItem();
selectionSet = true;
}
}
}
}
this._searchResultsSingleShownSection = null;
}
},
_findAnotherSectionWithItems: function(index, increment) {
let pos = _getIndexWrapped(index, increment, this._searchSections.length);
while (pos != index) {
if (this._searchSections[pos].resultArea.display.hasItems())
return pos;
pos = _getIndexWrapped(pos, increment, this._searchSections.length);
}
return -1;
}
};
Signals.addSignalMethods(Dash.prototype);

View File

@ -10,12 +10,16 @@ const Shell = imports.gi.Shell;
const Signals = imports.signals;
const St = imports.gi.St;
const Mainloop = imports.mainloop;
const Gettext = imports.gettext.domain('gnome-shell');
const _ = Gettext.gettext;
const DocInfo = imports.misc.docInfo;
const DND = imports.ui.dnd;
const GenericDisplay = imports.ui.genericDisplay;
const Main = imports.ui.main;
const Search = imports.ui.search;
const MAX_DASH_DOCS = 50;
const DASH_DOCS_ICON_SIZE = 16;
const DEFAULT_SPACING = 4;
@ -152,7 +156,8 @@ DocDisplay.prototype = {
_refreshCache : function() {
if (!this._docsStale)
return true;
this._allItems = this._docManager.getItems();
this._allItems = {};
Lang.copyProperties(this._docManager.getInfosByUri(), this._allItems);
this._docsStale = false;
return false;
},
@ -177,13 +182,8 @@ DocDisplay.prototype = {
this._matchedItemKeys = [];
let docIdsToRemove = [];
for (docId in this._allItems) {
// this._allItems[docId].exists() checks if the resource still exists
if (this._allItems[docId].exists()) {
this._matchedItems[docId] = 1;
this._matchedItemKeys.push(docId);
} else {
docIdsToRemove.push(docId);
}
this._matchedItems[docId] = 1;
this._matchedItemKeys.push(docId);
}
for (docId in docIdsToRemove) {
@ -275,16 +275,21 @@ DashDocDisplayItem.prototype = {
Main.overview.hide();
}));
this.actor._delegate = this;
this._icon = docInfo.createIcon(DASH_DOCS_ICON_SIZE);
let iconBox = new Big.Box({ y_align: Big.BoxAlignment.CENTER });
iconBox.append(this._icon, Big.BoxPackFlags.NONE);
this.actor.append(iconBox, Big.BoxPackFlags.NONE);
let name = new St.Label({ style_class: "dash-recent-docs-item",
let name = new St.Label({ style_class: 'dash-recent-docs-item',
text: docInfo.name });
this.actor.append(name, Big.BoxPackFlags.EXPAND);
let draggable = DND.makeDraggable(this.actor);
this.actor._delegate = this;
},
getUri: function() {
return this._info.uri;
},
getDragActorSource: function() {
@ -316,12 +321,14 @@ DashDocDisplay.prototype = {
this.actor.connect('get-preferred-width', Lang.bind(this, this._getPreferredWidth));
this.actor.connect('get-preferred-height', Lang.bind(this, this._getPreferredHeight));
this.actor.connect('allocate', Lang.bind(this, this._allocate));
this._workId = Main.initializeDeferredWork(this.actor, Lang.bind(this, this._redisplay));
this._actorsByUri = {};
this._docManager = DocInfo.getDocManager();
this._docManager.connect('changed', Lang.bind(this, function(mgr) {
this._redisplay();
}));
this._redisplay();
this._docManager.connect('changed', Lang.bind(this, this._onDocsChanged));
this._pendingDocsChange = true;
this._checkDocExistence = false;
},
_getPreferredWidth: function(actor, forHeight, alloc) {
@ -355,15 +362,17 @@ DashDocDisplay.prototype = {
let firstColumnChildren = Math.ceil(children.length / 2);
let natural = 0;
for (let i = 0; i < firstColumnChildren; i++) {
let child = children[i];
let [minSize, naturalSize] = child.get_preferred_height(forWidth);
alloc.natural_size += naturalSize;
let [minSize, naturalSize] = child.get_preferred_height(-1);
natural += naturalSize;
if (i > 0 && i < children.length - 1) {
alloc.natural_size += DEFAULT_SPACING;
natural += DEFAULT_SPACING;
}
}
alloc.natural_size = natural;
},
_allocate: function(actor, box, flags) {
@ -418,28 +427,49 @@ DashDocDisplay.prototype = {
i++;
}
// Everything else didn't fit, just hide it.
for (; i < children.length; i++) {
children[i].hide();
if (this._checkDocExistence) {
// Now we know how many docs we are displaying, queue a check to see if any of them
// have been deleted. If they are deleted, then we'll get a 'changed' signal; since
// we'll now be displaying items we weren't previously, we'll check again to see
// if they were deleted, and so forth and so on.
// TODO: We should change this to ask for as many as we can fit in the given space:
// https://bugzilla.gnome.org/show_bug.cgi?id=603522#c23
this._docManager.queueExistenceCheck(i);
this._checkDocExistence = false;
}
let skipPaint = [];
for (; i < children.length; i++)
this.actor.set_skip_paint(children[i], true);
},
_onDocsChanged: function() {
this._checkDocExistence = true;
Main.queueDeferredWork(this._workId);
},
_redisplay: function() {
// Should be kept alive by the _actorsByUri
this.actor.remove_all();
let docs = this._docManager.getItems();
let docUrls = [];
for (let url in docs) {
docUrls.push(url);
let docs = this._docManager.getTimestampOrderedInfos();
for (let i = 0; i < docs.length && i < MAX_DASH_DOCS; i++) {
let doc = docs[i];
let display = this._actorsByUri[doc.uri];
if (display) {
this.actor.add_actor(display.actor);
} else {
let display = new DashDocDisplayItem(doc);
this.actor.add_actor(display.actor);
this._actorsByUri[doc.uri] = display;
}
}
docUrls.sort(function (urlA, urlB) { return docs[urlB].timestamp - docs[urlA].timestamp; });
let textureCache = Shell.TextureCache.get_default();
for (let i = 0; i < docUrls.length; i++) {
let url = docUrls[i];
let docInfo = docs[url];
let display = new DashDocDisplayItem(docInfo);
this.actor.add_actor(display.actor);
// Any unparented actors must have been deleted
for (let uri in this._actorsByUri) {
let display = this._actorsByUri[uri];
if (display.actor.get_parent() == null) {
display.actor.destroy();
delete this._actorsByUri[uri];
}
}
this.emit('changed');
}
@ -447,3 +477,41 @@ DashDocDisplay.prototype = {
Signals.addSignalMethods(DashDocDisplay.prototype);
function DocSearchProvider() {
this._init();
}
DocSearchProvider.prototype = {
__proto__: Search.SearchProvider.prototype,
_init: function(name) {
Search.SearchProvider.prototype._init.call(this, _("DOCUMENTS"));
this._docManager = DocInfo.getDocManager();
},
getResultMeta: function(resultId) {
let docInfo = this._docManager.lookupByUri(resultId);
if (!docInfo)
return null;
return { 'id': resultId,
'name': docInfo.name,
'icon': docInfo.createIcon(Search.RESULT_ICON_SIZE)};
},
activateResult: function(id) {
let docInfo = this._docManager.lookupByUri(id);
docInfo.launch();
},
getInitialResultSet: function(terms) {
return this._docManager.initialSearch(terms);
},
getSubsearchResultSet: function(previousResults, terms) {
return this._docManager.subsearch(previousResults, terms);
},
expandSearch: function(terms) {
log("TODO expand docs search");
}
};

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 DND = imports.ui.dnd;
const Link = imports.ui.link;
const Main = imports.ui.main;
const RedisplayFlags = { NONE: 0,

View File

@ -3,78 +3,21 @@
const Clutter = imports.gi.Clutter;
const Lang = imports.lang;
const Signals = imports.signals;
const St = imports.gi.St;
// Link is a clickable link. Right now it just handles properly capturing
// press and release events and short-circuiting the button handling in
// ClutterText, but more features like different colors for hover/pressed states
// or a different mouse cursor could be implemented.
//
// The properties passed in are forwarded to the Clutter.Text() constructor,
// so can include, 'text', 'font_name', etc.
function Link(props) {
this._init(props);
}
Link.prototype = {
_init : function(props) {
let realProps = { reactive: true };
let realProps = { reactive: true,
style_class: 'shell-link' };
// The user can pass in reactive: false to override the above and get
// a non-reactive link (a link to the current page, perhaps)
Lang.copyProperties(props, realProps);
Lang.copyProperties(props, realProps);
this.actor = new Clutter.Text(realProps);
this.actor._delegate = this;
this.actor.connect('button-press-event', Lang.bind(this, this._onButtonPress));
this.actor.connect('button-release-event', Lang.bind(this, this._onButtonRelease));
this.actor.connect('enter-event', Lang.bind(this, this._onEnter));
this.actor.connect('leave-event', Lang.bind(this, this._onLeave));
this._buttonDown = false;
this._havePointer = false;
},
// Update the text of the link
setText : function(text) {
this.actor.text = text;
},
// We want to react on buttonDown, but if we override button-release-event for
// ClutterText, but not button-press-event, we get a stuck grab. Tracking
// buttonDown and doing the grab isn't really necessary, but doing it makes
// the behavior perfectly correct if the user clicks on one actor, drags
// to another and releases - that should not trigger either actor.
_onButtonPress : function(actor, event) {
this._buttonDown = true;
this._havePointer = true; // Hack to work around poor enter/leave tracking in Clutter
Clutter.grab_pointer(actor);
return true;
},
_onButtonRelease : function(actor, event) {
if (this._buttonDown) {
this._buttonDown = false;
Clutter.ungrab_pointer(actor);
if (this._havePointer)
this.emit('clicked');
}
return true;
},
_onEnter : function(actor, event) {
if (event.get_source() == actor)
this._havePointer = true;
return false;
},
_onLeave : function(actor, event) {
if (event.get_source() == actor)
this._havePointer = false;
return false;
this.actor = new St.Button(realProps);
}
};

View File

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

View File

@ -14,6 +14,7 @@ const St = imports.gi.St;
const Chrome = imports.ui.chrome;
const Environment = imports.ui.environment;
const ExtensionSystem = imports.ui.extensionSystem;
const MessageTray = imports.ui.messageTray;
const Messaging = imports.ui.messaging;
const Overview = imports.ui.overview;
@ -45,6 +46,8 @@ let recorder = null;
let shellDBusService = null;
let modalCount = 0;
let modalActorFocusStack = [];
let _errorLogStack = [];
let _startDate;
function start() {
// Add a binding for "global" in the global JS namespace; (gjs
@ -52,6 +55,12 @@ function start() {
// called "window".)
window.global = Shell.Global.get();
// Now monkey patch utility functions into the global proxy;
// This is easier and faster than indirecting down into global
// if we want to call back up into JS.
global.logError = _logError;
global.log = _logDebug;
Gio.DesktopAppInfo.set_desktop_env("GNOME");
global.grab_dbus_service();
@ -116,6 +125,8 @@ function start() {
notificationPopup = new MessageTray.Notification();
messageTray = new MessageTray.MessageTray();
_startDate = new Date();
global.screen.connect('toggle-recording', function() {
if (recorder == null) {
recorder = new Shell.Recorder({ stage: global.stage });
@ -130,6 +141,9 @@ function start() {
_relayout();
ExtensionSystem.init();
ExtensionSystem.loadExtensions();
panel.startupAnimation();
let display = global.screen.get_display();
@ -138,9 +152,52 @@ function start() {
global.stage.connect('captured-event', _globalKeyPressHandler);
_log('info', 'loaded at ' + _startDate);
Mainloop.idle_add(_removeUnusedWorkspaces);
}
/**
* _log:
* @category: string message type ('info', 'error')
* @msg: A message string
* ...: Any further arguments are converted into JSON notation,
* and appended to the log message, separated by spaces.
*
* Log a message into the LookingGlass error
* stream. This is primarily intended for use by the
* extension system as well as debugging.
*/
function _log(category, msg) {
let text = msg;
if (arguments.length > 2) {
text += ': ';
for (let i = 2; i < arguments.length; i++) {
text += JSON.stringify(arguments[i]);
if (i < arguments.length - 1)
text += " ";
}
}
_errorLogStack.push({timestamp: new Date().getTime(),
category: category,
message: text });
}
function _logError(msg) {
return _log('error', msg);
}
function _logDebug(msg) {
return _log('debug', msg);
}
// Used by the error display in lookingGlass.js
function _getAndClearErrorStack() {
let errors = _errorLogStack;
_errorLogStack = [];
return errors;
}
function _relayout() {
let primary = global.get_primary_monitor();
panel.actor.set_position(primary.x, primary.y);
@ -251,7 +308,7 @@ function _findModal(actor) {
*/
function pushModal(actor) {
if (modalCount == 0) {
if (!global.begin_modal(currentTime())) {
if (!global.begin_modal(global.get_current_time())) {
log("pushModal: invocation of begin_modal failed");
return false;
}
@ -304,7 +361,7 @@ function popModal(actor) {
if (modalCount > 0)
return;
global.end_modal(currentTime());
global.end_modal(global.get_current_time());
global.set_stage_input_mode(Shell.StageInputMode.NORMAL);
}
@ -323,45 +380,6 @@ function getRunDialog() {
return runDialog;
}
function createAppLaunchContext() {
let context = new Gdk.AppLaunchContext();
context.set_timestamp(currentTime());
// Make sure that the app is opened on the current workspace even if
// the user switches before it starts
context.set_desktop(global.screen.get_active_workspace_index());
return context;
}
/**
* currentTime:
*
* Gets the current X server time from the current Clutter, Gdk, or X
* event. If called from outside an event handler, this may return
* %Clutter.CURRENT_TIME (aka 0), or it may return a slightly
* out-of-date timestamp.
*/
function currentTime() {
// meta_display_get_current_time() will return the correct time
// when handling an X or Gdk event, but will return CurrentTime
// from some Clutter event callbacks.
//
// clutter_get_current_event_time() will return the correct time
// from a Clutter event callback, but may return an out-of-date
// timestamp if called at other times.
//
// So we try meta_display_get_current_time() first, since we
// can recognize a "wrong" answer from that, and then fall back
// to clutter_get_current_event_time().
let time = global.screen.get_display().get_current_time();
if (time != Clutter.CURRENT_TIME)
return time;
return Clutter.get_current_event_time();
}
/**
* activateWindow:
* @window: the Meta.Window to activate
@ -374,7 +392,7 @@ function activateWindow(window, time) {
let windowWorkspaceNum = window.get_workspace().index();
if (!time)
time = currentTime();
time = global.get_current_time();
if (windowWorkspaceNum != activeWorkspaceNum) {
let workspace = global.screen.get_workspace_by_index(windowWorkspaceNum);
@ -383,3 +401,120 @@ function activateWindow(window, time) {
window.activate(time);
}
}
// TODO - replace this timeout with some system to guess when the user might
// be e.g. just reading the screen and not likely to interact.
const DEFERRED_TIMEOUT_SECONDS = 20;
var _deferredWorkData = {};
// Work scheduled for some point in the future
var _deferredWorkQueue = [];
// Work we need to process before the next redraw
var _beforeRedrawQueue = [];
// Counter to assign work ids
var _deferredWorkSequence = 0;
var _deferredTimeoutId = 0;
function _runDeferredWork(workId) {
if (!_deferredWorkData[workId])
return;
let index = _deferredWorkQueue.indexOf(workId);
if (index < 0)
return;
_deferredWorkQueue.splice(index, 1);
_deferredWorkData[workId].callback();
if (_deferredWorkQueue.length == 0 && _deferredTimeoutId > 0) {
Mainloop.source_remove(_deferredTimeoutId);
_deferredTimeoutId = 0;
}
}
function _runAllDeferredWork() {
while (_deferredWorkQueue.length > 0)
_runDeferredWork(_deferredWorkQueue[0]);
}
function _runBeforeRedrawQueue() {
for (let i = 0; i < _beforeRedrawQueue.length; i++) {
let workId = _beforeRedrawQueue[i];
_runDeferredWork(workId);
}
_beforeRedrawQueue = [];
}
function _queueBeforeRedraw(workId) {
_beforeRedrawQueue.push(workId);
if (_beforeRedrawQueue.length == 1) {
Meta.later_add(Meta.LaterType.BEFORE_REDRAW, function () {
_runBeforeRedrawQueue();
return false;
}, null);
}
}
/**
* initializeDeferredWork:
* @actor: A #ClutterActor
* @callback: Function to invoke to perform work
*
* This function sets up a callback to be invoked when either the
* given actor is mapped, or after some period of time when the machine
* is idle. This is useful if your actor isn't always visible on the
* screen (for example, all actors in the overview), and you don't want
* to consume resources updating if the actor isn't actually going to be
* displaying to the user.
*
* Note that queueDeferredWork is called by default immediately on
* initialization as well, under the assumption that new actors
* will need it.
*
* Returns: A string work identifer
*/
function initializeDeferredWork(actor, callback, props) {
// Turn into a string so we can use as an object property
let workId = "" + (++_deferredWorkSequence);
_deferredWorkData[workId] = { 'actor': actor,
'callback': callback };
actor.connect('notify::mapped', function () {
if (!(actor.mapped && _deferredWorkQueue.indexOf(workId) >= 0))
return;
_queueBeforeRedraw(workId);
});
actor.connect('destroy', function() {
let index = _deferredWorkQueue.indexOf(workId);
if (index >= 0)
_deferredWorkQueue.splice(index, 1);
delete _deferredWorkData[workId];
});
queueDeferredWork(workId);
return workId;
}
/**
* queueDeferredWork:
* @workId: work identifier
*
* Ensure that the work identified by @workId will be
* run on map or timeout. You should call this function
* for example when data being displayed by the actor has
* changed.
*/
function queueDeferredWork(workId) {
let data = _deferredWorkData[workId];
if (!data) {
global.logError("invalid work id ", workId);
return;
}
if (_deferredWorkQueue.indexOf(workId) < 0)
_deferredWorkQueue.push(workId);
if (data.actor.mapped) {
_queueBeforeRedraw(workId);
return;
} else if (_deferredTimeoutId == 0) {
_deferredTimeoutId = Mainloop.timeout_add_seconds(DEFERRED_TIMEOUT_SECONDS, function () {
_runAllDeferredWork();
_deferredTimeoutId = 0;
return false;
});
}
}

View File

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

View File

@ -15,7 +15,7 @@ const _ = Gettext.gettext;
const DND = imports.ui.dnd;
const Main = imports.ui.main;
const GenericDisplay = imports.ui.genericDisplay;
const Search = imports.ui.search;
const NAUTILUS_PREFS_DIR = '/apps/nautilus/preferences';
const DESKTOP_IS_HOME_KEY = NAUTILUS_PREFS_DIR + '/desktop_is_home_dir';
@ -30,19 +30,78 @@ const PLACES_ICON_SIZE = 16;
* @iconFactory: A JavaScript callback which will create an icon texture given a size parameter
* @launch: A JavaScript callback to launch the entry
*/
function PlaceInfo(name, iconFactory, launch) {
this._init(name, iconFactory, launch);
function PlaceInfo(id, name, iconFactory, launch) {
this._init(id, name, iconFactory, launch);
}
PlaceInfo.prototype = {
_init: function(name, iconFactory, launch) {
_init: function(id, name, iconFactory, launch) {
this.id = id;
this.name = name;
this._lowerName = name.toLowerCase();
this.iconFactory = iconFactory;
this.launch = launch;
this.id = null;
},
matchTerms: function(terms) {
let mtype = Search.MatchType.NONE;
for (let i = 0; i < terms.length; i++) {
let term = terms[i];
let idx = this._lowerName.indexOf(term);
if (idx == 0)
return Search.MatchType.PREFIX;
else if (idx > 0)
mtype = Search.MatchType.SUBSTRING;
}
return mtype;
},
isRemovable: function() {
return false;
}
}
function PlaceDeviceInfo(mount) {
this._init(mount);
}
PlaceDeviceInfo.prototype = {
__proto__: PlaceInfo.prototype,
_init: function(mount) {
this._mount = mount;
this.name = mount.get_name();
this._lowerName = this.name.toLowerCase();
this.id = "mount:" + mount.get_root().get_uri();
},
iconFactory: function(size) {
let icon = this._mount.get_icon();
return Shell.TextureCache.get_default().load_gicon(icon, size);
},
launch: function() {
Gio.app_info_launch_default_for_uri(this._mount.get_root().get_uri(),
global.create_app_launch_context());
},
isRemovable: function() {
return this._mount.can_unmount();
},
remove: function() {
if (!this.isRemovable())
return;
this._mount.unmount(0, null, Lang.bind(this, this._removeFinish), null);
},
_removeFinish: function(o, res, data) {
this._mount.unmount_finish(res);
}
}
function PlacesManager() {
this._init();
}
@ -52,6 +111,7 @@ PlacesManager.prototype = {
let gconf = Shell.GConf.get_default();
gconf.watch_directory(NAUTILUS_PREFS_DIR);
this._defaultPlaces = [];
this._mounts = [];
this._bookmarks = [];
this._isDesktopHome = false;
@ -60,12 +120,12 @@ PlacesManager.prototype = {
let homeUri = homeFile.get_uri();
let homeLabel = Shell.util_get_label_for_uri (homeUri);
let homeIcon = Shell.util_get_icon_for_uri (homeUri);
this._home = new PlaceInfo(homeLabel,
this._home = new PlaceInfo('special:home', homeLabel,
function(size) {
return Shell.TextureCache.get_default().load_gicon(homeIcon, size);
},
function() {
Gio.app_info_launch_default_for_uri(homeUri, Main.createAppLaunchContext());
Gio.app_info_launch_default_for_uri(homeUri, global.create_app_launch_context());
});
let desktopPath = GLib.get_user_special_dir(GLib.UserDirectory.DIRECTORY_DESKTOP);
@ -73,15 +133,15 @@ PlacesManager.prototype = {
let desktopUri = desktopFile.get_uri();
let desktopLabel = Shell.util_get_label_for_uri (desktopUri);
let desktopIcon = Shell.util_get_icon_for_uri (desktopUri);
this._desktopMenu = new PlaceInfo(desktopLabel,
this._desktopMenu = new PlaceInfo('special:desktop', desktopLabel,
function(size) {
return Shell.TextureCache.get_default().load_gicon(desktopIcon, size);
},
function() {
Gio.app_info_launch_default_for_uri(desktopUri, Main.createAppLaunchContext());
Gio.app_info_launch_default_for_uri(desktopUri, global.create_app_launch_context());
});
this._connect = new PlaceInfo(_("Connect to..."),
this._connect = new PlaceInfo('special:connect', _("Connect to..."),
function (size) {
return Shell.TextureCache.get_default().load_icon_name("applications-internet", size);
},
@ -101,7 +161,7 @@ PlacesManager.prototype = {
}
if (networkApp != null) {
this._network = new PlaceInfo(networkApp.get_name(),
this._network = new PlaceInfo('special:network', networkApp.get_name(),
function(size) {
return networkApp.create_icon_texture(size);
},
@ -110,6 +170,16 @@ PlacesManager.prototype = {
});
}
this._defaultPlaces.push(this._home);
if (!this._isDesktopHome)
this._defaultPlaces.push(this._desktopMenu);
if (this._network)
this._defaultPlaces.push(this._network);
this._defaultPlaces.push(this._connect);
/*
* Show devices, code more or less ported from nautilus-places-sidebar.c
*/
@ -238,12 +308,12 @@ PlacesManager.prototype = {
continue;
let icon = Shell.util_get_icon_for_uri(bookmark);
let item = new PlaceInfo(label,
let item = new PlaceInfo('bookmark:' + bookmark, label,
function(size) {
return Shell.TextureCache.get_default().load_gicon(icon, size);
},
function() {
Gio.app_info_launch_default_for_uri(bookmark, Main.createAppLaunchContext());
Gio.app_info_launch_default_for_uri(bookmark, global.create_app_launch_context());
});
this._bookmarks.push(item);
}
@ -263,17 +333,7 @@ PlacesManager.prototype = {
},
_addMount: function(mount) {
let mountLabel = mount.get_name();
let mountIcon = mount.get_icon();
let root = mount.get_root();
let mountUri = root.get_uri();
let devItem = new PlaceInfo(mountLabel,
function(size) {
return Shell.TextureCache.get_default().load_gicon(mountIcon, size);
},
function() {
Gio.app_info_launch_default_for_uri(mountUri, Main.createAppLaunchContext());
});
let devItem = new PlaceDeviceInfo(mount);
this._mounts.push(devItem);
},
@ -282,16 +342,7 @@ PlacesManager.prototype = {
},
getDefaultPlaces: function () {
let places = [this._home];
if (!this._isDesktopHome)
places.push(this._desktopMenu);
if (this._network)
places.push(this._network);
places.push(this._connect);
return places;
return this._defaultPlaces;
},
getBookmarks: function () {
@ -300,6 +351,28 @@ PlacesManager.prototype = {
getMounts: function () {
return this._mounts;
},
_lookupById: function(sourceArray, id) {
for (let i = 0; i < sourceArray.length; i++) {
let place = sourceArray[i];
if (place.id == id)
return place;
}
return null;
},
lookupPlaceById: function(id) {
let colonIdx = id.indexOf(':');
let type = id.substring(0, colonIdx);
let sourceArray = null;
if (type == 'special')
sourceArray = this._defaultPlaces;
else if (type == 'mount')
sourceArray = this._mounts;
else if (type == 'bookmark')
sourceArray = this._bookmarks;
return this._lookupById(sourceArray, id);
}
};
@ -319,23 +392,37 @@ DashPlaceDisplayItem.prototype = {
this._info = info;
this._icon = info.iconFactory(PLACES_ICON_SIZE);
this.actor = new Big.Box({ orientation: Big.BoxOrientation.HORIZONTAL,
reactive: true,
spacing: 4 });
this.actor.connect('button-release-event', Lang.bind(this, function (b, e) {
this._info.launch();
Main.overview.hide();
}));
let text = new St.Label({ style_class: 'places-item',
text: info.name });
let iconBox = new Big.Box({ y_align: Big.BoxAlignment.CENTER });
iconBox.append(this._icon, Big.BoxPackFlags.NONE);
let text = new St.Button({ style_class: 'places-item',
label: info.name,
x_align: St.Align.START });
text.connect('clicked', Lang.bind(this, this._onClicked));
let iconBox = new St.Bin({ child: this._icon, reactive: true });
iconBox.connect('button-release-event',
Lang.bind(this, this._onClicked));
this.actor.append(iconBox, Big.BoxPackFlags.NONE);
this.actor.append(text, Big.BoxPackFlags.EXPAND);
if (info.isRemovable()) {
let removeIcon = Shell.TextureCache.get_default().load_icon_name ('media-eject', PLACES_ICON_SIZE);
let removeIconBox = new St.Button({ child: removeIcon,
reactive: true });
this.actor.append(removeIconBox, Big.BoxPackFlags.NONE);
removeIconBox.connect('clicked',
Lang.bind(this, function() {
this._info.remove();
}));
}
this.actor._delegate = this;
let draggable = DND.makeDraggable(this.actor);
},
_onClicked: function(b) {
this._info.launch();
Main.overview.hide();
},
getDragActorSource: function() {
return this._icon;
},
@ -421,120 +508,67 @@ DashPlaceDisplay.prototype = {
Signals.addSignalMethods(DashPlaceDisplay.prototype);
function PlaceDisplayItem(placeInfo) {
this._init(placeInfo);
function PlaceSearchProvider() {
this._init();
}
PlaceDisplayItem.prototype = {
__proto__: GenericDisplay.GenericDisplayItem.prototype,
PlaceSearchProvider.prototype = {
__proto__: Search.SearchProvider.prototype,
_init : function(placeInfo) {
GenericDisplay.GenericDisplayItem.prototype._init.call(this);
this._info = placeInfo;
this._setItemInfo(placeInfo.name, '');
_init: function() {
Search.SearchProvider.prototype._init.call(this, _("PLACES"));
},
//// Public method overrides ////
// Opens an application represented by this display item.
launch : function() {
this._info.launch();
getResultMeta: function(resultId) {
let placeInfo = Main.placesManager.lookupPlaceById(resultId);
if (!placeInfo)
return null;
return { 'id': resultId,
'name': placeInfo.name,
'icon': placeInfo.iconFactory(Search.RESULT_ICON_SIZE) };
},
shellWorkspaceLaunch: function() {
this._info.launch();
activateResult: function(id) {
let placeInfo = Main.placesManager.lookupPlaceById(id);
placeInfo.launch();
},
//// Protected method overrides ////
// Returns an icon for the item.
_createIcon: function() {
return this._info.iconFactory(GenericDisplay.ITEM_DISPLAY_ICON_SIZE);
_compareResultMeta: function (idA, idB) {
let infoA = Main.placesManager.lookupPlaceById(idA);
let infoB = Main.placesManager.lookupPlaceById(idB);
return infoA.name.localeCompare(infoB.name);
},
// Returns a preview icon for the item.
_createPreviewIcon: function() {
return this._info.iconFactory(GenericDisplay.PREVIEW_ICON_SIZE);
_searchPlaces: function(places, terms) {
let multipleResults = [];
let prefixResults = [];
let substringResults = [];
terms = terms.map(String.toLowerCase);
for (let i = 0; i < places.length; i++) {
let place = places[i];
let mtype = place.matchTerms(terms);
if (mtype == Search.MatchType.MULTIPLE)
multipleResults.push(place.id);
else if (mtype == Search.MatchType.PREFIX)
prefixResults.push(place.id);
else if (mtype == Search.MatchType.SUBSTRING)
substringResults.push(place.id);
}
multipleResults.sort(this._compareResultMeta);
prefixResults.sort(this._compareResultMeta);
substringResults.sort(this._compareResultMeta);
return multipleResults.concat(prefixResults.concat(substringResults));
},
getInitialResultSet: function(terms) {
let places = Main.placesManager.getAllPlaces();
return this._searchPlaces(places, terms);
},
getSubsearchResultSet: function(previousResults, terms) {
let places = previousResults.map(function (id) { return Main.placesManager.lookupPlaceById(id); });
return this._searchPlaces(places, terms);
}
};
function PlaceDisplay(flags) {
this._init(flags);
}
PlaceDisplay.prototype = {
__proto__: GenericDisplay.GenericDisplay.prototype,
_init: function(flags) {
GenericDisplay.GenericDisplay.prototype._init.call(this, flags);
this._stale = true;
Main.placesManager.connect('places-updated', Lang.bind(this, function (e) {
this._stale = true;
}));
},
//// Protected method overrides ////
_refreshCache: function () {
if (!this._stale)
return true;
this._allItems = {};
let array = Main.placesManager.getAllPlaces();
for (let i = 0; i < array.length; i ++) {
// We are using an array id as placeInfo id because placeInfo doesn't have any
// other information piece that can be used as a unique id. There are different
// types of placeInfo, such as devices and directories that would result in differently
// structured ids. Also the home directory can show up in both the default places and in
// bookmarks which means its URI can't be used as a unique id. (This does mean it can
// appear twice in search results, though that doesn't happen at the moment because we
// name it "Home Folder" in default places and it's named with the user's system name
// if it appears as a bookmark.)
let placeInfo = array[i];
placeInfo.id = i;
this._allItems[i] = placeInfo;
}
this._stale = false;
return false;
},
// Sets the list of the displayed items.
_setDefaultList: function() {
this._matchedItems = {};
this._matchedItemKeys = [];
for (id in this._allItems) {
this._matchedItems[id] = 1;
this._matchedItemKeys.push(id);
}
this._matchedItemKeys.sort(Lang.bind(this, this._compareItems));
},
// Checks if the item info can be a match for the search string by checking
// the name of the place. Item info is expected to be PlaceInfo.
// Returns a boolean flag indicating if itemInfo is a match.
_isInfoMatching: function(itemInfo, search) {
if (search == null || search == '')
return true;
let name = itemInfo.name.toLowerCase();
if (name.indexOf(search) >= 0)
return true;
return false;
},
// Compares items associated with the item ids based on the alphabetical order
// of the item names.
// Returns an integer value indicating the result of the comparison.
_compareItems: function(itemIdA, itemIdB) {
let placeA = this._allItems[itemIdA];
let placeB = this._allItems[itemIdB];
return placeA.name.localeCompare(placeB.name);
},
// Creates a PlaceDisplayItem based on itemInfo, which is expected to be a PlaceInfo object.
_createDisplayItem: function(itemInfo) {
return new PlaceDisplayItem(itemInfo);
}
};

View File

@ -2,6 +2,7 @@
const Big = imports.gi.Big;
const Clutter = imports.gi.Clutter;
const Gio = imports.gi.Gio;
const GLib = imports.gi.GLib;
const Lang = imports.lang;
const Mainloop = imports.mainloop;
@ -24,6 +25,144 @@ const DIALOG_WIDTH = 320;
const DIALOG_PADDING = 6;
const ICON_SIZE = 24;
const ICON_BOX_SIZE = 36;
const MAX_FILE_DELETED_BEFORE_INVALID = 10;
function CommandCompleter() {
this._init();
}
CommandCompleter.prototype = {
_init : function() {
this._changedCount = 0;
this._paths = GLib.getenv('PATH').split(':');
this._valid = false;
this._updateInProgress = false;
this._childs = new Array(this._paths.length);
this._monitors = new Array(this._paths.length);
for (let i = 0; i < this._paths.length; i++) {
this._childs[i] = [];
let file = Gio.file_new_for_path(this._paths[i]);
let info = file.query_info(Gio.FILE_ATTRIBUTE_STANDARD_TYPE, Gio.FileQueryInfoFlags.NONE, null);
if (info.get_attribute_uint32(Gio.FILE_ATTRIBUTE_STANDARD_TYPE) != Gio.FileType.DIRECTORY)
continue;
this._paths[i] = file.get_path();
this._monitors[i] = file.monitor_directory(Gio.FileMonitorFlags.NONE, null);
if (this._monitors[i] != null) {
this._monitors[i].connect("changed", Lang.bind(this, this._onChanged));
}
}
this._update(0);
},
_onGetEnumerateComplete : function(obj, res) {
this._enumerator = obj.enumerate_children_finish(res);
this._enumerator.next_files_async(100, GLib.PRIORITY_LOW, null, Lang.bind(this, this._onNextFileComplete), null);
},
_onNextFileComplete : function(obj, res) {
let files = obj.next_files_finish(res);
for (let i = 0; i < files.length; i++) {
this._childs[this._i].push(files[i].get_name());
}
if (files.length) {
this._enumerator.next_files_async(100, GLib.PRIORITY_LOW, null, Lang.bind(this, this._onNextFileComplete), null);
} else {
this._enumerator.close(null);
this._enumerator = null;
this._update(this._i + 1);
}
},
update : function() {
if (this._valid)
return;
this._update(0);
},
_update : function(i) {
if (i == 0 && this._updateInProgress)
return;
this._updateInProgress = true;
this._changedCount = 0;
this._i = i;
if (i >= this._paths.length) {
this._valid = true;
this._updateInProgress = false;
return;
}
let file = Gio.file_new_for_path(this._paths[i]);
this._childs[this._i] = [];
file.enumerate_children_async(Gio.FILE_ATTRIBUTE_STANDARD_NAME, Gio.FileQueryInfoFlags.NONE, GLib.PRIORITY_LOW, null, Lang.bind(this, this._onGetEnumerateComplete), null);
},
_onChanged : function(m, f, of, type) {
if (!this._valid)
return;
let path = f.get_parent().get_path();
let k = undefined;
for (let i = 0; i < this._paths.length; i++) {
if (this._paths[i] == path)
k = i;
}
if (k === undefined) {
return;
}
if (type == Gio.FileMonitorEvent.CREATED) {
this._childs[k].push(f.get_basename());
}
if (type == Gio.FileMonitorEvent.DELETED) {
this._changedCount++;
if (this._changedCount > MAX_FILE_DELETED_BEFORE_INVALID) {
this._valid = false;
}
let name = f.get_basename();
this._childs[k] = this._childs[k].filter(function(e) {
return e != name;
});
}
if (type == Gio.FileMonitorEvent.UNMOUNTED) {
this._childs[k] = [];
}
},
getCompletion: function(text) {
let common = "";
let notInit = true;
if (!this._valid) {
this._update(0);
return common;
}
function _getCommon(s1, s2) {
let k = 0;
for (; k < s1.length && k < s2.length; k++) {
if (s1[k] != s2[k])
break;
}
if (k == 0)
return "";
return s1.substr(0, k);
}
function _hasPrefix(s1, prefix) {
return s1.indexOf(prefix) == 0;
}
for (let i = 0; i < this._childs.length; i++) {
for (let k = 0; k < this._childs[i].length; k++) {
if (!_hasPrefix(this._childs[i][k], text))
continue;
if (notInit) {
common = this._childs[i][k];
notInit = false;
}
common = _getCommon(common, this._childs[i][k]);
}
}
if (common.length)
return common.substr(text.length);
return common;
}
};
function RunDialog() {
this._init();
@ -137,16 +276,55 @@ RunDialog.prototype = {
this.close();
}));
this._pathCompleter = new Gio.FilenameCompleter();
this._commandCompleter = new CommandCompleter();
this._group.connect('notify::visible', Lang.bind(this._commandCompleter, this._commandCompleter.update));
this._entry.connect('key-press-event', Lang.bind(this, function(o, e) {
let symbol = e.get_key_symbol();
if (symbol == Clutter.Escape) {
this.close();
return true;
}
if (symbol == Clutter.slash) {
// Need preload data before get completion. GFilenameCompleter load content of parent directory.
// Parent directory for /usr/include/ is /usr/. So need to add fake name('a').
let text = o.get_text().concat('/a');
let prefix;
if (text.lastIndexOf(' ') == -1)
prefix = text;
else
prefix = text.substr(text.lastIndexOf(' ') + 1);
this._getCompletion(prefix);
return false;
}
if (symbol == Clutter.Tab) {
let text = o.get_text();
let prefix;
if (text.lastIndexOf(' ') == -1)
prefix = text;
else
prefix = text.substr(text.lastIndexOf(' ') + 1);
let postfix = this._getCompletion(prefix);
if (postfix != null && postfix.length > 0) {
o.insert_text(postfix, -1);
o.set_cursor_position(text.length + postfix.length);
if (postfix[postfix.length - 1] == '/')
this._getCompletion(text + postfix + 'a');
}
return true;
}
return false;
}));
},
_getCompletion : function(text) {
if (text.indexOf('/') != -1) {
return this._pathCompleter.get_completion_suffix(text);
} else {
return this._commandCompleter.getCompletion(text);
}
},
_run : function(command) {
this._commandError = false;
let f;

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));
this._gconf.connect('changed::sidebar/visible',
Lang.bind(this, this._visibleChanged));
this._adjustPosition();
},
addWidget: function(widget) {
@ -82,6 +84,14 @@ Sidebar.prototype = {
this.box.append(widgetBox.actor, Big.BoxPackFlags.NONE);
this._widgets.push(widgetBox);
this._adjustPosition();
},
_adjustPosition: function() {
let primary=global.get_primary_monitor();
this.actor.y = Math.max(primary.y + Panel.PANEL_HEIGHT,primary.height/2 - this.actor.height/2);
this.actor.x = primary.x;
},
_visibleChanged: function() {

View File

@ -354,8 +354,7 @@ RecentDocsWidget.prototype = {
for (i = 0; i < docs.length; i++) {
let docInfo = new DocInfo.DocInfo (docs[i]);
if (docInfo.exists())
items.push(docInfo);
items.push(docInfo);
}
items.sort(function (a,b) { return b.timestamp - a.timestamp; });

View File

@ -337,8 +337,6 @@ WindowOverlay.prototype = {
button._overlap = 0;
windowClone.actor.connect('destroy', Lang.bind(this, this._onDestroy));
windowClone.actor.connect('notify::allocation',
Lang.bind(this, this._positionItems));
windowClone.actor.connect('enter-event',
Lang.bind(this, this._onEnter));
windowClone.actor.connect('leave-event',
@ -395,23 +393,30 @@ WindowOverlay.prototype = {
this.title.height + this.title._spacing;
},
_positionItems: function(win) {
/**
* @cloneX: x position of windowClone
* @cloneY: y position of windowClone
* @cloneWidth: width of windowClone
* @cloneHeight height of windowClone
*/
// These parameters are not the values retrieved with
// get_transformed_position() and get_transformed_size(),
// as windowClone might be moving.
// See Workspace._fadeInWindowOverlay
updatePositions: function(cloneX, cloneY, cloneWidth, cloneHeight) {
let button = this.closeButton;
let title = this.title;
let [x, y] = win.get_transformed_position();
let [w, h] = win.get_transformed_size();
let buttonX = x + w - button._overlap;
let buttonY = y - button.height + button._overlap;
let buttonX = cloneX + cloneWidth - button._overlap;
let buttonY = cloneY - button.height + button._overlap;
button.set_position(Math.floor(buttonX), Math.floor(buttonY));
if (!title.fullWidth)
title.fullWidth = title.width;
title.width = Math.min(title.fullWidth, w);
title.width = Math.min(title.fullWidth, cloneWidth);
let titleX = x + (w - title.width) / 2;
let titleY = y + h + title._spacing;
let titleX = cloneX + (cloneWidth - title.width) / 2;
let titleY = cloneY + cloneHeight + title._spacing;
title.set_position(Math.floor(titleX), Math.floor(titleY));
},
@ -496,8 +501,8 @@ WindowOverlay.prototype = {
let closeNode = this.closeButton.get_theme_node();
let [success, len] = closeNode.get_length('-shell-close-overlap',
false);
[success, len] = closeNode.get_length('-shell-close-overlap',
false);
if (success)
this.closeButton._overlap = len;
@ -1034,7 +1039,7 @@ Workspace.prototype = {
time: Overview.ANIMATION_TIME,
transition: "easeOutQuad",
onComplete: Lang.bind(this, function() {
overlay.fadeIn();
this._fadeInWindowOverlay(clone, overlay);
})
});
}
@ -1058,13 +1063,31 @@ Workspace.prototype = {
}
},
_fadeInWindowOverlay: function(clone, overlay) {
// This is a little messy and complicated because when we
// start the fade-in we may not have done the final positioning
// of the workspaces. (Tweener doesn't necessarily finish
// all animations before calling onComplete callbacks.)
// So we need to manually compute where the window will
// be after the workspace animation finishes.
let [cloneX, cloneY] = clone.actor.get_position();
let [cloneWidth, cloneHeight] = clone.actor.get_size();
cloneX = this.gridX + this.scale * cloneX;
cloneY = this.gridY + this.scale * cloneY;
cloneWidth = this.scale * clone.actor.scale_x * cloneWidth;
cloneHeight = this.scale * clone.actor.scale_y * cloneHeight;
overlay.updatePositions(cloneX, cloneY, cloneWidth, cloneHeight);
overlay.fadeIn();
},
_fadeInAllOverlays: function() {
for (let i = 1; i < this._windows.length; i++) {
let clone = this._windows[i];
let overlay = this._windowOverlays[i];
if (this._showOnlyWindows != null && !(clone.metaWindow in this._showOnlyWindows))
continue;
overlay.fadeIn();
this._fadeInWindowOverlay(clone, overlay);
}
},

View File

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

View File

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

181
po/es.po
View File

@ -8,8 +8,8 @@ msgstr ""
"Project-Id-Version: gnome-shell master\n"
"Report-Msgid-Bugs-To: http://bugzilla.gnome.org/enter_bug.cgi?product=gnome-"
"shell&component=general\n"
"POT-Creation-Date: 2009-10-27 20:37+0000\n"
"PO-Revision-Date: 2009-10-27 23:32+0100\n"
"POT-Creation-Date: 2009-12-18 15:06+0000\n"
"PO-Revision-Date: 2009-12-19 14:41+0100\n"
"Last-Translator: Jorge González <jorgegonz@svn.gnome.org>\n"
"Language-Team: Español <gnome-es-list@gnome.org>\n"
"MIME-Version: 1.0\n"
@ -25,76 +25,64 @@ msgstr "GNOME Shell"
msgid "Window management and application launching"
msgstr "Gestión de ventanas e inicio de aplicaciones"
#: ../js/ui/appDisplay.js:332
msgid "Frequent"
msgstr "Frecuentes"
#. **** Applications ****
#: ../js/ui/appDisplay.js:252 ../js/ui/dash.js:852
msgid "APPLICATIONS"
msgstr "APLICACIONES"
#: ../js/ui/appDisplay.js:867
msgid "Drag here to add favorites"
msgstr "Arrastrar aquí para añadir a los favoritos"
#: ../js/ui/appDisplay.js:276
msgid "PREFERENCES"
msgstr "PREFERENCIAS"
#: ../js/ui/appIcon.js:426
#: ../js/ui/appDisplay.js:707 ../js/ui/appIcon.js:425
msgid "New Window"
msgstr "Ventana nueva"
#: ../js/ui/appIcon.js:430
#: ../js/ui/appDisplay.js:711 ../js/ui/appIcon.js:429
msgid "Remove from Favorites"
msgstr "Quitar de los favoritos"
#: ../js/ui/appIcon.js:431
#: ../js/ui/appDisplay.js:712 ../js/ui/appIcon.js:430
msgid "Add to Favorites"
msgstr "Añadir a los favoritos"
#: ../js/ui/dash.js:283
#: ../js/ui/appDisplay.js:1064
msgid "Drag here to add favorites"
msgstr "Arrastrar aquí para añadir a los favoritos"
#: ../js/ui/dash.js:240
msgid "Find..."
msgstr "Buscar…"
#: ../js/ui/dash.js:400
msgid "More"
msgstr "Más"
#: ../js/ui/dash.js:543
msgid "(see all)"
msgstr "(ver todo)"
#. **** Applications ****
#: ../js/ui/dash.js:725 ../js/ui/dash.js:787
msgid "APPLICATIONS"
msgstr "APLICACIONES"
#: ../js/ui/dash.js:437
#| msgid "Search"
msgid "Searching..."
msgstr "Buscando…"
#. **** Places ****
#. Translators: This is in the sense of locations for documents,
#. network locations, etc.
#: ../js/ui/dash.js:745
#: ../js/ui/dash.js:872 ../js/ui/placeDisplay.js:471
msgid "PLACES"
msgstr "LUGARES"
#. **** Documents ****
#: ../js/ui/dash.js:752 ../js/ui/dash.js:797
#: ../js/ui/dash.js:879
msgid "RECENT DOCUMENTS"
msgstr "DOCUMENTOS RECIENTES"
#. **** Search Results ****
#: ../js/ui/dash.js:777 ../js/ui/dash.js:961
msgid "SEARCH RESULTS"
msgstr "RESULTADOS DE LA BÚSQUEDA"
#: ../js/ui/dash.js:792
msgid "PREFERENCES"
msgstr "PREFERENCIAS"
#. Button on the left side of the panel.
#. Translators: If there is no suitable word for "Activities" in your language, you can use the word for "Overview".
#: ../js/ui/panel.js:274
#: ../js/ui/panel.js:227
msgid "Activities"
msgstr "Actividades"
#. Translators: This is a time format.
#: ../js/ui/panel.js:491
#: ../js/ui/panel.js:440
msgid "%a %l:%M %p"
msgstr "%a %H:%M"
#: ../js/ui/places.js:178
#: ../js/ui/placeDisplay.js:99
msgid "Connect to..."
msgstr "Conectar a…"
@ -120,101 +108,49 @@ msgstr "Aplicaciones"
msgid "Recent Documents"
msgstr "Documentos recientes"
#: ../src/shell-global.c:812
#: ../src/shell-global.c:826
msgid "Less than a minute ago"
msgstr "Hace menos de un minuto"
#: ../src/shell-global.c:815
#: ../src/shell-global.c:829
#, c-format
msgid "%d minute ago"
msgid_plural "%d minutes ago"
msgstr[0] "Hace %d minuto"
msgstr[1] "Hace %d minutos"
#: ../src/shell-global.c:818
#: ../src/shell-global.c:832
#, c-format
msgid "%d hour ago"
msgid_plural "%d hours ago"
msgstr[0] "Hace %d hora"
msgstr[1] "Hace %d horas"
#: ../src/shell-global.c:821
#: ../src/shell-global.c:835
#, c-format
msgid "%d day ago"
msgid_plural "%d days ago"
msgstr[0] "Hace %d día"
msgstr[1] "Hace %d días"
#: ../src/shell-global.c:824
#: ../src/shell-global.c:838
#, c-format
msgid "%d week ago"
msgid_plural "%d weeks ago"
msgstr[0] "Hace %d semana"
msgstr[1] "Hace %d semanas"
#: ../src/shell-status-menu.c:156
msgid "Unknown"
msgstr "Desconocido"
#: ../src/shell-status-menu.c:212
#, c-format
msgid "Can't lock screen: %s"
msgstr "No se puede bloquear la pantalla: %s"
#: ../src/shell-status-menu.c:227
#, c-format
msgid "Can't temporarily set screensaver to blank screen: %s"
msgstr ""
"No se puede establecer temporalmente el salvapantallas a oscurecer pantalla: "
"%s"
#: ../src/shell-status-menu.c:351
#, c-format
msgid "Can't logout: %s"
msgstr "No se puede salir de la sesión: %s"
#: ../src/shell-status-menu.c:492
msgid "Account Information..."
msgstr "Información de la cuenta…"
#: ../src/shell-status-menu.c:502
msgid "Sidebar"
msgstr "Barra lateral"
#: ../src/shell-status-menu.c:510
msgid "System Preferences..."
msgstr "Preferencias del sistema…"
#: ../src/shell-status-menu.c:525
msgid "Lock Screen"
msgstr "Bloquear la pantalla"
#: ../src/shell-status-menu.c:535
msgid "Switch User"
msgstr "Cambiar de usuario"
#. Only show switch user if there are other users
#. Log Out
#: ../src/shell-status-menu.c:546
msgid "Log Out..."
msgstr "Salir…"
#. Shut down
#: ../src/shell-status-menu.c:557
msgid "Shut Down..."
msgstr "Apagar…"
#: ../src/shell-uri-util.c:87
#: ../src/shell-uri-util.c:89
msgid "Home Folder"
msgstr "Carpeta personal"
#. Translators: this is the same string as the one found in
#. * nautilus
#: ../src/shell-uri-util.c:102
#: ../src/shell-uri-util.c:104
msgid "File System"
msgstr "Sistema de archivos"
#: ../src/shell-uri-util.c:248
#: ../src/shell-uri-util.c:250
msgid "Search"
msgstr "Buscar"
@ -223,11 +159,58 @@ msgstr "Buscar"
#. * example, "Trash: some-directory". It means that the
#. * directory called "some-directory" is in the trash.
#.
#: ../src/shell-uri-util.c:298
#: ../src/shell-uri-util.c:300
#, c-format
msgid "%1$s: %2$s"
msgstr "%1$s: %2$s"
#~ msgid "Frequent"
#~ msgstr "Frecuentes"
#~ msgid "More"
#~ msgstr "Más"
#~ msgid "(see all)"
#~ msgstr "(ver todo)"
#~ msgid "SEARCH RESULTS"
#~ msgstr "RESULTADOS DE LA BÚSQUEDA"
#~ msgid "Unknown"
#~ msgstr "Desconocido"
#~ msgid "Can't lock screen: %s"
#~ msgstr "No se puede bloquear la pantalla: %s"
#~ msgid "Can't temporarily set screensaver to blank screen: %s"
#~ msgstr ""
#~ "No se puede establecer temporalmente el salvapantallas a oscurecer "
#~ "pantalla: %s"
#~ msgid "Can't logout: %s"
#~ msgstr "No se puede salir de la sesión: %s"
#~ msgid "Account Information..."
#~ msgstr "Información de la cuenta…"
#~ msgid "Sidebar"
#~ msgstr "Barra lateral"
#~ msgid "System Preferences..."
#~ msgstr "Preferencias del sistema…"
#~ msgid "Lock Screen"
#~ msgstr "Bloquear la pantalla"
#~ msgid "Switch User"
#~ msgstr "Cambiar de usuario"
#~ msgid "Log Out..."
#~ msgstr "Salir…"
#~ msgid "Shut Down..."
#~ msgstr "Apagar…"
#~ msgid "Browse"
#~ msgstr "Examine"

102
po/fr.po
View File

@ -6,13 +6,13 @@
#
msgid ""
msgstr ""
"Project-Id-Version: HEAD\n"
"Project-Id-Version: gnome-shell master fr\n"
"Report-Msgid-Bugs-To: http://bugzilla.gnome.org/enter_bug.cgi?product=gnome-"
"shell&component=general\n"
"POT-Creation-Date: 2009-09-09 21:30+0000\n"
"PO-Revision-Date: 2009-09-11 21:40+0200\n"
"Last-Translator: Mathieu Bridon <bochecha@fedoraproject.org>\n"
"Language-Team: GNOME French Team\n"
"POT-Creation-Date: 2009-11-13 17:44+0000\n"
"PO-Revision-Date: 2009-12-05 16:43+0100\n"
"Last-Translator: Pablo Martin-Gomez <pablo.martin-gomez@laposte.net>\n"
"Language-Team: GNOME French Team <gnomefr@traduc.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
@ -26,80 +26,115 @@ msgstr "GNOME Shell"
msgid "Window management and application launching"
msgstr "Gestion des fenêtres et lancement des applications"
#. left side
#: ../js/ui/panel.js:269
msgid "Activities"
msgstr "Activités"
#: ../js/ui/appDisplay.js:696
msgid "Drag here to add favorites"
msgstr "Glisser ici pour ajouter aux favoris"
#. Translators: This is a time format.
#: ../js/ui/panel.js:452
msgid "%a %l:%M %p"
msgstr "%a %H:%M"
#: ../js/ui/appIcon.js:425
msgid "New Window"
msgstr "Nouvelle fenêtre"
#: ../js/ui/dash.js:255
#: ../js/ui/appIcon.js:429
msgid "Remove from Favorites"
msgstr "Enlever des favoris"
#: ../js/ui/appIcon.js:430
msgid "Add to Favorites"
msgstr "Ajouter aux favoris"
#: ../js/ui/dash.js:237
msgid "Find..."
msgstr "Rechercher..."
#: ../js/ui/dash.js:372
msgid "Browse"
msgstr "Parcourir"
#: ../js/ui/dash.js:508
msgid "(see all)"
msgstr "(tout afficher)"
#. **** Applications ****
#: ../js/ui/dash.js:700 ../js/ui/dash.js:756 ../js/ui/dash.js:887
#: ../js/ui/dash.js:656 ../js/ui/dash.js:718
msgid "APPLICATIONS"
msgstr "APPLICATIONS"
#. **** Places ****
#. Translators: This is in the sense of locations for documents,
#. network locations, etc.
#: ../js/ui/dash.js:720
#: ../js/ui/dash.js:676 ../js/ui/dash.js:733
msgid "PLACES"
msgstr "RACCOURCIS"
#. **** Documents ****
#: ../js/ui/dash.js:727 ../js/ui/dash.js:768 ../js/ui/dash.js:861
#: ../js/ui/dash.js:683 ../js/ui/dash.js:728
msgid "RECENT DOCUMENTS"
msgstr "DOCUMENTS RÉCENTS"
#. **** Search Results ****
#: ../js/ui/dash.js:746 ../js/ui/dash.js:850 ../js/ui/dash.js:876
#: ../js/ui/dash.js:708 ../js/ui/dash.js:898
msgid "SEARCH RESULTS"
msgstr "RÉSULTATS DE LA RECHERCHE"
#: ../js/ui/runDialog.js:90
#: ../js/ui/dash.js:723
msgid "PREFERENCES"
msgstr "PRÉFÉRENCES"
#. Button on the left side of the panel.
#. Translators: If there is no suitable word for "Activities" in your language, you can use the word for "Overview".
#: ../js/ui/panel.js:274
msgid "Activities"
msgstr "Activités"
#. Translators: This is a time format.
#: ../js/ui/panel.js:491
msgid "%a %l:%M %p"
msgstr "%a %H:%M"
#: ../js/ui/placeDisplay.js:84
msgid "Connect to..."
msgstr "Connexion à..."
#: ../js/ui/runDialog.js:96
msgid "Please enter a command:"
msgstr "Veuillez saisir une commande :"
#: ../src/shell-global.c:799
#: ../js/ui/runDialog.js:173
#, c-format
msgid "Execution of '%s' failed:"
msgstr "Exécution de « %s » impossible :"
#. Translators: This is a time format.
#: ../js/ui/widget.js:163
msgid "%H:%M"
msgstr "%H:%M"
#: ../js/ui/widget.js:317
msgid "Applications"
msgstr "Applications"
#: ../js/ui/widget.js:339
msgid "Recent Documents"
msgstr "Documents récents"
#: ../src/shell-global.c:821
msgid "Less than a minute ago"
msgstr "Il y a moins d'une minute"
#: ../src/shell-global.c:802
#: ../src/shell-global.c:824
#, c-format
msgid "%d minute ago"
msgid_plural "%d minutes ago"
msgstr[0] "Il y a %d minute"
msgstr[1] "Il y a %d minutes"
#: ../src/shell-global.c:805
#: ../src/shell-global.c:827
#, c-format
msgid "%d hour ago"
msgid_plural "%d hours ago"
msgstr[0] "Il y a %d heure"
msgstr[1] "Il y a %d heures"
#: ../src/shell-global.c:808
#: ../src/shell-global.c:830
#, c-format
msgid "%d day ago"
msgid_plural "%d days ago"
msgstr[0] "Il y a %d jour"
msgstr[1] "Il y a %d jours"
#: ../src/shell-global.c:811
#: ../src/shell-global.c:833
#, c-format
msgid "%d week ago"
msgid_plural "%d weeks ago"
@ -180,3 +215,6 @@ msgstr "Recherche"
#, c-format
msgid "%1$s: %2$s"
msgstr "%1$s : %2$s"
#~ msgid "Browse"
#~ msgstr "Parcourir"

240
po/gl.po
View File

@ -2,19 +2,22 @@
# Copyright (C) 2009 gnome-shell's COPYRIGHT HOLDER
# This file is distributed under the same license as the gnome-shell package.
# Fran Diéguez <fran.dieguez@mabishu.com>, 2009.
#
# Anton Meixome <certima@certima.net>, 2009.
# Antón Méixome <meixome@certima.net>, 2009.
msgid ""
msgstr ""
"Project-Id-Version: gnome-shell master\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2009-09-15 23:06+0200\n"
"PO-Revision-Date: 2009-09-10 22:32+0100\n"
"Last-Translator: Fran Diéguez <fran.dieguez@glug.es>\n"
"Language-Team: Galician <gnome@mancomun.org>\n"
"POT-Creation-Date: 2009-12-28 17:07+0100\n"
"PO-Revision-Date: 2009-12-28 08:11+0100\n"
"Last-Translator: Antón Méixome <meixome@certima.net>\n"
"Language-Team: Galician Proxecto Trasno <proxecto@trasno.net>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: gl\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Virtaal 0.4.0\n"
#: ../data/gnome-shell.desktop.in.in.h:1
msgid "GNOME Shell"
@ -22,154 +25,133 @@ msgstr "GNOME Shell"
#: ../data/gnome-shell.desktop.in.in.h:2
msgid "Window management and application launching"
msgstr "Xestor de xanelas e lanzado de aplicativos"
msgstr "Xestor de xanelas e lanzamento de aplicativos"
#. left side
#: ../js/ui/panel.js:269
msgid "Activities"
msgstr "Actividades"
#. **** Applications ****
#: ../js/ui/appDisplay.js:252 ../js/ui/dash.js:858
msgid "APPLICATIONS"
msgstr "APLICATIVOS"
#. Translators: This is a time format.
#: ../js/ui/panel.js:452
msgid "%a %l:%M %p"
msgstr "%a %l:%M %p"
#: ../js/ui/appDisplay.js:276
msgid "PREFERENCES"
msgstr "PREFERENCIAS"
#: ../js/ui/dash.js:283
#: ../js/ui/appDisplay.js:707 ../js/ui/appIcon.js:425
msgid "New Window"
msgstr "Xanela nova"
#: ../js/ui/appDisplay.js:711 ../js/ui/appIcon.js:429
msgid "Remove from Favorites"
msgstr "Eliminar de Favoritos"
#: ../js/ui/appDisplay.js:712 ../js/ui/appIcon.js:430
msgid "Add to Favorites"
msgstr "Engadir a Favoritos"
#: ../js/ui/appDisplay.js:1064
msgid "Drag here to add favorites"
msgstr "Arrastra aquí para engadir favoritos"
#: ../js/ui/dash.js:240
msgid "Find..."
msgstr "Buscar..."
#: ../js/ui/dash.js:400
msgid "Browse"
msgstr "Explorar"
#: ../js/ui/dash.js:536
msgid "(see all)"
msgstr "(ver todos)"
#. **** Applications ****
#: ../js/ui/dash.js:753 ../js/ui/dash.js:809
msgid "APPLICATIONS"
msgstr "APLICATIVOS"
#: ../js/ui/dash.js:437
msgid "Searching..."
msgstr "Buscando..."
#. **** Places ****
#. Translators: This is in the sense of locations for documents,
#. network locations, etc.
#: ../js/ui/dash.js:773
#: ../js/ui/dash.js:878 ../js/ui/placeDisplay.js:519
msgid "PLACES"
msgstr "LUGARES"
#. **** Documents ****
#: ../js/ui/dash.js:780 ../js/ui/dash.js:819
#: ../js/ui/dash.js:885
msgid "RECENT DOCUMENTS"
msgstr "DOCUMENTOS RECENTES"
#. **** Search Results ****
#: ../js/ui/dash.js:799 ../js/ui/dash.js:931
msgid "SEARCH RESULTS"
msgstr "RESULTADOS DA BÚSQUEDA"
#. Button on the left side of the panel.
#. Translators: If there is no suitable word for "Activities" in your language, you can use the word for "Overview".
#: ../js/ui/panel.js:227
msgid "Activities"
msgstr "Actividades"
#: ../js/ui/dash.js:814
msgid "PREFERENCES"
msgstr ""
#. Translators: This is a time format.
#: ../js/ui/panel.js:440
msgid "%a %l:%M %p"
msgstr "%a %l:%M %p"
#: ../js/ui/runDialog.js:96
#: ../js/ui/placeDisplay.js:144
msgid "Connect to..."
msgstr "Conectar con..."
#: ../js/ui/runDialog.js:235
msgid "Please enter a command:"
msgstr "Insira unha orde:"
#: ../src/shell-global.c:799
msgid "Less than a minute ago"
msgstr "Menos de un minuto"
#: ../js/ui/runDialog.js:351
#, c-format
msgid "Execution of '%s' failed:"
msgstr "Fallou a execución de %s"
#: ../src/shell-global.c:802
#. Translators: This is a time format.
#: ../js/ui/widget.js:163
msgid "%H:%M"
msgstr "%H:%M"
#: ../js/ui/widget.js:317
msgid "Applications"
msgstr "Aplicativos"
#: ../js/ui/widget.js:339
msgid "Recent Documents"
msgstr "Documentos recentes"
#: ../src/shell-global.c:890
msgid "Less than a minute ago"
msgstr "Hai menos dun minuto"
#: ../src/shell-global.c:893
#, c-format
msgid "%d minute ago"
msgid_plural "%d minutes ago"
msgstr[0] "fai %d minuto"
msgstr[1] "fai %d minutos"
msgstr[0] "hai %d minuto"
msgstr[1] "hai %d minutos"
#: ../src/shell-global.c:805
#: ../src/shell-global.c:896
#, c-format
msgid "%d hour ago"
msgid_plural "%d hours ago"
msgstr[0] "fai %d hora"
msgstr[1] "fai %d horas"
msgstr[0] "hai %d hora"
msgstr[1] "hai %d horas"
#: ../src/shell-global.c:808
#: ../src/shell-global.c:899
#, c-format
msgid "%d day ago"
msgid_plural "%d days ago"
msgstr[0] "fai %d día"
msgstr[1] "fai %d días"
msgstr[0] "hai %d día"
msgstr[1] "hai %d días"
#: ../src/shell-global.c:811
#: ../src/shell-global.c:902
#, c-format
msgid "%d week ago"
msgid_plural "%d weeks ago"
msgstr[0] "fai %d semana"
msgstr[1] "fai %d semanas"
msgstr[0] "hai %d semana"
msgstr[1] "hai %d semanas"
#: ../src/shell-status-menu.c:156
msgid "Unknown"
msgstr "Descoñecido"
#: ../src/shell-status-menu.c:212
#, c-format
msgid "Can't lock screen: %s"
msgstr "Non foi posíbel bloquear a pantalla: %s"
#: ../src/shell-status-menu.c:227
#, c-format
msgid "Can't temporarily set screensaver to blank screen: %s"
msgstr ""
"Non foi posíbel establecer o salvapantallas a unha pantalla en branco: %s"
#: ../src/shell-status-menu.c:351
#, c-format
msgid "Can't logout: %s"
msgstr "Non foi posíbel pechar a sesión: %s"
#: ../src/shell-status-menu.c:492
msgid "Account Information..."
msgstr "Información da conta..."
#: ../src/shell-status-menu.c:502
msgid "Sidebar"
msgstr "Barra lateral"
#: ../src/shell-status-menu.c:510
msgid "System Preferences..."
msgstr "Preferenzas do sistema..."
#: ../src/shell-status-menu.c:525
msgid "Lock Screen"
msgstr "Bloquear pantalla"
#: ../src/shell-status-menu.c:535
msgid "Switch User"
msgstr "Cambiar de usuario"
#. Only show switch user if there are other users
#. Log Out
#: ../src/shell-status-menu.c:546
msgid "Log Out..."
msgstr "Pechar sesión..."
#. Shut down
#: ../src/shell-status-menu.c:557
msgid "Shut Down..."
msgstr "Apagar..."
#: ../src/shell-uri-util.c:87
#: ../src/shell-uri-util.c:89
msgid "Home Folder"
msgstr "Cartafol persoal"
#. Translators: this is the same string as the one found in
#. * nautilus
#: ../src/shell-uri-util.c:102
#: ../src/shell-uri-util.c:104
msgid "File System"
msgstr "Sistema de ficheiros"
#: ../src/shell-uri-util.c:248
#: ../src/shell-uri-util.c:250
msgid "Search"
msgstr "Buscar"
@ -178,11 +160,55 @@ msgstr "Buscar"
#. * example, "Trash: some-directory". It means that the
#. * directory called "some-directory" is in the trash.
#.
#: ../src/shell-uri-util.c:298
#: ../src/shell-uri-util.c:300
#, c-format
msgid "%1$s: %2$s"
msgstr "%1$s: %2$s"
#~ msgid "SEARCH RESULTS"
#~ msgstr "RESULTADOS DA BUSCA"
#~ msgid "Unknown"
#~ msgstr "Descoñecido"
#~ msgid "Can't lock screen: %s"
#~ msgstr "Non foi posíbel bloquear a pantalla: %s"
#~ msgid "Can't temporarily set screensaver to blank screen: %s"
#~ msgstr ""
#~ "Non foi posíbel estabelecer temporalmente o salvapantallas a unha "
#~ "pantalla en branco: %s"
#~ msgid "Can't logout: %s"
#~ msgstr "Non foi posíbel pechar a sesión: %s"
#~ msgid "Account Information..."
#~ msgstr "Información da conta..."
#~ msgid "Sidebar"
#~ msgstr "Barra lateral"
#~ msgid "System Preferences..."
#~ msgstr "Preferencias do sistema..."
#~ msgid "Lock Screen"
#~ msgstr "Bloquear pantalla"
#~ msgid "Switch User"
#~ msgstr "Cambiar de usuario"
#~ msgid "Log Out..."
#~ msgstr "Saír da sesión..."
#~ msgid "Shut Down..."
#~ msgstr "Apagar..."
#~ msgid "Browse"
#~ msgstr "Explorar"
#~ msgid "(see all)"
#~ msgstr "(ver todos)"
#~ msgid "Find apps or documents"
#~ msgstr "Atopar aplicativos ou documentos"

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 ""
"Project-Id-Version: gnome-shell\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2009-11-03 22:26+0100\n"
"PO-Revision-Date: 2009-11-03 22:28+0100\n"
"POT-Creation-Date: 2009-12-28 21:58+0100\n"
"PO-Revision-Date: 2009-12-28 21:59+0100\n"
"Last-Translator: Milo Casagrande <milo@ubuntu.com>\n"
"Language-Team: Italian <tp@lists.linux.it>\n"
"MIME-Version: 1.0\n"
@ -24,85 +24,72 @@ msgstr "GNOME Shell"
msgid "Window management and application launching"
msgstr "Gestione finestre e avvio applicazioni"
#: ../js/ui/appDisplay.js:332
msgid "Frequent"
msgstr "Frequente"
#. **** Applications ****
#: ../js/ui/appDisplay.js:252 ../js/ui/dash.js:858
msgid "APPLICATIONS"
msgstr "Applicazioni"
#: ../js/ui/appDisplay.js:867
msgid "Drag here to add favorites"
msgstr "Trascinare qui per aggiungere ai preferiti"
#: ../js/ui/appDisplay.js:276
msgid "PREFERENCES"
msgstr "Preferenze"
#: ../js/ui/appIcon.js:426
#: ../js/ui/appDisplay.js:707 ../js/ui/appIcon.js:425
msgid "New Window"
msgstr "Nuova finestra"
#: ../js/ui/appIcon.js:430
#: ../js/ui/appDisplay.js:711 ../js/ui/appIcon.js:429
msgid "Remove from Favorites"
msgstr "Rimuovi dai preferiti"
#: ../js/ui/appIcon.js:431
#: ../js/ui/appDisplay.js:712 ../js/ui/appIcon.js:430
msgid "Add to Favorites"
msgstr "Aggiungi ai preferiti"
#: ../js/ui/dash.js:283
#: ../js/ui/appDisplay.js:1064
msgid "Drag here to add favorites"
msgstr "Trascinare qui per aggiungere ai preferiti"
#: ../js/ui/dash.js:240
msgid "Find..."
msgstr "Trova..."
#: ../js/ui/dash.js:400
msgid "More"
msgstr "Altro"
#: ../js/ui/dash.js:543
msgid "(see all)"
msgstr "(vedi tutto)"
#. **** Applications ****
#: ../js/ui/dash.js:725 ../js/ui/dash.js:787
msgid "APPLICATIONS"
msgstr "Applicazioni"
#: ../js/ui/dash.js:437
msgid "Searching..."
msgstr "Ricerca..."
#. **** Places ****
#. Translators: This is in the sense of locations for documents,
#. network locations, etc.
#: ../js/ui/dash.js:745
#: ../js/ui/dash.js:878 ../js/ui/placeDisplay.js:519
msgid "PLACES"
msgstr "Risorse"
#. **** Documents ****
#: ../js/ui/dash.js:752 ../js/ui/dash.js:797
#: ../js/ui/dash.js:885
msgid "RECENT DOCUMENTS"
msgstr "Documenti recenti"
#. **** Search Results ****
#: ../js/ui/dash.js:777 ../js/ui/dash.js:961
msgid "SEARCH RESULTS"
msgstr "Risultati ricerca"
#: ../js/ui/dash.js:792
msgid "PREFERENCES"
msgstr "Preferenze"
#. Button on the left side of the panel.
#. Translators: If there is no suitable word for "Activities" in your language, you can use the word for "Overview".
#: ../js/ui/panel.js:274
#: ../js/ui/panel.js:227
msgid "Activities"
msgstr "Attività"
# (ndt) proviamo col k, se non funge, sappiamo il perché...
#. Translators: This is a time format.
#: ../js/ui/panel.js:491
#: ../js/ui/panel.js:440
msgid "%a %l:%M %p"
msgstr "%a %k.%M"
#: ../js/ui/places.js:178
#: ../js/ui/placeDisplay.js:144
msgid "Connect to..."
msgstr "Connetti a..."
#: ../js/ui/runDialog.js:96
#: ../js/ui/runDialog.js:235
msgid "Please enter a command:"
msgstr "Inserire un comando:"
#: ../js/ui/runDialog.js:173
#: ../js/ui/runDialog.js:351
#, c-format
msgid "Execution of '%s' failed:"
msgstr "Esecuzione di «%s» non riuscita:"
@ -120,102 +107,49 @@ msgstr "Applicazioni"
msgid "Recent Documents"
msgstr "Documenti recenti"
#: ../src/shell-global.c:821
#: ../src/shell-global.c:890
msgid "Less than a minute ago"
msgstr "Meno di un minuto fa"
#: ../src/shell-global.c:824
#: ../src/shell-global.c:893
#, c-format
msgid "%d minute ago"
msgid_plural "%d minutes ago"
msgstr[0] "%d minuto fa"
msgstr[1] "%d minuti fa"
#: ../src/shell-global.c:827
#: ../src/shell-global.c:896
#, c-format
msgid "%d hour ago"
msgid_plural "%d hours ago"
msgstr[0] "%d ora fa"
msgstr[1] "%d ore fa"
#: ../src/shell-global.c:830
#: ../src/shell-global.c:899
#, c-format
msgid "%d day ago"
msgid_plural "%d days ago"
msgstr[0] "%d giorno fa"
msgstr[1] "%d giorni fa"
#: ../src/shell-global.c:833
#: ../src/shell-global.c:902
#, c-format
msgid "%d week ago"
msgid_plural "%d weeks ago"
msgstr[0] "%d settimana fa"
msgstr[1] "%d settimane fa"
# (ndt) valutare se vada al femminile
#: ../src/shell-status-menu.c:156
msgid "Unknown"
msgstr "Sconosciuto"
#: ../src/shell-status-menu.c:212
#, c-format
msgid "Can't lock screen: %s"
msgstr "Impossibile bloccare lo schermo: %s"
#: ../src/shell-status-menu.c:227
#, c-format
msgid "Can't temporarily set screensaver to blank screen: %s"
msgstr ""
"Impossibile impostare temporaneamente il salva schermo a schermo nero: %s "
#: ../src/shell-status-menu.c:351
#, c-format
msgid "Can't logout: %s"
msgstr "Impossibile terminare la sessione: %s"
#: ../src/shell-status-menu.c:492
msgid "Account Information..."
msgstr "Informazioni account..."
#: ../src/shell-status-menu.c:502
msgid "Sidebar"
msgstr "Barra laterale"
#: ../src/shell-status-menu.c:510
msgid "System Preferences..."
msgstr "Preferenze di sistema..."
#: ../src/shell-status-menu.c:525
msgid "Lock Screen"
msgstr "Blocca schermo"
#: ../src/shell-status-menu.c:535
msgid "Switch User"
msgstr "Cambia utente"
#. Only show switch user if there are other users
#. Log Out
#: ../src/shell-status-menu.c:546
msgid "Log Out..."
msgstr "Termina sessione..."
# (ndt) da valutare... pare che ora anche Windows usi 'Arresta...'...
#. Shut down
#: ../src/shell-status-menu.c:557
msgid "Shut Down..."
msgstr "Spegni..."
#: ../src/shell-uri-util.c:87
#: ../src/shell-uri-util.c:89
msgid "Home Folder"
msgstr "Cartella home"
#. Translators: this is the same string as the one found in
#. * nautilus
#: ../src/shell-uri-util.c:102
#: ../src/shell-uri-util.c:104
msgid "File System"
msgstr "File system"
#: ../src/shell-uri-util.c:248
#: ../src/shell-uri-util.c:250
msgid "Search"
msgstr "Cerca"
@ -225,7 +159,55 @@ msgstr "Cerca"
#. * example, "Trash: some-directory". It means that the
#. * directory called "some-directory" is in the trash.
#.
#: ../src/shell-uri-util.c:298
#: ../src/shell-uri-util.c:300
#, c-format
msgid "%1$s: %2$s"
msgstr "%1$s: %2$s"
#~ msgid "Frequent"
#~ msgstr "Frequente"
#~ msgid "More"
#~ msgstr "Altro"
#~ msgid "(see all)"
#~ msgstr "(vedi tutto)"
#~ msgid "SEARCH RESULTS"
#~ msgstr "Risultati ricerca"
# (ndt) valutare se vada al femminile
#~ msgid "Unknown"
#~ msgstr "Sconosciuto"
#~ msgid "Can't lock screen: %s"
#~ msgstr "Impossibile bloccare lo schermo: %s"
#~ msgid "Can't temporarily set screensaver to blank screen: %s"
#~ msgstr ""
#~ "Impossibile impostare temporaneamente il salva schermo a schermo nero: %s "
#~ msgid "Can't logout: %s"
#~ msgstr "Impossibile terminare la sessione: %s"
#~ msgid "Account Information..."
#~ msgstr "Informazioni account..."
#~ msgid "Sidebar"
#~ msgstr "Barra laterale"
#~ msgid "System Preferences..."
#~ msgstr "Preferenze di sistema..."
#~ msgid "Lock Screen"
#~ msgstr "Blocca schermo"
#~ msgid "Switch User"
#~ msgstr "Cambia utente"
#~ msgid "Log Out..."
#~ msgstr "Termina sessione..."
# (ndt) da valutare... pare che ora anche Windows usi 'Arresta...'...
#~ msgid "Shut Down..."
#~ msgstr "Spegni..."

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.
# Matej Urbančič <mateju@svn.gnome.org>, 2009.
#
# Matej Urbančič <mateju@svn.gnome.org>, 2009 - 2010.
#
msgid ""
msgstr ""
"Project-Id-Version: gnome-shell master\n"
"Report-Msgid-Bugs-To: http://bugzilla.gnome.org/enter_bug.cgi?product=gnome-shell&component=general\n"
"POT-Creation-Date: 2009-11-12 21:33+0000\n"
"PO-Revision-Date: 2009-11-14 10:19+0100\n"
"POT-Creation-Date: 2010-01-05 01:21+0000\n"
"PO-Revision-Date: 2010-01-05 09:09+0100\n"
"Last-Translator: Matej Urbančič <mateju@svn.gnome.org>\n"
"Language-Team: Slovenian GNOME Translation Team <gnome-si@googlegroups.com>\n"
"MIME-Version: 1.0\n"
@ -27,76 +29,76 @@ msgstr "Gnome lupina"
msgid "Window management and application launching"
msgstr "Upravljanje oken in zaganjanje programov"
#: ../js/ui/appDisplay.js:696
msgid "Drag here to add favorites"
msgstr "S potegom na to mesto se izbor doda med priljubljene"
#. **** Applications ****
#: ../js/ui/appDisplay.js:252
#: ../js/ui/dash.js:858
msgid "APPLICATIONS"
msgstr "Programi"
#: ../js/ui/appDisplay.js:276
msgid "PREFERENCES"
msgstr "Možnosti"
#: ../js/ui/appDisplay.js:707
#: ../js/ui/appIcon.js:425
msgid "New Window"
msgstr "Novo okno"
#: ../js/ui/appDisplay.js:711
#: ../js/ui/appIcon.js:429
msgid "Remove from Favorites"
msgstr "Odstrani iz priljubljenih"
#: ../js/ui/appDisplay.js:712
#: ../js/ui/appIcon.js:430
msgid "Add to Favorites"
msgstr "Dodaj med priljubljene"
#: ../js/ui/dash.js:237
msgid "Find..."
msgstr "Poišči ..."
#: ../js/ui/appDisplay.js:1064
msgid "Drag here to add favorites"
msgstr "S potegom na to mesto se izbor doda med priljubljene"
#. **** Applications ****
#: ../js/ui/dash.js:656
#: ../js/ui/dash.js:718
msgid "APPLICATIONS"
msgstr "Programi"
#: ../js/ui/dash.js:240
msgid "Find..."
msgstr "Najdi ..."
#: ../js/ui/dash.js:437
msgid "Searching..."
msgstr "Iskanje ..."
#. **** Places ****
#. Translators: This is in the sense of locations for documents,
#. network locations, etc.
#: ../js/ui/dash.js:676
#: ../js/ui/dash.js:733
#: ../js/ui/dash.js:878
#: ../js/ui/placeDisplay.js:519
msgid "PLACES"
msgstr "Mesta"
#. **** Documents ****
#: ../js/ui/dash.js:683
#: ../js/ui/dash.js:728
#: ../js/ui/dash.js:885
msgid "RECENT DOCUMENTS"
msgstr "Nedavni dokumenti"
#. **** Search Results ****
#: ../js/ui/dash.js:708
#: ../js/ui/dash.js:898
msgid "SEARCH RESULTS"
msgstr "Rezultati iskanja"
#: ../js/ui/dash.js:723
msgid "PREFERENCES"
msgstr "Lastnosti"
#. Button on the left side of the panel.
#. Translators: If there is no suitable word for "Activities" in your language, you can use the word for "Overview".
#: ../js/ui/panel.js:274
#: ../js/ui/panel.js:227
msgid "Activities"
msgstr "Dejavnosti"
#. Translators: This is a time format.
#: ../js/ui/panel.js:491
#: ../js/ui/panel.js:440
msgid "%a %l:%M %p"
msgstr "%a, %H:%M"
#: ../js/ui/placeDisplay.js:84
#: ../js/ui/placeDisplay.js:144
msgid "Connect to..."
msgstr "Povezava z ..."
#: ../js/ui/runDialog.js:96
#: ../js/ui/runDialog.js:235
msgid "Please enter a command:"
msgstr "Vnos ukaza:"
#: ../js/ui/runDialog.js:173
#: ../js/ui/runDialog.js:351
#, c-format
msgid "Execution of '%s' failed:"
msgstr "Izvajanje '%s' je spodletelo:"
@ -114,11 +116,11 @@ msgstr "Programi"
msgid "Recent Documents"
msgstr "Nedavni dokumenti"
#: ../src/shell-global.c:821
#: ../src/shell-global.c:890
msgid "Less than a minute ago"
msgstr "Pred manj kot eno minuto"
#: ../src/shell-global.c:824
#: ../src/shell-global.c:893
#, c-format
msgid "%d minute ago"
msgid_plural "%d minutes ago"
@ -127,7 +129,7 @@ msgstr[1] "Pred %d minuto"
msgstr[2] "Pred %d minutama"
msgstr[3] "Pred %d minutami"
#: ../src/shell-global.c:827
#: ../src/shell-global.c:896
#, c-format
msgid "%d hour ago"
msgid_plural "%d hours ago"
@ -136,7 +138,7 @@ msgstr[1] "Pred %d uro"
msgstr[2] "Pred %d urama"
msgstr[3] "Pred %d urami"
#: ../src/shell-global.c:830
#: ../src/shell-global.c:899
#, c-format
msgid "%d day ago"
msgid_plural "%d days ago"
@ -145,7 +147,7 @@ msgstr[1] "Pred %d dnevom"
msgstr[2] "Pred %d dnevoma"
msgstr[3] "Pred %d dnevi"
#: ../src/shell-global.c:833
#: ../src/shell-global.c:902
#, c-format
msgid "%d week ago"
msgid_plural "%d weeks ago"
@ -154,80 +156,55 @@ msgstr[1] "Pred %d tednom"
msgstr[2] "Pred %d tednoma"
msgstr[3] "Pred %d tedni"
#: ../src/shell-status-menu.c:156
msgid "Unknown"
msgstr "Neznano"
#: ../src/shell-status-menu.c:212
#, c-format
msgid "Can't lock screen: %s"
msgstr "Ni mogoče zakleniti zaslona: %s"
#: ../src/shell-status-menu.c:227
#, c-format
msgid "Can't temporarily set screensaver to blank screen: %s"
msgstr "Ni mogoče začasno nastaviti črnega zaslona za ohranjevalnik zaslona: %s"
#: ../src/shell-status-menu.c:351
#, c-format
msgid "Can't logout: %s"
msgstr "Ni se mogoče odjaviti: %s"
#: ../src/shell-status-menu.c:492
msgid "Account Information..."
msgstr "Podrobnosti računa ..."
#: ../src/shell-status-menu.c:502
msgid "Sidebar"
msgstr "Stranska vrstica"
#: ../src/shell-status-menu.c:510
msgid "System Preferences..."
msgstr "Sistemske lastnosti ..."
#: ../src/shell-status-menu.c:525
msgid "Lock Screen"
msgstr "Zakleni zaslon"
#: ../src/shell-status-menu.c:535
msgid "Switch User"
msgstr "Preklop uporabnika"
#. Only show switch user if there are other users
#. Log Out
#: ../src/shell-status-menu.c:546
msgid "Log Out..."
msgstr "Odjava ..."
#. Shut down
#: ../src/shell-status-menu.c:557
msgid "Shut Down..."
msgstr "Izklopi ..."
#: ../src/shell-uri-util.c:87
#: ../src/shell-uri-util.c:89
msgid "Home Folder"
msgstr "Domača mapa"
#. Translators: this is the same string as the one found in
#. * nautilus
#: ../src/shell-uri-util.c:102
#: ../src/shell-uri-util.c:104
msgid "File System"
msgstr "Datotečni sistem"
#: ../src/shell-uri-util.c:248
#: ../src/shell-uri-util.c:250
msgid "Search"
msgstr "Iskanje"
msgstr "Poišči"
#. Translators: the first string is the name of a gvfs
#. * method, and the second string is a path. For
#. * example, "Trash: some-directory". It means that the
#. * directory called "some-directory" is in the trash.
#.
#: ../src/shell-uri-util.c:298
#: ../src/shell-uri-util.c:300
#, c-format
msgid "%1$s: %2$s"
msgstr "%1$s: %2$s"
#~ msgid "SEARCH RESULTS"
#~ msgstr "Rezultati iskanja"
#~ msgid "Unknown"
#~ msgstr "Neznano"
#~ msgid "Can't lock screen: %s"
#~ msgstr "Ni mogoče zakleniti zaslona: %s"
#~ msgid "Can't temporarily set screensaver to blank screen: %s"
#~ msgstr ""
#~ "Ni mogoče začasno nastaviti črnega zaslona za ohranjevalnik zaslona: %s"
#~ msgid "Can't logout: %s"
#~ msgstr "Ni se mogoče odjaviti: %s"
#~ msgid "Account Information..."
#~ msgstr "Podrobnosti računa ..."
#~ msgid "Sidebar"
#~ msgstr "Stranska vrstica"
#~ msgid "System Preferences..."
#~ msgstr "Sistemske možnosti ..."
#~ msgid "Lock Screen"
#~ msgstr "Zakleni zaslon"
#~ msgid "Switch User"
#~ msgstr "Preklop uporabnika"
#~ msgid "Log Out..."
#~ msgstr "Odjava ..."
#~ msgid "Shut Down..."
#~ msgstr "Izklopi ..."
#~ msgid "Frequent"
#~ msgstr "Pogosto"
#~ msgid "More"

185
po/sv.po
View File

@ -7,8 +7,8 @@ msgid ""
msgstr ""
"Project-Id-Version: gnome-shell\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2009-09-18 13:02+0200\n"
"PO-Revision-Date: 2009-09-18 13:02+0100\n"
"POT-Creation-Date: 2009-12-01 23:52+0100\n"
"PO-Revision-Date: 2009-12-01 23:53+0100\n"
"Last-Translator: Daniel Nylander <po@danielnylander.se>\n"
"Language-Team: Swedish <tp-sv@listor.tp-sv.se>\n"
"MIME-Version: 1.0\n"
@ -24,154 +24,139 @@ msgstr "GNOME-skal"
msgid "Window management and application launching"
msgstr "Fönsterhantering och programstarter"
#. left side
#: ../js/ui/panel.js:269
msgid "Activities"
msgstr "Aktiviteter"
#: ../js/ui/appDisplay.js:580
#: ../js/ui/appIcon.js:425
msgid "New Window"
msgstr "Nytt fönster"
#. Translators: This is a time format.
#: ../js/ui/panel.js:452
msgid "%a %l:%M %p"
msgstr "%a %H.%M"
#: ../js/ui/appDisplay.js:584
#: ../js/ui/appIcon.js:429
msgid "Remove from Favorites"
msgstr "Ta bort från favoriter"
#: ../js/ui/dash.js:283
#: ../js/ui/appDisplay.js:585
#: ../js/ui/appIcon.js:430
msgid "Add to Favorites"
msgstr "Lägg till som favorit"
#: ../js/ui/appDisplay.js:1029
msgid "Drag here to add favorites"
msgstr "Dra hit för att lägga till favorit"
#: ../js/ui/dash.js:236
msgid "Find..."
msgstr "Sök..."
#: ../js/ui/dash.js:400
msgid "Browse"
msgstr "Bläddra"
#: ../js/ui/dash.js:536
msgid "(see all)"
msgstr "(se alla)"
#. **** Applications ****
#: ../js/ui/dash.js:753
#: ../js/ui/dash.js:809
#: ../js/ui/dash.js:620
#: ../js/ui/dash.js:682
msgid "APPLICATIONS"
msgstr "PROGRAM"
#. **** Places ****
#. Translators: This is in the sense of locations for documents,
#. network locations, etc.
#: ../js/ui/dash.js:773
#: ../js/ui/dash.js:640
#: ../js/ui/dash.js:697
msgid "PLACES"
msgstr "PLATSER"
#. **** Documents ****
#: ../js/ui/dash.js:780
#: ../js/ui/dash.js:819
#: ../js/ui/dash.js:647
#: ../js/ui/dash.js:692
msgid "RECENT DOCUMENTS"
msgstr "SENASTE DOKUMENT"
#. **** Search Results ****
#: ../js/ui/dash.js:799
#: ../js/ui/dash.js:931
#: ../js/ui/dash.js:672
#: ../js/ui/dash.js:862
msgid "SEARCH RESULTS"
msgstr "SÖKRESULTAT"
#: ../js/ui/dash.js:814
#: ../js/ui/dash.js:687
msgid "PREFERENCES"
msgstr "INSTÄLLNINGAR"
#: ../js/ui/runDialog.js:101
#. Button on the left side of the panel.
#. Translators: If there is no suitable word for "Activities" in your language, you can use the word for "Overview".
#: ../js/ui/panel.js:227
msgid "Activities"
msgstr "Aktiviteter"
#. Translators: This is a time format.
#: ../js/ui/panel.js:440
msgid "%a %l:%M %p"
msgstr "%a %H.%M"
#: ../js/ui/placeDisplay.js:84
msgid "Connect to..."
msgstr "Anslut till..."
#: ../js/ui/runDialog.js:96
msgid "Please enter a command:"
msgstr "Ange ett kommando:"
#: ../src/shell-global.c:799
#: ../js/ui/runDialog.js:173
#, c-format
msgid "Execution of '%s' failed:"
msgstr "Körning av \"%s\" misslyckades:"
#. Translators: This is a time format.
#: ../js/ui/widget.js:163
msgid "%H:%M"
msgstr "%H.%M"
#: ../js/ui/widget.js:317
msgid "Applications"
msgstr "Program"
#: ../js/ui/widget.js:339
msgid "Recent Documents"
msgstr "Senaste dokument"
#: ../src/shell-global.c:826
msgid "Less than a minute ago"
msgstr "Mindre än en minut sedan"
#: ../src/shell-global.c:802
#: ../src/shell-global.c:829
#, c-format
msgid "%d minute ago"
msgid_plural "%d minutes ago"
msgstr[0] "%d minut sedan"
msgstr[1] "%d minuter sedan"
#: ../src/shell-global.c:805
#: ../src/shell-global.c:832
#, c-format
msgid "%d hour ago"
msgid_plural "%d hours ago"
msgstr[0] "%d timme sedan"
msgstr[1] "%d timmar sedan"
#: ../src/shell-global.c:808
#: ../src/shell-global.c:835
#, c-format
msgid "%d day ago"
msgid_plural "%d days ago"
msgstr[0] "%d dag sedan"
msgstr[1] "%d dagar sedan"
#: ../src/shell-global.c:811
#: ../src/shell-global.c:838
#, c-format
msgid "%d week ago"
msgid_plural "%d weeks ago"
msgstr[0] "%d vecka sedan"
msgstr[1] "%d veckor sedan"
#: ../src/shell-status-menu.c:156
msgid "Unknown"
msgstr "Okänt"
#: ../src/shell-status-menu.c:212
#, c-format
msgid "Can't lock screen: %s"
msgstr "Kan inte låsa skärmen: %s"
#: ../src/shell-status-menu.c:227
#, c-format
msgid "Can't temporarily set screensaver to blank screen: %s"
msgstr "Kan inte temporärt ställa in skärmsläckaren till blank skärm: %s"
#: ../src/shell-status-menu.c:351
#, c-format
msgid "Can't logout: %s"
msgstr "Kan inte logga ut: %s"
#: ../src/shell-status-menu.c:492
msgid "Account Information..."
msgstr "Kontoinformation..."
#: ../src/shell-status-menu.c:502
msgid "Sidebar"
msgstr "Sidopanel"
#: ../src/shell-status-menu.c:510
msgid "System Preferences..."
msgstr "Systeminställningar..."
#: ../src/shell-status-menu.c:525
msgid "Lock Screen"
msgstr "Lås skärmen"
#: ../src/shell-status-menu.c:535
msgid "Switch User"
msgstr "Växla användare"
#. Only show switch user if there are other users
#. Log Out
#: ../src/shell-status-menu.c:546
msgid "Log Out..."
msgstr "Logga ut..."
#. Shut down
#: ../src/shell-status-menu.c:557
msgid "Shut Down..."
msgstr "Stäng av..."
#: ../src/shell-uri-util.c:87
#: ../src/shell-uri-util.c:89
msgid "Home Folder"
msgstr "Hemmapp"
#. Translators: this is the same string as the one found in
#. * nautilus
#: ../src/shell-uri-util.c:102
#: ../src/shell-uri-util.c:104
msgid "File System"
msgstr "Filsystem"
#: ../src/shell-uri-util.c:248
#: ../src/shell-uri-util.c:250
msgid "Search"
msgstr "Sök"
@ -180,11 +165,37 @@ msgstr "Sök"
#. * example, "Trash: some-directory". It means that the
#. * directory called "some-directory" is in the trash.
#.
#: ../src/shell-uri-util.c:298
#: ../src/shell-uri-util.c:300
#, c-format
msgid "%1$s: %2$s"
msgstr "%1$s: %2$s"
#~ msgid "(see all)"
#~ msgstr "(se alla)"
#~ msgid "Unknown"
#~ msgstr "Okänt"
#~ msgid "Can't lock screen: %s"
#~ msgstr "Kan inte låsa skärmen: %s"
#~ msgid "Can't temporarily set screensaver to blank screen: %s"
#~ msgstr "Kan inte temporärt ställa in skärmsläckaren till blank skärm: %s"
#~ msgid "Can't logout: %s"
#~ msgstr "Kan inte logga ut: %s"
#~ msgid "Account Information..."
#~ msgstr "Kontoinformation..."
#~ msgid "Sidebar"
#~ msgstr "Sidopanel"
#~ msgid "System Preferences..."
#~ msgstr "Systeminställningar..."
#~ msgid "Lock Screen"
#~ msgstr "Lås skärmen"
#~ msgid "Switch User"
#~ msgstr "Växla användare"
#~ msgid "Log Out..."
#~ msgstr "Logga ut..."
#~ msgid "Shut Down..."
#~ msgstr "Stäng av..."
#~ msgid "Browse"
#~ msgstr "Bläddra"
#~ msgid "Find apps or documents"
#~ msgstr "Hitta program eller dokument"
#~ msgid "DOCUMENTS"

View File

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

View File

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

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

View File

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

View File

@ -37,18 +37,6 @@ struct _ShellAppSystemClass
GType shell_app_system_get_type (void) G_GNUC_CONST;
ShellAppSystem* shell_app_system_get_default(void);
GSList *shell_app_system_get_applications_for_menu (ShellAppSystem *system, const char *menu);
typedef struct _ShellAppMenuEntry ShellAppMenuEntry;
struct _ShellAppMenuEntry {
char *name;
char *id;
char *icon;
};
GType shell_app_menu_entry_get_type (void);
typedef struct _ShellAppInfo ShellAppInfo;
#define SHELL_TYPE_APP_INFO (shell_app_info_get_type ())
@ -85,8 +73,17 @@ ShellApp *shell_app_system_lookup_heuristic_basename (ShellAppSystem *system, co
ShellAppInfo *shell_app_system_create_from_window (ShellAppSystem *system, MetaWindow *window);
GSList *shell_app_system_get_menus (ShellAppSystem *system);
GSList *shell_app_system_get_flattened_apps (ShellAppSystem *system);
GSList *shell_app_system_get_all_settings (ShellAppSystem *system);
GSList *shell_app_system_initial_search (ShellAppSystem *system,
gboolean prefs,
GSList *terms);
GSList *shell_app_system_subsearch (ShellAppSystem *system,
gboolean prefs,
GSList *previous_results,
GSList *terms);
#endif /* __SHELL_APP_SYSTEM_H__ */

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);
struct _ShellGenericContainerPrivate {
gpointer dummy;
GHashTable *skip_paint;
};
/* Signals */
@ -182,15 +182,119 @@ shell_generic_container_get_preferred_height (ClutterActor *actor,
shell_generic_container_allocation_unref (alloc);
}
static void
shell_generic_container_paint (ClutterActor *actor)
{
ShellGenericContainer *self = (ShellGenericContainer*) actor;
GList *iter, *children;
children = clutter_container_get_children ((ClutterContainer*) actor);
for (iter = children; iter; iter = iter->next)
{
ClutterActor *child = iter->data;
if (g_hash_table_lookup (self->priv->skip_paint, child))
continue;
clutter_actor_paint (child);
}
g_list_free (children);
}
static void
shell_generic_container_pick (ClutterActor *actor,
const ClutterColor *color)
{
ShellGenericContainer *self = (ShellGenericContainer*) actor;
GList *iter, *children;
(CLUTTER_ACTOR_CLASS (g_type_class_peek (clutter_actor_get_type ())))->pick (actor, color);
children = clutter_container_get_children ((ClutterContainer*) actor);
for (iter = children; iter; iter = iter->next)
{
ClutterActor *child = iter->data;
if (g_hash_table_lookup (self->priv->skip_paint, child))
continue;
clutter_actor_paint (child);
}
g_list_free (children);
}
static void
on_skip_paint_weakref (gpointer user_data,
GObject *location)
{
ShellGenericContainer *self = SHELL_GENERIC_CONTAINER (user_data);
g_hash_table_remove (self->priv->skip_paint, location);
}
/**
* shell_generic_container_set_skip_paint:
* @container: A #ShellGenericContainer
* @child: Child #ClutterActor
* @skip %TRUE if we should skip painting
*
* Set whether or not we should skip painting @actor. Workaround for
* lack of gjs ability to override _paint vfunc.
*/
void
shell_generic_container_set_skip_paint (ShellGenericContainer *self,
ClutterActor *child,
gboolean skip)
{
gboolean currently_skipping;
currently_skipping = g_hash_table_lookup (self->priv->skip_paint, child) != NULL;
if (!!skip == currently_skipping)
return;
if (!skip)
{
g_object_weak_unref ((GObject*) child, on_skip_paint_weakref, self);
g_hash_table_remove (self->priv->skip_paint, child);
}
else
{
g_object_weak_ref ((GObject*) child, on_skip_paint_weakref, self);
g_hash_table_insert (self->priv->skip_paint, child, child);
}
}
static void
shell_generic_container_dispose (GObject *object)
{
ShellGenericContainer *self = (ShellGenericContainer*) object;
if (self->priv->skip_paint != NULL)
{
g_hash_table_destroy (self->priv->skip_paint);
self->priv->skip_paint = NULL;
}
G_OBJECT_CLASS (shell_generic_container_parent_class)->dispose (object);
}
static void
shell_generic_container_class_init (ShellGenericContainerClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass);
gobject_class->dispose = shell_generic_container_dispose;
actor_class->get_preferred_width = shell_generic_container_get_preferred_width;
actor_class->get_preferred_height = shell_generic_container_get_preferred_height;
actor_class->allocate = shell_generic_container_allocate;
actor_class->paint = shell_generic_container_paint;
actor_class->pick = shell_generic_container_pick;
shell_generic_container_signals[GET_PREFERRED_WIDTH] =
g_signal_new ("get-preferred-width",
@ -227,6 +331,7 @@ shell_generic_container_init (ShellGenericContainer *area)
{
area->priv = G_TYPE_INSTANCE_GET_PRIVATE (area, SHELL_TYPE_GENERIC_CONTAINER,
ShellGenericContainerPrivate);
area->priv->skip_paint = g_hash_table_new_full (NULL, NULL, (GDestroyNotify)g_object_unref, NULL);
}
GType shell_generic_container_allocation_get_type (void)

View File

@ -42,4 +42,8 @@ struct _ShellGenericContainerClass
GType shell_generic_container_get_type (void) G_GNUC_CONST;
void shell_generic_container_set_skip_paint (ShellGenericContainer *container,
ClutterActor *actor,
gboolean skip);
#endif /* __SHELL_GENERIC_CONTAINER_H__ */

View File

@ -522,6 +522,70 @@ shell_global_display_is_grabbed (ShellGlobal *global)
return meta_display_get_grab_op (display) != META_GRAB_OP_NONE;
}
/* Defining this here for now, see
* https://bugzilla.gnome.org/show_bug.cgi?id=604075
* for upstreaming status.
*/
JSContext * gjs_context_get_context (GjsContext *context);
/**
* shell_global_add_extension_importer:
* @target_object_script: JavaScript code evaluating to a target object
* @target_property: Name of property to use for importer
* @directory: Source directory:
* @error: A #GError
*
* This function sets a property named @target_property on the object
* resulting from the evaluation of @target_object_script code, which
* acts as a GJS importer for directory @directory.
*
* Returns: %TRUE on success
*/
gboolean
shell_global_add_extension_importer (ShellGlobal *global,
const char *target_object_script,
const char *target_property,
const char *directory,
GError **error)
{
jsval target_object;
JSObject *importer;
JSContext *context = gjs_context_get_context (global->js_context);
char *search_path[2] = { 0, 0 };
// This is a bit of a hack; ideally we'd be able to pass our target
// object directly into this function, but introspection doesn't
// support that at the moment. Instead evaluate a string to get it.
if (!JS_EvaluateScript(context,
JS_GetGlobalObject(context),
target_object_script,
strlen (target_object_script),
"<target_object_script>",
0,
&target_object))
{
char *message;
gjs_log_exception(context,
&message);
g_set_error(error,
G_IO_ERROR,
G_IO_ERROR_FAILED,
"%s", message ? message : "(unknown)");
g_free(message);
return FALSE;
}
if (!JSVAL_IS_OBJECT (target_object))
{
g_error ("shell_global_add_extension_importer: invalid target object");
return FALSE;
}
search_path[0] = (char*)directory;
importer = gjs_define_importer (context, JSVAL_TO_OBJECT (target_object), target_property, (const char **)search_path, FALSE);
return TRUE;
}
/* Code to close all file descriptors before we exec; copied from gspawn.c in GLib.
*
* Authors: Padraig O'Briain, Matthias Clasen, Lennart Poettering
@ -1064,3 +1128,63 @@ shell_popup_menu (GtkMenu *menu, int button, guint32 time,
gtk_menu_popup (menu, NULL, NULL, shell_popup_menu_position_func, NULL,
button, time);
}
/**
* shell_global_get_current_time:
* @global: A #ShellGlobal
*
* Returns: the current X server time from the current Clutter, Gdk, or X
* event. If called from outside an event handler, this may return
* %Clutter.CURRENT_TIME (aka 0), or it may return a slightly
* out-of-date timestamp.
*/
guint32
shell_global_get_current_time (ShellGlobal *global)
{
guint32 time;
MetaDisplay *display;
/* meta_display_get_current_time() will return the correct time
when handling an X or Gdk event, but will return CurrentTime
from some Clutter event callbacks.
clutter_get_current_event_time() will return the correct time
from a Clutter event callback, but may return an out-of-date
timestamp if called at other times.
So we try meta_display_get_current_time() first, since we
can recognize a "wrong" answer from that, and then fall back
to clutter_get_current_event_time().
*/
display = meta_screen_get_display (shell_global_get_screen (global));
time = meta_display_get_current_time (display);
if (time != CLUTTER_CURRENT_TIME)
return time;
return clutter_get_current_event_time ();
}
/**
* shell_global_get_app_launch_context:
* @global: A #ShellGlobal
*
* Create a #GAppLaunchContext set up with the correct timestamp, and
* targeted to activate on the current workspace.
*
* Return value: A new #GAppLaunchContext
*/
GAppLaunchContext *
shell_global_create_app_launch_context (ShellGlobal *global)
{
GdkAppLaunchContext *context;
context = gdk_app_launch_context_new ();
gdk_app_launch_context_set_timestamp (context, shell_global_get_current_time (global));
// Make sure that the app is opened on the current workspace even if
// the user switches before it starts
gdk_app_launch_context_set_desktop (context, meta_screen_get_active_workspace_index (shell_global_get_screen (global)));
return (GAppLaunchContext *)context;
}

View File

@ -43,6 +43,12 @@ ShellGlobal *shell_global_get (void);
MetaScreen *shell_global_get_screen (ShellGlobal *global);
gboolean shell_global_add_extension_importer (ShellGlobal *global,
const char *target_object_script,
const char *target_property,
const char *directory,
GError **error);
void shell_global_grab_dbus_service (ShellGlobal *global);
typedef enum {
@ -82,6 +88,9 @@ ClutterModifierType shell_get_event_state (ClutterEvent *event);
void shell_popup_menu (GtkMenu *menu, int button, guint32 time,
int menu_x, int menu_y);
guint32 shell_global_get_current_time (ShellGlobal *global);
GAppLaunchContext *shell_global_create_app_launch_context (ShellGlobal *global);
G_END_DECLS

View File

@ -98,10 +98,6 @@ static void
st_button_update_label_style (StButton *button)
{
ClutterActor *label;
StThemeNode *theme_node;
ClutterColor color;
const PangoFontDescription *font;
gchar *font_string = NULL;
label = st_bin_get_child ((StBin*) button);
@ -109,15 +105,7 @@ st_button_update_label_style (StButton *button)
if (!CLUTTER_IS_TEXT (label))
return;
theme_node = st_widget_get_theme_node (ST_WIDGET (button));
st_theme_node_get_foreground_color (theme_node, &color);
clutter_text_set_color (CLUTTER_TEXT (label), &color);
font = st_theme_node_get_font (theme_node);
font_string = pango_font_description_to_string (font);
clutter_text_set_font_name (CLUTTER_TEXT (label), font_string);
g_free (font_string);
_st_set_text_from_style ((ClutterText*) label, st_widget_get_theme_node (ST_WIDGET (button)));
}
static void

View File

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

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;
}
/**
* _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 y_fill);
void _st_set_text_from_style (ClutterText *text,
StThemeNode *theme_node);
#endif /* __ST_PRIVATE_H__ */

View File

@ -1953,7 +1953,10 @@ st_theme_node_get_font (StThemeNode *node)
}
if (family)
pango_font_description_set_family (node->font_desc, family);
{
pango_font_description_set_family (node->font_desc, family);
g_free (family);
}
if (size_set)
pango_font_description_set_absolute_size (node->font_desc, size);

View File

@ -44,6 +44,8 @@
#include <stdlib.h>
#include <string.h>
#include <gio/gio.h>
#include "st-theme-node.h"
#include "st-theme-private.h"
@ -68,6 +70,7 @@ struct _StTheme
char *application_stylesheet;
char *default_stylesheet;
char *theme_stylesheet;
GSList *custom_stylesheets;
GHashTable *stylesheets_by_filename;
GHashTable *filenames_by_stylesheet;
@ -193,24 +196,19 @@ convert_rgba_RGBA (char *buf)
}
static CRStyleSheet *
parse_stylesheet (const char *filename)
parse_stylesheet (const char *filename,
GError **error)
{
enum CRStatus status;
char *contents;
gsize length;
GError *error = NULL;
CRStyleSheet *stylesheet = NULL;
if (filename == NULL)
return NULL;
if (!g_file_get_contents (filename, &contents ,&length, &error))
{
g_warning("Couldn't read stylesheet: %s", error->message);
g_error_free (error);
return NULL;
}
if (!g_file_get_contents (filename, &contents, &length, error))
return NULL;
convert_rgba_RGBA (contents);
@ -218,11 +216,14 @@ parse_stylesheet (const char *filename)
length,
CR_UTF_8,
&stylesheet);
g_free (contents);
if (status != CR_OK)
g_warning ("Error parsing stylesheet '%s'", filename);
g_free (contents);
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Error parsing stylesheet '%s'; errcode:%d", filename, status);
return NULL;
}
return stylesheet;
}
@ -243,7 +244,8 @@ _st_theme_parse_declaration_list (const char *str)
}
#else /* LIBCROCO_VERSION_NUMBER >= 602 */
static CRStyleSheet *
parse_stylesheet (const char *filename)
parse_stylesheet (const char *filename,
GError **error)
{
enum CRStatus status;
CRStyleSheet *stylesheet;
@ -257,7 +259,8 @@ parse_stylesheet (const char *filename)
if (status != CR_OK)
{
g_warning ("Error parsing stylesheet '%s'", filename);
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Error parsing stylesheet '%s'; errcode:%d", filename, status);
return NULL;
}
@ -272,6 +275,22 @@ _st_theme_parse_declaration_list (const char *str)
}
#endif /* LIBCROCO_VERSION_NUMBER < 602 */
/* Just g_warning for now until we have something nicer to do */
static CRStyleSheet *
parse_stylesheet_nofail (const char *filename)
{
GError *error = NULL;
CRStyleSheet *result;
result = parse_stylesheet (filename, &error);
if (error)
{
g_warning ("%s", error->message);
g_clear_error (&error);
}
return result;
}
static void
insert_stylesheet (StTheme *theme,
const char *filename,
@ -289,6 +308,42 @@ insert_stylesheet (StTheme *theme,
g_hash_table_insert (theme->filenames_by_stylesheet, stylesheet, filename_copy);
}
gboolean
st_theme_load_stylesheet (StTheme *theme,
const char *path,
GError **error)
{
CRStyleSheet *stylesheet;
stylesheet = parse_stylesheet (path, error);
if (!stylesheet)
return FALSE;
insert_stylesheet (theme, path, stylesheet);
theme->custom_stylesheets = g_slist_prepend (theme->custom_stylesheets, stylesheet);
return TRUE;
}
void
st_theme_unload_stylesheet (StTheme *theme,
const char *path)
{
CRStyleSheet *stylesheet;
stylesheet = g_hash_table_lookup (theme->stylesheets_by_filename, path);
if (!stylesheet)
return;
if (!g_slist_find (theme->custom_stylesheets, stylesheet))
return;
theme->custom_stylesheets = g_slist_remove (theme->custom_stylesheets, stylesheet);
g_hash_table_remove (theme->stylesheets_by_filename, path);
g_hash_table_remove (theme->filenames_by_stylesheet, stylesheet);
cr_stylesheet_unref (stylesheet);
}
static GObject *
st_theme_constructor (GType type,
guint n_construct_properties,
@ -305,9 +360,9 @@ st_theme_constructor (GType type,
construct_properties);
theme = ST_THEME (object);
application_stylesheet = parse_stylesheet (theme->application_stylesheet);
theme_stylesheet = parse_stylesheet (theme->theme_stylesheet);
default_stylesheet = parse_stylesheet (theme->default_stylesheet);
application_stylesheet = parse_stylesheet_nofail (theme->application_stylesheet);
theme_stylesheet = parse_stylesheet_nofail (theme->theme_stylesheet);
default_stylesheet = parse_stylesheet_nofail (theme->default_stylesheet);
theme->cascade = cr_cascade_new (application_stylesheet,
theme_stylesheet,
@ -328,6 +383,10 @@ st_theme_finalize (GObject * object)
{
StTheme *theme = ST_THEME (object);
g_slist_foreach (theme->custom_stylesheets, (GFunc) cr_stylesheet_unref, NULL);
g_slist_free (theme->custom_stylesheets);
theme->custom_stylesheets = NULL;
g_hash_table_destroy (theme->stylesheets_by_filename);
g_hash_table_destroy (theme->filenames_by_stylesheet);
@ -559,6 +618,7 @@ id_add_sel_matches_style (CRAdditionalSel *a_add_sel,
}
/**
*additional_selector_matches_style:
*Evaluates if a given additional selector matches an style node.
*@param a_add_sel the additional selector to consider.
*@param a_node the style node to consider.
@ -862,7 +922,7 @@ add_matched_properties (StTheme *a_this,
import_rule->url->stryng->str);
if (filename)
import_rule->sheet = parse_stylesheet (filename);
import_rule->sheet = parse_stylesheet (filename, NULL);
if (import_rule->sheet)
{
@ -980,6 +1040,7 @@ _st_theme_get_matched_properties (StTheme *theme,
enum CRStyleOrigin origin = 0;
CRStyleSheet *sheet = NULL;
GPtrArray *props = g_ptr_array_new ();
GSList *iter;
g_return_val_if_fail (ST_IS_THEME (theme), NULL);
g_return_val_if_fail (ST_IS_THEME_NODE (node), NULL);
@ -993,6 +1054,9 @@ _st_theme_get_matched_properties (StTheme *theme,
add_matched_properties (theme, sheet, node, props);
}
for (iter = theme->custom_stylesheets; iter; iter = iter->next)
add_matched_properties (theme, iter->data, node, props);
/* We count on a stable sort here so that later declarations come
* after earlier declarations */
g_ptr_array_sort (props, compare_declarations);

View File

@ -33,6 +33,10 @@ StTheme *st_theme_new (const char *application_stylesheet,
const char *theme_stylesheet,
const char *default_stylesheet);
gboolean st_theme_load_stylesheet (StTheme *theme, const char *path, GError **error);
void st_theme_unload_stylesheet (StTheme *theme, const char *path);
G_END_DECLS
#endif /* __ST_THEME_H__ */

View File

@ -155,7 +155,7 @@ if test x$system = xMandrivaLinux ; then
fi
SOURCE=$HOME/Source
BASEURL=http://git.gnome.org/cgit/gnome-shell/plain/tools/build
BASEURL=http://git.gnome.org/browse/gnome-shell/plain/tools/build
if [ -d $SOURCE ] ; then : ; else
mkdir $SOURCE

View File

@ -18,7 +18,7 @@
# Only rebuild modules that have changed
build_policy = 'updated'
moduleset = 'http://git.gnome.org/cgit/gnome-shell/plain/tools/build/gnome-shell.modules'
moduleset = 'http://git.gnome.org/browse/gnome-shell/plain/tools/build/gnome-shell.modules'
modules = [ 'gnome-shell' ]