/*************************************************************************
* sbItem.cpp - common item
*
* author: Konstantin Maslyuk "Kalemas" mailto:kalemas@mail.ru
*
* 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 version 2.
*
* 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.
************************************************************************/
#include "sbTheme.h"
#include "sbItem.h"
#pragma warning(disable : 4018) // signed/unsigned mismatch
sbItem::sbItem ( sbItem::TYPE _type_, const TCHAR *_text_, int _width_, long _index_)
: type ( _type_ )
, width ( _width_ )
, index ( _index_ )
, next (NULL)
, prev (NULL)
, left (NULL)
, right (NULL)
{
style = sbTheme::STYLE::TEXT;
highlighted = false;
height = Theme->styles[style].size + Theme->itemMargins.top + Theme->itemMargins.bottom;
expanding = 0;
processed = false;
setText ( _text_ );
}
sbItem::~sbItem()
{
if (next != NULL) next->prev = prev;
if (prev != NULL) prev->next = next;
if (right != NULL) delete right;
}
void sbItem::attach (sbItem * item, sbDirection to)
{
sbAssert (this == item);
if (to == DIRECTION_NEXT)
{
// check space
if (next != NULL)
{
item->prev = this;
item->next = this->next;
next->prev = item;
next = item;
}
else
{
next = item;
item->prev = this;
}
}
else if (to == DIRECTION_PREV)
{
if (prev != NULL)
{
item->next = this;
item->prev = this->prev;
prev->next = item;
prev = item;
}
else
{
prev = item;
item->next = this;
}
}
else if (to == DIRECTION_LEFT)
{
sbAssert(left != NULL);
left = item;
item->right = this;
}
else if (to == DIRECTION_RIGHT)
{
sbAssert(right != NULL);
// place next if line filled
right = item;
item->left = this;
if (height < item->height)
{
sbItem *parse = this;
while(parse != NULL)
{
parse->height = item->height;
parse = parse->left;
}
}
if (height > item->height)
{
item->height = height;
}
}
else
{
sbAssert(true);
}
}
void sbItem::setText ( const TCHAR * newText )
{
int lineWidth = 0 , textWidth = width - Theme->itemMargins.left - Theme->itemMargins.right;
int length = _tcslen( newText );
int minHeight = 0;
bool skip = false;
bool alignCenter = false, alignRight = false; // align works before next formating tag,
bool manualLines = false;
sbFont font;
LINE currentLine;
std::vector underline;
sbAssert( newText == NULL );
if (!lines.empty()) lines.clear();
text = newText;
switch (type)
{
case DO_SEARCH_RANGE_START:
case DO_SEARCH_RANGE_END:
minHeight = Theme->size/6;
manualLines = true;
break;
case LOOK_BANNER:
case TYPE_HISTORY:
manualLines = true;
break;
case LOOK_SEPARATOR:
style = sbTheme::STYLE::DESCRIPTION;
alignCenter = true;
break;
case TYPE_TEXT_FIELD:
style = sbTheme::STYLE::BUTTON;
minHeight = Theme->styles[style].size + Theme->itemMargins.top + Theme->itemMargins.bottom;
break;
case TYPE_BUTTON_SELECTION_MODULE:
case TYPE_BUTTON_SELECTION_BOOK:
case TYPE_BUTTON_SELECTION_CHAPTER:
case TYPE_SWITCH_MODULE_OPTION:
case TYPE_START_SEARCH:
minHeight = Theme->size/6;
break;
case TYPE_BOOKMARK_VERSE_NUMBER:
textWidth = width;
break;
case TYPE_OPEN_MODULE_INSTALLER:
minHeight = Theme->size/4;
break;
}
font = Theme->styles[style].font;
currentLine.style = style;
if (manualLines) textWidth = 1000000;
// TODO use 'const TCHAR *' instead 'i', but for LINE::pos i use displacement
// parse text
for(int i = 0; i 0)
{
// finish current
lines.push_back(currentLine);
currentLine.count = currentLine.width = 0;
}
// and begin new
skip = true;
if (_tcsncmp(newText+i+1,L"verse ",6) == 0) //
{
i += 5;
int state = 0;
for (int ii = i+1; ii < length && newText[ii] != L'>'; i = ii++)
{
if (state == 3 && newText[ii] != L'\"')
{
if (currentLine.count == 0)
{
currentLine.pos = ii;
currentLine.type = LINE::TYPE_VERSE_NUMBER;
currentLine.style = sbTheme::STYLE::STANZA;
}
sbPoint sz = sbGetTextExtent(Theme->styles[currentLine.style].font,c,1);
if (sz.y > currentLine.height)
currentLine.height = (unsigned short) sz.y;
currentLine.width += (unsigned short) sz.x;
//lineWidth += (unsigned short) sz.x;
currentLine.count++;
}
else if (_tcsncmp(newText+ii,L"osisID=\"",8) == 0)
{
ii += 8;
state = 1;
}
else if ((newText[ii] == L'.' || newText[ii] == L'\"') && state > 0)
{
state++;
}
}
lines.push_back(currentLine);
currentLine = LINE(); currentLine.style = style;
}
else if (_tcsncmp(newText+i+1,L"transChange type=\"added\"",24) == 0)
{
i += 23;
currentLine.style = sbTheme::STYLE::TEXT_ITALIC;
}
else if (_tcsncmp(newText+i+1,L"/transChange",12) == 0)
{
i += 11;
currentLine.style = style;
}
else if (_tcsncmp(newText+i+1,L"w ",2) == 0)
{
i += 2;
if ( underline.size() == 0 && lineWidth > 0)
{
underline.push_back(LINE());
underline.back().width = lineWidth;
}
else
{
int width = 0;
for (int i = 0; i < underline.size(); i++) width += underline[i].width;
if (width < lineWidth)
{
underline.push_back(LINE());
underline.back().width = lineWidth - width;
}
}
bool lemma = false;
bool morph = false;
for (int ii = i+1; ii < length && newText[ii] != L'>'; i = ii++)
{
if (_tcsncmp(newText+ii,L"lemma=\"",7) == 0)
{
ii += 7;
lemma = true;
}
else if (_tcsncmp(newText+ii,L"morph=\"",7) == 0)
{
ii += 7;
morph = true;
}
if (_tcsncmp(newText+ii,L"strong:",7) == 0)
{
sbAssert(!lemma);
ii += 7;
for (int e = ii;;e++)
{
if (newText[e] == L' ' || newText[e] == L'\"')
{
underline.push_back(LINE());
underline.back().pos = ii;
underline.back().type = LINE::TYPE_STRONG;
underline.back().count = e-ii;
underline.back().height = Theme->styles[sbTheme::STYLE::TEXT_UNDER].size;
underline.back().style = sbTheme::STYLE::TEXT_UNDER;
underline.back().width = sbGetTextExtent(Theme->styles[sbTheme::STYLE::TEXT_UNDER].font,newText+ii,e-ii).x;
underline.back().width += sbGetTextExtent(Theme->styles[sbTheme::STYLE::TEXT_UNDER].font,L" ",1).x;
ii = e;
break;
}
}
}
else if (_tcsncmp(newText+ii,L"strongMorph:",12) == 0)
{
sbAssert(!morph);
ii += 12;
for (int e = ii;;e++)
{
if (newText[e] == L' ' || newText[e] == L'\"')
{
underline.push_back(LINE());
underline.back().pos = ii;
underline.back().type = LINE::TYPE_MORPH;
underline.back().count = e-ii;
underline.back().height = Theme->styles[sbTheme::STYLE::TEXT_UNDER].size;
underline.back().style = sbTheme::STYLE::TEXT_UNDER;
underline.back().width = sbGetTextExtent(Theme->styles[sbTheme::STYLE::TEXT_UNDER].font,newText+ii,e-ii).x;
underline.back().width += sbGetTextExtent(Theme->styles[sbTheme::STYLE::TEXT_UNDER].font,L" ",1).x;
ii = e;
break;
}
}
}
else if (_tcsncmp(newText+ii,L"robinson:",9) == 0)
{
sbAssert(!morph);
ii += 9;
for (int e = ii;;e++)
{
if (newText[e] == L' ' || newText[e] == L'\"')
{
underline.push_back(LINE());
underline.back().pos = ii;
underline.back().type = LINE::TYPE_MORPH;
underline.back().count = e-ii;
underline.back().height = Theme->styles[sbTheme::STYLE::TEXT_UNDER].size;
underline.back().style = sbTheme::STYLE::TEXT_UNDER;
underline.back().width = sbGetTextExtent(Theme->styles[sbTheme::STYLE::TEXT_UNDER].font,newText+ii,e-ii).x;
underline.back().width += sbGetTextExtent(Theme->styles[sbTheme::STYLE::TEXT_UNDER].font,L" ",1).x;
ii = e;
break;
}
}
}
}
}
else if (_tcsncmp(newText+i+1,L"s ",2) == 0)
{
i += 1;
for (int ii = i+1; ii < length && newText[ii] != L'>'; i = ii++)
{
if (_tcsncmp(newText+ii,L" t=",3) == 0)
{
if (_tcsncmp(newText+ii+3,L"\"6\"",3) == 0) currentLine.style = sbTheme::STYLE::DESCRIPTION;
else if (_tcsncmp(newText+ii+3,L"\"5\"",3) == 0) currentLine.style = sbTheme::STYLE::CAPTION;
else if (_tcsncmp(newText+ii+3,L"\"4\"",3) == 0) currentLine.style = sbTheme::STYLE::STANZA;
else if (_tcsncmp(newText+ii+3,L"\"3\"",3) == 0) currentLine.style = sbTheme::STYLE::BUTTON;
else if (_tcsncmp(newText+ii+3,L"\"1\"",3) == 0) currentLine.style = sbTheme::STYLE::TEXT_ITALIC;
else sbAssert (true);
ii += 5;
}
else if (_tcsncmp(newText+ii,L" a=",3) == 0)
{
if (_tcsncmp(newText+ii+3,L"\"c\"",3) == 0) { alignCenter = true; alignRight = false; }
else if (_tcsncmp(newText+ii+3,L"\"r\"",3) == 0) { alignCenter = false; alignRight = true; }
else if (_tcsncmp(newText+ii+3,L"\"l\"",3) == 0) { alignCenter = false; alignRight = false; }
else sbAssert (true);
ii += 5;
}
}
}
else if (_tcsncmp(newText+i+1,L"/s>",3) == 0)
{
i += 2;
currentLine.style = style;
currentLine.type = LINE::TYPE_NONE;
}
}
else if (*c == L'>')
{
sbAssert (!skip);
skip = false;
}
else if (*c == L'\n')
{
endLine = true;
}
else if (*c == L'\t' && currentLine.count == 0) // fix \t\tstyles[currentLine.style].font,c,1);
// add character
if (currentLine.count == 0) currentLine.pos = i;
if (sz.y > currentLine.height)
currentLine.height = (unsigned short) sz.y;
currentLine.width += (unsigned short) sz.x;
lineWidth += (unsigned short) sz.x;
currentLine.count++;
// check line break at space
if (*c == L' ' && lineWidth + (textWidth/4) > textWidth)
{
// forward processing
for (int ii = 1, skipped = 0, width = 0; i+ii < length && ii < 16; ii++)
{
const TCHAR * nc = newText+i+ii+skipped;
if (*nc == L'<')
{
for (;*(newText+i+ii+skipped) != L'>'; skipped++) ;
continue;
}
if (_tcschr(L" \n",*nc) != NULL) break;
if (lineWidth + width >= textWidth)
{
endLine = true;
break;
}
width += sbGetTextExtent(Theme->styles[currentLine.style].font,nc,1).x;
}
}
if (!endLine)
{
// ask next character
const TCHAR * nc = c+1;
while (*nc == L'<')
{
while (*nc != L'>' && *nc != L'\0') nc++;
nc++;
}
if (lineWidth >= textWidth && !manualLines && _tcschr(L"., !?;:»)",*nc) == NULL)
endLine = true;
}
}
// now process line end only here
if (endLine)
{
if (alignRight || alignCenter)
{
LINE l; l.width = max(0,(textWidth-lineWidth)/(alignRight ? 1 : 2)); l.type = LINE::TYPE_SPACE;
if (l.width > 0)
{
std::vector::reverse_iterator it = lines.rbegin();
while (it != lines.rend() && !(*it).ending) ++it;
lines.insert(it.base(),l);
// TODO work with underline, if necessary
}
}
// TODO compensate at space a very short lines somehow
if (currentLine.count > 0)
{
lines.push_back(currentLine);
lines.back().ending = true;
}
else if (lines.size() > 0)
{
lines.back().ending = true;
}
if (underline.size())
{
for (int i = 0; i < underline.size(); i++) lines.push_back(underline[i]);
underline.clear();
lines.back().ending = true;
}
lineWidth = 0;
currentLine.count = currentLine.width = currentLine.height = 0;
}
}
// calculate height
if (type == sbItem::TYPE_HISTORY)
{
height = Theme->itemMargins.top + Theme->itemMargins.bottom;
height += max (Theme->styles[sbTheme::STYLE::CAPTION].size,Theme->styles[sbTheme::STYLE::TEXT].size+Theme->styles[sbTheme::STYLE::DESCRIPTION].size);
}
else if (type == LOOK_BANNER && lines.size() == 2)
{
height = (Theme->BannerBorderSize * 2) + Theme->styles[lines[0].style].size + Theme->styles[lines[1].style].size;
}
else if (type == DO_SEARCH_RANGE_START || type == DO_SEARCH_RANGE_END)
{
height = Theme->itemMargins.top + Theme->itemMargins.bottom + lines[0].height;
width = Theme->itemMargins.left + Theme->itemMargins.left + lines[0].width;
}
else
{
height = Theme->itemMargins.top + Theme->itemMargins.bottom;
//if (type == TYPE_TEXT_FIELD && lines.size() == 0) height += Theme->styles[style].size;
for (int i=0; idrawElement( surface, &rect, sbTheme::ELEMENT::BANNER );
sbSelectColor(surface, Theme->fadeColor(Theme->elements[sbTheme::ELEMENT::BANNER].textColor,rect.left,rect.top));
sbSelectFont (surface, Theme->styles[lines[0].style].font);
sbDrawText (surface, rect.left + Theme->BannerBorderSize, rect.top + Theme->BannerBorderSize, text.c_str() + lines[0].pos, lines[0].count);
sbSelectFont (surface, Theme->styles[lines[1].style].font);
sbDrawText (surface, rect.left + Theme->BannerBorderSize, rect.top + Theme->BannerBorderSize + lines[0].height, text.c_str() + lines[1].pos, lines[1].count);
}
else if (type == TYPE_HISTORY)
{
sbAssert (lines.size() != 3);
Theme->drawElement( surface, &rect, sbTheme::ELEMENT::ITEM );
sbSelectFont (surface, Theme->styles[lines[0].style].font);
sbSelectColor(surface, Theme->fadeColor(Theme->elements[sbTheme::ELEMENT::ITEM].textColor,rect.left,rect.top));
sbDrawText (surface, rect.left + Theme->itemMargins.left, rect.top + Theme->itemMargins.top, text.c_str() + lines[0].pos, lines[0].count);
sbSelectFont (surface, Theme->styles[lines[1].style].font);
sbDrawText (surface, rect.right - Theme->itemMargins.right - lines[1].width, rect.top + Theme->itemMargins.top, text.c_str() + lines[1].pos, lines[1].count);
sbSelectFont (surface, Theme->styles[lines[2].style].font);
sbDrawText (surface, rect.left + Theme->itemMargins.left, rect.top + Theme->itemMargins.top + lines[0].height, text.c_str() + lines[2].pos, lines[2].count);
}
else if (type > LOOK_MENU && type < LOOK_MENU_COUNT)
{
Theme->drawElement( surface, &rect, sbTheme::ELEMENT::ANOTHER_MENU );
for (int i = 0, lx = 0, ly = rect.top + Theme->itemMargins.top; i < lines.size(); i++)
{
int x = lx + Theme->itemMargins.left + rect.left;
int y = ly;
lines[i].ending ? lx = 0, ly += lines[i].height : lx += lines[i].width;
if (lines[i].count == 0) continue;
sbSelectFont(surface, Theme->styles[lines[i].style].font);
sbSelectColor (surface, Theme->fadeColor(Theme->elements[sbTheme::ELEMENT::ANOTHER_MENU].textColor,x,y));
sbDrawText (surface, x, y, text.c_str() + lines[i].pos, lines[i].count);
}
}
else if (type > LOOK_BUTTON && type < LOOK_BUTTON_COUNT)
{
Theme->drawElement( surface, &rect, sbTheme::ELEMENT::PANEL );
for (int i = 0, lx = 0, ly = rect.top + Theme->itemMargins.top; i < lines.size(); i++)
{
int x = lx + Theme->itemMargins.left + rect.left;
int y = ly;
lines[i].ending ? lx = 0, ly += lines[i].height : lx += lines[i].width;
if (lines[i].count == 0) continue;
sbSelectFont(surface, Theme->styles[lines[i].style].font);
sbSelectColor (surface, Theme->fadeColor(Theme->elements[sbTheme::ELEMENT::PANEL].textColor,x,y));
sbDrawText (surface, x, y, text.c_str() + lines[i].pos, lines[i].count);
}
}
else if (type == LOOK_SEPARATOR)
{
Theme->drawElement( surface, &rect, sbTheme::ELEMENT::ITEM_SEPARATOR );
for (int i = 0, lx = 0, ly = rect.top + Theme->itemMargins.top; i < lines.size(); i++)
{
int x = lx + Theme->itemMargins.left + rect.left;
int y = ly;
lines[i].ending ? lx = 0, ly += lines[i].height : lx += lines[i].width;
if (lines[i].count == 0) continue;
sbSelectFont(surface, Theme->styles[lines[i].style].font);
sbSelectColor (surface, Theme->fadeColor(Theme->elements[sbTheme::ELEMENT::ITEM_SEPARATOR].textColor,x,y));
sbDrawText (surface, x, y, text.c_str() + lines[i].pos, lines[i].count);
}
}
else if (type > LOOK_PANEL && type < LOOK_PANEL_COUNT)
{
// TODO multiline panel
Theme->drawElement( surface, &rect, sbTheme::ELEMENT::PANEL );
for (int i = 0, lx = 0, ly = rect.top + Theme->itemMargins.top; i < lines.size(); i++)
{
int x = lx + Theme->itemMargins.left + rect.left;
int y = ly;
if (lines[i].ending)
{
lx = 0;
ly += lines[i].height;
}
else
lx += lines[i].width;
if (lines[i].count == 0) continue;
sbSelectFont(surface, Theme->styles[lines[i].style].font);
sbSelectColor (surface, Theme->fadeColor(Theme->elements[sbTheme::ELEMENT::PANEL].textColor,x,y));
sbDrawText (surface, x, y, text.c_str() + lines[i].pos, lines[i].count);
}
}
else
{
Theme->drawElement( surface, &rect, highlighted ? sbTheme::ELEMENT::ITEM_HIGHLIGHTED : sbTheme::ELEMENT::ITEM );
if ( lines.size() > 0 )
{
int i = 0;
if ( lines[0].type == LINE::TYPE_VERSE_NUMBER )
{
sbPoint pos (rect.left+((Theme->itemMargins.left-lines[0].width)/2),rect.top);
sbSelectFont(surface, Theme->styles[lines[0].style].font);
sbSelectColor(surface, Theme->fadeColor(Theme->ItemSubTextColor, pos.x, pos.y));
sbDrawText(surface, pos.x, pos.y, text.c_str()+lines[0].pos, lines[0].count);
i++;
}
for (int lx = 0, ly = rect.top + Theme->itemMargins.top; i < lines.size(); i++)
{
int x = lx + Theme->itemMargins.left + rect.left;
int y = ly;
if (lines[i].ending)
{
lx = 0;
ly += lines[i].height;
}
else
lx += lines[i].width;
if (lines[i].count == 0) continue;
if (y + Theme->styles[lines[i].style].size >= rect.top + height) break; // item expansion
if (y < -Theme->styles[lines[i].style].size) continue; // clip top
if (y >= rect.bottom) break; // clip bottom
sbSelectFont(surface, Theme->styles[lines[i].style].font);
switch (lines[i].type)
{
case sbItem::LINE::TYPE_NONE:
sbSelectColor(surface, Theme->fadeColor(Theme->elements[sbTheme::ELEMENT::ITEM].textColor,x,y));
break;
case sbItem::LINE::TYPE_STRONG:
sbSelectColor(surface, Theme->fadeColor(Theme->ItemStrongColor,x,y));
break;
case sbItem::LINE::TYPE_MORPH:
sbSelectColor(surface, Theme->fadeColor(Theme->ItemMorphColor,x,y));
break;
case sbItem::LINE::TYPE_REDWORDS:
sbSelectColor(surface, Theme->fadeColor(Theme->ItemRedColor,x,y));
break;
}
sbDrawText (surface, x, y, text.c_str() + lines[i].pos, lines[i].count);
}
}
}
}
sbItem * sbItem::append ( sbItem::TYPE type, const TCHAR * text, int width, long index )
{
sbItem * n = create (type,text,width,index);
if (this != NULL)
{
sbItem * l = getItem(DIRECTION_NEXT,true);
l->attach(n,DIRECTION_NEXT);
}
return n;
}
sbItem * sbItem::getItem ( TYPE type )
{
sbItem * w = this;
while (w->prev != NULL) w = w->prev;
while (w->type != type && w->next != NULL) w = w->next;
if (w->type == type)
return w;
else
return NULL;
}
sbItem * sbItem::getItem( sbDirection direction, bool last /*= false */ )
{
sbItem * i = this;
switch (direction)
{
case DIRECTION_NEXT:
if (last)
{
while ( i->left != NULL ) i = i->left;
while ( i->next != NULL ) i = i->next;
while ( i->right != NULL ) i = i->right;
return i;
}
else
{
int c = 0;
for ( ; i->left != NULL; c++, i = i->left ) ;
if ((i = i->next) != NULL) for ( ; c > 0 && i != NULL; c--, i = i->right ) ;
return i;
}
default:
sbAssert(true);
}
return i;
}
sbItem * sbItem::create( TYPE type, const TCHAR * text, int width, long index /*= -1 */ )
{
return new sbItem (type,text,width,index);
}