diff --git a/src/libs/gui_tk/Doxyfile b/src/libs/gui_tk/Doxyfile new file mode 100644 index 00000000..598d6fef --- /dev/null +++ b/src/libs/gui_tk/Doxyfile @@ -0,0 +1,238 @@ +# Doxyfile 1.5.2 + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- +DOXYFILE_ENCODING = UTF-8 +PROJECT_NAME = gui::tk +PROJECT_NUMBER = "Version 1.0" +OUTPUT_DIRECTORY = +CREATE_SUBDIRS = NO +OUTPUT_LANGUAGE = English +BRIEF_MEMBER_DESC = YES +REPEAT_BRIEF = YES +ABBREVIATE_BRIEF = "The $name class" \ + "The $name widget" \ + "The $name file" \ + is \ + provides \ + specifies \ + contains \ + represents \ + a \ + an \ + the +ALWAYS_DETAILED_SEC = NO +INLINE_INHERITED_MEMB = NO +FULL_PATH_NAMES = YES +STRIP_FROM_PATH = . +STRIP_FROM_INC_PATH = +SHORT_NAMES = NO +JAVADOC_AUTOBRIEF = NO +MULTILINE_CPP_IS_BRIEF = NO +DETAILS_AT_TOP = YES +INHERIT_DOCS = YES +SEPARATE_MEMBER_PAGES = NO +TAB_SIZE = 8 +ALIASES = +OPTIMIZE_OUTPUT_FOR_C = NO +OPTIMIZE_OUTPUT_JAVA = NO +BUILTIN_STL_SUPPORT = YES +CPP_CLI_SUPPORT = NO +DISTRIBUTE_GROUP_DOC = NO +SUBGROUPING = YES +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- +EXTRACT_ALL = NO +EXTRACT_PRIVATE = NO +EXTRACT_STATIC = NO +EXTRACT_LOCAL_CLASSES = YES +EXTRACT_LOCAL_METHODS = NO +HIDE_UNDOC_MEMBERS = NO +HIDE_UNDOC_CLASSES = NO +HIDE_FRIEND_COMPOUNDS = NO +HIDE_IN_BODY_DOCS = NO +INTERNAL_DOCS = NO +CASE_SENSE_NAMES = YES +HIDE_SCOPE_NAMES = NO +SHOW_INCLUDE_FILES = YES +INLINE_INFO = YES +SORT_MEMBER_DOCS = YES +SORT_BRIEF_DOCS = NO +SORT_BY_SCOPE_NAME = NO +GENERATE_TODOLIST = YES +GENERATE_TESTLIST = YES +GENERATE_BUGLIST = YES +GENERATE_DEPRECATEDLIST= YES +ENABLED_SECTIONS = +MAX_INITIALIZER_LINES = 30 +SHOW_USED_FILES = YES +SHOW_DIRECTORIES = YES +FILE_VERSION_FILTER = +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- +QUIET = NO +WARNINGS = YES +WARN_IF_UNDOCUMENTED = NO +WARN_IF_DOC_ERROR = YES +WARN_NO_PARAMDOC = NO +WARN_FORMAT = "$file:$line: $text" +WARN_LOGFILE = +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- +INPUT = . +INPUT_ENCODING = UTF-8 +FILE_PATTERNS = gui_tk.h \ + gui_tk.cpp +RECURSIVE = NO +EXCLUDE = +EXCLUDE_SYMLINKS = NO +EXCLUDE_PATTERNS = +EXCLUDE_SYMBOLS = +EXAMPLE_PATH = +EXAMPLE_PATTERNS = * +EXAMPLE_RECURSIVE = NO +IMAGE_PATH = +INPUT_FILTER = +FILTER_PATTERNS = +FILTER_SOURCE_FILES = NO +#--------------------------------------------------------------------------- +# configuration options related to source browsing +#--------------------------------------------------------------------------- +SOURCE_BROWSER = YES +INLINE_SOURCES = NO +STRIP_CODE_COMMENTS = YES +REFERENCED_BY_RELATION = YES +REFERENCES_RELATION = YES +REFERENCES_LINK_SOURCE = YES +USE_HTAGS = NO +VERBATIM_HEADERS = YES +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- +ALPHABETICAL_INDEX = NO +COLS_IN_ALPHA_INDEX = 5 +IGNORE_PREFIX = +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- +GENERATE_HTML = YES +HTML_OUTPUT = html +HTML_FILE_EXTENSION = .html +HTML_HEADER = +HTML_FOOTER = +HTML_STYLESHEET = +HTML_ALIGN_MEMBERS = YES +GENERATE_HTMLHELP = NO +CHM_FILE = +HHC_LOCATION = +GENERATE_CHI = NO +BINARY_TOC = NO +TOC_EXPAND = NO +DISABLE_INDEX = NO +ENUM_VALUES_PER_LINE = 4 +GENERATE_TREEVIEW = NO +TREEVIEW_WIDTH = 250 +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- +GENERATE_LATEX = NO +LATEX_OUTPUT = latex +LATEX_CMD_NAME = latex +MAKEINDEX_CMD_NAME = makeindex +COMPACT_LATEX = NO +PAPER_TYPE = a4wide +EXTRA_PACKAGES = +LATEX_HEADER = +PDF_HYPERLINKS = NO +USE_PDFLATEX = NO +LATEX_BATCHMODE = NO +LATEX_HIDE_INDICES = NO +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- +GENERATE_RTF = NO +RTF_OUTPUT = rtf +COMPACT_RTF = NO +RTF_HYPERLINKS = NO +RTF_STYLESHEET_FILE = +RTF_EXTENSIONS_FILE = +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- +GENERATE_MAN = NO +MAN_OUTPUT = man +MAN_EXTENSION = .3 +MAN_LINKS = NO +#--------------------------------------------------------------------------- +# configuration options related to the XML output +#--------------------------------------------------------------------------- +GENERATE_XML = NO +XML_OUTPUT = xml +XML_SCHEMA = +XML_DTD = +XML_PROGRAMLISTING = YES +#--------------------------------------------------------------------------- +# configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- +GENERATE_AUTOGEN_DEF = NO +#--------------------------------------------------------------------------- +# configuration options related to the Perl module output +#--------------------------------------------------------------------------- +GENERATE_PERLMOD = NO +PERLMOD_LATEX = NO +PERLMOD_PRETTY = YES +PERLMOD_MAKEVAR_PREFIX = +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- +ENABLE_PREPROCESSING = YES +MACRO_EXPANSION = NO +EXPAND_ONLY_PREDEF = NO +SEARCH_INCLUDES = YES +INCLUDE_PATH = +INCLUDE_FILE_PATTERNS = +PREDEFINED = TESTING SDL_MAJOR_VERSION +EXPAND_AS_DEFINED = +SKIP_FUNCTION_MACROS = YES +#--------------------------------------------------------------------------- +# Configuration::additions related to external references +#--------------------------------------------------------------------------- +TAGFILES = +GENERATE_TAGFILE = DOSBox.tag +ALLEXTERNALS = NO +EXTERNAL_GROUPS = YES +PERL_PATH = /usr/bin/perl +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- +CLASS_DIAGRAMS = YES +MSCGEN_PATH = +HIDE_UNDOC_RELATIONS = YES +HAVE_DOT = YES +CLASS_GRAPH = YES +COLLABORATION_GRAPH = YES +GROUP_GRAPHS = YES +UML_LOOK = YES +TEMPLATE_RELATIONS = YES +INCLUDE_GRAPH = YES +INCLUDED_BY_GRAPH = YES +CALL_GRAPH = YES +CALLER_GRAPH = YES +GRAPHICAL_HIERARCHY = YES +DIRECTORY_GRAPH = YES +DOT_IMAGE_FORMAT = png +DOT_PATH = +DOTFILE_DIRS = +DOT_GRAPH_MAX_NODES = 50 +DOT_TRANSPARENT = YES +DOT_MULTI_TARGETS = YES +GENERATE_LEGEND = YES +DOT_CLEANUP = YES +#--------------------------------------------------------------------------- +# Configuration::additions related to the search engine +#--------------------------------------------------------------------------- +SEARCHENGINE = NO diff --git a/src/libs/gui_tk/Makefile.am b/src/libs/gui_tk/Makefile.am new file mode 100644 index 00000000..de7764ed --- /dev/null +++ b/src/libs/gui_tk/Makefile.am @@ -0,0 +1,4 @@ +AM_CPPFLAGS = -I$(top_srcdir)/include + +noinst_LIBRARIES = libgui_tk.a +libgui_tk_a_SOURCES = gui_tk.cpp gui_tk.h diff --git a/src/libs/gui_tk/gui_tk.cpp b/src/libs/gui_tk/gui_tk.cpp new file mode 100644 index 00000000..8cbe59d6 --- /dev/null +++ b/src/libs/gui_tk/gui_tk.cpp @@ -0,0 +1,1705 @@ +/* + * gui_tk - framework-agnostic GUI toolkit + * Copyright (C) 2005-2007 Jörg Walter + * + * gui_tk 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. + * + * gui_tk 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 + */ + +/* TODO: + - make menu a bufferedwindow with shadow +*/ +/* $Id: gui_tk.cpp,v 1.1 2007-10-28 16:33:02 qbix79 Exp $ */ + +/** \file + * \brief Implementation file for gui_tk. + * + * It contains implementations for all non-inlined class methods. + * + * Also contained is a small test program that demonstrates all features of + * gui_tk. It is enabled by defining the preprocessor macro TESTING. + */ + +#include +#include "gui_tk.h" + +namespace GUI { + +namespace Color { + +RGB Background3D = 0xffc0c0c0; + +RGB Light3D = 0xfffcfcfc; + +RGB Shadow3D = 0xff808080; + +RGB Border = 0xff000000; + +RGB Text = 0xff000000; + +RGB Background = 0xffc0c0c0; + +RGB SelectionBackground = 0xff000080; + +RGB SelectionForeground = 0xffffffff; + +RGB EditableBackground = 0xffffffff; + +RGB Titlebar = 0xff000080; + +RGB TitlebarText = 0xffffffff; + +} + +void Drawable::drawText(const String& text, bool interpret, Size start, Size len) { + if (interpret) { + if (len > text.size()-start) len = text.size()-start; + len += start; + + Size wordstart = start; + int width = 0; + + while (start < len) { + switch (font->toSpecial(text[start])) { + case Font::CR: + if (wordstart != start) { + drawText(text,false,wordstart,start-wordstart); + wordstart = start; + width = 0; + } + wordstart++; + gotoXY(0,y); + break; + case Font::LF: + if (wordstart != start) { + drawText(text,false,wordstart,start-wordstart); + wordstart = start; + width = 0; + } + wordstart++; + gotoXY(0,y+font->getHeight()); + break; + case Font::BS: + if (wordstart != start) { + drawText(text,false,wordstart,start-wordstart); + wordstart = start; + width = 0; + } + wordstart++; + gotoXY(imax(0,x-font->getWidth()),y); + break; + case Font::Tab: + if (wordstart != start) { + drawText(text,false,wordstart,start-wordstart); + wordstart = start; + width = 0; + } + wordstart++; + gotoXY((((int)(x/font->getWidth()/8))+1)*8*font->getWidth(),y); + break; + case Font::Space: + if (wordstart != start) { + drawText(text,false,wordstart,start-wordstart); + wordstart = start; + width = 0; + } + wordstart++; + font->drawString(this,text,start,1); + break; + case Font::ESC: // ignore ANSI sequences except for colors + if (wordstart != start) { + drawText(text,false,wordstart,start-wordstart); + wordstart = start; + width = 0; + } + wordstart++; + do { + int seqstart = start+1; + Char c; + do { + start++; + wordstart++; + c = font->toSpecial(text[start]); + } while (start < len && ((c >= '0' && c <= '9') || c == ';' || c == '[')); + if (c == 'm' && start < len) { + if (font->toSpecial(text[seqstart++]) != '[') break; + c = font->toSpecial(text[seqstart++]); + while (c != 'm') { + int param = 0; + if (c == ';') c = '0'; + while (c != 'm' && c != ';') { + param = param * 10 + c-'0'; + c = font->toSpecial(text[seqstart++]); + } + const RGB bright = 0x00808080; + const RGB intensity = (color&bright?~0:~bright); + switch (param) { + case 0: setColor(Color::Black); break; + case 1: setColor(color | 0x00808080); break; + case 30: setColor(Color::Black|bright & intensity); break; + case 31: setColor(Color::Red & intensity); break; + case 32: setColor(Color::Green & intensity); break; + case 33: setColor(Color::Yellow & intensity); break; + case 34: setColor(Color::Blue & intensity); break; + case 35: setColor(Color::Magenta & intensity); break; + case 36: setColor(Color::Cyan & intensity); break; + case 37: setColor(Color::White & intensity); break; + default: break; + } + } + } + } while (0); + default: + width += font->getWidth(text[start]); + if (x > 0 && x+width > cw) gotoXY(0,y+font->getHeight()); + } + start++; + } + if (wordstart != start) drawText(text,false,wordstart,start-wordstart); + return; + } + + font->drawString(this,text,start,len); +} + +bool ToplevelWindow::mouseDown(int x, int y, MouseButton button) { + if (button == Left && x > 32 && x < width-6 && y > 4 && y < 31) { + dragx = x; + dragy = y; + mouseChild = NULL; + systemMenu->setVisible(false); + return true; + } else if (button == Left && x < 32 && x > 6 && y > 4 && y < 31) { + mouseChild = NULL; + raise(); + systemMenu->setVisible(!systemMenu->isVisible()); + return true; + } + systemMenu->setVisible(false); + BorderedWindow::mouseDown(x,y,button); + return true; +} + +Drawable::Drawable(int w, int h, RGB clear) : + buffer(new RGB[w*h]), + width(w), height(h), + owner(true), + color(Color::Black), + font(NULL), + lineWidth(1), + tx(0), ty(0), + cx(0), cy(0), + cw(w), ch(h), + x(0), y(0) +{ + this->clear(clear); +} + +Drawable::Drawable(Drawable &src, RGB clear) : + buffer(new RGB[src.cw*src.ch]), + width(src.cw), height(src.ch), + owner(true), + color(src.color), + font(src.font), + lineWidth(src.lineWidth), + tx(0), ty(0), + cx(0), cy(0), + cw(src.cw), ch(src.ch), + x(src.x), y(src.y) +{ + if (clear != 0) { + this->clear(clear); + } else { + for (int h = 0; h < src.ch; h++) { + memcpy(buffer+src.cw*h,src.buffer+src.width*(h+src.ty)+src.tx,4*src.cw); + } + } +} + +Drawable::Drawable(Drawable &src, int x, int y, int w, int h) : + buffer(src.buffer), + width(src.width), height(src.height), + owner(false), + color(src.color), + font(src.font), + lineWidth(src.lineWidth), + tx(src.tx+x), ty(src.ty+y), + cx(imax(imax(-x,src.cx-x),0)), cy(imax(imax(-y,src.cy-y),0)), + cw(imax(0,imin(src.cw-x,w))), ch(imax(0,imin(src.ch-y,h))), + x(imax(0,imin(src.tx-tx,cw))), y(imax(0,imin(src.ty-ty,cw))) +{ +} + +Drawable::~Drawable() +{ + if (owner) delete[] buffer; +} + +void Drawable::clear(RGB clear) +{ + for (int y = cy; y < ch; y++) { + for (int x = cx; x < cw; x++) { + buffer[(y+ty)*width+x+tx] = clear; + } + } +} + +void Drawable::drawLine(int x2, int y2) +{ + int x0 = x2, x1 = x, y0 = y2, y1 = y; + int dx = x2-x1, dy = y2-y1; + drawPixel(); + + if (abs(dx) > abs(dy)) { + if (x1 > x2) { + x = x2; x2 = x1; x1 = x; + y = y2; y2 = y1; y1 = y; + } + for (x = x1; x <= x2; x++) { + y = y1+(x-x1)*dy/dx-lineWidth/2; + for (int i = 0; i < lineWidth; i++, y++) { + drawPixel(); + } + } + } else if (y1 != y2) { + if (y1 > y2) { + x = x2; x2 = x1; x1 = x; + y = y2; y2 = y1; y1 = y; + } + for (y = y1; y <= y2; y ++) { + x = x1+(y-y1)*dx/dy-lineWidth/2; + for (int i = 0; i < lineWidth; i++, x++) { + drawPixel(); + } + } + } + + drawPixel(x0,y0); +} + +void Drawable::drawCircle(int d) { + int xo = 0, yo = d/2, rest = (d+1)/2-yo, x0 = x, y0 = y, rsq = d*d/4, lwo = lineWidth/2; + + while (xo <= yo) { + while (xo*xo+(2*yo-1)*(2*yo-1)/4 > rsq) yo--; + for (int i = 0, yow = yo+lwo; i < lineWidth; i++, yow--) { + drawPixel(x0+xo,y0-yow-rest); + drawPixel(x0+yow,y0-xo-rest); + drawPixel(x0+yow,y0+xo); + drawPixel(x0+xo,y0+yow); + + drawPixel(x0-xo-rest,y0-yow-rest); + drawPixel(x0-yow-rest,y0-xo-rest); + drawPixel(x0-yow-rest,y0+xo); + drawPixel(x0-xo-rest,y0+yow); + } + + xo++; + } + gotoXY(x0,y0); +} + +void Drawable::drawRect(int w, int h) +{ + gotoXY(x-lineWidth/2,y); + drawLine(x+w+lineWidth-1,y); + gotoXY(x-(lineWidth-1)/2,y); + drawLine(x,y+h); + gotoXY(x+(lineWidth-1)/2,y); + drawLine(x-w-lineWidth+1,y); + gotoXY(x+lineWidth/2,y); + drawLine(x,y-h); +} + +void Drawable::fill() +{ + int x0 = x, xmin; + RGB color = getPixel(); + + if (color == this->color) return; + + for (x--; x >= 0 && getPixel() == color; x--) drawPixel(); + xmin = ++x; + for (x = x0; x < cw && getPixel() == color; x++) drawPixel(); + y++; + for (x--; x >= xmin; x--) { + if (getPixel() == color) fill(); + y -= 2; + if (getPixel() == color) fill(); + y += 2; + } + y--; + x = x0; +} + +void Drawable::fillCircle(int d) +{ + int xo = 0, yo = d/2, rest = (d+1)/2-yo, x0 = x, y0 = y, rsq = d*d/4; + + while (xo <= yo) { + while (xo*xo+(2*yo-1)*(2*yo-1)/4 > rsq) yo--; + x = x0+xo; + for (y = y0-yo-rest; y <= y0+yo; y++) drawPixel(); + x = x0-xo-rest; + for (y = y0-yo-rest; y <= y0+yo; y++) drawPixel(); + + y = y0-xo-rest; + for (x = x0-yo-rest; x <= x0+yo; x++) drawPixel(); + y = y0+xo; + for (x = x0-yo-rest; x <= x0+yo; x++) drawPixel(); + + xo++; + } + gotoXY(x0,y0); +} + +void Drawable::fillRect(int w, int h) +{ + int x0 = x, y0 = y, w0 = w; + for (; h > 0; h--, y++) { + for (x = x0, w = w0; w > 0; w--, x++) { + drawPixel(); + } + } + gotoXY(x0,y0); +} + +void Drawable::drawDrawable(Drawable &d, unsigned char alpha) +{ + int scw = d.cw, sch = d.ch, w, h; + RGB *src, *dest; + + for (h = imax(d.cy,-ty-y); h < sch && y+h < ch; h++) { + src = d.buffer+d.width*(h+d.ty)+d.tx; + dest = buffer+width*(y+ty+h)+tx+x; + for (w = imax(d.cx,-tx-x); w < scw && x+w < cw; w++) { + RGB srcb = src[w], destb = dest[w]; + unsigned int sop = Color::A(srcb)*((unsigned int)alpha)/255; + unsigned int rop = Color::A(destb) + sop - Color::A(destb)*sop/255; + if (rop == 0) { + dest[w] = Color::Transparent; + } else { + unsigned int dop = Color::A(destb)*(255-sop)/255; + unsigned int magval = ((destb&Color::MagentaMask)*dop+(srcb&Color::MagentaMask)*sop); + dest[w] = (((magval&0xffff)/rop)&Color::BlueMask) | + (((magval&0xffff0000)/rop)&Color::RedMask) | + ((((destb&Color::GreenMask)*dop+(srcb&Color::GreenMask)*sop)/rop)&Color::GreenMask) | + (rop<toSpecial(c)) { + case Font::CR: gotoXY(0,y); return; + case Font::LF: gotoXY(0,y+font->getHeight()); return; + case Font::BS: gotoXY(imax(0,x-font->getWidth()),y); return; + case Font::Tab: gotoXY((((int)(x/font->getWidth()/8))+1)*8*font->getWidth(),y); return; + default: break; + } + if (font->getWidth(c)+x > cw) gotoXY(0,y+font->getHeight()); + } + font->drawChar(this,c); +} + +#define move(x) (ptr += ((x)+bit)/8-(((x)+bit)<0), bit = ((x)+bit+(((x)+bit)<0?8:0))%8) +void BitmapFont::drawChar(Drawable *d, const Char c) const { + const unsigned char *ptr = bitmap; + int bit = 0; + + if (c > last) return; + + if (char_position != NULL) { + ptr = char_position[c]; + bit = 0; + } else { + move(character_step*c); + } + + int rs = row_step; + int w = (widths != NULL?widths[c]:width); + int h = (ascents != NULL?ascents[c]:height); + Drawable out(*d,d->getX(),d->getY()-ascent,w,h); + + if (rs == 0) rs = isign(col_step)*w; + if (rs < 0) move(-rs*(h-1)); + if (col_step < 0) move(abs(rs)-1); + + for (int row = height-h; row < height; row++, move(rs-w*col_step)) { + for (int col = 0; col < w; col++, move(col_step)) { + if (!background_set ^ !(*ptr&(1<gotoXY(d->getX()+w,d->getY()); +} +#undef move + +std::map Font::registry; + +void Timer::check(unsigned int ticks) +{ + if (timers.empty()) return; + + if (Timer::ticks > (Timer::ticks+ticks)) { + ticks -= -1-Timer::ticks; + check(-1-Timer::ticks); + } + + std::multimap::iterator old, i = timers.lower_bound(Timer::ticks+1); + Timer::ticks += ticks; + + while (i != timers.end() && (*i).first <= Timer::ticks) { + Timer_Callback *c = (*i).second; + unsigned int time = (*i).first; + old = i; + ++i; + timers.erase(old); + unsigned int next = c->timerExpired(time); + if (next) add(c, time+next-Timer::ticks); + } +} + +void Timer::remove(const Timer_Callback *const timer) +{ + if (timers.empty()) return; + + std::multimap::iterator old, i = timers.begin(); + + while (i != timers.end()) { + old = i; + ++i; + if ((*old).second == timer) timers.erase(old); + } +} + +unsigned int Timer::next() +{ + if (timers.empty()) return 0; + + std::multimap::iterator i = timers.upper_bound(ticks); + + if (i == timers.end()) return 0; + return (*i).first-Timer::ticks; +} + +std::multimap Timer::timers; +unsigned int Timer::ticks = 0; + +BitmapFont::BitmapFont(const unsigned char *data, int height, int ascent, bool owner, + int width, bool background_set, + int col_step, int row_step, int character_step, Char last, + const int *widths, const int *ascents, const unsigned char *const* char_position, + const Font::SpecialChar *special) : + bitmap(data), + width(width), height(height), ascent(ascent), widths(widths), ascents(ascents), + background_set(background_set), col_step(col_step), row_step(row_step), + character_step(character_step?character_step:abs((row_step?row_step:width*col_step)*height)), + char_position(char_position), special(special), owner(owner), last(last) +{ +} + +BitmapFont::~BitmapFont() { + if (owner) { + if (bitmap != NULL) delete bitmap; + if (ascents != NULL) delete ascents; + if (widths != NULL) delete widths; + if (special != NULL) delete special; + } +} + +Window::Window(Window *parent, int x, int y, int w, int h) : + width(w), height(h), + x(x), y(y), + dirty(true), + visible(true), + parent(parent), + mouseChild(NULL) +{ + parent->addChild(this); +} + +Window::Window() : + width(0), height(0), + x(0), y(0), + dirty(false), + visible(true), + parent(NULL), + mouseChild(NULL) +{ +} + + +Window::~Window() +{ + while (!children.empty()) delete children.front(); + if (parent) parent->removeChild(this); + if (parent && parent->mouseChild == this) parent->mouseChild = NULL; +} + +void Window::addChild(Window *child) +{ + children.push_back(child); + setDirty(); +} + +void Window::removeChild(Window *child) +{ + children.remove(child); + setDirty(); +} + +void Window::move(int x, int y) +{ + this->x = x; + this->y = y; + std::list::iterator i = movehandlers.begin(); + bool end = (i == movehandlers.end()); + while (!end) { + Window_Callback *c = *i; + ++i; + end = (i == movehandlers.end()); + c->windowMoved(this,x,y); + } + parent->setDirty(); +} + +void Window::resize(int w, int h) +{ + this->width = w; + this->height = h; + setDirty(); +} + +void Window::paintAll(Drawable &d) const +{ + paint(d); + std::list::const_iterator i = children.begin(); + while (i != children.end()) { + Window *child = *i; + ++i; + if (child->visible) { + Drawable *cd = new Drawable(d,child->x,child->y,child->width,child->height); + child->paintAll(*cd); + delete cd; + } + } +} + +bool Window::keyDown(const Key &key) +{ + if (children.empty()) return false; + if ((*children.rbegin())->keyDown(key)) return true; + if (key.ctrl || key.alt || key.windows || key.special != Key::Tab) return false; + + if (key.shift) { + std::list::reverse_iterator i = children.rbegin(), e = children.rend(); + ++i; + while (i != e && !(*i)->raise()) ++i; + return i != e; + } else { + std::list::iterator i = children.begin(), e = children.end(); + while (i != e && !(*i)->raise()) ++i; + return (i != e); + } +} + +bool Window::keyUp(const Key &key) +{ + if (children.empty()) return false; + return (*children.rbegin())->keyUp(key); +} + +bool Window::mouseMoved(int x, int y) +{ + std::list::reverse_iterator i = children.rbegin(); + bool end = (i == children.rend()); + while (!end) { + Window *w = *i; + i++; + end = (i == children.rend()); + if (w->visible && x >= w->x && x <= w->x+w->width + && y >= w->y && y <= w->y+w->height + && w->mouseMoved(x-w->x, y-w->y)) return true; + } + return false; +} + +bool Window::mouseDragged(int x, int y, MouseButton button) +{ + if (mouseChild == NULL) return false; + return mouseChild->mouseDragged(x-mouseChild->x, y-mouseChild->y, button); +} + +bool Window::mouseDown(int x, int y, MouseButton button) +{ + Window *last = NULL; + std::list::reverse_iterator i = children.rbegin(); + bool end = (i == children.rend()); + while (!end) { + Window *w = *i; + i++; + end = (i == children.rend()); + if (w->visible && x >= w->x && x <= w->x+w->width + && y >= w->y && y <= w->y+w->height + && (mouseChild = last = w) + && w->mouseDown(x-w->x, y-w->y, button) + && w->raise()) { + return true; + } + } + mouseChild = NULL; + if (last != NULL) last->raise(); + return false; +} + +bool Window::mouseUp(int x, int y, MouseButton button) +{ + if (mouseChild == NULL) return false; + return mouseChild->mouseUp(x-mouseChild->x, y-mouseChild->y, button); +} + +bool Window::mouseClicked(int x, int y, MouseButton button) +{ + if (mouseChild == NULL) return false; + return mouseChild->mouseClicked(x-mouseChild->x, y-mouseChild->y, button); +} + +bool Window::mouseDoubleClicked(int x, int y, MouseButton button) +{ + if (mouseChild == NULL) return false; + return mouseChild->mouseDoubleClicked(x-mouseChild->x, y-mouseChild->y, button); +} + +bool BorderedWindow::mouseDown(int x, int y, MouseButton button) +{ + mouseChild = NULL; + if (x > width-border_right || y > width-border_bottom) return false; + x -= border_left; y -= border_top; + if (x < 0 || y < 0) return false; + return Window::mouseDown(x,y,button); +} + +bool BorderedWindow::mouseMoved(int x, int y) +{ + if (x > width-border_right || y > width-border_bottom) return false; + x -= border_left; y -= border_top; + if (x < 0 || y < 0) return false; + return Window::mouseMoved(x,y); +} + +bool BorderedWindow::mouseDragged(int x, int y, MouseButton button) +{ + if (x > width-border_right || y > width-border_bottom) return false; + x -= border_left; y -= border_top; + if (x < 0 || y < 0) return false; + return Window::mouseDragged(x,y,button); +} + +void ToplevelWindow::paint(Drawable &d) const +{ + int mask = (systemMenu->isVisible()?Color::RedMask|Color::GreenMask|Color::BlueMask:0); + d.clear(Color::Background); + + d.setColor(Color::Border); + d.drawLine(0,height-1,width-1,height-1); + d.drawLine(width-1,0,width-1,height-1); + + d.setColor(Color::Shadow3D); + d.drawLine(0,0,width-2,0); + d.drawLine(0,0,0,height-2); + d.drawLine(0,height-2,width-2,height-2); + d.drawLine(width-2,0,width-2,height-2); + + d.drawLine(5,4,width-7,4); + d.drawLine(5,4,5,30); + + d.setColor(Color::Light3D); + d.drawLine(1,1,width-3,1); + d.drawLine(1,1,1,height-3); + + d.drawLine(5,31,width-6,31); + d.drawLine(width-6,5,width-6,31); + + d.setColor(Color::Background3D^mask); + d.fillRect(6,5,26,26); + d.setColor(Color::Grey50^mask); + d.fillRect(9,17,20,4); + d.setColor(Color::Black^mask); + d.fillRect(8,16,20,4); + d.setColor(Color::White^mask); + d.fillRect(9,17,18,2); + + d.setColor(Color::Border); + d.drawLine(32,5,32,30); + + d.setColor(Color::Titlebar); + d.fillRect(33,5,width-39,26); + + const Font *font = Font::getFont("title"); + d.setColor(Color::TitlebarText); + d.setFont(font); + d.drawText(31+(width-39-font->getWidth(title))/2,5+(26-font->getHeight())/2+font->getAscent(),title,false,0); +} + +void Input::paint(Drawable &d) const +{ + d.clear(Color::EditableBackground); + + d.setColor(Color::Shadow3D); + d.drawLine(0,0,width-2,0); + d.drawLine(0,0,0,height-2); + + d.setColor(Color::Background3D); + d.drawLine(1,height-2,width-2,height-2); + d.drawLine(width-2,1,width-2,height-2); + + d.setColor(Color::Text); + d.drawLine(1,1,width-3,1); + d.drawLine(1,1,1,height-3); + + const Font *f = Font::getFont("input"); + d.setFont(f); + + Drawable d1(d,3,4,width-6,height-8); + Drawable dr(d1,(multi?0:-offset),(multi?-offset:0),width-6+(multi?0:offset),height-8+(multi?offset:0)); + + const Size start = imin(start_sel, end_sel), end = imax(start_sel, end_sel); + dr.drawText(0,f->getAscent()+1,text,multi,0,start); + + int sx = dr.getX(), sy = dr.getY(); + dr.drawText(text, multi, start, end-start); + int ex = dr.getX(), ey = dr.getY(); + + if (sx != ex || sy != ey) { + dr.setColor(Color::SelectionBackground); + if (sy == ey) dr.fillRect(sx,sy-f->getAscent(),ex-sx,f->getHeight()+1); + else { + dr.fillRect(sx, sy-f->getAscent(), width-sx+offset, f->getHeight() ); + dr.fillRect(0, sy-f->getAscent()+f->getHeight(), width+offset, ey-sy-f->getHeight()); + dr.fillRect(0, ey-f->getAscent(), ex, f->getHeight() ); + } + dr.setColor(Color::SelectionForeground); + dr.drawText(sx, sy, text, multi, start, end-start); + } + + dr.setColor(Color::Text); + + dr.drawText(text, multi, end); + + if (blink && hasFocus()) { + if (insert) dr.drawLine(posx,posy,posx,posy+f->getHeight()+1); + else dr.fillRect(posx,posy,f->getWidth(text[pos]),f->getHeight()+1); + } +} + +Size Input::findPos(int x, int y) { + const Font *f = Font::getFont("input"); + if (multi) y += offset; + else x += offset; + y = (y-4) / f->getHeight(); + int line = 0; + Size pos = 0; + while (line < y && pos < text.size()) if (f->toSpecial(text[pos++]) == Font::LF) line++; + Drawable d(width-6,1); + d.setFont(f); + while (pos <= text.size() && d.getY() == 0 && x > d.getX()) { + d.drawText(String(text), multi, pos, 1); + pos++; + } + if (pos > 0) pos--; + return pos; +} + +bool Input::mouseDown(int x, int y, MouseButton button) +{ + if (button == Left || (button == Middle && start_sel == end_sel)) { + end_sel = start_sel = pos = findPos(x,y); + blink = true; + checkOffset(); + } + if (button == Middle) keyDown(Key(0,Key::Insert,true,false,false,false)); + return true; +} + +bool Input::mouseDragged(int x, int y, MouseButton button) +{ + if (button == Left) { + end_sel = pos = findPos(x,y); + blink = true; + checkOffset(); + } + return true; +} + +bool Input::keyDown(const Key &key) +{ + const Font *f = Font::getFont("input"); + switch (key.special) { + case Key::None: + if (key.ctrl) { + switch (key.character) { + case 1: + case 'a': + case 'A': + if (key.shift) { + start_sel = end_sel = pos; + } else { + start_sel = 0; + pos = end_sel = text.size(); + } + break; + case 24: + case 'x': + case 'X': + cutSelection(); + break; + case 3: + case 'c': + case 'C': + copySelection(); + break; + case 22: + case 'v': + case 'V': + pasteSelection(); + break; + default: printf("Ctrl-0x%x\n",key.character); break; + } + break; + } + if (start_sel != end_sel) clearSelection(); + if (insert || pos >= text.size() ) text.insert(text.begin()+pos++,key.character); + else text[pos++] = key.character; + break; + case Key::Left: + if (pos > 0) pos--; + break; + case Key::Right: + if (pos < text.size()) pos++; + break; + case Key::Down: + if (multi) pos = findPos(posx+3, posy-offset+f->getHeight()+4); + break; + case Key::Up: + if (multi) pos = findPos(posx+3, posy-offset-f->getHeight()+4); + break; + case Key::Home: + if (multi) { + while (pos > 0 && f->toSpecial(text[pos-1]) != Font::LF) pos--; + } else pos = 0; + break; + case Key::End: + if (multi) { + while (pos < text.size() && f->toSpecial(text[pos]) != Font::LF) pos++; + } else pos = text.size(); + break; + case Key::Backspace: + if (!key.shift && start_sel != end_sel) clearSelection(); + else if (pos > 0) text.erase(text.begin()+ --pos); + break; + case Key::Delete: + if (key.shift) cutSelection(); + else if (start_sel != end_sel) clearSelection(); + else if (pos < text.size()) text.erase(text.begin()+pos); + break; + case Key::Insert: + if (key.ctrl) copySelection(); + else if (key.shift) pasteSelection(); + else insert = !insert; + break; + case Key::Enter: + if (multi) { + if (start_sel != end_sel) clearSelection(); + if (insert || pos >= text.size() ) text.insert(text.begin()+pos++,f->fromSpecial(Font::LF)); + else text[pos++] = f->fromSpecial(Font::LF); + } else executeAction(text); + break; + case Key::Tab: + if (multi) { + if (start_sel != end_sel) clearSelection(); + if (insert || pos >= text.size() ) text.insert(text.begin()+pos++,f->fromSpecial(Font::Tab)); + else text[pos++] = f->fromSpecial(Font::Tab); + } else return false; + break; + default: + return false; + } + if (!key.ctrl) { + if (!key.shift || key.special == Key::None) start_sel = end_sel = pos; + else end_sel = pos; + } + checkOffset(); + blink = true; + return true; +} + +void BorderedWindow::paintAll(Drawable &d) const +{ + this->paint(d); + Drawable dchild(d,border_left,border_top,width-border_left-border_right,height-border_top-border_bottom); + for (std::list::const_iterator i = children.begin(); i != children.end(); ++i) { + Window *child = *i; + if (child->isVisible()) { + Drawable cd(dchild,child->getX(),child->getY(),child->getWidth(),child->getHeight()); + child->paintAll(cd); + } + } +} + +void Button::paint(Drawable &d) const +{ + int offset = -1; + + if (hasFocus()) { + offset = 0; + d.setColor(Color::Border); + d.drawLine(0,0,width,0); + d.drawLine(0,0,0,height); + + d.drawLine(0,height-1,width,height-1); + d.drawLine(width-1,0,width-1,height); + } + + d.setColor(Color::Background3D); + d.fillRect(2,2,width-4,height-4); + + if (pressed) { + d.setColor(Color::Shadow3D); + + d.drawLine(1+offset,1+offset,width-2-offset,1+offset); + d.drawLine(1+offset,1+offset,1+offset,height-2-offset); + } else { + d.setColor(Color::Background3D); + + d.drawLine(1+offset,1+offset,width-3-offset,1+offset); + d.drawLine(1+offset,1+offset,1+offset,height-3-offset); + + d.setColor(Color::Light3D); + + d.drawLine(2+offset,2+offset,width-4-offset,2+offset); + d.drawLine(2+offset,2+offset,2+offset,height-4-offset); + + d.setColor(Color::Shadow3D); + + d.drawLine(2+offset,height-3-offset,width-2-offset,height-3-offset); + d.drawLine(width-3-offset,2+offset,width-3-offset,height-2-offset); + + d.setColor(Color::Border); + + d.drawLine(width-2-offset,1+offset,width-2-offset,height-2-offset); + d.drawLine(1+offset,height-2-offset,width-2-offset,height-2-offset); + } +} + +bool Checkbox::keyDown(const Key &key) +{ + switch (key.special) { + case Key::None: + if (key.character != ' ') return false; + case Key::Enter: + break; + default: return false; + } + mouseDown(0,0,Left); + return true; +} + +bool Checkbox::keyUp(const Key &key) +{ + if (key.ctrl || key.alt || key.windows || (key.character != ' ' && key.special != Key::Enter)) return false; + mouseUp(0,0,Left); + mouseClicked(0,0,Left); + return true; +} + +void Checkbox::paint(Drawable &d) const +{ + d.setColor(Color::Background3D); + d.fillRect(2,(height/2)-7,14,14); + + d.setColor(Color::Shadow3D); + d.drawLine(2,(height/2)-7,13,(height/2)-7); + d.drawLine(2,(height/2)-7,2,(height/2)+5); + + d.setColor(Color::Light3D); + d.drawLine(2,(height/2)+5,14,(height/2)+5); + d.drawLine(14,(height/2)-7,14,(height/2)+5); + + d.setColor(Color::EditableBackground); + d.fillRect(4,(height/2)-5,9,9); + + d.setColor(Color::Border); + d.drawLine(3,(height/2)-6,12,(height/2)-6); + d.drawLine(3,(height/2)-6,3,(height/2)+4); + + if (checked) { + d.setColor(Color::Text); + d.drawLine(5,(height/2)-2,7,(height/2) ); + d.drawLine(11,(height/2)-4); + d.drawLine(5,(height/2)-1,7,(height/2)+1); + d.drawLine(11,(height/2)-3); + d.drawLine(5,(height/2) ,7,(height/2)+2); + d.drawLine(11,(height/2)-2); + } +} + +Radiobox::Radiobox(Frame *parent, int x, int y, int w, int h) : BorderedWindow(static_cast(parent),x,y,w,h,16,0,0,0), ActionEventSource("GUI::Radiobox"), checked(0) +{ + addActionHandler(parent); +} + +bool Radiobox::keyDown(const Key &key) +{ + switch (key.special) { + case Key::None: + if (key.character != ' ') return false; + case Key::Enter: + break; + default: return false; + } + mouseDown(0,0,Left); + return true; +} + +bool Radiobox::keyUp(const Key &key) +{ + if (key.ctrl || key.alt || key.windows || (key.character != ' ' && key.special != Key::Enter)) return false; + mouseUp(0,0,Left); + mouseClicked(0,0,Left); + return true; +} + +void Radiobox::paint(Drawable &d) const +{ + d.setColor(Color::Light3D); + d.drawLine(6,(height/2)+6,9,(height/2)+6); + d.drawLine(4,(height/2)+5,11,(height/2)+5); + d.drawLine(13,(height/2)-1,13,(height/2)+2); + d.drawLine(12,(height/2)-2,12,(height/2)+4); + + d.setColor(Color::Background3D); + d.drawLine(6,(height/2)+5,9,(height/2)+5); + d.drawLine(4,(height/2)+4,11,(height/2)+4); + d.drawLine(12,(height/2)-1,12,(height/2)+2); + d.drawLine(11,(height/2)-2,11,(height/2)+4); + + d.setColor(Color::Shadow3D); + d.drawLine(6,(height/2)-5,9,(height/2)-5); + d.drawLine(4,(height/2)-4,11,(height/2)-4); + d.drawLine(2,(height/2)-1,2,(height/2)+2); + d.drawLine(3,(height/2)-3,3,(height/2)+4); + + d.setColor(Color::Border); + d.drawLine(6,(height/2)-4,9,(height/2)-4); + d.drawLine(4,(height/2)-3,11,(height/2)-3); + d.drawLine(3,(height/2)-1,3,(height/2)+2); + d.drawLine(4,(height/2)-3,4,(height/2)+3); + + d.setColor(Color::EditableBackground); + d.fillRect(5,(height/2)-2,6,6); + d.fillRect(4,(height/2)-1,8,4); + d.fillRect(6,(height/2)-3,4,8); + + if (checked) { + d.setColor(Color::Text); + d.fillRect(6,(height/2),4,2); + d.fillRect(7,(height/2)-1,2,4); + } +} + +void Menu::paint(Drawable &d) const +{ + d.clear(Color::Background3D); + + d.setColor(Color::Border); + d.drawLine(0,height-1,width-1,height-1); + d.drawLine(width-1,0,width-1,height-1); + + d.setColor(Color::Shadow3D); + d.drawLine(0,0,width-2,0); + d.drawLine(0,0,0,height-2); + d.drawLine(0,height-2,width-2,height-2); + d.drawLine(width-2,0,width-2,height-2); + + d.setColor(Color::Light3D); + d.drawLine(1,1,width-3,1); + d.drawLine(1,1,1,height-3); + + d.setFont(Font::getFont("menu")); + const int asc = Font::getFont("menu")->getAscent()+1; + const int height = Font::getFont("menu")->getHeight()+2; + int y = asc+3; + int index = 0; + for (std::vector::const_iterator i = items.begin(); i != items.end(); ++i) { + if ((*i).empty()) { + d.setColor(Color::Shadow3D); + d.drawLine(4,y-asc+6,width-5,y-asc+6); + d.setColor(Color::Light3D); + d.drawLine(4,y-asc+7,width-5,y-asc+7); + y += 12; + } else { + if (index == selected && hasFocus()) { + d.setColor(Color::SelectionBackground); + d.fillRect(3,y-asc,width-6,height); + d.setColor(Color::SelectionForeground); + } else { + d.setColor(Color::Text); + } + d.drawText(20,y,(*i),false,0); + y += height; + } + index++; + } +} + +void Menubar::paint(Drawable &d) const +{ + const Font *f = Font::getFont("menu"); + + d.setColor(Color::Light3D); + d.drawLine(0,height-1,width-1,height-1); + d.setColor(Color::Shadow3D); + d.drawLine(0,height-2,width-1,height-2); + + d.gotoXY(7,f->getAscent()+2); + + int index = 0; + for (std::vector::const_iterator i = menus.begin(); i != menus.end(); ++i, ++index) { + if (index == selected && (*i)->isVisible()) { + int w = f->getWidth((*i)->getName()); + d.setColor(Color::SelectionBackground); + d.fillRect(d.getX()-7,0,w+14,height-2); + d.setColor(Color::SelectionForeground); + d.gotoXY(d.getX()+7,f->getAscent()+2); + } else { + d.setColor(Color::Text); + } + d.drawText((*i)->getName(),false); + d.gotoXY(d.getX()+14,f->getAscent()+2); + } +} + +bool Button::keyDown(const Key &key) +{ + switch (key.special) { + case Key::None: + if (key.character != ' ') return false; + case Key::Enter: + break; + default: return false; + } + mouseDown(0,0,Left); + return true; +} + +bool Button::keyUp(const Key &key) +{ + if (key.ctrl || key.alt || key.windows || (key.character != ' ' && key.special != Key::Enter)) return false; + mouseUp(0,0,Left); + mouseClicked(0,0,Left); + return true; +} + +void Frame::paint(Drawable &d) const { + const Font *f = Font::getFont("default"); + const int top = (label.empty()?1:f->getAscent()/2+1); + + d.setColor(Color::Shadow3D); + d.drawLine(1,height-2,1,top); + d.drawLine(8,top); + d.drawLine((label.empty()?8:f->getWidth(label)+14),top,width-2,top); + d.drawLine(2,height-3,width-3,height-3); + d.drawLine(width-3,top+1); + + d.setColor(Color::Light3D); + d.drawLine(2,height-3,2,top+1); + d.drawLine(8,top+1); + d.drawLine((label.empty()?8:f->getWidth(label)+14),top+1,width-3,top+1); + d.drawLine(2,height-2,width-2,height-2); + d.drawLine(width-2,top+1); + + d.setColor(Color::Text); + d.drawText(11,f->getAscent()+1,label,false,0); +} + +Screen *Window::getScreen() { return (parent == NULL?dynamic_cast(this):parent->getScreen()); } + +Screen::Screen(unsigned int width, unsigned int height) : + Window(), + buffer(new Drawable(width, height)) +{ + this->width = width; + this->height = height; +} + +Screen::Screen(Drawable *d) : + Window(), + buffer(d) +{ + this->width = d->getClipWidth(); + this->height = d->getClipHeight(); +} + +Screen::~Screen() +{ +} + +void Screen::paint(Drawable &d) const +{ + d.clear(Color::Transparent); +} + +unsigned int Screen::update(void *surface, unsigned int ticks) +{ + Timer::check(ticks); + + paintAll(*buffer); + RGB *buf = buffer->buffer; + for (y = 0; y < height; y++) { + for (x = 0; x < width; x++, buf++) { + RGB sval = surfaceToRGB(surface); + RGB bval = *buf; + int a = Color::A(bval); + bval = ((((sval&Color::MagentaMask)*a+(bval&Color::MagentaMask)*(256-a))>>8)&Color::MagentaMask) + | ((((sval&Color::GreenMask)*a+(bval&Color::GreenMask)*(256-a))>>8)&Color::GreenMask); + rgbToSurface(bval, &surface); + } + } + + return Timer::next(); +} + +void Screen::move(int x, int y) +{ +} + +void Screen::resize(int w, int h) +{ +} + +#ifdef TESTING +static void test(Drawable &d) { + const int width = d.getClipWidth(); + const int height = d.getClipHeight(); + + d.clear(Color::rgba(0,0,255,128)); + + d.setColor(Color::Black); + for (int x = 0; x < width; x += 10) d.drawLine(x,0,x,height); + for (int y = 0; y < height; y += 10) d.drawLine(0,y,width,y); + + d.setColor(Color::Red); + for (int x = 10; x <= 130 ; x += 10) { + d.drawLine(x,10,70,70); + d.drawLine(x,130); + } + for (int y = 10; y <= 130 ; y += 10) { + d.drawLine(10,y,70,70); + d.drawLine(130,y); + } + + d.setColor(Color::Yellow); + d.fillRect(150,10,30,30); + d.setColor(Color::Blue); + d.drawRect(30,30); + + d.drawRect(150,60,30,30); + d.setColor(Color::Yellow); + d.fillRect(30,30); + + for (int x = 0; x <= 100 ; x += 10) { + d.setColor(Color::rgba(0xff,0x00,0xff,(255*x/100)&255)); + d.fillRect(200+2*x,0,20,20); + } + + d.setColor(Color::Yellow); + d.fillCircle(210,60,40); + d.setColor(Color::Blue); + d.drawCircle(40); + + d.drawCircle(210,110,40); + d.setColor(Color::Yellow); + d.fillCircle(40); + + d.setColor(Color::rgba(0,0,255,128)); + d.fillRect(251,41,9,59); + d.fillRect(251,41,59,9); + d.fillRect(301,41,9,59); + d.fillRect(291,91,19,9); + d.fillRect(291,51,9,49); + d.fillRect(261,51,39,9); + d.fillRect(261,51,9,49); + d.fillRect(261,91,29,9); + d.fillRect(281,61,9,39); + d.fillRect(271,61,19,9); + d.fillRect(271,61,9,29); + d.fillRect(241,41,9,59); + d.fillRect(241,41,19,9); + d.fillRect(241,91,19,9); + d.setColor(Color::rgba(255,0,0,128)); + d.fill(255,64); + + d.setColor(Color::Green); + Drawable(d,500,355,30,30).fillCircle(65); + + for (int i = 0; i <= 100; i += 10) { + Drawable(d,25,155+i*3,420,30).drawDrawable(0,0,d,255*i/100); + } + + d.setColor(Color::White); + d.setFont(Font::getFont("VGA14")); + d.drawText(270,110,"GUI:: Test Program\n"); + d.drawText("Still testing\tTable\n"); + d.drawText("More of...\tTable\n"); + d.drawText("Overwrite\rXXXXXXXXX\n"); + d.drawText("Fake int'l chars: O\b/e\b\"\n"); + d.drawText("Real ones: \211\222\234\345\246\321"); +} +#else +static void test(Drawable &d) { (void)d; } +#endif + + +void ScreenRGB32le::paint(Drawable &d) const +{ + parent->paint(d); + test(d); +} + +static const MouseButton SDL_to_GUI(const int button) +{ + switch (button) { + case SDL_BUTTON_LEFT: return GUI::Left; + case SDL_BUTTON_RIGHT: return GUI::Right; + case SDL_BUTTON_MIDDLE: return GUI::Middle; + case SDL_BUTTON_WHEELUP: return GUI::WheelUp; + case SDL_BUTTON_WHEELDOWN: return GUI::WheelDown; + default: return GUI::NoButton; + } +} + +static const Key SDL_to_GUI(const SDL_keysym &key) +{ + GUI::Key::Special ksym = GUI::Key::None; + switch (key.sym) { + case SDLK_ESCAPE: ksym = GUI::Key::Escape; break; + case SDLK_BACKSPACE: ksym = GUI::Key::Backspace; break; + case SDLK_TAB: ksym = GUI::Key::Tab; break; + case SDLK_LEFT: ksym = GUI::Key::Left; break; + case SDLK_RIGHT: ksym = GUI::Key::Right; break; + case SDLK_UP: ksym = GUI::Key::Up; break; + case SDLK_DOWN: ksym = GUI::Key::Down; break; + case SDLK_HOME: ksym = GUI::Key::Home; break; + case SDLK_END: ksym = GUI::Key::End; break; + case SDLK_DELETE: ksym = GUI::Key::Delete; break; + case SDLK_INSERT: ksym = GUI::Key::Insert; break; + case SDLK_RETURN: ksym = GUI::Key::Enter; break; + case SDLK_MENU: ksym = GUI::Key::Menu; break; + case SDLK_PAGEUP: ksym = GUI::Key::PageUp; break; + case SDLK_PAGEDOWN: ksym = GUI::Key::PageDown; break; + case SDLK_PRINT: ksym = GUI::Key::Print; break; + case SDLK_PAUSE: ksym = GUI::Key::Pause; break; + case SDLK_BREAK: ksym = GUI::Key::Break; break; + case SDLK_CAPSLOCK: ksym = GUI::Key::CapsLock; break; + case SDLK_NUMLOCK: ksym = GUI::Key::NumLock; break; + case SDLK_SCROLLOCK: ksym = GUI::Key::ScrollLock; break; + case SDLK_F1:case SDLK_F2:case SDLK_F3:case SDLK_F4:case SDLK_F5:case SDLK_F6: + case SDLK_F7:case SDLK_F8:case SDLK_F9:case SDLK_F10:case SDLK_F11:case SDLK_F12: + ksym = (GUI::Key::Special)(GUI::Key::F1 + key.sym-SDLK_F1); + default: break; + } + return Key(key.unicode, ksym, + key.mod&KMOD_SHIFT, + key.mod&KMOD_CTRL, + key.mod&KMOD_ALT, + key.mod&KMOD_META); +} + +/** \brief Internal class that handles different screen bit depths and layouts the SDL way */ +class SDL_Drawable : public Drawable { +protected: + SDL_Surface *const surface; + +public: + SDL_Drawable(int width, int height, RGB clear = Color::Transparent) : Drawable(width, height, clear), surface(SDL_CreateRGBSurfaceFrom(buffer, width, height, 32, width*4, Color::RedMask, Color::GreenMask, Color::BlueMask, Color::AlphaMask)) { + surface->flags |= SDL_SRCALPHA; + } + + ~SDL_Drawable() { + SDL_FreeSurface(surface); + } + + void update(SDL_Surface *dest) const { + SDL_BlitSurface(surface, NULL, dest, NULL); + } +}; + +ScreenSDL::ScreenSDL(SDL_Surface *surface) : Screen(new SDL_Drawable(surface->w, surface->h)), surface(surface), downx(0), downy(0), lastclick(0) {} + +Ticks ScreenSDL::update(Ticks ticks) +{ + Timer::check(ticks); + + paintAll(*buffer); + static_cast(buffer)->update(surface); + + return Timer::next(); +} + +void ScreenSDL::paint(Drawable &d) const { + d.clear(Color::Transparent); + test(d); +} + +bool ScreenSDL::event(const SDL_Event &event) { + bool rc; + + switch (event.type) { + case SDL_KEYUP: { + const Key &key = SDL_to_GUI(event.key.keysym); + if (key.special == GUI::Key::None && key.character == 0) break; + if (key.special == GUI::Key::CapsLock || key.special == GUI::Key::NumLock) keyDown(key); + return keyUp(key); + } + case SDL_KEYDOWN: { + const Key &key = SDL_to_GUI(event.key.keysym); + if (key.special == GUI::Key::None && key.character == 0) break; + rc = keyDown(key); + if (key.special == GUI::Key::CapsLock || key.special == GUI::Key::NumLock) keyUp(key); + return rc; + } + case SDL_MOUSEMOTION: + if (event.motion.state) { + if (abs(event.motion.x-downx) <= 10 && abs(event.motion.y-downy) <= 10) + break; + downx = -11; downy = -11; + if (event.motion.state&SDL_BUTTON(1)) + return mouseDragged(event.motion.x, event.motion.y, GUI::Left); + else if (event.motion.state&SDL_BUTTON(2)) + return mouseDragged(event.motion.x, event.motion.y, GUI::Middle); + else if (event.motion.state&SDL_BUTTON(3)) + return mouseDragged(event.motion.x, event.motion.y, GUI::Right); + break; + } + + return mouseMoved(event.motion.x, event.motion.y); + + case SDL_MOUSEBUTTONDOWN: + rc = mouseDown(event.button.x, event.button.y, SDL_to_GUI(event.button.button)); + if (abs(event.button.x-downx) > 10 || abs(event.button.y-downy) > 10) lastclick = 0; + downx = event.button.x; downy = event.button.y; + return rc; + + case SDL_MOUSEBUTTONUP: + rc = mouseUp(event.button.x, event.button.y, SDL_to_GUI(event.button.button)); + if (abs(event.button.x-downx) < 10 && abs(event.button.y-downy) < 10) { + if (lastclick == 0 || (GUI::Timer::now()-lastclick) > 20) { + lastclick = GUI::Timer::now(); + rc |= mouseClicked(downx, downy, SDL_to_GUI(event.button.button)); + } else if (lastclick != 0) { + rc |= mouseDoubleClicked(downx, downy, SDL_to_GUI(event.button.button)); + lastclick = 0; + } else { + lastclick = 0; + } + } else { + lastclick = 0; + } + return rc; + } + + return false; +} + +} + + + +#ifdef TESTING +#include + +/** \brief A test program that serves as an example for all GUI elements. + * + * Note that you need SDL installed and a file "testfont.h" for this + * to compile, which must contain a bitmap font declared as + * + * \code + * static const unsigned char testfont[256 * 14] = { ... }; + * \endcode + * + * (256 chars, 8x14 fixed-width font, for example the IBM PC VGA 14-line font, + * you can get it from DOSBox) + * + * To compile it, use a command line like this: + * + * \code + * g++ -DTESTING `sdl-config --cflags` `sdl-config --libs` gui_tk.cpp -o testgui_tk + * \endcode + * + */ + +int main(int argc, char *argv[]) +{ + printf("GUI:: test program\n"); + + SDL_Surface *screen; + + if (SDL_Init(SDL_INIT_VIDEO) < 0) { + fprintf(stderr, "Couldn't initialize SDL: %s\n", SDL_GetError()); + exit(1); + } + + atexit(SDL_Quit); + + screen = SDL_SetVideoMode(640, 480, 32, SDL_SWSURFACE); + if (screen == NULL) { + fprintf(stderr, "Couldn't set 640x480x32 video mode: %s\n", SDL_GetError()); + exit(1); + } + printf("GUI:: color depth %i\n",screen->format->BitsPerPixel); + + SDL_EnableUNICODE(true); + SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY,SDL_DEFAULT_REPEAT_INTERVAL); + + #include "testfont.h" + GUI::Font::addFont("default",new GUI::BitmapFont(testfont,14,10)); + + GUI::ScreenSDL guiscreen(screen); + GUI::ToplevelWindow *frame = new GUI::ToplevelWindow(&guiscreen,205,100,380,250,"GUI::Frame"); + static struct delwin : public GUI::ActionEventSource_Callback { + void actionExecuted(GUI::ActionEventSource *b, const GUI::String &arg) { + dynamic_cast(dynamic_cast(b)->getParent())->close(); + } + } dw; + struct newwin : public GUI::ActionEventSource_Callback { + GUI::Screen *screen; + int n; + void actionExecuted(GUI::ActionEventSource *b, const GUI::String &arg) { + char title[256]; + sprintf(title,"Window %i",++n); + GUI::ToplevelWindow *w = new GUI::ToplevelWindow(screen,405,100,120,150,title); + GUI::Button *close = new GUI::Button(w,5,5,"Close"); + close->addActionHandler(&dw); + } + } nw; + nw.screen = &guiscreen; + nw.n = 0; + struct quit : public GUI::ActionEventSource_Callback { + GUI::ToplevelWindow *frame; + void actionExecuted(GUI::ActionEventSource *b, const GUI::String &arg) { + if (arg == "Quit" && b->getName() != "GUI::Input") exit(0); + frame->setTitle(arg); + } + } ex; + ex.frame = frame; + + GUI::Button *b = new GUI::Button(frame,8,20,"Open a new Window"); + b->addActionHandler(&nw); + (new GUI::Button(frame,200,20,"1"))->addActionHandler(&ex); + (new GUI::Button(frame,235,20,"2"))->addActionHandler(&ex); + b = new GUI::Button(frame,270,20,"Quit"); + b->addActionHandler(&ex); + (new GUI::Input(frame,16,55,150))->addActionHandler(&ex); + + printf("Title: %s\n",(const char*)frame->getTitle()); + + struct movewin : public GUI::Timer_Callback, public GUI::ToplevelWindow_Callback { + public: + GUI::ToplevelWindow *frame; + GUI::Checkbox *cb1, *cb2; + int x, y; + virtual unsigned int timerExpired(unsigned int t) { + if (cb2->isChecked()) frame->move(frame->getX()+x,frame->getY()+y); + if (frame->getX() <= -frame->getWidth()) x = 1; + if (frame->getX() >= 640) x = -1; + if (frame->getY() <= -frame->getHeight()) y = 1; + if (frame->getY() >= 480) y = -1; + return 10; + } + virtual bool windowClosing(GUI::ToplevelWindow *win) { + if (!cb1->isChecked()) return false; + return true; + } + virtual void windowClosed(GUI::ToplevelWindow *win) { + GUI::Timer::remove(this); + } + } mw; + mw.frame = frame; + mw.x = -1; + mw.y = 1; + GUI::Frame *box = new GUI::Frame(frame,16,80,300,50); + mw.cb1 = new GUI::Checkbox(box,0,0,"Allow to close this window"); + mw.cb2 = new GUI::Checkbox(box,0,20,"Move this window"); + box = new GUI::Frame(frame,16,130,300,80,"Radio Buttons"); + (new GUI::Radiobox(box,0,0,"Normal"))->setChecked(true); + new GUI::Radiobox(box,0,20,"Dynamic"); + new GUI::Radiobox(box,0,40,"Simple"); + box->addActionHandler(&ex); + GUI::Timer::add(&mw,10); + frame->addWindowHandler(&mw); + GUI::Menubar *bar = new GUI::Menubar(frame,0,0,frame->getWidth()); + bar->addMenu("File"); + bar->addItem(0,"New..."); + bar->addItem(0,"Open..."); + bar->addItem(0,""); + bar->addItem(0,"Save"); + bar->addItem(0,"Save as..."); + bar->addItem(0,""); + bar->addItem(0,"Close"); + bar->addItem(0,"Quit"); + bar->addMenu("Edit"); + bar->addItem(1,"Undo"); + bar->addItem(1,"Redo"); + bar->addItem(1,""); + bar->addItem(1,"Cut"); + bar->addItem(1,"Copy"); + bar->addItem(1,"Paste"); + bar->addItem(1,""); + bar->addItem(1,"Select all"); + bar->addItem(1,"Select none"); + bar->addMenu("View"); + bar->addItem(2,"Zoom..."); + bar->addItem(2,"Zoom in"); + bar->addItem(2,"Zoom out"); + bar->addMenu("?"); + bar->addItem(3,"Manual"); + bar->addItem(3,"Search..."); + bar->addItem(3,""); + bar->addItem(3,"About"); + bar->addActionHandler(&ex); + + SDL_Event event; + while (1) { + while (SDL_PollEvent(&event)) { + if (!guiscreen.event(event)) { + if (event.type == SDL_QUIT) exit(0); + } + } + + if (SDL_MUSTLOCK(screen)) SDL_LockSurface(screen); + memset(screen->pixels,0xff,4*640*15); + memset(((char *)screen->pixels)+4*640*15,0x00,4*640*450); + memset(((char *)screen->pixels)+4*640*465,0xff,4*640*15); + if (SDL_MUSTLOCK(screen)) SDL_UnlockSurface(screen); + guiscreen.update(4); + SDL_UpdateRect(screen, 0, 0, screen->w, screen->h); + + SDL_Delay(40); + } +} +#endif + diff --git a/src/libs/gui_tk/gui_tk.h b/src/libs/gui_tk/gui_tk.h new file mode 100644 index 00000000..a59c7fbe --- /dev/null +++ b/src/libs/gui_tk/gui_tk.h @@ -0,0 +1,2221 @@ +/** \mainpage gui::tk - framework-agnostic C++ GUI toolkit + * + * \section i Introduction + * + * gui::tk is a simple one-file C++ GUI toolkit for use with arbitrary + * memory framebuffers. + * + * \section f Features + * + * \li small source and binary code size + * \li reasonable performance and memory usage + * \li comfortable usage + * \li suitable for embedded usage: integer math only + * \li extensibility via OO + * \li non-intrusive: can be integrated with any event mechanism of your liking + * \li no dependencies apart from standards-conformant ANSI C++ (including a little STL) + * \li support for different encodings, single- and multibyte + * \li flexible font support + * + * \section o Overview + * + * The toolkit draws on a surface you provide, using any size or pixel format. + * Create a GUI::Screen with the buffer to draw on, then pass that object + * (or a GUI::Window created from it) to all other widgets' constructors. + * + * It doesn't provide an own event loop. Instead, it relies on you passing events + * and updating the screen regularly. This way, it can easily be integrated with + * any event loop available (SDL, Qt, glib, X11, Win32, selfmade, ...) + * + * Many functions and concepts were taken from other well-known toolkits, so if you + * know Qt, Java or wxWindows, you may encounter (intentional) similarities. However, + * simplicity of code has been given priority over maximum optimization and pixel-exact + * replication of their appearance. + * + * A note about font support and encodings: All text-related functions use templates to + * support any encoding. Fonts define the charset. + * + * \section g Getting Started + * + * gui::tk contains an SDL adapter, so if your program is already using SDL, + * things are really easy. The rough sequence to get up and running is: + * + * \code + * // setup a suitable video mode with SDL + * SDL_surface *screen = SDL_SetVideoMode(640, 480, 32, SDL_SWSURFACE); + * + * // add a default font, you will most probably need it + * GUI::Font::addFont("default",new GUI::BitmapFont(testfont,14,10)); + * + * // create the root-level screen object, the parent of all top-level windows + * GUI::ScreenSDL guiscreen(screen); + * + * // create any amount of toplevel windows you like + * GUI::ToplevelWindow *frame = new GUI::ToplevelWindow(&guiscreen, 205, 100, 380, 250, "gui::tk example"); + * + * // add some GUI elements to the toplevel window + * GUI::Button *b = new GUI::Button(frame,8,20,"Quit"); + * + * // Define and add an event handler + * struct quit : public GUI::ActionEventSource_Callback { + * void actionExecuted(GUI::ActionEventSource *b, const GUI::String &arg) { + * exit(0); + * } + * } ex; + * b->addActionHandler(&ex); + * + * // Enter an event loop, calling update() regularly. + * SDL_Event event; + * while (1) { + * while (SDL_PollEvent(&event)) { + * if (!guiscreen.event(event)) { // gui::tk didn't handle that event + * if (event.type == SDL_QUIT) exit(0); + * } + * } + * + * guiscreen.update(4); // 4 ticks = 40ms + * SDL_UpdateRect(screen, 0, 0, screen->w, screen->h); + * + * SDL_Delay(40); // wait 40ms + * } + * \endcode + * + * Look at gui_tk.h for more detailed documentation and reference of all classes. + * + * A test program that shows off the capabilities of gui::tk is available as part of gui_tk.cpp. + * + * \section l License + * + * Copyright (C) 2005-2007 Jörg Walter + * + * gui::tk 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. + * + * gui::tk 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 + */ + +/* $Id: gui_tk.h,v 1.1 2007-10-28 16:33:02 qbix79 Exp $ */ + +#ifndef GUI__TOOLKIT_H +#define GUI__TOOLKIT_H + +#define imin(x,y) (xy?x:y) +#define isign(x) (((x)<0?-1:1)) + +/** \file + * \brief Header file for gui::tk. + * + * All you need to do is to include "gui_tk.h". If you want SDL support, then include \c SDL.h first. + */ + +#ifdef _MSC_VER +typedef signed __int8 int8_t; +typedef signed __int16 int16_t; +typedef signed __int32 int32_t; +typedef unsigned __int8 uint8_t; +typedef unsigned __int16 uint16_t; +typedef unsigned __int32 uint32_t; +#else +#include +#endif + +#include +#include +#include +#include +#include +#include + +/// This namespace contains all GUI toolkit classes, types and functions. +namespace GUI { + +/// ARGB 24-bit color value: (a<<24)|(r<<16)|(g<<8)|(b) +typedef uint32_t RGB; + +/// Collection of all color-related constants and functions. +namespace Color { +/// A fully transparent pixel. +const RGB Transparent = 0x00ffffff; + +/// A fully opaque black pixel. +const RGB Black = 0xff000000; + +/// A fully opaque white pixel. +const RGB White = 0xffffffff; + +/// Alpha mask. +const RGB AlphaMask = 0xff000000; + +/// Offset of alpha value. +const int AlphaShift = 24; + +/// Red mask. +const RGB RedMask = 0x00ff0000; + +/// Full-intensity red. +const RGB Red = Black|RedMask; + +/// Offset of red value. +const int RedShift = 16; + +/// Green mask. +const RGB GreenMask = 0x0000ff00; + +/// Full-intensity green. +const RGB Green = Black|GreenMask; + +/// Offset of green value. +const int GreenShift = 8; + +/// Blue mask. +const RGB BlueMask = 0x000000ff; + +/// Full-intensity blue. +const RGB Blue = Black|BlueMask; + +/// Offset of blue value. +const int BlueShift = 0; + +/// Full-intensity Magenta. +const RGB Magenta = Red|Blue; + +/// Magenta mask. +const RGB MagentaMask = RedMask|BlueMask; + +/// Full-intensity Cyan. +const RGB Cyan = Green|Blue; + +/// Cyan mask. +const RGB CyanMask = GreenMask|BlueMask; + +/// Full-intensity Yellow. +const RGB Yellow = Red|Green; + +/// Yellow mask. +const RGB YellowMask = RedMask|GreenMask; + +/// 50% grey +const RGB Grey50 = 0xff808080; + +/// Background color for 3D effects. May be customized. +extern RGB Background3D; + +/// Light highlight color for 3D effects. May be customized. +extern RGB Light3D; + +/// Dark highlight color (shadow) for 3D effects. May be customized. +extern RGB Shadow3D; + +/// Generic border color for highlights or similar. May be customized. +extern RGB Border; + +/// Foreground color for regular content (mainly text). May be customized. +extern RGB Text; + +/// Background color for inactive areas. May be customized. +extern RGB Background; + +/// Background color for selected areas. May be customized. +extern RGB SelectionBackground; + +/// Foreground color for selected areas. May be customized. +extern RGB SelectionForeground; + +/// Background color for inputs / application area. May be customized. +extern RGB EditableBackground; + +/// Title bar color for windows. May be customized. +extern RGB Titlebar; + +/// Title bar text color for windows. May be customized. +extern RGB TitlebarText; + +/// Convert separate r, g, b and a values (each 0-255) to an RGB value. +static inline RGB rgba(int r, int g, int b, int a=0) { + return (((r&255)<>Color::RedShift); } +/// Get green value (0-255) from an RGB value. +static inline int G(RGB val) { return ((val&Color::GreenMask)>>Color::GreenShift); } +/// Get blue value (0-255) from an RGB value. +static inline int B(RGB val) { return ((val&Color::BlueMask)>>Color::BlueShift); } +/// Get alpha value (0-255) from an RGB value. +static inline int A(RGB val) { return ((val&Color::AlphaMask)>>Color::AlphaShift); } + +} + +/// Identifies a mouse button. +enum MouseButton { NoButton, Left, Right, Middle, WheelUp, WheelDown, WheelLeft, WheelRight }; + +/// A type which holds size values that can be very large. +typedef unsigned int Size; + +/// A type which holds a single character. Large enough for Unicode. +typedef uint32_t Char; + +/// A type which holds a number of timer ticks. +typedef unsigned int Ticks; + +/// Identifies a keyboard key. +class Key { +public: + /// Translated character value. + /** No encoding is implied. The Font that is used to display this character + * determines the appearance. */ + Char character; + /// Special keyboard keys. + /** It is modeled after PC keyboards. When you feed keyboard events to a GUI::Screen, + * try to map native keys to this set. Some special keys have a character value. Set + * to \c None if the key has no special meaning. */ + enum Special { + None, + F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, + Up, Down, Left, Right, Backspace, Tab, Backtab, Enter, Escape, + Home, End, PageUp, PageDown, Insert, Delete, Menu, + Print, Pause, Break, CapsLock, NumLock, ScrollLock, + Alt, Ctrl, Shift, Windows + } special; + /// Set if the Shift key is currently down. + bool shift; + /// Set if the Ctrl key is currently down. + bool ctrl; + /// Set if the Alt (PC) or Meta (classic Unix) key is currently down. + bool alt; + /// Set if the "Windows"/Meta (PC) or Super (classic Unix) key is currently down. + /** Do not depend too heavily on this, as many keyboards and systems do not have such a key. */ + bool windows; + + /// Constructor. + Key(int character, Special special, bool shift, bool ctrl, bool alt, bool windows) : + character(character), special(special), + shift(shift), ctrl(ctrl), alt(alt), windows(windows) {} +}; + +class Drawable; +class String; + +/** \brief Converts between strings of various types and String objects. + * + * It is used to deal with string encoding. It is intended to be used with template + * specializations. Having such a specialization means you can feed the corresponding type to + * all functions that expect a string -- without any conversion. You can add specializations + * yourself in your program's code to deal with unsupported types. + * + * As an example, see the std::string version. + * + * Encodings, as opposed to character sets, define how bytes map to character values. + * ASCII, for example, has an \em encoding where each byte is one character, while the + * ASCII \em character \em set says that values 0-127 are valid and 65 is the upper case + * letter 'A'. UTF-8 has 1-6 bytes per character, and the character set is unicode. + * + * GUI::Font deals with the character set, and this class encapsulates encodings. + * + */ +template class NativeString { +protected: + friend class String; + + /// Converts a native string into a String object + static void getString(String &dest, const STR &src) { STR::_this_string_type_is_not_supported_(); } + + /** \brief Converts a string object to native representation. + * + * If some characters cannot be converted, they should silently be skipped. Apart from that, + * \c nativeString(stringNative(String(),X)) should be value-equal to \c X. + */ + static STR& getNative(const String &src) { STR::_this_string_type_is_not_supported_();return*new STR(); } +}; + +template class NativeString { +protected: + friend class String; + static void getString(String &dest, const STR *src); + static STR* getNative(const String &src); +}; + +template class NativeString : public NativeString {}; +template class NativeString : public NativeString {}; +template class NativeString : public NativeString {}; + +/// 'less-than' comparison between pointer addresses +struct ltvoid { bool operator()(const void* s1, const void* s2) const { return s1 < s2; } }; + +/** \brief Simple STL-based string class. + * + * This is intended as internal helper class to allow gui::tk to work with any kind of + * string objects. While you can use this in normal application code, you should better + * use the string class of your application framework (like Qt). If you don't have any, + * use std::string. + * + * It supports arbitrary characters, no character set is implied. Conversion from/to usual + * string types like \c char* is automatic but not thread-safe for non-class types. + */ +class String : public std::vector { +protected: + template friend class NativeString; + /// Simple pointer encapsulation class for memory management. + class Native { public: virtual ~Native() {}; }; + /// Simple pointer encapsulation class for memory management. + template class NativeArray: public Native { + STR *data; + public: + NativeArray(STR *data) : data(data) {} + virtual ~NativeArray() { delete[] data; } + }; + template class NativeObject: public Native { + STR *data; + public: + NativeObject(STR *data) : data(data) {} + virtual ~NativeObject() { delete data; } + }; + +private: + /// Semi-static memory management for pointer string types. + mutable std::map strings; + +protected: + /// Manage a native string's memory. + void addNative(Native *dest) const { + const class std::type_info &type = typeid(dest); + if (strings[&type] != NULL) delete strings[&type]; + strings[&type] = dest; + } + +public: + /// Allocate a new String initialized from native string. + template String(const STR& src) { NativeString::getString(*this, src); } + + /// Taken from STL. + template String(InputIterator a, InputIterator b) : std::vector(a,b) {} + + /// Copy-constructor + String(const String &src) : std::vector(src), strings() {}; + + /// Allocate a new String. + String() { } + + /// Deallocate a String. + ~String() { + for (std::map::iterator i = strings.begin(); i != strings.end(); ++i) + delete (*i).second; + }; + + /// Convert to native representation. + /** For pointer types like \c char*, the returned pointer is usually only valid as long as + * this object exists, or until it is modified and cast to the same type again. */ + template operator T() const { return NativeString::getNative(*this); } + + /// Compare with native representation. + template bool operator==(const T &src) const { return *this == String(src); } + /// Compare with other Strings. + bool operator==(const String &src) const { return *(std::vector*)this == src; } + + /// Compare with native representation. + template bool operator!=(const T &src) const { return *this != String(src); } + /// Compare with other Strings. + bool operator!=(const String &src) const { return *(std::vector*)this != src; } +}; + +template void NativeString::getString(String &dest, const STR* src) { + Size strlen = 0; + while (src[strlen]) strlen++; + dest.resize(strlen); + for (strlen = 0; src[strlen]; strlen++) dest[strlen] = (sizeof(STR)==1?(unsigned char)src[strlen]:sizeof(STR)==2?(unsigned short)src[strlen]:src[strlen]); +} + +template STR* NativeString::getNative(const String &src) { + Size strlen = src.size(); + STR* dest = new STR[strlen+1]; + dest[strlen] = 0; + for (; strlen > 0; strlen--) dest[strlen-1] = src[strlen-1]; + src.addNative(new String::NativeArray(dest)); + return dest; +} + +template <> class NativeString { +protected: + friend class String; + static void getString(String &dest, const std::string *src) { + Size strlen = src->length(); + dest.resize(strlen); + for (Size i = 0; i< strlen; i++) dest[i] = (*src)[i]; + } + static std::string* getNative(const String &src) { + Size strlen = src.size(); + std::string* dest = new std::string(); + for (Size i = 0; i < strlen; i++) dest->append(1,src[i]); + src.addNative(new String::NativeObject(dest)); + return dest; + } +}; + +template <> class NativeString : public NativeString {}; + +template <> class NativeString { +protected: + friend class String; + static void getString(String &dest, const std::string &src) { + Size strlen = src.length(); + dest.resize(strlen); + for (Size i = 0; i< strlen; i++) dest[i] = src[i]; + } + static std::string& getNative(const String &src) { + Size strlen = src.size(); + std::string* dest = new std::string(); + for (Size i = 0; i < strlen; i++) dest->append(1,src[i]); + src.addNative(new String::NativeObject(dest)); + return *dest; + } +}; + +template <> class NativeString : public NativeString {}; + +class ToplevelWindow; +class Menu; +class TransientWindow; +class Screen; +class Window; + +/// Callback for window events. +struct Window_Callback { +public: + /// Called whenever this window changes position. + virtual void windowMoved(Window *win, int x, int y) = 0; + virtual ~Window_Callback() {} +}; + + +/** \brief A Window is a rectangular sub-area of another window. + * + * Windows are arranged hierarchically. A Window cannot leave its parent's + * area. They may contain their own buffer or share it with their parent. + * + * Usually, every GUI element is a subclass of Window. Note that this is + * \em not a GUI window with decorations like border and title bar. See ToplevelWindow + * for that. + */ +class Window { +protected: + friend class ToplevelWindow; + friend class TransientWindow; + friend class Menu; + + /// Width of the window. + int width; + /// Height of the window. + int height; + /// X position relative to parent. + int x; + /// Y position relative to parent. + int y; + + /// \c true if anything changed in this window or one of it's children + bool dirty; + + /// \c true if this window should be visible on screen. + bool visible; + + /// Parent window. + Window *const parent; + + /// Child window of last button-down event + /** It receives all drag/up/click/doubleclick events until an up event is received */ + Window *mouseChild; + + /// Child windows. + /** Z ordering is done in list order. The first element is the lowermost + * window. This window's content is below all children. */ + std::list children; + + /// List of registered event handlers. + std::list movehandlers; + + /// Register child window. + virtual void addChild(Window *child); + + /// Remove child window. + virtual void removeChild(Window *child); + + /// Mark this window and all parents as dirty. + void setDirty() { if (dirty) return; dirty = true; if (parent != NULL) parent->setDirty(); }; + + /// Replace clipboard content. + virtual void setClipboard(const String &s) { parent->setClipboard(s); }; + + /// Get clipboard content. + virtual const String& getClipboard() { return parent->getClipboard(); }; + + /// Default constructor. Only used in class Screen. Do not use. + Window(); + + /// Called whenever the focus changes. + virtual void focusChanged(bool gained) { + if (children.size() > 0) children.back()->focusChanged(gained); + } + +public: + + /// Add an event handler. + void addWindowHandler(Window_Callback *handler) { movehandlers.push_back(handler); } + + /// Remove an event handler. + void removeWindowHandler(Window_Callback *handler) { movehandlers.remove(handler); } + + /// Create a subwindow of the given parent window. + Window(Window *parent, int x, int y, int w, int h); + + /// Destructor. + virtual ~Window(); + + /// Resize this window to the given dimensions. + /** If that would move part of the window outside of this window's area, + * the outside area will silently be clipped while drawing. */ + virtual void resize(int w, int h); + /// Return this window's width + virtual int getWidth() const { return width; } + /// Return this window's height + virtual int getHeight() const { return height; } + + /// Move this window to the given position relative to the parent window's origin. + /** If that would move part of the window outside of this window's area, + * the outside area will silently be clipped while drawing. */ + virtual void move(int x, int y); + /// Return this window's X position relative to the parent's top left corner + virtual int getX() const { return x; } + /// Return this window's Y position relative to the parent's top left corner + virtual int getY() const { return y; } + /// Return this window's contents' X position relative to the screen's top left corner + virtual int getScreenX() const { return (parent == NULL?0:parent->getScreenX()+x); } + /// Return this window's contents' Y position relative to the screen's top left corner + virtual int getScreenY() const { return (parent == NULL?0:parent->getScreenY()+y); } + + /// Draw this window's content including all children. + virtual void paintAll(Drawable &d) const; + + /// Draw this window's content. + virtual void paint(Drawable &d) const {}; + + /// Show or hide this window. + /** By default, most windows are shown when created. Hidden windows do not receive any events. */ + virtual void setVisible(bool v) { visible = !!v; parent->setDirty(); } + + /// Returns \c true if this window is visible. + virtual bool isVisible() const { return (!parent || parent->isVisible()) && visible; } + + /// Return parent window. + /** May return NULL if this is the topmost window (the Screen). */ + Window *getParent() const { return parent; } + + /// Get the topmost window (the Screen) + Screen *getScreen(); + + /// Returns \c true if this window has currently the keyboard focus. + virtual bool hasFocus() const { return parent->hasFocus() && *parent->children.rbegin() == this; } + + /// Mouse was moved. Returns true if event was handled. + virtual bool mouseMoved(int x, int y); + /// Mouse was moved while a button was pressed. Returns true if event was handled. + virtual bool mouseDragged(int x, int y, MouseButton button); + /// Mouse was pressed. Returns true if event was handled. + virtual bool mouseDown(int x, int y, MouseButton button); + /// Mouse was released. Returns true if event was handled. + virtual bool mouseUp(int x, int y, MouseButton button); + /// Mouse was clicked. Returns true if event was handled. + /** Clicking means pressing and releasing the mouse button while not moving it. */ + virtual bool mouseClicked(int x, int y, MouseButton button); + /// Mouse was double-clicked. Returns true if event was handled. + virtual bool mouseDoubleClicked(int x, int y, MouseButton button); + + /// Key was pressed. Returns true if event was handled. + virtual bool keyDown(const Key &key); + /// Key was released. Returns true if event was handled. + virtual bool keyUp(const Key &key); + + /// Put this window on top of all it's siblings. Preserves relative order. + /** Returns true if the window accepts the raise request. */ + virtual bool raise() { + Window *last = parent->children.back(); + for (Window *cur = parent->children.back(); cur != this; cur = parent->children.back()) { + parent->children.remove(cur); + parent->children.push_front(cur); + } + if (last != this) { + focusChanged(true); + last->focusChanged(false); + } + parent->setDirty(); + return true; + } + + /// Put this window below all of it's siblings. Does not preserve relative order. + virtual void lower() { + parent->children.remove(this); + parent->children.push_front(this); + if (this != parent->children.back()) { + parent->children.back()->focusChanged(true); + focusChanged(false); + } + parent->setDirty(); + } + + /// Return the \p n th child + Window *getChild(int n) { + for (std::list::const_iterator i = children.begin(); i != children.end(); ++i) { + if (n--) return *i; + } + return NULL; + } + +}; + +/** \brief A Screen represents the framebuffer that is the final destination of the GUI. + * + * It's main purpose is to manage the current contents of the surface and to combine + * it with all GUI elements. You can't resize and move the screen. Requests to do so + * will be ignored. + * + * This is an abstract base class. You need to use a subclass which implements rgbToSurface + * and surfaceToRGB. + * + * To make things work, Screen needs events. Call the mouse and key event functions (see Window) + * whenever such an event occurs. Call update(void *surface, int ticks) regularly, if possible about + * every 40msec (25 fps). If nothing has changed, screen updates are quite fast. + */ +class Screen : public Window { +protected: + /// Screen buffer. + Drawable *const buffer; + + /// Clipboard. + String clipboard; + + /// Currently pressed mouse button. + MouseButton button; + + /// Store a single RGB triple (8 bit each) as a native pixel value and advance pointer. + virtual void rgbToSurface(RGB color, void **pixel) = 0; + + /// Map a single framebuffer pixel to an RGB value. + virtual RGB surfaceToRGB(void *pixel) = 0; + + /// Create a new screen with the given characteristics. + Screen(Size width, Size height); + + /// Create a new screen from the given GUI::Drawable. + Screen(Drawable *d); + +public: + /// Destructor. + virtual ~Screen(); + + /// Set clipboard content. + template void setClipboard(const STR s) { this->setClipboard(String(s)); } + + /// Set clipboard content. + virtual void setClipboard(const String &s) { clipboard = s; } + + /// Get clipboard content. + virtual const String& getClipboard() { return clipboard; } + + /// Do nothing. + virtual void resize(int w, int h); + + /// Do nothing. + virtual void move(int x, int y); + + /// Screen has always focus. + virtual bool hasFocus() const { return true; } + + /// Update the given surface with this screen's content, fully honouring the alpha channel. + /** \p ticks can be set to a different value depending on how much time has passed. Timing + * doesn't need to be perfect, but try to call this at least every 40 msec. Each tick + * amounts to about 10 msec. Returns the number of ticks until the next event is scheduled, or + * 0 if none. */ + Ticks update(void *surface, Ticks ticks = 1); + + /// Default: clear screen. + virtual void paint(Drawable &d) const; +}; + +class Timer; + +/// Timer callback type +struct Timer_Callback { +public: + /// The timer has expired. + /** Callbacks for timers take one parameter, the number of ticks since + * application start. Note that this value may wrap after a little less + * than 500 days. If you want callbacks to be called again, return the + * delay in ticks relative to the scheduled time of this + * callback (which may be earlier than now() ). Otherwise return 0. */ + virtual Ticks timerExpired(Ticks time) = 0; + virtual ~Timer_Callback() {} +}; + +/** \brief Timing service. + * Time is measured in ticks. A tick is about 10 msec. + * + * Note that this is not suitable as a high-accuracy timing service. Timer + * events can be off by many ticks, and a tick may be slightly more or + * slightly less than 10 msec. Because of that, Timers are only intended for + * simple animation effects, input timeouts and similar things. + * + */ +class Timer { +protected: + /// Number of ticks since application start. + static Ticks ticks; + + /// Compare two integers for 'less-than'. + struct ltuint { bool operator()(Ticks i1, Ticks i2) const { + return (i1 < i2); + } }; + + /// Active timers. + static std::multimap timers; + +public: + /// Advance time and check for expired timers. + static void check(Ticks ticks); + + /// Add a timed callback. \p ticks is a value relative to now(). + /** \p cb is not copied. */ + static void add(Timer_Callback *cb, const Ticks ticks) { timers.insert(std::pair(ticks+Timer::ticks,cb)); } + + static void remove(const Timer_Callback *const cb); + + /// Return current time (ticks since application start) + static Ticks now() { return ticks; } + + /// Return number of ticks until next scheduled timer or 0 if no timers + static Ticks next(); +}; + + +/** \brief A 24 bit per pixel RGB framebuffer aligned to 32 bit per pixel. + * + * Warning: This framebuffer type varies with CPU endiannes. It is meant as + * a testing/debugging tool. + */ +class ScreenRGB32le : public Screen { +protected: + /// Map a single RGB triple (8 bit each) to a native pixel value. + virtual void rgbToSurface(RGB color, void **pixel) { RGB **p = (RGB **)pixel; **p = color; (*p)++; }; + + /// Map a single surface pixel to an RGB value. + virtual RGB surfaceToRGB(void *pixel) { return *(RGB*)pixel; }; +public: + ScreenRGB32le(Size width, Size height) : Screen(width,height) {}; + + virtual void paint(Drawable &d) const; +}; + + +#ifdef SDL_MAJOR_VERSION +/** \brief An SDL surface adapter. + * + * This screen type will draw on an SDL surface (honouring transparency if the + * surface is already filled) and react on SDL events. It does not provide an + * application event loop, you have to call update() and event() regularly until + * you decide to quit the application. + * + * The implementation of this class could as well have been integrated into + * GUI::Screen. That might have been a little more efficient, but this way + * this class serves as an example for custom screen classes. + * + * Note that you have to include \c SDL.h \em before \c gui_tk.h for this class + * to become available. + */ +class ScreenSDL : public Screen { +protected: + /// not used. + virtual void rgbToSurface(RGB color, void **pixel) {}; + + /// not used. + virtual RGB surfaceToRGB(void *pixel) { return 0; }; + + /// The SDL surface being drawn to. + SDL_Surface *surface; + + /// Position of last mouse down. + int downx, downy; + + /// time of last click for double-click detection. + Ticks lastclick; + +public: + + /** Initialize SDL screen with a surface + * + * The dimensions of this surface will define the screen dimensions. Changing the surface + * later on will not change the available area. + */ + ScreenSDL(SDL_Surface *surface); + + /** Change current surface + * + * The new surface may have different dimensions than the current one, but + * the screen size will not change. This means that either the screen will + * not be displayed fully, or there will be borders around the screen. + */ + void setSurface(SDL_Surface *surface) { this->surface = surface; } + + /// Retrieve current surface. + SDL_Surface *getSurface() { return surface; } + + /// Overridden: makes background transparent by default. + virtual void paint(Drawable &d) const; + + /// Use this to update the SDL surface. The screen must not be locked. + Ticks update(Ticks ticks); + + /// Process an SDL event. Returns \c true if event was handled. + bool event(const SDL_Event *ev) { return event(*ev); } + + + /// Process an SDL event. Returns \c true if event was handled. + bool event(const SDL_Event& ev); +}; +#endif + +class Font; + +/** \brief A drawable represents a rectangular off-screen drawing area. + * + * It is an off-screen buffer which can be copied to other drawables or + * to a Screen's framebuffer. The drawable's origin is at the top left. All + * operations are silently clipped to the available area. The alpha channel + * is honoured while copying one drawable to another, but not during other + * drawing operations. + * + * Drawables have a current font, color and (x,y) position. Drawing takes place + * at the given point using the given font and color. The current position is + * then moved to the final point of the drawing primitive unless otherwise + * noted. Pixel width of all lines is selectable, but be aware that visual + * appearance of lines with width > 1 is not as sophisticated as in well-known + * toolkits like Java or Qt. + * + * Some drawing primitives are overloaded to take full coordinates. These ignore + * the current position, but update it just like their regular brethren. + */ +class Drawable { +protected: + friend Ticks Screen::update(void *, Ticks); + /// The actual pixel buffer. + RGB *const buffer; + /// Total width of buffer. + const int width; + /// Total height of buffer. + const int height; + /// \c true if \a buffer must be freed by this instance. + const bool owner; + + /// Current color. + RGB color; + /// Current font. + const Font *font; + /// Current line width. + int lineWidth; + + /// X translation. + const int tx; + /// Y translation. + const int ty; + /// clip x. + const int cx; + /// clip y. + const int cy; + /// clip width. + const int cw; + /// clip height. + const int ch; + + /// Current position x coordinate. + int x; + /// Current position y coordinate. + int y; + +public: + /// Create an empty drawable object with given dimensions. + /** Optionally, the area is cleared with a given color (default: fully transparent). */ + Drawable(int w, int h, RGB clear = Color::Transparent); + + /// Deep-copying copy constructor. + /** It honours clip and translate so that the resulting drawable, if drawn + * to the source drawable at (0,0), yields the same result as drawing + * directly to the source surface. If \p fill is not explicitly set, will + * copy the original surface's contents */ + Drawable(Drawable &src, RGB clear = 0); + + /// Shallow-copying copy constructor with clip & translate. See setClipTranslate(int x, int y, int w, int h). + Drawable(Drawable &src, int x, int y, int w, int h); + + /// Destructor. + virtual ~Drawable(); + + /// Clears the surface. + void clear(RGB clear = Color::Transparent); + + /// Change current drawing color. + /** The alpha part is honoured in all drawing primitives like this: + All drawing operations in this window will unconditionally overwrite + earlier content of this window. Only when combining this window with + it's parent, the alpha channel is fully honoured. */ + void setColor(RGB c) { color = c; }; + /// Return the currently selected drawing color. + RGB getColor() { return color; }; + + /// Change current drawing font. + void setFont(const Font *f) { font = f; }; + /// Return the currently selected drawing font. + const Font *getFont() { return font; }; + + /// Change current line width. + void setLineWidth(int w) { lineWidth = w; }; + /// Return the current line width. + int getLineWidth() { return lineWidth; }; + + /// Move the current position to the given coordinates. + void gotoXY(int x, int y) { this->x = x; this->y = y; }; + /// Return current position X. + int getX() { return x; } + /// Return current position Y. + int getY() { return y; } + + /// Return clip width + int getClipX() { return cx; } + /// Return clip height + int getClipY() { return cy; } + /// Return clip width + int getClipWidth() { return cw; } + /// Return clip height + int getClipHeight() { return ch; } + + /// Paint a single pixel at the current position. + void drawPixel() { if (x >= cx && x < cw && y >= cy && y < ch) buffer[x+tx+(y+ty)*width] = color; }; + /// Paint a single pixel at the given coordinates. + void drawPixel(int x, int y) { gotoXY(x,y); drawPixel(); }; + + /// Return the pixel color at the current position. + RGB getPixel() { if (x >= cx && x < cw && y >= cy && y < ch) return buffer[x+tx+(y+ty)*width]; return Color::Transparent; }; + /// Return the pixel color at the given coordinates. + RGB getPixel(int x, int y) { gotoXY(x,y); return getPixel(); }; + + /// Draw a straight line from the current position to the given coordinates. + void drawLine(int x2, int y2); + /// Draw a straight line from (\p x1,\p y1) to (\p x2,\p y2). + void drawLine(int x1, int y1, int x2, int y2) { gotoXY(x1,y1); drawLine(x2,y2); }; + + /// Draw a circle centered at the current position with diameter \p d. + /** The current position is not changed. */ + void drawCircle(int d); + /// Draw a circle centered at the given coordinates with diameter \p d. + /** The current position is set to the center. */ + void drawCircle(int x, int y, int d) { gotoXY(x, y); drawCircle(d); }; + + /// Draw a rectangle with top left at the current position and size \p w, \p h. + /** The current position is not changed. */ + void drawRect(int w, int h); + /// Draw a rectangle with top left at the given coordinates and size \p w, \p h. + /** The current position is set to the top left corner. */ + void drawRect(int x, int y, int w, int h) { gotoXY(x, y); drawRect(w, h); }; + + /// Flood-fill an area at the current position. + /** A continuous area with the same RGB value as the selected pixel is + changed to the current color. The current position is not changed. */ + void fill(); + /// Flood-fill an area at a given position. + /** see fill(), but moves current position to the given coordinates */ + void fill(int x, int y) { gotoXY(x,y); fill(); }; + + /// Draw a filled circle centered at the current position with diameter \p d. + /** The current position is not changed. */ + void fillCircle(int d); + /// Draw a filled circle centered at the given coordinates with diameter \p d. + /** The current position is set to the center. */ + void fillCircle(int x, int y, int d) { gotoXY(x, y); fillCircle(d); }; + + /// Draw a filled rectangle with top left at the current position and size \p w, \p h. + /** The current position is not changed. */ + void fillRect(int w, int h); + /// Draw a filled rectangle with top left at the given coordinates and size \p w, \p h. + /** The current position is set to the top left corner. */ + void fillRect(int x, int y, int w, int h) { gotoXY(x, y); fillRect(w, h); }; + + /// Draw a text string. + /** Current position is the leftmost pixel on the baseline of the character. + The current position is moved to the next character. Background is not + cleared. If \p interpret is \c true, some ASCII control characters like + backspace, line-feed, tab and ANSI colors are interpreted, and text is word-wrapped at + the window borders. You can optionally pass start and length of a substring + to print */ + void drawText(const String& text, bool interpret = true, Size start = 0, Size len = -1); + + /// Draw a text string at a fixed position. + /** see drawText(const String& text, bool interpret, Size start, Size len) */ + template void drawText(int x, int y, const STR text, bool interpret, Size start, Size len = -1) { gotoXY(x,y); drawText(String(text), interpret, start, len); }; + /// Draw a single character. + /** see drawText(const STR text, bool interpret), except wrapping is + performed on this character only */ + void drawText(const Char c, bool interpret = false); + /// Draw a single character at a fixed position. + /** see drawText(const Char c, bool interpret). */ + void drawText(int x, int y, const Char c, bool interpret = false) { gotoXY(x,y); drawText(c, interpret); }; + + /// Copy the contents of another Drawable to this one. + /** The top left corner of the source Drawable is put at the current + position. The alpha channel is fully honoured. Additionally, an extra + \p alpha value may be given which is multiplied with the source + alpha channel. The current position remains unchanged. */ + void drawDrawable(Drawable &d, unsigned char alpha = 1); + /// Copy the contents of another Drawable to this one at a given position. + /** see drawDrawable(Drawable &d, unsigned char alpha). The current position + is moved to the given coordinates. */ + void drawDrawable(int x, int y, Drawable &d, unsigned char alpha = 1) { gotoXY(x,y); drawDrawable(d, alpha); }; + +}; + +/** \brief A variable- or fixed-width fixed-size Font. + * + * These Fonts can't be scaled once instantiated, but it is possible + * to subclass this abstract font class to encapsulate scalable + * fonts. Fonts can be anti-aliasing and multi-coloured, depending on + * subclass. There is no encoding enforced. The font object implicitly + * knows about it's encoding. Because of that, there is a utility + * function for string examination as well. + * + * You can't instantiate objects of this class directly, use one of the + * subclasses like BitmapFont. + * + * This class provides a font registry which allows you to register + * and retrieve font objects using a name of your choice. It is recommended + * to use a naming scheme like "FontName-variant-pixelsize-encoding" where + * variant is "normal", "bold", "italic" or "bolditalic". No one enforces + * this, however. + * + * The GUI uses some special font names. You must add them before creating + * the relevant GUI item. + * \li \c default - used if a requested font was not found + * \li \c title - GUI::ToplevelWindow title + * \li \c input - GUI::Input + * + */ +class Font { +protected: + friend void Drawable::drawText(const Char c, bool interpret); + friend void Drawable::drawText(const String& s, bool interpret, Size start, Size len); + + /// Compare two strings for 'less-than'. + struct ltstr { bool operator()(const char* s1, const char* s2) const { + return strcmp(s1, s2) < 0; + } }; + /// Font registry. Contains all known font objects indexed by name. + static std::map registry; + + /// Default constructor. + Font() {}; + + /// Draw character to a drawable at the current position. + /** \p d's current position is advanced to the position of the next character. + * The y coordinate is located at the baseline before and after the call. */ + virtual void drawChar(Drawable *d, const Char c) const = 0; + + /// Draw \p len characters to a drawable at the current position, starting at string position \p start. + /** This can be used to provide kerning. */ + virtual void drawString(Drawable *d, const String &s, Size start, Size len) const { if (len > s.size()-start) len = s.size()-start; len += start; while (start < len) drawChar(d,s[start++]); } + +public: + /// Return a font with the given name (case-sensitive). + /** If no font was registered with that name, returns NULL. */ + static const Font *getFont(const char *name) { + std::map::iterator i = registry.find(name); + if (i == registry.end()) return(strcmp(name,"default")?getFont("default"):NULL); + return (*i).second; + } + + /// Add a font with a given name. + /** Don't delete this font once added. This class will do that for you. + * If a font with that name already exists, it will be replaced. */ + static void addFont(const char *name, Font *font) { + std::map::iterator i = registry.find(name); + if (i != registry.end()) delete (*i).second; + registry[name] = font; + } + + virtual ~Font() {}; + + /// Retrieve total height of font in pixels. + virtual int getHeight() const = 0; + + /// Retrieve the ascent, i.e. the number of pixels above the base line. + virtual int getAscent() const = 0; + + /// Return width of a string. + template int getWidth(const STR s, Size start = 0, Size len = (Size)-1) const { + return this->getWidth(String(s), start, len); + } + + /// Retrieve width of a character. + virtual int getWidth(Char c = 'M') const = 0; + + /// Retrieve width of a string of characters. + /** Can be used to provide kerning. */ + virtual int getWidth(const String &s, Size start = 0, Size len = (Size)-1) const { + int width = 0; + if (start+len > s.size()) len = s.size()-start; + while (len--) width += getWidth(s[start++]); + return width; + } + + /// Characters with special appearance or meaning. + enum SpecialChar { CR = '\r', LF = '\n', BS = '\b', Tab = '\t', Space = ' ', ESC = 27 }; + + /// Convert a character to an equivalent SpecialChar. May return values not in SpecialChar. + virtual SpecialChar toSpecial(Char c) const { return (SpecialChar)(c<255?c:255); } + + /// Convert a SpecialChar to an equivalent character. + virtual Char fromSpecial(SpecialChar c) const { return c; } +}; + +/** \brief A bitmap font. + * This is a font which is defined by a binary bit map. Each bit in the bitmap + * defines one pixel. Bits may be arranged in various common patterns. + * + * Encoding free, character size depends on number of characters in the font. + */ +class BitmapFont : public Font { +protected: + /// The actual font data. + const unsigned char *const bitmap; + + /// Width of a character cell. + const int width; + + /// Height of all characters. + const int height; + + /// Ascent of all characters. + const int ascent; + + /// Array of character widths. If font is fixed-width, this is NULL and \a width is used. + const int *const widths; + + /// Array of character ascents. If this is NULL, \a ascent is used for all characters. + /** This allows character data to be flush to the top or bottom of it's bitmap area. */ + const int *const ascents; + + /// True if set bits are background, false otherwise. + const bool background_set; + + /// Number of bits added to get from a column to the next column. + const int col_step; + + /// Distance between 2 rows of a character, or 0 for variable-width rows. + const int row_step; + + /// Distance of two characters in the bitmap in bits. + /** This is calculated as abs(row_step*height) unless explicitly specified. */ + const int character_step; + + /// Array of pointers to font data. If set, neither \a bitmap nor \a character_step are used. + const unsigned char *const*const char_position; + + /// Array of SpecialChar equivalents. + /** If unset, encoding is assumed ASCII-like for the first 32 characters */ + const SpecialChar *const special; + + /// If \c true, then all arrays are freed on destruction. + const bool owner; + + /// Last defined character. Characters above this will be ignored. + const Char last; + + /// Draw character to a drawable at the current position. + /** \p d's current position is advanced to the position of the next character. + * The y coordinate is located at the baseline before and after the call. */ + virtual void drawChar(Drawable *d, const Char c) const; + +public: + /// Constructor. + /** The default values provide an 8 bit wide fixed-width pixel layout with each byte a row, + * arranged top-to-bottom just like a PC's VGA font. See the individual member documentation + * for details on the parameters. */ + BitmapFont(const unsigned char *data, int height, int ascent, bool owner = false, + int width = 8, bool background_set = false, + int col_step = -1, int row_step = 8, int character_step = 0, Char last = 256, + const int *widths = NULL, const int *ascents = NULL, + const unsigned char *const* char_position = NULL, + const SpecialChar *special = NULL); + + virtual ~BitmapFont(); + + /// Retrieve total height of font in pixels. + virtual int getHeight() const { return height; }; + + /// Retrieve the ascent, i.e. the number of pixels above the base line. + virtual int getAscent() const { return ascent; }; + + /// Retrieve width of a character. + virtual int getWidth(Char c = 'M') const { return (widths != NULL?widths[c]:width); }; + + /// Convert a character to an equivalent SpecialChar. See Font::toSpecial(Char c) + virtual SpecialChar toSpecial(Char c) const { return (special != NULL?special[c]:Font::toSpecial(c)); } + + /// Convert a character to an equivalent character. See Font::fromSpecial(SpecialChar c). + virtual Char fromSpecial(SpecialChar c) const { if (special == NULL) return Font::fromSpecial(c); Char i = 0; while(special[i] != c) i++; return i; } + +}; + +class ActionEventSource; +/// Callback for action events. +struct ActionEventSource_Callback { +public: + /// Handler with optional String argument. + /** If the event source doesn't provide an additional argument, the name will be used. */ + virtual void actionExecuted(ActionEventSource *source, const String &arg) = 0; + virtual ~ActionEventSource_Callback() {} +}; + +/// Event class for action events. +/** Action events are events generated by GUI elements like Buttons, Menus and by pressing Enter in + * an Input. All of these are handled similarly: The source of such an event has a name, and the + * Event may also be connected with a String describing what was executed, like the name of the + * Menu entry or the contents of the Input. + */ +class ActionEventSource { +protected: + /// List of registered action handlers. + std::list actionHandlers; + + /// This event source's name. + /** The name is primarily meant for your own purposes, for example identification of the activated + * element. One exception are Menubars, which display the name of their Menus. */ + String name; + +public: + /// Create a named event source. + template ActionEventSource(const STR name) : name(String(name)) { } + + /// Dummy destructor. + virtual ~ActionEventSource() {} + + /// Add a button press event handler. + void addActionHandler(ActionEventSource_Callback *handler) { actionHandlers.push_back(handler); } + + /// Remove a button press event handler. + void removeActionHandler(ActionEventSource_Callback *handler) { actionHandlers.remove(handler); } + + /// Set the name of this event source. + template void setName(const STR name) { this->name = String(name); } + + /// Get the name of this event source. + const String &getName() const { return name; } + + /// Execute handlers. + void executeAction(const String &arg) { + std::list::iterator i = actionHandlers.begin(); + bool end = (i == actionHandlers.end()); + while (!end) { + ActionEventSource_Callback *c = *i; + ++i; + end = (i == actionHandlers.end()); + c->actionExecuted(this,arg); + } + } + + /// Execute handlers. + void executeAction() { executeAction(name); } +}; + +/// Internal class for windows whose child content should not span the entire area. +class BorderedWindow : public Window { +protected: + /// Borders. + int border_left, border_top, border_right, border_bottom; + + /// Create a bordered window. + BorderedWindow(Window *parent, int x, int y, int w, int h, int bl, int bt, int br, int bb) : + Window(parent,x,y,w,h), border_left(bl), border_top(bt), border_right(br), border_bottom(bb) {} + +public: + virtual void paintAll(Drawable &d) const; + virtual bool mouseMoved(int x, int y); + virtual bool mouseDown(int x, int y, MouseButton button); + virtual bool mouseDragged(int x, int y, MouseButton button); + virtual int getScreenX() const { return Window::getScreenX()+border_left; } + virtual int getScreenY() const { return Window::getScreenY()+border_top; } +}; + +/// A text label +/** Text labels are positioned relative to the top left corner of their bounding box. + * They size themselves automatically and display their text in non-interpreted mode. + */ + +class Label : public Window { + /// The Font used + const Font *font; + + /// Text color + RGB color; + + /// Text + String text; + + /// multiline text? + bool interpret; + +public: + /// Create a text label with given position, \p text, \p font and \p color. + /** If \p width is given, the resulting label is a word-wrapped multiline label */ + template Label(Window *parent, int x, int y, const STR text, int width = 0, const Font *font = Font::getFont("default"), RGB color = Color::Text) : + Window(parent, x, y, (width?width:1), 1), font(font), color(color), text(text), interpret(width != 0) + { resize(); } + + /// Set a new text. Size of the label is adjusted accordingly. + template void setText(const STR text) { this->text = text; resize(); } + /// Retrieve current text + const String& getText() { return text; } + + /// Set a new font. Size of the label is adjusted accordingly. + void setFont(const Font *font) { this->font = font; resize(); } + /// Retrieve current font + const Font *getFont() { return font; } + + /// Set a new text color. + void setColor(const RGB color) { this->color = color; resize(); } + /// Retrieve current color + RGB getColor() { return color; } + + /// Calculate label size. Parameters are ignored. + virtual void resize(int w = -1, int h = -1) { + if (w == -1) w = (interpret?getWidth():0); + else interpret = (w != 0); + Drawable d((w?w:1), 1); + d.setFont(font); + d.drawText(0, font->getAscent(), text, interpret, 0); + if (interpret) Window::resize(w, d.getY()-font->getAscent()+font->getHeight()); + else Window::resize(d.getX(), font->getHeight()); + } + + /// Paint label + virtual void paint(Drawable &d) const { d.setColor(color); d.drawText(0, font->getAscent(), text, interpret, 0); } + + virtual bool raise() { return false; } +}; + + +/// A single-line text input +/** It uses Font::getFont("input") to display content. + * It supports selection, the clipboard and all well-known key bindings (except undo). + */ +class Input : public Window, public Timer_Callback, public ActionEventSource { +protected: + /// The text entered. + String text; + + /// Current position in \a text. + Size pos; + + /// Last updated position in \a text. + Size lastpos; + + /// Coordinates according to pos. + int posx, posy; + + /// Selection in \a text. + Size start_sel, end_sel; + + /// Is cursor visible at the moment? + bool blink; + + /// Insert mode? + bool insert; + + /// Multiline? + bool multi; + + /// Horizontal scrolling offset. + int offset; + + /// Ensure that pos is visible. + void checkOffset() { + if (lastpos == pos) return; + const Font *f = Font::getFont("input"); + if (multi) { + Drawable d(width-6,1); + d.setFont(f); + d.drawText(0, 0, text, multi, 0, pos); + posy = d.getY(); + posx = d.getX(); + if (posy-offset > height-8-f->getHeight()) offset = posy-height+8+f->getHeight(); + if (posy-offset < 0) offset = posy; + } else { + posy = 0; + posx = f->getWidth(text,0,pos); + if (f->getWidth(text,0,pos+1)-offset > width-10) offset = f->getWidth(text,0,pos+1)-width+10; + if (f->getWidth(text,0,(pos>0?pos-1:0))-offset < 0) offset = f->getWidth(text,0,(pos>0?pos-1:0)); + } + lastpos = pos; + setDirty(); + } + +public: + /// Create an input with given position and width. If not set, height is calculated from the font and input is single-line. + Input(Window *parent, int x, int y, int w, int h = 0) : + Window(parent,x,y,w,(h?h:Font::getFont("input")->getHeight()+10)), ActionEventSource("GUI::Input"), + text(""), pos(0), lastpos(0), posx(0), posy(0), start_sel(0), end_sel(0), blink(true), insert(true), multi(h != 0), offset(0) + { Timer::add(this,30); } + + ~Input() { + Timer::remove(this); + } + + /// Paint input. + virtual void paint(Drawable &d) const; + + /// Clear selected area. + void clearSelection() { + text.erase(text.begin()+(pos = imin(start_sel,end_sel)),text.begin()+imax(start_sel,end_sel)); + start_sel = end_sel = pos; + } + + /// Copy selection to clipboard. + void copySelection() { + setClipboard(String(text.begin()+imin(start_sel,end_sel),text.begin()+imax(start_sel,end_sel))); + } + + /// Cut selection to clipboard. + void cutSelection() { + setClipboard(String(text.begin()+imin(start_sel,end_sel),text.begin()+imax(start_sel,end_sel))); + clearSelection(); + } + + /// Paste from clipboard. + void pasteSelection() { + String c = getClipboard(); + clearSelection(); + text.insert(text.begin()+pos,c.begin(),c.end()); + start_sel = end_sel = pos += c.size(); + } + + /// get character position corresponding to coordinates + Size findPos(int x, int y); + + /// Set text to be edited. + template void setText(const STR text) { this->text = text; setDirty(); }; + /// Get the entered text. If you need it longer, copy it immediately. + const String& getText() { return text; }; + + /// Handle text input. + virtual bool keyDown(const Key &key); + + /// Handle mouse input. + virtual bool mouseDown(int x, int y, MouseButton button); + + /// Handle mouse input. + virtual bool mouseDragged(int x, int y, MouseButton button); + + /// Timer callback function + virtual Ticks timerExpired(Ticks time) + { blink = !blink; setDirty(); return 30; } + +}; + +class ToplevelWindow; +/// Callbacks for window events. +struct ToplevelWindow_Callback { +public: + /// The window has been asked to be closed. + /** Return \c false in order to block the requested action. Do not do any + * deallocation here, as other callbacks may abort the close process. + */ + virtual bool windowClosing(ToplevelWindow *win) = 0; + + /// The window will been closed. + /** Now it is safe to deallocate all external resources that applications + * may have associated with this window, like registering this window + * with external callbacks. + */ + virtual void windowClosed(ToplevelWindow *win) = 0; + virtual ~ToplevelWindow_Callback() {} +}; + +/// An actual decorated window. +class ToplevelWindow : public BorderedWindow, public ActionEventSource_Callback { +protected: + /// Title text + String title; + + /// Drag base + int dragx, dragy; + + /// List of registered event handlers. + std::list closehandlers; + + /// System menu (top left) + Menu *systemMenu; + +public: + /// Create a new GUI Frame with title bar, border and close button + template ToplevelWindow(Screen *parent, int x, int y, int w, int h, const STR title); + + /// Call cleanup handlers + ~ToplevelWindow() { + std::list::iterator i = closehandlers.begin(); + bool end = (i == closehandlers.end()); + while (!end) { + ToplevelWindow_Callback *c = *i; + ++i; + end = (i == closehandlers.end()); + c->windowClosed(this); + } + } + + /// Menu callback function + virtual void actionExecuted(ActionEventSource *src, const String &item) { + if (item == String("Close")) close(); + } + + /// Add a window event handler. + void addCloseHandler(ToplevelWindow_Callback *handler) { closehandlers.push_back(handler); } + + /// Remove a window event handler. + void removeCloseHandler(ToplevelWindow_Callback *handler) { closehandlers.remove(handler); } + + virtual void paint(Drawable &d) const; + virtual bool mouseDown(int x, int y, MouseButton button); + virtual bool mouseDoubleClicked(int x, int y, MouseButton button) { + if (button == Left && x < 32 && x > 6 && y > 4 && y < 31) { + close(); + return true; + } + BorderedWindow::mouseClicked(x,y,button); + return true; + } + virtual bool mouseUp(int x, int y, MouseButton button) { + if (button == Left && dragx >= 0 && dragy >= 0) { + dragx = dragy = -1; + return true; + } + BorderedWindow::mouseUp(x,y,button); + return true; + } + virtual bool mouseDragged(int x, int y, MouseButton button) { + if (button == Left && dragx >= 0 && dragy >= 0) { + move(x-dragx+this->x,y-dragy+this->y); + return true; + } + BorderedWindow::mouseDragged(x,y,button); + return true; + } + virtual bool mouseMoved(int x, int y) { + BorderedWindow::mouseMoved(x,y); + return true; + } + + /// Put window on top of all other windows without changing their relative order + virtual bool raise() { + Window *last = parent->children.back(); + parent->children.remove(this); + parent->children.push_back(this); + if (last != this) { + focusChanged(true); + last->focusChanged(false); + } + return true; + } + + /// Set a new title. + template void setTitle(const STR title) { this->title = title; setDirty(); } + /// Retrieve current title. + const String& getTitle() { return title; } + + /// Close window. + void close() { + bool doit = true; + std::list::iterator i = closehandlers.begin(); + bool end = (i == closehandlers.end()); + while (!end) { + ToplevelWindow_Callback *c = *i; + ++i; + end = (i == closehandlers.end()); + doit = doit && c->windowClosing(this); + } + if (doit) delete this; + } +}; + +/// A floating temporary window that is not restricted by it's parent's area. +/** They have a parent which displays them, + * but that parent is not their real parent in the window hierarchy: They are always top-level elements, thus + * they are not clipped to their logical parent's area, they can freely overlay any part of the screen. + * As another consequence, they are not automatically deleted when their logical parent is deleted. + * + * You should observe the following points: + * \li TransientWindows behave mostly as if their logical parent was their true parent, but are not + * restricted to it's area + * \li TransientWindows are deleted automatically when the ToplevelWindow is deleted in which their logical + * parent resides; do NOT delete them in your destructor or bad things will happen + * \li only the logical parent object can show/hide a TransientWindow at will + * \li it will close automatically upon clicking other GUI elements, \em except for the + * logical parent and it's children + */ +class TransientWindow : public Window, Window_Callback, ToplevelWindow_Callback { +protected: + /// The true parent window. + Window *realparent; + + /// User selected position relative to logical parent. + int relx, rely; + +public: + /// Handle automatic hiding + virtual void focusChanged(bool gained) { + Window::focusChanged(gained); + if (isVisible() && !gained) { + if (realparent->hasFocus()) raise(); + else setVisible(false); + } + } + + /// Handle automatic delete + void windowClosed(ToplevelWindow *win) { + delete this; + } + + /// No-op + bool windowClosing(ToplevelWindow *win) { return true; } + + /// Create a transient window with given position and size + /** \a parent is the logical parent. The true parent object is + * always the screen the logical parent resides on. */ + TransientWindow(Window *parent, int x, int y, int w, int h) : + Window(parent->getScreen(),x+parent->getScreenX(),y+parent->getScreenY(),w,h), + realparent(parent), relx(x), rely(y) { + Window *p = realparent, *last = NULL, *last2 = NULL; + while (p != NULL) { + p->addWindowHandler(this); + last2 = last; + last = p; + p = p->getParent(); + } + dynamic_cast(last2)->addCloseHandler(this); + } + + ~TransientWindow() { + Window *p = realparent, *last = NULL, *last2 = NULL; + while (p != NULL) { + p->removeWindowHandler(this); + last2 = last; + last = p; + p = p->getParent(); + } + dynamic_cast(last2)->removeCloseHandler(this); + } + + virtual void move(int x, int y) { relx = x; rely = y; + Window::move(x+realparent->getScreenX(),y+realparent->getScreenY()); } + virtual int getX() { return x-realparent->getScreenX(); } + virtual int getY() { return y-realparent->getScreenY(); } + virtual void setVisible(bool v) { if (v) raise(); Window::setVisible(v); } + virtual void windowMoved(Window *src, int x, int y) { move(relx,rely); } + + /// Put window on top of all other windows without changing their relative order + virtual bool raise() { + Window *last = parent->children.back(); + parent->children.remove(this); + parent->children.push_back(this); + if (last != this) { + focusChanged(true); + last->focusChanged(false); + } + return true; + } + +}; + +/// A popup menu. +/** Popup menus are used as context menus or as part of a menu bar. Menus are not visible when created. + * Menus have a name which can be used in event handlers to identify it, and it is used in Menubars as well. + * + * Currently, menu entries are not interpreted in any way. This may change in the future. To ensure upwards + * compatibility, avoid the \c '&' character in menu entries, since it may have a special meaning in future + * versions. Menus use the Font named "menu". + * + * Please note the general remarks in GUI::TransientWindow + */ +class Menu : public TransientWindow, public ActionEventSource { +protected: + /// List of menu items (displayed text) + std::vector items; + + /// Currently selected menu item. + /** Can be -1 if no item is currently active. */ + int selected; + + /// Flag to skip the first mouseUp-event + bool firstMouseUp; + + /// Where we grabbed the mouse from. + Window *mouseTakenFrom; + + /// Selects menu item at position (x,y). + /** \a selected is set to -1 if there is no active item at that location. */ + virtual void selectItem(int x, int y) { + y -= 2; + selected = -1; + const int height = Font::getFont("menu")->getHeight()+2; + std::vector::iterator i; + for (i = items.begin(); i != items.end() && y > 0; ++i) { + selected++; + if ((*i).size() > 0) y -= height; + else y -= 12; + } + if (y > 0 || (selected >= 0 && items[selected].size() == 0)) selected = -1; + } + + virtual Size getPreferredWidth() { + Size width = 0; + const Font *f = Font::getFont("menu"); + std::vector::iterator i; + for (i = items.begin(); i != items.end() && y > 0; ++i) { + Size newwidth = f->getWidth(*i); + if (newwidth > width) width = newwidth; + } + return width+39; + } + + virtual Size getPreferredHeight() { + Size height = 0; + const Size h = Font::getFont("menu")->getHeight()+2; + std::vector::iterator i; + for (i = items.begin(); i != items.end() && y > 0; ++i) { + height += ((*i).size() > 0?h:12); + } + return height+6; + } + +public: + /// Create a menu with given position + /** Size is determined dynamically. \a parent is the logical parent. The true parent object is + * always the screen the logical parent resides on. */ + template Menu(Window *parent, int x, int y, const STR name) : + TransientWindow(parent,x,y,4,4), ActionEventSource(name), selected(-1) + { setVisible(false); } + + ~Menu() { + setVisible(false); + } + + /// Paint button. + virtual void paint(Drawable &d) const; + + /// Highlight current item. + virtual bool mouseMoved(int x, int y) { + selectItem(x,y); + return true; + } + + /// Highlight current item. + virtual bool mouseDragged(int x, int y, MouseButton b) { + selectItem(x,y); + return true; + } + + virtual bool mouseDown(int x, int y, MouseButton button) { return true; } + + /// Possibly select item. + virtual bool mouseUp(int x, int y, MouseButton button) { + selectItem(x,y); + if (firstMouseUp) firstMouseUp = false; + else setVisible(false); + execute(); + return true; + } + + /// Handle keyboard input. + virtual bool keyDown(const Key &key) { + if (key.special == Key::Up) selected--; + else if (key.special == Key::Down) selected++; + else if (key.special == Key::Enter) { execute(); return true; } + else if (key.special == Key::Escape) { setVisible(false); return true; } + else return true; + if (items[selected].size() == 0 && items.size() > 1) return keyDown(key); + if (selected < 0) selected = items.size()-1; + if (selected >= (int)items.size()) selected = 0; + return true; + } + + + /// Add a menu item at end. An empty string denotes a separator. + template void addItem(const T item) { + items.push_back(String(item)); + resize(getPreferredWidth(),getPreferredHeight()); + } + + /// Remove an existing menu item. + template void removeItem(const T item) { + const String s(item); + std::vector::iterator i = items.begin(); + while (i != items.end() && s != (*i)) ++i; + if (i != items.end()) items.erase(i); + resize(getPreferredWidth(),getPreferredHeight()); + } + + virtual void setVisible(bool v) { + TransientWindow::setVisible(v); + if (v) { + parent->mouseChild = this; + raise(); + firstMouseUp = true; + } + } + + /// Execute menu item. + void execute() { + if (selected >= 0) { + setVisible(false); + executeAction(items[selected]); + } + } +}; + +/// A push button +/** Buttons have 3D appearance and can have any child widget as content. + * There are convenience constructors for common forms of buttons. + */ +class Button : public BorderedWindow, public ActionEventSource { +protected: + /// \c true, if button is currently pressed down. + bool pressed; + +public: + /// Create a button with given position and size + Button(Window *parent, int x, int y, int w, int h) : BorderedWindow(parent,x,y,w,h,6,5,6,5), ActionEventSource("GUI::Button"), pressed(0) {} + + /// Create a text button. + /** If a size is specified, text is centered. Otherwise, button size is adjusted for the text. */ + template Button(Window *parent, int x, int y, const T text, int w = -1, int h = -1); + + /// Paint button. + virtual void paint(Drawable &d) const; + + /// Press button. + virtual bool mouseDown(int x, int y, MouseButton button) { + border_left = 7; border_right = 5; border_top = 7; border_bottom = 3; + pressed = true; + return true; + } + + /// Release button. + virtual bool mouseUp(int x, int y, MouseButton button) { + border_left = 6; border_right = 6; border_top = 5; border_bottom = 5; + pressed = false; + return true; + } + + /// Handle mouse activation. + virtual bool mouseClicked(int x, int y, MouseButton button) { + if (button == Left) { + executeAction(); + return true; + } + return false; + } + + /// Handle keyboard input. + virtual bool keyDown(const Key &key); + + /// Handle keyboard input. + virtual bool keyUp(const Key &key); + +}; + +/// A menu bar at the top of a ToplevelWindow +/** Menu bars aggregate several Menus. They have a simple border at the bottom. If your application + * has a work area with 3D sunken look, place it so that it overlaps the menu by one row. + * + * As with Menus, avoid the character \c '&' in Menu names as it may + * have a special meaning in future versions. Menubars use the Font named "menu". + */ +class Menubar : public Window, public ActionEventSource, ActionEventSource_Callback { +protected: + /// Currently activated menu index. + int selected; + + /// Horizontal position of next menu. + int lastx; + + /// List of Menus. + std::vector menus; + +public: + /// Create a menubar with given position and size + /** Height is autocalculated from font size */ + Menubar(Window *parent, int x, int y, int w) : Window(parent,x,y,w,Font::getFont("menu")->getHeight()+5), ActionEventSource("GUI::Menubar"), selected(-1), lastx(0) {} + + /// Add a Menu. + template void addMenu(const STR name) { + const String n(name); + menus.push_back(new Menu(this,lastx,height-2,n)); + menus.back()->addActionHandler(this); + lastx += Font::getFont("menu")->getWidth(n)+14; + } + + /// Add a Menuitem. + template void addItem(int index, const STR name) { menus[index]->addItem(name); } + + /// Remove a Menuitem. + template void removeItem(int index, const STR name) { menus[index]->removeItem(name); } + + /// Paint menubar. + virtual void paint(Drawable &d) const; + + /// Open menu. + virtual bool mouseDown(int x, int y, MouseButton button) { + int oldselected = selected; + if (selected >= 0 && !menus[selected]->isVisible()) oldselected = -1; + if (selected >= 0) menus[selected]->setVisible(false); + if (x < 0 || x >= lastx) return true; + for (selected = menus.size()-1; menus[selected]->getX() > x; selected--); + if (oldselected == selected) selected = -1; + else menus[selected]->setVisible(true); + return true; + } + + /// Handle keyboard input. + virtual bool keyDown(const Key &key) { return true; }; + + /// Handle keyboard input. + virtual bool keyUp(const Key &key) { return true; }; + + virtual void actionExecuted(ActionEventSource *src, const String &arg) { + std::list::iterator i = actionHandlers.begin(); + bool end = (i == actionHandlers.end()); + while (!end) { + ActionEventSource_Callback *c = *i; + ++i; + end = (i == actionHandlers.end()); + c->actionExecuted(src,arg); + } + } +}; + +/// A checkbox +/** Checkboxes can have any child widget as content. + * There are convenience constructors for common forms of checkboxes. + */ +class Checkbox : public BorderedWindow, public ActionEventSource { +protected: + /// \c true, if checkbox is currently selected. + bool checked; + +public: + /// Create a checkbox with given position and size + Checkbox(Window *parent, int x, int y, int w, int h) : BorderedWindow(parent,x,y,w,h,16,0,0,0), ActionEventSource("GUI::Checkbox"), checked(0) {} + + /// Create a checkbox with text label. + /** If a size is specified, text is centered. Otherwise, checkbox size is adjusted for the text. */ + template Checkbox(Window *parent, int x, int y, const T text, int w = -1, int h = -1); + + /// Paint checkbox. + virtual void paint(Drawable &d) const; + + /// Change checkbox state. + virtual void setChecked(bool checked) { this->checked = checked; } + + /// Get checkbox state. + virtual bool isChecked() { return checked; } + + /// Press checkbox. + virtual bool mouseDown(int x, int y, MouseButton button) { + checked = !checked; + return true; + } + + /// Release checkbox. + virtual bool mouseUp(int x, int y, MouseButton button) { + execute(); + return true; + } + + /// Handle keyboard input. + virtual bool keyDown(const Key &key); + + /// Handle keyboard input. + virtual bool keyUp(const Key &key); + + /// Execute handlers. + virtual void execute() { + String arg(name); + if (!checked) arg.insert(arg.begin(),'!'); + executeAction(arg); + } +}; + +class Frame; + +/// A radio box. +/** Radio boxes can have any child widget as content. + * There are convenience constructors for common forms of radio boxes. + */ +class Radiobox : public BorderedWindow, public ActionEventSource { +protected: + /// \c true, if radio box is currently selected. + bool checked; + +public: + /// Create a radio box with given position and size + Radiobox(Frame *parent, int x, int y, int w, int h); + + /// Create a radio box with text label. + /** If a size is specified, text is centered. Otherwise, checkbox size is adjusted for the text. */ + template Radiobox(Frame *parent, int x, int y, const T text, int w = -1, int h = -1); + + /// Paint radio box. + virtual void paint(Drawable &d) const; + + /// Change radio box state. + virtual void setChecked(bool checked) { this->checked = checked; } + + /// Get radio box state. + virtual bool isChecked() { return checked; } + + /// Press radio box. + virtual bool mouseDown(int x, int y, MouseButton button) { + checked = true; + return true; + } + + /// Release checkbox. + virtual bool mouseUp(int x, int y, MouseButton button) { + executeAction(); + return true; + } + + /// Handle keyboard input. + virtual bool keyDown(const Key &key); + + /// Handle keyboard input. + virtual bool keyUp(const Key &key); +}; + +/// A rectangular 3D sunken frame +/** These can be used as generic grouping elements and also serve as aggregators for RadioBoxes. + */ +class Frame : public BorderedWindow, public ActionEventSource, protected ActionEventSource_Callback { +protected: + friend class Radiobox; + + /// Currently selected radio box. + int selected; + + /// Label of frame. + String label; + + /// Execute handlers. + virtual void actionExecuted(ActionEventSource *src, const String &arg) { + for (std::list::iterator i = children.begin(); i != children.end(); ++i) { + Radiobox *r = dynamic_cast(*i); + if (r != NULL && src != dynamic_cast(r)) r->setChecked(false); + } + executeAction(src->getName()); + } + +public: + /// Create a non-labeled frame with given position and size + Frame(Window *parent, int x, int y, int w, int h) : BorderedWindow(parent,x,y,w,h,5,5,5,5), ActionEventSource("GUI::Frame"), selected(0) {} + + /// Create a frame with text label. + template Frame(Window *parent, int x, int y, int w, int h, const T text) : + BorderedWindow(parent,x,y,w,h,5,Font::getFont("default")->getHeight()+2,5,5), + ActionEventSource(text), selected(0), label(text) { } + + /// Paint frame. + virtual void paint(Drawable &d) const; + +}; + +/// A message box with a single "Close" button. +class MessageBox : public GUI::ToplevelWindow { +protected: + Label *message; + Button *close; +public: + /// Create a new message box + template MessageBox(Screen *parent, int x, int y, int width, const STR title, const STR text) : + ToplevelWindow(parent, x, y, width, 1, title) { + message = new Label(this, 5, 5, text, width-10); + close = new GUI::Button(this, width/2-40, 10, "Close", 70); + close->addActionHandler(this); + setText(text); + } + + /// Set a new text. Size of the box is adjusted accordingly. + template void setText(const STR text) { + message->setText(text); + close->move(width/2-40, 20+message->getHeight()); + resize(width, message->getHeight()+100); + } +}; + +template ToplevelWindow::ToplevelWindow(Screen *parent, int x, int y, int w, int h, const STR title) : + BorderedWindow(parent, x, y, w, h, 6, 33, 6, 3), title(title), + dragx(-1), dragy(-1), closehandlers(), systemMenu(new Menu(this,-1,-2,"System Menu")) { + systemMenu->addItem("Move"); + systemMenu->addItem("Resize"); + systemMenu->addItem(""); + systemMenu->addItem("Minimize"); + systemMenu->addItem("Maximize"); + systemMenu->addItem("Restore"); + systemMenu->addItem(""); + systemMenu->addItem("Close"); + systemMenu->addActionHandler(this); +} + +template Button::Button(Window *parent, int x, int y, const STR text, int w, int h) : + BorderedWindow(parent,x,y,w,h,6,5,6,5), ActionEventSource(text), pressed(0) +{ + + Label *l = new Label(this,0,0,text); + if (width < 0) resize(l->getWidth()+border_left+border_right+10,height); + if (height < 0) resize(width,l->getHeight()+border_top+border_bottom+6); + l->move((width-border_left-border_right-l->getWidth())/2, + (height-border_top-border_bottom-l->getHeight())/2); +} + +template Checkbox::Checkbox(Window *parent, int x, int y, const STR text, int w, int h) : + BorderedWindow(parent,x,y,w,h,16,0,0,0), ActionEventSource(text), checked(0) +{ + Label *l = new Label(this,0,0,text); + if (width < 0) resize(l->getWidth()+border_left+border_right+4,height); + if (height < 0) resize(width,l->getHeight()+border_top+border_bottom+4); + l->move((width-border_left-border_right-l->getWidth())/2, + (height-border_top-border_bottom-l->getHeight())/2); +} + +template Radiobox::Radiobox(Frame *parent, int x, int y, const STR text, int w, int h) : + BorderedWindow(parent,x,y,w,h,16,0,0,0), ActionEventSource(text), checked(0) +{ + Label *l = new Label(this,0,0,text); + if (width < 0) resize(l->getWidth()+border_left+border_right+4,height); + if (height < 0) resize(width,l->getHeight()+border_top+border_bottom+4); + l->move((width-border_left-border_right-l->getWidth())/2, + (height-border_top-border_bottom-l->getHeight())/2); + addActionHandler(parent); +} + +}; + +#endif