mirror of
https://github.com/libretro/scummvm.git
synced 2024-12-05 00:36:57 +00:00
4af395ebb9
No functional change.
1141 lines
35 KiB
C++
1141 lines
35 KiB
C++
/* ScummVM - Graphic Adventure Engine
|
|
*
|
|
* ScummVM is the legal property of its developers, whose names
|
|
* are too numerous to list here. Please refer to the COPYRIGHT
|
|
* file distributed with this source distribution.
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
#include "common/system.h"
|
|
#include "common/file.h"
|
|
#include "common/language.h"
|
|
#include "common/platform.h"
|
|
#include "common/tokenizer.h"
|
|
#include "common/translation.h"
|
|
|
|
#include "gui/gui-manager.h"
|
|
#include "gui/widgets/grid.h"
|
|
|
|
#include "gui/ThemeEval.h"
|
|
|
|
namespace GUI {
|
|
|
|
GridItemWidget::GridItemWidget(GridWidget *boss)
|
|
: ContainerWidget(boss, 0, 0, 0, 0), CommandSender(boss) {
|
|
|
|
setFlags(WIDGET_ENABLED | WIDGET_TRACK_MOUSE | WIDGET_CLEARBG);
|
|
_activeEntry = nullptr;
|
|
_grid = boss;
|
|
_isHighlighted = false;
|
|
}
|
|
|
|
void GridItemWidget::setActiveEntry(GridItemInfo &entry) {
|
|
_activeEntry = &entry;
|
|
}
|
|
|
|
void GridItemWidget::updateThumb() {
|
|
const Graphics::ManagedSurface *gfx = _grid->filenameToSurface(_activeEntry->thumbPath);
|
|
_thumbGfx.free();
|
|
if (gfx)
|
|
_thumbGfx.copyFrom(*gfx);
|
|
}
|
|
|
|
void GridItemWidget::update() {
|
|
if (_activeEntry) {
|
|
updateThumb();
|
|
setTooltip(_activeEntry->description);
|
|
markAsDirty();
|
|
}
|
|
}
|
|
|
|
void GridItemWidget::move(int x, int y) {
|
|
Widget::setPos(getRelX() + x, getRelY() + y);
|
|
}
|
|
|
|
void GridItemWidget::drawWidget() {
|
|
if (_activeEntry->isHeader) {
|
|
const int indicatorSize = MIN(MIN((int)_h, _grid->_gridXSpacing), (int)_w);
|
|
g_gui.theme()->drawFoldIndicator(Common::Rect(_x, _y, _x + indicatorSize, _y + indicatorSize), _grid->groupExpanded(_activeEntry->entryID));
|
|
g_gui.theme()->drawText(Common::Rect(_x + indicatorSize, _y, _x + _w, _y + _h), Common::U32String(_activeEntry->title),
|
|
ThemeEngine::kStateEnabled, Graphics::kTextAlignLeft, ThemeEngine::kTextInversionFocus, _h, false);
|
|
return;
|
|
}
|
|
int thumbHeight = _grid->getThumbnailHeight();
|
|
int thumbWidth = _grid->getThumbnailWidth();
|
|
Common::Array<Common::U32String> titleLines;
|
|
g_gui.getFont().wordWrapText(_activeEntry->title, thumbWidth, titleLines);
|
|
|
|
// FIXME/HACK: We reserve 1/3 of the space between two items to draw the
|
|
// selection border. This can break when the stroke width of
|
|
// the border is comparable to 1/3 of grid item spacing. Also,
|
|
// border shadow is not considered.
|
|
const int kMarginX = _grid->_gridXSpacing / 3;
|
|
const int kMarginY = _grid->_gridYSpacing / 3;
|
|
|
|
if ((_isHighlighted) || (_grid->getSelected() == _activeEntry->entryID)) {
|
|
Common::Rect r(_x - kMarginX, _y - kMarginY,
|
|
_x + _w + kMarginX, _y + _h + kMarginY);
|
|
// Draw a highlighted BG on hover
|
|
g_gui.theme()->drawWidgetBackground(r, ThemeEngine::kGridItemHighlight);
|
|
} else {
|
|
Common::Rect r(_x - 2 * kMarginX, _y - 2 * kMarginY,
|
|
_x + _w + 2 * kMarginX, _y + _h + 2 * kMarginY);
|
|
// Draw a BG of the same color as grid area
|
|
// when it is not highlighted to cover up
|
|
// the highlight shadow
|
|
// FIXME: Find a way to redraw the area around the widget
|
|
// instead of just drawing a cover-up
|
|
g_gui.theme()->drawWidgetBackground(r, ThemeEngine::kGridItemBackground);
|
|
}
|
|
|
|
// Draw Thumbnail Background
|
|
g_gui.theme()->drawWidgetBackground(Common::Rect(_x, _y, _x + thumbWidth, _y + thumbHeight),
|
|
ThemeEngine::kThumbnailBackground);
|
|
|
|
// Draw Thumbnail
|
|
if (_thumbGfx.empty()) {
|
|
// Draw Title when thumbnail is missing
|
|
int linesInThumb = MIN(thumbHeight / kLineHeight, (int)titleLines.size());
|
|
Common::Rect r(_x, _y + (thumbHeight - linesInThumb * kLineHeight) / 2,
|
|
_x + thumbWidth, _y + (thumbHeight - linesInThumb * kLineHeight) / 2 + kLineHeight);
|
|
for (int i = 0; i < linesInThumb; ++i) {
|
|
g_gui.theme()->drawText(r, titleLines[i], ThemeEngine::kStateEnabled,
|
|
Graphics::kTextAlignCenter, ThemeEngine::kTextInversionNone,
|
|
0, true, ThemeEngine::kFontStyleNormal,
|
|
ThemeEngine::kFontColorAlternate, false);
|
|
r.translate(0, kLineHeight);
|
|
}
|
|
} else {
|
|
g_gui.theme()->drawManagedSurface(Common::Point(_x + _grid->_thumbnailMargin, _y + _grid->_thumbnailMargin), _thumbGfx);
|
|
}
|
|
|
|
// Draw Platform Icon
|
|
const Graphics::ManagedSurface *platGfx = _grid->platformToSurface(_activeEntry->platform);
|
|
if (platGfx) {
|
|
Common::Point p(_x + thumbWidth - platGfx->w, _y + thumbHeight - platGfx->h);
|
|
g_gui.theme()->drawManagedSurface(p, *platGfx);
|
|
}
|
|
|
|
// Draw Flag
|
|
const Graphics::ManagedSurface *flagGfx = _grid->languageToSurface(_activeEntry->language);
|
|
if (flagGfx) {
|
|
// SVG and PNG can resize differently so it's better to use thumbWidth as reference to
|
|
// ensure all flags are aligned
|
|
Common::Point p(_x + thumbWidth - (thumbWidth / 5), _y + 5);
|
|
g_gui.theme()->drawManagedSurface(p, *flagGfx);
|
|
}
|
|
|
|
// Draw Demo Overlay
|
|
const Graphics::ManagedSurface *demoGfx = _grid->demoToSurface(_activeEntry->extra);
|
|
if (demoGfx) {
|
|
Common::Point p(_x, _y);
|
|
g_gui.theme()->drawManagedSurface(p, *demoGfx);
|
|
}
|
|
|
|
bool validEntry = _activeEntry->validEntry;
|
|
|
|
// Darken thumbnail if path is unreachable
|
|
if (!validEntry) {
|
|
const Graphics::ManagedSurface *darkenGfx = _grid->disabledThumbnail();
|
|
if (darkenGfx) {
|
|
Common::Point p(_x, _y);
|
|
g_gui.theme()->drawManagedSurface(p, *darkenGfx);
|
|
}
|
|
}
|
|
|
|
// Draw Title
|
|
if (_grid->_isTitlesVisible) {
|
|
// TODO: Currently title is fixed to two lines at all times, we may want
|
|
// flexibility to let the theme define the number of title lines.
|
|
if (titleLines.size() > 2) {
|
|
for (uint k = 0; (k < 3) && (titleLines[1].size() > 0); ++k)
|
|
titleLines[1].deleteLastChar();
|
|
titleLines[1] += Common::U32String("...");
|
|
}
|
|
// Display text in alternate color if the path is unreachable
|
|
// Should be ok using kStateDisabled, but the list widget uses FontColorAlternate so let's stick to that
|
|
GUI::ThemeEngine::FontColor color = validEntry ? GUI::ThemeEngine::kFontColorNormal : GUI::ThemeEngine::kFontColorAlternate;
|
|
Common::Rect r(_x, _y + thumbHeight, _x + thumbWidth, _y + thumbHeight + kLineHeight);
|
|
for (uint k = 0; k < MIN(2U, titleLines.size()); ++k) {
|
|
g_gui.theme()->drawText(r, titleLines[k], GUI::ThemeEngine::kStateEnabled, Graphics::kTextAlignCenter, GUI::ThemeEngine::kTextInversionNone,
|
|
0, true, ThemeEngine::kFontStyleBold, color);
|
|
r.translate(0, kLineHeight);
|
|
}
|
|
}
|
|
}
|
|
|
|
void GridItemWidget::handleMouseWheel(int x, int y, int direction) {
|
|
_grid->handleMouseWheel(x, y, direction);
|
|
_isHighlighted = false;
|
|
}
|
|
|
|
void GridItemWidget::handleMouseEntered(int button) {
|
|
if (!_isHighlighted) {
|
|
_isHighlighted = true;
|
|
markAsDirty();
|
|
}
|
|
}
|
|
|
|
void GridItemWidget::handleMouseLeft(int button) {
|
|
if (_isHighlighted) {
|
|
_isHighlighted = false;
|
|
markAsDirty();
|
|
}
|
|
}
|
|
|
|
void GridItemWidget::handleMouseMoved(int x, int y, int button) {
|
|
if (!_isHighlighted) {
|
|
handleMouseEntered(button);
|
|
}
|
|
}
|
|
|
|
void GridWidget::toggleGroup(int groupID) {
|
|
_groupExpanded[groupID] = !_groupExpanded[groupID];
|
|
sortGroups();
|
|
// TODO: Replace reflowLayout with only the necessary sequence of steps
|
|
reflowLayout();
|
|
}
|
|
|
|
void GridWidget::loadClosedGroups(const Common::U32String &groupName) {
|
|
// Recalls what groups were closed from the config
|
|
if (ConfMan.hasKey("group_" + groupName, ConfMan.kApplicationDomain)) {
|
|
const Common::String &val = ConfMan.get("group_" + groupName, ConfMan.kApplicationDomain);
|
|
Common::StringTokenizer hiddenGroups(val);
|
|
|
|
for (Common::String tok = hiddenGroups.nextToken(); tok.size(); tok = hiddenGroups.nextToken()) {
|
|
// See if the hidden group is in our group headers still, if so, hide it
|
|
for (Common::U32StringArray::size_type i = 0; i < _groupHeaders.size(); ++i) {
|
|
if (_groupHeaders[i] == tok || (tok == "unnamed" && _groupHeaders[i].size() == 0)) {
|
|
_groupExpanded[i] = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
sortGroups();
|
|
// TODO: Replace reflowLayout with only the necessary sequence of steps
|
|
// reflowLayout();
|
|
}
|
|
}
|
|
|
|
void GridWidget::saveClosedGroups(const Common::U32String &groupName) {
|
|
// Save the hidden groups to the config
|
|
Common::String hiddenGroups;
|
|
for (Common::U32StringArray::size_type i = 0; i < _groupHeaders.size(); ++i) {
|
|
if (!_groupExpanded[i]) {
|
|
if (_groupHeaders[i].size()) {
|
|
hiddenGroups += _groupHeaders[i];
|
|
} else {
|
|
hiddenGroups += "unnamed";
|
|
}
|
|
hiddenGroups += ' ';
|
|
}
|
|
}
|
|
ConfMan.set("group_" + groupName, hiddenGroups, ConfMan.kApplicationDomain);
|
|
ConfMan.flushToDisk();
|
|
}
|
|
|
|
void GridItemWidget::handleMouseDown(int x, int y, int button, int clickCount) {
|
|
if (_activeEntry->isHeader) {
|
|
_grid->_selectedEntry = nullptr;
|
|
_grid->toggleGroup(_activeEntry->entryID);
|
|
} else if (_isHighlighted && isVisible()) {
|
|
_grid->_selectedEntry = _activeEntry;
|
|
sendCommand(kItemClicked, _activeEntry->entryID);
|
|
}
|
|
}
|
|
|
|
#pragma mark -
|
|
|
|
GridItemTray::GridItemTray(GuiObject *boss, int x, int y, int w, int h, int entryID, GridWidget *grid)
|
|
: Dialog(x, y, w, h), CommandSender(boss) {
|
|
|
|
_entryID = entryID;
|
|
_boss = boss;
|
|
_grid = grid;
|
|
|
|
// TODO: Currently, the tray has a hardcoded layout. Theme file may
|
|
// provide a different layout of buttons.
|
|
int buttonWidth = w / 3;
|
|
int buttonHeight = h / 3;
|
|
int buttonSpacingX = buttonWidth / 3;
|
|
int buttonSpacingY = buttonHeight / 3;
|
|
int trayPaddingX = buttonSpacingX;
|
|
int trayPaddingY = buttonSpacingY;
|
|
|
|
_playButton = new PicButtonWidget(this, trayPaddingX, trayPaddingY,
|
|
2 * buttonWidth + buttonSpacingX, buttonHeight,
|
|
_("Play"), kPlayButtonCmd);
|
|
_loadButton = new PicButtonWidget(this, trayPaddingX, trayPaddingY + buttonHeight + buttonSpacingY,
|
|
buttonWidth, buttonHeight,
|
|
_("Saves"), kLoadButtonCmd);
|
|
_editButton = new PicButtonWidget(this, trayPaddingX + buttonWidth + buttonSpacingX, trayPaddingY + buttonHeight + buttonSpacingY,
|
|
buttonWidth, buttonHeight,
|
|
_("Edit"), kEditButtonCmd);
|
|
}
|
|
|
|
void GridItemTray::reflowLayout() {
|
|
_playButton->setGfxFromTheme("button_play.bmp", 0, false);
|
|
_loadButton->setGfxFromTheme("button_load.bmp", 0, false);
|
|
_editButton->setGfxFromTheme("button_options.bmp", 0, false);
|
|
}
|
|
|
|
void GridItemTray::handleCommand(CommandSender *sender, uint32 cmd, uint32 data) {
|
|
switch (cmd) {
|
|
case kPlayButtonCmd:
|
|
close();
|
|
sendCommand(kPlayButtonCmd, _entryID);
|
|
break;
|
|
case kLoadButtonCmd:
|
|
close();
|
|
sendCommand(kLoadButtonCmd, _entryID);
|
|
break;
|
|
case kEditButtonCmd:
|
|
close();
|
|
sendCommand(kEditButtonCmd, _entryID);
|
|
break;
|
|
case kCloseCmd:
|
|
close();
|
|
break;
|
|
default:
|
|
Dialog::handleCommand(sender, cmd, 0);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void GridItemTray::handleMouseDown(int x, int y, int button, int clickCount) {
|
|
Dialog::handleMouseDown(x, y, button, clickCount);
|
|
if ((x < 0 || x > _w) || (y > _h || y < -(_grid->_gridItemHeight))) {
|
|
// Close on clicking outside
|
|
close();
|
|
} else if (y < 0 && clickCount >= 2) {
|
|
// Run on double clicking thumbnail
|
|
close();
|
|
sendCommand(kItemDoubleClickedCmd, _entryID);
|
|
}
|
|
}
|
|
|
|
void GridItemTray::handleMouseWheel(int x, int y, int direction) {
|
|
close();
|
|
}
|
|
|
|
void GridItemTray::handleMouseMoved(int x, int y, int button) {
|
|
Dialog::handleMouseMoved(x, y, button);
|
|
if ((x < 0 || x > _w) || (y > _h || y < -(_grid->_gridItemHeight))) {
|
|
// Close on going outside
|
|
close();
|
|
}
|
|
}
|
|
|
|
#pragma mark -
|
|
|
|
// Load an image file by String name, provide additional render dimensions for SVG images.
|
|
// TODO: Add BMP support, and add scaling of non-vector images.
|
|
Graphics::ManagedSurface *loadSurfaceFromFile(const Common::String &name, int renderWidth = 0, int renderHeight = 0) {
|
|
Common::Path path(name);
|
|
Graphics::ManagedSurface *surf = nullptr;
|
|
if (name.hasSuffix(".png")) {
|
|
#ifdef USE_PNG
|
|
const Graphics::Surface *srcSurface = nullptr;
|
|
Image::PNGDecoder decoder;
|
|
g_gui.lockIconsSet();
|
|
if (g_gui.getIconsSet().hasFile(path)) {
|
|
Common::SeekableReadStream *stream = g_gui.getIconsSet().createReadStreamForMember(path);
|
|
if (!decoder.loadStream(*stream)) {
|
|
g_gui.unlockIconsSet();
|
|
warning("Error decoding PNG");
|
|
return surf;
|
|
}
|
|
|
|
srcSurface = decoder.getSurface();
|
|
delete stream;
|
|
if (!srcSurface) {
|
|
warning("Failed to load surface : %s", name.c_str());
|
|
} else if (srcSurface->format.bytesPerPixel != 1) {
|
|
surf = new Graphics::ManagedSurface(srcSurface);
|
|
}
|
|
} else {
|
|
debug(5, "GridWidget: Cannot read file '%s'", name.c_str());
|
|
}
|
|
g_gui.unlockIconsSet();
|
|
#else
|
|
error("No PNG support compiled");
|
|
#endif
|
|
} else if (name.hasSuffix(".svg")) {
|
|
g_gui.lockIconsSet();
|
|
if (g_gui.getIconsSet().hasFile(path)) {
|
|
Common::SeekableReadStream *stream = g_gui.getIconsSet().createReadStreamForMember(path);
|
|
surf = new Graphics::SVGBitmap(stream, renderWidth, renderHeight);
|
|
delete stream;
|
|
} else {
|
|
debug(5, "GridWidget: Cannot read file '%s'", name.c_str());
|
|
}
|
|
g_gui.unlockIconsSet();
|
|
}
|
|
return surf;
|
|
}
|
|
|
|
#pragma mark -
|
|
|
|
GridWidget::GridWidget(GuiObject *boss, const Common::String &name)
|
|
: ContainerWidget(boss, name), CommandSender(boss) {
|
|
|
|
_thumbnailHeight = 0;
|
|
_thumbnailWidth = 0;
|
|
_flagIconHeight = 0;
|
|
_flagIconWidth = 0;
|
|
_platformIconHeight = 0;
|
|
_platformIconWidth = 0;
|
|
_extraIconHeight = 0;
|
|
_extraIconWidth = 0;
|
|
_disabledIconOverlay = nullptr;
|
|
|
|
_minGridXSpacing = 0;
|
|
_minGridYSpacing = 0;
|
|
_isTitlesVisible = 0;
|
|
_scrollBarWidth = 0;
|
|
_thumbnailMargin = 0;
|
|
|
|
_scrollWindowPaddingX = 0;
|
|
_scrollWindowPaddingY = 0;
|
|
|
|
_scrollBar = new ScrollBarWidget(this, _w - _scrollBarWidth, _y, _scrollBarWidth, _y + _h);
|
|
_scrollBar->setTarget(this);
|
|
_scrollPos = 0;
|
|
_scrollSpeed = 1;
|
|
_firstVisibleItem = 0;
|
|
_lastVisibleItem = 0;
|
|
_rows = 0;
|
|
_itemsPerRow = 0;
|
|
|
|
_innerHeight = 0;
|
|
_innerWidth = 0;
|
|
_scrollWindowHeight = 0;
|
|
_scrollWindowWidth = 0;
|
|
_gridYSpacing = 0;
|
|
_gridXSpacing = 0;
|
|
_gridHeaderHeight = kLineHeight;
|
|
_gridHeaderWidth = 0;
|
|
_gridItemHeight = 0;
|
|
_gridItemWidth = 0;
|
|
_trayHeight = kLineHeight * 3;
|
|
|
|
_selectedEntry = nullptr;
|
|
_isGridInvalid = true;
|
|
}
|
|
|
|
GridWidget::~GridWidget() {
|
|
unloadSurfaces(_platformIcons);
|
|
unloadSurfaces(_languageIcons);
|
|
unloadSurfaces(_extraIcons);
|
|
unloadSurfaces(_loadedSurfaces);
|
|
delete _disabledIconOverlay;
|
|
_gridItems.clear();
|
|
_dataEntryList.clear();
|
|
_headerEntryList.clear();
|
|
_sortedEntryList.clear();
|
|
_visibleEntryList.clear();
|
|
}
|
|
|
|
template<typename T>
|
|
void GridWidget::unloadSurfaces(Common::HashMap<T, const Graphics::ManagedSurface *> &surfaces) {
|
|
for (typename Common::HashMap<T, const Graphics::ManagedSurface *>::iterator i = surfaces.begin(); i != surfaces.end(); ++i) {
|
|
delete i->_value;
|
|
}
|
|
surfaces.clear();
|
|
}
|
|
|
|
const Graphics::ManagedSurface *GridWidget::filenameToSurface(const Common::String &name) {
|
|
if (name.empty())
|
|
return nullptr;
|
|
return _loadedSurfaces[name];
|
|
}
|
|
|
|
const Graphics::ManagedSurface *GridWidget::languageToSurface(Common::Language languageCode) {
|
|
if (languageCode == Common::UNK_LANG)
|
|
return nullptr;
|
|
return _languageIcons[languageCode];
|
|
}
|
|
|
|
const Graphics::ManagedSurface *GridWidget::platformToSurface(Common::Platform platformCode) {
|
|
if (platformCode == Common::kPlatformUnknown)
|
|
return nullptr;
|
|
return _platformIcons[platformCode];
|
|
}
|
|
|
|
const Graphics::ManagedSurface *GridWidget::demoToSurface(const Common::String extraString) {
|
|
if (! extraString.contains("Demo") )
|
|
return nullptr;
|
|
return _extraIcons[0];
|
|
}
|
|
|
|
const Graphics::ManagedSurface *GridWidget::disabledThumbnail() {
|
|
return _disabledIconOverlay;
|
|
}
|
|
|
|
void GridWidget::setEntryList(Common::Array<GridItemInfo> *list) {
|
|
_dataEntryList.clear();
|
|
_headerEntryList.clear();
|
|
_sortedEntryList.clear();
|
|
_visibleEntryList.clear();
|
|
_isGridInvalid = true;
|
|
_selectedEntry = nullptr;
|
|
|
|
for (Common::Array<GridItemInfo>::iterator entryIter = list->begin(); entryIter != list->end(); ++entryIter) {
|
|
_dataEntryList.push_back(*entryIter);
|
|
}
|
|
// TODO: Remove this below, add drawWidget(), that should do the drawing
|
|
if (!_gridItems.empty()) {
|
|
reflowLayout();
|
|
}
|
|
}
|
|
|
|
void GridWidget::setAttributeValues(const Common::Array<Common::U32String> &attrs) {
|
|
assert(attrs.size() == _dataEntryList.size());
|
|
for (uint i = 0; i < _dataEntryList.size(); ++i) {
|
|
_dataEntryList[i].attribute = attrs[i];
|
|
}
|
|
}
|
|
|
|
void GridWidget::setMetadataNames(const Common::StringMap &metadata) {
|
|
_metadataNames = metadata;
|
|
}
|
|
|
|
void GridWidget::groupEntries() {
|
|
_groupExpanded.clear();
|
|
_groupHeaders.clear();
|
|
_groupValueIndex.clear();
|
|
_itemsInGroup.clear();
|
|
|
|
for (uint i = 0; i < _dataEntryList.size(); ++i) {
|
|
Common::U32String attrVal = _dataEntryList[i].attribute;
|
|
if (!_groupValueIndex.contains(attrVal)) {
|
|
int newGroupID = _groupValueIndex.size();
|
|
_groupValueIndex.setVal(attrVal, newGroupID);
|
|
_groupHeaders.push_back(attrVal);
|
|
_groupExpanded.push_back(true);
|
|
}
|
|
int groupID = _groupValueIndex.getVal(attrVal);
|
|
|
|
_itemsInGroup[groupID].push_back(i);
|
|
}
|
|
|
|
sortGroups();
|
|
}
|
|
|
|
void GridWidget::sortGroups() {
|
|
uint oldHeight = _innerHeight;
|
|
_sortedEntryList.clear();
|
|
_headerEntryList.clear();
|
|
|
|
if (_filter.empty()) {
|
|
// No filter -> display everything with group headers
|
|
Common::sort(_groupHeaders.begin(), _groupHeaders.end());
|
|
|
|
// Avoid reallocation during iteration: that would invalidate our _sortedEntryList items
|
|
_headerEntryList.reserve(_groupHeaders.size());
|
|
|
|
for (uint i = 0; i != _groupHeaders.size(); ++i) {
|
|
Common::U32String header = _groupHeaders[i];
|
|
Common::U32String displayedHeader;
|
|
if (_metadataNames.contains(header)) {
|
|
displayedHeader = _metadataNames[header];
|
|
} else {
|
|
displayedHeader = header;
|
|
}
|
|
uint groupID = _groupValueIndex[header];
|
|
|
|
// Keep the header in a buffer to be used later
|
|
_headerEntryList.push_back(GridItemInfo(_groupHeaderPrefix + displayedHeader + _groupHeaderSuffix, groupID));
|
|
_sortedEntryList.push_back(&_headerEntryList.back());
|
|
|
|
if (_groupExpanded[groupID]) {
|
|
for (int *k = _itemsInGroup[groupID].begin(); k != _itemsInGroup[groupID].end(); ++k) {
|
|
_sortedEntryList.push_back(&_dataEntryList[*k]);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
// With filter don't display any group header
|
|
// Restrict the list to everything which contains all words in _filter
|
|
// as substrings, ignoring case.
|
|
|
|
Common::U32StringTokenizer tok(_filter);
|
|
Common::U32String tmp;
|
|
int n = 0;
|
|
|
|
_sortedEntryList.clear();
|
|
|
|
for (GridItemInfo *i = _dataEntryList.begin(); i != _dataEntryList.end(); ++i, ++n) {
|
|
tmp = i->title;
|
|
tmp.toLowercase();
|
|
bool matches = true;
|
|
tok.reset();
|
|
while (!tok.empty()) {
|
|
if (!tmp.contains(tok.nextToken())) {
|
|
matches = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (matches) {
|
|
_sortedEntryList.push_back(i);
|
|
}
|
|
}
|
|
}
|
|
|
|
calcEntrySizes();
|
|
calcInnerHeight();
|
|
markGridAsInvalid();
|
|
|
|
scrollBarRecalc();
|
|
|
|
if (calcVisibleEntries()) {
|
|
reloadThumbnails();
|
|
}
|
|
|
|
assignEntriesToItems();
|
|
// FIXME: Temporary solution to clear/display the background ofthe scrollbar when list
|
|
// grows too small or large during group toggle. We shouldn't have to redraw the top dialog,
|
|
// but not doing so the background of scrollbar isn't cleared.
|
|
if ((((uint)_scrollBar->_entriesPerPage < oldHeight) && (_scrollBar->_entriesPerPage > _innerHeight)) ||
|
|
(((uint)_scrollBar->_entriesPerPage > oldHeight) && (_scrollBar->_entriesPerPage < _innerHeight))) {
|
|
g_gui.scheduleTopDialogRedraw();
|
|
} else {
|
|
markAsDirty();
|
|
}
|
|
}
|
|
|
|
// Perform a binary search to find the last element before position yPos in arr.
|
|
int lastItemBeforeY(const Common::Array<GridItemInfo *> &arr, int yPos) {
|
|
// Binary search to find the last element whose y value is less
|
|
// than _scrollPos, i.e., the last item of the topmost visible row.
|
|
int start = 0;
|
|
int end = (int)arr.size() - 1;
|
|
int mid;
|
|
int ans = -1;
|
|
while (start <= end) {
|
|
mid = start + (end - start) / 2;
|
|
if (arr[mid]->y >= yPos) {
|
|
end = mid - 1;
|
|
} else {
|
|
ans = mid;
|
|
start = mid + 1;
|
|
}
|
|
}
|
|
return ans;
|
|
}
|
|
|
|
bool GridWidget::calcVisibleEntries() {
|
|
bool needsReload = false;
|
|
|
|
int nFirstVisibleItem = 0, nLastVisibleItem = 0;
|
|
int temp = lastItemBeforeY(_sortedEntryList, _scrollPos);
|
|
nFirstVisibleItem = temp;
|
|
// We want the leftmost item from the topmost visible row, so we traverse backwards
|
|
while ((nFirstVisibleItem >= 0) &&
|
|
(_sortedEntryList[nFirstVisibleItem]->y == _sortedEntryList[temp]->y)) {
|
|
nFirstVisibleItem--;
|
|
}
|
|
nFirstVisibleItem++;
|
|
nFirstVisibleItem = (nFirstVisibleItem < 0) ? 0 : nFirstVisibleItem;
|
|
|
|
nLastVisibleItem = lastItemBeforeY(_sortedEntryList, _scrollPos + _scrollWindowHeight);
|
|
nLastVisibleItem = (nLastVisibleItem < 0) ? 0 : nLastVisibleItem;
|
|
|
|
if ((nFirstVisibleItem != _firstVisibleItem) || (nLastVisibleItem != _lastVisibleItem) || (_isGridInvalid)) {
|
|
needsReload = true;
|
|
_isGridInvalid = false;
|
|
_lastVisibleItem = nLastVisibleItem;
|
|
_firstVisibleItem = nFirstVisibleItem;
|
|
|
|
int toRender = MIN(_lastVisibleItem + 1, (int)_sortedEntryList.size());
|
|
|
|
_visibleEntryList.clear();
|
|
for (int ind = _firstVisibleItem; ind < toRender; ++ind) {
|
|
_visibleEntryList.push_back(_sortedEntryList[ind]);
|
|
}
|
|
}
|
|
return needsReload;
|
|
}
|
|
|
|
void GridWidget::setTitlesVisible(bool vis) {
|
|
_isTitlesVisible = vis;
|
|
}
|
|
|
|
void GridWidget::setGroupHeaderFormat(const Common::U32String &prefix, const Common::U32String &suffix) {
|
|
_groupHeaderPrefix = prefix;
|
|
_groupHeaderSuffix = suffix;
|
|
}
|
|
|
|
void GridWidget::reloadThumbnails() {
|
|
const int thumbnailWidth = MAX(_thumbnailWidth - 2 * _thumbnailMargin, 0);
|
|
const int thumbnailHeight = MAX(_thumbnailHeight - 2 * _thumbnailMargin, 0);
|
|
for (Common::Array<GridItemInfo *>::iterator iter = _visibleEntryList.begin(); iter != _visibleEntryList.end(); ++iter) {
|
|
GridItemInfo *entry = *iter;
|
|
if (entry->thumbPath.empty())
|
|
continue;
|
|
|
|
if (!_loadedSurfaces.contains(entry->thumbPath)) {
|
|
_loadedSurfaces[entry->thumbPath] = nullptr;
|
|
Common::String path = Common::String::format("icons/%s-%s.png", entry->engineid.c_str(), entry->gameid.c_str());
|
|
Graphics::ManagedSurface *surf = loadSurfaceFromFile(path);
|
|
if (!surf) {
|
|
path = Common::String::format("icons/%s.png", entry->engineid.c_str());
|
|
if (!_loadedSurfaces.contains(path)) {
|
|
surf = loadSurfaceFromFile(path);
|
|
} else {
|
|
const Graphics::ManagedSurface *scSurf = _loadedSurfaces[path];
|
|
_loadedSurfaces[entry->thumbPath] = new Graphics::ManagedSurface(*scSurf);
|
|
}
|
|
}
|
|
|
|
if (surf) {
|
|
const Graphics::ManagedSurface *scSurf(scaleGfx(surf, thumbnailWidth, thumbnailHeight, true));
|
|
_loadedSurfaces[entry->thumbPath] = scSurf;
|
|
|
|
if (path != entry->thumbPath) {
|
|
_loadedSurfaces[path] = new Graphics::ManagedSurface(*scSurf);
|
|
}
|
|
|
|
if (surf != scSurf) {
|
|
surf->free();
|
|
delete surf;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void GridWidget::loadFlagIcons() {
|
|
const Common::LanguageDescription *l = Common::g_languages;
|
|
for (; l->code; ++l) {
|
|
Common::String path = Common::String::format("icons/flags/%s.svg", l->code);
|
|
Graphics::ManagedSurface *gfx = loadSurfaceFromFile(path, _flagIconWidth, _flagIconHeight);
|
|
if (gfx) {
|
|
_languageIcons[l->id] = gfx;
|
|
continue;
|
|
} // if no .svg flag is available, search for a .png
|
|
path = Common::String::format("icons/flags/%s.png", l->code);
|
|
gfx = loadSurfaceFromFile(path);
|
|
if (gfx) {
|
|
const Graphics::ManagedSurface *scGfx = scaleGfx(gfx, _flagIconWidth, _flagIconHeight, true);
|
|
_languageIcons[l->id] = scGfx;
|
|
if (gfx != scGfx) {
|
|
gfx->free();
|
|
delete gfx;
|
|
}
|
|
} else {
|
|
_languageIcons[l->id] = nullptr; // nothing found, set to nullptr
|
|
}
|
|
}
|
|
}
|
|
|
|
void GridWidget::loadPlatformIcons() {
|
|
const Common::PlatformDescription *l = Common::g_platforms;
|
|
for (; l->code; ++l) {
|
|
Common::String path = Common::String::format("icons/platforms/%s.png", l->code);
|
|
Graphics::ManagedSurface *gfx = loadSurfaceFromFile(path);
|
|
if (gfx) {
|
|
const Graphics::ManagedSurface *scGfx = scaleGfx(gfx, _platformIconWidth, _platformIconHeight, true);
|
|
_platformIcons[l->id] = scGfx;
|
|
if (gfx != scGfx) {
|
|
gfx->free();
|
|
delete gfx;
|
|
}
|
|
} else {
|
|
_platformIcons[l->id] = nullptr;
|
|
}
|
|
}
|
|
}
|
|
|
|
void GridWidget::loadExtraIcons() { // for now only the demo icon is available
|
|
Graphics::ManagedSurface *gfx = loadSurfaceFromFile("icons/extra/demo.svg", _extraIconWidth, _extraIconHeight);
|
|
if (gfx) {
|
|
_extraIcons[0] = gfx;
|
|
return;
|
|
} // if no .svg file is available, search for a .png
|
|
gfx = loadSurfaceFromFile("icons/extra/demo.png");
|
|
if (gfx) {
|
|
const Graphics::ManagedSurface *scGfx = scaleGfx(gfx, _extraIconWidth, _extraIconHeight, true);
|
|
_extraIcons[0] = scGfx;
|
|
if (gfx != scGfx) {
|
|
gfx->free();
|
|
delete gfx;
|
|
}
|
|
} else {
|
|
_extraIcons[0] = nullptr;
|
|
}
|
|
}
|
|
|
|
void GridWidget::destroyItems() {
|
|
for (Common::Array<GridItemWidget *>::iterator i = _gridItems.begin(), end = _gridItems.end(); i != end; ++i) {
|
|
removeWidget((*i));
|
|
delete (*i);
|
|
}
|
|
|
|
_gridItems.clear();
|
|
}
|
|
|
|
void GridWidget::move(int x, int y) {
|
|
for (Common::Array<GridItemWidget *>::iterator i = _gridItems.begin(); i != _gridItems.end(); ++i) {
|
|
(*i)->move(x, y);
|
|
}
|
|
}
|
|
|
|
// Scroll to entry id. Optional parameter to decide if the entry should be forced to be on the top, or merely
|
|
// scrolled into view.
|
|
void GridWidget::scrollToEntry(int id, bool forceToTop) {
|
|
int newScrollPos = _scrollPos;
|
|
for (uint i = 0; i < _sortedEntryList.size(); ++i) {
|
|
if ((!_sortedEntryList[i]->isHeader) && (_sortedEntryList[i]->entryID == id)) {
|
|
if (forceToTop) {
|
|
newScrollPos = _sortedEntryList[i]->y + _scrollWindowPaddingY + _gridYSpacing;
|
|
} else {
|
|
if (_sortedEntryList[i]->y < _scrollPos) {
|
|
// Item is above the visible view
|
|
newScrollPos = _sortedEntryList[i]->y - _scrollWindowPaddingY - _gridYSpacing;
|
|
} else if (_sortedEntryList[i]->y > _scrollPos + _scrollWindowHeight - _gridItemHeight - _trayHeight) {
|
|
// Item is below the visible view
|
|
newScrollPos = _sortedEntryList[i]->y - _scrollWindowHeight + _gridItemHeight + _trayHeight;
|
|
} else {
|
|
// Item already in view, do nothing
|
|
newScrollPos = _scrollPos;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
handleCommand(this, kSetPositionCmd, newScrollPos);
|
|
}
|
|
|
|
void GridWidget::updateGrid() {
|
|
for (Common::Array<GridItemWidget *>::iterator i = _gridItems.begin(); i != _gridItems.end(); ++i) {
|
|
(*i)->update();
|
|
}
|
|
}
|
|
|
|
void GridWidget::assignEntriesToItems() {
|
|
// Assign entries from _visibleEntries to each GridItem in _gridItems
|
|
|
|
// In case we have less ContainerWidgets than the number of visible entries
|
|
if (_visibleEntryList.size() > _gridItems.size()) {
|
|
for (uint l = _gridItems.size(); l < _visibleEntryList.size(); ++l) {
|
|
GridItemWidget *newItem = new GridItemWidget(this);
|
|
newItem->setVisible(false);
|
|
newItem->markAsDirty();
|
|
_gridItems.push_back(newItem);
|
|
}
|
|
}
|
|
|
|
for (uint k = 0; k < _gridItems.size(); ++k) {
|
|
GridItemWidget *item = _gridItems[k];
|
|
if (k >= _visibleEntryList.size()) {
|
|
// If we run out of visible entries to display.
|
|
// e.g., scrolled to the very bottom, we make items invisible,
|
|
// and move them out of view to keep them from registering mouse events.
|
|
item->setPos(_scrollWindowWidth, _scrollWindowHeight);
|
|
item->setVisible(false);
|
|
} else {
|
|
// Assign entry and update
|
|
item->setVisible(true);
|
|
GridItemInfo *entry = _visibleEntryList[k];
|
|
item->setActiveEntry(*entry);
|
|
item->setPos(entry->x, entry->y - _scrollPos);
|
|
item->setSize(entry->w, entry->h);
|
|
item->update();
|
|
}
|
|
}
|
|
}
|
|
|
|
int GridWidget::getNextPos(int oldSel) {
|
|
int pos = 0;
|
|
|
|
// Find the next item in the grid
|
|
for (uint i = 0; i < _sortedEntryList.size(); i++) {
|
|
if (_sortedEntryList[i]->entryID == oldSel) {
|
|
return pos;
|
|
} else if (!_sortedEntryList[i]->isHeader) {
|
|
pos++;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
int GridWidget::getNewSel(int index) {
|
|
if (_sortedEntryList.size() == 0) {
|
|
return -1;
|
|
}
|
|
|
|
// Find the index-th item in the grid
|
|
for (uint i = 0; i < _sortedEntryList.size(); i++) {
|
|
if (index == 0 && _sortedEntryList[i]->isHeader == 0) {
|
|
return _sortedEntryList[i]->entryID;
|
|
} else if (_sortedEntryList[i]->isHeader == 0) {
|
|
index--;
|
|
}
|
|
}
|
|
|
|
if (index == 0) {
|
|
return _sortedEntryList[_sortedEntryList.size() - 1]->entryID;
|
|
} else {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
void GridWidget::handleMouseWheel(int x, int y, int direction) {
|
|
_scrollBar->handleMouseWheel(x, y, direction);
|
|
_scrollPos = _scrollBar->_currentPos;
|
|
}
|
|
|
|
void GridWidget::handleCommand(CommandSender *sender, uint32 cmd, uint32 data) {
|
|
// Work in progress
|
|
switch (cmd) {
|
|
case kSetPositionCmd:
|
|
if (_scrollPos != (int)data) {
|
|
_scrollPos = data;
|
|
|
|
if (calcVisibleEntries()) {
|
|
reloadThumbnails();
|
|
}
|
|
|
|
assignEntriesToItems();
|
|
scrollBarRecalc();
|
|
markAsDirty();
|
|
|
|
((GUI::Dialog *)_boss)->setFocusWidget(this);
|
|
}
|
|
break;
|
|
case kItemClicked:
|
|
scrollToEntry(data, false);
|
|
// Redraw the grid, before we open the tray dialog
|
|
draw();
|
|
openTrayAtSelected();
|
|
// fall through
|
|
default:
|
|
sendCommand(cmd, 0);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void GridWidget::calcInnerHeight() {
|
|
int row = 0;
|
|
int col = 0;
|
|
int32 lastRowHeight = 0;
|
|
int32 x = _scrollWindowPaddingX + _gridXSpacing, y = _scrollWindowPaddingY;
|
|
|
|
for (int k = 0; k < (int)_sortedEntryList.size(); ++k) {
|
|
if (_sortedEntryList[k]->isHeader) {
|
|
while (col != 0) {
|
|
if (++col >= _itemsPerRow) {
|
|
col = 0;
|
|
++row;
|
|
y += lastRowHeight;
|
|
lastRowHeight = 0;
|
|
}
|
|
}
|
|
x = _scrollWindowPaddingX;
|
|
_sortedEntryList[k]->x = x;
|
|
_sortedEntryList[k]->y = y;
|
|
x = _scrollWindowPaddingX + _gridXSpacing;
|
|
++row;
|
|
y += _sortedEntryList[k]->h + _gridYSpacing;
|
|
lastRowHeight = 0;
|
|
} else {
|
|
_sortedEntryList[k]->x = x;
|
|
_sortedEntryList[k]->y = y;
|
|
lastRowHeight = MAX(lastRowHeight, _sortedEntryList[k]->h + _gridYSpacing);
|
|
if (++col >= _itemsPerRow) {
|
|
++row;
|
|
y += lastRowHeight;
|
|
lastRowHeight = 0;
|
|
col = 0;
|
|
x = _scrollWindowPaddingX + _gridXSpacing;
|
|
} else {
|
|
x += _sortedEntryList[k]->w + _gridXSpacing;
|
|
}
|
|
}
|
|
}
|
|
|
|
_rows = row;
|
|
|
|
_innerHeight = y + _gridItemHeight + _scrollWindowPaddingY + _trayHeight;
|
|
_innerWidth = 2 * _scrollWindowPaddingX + (_itemsPerRow * (_gridItemWidth + _gridXSpacing) - _gridXSpacing);
|
|
}
|
|
|
|
void GridWidget::calcEntrySizes() {
|
|
_gridHeaderHeight = kLineHeight;
|
|
_gridHeaderWidth = _scrollWindowWidth - _scrollBarWidth - 2 * _scrollWindowPaddingX;
|
|
|
|
for (uint i = 0; i != _sortedEntryList.size(); ++i) {
|
|
GridItemInfo *entry = _sortedEntryList[i];
|
|
if (entry->isHeader) {
|
|
entry->h = _gridHeaderHeight;
|
|
entry->w = _gridHeaderWidth;
|
|
} else {
|
|
int titleRows;
|
|
if (_isTitlesVisible) {
|
|
Common::Array<Common::U32String> titleLines;
|
|
g_gui.getFont().wordWrapText(entry->title, _gridItemWidth, titleLines);
|
|
titleRows = MIN(2U, titleLines.size());
|
|
} else {
|
|
titleRows = 0;
|
|
}
|
|
entry->h = _thumbnailHeight + titleRows * kLineHeight;
|
|
entry->w = _gridItemWidth;
|
|
}
|
|
}
|
|
}
|
|
|
|
void GridWidget::reflowLayout() {
|
|
Widget::reflowLayout();
|
|
destroyItems();
|
|
|
|
// Recompute thumbnail size
|
|
int oldThumbnailHeight = _thumbnailHeight;
|
|
int oldThumbnailWidth = _thumbnailWidth;
|
|
int oldThumbnailMargin = _thumbnailMargin;
|
|
|
|
_scrollWindowHeight = _h;
|
|
_scrollWindowWidth = _w;
|
|
|
|
_itemsPerRow = ConfMan.getInt("grid_items_per_row");
|
|
|
|
_minGridXSpacing = int(g_gui.xmlEval()->getVar("Globals.Grid.XSpacing") * g_gui.getScaleFactor() + .5f);
|
|
_minGridYSpacing = int(g_gui.xmlEval()->getVar("Globals.Grid.YSpacing") * g_gui.getScaleFactor() + .5f);
|
|
_isTitlesVisible = g_gui.xmlEval()->getVar("Globals.Grid.ShowTitles");
|
|
_scrollBarWidth = g_gui.xmlEval()->getVar("Globals.Scrollbar.Width", 0);
|
|
_thumbnailMargin = g_gui.xmlEval()->getVar("Globals.Grid.ThumbnailMargin", 0);
|
|
|
|
_scrollWindowPaddingX = _minGridXSpacing;
|
|
_scrollWindowPaddingY = _minGridYSpacing;
|
|
_gridYSpacing = _minGridYSpacing;
|
|
|
|
int availableWidth = _scrollWindowWidth - (2 * _scrollWindowPaddingX) - _scrollBarWidth;
|
|
_thumbnailWidth = availableWidth / _itemsPerRow - _minGridXSpacing;
|
|
const int minimumthumbnailWidth = int(36 * g_gui.getScaleFactor() + .5f);
|
|
if (_thumbnailWidth < minimumthumbnailWidth) {
|
|
_thumbnailWidth = minimumthumbnailWidth;
|
|
_itemsPerRow = MAX((availableWidth / (_thumbnailWidth + _minGridXSpacing)), 1);
|
|
}
|
|
|
|
_thumbnailHeight = _thumbnailWidth;
|
|
_flagIconWidth = _thumbnailWidth / 4;
|
|
_flagIconHeight = _flagIconWidth / 2;
|
|
_platformIconHeight = _platformIconWidth = _thumbnailWidth / 6;
|
|
_extraIconWidth = _thumbnailWidth;
|
|
_extraIconHeight = _thumbnailHeight;
|
|
|
|
if ((oldThumbnailHeight != _thumbnailHeight) ||
|
|
(oldThumbnailWidth != _thumbnailWidth) ||
|
|
(oldThumbnailMargin != _thumbnailMargin)) {
|
|
unloadSurfaces(_extraIcons);
|
|
unloadSurfaces(_platformIcons);
|
|
unloadSurfaces(_languageIcons);
|
|
unloadSurfaces(_loadedSurfaces);
|
|
if (_disabledIconOverlay)
|
|
_disabledIconOverlay->free();
|
|
reloadThumbnails();
|
|
loadFlagIcons();
|
|
loadPlatformIcons();
|
|
loadExtraIcons();
|
|
|
|
Graphics::ManagedSurface *gfx = new Graphics::ManagedSurface(_thumbnailWidth, _thumbnailHeight, g_system->getOverlayFormat());
|
|
uint32 disabledThumbnailColor = gfx->format.ARGBToColor(153, 0, 0, 0); // 60% opacity black
|
|
gfx->fillRect(Common::Rect(0, 0, _thumbnailWidth, _thumbnailHeight), disabledThumbnailColor);
|
|
if (gfx)
|
|
_disabledIconOverlay = gfx;
|
|
else
|
|
_disabledIconOverlay = nullptr;
|
|
}
|
|
|
|
_trayHeight = kLineHeight * 3;
|
|
_gridItemHeight = _thumbnailHeight + (2 * kLineHeight * _isTitlesVisible);
|
|
_gridItemWidth = _thumbnailWidth;
|
|
|
|
_gridXSpacing = MAX(((_scrollWindowWidth - _scrollBarWidth - (2 * _scrollWindowPaddingX)) - (_itemsPerRow * _gridItemWidth)) / (_itemsPerRow + 1), _minGridXSpacing);
|
|
|
|
calcEntrySizes();
|
|
calcInnerHeight();
|
|
|
|
_scrollBar->resize(_scrollWindowWidth - _scrollBarWidth, 0, _scrollBarWidth, _scrollWindowHeight, false);
|
|
|
|
if (calcVisibleEntries()) {
|
|
reloadThumbnails();
|
|
}
|
|
|
|
assignEntriesToItems();
|
|
if (_selectedEntry) {
|
|
scrollToEntry(_selectedEntry->entryID, false);
|
|
}
|
|
scrollBarRecalc();
|
|
markAsDirty();
|
|
}
|
|
|
|
void GridWidget::openTray(int x, int y, int entryId) {
|
|
GridItemTray *tray = new GridItemTray(this, x - _gridXSpacing / 3, y, _gridItemWidth + 2 * (_gridXSpacing / 3), _trayHeight, entryId, this);
|
|
tray->runModal();
|
|
delete tray;
|
|
}
|
|
|
|
void GridWidget::openTrayAtSelected() {
|
|
if (_selectedEntry) {
|
|
GridItemTray *tray = new GridItemTray(this, _x + _selectedEntry->x - _gridXSpacing / 3, _y + _selectedEntry->y + _selectedEntry->h - _scrollPos,
|
|
_gridItemWidth + 2 * (_gridXSpacing / 3), _trayHeight, _selectedEntry->entryID, this);
|
|
tray->runModal();
|
|
delete tray;
|
|
}
|
|
}
|
|
|
|
void GridWidget::scrollBarRecalc() {
|
|
_scrollBar->_numEntries = _innerHeight;
|
|
_scrollBar->_entriesPerPage = _scrollWindowHeight - 2 * _scrollWindowPaddingY;
|
|
_scrollBar->_currentPos = _scrollPos;
|
|
_scrollBar->_singleStep = kLineHeight;
|
|
|
|
_scrollBar->checkBounds(_scrollBar->_currentPos);
|
|
_scrollPos = _scrollBar->_currentPos;
|
|
_scrollBar->recalc();
|
|
}
|
|
|
|
void GridWidget::setFilter(const Common::U32String &filter) {
|
|
Common::U32String filt = filter;
|
|
filt.toLowercase();
|
|
|
|
if (_filter == filt) // Filter was not changed
|
|
return;
|
|
|
|
_filter = filt;
|
|
|
|
// Reset the scrollbar and deselect everything if filter has changed
|
|
_scrollPos = 0;
|
|
_selectedEntry = nullptr;
|
|
|
|
sortGroups();
|
|
}
|
|
|
|
void GridWidget::setSelected(int id) {
|
|
for (uint i = 0; i < _sortedEntryList.size(); ++i) {
|
|
if ((!_sortedEntryList[i]->isHeader) && (_sortedEntryList[i]->entryID == id)) {
|
|
_selectedEntry = _sortedEntryList[i];
|
|
scrollToEntry(id, false);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
} // End of namespace GUI
|