Bug 574117 - Allow paging through the application and document results in the overlay

Add a display control that contains page numbers for the result
pages and is shown in the expanded results view. All of the page selection and
switching is handled by the GenericDisplay which exposes a displayControl
actor, which is then displayed by the Sideshow.
This commit is contained in:
Marina Zhurakhinskaya 2009-03-09 16:52:11 -04:00
parent 8b1f732228
commit 5af37f140e
4 changed files with 137 additions and 46 deletions

View File

@ -166,16 +166,15 @@ AppDisplay.prototype = {
// Sets the list of the displayed items based on the list of DEFAULT_APPLICATIONS. // Sets the list of the displayed items based on the list of DEFAULT_APPLICATIONS.
_setDefaultList : function() { _setDefaultList : function() {
this._removeAllDisplayItems(); this._matchedItems = [];
let added = 0; for (let i = 0; i < DEFAULT_APPLICATIONS.length; i++) {
for (let i = 0; i < DEFAULT_APPLICATIONS.length && added < this._maxItems; i++) {
let appId = DEFAULT_APPLICATIONS[i]; let appId = DEFAULT_APPLICATIONS[i];
let appInfo = this._allItems[appId]; let appInfo = this._allItems[appId];
if (appInfo) { if (appInfo) {
this._addDisplayItem(appId); this._matchedItems.push(appId);
added += 1;
} }
} }
this._displayMatchedItems(true);
}, },
// Compares items associated with the item ids based on the alphabetical order // Compares items associated with the item ids based on the alphabetical order

View File

@ -134,8 +134,6 @@ DocDisplay.prototype = {
// Sets the list of the displayed items based on how recently they were last visited. // Sets the list of the displayed items based on how recently they were last visited.
_setDefaultList : function() { _setDefaultList : function() {
this._removeAllDisplayItems();
// It seems to be an implementation detail of the Mozilla JavaScript that object // It seems to be an implementation detail of the Mozilla JavaScript that object
// properties are returned during the iteration in the same order in which they were // properties are returned during the iteration in the same order in which they were
// defined, but it is not a guarantee according to this // defined, but it is not a guarantee according to this
@ -150,21 +148,23 @@ DocDisplay.prototype = {
// them once when they are returned by this._recentManager.get_items() to avoid having to do // them once when they are returned by this._recentManager.get_items() to avoid having to do
// this sorting each time, but the sorting seems to be very fast anyway, so there is no need // this sorting each time, but the sorting seems to be very fast anyway, so there is no need
// to introduce an additional class variable. // to introduce an additional class variable.
let docIds = []; this._matchedItems = [];
let docIdsToRemove = [];
for (docId in this._allItems) { for (docId in this._allItems) {
docIds.push(docId); // this._allItems[docId].exists() checks if the resource still exists
if (this._allItems[docId].exists())
this._matchedItems.push(docId);
else
docIdsToRemove.push(docId);
} }
docIds.sort(Lang.bind(this, function (a,b) { return this._compareItems(a,b); }));
let added = 0; for (docId in docIdsToRemove) {
for (let i = 0; i < docIds.length && added < this._maxItems; i++) { delete this._allItems[docId];
let docInfo = this._allItems[docIds[i]];
// docInfo.exists() checks if the resource still exists
if (docInfo.exists()) {
this._addDisplayItem(docIds[i]);
added += 1;
}
} }
this._matchedItems.sort(Lang.bind(this, function (a,b) { return this._compareItems(a,b); }));
this._displayMatchedItems(true);
}, },
// Compares items associated with the item ids based on how recently the items // Compares items associated with the item ids based on how recently the items

View File

@ -11,6 +11,7 @@ const Shell = imports.gi.Shell;
const Tidy = imports.gi.Tidy; const Tidy = imports.gi.Tidy;
const DND = imports.ui.dnd; const DND = imports.ui.dnd;
const Link = imports.ui.link;
const ITEM_DISPLAY_NAME_COLOR = new Clutter.Color(); const ITEM_DISPLAY_NAME_COLOR = new Clutter.Color();
ITEM_DISPLAY_NAME_COLOR.from_pixel(0xffffffff); ITEM_DISPLAY_NAME_COLOR.from_pixel(0xffffffff);
@ -20,11 +21,14 @@ const ITEM_DISPLAY_BACKGROUND_COLOR = new Clutter.Color();
ITEM_DISPLAY_BACKGROUND_COLOR.from_pixel(0x00000000); ITEM_DISPLAY_BACKGROUND_COLOR.from_pixel(0x00000000);
const ITEM_DISPLAY_SELECTED_BACKGROUND_COLOR = new Clutter.Color(); const ITEM_DISPLAY_SELECTED_BACKGROUND_COLOR = new Clutter.Color();
ITEM_DISPLAY_SELECTED_BACKGROUND_COLOR.from_pixel(0x00ff0055); ITEM_DISPLAY_SELECTED_BACKGROUND_COLOR.from_pixel(0x00ff0055);
const DISPLAY_CONTROL_SELECTED_COLOR = new Clutter.Color();
DISPLAY_CONTROL_SELECTED_COLOR.from_pixel(0x112288ff);
const ITEM_DISPLAY_HEIGHT = 50; const ITEM_DISPLAY_HEIGHT = 50;
const ITEM_DISPLAY_ICON_SIZE = 48; const ITEM_DISPLAY_ICON_SIZE = 48;
const ITEM_DISPLAY_PADDING = 1; const ITEM_DISPLAY_PADDING = 1;
const DEFAULT_COLUMN_GAP = 6; const DEFAULT_COLUMN_GAP = 6;
const LABEL_HEIGHT = 16;
/* This is a virtual class that represents a single display item containing /* This is a virtual class that represents a single display item containing
* a name, a description, and an icon. It allows selecting an item and represents * a name, a description, and an icon. It allows selecting an item and represents
@ -180,21 +184,30 @@ GenericDisplay.prototype = {
if (this._columnGap == null) if (this._columnGap == null)
this._columnGap = DEFAULT_COLUMN_GAP; this._columnGap = DEFAULT_COLUMN_GAP;
this._maxItems = null; this._maxItemsPerPage = null;
this._setDimensionsAndMaxItems(width, height); this._setDimensionsAndMaxItems(width, height);
this._grid = new Tidy.Grid({width: this._width, height: this._height}); this._grid = new Tidy.Grid({width: this._width, height: this._height});
this._grid.column_major = true; this._grid.column_major = true;
this._grid.column_gap = this._columnGap; this._grid.column_gap = this._columnGap;
// map<itemId, Object> where Object represents the item info // map<itemId, Object> where Object represents the item info
this._allItems = {}; this._allItems = {};
// an array of itemIds of items that match the current request
// in the order in which the items should be displayed
this._matchedItems = [];
// map<itemId, GenericDisplayItem> // map<itemId, GenericDisplayItem>
this._displayedItems = {}; this._displayedItems = {};
this._displayedItemsCount = 0; this._displayedItemsCount = 0;
this._pageDisplayed = 0;
// GenericDisplayItem // GenericDisplayItem
this._activatedItem = null; this._activatedItem = null;
this._selectedIndex = -1; this._selectedIndex = -1;
this._keepDisplayCurrent = false; this._keepDisplayCurrent = false;
this.actor = this._grid; this.actor = this._grid;
this.displayControl = new Big.Box({ background_color: ITEM_DISPLAY_BACKGROUND_COLOR,
corner_radius: 4,
height: 24,
spacing: 12,
orientation: Big.BoxOrientation.HORIZONTAL});
}, },
//// Public methods //// //// Public methods ////
@ -291,7 +304,7 @@ GenericDisplay.prototype = {
this._setDimensionsAndMaxItems(width, height); this._setDimensionsAndMaxItems(width, height);
this._grid.width = this._width; this._grid.width = this._width;
this._grid.height = this._height; this._grid.height = this._height;
this._redisplay(); this._displayMatchedItems(true);
}, },
// Updates the displayed items and makes the display actor visible. // Updates the displayed items and makes the display actor visible.
@ -310,6 +323,38 @@ GenericDisplay.prototype = {
//// Protected methods //// //// Protected methods ////
/*
* Displays items that match the current request and should show up on the current page.
* Updates the display control to reflect the matched items set and the page selected.
*
* matchedItemsChanged - indicates if the set of the matched items changed prior to the
* request. If it did, the current page is reset to 0 and the display
* control is updated.
*/
_displayMatchedItems: function(matchedItemsChanged) {
// When generating a new list to display, we first remove all the old
// displayed items which will unset the selection. So we need
// to keep a flag which indicates if this display had the selection.
let hadSelected = this.hasSelected();
if (matchedItemsChanged)
this._pageDisplayed = 0;
this._removeAllDisplayItems();
for (let i = this._maxItemsPerPage * this._pageDisplayed; i < this._matchedItems.length && i < this._maxItemsPerPage * (this._pageDisplayed + 1); i++) {
this._addDisplayItem(this._matchedItems[i]);
}
if (hadSelected) {
this._selectedIndex = -1;
this.selectFirstItem();
}
this._updateDisplayControl(matchedItemsChanged);
},
// Creates a display item based on the information associated with itemId // Creates a display item based on the information associated with itemId
// and adds it to the displayed items. // and adds it to the displayed items.
_addDisplayItem : function(itemId) { _addDisplayItem : function(itemId) {
@ -359,22 +404,12 @@ GenericDisplay.prototype = {
if (!this._keepDisplayCurrent) if (!this._keepDisplayCurrent)
return; return;
// When generating a new list to display, we first remove all the old
// displayed items which will unset the selection. So we need
// to keep a flag which indicates if this display had the selection.
let hadSelected = this.hasSelected();
this._refreshCache(); this._refreshCache();
if (!this._search) if (!this._search)
this._setDefaultList(); this._setDefaultList();
else else
this._doSearchFilter(); this._doSearchFilter();
if (hadSelected) {
this._selectedIndex = -1;
this.selectFirstItem();
}
this.emit('redisplayed'); this.emit('redisplayed');
}, },
@ -412,21 +447,20 @@ GenericDisplay.prototype = {
//// Private methods //// //// Private methods ////
// Sets this._width, this._height, this._columnWidth, and this._maxItems based on the // Sets this._width, this._height, this._columnWidth, and this._maxItemsPerPage based on the
// space available for the display, number of columns, and the number of items it can fit. // space available for the display, number of columns, and the number of items it can fit.
_setDimensionsAndMaxItems: function(width, height) { _setDimensionsAndMaxItems: function(width, height) {
this._width = width; this._width = width;
this._columnWidth = (this._width - this._columnGap * (this._numberOfColumns - 1)) / this._numberOfColumns; this._columnWidth = (this._width - this._columnGap * (this._numberOfColumns - 1)) / this._numberOfColumns;
let maxItemsInColumn = Math.floor(height / ITEM_DISPLAY_HEIGHT); let maxItemsInColumn = Math.floor(height / ITEM_DISPLAY_HEIGHT);
this._maxItems = maxItemsInColumn * this._numberOfColumns; this._maxItemsPerPage = maxItemsInColumn * this._numberOfColumns;
this._height = maxItemsInColumn * ITEM_DISPLAY_HEIGHT; this._height = maxItemsInColumn * ITEM_DISPLAY_HEIGHT;
}, },
// Applies the search string to the list of items to find matches, // Applies the search string to the list of items to find matches,
// and displays up to this._maxItems that matched. // and displays the matching items.
_doSearchFilter: function() { _doSearchFilter: function() {
this._removeAllDisplayItems(); let matchedItemsForSearch = {};
let matchedItems = {};
// Break the search up into terms, and search for each // Break the search up into terms, and search for each
// individual term. Keep track of the number of terms // individual term. Keep track of the number of terms
@ -437,22 +471,22 @@ GenericDisplay.prototype = {
for (itemId in this._allItems) { for (itemId in this._allItems) {
let item = this._allItems[itemId]; let item = this._allItems[itemId];
if (this._isInfoMatching(item, term)) { if (this._isInfoMatching(item, term)) {
let count = matchedItems[itemId]; let count = matchedItemsForSearch[itemId];
if (!count) if (!count)
count = 0; count = 0;
count += 1; count += 1;
matchedItems[itemId] = count; matchedItemsForSearch[itemId] = count;
} }
} }
} }
let matchedList = []; this._matchedItems = [];
for (itemId in matchedItems) { for (itemId in matchedItemsForSearch) {
matchedList.push(itemId); this._matchedItems.push(itemId);
} }
matchedList.sort(Lang.bind(this, function (a, b) { this._matchedItems.sort(Lang.bind(this, function (a, b) {
let countA = matchedItems[a]; let countA = matchedItemsForSearch[a];
let countB = matchedItems[b]; let countB = matchedItemsForSearch[b];
if (countA > countB) if (countA > countB)
return -1; return -1;
else if (countA < countB) else if (countA < countB)
@ -461,8 +495,56 @@ GenericDisplay.prototype = {
return this._compareItems(a, b); return this._compareItems(a, b);
})); }));
for (var i = 0; i < matchedList.length && i < this._maxItems; i++) { this._displayMatchedItems(true);
this._addDisplayItem(matchedList[i]); },
// Displays the page specified by the pageNumber argument. The pageNumber is 0-based.
_displayPage: function(pageNumber) {
this._pageDisplayed = pageNumber;
this._displayMatchedItems(false);
},
/*
* Updates the display control to reflect the matched items set and the page selected.
*
* matchedItemsChanged - indicates if the set of the matched items changed prior to the
* request. If it did, the display control is updated to reflect the
* new set of pages. Otherwise, the page links are updated for the
* current set of pages.
*/
_updateDisplayControl: function(matchedItemsChanged) {
if (matchedItemsChanged) {
this.displayControl.remove_all();
let pageNumber = 0;
for (let i = 0; i < this._matchedItems.length; i = i + this._maxItemsPerPage) {
let pageControl = new Link.Link({ color: (pageNumber == this._pageDisplayed) ? DISPLAY_CONTROL_SELECTED_COLOR : ITEM_DISPLAY_DESCRIPTION_COLOR,
font_name: "Sans Bold 16px",
text: (pageNumber + 1) + "",
height: LABEL_HEIGHT,
reactive: (pageNumber == this._pageDisplayed) ? false : true});
this.displayControl.append(pageControl.actor, Big.BoxPackFlags.NONE);
// we use pageNumberLocalScope to get the page number right in the callback function
let pageNumberLocalScope = pageNumber;
pageControl.connect('clicked',
Lang.bind(this,
function(o, event) {
this._displayPage(pageNumberLocalScope);
}));
pageNumber ++;
}
} else {
let pageControlActors = this.displayControl.get_children();
for (let i = 0; i < pageControlActors.length; i++) {
let pageControlActor = pageControlActors[i];
if (i == this._pageDisplayed) {
pageControlActor.color = DISPLAY_CONTROL_SELECTED_COLOR;
pageControlActor.reactive = false;
} else {
pageControlActor.color = ITEM_DISPLAY_DESCRIPTION_COLOR;
pageControlActor.reactive = true;
}
}
} }
}, },

View File

@ -217,6 +217,9 @@ Sideshow.prototype = {
this._appsSectionDefaultHeight = this._appsSection.height; this._appsSectionDefaultHeight = this._appsSection.height;
this._appsDisplayControlBox = new Big.Box({x_align: Big.BoxAlignment.CENTER});
this._appsDisplayControlBox.append(this._appDisplay.displayControl, Big.BoxPackFlags.NONE);
this._docsSection = new Big.Box({ background_color: OVERLAY_BACKGROUND_COLOR, this._docsSection = new Big.Box({ background_color: OVERLAY_BACKGROUND_COLOR,
x: SIDESHOW_PAD, x: SIDESHOW_PAD,
y: this._appsSection.y + this._appsSection.height, y: this._appsSection.y + this._appsSection.height,
@ -244,6 +247,9 @@ Sideshow.prototype = {
this._docsSectionDefaultHeight = this._docsSection.height; this._docsSectionDefaultHeight = this._docsSection.height;
this._docsDisplayControlBox = new Big.Box({x_align: Big.BoxAlignment.CENTER});
this._docsDisplayControlBox.append(this._docDisplay.displayControl, Big.BoxPackFlags.NONE);
/* Proxy the activated signals */ /* Proxy the activated signals */
this._appDisplay.connect('activated', function(appDisplay) { this._appDisplay.connect('activated', function(appDisplay) {
// we allow clicking on an item to launch it, and this unsets the selection // we allow clicking on an item to launch it, and this unsets the selection
@ -415,9 +421,11 @@ Sideshow.prototype = {
this._itemDisplayHeight + SIDESHOW_SECTION_MISC_HEIGHT, this._itemDisplayHeight + SIDESHOW_SECTION_MISC_HEIGHT,
EXPANDED_SIDESHOW_COLUMNS); EXPANDED_SIDESHOW_COLUMNS);
this._moreAppsLink.setText("Less..."); this._moreAppsLink.setText("Less...");
this._appsSection.insert_after(this._appsDisplayControlBox, this._appDisplay.actor, Big.BoxPackFlags.NONE);
} else { } else {
this._appDisplay.updateDimensions(this._width, this._appsSectionDefaultHeight - SIDESHOW_SECTION_MISC_HEIGHT, SIDESHOW_COLUMNS); this._appDisplay.updateDimensions(this._width, this._appsSectionDefaultHeight - SIDESHOW_SECTION_MISC_HEIGHT, SIDESHOW_COLUMNS);
this._moreAppsLink.setText("More..."); this._moreAppsLink.setText("More...");
this._appsSection.remove_actor(this._appsDisplayControlBox);
} }
this._moreAppsLink.actor.show(); this._moreAppsLink.actor.show();
}, },
@ -525,9 +533,11 @@ Sideshow.prototype = {
this._itemDisplayHeight + SIDESHOW_SECTION_MISC_HEIGHT, this._itemDisplayHeight + SIDESHOW_SECTION_MISC_HEIGHT,
EXPANDED_SIDESHOW_COLUMNS); EXPANDED_SIDESHOW_COLUMNS);
this._moreDocsLink.setText("Less..."); this._moreDocsLink.setText("Less...");
this._docsSection.insert_after(this._docsDisplayControlBox, this._docDisplay.actor, Big.BoxPackFlags.NONE);
} else { } else {
this._docDisplay.updateDimensions(this._width, this._docsSectionDefaultHeight - SIDESHOW_SECTION_MISC_HEIGHT, SIDESHOW_COLUMNS); this._docDisplay.updateDimensions(this._width, this._docsSectionDefaultHeight - SIDESHOW_SECTION_MISC_HEIGHT, SIDESHOW_COLUMNS);
this._moreDocsLink.setText("More..."); this._moreDocsLink.setText("More...");
this._docsSection.remove_actor(this._docsDisplayControlBox);
} }
this._moreDocsLink.actor.show(); this._moreDocsLink.actor.show();
} }