vdr-plugin-tvguide/recmenu.c
kamel5 eb259fb961 Fixed possible segfault when showing scrollbar in search result lists
A segfault is possible if numSteps is greater than height in
"cRecMenu::createScrollbar()".
2022-04-29 12:18:36 +02:00

702 lines
20 KiB
C

#include <list>
#include "recmenu.h"
// --- cRecMenu -------------------------------------------------------------
cRecMenu::cRecMenu(void) {
border = geoManager.borderRecMenus;
height = 2 * border;
headerHeight = 0;
footerHeight = 0;
currentHeight = 0;
deleteMenuItems = true;
scrollable = false;
scrollbarWidth = 3 * border;
pixmapScrollBar = NULL;
imgScrollBar = NULL;
startIndex = 0;
stopIndex = 0;
numItems = 0;
header = NULL;
footer = NULL;
}
cRecMenu::~cRecMenu(void) {
if (header)
delete header;
ClearMenuItems(true);
if (footer)
delete footer;
if (pixmapScrollBar)
osdManager.releasePixmap(pixmapScrollBar);
if (imgScrollBar)
delete imgScrollBar;
}
void cRecMenu::SetWidthPercent(int percentOSDWidth) {
width = geoManager.osdWidth * percentOSDWidth / 100;
x = (geoManager.osdWidth - width) / 2;
}
void cRecMenu::SetWidthPixel(int pixel) {
width = pixel;
x = (geoManager.osdWidth - width) / 2;
}
int cRecMenu::CalculateOptimalWidth(void) {
int optWidth = 0;
for (std::list<cRecMenuItem*>::iterator item = menuItems.begin(); item != menuItems.end(); item++) {
int itemWidth = (*item)->GetWidth();
if (itemWidth > optWidth)
optWidth = itemWidth;
}
return optWidth;
}
bool cRecMenu::CalculateHeight(bool reDraw) {
int newHeight = 2 * border;
bool returnvalue = false;
if (header) {
newHeight += headerHeight;
}
for (std::list<cRecMenuItem*>::iterator item = menuItems.begin(); item != menuItems.end(); item++) {
newHeight += (*item)->GetHeight();
}
if (footer) {
newHeight += footerHeight;
}
y = (geoManager.osdHeight - newHeight) / 2;
if (newHeight != height) {
height = newHeight;
returnvalue = true;
}
if (scrollable && !reDraw) {
width += scrollbarWidth + border;
returnvalue = true;
}
return returnvalue;
}
void cRecMenu::CreatePixmap(void) {
if (pixmap)
osdManager.releasePixmap(pixmap);
pixmap = osdManager.requestPixmap(3, cRect(x, y, width, height));
if (scrollable) {
int scrollBarX = x + width - scrollbarWidth - border;
int scrollBarY = y + border + headerHeight;
int scrollBarHeight = height - headerHeight - footerHeight - 2 * border;
if (pixmapScrollBar)
osdManager.releasePixmap(pixmapScrollBar);
pixmapScrollBar = osdManager.requestPixmap(4, cRect(scrollBarX, scrollBarY, scrollbarWidth, scrollBarHeight));
} else
pixmapScrollBar = NULL;
}
void cRecMenu::SetHeader(cRecMenuItem *header) {
this->header = header;
headerHeight = header->GetHeight();
height += headerHeight;
}
void cRecMenu::SetFooter(cRecMenuItem *footer) {
this->footer = footer;
footerHeight = footer->GetHeight();
height += footerHeight;
}
void cRecMenu::ClearMenuItems(bool destructor) {
for (std::list<cRecMenuItem*>::iterator it = menuItems.begin(); it != menuItems.end(); it++) {
if (deleteMenuItems)
delete *it;
else if (!destructor)
(*it)->Hide();
}
menuItems.clear();
};
void cRecMenu::InitMenu(bool complete) {
currentHeight = 0;
numItems = 0;
if (scrollable) {
width -= scrollbarWidth + border;
osdManager.releasePixmap(pixmapScrollBar);
pixmapScrollBar = NULL;
DELETENULL(imgScrollBar);
}
osdManager.releasePixmap(pixmap);
pixmap = NULL;
for (std::list<cRecMenuItem*>::iterator it = menuItems.begin(); it != menuItems.end(); it++) {
if (deleteMenuItems)
delete *it;
else
(*it)->Hide();
}
menuItems.clear();
if (complete) {
startIndex = 0;
stopIndex = 0;
scrollable = false;
} else {
stopIndex = startIndex;
}
}
void cRecMenu::AddMenuItem(cRecMenuItem *item, bool inFront) {
item->Show();
if (!inFront)
menuItems.push_back(item);
else
menuItems.push_front(item);
}
bool cRecMenu::AddMenuItemInitial(cRecMenuItem *item, bool inFront) {
currentHeight += item->GetHeight();
int totalHeight = headerHeight + footerHeight + currentHeight + 2*border;
if (totalHeight >= geoManager.osdHeight - 80) {
scrollable = true;
currentHeight -= item->GetHeight();
if (deleteMenuItems) {
delete item;
}
return false;
}
numItems++;
if (!inFront) {
stopIndex++;
menuItems.push_back(item);
} else {
startIndex--;
menuItems.push_front(item);
}
return true;
}
void cRecMenu::Activate(cRecMenuItem *itemOld, cRecMenuItem *item) {
itemOld->setInactive();
itemOld->setBackground();
itemOld->Draw();
item->setActive();
item->setBackground();
item->Draw();
}
bool cRecMenu::ActivatePrev(void) {
cRecMenuItem *activeItem = GetActiveMenuItem();
if (!scrollable && footer && footer->isActive()) {
if (menuItems.size() > 0) {
cRecMenuItem *itemLast = menuItems.back();
Activate(footer, itemLast);
return true;
}
} else if (activeItem) {
cRecMenuItem *prev = NULL;
bool foundActive = false;
for (std::list<cRecMenuItem*>::iterator item = menuItems.end(); item != menuItems.begin(); ) {
item--;
if (*item == activeItem) {
foundActive = true;
continue;
}
if (!foundActive)
continue;
if ((*item)->isSelectable()) {
prev = *item;
break;
}
}
if (prev) {
Activate(activeItem , prev);
return true;
}
}
return false;
}
bool cRecMenu::ScrollUp(void) {
if (footer && footer->isActive()) {
if (menuItems.size() > 0)
Activate(footer, menuItems.back());
} else {
//get perv x items
int numNewItems = numItems / 2;
int numAdded = 0;
cRecMenuItem *newItem = NULL;
while (newItem = GetMenuItem(startIndex-1)) {
AddMenuItem(newItem, true);
cRecMenuItem *last = menuItems.back();
if (deleteMenuItems) {
delete last;
} else {
last->setInactive();
last->Hide();
}
menuItems.pop_back();
stopIndex--;
startIndex--;
numAdded++;
if (numAdded >= numNewItems)
break;
}
if (numAdded != 0) {
scrollable = true;
if (CalculateHeight(true))
CreatePixmap();
Arrange(deleteMenuItems);
Display(deleteMenuItems);
ActivatePrev();
} else {
return false;
}
}
return true;
}
bool cRecMenu::ActivateNext(void) {
cRecMenuItem *activeItem = GetActiveMenuItem();
if (activeItem) {
cRecMenuItem *next = NULL;
bool foundActive = false;
for (std::list<cRecMenuItem*>::iterator item = menuItems.begin(); item != menuItems.end(); item++) {
if (*item == activeItem) {
foundActive = true;
continue;
}
if (!foundActive)
continue;
if ((*item)->isSelectable()) {
next = *item;
break;
}
}
if (next) {
Activate(activeItem , next);
return true;
} else if (!scrollable && footer && footer->isSelectable() && !footer->isActive()) {
Activate(activeItem , footer);
return true;
}
}
return false;
}
bool cRecMenu::ScrollDown(void) {
//get next x items
int numNewItems = numItems / 2;
int numAdded = 0;
cRecMenuItem *newItem = NULL;
while (newItem = GetMenuItem(stopIndex)) {
menuItems.push_back(newItem);
cRecMenuItem *first = menuItems.front();
if (deleteMenuItems) {
delete first;
} else {
first->setInactive();
first->Hide();
}
menuItems.pop_front();
startIndex++;
stopIndex++;
numAdded++;
if (numAdded >= numNewItems)
break;
}
if (numAdded != 0) {
scrollable = true;
if (CalculateHeight(true))
CreatePixmap();
Arrange(deleteMenuItems);
Display(deleteMenuItems);
ActivateNext();
} else {
//last item reached, activate footer if not already active
if ((footer) && !(footer->isActive())) {
cRecMenuItem *activeItem = GetActiveMenuItem();
Activate(activeItem , footer);
} else {
return false;
}
}
return true;
}
void cRecMenu::PageUp(void) {
cRecMenuItem *activeItem = GetActiveMenuItem();
if (!activeItem)
return;
if (!scrollable) {
JumpBegin();
return;
}
if (footer && activeItem == footer) {
Activate(footer, menuItems.front());
return;
}
int newActive = GetActive() - numItems;
if (newActive < 0)
newActive = 0;
activeItem->setInactive();
activeItem->setBackground();
ClearMenuItems();
currentHeight = 0;
stopIndex = startIndex;
numItems = 0;
cRecMenuItem *newItem = NULL;
bool spaceLeft = true;
while (newItem = GetMenuItem(startIndex-1)) {
if (startIndex-1 == newActive)
newItem->setActive();
spaceLeft = AddMenuItemInitial(newItem, true);
if (!spaceLeft)
break;
}
if (spaceLeft) {
while (newItem = GetMenuItem(stopIndex)) {
spaceLeft = AddMenuItemInitial(newItem);
if (!spaceLeft)
break;
}
}
if (GetActive() == numItems)
menuItems.front()->setActive();
if (CalculateHeight(true))
CreatePixmap();
Arrange(deleteMenuItems);
Display(deleteMenuItems);
}
void cRecMenu::PageDown(void) {
cRecMenuItem *activeItem = GetActiveMenuItem();
if (!activeItem)
return;
if (footer && activeItem == footer) {
return;
}
if (!scrollable) {
JumpEnd();
return;
}
int newActive = GetActive() + numItems;
activeItem->setInactive();
activeItem->setBackground();
ClearMenuItems();
currentHeight = 0;
numItems = 0;
startIndex = stopIndex;
cRecMenuItem *newItem = NULL;
bool spaceLeft = true;
while (newItem = GetMenuItem(stopIndex)) {
if (stopIndex == newActive)
newItem->setActive();
spaceLeft = AddMenuItemInitial(newItem);
if (!spaceLeft)
break;
}
if (spaceLeft) {
while (newItem = GetMenuItem(startIndex-1)) {
spaceLeft = AddMenuItemInitial(newItem, true);
if (!spaceLeft)
break;
}
}
if (GetActive() == GetTotalNumMenuItems())
menuItems.back()->setActive();
if (CalculateHeight(true))
CreatePixmap();
Arrange(deleteMenuItems);
Display(deleteMenuItems);
}
void cRecMenu::JumpBegin(void) {
cRecMenuItem *activeItem = GetActiveMenuItem();
if (!scrollable) {
cRecMenuItem *firstSelectable= NULL;
for (std::list<cRecMenuItem*>::iterator item = menuItems.begin(); item != menuItems.end(); item++) {
if ((*item)->isSelectable()) {
firstSelectable = *item;
break;
}
}
if (activeItem && firstSelectable) {
Activate(activeItem , firstSelectable);
}
} else {
activeItem->setInactive();
activeItem->setBackground();
if (footer)
footer->Draw();
ClearMenuItems();
int currentItem = 0;
cRecMenuItem *newItem = NULL;
while (newItem = GetMenuItem(currentItem)) {
AddMenuItem(newItem);
currentItem++;
if (currentItem >= numItems)
break;
}
if (CalculateHeight(true))
CreatePixmap();
Arrange(false);
startIndex = 0;
stopIndex = currentItem;
cRecMenuItem *first = menuItems.front();
first->setActive();
first->setBackground();
first->Draw();
Display(false);
}
}
void cRecMenu::JumpEnd(void) {
cRecMenuItem *activeItem = GetActiveMenuItem();
if (!activeItem)
return;
if (!scrollable) {
cRecMenuItem *lastSelectable= NULL;
if (footer && footer->isSelectable()) {
lastSelectable = footer;
} else {
for (std::list<cRecMenuItem*>::iterator item = menuItems.end(); item != menuItems.begin(); ) {
item--;
if ((*item)->isSelectable()) {
lastSelectable = *item;
break;
}
}
}
if (lastSelectable) {
Activate(activeItem , lastSelectable);
}
} else {
activeItem->setInactive();
activeItem->setBackground();
ClearMenuItems();
int totalNumItems = GetTotalNumMenuItems();
int currentItem = totalNumItems - 1;
int itemsAdded = 0;
cRecMenuItem *newItem = NULL;
while (newItem = GetMenuItem(currentItem)) {
AddMenuItem(newItem, true);
currentItem--;
itemsAdded++;
if (itemsAdded >= numItems)
break;
}
if (CalculateHeight(true))
CreatePixmap();
Arrange(false);
stopIndex = totalNumItems;
startIndex = stopIndex - numItems;
if (footer) {
footer->setActive();
footer->setBackground();
footer->Draw();
} else {
cRecMenuItem *last = menuItems.back();
last->setActive();
last->setBackground();
last->Draw();
}
Display(false);
}
}
void cRecMenu::Arrange(bool scroll) {
int xElement = x + border;
int yElement = y + border;
int widthElement = width - 2 * border;
if (scrollable)
widthElement -= scrollbarWidth + border;
if (header) {
if (!scroll) {
header->SetGeometry(xElement, yElement, widthElement);
header->SetPixmaps();
header->setBackground();
}
yElement += header->GetHeight();
}
for (std::list<cRecMenuItem*>::iterator item = menuItems.begin(); item != menuItems.end(); item++) {
(*item)->SetGeometry(xElement, yElement, widthElement);
(*item)->SetPixmaps();
yElement += (*item)->GetHeight();
}
if (footer && !scroll) {
footer->SetGeometry(xElement, yElement, widthElement);
footer->SetPixmaps();
footer->setBackground();
}
}
void cRecMenu::Display(bool scroll) {
if (config.style == eStyleGraphical) {
drawBackgroundGraphical(bgRecMenuBack);
} else {
pixmap->Fill(theme.Color(clrBackground));
drawBorder();
}
if (header && !scroll) {
header->setBackground();
header->Draw();
}
for (std::list<cRecMenuItem*>::iterator item = menuItems.begin(); item != menuItems.end(); item++) {
(*item)->setBackground();
(*item)->Show();
(*item)->Draw();
}
if (footer && !scroll) {
footer->setBackground();
footer->Draw();
}
if (scrollable)
DrawScrollBar();
}
void cRecMenu::Hide(void) {
pixmap->SetLayer(-1);
if (pixmapScrollBar)
pixmapScrollBar->SetLayer(-1);
if (header)
header->Hide();
if (footer)
footer->Hide();
for (std::list<cRecMenuItem*>::iterator item = menuItems.begin(); item != menuItems.end(); item++) {
(*item)->Hide();
}
}
void cRecMenu::Show(void) {
pixmap->SetLayer(3);
if (pixmapScrollBar)
pixmapScrollBar->SetLayer(3);
if (header)
header->Show();
if (footer)
footer->Show();
for (std::list<cRecMenuItem*>::iterator item = menuItems.begin(); item != menuItems.end(); item++) {
(*item)->Show();
}
}
void cRecMenu::UpdateActiveMenuItem(void) {
cRecMenuItem *activeItem = GetActiveMenuItem();
if (activeItem)
activeItem->Draw();
}
void cRecMenu::DrawScrollBar(void) {
if (!pixmapScrollBar)
return;
pixmapScrollBar->Fill(theme.Color(clrBorder));
pixmapScrollBar->DrawRectangle(cRect(2, 2, pixmapScrollBar->ViewPort().Width() - 4, pixmapScrollBar->ViewPort().Height() - 4), theme.Color(clrBackground));
int totalNumItems = GetTotalNumMenuItems();
if (!totalNumItems)
return;
if (!imgScrollBar) {
int scrollBarImgHeight = (pixmapScrollBar->ViewPort().Height() - 8) * numItems / totalNumItems;
imgScrollBar = createScrollbar(pixmapScrollBar->ViewPort().Width() - 8, scrollBarImgHeight, theme.Color(clrHighlight), theme.Color(clrHighlightBlending));
}
int offset = (pixmapScrollBar->ViewPort().Height() - 8) * startIndex / totalNumItems;
pixmapScrollBar->DrawImage(cPoint(4, 2 + offset), *imgScrollBar);
}
cRecMenuItem *cRecMenu::GetActiveMenuItem(void) {
for (std::list<cRecMenuItem*>::iterator item = menuItems.begin(); item != menuItems.end(); item++) {
if ((*item)->isActive())
return *item;
}
if (footer && footer->isActive())
return footer;
return NULL;
}
cRecMenuItem *cRecMenu::GetMenuItemAt(int num) {
int current = 0;
for (std::list<cRecMenuItem*>::iterator item = menuItems.begin(); item != menuItems.end(); item++) {
if (current == num)
return *item;
current++;
}
return NULL;
}
int cRecMenu::GetActive(void) {
int numActive = startIndex;
for (std::list<cRecMenuItem*>::iterator item = menuItems.begin(); item != menuItems.end(); item++) {
if ((*item)->isActive()) {
break;
}
numActive++;
}
return numActive;
}
eRecMenuState cRecMenu::ProcessKey(eKeys Key) {
cRecMenuItem *activeItem = GetActiveMenuItem();
eRecMenuState state = rmsContinue;
if (!activeItem)
return state;
state = activeItem->ProcessKey(Key);
if (state == rmsRefresh) {
CreateMenuItems();
Display();
} else if (state == rmsNotConsumed) {
switch (Key & ~k_Repeat) {
case kUp:
if (!ActivatePrev())
if (!ScrollUp())
JumpEnd();
state = rmsConsumed;
break;
case kDown:
if (!ActivateNext())
if (!ScrollDown())
JumpBegin();
state = rmsConsumed;
break;
case kLeft:
PageUp();
state = rmsConsumed;
break;
case kRight:
PageDown();
state = rmsConsumed;
break;
default:
break;
}
}
return state;
}
cImage *cRecMenu::createScrollbar(int width, int height, tColor clrBgr, tColor clrBlend) {
cImage *image = new cImage(cSize(width, height));
image->Fill(clrBgr);
if (height >= 32 && config.style != eStyleFlat) {
int numSteps = 64;
int alphaStep = 0x03;
if (height < 100) {
numSteps = 32;
alphaStep = 0x06;
}
int stepY = std::max(1, (int)(0.5 * height / numSteps));
int alpha = 0x40;
tColor clr;
for (int i = 0; i < numSteps; i++) {
clr = AlphaBlend(clrBgr, clrBlend, alpha);
for (int y = i * stepY; y < (i + 1) * stepY; y++) {
for (int x = 0; x < width; x++) {
image->SetPixel(cPoint(x, y), clr);
}
}
alpha += alphaStep;
}
}
return image;
}