diff options
author | Rodrigo Arias Mallo <rodarima@gmail.com> | 2024-12-10 22:30:12 +0100 |
---|---|---|
committer | Rodrigo Arias Mallo <rodarima@gmail.com> | 2024-12-10 22:30:12 +0100 |
commit | 429d5f88b94ff28416cbfc6420b6389fa284df97 (patch) | |
tree | fb6fdaf7731de1ef396f98b748c56f3149801c84 /dw |
Import RTFL 0.1.1v0.1.1
Diffstat (limited to 'dw')
40 files changed, 17220 insertions, 0 deletions
diff --git a/dw/Makefile.am b/dw/Makefile.am new file mode 100644 index 0000000..471a309 --- /dev/null +++ b/dw/Makefile.am @@ -0,0 +1,57 @@ +AM_CPPFLAGS = \ + -I$(top_srcdir) \ + -DDILLO_LIBDIR='"$(pkglibdir)/"' \ + -DCUR_WORKING_DIR='"@BASE_CUR_WORKING_DIR@/dw"' + +noinst_LIBRARIES = \ + libDw-core.a \ + libDw-fltk.a + +libDw_core_a_SOURCES = \ + core.hh \ + events.hh \ + findtext.cc \ + findtext.hh \ + imgbuf.hh \ + imgrenderer.hh \ + imgrenderer.cc \ + iterator.cc \ + iterator.hh \ + layout.cc \ + layout.hh \ + platform.hh \ + selection.hh \ + selection.cc \ + style.cc \ + style.hh \ + types.cc \ + types.hh \ + ui.cc \ + ui.hh \ + view.hh \ + widget.cc \ + widget.hh + +# "fltkcomplexbutton.cc", "fltkcomplexbutton.hh", "fltkflatview.cc", +# and "fltkflatview.hh" have been removed from libDw-fltk.a. + +libDw_fltk_a_SOURCES = \ + fltkcore.hh \ + fltkimgbuf.cc \ + fltkimgbuf.hh \ + fltkmisc.cc \ + fltkmisc.hh \ + fltkplatform.cc \ + fltkplatform.hh \ + fltkpreview.hh \ + fltkpreview.cc \ + fltkui.cc \ + fltkui.hh \ + fltkviewbase.cc \ + fltkviewbase.hh \ + fltkviewport.cc \ + fltkviewport.hh + +libDw_fltk_a_CXXFLAGS = @LIBFLTK_CXXFLAGS@ + +EXTRA_DIST = preview.xbm diff --git a/dw/core.hh b/dw/core.hh new file mode 100644 index 0000000..022574b --- /dev/null +++ b/dw/core.hh @@ -0,0 +1,60 @@ +#ifndef __DW_CORE_HH__ +#define __DW_CORE_HH__ + +#define __INCLUDED_FROM_DW_CORE_HH__ + +/** + * \brief Dw is in this namespace, or sub namespaces of this one. + * + * The core can be found in dw::core, widgets are defined directly here. + * + * \sa \ref dw-overview + */ +namespace dw { + +/** + * \brief The core of Dw is defined in this namespace. + * + * \sa \ref dw-overview + */ +namespace core { + +typedef unsigned char byte; + +class Layout; +class View; +class Widget; +class Iterator; + +// Nothing yet to free. +inline void freeall () { } + +namespace ui { + +class ResourceFactory; + +} // namespace ui +} // namespace core +} // namespace dw + +#include "../lout/object.hh" +#include "../lout/container.hh" +#include "../lout/signal.hh" + +#include "types.hh" +#include "events.hh" +#include "imgbuf.hh" +#include "imgrenderer.hh" +#include "style.hh" +#include "view.hh" +#include "platform.hh" +#include "iterator.hh" +#include "findtext.hh" +#include "selection.hh" +#include "layout.hh" +#include "widget.hh" +#include "ui.hh" + +#undef __INCLUDED_FROM_DW_CORE_HH__ + +#endif // __DW_CORE_HH__ diff --git a/dw/events.hh b/dw/events.hh new file mode 100644 index 0000000..5309186 --- /dev/null +++ b/dw/events.hh @@ -0,0 +1,83 @@ +#ifndef __DW_EVENTS_HH__ +#define __DW_EVENTS_HH__ + +#ifndef __INCLUDED_FROM_DW_CORE_HH__ +# error Do not include this file directly, use "core.hh" instead. +#endif + +namespace dw { +namespace core { + +/** + * \brief Platform independent representation. + */ +enum ButtonState +{ + /* We won't use more than these ones. */ + SHIFT_MASK = 1 << 0, + CONTROL_MASK = 1 << 1, + META_MASK = 1 << 2, + BUTTON1_MASK = 1 << 3, + BUTTON2_MASK = 1 << 4, + BUTTON3_MASK = 1 << 5 +}; + +/** + * \brief Base class for all events. + * + * The dw::core::Event hierarchy describes events in a platform independent + * way. + */ +class Event: public lout::object::Object +{ +public: +}; + +/** + * \brief Base class for all mouse events. + */ +class MouseEvent: public Event +{ +public: + ButtonState state; +}; + +/** + * \brief Base class for all mouse events related to a specific position. + */ +class MousePositionEvent: public MouseEvent +{ +public: + int xCanvas, yCanvas, xWidget, yWidget; +}; + +/** + * \brief Represents a button press or release event. + */ +class EventButton: public MousePositionEvent +{ +public: + int numPressed; /* 1 for simple click, 2 for double click, etc. */ + int button; +}; + +/** + * \brief Represents a mouse motion event. + */ +class EventMotion: public MousePositionEvent +{ +}; + +/** + * \brief Represents a enter or leave notify event. + */ +class EventCrossing: public MouseEvent +{ +public: + Widget *lastWidget, *currentWidget; +}; + +} // namespace core +} // namespace dw + +#endif // __DW_EVENTS_HH__ diff --git a/dw/findtext.cc b/dw/findtext.cc new file mode 100644 index 0000000..57c83c5 --- /dev/null +++ b/dw/findtext.cc @@ -0,0 +1,307 @@ +/* + * RTFL (originally part of dillo) + * + * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version; with the following exception: + * + * The copyright holders of RTFL give you permission to link this file + * statically or dynamically against all versions of the graphviz + * library, which are published by AT&T Corp. under one of the following + * licenses: + * + * - Common Public License version 1.0 as published by International + * Business Machines Corporation (IBM), or + * - Eclipse Public License version 1.0 as published by the Eclipse + * Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + + +#include "core.hh" +#include "../lout/debug.hh" +#include "../lout/msg.h" + +namespace dw { +namespace core { + +FindtextState::FindtextState () +{ + DBG_OBJ_CREATE ("dw::core::FindtextState"); + + key = NULL; + nexttab = NULL; + widget = NULL; + iterator = NULL; + hlIterator = NULL; +} + +FindtextState::~FindtextState () +{ + if (key) + free(key); + if (nexttab) + delete[] nexttab; + if (iterator) + delete iterator; + if (hlIterator) + delete hlIterator; + + DBG_OBJ_DELETE (); +} + +void FindtextState::setWidget (Widget *widget) +{ + this->widget = widget; + + // A widget change will restart the search. + if (key) + free(key); + key = NULL; + if (nexttab) + delete[] nexttab; + nexttab = NULL; + + if (iterator) + delete iterator; + iterator = NULL; + if (hlIterator) + delete hlIterator; + hlIterator = NULL; +} + +FindtextState::Result FindtextState::search (const char *key, bool caseSens, + bool backwards) +{ + if (!widget || *key == 0) // empty keys are not found + return NOT_FOUND; + + bool wasHighlighted = unhighlight (); + bool newKey; + + // If the key (or the widget) changes (including case sensitivity), + // the search is started from the beginning. + if (this->key == NULL || this->caseSens != caseSens || + strcmp (this->key, key) != 0) { + newKey = true; + if (this->key) + free(this->key); + this->key = strdup (key); + this->caseSens = caseSens; + + if (nexttab) + delete[] nexttab; + nexttab = createNexttab (key, caseSens, backwards); + + if (iterator) + delete iterator; + iterator = new CharIterator (widget, true); + + if (backwards) { + /* Go to end */ + while (iterator->next () ) ; + iterator->prev (); //We don't want to be at CharIterator::END. + } else { + iterator->next (); + } + } else + newKey = false; + + bool firstTrial = !wasHighlighted || newKey; + + if (search0 (backwards, firstTrial)) { + // Highlighting is done with a clone. + hlIterator = iterator->cloneCharIterator (); + for (int i = 0; key[i]; i++) + hlIterator->next (); + CharIterator::highlight (iterator, hlIterator, HIGHLIGHT_FINDTEXT); + CharIterator::scrollTo (iterator, hlIterator, + HPOS_INTO_VIEW, VPOS_CENTER); + + // The search will continue from the word after the found position. + iterator->next (); + return SUCCESS; + } else { + if (firstTrial) { + return NOT_FOUND; + } else { + // Nothing found anymore, reset the state for the next trial. + delete iterator; + iterator = new CharIterator (widget, true); + if (backwards) { + /* Go to end */ + while (iterator->next ()) ; + iterator->prev (); //We don't want to be at CharIterator::END. + } else { + iterator->next (); + } + // We expect a success. + Result result2 = search (key, caseSens, backwards); + assert (result2 == SUCCESS); + return RESTART; + } + } +} + +/** + * \brief This method is called when the user closes the "find text" dialog. + */ +void FindtextState::resetSearch () +{ + unhighlight (); + + if (key) + free(key); + key = NULL; +} + +/* + * Return a new string: with the reverse of the original. + */ +const char* FindtextState::rev(const char *str) +{ + if (!str) + return NULL; + + int len = strlen(str); + char *nstr = new char[len+1]; + for (int i = 0; i < len; ++i) + nstr[i] = str[len-1 -i]; + nstr[len] = 0; + + return nstr; +} + +int *FindtextState::createNexttab (const char *needle, bool caseSens, + bool backwards) +{ + const char* key; + + key = (backwards) ? rev(needle) : needle; + int i = 0; + int j = -1; + int l = strlen (key); + int *nexttab = new int[l + 1]; // + 1 is necessary for l == 1 case + nexttab[0] = -1; + + do { + if (j == -1 || charsEqual (key[i], key[j], caseSens)) { + i++; + j++; + nexttab[i] = j; + //_MSG ("nexttab[%d] = %d\n", i, j); + } else + j = nexttab[j]; + } while (i < l - 1); + + if (backwards) + delete [] key; + + return nexttab; +} + +/** + * \brief Unhighlight, and return whether a region was highlighted. + */ +bool FindtextState::unhighlight () +{ + if (hlIterator) { + CharIterator *start = hlIterator->cloneCharIterator (); + for (int i = 0; key[i]; i++) + start->prev (); + + CharIterator::unhighlight (start, hlIterator, HIGHLIGHT_FINDTEXT); + delete start; + delete hlIterator; + hlIterator = NULL; + + return true; + } else + return false; +} + +bool FindtextState::search0 (bool backwards, bool firstTrial) +{ + if (iterator->getChar () == CharIterator::END) + return false; + + bool ret = false; + const char* searchKey = (backwards) ? rev(key) : key; + int j = 0; + bool nextit = true; + int l = strlen (key); + + if (backwards && !firstTrial) { + _MSG("Having to do."); + /* Position correctly */ + /* In order to achieve good results (i.e: find a word that ends within + * the previously searched word's limit) we have to position the + * iterator in the semilast character of the previously searched word. + * + * Since we know that if a word was found before it was exactly the + * same word as the one we are searching for now, we can apply the + * following expression: + * + * Where l=length of the key and n=num of positions to move: + * + * n = l - 3 + * + * If n is negative, we have to move backwards, but if it is + * positive, we have to move forward. So, when l>=4, we start moving + * the iterator forward. */ + + if (l==1) { + iterator->prev(); + iterator->prev(); + } else if (l==2) { + iterator->prev(); + } else if (l>=4) { + for (int i=0; i<l-3; i++) { + iterator->next(); + } + } + + } else if (backwards && l==1) { + /* Particular case where we can't find the last character */ + iterator->next(); + } + + do { + if (j == -1 || charsEqual (iterator->getChar(),searchKey[j],caseSens)) { + j++; + nextit = backwards ? iterator->prev () : iterator->next (); + } else + j = nexttab[j]; + } while (nextit && j < l); + + if (j >= l) { + if (backwards) { + //This is the location of the key + iterator->next(); + } else { + // Go back to where the key was found. + for (int i = 0; i < l; i++) + iterator->prev (); + } + ret = true; + } + + if (backwards) + delete [] searchKey; + + return ret; +} + +} // namespace core +} // namespace dw diff --git a/dw/findtext.hh b/dw/findtext.hh new file mode 100644 index 0000000..c680348 --- /dev/null +++ b/dw/findtext.hh @@ -0,0 +1,84 @@ +#ifndef __DW_FINDTEXT_STATE_H__ +#define __DW_FINDTEXT_STATE_H__ + +#ifndef __INCLUDED_FROM_DW_CORE_HH__ +# error Do not include this file directly, use "core.hh" instead. +#endif + +#include <ctype.h> + +namespace dw { +namespace core { + +class FindtextState +{ +public: + typedef enum { + /** \brief The next occurrence of the pattern has been found. */ + SUCCESS, + + /** + * \brief There is no further occurrence of the pattern, instead, the + * first occurrence has been selected. + */ + RESTART, + + /** \brief The patten does not at all occur in the text. */ + NOT_FOUND + } Result; + +private: + /** + * \brief The key used for the last search. + * + * If dw::core::Findtext::search is called with the same key, the search + * is continued, otherwise it is restarted. + */ + char *key; + + /** \brief Whether the last search was case sensitive. */ + bool caseSens; + + /** \brief The table used for KMP search. */ + int *nexttab; + + /** \brief The top of the widget tree, in which the search is done. + * + * From this, the iterator will be constructed. Set by + * dw::core::Findtext::widget + */ + Widget *widget; + + /** \brief The position from where the next search will start. */ + CharIterator *iterator; + + /** + * \brief The position from where the characters are highlighted. + * + * NULL, when no text is highlighted. + */ + CharIterator *hlIterator; + + static const char* rev(const char* _str); /* reverse a C string */ + + static int *createNexttab (const char *needle,bool caseSens,bool backwards); + bool unhighlight (); + bool search0 (bool backwards, bool firstTrial); + + inline static bool charsEqual (char c1, char c2, bool caseSens) + { return caseSens ? c1 == c2 : tolower (c1) == tolower (c2) || + (isspace (c1) && isspace (c2)); } + +public: + FindtextState (); + ~FindtextState (); + + void setWidget (Widget *widget); + Result search (const char *key, bool caseSens, bool backwards); + void resetSearch (); +}; + +} // namespace core +} // namespace dw + +#endif // __DW_FINDTEXT_STATE_H__ diff --git a/dw/fltkcore.hh b/dw/fltkcore.hh new file mode 100644 index 0000000..5ac619b --- /dev/null +++ b/dw/fltkcore.hh @@ -0,0 +1,36 @@ +#ifndef __DW_FLTK_CORE_HH__ +#define __DW_FLTK_CORE_HH__ + +#define __INCLUDED_FROM_DW_FLTK_CORE_HH__ + +namespace dw { +namespace fltk { +namespace ui { + +class FltkResource; + +} // namespace ui +} // namespace fltk +} // namespace dw + +#include <FL/Fl_Widget.H> + +#include "core.hh" +#include "fltkimgbuf.hh" +#include "fltkplatform.hh" +#include "fltkui.hh" + +namespace dw { +namespace fltk { + +inline void freeall () +{ + FltkImgbuf::freeall (); +} + +} // namespace fltk +} // namespace dw + +#undef __INCLUDED_FROM_DW_FLTK_CORE_HH__ + +#endif // __DW_FLTK_CORE_HH__ diff --git a/dw/fltkimgbuf.cc b/dw/fltkimgbuf.cc new file mode 100644 index 0000000..6621dc5 --- /dev/null +++ b/dw/fltkimgbuf.cc @@ -0,0 +1,584 @@ +/* + * RTFL (originally part of dillo) + * + * Copyright 2005-2007, 2012-2013 Sebastian Geerken <sgeerken@dillo.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version; with the following exception: + * + * The copyright holders of RTFL give you permission to link this file + * statically or dynamically against all versions of the graphviz + * library, which are published by AT&T Corp. under one of the following + * licenses: + * + * - Common Public License version 1.0 as published by International + * Business Machines Corporation (IBM), or + * - Eclipse Public License version 1.0 as published by the Eclipse + * Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "fltkcore.hh" +#include "../lout/msg.h" +#include "../lout/misc.hh" + +#include <FL/fl_draw.H> +#include <math.h> + +#define IMAGE_MAX_AREA (6000 * 6000) + +#define MAX_WIDTH 0x8000 +#define MAX_HEIGHT 0x8000 + +namespace dw { +namespace fltk { + +using namespace lout::container::typed; + +const enum ScaleMode { SIMPLE, BEAUTIFUL, BEAUTIFUL_GAMMA } + scaleMode = BEAUTIFUL_GAMMA; + +Vector <FltkImgbuf::GammaCorrectionTable> *FltkImgbuf::gammaCorrectionTables + = new Vector <FltkImgbuf::GammaCorrectionTable> (true, 2); + +uchar *FltkImgbuf::findGammaCorrectionTable (double gamma) +{ + // Since the number of possible keys is low, a linear search is + // sufficiently fast. + + for (int i = 0; i < gammaCorrectionTables->size(); i++) { + GammaCorrectionTable *gct = gammaCorrectionTables->get(i); + if (gct->gamma == gamma) + return gct->map; + } + + _MSG("Creating new table for gamma = %g\n", gamma); + + GammaCorrectionTable *gct = new GammaCorrectionTable(); + gct->gamma = gamma; + + for (int i = 0; i < 256; i++) + gct->map[i] = 255 * pow((double)i / 255, gamma); + + gammaCorrectionTables->put (gct); + return gct->map; +} + +bool FltkImgbuf::excessiveImageDimensions (int width, int height) +{ + return width <= 0 || height <= 0 || + width > IMAGE_MAX_AREA / height; +} + +void FltkImgbuf::freeall () +{ + _MSG("Deleting gammaCorrectionTables\n"); + delete gammaCorrectionTables; + gammaCorrectionTables = NULL; +} + +FltkImgbuf::FltkImgbuf (Type type, int width, int height, double gamma) +{ + DBG_OBJ_CREATE ("dw::fltk::FltkImgbuf"); + + _MSG ("FltkImgbuf::FltkImgbuf: new root %p\n", this); + init (type, width, height, gamma, NULL); +} + +FltkImgbuf::FltkImgbuf (Type type, int width, int height, double gamma, + FltkImgbuf *root) +{ + DBG_OBJ_CREATE ("dw::fltk::FltkImgbuf"); + + _MSG ("FltkImgbuf::FltkImgbuf: new scaled %p, root is %p\n", this, root); + init (type, width, height, gamma, root); +} + +void FltkImgbuf::init (Type type, int width, int height, double gamma, + FltkImgbuf *root) +{ + if (excessiveImageDimensions (width, height)) { + // Excessive image sizes which would cause crashes due to too + // big allocations for the image buffer (for root buffers, when + // the image was specially prepared). In this case we use a 1 x + // 1 size. + MSG("FltkImgbuf::init: suspicious image size request %d x %d\n", + width, height); + init (type, 1, 1, gamma, root); + } else if (width > MAX_WIDTH) { + // Too large dimensions cause dangerous overflow errors, so we + // limit dimensions to harmless values. + // + // Example: 65535 * 65536 / 65536 (see scaling below) results in + // the negative value -1. + + MSG("FltkImgbuf::init: cannot handle large width %d\n", width); + init (type, MAX_WIDTH, height, gamma, root); + } else if (height > MAX_HEIGHT) { + MSG("FltkImgbuf::init: cannot handle large height %d\n", height); + init (type, width, MAX_HEIGHT, gamma, root); + } else if (gamma <= 0) { + MSG("FltkImgbuf::init: non-positive gamma %g\n", gamma); + init (type, width, height, 1, root); + } else { + this->root = root; + this->type = type; + this->width = width; + this->height = height; + this->gamma = gamma; + + DBG_OBJ_SET_NUM ("width", width); + DBG_OBJ_SET_NUM ("height", height); + + // TODO: Maybe this is only for root buffers + switch (type) { + case RGBA: bpp = 4; break; + case RGB: bpp = 3; break; + default: bpp = 1; break; + } + _MSG("FltkImgbuf::init this=%p width=%d height=%d bpp=%d gamma=%g\n", + this, width, height, bpp, gamma); + rawdata = new uchar[bpp * width * height]; + // Set light-gray as interim background color. + memset(rawdata, 222, width*height*bpp); + + refCount = 1; + deleteOnUnref = true; + copiedRows = new lout::misc::BitSet (height); + + // The list is only used for root buffers. + if (isRoot()) + scaledBuffers = new lout::container::typed::List <FltkImgbuf> (true); + else + scaledBuffers = NULL; + + if (!isRoot()) { + // Scaling + for (int row = 0; row < root->height; row++) { + if (root->copiedRows->get (row)) + scaleRow (row, root->rawdata + row*root->width*root->bpp); + } + } + } +} + +FltkImgbuf::~FltkImgbuf () +{ + _MSG ("FltkImgbuf::~FltkImgbuf\n"); + + if (!isRoot()) + root->detachScaledBuf (this); + + delete[] rawdata; + delete copiedRows; + + if (scaledBuffers) + delete scaledBuffers; + + DBG_OBJ_DELETE (); +} + +/** + * \brief This method is called for the root buffer, when a scaled buffer + * removed. + */ +void FltkImgbuf::detachScaledBuf (FltkImgbuf *scaledBuf) +{ + scaledBuffers->detachRef (scaledBuf); + + _MSG("FltkImgbuf[root %p]: scaled buffer %p is detached, %d left\n", + this, scaledBuf, scaledBuffers->size ()); + + if (refCount == 0 && scaledBuffers->isEmpty () && deleteOnUnref) + // If the root buffer is not used anymore, but this is the last scaled + // buffer. + // See also: FltkImgbuf::unref(). + delete this; +} + +void FltkImgbuf::setCMap (int *colors, int num_colors) +{ +} + +inline void FltkImgbuf::scaleRow (int row, const core::byte *data) +{ + if (row < root->height) { + if (scaleMode == SIMPLE) + scaleRowSimple (row, data); + else + scaleRowBeautiful (row, data); + } +} + +inline void FltkImgbuf::scaleRowSimple (int row, const core::byte *data) +{ + int sr1 = scaledY (row); + int sr2 = scaledY (row + 1); + + for (int sr = sr1; sr < sr2; sr++) { + // Avoid multiple passes. + if (copiedRows->get(sr)) continue; + + copiedRows->set (sr, true); + if (sr == sr1) { + for (int px = 0; px < root->width; px++) { + int px1 = px * width / root->width; + int px2 = (px+1) * width / root->width; + for (int sp = px1; sp < px2; sp++) { + memcpy(rawdata + (sr*width + sp)*bpp, data + px*bpp, bpp); + } + } + } else { + memcpy(rawdata + sr*width*bpp, rawdata + sr1*width*bpp, width*bpp); + } + } +} + +inline void FltkImgbuf::scaleRowBeautiful (int row, const core::byte *data) +{ + int sr1 = scaledY (row); + int sr2 = scaledY (row + 1); + bool allRootRows = false; + + // Don't rescale rows! + if (copiedRows->get(sr1)) return; + + if (height > root->height) { + scaleBuffer (data, root->width, 1, + rawdata + sr1 * width * bpp, width, sr2 - sr1, + bpp, gamma); + // Mark scaled rows done + for (int sr = sr1; sr < sr2 || sr == sr1; sr++) + copiedRows->set (sr, true); + } else { + assert (sr1 == sr2 || sr1 + 1 == sr2); + int row1 = backscaledY(sr1), row2 = backscaledY(sr1 + 1); + + // Check all the necessary root lines already arrived, + // a larger area than a single row may be accessed here. + for (int r=row1; (allRootRows=root->copiedRows->get(r)) && ++r < row2; ); + if (allRootRows) { + scaleBuffer (root->rawdata + row1 * root->width * bpp, + root->width, row2 - row1, + rawdata + sr1 * width * bpp, width, 1, + bpp, gamma); + // Mark scaled row done + copiedRows->set (sr1, true); + } + } +} + +/** + * General method to scale an image buffer. Used to scale single lines + * in scaleRowBeautiful. + * + * The algorithm is rather simple. If the scaled buffer is smaller + * (both width and height) than the original buffer, each pixel in the + * scaled buffer is assigned a rectangle of pixels in the original + * buffer; the resulting pixel value (red, green, blue) is simply the + * average of all pixel values. This is pretty fast and leads to + * rather good results. + * + * Nothing special (like interpolation) is done when scaling up. + * + * If scaleMode is set to BEAUTIFUL_GAMMA, gamma correction is + * considered, see <http://www.4p8.com/eric.brasseur/gamma.html>. + * + * TODO Could be optimized as in scaleRowSimple: when the destination + * image is larger, calculate only one row/column, and copy it to the + * other rows/columns. + */ +inline void FltkImgbuf::scaleBuffer (const core::byte *src, int srcWidth, + int srcHeight, core::byte *dest, + int destWidth, int destHeight, int bpp, + double gamma) +{ + uchar *gammaMap1, *gammaMap2; + + if (scaleMode == BEAUTIFUL_GAMMA) { + gammaMap1 = findGammaCorrectionTable (gamma); + gammaMap2 = findGammaCorrectionTable (1 / gamma); + } + + for(int x = 0; x < destWidth; x++) + for(int y = 0; y < destHeight; y++) { + int xo1 = x * srcWidth / destWidth; + int xo2 = lout::misc::max ((x + 1) * srcWidth / destWidth, xo1 + 1); + int yo1 = y * srcHeight / destHeight; + int yo2 = lout::misc::max ((y + 1) * srcHeight / destHeight, yo1 + 1); + int n = (xo2 - xo1) * (yo2 - yo1); + + int v[bpp]; + for(int i = 0; i < bpp; i++) + v[i] = 0; + + for(int xo = xo1; xo < xo2; xo++) + for(int yo = yo1; yo < yo2; yo++) { + const core::byte *ps = src + bpp * (yo * srcWidth + xo); + for(int i = 0; i < bpp; i++) + v[i] += + (scaleMode == BEAUTIFUL_GAMMA ? gammaMap2[ps[i]] : ps[i]); + } + + core::byte *pd = dest + bpp * (y * destWidth + x); + for(int i = 0; i < bpp; i++) + pd[i] = + scaleMode == BEAUTIFUL_GAMMA ? gammaMap1[v[i] / n] : v[i] / n; + } +} + +void FltkImgbuf::copyRow (int row, const core::byte *data) +{ + assert (isRoot()); + + if (row < height) { + // Flag the row done and copy its data. + copiedRows->set (row, true); + memcpy(rawdata + row * width * bpp, data, width * bpp); + + // Update all the scaled buffers of this root image. + for (Iterator <FltkImgbuf> it = scaledBuffers->iterator(); + it.hasNext(); ) { + FltkImgbuf *sb = it.getNext (); + sb->scaleRow (row, data); + } + } +} + +void FltkImgbuf::newScan () +{ + if (isRoot()) { + for (Iterator<FltkImgbuf> it = scaledBuffers->iterator(); it.hasNext();){ + FltkImgbuf *sb = it.getNext (); + sb->copiedRows->clear(); + } + } +} + +core::Imgbuf* FltkImgbuf::getScaledBuf (int width, int height) +{ + if (!isRoot()) + return root->getScaledBuf (width, height); + + if (width > MAX_WIDTH) { + // Similar to init. + MSG("FltkImgbuf::getScaledBuf: cannot handle large width %d\n", width); + return getScaledBuf (MAX_WIDTH, height); + } + if (height > MAX_HEIGHT) { + MSG("FltkImgbuf::getScaledBuf: cannot handle large height %d\n", height); + return getScaledBuf (width, MAX_HEIGHT); + } + + if (width == this->width && height == this->height) { + ref (); + return this; + } + + for (Iterator <FltkImgbuf> it = scaledBuffers->iterator(); it.hasNext(); ) { + FltkImgbuf *sb = it.getNext (); + if (sb->width == width && sb->height == height) { + sb->ref (); + return sb; + } + } + + // Check for excessive image sizes which would cause crashes due to + // too big allocations for the image buffer. In this case we return + // a pointer to the unscaled image buffer. + if (excessiveImageDimensions (width, height)) { + MSG("FltkImgbuf::getScaledBuf: suspicious image size request %d x %d\n", + width, height); + ref (); + return this; + } + + // This size is not yet used, so a new buffer has to be created. + FltkImgbuf *sb = new FltkImgbuf (type, width, height, gamma, this); + scaledBuffers->append (sb); + DBG_OBJ_ASSOC_CHILD (sb); + + return sb; +} + +void FltkImgbuf::getRowArea (int row, dw::core::Rectangle *area) +{ + // TODO: May have to be adjusted. + + if (isRoot()) { + /* root buffer */ + area->x = 0; + area->y = row; + area->width = width; + area->height = 1; + _MSG("::getRowArea: area x=%d y=%d width=%d height=%d\n", + area->x, area->y, area->width, area->height); + } else { + if (row > root->height) + area->x = area->y = area->width = area->height = 0; + else { + // scaled buffer + int sr1 = scaledY (row); + int sr2 = scaledY (row + 1); + + area->x = 0; + area->y = sr1; + area->width = width; + area->height = sr2 - sr1; + _MSG("::getRowArea: area x=%d y=%d width=%d height=%d\n", + area->x, area->y, area->width, area->height); + } + } +} + +int FltkImgbuf::getRootWidth () +{ + return root ? root->width : width; +} + +int FltkImgbuf::getRootHeight () +{ + return root ? root->height : height; +} + +core::Imgbuf *FltkImgbuf::createSimilarBuf (int width, int height) +{ + return new FltkImgbuf (type, width, height, gamma); +} + +void FltkImgbuf::copyTo (Imgbuf *dest, int xDestRoot, int yDestRoot, + int xSrc, int ySrc, int widthSrc, int heightSrc) +{ + FltkImgbuf *fDest = (FltkImgbuf*)dest; + assert (bpp == fDest->bpp); + + int xSrc2 = lout::misc::min (xSrc + widthSrc, fDest->width - xDestRoot); + int ySrc2 = lout::misc::min (ySrc + heightSrc, fDest->height - yDestRoot); + + //printf ("copying from (%d, %d), %d x %d to (%d, %d) (root) => " + // "xSrc2 = %d, ySrc2 = %d\n", + // xSrc, ySrc, widthSrc, heightSrc, xDestRoot, yDestRoot, + // xSrc2, ySrc2); + + for (int x = xSrc; x < xSrc2; x++) + for (int y = ySrc; y < ySrc2; y++) { + int iSrc = x + width * y; + int iDest = xDestRoot + x + fDest->width * (yDestRoot + y); + + //printf (" (%d, %d): %d -> %d\n", x, y, iSrc, iDest); + + for (int b = 0; b < bpp; b++) + fDest->rawdata[bpp * iDest + b] = rawdata[bpp * iSrc + b]; + } +} + +void FltkImgbuf::ref () +{ + refCount++; + + //if (root) + // MSG("FltkImgbuf[scaled %p, root is %p]: ref() => %d\n", + // this, root, refCount); + //else + // MSG("FltkImgbuf[root %p]: ref() => %d\n", this, refCount); +} + +void FltkImgbuf::unref () +{ + //if (root) + // MSG("FltkImgbuf[scaled %p, root is %p]: ref() => %d\n", + // this, root, refCount - 1); + //else + // MSG("FltkImgbuf[root %p]: ref() => %d\n", this, refCount - 1); + + if (--refCount == 0) { + if (isRoot ()) { + // Root buffer, it must be ensured that no scaled buffers are left. + // See also FltkImgbuf::detachScaledBuf(). + if (scaledBuffers->isEmpty () && deleteOnUnref) { + delete this; + } else { + _MSG("FltkImgbuf[root %p]: not deleted. numScaled=%d\n", + this, scaledBuffers->size ()); + } + } else + // Scaled buffer buffer, simply delete it. + delete this; + } +} + +bool FltkImgbuf::lastReference () +{ + return refCount == 1 && + (scaledBuffers == NULL || scaledBuffers->isEmpty ()); +} + +void FltkImgbuf::setDeleteOnUnref (bool deleteOnUnref) +{ + assert (isRoot ()); + this->deleteOnUnref = deleteOnUnref; +} + +bool FltkImgbuf::isReferred () +{ + return refCount != 0 || + (scaledBuffers != NULL && !scaledBuffers->isEmpty ()); +} + + +int FltkImgbuf::scaledY(int ySrc) +{ + // TODO: May have to be adjusted. + assert (root != NULL); + return ySrc * height / root->height; +} + +int FltkImgbuf::backscaledY(int yScaled) +{ + assert (root != NULL); + + // Notice that rounding errors because of integers do not play a + // role. This method cannot be the exact inverse of scaledY, since + // scaleY is not bijective, and so not invertible. Instead, both + // values always return the smallest value. + return yScaled * root->height / height; +} + +void FltkImgbuf::draw (Fl_Widget *target, int xRoot, int yRoot, + int x, int y, int width, int height) +{ + // TODO: Clarify the question, whether "target" is the current widget + // (and so has not to be passed at all). + + _MSG("::draw: xRoot=%d x=%d yRoot=%d y=%d width=%d height=%d\n" + " this->width=%d this->height=%d\n", + xRoot, x, yRoot, y, width, height, this->width, this->height); + + if (x > this->width || y > this->height) { + return; + } + + if (x + width > this->width) { + width = this->width - x; + } + + if (y + height > this->height) { + height = this->height - y; + } + + fl_draw_image(rawdata+bpp*(y*this->width + x), xRoot + x, yRoot + y, width, + height, bpp, this->width * bpp); + +} + +} // namespace fltk +} // namespace dw diff --git a/dw/fltkimgbuf.hh b/dw/fltkimgbuf.hh new file mode 100644 index 0000000..0b8b554 --- /dev/null +++ b/dw/fltkimgbuf.hh @@ -0,0 +1,93 @@ +#ifndef __DW_FLTKIMGBUF_HH__ +#define __DW_FLTKIMGBUF_HH__ + +#ifndef __INCLUDED_FROM_DW_FLTK_CORE_HH__ +# error Do not include this file directly, use "fltkcore.hh" instead. +#endif + +namespace dw { +namespace fltk { + +class FltkImgbuf: public core::Imgbuf +{ +private: + class GammaCorrectionTable: public lout::object::Object + { + public: + double gamma; + uchar map[256]; + }; + + FltkImgbuf *root; + int refCount; + bool deleteOnUnref; + lout::container::typed::List <FltkImgbuf> *scaledBuffers; + + int width, height; + Type type; + double gamma; + +//{ + int bpp; + uchar *rawdata; +//} + + // This is just for testing drawing, it has to be replaced by + // the image buffer. + lout::misc::BitSet *copiedRows; + + static lout::container::typed::Vector <GammaCorrectionTable> + *gammaCorrectionTables; + + static uchar *findGammaCorrectionTable (double gamma); + static bool excessiveImageDimensions (int width, int height); + + FltkImgbuf (Type type, int width, int height, double gamma, + FltkImgbuf *root); + void init (Type type, int width, int height, double gamma, FltkImgbuf *root); + int scaledY(int ySrc); + int backscaledY(int yScaled); + int isRoot() { return (root == NULL); } + void detachScaledBuf (FltkImgbuf *scaledBuf); + +protected: + ~FltkImgbuf (); + +public: + FltkImgbuf (Type type, int width, int height, double gamma); + + static void freeall (); + + void setCMap (int *colors, int num_colors); + inline void scaleRow (int row, const core::byte *data); + inline void scaleRowSimple (int row, const core::byte *data); + inline void scaleRowBeautiful (int row, const core::byte *data); + inline static void scaleBuffer (const core::byte *src, int srcWidth, + int srcHeight, core::byte *dest, + int destWidth, int destHeight, int bpp, + double gamma); + + void newScan (); + void copyRow (int row, const core::byte *data); + core::Imgbuf* getScaledBuf (int width, int height); + void getRowArea (int row, dw::core::Rectangle *area); + int getRootWidth (); + int getRootHeight (); + core::Imgbuf *createSimilarBuf (int width, int height); + void copyTo (Imgbuf *dest, int xDestRoot, int yDestRoot, + int xSrc, int ySrc, int widthSrc, int heightSrc); + void ref (); + void unref (); + + bool lastReference (); + void setDeleteOnUnref (bool deleteOnUnref); + bool isReferred (); + + void draw (Fl_Widget *target, int xRoot, int yRoot, + int x, int y, int width, int height); +}; + +} // namespace fltk +} // namespace dw + +#endif // __DW_FLTK_IMGBUF_HH__ diff --git a/dw/fltkmisc.cc b/dw/fltkmisc.cc new file mode 100644 index 0000000..45230ad --- /dev/null +++ b/dw/fltkmisc.cc @@ -0,0 +1,58 @@ +/* + * RTFL (originally part of dillo) + * + * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version; with the following exception: + * + * The copyright holders of RTFL give you permission to link this file + * statically or dynamically against all versions of the graphviz + * library, which are published by AT&T Corp. under one of the following + * licenses: + * + * - Common Public License version 1.0 as published by International + * Business Machines Corporation (IBM), or + * - Eclipse Public License version 1.0 as published by the Eclipse + * Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#include "../lout/msg.h" +#include "fltkmisc.hh" + +#include <FL/Fl.H> +#include <stdio.h> + +namespace dw { +namespace fltk { +namespace misc { + +int screenWidth () +{ + return Fl::w (); +} + +int screenHeight () +{ + return Fl::h (); +} + +void warpPointer (int x, int y) +{ + MSG_ERR("no warpPointer mechanism available.\n"); +} + +} // namespace misc +} // namespace fltk +} // namespace dw diff --git a/dw/fltkmisc.hh b/dw/fltkmisc.hh new file mode 100644 index 0000000..fc00431 --- /dev/null +++ b/dw/fltkmisc.hh @@ -0,0 +1,22 @@ +#ifndef __FLTKMISC_HH__ +#define __FLTKMISC_HH__ + +namespace dw { +namespace fltk { + +/** + * \brief Miscellaneous FLTK stuff. + */ +namespace misc { + +int screenWidth (); +int screenHeight (); + +void warpPointer (int x, int y); + +} // namespace misc +} // namespace fltk +} // namespace dw + + +#endif // __FLTKMISC_HH__ diff --git a/dw/fltkplatform.cc b/dw/fltkplatform.cc new file mode 100644 index 0000000..a244765 --- /dev/null +++ b/dw/fltkplatform.cc @@ -0,0 +1,739 @@ +/* + * RTFL (originally part of dillo) + * + * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version; with the following exception: + * + * The copyright holders of RTFL give you permission to link this file + * statically or dynamically against all versions of the graphviz + * library, which are published by AT&T Corp. under one of the following + * licenses: + * + * - Common Public License version 1.0 as published by International + * Business Machines Corporation (IBM), or + * - Eclipse Public License version 1.0 as published by the Eclipse + * Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <stdio.h> + +#include "../lout/msg.h" +#include "../lout/debug.hh" +#include "fltkcore.hh" + +#include <FL/fl_draw.H> +#include <FL/Fl_Box.H> +#include <FL/Fl_Tooltip.H> +#include <FL/Fl_Menu_Window.H> +#include <FL/Fl_Paged_Device.H> + +/* + * Local data + */ + +/* Tooltips */ +static Fl_Menu_Window *tt_window = NULL; +static int in_tooltip = 0, req_tooltip = 0; + +namespace dw { +namespace fltk { + +using namespace lout; + +/** + * \todo Distinction between italics and oblique would be nice. + */ + +container::typed::HashTable <dw::core::style::FontAttrs, + FltkFont> *FltkFont::fontsTable = + new container::typed::HashTable <dw::core::style::FontAttrs, + FltkFont> (false, false); + +container::typed::HashTable <lout::object::ConstString, + FltkFont::FontFamily> *FltkFont::systemFonts = + NULL; + +FltkFont::FontFamily FltkFont::standardFontFamily (FL_HELVETICA, + FL_HELVETICA_BOLD, + FL_HELVETICA_ITALIC, + FL_HELVETICA_BOLD_ITALIC); + +FltkFont::FontFamily::FontFamily (Fl_Font fontNormal, Fl_Font fontBold, + Fl_Font fontItalic, Fl_Font fontBoldItalic) +{ + font[0] = fontNormal; + font[1] = fontBold; + font[2] = fontItalic; + font[3] = fontBoldItalic; +} + +void FltkFont::FontFamily::set (Fl_Font f, int attrs) +{ + int idx = 0; + if (attrs & FL_BOLD) + idx += 1; + if (attrs & FL_ITALIC) + idx += 2; + font[idx] = f; +} + +Fl_Font FltkFont::FontFamily::get (int attrs) +{ + int idx = 0; + if (attrs & FL_BOLD) + idx += 1; + if (attrs & FL_ITALIC) + idx += 2; + + // should the desired font style not exist, we + // return the normal font of the fontFamily + return font[idx] >= 0 ? font[idx] : font[0]; +} + + + +FltkFont::FltkFont (core::style::FontAttrs *attrs) +{ + if (!systemFonts) + initSystemFonts (); + + copyAttrs (attrs); + + int fa = 0; + if (weight >= 500) + fa |= FL_BOLD; + if (style != core::style::FONT_STYLE_NORMAL) + fa |= FL_ITALIC; + + object::ConstString nameString (name); + FontFamily *family = systemFonts->get (&nameString); + if (!family) + family = &standardFontFamily; + + font = family->get (fa); + + fl_font(font, size); + // WORKAROUND: A bug with fl_width(uint_t) on non-xft X was present in + // 1.3.0 (STR #2688). + spaceWidth = misc::max(0, (int)fl_width(" ") + letterSpacing); + int xx, xy, xw, xh; + fl_text_extents("x", xx, xy, xw, xh); + xHeight = xh; + descent = fl_descent(); + ascent = fl_height() - descent; +} + +FltkFont::~FltkFont () +{ + fontsTable->remove (this); +} + +static void strstrip(char *big, const char *little) +{ + if (strlen(big) >= strlen(little) && + misc::AsciiStrcasecmp(big + strlen(big) - strlen(little), little) == 0) + *(big + strlen(big) - strlen(little)) = '\0'; +} + +void FltkFont::initSystemFonts () +{ + systemFonts = new container::typed::HashTable + <lout::object::ConstString, FontFamily> (true, true); + + int k = Fl::set_fonts ("-*-iso10646-1"); + for (int i = 0; i < k; i++) { + int t; + char *name = strdup (Fl::get_font_name ((Fl_Font) i, &t)); + + // normalize font family names (strip off "bold", "italic") + if (t & FL_ITALIC) + strstrip(name, " italic"); + if (t & FL_BOLD) + strstrip(name, " bold"); + + _MSG("Found font: %s%s%s\n", name, t & FL_BOLD ? " bold" : "", + t & FL_ITALIC ? " italic" : ""); + + object::String *familyName = new object::String(name); + free (name); + FontFamily *family = systemFonts->get (familyName); + + if (family) { + family->set ((Fl_Font) i, t); + delete familyName; + } else { + // set first font of family also as normal font in case there + // is no normal (non-bold, non-italic) font + family = new FontFamily ((Fl_Font) i, -1, -1, -1); + family->set ((Fl_Font) i, t); + systemFonts->put (familyName, family); + } + } +} + +bool +FltkFont::fontExists (const char *name) +{ + if (!systemFonts) + initSystemFonts (); + object::ConstString familyName (name); + return systemFonts->get (&familyName) != NULL; +} + +Fl_Font +FltkFont::get (const char *name, int attrs) +{ + if (!systemFonts) + initSystemFonts (); + object::ConstString familyName (name); + FontFamily *family = systemFonts->get (&familyName); + if (family) + return family->get (attrs); + else + return FL_HELVETICA; +} + +bool +FltkPlatform::fontExists (const char *name) +{ + return FltkFont::fontExists (name); +} + +FltkFont* +FltkFont::create (core::style::FontAttrs *attrs) +{ + FltkFont *font = fontsTable->get (attrs); + + if (font == NULL) { + font = new FltkFont (attrs); + fontsTable->put (font, font); + } + + return font; +} + +container::typed::HashTable <dw::core::style::ColorAttrs, + FltkColor> + *FltkColor::colorsTable = + new container::typed::HashTable <dw::core::style::ColorAttrs, + FltkColor> (false, false); + +FltkColor::FltkColor (int color): Color (color) +{ + this->color = color; + + if (!(colors[SHADING_NORMAL] = shadeColor (color, SHADING_NORMAL) << 8)) + colors[SHADING_NORMAL] = FL_BLACK; + if (!(colors[SHADING_INVERSE] = shadeColor (color, SHADING_INVERSE) << 8)) + colors[SHADING_INVERSE] = FL_BLACK; + if (!(colors[SHADING_DARK] = shadeColor (color, SHADING_DARK) << 8)) + colors[SHADING_DARK] = FL_BLACK; + if (!(colors[SHADING_LIGHT] = shadeColor (color, SHADING_LIGHT) << 8)) + colors[SHADING_LIGHT] = FL_BLACK; +} + +FltkColor::~FltkColor () +{ + colorsTable->remove (this); +} + +FltkColor * FltkColor::create (int col) +{ + ColorAttrs attrs(col); + FltkColor *color = colorsTable->get (&attrs); + + if (color == NULL) { + color = new FltkColor (col); + colorsTable->put (color, color); + } + + return color; +} + +FltkTooltip::FltkTooltip (const char *text) : Tooltip(text) +{ +} + +FltkTooltip::~FltkTooltip () +{ + if (in_tooltip || req_tooltip) + cancel(); /* cancel tooltip window */ +} + +FltkTooltip *FltkTooltip::create (const char *text) +{ + return new FltkTooltip(text); +} + +/* + * Tooltip callback: used to delay it a bit + * INVARIANT: Only one instance of this function is requested. + */ +static void tooltip_tcb(void *data) +{ + req_tooltip = 2; + ((FltkTooltip *)data)->onEnter(); + req_tooltip = 0; +} + +void FltkTooltip::onEnter() +{ + _MSG("FltkTooltip::onEnter\n"); + if (!str || !*str) + return; + if (req_tooltip == 0) { + Fl::remove_timeout(tooltip_tcb); + Fl::add_timeout(1.0, tooltip_tcb, this); + req_tooltip = 1; + return; + } + + if (!tt_window) { + tt_window = new Fl_Menu_Window(0,0,100,24); + tt_window->set_override(); + tt_window->box(FL_NO_BOX); + Fl_Box *b = new Fl_Box(0,0,100,24); + b->box(FL_BORDER_BOX); + b->color(fl_color_cube(FL_NUM_RED-1, FL_NUM_GREEN-1, FL_NUM_BLUE-2)); + b->labelcolor(FL_BLACK); + b->labelfont(FL_HELVETICA); + b->labelsize(14); + b->align(FL_ALIGN_WRAP|FL_ALIGN_LEFT|FL_ALIGN_INSIDE); + tt_window->resizable(b); + tt_window->end(); + } + + /* prepare tooltip window */ + int x, y; + Fl_Box *box = (Fl_Box*)tt_window->child(0); + box->label(str); + Fl::get_mouse(x,y); y += 6; + /* calculate window size */ + int ww, hh; + ww = 800; // max width; + box->measure_label(ww, hh); + ww += 6 + 2 * Fl::box_dx(box->box()); + hh += 6 + 2 * Fl::box_dy(box->box()); + tt_window->resize(x,y,ww,hh); + tt_window->show(); + in_tooltip = 1; +} + +/* + * Leaving the widget cancels the tooltip + */ +void FltkTooltip::onLeave() +{ + _MSG(" FltkTooltip::onLeave in_tooltip=%d\n", in_tooltip); + cancel(); +} + +void FltkPlatform::cancelTooltip() +{ + FltkTooltip::cancel(); +} + +/* + * Remove a shown tooltip or cancel a pending one + */ +void FltkTooltip::cancel() +{ + if (req_tooltip) { + Fl::remove_timeout(tooltip_tcb); + req_tooltip = 0; + } + if (!in_tooltip) return; + in_tooltip = 0; + tt_window->hide(); + + /* WORKAROUND: (Black magic here) + * Hiding a tooltip with the keyboard or mousewheel doesn't work. + * The code below "fixes" the problem */ + Fl_Widget *widget = Fl::belowmouse(); + if (widget && widget->window()) { + widget->window()->damage(FL_DAMAGE_EXPOSE,0,0,1,1); + } +} + +void FltkTooltip::onMotion() +{ +} + +void FltkView::addFltkWidget (Fl_Widget *widget, + core::Allocation *allocation) +{ +} + +void FltkView::removeFltkWidget (Fl_Widget *widget) +{ +} + +void FltkView::allocateFltkWidget (Fl_Widget *widget, + core::Allocation *allocation) +{ +} + +void FltkView::drawFltkWidget (Fl_Widget *widget, core::Rectangle *area) +{ +} + + +core::ui::LabelButtonResource * +FltkPlatform::FltkResourceFactory::createLabelButtonResource (const char + *label) +{ + return new ui::FltkLabelButtonResource (platform, label); +} + +core::ui::ComplexButtonResource * +FltkPlatform::FltkResourceFactory::createComplexButtonResource (core::Widget + *widget, + bool relief) +{ + // Not needed within RTFL. + lout::misc::assertNotReached (); + return NULL; +} + +core::ui::ListResource * +FltkPlatform::FltkResourceFactory::createListResource (core::ui + ::ListResource + ::SelectionMode + selectionMode, int rows) +{ + return new ui::FltkListResource (platform, selectionMode, rows); +} + +core::ui::OptionMenuResource * +FltkPlatform::FltkResourceFactory::createOptionMenuResource () +{ + return new ui::FltkOptionMenuResource (platform); +} + +core::ui::EntryResource * +FltkPlatform::FltkResourceFactory::createEntryResource (int size, + bool password, + const char *label) +{ + return new ui::FltkEntryResource (platform, size, password, label); +} + +core::ui::MultiLineTextResource * +FltkPlatform::FltkResourceFactory::createMultiLineTextResource (int cols, + int rows) +{ + return new ui::FltkMultiLineTextResource (platform, cols, rows); +} + +core::ui::CheckButtonResource * +FltkPlatform::FltkResourceFactory::createCheckButtonResource (bool activated) +{ + return new ui::FltkCheckButtonResource (platform, activated); +} + +core::ui::RadioButtonResource +*FltkPlatform::FltkResourceFactory::createRadioButtonResource +(core::ui::RadioButtonResource *groupedWith, bool activated) +{ + return + new ui::FltkRadioButtonResource (platform, + (ui::FltkRadioButtonResource*) + groupedWith, + activated); +} + +// ---------------------------------------------------------------------- + +FltkPlatform::FltkPlatform () +{ + DBG_OBJ_CREATE ("dw::fltk::FltkPlatform"); + + layout = NULL; + idleQueue = new container::typed::List <IdleFunc> (true); + idleFuncRunning = false; + idleFuncId = 0; + + view = NULL; + resources = new container::typed::List <ui::FltkResource> (false); + + resourceFactory.setPlatform (this); +} + +FltkPlatform::~FltkPlatform () +{ + if (idleFuncRunning) + Fl::remove_idle (generalStaticIdle, (void*)this); + delete idleQueue; + delete resources; + + DBG_OBJ_DELETE (); +} + +void FltkPlatform::setLayout (core::Layout *layout) +{ + this->layout = layout; + DBG_OBJ_ASSOC_CHILD (layout); +} + + +void FltkPlatform::attachView (core::View *view) +{ + if (this->view) + MSG_ERR("FltkPlatform::attachView: multiple views!\n"); + this->view = (FltkView*)view; + + for (container::typed::Iterator <ui::FltkResource> it = + resources->iterator (); it.hasNext (); ) { + ui::FltkResource *resource = it.getNext (); + resource->attachView (this->view); + } +} + + +void FltkPlatform::detachView (core::View *view) +{ + if (this->view != view) + MSG_ERR("FltkPlatform::detachView: this->view: %p view: %p\n", + this->view, view); + + for (container::typed::Iterator <ui::FltkResource> it = + resources->iterator (); it.hasNext (); ) { + ui::FltkResource *resource = it.getNext (); + resource->detachView ((FltkView*)view); + } + this->view = NULL; +} + + +int FltkPlatform::textWidth (core::style::Font *font, const char *text, + int len) +{ + char chbuf[4]; + int c, cu; + int width = 0; + FltkFont *ff = (FltkFont*) font; + int curr = 0, next = 0, nb; + + if (font->fontVariant == core::style::FONT_VARIANT_SMALL_CAPS) { + int sc_fontsize = lout::misc::roundInt(ff->size * 0.78); + for (curr = 0; next < len; curr = next) { + next = nextGlyph(text, curr); + c = fl_utf8decode(text + curr, text + next, &nb); + if ((cu = fl_toupper(c)) == c) { + /* already uppercase, just draw the character */ + fl_font(ff->font, ff->size); + if (fl_nonspacing(cu) == 0) { + width += font->letterSpacing; + width += (int)fl_width(text + curr, next - curr); + } + } else { + /* make utf8 string for converted char */ + nb = fl_utf8encode(cu, chbuf); + fl_font(ff->font, sc_fontsize); + if (fl_nonspacing(cu) == 0) { + width += font->letterSpacing; + width += (int)fl_width(chbuf, nb); + } + } + } + } else { + fl_font (ff->font, ff->size); + width = (int) fl_width (text, len); + + if (font->letterSpacing) { + int curr = 0, next = 0; + + while (next < len) { + next = nextGlyph(text, curr); + c = fl_utf8decode(text + curr, text + next, &nb); + if (fl_nonspacing(c) == 0) + width += font->letterSpacing; + curr = next; + } + } + } + + return width; +} + +char *FltkPlatform::textToUpper (const char *text, int len) +{ + char *newstr = NULL; + + if (len > 0) { + int newlen; + + newstr = (char*) malloc(3 * len + 1); + newlen = fl_utf_toupper((const unsigned char*)text, len, newstr); + assert(newlen <= 3 * len); + newstr[newlen] = '\0'; + } + return newstr; +} + +char *FltkPlatform::textToLower (const char *text, int len) +{ + char *newstr = NULL; + + if (len > 0) { + int newlen; + + newstr = (char*) malloc(3 * len + 1); + newlen = fl_utf_tolower((const unsigned char*)text, len, newstr); + assert(newlen <= 3 * len); + newstr[newlen] = '\0'; + } + return newstr; +} + +int FltkPlatform::nextGlyph (const char *text, int idx) +{ + return fl_utf8fwd (&text[idx + 1], text, &text[strlen (text)]) - text; +} + +int FltkPlatform::prevGlyph (const char *text, int idx) +{ + return fl_utf8back (&text[idx - 1], text, &text[strlen (text)]) - text; +} + +float FltkPlatform::dpiX () +{ + float horizontal, vertical; + + Fl::screen_dpi(horizontal, vertical); + return horizontal; +} + +float FltkPlatform::dpiY () +{ + float horizontal, vertical; + + Fl::screen_dpi(horizontal, vertical); + return vertical; +} + +void FltkPlatform::generalStaticIdle (void *data) +{ + ((FltkPlatform*)data)->generalIdle(); +} + +void FltkPlatform::generalIdle () +{ + IdleFunc *idleFunc; + + if (!idleQueue->isEmpty ()) { + /* Execute the first function in the list. */ + idleFunc = idleQueue->getFirst (); + (layout->*(idleFunc->func)) (); + + /* Remove this function. */ + idleQueue->removeRef(idleFunc); + } + + if (idleQueue->isEmpty()) { + idleFuncRunning = false; + Fl::remove_idle (generalStaticIdle, (void*)this); + } +} + +/** + * \todo Incomplete comments. + */ +int FltkPlatform::addIdle (void (core::Layout::*func) ()) +{ + /* + * Since ... (todo) we have to wrap around fltk_add_idle. There is only one + * idle function, the passed idle function is put into a queue. + */ + if (!idleFuncRunning) { + Fl::add_idle (generalStaticIdle, (void*)this); + idleFuncRunning = true; + } + + idleFuncId++; + + IdleFunc *idleFunc = new IdleFunc(); + idleFunc->id = idleFuncId; + idleFunc->func = func; + idleQueue->append (idleFunc); + + return idleFuncId; +} + +void FltkPlatform::removeIdle (int idleId) +{ + bool found; + container::typed::Iterator <IdleFunc> it; + IdleFunc *idleFunc; + + for (found = false, it = idleQueue->iterator(); !found && it.hasNext(); ) { + idleFunc = it.getNext(); + if (idleFunc->id == idleId) { + idleQueue->removeRef (idleFunc); + found = true; + } + } + + if (idleFuncRunning && idleQueue->isEmpty()) + Fl::remove_idle (generalStaticIdle, (void*)this); +} + +core::style::Font *FltkPlatform::createFont (core::style::FontAttrs + *attrs, + bool tryEverything) +{ + return FltkFont::create (attrs); +} + +core::style::Color *FltkPlatform::createColor (int color) +{ + return FltkColor::create (color); +} + +core::style::Tooltip *FltkPlatform::createTooltip (const char *text) +{ + return FltkTooltip::create (text); +} + +void FltkPlatform::copySelection(const char *text) +{ + Fl::copy(text, strlen(text), 0); +} + +core::Imgbuf *FltkPlatform::createImgbuf (core::Imgbuf::Type type, + int width, int height, double gamma) +{ + return new FltkImgbuf (type, width, height, gamma); +} + +core::ui::ResourceFactory *FltkPlatform::getResourceFactory () +{ + return &resourceFactory; +} + + +void FltkPlatform::attachResource (ui::FltkResource *resource) +{ + resources->append (resource); + resource->attachView (view); +} + +void FltkPlatform::detachResource (ui::FltkResource *resource) +{ + resources->removeRef (resource); +} + +} // namespace fltk +} // namespace dw diff --git a/dw/fltkplatform.hh b/dw/fltkplatform.hh new file mode 100644 index 0000000..2fb9563 --- /dev/null +++ b/dw/fltkplatform.hh @@ -0,0 +1,186 @@ +#ifndef __DW_FLTKPLATFORM_HH__ +#define __DW_FLTKPLATFORM_HH__ + +#ifndef __INCLUDED_FROM_DW_FLTK_CORE_HH__ +# error Do not include this file directly, use "fltkcore.hh" instead. +#endif + +namespace dw { + +/** + * \brief This namespace contains FLTK implementations of Dw interfaces. + */ +namespace fltk { + +class FltkFont: public core::style::Font +{ + class FontFamily: public lout::object::Object { + Fl_Font font[4]; + public: + FontFamily (Fl_Font fontNormal, Fl_Font fontBold, + Fl_Font fontItalic, Fl_Font fontBoldItalic); + void set (Fl_Font, int attrs); + Fl_Font get (int attrs); + }; + + static FontFamily standardFontFamily; + + static lout::container::typed::HashTable <lout::object::ConstString, + FontFamily> *systemFonts; + static lout::container::typed::HashTable <dw::core::style::FontAttrs, + FltkFont> *fontsTable; + + FltkFont (core::style::FontAttrs *attrs); + ~FltkFont (); + + static void initSystemFonts (); + +public: + Fl_Font font; + + static FltkFont *create (core::style::FontAttrs *attrs); + static bool fontExists (const char *name); + static Fl_Font get (const char *name, int attrs); +}; + + +class FltkColor: public core::style::Color +{ + static lout::container::typed::HashTable <dw::core::style::ColorAttrs, + FltkColor> *colorsTable; + + FltkColor (int color); + ~FltkColor (); + +public: + int colors[SHADING_NUM]; + + static FltkColor *create(int color); +}; + +class FltkTooltip: public core::style::Tooltip +{ +private: + FltkTooltip (const char *text); + ~FltkTooltip (); +public: + static FltkTooltip *create(const char *text); + static void cancel(); + void onEnter(); + void onLeave(); + void onMotion(); +}; + + +/** + * \brief This interface adds some more methods for all flkt-based views. + */ +class FltkView: public core::View +{ +public: + virtual bool usesFltkWidgets () = 0; + + virtual void addFltkWidget (Fl_Widget *widget, + core::Allocation *allocation); + virtual void removeFltkWidget (Fl_Widget *widget); + virtual void allocateFltkWidget (Fl_Widget *widget, + core::Allocation *allocation); + virtual void drawFltkWidget (Fl_Widget *widget, core::Rectangle *area); +}; + + +class FltkPlatform: public core::Platform +{ +private: + class FltkResourceFactory: public core::ui::ResourceFactory + { + private: + FltkPlatform *platform; + + public: + inline void setPlatform (FltkPlatform *platform) { + this->platform = platform; } + + core::ui::LabelButtonResource *createLabelButtonResource (const char + *label); + core::ui::ComplexButtonResource * + createComplexButtonResource (core::Widget *widget, bool relief); + core::ui::ListResource * + createListResource (core::ui::ListResource::SelectionMode selectionMode, + int rows); + core::ui::OptionMenuResource *createOptionMenuResource (); + core::ui::EntryResource *createEntryResource (int size, bool password, + const char *label); + core::ui::MultiLineTextResource *createMultiLineTextResource (int cols, + int rows); + core::ui::CheckButtonResource *createCheckButtonResource (bool + activated); + core::ui::RadioButtonResource * + createRadioButtonResource (core::ui::RadioButtonResource + *groupedWith, bool activated); + }; + + FltkResourceFactory resourceFactory; + + class IdleFunc: public lout::object::Object + { + public: + int id; + void (core::Layout::*func) (); + }; + + core::Layout *layout; + + lout::container::typed::List <IdleFunc> *idleQueue; + bool idleFuncRunning; + int idleFuncId; + + static void generalStaticIdle(void *data); + void generalIdle(); + + FltkView *view; + lout::container::typed::List <ui::FltkResource> *resources; + +public: + FltkPlatform (); + ~FltkPlatform (); + + void setLayout (core::Layout *layout); + + void attachView (core::View *view); + + void detachView (core::View *view); + + int textWidth (core::style::Font *font, const char *text, int len); + char *textToUpper (const char *text, int len); + char *textToLower (const char *text, int len); + int nextGlyph (const char *text, int idx); + int prevGlyph (const char *text, int idx); + float dpiX (); + float dpiY (); + + int addIdle (void (core::Layout::*func) ()); + void removeIdle (int idleId); + + core::style::Font *createFont (core::style::FontAttrs *attrs, + bool tryEverything); + bool fontExists (const char *name); + core::style::Color *createColor (int color); + core::style::Tooltip *createTooltip (const char *text); + void cancelTooltip(); + + core::Imgbuf *createImgbuf (core::Imgbuf::Type type, int width, int height, + double gamma); + + void copySelection(const char *text); + + core::ui::ResourceFactory *getResourceFactory (); + + void attachResource (ui::FltkResource *resource); + void detachResource (ui::FltkResource *resource); +}; + +} // namespace fltk +} // namespace dw + +#endif // __DW_FLTKPLATFORM_HH__ diff --git a/dw/fltkpreview.cc b/dw/fltkpreview.cc new file mode 100644 index 0000000..b234e81 --- /dev/null +++ b/dw/fltkpreview.cc @@ -0,0 +1,316 @@ +/* + * RTFL (originally part of dillo) + * + * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version; with the following exception: + * + * The copyright holders of RTFL give you permission to link this file + * statically or dynamically against all versions of the graphviz + * library, which are published by AT&T Corp. under one of the following + * licenses: + * + * - Common Public License version 1.0 as published by International + * Business Machines Corporation (IBM), or + * - Eclipse Public License version 1.0 as published by the Eclipse + * Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "../lout/msg.h" + +#include "fltkpreview.hh" +#include "fltkmisc.hh" + +#include <FL/Fl.H> +#include <FL/Fl_Bitmap.H> +#include <FL/fl_draw.H> +#include <stdio.h> + +#include "preview.xbm" + +namespace dw { +namespace fltk { + +FltkPreview::FltkPreview (int x, int y, int w, int h, + dw::core::Layout *layout, const char *label): + FltkViewBase (x, y, w, h, label) +{ + layout->attachView (this); + + scrollX = 0; + scrollY = 0; + scrollWidth = 1; + scrollHeight = 1; +} + +FltkPreview::~FltkPreview () +{ +} + +int FltkPreview::handle (int event) +{ + return FltkViewBase::handle (event); +} + +int FltkPreview::translateViewXToCanvasX (int x) +{ + return x * canvasWidth / w (); +} + +int FltkPreview::translateViewYToCanvasY (int y) +{ + return y * canvasHeight / h (); +} + +int FltkPreview::translateCanvasXToViewX (int x) +{ + return x * w () / canvasWidth; +} + +int FltkPreview::translateCanvasYToViewY (int y) +{ + return y * h () / canvasHeight; +} + +void FltkPreview::setCanvasSize (int width, int ascent, int descent) +{ + FltkViewBase::setCanvasSize (width, ascent, descent); + if (parent() && parent()->visible ()) + ((FltkPreviewWindow*)parent())->reallocate (); +} + +bool FltkPreview::usesViewport () +{ + return true; +} + +int FltkPreview::getHScrollbarThickness () +{ + return 0; +} + +int FltkPreview::getVScrollbarThickness () +{ + return 0; +} + +void FltkPreview::scrollTo (int x, int y) +{ + scrollX = x; + scrollY = y; +} + +void FltkPreview::scroll (dw::core::ScrollCommand cmd) +{ + MSG_ERR("FltkPreview::scroll not implemented\n"); +} + +void FltkPreview::setViewportSize (int width, int height, + int hScrollbarThickness, + int vScrollbarThickness) +{ + scrollWidth = width - vScrollbarThickness; + scrollHeight = height - hScrollbarThickness; +} + +void FltkPreview::drawText (core::style::Font *font, + core::style::Color *color, + core::style::Color::Shading shading, + int x, int y, const char *text, int len) +{ + /* + * We must call setfont() before calling getwidth() (or anything + * else that measures text). + */ + FltkFont *ff = (FltkFont*)font; + Fl::set_font(ff->font, translateCanvasXToViewX (ff->size)); +#if 0 + /** + * \todo Normally, this should already be known, maybe it + * should be passed? + */ + int width = (int)getwidth (text, len); + int height = font->ascent; // No descent, this would look to "bold". + + int x1 = translateCanvasXToViewX (x); + int y1 = translateCanvasYToViewY (y); + int x2 = translateCanvasXToViewX (x + width); + int y2 = translateCanvasYToViewY (y + height); + Rectangle rect (x1, y1, x2 - x1, y2 - y1); + + setcolor(((FltkColor*)color)->colors[shading]); + fillrect (rect); +#endif + fl_color(((FltkColor*)color)->colors[shading]); + fl_draw(text, len, translateCanvasXToViewX (x), translateCanvasYToViewY(y)); +} + +void FltkPreview::drawSimpleWrappedText (core::style::Font *font, + core::style::Color *color, + core::style::Color::Shading shading, + int x, int y, int w, int h, + const char *text) +{ +} + +void FltkPreview::drawImage (core::Imgbuf *imgbuf, int xRoot, int yRoot, + int x, int y, int width, int height) +{ +} + +bool FltkPreview::usesFltkWidgets () +{ + return false; +} + +void FltkPreview::drawFltkWidget (Fl_Widget *widget, + core::Rectangle *area) +{ +} + +// ---------------------------------------------------------------------- + +FltkPreviewWindow::FltkPreviewWindow (dw::core::Layout *layout): + Fl_Menu_Window (1, 1) +{ + box (FL_EMBOSSED_BOX); + + begin (); + preview = new FltkPreview (BORDER_WIDTH, BORDER_WIDTH, 1, 1, layout); + end (); + + hide (); +} + +FltkPreviewWindow::~FltkPreviewWindow () +{ +} + +void FltkPreviewWindow::showWindow () +{ + reallocate (); + show (); +} + +void FltkPreviewWindow::reallocate () +{ + int maxWidth = misc::screenWidth () / 2; + int maxHeight = misc::screenHeight () * 4 / 5; + int mx, my, width, height; + bool warp = false; + + if (preview->canvasHeight * maxWidth > maxHeight * preview->canvasWidth) { + // Expand to maximal height (most likely case). + width = preview->canvasWidth * maxHeight / preview->canvasHeight; + height = maxHeight; + } else { + // Expand to maximal width. + width = maxWidth; + height = preview->canvasHeight * maxWidth / preview->canvasWidth; + } + + Fl::get_mouse(mx, my); + + posX = mx - preview->translateCanvasXToViewX (preview->scrollX + + preview->scrollWidth / 2); + posY = my - preview->translateCanvasYToViewY (preview->scrollY + + preview->scrollHeight / 2); + + if (posX < 0) { + mx -= posX; + posX = 0; + warp = true; + } else if (posX + width > misc::screenWidth ()) { + mx -= (posX - (misc::screenWidth () - width)); + posX = misc::screenWidth () - width; + warp = true; + } + + if (posY < 0) { + my -= posY; + posY = 0; + warp = true; + } else if (posY + height > misc::screenHeight ()) { + my -= (posY - (misc::screenHeight () - height)); + posY = misc::screenHeight () - height; + warp = true; + } + + if (warp) + misc::warpPointer (mx, my); + + resize (posX, posY, width, height); + + preview->size(w () - 2 * BORDER_WIDTH, h () - 2 * BORDER_WIDTH); +} + +void FltkPreviewWindow::hideWindow () +{ + Fl_Window::hide (); +} + +void FltkPreviewWindow::scrollTo (int mouseX, int mouseY) +{ + preview->scrollX = + preview->translateViewXToCanvasX (mouseX - posX - BORDER_WIDTH) + - preview->scrollWidth / 2; + preview->scrollY = + preview->translateViewYToCanvasY (mouseY - posY - BORDER_WIDTH) + - preview->scrollHeight / 2; + preview->theLayout->scrollPosChanged (preview, + preview->scrollX, preview->scrollY); +} + +// ---------------------------------------------------------------------- + +FltkPreviewButton::FltkPreviewButton (int x, int y, int w, int h, + dw::core::Layout *layout, + const char *label): + Fl_Button (x, y, w, h, label) +{ + image (new Fl_Bitmap (preview_bits, preview_width, preview_height)); + window = new FltkPreviewWindow (layout); +} + +FltkPreviewButton::~FltkPreviewButton () +{ +} + +int FltkPreviewButton::handle (int event) +{ + /** \bug Some parts are missing. */ + + switch (event) { + case FL_PUSH: + window->showWindow (); + return Fl_Button::handle (event); + + case FL_DRAG: + if (window->visible ()) { + window->scrollTo (Fl::event_x_root (), Fl::event_y_root ()); + return 1; + } + return Fl_Button::handle (event); + + case FL_RELEASE: + window->hideWindow (); + return Fl_Button::handle (event); + + default: + return Fl_Button::handle (event); + } +} + +} // namespace fltk +} // namespace dw diff --git a/dw/fltkpreview.hh b/dw/fltkpreview.hh new file mode 100644 index 0000000..2382b86 --- /dev/null +++ b/dw/fltkpreview.hh @@ -0,0 +1,95 @@ +#ifndef __FlTKPREVIEW_HH__ +#define __FlTKPREVIEW_HH__ + +#include <FL/Fl_Button.H> +#include <FL/Fl_Menu_Window.H> +#include "fltkviewbase.hh" + +namespace dw { +namespace fltk { + +class FltkPreview: public FltkViewBase +{ + friend class FltkPreviewWindow; + +private: + int scrollX, scrollY, scrollWidth, scrollHeight; + +protected: + int translateViewXToCanvasX (int x); + int translateViewYToCanvasY (int y); + int translateCanvasXToViewX (int x); + int translateCanvasYToViewY (int y); + +public: + FltkPreview (int x, int y, int w, int h, dw::core::Layout *layout, + const char *label = 0); + ~FltkPreview (); + + int handle (int event); + + void setCanvasSize (int width, int ascent, int descent); + + bool usesViewport (); + int getHScrollbarThickness (); + int getVScrollbarThickness (); + void scrollTo (int x, int y); + void scroll (dw::core::ScrollCommand cmd); + void setViewportSize (int width, int height, + int hScrollbarThickness, int vScrollbarThickness); + + void drawText (core::style::Font *font, + core::style::Color *color, + core::style::Color::Shading shading, + int x, int y, const char *text, int len); + void drawSimpleWrappedText (core::style::Font *font, + core::style::Color *color, + core::style::Color::Shading shading, + int x, int y, int w, int h, + const char *text); + void drawImage (core::Imgbuf *imgbuf, int xRoot, int yRoot, + int x, int y, int width, int height); + + bool usesFltkWidgets (); + void drawFltkWidget (Fl_Widget *widget, core::Rectangle *area); +}; + + +class FltkPreviewWindow: public Fl_Menu_Window +{ +private: + enum { BORDER_WIDTH = 2 }; + + FltkPreview *preview; + int posX, posY; + +public: + FltkPreviewWindow (dw::core::Layout *layout); + ~FltkPreviewWindow (); + + void reallocate (); + + void showWindow (); + void hideWindow (); + + void scrollTo (int mouseX, int mouseY); +}; + + +class FltkPreviewButton: public Fl_Button +{ +private: + FltkPreviewWindow *window; + +public: + FltkPreviewButton (int x, int y, int w, int h, + dw::core::Layout *layout, const char *label = 0); + ~FltkPreviewButton (); + + int handle (int event); +}; + +} // namespace fltk +} // namespace dw + +#endif // __FlTKPREVIEW_HH__ diff --git a/dw/fltkui.cc b/dw/fltkui.cc new file mode 100644 index 0000000..ce47dcd --- /dev/null +++ b/dw/fltkui.cc @@ -0,0 +1,1395 @@ +/* + * RTFL (originally part of dillo) + * + * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version; with the following exception: + * + * The copyright holders of RTFL give you permission to link this file + * statically or dynamically against all versions of the graphviz + * library, which are published by AT&T Corp. under one of the following + * licenses: + * + * - Common Public License version 1.0 as published by International + * Business Machines Corporation (IBM), or + * - Eclipse Public License version 1.0 as published by the Eclipse + * Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + + +#include "fltkcore.hh" +#include "../lout/msg.h" +#include "../lout/misc.hh" + +#include <FL/Fl.H> +#include <FL/fl_draw.H> +#include <FL/Fl_Input.H> +#include <FL/Fl_Text_Editor.H> +#include <FL/Fl_Check_Button.H> +#include <FL/Fl_Round_Button.H> +#include <FL/Fl_Choice.H> +#include <FL/Fl_Browser.H> + +#include <stdio.h> + +//---------------------------------------------------------------------------- +/* + * Local sub classes + */ + +/* + * Used to enable CTRL+{a,e,d,k} in form inputs (for start,end,del,cut) + */ +class CustInput2 : public Fl_Input { +public: + CustInput2 (int x, int y, int w, int h, const char* l=0) : + Fl_Input(x,y,w,h,l) {}; + int handle(int e); +}; + +int CustInput2::handle(int e) +{ + int k = Fl::event_key(); + + _MSG("CustInput2::handle event=%d\n", e); + + // We're only interested in some flags + unsigned modifier = Fl::event_state() & (FL_SHIFT | FL_CTRL | FL_ALT); + + if (e == FL_KEYBOARD) { + if (k == FL_Page_Down || k == FL_Page_Up || k == FL_Up || k == FL_Down) { + // Let them through for key commands and viewport motion. + return 0; + } + if (modifier == FL_CTRL) { + if (k == 'a' || k == 'e') { + position(k == 'a' ? 0 : size()); + return 1; + } else if (k == 'k') { + cut(position(), size()); + return 1; + } else if (k == 'd') { + cut(position(), position()+1); + return 1; + } else if (k == 'h' || k == 'i' || k == 'j' || k == 'l' || k == 'm') { + // Fl_Input wants to use ^H as backspace, and also "insert a few + // selected control characters literally", but this gets in the way + // of key commands. + return 0; + } + } + } + return Fl_Input::handle(e); +} + + +/* + * Used to handle some keystrokes as shortcuts to option menuitems + * (i.e. jump to the next menuitem whose label starts with the pressed key) + */ +class CustChoice : public Fl_Choice { +public: + CustChoice (int x, int y, int w, int h, const char* l=0) : + Fl_Choice(x,y,w,h,l) {}; + int handle(int e); +}; + +int CustChoice::handle(int e) +{ + int k = Fl::event_key(); + unsigned modifier = Fl::event_state() & (FL_SHIFT|FL_CTRL|FL_ALT|FL_META); + + _MSG("CustChoice::handle %p e=%d active=%d focus=%d\n", + this, e, active(), (Fl::focus() == this)); + if (Fl::focus() != this) { + ; // Not Focused, let FLTK handle it + } else if (e == FL_KEYDOWN && modifier == 0) { + if (k == FL_Enter || k == FL_Down) { + return Fl_Choice::handle(FL_PUSH); // activate menu + + } else if (isalnum(k)) { // try key as shortcut to menuitem + int t = value()+1 >= size() ? 0 : value()+1; + while (t != value()) { + const Fl_Menu_Item *mi = &(menu()[t]); + if (mi->submenu()) // submenu? + ; + else if (mi->label() && mi->active()) { // menu item? + if (k == tolower(mi->label()[0])) { + value(mi); + return 1; // Let FLTK know we used this key + } + } + if (++t == size()) + t = 0; + } + } + } + + return Fl_Choice::handle(e); +} + +//---------------------------------------------------------------------------- + +namespace dw { +namespace fltk { +namespace ui { + +enum { RELIEF_X_THICKNESS = 3, RELIEF_Y_THICKNESS = 3 }; + +using namespace lout::object; +using namespace lout::container::typed; + +FltkResource::FltkResource (FltkPlatform *platform) +{ + DBG_OBJ_CREATE ("dw::fltk::ui::FltkResource"); + + this->platform = platform; + + allocation.x = 0; + allocation.y = 0; + allocation.width = 1; + allocation.ascent = 1; + allocation.descent = 0; + + style = NULL; + + enabled = true; +} + +/** + * This is not a constructor, since it calls some virtual methods, which + * should not be done in a C++ base constructor. + */ +void FltkResource::init (FltkPlatform *platform) +{ + view = NULL; + widget = NULL; + platform->attachResource (this); +} + +FltkResource::~FltkResource () +{ + platform->detachResource (this); + if (widget) { + if (view) { + view->removeFltkWidget(widget); + } + delete widget; + } + if (style) + style->unref (); + + DBG_OBJ_DELETE (); +} + +void FltkResource::attachView (FltkView *view) +{ + if (this->view) + MSG_ERR("FltkResource::attachView: multiple views!\n"); + + if (view->usesFltkWidgets ()) { + this->view = view; + + widget = createNewWidget (&allocation); + view->addFltkWidget (widget, &allocation); + if (style) + setWidgetStyle (widget, style); + if (! enabled) + widget->deactivate (); + } +} + +void FltkResource::detachView (FltkView *view) +{ + if (this->view != view) + MSG_ERR("FltkResource::detachView: this->view: %p view: %p\n", + this->view, view); + this->view = NULL; +} + +void FltkResource::sizeAllocate (core::Allocation *allocation) +{ + DBG_OBJ_ENTER ("resize", 0, "sizeAllocate", "%d, %d; %d * (%d + %d)", + allocation->x, allocation->y, allocation->width, + allocation->ascent, allocation->descent); + + this->allocation = *allocation; + view->allocateFltkWidget (widget, allocation); + + DBG_OBJ_LEAVE (); +} + +void FltkResource::draw (core::View *view, core::Rectangle *area) +{ + FltkView *fltkView = (FltkView*)view; + if (fltkView->usesFltkWidgets () && this->view == fltkView) { + fltkView->drawFltkWidget (widget, area); + } +} + +void FltkResource::setStyle (core::style::Style *style) +{ + if (this->style) + this->style->unref (); + + this->style = style; + style->ref (); + + setWidgetStyle (widget, style); +} + +void FltkResource::setWidgetStyle (Fl_Widget *widget, + core::style::Style *style) +{ + FltkFont *font = (FltkFont*)style->font; + widget->labelsize (font->size); + widget->labelfont (font->font); + + FltkColor *bg = (FltkColor*)style->backgroundColor; + if (bg) { + int normal_bg = bg->colors[FltkColor::SHADING_NORMAL]; + + if (style->color) { + int style_fg = ((FltkColor*)style->color)->colors + [FltkColor::SHADING_NORMAL]; + Fl_Color fg = fl_contrast(style_fg, normal_bg); + + widget->labelcolor(fg); + widget->selection_color(fg); + } + + widget->color(normal_bg); + } +} + +void FltkResource::setDisplayed(bool displayed) +{ + if (displayed) + widget->show(); + else + widget->hide(); +} + +bool FltkResource::displayed() +{ + bool ret = false; + + if (widget) { + // visible() is not the same thing as being show()n exactly, but + // show()/hide() set it appropriately for our purposes. + ret = widget->visible(); + } + return ret; +} + +bool FltkResource::isEnabled () +{ + return enabled; +} + +void FltkResource::setEnabled (bool enabled) +{ + this->enabled = enabled; + + if (enabled) + widget->activate (); + else + widget->deactivate (); +} + +// ---------------------------------------------------------------------- + +template <class I> FltkSpecificResource<I>::FltkSpecificResource (FltkPlatform + *platform) : + FltkResource (platform) +{ + DBG_OBJ_CREATE ("dw::fltk::ui::FltkSpecificResource<>"); + DBG_OBJ_BASECLASS (I); + DBG_OBJ_BASECLASS (FltkResource); +} + +template <class I> FltkSpecificResource<I>::~FltkSpecificResource () +{ + DBG_OBJ_DELETE (); +} + +template <class I> void FltkSpecificResource<I>::sizeAllocate (core::Allocation + *allocation) +{ + FltkResource::sizeAllocate (allocation); +} + +template <class I> void FltkSpecificResource<I>::draw (core::View *view, + core::Rectangle *area) +{ + FltkResource::draw (view, area); +} + +template <class I> void FltkSpecificResource<I>::setStyle (core::style::Style + *style) +{ + FltkResource::setStyle (style); +} + +template <class I> bool FltkSpecificResource<I>::isEnabled () +{ + return FltkResource::isEnabled (); +} + +template <class I> void FltkSpecificResource<I>::setEnabled (bool enabled) +{ + FltkResource::setEnabled (enabled); +} + +// ---------------------------------------------------------------------- + +class EnterButton : public Fl_Button { +public: + EnterButton (int x,int y,int w,int h, const char* label = 0) : + Fl_Button (x,y,w,h,label) {}; + int handle(int e); +}; + +int EnterButton::handle(int e) +{ + if (e == FL_KEYBOARD && Fl::focus() == this && Fl::event_key() == FL_Enter){ + set_changed(); + simulate_key_action(); + do_callback(); + return 1; + } + return Fl_Button::handle(e); +} + +FltkLabelButtonResource::FltkLabelButtonResource (FltkPlatform *platform, + const char *label): + FltkSpecificResource <dw::core::ui::LabelButtonResource> (platform) +{ + this->label = strdup (label); + init (platform); +} + +FltkLabelButtonResource::~FltkLabelButtonResource () +{ + free((char *)label); +} + +Fl_Widget *FltkLabelButtonResource::createNewWidget (core::Allocation + *allocation) +{ + Fl_Button *button = + new EnterButton (allocation->x, allocation->y, allocation->width, + allocation->ascent + allocation->descent, label); + button->callback (widgetCallback, this); + button->when (FL_WHEN_RELEASE); + return button; +} + +void FltkLabelButtonResource::sizeRequest (core::Requisition *requisition) +{ + DBG_OBJ_ENTER0 ("resize", 0, "sizeRequest"); + + if (style) { + FltkFont *font = (FltkFont*)style->font; + fl_font(font->font,font->size); + requisition->width = + (int)fl_width (label, strlen (label)) + + 2 * RELIEF_X_THICKNESS; + requisition->ascent = font->ascent + RELIEF_Y_THICKNESS; + requisition->descent = font->descent + RELIEF_Y_THICKNESS; + } else { + requisition->width = 1; + requisition->ascent = 1; + requisition->descent = 0; + } + + DBG_OBJ_MSGF ("resize", 1, "result: %d * (%d + %d)", + requisition->width, requisition->ascent, requisition->descent); + DBG_OBJ_LEAVE (); +} + +/* + * Get FLTK state and translate to dw + * + * TODO: find a good home for this and the fltkviewbase.cc original. + */ +static core::ButtonState getDwButtonState () +{ + int s1 = Fl::event_state (); + int s2 = (core::ButtonState)0; + + if (s1 & FL_SHIFT) s2 |= core::SHIFT_MASK; + if (s1 & FL_CTRL) s2 |= core::CONTROL_MASK; + if (s1 & FL_ALT) s2 |= core::META_MASK; + if (s1 & FL_BUTTON1) s2 |= core::BUTTON1_MASK; + if (s1 & FL_BUTTON2) s2 |= core::BUTTON2_MASK; + if (s1 & FL_BUTTON3) s2 |= core::BUTTON3_MASK; + + return (core::ButtonState)s2; +} + +static void setButtonEvent(dw::core::EventButton *event) +{ + event->xCanvas = Fl::event_x(); + event->yCanvas = Fl::event_y(); + event->state = getDwButtonState(); + event->button = Fl::event_button(); + event->numPressed = Fl::event_clicks() + 1; +} + +void FltkLabelButtonResource::widgetCallback (Fl_Widget *widget, + void *data) +{ + if (!Fl::event_button3()) { + FltkLabelButtonResource *lbr = (FltkLabelButtonResource*) data; + dw::core::EventButton event; + setButtonEvent(&event); + lbr->emitClicked(&event); + } +} + +const char *FltkLabelButtonResource::getLabel () +{ + return label; +} + + +void FltkLabelButtonResource::setLabel (const char *label) +{ + free((char *)this->label); + this->label = strdup (label); + + widget->label (this->label); + queueResize (true); +} + +// ---------------------------------------------------------------------- + +FltkEntryResource::FltkEntryResource (FltkPlatform *platform, int size, + bool password, const char *label): + FltkSpecificResource <dw::core::ui::EntryResource> (platform) +{ + this->size = size; + this->password = password; + this->label = label ? strdup(label) : NULL; + this->label_w = 0; + + initText = NULL; + editable = false; + + init (platform); +} + +FltkEntryResource::~FltkEntryResource () +{ + if (initText) + free((char *)initText); + if (label) + free(label); +} + +Fl_Widget *FltkEntryResource::createNewWidget (core::Allocation + *allocation) +{ + Fl_Input *input = + new CustInput2(allocation->x, allocation->y, allocation->width, + allocation->ascent + allocation->descent); + if (password) + input->type(FL_SECRET_INPUT); + input->callback (widgetCallback, this); + input->when (FL_WHEN_ENTER_KEY_ALWAYS); + + if (label) { + input->label(label); + input->align(FL_ALIGN_LEFT); + } + if (initText) + input->value (initText); + + return input; +} + +void FltkEntryResource::setWidgetStyle (Fl_Widget *widget, + core::style::Style *style) +{ + Fl_Input *in = (Fl_Input *)widget; + + FltkResource::setWidgetStyle(widget, style); + + in->textcolor(widget->labelcolor()); + in->cursor_color(in->textcolor()); + in->textsize(in->labelsize()); + in->textfont(in->labelfont()); + + if (label) { + int h; + label_w = 0; + widget->measure_label(label_w, h); + label_w += RELIEF_X_THICKNESS; + } +} + +void FltkEntryResource::setDisplayed(bool displayed) +{ + FltkResource::setDisplayed(displayed); + queueResize(true); +} + +void FltkEntryResource::sizeRequest (core::Requisition *requisition) +{ + DBG_OBJ_ENTER0 ("resize", 0, "sizeRequest"); + + if (displayed() && style) { + FltkFont *font = (FltkFont*)style->font; + fl_font(font->font,font->size); + // WORKAROUND: A bug with fl_width(uint_t) on non-xft X was present in + // 1.3.0 (STR #2688). + requisition->width = + (int)fl_width ("n") + * (size == UNLIMITED_SIZE ? 10 : size) + + label_w + (2 * RELIEF_X_THICKNESS); + requisition->ascent = font->ascent + RELIEF_Y_THICKNESS; + requisition->descent = font->descent + RELIEF_Y_THICKNESS; + } else { + requisition->width = 0; + requisition->ascent = 0; + requisition->descent = 0; + } + + DBG_OBJ_MSGF ("resize", 1, "result: %d * (%d + %d)", + requisition->width, requisition->ascent, requisition->descent); + DBG_OBJ_LEAVE (); +} + +void FltkEntryResource::sizeAllocate (core::Allocation *allocation) +{ + if (!label) { + FltkResource::sizeAllocate(allocation); + } else { + DBG_OBJ_MSGF ("resize", 0, + "<b>sizeAllocate</b> (%d, %d; %d * (%d + %d))", + allocation->x, allocation->y, allocation->width, + allocation->ascent, allocation->descent); + + this->allocation = *allocation; + + /* push the Fl_Input over to the right of the label */ + core::Allocation a = this->allocation; + a.x += this->label_w; + a.width -= this->label_w; + view->allocateFltkWidget (widget, &a); + } +} + +void FltkEntryResource::widgetCallback (Fl_Widget *widget, void *data) +{ + ((FltkEntryResource*)data)->emitActivate (); +} + +const char *FltkEntryResource::getText () +{ + return ((Fl_Input*)widget)->value (); +} + +void FltkEntryResource::setText (const char *text) +{ + if (initText) + free((char *)initText); + initText = strdup (text); + + ((Fl_Input*)widget)->value (initText); +} + +bool FltkEntryResource::isEditable () +{ + return editable; +} + +void FltkEntryResource::setEditable (bool editable) +{ + this->editable = editable; +} + +void FltkEntryResource::setMaxLength (int maxlen) +{ + ((Fl_Input *)widget)->maximum_size(maxlen); +} + +// ---------------------------------------------------------------------- + +static int kf_backspace_word (int c, Fl_Text_Editor *e) +{ + int p1, p2 = e->insert_position(); + + e->previous_word(); + p1 = e->insert_position(); + e->buffer()->remove(p1, p2); + e->show_insert_position(); + e->set_changed(); + if (e->when() & FL_WHEN_CHANGED) + e->do_callback(); + return 0; +} + +FltkMultiLineTextResource::FltkMultiLineTextResource (FltkPlatform *platform, + int cols, int rows): + FltkSpecificResource <dw::core::ui::MultiLineTextResource> (platform) +{ + buffer = new Fl_Text_Buffer; + text_copy = NULL; + editable = false; + + numCols = cols; + numRows = rows; + + // Check values. Upper bound check is left to the caller. + if (numCols < 1) { + MSG_WARN("numCols = %d is set to 1.\n", numCols); + numCols = 1; + } + if (numRows < 1) { + MSG_WARN("numRows = %d is set to 1.\n", numRows); + numRows = 1; + } + + init (platform); +} + +FltkMultiLineTextResource::~FltkMultiLineTextResource () +{ + /* Free memory avoiding a double-free of text buffers */ + ((Fl_Text_Editor *) widget)->buffer (0); + delete buffer; + if (text_copy) + free(text_copy); +} + +Fl_Widget *FltkMultiLineTextResource::createNewWidget (core::Allocation + *allocation) +{ + Fl_Text_Editor *text = + new Fl_Text_Editor (allocation->x, allocation->y, allocation->width, + allocation->ascent + allocation->descent); + text->wrap_mode(Fl_Text_Display::WRAP_AT_BOUNDS, 0); + text->buffer (buffer); + text->remove_key_binding(FL_BackSpace, FL_TEXT_EDITOR_ANY_STATE); + text->add_key_binding(FL_BackSpace, 0, Fl_Text_Editor::kf_backspace); + text->add_key_binding(FL_BackSpace, FL_CTRL, kf_backspace_word); + return text; +} + +void FltkMultiLineTextResource::setWidgetStyle (Fl_Widget *widget, + core::style::Style *style) +{ + Fl_Text_Editor *ed = (Fl_Text_Editor *)widget; + + FltkResource::setWidgetStyle(widget, style); + + ed->textcolor(widget->labelcolor()); + ed->cursor_color(ed->textcolor()); + ed->textsize(ed->labelsize()); + ed->textfont(ed->labelfont()); +} + +void FltkMultiLineTextResource::sizeRequest (core::Requisition *requisition) +{ + DBG_OBJ_ENTER0 ("resize", 0, "sizeRequest"); + + if (style) { + FltkFont *font = (FltkFont*)style->font; + fl_font(font->font,font->size); + // WORKAROUND: A bug with fl_width(uint_t) on non-xft X was present in + // 1.3.0 (STR #2688). + requisition->width = + (int)fl_width ("n") * numCols + 2 * RELIEF_X_THICKNESS; + requisition->ascent = + RELIEF_Y_THICKNESS + font->ascent + + (font->ascent + font->descent) * (numRows - 1); + requisition->descent = + font->descent + + RELIEF_Y_THICKNESS; + } else { + requisition->width = 1; + requisition->ascent = 1; + requisition->descent = 0; + } + + DBG_OBJ_MSGF ("resize", 1, "result: %d * (%d + %d)", + requisition->width, requisition->ascent, requisition->descent); + DBG_OBJ_LEAVE (); +} + +const char *FltkMultiLineTextResource::getText () +{ + /* FLTK-1.3 insists upon returning a new copy of the buffer text, so + * we have to keep track of it. + */ + if (text_copy) + free(text_copy); + text_copy = buffer->text(); + return text_copy; +} + +void FltkMultiLineTextResource::setText (const char *text) +{ + buffer->text (text); +} + +bool FltkMultiLineTextResource::isEditable () +{ + return editable; +} + +void FltkMultiLineTextResource::setEditable (bool editable) +{ + this->editable = editable; +} + +// ---------------------------------------------------------------------- + +template <class I> +FltkToggleButtonResource<I>::FltkToggleButtonResource (FltkPlatform *platform, + bool activated): + FltkSpecificResource <I> (platform) +{ + initActivated = activated; +} + + +template <class I> +FltkToggleButtonResource<I>::~FltkToggleButtonResource () +{ +} + + +template <class I> +Fl_Widget *FltkToggleButtonResource<I>::createNewWidget (core::Allocation + *allocation) +{ + Fl_Button *button = createNewButton (allocation); + button->value (initActivated); + return button; +} + +template <class I> +void FltkToggleButtonResource<I>::setWidgetStyle (Fl_Widget *widget, + core::style::Style *style) +{ + FltkResource::setWidgetStyle(widget, style); + + widget->selection_color(FL_BLACK); +} + + +template <class I> +void FltkToggleButtonResource<I>::sizeRequest (core::Requisition *requisition) +{ + DBG_OBJ_ENTER0 ("resize", 0, "sizeRequest"); + + FltkFont *font = (FltkFont *) + (this->FltkResource::style ? this->FltkResource::style->font : NULL); + + if (font) { + fl_font(font->font, font->size); + requisition->width = font->ascent + font->descent + 2*RELIEF_X_THICKNESS; + requisition->ascent = font->ascent + RELIEF_Y_THICKNESS; + requisition->descent = font->descent + RELIEF_Y_THICKNESS; + } else { + requisition->width = 1; + requisition->ascent = 1; + requisition->descent = 0; + } + + DBG_OBJ_MSGF ("resize", 1, "result: %d * (%d + %d)", + requisition->width, requisition->ascent, requisition->descent); + DBG_OBJ_LEAVE (); +} + + +template <class I> +bool FltkToggleButtonResource<I>::isActivated () +{ + return ((Fl_Button*)this->widget)->value (); +} + + +template <class I> +void FltkToggleButtonResource<I>::setActivated (bool activated) +{ + initActivated = activated; + ((Fl_Button*)this->widget)->value (initActivated); +} + +// ---------------------------------------------------------------------- + +FltkCheckButtonResource::FltkCheckButtonResource (FltkPlatform *platform, + bool activated): + FltkToggleButtonResource<dw::core::ui::CheckButtonResource> (platform, + activated) +{ + init (platform); +} + + +FltkCheckButtonResource::~FltkCheckButtonResource () +{ +} + + +Fl_Button *FltkCheckButtonResource::createNewButton (core::Allocation + *allocation) +{ + Fl_Check_Button *cb = + new Fl_Check_Button (allocation->x, allocation->y, allocation->width, + allocation->ascent + allocation->descent); + return cb; +} + +// ---------------------------------------------------------------------- + +bool FltkRadioButtonResource::Group::FltkGroupIterator::hasNext () +{ + return it.hasNext (); +} + +dw::core::ui::RadioButtonResource +*FltkRadioButtonResource::Group::FltkGroupIterator::getNext () +{ + return (dw::core::ui::RadioButtonResource*)it.getNext (); +} + +void FltkRadioButtonResource::Group::FltkGroupIterator::unref () +{ + delete this; +} + + +FltkRadioButtonResource::Group::Group (FltkRadioButtonResource + *radioButtonResource) +{ + list = new lout::container::typed::List <FltkRadioButtonResource> (false); + connect (radioButtonResource); +} + +FltkRadioButtonResource::Group::~Group () +{ + delete list; +} + +void FltkRadioButtonResource::Group::connect (FltkRadioButtonResource + *radioButtonResource) +{ + list->append (radioButtonResource); +} + +void FltkRadioButtonResource::Group::unconnect (FltkRadioButtonResource + *radioButtonResource) +{ + list->removeRef (radioButtonResource); + if (list->isEmpty ()) + delete this; +} + + +FltkRadioButtonResource::FltkRadioButtonResource (FltkPlatform *platform, + FltkRadioButtonResource + *groupedWith, + bool activated): + FltkToggleButtonResource<dw::core::ui::RadioButtonResource> (platform, + activated) +{ + init (platform); + + if (groupedWith) { + group = groupedWith->group; + group->connect (this); + } else + group = new Group (this); +} + + +FltkRadioButtonResource::~FltkRadioButtonResource () +{ + group->unconnect (this); +} + +dw::core::ui::RadioButtonResource::GroupIterator +*FltkRadioButtonResource::groupIterator () +{ + return group->groupIterator (); +} + +void FltkRadioButtonResource::widgetCallback (Fl_Widget *widget, + void *data) +{ + if (widget->when () & FL_WHEN_CHANGED) + ((FltkRadioButtonResource*)data)->buttonClicked (); +} + +void FltkRadioButtonResource::buttonClicked () +{ + for (Iterator <FltkRadioButtonResource> it = group->iterator (); + it.hasNext (); ) { + FltkRadioButtonResource *other = it.getNext (); + other->setActivated (other == this); + } +} + +Fl_Button *FltkRadioButtonResource::createNewButton (core::Allocation + *allocation) +{ + /* + * Groups of Fl_Radio_Button must be added to one Fl_Group, which is + * not possible in this context. For this, we do the grouping ourself, + * based on FltkRadioButtonResource::Group. + * + * What we actually need for this, is a widget, which behaves like a + * check button, but looks like a radio button. The first depends on the + * type, the second on the style. Since the type is simpler to change + * than the style, we create a radio button, and then change the type + * (instead of creating a check button, and changing the style). + */ + + Fl_Button *button = + new Fl_Round_Button (allocation->x, allocation->y, allocation->width, + allocation->ascent + allocation->descent); + button->when (FL_WHEN_CHANGED); + button->callback (widgetCallback, this); + button->type (FL_TOGGLE_BUTTON); + + return button; +} + +// ---------------------------------------------------------------------- + +template <class I> dw::core::Iterator * +FltkSelectionResource<I>::iterator (dw::core::Content::Type mask, bool atEnd) +{ + /** \bug Implementation. */ + return new core::EmptyIterator (this->getEmbed (), mask, atEnd); +} + +// ---------------------------------------------------------------------- + +FltkOptionMenuResource::FltkOptionMenuResource (FltkPlatform *platform): + FltkSelectionResource <dw::core::ui::OptionMenuResource> (platform) +{ + /* Fl_Menu_ does not like multiple menu items with the same label, and + * insert() treats some characters specially unless escaped, so let's + * do our own menu handling. + */ + itemsAllocated = 0x10; + menu = new Fl_Menu_Item[itemsAllocated]; + memset(menu, 0, itemsAllocated * sizeof(Fl_Menu_Item)); + itemsUsed = 1; // menu[0].text == NULL, which is an end-of-menu marker. + + init (platform); +} + +FltkOptionMenuResource::~FltkOptionMenuResource () +{ + for (int i = 0; i < itemsUsed; i++) { + if (menu[i].text) + free((char *) menu[i].text); + } + delete[] menu; +} + +void FltkOptionMenuResource::setWidgetStyle (Fl_Widget *widget, + core::style::Style *style) +{ + Fl_Choice *ch = (Fl_Choice *)widget; + + FltkResource::setWidgetStyle(widget, style); + + ch->textcolor(widget->labelcolor()); + ch->textfont(ch->labelfont()); + ch->textsize(ch->labelsize()); +} + +Fl_Widget *FltkOptionMenuResource::createNewWidget (core::Allocation + *allocation) +{ + Fl_Choice *choice = + new CustChoice (allocation->x, allocation->y, + allocation->width, + allocation->ascent + allocation->descent); + choice->menu(menu); + return choice; +} + +void FltkOptionMenuResource::widgetCallback (Fl_Widget *widget, + void *data) +{ +} + +int FltkOptionMenuResource::getMaxItemWidth() +{ + int i, max = 0; + + for (i = 0; i < itemsUsed; i++) { + int width = 0; + const char *str = menu[i].text; + + if (str) { + width = fl_width(str); + if (width > max) + max = width; + } + } + return max; +} + +void FltkOptionMenuResource::sizeRequest (core::Requisition *requisition) +{ + DBG_OBJ_ENTER0 ("resize", 0, "sizeRequest"); + + if (style) { + FltkFont *font = (FltkFont*)style->font; + fl_font(font->font, font->size); + int maxItemWidth = getMaxItemWidth (); + requisition->ascent = font->ascent + RELIEF_Y_THICKNESS; + requisition->descent = font->descent + RELIEF_Y_THICKNESS; + requisition->width = maxItemWidth + + (requisition->ascent + requisition->descent) + + 2 * RELIEF_X_THICKNESS; + } else { + requisition->width = 1; + requisition->ascent = 1; + requisition->descent = 0; + } + + DBG_OBJ_MSGF ("resize", 1, "result: %d * (%d + %d)", + requisition->width, requisition->ascent, requisition->descent); + DBG_OBJ_LEAVE (); +} + +void FltkOptionMenuResource::enlargeMenu () +{ + Fl_Choice *ch = (Fl_Choice *)widget; + int selected = ch->value(); + Fl_Menu_Item *newMenu; + + itemsAllocated += 0x10; + newMenu = new Fl_Menu_Item[itemsAllocated]; + memcpy(newMenu, menu, itemsUsed * sizeof(Fl_Menu_Item)); + memset(newMenu + itemsUsed, 0, 0x10 * sizeof(Fl_Menu_Item)); + delete[] menu; + menu = newMenu; + ch->menu(menu); + ch->value(selected); +} + +Fl_Menu_Item *FltkOptionMenuResource::newItem() +{ + Fl_Menu_Item *item; + + if (itemsUsed == itemsAllocated) + enlargeMenu(); + + item = menu + itemsUsed - 1; + itemsUsed++; + + return item; +} + +void FltkOptionMenuResource::addItem (const char *str, + bool enabled, bool selected) +{ + Fl_Menu_Item *item = newItem(); + + item->text = strdup(str); + + if (enabled == false) + item->flags = FL_MENU_INACTIVE; + + if (selected) + ((Fl_Choice *)widget)->value(item); + + queueResize (true); +} + +void FltkOptionMenuResource::setItem (int index, bool selected) +{ + if (selected) + ((Fl_Choice *)widget)->value(menu+index); +} + +void FltkOptionMenuResource::pushGroup (const char *name, bool enabled) +{ + Fl_Menu_Item *item = newItem(); + + item->text = strdup(name); + + if (enabled == false) + item->flags = FL_MENU_INACTIVE; + + item->flags |= FL_SUBMENU; + + queueResize (true); +} + +void FltkOptionMenuResource::popGroup () +{ + /* Item with NULL text field closes the submenu */ + newItem(); + queueResize (true); +} + +bool FltkOptionMenuResource::isSelected (int index) +{ + return index == ((Fl_Choice *)widget)->value(); +} + +int FltkOptionMenuResource::getNumberOfItems() +{ + return ((Fl_Choice*)widget)->size(); +} + +// ---------------------------------------------------------------------- + +class CustBrowser : public Fl_Browser { +public: + CustBrowser(int x, int y, int w, int h) : Fl_Browser(x, y, w, h) {}; + int full_width() const; + int full_height() const {return Fl_Browser::full_height();} + int avg_height() {return size() ? Fl_Browser_::incr_height() : 0;} +}; + +/* + * Fl_Browser_ has a full_width(), but it has a tendency to contain 0, so... + */ +int CustBrowser::full_width() const +{ + int max = 0; + void *item = item_first(); + + while (item) { + int w = item_width(item); + + if (w > max) + max = w; + + item = item_next(item); + } + return max; +} + +FltkListResource::FltkListResource (FltkPlatform *platform, + core::ui::ListResource::SelectionMode + selectionMode, int rowCount): + FltkSelectionResource <dw::core::ui::ListResource> (platform), + currDepth(0) +{ + mode = selectionMode; + showRows = rowCount; + init (platform); +} + +FltkListResource::~FltkListResource () +{ +} + + +Fl_Widget *FltkListResource::createNewWidget (core::Allocation *allocation) +{ + CustBrowser *b = + new CustBrowser (allocation->x, allocation->y, allocation->width, + allocation->ascent + allocation->descent); + + b->type((mode == SELECTION_MULTIPLE) ? FL_MULTI_BROWSER : FL_HOLD_BROWSER); + b->callback(widgetCallback, this); + b->when(FL_WHEN_CHANGED); + b->column_widths(colWidths); + b->column_char('\a'); // I just chose a nonprinting character. + + return b; +} + +void FltkListResource::setWidgetStyle (Fl_Widget *widget, + core::style::Style *style) +{ + Fl_Browser *b = (Fl_Browser *)widget; + + FltkResource::setWidgetStyle(widget, style); + + b->textfont(widget->labelfont()); + b->textsize(widget->labelsize()); + b->textcolor(widget->labelcolor()); + + colWidths[0] = b->textsize(); + colWidths[1] = colWidths[0]; + colWidths[2] = colWidths[0]; + colWidths[3] = 0; +} + +void FltkListResource::widgetCallback (Fl_Widget *widget, void *data) +{ + Fl_Browser *b = (Fl_Browser *) widget; + + if (b->selected(b->value())) { + /* If it shouldn't be selectable, deselect it again. It would be nice to + * have a less unpleasant way to do this. + */ + const char *inactive_code; + if ((inactive_code = strstr(b->text(b->value()), "@N"))) { + const char *ignore_codes = strstr(b->text(b->value()), "@."); + + if (inactive_code < ignore_codes) + b->select(b->value(), 0); + } + } +} + +void *FltkListResource::newItem (const char *str, bool enabled, bool selected) +{ + Fl_Browser *b = (Fl_Browser *) widget; + int index = b->size() + 1; + char *label = (char *)malloc(strlen(str) + 1 + currDepth + 4), + *s = label; + + memset(s, '\a', currDepth); + s += currDepth; + if (!enabled) { + // FL_INACTIVE_COLOR + *s++ = '@'; + *s++ = 'N'; + } + // ignore further '@' chars + *s++ = '@'; + *s++ = '.'; + + strcpy(s, str); + + b->add(label); + free(label); + + if (selected) { + b->select(index, selected); + if (b->type() == FL_HOLD_BROWSER) { + /* Left to its own devices, it sometimes has some suboptimal ideas + * about how to scroll, and sometimes doesn't seem to show everything + * where it thinks it is. + */ + if (index > showRows) { + /* bottomline() and middleline() don't work because the widget is + * too tiny at this point for the bbox() call in + * Fl_Browser::lineposition() to do what one would want. + */ + b->topline(index - showRows + 1); + } else { + b->topline(1); + } + } + } + queueResize (true); + return NULL; +} + +void FltkListResource::addItem (const char *str, bool enabled, bool selected) +{ + // Fl_Browser_::incr_height() for item height won't do the right thing if + // the first item doesn't have anything to it. + if (!str || !*str) + str = " "; + newItem(str, enabled, selected); +} + +void FltkListResource::setItem (int index, bool selected) +{ + Fl_Browser *b = (Fl_Browser *) widget; + + b->select(index + 1, selected); +} + +void FltkListResource::pushGroup (const char *name, bool enabled) +{ + bool en = false; + bool selected = false; + + // Fl_Browser_::incr_height() for item height won't do the right thing if + // the first item doesn't have anything to it. + if (!name || !*name) + name = " "; + + // TODO: Proper disabling of item groups + newItem(name, en, selected); + + if (currDepth < 3) + currDepth++; +} + +void FltkListResource::popGroup () +{ + CustBrowser *b = (CustBrowser *) widget; + + newItem(" ", false, false); + b->hide(b->size()); + + if (currDepth) + currDepth--; +} + +int FltkListResource::getMaxItemWidth() +{ + return ((CustBrowser *) widget)->full_width(); +} + +void FltkListResource::sizeRequest (core::Requisition *requisition) +{ + DBG_OBJ_ENTER0 ("resize", 0, "sizeRequest"); + + if (style) { + CustBrowser *b = (CustBrowser *) widget; + int height = b->full_height(); + requisition->width = getMaxItemWidth() + 4; + + if (showRows * b->avg_height() < height) { + height = showRows * b->avg_height(); + b->has_scrollbar(Fl_Browser_::VERTICAL_ALWAYS); + requisition->width += Fl::scrollbar_size(); + } else { + b->has_scrollbar(0); + } + + requisition->descent = style->font->descent + 2; + requisition->ascent = height - style->font->descent + 2; + } else { + requisition->width = 1; + requisition->ascent = 1; + requisition->descent = 0; + } + + DBG_OBJ_MSGF ("resize", 1, "result: %d * (%d + %d)", + requisition->width, requisition->ascent, requisition->descent); + DBG_OBJ_LEAVE (); +} + +int FltkListResource::getNumberOfItems() +{ + return ((Fl_Browser*)widget)->size(); +} + +bool FltkListResource::isSelected (int index) +{ + Fl_Browser *b = (Fl_Browser *) widget; + + return b->selected(index + 1) ? true : false; +} + +} // namespace ui +} // namespace fltk +} // namespace dw + diff --git a/dw/fltkui.hh b/dw/fltkui.hh new file mode 100644 index 0000000..fa47992 --- /dev/null +++ b/dw/fltkui.hh @@ -0,0 +1,505 @@ +#ifndef __DW_FLTK_UI_HH__ +#define __DW_FLTK_UI_HH__ + +#ifndef __INCLUDED_FROM_DW_FLTK_CORE_HH__ +# error Do not include this file directly, use "fltkcore.hh" instead. +#endif + +#include <FL/Fl_Button.H> +#include <FL/Fl_Menu.H> +#include <FL/Fl_Text_Buffer.H> + +namespace dw { +namespace fltk { + +/** + * \brief FLTK implementation of dw::core::ui. + * + * <div style="border: 2px solid #ff0000; margin-top: 0.5em; + * margin-bottom: 0.5em; padding: 0.5em 1em; + * background-color: #ffefe0"><b>Update:</b> The complicated design + * results from my insufficient knowledge of C++ some years ago; since + * then, I've learned how to deal with "diamond inheritance", as the + * (ideal, not actually implemented) design in the first diagram + * shows. It should be possible to implement this ideal design in a + * straightforward way, and so get rid of templates. --SG</div> + * + * The design should be like this: + * + * \dot + * digraph G { + * node [shape=record, fontname=Helvetica, fontsize=10]; + * edge [arrowhead="none", arrowtail="empty", dir="both", + * labelfontname=Helvetica, labelfontsize=10, color="#404040", + * labelfontcolor="#000080"]; + * fontname=Helvetica; fontsize=10; + * + * subgraph cluster_core { + * style="dashed"; color="#000080"; fontname=Helvetica; fontsize=10; + * label="dw::core::ui"; + * + * Resource [color="#a0a0a0", URL="\ref dw::core::ui::Resource"]; + * LabelButtonResource [color="#a0a0a0", + * URL="\ref dw::core::ui::LabelButtonResource"]; + * EntryResource [color="#a0a0a0", + * URL="\ref dw::core::ui::EntryResource"]; + * } + * + * subgraph cluster_fltk { + * style="dashed"; color="#000080"; fontname=Helvetica; fontsize=10; + * label="dw::fltk::ui"; + * + * FltkResource [color="#a0a0a0", URL="\ref dw::fltk::ui::FltkResource"]; + * FltkLabelButtonResource + * [URL="\ref dw::fltk::ui::FltkLabelButtonResource"]; + * FltkEntryResource [URL="\ref dw::fltk::ui::FltkEntryResource"]; + * } + * + * Resource -> LabelButtonResource; + * Resource -> EntryResource; + * FltkResource -> FltkLabelButtonResource; + * FltkResource -> FltkEntryResource; + * Resource -> FltkResource; + * LabelButtonResource -> FltkLabelButtonResource; + * EntryResource -> FltkEntryResource; + * } + * \enddot + * + * <center>[\ref uml-legend "legend"]</center> + * + * where dw::fltk::ui::FltkResource provides some base funtionality for all + * conctrete FLTK implementations of sub-interfaces of dw::core::ui::Resource. + * However, this is not directly possible in C++, since the base class + * dw::core::ui::Resource is ambiguous for + * dw::fltk::ui::FltkLabelButtonResource. + * + * To solve this, we have to remove the dependency between + * dw::fltk::ui::FltkResource and dw::core::ui::Resource, instead, the part + * of dw::core::ui::Resource, which is implemented in + * dw::fltk::ui::FltkResource, must be explicitly delegated from + * dw::fltk::ui::FltkLabelButtonResourceto dw::fltk::ui::FltkResource: + * + * \dot + * digraph G { + * node [shape=record, fontname=Helvetica, fontsize=10]; + * edge [arrowhead="none", arrowtail="empty", dir="both", + * labelfontname=Helvetica, labelfontsize=10, color="#404040", + * labelfontcolor="#000080"]; + * fontname=Helvetica; fontsize=10; + * + * subgraph cluster_core { + * style="dashed"; color="#000080"; fontname=Helvetica; fontsize=10; + * label="dw::core::ui"; + * + * Resource [color="#a0a0a0", URL="\ref dw::core::ui::Resource"]; + * LabelButtonResource [color="#a0a0a0", + * URL="\ref dw::core::ui::LabelButtonResource"]; + * EntryResource [color="#a0a0a0", + * URL="\ref dw::core::ui::EntryResource"]; + * } + * + * subgraph cluster_fltk { + * style="dashed"; color="#000080"; fontname=Helvetica; fontsize=10; + * label="dw::fltk::ui"; + * + * FltkResource [color="#a0a0a0", URL="\ref dw::fltk::ui::FltkResource"]; + * FltkLabelButtonResource + * [URL="\ref dw::fltk::ui::FltkLabelButtonResource"]; + * FltkEntryResource [URL="\ref dw::fltk::ui::FltkEntryResource"]; + * } + * + * Resource -> LabelButtonResource; + * Resource -> EntryResource; + * FltkResource -> FltkLabelButtonResource; + * FltkResource -> FltkEntryResource; + * LabelButtonResource -> FltkLabelButtonResource; + * EntryResource -> FltkEntryResource; + * } + * \enddot + * + * <center>[\ref uml-legend "legend"]</center> + * + * To make this a bit simpler, we use templates: + * + * \dot + * digraph G { + * node [shape=record, fontname=Helvetica, fontsize=10]; + * edge [arrowhead="none", arrowtail="empty", dir="both", + * labelfontname=Helvetica, labelfontsize=10, color="#404040", + * labelfontcolor="#000080"]; + * fontname=Helvetica; fontsize=10; + * + * subgraph cluster_core { + * style="dashed"; color="#000080"; fontname=Helvetica; fontsize=10; + * label="dw::core::ui"; + * + * Resource [color="#a0a0a0", URL="\ref dw::core::ui::Resource"]; + * LabelButtonResource [color="#a0a0a0", + * URL="\ref dw::core::ui::LabelButtonResource"]; + * EntryResource [color="#a0a0a0", + * URL="\ref dw::core::ui::EntryResource"]; + * } + * + * subgraph cluster_fltk { + * style="dashed"; color="#000080"; fontname=Helvetica; fontsize=10; + * label="dw::fltk::ui"; + * + * FltkResource [color="#a0a0a0", URL="\ref dw::fltk::ui::FltkResource"]; + * FltkSpecificResource [color="#a0a0a0", + * fillcolor="#ffffc0", style="filled" + * URL="\ref dw::fltk::ui::FltkSpecificResource"]; + * FltkSpecificResource_button [color="#a0a0a0", + * label="FltkSpecificResource \<LabelButtonResource\>"]; + * FltkSpecificResource_entry [color="#a0a0a0", + * label="FltkSpecificResource \<EntryResource\>"]; + * FltkEntryResource [URL="\ref dw::fltk::ui::FltkEntryResource"]; + * FltkLabelButtonResource + * [URL="\ref dw::fltk::ui::FltkLabelButtonResource"]; + * } + * + * Resource -> LabelButtonResource; + * Resource -> EntryResource; + * FltkResource -> FltkSpecificResource; + * FltkSpecificResource -> FltkSpecificResource_button [arrowhead="open", + * arrowtail="none", + * dir="both", + * style="dashed", + * color="#808000"]; + * FltkSpecificResource -> FltkSpecificResource_entry [arrowhead="open", + * arrowtail="none", + * dir="both", + * style="dashed", + * color="#808000"]; + * LabelButtonResource -> FltkSpecificResource_button; + * EntryResource -> FltkSpecificResource_entry; + * FltkSpecificResource_button -> FltkLabelButtonResource; + * FltkSpecificResource_entry -> FltkEntryResource; + * } + * \enddot + * + * <center>[\ref uml-legend "legend"]</center> + */ +namespace ui { + +/** + * ... + */ +class FltkResource: public lout::object::Object +{ +private: + bool enabled; + +protected: + FltkView *view; + Fl_Widget *widget; + core::Allocation allocation; + FltkPlatform *platform; + + core::style::Style *style; + + FltkResource (FltkPlatform *platform); + void init (FltkPlatform *platform); + virtual Fl_Widget *createNewWidget (core::Allocation *allocation) = 0; + + virtual void setWidgetStyle (Fl_Widget *widget, core::style::Style *style); + void setDisplayed (bool displayed); + bool displayed(); +public: + ~FltkResource (); + + virtual void attachView (FltkView *view); + virtual void detachView (FltkView *view); + + void sizeAllocate (core::Allocation *allocation); + void draw (core::View *view, core::Rectangle *area); + + void setStyle (core::style::Style *style); + + bool isEnabled (); + void setEnabled (bool enabled); +}; + + +template <class I> class FltkSpecificResource: public I, public FltkResource +{ +public: + FltkSpecificResource (FltkPlatform *platform); + ~FltkSpecificResource (); + + void sizeAllocate (core::Allocation *allocation); + void draw (core::View *view, core::Rectangle *area); + void setStyle (core::style::Style *style); + + bool isEnabled (); + void setEnabled (bool enabled); +}; + + +class FltkLabelButtonResource: + public FltkSpecificResource <dw::core::ui::LabelButtonResource> +{ +private: + const char *label; + + static void widgetCallback (Fl_Widget *widget, void *data); + +protected: + Fl_Widget *createNewWidget (core::Allocation *allocation); + +public: + FltkLabelButtonResource (FltkPlatform *platform, const char *label); + ~FltkLabelButtonResource (); + + void sizeRequest (core::Requisition *requisition); + + const char *getLabel (); + void setLabel (const char *label); +}; + +/** + * \bug Maximal length not supported yet. + * \todo Text values are not synchronized (not needed in dillo). + */ +class FltkEntryResource: + public FltkSpecificResource <dw::core::ui::EntryResource> +{ +private: + int size; + bool password; + const char *initText; + char *label; + int label_w; + bool editable; + + static void widgetCallback (Fl_Widget *widget, void *data); + void setDisplayed (bool displayed); + +protected: + Fl_Widget *createNewWidget (core::Allocation *allocation); + void setWidgetStyle (Fl_Widget *widget, core::style::Style *style); + +public: + FltkEntryResource (FltkPlatform *platform, int size, bool password, + const char *label); + ~FltkEntryResource (); + + void sizeRequest (core::Requisition *requisition); + void sizeAllocate (core::Allocation *allocation); + + const char *getText (); + void setText (const char *text); + bool isEditable (); + void setEditable (bool editable); + void setMaxLength (int maxlen); +}; + + +class FltkMultiLineTextResource: + public FltkSpecificResource <dw::core::ui::MultiLineTextResource> +{ +private: + Fl_Text_Buffer *buffer; + char *text_copy; + bool editable; + int numCols, numRows; + +protected: + Fl_Widget *createNewWidget (core::Allocation *allocation); + void setWidgetStyle (Fl_Widget *widget, core::style::Style *style); + +public: + FltkMultiLineTextResource (FltkPlatform *platform, int cols, int rows); + ~FltkMultiLineTextResource (); + + void sizeRequest (core::Requisition *requisition); + + const char *getText (); + void setText (const char *text); + bool isEditable (); + void setEditable (bool editable); +}; + + +template <class I> class FltkToggleButtonResource: + public FltkSpecificResource <I> +{ +private: + bool initActivated; + +protected: + virtual Fl_Button *createNewButton (core::Allocation *allocation) = 0; + Fl_Widget *createNewWidget (core::Allocation *allocation); + void setWidgetStyle (Fl_Widget *widget, core::style::Style *style); + +public: + FltkToggleButtonResource (FltkPlatform *platform, + bool activated); + ~FltkToggleButtonResource (); + + void sizeRequest (core::Requisition *requisition); + + bool isActivated (); + void setActivated (bool activated); +}; + + +class FltkCheckButtonResource: + public FltkToggleButtonResource <dw::core::ui::CheckButtonResource> +{ +protected: + Fl_Button *createNewButton (core::Allocation *allocation); + +public: + FltkCheckButtonResource (FltkPlatform *platform, + bool activated); + ~FltkCheckButtonResource (); +}; + + +class FltkRadioButtonResource: + public FltkToggleButtonResource <dw::core::ui::RadioButtonResource> +{ +private: + class Group + { + private: + class FltkGroupIterator: + public dw::core::ui::RadioButtonResource::GroupIterator + { + private: + lout::container::typed::Iterator <FltkRadioButtonResource> it; + + public: + inline FltkGroupIterator (lout::container::typed::List + <FltkRadioButtonResource> + *list) + { it = list->iterator (); } + + bool hasNext (); + dw::core::ui::RadioButtonResource *getNext (); + void unref (); + }; + + lout::container::typed::List <FltkRadioButtonResource> *list; + + protected: + ~Group (); + + public: + Group (FltkRadioButtonResource *radioButtonResource); + + inline lout::container::typed::Iterator <FltkRadioButtonResource> + iterator () + { + return list->iterator (); + } + + inline dw::core::ui::RadioButtonResource::GroupIterator + *groupIterator () + { + return new FltkGroupIterator (list); + } + + void connect (FltkRadioButtonResource *radioButtonResource); + void unconnect (FltkRadioButtonResource *radioButtonResource); + }; + + Group *group; + + static void widgetCallback (Fl_Widget *widget, void *data); + void buttonClicked (); + +protected: + Fl_Button *createNewButton (core::Allocation *allocation); + +public: + FltkRadioButtonResource (FltkPlatform *platform, + FltkRadioButtonResource *groupedWith, + bool activated); + ~FltkRadioButtonResource (); + + GroupIterator *groupIterator (); +}; + + +template <class I> class FltkSelectionResource: + public FltkSpecificResource <I> +{ +protected: + virtual bool setSelectedItems() { return false; } + virtual void addItem (const char *str, bool enabled, bool selected) = 0; + virtual void setItem (int index, bool selected) = 0; + virtual void pushGroup (const char *name, bool enabled) = 0; + virtual void popGroup () = 0; +public: + FltkSelectionResource (FltkPlatform *platform) : + FltkSpecificResource<I> (platform) {}; + dw::core::Iterator *iterator (dw::core::Content::Type mask, bool atEnd); +}; + + +class FltkOptionMenuResource: + public FltkSelectionResource <dw::core::ui::OptionMenuResource> +{ +protected: + Fl_Widget *createNewWidget (core::Allocation *allocation); + virtual bool setSelectedItems() { return true; } + void setWidgetStyle (Fl_Widget *widget, core::style::Style *style); + int getNumberOfItems(); + int getMaxItemWidth (); +private: + static void widgetCallback (Fl_Widget *widget, void *data); + void enlargeMenu(); + Fl_Menu_Item *newItem(); + Fl_Menu_Item *menu; + int itemsAllocated, itemsUsed; +public: + FltkOptionMenuResource (FltkPlatform *platform); + ~FltkOptionMenuResource (); + + void addItem (const char *str, bool enabled, bool selected); + void setItem (int index, bool selected); + void pushGroup (const char *name, bool enabled); + void popGroup (); + + void sizeRequest (core::Requisition *requisition); + bool isSelected (int index); +}; + +class FltkListResource: + public FltkSelectionResource <dw::core::ui::ListResource> +{ +protected: + Fl_Widget *createNewWidget (core::Allocation *allocation); + void setWidgetStyle (Fl_Widget *widget, core::style::Style *style); + int getNumberOfItems(); + int getMaxItemWidth (); +private: + static void widgetCallback (Fl_Widget *widget, void *data); + void *newItem (const char *str, bool enabled, bool selected); + int currDepth; + int colWidths[4]; + int showRows; + ListResource::SelectionMode mode; +public: + FltkListResource (FltkPlatform *platform, + core::ui::ListResource::SelectionMode selectionMode, + int rows); + ~FltkListResource (); + + void addItem (const char *str, bool enabled, bool selected); + void setItem (int index, bool selected); + void pushGroup (const char *name, bool enabled); + void popGroup (); + + void sizeRequest (core::Requisition *requisition); + bool isSelected (int index); +}; + + +} // namespace ui +} // namespace fltk +} // namespace dw + + +#endif // __DW_FLTK_UI_HH__ diff --git a/dw/fltkviewbase.cc b/dw/fltkviewbase.cc new file mode 100644 index 0000000..3b1c0cc --- /dev/null +++ b/dw/fltkviewbase.cc @@ -0,0 +1,744 @@ +/* + * RTFL (originally part of dillo) + * + * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version; with the following exception: + * + * The copyright holders of RTFL give you permission to link this file + * statically or dynamically against all versions of the graphviz + * library, which are published by AT&T Corp. under one of the following + * licenses: + * + * - Common Public License version 1.0 as published by International + * Business Machines Corporation (IBM), or + * - Eclipse Public License version 1.0 as published by the Eclipse + * Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + + +#include "fltkviewport.hh" + +#include <FL/Fl.H> +#include <FL/fl_draw.H> + +#include <stdio.h> +#include "../lout/msg.h" + +extern Fl_Widget* fl_oldfocus; + +using namespace lout::object; +using namespace lout::container::typed; + +namespace dw { +namespace fltk { + +FltkViewBase::BackBuffer::BackBuffer () +{ + w = 0; + h = 0; + created = false; +} + +FltkViewBase::BackBuffer::~BackBuffer () +{ + if (created) + fl_delete_offscreen (offscreen); +} + +void FltkViewBase::BackBuffer::setSize (int w, int h) +{ + if (!created || w > this->w || h > this->h) { + this->w = w; + this->h = h; + if (created) + fl_delete_offscreen (offscreen); + offscreen = fl_create_offscreen (w, h); + created = true; + } +} + +FltkViewBase::BackBuffer *FltkViewBase::backBuffer; +bool FltkViewBase::backBufferInUse; + +FltkViewBase::FltkViewBase (int x, int y, int w, int h, const char *label): + Fl_Group (x, y, w, h, label) +{ + Fl_Group::current(0); + canvasWidth = 1; + canvasHeight = 1; + bgColor = FL_WHITE; + mouse_x = mouse_y = 0; + focused_child = NULL; + exposeArea = NULL; + if (backBuffer == NULL) { + backBuffer = new BackBuffer (); + } + box(FL_NO_BOX); + resizable(NULL); +} + +FltkViewBase::~FltkViewBase () +{ + cancelQueueDraw (); +} + +void FltkViewBase::setBufferedDrawing (bool b) { + if (b && backBuffer == NULL) { + backBuffer = new BackBuffer (); + } else if (!b && backBuffer != NULL) { + delete backBuffer; + backBuffer = NULL; + } +} + +void FltkViewBase::draw () +{ + int d = damage (); + + if ((d & FL_DAMAGE_USER1) && !(d & FL_DAMAGE_EXPOSE)) { + lout::container::typed::Iterator <core::Rectangle> it; + + for (it = drawRegion.rectangles (); it.hasNext (); ) { + draw (it.getNext (), DRAW_BUFFERED); + } + + drawRegion.clear (); + d &= ~FL_DAMAGE_USER1; + } + + if (d & FL_DAMAGE_CHILD) { + drawChildWidgets (); + d &= ~FL_DAMAGE_CHILD; + } + + if (d) { + dw::core::Rectangle rect ( + translateViewXToCanvasX (x ()), + translateViewYToCanvasY (y ()), + w (), + h ()); + + if (d == FL_DAMAGE_SCROLL) { + // a clipping rectangle has already been set by fltk::scrollrect () + draw (&rect, DRAW_PLAIN); + } else { + draw (&rect, DRAW_CLIPPED); + drawRegion.clear (); + } + } +} + +void FltkViewBase::draw (const core::Rectangle *rect, + DrawType type) +{ + int X = translateCanvasXToViewX (rect->x); + int Y = translateCanvasYToViewY (rect->y); + int W, H; + + // fl_clip_box() can't handle values greater than SHRT_MAX! + if (X > x () + w () || Y > y () + h ()) + return; + + W = X + rect->width > x () + w () ? x () + w () - X : rect->width; + H = Y + rect->height > y () + h () ? y () + h () - Y : rect->height; + + fl_clip_box(X, Y, W, H, X, Y, W, H); + + core::Rectangle r (translateViewXToCanvasX (X), + translateViewYToCanvasY (Y), W, H); + + if (r.isEmpty ()) + return; + + exposeArea = &r; + + if (type == DRAW_BUFFERED && backBuffer && !backBufferInUse) { + backBufferInUse = true; + backBuffer->setSize (X + W, Y + H); // would be nicer to use (W, H)... + fl_begin_offscreen (backBuffer->offscreen); + fl_push_matrix (); + fl_color (bgColor); + fl_rectf (X, Y, W, H); + theLayout->expose (this, &r); + fl_pop_matrix (); + fl_end_offscreen (); + fl_copy_offscreen (X, Y, W, H, backBuffer->offscreen, X, Y); + backBufferInUse = false; + } else if (type == DRAW_BUFFERED || type == DRAW_CLIPPED) { + // if type == DRAW_BUFFERED but we do not have backBuffer available + // we fall back to clipped drawing + fl_push_clip (X, Y, W, H); + fl_color (bgColor); + fl_rectf (X, Y, W, H); + theLayout->expose (this, &r); + fl_pop_clip (); + } else { + fl_color (bgColor); + fl_rectf (X, Y, W, H); + theLayout->expose (this, &r); + } + // DEBUG: + //fl_color(FL_RED); + //fl_rect(X, Y, W, H); + + exposeArea = NULL; +} + +void FltkViewBase::drawChildWidgets () { + for (int i = children () - 1; i >= 0; i--) { + Fl_Widget& w = *child(i); +#if 0 +PORT1.3 + if (w.damage() & DAMAGE_CHILD_LABEL) { + draw_outside_label(w); + w.set_damage(w.damage() & ~DAMAGE_CHILD_LABEL); + } +#endif + update_child(w); + } +} + +core::ButtonState getDwButtonState () +{ + int s1 = Fl::event_state (); + int s2 = (core::ButtonState)0; + + if (s1 & FL_SHIFT) s2 |= core::SHIFT_MASK; + if (s1 & FL_CTRL) s2 |= core::CONTROL_MASK; + if (s1 & FL_ALT) s2 |= core::META_MASK; + if (s1 & FL_BUTTON1) s2 |= core::BUTTON1_MASK; + if (s1 & FL_BUTTON2) s2 |= core::BUTTON2_MASK; + if (s1 & FL_BUTTON3) s2 |= core::BUTTON3_MASK; + + return (core::ButtonState)s2; +} + +/* + * We handle Tab to determine which FLTK widget should get focus. + * + * Presumably a proper solution that allows focusing links, etc., would live + * in Textblock and use iterators. + */ +int FltkViewBase::manageTabToFocus() +{ + int i, ret = 0; + Fl_Widget *old_child = NULL; + + if (this == Fl::focus()) { + // if we have focus, give it to a child. Go forward typically, + // or backward with Shift pressed. + if (!(Fl::event_state() & FL_SHIFT)) { + for (i = 0; i < children(); i++) { + if (child(i)->take_focus()) { + ret = 1; + break; + } + } + } else { + for (i = children() - 1; i >= 0; i--) { + if (child(i)->take_focus()) { + ret = 1; + break; + } + } + } + } else { + // tabbing between children + old_child = Fl::focus(); + + if (!(ret = Fl_Group::handle (FL_KEYBOARD))) { + // group didn't have any more children to focus. + Fl::focus(this); + return 1; + } else { + // which one did it focus? (Note i == children() if not found) + i = find(Fl::focus()); + } + } + if (ret) { + if (i >= 0 && i < children()) { + Fl_Widget *c = child(i); + int canvasX = translateViewXToCanvasX(c->x()), + canvasY = translateViewYToCanvasY(c->y()); + + theLayout->scrollTo(core::HPOS_INTO_VIEW, core::VPOS_INTO_VIEW, + canvasX, canvasY, c->w(), c->h()); + + // Draw the children who gained and lost focus. Otherwise a + // widget that had been only partly visible still shows its old + // appearance in the previously-visible portion. + core::Rectangle r(canvasX, canvasY, c->w(), c->h()); + + queueDraw(&r); + + if (old_child) { + r.x = translateViewXToCanvasX(old_child->x()); + r.y = translateViewYToCanvasY(old_child->y()); + r.width = old_child->w(); + r.height = old_child->h(); + queueDraw(&r); + } + } + } + return ret; +} + +int FltkViewBase::handle (int event) +{ + bool processed; + + /** + * \todo Consider, whether this from the FLTK documentation has any + * impacts: "To receive fltk::RELEASE events you must return non-zero + * when passed a fltk::PUSH event. " + */ + switch(event) { + case FL_PUSH: + /* Hide the tooltip */ + theLayout->cancelTooltip(); + + processed = + theLayout->buttonPress (this, Fl::event_clicks () + 1, + translateViewXToCanvasX (Fl::event_x ()), + translateViewYToCanvasY (Fl::event_y ()), + getDwButtonState (), Fl::event_button ()); + _MSG("PUSH => %s\n", processed ? "true" : "false"); + if (processed) { + /* pressed dw content; give focus to the view */ + if (Fl::event_button() != FL_RIGHT_MOUSE) + Fl::focus(this); + return true; + } + break; + case FL_RELEASE: + processed = + theLayout->buttonRelease (this, Fl::event_clicks () + 1, + translateViewXToCanvasX (Fl::event_x ()), + translateViewYToCanvasY (Fl::event_y ()), + getDwButtonState (), Fl::event_button ()); + _MSG("RELEASE => %s\n", processed ? "true" : "false"); + if (processed) + return true; + break; + case FL_MOVE: + mouse_x = Fl::event_x(); + mouse_y = Fl::event_y(); + processed = + theLayout->motionNotify (this, + translateViewXToCanvasX (mouse_x), + translateViewYToCanvasY (mouse_y), + getDwButtonState ()); + _MSG("MOVE => %s\n", processed ? "true" : "false"); + if (processed) + return true; + break; + case FL_DRAG: + processed = + theLayout->motionNotify (this, + translateViewXToCanvasX (Fl::event_x ()), + translateViewYToCanvasY (Fl::event_y ()), + getDwButtonState ()); + _MSG("DRAG => %s\n", processed ? "true" : "false"); + if (processed) + return true; + break; + case FL_ENTER: + theLayout->enterNotify (this, + translateViewXToCanvasX (Fl::event_x ()), + translateViewYToCanvasY (Fl::event_y ()), + getDwButtonState ()); + break; + case FL_HIDE: + /* WORKAROUND: strangely, the tooltip window is not automatically hidden + * with its parent. Here we fake a LEAVE to achieve it. */ + case FL_LEAVE: + theLayout->leaveNotify (this, getDwButtonState ()); + break; + case FL_FOCUS: + if (focused_child && find(focused_child) < children()) { + /* strangely, find() == children() if the child is not found */ + focused_child->take_focus(); + } + return 1; + case FL_UNFOCUS: + focused_child = fl_oldfocus; + return 0; + case FL_KEYBOARD: + if (Fl::event_key() == FL_Tab) + return manageTabToFocus(); + break; + default: + break; + } + return Fl_Group::handle (event); +} + +// ---------------------------------------------------------------------- + +void FltkViewBase::setLayout (core::Layout *layout) +{ + theLayout = layout; + if (usesViewport()) + theLayout->viewportSizeChanged(this, w(), h()); +} + +void FltkViewBase::setCanvasSize (int width, int ascent, int descent) +{ + canvasWidth = width; + canvasHeight = ascent + descent; +} + +void FltkViewBase::setCursor (core::style::Cursor cursor) +{ + static Fl_Cursor mapDwToFltk[] = { + FL_CURSOR_CROSS, + FL_CURSOR_DEFAULT, + FL_CURSOR_HAND, + FL_CURSOR_MOVE, + FL_CURSOR_WE, + FL_CURSOR_NESW, + FL_CURSOR_NWSE, + FL_CURSOR_NS, + FL_CURSOR_NWSE, + FL_CURSOR_NESW, + FL_CURSOR_NS, + FL_CURSOR_WE, + FL_CURSOR_INSERT, + FL_CURSOR_WAIT, + FL_CURSOR_HELP + }; + + fl_cursor (mapDwToFltk[cursor]); +} + +void FltkViewBase::setBgColor (core::style::Color *color) +{ + bgColor = color ? + ((FltkColor*)color)->colors[dw::core::style::Color::SHADING_NORMAL] : + FL_WHITE; +} + +void FltkViewBase::startDrawing (core::Rectangle *area) +{ +} + +void FltkViewBase::finishDrawing (core::Rectangle *area) +{ +} + +void FltkViewBase::queueDraw (core::Rectangle *area) +{ + drawRegion.addRectangle (area); + damage (FL_DAMAGE_USER1); +} + +void FltkViewBase::queueDrawTotal () +{ + damage (FL_DAMAGE_EXPOSE); +} + +void FltkViewBase::cancelQueueDraw () +{ +} + +void FltkViewBase::drawPoint (core::style::Color *color, + core::style::Color::Shading shading, + int x, int y) +{ +} + +void FltkViewBase::drawLine (core::style::Color *color, + core::style::Color::Shading shading, + int x1, int y1, int x2, int y2) +{ + fl_color(((FltkColor*)color)->colors[shading]); + // we clip with a large border (5000px), as clipping causes artefacts + // with non-solid line styles. + // However it's still better than no clipping at all. + clipPoint (&x1, &y1, 5000); + clipPoint (&x2, &y2, 5000); + fl_line (translateCanvasXToViewX (x1), + translateCanvasYToViewY (y1), + translateCanvasXToViewX (x2), + translateCanvasYToViewY (y2)); +} + +void FltkViewBase::drawTypedLine (core::style::Color *color, + core::style::Color::Shading shading, + core::style::LineType type, int width, + int x1, int y1, int x2, int y2) +{ + char dashes[3], w, ng, d, gap, len; + const int f = 2; + + w = (width == 1) ? 0 : width; + if (type == core::style::LINE_DOTTED) { + /* customized drawing for dotted lines */ + len = (x2 == x1) ? y2 - y1 + 1 : (y2 == y1) ? x2 - x1 + 1 : 0; + ng = len / f*width; + d = len % f*width; + gap = ng ? d/ng + (w > 3 ? 2 : 0) : 0; + dashes[0] = 1; dashes[1] = f*width-gap; dashes[2] = 0; + fl_line_style(FL_DASH + FL_CAP_ROUND, w, dashes); + + /* These formulas also work, but ain't pretty ;) + * fl_line_style(FL_DOT + FL_CAP_ROUND, w); + * dashes[0] = 1; dashes[1] = 3*width-2; dashes[2] = 0; + */ + } else if (type == core::style::LINE_DASHED) { + fl_line_style(FL_DASH + FL_CAP_ROUND, w); + } + + fl_color(((FltkColor*)color)->colors[shading]); + drawLine (color, shading, x1, y1, x2, y2); + + if (type != core::style::LINE_NORMAL) + fl_line_style(FL_SOLID); +} + +void FltkViewBase::drawRectangle (core::style::Color *color, + core::style::Color::Shading shading, + bool filled, + int X, int Y, int width, int height) +{ + fl_color(((FltkColor*)color)->colors[shading]); + if (width < 0) { + X += width; + width = -width; + } + if (height < 0) { + Y += height; + height = -height; + } + + int x1 = X; + int y1 = Y; + int x2 = X + width; + int y2 = Y + height; + + // We only support rectangles with line width 1px, so we clip with + // a rectangle 1px wider and higher than what we actually expose. + // This is only really necessary for non-filled rectangles. + clipPoint (&x1, &y1, 1); + clipPoint (&x2, &y2, 1); + + x1 = translateCanvasXToViewX (x1); + y1 = translateCanvasYToViewY (y1); + x2 = translateCanvasXToViewX (x2); + y2 = translateCanvasYToViewY (y2); + + if (filled) + fl_rectf (x1, y1, x2 - x1, y2 - y1); + else + fl_rect (x1, y1, x2 - x1, y2 - y1); +} + +void FltkViewBase::drawArc (core::style::Color *color, + core::style::Color::Shading shading, bool filled, + int centerX, int centerY, int width, int height, + int angle1, int angle2) +{ + fl_color(((FltkColor*)color)->colors[shading]); + int x = translateCanvasXToViewX (centerX) - width / 2; + int y = translateCanvasYToViewY (centerY) - height / 2; + + fl_arc(x, y, width, height, angle1, angle2); + if (filled) { + // WORKAROUND: We call both fl_arc and fl_pie due to a FLTK bug + // (STR #2703) that was present in 1.3.0. + fl_pie(x, y, width, height, angle1, angle2); + } +} + +void FltkViewBase::drawPolygon (core::style::Color *color, + core::style::Color::Shading shading, + bool filled, bool convex, core::Point *points, + int npoints) +{ + if (npoints > 0) { + fl_color(((FltkColor*)color)->colors[shading]); + + if (filled) { + if (convex) + fl_begin_polygon(); + else + fl_begin_complex_polygon(); + } else + fl_begin_loop(); + + for (int i = 0; i < npoints; i++) { + fl_vertex(translateCanvasXToViewX(points[i].x), + translateCanvasYToViewY(points[i].y)); + } + if (filled) { + if (convex) + fl_end_polygon(); + else + fl_end_complex_polygon(); + } else + fl_end_loop(); + } +} + +core::View *FltkViewBase::getClippingView (int x, int y, int width, int height) +{ + fl_push_clip (translateCanvasXToViewX (x), translateCanvasYToViewY (y), + width, height); + return this; +} + +void FltkViewBase::mergeClippingView (core::View *clippingView) +{ + fl_pop_clip (); +} + +// ---------------------------------------------------------------------- + +FltkWidgetView::FltkWidgetView (int x, int y, int w, int h, + const char *label): + FltkViewBase (x, y, w, h, label) +{ +} + +FltkWidgetView::~FltkWidgetView () +{ +} + +void FltkWidgetView::drawText (core::style::Font *font, + core::style::Color *color, + core::style::Color::Shading shading, + int X, int Y, const char *text, int len) +{ + //printf ("drawText (..., %d, %d, '", X, Y); + //for (int i = 0; i < len; i++) + // putchar (text[i]); + //printf ("'\n"); + + FltkFont *ff = (FltkFont*)font; + fl_font(ff->font, ff->size); + fl_color(((FltkColor*)color)->colors[shading]); + + if (!font->letterSpacing && !font->fontVariant) { + fl_draw(text, len, + translateCanvasXToViewX (X), translateCanvasYToViewY (Y)); + } else { + /* Nonzero letter spacing adjustment, draw each glyph individually */ + int viewX = translateCanvasXToViewX (X), + viewY = translateCanvasYToViewY (Y); + int curr = 0, next = 0, nb; + char chbuf[4]; + int c, cu, width; + + if (font->fontVariant == core::style::FONT_VARIANT_SMALL_CAPS) { + int sc_fontsize = lout::misc::roundInt(ff->size * 0.78); + for (curr = 0; next < len; curr = next) { + next = theLayout->nextGlyph(text, curr); + c = fl_utf8decode(text + curr, text + next, &nb); + if ((cu = fl_toupper(c)) == c) { + /* already uppercase, just draw the character */ + fl_font(ff->font, ff->size); + width = (int)fl_width(text + curr, next - curr); + if (curr && width) + viewX += font->letterSpacing; + fl_draw(text + curr, next - curr, viewX, viewY); + viewX += width; + } else { + /* make utf8 string for converted char */ + nb = fl_utf8encode(cu, chbuf); + fl_font(ff->font, sc_fontsize); + width = (int)fl_width(chbuf, nb); + if (curr && width) + viewX += font->letterSpacing; + fl_draw(chbuf, nb, viewX, viewY); + viewX += width; + } + } + } else { + while (next < len) { + next = theLayout->nextGlyph(text, curr); + width = (int)fl_width(text + curr, next - curr); + if (curr && width) + viewX += font->letterSpacing; + fl_draw(text + curr, next - curr, viewX, viewY); + viewX += width; + curr = next; + } + } + } +} + +/* + * "simple" in that it ignores letter-spacing, etc. This was added for image + * alt text where none of that matters. + */ +void FltkWidgetView::drawSimpleWrappedText (core::style::Font *font, + core::style::Color *color, + core::style::Color::Shading shading, + int X, int Y, int W, int H, + const char *text) +{ + FltkFont *ff = (FltkFont*)font; + fl_font(ff->font, ff->size); + fl_color(((FltkColor*)color)->colors[shading]); + fl_draw(text, + translateCanvasXToViewX (X), translateCanvasYToViewY (Y), + W, H, FL_ALIGN_TOP|FL_ALIGN_LEFT|FL_ALIGN_WRAP, NULL, 0); +} + +void FltkWidgetView::drawImage (core::Imgbuf *imgbuf, int xRoot, int yRoot, + int X, int Y, int width, int height) +{ + ((FltkImgbuf*)imgbuf)->draw (this, + translateCanvasXToViewX (xRoot), + translateCanvasYToViewY (yRoot), + X, Y, width, height); +} + +bool FltkWidgetView::usesFltkWidgets () +{ + return true; +} + +void FltkWidgetView::addFltkWidget (Fl_Widget *widget, + core::Allocation *allocation) +{ + allocateFltkWidget (widget, allocation); + add (widget); +} + +void FltkWidgetView::removeFltkWidget (Fl_Widget *widget) +{ + remove (widget); +} + +void FltkWidgetView::allocateFltkWidget (Fl_Widget *widget, + core::Allocation *allocation) +{ + widget->resize (translateCanvasXToViewX (allocation->x), + translateCanvasYToViewY (allocation->y), + allocation->width, + allocation->ascent + allocation->descent); +} + +void FltkWidgetView::drawFltkWidget (Fl_Widget *widget, + core::Rectangle *area) +{ + draw_child (*widget); + draw_outside_label(*widget); +} + +} // namespace fltk +} // namespace dw diff --git a/dw/fltkviewbase.hh b/dw/fltkviewbase.hh new file mode 100644 index 0000000..eb4ec32 --- /dev/null +++ b/dw/fltkviewbase.hh @@ -0,0 +1,141 @@ +#ifndef __DW_FLTKVIEWBASE_HH__ +#define __DW_FLTKVIEWBASE_HH__ + +#include <time.h> // for time_t +#include <sys/time.h> // for time_t in FreeBSD + +#include <FL/Fl_Group.H> +#include <FL/x.H> + +#include "fltkcore.hh" + +namespace dw { +namespace fltk { + +class FltkViewBase: public FltkView, public Fl_Group +{ +private: + class BackBuffer { + private: + int w; + int h; + bool created; + + public: + Fl_Offscreen offscreen; + + BackBuffer (); + ~BackBuffer (); + void setSize(int w, int h); + }; + + typedef enum { DRAW_PLAIN, DRAW_CLIPPED, DRAW_BUFFERED } DrawType; + + int bgColor; + core::Region drawRegion; + core::Rectangle *exposeArea; + static BackBuffer *backBuffer; + static bool backBufferInUse; + + void draw (const core::Rectangle *rect, DrawType type); + void drawChildWidgets (); + int manageTabToFocus(); + inline void clipPoint (int *x, int *y, int border) { + if (exposeArea) { + if (*x < exposeArea->x - border) + *x = exposeArea->x - border; + if (*x > exposeArea->x + exposeArea->width + border) + *x = exposeArea->x + exposeArea->width + border; + if (*y < exposeArea->y - border) + *y = exposeArea->y - border; + if (*y > exposeArea->y + exposeArea->height + border) + *y = exposeArea->y + exposeArea->height + border; + } + } +protected: + core::Layout *theLayout; + int canvasWidth, canvasHeight; + int mouse_x, mouse_y; + Fl_Widget *focused_child; + + virtual int translateViewXToCanvasX (int x) = 0; + virtual int translateViewYToCanvasY (int y) = 0; + virtual int translateCanvasXToViewX (int x) = 0; + virtual int translateCanvasYToViewY (int y) = 0; + +public: + FltkViewBase (int x, int y, int w, int h, const char *label = 0); + ~FltkViewBase (); + + void draw(); + int handle (int event); + + void setLayout (core::Layout *layout); + void setCanvasSize (int width, int ascent, int descent); + void setCursor (core::style::Cursor cursor); + void setBgColor (core::style::Color *color); + + void startDrawing (core::Rectangle *area); + void finishDrawing (core::Rectangle *area); + void queueDraw (core::Rectangle *area); + void queueDrawTotal (); + void cancelQueueDraw (); + void drawPoint (core::style::Color *color, + core::style::Color::Shading shading, + int x, int y); + void drawLine (core::style::Color *color, + core::style::Color::Shading shading, + int x1, int y1, int x2, int y2); + void drawTypedLine (core::style::Color *color, + core::style::Color::Shading shading, + core::style::LineType type, int width, + int x1, int y1, int x2, int y2); + void drawRectangle (core::style::Color *color, + core::style::Color::Shading shading, bool filled, + int x, int y, int width, int height); + void drawArc (core::style::Color *color, + core::style::Color::Shading shading, bool filled, + int centerX, int centerY, int width, int height, + int angle1, int angle2); + void drawPolygon (core::style::Color *color, + core::style::Color::Shading shading, + bool filled, bool convex, + core::Point *points, int npoints); + + core::View *getClippingView (int x, int y, int width, int height); + void mergeClippingView (core::View *clippingView); + void setBufferedDrawing (bool b); +}; + + +class FltkWidgetView: public FltkViewBase +{ +public: + FltkWidgetView (int x, int y, int w, int h, const char *label = 0); + ~FltkWidgetView (); + + void drawText (core::style::Font *font, + core::style::Color *color, + core::style::Color::Shading shading, + int x, int y, const char *text, int len); + void drawSimpleWrappedText (core::style::Font *font, + core::style::Color *color, + core::style::Color::Shading shading, + int x, int y, int w, int h, + const char *text); + void drawImage (core::Imgbuf *imgbuf, int xRoot, int yRoot, + int x, int y, int width, int height); + + bool usesFltkWidgets (); + void addFltkWidget (Fl_Widget *widget, core::Allocation *allocation); + void removeFltkWidget (Fl_Widget *widget); + void allocateFltkWidget (Fl_Widget *widget, + core::Allocation *allocation); + void drawFltkWidget (Fl_Widget *widget, core::Rectangle *area); +}; + +} // namespace fltk +} // namespace dw + +#endif // __DW_FLTKVIEWBASE_HH__ + diff --git a/dw/fltkviewport.cc b/dw/fltkviewport.cc new file mode 100644 index 0000000..804b62f --- /dev/null +++ b/dw/fltkviewport.cc @@ -0,0 +1,558 @@ +/* + * RTFL (originally part of dillo) + * + * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version; with the following exception: + * + * The copyright holders of RTFL give you permission to link this file + * statically or dynamically against all versions of the graphviz + * library, which are published by AT&T Corp. under one of the following + * licenses: + * + * - Common Public License version 1.0 as published by International + * Business Machines Corporation (IBM), or + * - Eclipse Public License version 1.0 as published by the Eclipse + * Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + + +#include "fltkviewport.hh" + +#include <FL/Fl.H> +#include <FL/fl_draw.H> +#include <FL/names.h> + +#include <stdio.h> +#include "../lout/msg.h" +#include "../lout/debug.hh" + +using namespace lout; +using namespace lout::object; +using namespace lout::container::typed; + +namespace dw { +namespace fltk { + +/* + * Lets SHIFT+{Left,Right} go to the parent + */ +class CustScrollbar : public Fl_Scrollbar +{ +public: + CustScrollbar(int x, int y, int w, int h) : Fl_Scrollbar(x,y,w,h) {}; + int handle(int e) { + if (e == FL_SHORTCUT && Fl::event_state() == FL_SHIFT && + (Fl::event_key() == FL_Left || Fl::event_key() == FL_Right)) + return 0; + return Fl_Scrollbar::handle(e); + } +}; + +FltkViewport::FltkViewport (int X, int Y, int W, int H, const char *label): + FltkWidgetView (X, Y, W, H, label) +{ + DBG_OBJ_CREATE ("dw::fltk::FltkViewport"); + + hscrollbar = new CustScrollbar (x (), y (), 1, 1); + hscrollbar->type(FL_HORIZONTAL); + hscrollbar->callback (hscrollbarCallback, this); + hscrollbar->hide(); + add (hscrollbar); + + vscrollbar = new Fl_Scrollbar (x (), y(), 1, 1); + vscrollbar->type(FL_VERTICAL); + vscrollbar->callback (vscrollbarCallback, this); + vscrollbar->hide(); + add (vscrollbar); + + hasDragScroll = 1; + scrollX = scrollY = scrollDX = scrollDY = 0; + horScrolling = verScrolling = dragScrolling = 0; + + gadgetOrientation[0] = GADGET_HORIZONTAL; + gadgetOrientation[1] = GADGET_HORIZONTAL; + gadgetOrientation[2] = GADGET_VERTICAL; + gadgetOrientation[3] = GADGET_HORIZONTAL; + + gadgets = + new container::typed::List <object::TypedPointer < Fl_Widget> > + (true); +} + +FltkViewport::~FltkViewport () +{ + delete gadgets; + DBG_OBJ_DELETE (); +} + +void FltkViewport::adjustScrollbarsAndGadgetsAllocation () +{ + int hdiff = 0, vdiff = 0; + int visibility = 0; + + _MSG(" >>FltkViewport::adjustScrollbarsAndGadgetsAllocation\n"); + if (hscrollbar->visible ()) + visibility |= 1; + if (vscrollbar->visible ()) + visibility |= 2; + + if (gadgets->size () > 0) { + switch (gadgetOrientation [visibility]) { + case GADGET_VERTICAL: + hdiff = SCROLLBAR_THICKNESS; + vdiff = SCROLLBAR_THICKNESS * gadgets->size (); + break; + + case GADGET_HORIZONTAL: + hdiff = SCROLLBAR_THICKNESS * gadgets->size (); + vdiff = SCROLLBAR_THICKNESS; + break; + } + } else { + hdiff = vscrollbar->visible () ? SCROLLBAR_THICKNESS : 0; + vdiff = hscrollbar->visible () ? SCROLLBAR_THICKNESS : 0; + } + + hscrollbar->resize(x (), y () + h () - SCROLLBAR_THICKNESS, + w () - hdiff, SCROLLBAR_THICKNESS); + vscrollbar->resize(x () + w () - SCROLLBAR_THICKNESS, y (), + SCROLLBAR_THICKNESS, h () - vdiff); + + int X = x () + w () - SCROLLBAR_THICKNESS; + int Y = y () + h () - SCROLLBAR_THICKNESS; + for (Iterator <TypedPointer < Fl_Widget> > it = gadgets->iterator (); + it.hasNext (); ) { + Fl_Widget *widget = it.getNext()->getTypedValue (); + widget->resize(x (), y (), SCROLLBAR_THICKNESS, SCROLLBAR_THICKNESS); + + switch (gadgetOrientation [visibility]) { + case GADGET_VERTICAL: + Y -= SCROLLBAR_THICKNESS; + break; + + case GADGET_HORIZONTAL: + X -= SCROLLBAR_THICKNESS; + break; + } + } +} + +void FltkViewport::adjustScrollbarValues () +{ + hscrollbar->value (scrollX, hscrollbar->w (), 0, canvasWidth); + vscrollbar->value (scrollY, vscrollbar->h (), 0, canvasHeight); +} + +void FltkViewport::hscrollbarChanged () +{ + scroll (hscrollbar->value () - scrollX, 0); +} + +void FltkViewport::vscrollbarChanged () +{ + scroll (0, vscrollbar->value () - scrollY); +} + +void FltkViewport::vscrollbarCallback (Fl_Widget *vscrollbar,void *viewportPtr) +{ + ((FltkViewport*)viewportPtr)->vscrollbarChanged (); +} + +void FltkViewport::hscrollbarCallback (Fl_Widget *hscrollbar,void *viewportPtr) +{ + ((FltkViewport*)viewportPtr)->hscrollbarChanged (); +} + +// ---------------------------------------------------------------------- + +void FltkViewport::resize(int X, int Y, int W, int H) +{ + bool dimension_changed = W != w() || H != h(); + + Fl_Group::resize(X, Y, W, H); + if (dimension_changed) { + theLayout->viewportSizeChanged (this, W, H); + adjustScrollbarsAndGadgetsAllocation (); + } +} + +void FltkViewport::draw_area (void *data, int x, int y, int w, int h) +{ + FltkViewport *vp = (FltkViewport*) data; + fl_push_clip(x, y, w, h); + + vp->FltkWidgetView::draw (); + + for (Iterator <TypedPointer < Fl_Widget> > it = vp->gadgets->iterator(); + it.hasNext (); ) { + Fl_Widget *widget = it.getNext()->getTypedValue (); + vp->draw_child (*widget); + } + + fl_pop_clip(); + +} + +void FltkViewport::draw () +{ + int hdiff = vscrollbar->visible () ? SCROLLBAR_THICKNESS : 0; + int vdiff = hscrollbar->visible () ? SCROLLBAR_THICKNESS : 0; + int d = damage(); + + if (d & FL_DAMAGE_SCROLL) { + clear_damage (FL_DAMAGE_SCROLL); + fl_scroll(x(), y(), w() - hdiff, h() - vdiff, + -scrollDX, -scrollDY, draw_area, this); + clear_damage (d & ~FL_DAMAGE_SCROLL); + } + + if (d) { + draw_area(this, x(), y(), w () - hdiff, h () - vdiff); + + if (d == FL_DAMAGE_ALL || hscrollbar->damage ()) + draw_child (*hscrollbar); + if (d == FL_DAMAGE_ALL || vscrollbar->damage ()) + draw_child (*vscrollbar); + + if (d == FL_DAMAGE_ALL && hdiff && vdiff) { + fl_color(FL_BACKGROUND_COLOR); + fl_rectf(x()+w()-hdiff, y()+h()-vdiff, hdiff, vdiff); + } + } + + scrollDX = 0; + scrollDY = 0; +} + +int FltkViewport::handle (int event) +{ + _MSG("FltkViewport::handle %s\n", fl_eventnames[event]); + + switch(event) { + case FL_KEYBOARD: + /* When the viewport has focus (and not one of its children), FLTK + * sends the event here. Returning zero tells FLTK to resend the + * event as SHORTCUT, which we finally route to the parent. */ + + /* As we don't know the exact keybindings set by the user, we ask for + * all of them (except for the minimum needed to keep form navigation).*/ + if (Fl::event_key() != FL_Tab || Fl::event_ctrl()) + return 0; + break; + + case FL_SHORTCUT: + /* send it to the parent (UI) */ + return 0; + + case FL_FOCUS: + /** \bug Draw focus box. */ + break; + + case FL_UNFOCUS: + /** \bug Undraw focus box. */ + break; + + case FL_PUSH: + if (vscrollbar->visible() && Fl::event_inside(vscrollbar)) { + if (vscrollbar->handle(event)) + verScrolling = 1; + } else if (hscrollbar->visible() && Fl::event_inside(hscrollbar)) { + if (hscrollbar->handle(event)) + horScrolling = 1; + } else if (FltkWidgetView::handle(event) == 0 && + Fl::event_button() == FL_MIDDLE_MOUSE) { + if (!hasDragScroll) { + /* let the parent widget handle it... */ + return 0; + } else { + /* receive FL_DRAG and FL_RELEASE */ + dragScrolling = 1; + dragX = Fl::event_x(); + dragY = Fl::event_y(); + setCursor (core::style::CURSOR_MOVE); + } + } + return 1; + break; + + case FL_DRAG: + if (Fl::event_inside(this)) + Fl::remove_timeout(selectionScroll); + if (dragScrolling) { + scroll(dragX - Fl::event_x(), dragY - Fl::event_y()); + dragX = Fl::event_x(); + dragY = Fl::event_y(); + return 1; + } else if (verScrolling) { + vscrollbar->handle(event); + return 1; + } else if (horScrolling) { + hscrollbar->handle(event); + return 1; + } else if (!Fl::event_inside(this)) { + mouse_x = Fl::event_x(); + mouse_y = Fl::event_y(); + if (!Fl::has_timeout(selectionScroll, this)) + Fl::add_timeout(0.025, selectionScroll, this); + } + break; + + case FL_MOUSEWHEEL: + return (Fl::event_dx() ? hscrollbar : vscrollbar)->handle(event); + break; + + case FL_RELEASE: + Fl::remove_timeout(selectionScroll); + if (Fl::event_button() == FL_MIDDLE_MOUSE) { + setCursor (core::style::CURSOR_DEFAULT); + } else if (verScrolling) { + vscrollbar->handle(event); + } else if (horScrolling) { + hscrollbar->handle(event); + } + horScrolling = verScrolling = dragScrolling = 0; + break; + + case FL_ENTER: + /* could be the result of, e.g., closing another window. */ + mouse_x = Fl::event_x(); + mouse_y = Fl::event_y(); + positionChanged(); + break; + + case FL_LEAVE: + mouse_x = mouse_y = -1; + break; + } + + return FltkWidgetView::handle (event); +} + +// ---------------------------------------------------------------------- + +void FltkViewport::setCanvasSize (int width, int ascent, int descent) +{ + FltkWidgetView::setCanvasSize (width, ascent, descent); + adjustScrollbarValues (); +} + +/* + * This is used to simulate mouse motion (e.g., when scrolling). + */ +void FltkViewport::positionChanged () +{ + if (!dragScrolling && mouse_x >= x() && mouse_x < x()+w() && mouse_y >= y() + && mouse_y < y()+h()) + (void)theLayout->motionNotify (this, + translateViewXToCanvasX (mouse_x), + translateViewYToCanvasY (mouse_y), + (core::ButtonState)0); +} + +/* + * For scrollbars, this currently sets the same step to both vertical and + * horizontal. It may be differentiated if necessary. + */ +void FltkViewport::setScrollStep(int step) +{ + vscrollbar->linesize(step); + hscrollbar->linesize(step); +} + +bool FltkViewport::usesViewport () +{ + return true; +} + +int FltkViewport::getHScrollbarThickness () +{ + return SCROLLBAR_THICKNESS; +} + +int FltkViewport::getVScrollbarThickness () +{ + return SCROLLBAR_THICKNESS; +} + +void FltkViewport::scrollTo (int x, int y) +{ + int hdiff = vscrollbar->visible () ? SCROLLBAR_THICKNESS : 0; + int vdiff = hscrollbar->visible () ? SCROLLBAR_THICKNESS : 0; + + x = misc::min (x, canvasWidth - w() + hdiff); + x = misc::max (x, 0); + + y = misc::min (y, canvasHeight - h() + vdiff); + y = misc::max (y, 0); + + if (x == scrollX && y == scrollY) { + return; + } + + /* multiple calls to scroll can happen before a redraw occurs. + * scrollDX and scrollDY can therefore be non-zero here. + */ + updateCanvasWidgets (x - scrollX, y - scrollY); + scrollDX += x - scrollX; + scrollDY += y - scrollY; + + scrollX = x; + scrollY = y; + + adjustScrollbarValues (); + damage(FL_DAMAGE_SCROLL); + theLayout->scrollPosChanged (this, scrollX, scrollY); + positionChanged(); +} + +void FltkViewport::scroll (int dx, int dy) +{ + scrollTo (scrollX + dx, scrollY + dy); +} + +void FltkViewport::scroll (core::ScrollCommand cmd) +{ + if (cmd == core::SCREEN_UP_CMD) { + scroll (0, -h () + vscrollbar->linesize ()); + } else if (cmd == core::SCREEN_DOWN_CMD) { + scroll (0, h () - vscrollbar->linesize ()); + } else if (cmd == core::SCREEN_LEFT_CMD) { + scroll (-w() + hscrollbar->linesize (), 0); + } else if (cmd == core::SCREEN_RIGHT_CMD) { + scroll (w() - hscrollbar->linesize (), 0); + } else if (cmd == core::LINE_UP_CMD) { + scroll (0, (int) -vscrollbar->linesize ()); + } else if (cmd == core::LINE_DOWN_CMD) { + scroll (0, (int) vscrollbar->linesize ()); + } else if (cmd == core::LEFT_CMD) { + scroll ((int) -hscrollbar->linesize (), 0); + } else if (cmd == core::RIGHT_CMD) { + scroll ((int) hscrollbar->linesize (), 0); + } else if (cmd == core::TOP_CMD) { + scrollTo (scrollX, 0); + } else if (cmd == core::BOTTOM_CMD) { + scrollTo (scrollX, canvasHeight); /* gets adjusted in scrollTo () */ + } +} + +/* + * Scrolling in response to selection where the cursor is outside the view. + */ +void FltkViewport::selectionScroll () +{ + int distance; + int dx = 0, dy = 0; + + if ((distance = x() - mouse_x) > 0) + dx = -distance * hscrollbar->linesize () / 48 - 1; + else if ((distance = mouse_x - (x() + w())) > 0) + dx = distance * hscrollbar->linesize () / 48 + 1; + if ((distance = y() - mouse_y) > 0) + dy = -distance * vscrollbar->linesize () / 48 - 1; + else if ((distance = mouse_y - (y() + h())) > 0) + dy = distance * vscrollbar->linesize () / 48 + 1; + + scroll (dx, dy); +} + +void FltkViewport::selectionScroll (void *data) +{ + ((FltkViewport *)data)->selectionScroll (); + Fl::repeat_timeout(0.025, selectionScroll, data); +} + +void FltkViewport::setViewportSize (int width, int height, + int hScrollbarThickness, + int vScrollbarThickness) +{ + int adjustReq = + (hscrollbar->visible() ? !hScrollbarThickness : hScrollbarThickness) || + (vscrollbar->visible() ? !vScrollbarThickness : vScrollbarThickness); + + _MSG("FltkViewport::setViewportSize old_w,old_h=%dx%d -> w,h=%dx%d\n" + "\t hThick=%d hVis=%d, vThick=%d vVis=%d, adjustReq=%d\n", + w(),h(),width,height, + hScrollbarThickness,hscrollbar->visible(), + vScrollbarThickness,vscrollbar->visible(), adjustReq); + + (hScrollbarThickness > 0) ? hscrollbar->show () : hscrollbar->hide (); + (vScrollbarThickness > 0) ? vscrollbar->show () : vscrollbar->hide (); + + /* If no scrollbar, go to the beginning */ + scroll(hScrollbarThickness ? 0 : -scrollX, + vScrollbarThickness ? 0 : -scrollY); + + /* Adjust when scrollbar visibility changes */ + if (adjustReq) + adjustScrollbarsAndGadgetsAllocation (); +} + +void FltkViewport::updateCanvasWidgets (int dx, int dy) +{ + // scroll all child widgets except scroll bars + for (int i = children () - 1; i > 0; i--) { + Fl_Widget *widget = child (i); + + if (widget == hscrollbar || widget == vscrollbar) + continue; + + widget->position(widget->x () - dx, widget->y () - dy); + } +} + +int FltkViewport::translateViewXToCanvasX (int X) +{ + return X - x () + scrollX; +} + +int FltkViewport::translateViewYToCanvasY (int Y) +{ + return Y - y () + scrollY; +} + +int FltkViewport::translateCanvasXToViewX (int X) +{ + return X + x () - scrollX; +} + +int FltkViewport::translateCanvasYToViewY (int Y) +{ + return Y + y () - scrollY; +} + +// ---------------------------------------------------------------------- + +void FltkViewport::setGadgetOrientation (bool hscrollbarVisible, + bool vscrollbarVisible, + FltkViewport::GadgetOrientation + gadgetOrientation) +{ + this->gadgetOrientation[(hscrollbarVisible ? 0 : 1) | + (vscrollbarVisible ? 0 : 2)] = gadgetOrientation; + adjustScrollbarsAndGadgetsAllocation (); +} + +void FltkViewport::addGadget (Fl_Widget *gadget) +{ + /** \bug Reparent? */ + + gadgets->append (new TypedPointer < Fl_Widget> (gadget)); + adjustScrollbarsAndGadgetsAllocation (); +} + + +} // namespace fltk +} // namespace dw diff --git a/dw/fltkviewport.hh b/dw/fltkviewport.hh new file mode 100644 index 0000000..1569a7d --- /dev/null +++ b/dw/fltkviewport.hh @@ -0,0 +1,84 @@ +#ifndef __DW_FLTKVIEWPORT_HH__ +#define __DW_FLTKVIEWPORT_HH__ + +#include <FL/Fl_Group.H> +#include <FL/Fl_Scrollbar.H> + +#include "core.hh" +#include "fltkcore.hh" +#include "fltkviewbase.hh" + +namespace dw { +namespace fltk { + +class FltkViewport: public FltkWidgetView +{ +public: + enum GadgetOrientation { GADGET_VERTICAL, GADGET_HORIZONTAL }; + +private: + enum { SCROLLBAR_THICKNESS = 15 }; + + int scrollX, scrollY; + int scrollDX, scrollDY; + int hasDragScroll, dragScrolling, dragX, dragY; + int horScrolling, verScrolling; + + Fl_Scrollbar *vscrollbar, *hscrollbar; + + GadgetOrientation gadgetOrientation[4]; + lout::container::typed::List <lout::object::TypedPointer < Fl_Widget> > + *gadgets; + + void adjustScrollbarsAndGadgetsAllocation (); + void adjustScrollbarValues (); + void hscrollbarChanged (); + void vscrollbarChanged (); + void positionChanged (); + + static void hscrollbarCallback (Fl_Widget *hscrollbar, void *viewportPtr); + static void vscrollbarCallback (Fl_Widget *vscrollbar, void *viewportPtr); + + void selectionScroll(); + static void selectionScroll(void *vport); + + void updateCanvasWidgets (int oldScrollX, int oldScrollY); + static void draw_area (void *data, int x, int y, int w, int h); + +protected: + int translateViewXToCanvasX (int x); + int translateViewYToCanvasY (int y); + int translateCanvasXToViewX (int x); + int translateCanvasYToViewY (int y); + +public: + FltkViewport (int x, int y, int w, int h, const char *label = 0); + ~FltkViewport (); + + void resize(int x, int y, int w, int h); + void draw (); + int handle (int event); + + void setCanvasSize (int width, int ascent, int descent); + + bool usesViewport (); + int getHScrollbarThickness (); + int getVScrollbarThickness (); + void scroll(int dx, int dy); + void scroll(dw::core::ScrollCommand cmd); + void scrollTo (int x, int y); + void setViewportSize (int width, int height, + int hScrollbarThickness, int vScrollbarThickness); + void setScrollStep(int step); + + void setGadgetOrientation (bool hscrollbarVisible, bool vscrollbarVisible, + GadgetOrientation gadgetOrientation); + void setDragScroll (bool enable) { hasDragScroll = enable ? 1 : 0; } + void addGadget (Fl_Widget *gadget); +}; + +} // namespace fltk +} // namespace dw + +#endif // __DW_FLTKVIEWPORT_HH__ + diff --git a/dw/imgbuf.hh b/dw/imgbuf.hh new file mode 100644 index 0000000..f9870bc --- /dev/null +++ b/dw/imgbuf.hh @@ -0,0 +1,229 @@ +#ifndef __DW_IMGBUF_HH__ +#define __DW_IMGBUF_HH__ + +#ifndef __INCLUDED_FROM_DW_CORE_HH__ +# error Do not include this file directly, use "core.hh" instead. +#endif + +#include "../lout/debug.hh" + +namespace dw { +namespace core { + +/** + * \brief The platform independent interface for image buffers. + * + * %Image buffers depend on the platform (see \ref dw-images-and-backgrounds), + * but have this general, platform independent interface. The purpose of + * an image buffer is + * + * <ol> + * <li> storing the image data, + * <li> handling scaled versions of this buffer, and + * <li> drawing. + * </ol> + * + * The latter must be done independently from the window. + * + * <h3>Creating</h3> + * + * %Image buffers are created by calling dw::core::Platform::createImgbuf. + * + * <h3>Storing %Image Data</h3> + * + * dw::core::Imgbuf supports five image types, which are listed in the table + * below. The representation defines, how the colors are stored within + * the data, which is passed to dw::core::Imgbuf::copyRow. + * + * <table> + * <tr><th>Type (dw::core::Imgbuf::Type) <th>Bytes per + * Pixel <th>Representation + * <tr><td>dw::core::Imgbuf::RGB <td>3 <td>red, green, blue + * <tr><td>dw::core::Imgbuf::RGBA <td>4 <td>red, green, blue, alpha + * <tr><td>dw::core::Imgbuf::GRAY <td>1 <td>gray value + * <tr><td>dw::core::Imgbuf::INDEXED <td>1 <td>index to colormap + * <tr><td>dw::core::Imgbuf::INDEXED_ALPHA <td>1 <td>index to colormap + * </table> + * + * The last two types need a colormap, which is set by + * dw::core::Imgbuf::setCMap, which must be called before + * dw::core::Imgbuf::copyRow. This function expects the colors as 32 bit + * unsigned integers, which have the format 0xrrbbgg (for indexed + * images), or 0xaarrggbb (for indexed alpha), respectively. + * + * + * <h3>Scaling</h3> + * + * The buffer with the original size, which was created by + * dw::core::Platform::createImgbuf, is called root buffer. Imgbuf provides + * the ability to scale buffers. Generally, both root buffers, as well as + * scaled buffers, may be shared, memory management is done by reference + * counters. + * + * Via dw::core::Imgbuf::getScaledBuf, you can retrieve a scaled buffer. + * Generally, something like this must work always, in an efficient way: + * + * \code + * dw::core::Imgbuf *curBuf, *oldBuf; + * int width, heigt, + * // ... + * oldBuf = curBuf; + * curBuf = oldBuf->getScaledBuf(oldBuf, width, height); + * oldBuf->unref(); + * \endcode + * + * \em oldBuf may both be a root buffer, or a scaled buffer. + * + * The root buffer keeps a list of all children, and all methods + * operating on the image data (dw::core::Imgbuf::copyRow and + * dw::core::Imgbuf::setCMap) are delegated to the scaled buffers, when + * processed, and inherited, when a new scaled buffer is created. This + * means, that they must only be performed for the root buffer. + * + * A possible implementation could be (dw::fltk::FltkImgbuf does it this way): + * + * <ul> + * <li> If the method is called with an already scaled image buffer, this is + * delegated to the root buffer. + * + * <li> If the given size is the original size, the root buffer is + * returned, with an increased reference counter. + * + * <li> Otherwise, if this buffer has already been scaled to the given + * size, return this scaled buffer, with an increased reference + * counter. + * + * <li> Otherwise, return a new scaled buffer with reference counter 1. + * </ul> + * + * Special care is to be taken, when the root buffer is not used anymore, + * i.e. after dw::core::Imgbuf::unref the reference counter is 0, but there + * are still scaled buffers. Since all methods operating on the image data + * (dw::core::Imgbuf::copyRow and dw::core::Imgbuf::setCMap) are called for + * the root buffer, the root buffer is still needed, and so must not be + * deleted at this point. This is, how dw::fltk::FltkImgbuf solves this + * problem: + * + * <ul> + * <li> dw::fltk::FltkImgbuf::unref does, for root buffers, check, not only + * whether dw::fltk::FltkImgbuf::refCount is 0, but also, whether + * there are children left. When the latter is the case, the buffer + * is not deleted. + * + * <li> There is an additional check in dw::fltk::FltkImgbuf::detachScaledBuf, + * which deals with the case, that dw::fltk::FltkImgbuf::refCount is 0, + * and the last scaled buffer is removed. + * </ul> + * + * In the following example: + * + * \code + * dw::fltk::FltkPlatform *platform = new dw::fltk::FltkPlatform (); + * dw::core::Layout *layout = new dw::core::Layout (platform); + * + * dw::core::Imgbuf *rootbuf = + * layout->createImgbuf (dw::core::Imgbuf::RGB, 100, 100); + * dw::core::Imgbuf *scaledbuf = rootbuf->getScaledBuf (50, 50); + * rootbuf->unref (); + * scaledbuf->unref (); + * \endcode + * + * the root buffer is not deleted, when dw::core::Imgbuf::unref is called, + * since a scaled buffer is left. After calling dw::core::Imgbuf::unref for + * the scaled buffer, it is deleted, and after it, the root buffer. + * + * <h3>Drawing</h3> + * + * dw::core::Imgbuf provides no methods for drawing, instead, this is + * done by the views (implementation of dw::core::View). + * + * There are two situations, when drawing is necessary: + * + * <ol> + * <li> To react on expose events, the function dw::core::View::drawImage + * should be used, with the following parameters: + * <ul> + * <li> of course, the image buffer, + * <li> where the root of the image would be displayed (as \em xRoot + * and \em yRoot), and + * <li> the region within the image, which should be displayed (\em x, + * \em y, \em width, \em height). + * </ul> + * + * <li> When a row has been copied, it has to be drawn. To determine the + * area, which has to be drawn, the dw::core::Imgbuf::getRowArea + * should be used. The result can then passed + * to dw::core::View::drawImage. + * </ol> + * + * \sa \ref dw-images-and-backgrounds + */ +class Imgbuf: public lout::object::Object, public lout::signal::ObservedObject +{ +public: + enum Type { RGB, RGBA, GRAY, INDEXED, INDEXED_ALPHA }; + + inline Imgbuf () { + DBG_OBJ_CREATE ("dw::core::Imgbuf"); + DBG_OBJ_BASECLASS (lout::object::Object); + DBG_OBJ_BASECLASS (lout::signal::ObservedObject); + } + + /* + * Methods called from the image decoding + */ + + virtual void setCMap (int *colors, int num_colors) = 0; + virtual void copyRow (int row, const byte *data) = 0; + virtual void newScan () = 0; + + /* + * Methods called from dw::Image + */ + + virtual Imgbuf* getScaledBuf (int width, int height) = 0; + virtual void getRowArea (int row, dw::core::Rectangle *area) = 0; + virtual int getRootWidth () = 0; + virtual int getRootHeight () = 0; + + + /** + * Creates an image buffer with same parameters (type, gamma etc.) + * except size. + */ + virtual Imgbuf *createSimilarBuf (int width, int height) = 0; + + /** + * Copies another image buffer into this image buffer. + */ + virtual void copyTo (Imgbuf *dest, int xDestRoot, int yDestRoot, + int xSrc, int ySrc, int widthSrc, int heightSrc) = 0; + + /* + * Reference counting. + */ + + virtual void ref () = 0; + virtual void unref () = 0; + + /** + * \todo Comment + */ + virtual bool lastReference () = 0; + + + /** + * \todo Comment + */ + virtual void setDeleteOnUnref (bool deleteOnUnref) = 0; + + /** + * \todo Comment + */ + virtual bool isReferred () = 0; +}; + +} // namespace core +} // namespace dw + +#endif // __DW_IMGBUF_HH__ diff --git a/dw/imgrenderer.cc b/dw/imgrenderer.cc new file mode 100644 index 0000000..1868122 --- /dev/null +++ b/dw/imgrenderer.cc @@ -0,0 +1,77 @@ +/* + * RTFL (originally part of dillo) + * + * Copyright 2013 Sebastian Geerken <sgeerken@dillo.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version; with the following exception: + * + * The copyright holders of RTFL give you permission to link this file + * statically or dynamically against all versions of the graphviz + * library, which are published by AT&T Corp. under one of the following + * licenses: + * + * - Common Public License version 1.0 as published by International + * Business Machines Corporation (IBM), or + * - Eclipse Public License version 1.0 as published by the Eclipse + * Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "core.hh" + +namespace dw { +namespace core { + +using namespace lout::container; +using namespace lout::object; + +void ImgRendererDist::setBuffer (core::Imgbuf *buffer, bool resize) +{ + for (typed::Iterator <TypedPointer <ImgRenderer> > it = + children->iterator (); it.hasNext (); ) { + TypedPointer <ImgRenderer> *tp = it.getNext (); + tp->getTypedValue()->setBuffer (buffer, resize); + } +} + +void ImgRendererDist::drawRow (int row) +{ + for (typed::Iterator <TypedPointer <ImgRenderer> > it = + children->iterator (); it.hasNext (); ) { + TypedPointer <ImgRenderer> *tp = it.getNext (); + tp->getTypedValue()->drawRow (row); + } +} + + +void ImgRendererDist::finish () +{ + for (typed::Iterator <TypedPointer <ImgRenderer> > it = + children->iterator (); it.hasNext (); ) { + TypedPointer <ImgRenderer> *tp = it.getNext (); + tp->getTypedValue()->finish (); + } +} + +void ImgRendererDist::fatal () +{ + for (typed::Iterator <TypedPointer <ImgRenderer> > it = + children->iterator (); it.hasNext (); ) { + TypedPointer <ImgRenderer> *tp = it.getNext (); + tp->getTypedValue()->fatal (); + } +} + + +} // namespace core +} // namespace dw diff --git a/dw/imgrenderer.hh b/dw/imgrenderer.hh new file mode 100644 index 0000000..e3b5e95 --- /dev/null +++ b/dw/imgrenderer.hh @@ -0,0 +1,87 @@ +#ifndef __DW_IMGRENDERER_HH__ +#define __DW_IMGRENDERER_HH__ + +#ifndef __INCLUDED_FROM_DW_CORE_HH__ +# error Do not include this file directly, use "core.hh" instead. +#endif + +namespace dw { +namespace core { + +/** + * \brief ... + * + * \sa \ref dw-images-and-backgrounds + */ +class ImgRenderer +{ +public: + virtual ~ImgRenderer () { } + + /** + * \brief Called, when an image buffer is attached. + * + * This is typically the case when all meta data (size, depth) has been read. + */ + virtual void setBuffer (core::Imgbuf *buffer, bool resize = false) = 0; + + /** + * \brief Called, when data from a row is available and has been copied into + * the image buffer. + * + * The implementation will typically queue the respective area for drawing. + */ + virtual void drawRow (int row) = 0; + + /** + * \brief Called, when all image data has been retrieved. + * + * The implementation may use this instead of "drawRow" for drawing, to + * limit the number of draws. + */ + virtual void finish () = 0; + + /** + * \brief Called, when there are problems with the retrieval of image data. + * + * The implementation may use this to indicate an error. + */ + virtual void fatal () = 0; +}; + +/** + * \brief Implementation of ImgRenderer, which distributes all calls + * to a set of other implementations of ImgRenderer. + * + * The order of the call children is not defined, especially not + * identical to the order in which they have been added. + */ +class ImgRendererDist: public ImgRenderer +{ + lout::container::typed::HashSet <lout::object::TypedPointer <ImgRenderer> > + *children; + +public: + inline ImgRendererDist () + { children = new lout::container::typed::HashSet + <lout::object::TypedPointer <ImgRenderer> > (true); } + ~ImgRendererDist () { delete children; } + + void setBuffer (core::Imgbuf *buffer, bool resize); + void drawRow (int row); + void finish (); + void fatal (); + + void put (ImgRenderer *child) + { children->put (new lout::object::TypedPointer <ImgRenderer> (child)); } + void remove (ImgRenderer *child) + { lout::object::TypedPointer <ImgRenderer> tp (child); + children->remove (&tp); } +}; + +} // namespace core +} // namespace dw + +#endif // __DW_IMGRENDERER_HH__ + + diff --git a/dw/iterator.cc b/dw/iterator.cc new file mode 100644 index 0000000..de934d0 --- /dev/null +++ b/dw/iterator.cc @@ -0,0 +1,920 @@ +/* + * RTFL (originally part of dillo) + * + * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version; with the following exception: + * + * The copyright holders of RTFL give you permission to link this file + * statically or dynamically against all versions of the graphviz + * library, which are published by AT&T Corp. under one of the following + * licenses: + * + * - Common Public License version 1.0 as published by International + * Business Machines Corporation (IBM), or + * - Eclipse Public License version 1.0 as published by the Eclipse + * Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + + +#include "core.hh" +#include <limits.h> + +using namespace lout; + +namespace dw { +namespace core { + +// -------------- +// Iterator +// -------------- + +Iterator::Iterator(Widget *widget, Content::Type mask, bool atEnd) +{ + this->widget = widget; + this->mask = mask; +} + +Iterator::Iterator(Iterator &it): object::Comparable () +{ + widget = it.widget; + content = it.content; +} + +Iterator::~Iterator() +{ +} + +bool Iterator::equals (Object *other) +{ + Iterator *otherIt = (Iterator*)other; + return + this == otherIt || + (getWidget() == otherIt->getWidget() && compareTo(otherIt) == 0); +} + +void Iterator::intoStringBuffer(misc::StringBuffer *sb) +{ + sb->append ("{ widget = "); + //widget->intoStringBuffer (sb); + sb->appendPointer (widget); + sb->append (" ("); + sb->append (widget->getClassName()); + sb->append (")>"); + + sb->append (", mask = "); + Content::maskIntoStringBuffer (mask, sb); + + sb->append (", content = "); + Content::intoStringBuffer (&content, sb); + + sb->append (" }"); +} + +/** + * \brief Delete the iterator. + * + * The destructor is hidden, implementations may use optimizations for + * the allocation. (Will soon be the case for dw::core::EmptyIteratorFactory.) + */ +void Iterator::unref () +{ + delete this; +} + +/** + * \brief Scrolls the viewport, so that the region between \em it1 and + * \em it2 is seen, according to \em hpos and \em vpos. + * + * The parameters \em start and \em end have the same meaning as in + * dw::core::Iterator::getAllocation, \em start refers + * to \em it1, while \em end rerers to \em it2. + * + * If \em it1 and \em it2 point to the same location (see code), only + * \em it1 is regarded, and both belowstart and belowend refer to it. + */ +void Iterator::scrollTo (Iterator *it1, Iterator *it2, int start, int end, + HPosition hpos, VPosition vpos) +{ + Allocation alloc1, alloc2, alloc; + int x1, x2, y1, y2; + DeepIterator *eit1, *eit2, *eit3; + int curStart, curEnd, cmp; + bool atStart; + + if (it1->equals(it2)) { + it1->getAllocation (start, end, &alloc); + it1->getWidget()->getLayout()->scrollTo (hpos, vpos, alloc.x, alloc.y, + alloc.width, + alloc.ascent + alloc.descent); + } else { + // First, determine the rectangle all iterators from it1 and it2 + // allocate, i.e. the smallest rectangle containing all allocations of + // these iterators. + eit1 = new DeepIterator (it1); + eit2 = new DeepIterator (it2); + + x1 = INT_MAX; + x2 = INT_MIN; + y1 = INT_MAX; + y2 = INT_MIN; + + for (eit3 = (DeepIterator*)eit1->clone (), atStart = true; + (cmp = eit3->compareTo (eit2)) <= 0; + eit3->next (), atStart = false) { + if (atStart) + curStart = start; + else + curStart = 0; + + if (cmp == 0) + curEnd = end; + else + curEnd = INT_MAX; + + eit3->getAllocation (curStart, curEnd, &alloc); + x1 = misc::min (x1, alloc.x); + x2 = misc::max (x2, alloc.x + alloc.width); + y1 = misc::min (y1, alloc.y); + y2 = misc::max (y2, alloc.y + alloc.ascent + alloc.descent); + } + + delete eit3; + delete eit2; + delete eit1; + + it1->getAllocation (start, INT_MAX, &alloc1); + it2->getAllocation (0, end, &alloc2); + + if (alloc1.x > alloc2.x) { + // + // This is due to a line break within the region. When the line is + // longer than the viewport, and the region is actually quite short, + // the user would not see anything of the region, as in this figure + // (with region marked as "#"): + // + // +----------+ ,-- alloc1 + // | | V + // | | ### ### + // ### ### | | + // ^ | | <-- viewport + // | +----------+ + // `-- alloc2 + // |----------------------------| + // width + // + // Therefore, we make the region smaller, so that the region will be + // displayed like this: + // + // ,-- alloc1 + // +----|-----+ + // | V | + // | ### ###| + // ### ### | | + // ^ | | <-- viewport + // `-- alloc2 +----------+ + // |----------| + // width + // + + /** \todo Changes in the viewport size, until the idle function is + * called, are not regarded. */ + + if (it1->getWidget()->getLayout()->getUsesViewport() && + x2 - x1 > it1->getWidget()->getLayout()->getWidthViewport()) { + x1 = x2 - it1->getWidget()->getLayout()->getWidthViewport(); + x2 = x1 + it1->getWidget()->getLayout()->getWidthViewport(); + } + } + + if (alloc1.y > alloc2.y) { + // This is similar to the case above, e.g. if the region ends in + // another table column. + if (it1->getWidget()->getLayout()->getUsesViewport() && + y2 - y1 > it1->getWidget()->getLayout()->getHeightViewport()) { + y1 = y2 - it1->getWidget()->getLayout()->getHeightViewport(); + y2 = y1 + it1->getWidget()->getLayout()->getHeightViewport(); + } + } + + it1->getWidget()->getLayout()->scrollTo (hpos, vpos, + x1, y1, x2 - x1, y2 - y1); + } +} + + +void Iterator::print () +{ + misc::StringBuffer sb; + intoStringBuffer (&sb); + printf ("%s", sb.getChars ()); +} + +// ------------------- +// EmptyIterator +// ------------------- + +EmptyIterator::EmptyIterator (Widget *widget, Content::Type mask, bool atEnd): + Iterator (widget, mask, atEnd) +{ + this->content.type = (atEnd ? Content::END : Content::START); +} + +EmptyIterator::EmptyIterator (EmptyIterator &it): Iterator (it) +{ +} + +object::Object *EmptyIterator::clone () +{ + return new EmptyIterator (*this); +} + +int EmptyIterator::compareTo (object::Comparable *other) +{ + EmptyIterator *otherIt = (EmptyIterator*)other; + + if (content.type == otherIt->content.type) + return 0; + else if (content.type == Content::START) + return -1; + else + return +1; +} + +bool EmptyIterator::next () +{ + content.type = Content::END; + return false; +} + +bool EmptyIterator::prev () +{ + content.type = Content::START; + return false; +} + +void EmptyIterator::highlight (int start, int end, HighlightLayer layer) +{ +} + +void EmptyIterator::unhighlight (int direction, HighlightLayer layer) +{ +} + +void EmptyIterator::getAllocation (int start, int end, Allocation *allocation) +{ +} + +// ------------------ +// TextIterator +// ------------------ + +TextIterator::TextIterator (Widget *widget, Content::Type mask, bool atEnd, + const char *text): Iterator (widget, mask, atEnd) +{ + this->content.type = (atEnd ? Content::END : Content::START); + this->text = (mask & Content::TEXT) ? text : NULL; +} + +TextIterator::TextIterator (TextIterator &it): Iterator (it) +{ + text = it.text; +} + +int TextIterator::compareTo (object::Comparable *other) +{ + TextIterator *otherIt = (TextIterator*)other; + + if (content.type == otherIt->content.type) + return 0; + + switch (content.type) { + case Content::START: + return -1; + + case Content::TEXT: + if (otherIt->content.type == Content::START) + return +1; + else + return -1; + + case Content::END: + return +1; + + default: + misc::assertNotReached(); + return 0; + } +} + +bool TextIterator::next () +{ + if (content.type == Content::START && text != NULL) { + content.type = Content::TEXT; + content.text = text; + return true; + } else { + content.type = Content::END; + return false; + } +} + +bool TextIterator::prev () +{ + if (content.type == Content::END && text != NULL) { + content.type = Content::TEXT; + content.text = text; + return true; + } else { + content.type = Content::START; + return false; + } +} + +void TextIterator::getAllocation (int start, int end, Allocation *allocation) +{ + // Return the allocation of the widget. + *allocation = *(getWidget()->getAllocation ()); +} + +// ------------------ +// DeepIterator +// ------------------ + +DeepIterator::Stack::~Stack () +{ + for (int i = 0; i < size (); i++) + get(i)->unref (); +} + +/* + * The following two methods are used by dw::core::DeepIterator::DeepIterator, + * when the passed dw::core::Iterator points to a widget. Since a + * dw::core::DeepIterator never returns a widget, the dw::core::Iterator has + * to be corrected, by searching for the next content downwards (within the + * widget pointed to), forwards, and backwards (in the traversed tree). + */ + +/* + * Search downwards. If fromEnd is true, start search at the end, + * otherwise at the beginning. + */ +Iterator *DeepIterator::searchDownward (Iterator *it, Content::Type mask, + bool fromEnd) +{ + Iterator *it2, *it3; + + //DEBUG_MSG (1, "%*smoving down (%swards) from %s\n", + // indent, "", from_end ? "back" : "for", a_Dw_iterator_text (it)); + + assert (it->getContent()->type & Content::ANY_WIDGET); + it2 = it->getContent()->widget->iterator (mask, fromEnd); + + if (it2 == NULL) { + // Moving downwards failed. + //DEBUG_MSG (1, "%*smoving down failed\n", indent, ""); + return NULL; + } + + while (fromEnd ? it2->prev () : it2->next ()) { + //DEBUG_MSG (1, "%*sexamining %s\n", + // indent, "", a_Dw_iterator_text (it2)); + + if (it2->getContent()->type & Content::ANY_WIDGET) { + // Another widget. Search in it downwards. + it3 = searchDownward (it2, mask, fromEnd); + if (it3 != NULL) { + it2->unref (); + return it3; + } + // Else continue in this widget. + } else { + // Success! + //DEBUG_MSG (1, "%*smoving down succeeded: %s\n", + // indent, "", a_Dw_iterator_text (it2)); + return it2; + } + } + + // Nothing found. + it2->unref (); + //DEBUG_MSG (1, "%*smoving down failed (nothing found)\n", indent, ""); + return NULL; +} + +/* + * Search sidewards. fromEnd specifies the direction, false means forwards, + * true means backwards. + */ +Iterator *DeepIterator::searchSideward (Iterator *it, Content::Type mask, + bool fromEnd) +{ + Iterator *it2, *it3; + + //DEBUG_MSG (1, "%*smoving %swards from %s\n", + // indent, "", from_end ? "back" : "for", a_Dw_iterator_text (it)); + + assert (it->getContent()->type & Content::ANY_WIDGET); + it2 = it->cloneIterator (); + + while (fromEnd ? it2->prev () : it2->next ()) { + if (it2->getContent()->type & Content::ANY_WIDGET) { + // Search downwards in this widget. + it3 = searchDownward (it2, mask, fromEnd); + if (it3 != NULL) { + it2->unref (); + //DEBUG_MSG (1, "%*smoving %swards succeeded: %s\n", + // indent, "", from_end ? "back" : "for", + // a_Dw_iterator_text (it3)); + return it3; + } + // Else continue in this widget. + } else { + // Success! + // DEBUG_MSG (1, "%*smoving %swards succeeded: %s\n", + // indent, "", from_end ? "back" : "for", + // a_Dw_iterator_text (it2)); + return it2; + } + } + + /* Nothing found, go upwards in the tree (if possible). */ + it2->unref (); + Widget *respParent = getRespectiveParent (it->getWidget(), it->getMask()); + if (respParent) { + it2 = respParent->iterator (mask, false); + while (true) { + if (!it2->next ()) + misc::assertNotReached (); + + if (it2->getContent()->type & Content::ANY_WIDGET && + it2->getContent()->widget == it->getWidget ()) { + it3 = searchSideward (it2, mask, fromEnd); + it2->unref (); + //DEBUG_MSG (1, "%*smoving %swards succeeded: %s\n", + // indent, "", from_end ? "back" : "for", + // a_Dw_iterator_text (it3)); + return it3; + } + } + } + + // Nothing found at all. + // DEBUG_MSG (1, "%*smoving %swards failed (nothing found)\n", + // indent, "", from_end ? "back" : "for"); + return NULL; +} + +Widget *DeepIterator::getRespectiveParent (Widget *widget, Content::Type mask) +{ + // Return, depending on which is requested indirectly (follow + // references or containments) the parent (container) or the + // generator. At this point, the type of the parent/generator is + // not known (since the parent/generator is not known), so we have + // to examine the mask. This is the reason why only one of + // WIDGET_OOF_CONT and WIDGET_OOF_REF is allowed. + + return (mask & Content::WIDGET_OOF_REF) ? + widget->getGenerator() : widget->getParent(); +} + +int DeepIterator::getRespectiveLevel (Widget *widget, Content::Type mask) +{ + // Similar to getRespectiveParent. + + return (mask & Content::WIDGET_OOF_REF) ? + widget->getGeneratorLevel() : widget->getLevel(); +} + +/** + * \brief Create a new deep iterator from an existing dw::core::Iterator. + * + * The content of the return value will be the content of \em it. If within + * the widget tree, there is no non-widget content, the resulting deep + * iterator is empty (denoted by dw::core::DeepIterator::stack == NULL). + * + * Notes: + * + * <ol> + * <li> The mask of \em i" must include DW_CONTENT_WIDGET, but + * dw::core::DeepIterator::next will never return widgets. + * </ol> + */ +DeepIterator::DeepIterator (Iterator *it) +{ + //printf ("Starting creating DeepIterator %p ...\n", this); + //printf ("Initial iterator: "); + //it->print (); + //printf ("\n"); + + // Widgets out of flow are either followed widtin containers, or + // generators. Both (and also nothing at all) is not allowed. See + // also comment in getRespectiveParent. + int oofMask = + it->getMask() & (Content::WIDGET_OOF_CONT | Content::WIDGET_OOF_REF); + assert (oofMask == Content::WIDGET_OOF_CONT || + oofMask == Content::WIDGET_OOF_REF); + + //DEBUG_MSG (1, "a_Dw_ext_iterator_new: %s\n", a_Dw_iterator_text (it)); + + // Clone input iterator, so the iterator passed as parameter + // remains untouched. + it = it->cloneIterator (); + this->mask = it->getMask (); + + hasContents = true; + + // If it points to a widget, find a near non-widget content, + // since an DeepIterator should never return widgets. + if (it->getContent()->type & Content::ANY_WIDGET) { + Iterator *it2; + + // The second argument of searchDownward is actually a matter of + // taste :-) + if ((it2 = searchDownward (it, mask, false)) || + (it2 = searchSideward (it, mask, false)) || + (it2 = searchSideward (it, mask, true))) { + it->unref (); + it = it2; + } else { + // This may happen, when a page does not contain any non-widget + // content. + //DEBUG_MSG (1, "a_Dw_ext_iterator_new got totally helpless!\n"); + it->unref (); + hasContents = false; + } + } + + //DEBUG_MSG (1, " => %s\n", a_Dw_iterator_text (it)); + + if (hasContents) { + // If this widget has parents, we must construct appropriate iterators. + // + // \todo There may be a faster way instead of iterating through the + // parent widgets. + + //printf ("Starting with: "); + //it->print (); + //printf ("\n"); + + // Construct the iterators. + int thisLevel = getRespectiveLevel (it->getWidget()), level; + Widget *w; + for (w = it->getWidget (), level = thisLevel; + getRespectiveParent (w) != NULL; + w = getRespectiveParent (w), level--) { + Iterator *it = getRespectiveParent(w)->iterator (mask, false); + + //printf (" parent: %s %p\n", w->getClassName (), w); + + stack.put (it, level - 1); + while (true) { + //printf (" "); + //it->print (); + //printf ("\n"); + + bool hasNext = it->next(); + assert (hasNext); + + if (it->getContent()->type & Content::ANY_WIDGET && + it->getContent()->widget == w) + break; + } + + //printf (" %d: ", level - 1); + //it->print (); + //printf ("\n"); + } + + stack.put (it, thisLevel); + content = *(it->getContent()); + } + + //printf ("... done creating DeepIterator %p.\n", this); +} + + +DeepIterator::~DeepIterator () +{ + //printf ("Deleting DeepIterator %p ...\n", this); +} + +object::Object *DeepIterator::clone () +{ + DeepIterator *it = new DeepIterator (); + + for (int i = 0; i < stack.size (); i++) + it->stack.put (stack.get(i)->cloneIterator (), i); + + it->mask = mask; + it->content = content; + it->hasContents = hasContents; + + return it; +} + +int DeepIterator::compareTo (object::Comparable *other) +{ + DeepIterator *otherDeepIterator = (DeepIterator*)other; + + //printf ("Compare: %s\n", stack.toString ()); + //printf (" to: %s\n", otherDeepIterator->stack.toString ()); + + // Search the highest level, where the widgets are the same. + int level = 0; + + // The Comparable interface does not define "uncomparable". Deep + // iterators are only comparable if they belong to the same widget + // tree, so have the same widget at the bottom at the + // stack. If this is not the case, we abort. + + assert (stack.size() > 0); + assert (otherDeepIterator->stack.size() > 0); + + //printf ("Equal? The %s %p (of %p) and the %s %p (of %p)?\n", + // stack.get(0)->getWidget()->getClassName(), + // stack.get(0)->getWidget(), this, + // otherDeepIterator->stack.get(0)->getWidget()->getClassName(), + // otherDeepIterator->stack.get(0)->getWidget(), otherDeepIterator); + + assert (stack.get(0)->getWidget() + == otherDeepIterator->stack.get(level)->getWidget()); + + while (stack.get(level)->getWidget () + == otherDeepIterator->stack.get(level)->getWidget ()) { + if (level == stack.size() - 1 || + level == otherDeepIterator->stack.size() - 1) + break; + level++; + } + + //printf (" => level = %d (temorally)\n", level); + + while (stack.get(level)->getWidget () + != otherDeepIterator->stack.get(level)->getWidget ()) + level--; + + //printf (" => level = %d (finally)\n", level); + + return stack.get(level)->compareTo (otherDeepIterator->stack.get(level)); +} + +DeepIterator *DeepIterator::createVariant(Iterator *it) +{ + /** \todo Not yet implemented, and actually not yet needed very much. */ + return new DeepIterator (it); +} + +bool DeepIterator::isEmpty () { + return !hasContents; +} + +/** + * \brief Move iterator forward and store content it. + * + * Returns true on success. + */ +bool DeepIterator::next () +{ + Iterator *it = stack.getTop (); + + if (it->next ()) { + if (it->getContent()->type & Content::ANY_WIDGET) { + // Widget: new iterator on stack, to search in this widget. + stack.push (it->getContent()->widget->iterator (mask, false)); + return next (); + } else { + // Simply return the content of the iterartor. + content = *(it->getContent ()); + return true; + } + } else { + // No more data in the top-most widget. + if (stack.size () > 1) { + // Pop iterator from stack, and move to next item in the old one. + stack.pop (); + return next (); + } else { + // Stack is empty. + content.type = Content::END; + return false; + } + } +} + +/** + * \brief Move iterator backward and store content it. + * + * Returns true on success. + */ +bool DeepIterator::prev () +{ + Iterator *it = stack.getTop (); + + if (it->prev ()) { + if (it->getContent()->type & Content::ANY_WIDGET) { + // Widget: new iterator on stack, to search in this widget. + stack.push (it->getContent()->widget->iterator (mask, true)); + return prev (); + } else { + // Simply return the content of the iterartor. + content = *(it->getContent ()); + return true; + } + } else { + // No more data in the top-most widget. + if (stack.size () > 1) { + // Pop iterator from stack, and move to next item in the old one. + stack.pop (); + return prev (); + } else { + // Stack is empty. + content.type = Content::START; + return false; + } + } +} + +// ----------------- +// CharIterator +// ----------------- + +CharIterator::CharIterator () +{ + it = NULL; +} + +/** + * \brief ... + * + * If followReferences is true, only the reference are followed, when + * the container and generator for a widget is different. If false, + * only the container is followed. + */ +CharIterator::CharIterator (Widget *widget, bool followReferences) +{ + Iterator *i = + widget->iterator (Content::maskForSelection (followReferences), false); + it = new DeepIterator (i); + i->unref (); + ch = START; +} + +CharIterator::~CharIterator () +{ + if (it) + delete it; +} + +object::Object *CharIterator::clone() +{ + CharIterator *cloned = new CharIterator (); + cloned->it = it->cloneDeepIterator (); + cloned->ch = ch; + cloned->pos = pos; + return cloned; +} + +int CharIterator::compareTo(object::Comparable *other) +{ + CharIterator *otherIt = (CharIterator*)other; + int c = it->compareTo(otherIt->it); + if (c != 0) + return c; + else + return pos - otherIt->pos; +} + +bool CharIterator::next () +{ + if (ch == START || it->getContent()->type == Content::BREAK || + (it->getContent()->type == Content::TEXT && + it->getContent()->text[pos] == 0)) { + if (it->next()) { + if (it->getContent()->type == Content::BREAK) + ch = '\n'; + else { // if (it->getContent()->type == Content::TEXT) + pos = 0; + ch = it->getContent()->text[pos]; + if (ch == 0) + // should not happen, actually + return next (); + } + return true; + } + else { + ch = END; + return false; + } + } else if (ch == END) + return false; + else { + // at this point, it->getContent()->type == Content::TEXT + pos++; + ch = it->getContent()->text[pos]; + if (ch == 0) { + if (it->getContent()->space) { + ch = ' '; + } else { + return next (); + } + } + + return true; + } +} + +bool CharIterator::prev () +{ + if (ch == END || it->getContent()->type == Content::BREAK || + (it->getContent()->type == Content::TEXT && pos == 0)) { + if (it->prev()) { + if (it->getContent()->type == Content::BREAK) + ch = '\n'; + else { // if (it->getContent()->type == Content::TEXT) + if (it->getContent()->text[0] == 0) + return prev (); + else { + pos = strlen (it->getContent()->text); + if (it->getContent()->space) { + ch = ' '; + } else { + pos--; + ch = it->getContent()->text[pos]; + } + } + } + return true; + } + else { + ch = START; + return false; + } + } else if (ch == START) + return false; + else { + // at this point, it->getContent()->type == Content::TEXT + pos--; + ch = it->getContent()->text[pos]; + return true; + } +} + +void CharIterator::highlight (CharIterator *it1, CharIterator *it2, + HighlightLayer layer) +{ + if (it2->getChar () == CharIterator::END) + it2->prev (); + + if (it1->it->compareTo (it2->it) == 0) + // Only one content => highlight part of it. + it1->it->highlight (it1->pos, it2->pos, layer); + else { + DeepIterator *it = it1->it->cloneDeepIterator (); + int c; + bool start; + for (start = true; + (c = it->compareTo (it2->it)) <= 0; + it->next (), start = false) { + int endOfWord = + it->getContent()->type == Content::TEXT ? + strlen (it->getContent()->text) : 1; + if (start) // first iteration + it->highlight (it1->pos, endOfWord, layer); + else if (c == 0) // last iteration + it->highlight (0, it2->pos, layer); + else + it->highlight (0, endOfWord, layer); + } + delete it; + } +} + +void CharIterator::unhighlight (CharIterator *it1, CharIterator *it2, + HighlightLayer layer) +{ + if (it1->it->compareTo (it2->it) == 0) + // Only one content => unhighlight it (only for efficiency). + it1->it->unhighlight (0, layer); + else { + DeepIterator *it = it1->it->cloneDeepIterator (); + for (; it->compareTo (it2->it) <= 0; it->next ()) + it->unhighlight (-1, layer); + delete it; + } +} + +} // namespace core +} // namespace dw diff --git a/dw/iterator.hh b/dw/iterator.hh new file mode 100644 index 0000000..abf31d0 --- /dev/null +++ b/dw/iterator.hh @@ -0,0 +1,271 @@ +#ifndef __ITERATOR_HH__ +#define __ITERATOR_HH__ + +#ifndef __INCLUDED_FROM_DW_CORE_HH__ +# error Do not include this file directly, use "core.hh" instead. +#endif + +namespace dw { +namespace core { + +/** + * \brief Iterators are used to iterate through the contents of a widget. + * + * When using iterators, you should care about the results of + * dw::core::Widget::hasContents. + * + * \sa dw::core::Widget::iterator + */ +class Iterator: public lout::object::Comparable +{ +protected: + Iterator(Widget *widget, Content::Type mask, bool atEnd); + Iterator(Iterator &it); + ~Iterator(); + + Content content; + +private: + Widget *widget; + Content::Type mask; + +public: + bool equals (Object *other); + void intoStringBuffer(lout::misc::StringBuffer *sb); + + inline Widget *getWidget () { return widget; } + inline Content *getContent () { return &content; } + inline Content::Type getMask () { return mask; } + + virtual void unref (); + + /** + * \brief Move iterator forward and store content it. + * + * Returns true on success. + */ + virtual bool next () = 0; + + /** + * \brief Move iterator backward and store content it. + * + * Returns true on success. + */ + virtual bool prev () = 0; + + /** + * \brief Extend highlighted region to contain part of the current content. + * + * For text, start and end define the + * characters, otherwise, the shape is defined as [0, 1], i.e. for + * highlighting a whole dw::core::Content, pass 0 and >= 1. + * To unhighlight see also dw::core::Iterator::unhighlight. + */ + virtual void highlight (int start, int end, HighlightLayer layer) = 0; + + /** + * \brief Shrink highlighted region to no longer contain the + * current content. + * + * The direction parameter indicates whether the highlighted region should + * be reduced from the start (direction > 0) or from the end + * (direction < 0). If direction is 0 all content is unhighlighted. + */ + virtual void unhighlight (int direction, HighlightLayer layer) = 0; + + /** + * \brief Return the shape, which a part of the item, the iterator points + * on, allocates. + * + * The parameters start and end have the same meaning as in + * DwIterator::highlight(). + */ + virtual void getAllocation (int start, int end, Allocation *allocation) = 0; + + inline Iterator *cloneIterator () { return (Iterator*)clone(); } + + static void scrollTo (Iterator *it1, Iterator *it2, int start, int end, + HPosition hpos, VPosition vpos); + + virtual void print (); +}; + + +/** + * \brief This implementation of dw::core::Iterator can be used by widgets + * with no contents. + */ +class EmptyIterator: public Iterator +{ +private: + EmptyIterator (EmptyIterator &it); + +public: + EmptyIterator (Widget *widget, Content::Type mask, bool atEnd); + + lout::object::Object *clone(); + int compareTo(lout::object::Comparable *other); + bool next (); + bool prev (); + void highlight (int start, int end, HighlightLayer layer); + void unhighlight (int direction, HighlightLayer layer); + void getAllocation (int start, int end, Allocation *allocation); +}; + + +/** + * \brief This implementation of dw::core::Iterator can be used by widgets + * having one text word as contents + */ +class TextIterator: public Iterator +{ +private: + /** May be NULL, in this case, the next is skipped. */ + const char *text; + + TextIterator (TextIterator &it); + +public: + TextIterator (Widget *widget, Content::Type mask, bool atEnd, + const char *text); + + int compareTo(lout::object::Comparable *other); + + bool next (); + bool prev (); + void getAllocation (int start, int end, Allocation *allocation); +}; + + +/** + * \brief A stack of iterators, to iterate recursively through a widget tree. + * + * This class is similar to dw::core::Iterator, but not + * created by a widget, but explicitly from another iterator. Deep + * iterators do not have the limitation, that iteration is only done within + * a widget, instead, child widgets are iterated through recursively. + */ +class DeepIterator: public lout::object::Comparable +{ +private: + class Stack: public lout::container::typed::Vector<Iterator> + { + public: + inline Stack (): lout::container::typed::Vector<Iterator> (4, false) { } + ~Stack (); + inline Iterator *getTop () { return get (size () - 1); } + inline void push (Iterator *it) { put(it, -1); } + inline void pop() { getTop()->unref (); remove (size () - 1); } + }; + + Stack stack; + + static Iterator *searchDownward (Iterator *it, Content::Type mask, + bool fromEnd); + static Iterator *searchSideward (Iterator *it, Content::Type mask, + bool fromEnd); + + Content::Type mask; + Content content; + bool hasContents; + + inline DeepIterator () { } + + static Widget *getRespectiveParent (Widget *widget, Content::Type mask); + inline Widget *getRespectiveParent (Widget *widget) { + return getRespectiveParent (widget, mask); + } + + static int getRespectiveLevel (Widget *widget, Content::Type mask); + inline int getRespectiveLevel (Widget *widget) { + return getRespectiveLevel (widget, mask); + } + +public: + DeepIterator(Iterator *it); + ~DeepIterator(); + + lout::object::Object *clone (); + + DeepIterator *createVariant(Iterator *it); + inline Iterator *getTopIterator () { return stack.getTop(); } + inline Content *getContent () { return &content; } + + bool isEmpty (); + + bool next (); + bool prev (); + inline DeepIterator *cloneDeepIterator() { return (DeepIterator*)clone(); } + int compareTo(lout::object::Comparable *other); + + /** + * \brief Highlight a part of the current content. + * + * Unhighlight the current content by passing -1 as start (see also + * (dw::core::Iterator::unhighlight). For text, start and end define the + * characters, otherwise, the shape is defined as [0, 1], i.e. for + * highlighting a whole dw::core::Content, pass 0 and >= 1. + */ + inline void highlight (int start, int end, HighlightLayer layer) + { stack.getTop()->highlight (start, end, layer); } + + /** + * \brief Return the shape, which a part of the item, the iterator points + * on, allocates. + * + * The parameters start and end have the same meaning as in + * DwIterator::highlight(). + */ + inline void getAllocation (int start, int end, Allocation *allocation) + { stack.getTop()->getAllocation (start, end, allocation); } + + inline void unhighlight (int direction, HighlightLayer layer) + { stack.getTop()->unhighlight (direction, layer); } + + inline static void scrollTo (DeepIterator *it1, DeepIterator *it2, + int start, int end, + HPosition hpos, VPosition vpos) + { Iterator::scrollTo(it1->stack.getTop(), it2->stack.getTop(), + start, end, hpos, vpos); } +}; + +class CharIterator: public lout::object::Comparable +{ +public: + // START and END must not clash with any char value + // neither for signed nor unsigned char. + enum { START = 257, END = 258 }; + +private: + DeepIterator *it; + int pos, ch; + + CharIterator (); + +public: + CharIterator (Widget *widget, bool followReferences); + ~CharIterator (); + + lout::object::Object *clone(); + int compareTo(lout::object::Comparable *other); + + bool next (); + bool prev (); + inline int getChar() { return ch; } + inline CharIterator *cloneCharIterator() { return (CharIterator*)clone(); } + + static void highlight (CharIterator *it1, CharIterator *it2, + HighlightLayer layer); + static void unhighlight (CharIterator *it1, CharIterator *it2, + HighlightLayer layer); + + inline static void scrollTo (CharIterator *it1, CharIterator *it2, + HPosition hpos, VPosition vpos) + { DeepIterator::scrollTo(it1->it, it2->it, it1->pos, it2->pos, + hpos, vpos); } +}; + +} // namespace core +} // namespace dw + +#endif // __ITERATOR_HH__ diff --git a/dw/layout.cc b/dw/layout.cc new file mode 100644 index 0000000..96de6c2 --- /dev/null +++ b/dw/layout.cc @@ -0,0 +1,1445 @@ +/* + * RTFL (originally part of dillo) + * + * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version; with the following exception: + * + * The copyright holders of RTFL give you permission to link this file + * statically or dynamically against all versions of the graphviz + * library, which are published by AT&T Corp. under one of the following + * licenses: + * + * - Common Public License version 1.0 as published by International + * Business Machines Corporation (IBM), or + * - Eclipse Public License version 1.0 as published by the Eclipse + * Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + + +#include "core.hh" + +#include "../lout/msg.h" +#include "../lout/debug.hh" +#include "../lout/misc.hh" + +using namespace lout; +using namespace lout::container; +using namespace lout::object; + +namespace dw { +namespace core { + +bool Layout::LayoutImgRenderer::readyToDraw () +{ + return true; +} + +void Layout::LayoutImgRenderer::getBgArea (int *x, int *y, int *width, + int *height) +{ + // TODO Actually not padding area, but visible area? + getRefArea (x, y, width, height); +} + +void Layout::LayoutImgRenderer::getRefArea (int *xRef, int *yRef, int *widthRef, + int *heightRef) +{ + *xRef = 0; + *yRef = 0; + *widthRef = misc::max (layout->viewportWidth + - (layout->canvasHeightGreater ? + layout->vScrollbarThickness : 0), + layout->canvasWidth); + *heightRef = misc::max (layout->viewportHeight + - layout->hScrollbarThickness, + layout->canvasAscent + layout->canvasDescent); +} + +style::StyleImage *Layout::LayoutImgRenderer::getBackgroundImage () +{ + return layout->bgImage; +} + +style::BackgroundRepeat Layout::LayoutImgRenderer::getBackgroundRepeat () +{ + return layout->bgRepeat; +} + +style::BackgroundAttachment + Layout::LayoutImgRenderer::getBackgroundAttachment () +{ + return layout->bgAttachment; +} + +style::Length Layout::LayoutImgRenderer::getBackgroundPositionX () +{ + return layout->bgPositionX; +} + +style::Length Layout::LayoutImgRenderer::getBackgroundPositionY () +{ + return layout->bgPositionY; +} + +void Layout::LayoutImgRenderer::draw (int x, int y, int width, int height) +{ + layout->queueDraw (x, y, width, height); +} + +// ---------------------------------------------------------------------- + +void Layout::Receiver::resizeQueued (bool extremesChanged) +{ +} + +void Layout::Receiver::canvasSizeChanged (int width, int ascent, int descent) +{ +} + +// ---------------------------------------------------------------------- + +bool Layout::Emitter::emitToReceiver (lout::signal::Receiver *receiver, + int signalNo, int argc, + lout::object::Object **argv) +{ + Receiver *layoutReceiver = (Receiver*)receiver; + + switch (signalNo) { + case CANVAS_SIZE_CHANGED: + layoutReceiver->canvasSizeChanged (((Integer*)argv[0])->getValue (), + ((Integer*)argv[1])->getValue (), + ((Integer*)argv[2])->getValue ()); + break; + + case RESIZE_QUEUED: + layoutReceiver->resizeQueued (((Boolean*)argv[0])->getValue ()); + break; + + default: + misc::assertNotReached (); + } + + return false; +} + +void Layout::Emitter::emitResizeQueued (bool extremesChanged) +{ + Boolean ec (extremesChanged); + Object *argv[1] = { &ec }; + emitVoid (RESIZE_QUEUED, 1, argv); +} + +void Layout::Emitter::emitCanvasSizeChanged (int width, + int ascent, int descent) +{ + Integer w (width), a (ascent), d (descent); + Object *argv[3] = { &w, &a, &d }; + emitVoid (CANVAS_SIZE_CHANGED, 3, argv); +} + +// ---------------------------------------------------------------------- + +bool Layout::LinkReceiver::enter (Widget *widget, int link, int img, + int x, int y) +{ + return false; +} + +bool Layout::LinkReceiver::press (Widget *widget, int link, int img, + int x, int y, EventButton *event) +{ + return false; +} + +bool Layout::LinkReceiver::release (Widget *widget, int link, int img, + int x, int y, EventButton *event) +{ + return false; +} + +bool Layout::LinkReceiver::click (Widget *widget, int link, int img, + int x, int y, EventButton *event) +{ + return false; +} + +// ---------------------------------------------------------------------- + +bool Layout::LinkEmitter::emitToReceiver (lout::signal::Receiver *receiver, + int signalNo, int argc, + lout::object::Object **argv) +{ + LinkReceiver *linkReceiver = (LinkReceiver*)receiver; + + switch (signalNo) { + case ENTER: + return linkReceiver->enter ((Widget*)argv[0], + ((Integer*)argv[1])->getValue (), + ((Integer*)argv[2])->getValue (), + ((Integer*)argv[3])->getValue (), + ((Integer*)argv[4])->getValue ()); + + case PRESS: + return linkReceiver->press ((Widget*)argv[0], + ((Integer*)argv[1])->getValue (), + ((Integer*)argv[2])->getValue (), + ((Integer*)argv[3])->getValue (), + ((Integer*)argv[4])->getValue (), + (EventButton*)argv[5]); + + case RELEASE: + return linkReceiver->release ((Widget*)argv[0], + ((Integer*)argv[1])->getValue (), + ((Integer*)argv[2])->getValue (), + ((Integer*)argv[3])->getValue (), + ((Integer*)argv[4])->getValue (), + (EventButton*)argv[5]); + + case CLICK: + return linkReceiver->click ((Widget*)argv[0], + ((Integer*)argv[1])->getValue (), + ((Integer*)argv[2])->getValue (), + ((Integer*)argv[3])->getValue (), + ((Integer*)argv[4])->getValue (), + (EventButton*)argv[5]); + + default: + misc::assertNotReached (); + } + return false; +} + +bool Layout::LinkEmitter::emitEnter (Widget *widget, int link, int img, + int x, int y) +{ + Integer ilink (link), iimg (img), ix (x), iy (y); + Object *argv[5] = { widget, &ilink, &iimg, &ix, &iy }; + return emitBool (ENTER, 5, argv); +} + +bool Layout::LinkEmitter::emitPress (Widget *widget, int link, int img, + int x, int y, EventButton *event) +{ + Integer ilink (link), iimg (img), ix (x), iy (y); + Object *argv[6] = { widget, &ilink, &iimg, &ix, &iy, event }; + return emitBool (PRESS, 6, argv); +} + +bool Layout::LinkEmitter::emitRelease (Widget *widget, int link, int img, + int x, int y, EventButton *event) +{ + Integer ilink (link), iimg (img), ix (x), iy (y); + Object *argv[6] = { widget, &ilink, &iimg, &ix, &iy, event }; + return emitBool (RELEASE, 6, argv); +} + +bool Layout::LinkEmitter::emitClick (Widget *widget, int link, int img, + int x, int y, EventButton *event) +{ + Integer ilink (link), iimg (img), ix (x), iy (y); + Object *argv[6] = { widget, &ilink, &iimg, &ix, &iy, event }; + return emitBool (CLICK, 6, argv); +} + +// --------------------------------------------------------------------- + +Layout::Anchor::~Anchor () +{ + free(name); +} + +// --------------------------------------------------------------------- + +Layout::ScrollTarget::~ScrollTarget () +{ +} + +Layout::ScrollTargetBase::ScrollTargetBase (HPosition hPos, VPosition vPos) +{ + this->hPos = hPos; + this->vPos = vPos; +} + +HPosition Layout::ScrollTargetBase::getHPos () +{ + return hPos; +} + +VPosition Layout::ScrollTargetBase::getVPos () +{ + return vPos; +} + +Layout::ScrollTargetFixed::ScrollTargetFixed (HPosition hPos, VPosition vPos, + int x, int y, int width, int height) : + ScrollTargetBase (hPos, vPos) +{ + this->x = x; + this->y = y; + this->width = width; + this->height = height; +} + +int Layout::ScrollTargetFixed::getX () +{ + return x; +} + +int Layout::ScrollTargetFixed::getY () +{ + return y; +} + +int Layout::ScrollTargetFixed::getWidth () +{ + return width; +} + +int Layout::ScrollTargetFixed::getHeight () +{ + return height; +} + +Layout::ScrollTargetWidget::ScrollTargetWidget (HPosition hPos, VPosition vPos, + Widget *widget) : + ScrollTargetBase (hPos, vPos) +{ + this->widget = widget; +} + +int Layout::ScrollTargetWidget::getX () +{ + return widget->allocation.x; +} + +int Layout::ScrollTargetWidget::getY () +{ + return widget->allocation.y; +} + +int Layout::ScrollTargetWidget::getWidth () +{ + return widget->allocation.width; +} + +int Layout::ScrollTargetWidget::getHeight () +{ + return widget->allocation.ascent + widget->allocation.descent; +} + +// ---------------------------------------------------------------------- + +Layout::Layout (Platform *platform) +{ + this->platform = platform; + view = NULL; + topLevel = NULL; + widgetAtPoint = NULL; + + queueQueueResizeList = new typed::Stack<QueueResizeItem> (true); + queueResizeList = new typed::Vector<Widget> (4, false); + + DBG_OBJ_CREATE ("dw::core::Layout"); + + bgColor = NULL; + bgImage = NULL; + cursor = style::CURSOR_DEFAULT; + + canvasWidth = canvasAscent = canvasDescent = 0; + + usesViewport = false; + drawAfterScrollReq = false; + scrollX = scrollY = 0; + viewportWidth = viewportHeight = 0; + hScrollbarThickness = vScrollbarThickness = 0; + + DBG_OBJ_SET_NUM ("viewportWidth", viewportWidth); + DBG_OBJ_SET_NUM ("viewportHeight", viewportHeight); + DBG_OBJ_SET_NUM ("hScrollbarThickness", hScrollbarThickness); + DBG_OBJ_SET_NUM ("vScrollbarThickness", vScrollbarThickness); + + requestedAnchor = NULL; + scrollTarget = NULL; + scrollIdleId = -1; + scrollIdleNotInterrupted = false; + + anchorsTable = + new container::typed::HashTable <object::String, Anchor> (true, true); + + resizeIdleId = -1; + + textZone = new misc::ZoneAllocator (16 * 1024); + + DBG_OBJ_ASSOC_CHILD (&findtextState); + DBG_OBJ_ASSOC_CHILD (&selectionState); + + platform->setLayout (this); + + selectionState.setLayout(this); + + queueResizeCounter = sizeAllocateCounter = sizeRequestCounter = + getExtremesCounter = 0; + + layoutImgRenderer = NULL; + + resizeIdleCounter = queueResizeCounter = sizeAllocateCounter + = sizeRequestCounter = getExtremesCounter = 0; +} + +Layout::~Layout () +{ + widgetAtPoint = NULL; + + if (layoutImgRenderer) { + if (bgImage) + bgImage->removeExternalImgRenderer (layoutImgRenderer); + delete layoutImgRenderer; + } + + if (scrollIdleId != -1) + platform->removeIdle (scrollIdleId); + if (resizeIdleId != -1) + platform->removeIdle (resizeIdleId); + if (bgColor) + bgColor->unref (); + if (bgImage) + bgImage->unref (); + if (topLevel) { + detachWidget (topLevel); + Widget *w = topLevel; + topLevel = NULL; + delete w; + } + + delete queueQueueResizeList; + delete queueResizeList; + delete platform; + delete view; + delete anchorsTable; + delete textZone; + + if (requestedAnchor) + free (requestedAnchor); + if (scrollTarget) + delete scrollTarget; + + DBG_OBJ_DELETE (); +} + +void Layout::detachWidget (Widget *widget) +{ + // Called form ~Layout. Sometimes, the widgets (not only the toplevel widget) + // do some stuff after the layout has been deleted, so *all* widgets have to + // be detached, and check "layout != NULL" at relevant points. + + // Could be replaced by a virtual method in Widget, like getWidgetAtPoint, + // if performace were really a problem. + + widget->layout = NULL; + Iterator *it = + widget->iterator ((Content::Type) + (Content::WIDGET_IN_FLOW | Content::WIDGET_OOF_CONT), + false); + while (it->next ()) + detachWidget (it->getContent()->widget); + + it->unref (); +} + +void Layout::addWidget (Widget *widget) +{ + if (topLevel) { + MSG_WARN("widget already set\n"); + return; + } + + topLevel = widget; + widget->layout = this; + widget->container = NULL; + DBG_OBJ_SET_PTR_O (widget, "container", widget->container); + + queueResizeList->clear (); + widget->notifySetAsTopLevel (); + + findtextState.setWidget (widget); + + canvasHeightGreater = false; + DBG_OBJ_SET_SYM ("canvasHeightGreater", + canvasHeightGreater ? "true" : "false"); + + // Do not directly call Layout::queueResize(), but + // Widget::queueResize(), so that all flags are set properly, + // queueResizeList is filled, etc. + topLevel->queueResize (-1, false); +} + +void Layout::removeWidget () +{ + /** + * \bug Some more attributes must be reset here. + */ + topLevel = NULL; + queueResizeList->clear (); + widgetAtPoint = NULL; + canvasWidth = canvasAscent = canvasDescent = 0; + scrollX = scrollY = 0; + + view->setCanvasSize (canvasWidth, canvasAscent, canvasDescent); + if (view->usesViewport ()) + view->setViewportSize (viewportWidth, viewportHeight, 0, 0); + view->queueDrawTotal (); + + setAnchor (NULL); + updateAnchor (); + + emitter.emitCanvasSizeChanged (canvasWidth, canvasAscent, canvasDescent); + + findtextState.setWidget (NULL); + selectionState.reset (); + + updateCursor (); +} + +void Layout::setWidget (Widget *widget) +{ + DBG_OBJ_ASSOC_CHILD (widget); + + widgetAtPoint = NULL; + if (topLevel) { + Widget *w = topLevel; + topLevel = NULL; + delete w; + } + textZone->zoneFree (); + addWidget (widget); + + updateCursor (); +} + +/** + * \brief Attach a view to the layout. + * + * It will become a child of the layout, + * and so it will be destroyed, when the layout will be destroyed. + */ +void Layout::attachView (View *view) +{ + if (this->view) + MSG_ERR("attachView: Multiple views for layout!\n"); + + DBG_OBJ_ASSOC_CHILD (view); + + this->view = view; + platform->attachView (view); + + /* + * The layout of the view is set later, first, we "project" the current + * state of the layout into the new view. A view must handle this without + * a layout. See also at the end of this function. + */ + if (bgColor) + view->setBgColor (bgColor); + view->setCursor (cursor); + view->setCanvasSize (canvasWidth, canvasAscent, canvasDescent); + + if (view->usesViewport ()) { + if (usesViewport) { + view->scrollTo (scrollX, scrollY); + view->setViewportSize (viewportWidth, viewportHeight, + hScrollbarThickness, vScrollbarThickness); + hScrollbarThickness = misc::max (hScrollbarThickness, + view->getHScrollbarThickness ()); + vScrollbarThickness = misc::max (vScrollbarThickness, + view->getVScrollbarThickness ()); + } + else { + usesViewport = true; + scrollX = scrollY = 0; + viewportWidth = viewportHeight = 100; // random values + hScrollbarThickness = view->getHScrollbarThickness (); + vScrollbarThickness = view->getVScrollbarThickness (); + } + + DBG_OBJ_SET_NUM ("viewportWidth", viewportWidth); + DBG_OBJ_SET_NUM ("viewportHeight", viewportHeight); + DBG_OBJ_SET_NUM ("hScrollbarThickness", hScrollbarThickness); + DBG_OBJ_SET_NUM ("vScrollbarThickness", vScrollbarThickness); + } + + /* + * This is the last call within this function, so that it is safe for + * the implementation of dw::core::View::setLayout, to call methods + * of dw::core::Layout. + */ + view->setLayout (this); +} + +void Layout::detachView (View *view) +{ + if (this->view != view) + MSG_ERR("detachView: this->view: %p view %p\n", this->view, view); + + view->setLayout (NULL); + platform->detachView (view); + this->view = NULL; + /** + * \todo Actually, viewportMarkerWidthDiff and + * viewportMarkerHeightDiff have to be recalculated here, since the + * effective (i.e. maximal) values may change, after the view has been + * detached. Same applies to the usage of viewports. + */ +} + +void Layout::scroll(ScrollCommand cmd) +{ + if (view->usesViewport ()) + view->scroll(cmd); +} + +/** + * \brief Scrolls all viewports, so that the region [x, y, width, height] + * is seen, according to hpos and vpos. + */ +void Layout::scrollTo (HPosition hpos, VPosition vpos, + int x, int y, int width, int height) +{ + scrollTo0 (new ScrollTargetFixed (hpos, vpos, x, y, width, height), true); +} + +void Layout::scrollToWidget (HPosition hpos, VPosition vpos, Widget *widget) +{ + scrollTo0 (new ScrollTargetWidget (hpos, vpos, widget), true); +} + +void Layout::scrollTo0 (ScrollTarget *scrollTarget, bool scrollingInterrupted) +{ + if (usesViewport) { + _MSG("scrollTo (%d, %d, %s)\n", + x, y, scrollingInterrupted ? "true" : "false"); + + if (this->scrollTarget) + delete this->scrollTarget; + + this->scrollTarget = scrollTarget; + + if (scrollIdleId == -1) { + scrollIdleId = platform->addIdle (&Layout::scrollIdle); + scrollIdleNotInterrupted = true; + } + + scrollIdleNotInterrupted = + scrollIdleNotInterrupted || !scrollingInterrupted; + } +} + +void Layout::scrollIdle () +{ + DBG_OBJ_ENTER0 ("scroll", 0, "scrollIdle"); + + bool xChanged = true; + switch (scrollTarget->getHPos ()) { + case HPOS_LEFT: + scrollX = scrollTarget->getX (); + break; + case HPOS_CENTER: + scrollX = + scrollTarget->getX () - (viewportWidth - currVScrollbarThickness() + - scrollTarget->getWidth ()) / 2; + break; + case HPOS_RIGHT: + scrollX = + scrollTarget->getX () - (viewportWidth - currVScrollbarThickness() + - scrollTarget->getWidth ()); + break; + case HPOS_INTO_VIEW: + xChanged = calcScrollInto (scrollTarget->getX (), + scrollTarget->getWidth (), &scrollX, + viewportWidth - currVScrollbarThickness()); + break; + case HPOS_NO_CHANGE: + xChanged = false; + break; + } + + bool yChanged = true; + switch (scrollTarget->getVPos ()) { + case VPOS_TOP: + scrollY = scrollTarget->getY (); + break; + case VPOS_CENTER: + scrollY = + scrollTarget->getY () - (viewportHeight - currHScrollbarThickness() + - scrollTarget->getHeight ()) / 2; + break; + case VPOS_BOTTOM: + scrollY = + scrollTarget->getY () - (viewportHeight - currHScrollbarThickness() + - scrollTarget->getHeight ()); + break; + case VPOS_INTO_VIEW: + yChanged = calcScrollInto (scrollTarget->getY (), + scrollTarget->getHeight (), &scrollY, + viewportHeight - currHScrollbarThickness()); + break; + case VPOS_NO_CHANGE: + yChanged = false; + break; + } + + DBG_OBJ_MSGF ("scroll", 1, "xChanged = %s, yChanged = %s", + xChanged ? "true" : "false", yChanged ? "true" : "false"); + + if (xChanged || yChanged) { + adjustScrollPos (); + view->scrollTo (scrollX, scrollY); + } + + // The following code was once inside the block above ("if + // (xChanged || yChanged)"). I'm not sure but it looks that this + // should be outside, or at least setting drawAfterScrollReq in + // Layout::draw should consider whether the scroll position will + // change (but this would be rather difficult). --SG + + if (drawAfterScrollReq) { + drawAfterScrollReq = false; + view->queueDrawTotal (); + } + + delete scrollTarget; + scrollTarget = NULL; + + scrollIdleId = -1; + + DBG_OBJ_LEAVE (); +} + +void Layout::adjustScrollPos () +{ + scrollX = misc::min (scrollX, + canvasWidth - (viewportWidth - vScrollbarThickness)); + scrollX = misc::max (scrollX, 0); + + scrollY = misc::min (scrollY, + canvasAscent + canvasDescent - (viewportHeight - hScrollbarThickness)); + scrollY = misc::max (scrollY, 0); + + _MSG("adjustScrollPos: scrollX=%d scrollY=%d\n", scrollX, scrollY); +} + +bool Layout::calcScrollInto (int requestedValue, int requestedSize, + int *value, int viewportSize) +{ + if (requestedSize > viewportSize) { + // The viewport size is smaller than the size of the region which will + // be shown. If the region is already visible, do not change the + // position. Otherwise, show the left/upper border, this is most likely + // what is needed. + if (*value >= requestedValue && + *value + viewportSize < requestedValue + requestedSize) + return false; + else + requestedSize = viewportSize; + } + + if (requestedValue < *value) { + *value = requestedValue; + return true; + } else if (requestedValue + requestedSize > *value + viewportSize) { + *value = requestedValue - viewportSize + requestedSize; + return true; + } else + return false; +} + +void Layout::draw (View *view, Rectangle *area) +{ + DBG_OBJ_ENTER ("draw", 0, "draw", "%d, %d, %d * %d", + area->x, area->y, area->width, area->height); + + Rectangle widgetArea, intersection, widgetDrawArea; + + // First of all, draw background image. (Unlike background *color*, + // this is not a feature of the views.) + if (bgImage != NULL && bgImage->getImgbufSrc() != NULL) + style::drawBackgroundImage (view, bgImage, bgRepeat, bgAttachment, + bgPositionX, bgPositionY, + area->x, area->y, area->width, + area->height, 0, 0, + // Reference area: maximum of canvas size and + // viewport size. + misc::max (viewportWidth + - (canvasHeightGreater ? + vScrollbarThickness : 0), + canvasWidth), + misc::max (viewportHeight + - hScrollbarThickness, + canvasAscent + canvasDescent)); + + if (scrollIdleId != -1) { + /* scroll is pending, defer draw until after scrollIdle() */ + drawAfterScrollReq = true; + DBG_OBJ_MSGF ("draw", 1, "scrollIdleId = %d => delayed", scrollIdleId); + } else if (topLevel) { + /* Draw the top level widget. */ + widgetArea.x = topLevel->allocation.x; + widgetArea.y = topLevel->allocation.y; + widgetArea.width = topLevel->allocation.width; + widgetArea.height = topLevel->getHeight (); + + if (area->intersectsWith (&widgetArea, &intersection)) { + DBG_OBJ_MSG ("draw", 1, "drawing toplevel widget"); + + view->startDrawing (&intersection); + + /* Intersection in widget coordinates. */ + widgetDrawArea.x = intersection.x - topLevel->allocation.x; + widgetDrawArea.y = intersection.y - topLevel->allocation.y; + widgetDrawArea.width = intersection.width; + widgetDrawArea.height = intersection.height; + + topLevel->draw (view, &widgetDrawArea); + + view->finishDrawing (&intersection); + } else + DBG_OBJ_MSG ("draw", 1, "no intersection"); + } else + DBG_OBJ_MSG ("draw", 1, "no toplevel widget"); + + DBG_OBJ_LEAVE (); +} + +int Layout::currHScrollbarThickness() +{ + return (canvasWidth > viewportWidth) ? hScrollbarThickness : 0; +} + +int Layout::currVScrollbarThickness() +{ + return (canvasAscent + canvasDescent > viewportHeight) ? + vScrollbarThickness : 0; +} + +/** + * Sets the anchor to scroll to. + */ +void Layout::setAnchor (const char *anchor) +{ + _MSG("setAnchor (%s)\n", anchor); + + if (requestedAnchor) + free (requestedAnchor); + requestedAnchor = anchor ? strdup (anchor) : NULL; + updateAnchor (); +} + +/** + * Used, when the widget is not allocated yet. + */ +char *Layout::addAnchor (Widget *widget, const char* name) +{ + return addAnchor (widget, name, -1); +} + +char *Layout::addAnchor (Widget *widget, const char* name, int y) +{ + String key (name); + if (anchorsTable->contains (&key)) + return NULL; + else { + Anchor *anchor = new Anchor (); + anchor->name = strdup (name); + anchor->widget = widget; + anchor->y = y; + + anchorsTable->put (new String (name), anchor); + updateAnchor (); + + return anchor->name; + } +} + +void Layout::changeAnchor (Widget *widget, char* name, int y) +{ + String key (name); + Anchor *anchor = anchorsTable->get (&key); + assert (anchor); + assert (anchor->widget == widget); + anchor->y = y; + updateAnchor (); +} + +void Layout::removeAnchor (Widget *widget, char* name) +{ + String key (name); + anchorsTable->remove (&key); +} + +void Layout::updateAnchor () +{ + Anchor *anchor; + if (requestedAnchor) { + String key (requestedAnchor); + anchor = anchorsTable->get (&key); + } else + anchor = NULL; + + if (anchor == NULL) { + /** \todo Copy comment from old docs. */ + if (scrollIdleId != -1 && !scrollIdleNotInterrupted) { + platform->removeIdle (scrollIdleId); + scrollIdleId = -1; + } + } else + if (anchor->y != -1) + scrollTo0 (new ScrollTargetFixed (HPOS_NO_CHANGE, VPOS_TOP, + 0, anchor->y, 0, 0), false); +} + +void Layout::setCursor (style::Cursor cursor) +{ + if (cursor != this->cursor) { + this->cursor = cursor; + view->setCursor (cursor); + } +} + +void Layout::updateCursor () +{ + if (widgetAtPoint && widgetAtPoint->style) + setCursor (widgetAtPoint->style->cursor); + else + setCursor (style::CURSOR_DEFAULT); +} + +void Layout::setBgColor (style::Color *color) +{ + color->ref (); + + if (bgColor) + bgColor->unref (); + + bgColor = color; + + if (view) + view->setBgColor (bgColor); +} + +void Layout::setBgImage (style::StyleImage *bgImage, + style::BackgroundRepeat bgRepeat, + style::BackgroundAttachment bgAttachment, + style::Length bgPositionX, style::Length bgPositionY) +{ + if (layoutImgRenderer && this->bgImage) + this->bgImage->removeExternalImgRenderer (layoutImgRenderer); + + if (bgImage) + bgImage->ref (); + + if (this->bgImage) + this->bgImage->unref (); + + this->bgImage = bgImage; + this->bgRepeat = bgRepeat; + this->bgAttachment = bgAttachment; + this->bgPositionX = bgPositionX; + this->bgPositionY = bgPositionY; + + if (bgImage) { + // Create instance of LayoutImgRenderer when needed. Until this + // layout is deleted, "layoutImgRenderer" will be kept, since it + // is not specific to the style, but only to this layout. + if (layoutImgRenderer == NULL) + layoutImgRenderer = new LayoutImgRenderer (this); + bgImage->putExternalImgRenderer (layoutImgRenderer); + } +} + + +void Layout::resizeIdle () +{ + DBG_OBJ_ENTER0 ("resize", 0, "resizeIdle"); + + enterResizeIdle (); + + //static int calls = 0; + //printf ("Layout::resizeIdle calls = %d\n", ++calls); + + assert (resizeIdleId != -1); + + for (typed::Iterator <Widget> it = queueResizeList->iterator(); + it.hasNext (); ) { + Widget *widget = it.getNext (); + + //printf (" the %stop-level %s %p was queued (extremes changed: %s)\n", + // widget->parent ? "non-" : "", widget->getClassName(), widget, + // widget->extremesQueued () ? "yes" : "no"); + + if (widget->resizeQueued ()) { + widget->setFlags (Widget::NEEDS_RESIZE); + widget->unsetFlags (Widget::RESIZE_QUEUED); + } + + if (widget->allocateQueued ()) { + widget->setFlags (Widget::NEEDS_ALLOCATE); + widget->unsetFlags (Widget::ALLOCATE_QUEUED); + } + + if (widget->extremesQueued ()) { + widget->setFlags (Widget::EXTREMES_CHANGED); + widget->unsetFlags (Widget::EXTREMES_QUEUED); + } + } + queueResizeList->clear (); + + // Reset already here, since in this function, queueResize() may be + // called again. + resizeIdleId = -1; + + // If this method is triggered by a viewport change, we can save + // time when the toplevel widget is not affected (as for a toplevel + // image resource). + if (topLevel && (topLevel->needsResize () || topLevel->needsAllocate ())) { + Requisition requisition; + Allocation allocation; + + topLevel->sizeRequest (&requisition); + DBG_OBJ_MSGF ("resize", 1, "toplevel size: %d * (%d + %d)", + requisition.width, requisition.ascent, requisition.descent); + + // This method is triggered by Widget::queueResize, which will, + // in any case, set NEEDS_ALLOCATE (indirectly, as ALLOCATE_QUEUED). + // This assertion helps to find inconsistences. (Cases where + // this method is triggered by a viewport change, but the + // toplevel widget is not affected, are filtered out some lines + // above: "if (topLevel && topLevel->needsResize ())".) + assert (topLevel->needsAllocate ()); + + allocation.x = allocation.y = 0; + allocation.width = requisition.width; + allocation.ascent = requisition.ascent; + allocation.descent = requisition.descent; + topLevel->sizeAllocate (&allocation); + + canvasWidth = requisition.width; + canvasAscent = requisition.ascent; + canvasDescent = requisition.descent; + + emitter.emitCanvasSizeChanged (canvasWidth, canvasAscent, canvasDescent); + + // Tell the view about the new world size. + view->setCanvasSize (canvasWidth, canvasAscent, canvasDescent); + // view->queueDrawTotal (false); + + if (usesViewport) { + int currHThickness = currHScrollbarThickness(); + int currVThickness = currVScrollbarThickness(); + + if (!canvasHeightGreater && + canvasAscent + canvasDescent > viewportHeight - currHThickness) { + canvasHeightGreater = true; + DBG_OBJ_SET_SYM ("canvasHeightGreater", + canvasHeightGreater ? "true" : "false"); + containerSizeChanged (); + } + + // Set viewport sizes. + view->setViewportSize (viewportWidth, viewportHeight, + currHThickness, currVThickness); + } + + // views are redrawn via Widget::resizeDrawImpl () + } + + updateAnchor (); + + DBG_OBJ_MSGF ("resize", 1, + "after resizeIdle: resizeIdleId = %d", resizeIdleId); + DBG_OBJ_LEAVE (); + + leaveResizeIdle (); +} + +void Layout::queueDraw (int x, int y, int width, int height) +{ + DBG_OBJ_ENTER ("draw", 0, "queueDrawArea", "%d, %d, %d, %d", + x, y, width, height); + + Rectangle area; + area.x = x; + area.y = y; + area.width = width; + area.height = height; + + if (!area.isEmpty ()) + view->queueDraw (&area); + + DBG_OBJ_LEAVE (); +} + +void Layout::queueDrawExcept (int x, int y, int width, int height, + int ex, int ey, int ewidth, int eheight) { + + if (x == ex && y == ey && width == ewidth && height == eheight) + return; + + // queueDraw() the four rectangles within rectangle (x, y, width, height) + // around rectangle (ex, ey, ewidth, eheight). + // Some or all of these may be empty. + + // upper left corner of the intersection rectangle + int ix1 = misc::max (x, ex); + int iy1 = misc::max (y, ey); + // lower right corner of the intersection rectangle + int ix2 = misc::min (x + width, ex + ewidth); + int iy2 = misc::min (y + height, ey + eheight); + + queueDraw (x, y, width, iy1 - y); + queueDraw (x, iy2, width, y + height - iy2); + queueDraw (x, iy1, ix1 - x, iy2 - iy1); + queueDraw (ix2, iy1, x + width - ix2, iy2 - iy1); +} + +void Layout::queueResize (bool extremesChanged) +{ + DBG_OBJ_ENTER ("resize", 0, "queueResize", "%s", + extremesChanged ? "true" : "false"); + + if (resizeIdleId == -1) { + view->cancelQueueDraw (); + + resizeIdleId = platform->addIdle (&Layout::resizeIdle); + DBG_OBJ_MSGF ("resize", 1, "setting resizeIdleId = %d", resizeIdleId); + } + + emitter.emitResizeQueued (extremesChanged); + + DBG_OBJ_LEAVE (); +} + + +// Views + +bool Layout::buttonEvent (ButtonEventType type, View *view, int numPressed, + int x, int y, ButtonState state, int button) + +{ + EventButton event; + + moveToWidgetAtPoint (x, y, state); + + event.xCanvas = x; + event.yCanvas = y; + event.state = state; + event.button = button; + event.numPressed = numPressed; + + return processMouseEvent (&event, type); +} + +/** + * \brief This function is called by a view, to delegate a motion notify + * event. + * + * Arguments are similar to dw::core::Layout::buttonPress. + */ +bool Layout::motionNotify (View *view, int x, int y, ButtonState state) +{ + EventButton event; + + moveToWidgetAtPoint (x, y, state); + + event.xCanvas = x; + event.yCanvas = y; + event.state = state; + + return processMouseEvent (&event, MOTION_NOTIFY); +} + +/** + * \brief This function is called by a view, to delegate a enter notify event. + * + * Arguments are similar to dw::core::Layout::buttonPress. + */ +void Layout::enterNotify (View *view, int x, int y, ButtonState state) +{ + Widget *lastWidget; + EventCrossing event; + + lastWidget = widgetAtPoint; + moveToWidgetAtPoint (x, y, state); + + if (widgetAtPoint) { + event.state = state; + event.lastWidget = lastWidget; + event.currentWidget = widgetAtPoint; + widgetAtPoint->enterNotify (&event); + } +} + +/** + * \brief This function is called by a view, to delegate a leave notify event. + * + * Arguments are similar to dw::core::Layout::buttonPress. + */ +void Layout::leaveNotify (View *view, ButtonState state) +{ +#if 0 + Widget *lastWidget; + EventCrossing event; + + lastWidget = widgetAtPoint; + moveOutOfView (state); + + if (lastWidget) { + event.state = state; + event.lastWidget = lastWidget; + event.currentWidget = widgetAtPoint; + lastWidget->leaveNotify (&event); + } +#else + moveOutOfView (state); +#endif +} + +/* + * Return the widget at position (x, y). Return NULL, if there is no widget. + */ +Widget *Layout::getWidgetAtPoint (int x, int y) +{ + _MSG ("------------------------------------------------------------\n"); + _MSG ("widget at (%d, %d)\n", x, y); + if (topLevel && topLevel->wasAllocated ()) + return topLevel->getWidgetAtPoint (x, y, 0); + else + return NULL; +} + + +/* + * Emit the necessary crossing events, when the mouse pointer has moved to + * the given widget (by mouse or scrolling). + */ +void Layout::moveToWidget (Widget *newWidgetAtPoint, ButtonState state) +{ + Widget *ancestor, *w; + Widget **track; + int trackLen, i, i_a; + EventCrossing crossingEvent; + + _MSG("moveToWidget: wap=%p nwap=%p\n",widgetAtPoint,newWidgetAtPoint); + if (newWidgetAtPoint != widgetAtPoint) { + // The mouse pointer has been moved into another widget. + if (newWidgetAtPoint && widgetAtPoint) + ancestor = + newWidgetAtPoint->getNearestCommonAncestor (widgetAtPoint); + else if (newWidgetAtPoint) + ancestor = newWidgetAtPoint->getTopLevel (); + else + ancestor = widgetAtPoint->getTopLevel (); + + // Construct the track. + trackLen = 0; + if (widgetAtPoint) + // first part + for (w = widgetAtPoint; w != ancestor; w = w->getParent ()) + trackLen++; + trackLen++; // for the ancestor + if (newWidgetAtPoint) + // second part + for (w = newWidgetAtPoint; w != ancestor; w = w->getParent ()) + trackLen++; + + track = new Widget* [trackLen]; + i = 0; + if (widgetAtPoint) + /* first part */ + for (w = widgetAtPoint; w != ancestor; w = w->getParent ()) + track[i++] = w; + i_a = i; + track[i++] = ancestor; + if (newWidgetAtPoint) { + /* second part */ + i = trackLen - 1; + for (w = newWidgetAtPoint; w != ancestor; w = w->getParent ()) + track[i--] = w; + } +#if 0 + MSG("Track: %s[ ", widgetAtPoint ? "" : "nil "); + for (i = 0; i < trackLen; i++) + MSG("%s%p ", i == i_a ? ">" : "", track[i]); + MSG("] %s\n", newWidgetAtPoint ? "" : "nil"); +#endif + + /* Send events to the widgets on the track */ + for (i = 0; i < trackLen; i++) { + crossingEvent.state = state; + crossingEvent.currentWidget = widgetAtPoint; // ??? + crossingEvent.lastWidget = widgetAtPoint; // ??? + if (i < i_a) { + track[i]->leaveNotify (&crossingEvent); + } else if (i == i_a) { /* ancestor */ + /* Don't touch ancestor unless: + * - moving into/from NULL, + * - ancestor becomes the newWidgetAtPoint */ + if (i_a == trackLen-1 && !newWidgetAtPoint) + track[i]->leaveNotify (&crossingEvent); + else if ((i_a == 0 && !widgetAtPoint) || + (i_a == trackLen-1 && newWidgetAtPoint)) + track[i]->enterNotify (&crossingEvent); + } else { + track[i]->enterNotify (&crossingEvent); + } + } + + delete[] track; + + widgetAtPoint = newWidgetAtPoint; + updateCursor (); + } +} + +/** + * \brief Common processing of press, release and motion events. + * + * This function depends on that move_to_widget_at_point() + * has been called before. + */ +bool Layout::processMouseEvent (MousePositionEvent *event, + ButtonEventType type) +{ + Widget *widget; + + /* + * If the event is outside of the visible region of the canvas, treat it + * as occurring at the region's edge. Notably, this helps when selecting + * text. + */ + if (event->xCanvas < scrollX) + event->xCanvas = scrollX; + else { + int maxX = scrollX + viewportWidth - currVScrollbarThickness() - 1; + + if (event->xCanvas > maxX) + event->xCanvas = maxX; + } + if (event->yCanvas < scrollY) + event->yCanvas = scrollY; + else { + int maxY = misc::min(scrollY + viewportHeight -currHScrollbarThickness(), + canvasAscent + canvasDescent) - 1; + + if (event->yCanvas > maxY) + event->yCanvas = maxY; + } + + widget = getWidgetAtPoint(event->xCanvas, event->yCanvas); + + for (; widget; widget = widget->getParent ()) { + if (widget->isButtonSensitive ()) { + event->xWidget = event->xCanvas - widget->getAllocation()->x; + event->yWidget = event->yCanvas - widget->getAllocation()->y; + + switch (type) { + case BUTTON_PRESS: + return widget->buttonPress ((EventButton*)event); + + case BUTTON_RELEASE: + return widget->buttonRelease ((EventButton*)event); + + case MOTION_NOTIFY: + return widget->motionNotify ((EventMotion*)event); + + default: + misc::assertNotReached (); + } + } + } + if (type == BUTTON_PRESS) + return emitLinkPress (NULL, -1, -1, -1, -1, (EventButton*)event); + else if (type == BUTTON_RELEASE) + return emitLinkRelease(NULL, -1, -1, -1, -1, (EventButton*)event); + + return false; +} + +/* + * This function must be called by a view, when the user has manually changed + * the viewport position. It is *not* called, when the layout has requested the + * position change. + */ +void Layout::scrollPosChanged (View *view, int x, int y) +{ + if (x != scrollX || y != scrollY) { + scrollX = x; + scrollY = y; + + setAnchor (NULL); + updateAnchor (); + } +} + +/* + * This function must be called by a viewport view, when its viewport size has + * changed. It is *not* called, when the layout has requested the size change. + */ +void Layout::viewportSizeChanged (View *view, int width, int height) +{ + DBG_OBJ_ENTER ("resize", 0, "viewportSizeChanged", "%p, %d, %d", + view, width, height); + + /* If the width has become higher, we test again, whether the vertical + * scrollbar (so to speak) can be hidden again. */ + if (usesViewport && width > viewportWidth) { + canvasHeightGreater = false; + DBG_OBJ_SET_SYM ("canvasHeightGreater", + canvasHeightGreater ? "true" : "false"); + } + + /* if size changes, redraw this view. + * TODO: this is a resize call (redraw/resize code needs a review). */ + if (viewportWidth != width || viewportHeight != height) { + if (topLevel) + // similar to addWidget() + topLevel->queueResize (-1, false); + else + queueResize (false); + } + + viewportWidth = width; + viewportHeight = height; + + DBG_OBJ_SET_NUM ("viewportWidth", viewportWidth); + DBG_OBJ_SET_NUM ("viewportHeight", viewportHeight); + + containerSizeChanged (); + + DBG_OBJ_LEAVE (); +} + +void Layout::containerSizeChanged () +{ + DBG_OBJ_ENTER0 ("resize", 0, "containerSizeChanged"); + + if (topLevel) { + topLevel->containerSizeChanged (); + queueResize (true); + } + + DBG_OBJ_LEAVE (); +} + +} // namespace core +} // namespace dw diff --git a/dw/layout.hh b/dw/layout.hh new file mode 100644 index 0000000..dbcff99 --- /dev/null +++ b/dw/layout.hh @@ -0,0 +1,532 @@ +#ifndef __DW_LAYOUT_HH__ +#define __DW_LAYOUT_HH__ + +#ifndef __INCLUDED_FROM_DW_CORE_HH__ +# error Do not include this file directly, use "core.hh" instead. +#endif + +namespace dw { +namespace core { + +/** + * \brief The central class for managing and drawing a widget tree. + * + * \sa\ref dw-overview, \ref dw-layout-widgets, \ref dw-layout-views + */ +class Layout: public lout::object::Object +{ + friend class Widget; + +private: + class LayoutImgRenderer: public style::StyleImage::ExternalImgRenderer + { + Layout *layout; + + public: + LayoutImgRenderer (Layout *layout) { this->layout = layout; } + + bool readyToDraw (); + void getBgArea (int *x, int *y, int *width, int *height); + void getRefArea (int *xRef, int *yRef, int *widthRef, int *heightRef); + style::StyleImage *getBackgroundImage (); + style::BackgroundRepeat getBackgroundRepeat (); + style::BackgroundAttachment getBackgroundAttachment (); + style::Length getBackgroundPositionX (); + style::Length getBackgroundPositionY (); + void draw (int x, int y, int width, int height); + }; + + LayoutImgRenderer *layoutImgRenderer; + +public: + /** + * \brief Receiver interface different signals. + * + * May be extended. + */ + class Receiver: public lout::signal::Receiver + { + public: + virtual void resizeQueued (bool extremesChanged); + virtual void canvasSizeChanged (int width, int ascent, int descent); + }; + + class LinkReceiver: public lout::signal::Receiver + { + public: + /** + * \brief Called, when a link is entered, left, or the position has + * changed. + * + * When a link is entered, this method is called with the respective + * arguments. When a link is left, this method is called with all + * three arguments (\em link, \em x, \em y) set to -1. + * + * When coordinates are supported, a change of the coordinates also + * causes emitting this signal. + */ + virtual bool enter (Widget *widget, int link, int img, int x, int y); + + /** + * \brief Called, when the user has pressed the mouse button on a + * link (but not yet released). + * + * The causing event is passed as \em event. + */ + virtual bool press (Widget *widget, int link, int img, int x, int y, + EventButton *event); + + /** + * \brief Called, when the user has released the mouse button on a + * link. + * + * The causing event is passed as \em event. + */ + virtual bool release (Widget *widget, int link, int img, int x, int y, + EventButton *event); + + /** + * \brief Called, when the user has clicked on a link. + * + * For mouse interaction, this is equivalent to "press" and "release" + * on the same link. In this case, \em event contains the "release" + * event. + * + * + * When activating links via keyboard is supported, only a "clicked" + * signal will be emitted, and \em event will be NULL. + */ + virtual bool click (Widget *widget, int link, int img, int x, int y, + EventButton *event); + }; + + class LinkEmitter: public lout::signal::Emitter + { + private: + enum { ENTER, PRESS, RELEASE, CLICK }; + + protected: + bool emitToReceiver (lout::signal::Receiver *receiver, int signalNo, + int argc, lout::object::Object **argv); + + public: + inline void connectLink (LinkReceiver *receiver) { connect (receiver); } + + bool emitEnter (Widget *widget, int link, int img, int x, int y); + bool emitPress (Widget *widget, int link, int img, int x, int y, + EventButton *event); + bool emitRelease (Widget *widget, int link, int img, int x, int y, + EventButton *event); + bool emitClick (Widget *widget, int link, int img, int x, int y, + EventButton *event); + }; + + LinkEmitter linkEmitter; + +private: + class Emitter: public lout::signal::Emitter + { + private: + enum { RESIZE_QUEUED, CANVAS_SIZE_CHANGED }; + + protected: + bool emitToReceiver (lout::signal::Receiver *receiver, int signalNo, + int argc, lout::object::Object **argv); + + public: + inline void connectLayout (Receiver *receiver) { connect (receiver); } + + void emitResizeQueued (bool extremesChanged); + void emitCanvasSizeChanged (int width, int ascent, int descent); + }; + + Emitter emitter; + + class Anchor: public lout::object::Object + { + public: + char *name; + Widget *widget; + int y; + + ~Anchor (); + }; + + class QueueResizeItem: public lout::object::Object + { + public: + Widget *widget; + int ref; + bool extremesChanged, fast; + + inline QueueResizeItem (Widget *widget, int ref, bool extremesChanged, + bool fast) + { + this->widget = widget; + this->ref = ref; + this->extremesChanged = extremesChanged; + this->fast = fast; + } + }; + + /** + * \brief An abstract scrolling target. The values are first + * calculated when they are needed in scrollIdle(). + * + * (Note: perhaps a subclass should be uses for anchors.) + */ + class ScrollTarget + { + public: + virtual ~ScrollTarget (); + + virtual HPosition getHPos () = 0; + virtual VPosition getVPos () = 0; + virtual int getX () = 0; + virtual int getY () = 0; + virtual int getWidth () = 0; + virtual int getHeight () = 0; + }; + + class ScrollTargetBase: public ScrollTarget + { + HPosition hPos; + VPosition vPos; + + public: + ScrollTargetBase (HPosition hPos, VPosition vPos); + + HPosition getHPos (); + VPosition getVPos (); + }; + + /** + * \brief Scrolling target with concrete values. + */ + class ScrollTargetFixed: public ScrollTargetBase + { + int x, y, width, height; + + public: + ScrollTargetFixed (HPosition hPos, VPosition vPos, + int x, int y, int width, int height); + + int getX (); + int getY (); + int getWidth (); + int getHeight (); + }; + + /** + * \brief Scrolling target for a widget allocation. + * + * If the widget is allocated between scrollToWidget() and + * scrollIdle(), this is taken into account. + */ + class ScrollTargetWidget: public ScrollTargetBase + { + Widget *widget; + + public: + ScrollTargetWidget (HPosition hPos, VPosition vPos, Widget *widget); + + int getX (); + int getY (); + int getWidth (); + int getHeight (); + }; + + Platform *platform; + View *view; + Widget *topLevel, *widgetAtPoint; + lout::container::typed::Stack<QueueResizeItem> *queueQueueResizeList; + lout::container::typed::Vector<Widget> *queueResizeList; + + /* The state, which must be projected into the view. */ + style::Color *bgColor; + style::StyleImage *bgImage; + style::BackgroundRepeat bgRepeat; + style::BackgroundAttachment bgAttachment; + style::Length bgPositionX, bgPositionY; + + style::Cursor cursor; + int canvasWidth, canvasAscent, canvasDescent; + + bool usesViewport, drawAfterScrollReq; + int scrollX, scrollY, viewportWidth, viewportHeight; + bool canvasHeightGreater; + int hScrollbarThickness, vScrollbarThickness; + + ScrollTarget *scrollTarget; + + char *requestedAnchor; + int scrollIdleId, resizeIdleId; + bool scrollIdleNotInterrupted; + + /* Anchors of the widget tree */ + lout::container::typed::HashTable <lout::object::String, Anchor> + *anchorsTable; + + SelectionState selectionState; + FindtextState findtextState; + + enum ButtonEventType { BUTTON_PRESS, BUTTON_RELEASE, MOTION_NOTIFY }; + + void detachWidget (Widget *widget); + + Widget *getWidgetAtPoint (int x, int y); + void moveToWidget (Widget *newWidgetAtPoint, ButtonState state); + + /** + * \brief Emit the necessary crossing events, when the mouse pointer has + * moved to position (\em x, \em ); + */ + void moveToWidgetAtPoint (int x, int y, ButtonState state) + { moveToWidget (getWidgetAtPoint (x, y), state); } + + /** + * \brief Emit the necessary crossing events, when the mouse pointer + * has moved out of the view. + */ + void moveOutOfView (ButtonState state) { moveToWidget (NULL, state); } + + bool processMouseEvent (MousePositionEvent *event, ButtonEventType type); + bool buttonEvent (ButtonEventType type, View *view, + int numPressed, int x, int y, ButtonState state, + int button); + void resizeIdle (); + void setSizeHints (); + void draw (View *view, Rectangle *area); + + void scrollTo0(ScrollTarget *scrollTarget, bool scrollingInterrupted); + void scrollIdle (); + void adjustScrollPos (); + static bool calcScrollInto (int targetValue, int requestedSize, + int *value, int viewportSize); + int currHScrollbarThickness(); + int currVScrollbarThickness(); + + void updateAnchor (); + + /* Widget */ + + char *addAnchor (Widget *widget, const char* name); + char *addAnchor (Widget *widget, const char* name, int y); + void changeAnchor (Widget *widget, char* name, int y); + void removeAnchor (Widget *widget, char* name); + void setCursor (style::Cursor cursor); + void updateCursor (); + void queueDraw (int x, int y, int width, int height); + void queueDrawExcept (int x, int y, int width, int height, + int ex, int ey, int ewidth, int eheight); + void queueResize (bool extremesChanged); + void removeWidget (); + + /* For tests regarding the respective Layout and (mostly) Widget + methods. Accessed by respective methods (enter..., leave..., + ...Entered) defined here and in Widget. */ + + int resizeIdleCounter, queueResizeCounter, sizeAllocateCounter, + sizeRequestCounter, getExtremesCounter; + + void enterResizeIdle () { resizeIdleCounter++; } + void leaveResizeIdle () { resizeIdleCounter--; } + +public: + Layout (Platform *platform); + ~Layout (); + + inline void connectLink (LinkReceiver *receiver) + { linkEmitter.connectLink (receiver); } + + inline bool emitLinkEnter (Widget *w, int link, int img, int x, int y) + { return linkEmitter.emitEnter (w, link, img, x, y); } + + inline bool emitLinkPress (Widget *w, int link, int img, + int x, int y, EventButton *event) + { return linkEmitter.emitPress (w, link, img, x, y, event); } + + inline bool emitLinkRelease (Widget *w, int link, int img, + int x, int y, EventButton *event) + { return linkEmitter.emitRelease (w, link, img, x, y, event); } + + inline bool emitLinkClick (Widget *w, int link, int img, + int x, int y, EventButton *event) + { return linkEmitter.emitClick (w, link, img, x, y, event); } + + lout::misc::ZoneAllocator *textZone; + + void addWidget (Widget *widget); + void setWidget (Widget *widget); + + void attachView (View *view); + void detachView (View *view); + + inline bool getUsesViewport () { return usesViewport; } + inline int getWidthViewport () { return viewportWidth; } + inline int getHeightViewport () { return viewportHeight; } + inline int getScrollPosX () { return scrollX; } + inline int getScrollPosY () { return scrollY; } + + /* public */ + + void scrollTo (HPosition hpos, VPosition vpos, + int x, int y, int width, int height); + void scrollToWidget (HPosition hpos, VPosition vpos, Widget *widget); + void scroll (ScrollCommand cmd); + void setAnchor (const char *anchor); + + /* View */ + + inline void expose (View *view, Rectangle *area) { + DBG_OBJ_ENTER ("draw", 0, "expose", "%d, %d, %d * %d", + area->x, area->y, area->width, area->height); + draw (view, area); + DBG_OBJ_LEAVE (); + } + + /** + * \brief This function is called by a view, to delegate a button press + * event. + * + * \em numPressed is 1 for simple presses, 2 for double presses etc. (more + * that 2 is never needed), \em x and \em y the world coordinates, and + * \em button the number of the button pressed. + */ + inline bool buttonPress (View *view, int numPressed, int x, int y, + ButtonState state, int button) + { + return buttonEvent (BUTTON_PRESS, view, numPressed, x, y, state, button); + } + + void containerSizeChanged (); + + /** + * \brief This function is called by a view, to delegate a button press + * event. + * + * Arguments are similar to dw::core::Layout::buttonPress. + */ + inline bool buttonRelease (View *view, int numPressed, int x, int y, + ButtonState state, int button) + { + return buttonEvent (BUTTON_RELEASE, view, numPressed, x, y, state, + button); + } + + bool motionNotify (View *view, int x, int y, ButtonState state); + void enterNotify (View *view, int x, int y, ButtonState state); + void leaveNotify (View *view, ButtonState state); + + void scrollPosChanged (View *view, int x, int y); + void viewportSizeChanged (View *view, int width, int height); + + inline Platform *getPlatform () + { + return platform; + } + + /* delegated */ + + inline int textWidth (style::Font *font, const char *text, int len) + { + return platform->textWidth (font, text, len); + } + + inline char *textToUpper (const char *text, int len) + { + return platform->textToUpper (text, len); + } + + inline char *textToLower (const char *text, int len) + { + return platform->textToLower (text, len); + } + + inline int nextGlyph (const char *text, int idx) + { + return platform->nextGlyph (text, idx); + } + + inline int prevGlyph (const char *text, int idx) + { + return platform->prevGlyph (text, idx); + } + + inline float dpiX () + { + return platform->dpiX (); + } + + inline float dpiY () + { + return platform->dpiY (); + } + + inline style::Font *createFont (style::FontAttrs *attrs, bool tryEverything) + { + return platform->createFont (attrs, tryEverything); + } + + inline bool fontExists (const char *name) + { + return platform->fontExists (name); + } + + inline style::Color *createColor (int color) + { + return platform->createColor (color); + } + + inline style::Tooltip *createTooltip (const char *text) + { + return platform->createTooltip (text); + } + + inline void cancelTooltip () + { + return platform->cancelTooltip (); + } + + inline Imgbuf *createImgbuf (Imgbuf::Type type, int width, int height, + double gamma) + { + return platform->createImgbuf (type, width, height, gamma); + } + + inline void copySelection(const char *text) + { + platform->copySelection(text); + } + + inline ui::ResourceFactory *getResourceFactory () + { + return platform->getResourceFactory (); + } + + inline void connect (Receiver *receiver) { + emitter.connectLayout (receiver); } + + /** \brief See dw::core::FindtextState::search. */ + inline FindtextState::Result search (const char *str, bool caseSens, + int backwards) + { return findtextState.search (str, caseSens, backwards); } + + /** \brief See dw::core::FindtextState::resetSearch. */ + inline void resetSearch () { findtextState.resetSearch (); } + + void setBgColor (style::Color *color); + void setBgImage (style::StyleImage *bgImage, + style::BackgroundRepeat bgRepeat, + style::BackgroundAttachment bgAttachment, + style::Length bgPositionX, style::Length bgPositionY); + + inline style::Color* getBgColor () { return bgColor; } + inline style::StyleImage* getBgImage () { return bgImage; } +}; + +} // namespace core +} // namespace dw + +#endif // __DW_LAYOUT_HH__ + diff --git a/dw/platform.hh b/dw/platform.hh new file mode 100644 index 0000000..227cda3 --- /dev/null +++ b/dw/platform.hh @@ -0,0 +1,171 @@ +#ifndef __DW_PLATFORM_HH__ +#define __DW_PLATFORM_HH__ + +#ifndef __INCLUDED_FROM_DW_CORE_HH__ +# error Do not include this file directly, use "core.hh" instead. +#endif + +namespace dw { +namespace core { + +/** + * \brief An interface to encapsulate some platform dependencies. + * + * \sa\ref dw-overview + */ +class Platform: public lout::object::Object +{ +public: + /* + * ----------------------------------- + * General + * ----------------------------------- + */ + + /** + * \brief This methods notifies the platform, that it has been attached to + * a layout. + */ + virtual void setLayout (Layout *layout) = 0; + + /* + * ------------------------- + * Operations on views + * ------------------------- + */ + + /** + * \brief This methods notifies the platform, that a view has been attached + * to the related layout. + */ + virtual void attachView (View *view) = 0; + + /** + * \brief This methods notifies the platform, that a view has been detached + * from the related layout. + */ + virtual void detachView (View *view) = 0; + + /* + * ----------------------------------- + * Platform dependent properties + * ----------------------------------- + */ + + /** + * \brief Return the width of a text, with a given length and font. + */ + virtual int textWidth (style::Font *font, const char *text, int len) = 0; + + /** + * \brief Return the string resulting from transforming text to uppercase. + */ + virtual char *textToUpper (const char *text, int len) = 0; + + /** + * \brief Return the string resulting from transforming text to lowercase. + */ + virtual char *textToLower (const char *text, int len) = 0; + + /** + * \brief Return the index of the next glyph in string text. + */ + virtual int nextGlyph (const char *text, int idx) = 0; + + /** + * \brief Return the index of the previous glyph in string text. + */ + virtual int prevGlyph (const char *text, int idx) = 0; + + /** + * \brief Return screen resolution in x-direction. + */ + virtual float dpiX () = 0; + + /** + * \brief Return screen resolution in y-direction. + */ + virtual float dpiY () = 0; + + /* + * --------------------------------------------------------- + * These are to encapsulate some platform dependencies + * --------------------------------------------------------- + */ + + /** + * \brief Add an idle function. + * + * An idle function is called once, when no other + * tasks are to be done (e.g. there are no events to process), and then + * removed from the queue. The return value is a number, which can be + * used in removeIdle below. + */ + virtual int addIdle (void (Layout::*func) ()) = 0; + + /** + * \brief Remove an idle function, which has not been processed yet. + */ + virtual void removeIdle (int idleId) = 0; + + /* + * --------------------- + * Style Resources + * --------------------- + */ + + /** + * \brief Create a (platform dependent) font. + * + * Typically, within a platform, a sub class of dw::core::style::Font + * is defined, which holds more platform dependent data. + * + * Also, this method must fill the attributes "font" (when needed), + * "ascent", "descent", "spaceSidth" and "xHeight". If "tryEverything" + * is true, several methods should be used to use another font, when + * the requested font is not available. Passing false is typically done, + * if the caller wants to test different variations. + */ + virtual style::Font *createFont (style::FontAttrs *attrs, + bool tryEverything) = 0; + + virtual bool fontExists (const char *name) = 0; + + /** + * \brief Create a color resource for a given 0xrrggbb value. + */ + virtual style::Color *createColor (int color) = 0; + + /** + * \brief Create a tooltip + */ + virtual style::Tooltip *createTooltip (const char *text) = 0; + + /** + * \brief Cancel a tooltip (either shown or requested) + */ + virtual void cancelTooltip () = 0; + + /** + * \brief Create a (platform speficic) image buffer. + * + * "gamma" is the value by which the image data is gamma-encoded. + */ + virtual Imgbuf *createImgbuf (Imgbuf::Type type, int width, int height, + double gamma) = 0; + + /** + * \brief Copy selected text (0-terminated). + */ + virtual void copySelection(const char *text) = 0; + + /** + * ... + */ + virtual ui::ResourceFactory *getResourceFactory () = 0; +}; + +} // namespace core +} // namespace dw + +#endif // __DW_PLATFORM_HH__ diff --git a/dw/preview.xbm b/dw/preview.xbm new file mode 100644 index 0000000..85ea829 --- /dev/null +++ b/dw/preview.xbm @@ -0,0 +1,5 @@ +#define preview_width 11 +#define preview_height 11 +static unsigned char preview_bits[] = { + 0x20, 0x00, 0x70, 0x00, 0x20, 0x00, 0x20, 0x00, 0x22, 0x02, 0xff, 0x07, + 0x22, 0x02, 0x20, 0x00, 0x20, 0x00, 0x70, 0x00, 0x20, 0x00}; diff --git a/dw/selection.cc b/dw/selection.cc new file mode 100644 index 0000000..f5c7bda --- /dev/null +++ b/dw/selection.cc @@ -0,0 +1,494 @@ +/* + * RTFL (originally part of dillo) + * + * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version; with the following exception: + * + * The copyright holders of RTFL give you permission to link this file + * statically or dynamically against all versions of the graphviz + * library, which are published by AT&T Corp. under one of the following + * licenses: + * + * - Common Public License version 1.0 as published by International + * Business Machines Corporation (IBM), or + * - Eclipse Public License version 1.0 as published by the Eclipse + * Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + + +#include "core.hh" +#include "../lout/debug.hh" + +#include <string.h> + +using namespace lout; + +/* + * strndup() is a GNU extension. + */ +extern "C" char *strndup(const char *s, size_t size) +{ + char *r = (char *) malloc (size + 1); + + if (r) { + strncpy (r, s, size); + r[size] = 0; + } + + return r; +} + +namespace dw { +namespace core { + +SelectionState::SelectionState () +{ + DBG_OBJ_CREATE ("dw::core::SelectionState"); + + layout = NULL; + + selectionState = NONE; + from = NULL; + to = NULL; + + linkState = LINK_NONE; + link = NULL; +} + +SelectionState::~SelectionState () +{ + reset (); + DBG_OBJ_DELETE (); +} + +void SelectionState::reset () +{ + resetSelection (); + resetLink (); +} + +void SelectionState::resetSelection () +{ + if (from) + delete from; + from = NULL; + if (to) + delete to; + to = NULL; + selectionState = NONE; +} + + +void SelectionState::resetLink () +{ + if (link) + delete link; + link = NULL; + linkState = LINK_NONE; +} + +bool SelectionState::buttonPress (Iterator *it, int charPos, int linkNo, + EventButton *event) +{ + Widget *itWidget = it->getWidget (); + bool ret = false; + + if (!event) return ret; + + if (event->button == 3) { + // menu popup + layout->emitLinkPress (itWidget, linkNo, -1, -1, -1, event); + ret = true; + } else if (linkNo != -1) { + // link handling + (void) layout->emitLinkPress (itWidget, linkNo, -1, -1, -1, event); + resetLink (); + linkState = LINK_PRESSED; + linkButton = event->button; + DeepIterator *newLink = new DeepIterator (it); + if (newLink->isEmpty ()) { + delete newLink; + resetLink (); + } else { + link = newLink; + // It may be that the user has pressed on something activatable + // (linkNo != -1), but there is no contents, e.g. with images + // without ALTernative text. + if (link) { + linkChar = correctCharPos (link, charPos); + linkNumber = linkNo; + } + } + // We do not return the value of the signal method, + // but we do actually process this event. + ret = true; + } else if (event->button == 1) { + // normal selection handling + highlight (false, 0); + resetSelection (); + + selectionState = SELECTING; + DeepIterator *newFrom = new DeepIterator (it); + if (newFrom->isEmpty ()) { + delete newFrom; + resetSelection (); + } else { + from = newFrom; + fromChar = correctCharPos (from, charPos); + to = from->cloneDeepIterator (); + toChar = correctCharPos (to, charPos); + } + ret = true; + } + + return ret; +} + +bool SelectionState::buttonRelease (Iterator *it, int charPos, int linkNo, + EventButton *event) +{ + Widget *itWidget = it->getWidget (); + bool ret = false; + + if (linkState == LINK_PRESSED && event && event->button == linkButton) { + // link handling + ret = true; + if (linkNo != -1) + (void) layout->emitLinkRelease (itWidget, linkNo, -1, -1, -1, event); + + // The link where the user clicked the mouse button? + if (linkNo == linkNumber) { + resetLink (); + (void) layout->emitLinkClick (itWidget, linkNo, -1, -1, -1, event); + } else { + if (event->button == 1) + // Reset links and switch to selection mode. The selection + // state will be set to SELECTING, which is handled some lines + // below. + switchLinkToSelection (it, charPos); + } + } + + if (selectionState == SELECTING && event && event->button == 1) { + // normal selection + ret = true; + adjustSelection (it, charPos); + + if (from->compareTo (to) == 0 && fromChar == toChar) + // nothing selected + resetSelection (); + else { + copy (); + selectionState = SELECTED; + } + } + + return ret; +} + +bool SelectionState::buttonMotion (Iterator *it, int charPos, int linkNo, + EventMotion *event) +{ + if (linkState == LINK_PRESSED) { + //link handling + if (linkNo != linkNumber) + // No longer the link where the user clicked the mouse button. + // Reset links and switch to selection mode. + switchLinkToSelection (it, charPos); + // Still in link: do nothing. + } else if (selectionState == SELECTING) { + // selection + adjustSelection (it, charPos); + } + + return true; +} + +/** + * \brief General form of dw::core::SelectionState::buttonPress, + * dw::core::SelectionState::buttonRelease and + * dw::core::SelectionState::buttonMotion. + */ +bool SelectionState::handleEvent (EventType eventType, Iterator *it, + int charPos, int linkNo, + MousePositionEvent *event) +{ + switch (eventType) { + case BUTTON_PRESS: + return buttonPress (it, charPos, linkNo, (EventButton*)event); + + case BUTTON_RELEASE: + return buttonRelease (it, charPos, linkNo, (EventButton*)event); + + case BUTTON_MOTION: + return buttonMotion (it, charPos, linkNo, (EventMotion*)event); + + + default: + misc::assertNotReached (); + } + + return false; +} + + +/** + * \brief This method is called when the user decides not to activate a link, + * but instead select text. + */ +void SelectionState::switchLinkToSelection (Iterator *it, int charPos) +{ + // It may be that selection->link is NULL, see a_Selection_button_press. + if (link) { + // Reset old selection. + highlight (false, 0); + resetSelection (); + + // Transfer link state into selection state. + from = link->cloneDeepIterator (); + fromChar = linkChar; + to = from->createVariant (it); + toChar = correctCharPos (to, charPos); + selectionState = SELECTING; + + // Reset link status. + resetLink (); + + highlight (true, 0); + + } else { + // A link was pressed on, but there is nothing to select. Reset + // everything. + resetSelection (); + resetLink (); + } +} + +/** + * \brief This method is used by core::dw::SelectionState::buttonMotion and + * core::dw::SelectionState::buttonRelease, and changes the second limit of + * the already existing selection region. + */ +void SelectionState::adjustSelection (Iterator *it, int charPos) +{ + DeepIterator *newTo; + int newToChar, cmpOld, cmpNew, cmpDiff, len; + bool bruteHighlighting = false; + + newTo = to->createVariant (it); + newToChar = correctCharPos (newTo, charPos); + + cmpOld = to->compareTo (from); + cmpNew = newTo->compareTo (from); + + if (cmpOld == 0 || cmpNew == 0) { + // Either before, or now, the limits differ only by the character + // position. + bruteHighlighting = true; + } else if (cmpOld * cmpNew < 0) { + // The selection order has changed, i.e. the user moved the selection + // end again beyond the position he started. + bruteHighlighting = true; + } else { + // Here, cmpOld and cmpNew are equivalent and != 0. + cmpDiff = newTo->compareTo (to); + + if (cmpOld * cmpDiff > 0) { + // The user has enlarged the selection. Highlight the difference. + if (cmpDiff < 0) { + len = correctCharPos (to, END_OF_WORD); + highlight0 (true, newTo, newToChar, to, len + 1, 1); + } else { + highlight0 (true, to, 0, newTo, newToChar, -1); + } + } else { + if (cmpOld * cmpDiff < 0) { + // The user has reduced the selection. Unhighlight the difference. + highlight0 (false, to, 0, newTo, 0, cmpDiff); + } + + // Otherwise, the user has changed the position only slightly. + // In both cases, re-highlight the new position. + if (cmpOld < 0) { + len = correctCharPos (newTo, END_OF_WORD); + newTo->highlight (newToChar, len + 1, HIGHLIGHT_SELECTION); + } else + newTo->highlight (0, newToChar, HIGHLIGHT_SELECTION); + } + } + + if (bruteHighlighting) + highlight (false, 0); + + delete to; + to = newTo; + toChar = newToChar; + + if (bruteHighlighting) + highlight (true, 0); +} + +/** + * \brief This method deals especially with the case that a widget passes + * dw::core::SelectionState::END_OF_WORD. + */ +int SelectionState::correctCharPos (DeepIterator *it, int charPos) +{ + Iterator *top = it->getTopIterator (); + int len; + + if (top->getContent()->type == Content::TEXT) + len = strlen(top->getContent()->text); + else + len = 1; + + return misc::min(charPos, len); +} + +void SelectionState::highlight0 (bool fl, DeepIterator *from, int fromChar, + DeepIterator *to, int toChar, int dir) +{ + DeepIterator *a, *b, *i; + int cmp, aChar, bChar; + bool start; + + if (from && to) { + cmp = from->compareTo (to); + if (cmp == 0) { + if (fl) { + if (fromChar < toChar) + from->highlight (fromChar, toChar, HIGHLIGHT_SELECTION); + else + from->highlight (toChar, fromChar, HIGHLIGHT_SELECTION); + } else + from->unhighlight (0, HIGHLIGHT_SELECTION); + return; + } + + if (cmp < 0) { + a = from; + aChar = fromChar; + b = to; + bChar = toChar; + } else { + a = to; + aChar = toChar; + b = from; + bChar = fromChar; + } + + for (i = a->cloneDeepIterator (), start = true; + (cmp = i->compareTo (b)) <= 0; + i->next (), start = false) { + if (i->getContent()->type == Content::TEXT) { + if (fl) { + if (start) { + i->highlight (aChar, strlen (i->getContent()->text) + 1, + HIGHLIGHT_SELECTION); + } else if (cmp == 0) { + // the end + i->highlight (0, bChar, HIGHLIGHT_SELECTION); + } else { + i->highlight (0, strlen (i->getContent()->text) + 1, + HIGHLIGHT_SELECTION); + } + } else { + i->unhighlight (dir, HIGHLIGHT_SELECTION); + } + } + } + delete i; + } +} + +void SelectionState::copy() +{ + if (from && to) { + Iterator *si; + DeepIterator *a, *b, *i; + int cmp, aChar, bChar; + bool start; + char *tmp; + misc::StringBuffer strbuf; + + cmp = from->compareTo (to); + if (cmp == 0) { + if (from->getContent()->type == Content::TEXT) { + si = from->getTopIterator (); + if (fromChar < toChar) + tmp = strndup (si->getContent()->text + fromChar, + toChar - fromChar); + else + tmp = strndup (si->getContent()->text + toChar, + fromChar - toChar); + strbuf.appendNoCopy (tmp); + } + } else { + if (cmp < 0) { + a = from; + aChar = fromChar; + b = to; + bChar = toChar; + } else { + a = to; + aChar = toChar; + b = from; + bChar = fromChar; + } + + for (i = a->cloneDeepIterator (), start = true; + (cmp = i->compareTo (b)) <= 0; + i->next (), start = false) { + si = i->getTopIterator (); + switch (si->getContent()->type) { + case Content::TEXT: + if (start) { + tmp = strndup (si->getContent()->text + aChar, + strlen (i->getContent()->text) - aChar); + strbuf.appendNoCopy (tmp); + } else if (cmp == 0) { + // the end + tmp = strndup (si->getContent()->text, bChar); + strbuf.appendNoCopy (tmp); + } else + strbuf.append (si->getContent()->text); + + if (si->getContent()->space && cmp != 0) + strbuf.append (" "); + + break; + + case Content::BREAK: + if (si->getContent()->breakSpace > 0) + strbuf.append ("\n\n"); + else + strbuf.append ("\n"); + break; + default: + // Make pedantic compilers happy. Especially + // DW_CONTENT_WIDGET is never returned by a DwDeepIterator. + break; + } + } + delete i; + } + + layout->copySelection(strbuf.getChars()); + } +} + +} // namespace core +} // namespace dw diff --git a/dw/selection.hh b/dw/selection.hh new file mode 100644 index 0000000..ef9df0e --- /dev/null +++ b/dw/selection.hh @@ -0,0 +1,241 @@ +#ifndef __DW_SELECTION_H__ +#define __DW_SELECTION_H__ + +#ifndef __INCLUDED_FROM_DW_CORE_HH__ +# error Do not include this file directly, use "core.hh" instead. +#endif + +namespace dw { +namespace core { + +/** + * \brief This class handles selections, as well as activation of links, + * which is closely related. + * + * <h3>General Overview</h3> + * + * dw::core::SelectionState is associated with dw::core::Layout. The selection + * state is controlled by "abstract events", which are sent by single + * widgets by calling one of the following methods: + * + * <ul> + * <li> dw::core::SelectionState::buttonPress for button press events, + * <li> dw::core::SelectionState::buttonRelease for button release events, and + * <li> dw::core::SelectionState::buttonMotion for motion events (with pressed + * mouse button). + * </ul> + * + * The widget must construct simple iterators (dw::core::Iterator), which will + * be transferred to deep iterators (dw::core::DeepIterator), see below for + * more details. All event handling methods have the same signature, the + * arguments in detail are: + * + * <table> + * <tr><td>dw::core::Iterator *it <td>the iterator pointing on the item + * under the mouse pointer; this + * iterator \em must be created with + * dw::core::Content::SELECTION_CONTENT + * as mask + * <tr><td>int charPos <td>the exact (character) position + * within the iterator, + * <tr><td>int linkNo <td>if this item is associated with a + * link, its number (see + * dw::core::Layout::LinkReceiver), + * otherwise -1 + * <tr><td>dw::core::EventButton *event <td>the event itself; only the button + * is used + * </table> + * + * Look also at dw::core::SelectionState::handleEvent, which may be useful + * in some circumstances. + * + * In some cases, \em charPos would be difficult to determine. E.g., when + * the dw::Textblock widget decides that the user is pointing on a position + * <i>at the end</i> of an image (DwImage), it constructs a simple iterator + * pointing on this image widget. In a simple iterator, that fact that + * the pointer is at the end, would be represented by \em charPos == 1. But + * when transferring this simple iterator into an deep iterator, this + * simple iterator is discarded and instead the stack has an iterator + * pointing to text at the top. As a result, only the first letter of the + * ALT text would be copied. + * + * To avoid this problem, widgets should in this case pass + * dw::core::SelectionState::END_OF_WORD as \em charPos, which is then + * automatically reduced to the actual length of the deep(!) iterator. + * + * The return value is the same as in DwWidget event handling methods. + * I.e., in most cases, they should simply return it. The events + * dw::core::Layout::LinkReceiver::press, + * dw::core::Layout::LinkReceiver::release and + * dw::core::Layout::LinkReceiver::click (but not + * dw::core::Layout::LinkReceiver::enter) are emitted by these methods, so + * that widgets which let dw::core::SelectionState handle links, should only + * emit dw::core::Layout::LinkReceiver::enter for themselves. + * + * <h3>Selection State</h3> + * + * Selection interferes with handling the activation of links, so the + * latter is also handled by the dw::core::SelectionState. Details are based on + * following guidelines: + * + * <ol> + * <li> It should be simple to select links and to start selection in + * links. The rule to distinguish between link activation and + * selection is that the selection starts as soon as the user leaves + * the link. (This is, IMO, a useful feature. Even after drag and + * drop has been implemented in dillo, this should be somehow + * preserved.) + * + * <li> The selection should stay as long as possible, i.e., the old + * selection is only cleared when a new selection is started. + * </ol> + * + * The latter leads to a model with two states: the selection state and + * the link handling state. + * + * The general selection works, for events not pointing on links, like + * this (numbers in parantheses after the event denote the button, "n" + * means arbitrary button): + * + * \dot + * digraph G { + * node [shape=ellipse, fontname=Helvetica, fontsize=10]; + * edge [arrowhead="open", labelfontname=Helvetica, labelfontsize=10, + * color="#404040", labelfontcolor="#000080", + * fontname=Helvetica, fontsize=10, fontcolor="#000080"]; + * fontname=Helvetica; fontsize=10; + * + * NONE; + * SELECTING; + * q [label="Anything selected?", shape=plaintext]; + * SELECTED; + * + * NONE -> SELECTING [label="press(1)\non non-link"]; + * SELECTING -> SELECTING [label="motion(1)"]; + * SELECTING -> q [label="release(1)"]; + * q -> SELECTED [label="yes"]; + * q -> NONE [label="no"]; + * SELECTED -> SELECTING [label="press(1)"]; + * + * } + * \enddot + * + * The selected region is represented by two instances of + * dw::core::DeepIterator. + * + * Links are handled by a different state machine: + * + * \dot + * digraph G { + * node [shape=ellipse, fontname=Helvetica, fontsize=10]; + * edge [arrowhead="open", labelfontname=Helvetica, labelfontsize=10, + * color="#404040", labelfontcolor="#000080", + * fontname=Helvetica, fontsize=10, fontcolor="#000080"]; + * fontname=Helvetica; fontsize=10; + * + * LINK_NONE; + * LINK_PRESSED; + * click [label="Emit \"click\" signal.", shape=record]; + * q11 [label="Still the same link?", shape=plaintext]; + * q21 [label="Still the same link?", shape=plaintext]; + * q22 [label="n == 1?", shape=plaintext]; + * SELECTED [label="Switch selection\nto SELECTED", shape=record]; + * q12 [label="n == 1?", shape=plaintext]; + * SELECTING [label="Switch selection\nto SELECTING", shape=record]; + * + * LINK_NONE -> LINK_PRESSED [label="press(n)\non link"]; + * LINK_PRESSED -> q11 [label="motion(n)"]; + * q11 -> LINK_PRESSED [label="yes"]; + * q11 -> q12 [label="no"]; + * q12 -> SELECTING [label="yes"]; + * SELECTING -> LINK_NONE; + * q12 -> LINK_NONE [label="no"]; + * LINK_PRESSED -> q21 [label="release(n)"]; + * q21 -> click [label="yes"]; + * click -> LINK_NONE; + * q21 -> q22 [label="no"]; + * q22 -> SELECTED [label="yes"]; + * SELECTED -> LINK_NONE; + * q22 -> LINK_NONE [label="no"]; + * } + * \enddot + * + * Switching selection simply means that the selection state will + * eventually be SELECTED/SELECTING, with the original and the current + * position making up the selection region. This happens for button 1, + * events with buttons other than 1 do not affect selection at all. + * + * + * \todo dw::core::SelectionState::buttonMotion currently always assumes + * that button 1 has been pressed (since otherwise it would not do + * anything). This should be made a bit cleaner. + * + * \todo The selection should be cleared, when the user selects something + * somewhere else (perhaps switched into "non-active" mode, as e.g. Gtk+ + * does). + * + */ +class SelectionState +{ +public: + enum { END_OF_WORD = 1 << 30 }; + +private: + Layout *layout; + + // selection + enum { + NONE, + SELECTING, + SELECTED + } selectionState; + + DeepIterator *from, *to; + int fromChar, toChar; + + // link handling + enum { + LINK_NONE, + LINK_PRESSED + } linkState; + + int linkButton; + DeepIterator *link; + int linkChar, linkNumber; + + void resetSelection (); + void resetLink (); + void switchLinkToSelection (Iterator *it, int charPos); + void adjustSelection (Iterator *it, int charPos); + static int correctCharPos (DeepIterator *it, int charPos); + + void highlight (bool fl, int dir) + { highlight0 (fl, from, fromChar, to, toChar, dir); } + + void highlight0 (bool fl, DeepIterator *from, int fromChar, + DeepIterator *to, int toChar, int dir); + void copy (); + +public: + enum EventType { BUTTON_PRESS, BUTTON_RELEASE, BUTTON_MOTION }; + + SelectionState (); + ~SelectionState (); + + inline void setLayout (Layout *layout) { this->layout = layout; } + void reset (); + bool buttonPress (Iterator *it, int charPos, int linkNo, + EventButton *event); + bool buttonRelease (Iterator *it, int charPos, int linkNo, + EventButton *event); + bool buttonMotion (Iterator *it, int charPos, int linkNo, + EventMotion *event); + + bool handleEvent (EventType eventType, Iterator *it, int charPos, + int linkNo, MousePositionEvent *event); +}; + +} // namespace core +} // namespace dw + +#endif // __DW_SELECTION_H__ diff --git a/dw/style.cc b/dw/style.cc new file mode 100644 index 0000000..aaeb959 --- /dev/null +++ b/dw/style.cc @@ -0,0 +1,1468 @@ +/* + * RTFL (originally part of dillo) + * + * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version; with the following exception: + * + * The copyright holders of RTFL give you permission to link this file + * statically or dynamically against all versions of the graphviz + * library, which are published by AT&T Corp. under one of the following + * licenses: + * + * - Common Public License version 1.0 as published by International + * Business Machines Corporation (IBM), or + * - Eclipse Public License version 1.0 as published by the Eclipse + * Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <ctype.h> +#include <math.h> + +#include "core.hh" +#include "../lout/msg.h" + +using namespace lout; + +namespace dw { +namespace core { +namespace style { + +const bool drawBackgroundLineByLine = false; + +const int MIN_BG_IMG_W = 10; +const int MIN_BG_IMG_H = 10; +const int OPT_BG_IMG_W = 50; +const int OPT_BG_IMG_H = 50; + +static void calcBackgroundRelatedValues (StyleImage *backgroundImage, + BackgroundRepeat backgroundRepeat, + BackgroundAttachment + backgroundAttachment, + Length backgroundPositionX, + Length backgroundPositionY, + int xDraw, int yDraw, int widthDraw, + int heightDraw, int xRef, int yRef, + int widthRef, int heightRef, + bool *repeatX, bool *repeatY, + int *origX, int *origY, + int *tileX1, int *tileX2, int *tileY1, + int *tileY2, bool *doDraw); + +void StyleAttrs::initValues () +{ + x_link = -1; + x_lang[0] = x_lang[1] = 0; + x_img = -1; + x_tooltip = NULL; + textDecoration = TEXT_DECORATION_NONE; + textAlign = TEXT_ALIGN_LEFT; + textAlignChar = '.'; + textTransform = TEXT_TRANSFORM_NONE; + listStylePosition = LIST_STYLE_POSITION_OUTSIDE; + listStyleType = LIST_STYLE_TYPE_DISC; + valign = VALIGN_BASELINE; + backgroundColor = NULL; + backgroundImage = NULL; + backgroundRepeat = BACKGROUND_REPEAT; + backgroundAttachment = BACKGROUND_ATTACHMENT_SCROLL; + backgroundPositionX = createPerLength (0); + backgroundPositionY = createPerLength (0); + width = height = lineHeight = LENGTH_AUTO; + minWidth = maxWidth = minHeight = maxHeight = LENGTH_AUTO; + vloat = FLOAT_NONE; + clear = CLEAR_NONE; + overflow = OVERFLOW_VISIBLE; + position = POSITION_STATIC; + top = bottom = left = right = LENGTH_AUTO; + textIndent = 0; + margin.setVal (0); + borderWidth.setVal (0); + padding.setVal (0); + borderCollapse = BORDER_MODEL_SEPARATE; + setBorderColor (NULL); + setBorderStyle (BORDER_NONE); + hBorderSpacing = 0; + vBorderSpacing = 0; + wordSpacing = 0; + + display = DISPLAY_INLINE; + whiteSpace = WHITE_SPACE_NORMAL; + cursor = CURSOR_DEFAULT; +} + +/** + * \brief Reset those style attributes to their standard values, which are + * not inherited, according to CSS. + */ +void StyleAttrs::resetValues () +{ + x_img = -1; + + valign = VALIGN_BASELINE; + textAlignChar = '.'; + vloat = FLOAT_NONE; /** \todo Correct? Check specification. */ + clear = CLEAR_NONE; /** \todo Correct? Check specification. */ + overflow = OVERFLOW_VISIBLE; + position = POSITION_STATIC; /** \todo Correct? Check specification. */ + top = bottom = left = right = LENGTH_AUTO; /** \todo Correct? Check + specification. */ + backgroundColor = NULL; + backgroundImage = NULL; + backgroundRepeat = BACKGROUND_REPEAT; + backgroundAttachment = BACKGROUND_ATTACHMENT_SCROLL; + backgroundPositionX = createPerLength (0); + backgroundPositionY = createPerLength (0); + width = LENGTH_AUTO; + height = LENGTH_AUTO; + minWidth = maxWidth = minHeight = maxHeight = LENGTH_AUTO; + + margin.setVal (0); + borderWidth.setVal (0); + padding.setVal (0); + setBorderColor (NULL); + setBorderStyle (BORDER_NONE); + hBorderSpacing = 0; + vBorderSpacing = 0; + + display = DISPLAY_INLINE; +} + +/** + * \brief This method returns whether something may change its size, when + * its style changes from this style to \em otherStyle. + * + * It is mainly for optimizing style changes where only colors etc change + * (where false would be returned), in some cases it may return true, although + * a size change does not actually happen (e.g. when in a certain + * context a particular attribute is ignored). + * + * \todo Should for CSS implemented properly. Currently, size changes are + * not needed, so always false is returned. See also + * dw::core::Widget::setStyle. + */ +bool StyleAttrs::sizeDiffs (StyleAttrs *otherStyle) +{ + return false; +} + +bool StyleAttrs::equals (object::Object *other) { + StyleAttrs *otherAttrs = (StyleAttrs *) other; + + return this == otherAttrs || + (font == otherAttrs->font && + textDecoration == otherAttrs->textDecoration && + color == otherAttrs->color && + backgroundColor == otherAttrs->backgroundColor && + backgroundImage == otherAttrs->backgroundImage && + backgroundRepeat == otherAttrs->backgroundRepeat && + backgroundAttachment == otherAttrs->backgroundAttachment && + backgroundPositionX == otherAttrs->backgroundPositionX && + backgroundPositionY == otherAttrs->backgroundPositionY && + textAlign == otherAttrs->textAlign && + valign == otherAttrs->valign && + textAlignChar == otherAttrs->textAlignChar && + textTransform == otherAttrs->textTransform && + vloat == otherAttrs->vloat && + clear == otherAttrs->clear && + overflow == otherAttrs->overflow && + position == otherAttrs->position && + top == otherAttrs->top && + bottom == otherAttrs->bottom && + left == otherAttrs->left && + right == otherAttrs->right && + hBorderSpacing == otherAttrs->hBorderSpacing && + vBorderSpacing == otherAttrs->vBorderSpacing && + wordSpacing == otherAttrs->wordSpacing && + width == otherAttrs->width && + height == otherAttrs->height && + minWidth == otherAttrs->minWidth && + maxWidth == otherAttrs->maxWidth && + minHeight == otherAttrs->minHeight && + maxHeight == otherAttrs->maxHeight && + lineHeight == otherAttrs->lineHeight && + textIndent == otherAttrs->textIndent && + margin.equals (&otherAttrs->margin) && + borderWidth.equals (&otherAttrs->borderWidth) && + padding.equals (&otherAttrs->padding) && + borderCollapse == otherAttrs->borderCollapse && + borderColor.top == otherAttrs->borderColor.top && + borderColor.right == otherAttrs->borderColor.right && + borderColor.bottom == otherAttrs->borderColor.bottom && + borderColor.left == otherAttrs->borderColor.left && + borderStyle.top == otherAttrs->borderStyle.top && + borderStyle.right == otherAttrs->borderStyle.right && + borderStyle.bottom == otherAttrs->borderStyle.bottom && + borderStyle.left == otherAttrs->borderStyle.left && + display == otherAttrs->display && + whiteSpace == otherAttrs->whiteSpace && + listStylePosition == otherAttrs->listStylePosition && + listStyleType == otherAttrs->listStyleType && + cursor == otherAttrs->cursor && + x_link == otherAttrs->x_link && + x_lang[0] == otherAttrs->x_lang[0] && + x_lang[1] == otherAttrs->x_lang[1] && + x_img == otherAttrs->x_img && + x_tooltip == otherAttrs->x_tooltip); +} + +int StyleAttrs::hashValue () { + return (intptr_t) font + + textDecoration + + (intptr_t) color + + (intptr_t) backgroundColor + + (intptr_t) backgroundImage + + backgroundRepeat + + backgroundAttachment + + backgroundPositionX + + backgroundPositionY + + textAlign + + valign + + textAlignChar + + textTransform + + vloat + + clear + + overflow + + position + + top + + bottom + + left + + right + + hBorderSpacing + + vBorderSpacing + + wordSpacing + + width + + height + + minWidth + + maxWidth + + minHeight + + maxHeight + + lineHeight + + textIndent + + margin.hashValue () + + borderWidth.hashValue () + + padding.hashValue () + + borderCollapse + + (intptr_t) borderColor.top + + (intptr_t) borderColor.right + + (intptr_t) borderColor.bottom + + (intptr_t) borderColor.left + + borderStyle.top + + borderStyle.right + + borderStyle.bottom + + borderStyle.left + + display + + whiteSpace + + listStylePosition + + listStyleType + + cursor + + x_link + + x_lang[0] + x_lang[1] + + x_img + + (intptr_t) x_tooltip; +} + +int Style::totalRef = 0; +container::typed::HashTable <StyleAttrs, Style> * Style::styleTable = + new container::typed::HashTable <StyleAttrs, Style> (false, false, 1024); + +Style::Style (StyleAttrs *attrs) +{ + DBG_OBJ_CREATE ("dw::core::style::Style"); + + copyAttrs (attrs); + + DBG_OBJ_ASSOC_CHILD (font); + DBG_OBJ_ASSOC_CHILD (color); + DBG_OBJ_ASSOC_CHILD (backgroundColor); + DBG_OBJ_ASSOC_CHILD (backgroundImage); + DBG_OBJ_ASSOC_CHILD (borderColor.top); + DBG_OBJ_ASSOC_CHILD (borderColor.bottom); + DBG_OBJ_ASSOC_CHILD (borderColor.left); + DBG_OBJ_ASSOC_CHILD (borderColor.right); + //DBG_OBJ_ASSOC_CHILD (x_tooltip); + + refCount = 1; + + font->ref (); + if (color) + color->ref (); + if (backgroundColor) + backgroundColor->ref (); + if (backgroundImage) + backgroundImage->ref (); + if (borderColor.top) + borderColor.top->ref(); + if (borderColor.bottom) + borderColor.bottom->ref(); + if (borderColor.left) + borderColor.left->ref(); + if (borderColor.right) + borderColor.right->ref(); + if (x_tooltip) + x_tooltip->ref(); + + totalRef++; +} + +Style::~Style () +{ + font->unref (); + + if (color) + color->unref (); + if (backgroundColor) + backgroundColor->unref (); + if (backgroundImage) + backgroundImage->unref (); + if (borderColor.top) + borderColor.top->unref(); + if (borderColor.bottom) + borderColor.bottom->unref(); + if (borderColor.left) + borderColor.left->unref(); + if (borderColor.right) + borderColor.right->unref(); + if (x_tooltip) + x_tooltip->unref(); + + styleTable->remove (this); + totalRef--; + + DBG_OBJ_DELETE (); +} + +void Style::copyAttrs (StyleAttrs *attrs) +{ + font = attrs->font; + textDecoration = attrs->textDecoration; + color = attrs->color; + backgroundColor = attrs->backgroundColor; + backgroundImage = attrs->backgroundImage; + backgroundRepeat = attrs->backgroundRepeat; + backgroundAttachment = attrs->backgroundAttachment; + backgroundPositionX = attrs->backgroundPositionX; + backgroundPositionY = attrs->backgroundPositionY; + textAlign = attrs->textAlign; + valign = attrs->valign; + textAlignChar = attrs->textAlignChar; + textTransform = attrs->textTransform; + vloat = attrs->vloat; + clear = attrs->clear; + overflow = attrs->overflow; + position = attrs->position; + top = attrs->top; + bottom = attrs->bottom; + left = attrs->left; + right = attrs->right; + hBorderSpacing = attrs->hBorderSpacing; + vBorderSpacing = attrs->vBorderSpacing; + wordSpacing = attrs->wordSpacing; + width = attrs->width; + height = attrs->height; + lineHeight = attrs->lineHeight; + textIndent = attrs->textIndent; + minWidth = attrs->minWidth; + maxWidth = attrs->maxWidth; + minHeight = attrs->minHeight; + maxHeight = attrs->maxHeight; + margin = attrs->margin; + borderWidth = attrs->borderWidth; + padding = attrs->padding; + borderCollapse = attrs->borderCollapse; + borderColor = attrs->borderColor; + borderStyle = attrs->borderStyle; + display = attrs->display; + whiteSpace = attrs->whiteSpace; + listStylePosition = attrs->listStylePosition; + listStyleType = attrs->listStyleType; + cursor = attrs->cursor; + x_link = attrs->x_link; + x_lang[0] = attrs->x_lang[0]; + x_lang[1] = attrs->x_lang[1]; + x_img = attrs->x_img; + x_tooltip = attrs->x_tooltip; +} + +// ---------------------------------------------------------------------- + +bool FontAttrs::equals(object::Object *other) +{ + FontAttrs *otherAttrs = (FontAttrs*)other; + return + this == otherAttrs || + (size == otherAttrs->size && + weight == otherAttrs->weight && + style == otherAttrs->style && + letterSpacing == otherAttrs->letterSpacing && + fontVariant == otherAttrs->fontVariant && + strcmp (name, otherAttrs->name) == 0); +} + +int FontAttrs::hashValue() +{ + int h = object::String::hashValue (name); + h = (h << 5) - h + size; + h = (h << 5) - h + weight; + h = (h << 5) - h + style; + h = (h << 5) - h + letterSpacing; + h = (h << 5) - h + fontVariant; + return h; +} + +Font::~Font () +{ + free ((char*)name); + DBG_OBJ_DELETE (); +} + +void Font::copyAttrs (FontAttrs *attrs) +{ + name = strdup (attrs->name); + size = attrs->size; + weight = attrs->weight; + style = attrs->style; + letterSpacing = attrs->letterSpacing; + fontVariant = attrs->fontVariant; +} + +Font *Font::create0 (Layout *layout, FontAttrs *attrs, + bool tryEverything) +{ + return layout->createFont (attrs, tryEverything); +} + +Font *Font::create (Layout *layout, FontAttrs *attrs) +{ + return create0 (layout, attrs, false); +} + +bool Font::exists (Layout *layout, const char *name) +{ + return layout->fontExists (name); +} + +// ---------------------------------------------------------------------- + +bool ColorAttrs::equals(object::Object *other) +{ + ColorAttrs *oc = (ColorAttrs*)other; + return this == oc || (color == oc->color); +} + +int ColorAttrs::hashValue() +{ + return color; +} + +Color::~Color () +{ + DBG_OBJ_DELETE (); +} + +int Color::shadeColor (int color, int d) +{ + int red = (color >> 16) & 255; + int green = (color >> 8) & 255; + int blue = color & 255; + + double oldLightness = ((double) misc::max (red, green, blue)) / 255; + double newLightness; + + if (oldLightness > 0.8) { + if (d > 0) + newLightness = oldLightness - 0.2; + else + newLightness = oldLightness - 0.4; + } else if (oldLightness < 0.2) { + if (d > 0) + newLightness = oldLightness + 0.4; + else + newLightness = oldLightness + 0.2; + } else + newLightness = oldLightness + d * 0.2; + + if (oldLightness) { + double f = (newLightness / oldLightness); + red = (int)(red * f); + green = (int)(green * f); + blue = (int)(blue * f); + } else { + red = green = blue = (int)(newLightness * 255); + } + + return (red << 16) | (green << 8) | blue; +} + +int Color::shadeColor (int color, Shading shading) +{ + switch (shading) { + case SHADING_NORMAL: + return color; + + case SHADING_LIGHT: + return shadeColor(color, +1); + + case SHADING_INVERSE: + return color ^ 0xffffff; + + case SHADING_DARK: + return shadeColor(color, -1); + + default: + // compiler happiness + misc::assertNotReached (); + return -1; + } +} + + +Color *Color::create (Layout *layout, int col) +{ + ColorAttrs attrs(col); + + return layout->createColor (col); +} + +Tooltip *Tooltip::create (Layout *layout, const char *text) +{ + return layout->createTooltip (text); +} + +// ---------------------------------------------------------------------- + +void StyleImage::StyleImgRenderer::setBuffer (core::Imgbuf *buffer, bool resize) +{ + if (image->imgbufSrc) + image->imgbufSrc->unref (); + if (image->imgbufTiled) + image->imgbufTiled->unref (); + + image->imgbufTiled = NULL; + + image->imgbufSrc = buffer; + DBG_OBJ_ASSOC (image, image->imgbufSrc); + + if (image->imgbufSrc) { + image->imgbufSrc->ref (); + + // If the image is too small, drawing a background will cause + // many calls of View::drawImgbuf. For this reason, we create + // another image buffer, the "tiled" image buffer, which is + // larger (the "optimal" size is defined as OPT_BG_IMG_W * + // OPT_BG_IMG_H) and contains the "source" buffer several times. + // + // This "tiled" buffer is not used when 'background-repeat' has + // another value than 'repeat', for obvious reasons. Image + // buffers only "tiled" in one dimension (to optimize 'repeat-x' + // and 'repeat-y') are not supported. + + if (image->imgbufSrc->getRootWidth() * image->imgbufSrc->getRootHeight() + < MIN_BG_IMG_W * MIN_BG_IMG_H) { + image->tilesX = + misc::max (OPT_BG_IMG_W / image->imgbufSrc->getRootWidth(), 1); + image->tilesY = + misc::max (OPT_BG_IMG_H / image->imgbufSrc->getRootHeight(), 1); + image->imgbufTiled = + image->imgbufSrc->createSimilarBuf + (image->tilesX * image->imgbufSrc->getRootWidth(), + image->tilesY * image->imgbufSrc->getRootHeight()); + + DBG_OBJ_ASSOC (image, image->imgbufTiled); + } + } +} + +void StyleImage::StyleImgRenderer::drawRow (int row) +{ + if (image->imgbufTiled) { + // A row of data has been copied to the source buffer, here it + // is copied into the tiled buffer. + + // Unfortunately, this code may be called *after* some other + // implementations of ImgRenderer::drawRow, which actually + // *draw* the tiled buffer, which is so not up to date + // (ImgRendererDist does not define an order). OTOH, these + // drawing implementations calle Widget::queueResize, so the + // actual drawing (and so access to the tiled buffer) is done + // later. + + int w = image->imgbufSrc->getRootWidth (); + int h = image->imgbufSrc->getRootHeight (); + + for (int x = 0; x < image->tilesX; x++) + for (int y = 0; y < image->tilesX; y++) + image->imgbufSrc->copyTo (image->imgbufTiled, x * w, y * h, + 0, row, w, 1); + } +} + +void StyleImage::StyleImgRenderer::finish () +{ + // Nothing to do. +} + +void StyleImage::StyleImgRenderer::fatal () +{ + // Nothing to do. +} + +StyleImage::StyleImage () +{ + DBG_OBJ_CREATE ("dw::core::style::StyleImage"); + + refCount = 0; + imgbufSrc = NULL; + imgbufTiled = NULL; + + imgRendererDist = new ImgRendererDist (); + styleImgRenderer = new StyleImgRenderer (this); + imgRendererDist->put (styleImgRenderer); +} + +StyleImage::~StyleImage () +{ + if (imgbufSrc) + imgbufSrc->unref (); + if (imgbufTiled) + imgbufTiled->unref (); + + delete imgRendererDist; + delete styleImgRenderer; + + DBG_OBJ_DELETE (); +} + +void StyleImage::ExternalImgRenderer::setBuffer (core::Imgbuf *buffer, + bool resize) +{ + // Nothing to do? +} + +void StyleImage::ExternalImgRenderer::drawRow (int row) +{ + if (drawBackgroundLineByLine) { + StyleImage *backgroundImage; + if (readyToDraw () && (backgroundImage = getBackgroundImage ())) { + // All single rows are drawn. + + Imgbuf *imgbuf = backgroundImage->getImgbufSrc(); + int imgWidth = imgbuf->getRootWidth (); + int imgHeight = imgbuf->getRootHeight (); + + int x, y, width, height; + getBgArea (&x, &y, &width, &height); + + int xRef, yRef, widthRef, heightRef; + getRefArea (&xRef, &yRef, &widthRef, &heightRef); + + bool repeatX, repeatY, doDraw; + int origX, origY, tileX1, tileX2, tileY1, tileY2; + + calcBackgroundRelatedValues (backgroundImage, + getBackgroundRepeat (), + getBackgroundAttachment (), + getBackgroundPositionX (), + getBackgroundPositionY (), + x, y, width, height, xRef, yRef, widthRef, + heightRef, &repeatX, &repeatY, &origX, + &origY, &tileX1, &tileX2, &tileY1, + &tileY2, &doDraw); + + //printf ("tileX1 = %d, tileX2 = %d, tileY1 = %d, tileY2 = %d\n", + // tileX1, tileX2, tileY1, tileY2); + + if (doDraw) + // Only iterate over y, because the rows can be combined + // horizontally. + for (int tileY = tileY1; tileY <= tileY2; tileY++) { + int x1 = misc::max (origX + tileX1 * imgWidth, x); + int x2 = misc::min (origX + (tileX2 + 1) * imgWidth, x + width); + + int yt = origY + tileY * imgHeight + row; + if (yt >= y && yt < y + height) + draw (x1, yt, x2 - x1, 1); + } + } + } +} + +void StyleImage::ExternalImgRenderer::finish () +{ + if (!drawBackgroundLineByLine) { + if (readyToDraw ()) { + // Draw total area, as a whole. + int x, y, width, height; + getBgArea (&x, &y, &width, &height); + draw (x, y, width, height); + } + } +} + +void StyleImage::ExternalImgRenderer::fatal () +{ + // Nothing to do. +} + +// ---------------------------------------------------------------------- + +StyleImage *StyleImage::ExternalWidgetImgRenderer::getBackgroundImage () +{ + Style *style = getStyle (); + return style ? style->backgroundImage : NULL; +} + +BackgroundRepeat StyleImage::ExternalWidgetImgRenderer::getBackgroundRepeat () +{ + Style *style = getStyle (); + return style ? style->backgroundRepeat : BACKGROUND_REPEAT; +} + +BackgroundAttachment + StyleImage::ExternalWidgetImgRenderer::getBackgroundAttachment () +{ + Style *style = getStyle (); + return style ? style->backgroundAttachment : BACKGROUND_ATTACHMENT_SCROLL; +} + +Length StyleImage::ExternalWidgetImgRenderer::getBackgroundPositionX () +{ + Style *style = getStyle (); + return style ? style->backgroundPositionX : createPerLength (0); +} + +Length StyleImage::ExternalWidgetImgRenderer::getBackgroundPositionY () +{ + Style *style = getStyle (); + return style ? style->backgroundPositionY : createPerLength (0); +} + +// ---------------------------------------------------------------------- + +/* + * The drawBorder{Top,Bottom,Left,Right} functions are similar. They + * use a trapezium as draw polygon, or drawTypedLine() for dots and dashes. + * Although the concept is simple, achieving pixel accuracy is laborious [1]. + * + * [1] http://www.dillo.org/css_compat/tests/border-style.html + */ +static void drawBorderTop(View *view, Style *style, + int x1, int y1, int x2, int y2) + +{ + int d, w; + Point points[4]; + const bool filled = true, convex = true; + bool ridge = false, inset = false, dotted = false; + Color::Shading shading = Color::SHADING_NORMAL; + + if (!style->borderColor.top || style->borderWidth.top == 0) + return; + + switch (style->borderStyle.top) { + case BORDER_NONE: + case BORDER_HIDDEN: + break; + case BORDER_DOTTED: + dotted = true; + case BORDER_DASHED: + w = style->borderWidth.top; + view->drawTypedLine(style->borderColor.top, shading, + dotted ? LINE_DOTTED : LINE_DASHED, + w, x1+w/2, y1+w/2, x2-w/2, y2+w/2); + break; + case BORDER_SOLID: + case BORDER_INSET: + inset = true; + case BORDER_OUTSET: + if (style->borderStyle.top != BORDER_SOLID) + shading = (inset) ? Color::SHADING_DARK : Color::SHADING_LIGHT; + + if (style->borderWidth.top == 1) { + view->drawLine(style->borderColor.top, shading, x1, y1, x2, y2); + } else { + points[0].x = x1; + points[1].x = x2 + 1; + points[0].y = points[1].y = y1; + points[2].x = points[1].x - style->borderWidth.right; + points[3].x = x1 + style->borderWidth.left; + points[2].y = points[3].y = points[0].y + style->borderWidth.top; + view->drawPolygon (style->borderColor.top, shading, filled, convex, + points, 4); + } + break; + case BORDER_RIDGE: + ridge = true; + case BORDER_GROOVE: + d = style->borderWidth.top & 1; + points[0].x = x1; + points[1].x = x2 + 1; + points[0].y = points[1].y = y1; + points[2].x = x2 - style->borderWidth.right / 2; + points[3].x = x1 + style->borderWidth.left / 2; + points[2].y = points[3].y = y1 + style->borderWidth.top / 2 + d; + shading = (ridge) ? Color::SHADING_LIGHT : Color::SHADING_DARK; + view->drawPolygon (style->borderColor.top, shading, filled, convex, + points, 4); + points[0].x = x1 + style->borderWidth.left / 2 + d; + points[1].x = x2 - style->borderWidth.right / 2 + 1 - d; + points[0].y = points[1].y = y1 + style->borderWidth.top / 2 + d; + points[2].x = x2 - style->borderWidth.right + 1 - d; + points[3].x = x1 + style->borderWidth.left; + points[2].y = points[3].y = y1 + style->borderWidth.top; + shading = (ridge) ? Color::SHADING_DARK : Color::SHADING_LIGHT; + view->drawPolygon (style->borderColor.top, shading, filled, convex, + points, 4); + break; + case BORDER_DOUBLE: + w = (int) rint(style->borderWidth.top / 3.0); + d = w ? style->borderWidth.top - 2 * w : 0; + int w_l = (int) rint(style->borderWidth.left / 3.0); + int w_r = (int) rint(style->borderWidth.right / 3.0); + if (style->borderWidth.top == 1) { + view->drawLine(style->borderColor.top, shading, x1, y1, x2, y2); + break; + } + points[0].x = x1; + points[1].x = x2 + 1; + points[0].y = points[1].y = y1; + points[2].x = points[1].x - w_r; + points[3].x = points[0].x + w_l; + points[2].y = points[3].y = points[0].y + w; + view->drawPolygon (style->borderColor.top, shading, filled, convex, + points, 4); + points[0].x = x1 + style->borderWidth.left - w_l; + points[1].x = x2 + 1 - style->borderWidth.right + w_r; + points[0].y = points[1].y = y1 + w + d; + points[2].x = x2 + 1 - style->borderWidth.right; + points[3].x = x1 + style->borderWidth.left; + points[2].y = points[3].y = y1 + style->borderWidth.top; + view->drawPolygon (style->borderColor.top, shading, filled, convex, + points, 4); + break; + } +} + +static void drawBorderBottom(View *view, Style *style, + int x1, int y1, int x2, int y2) + +{ + int d, w; + Point points[4]; + const bool filled = true, convex = true; + bool ridge = false, inset = false, dotted = false; + Color::Shading shading = Color::SHADING_NORMAL; + + if (!style->borderColor.bottom || style->borderWidth.bottom == 0) + return; + + switch (style->borderStyle.bottom) { + case BORDER_NONE: + case BORDER_HIDDEN: + break; + case BORDER_DOTTED: + dotted = true; + case BORDER_DASHED: + w = style->borderWidth.bottom; + view->drawTypedLine(style->borderColor.bottom, shading, + dotted ? LINE_DOTTED : LINE_DASHED, + w, x1+w/2, y1-w/2, x2-w/2, y2-w/2); + break; + case BORDER_SOLID: + case BORDER_INSET: + inset = true; + case BORDER_OUTSET: + if (style->borderStyle.bottom != BORDER_SOLID) + shading = (inset) ? Color::SHADING_LIGHT : Color::SHADING_DARK; + + if (style->borderWidth.bottom == 1) { /* 1 pixel line */ + view->drawLine(style->borderColor.bottom, shading, x1, y1, x2, y2); + } else { + points[0].x = x1 - 1; + points[1].x = x2 + 2; + points[0].y = points[1].y = y1 + 1; + points[2].x = points[1].x - style->borderWidth.right; + points[3].x = points[0].x + style->borderWidth.left; + points[2].y = points[3].y = points[0].y-style->borderWidth.bottom; + view->drawPolygon (style->borderColor.bottom, shading, filled, convex, + points, 4); + } + break; + case BORDER_RIDGE: + ridge = true; + case BORDER_GROOVE: + w = style->borderWidth.bottom; + d = w & 1; + points[0].x = x1 - 1; + points[1].x = x2 + 2 - d; + points[0].y = points[1].y = y1 + 1; + points[2].x = points[1].x - style->borderWidth.right / 2; + points[3].x = points[0].x + style->borderWidth.left / 2 + d; + points[2].y = points[3].y = points[0].y - w/2 - d; + shading = (ridge) ? Color::SHADING_DARK : Color::SHADING_LIGHT; + view->drawPolygon (style->borderColor.bottom, shading, filled, convex, + points, 4); + // clockwise + points[0].x = x1 + style->borderWidth.left - 1; + points[1].x = x2 + 1 - style->borderWidth.right + 1; + points[0].y = points[1].y = y1 - w + 1; + points[2].x = points[1].x + style->borderWidth.right / 2; + points[3].x = points[0].x - style->borderWidth.left / 2; + points[2].y = points[3].y = points[0].y + w/2; + shading = (ridge) ? Color::SHADING_LIGHT : Color::SHADING_DARK; + view->drawPolygon (style->borderColor.bottom, shading, filled, convex, + points, 4); + break; + case BORDER_DOUBLE: + w = (int) rint(style->borderWidth.bottom / 3.0); + d = w ? style->borderWidth.bottom - 2 * w : 0; + int w_l = (int) rint(style->borderWidth.left / 3.0); + int w_r = (int) rint(style->borderWidth.right / 3.0); + if (style->borderWidth.bottom == 1) { + view->drawLine(style->borderColor.bottom, shading, x1, y1, x2, y2); + break; + } + points[0].x = x2 + 2; + points[1].x = x1 - 1; + points[0].y = points[1].y = y1 + 1; + points[2].x = points[1].x + w_l; + points[3].x = points[0].x - w_r; + points[2].y = points[3].y = points[0].y - w; + view->drawPolygon (style->borderColor.bottom, shading, filled, convex, + points, 4); + points[0].x = x2 + 2 - style->borderWidth.right + w_r; + points[1].x = x1 - 1 + style->borderWidth.left - w_l; + points[0].y = points[1].y = y1 + 1 - w - d; + points[2].x = x1 - 1 + style->borderWidth.left; + points[3].x = x2 + 2 - style->borderWidth.right; + points[2].y = points[3].y = y1 + 1 - style->borderWidth.bottom; + view->drawPolygon (style->borderColor.bottom, shading, filled, convex, + points, 4); + break; + } +} + +static void drawBorderLeft(View *view, Style *style, + int x1, int y1, int x2, int y2) + +{ + int d, w; + Point points[4]; + bool filled = true, convex = true; + bool ridge = false, inset = false, dotted = false; + Color::Shading shading = Color::SHADING_NORMAL; + + if (!style->borderColor.left || style->borderWidth.left == 0) + return; + + switch (style->borderStyle.left) { + case BORDER_NONE: + case BORDER_HIDDEN: + break; + case BORDER_DOTTED: + dotted = true; + case BORDER_DASHED: + w = style->borderWidth.left; + view->drawTypedLine(style->borderColor.left, shading, + dotted ? LINE_DOTTED : LINE_DASHED, + w, x1+w/2, y1+w/2, x1+w/2, y2-w/2); + break; + case BORDER_SOLID: + case BORDER_INSET: + inset = true; + case BORDER_OUTSET: + if (style->borderStyle.left != BORDER_SOLID) + shading = (inset) ? Color::SHADING_DARK : Color::SHADING_LIGHT; + if (style->borderWidth.left == 1) { /* 1 pixel line */ + view->drawLine(style->borderColor.left, shading, x1, y1, x2, y2); + } else { + points[0].x = points[1].x = x1; + points[0].y = y1 - 1; + points[1].y = y2 + 1; + points[2].x = points[3].x = points[0].x + style->borderWidth.left; + points[2].y = points[1].y - style->borderWidth.bottom; + points[3].y = points[0].y + style->borderWidth.top; + view->drawPolygon (style->borderColor.left, shading, filled, convex, + points, 4); + } + break; + case BORDER_RIDGE: + ridge = true; + case BORDER_GROOVE: + w = style->borderWidth.left; + d = w & 1; + points[0].x = points[1].x = x1; + points[0].y = y1; + points[1].y = y2; + points[2].x = points[3].x = x1 + w / 2 + d; + points[2].y = y2 - style->borderWidth.bottom / 2; + points[3].y = y1 + style->borderWidth.top / 2; + shading = (ridge) ? Color::SHADING_LIGHT : Color::SHADING_DARK; + view->drawPolygon (style->borderColor.left, shading, filled, convex, + points, 4); + points[0].x = points[1].x = x1 + w / 2 + d; + points[0].y = y1 + style->borderWidth.top / 2; + points[1].y = y2 - style->borderWidth.bottom / 2; + points[2].x = points[3].x = x1 + w; + points[2].y = y2 - style->borderWidth.bottom; + points[3].y = y1 + style->borderWidth.top; + shading = (ridge) ? Color::SHADING_DARK : Color::SHADING_LIGHT; + view->drawPolygon (style->borderColor.left, shading, filled, convex, + points, 4); + break; + case BORDER_DOUBLE: + w = (int) rint(style->borderWidth.left / 3.0); + d = w ? style->borderWidth.left - 2 * w : 0; + int w_b = (int) rint(style->borderWidth.bottom / 3.0); + int w_t = (int) rint(style->borderWidth.top / 3.0); + if (style->borderWidth.left == 1) { + view->drawLine(style->borderColor.left, shading, x1, y1, x2, y2-1); + break; + } + points[0].x = points[1].x = x1; + points[0].y = y1 - 1; + points[1].y = y2 + 1; + points[2].x = points[3].x = points[0].x + w; + points[2].y = points[1].y - w_b; + points[3].y = points[0].y + w_t; + view->drawPolygon (style->borderColor.left, shading, filled, convex, + points, 4); + points[0].x = points[1].x = x1 + w + d; + points[0].y = y1 - 1 + style->borderWidth.top - w_t; + points[1].y = y2 + 1 - style->borderWidth.bottom + w_b; + points[2].x = points[3].x = points[0].x + w; + points[2].y = y2 + 1 - style->borderWidth.bottom; + points[3].y = y1 - 1 + style->borderWidth.top; + view->drawPolygon (style->borderColor.left, shading, filled, convex, + points, 4); + break; + } +} + +static void drawBorderRight(View *view, Style *style, + int x1, int y1, int x2, int y2) + +{ + int d, w; + Point points[4]; + const bool filled = true, convex = true; + bool ridge = false, inset = false, dotted = false; + Color::Shading shading = Color::SHADING_NORMAL; + + if (!style->borderColor.right || style->borderWidth.right == 0) + return; + + switch (style->borderStyle.right) { + case BORDER_NONE: + case BORDER_HIDDEN: + break; + case BORDER_DOTTED: + dotted = true; + case BORDER_DASHED: + w = style->borderWidth.right; + view->drawTypedLine(style->borderColor.right, shading, + dotted ? LINE_DOTTED : LINE_DASHED, + w, x1 - w/2, y1 + w/2, x1 - w/2, y2 - w/2); + break; + case BORDER_SOLID: + case BORDER_INSET: + inset = true; + case BORDER_OUTSET: + if (style->borderStyle.right != BORDER_SOLID) + shading = (inset) ? Color::SHADING_LIGHT : Color::SHADING_DARK; + if (style->borderWidth.right == 1) { /* 1 pixel line */ + view->drawLine(style->borderColor.right, shading, x1, y1, x2, y2); + } else { + points[0].x = points[1].x = x1 + 1; + points[0].y = y1 - 1; + points[1].y = y2 + 1; + points[2].x = points[3].x = points[0].x-style->borderWidth.right; + points[2].y = points[1].y - style->borderWidth.bottom; + points[3].y = points[0].y + style->borderWidth.top; + view->drawPolygon (style->borderColor.right, shading, filled, convex, + points,4); + } + break; + case BORDER_RIDGE: + ridge = true; + case BORDER_GROOVE: + w = style->borderWidth.right; + d = w & 1; + points[0].x = points[1].x = x1 + 1; + points[0].y = y1; + points[1].y = y2; + points[2].x = points[3].x = points[0].x - w / 2 - d; + points[2].y = y2 - style->borderWidth.bottom / 2; + points[3].y = points[0].y + style->borderWidth.top / 2; + shading = (ridge) ? Color::SHADING_DARK : Color::SHADING_LIGHT; + view->drawPolygon (style->borderColor.right, shading, filled, convex, + points, 4); + points[0].x = points[1].x = x1 + 1 - w / 2 - d; + points[0].y = y1 + style->borderWidth.top / 2; + points[1].y = y2 - style->borderWidth.bottom / 2; + points[2].x = points[3].x = x1 + 1 - w; + points[2].y = y2 - style->borderWidth.bottom; + points[3].y = y1 + style->borderWidth.top; + shading = (ridge) ? Color::SHADING_LIGHT: Color::SHADING_DARK; + view->drawPolygon (style->borderColor.right, shading, filled, convex, + points, 4); + break; + case BORDER_DOUBLE: + w = (int) rint(style->borderWidth.right / 3.0); + d = w ? style->borderWidth.right - 2 * w : 0; + int w_b = (int) rint(style->borderWidth.bottom / 3.0); + int w_t = (int) rint(style->borderWidth.top / 3.0); + if (style->borderWidth.right == 1) { + view->drawLine(style->borderColor.right, shading, x1, y1, x2, y2); + break; + } + points[0].x = points[1].x = x1 + 1; + points[0].y = y1 - 1; + points[1].y = y2 + 1; + points[2].x = points[3].x = points[0].x - w; + points[2].y = points[1].y - w_b; + points[3].y = points[0].y + w_t; + view->drawPolygon (style->borderColor.right, shading, filled, convex, + points, 4); + points[0].x = points[1].x = x1 + 1 - w - d; + points[0].y = y1 - 1 + style->borderWidth.top - w_t; + points[1].y = y2 + 1 - style->borderWidth.bottom + w_b; + points[2].x = points[3].x = points[0].x - w; + points[2].y = y2 + 1 - style->borderWidth.bottom; + points[3].y = y1 - 1 + style->borderWidth.top; + view->drawPolygon (style->borderColor.right, shading, filled, convex, + points, 4); + break; + } +} + +/** + * \brief Draw the border of a region in window, according to style. + * + * Used by dw::core::Widget::drawBox and dw::core::Widget::drawWidgetBox. + * + * "area" is the area to be drawn, "x", "y", "width" and "height" + * define the box itself. All are given in canvas coordinates. + */ +void drawBorder (View *view, Layout *layout, Rectangle *area, + int x, int y, int width, int height, + Style *style, bool inverse) +{ + /** \todo a lot! */ + int xb1, yb1, xb2, yb2; + + // top left and bottom right point of outer border boundary + xb1 = x + style->margin.left; + yb1 = y + style->margin.top; + xb2 = x + (width > 0 ? width - 1 : 0) - style->margin.right; + yb2 = y + (height > 0 ? height - 1 : 0) - style->margin.bottom; + + /* + // top left and bottom right point of inner border boundary + xp1 = xb1 + style->borderWidth.left; + yp1 = yb1 + style->borderWidth.top; + xp2 = xb2 - style->borderWidth.right; + yp2 = yb2 - style->borderWidth.bottom; + + light = inverse ? Color::SHADING_DARK : Color::SHADING_LIGHT; + dark = inverse ? Color::SHADING_LIGHT : Color::SHADING_DARK; + normal = inverse ? Color::SHADING_INVERSE : Color::SHADING_NORMAL; + */ + + drawBorderRight(view, style, xb2, yb1, xb2, yb2); + drawBorderLeft(view, style, xb1, yb1, xb1, yb2); + drawBorderTop(view, style, xb1, yb1, xb2, yb1); + drawBorderBottom(view, style, xb1, yb2, xb2, yb2); +} + + +/** + * \brief Draw the background (content plus padding) of a region in window, + * according to style. + * + * Used by dw::core::Widget::drawBox and dw::core::Widget::drawWidgetBox. + * + * "area" is the area to be drawn, "x", "y", "width" and "height" + * define the box itself (padding box). "xRef", "yRef", "widthRef" and + * "heightRef" define the reference area, which is important for the + * tiling of background images (for position 0%/0%, a tile is set at + * xRef/yRef; for position 100%/100%, a tile is set at xRef + + * widthRef/yRef + widthRef). See calls for more informations; in most + * cases, these boxes are identical (padding box). All these + * coordinates are given in canvas coordinates. + * + * "atTop" should be true, only if the area is drawn directly on the + * canvas, not on top of other areas; this is only true for the + * toplevel widget itself (not parts of its contents). Toplevel widget + * background colors are already set as viewport background color, so + * that drawing again is is not neccessary, but some time can be + * saved. + * + * Otherwise, the caller should not try to increase the performance by + * doing some tests before; this is all done in this method. + * + * "bgColor" is passes implicitly. For non-inversed drawing, + * style->backgroundColor may simply used. However, when drawing is + * inversed, and style->backgroundColor is undefined (NULL), a + * background color defined higher in the hierarchy (which is not + * accessable here) must be used. + * + * (Background *images* are never drawn inverse.) + */ +void drawBackground (View *view, Layout *layout, Rectangle *area, + int x, int y, int width, int height, + int xRef, int yRef, int widthRef, int heightRef, + Style *style, Color *bgColor, bool inverse, bool atTop) +{ + bool hasBgColor = bgColor != NULL && + // The test for background colors is rather simple, since only the color + // has to be compared, ... + (!atTop || layout->getBgColor () != bgColor); + bool hasBgImage = (style->backgroundImage != NULL && + style->backgroundImage->getImgbufSrc() != NULL) && + // ... but for backgrounds, it would be rather complicated. To handle the + // two cases (normal HTML in a viewport, where the layout background + // image is set, and contents of <button> within a flat view, where the + // background image of the toplevel widget is set), only the background + // images are compared. A full test, which also deals with all other + // attributes related to background images (repeat, position etc.) would + // be complicated and useless, so not worth the work. + (!atTop || layout->getBgImage () != style->backgroundImage); + + // Since widgets are always drawn from top to bottom, it is *not* + // necessary to draw the background if background color and image + // are not set (NULL), i. e. shining through. + + if (hasBgColor || hasBgImage) { + Rectangle bgArea, intersection; + bgArea.x = x; + bgArea.y = y; + bgArea.width = width; + bgArea.height = height; + + if (area->intersectsWith (&bgArea, &intersection)) { + if (hasBgColor) + view->drawRectangle (bgColor, + inverse ? + Color::SHADING_INVERSE : Color::SHADING_NORMAL, + true, intersection.x, intersection.y, + intersection.width, intersection.height); + + if (hasBgImage) + drawBackgroundImage (view, style->backgroundImage, + style->backgroundRepeat, + style->backgroundAttachment, + style->backgroundPositionX, + style->backgroundPositionY, + intersection.x, intersection.y, + intersection.width, intersection.height, + xRef, yRef, widthRef, heightRef); + + } + } +} + +void drawBackgroundImage (View *view, StyleImage *backgroundImage, + BackgroundRepeat backgroundRepeat, + BackgroundAttachment backgroundAttachment, + Length backgroundPositionX, + Length backgroundPositionY, + int x, int y, int width, int height, + int xRef, int yRef, int widthRef, int heightRef) +{ + //printf ("drawBackgroundImage (..., [img: %d, %d], ..., (%d, %d), %d x %d, " + // "(%d, %d), %d x %d)\n", imgWidth, imgHeight, x, y, width, height, + // xRef, yRef, widthRef, heightRef); + + bool repeatX, repeatY, doDraw; + int origX, origY, tileX1, tileX2, tileY1, tileY2; + + calcBackgroundRelatedValues (backgroundImage, backgroundRepeat, + backgroundAttachment, backgroundPositionX, + backgroundPositionY, x, y, width, height, + xRef, yRef, widthRef, heightRef, + &repeatX, &repeatY, &origX, &origY, + &tileX1, &tileX2, &tileY1, &tileY2, &doDraw); + + //printf ("tileX1 = %d, tileX2 = %d, tileY1 = %d, tileY2 = %d\n", + // tileX1, tileX2, tileY1, tileY2); + + if (doDraw) { + // Drawing is done with the "tiled" buffer, but all calculations + // before have been done with the "source" buffer. + + Imgbuf *imgbufS = backgroundImage->getImgbufSrc(); + int imgWidthS = imgbufS->getRootWidth (); + int imgHeightS = imgbufS->getRootHeight (); + + Imgbuf *imgbufT = backgroundImage->getImgbufTiled(repeatX, repeatY); + int imgWidthT = imgbufT->getRootWidth (); + int imgHeightT = imgbufT->getRootHeight (); + int tilesX = backgroundImage->getTilesX (repeatX, repeatY); + int tilesY = backgroundImage->getTilesY (repeatX, repeatY); + + for (int tileX = tileX1; tileX <= tileX2; tileX += tilesX) + for (int tileY = tileY1; tileY <= tileY2; tileY += tilesY) { + int xt = origX + tileX * imgWidthS; + int x1 = misc::max (xt, x); + int x2 = misc::min (xt + imgWidthT, x + width); + int yt = origY + tileY * imgHeightS; + int y1 = misc::max (yt, y); + int y2 = misc::min (yt + imgHeightT, y + height); + + view->drawImage (imgbufT, xt, yt, x1 - xt, y1 - yt, + x2 - x1, y2 - y1); + } + } +} + +void calcBackgroundRelatedValues (StyleImage *backgroundImage, + BackgroundRepeat backgroundRepeat, + BackgroundAttachment backgroundAttachment, + Length backgroundPositionX, + Length backgroundPositionY, + int xDraw, int yDraw, int widthDraw, + int heightDraw, int xRef, int yRef, + int widthRef, int heightRef, bool *repeatX, + bool *repeatY, int *origX, int *origY, + int *tileX1, int *tileX2, int *tileY1, + int *tileY2, bool *doDraw) +{ + Imgbuf *imgbuf = backgroundImage->getImgbufSrc(); + int imgWidth = imgbuf->getRootWidth (); + int imgHeight = imgbuf->getRootHeight (); + + *repeatX = backgroundRepeat == BACKGROUND_REPEAT || + backgroundRepeat == BACKGROUND_REPEAT_X; + *repeatY = backgroundRepeat == BACKGROUND_REPEAT || + backgroundRepeat == BACKGROUND_REPEAT_Y; + + *origX = xRef + + (isPerLength (backgroundPositionX) ? + multiplyWithPerLength (widthRef - imgWidth, backgroundPositionX) : + absLengthVal (backgroundPositionX)); + *origY = yRef + + (isPerLength (backgroundPositionY) ? + multiplyWithPerLength (heightRef - imgHeight, backgroundPositionY) : + absLengthVal (backgroundPositionY)); + + *tileX1 = xDraw < *origX ? + - (*origX - xDraw + imgWidth - 1) / imgWidth : + (xDraw - *origX) / imgWidth; + *tileX2 = *origX < xDraw + widthDraw ? + (xDraw + widthDraw - *origX - 1) / imgWidth : + - (*origX - (xDraw + widthDraw) + imgWidth - 1) / imgWidth; + *tileY1 = yDraw < *origY ? + - (*origY - yDraw + imgHeight - 1) / imgHeight : + (yDraw - *origY) / imgHeight; + *tileY2 = *origY < yDraw + heightDraw ? + (yDraw + heightDraw - *origY - 1) / imgHeight : + - (*origY - (yDraw + heightDraw) + imgHeight - 1) / imgHeight; + + *doDraw = true; + if (!*repeatX) { + // Only center tile (tileX = 0) is drawn, ... + if (*tileX1 <= 0 && *tileX2 >= 0) + // ... and is visible. + *tileX1 = *tileX2 = 0; + else + // ... but is not visible. + *doDraw = false; + } + + if (!*repeatY) { + // Analogue. + if (*tileY1 <= 0 && *tileY2 >= 0) + *tileY1 = *tileY2 = 0; + else + *doDraw = false; + } +} + +// ---------------------------------------------------------------------- + +static const char + *const roman_I0[] = { "","I","II","III","IV","V","VI","VII","VIII","IX" }, + *const roman_I1[] = { "","X","XX","XXX","XL","L","LX","LXX","LXXX","XC" }, + *const roman_I2[] = { "","C","CC","CCC","CD","D","DC","DCC","DCCC","CM" }, + *const roman_I3[] = { "","M","MM","MMM","MMMM" }; + +static void strAsciiTolower (char *s) +{ + for ( ; *s; s++) + *s = misc::AsciiTolower (*s); +} + +/** + * \brief Convert a number into a string, in a given list style. + * + * Used for ordered lists. + */ +void numtostr (int num, char *buf, int buflen, ListStyleType listStyleType) +{ + int i3, i2, i1, i0; + bool low = false; + int start_ch = 'A'; + + if (buflen <= 0) + return; + + switch(listStyleType){ + case LIST_STYLE_TYPE_LOWER_ALPHA: + case LIST_STYLE_TYPE_LOWER_LATIN: + start_ch = 'a'; + case LIST_STYLE_TYPE_UPPER_ALPHA: + case LIST_STYLE_TYPE_UPPER_LATIN: + i0 = num - 1; + i1 = i0/26 - 1; i2 = i1/26 - 1; + if (i2 > 25) /* more than 26+26^2+26^3=18278 elements ? */ + snprintf(buf, buflen, "****."); + else + snprintf(buf, buflen, "%c%c%c.", + i2<0 ? ' ' : start_ch + i2%26, + i1<0 ? ' ' : start_ch + i1%26, + i0<0 ? ' ' : start_ch + i0%26); + break; + case LIST_STYLE_TYPE_LOWER_ROMAN: + low = true; + case LIST_STYLE_TYPE_UPPER_ROMAN: + i0 = num; + i1 = i0/10; i2 = i1/10; i3 = i2/10; + i0 %= 10; i1 %= 10; i2 %= 10; + if (num < 0 || i3 > 4) /* more than 4999 elements ? */ + snprintf(buf, buflen, "****."); + else + snprintf(buf, buflen, "%s%s%s%s.", roman_I3[i3], roman_I2[i2], + roman_I1[i1], roman_I0[i0]); + break; + case LIST_STYLE_TYPE_DECIMAL: + default: + snprintf(buf, buflen, "%d.", num); + break; + } + + // ensure termination + buf[buflen - 1] = '\0'; + + if (low) + strAsciiTolower(buf); + +} + +} // namespace style +} // namespace core +} // namespace dw diff --git a/dw/style.hh b/dw/style.hh new file mode 100644 index 0000000..230baa2 --- /dev/null +++ b/dw/style.hh @@ -0,0 +1,907 @@ +#ifndef __DW_STYLE_HH__ +#define __DW_STYLE_HH__ + +#include <stdint.h> + +#ifndef __INCLUDED_FROM_DW_CORE_HH__ +# error Do not include this file directly, use "core.hh" instead. +#endif + +#include "../lout/signal.hh" +#include "../lout/debug.hh" + +namespace dw { +namespace core { + +/** + * \brief Anything related to Dillo %Widget styles is defined here. + * + * <h3>Overview</h3> + * + * dw::core::style::Style provides some resources and attributes for + * drawing widgets, as well as for parts of a widget (e.g., dw::Textblock + * uses styles for its words). Creating a style is done by filling a + * dw::core::style::StyleAttrs with the attributes and calling + * dw::core::style::Style::create: + * + * \code + * dw::core::style::Style styleAttrs; + * dw::core::style::Style *style; + * dw::core::Layout *layout; + * + * // ... + * + * styleAttrs.foo = bar; + * // etc. + * style = dw::core::style::Style::create (&styleAttrs, layout); + * // do something with style + * \endcode + * + * After this, the attributes of a dw::core::style::Style should not be + * changed anymore, since styles are often shared between different + * widgets etc. (see below). Most times, you simply copy the attributes + * of another style (possible, since dw::core::style::Style is a sub + * class of dw::core::style::StyleAttrs), modify them and create a new + * style: + * + * \code + * styleAttrs = *anotherStyle; + * styleAttrs.foo = baz; + * style = dw::core::style::Style::create (&styleAttrs, layout); + * \endcode + * + * The dw::core::style::Font structure can be created by + * dw::core::style::Font::create, in a similar, with + * dw::core::style::FontAttrs, and colors by + * dw::core::style::Color::create, passing 0xrrggbb as an + * argument. Furthermore, there is dw::core::style::Tooltip, created by + * dw::core::style::Tooltip::create. + * + * Notice that fonts, colors and tooltips are only intended to be used in + * conjunction with dw::core::style::Style. + * + * + * <h3>Naming</h3> + * + * dw::core::style::Style will become important for CSS, each CSS + * attribute, which is supported by dillo, will refer to an attribute in + * dw::core::style::Style. For this reason, the attributes in + * dw::core::style::Style get the names from the CSS attributes, with + * "camelCase" instead of hyphens (e.g. "background-color" becomes + * "backgroundColor"). + * + * However, dw::core::style::Style will be extended by some more + * attributes, which are not defined by CSS. To distinguish them, they + * get the prefix "x_", e.g. dw::core::style::Style::x_link. + * + * + * <h3>Lengths and Percentages</h3> + * + * dw::core::style::Length is a simple data type for lengths and + * percentages: + * + * <ul> + * <li> A length refers to an absolute measurement. It is used to + * represent the HTML type %Pixels; and the CSS type \<length\>. + * + * For CSS lengths, there are two units: (i) pixels and absolute + * units, which have to be converted to pixels (a pixel is, unlike + * in the CSS specification, treated as absolute unit), and (ii) the + * relative units "em" and "ex" (see below). + * + * <li> A percentage refers to a value relative to another value. It is + * used for the HTML type %Length; (except %Pixels;), and the CSS + * type \<percentage\>. + * + * <li> A relative length can be used in lists of HTML MultiLengths. + * </ul> + * + * Since many values in CSS may be either lengths or percentages, a + * single type is very useful. + * + * <h4>Useful Functions</h4> + * + * Creating lengths: + * + * <ul> + * <li> dw::core::style::createAbsLength + * <li> dw::core::style::createPerLength + * <li> dw::core::style::createRelLength + * </ul> + * + * Examine lengths: + * + * <ul> + * <li> dw::core::style::isAbsLength + * <li> dw::core::style::isPerLength + * <li> dw::core::style::isRelLength + * <li> dw::core::style::absLengthVal + * <li> dw::core::style::perLengthVal + * <li> dw::core::style::relLengthVal + * </ul> + * + * + * <h3>Boxes</h3> + * + * <h4>The CSS %Box Model</h4> + * + * For borders, margins etc., the box model defined by CSS2 is + * used. dw::core::style::Style contains some members defining these + * attributes. A dw::core::Widget must use these values for any + * calculation of sizes. There are some helper functions (see + * dw/style.hh). A dw::core::style::Style box looks quite similar to a + * CSS box: + * + * \image html dw-style-box-model.png + * + * <h4>Background colors</h4> + * + * The background color is stored in + * dw::core::style::Style::backgroundColor, which may be NULL (the + * background color of the parent widget is shining through). + * + * For toplevel widgets, this color is set as the background color of the + * views (dw::core::View::setBgColor), for other widgets, a filled + * rectangle is drawn, covering the content and padding. (This is + * compliant with CSS2, the background color of the toplevel element + * covers the whole canvas.) + * + * <h4>Drawing</h4> + * + * The following methods may be useful: + * + * <ul> + * <li> dw::core::Widget::drawWidgetBox for drawing the box of a widget + * (typically at the beginning of the implementation of + * dw::core::Widget::draw), and + * + * <li> dw::core::Widget::drawBox, for drawing parts of a widget (e.g. + * dw::Textblock::Word, which has its own dw::Textblock::Word::style). + * </ul> + * + * + * <h3>Notes on Memory Management</h3> + * + * Memory management is done by reference counting, + * dw::core::style::Style::create returns a pointer to + * dw::core::style::Style with an increased reference counter, so you + * should care about calling dw::core::style::Style::unref if it is not + * used anymore. You do \em not need to care about the reference counters + * of fonts and styles. + * + * In detail: + * + * <ul> + * <li> dw::core::style::Style::ref is called in + * + * <ul> + * <li> dw::core::Widget::setStyle to assign a style to a widget, + * <li> dw::Textblock::addText, dw::Textblock::addWidget, + * dw::Textblock::addAnchor, dw::Textblock::addSpace, + * dw::Textblock::addParbreak and dw::Textblock::addLinebreak, + * to assign a style to a dw::Textblock::Word, and + * <li> by the HTML parser, when pushing an element on the stack. + * </ul> + * + * <li> dw::core::style::Style::unref is called in + * + * <ul> + * <li> dw::core::Widget::~Widget, dw::Textblock::~Textblock, by the + * HTML parser, when popping an element fom the stack, and + * <li> dw::core::Widget::setStyle, dw::Textblock::addText etc., + * these methods overwrite an existing style. + * </ul> + * </ul> + */ +namespace style { + +enum Cursor { + CURSOR_CROSSHAIR, + CURSOR_DEFAULT, + CURSOR_POINTER, + CURSOR_MOVE, + CURSOR_E_RESIZE, + CURSOR_NE_RESIZE, + CURSOR_NW_RESIZE, + CURSOR_N_RESIZE, + CURSOR_SE_RESIZE, + CURSOR_SW_RESIZE, + CURSOR_S_RESIZE, + CURSOR_W_RESIZE, + CURSOR_TEXT, + CURSOR_WAIT, + CURSOR_HELP +}; + +enum BorderCollapse { + BORDER_MODEL_SEPARATE, + BORDER_MODEL_COLLAPSE +}; + +enum BorderStyle { + BORDER_NONE, + BORDER_HIDDEN, + BORDER_DOTTED, + BORDER_DASHED, + BORDER_SOLID, + BORDER_DOUBLE, + BORDER_GROOVE, + BORDER_RIDGE, + BORDER_INSET, + BORDER_OUTSET +}; + +enum BackgroundRepeat { + BACKGROUND_REPEAT, + BACKGROUND_REPEAT_X, + BACKGROUND_REPEAT_Y, + BACKGROUND_NO_REPEAT +}; + +enum BackgroundAttachment { + BACKGROUND_ATTACHMENT_SCROLL, + BACKGROUND_ATTACHMENT_FIXED +}; + +enum TextAlignType { + TEXT_ALIGN_LEFT, + TEXT_ALIGN_RIGHT, + TEXT_ALIGN_CENTER, + TEXT_ALIGN_JUSTIFY, + TEXT_ALIGN_STRING +}; + +enum VAlignType { + VALIGN_TOP, + VALIGN_BOTTOM, + VALIGN_MIDDLE, + VALIGN_BASELINE, + VALIGN_SUB, + VALIGN_SUPER, + VALIGN_TEXT_TOP, + VALIGN_TEXT_BOTTOM, +}; + +enum TextTransform { + TEXT_TRANSFORM_NONE, + TEXT_TRANSFORM_CAPITALIZE, + TEXT_TRANSFORM_UPPERCASE, + TEXT_TRANSFORM_LOWERCASE, +}; + +/** + * \todo Incomplete. Has to be completed for a CSS implementation. + */ +enum DisplayType { + DISPLAY_BLOCK, + DISPLAY_INLINE, + DISPLAY_INLINE_BLOCK, + DISPLAY_LIST_ITEM, + DISPLAY_NONE, + DISPLAY_TABLE, + DISPLAY_TABLE_ROW_GROUP, + DISPLAY_TABLE_HEADER_GROUP, + DISPLAY_TABLE_FOOTER_GROUP, + DISPLAY_TABLE_ROW, + DISPLAY_TABLE_CELL +}; + +enum LineType { + LINE_NORMAL, + LINE_DOTTED, + LINE_DASHED +}; + +enum ListStylePosition { + LIST_STYLE_POSITION_INSIDE, + LIST_STYLE_POSITION_OUTSIDE +}; +enum ListStyleType { + LIST_STYLE_TYPE_DISC, + LIST_STYLE_TYPE_CIRCLE, + LIST_STYLE_TYPE_SQUARE, + LIST_STYLE_TYPE_DECIMAL, + LIST_STYLE_TYPE_DECIMAL_LEADING_ZERO, + LIST_STYLE_TYPE_LOWER_ROMAN, + LIST_STYLE_TYPE_UPPER_ROMAN, + LIST_STYLE_TYPE_LOWER_GREEK, + LIST_STYLE_TYPE_LOWER_ALPHA, + LIST_STYLE_TYPE_LOWER_LATIN, + LIST_STYLE_TYPE_UPPER_ALPHA, + LIST_STYLE_TYPE_UPPER_LATIN, + LIST_STYLE_TYPE_HEBREW, + LIST_STYLE_TYPE_ARMENIAN, + LIST_STYLE_TYPE_GEORGIAN, + LIST_STYLE_TYPE_CJK_IDEOGRAPHIC, + LIST_STYLE_TYPE_HIRAGANA, + LIST_STYLE_TYPE_KATAKANA, + LIST_STYLE_TYPE_HIRAGANA_IROHA, + LIST_STYLE_TYPE_KATAKANA_IROHA, + LIST_STYLE_TYPE_NONE +}; + +enum FontStyle { + FONT_STYLE_NORMAL, + FONT_STYLE_ITALIC, + FONT_STYLE_OBLIQUE +}; + +enum FontVariant { + FONT_VARIANT_NORMAL, + FONT_VARIANT_SMALL_CAPS +}; + +enum Overflow { + OVERFLOW_VISIBLE, + OVERFLOW_HIDDEN, + OVERFLOW_SCROLL, + OVERFLOW_AUTO +}; + +enum Position { + POSITION_STATIC, + POSITION_RELATIVE, + POSITION_ABSOLUTE, + POSITION_FIXED, +}; + +enum TextDecoration { + TEXT_DECORATION_NONE = 0, + TEXT_DECORATION_UNDERLINE = 1 << 0, + TEXT_DECORATION_OVERLINE = 1 << 1, + TEXT_DECORATION_LINE_THROUGH = 1 << 2, + TEXT_DECORATION_BLINK = 1 << 3 +}; + +enum WhiteSpace { + WHITE_SPACE_NORMAL, + WHITE_SPACE_PRE, + WHITE_SPACE_NOWRAP, + WHITE_SPACE_PRE_WRAP, + WHITE_SPACE_PRE_LINE, +}; + +enum FloatType { + FLOAT_NONE, + FLOAT_LEFT, + FLOAT_RIGHT +}; + +enum ClearType { + CLEAR_LEFT, + CLEAR_RIGHT, + CLEAR_BOTH, + CLEAR_NONE +}; + +/** + * \brief Type for representing all lengths within dw::core::style. + * + * Lengths are int's. Absolute lengths are represented in the following way: + * + * \image html dw-style-length-absolute.png + * + * Percentages: + * + * \image html dw-style-length-percentage.png + * + * Relative lengths (only used in HTML): + * + * \image html dw-style-length-relative.png + * + * This is an implementation detail, use one of the following functions: + * + * Creating lengths: + * + * <ul> + * <li> dw::core::style::createAbsLength + * <li> dw::core::style::createPerLength + * <li> dw::core::style::createRelLength + * </ul> + * + * Examine lengths: + * + * <ul> + * <li> dw::core::style::isAbsLength + * <li> dw::core::style::isPerLength + * <li> dw::core::style::isRelLength + * <li> dw::core::style::absLengthVal + * <li> dw::core::style::perLengthVal + * <li> dw::core::style::relLengthVal + * </ul> + * + * "auto" lengths are represented as dw::core::style::LENGTH_AUTO. + */ +typedef int Length; + +/** \brief Returns a length of \em n pixels. */ +inline Length createAbsLength(int n) { return (n << 2) | 1; } + +/** \brief Returns a percentage, \em v is relative to 1, not to 100. */ +inline Length createPerLength(double v) { + return ((int)(v * (1 << 18)) & ~3) | 2; } + +/** \brief Returns a relative length. */ +inline Length createRelLength(double v) { + return ((int)(v * (1 << 18)) & ~3) | 3; } + +/** \brief Returns true if \em l is an absolute length. */ +inline bool isAbsLength(Length l) { return (l & 3) == 1; } + +/** \brief Returns true if \em l is a percentage. */ +inline bool isPerLength(Length l) { return (l & 3) == 2; } + +/** \brief Returns true if \em l is a relative length. */ +inline bool isRelLength(Length l) { return (l & 3) == 3; } + +/** \brief Returns the value of a length in pixels, as an integer. */ +inline int absLengthVal(Length l) { return l >> 2; } + +/** \brief Returns the value of a percentage, relative to 1, as a double. + * + * When possible, do not use this function directly; it may be removed + * soon. Instead, use multiplyWithPerLength or multiplyWithPerLengthRounded. + */ +inline double perLengthVal_useThisOnlyForDebugging(Length l) +{ return (double)(l & ~3) / (1 << 18); } + +/** \brief Returns the value of a relative length, as a float. + * + * When possible, do not use this function directly; it may be removed + * soon. + */ +inline double relLengthVal(Length l) { return (double)(l & ~3) / (1 << 18); } + +/** + * \brief Multiply an int with a percentage length, returning int. + * + * Use this instead of perLengthVal, when possible. + */ +inline int multiplyWithPerLength(int x, Length l) { + return x * perLengthVal_useThisOnlyForDebugging (l); +} + +/** + * \brief Like multiplyWithPerLength, but rounds to nearest integer + * instead of down. + * + * (This function exists for backward compatibility.) + */ +inline int multiplyWithPerLengthRounded(int x, Length l) { + return lout::misc::roundInt (x * perLengthVal_useThisOnlyForDebugging (l)); +} + +inline int multiplyWithRelLength(int x, Length l) { + return x * relLengthVal(l); +} + + +enum { + /** \brief Represents "auto" lengths. */ + LENGTH_AUTO = 0 +}; + +/** + * \brief Represents a dimension box according to the CSS box model. + * + * Used for dw::core::style::Style::margin, + * dw::core::style::Style::borderWidth, and dw::core::style::Style::padding. + */ +class Box +{ +public: + /* in future also percentages */ + int top, right, bottom, left; + + inline void setVal(int val) { top = right = bottom = left = val; } + inline bool equals (Box *other) { + return top == other->top && + right == other->right && + bottom == other->bottom && + left == other->left; + } + inline int hashValue () { + return top + right + bottom + left; + } +}; + +class Tooltip; +class Font; +class Color; +class StyleImage; + +/** + * \sa dw::core::style + */ +class StyleAttrs : public lout::object::Object +{ +public: + Font *font; + int textDecoration; /* No TextDecoration because of problems converting + * TextDecoration <-> int */ + Color *color, *backgroundColor; + StyleImage *backgroundImage; + BackgroundRepeat backgroundRepeat; + BackgroundAttachment backgroundAttachment; + Length backgroundPositionX; // "left" defined by "0%" etc. (see CSS spec) + Length backgroundPositionY; // "top" defined by "0%" etc. (see CSS spec) + + TextAlignType textAlign; + VAlignType valign; + char textAlignChar; /* In future, strings will be supported. */ + TextTransform textTransform; + + FloatType vloat; /* "float" is a keyword. */ + ClearType clear; + + Overflow overflow; + + Position position; + Length top, bottom, left, right; + + int hBorderSpacing, vBorderSpacing, wordSpacing; + Length width, height, lineHeight, textIndent; + Length minWidth, maxWidth, minHeight, maxHeight; + + Box margin, borderWidth, padding; + BorderCollapse borderCollapse; + struct { Color *top, *right, *bottom, *left; } borderColor; + struct { BorderStyle top, right, bottom, left; } borderStyle; + + DisplayType display; + WhiteSpace whiteSpace; + ListStylePosition listStylePosition; + ListStyleType listStyleType; + Cursor cursor; + + int x_link; + int x_img; + Tooltip *x_tooltip; + char x_lang[2]; /* Either x_lang[0] == x_lang[1] == 0 (no language + set), or x_lang contains the RFC 1766 country + code in lower case letters. (Only two letters + allowed, currently.) */ + + void initValues (); + void resetValues (); + + bool sizeDiffs (StyleAttrs *otherStyleAttrs); + + inline void setBorderColor(Color *val) { + borderColor.top = borderColor.right = borderColor.bottom + = borderColor.left = val; } + inline void setBorderStyle(BorderStyle val) { + borderStyle.top = borderStyle.right = borderStyle.bottom + = borderStyle.left = val; } + + inline int boxOffsetX () + { return margin.left + borderWidth.left + padding.left; } + inline int boxRestWidth () + { return margin.right + borderWidth.right + padding.right; } + inline int boxDiffWidth () { return boxOffsetX () + boxRestWidth (); } + inline int boxOffsetY () + { return margin.top + borderWidth.top + padding.top; } + inline int boxRestHeight () + { return margin.bottom + borderWidth.bottom + padding.bottom; } + inline int boxDiffHeight () { return boxOffsetY () + boxRestHeight (); } + + inline bool hasBackground () + { return backgroundColor != NULL || backgroundImage != NULL; } + + bool equals (lout::object::Object *other); + int hashValue (); +}; + + +/** + * \sa dw::core::style + */ +class Style: public StyleAttrs +{ +private: + static int totalRef; + int refCount; + static lout::container::typed::HashTable <StyleAttrs, Style> *styleTable; + + Style (StyleAttrs *attrs); + +protected: + ~Style(); + + void copyAttrs (StyleAttrs *attrs); + +public: + inline static Style *create (StyleAttrs *attrs) + { + Style *style = styleTable->get (attrs); + if (style) { + style->ref (); + } else { + style = new Style (attrs); + styleTable->put(style, style); + } + return style; + } + + inline void ref () { refCount++; } + inline void unref () { if (--refCount == 0) delete this; } +}; + + +/** + * \sa dw::core::style + */ +class TooltipAttrs: public lout::object::String +{ +public: + TooltipAttrs(const char *text): lout::object::String(text) { } +}; + +/** + * \sa dw::core::style + */ +class Tooltip: public TooltipAttrs +{ +private: + int refCount; + +protected: + Tooltip (const char *text): TooltipAttrs(text) { refCount = 0; } + +public: + static Tooltip *create (dw::core::Layout *layout, const char *text); + inline void ref () { refCount++; } + inline void unref () + { if (--refCount == 0) delete this; } + + inline virtual void onEnter () { } + inline virtual void onLeave () { } + inline virtual void onMotion () { } +}; + + +/** + * \sa dw::core::style + */ +class FontAttrs: public lout::object::Object +{ +public: + const char *name; + int size; + int weight; + int letterSpacing; + FontVariant fontVariant; + FontStyle style; + + bool equals(lout::object::Object *other); + int hashValue(); +}; + + +/** + * \sa dw::core::style + */ +class Font: public FontAttrs +{ +private: + int refCount; + + static Font *create0 (Layout *layout, FontAttrs *attrs, bool tryEverything); + +protected: + inline Font () { + DBG_OBJ_CREATE ("dw::core::style::Font"); + refCount = 0; + } + virtual ~Font (); + + void copyAttrs (FontAttrs *attrs); + +public: + int ascent, descent; + int spaceWidth; + int xHeight; + + static Font *create (Layout *layout, FontAttrs *attrs); + static bool exists (Layout *layout, const char *name); + + inline void ref () { refCount++; } + inline void unref () { if (--refCount == 0) delete this; } +}; + + +/** + * \sa dw::core::style + */ +class ColorAttrs: public lout::object::Object +{ +protected: + int color; + +public: + inline ColorAttrs(int color) + { + this->color = color; + } + + inline int getColor () { return color; } + + bool equals(lout::object::Object *other); + int hashValue(); +}; + + +/** + * \sa dw::core::style + */ +class Color: public ColorAttrs +{ +private: + int refCount; + + void remove(dw::core::Layout *layout); + int shadeColor (int color, int d); + +protected: + inline Color (int color): ColorAttrs (color) { + DBG_OBJ_CREATE ("dw::core::style::Color"); + refCount = 0; + } + virtual ~Color (); + +public: + enum Shading { SHADING_NORMAL, SHADING_INVERSE, SHADING_DARK, SHADING_LIGHT, + SHADING_NUM }; + +protected: + int shadeColor (int color, Shading shading); + +public: + static Color *create (Layout *layout, int color); + + inline void ref () { refCount++; } + inline void unref () + { if (--refCount == 0) delete this; } +}; + + +class StyleImage: public lout::signal::ObservedObject +{ +private: + class StyleImgRenderer: public ImgRenderer + { + private: + StyleImage *image; + + public: + inline StyleImgRenderer (StyleImage *image) { this->image = image; } + + void setBuffer (core::Imgbuf *buffer, bool resize); + void drawRow (int row); + void finish (); + void fatal (); + }; + + int refCount, tilesX, tilesY; + Imgbuf *imgbufSrc, *imgbufTiled; + ImgRendererDist *imgRendererDist; + StyleImgRenderer *styleImgRenderer; + + StyleImage (); + ~StyleImage (); + +public: + /** + * \brief Useful (but not mandatory) base class for updates of + * areas with background images. + */ + class ExternalImgRenderer: public ImgRenderer + { + public: + void setBuffer (core::Imgbuf *buffer, bool resize); + void drawRow (int row); + void finish (); + void fatal (); + + /** + * \brief If this method returns false, nothing is done at all. + */ + virtual bool readyToDraw () = 0; + + /** + * \brief Return the area covered by the background image. + */ + virtual void getBgArea (int *x, int *y, int *width, int *height) = 0; + + /** + * \brief Return the "reference area". + * + * See comment of "drawBackground". + */ + virtual void getRefArea (int *xRef, int *yRef, int *widthRef, + int *heightRef) = 0; + + virtual StyleImage *getBackgroundImage () = 0; + virtual BackgroundRepeat getBackgroundRepeat () = 0; + virtual BackgroundAttachment getBackgroundAttachment () = 0; + virtual Length getBackgroundPositionX () = 0; + virtual Length getBackgroundPositionY () = 0; + + /** + * \brief Draw (or queue for drawing) an area, which is given in + * canvas coordinates. + */ + virtual void draw (int x, int y, int width, int height) = 0; + }; + + /** + * \brief Suitable for widgets and parts of widgets. + */ + class ExternalWidgetImgRenderer: public ExternalImgRenderer + { + public: + void getPaddingArea (int *x, int *y, int *width, int *height); + + StyleImage *getBackgroundImage (); + BackgroundRepeat getBackgroundRepeat (); + BackgroundAttachment getBackgroundAttachment (); + Length getBackgroundPositionX (); + Length getBackgroundPositionY (); + + /** + * \brief Return the style this background image is part of. + */ + virtual Style *getStyle () = 0; + }; + + static StyleImage *create () { return new StyleImage (); } + + inline void ref () { refCount++; } + inline void unref () + { if (--refCount == 0) delete this; } + + inline Imgbuf *getImgbufSrc () { return imgbufSrc; } + inline Imgbuf *getImgbufTiled (bool repeatX, bool repeatY) + { return (imgbufTiled && repeatX && repeatY) ? imgbufTiled : imgbufSrc; } + inline int getTilesX (bool repeatX, bool repeatY) + { return (imgbufTiled && repeatX && repeatY) ? tilesX : 1; } + inline int getTilesY (bool repeatX, bool repeatY) + { return (imgbufTiled && repeatX && repeatY) ? tilesY : 1; } + inline ImgRenderer *getMainImgRenderer () { return imgRendererDist; } + + /** + * \brief Add an additional ImgRenderer, especially used for + * drawing. + */ + inline void putExternalImgRenderer (ImgRenderer *ir) + { imgRendererDist->put (ir); } + + /** + * \brief Remove a previously added additional ImgRenderer. + */ + inline void removeExternalImgRenderer (ImgRenderer *ir) + { imgRendererDist->remove (ir); } +}; + +void drawBorder (View *view, Layout *layout, Rectangle *area, + int x, int y, int width, int height, + Style *style, bool inverse); +void drawBackground (View *view, Layout *layout, Rectangle *area, + int x, int y, int width, int height, + int xRef, int yRef, int widthRef, int heightRef, + Style *style, Color *bgColor, bool inverse, bool atTop); +void drawBackgroundImage (View *view, StyleImage *backgroundImage, + BackgroundRepeat backgroundRepeat, + BackgroundAttachment backgroundAttachment, + Length backgroundPositionX, + Length backgroundPositionY, + int x, int y, int width, int height, + int xRef, int yRef, int widthRef, int heightRef); +void numtostr (int num, char *buf, int buflen, ListStyleType listStyleType); + +} // namespace style +} // namespace core +} // namespace dw + +#endif // __DW_STYLE_HH__ + diff --git a/dw/types.cc b/dw/types.cc new file mode 100644 index 0000000..3962d97 --- /dev/null +++ b/dw/types.cc @@ -0,0 +1,367 @@ +/* + * RTFL (originally part of dillo) + * + * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version; with the following exception: + * + * The copyright holders of RTFL give you permission to link this file + * statically or dynamically against all versions of the graphviz + * library, which are published by AT&T Corp. under one of the following + * licenses: + * + * - Common Public License version 1.0 as published by International + * Business Machines Corporation (IBM), or + * - Eclipse Public License version 1.0 as published by the Eclipse + * Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + + +#include "core.hh" +#include "../lout/msg.h" + +using namespace lout; + +namespace dw { +namespace core { + +Rectangle::Rectangle (int x, int y, int width, int height) +{ + this->x = x; + this->y = y; + this->width = width; + this->height = height; +} + +/* + * Draw rectangle in view relative to point (x,y). + */ +void Rectangle::draw (core::View *view, core::style::Style *style, int x,int y) +{ + const bool filled = false; + + view->drawRectangle(style->color, core::style::Color::SHADING_NORMAL,filled, + x + this->x, y + this->y, this->width, this->height); +} + +/** + * Return whether this rectangle and otherRect intersect. If yes, + * return the intersection rectangle in dest. + */ +bool Rectangle::intersectsWith (Rectangle *otherRect, Rectangle *dest) +{ + bool doIntersect = + this->x < otherRect->x + otherRect->width && + this->y < otherRect->y + otherRect->height && + otherRect->x < this->x + this->width && + otherRect->y < this->y + this->height; + + if (doIntersect) { + dest->x = misc::max(this->x, otherRect->x); + dest->y = misc::max(this->y, otherRect->y); + dest->width = misc::min(this->x + this->width, + otherRect->x + otherRect->width) - dest->x; + dest->height = misc::min(this->y + this->height, + otherRect->y + otherRect->height) - dest->y; + } else { + dest->x = dest->y = dest->width = dest->height = 0; + } + + return doIntersect; +} + +/* + * Return whether this is a subset of otherRect. + */ +bool Rectangle::isSubsetOf (Rectangle *otherRect) +{ + return + x >= otherRect->x && + y >= otherRect->y && + x + width <= otherRect->x + otherRect->width && + y + height <= otherRect->y + otherRect->height; +} + +bool Rectangle::isPointWithin (int x, int y) +{ + return + x >= this->x && y >= this->y && + x < this->x + width && y < this->y + height; +} + +// ---------------------------------------------------------------------- + +Circle::Circle (int x, int y, int radius) +{ + this->x = x; + this->y = y; + this->radius = radius; +} + +/* + * Draw circle in view relative to point (x,y). + */ +void Circle::draw (core::View *view, core::style::Style *style, int x, int y) +{ + const bool filled = false; + + view->drawArc(style->color, core::style::Color::SHADING_NORMAL, filled, + x + this->x, y + this->y, 2 * this->radius, 2 * this->radius, + 0, 360); +} + +bool Circle::isPointWithin (int x, int y) +{ + return + (x - this->x) * (x - this->x) + (y - this->y) * (y - this->y) + <= radius * radius; +} + +// ---------------------------------------------------------------------- + +Polygon::Polygon () +{ + points = new misc::SimpleVector<Point> (8); + minx = miny = 0xffffff; + maxx = maxy = -0xffffff; +} + +Polygon::~Polygon () +{ + delete points; +} + +/* + * Draw polygon in view relative to point (x,y). + */ +void Polygon::draw (core::View *view, core::style::Style *style, int x, int y) +{ + if (points->size()) { + int i; + const bool filled = false, convex = false; + Point *pointArray = (Point *)malloc(points->size()*sizeof(struct Point)); + + for (i = 0; i < points->size(); i++) { + pointArray[i].x = x + points->getRef(i)->x; + pointArray[i].y = y + points->getRef(i)->y; + } + view->drawPolygon(style->color, core::style::Color::SHADING_NORMAL, + filled, convex, pointArray, i); + free(pointArray); + } +} + +void Polygon::addPoint (int x, int y) +{ + points->increase (); + points->getRef(points->size () - 1)->x = x; + points->getRef(points->size () - 1)->y = y; + + minx = misc::min(minx, x); + miny = misc::min(miny, y); + maxx = misc::max(maxx, x); + maxy = misc::max(maxy, y); +} + +/** + * \brief Return, whether the line, limited by (ax1, ay1) and (ax2, ay2), + * crosses the unlimited line, determined by two points (bx1, by1) and + * (bx2, by2). + */ +bool Polygon::linesCross0(int ax1, int ay1, int ax2, int ay2, + int bx1, int by1, int bx2, int by2) +{ + /** TODO Some more description */ + // If the scalar product is 0, it means that one point is on the second + // line, so we check for <= 0, not < 0. + int z1 = zOfVectorProduct (ax1 - bx1, ay1 - by1, bx2 - bx1, by2 - by1); + int z2 = zOfVectorProduct (ax2 - bx1, ay2 - by1, bx2 - bx1, by2 - by1); + + return (z1 <= 0 && z2 >= 0) || (z1 >= 0 && z2 <= 0); +} + +/** + * \brief Return, whether the line, limited by (ax1, ay1) and (ax2, ay2), + * crosses the line, limited by (bx1, by1) and (bx2, by2). + */ +bool Polygon::linesCross(int ax1, int ay1, int ax2, int ay2, + int bx1, int by1, int bx2, int by2) +{ + bool cross = + linesCross0 (ax1, ay1, ax2, ay2, bx1, by1, bx2, by2) && + linesCross0 (bx1, by1, bx2, by2, ax1, ay1, ax2, ay2); + _MSG("(%d, %d) - (%d, %d) and (%d, %d) - (%d, %d) cross? %s.\n", + ax1, ay1, ax2, ay2, bx1, by1, bx2, by2, cross ? "Yes" : "No"); + return cross; +} + +bool Polygon::isPointWithin (int x, int y) +{ + if (points->size () < 3 || + (x < minx || x > maxx || y < miny || y >= maxy)) + return false; + else { + int numCrosses = 0; + for (int i = 0; i < points->size () - 1; i++) { + if (linesCross (minx - 1, miny - 1, x, y, + points->getRef(i)->x, points->getRef(i)->y, + points->getRef(i + 1)->x, points->getRef(i + 1)->y)) + numCrosses++; + } + if (linesCross (minx - 1, miny - 1, x, y, + points->getRef(points->size () - 1)->x, + points->getRef(points->size () - 1)->y, + points->getRef(0)->x, points->getRef(0)->y)) + numCrosses++; + + return numCrosses % 2 == 1; + } +} + +Region::Region() +{ + rectangleList = new container::typed::List <Rectangle> (true); +} + +Region::~Region() +{ + delete rectangleList; +} + +/** + * \brief Add a rectangle to the region and combine it with + * existing rectangles if possible. + * The number of rectangles is forced to be less than 16 + * by combining excessive rectangles. + */ +void Region::addRectangle (Rectangle *rPointer) +{ + container::typed::Iterator <Rectangle> it; + Rectangle *r = new Rectangle (rPointer->x, rPointer->y, + rPointer->width, rPointer->height); + + for (it = rectangleList->iterator (); it.hasNext (); ) { + Rectangle *ownRect = it.getNext (); + + int combinedHeight = + misc::max(r->y + r->height, ownRect->y + ownRect->height) - + misc::min(r->y, ownRect->y); + int combinedWidth = + misc::max(r->x + r->width, ownRect->x + ownRect->width) - + misc::min(r->x, ownRect->x); + + if (rectangleList->size() >= 16 || + combinedWidth * combinedHeight <= + ownRect->width * ownRect->height + r->width * r->height) { + + r->x = misc::min(r->x, ownRect->x); + r->y = misc::min(r->y, ownRect->y); + r->width = combinedWidth; + r->height = combinedHeight; + + rectangleList->removeRef (ownRect); + } + } + + rectangleList->append (r); +} + +Content::Type Content::maskForSelection (bool followReferences) +{ + Content::Type widgetMask = (Content::Type) + (Content::WIDGET_IN_FLOW | + (followReferences ? Content::WIDGET_OOF_REF : Content::WIDGET_OOF_CONT)); + return (Content::Type)(Content::SELECTION_CONTENT | widgetMask); +} + +void Content::intoStringBuffer(Content *content, misc::StringBuffer *sb) +{ + switch(content->type) { + case START: + sb->append ("<start>"); + break; + case END: + sb->append ("<end>"); + break; + case TEXT: + sb->append ("\""); + sb->append (content->text); + sb->append ("\""); + break; + case WIDGET_IN_FLOW: + sb->append ("<widget in flow: "); + sb->appendPointer (content->widget); + sb->append (" ("); + sb->append (content->widget->getClassName()); + sb->append (")>"); + break; + case WIDGET_OOF_REF: + sb->append ("<widget oof ref: "); + sb->appendPointer (content->widget); + sb->append (" ("); + sb->append (content->widget->getClassName()); + sb->append (")>"); + break; + case WIDGET_OOF_CONT: + sb->append ("<widget oof cont: "); + sb->appendPointer (content->widget); + sb->append (" ("); + sb->append (content->widget->getClassName()); + sb->append (")>"); + break; + case BREAK: + sb->append ("<break>"); + break; + default: + sb->append ("<"); + sb->appendInt (content->type); + sb->append ("?>"); + break; + } +} + +void Content::maskIntoStringBuffer(Type mask, misc::StringBuffer *sb) +{ + sb->append ((mask & START) ? "st" : "--"); + sb->append (":"); + sb->append ((mask & END) ? "en" : "--"); + sb->append (":"); + sb->append ((mask & TEXT) ? "tx" : "--"); + sb->append (":"); + sb->append ((mask & WIDGET_IN_FLOW) ? "wf" : "--"); + sb->append (":"); + sb->append ((mask & WIDGET_OOF_REF) ? "Wr" : "--"); + sb->append (":"); + sb->append ((mask & WIDGET_OOF_CONT) ? "Wc" : "--"); + sb->append (":"); + sb->append ((mask & BREAK) ? "br" : "--"); +} + +void Content::print (Content *content) +{ + misc::StringBuffer sb; + intoStringBuffer (content, &sb); + printf ("%s", sb.getChars ()); +} + +void Content::printMask (Type mask) +{ + misc::StringBuffer sb; + maskIntoStringBuffer (mask, &sb); + printf ("%s", sb.getChars ()); +} + +} // namespace core +} // namespace dw diff --git a/dw/types.hh b/dw/types.hh new file mode 100644 index 0000000..36d6caa --- /dev/null +++ b/dw/types.hh @@ -0,0 +1,238 @@ +#ifndef __DW_TYPES_HH__ +#define __DW_TYPES_HH__ + +#ifndef __INCLUDED_FROM_DW_CORE_HH__ +# error Do not include this file directly, use "core.hh" instead. +#endif + +namespace dw { +namespace core { + +namespace style { + class Style; +} + +enum HPosition +{ + HPOS_LEFT, + HPOS_CENTER, + HPOS_RIGHT, + HPOS_INTO_VIEW, /* scroll only, until the content in question comes + * into view */ + HPOS_NO_CHANGE +}; + +enum VPosition +{ + VPOS_TOP, + VPOS_CENTER, + VPOS_BOTTOM, + VPOS_INTO_VIEW, /* scroll only, until the content in question comes + * into view */ + VPOS_NO_CHANGE +}; + +enum ScrollCommand {SCREEN_UP_CMD, SCREEN_DOWN_CMD, SCREEN_LEFT_CMD, + SCREEN_RIGHT_CMD, LINE_UP_CMD, LINE_DOWN_CMD, + LEFT_CMD, RIGHT_CMD, TOP_CMD, BOTTOM_CMD}; + +/* + * Different "layers" may be highlighted in a widget. + */ +enum HighlightLayer +{ + HIGHLIGHT_SELECTION, + HIGHLIGHT_FINDTEXT, + HIGHLIGHT_NUM_LAYERS +}; + +struct Point +{ + int x; + int y; +}; + +/** + * \brief Abstract interface for different shapes. + */ +class Shape: public lout::object::Object +{ +public: + virtual bool isPointWithin (int x, int y) = 0; + virtual void draw (core::View *view, core::style::Style *style, int x, + int y) = 0; +}; + +/** + * \brief dw::core::Shape implemtation for simple rectangles. + */ +class Rectangle: public Shape +{ +public: + int x; + int y; + int width; + int height; + + inline Rectangle () { } + Rectangle (int x, int y, int width, int height); + + void draw (core::View *view, core::style::Style *style, int x, int y); + bool intersectsWith (Rectangle *otherRect, Rectangle *dest); + bool isSubsetOf (Rectangle *otherRect); + bool isPointWithin (int x, int y); + bool isEmpty () { return width <= 0 || height <= 0; }; +}; + +/** + * \brief dw::core::Shape implemtation for simple circles. + */ +class Circle: public Shape +{ +public: + int x, y, radius; + + Circle (int x, int y, int radius); + + void draw (core::View *view, core::style::Style *style, int x, int y); + bool isPointWithin (int x, int y); +}; + +/** + * \brief dw::core::Shape implemtation for polygons. + */ +class Polygon: public Shape +{ +private: + lout::misc::SimpleVector<Point> *points; + int minx, miny, maxx, maxy; + + /** + * \brief Return the z-coordinate of the vector product of two + * vectors, whose z-coordinate is 0 (so that x and y of + * the vector product is 0, too). + */ + inline int zOfVectorProduct(int x1, int y1, int x2, int y2) { + return x1 * y2 - x2 * y1; + } + + bool linesCross0(int ax1, int ay1, int ax2, int ay2, + int bx1, int by1, int bx2, int by2); + bool linesCross(int ax1, int ay1, int ax2, int ay2, + int bx1, int by1, int bx2, int by2); + +public: + Polygon (); + ~Polygon (); + + void draw (core::View *view, core::style::Style *style, int x, int y); + void addPoint (int x, int y); + bool isPointWithin (int x, int y); +}; + +/** + * Implementation for a point set. + * Currently represented as a set of rectangles not containing + * each other. + * It is guaranteed that the rectangles returned by rectangles () + * cover all rectangles that were added with addRectangle (). + */ +class Region +{ +private: + lout::container::typed::List <Rectangle> *rectangleList; + +public: + Region (); + ~Region (); + + void clear () { rectangleList->clear (); }; + + void addRectangle (Rectangle *r); + + lout::container::typed::Iterator <Rectangle> rectangles () + { + return rectangleList->iterator (); + }; +}; + +/** + * \brief Represents the allocation, i.e. actual position and size of a + * dw::core::Widget. + */ +struct Allocation +{ + int x; + int y; + int width; + int ascent; + int descent; +}; + +struct Requisition +{ + int width; + int ascent; + int descent; +}; + +struct Extremes +{ + int minWidth; + int maxWidth; + int minWidthIntrinsic; + int maxWidthIntrinsic; +}; + +struct Content +{ + enum Type { + START = 1 << 0, + END = 1 << 1, + TEXT = 1 << 2, + + /** \brief widget in normal flow, so that _this_ widget + (containing this content) is both container (parent) and + generator */ + WIDGET_IN_FLOW = 1 << 3, + + /** \brief widget out of flow (OOF); _this_ widget (containing + this content) is only the container (parent), but _not_ + generator */ + WIDGET_OOF_CONT = 1 << 4, + + /** \brief reference to a widget out of flow (OOF); _this_ + widget (containing this content) is only the generator + (parent), but _not_ container */ + WIDGET_OOF_REF = 1 << 5, + BREAK = 1 << 6, + + ALL = 0xff, + REAL_CONTENT = 0xff ^ (START | END), + SELECTION_CONTENT = TEXT | BREAK, // WIDGET_* must be set additionally + ANY_WIDGET = WIDGET_IN_FLOW | WIDGET_OOF_CONT | WIDGET_OOF_REF, + }; + + /* Content is embedded in struct Word therefore we + * try to be space efficient. + */ + short type; + bool space; + union { + const char *text; + Widget *widget; + int breakSpace; + }; + + static Content::Type maskForSelection (bool followReferences); + + static void intoStringBuffer(Content *content, lout::misc::StringBuffer *sb); + static void maskIntoStringBuffer(Type mask, lout::misc::StringBuffer *sb); + static void print (Content *content); + static void printMask (Type mask); +}; + +} // namespace core +} // namespace dw + +#endif // __DW_TYPES_HH__ diff --git a/dw/ui.cc b/dw/ui.cc new file mode 100644 index 0000000..c021d49 --- /dev/null +++ b/dw/ui.cc @@ -0,0 +1,519 @@ +/* + * RTFL (originally part of dillo) + * + * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version; with the following exception: + * + * The copyright holders of RTFL give you permission to link this file + * statically or dynamically against all versions of the graphviz + * library, which are published by AT&T Corp. under one of the following + * licenses: + * + * - Common Public License version 1.0 as published by International + * Business Machines Corporation (IBM), or + * - Eclipse Public License version 1.0 as published by the Eclipse + * Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + + +#include "core.hh" +#include "../lout/debug.hh" + +#include <stdio.h> + +namespace dw { +namespace core { +namespace ui { + +using namespace lout; +using namespace lout::object; + +int Embed::CLASS_ID = -1; + +Embed::Embed(Resource *resource) +{ + DBG_OBJ_CREATE ("dw::core::ui::Embed"); + registerName ("dw::core::ui::Embed", &CLASS_ID); + this->resource = resource; + resource->setEmbed (this); + DBG_OBJ_ASSOC_CHILD (resource); +} + +Embed::~Embed() +{ + delete resource; + DBG_OBJ_DELETE (); +} + +void Embed::sizeRequestImpl (Requisition *requisition) +{ + resource->sizeRequest (requisition); +} + +void Embed::getExtremesImpl (Extremes *extremes) +{ + resource->getExtremes (extremes); + correctExtremes (extremes); +} + +void Embed::sizeAllocateImpl (Allocation *allocation) +{ + resource->sizeAllocate (allocation); +} + +int Embed::getAvailWidthOfChild (Widget *child, bool forceValue) +{ + return resource->getAvailWidthOfChild (child, forceValue); +} + +int Embed::getAvailHeightOfChild (Widget *child, bool forceValue) +{ + return resource->getAvailHeightOfChild (child, forceValue); +} + +void Embed::correctRequisitionOfChild (Widget *child, + Requisition *requisition, + void (*splitHeightFun) (int, int*, int*)) +{ + resource->correctRequisitionOfChild (child, requisition, splitHeightFun); +} + +void Embed::correctExtremesOfChild (Widget *child, Extremes *extremes) +{ + resource->correctExtremesOfChild (child, extremes); +} + +void Embed::containerSizeChangedForChildren () +{ + DBG_OBJ_ENTER0 ("resize", 0, "containerSizeChangedForChildren"); + resource->containerSizeChangedForChildren (); + DBG_OBJ_LEAVE (); +} + +void Embed::enterNotifyImpl (core::EventCrossing *event) +{ + resource->emitEnter(); + Widget::enterNotifyImpl(event); +} + +void Embed::leaveNotifyImpl (core::EventCrossing *event) +{ + resource->emitLeave(); + Widget::leaveNotifyImpl(event); +} + +bool Embed::buttonPressImpl (core::EventButton *event) +{ + bool handled; + + if (event->button == 3) { + resource->emitClicked(event); + handled = true; + } else { + handled = false; + } + return handled; +} + +void Embed::setDisplayed (bool displayed) +{ + resource->setDisplayed (displayed); +} + +void Embed::setEnabled (bool enabled) +{ + resource->setEnabled (enabled); +} + +void Embed::draw (View *view, Rectangle *area) +{ + drawWidgetBox (view, area, false); + resource->draw (view, area); +} + +Iterator *Embed::iterator (Content::Type mask, bool atEnd) +{ + return resource->iterator (mask, atEnd); +} + +void Embed::setStyle (style::Style *style) +{ + resource->setStyle (style); + Widget::setStyle (style); +} + +// ---------------------------------------------------------------------- + +bool Resource::ActivateEmitter::emitToReceiver (lout::signal::Receiver + *receiver, + int signalNo, + int argc, Object **argv) +{ + ActivateReceiver *ar = (ActivateReceiver*)receiver; + Resource *res = (Resource*)((Pointer*)argv[0])->getValue (); + + switch (signalNo) { + case 0: + ar->activate (res); + break; + case 1: + ar->enter (res); + break; + case 2: + ar->leave (res); + break; + default: + misc::assertNotReached (); + } + return false; +} + +void Resource::ActivateEmitter::emitActivate (Resource *resource) +{ + Pointer p (resource); + Object *argv[1] = { &p }; + emitVoid (0, 1, argv); +} + +void Resource::ActivateEmitter::emitEnter (Resource *resource) +{ + Pointer p (resource); + Object *argv[1] = { &p }; + emitVoid (1, 1, argv); +} + +void Resource::ActivateEmitter::emitLeave (Resource *resource) +{ + Pointer p (resource); + Object *argv[1] = { &p }; + emitVoid (2, 1, argv); +} + +// ---------------------------------------------------------------------- + +Resource::~Resource () +{ + DBG_OBJ_DELETE (); +} + +void Resource::setEmbed (Embed *embed) +{ + this->embed = embed; +} + +void Resource::getExtremes (Extremes *extremes) +{ + DBG_OBJ_ENTER0 ("resize", 0, "getExtremes"); + + /* Simply return the requisition width */ + Requisition requisition; + sizeRequest (&requisition); + extremes->minWidth = extremes->maxWidth = requisition.width; + extremes->minWidthIntrinsic = extremes->minWidth; + extremes->maxWidthIntrinsic = extremes->maxWidth; + + DBG_OBJ_MSGF ("resize", 1, "result: %d / %d", + extremes->minWidth, extremes->maxWidth); + DBG_OBJ_LEAVE (); +} + +void Resource::sizeAllocate (Allocation *allocation) +{ +} + +int Resource::getAvailWidthOfChild (Widget *child, bool forceValue) +{ + // Only used when the resource contains other dillo widgets. + misc::assertNotReached (); + return 0; +} + +int Resource::getAvailHeightOfChild (Widget *child, bool forceValue) +{ + // Only used when the resource contains other dillo widgets. + misc::assertNotReached (); + return 0; +} + +void Resource::correctRequisitionOfChild (Widget *child, + Requisition *requisition, + void (*splitHeightFun) (int, int*, + int*)) +{ + // Only used when the resource contains other dillo widgets. + misc::assertNotReached (); +} + +void Resource::correctExtremesOfChild (Widget *child, Extremes *extremes) +{ + // Only used when the resource contains other dillo widgets. + misc::assertNotReached (); +} + +void Resource::containerSizeChangedForChildren () +{ + // No children by default. +} + +void Resource::setDisplayed (bool displayed) +{ +} + +void Resource::draw (View *view, Rectangle *area) +{ +} + +void Resource::setStyle (style::Style *style) +{ +} + +void Resource::emitEnter () +{ + activateEmitter.emitEnter(this); +} + +void Resource::emitLeave () +{ + activateEmitter.emitLeave(this); +} + +bool Resource::ClickedEmitter::emitToReceiver(lout::signal::Receiver *receiver, + int signalNo, int argc, + Object **argv) +{ + ((ClickedReceiver*)receiver) + ->clicked ((Resource*)((Pointer*)argv[0])->getValue (), + (EventButton*)((Pointer*)argv[1])->getValue()); + return false; +} + +void Resource::ClickedEmitter::emitClicked (Resource *resource, + EventButton *event) +{ + Pointer p1 (resource); + Pointer p2 (event); + Object *argv[2] = { &p1, &p2 }; + emitVoid (0, 2, argv); +} + +// ---------------------------------------------------------------------- + +Iterator *LabelButtonResource::iterator (Content::Type mask, bool atEnd) +{ + /** \todo Perhaps in brackets? */ + // return new TextIterator (getEmbed (), mask, atEnd, getLabel ()); + /** \bug Not implemented. */ + return new EmptyIterator (getEmbed (), mask, atEnd); +} + +// ---------------------------------------------------------------------- + +void ComplexButtonResource::LayoutReceiver::resizeQueued (bool extremesChanged) +{ + DBG_OBJ_ENTER ("resize", 0, "LayoutReceiver/resizeQueued", "%s", + extremesChanged ? "true" : "false"); + resource->queueResize (extremesChanged); + DBG_OBJ_LEAVE (); +} + +ComplexButtonResource::ComplexButtonResource () +{ + DBG_OBJ_CREATE ("dw::core::ui::ComplexButtonResource"); + layout = NULL; + layoutReceiver.resource = this; + click_x = click_y = -1; +} + +void ComplexButtonResource::init (Widget *widget) +{ + childWidget = widget; + + layout = new Layout (createPlatform ()); + setLayout (layout); + DBG_OBJ_ASSOC_CHILD (layout); + layout->setWidget (widget); + layout->connect (&layoutReceiver); + + if (getEmbed ()) + childWidget->setQuasiParent (getEmbed ()); +} + +void ComplexButtonResource::setEmbed (Embed *embed) +{ + ButtonResource::setEmbed (embed); + + if (childWidget) + childWidget->setQuasiParent (getEmbed ()); +} + +ComplexButtonResource::~ComplexButtonResource () +{ + delete layout; + DBG_OBJ_DELETE (); +} + +void ComplexButtonResource::sizeRequest (Requisition *requisition) +{ + DBG_OBJ_ENTER0 ("resize", 0, "sizeRequest"); + + Requisition widgetRequisition; + childWidget->sizeRequest (&widgetRequisition); + requisition->width = widgetRequisition.width + 2 * reliefXThickness (); + requisition->ascent = widgetRequisition.ascent + reliefYThickness (); + requisition->descent = widgetRequisition.descent + reliefYThickness (); + + DBG_OBJ_MSGF ("resize", 1, "result: %d * (%d + %d)", + requisition->width, requisition->ascent, requisition->descent); + DBG_OBJ_LEAVE (); +} + +void ComplexButtonResource::getExtremes (Extremes *extremes) +{ + DBG_OBJ_ENTER0 ("resize", 0, "getExtremes"); + + Extremes widgetExtremes; + childWidget->getExtremes (&widgetExtremes); + extremes->minWidth = widgetExtremes.minWidth + 2 * reliefXThickness (); + extremes->maxWidth = widgetExtremes.maxWidth + 2 * reliefXThickness (); + extremes->minWidthIntrinsic = extremes->minWidth; + extremes->maxWidthIntrinsic = extremes->maxWidth; + + DBG_OBJ_MSGF ("resize", 1, "result: %d / %d", + extremes->minWidth, extremes->maxWidth); + DBG_OBJ_LEAVE (); +} + +void ComplexButtonResource::sizeAllocate (Allocation *allocation) +{ +} + +int ComplexButtonResource::getAvailWidthOfChild (Widget *child, bool forceValue) +{ + int embedWidth = getEmbed()->getAvailWidth (forceValue); + if (embedWidth == -1) + return -1; + else + return misc::max (embedWidth - 2 * reliefXThickness (), 0); +} + +int ComplexButtonResource::getAvailHeightOfChild (Widget *child, + bool forceValue) +{ + int embedHeight = getEmbed()->getAvailHeight (forceValue); + if (embedHeight == -1) + return -1; + else + return misc::max (embedHeight - 2 * reliefYThickness (), 0); +} + +void ComplexButtonResource::correctRequisitionOfChild (Widget *child, + Requisition *requisition, + void (*splitHeightFun) + (int, int*, int*)) +{ + // Similar to Widget::correctRequisitionOfChild, but for percentage + // the relief has to be considered. + + if (style::isPerLength (child->getStyle()->width)) { + int availWidth = getEmbed()->getAvailHeight (false); + if (availWidth != -1) { + int baseWidth = misc::max (availWidth + - getEmbed()->boxDiffWidth () + - 2 * reliefXThickness (), + 0); + requisition->width = + child->applyPerWidth (baseWidth, child->getStyle()->width); + } + } else + getEmbed()->correctReqWidthOfChildNoRec (child, requisition); + + // TODO Percentage heights are ignored again. + getEmbed()->correctReqHeightOfChildNoRec (child, requisition, + splitHeightFun); + +} + +void ComplexButtonResource::correctExtremesOfChild (Widget *child, + Extremes *extremes) +{ + // Similar to Widget::correctExtremesOfChild, but for percentage + // the relief has to be considered. + + if (style::isPerLength (child->getStyle()->width)) { + int availWidth = getEmbed()->getAvailHeight (false); + if (availWidth != -1) { + int baseWidth = misc::max (availWidth + - getEmbed()->boxDiffWidth () + - 2 * reliefXThickness (), + 0); + extremes->minWidth = extremes->maxWidth = + child->applyPerWidth (baseWidth, child->getStyle()->width); + } + } else + getEmbed()->correctExtremesOfChildNoRec (child, extremes); +} + +void ComplexButtonResource::containerSizeChangedForChildren () +{ + layout->containerSizeChanged (); +} + +Iterator *ComplexButtonResource::iterator (Content::Type mask, bool atEnd) +{ + /** + * \bug Implementation. + * This is a bit more complicated: We have two layouts here. + */ + return new EmptyIterator (getEmbed (), mask, atEnd); +} + +// ---------------------------------------------------------------------- + +Iterator *TextResource::iterator (Content::Type mask, bool atEnd) +{ + // return new TextIterator (getEmbed (), mask, atEnd, getText ()); + /** \bug Not implemented. */ + return new EmptyIterator (getEmbed (), mask, atEnd); +} + +// ---------------------------------------------------------------------- + +Iterator *CheckButtonResource::iterator (Content::Type mask, bool atEnd) +{ + //return new TextIterator (getEmbed (), mask, atEnd, + // isActivated () ? "[X]" : "[ ]"); + /** \bug Not implemented. */ + return new EmptyIterator (getEmbed (), mask, atEnd); +} + +// ---------------------------------------------------------------------- + +RadioButtonResource::GroupIterator::~GroupIterator () +{ +} + +Iterator *RadioButtonResource::iterator (Content::Type mask, bool atEnd) +{ + //return new TextIterator (getEmbed (), mask, atEnd, + // isActivated () ? "(*)" : "( )"); + /** \bug Not implemented. */ + return new EmptyIterator (getEmbed (), mask, atEnd); +} + +} // namespace ui +} // namespace core +} // namespace dw + diff --git a/dw/ui.hh b/dw/ui.hh new file mode 100644 index 0000000..6703ccc --- /dev/null +++ b/dw/ui.hh @@ -0,0 +1,592 @@ +#ifndef __DW_UI_HH__ +#define __DW_UI_HH__ + +#ifndef __INCLUDED_FROM_DW_CORE_HH__ +# error Do not include this file directly, use "core.hh" instead. +#endif + +namespace dw { +namespace core { + +/** + * \brief Anything related to embedded UI widgets is defined here. + * + * UI resources are another abstraction for Dw widgets, which are not + * fully implemented in a platform-independent way. Typically, they + * involve creating widgets, which the underlying UI toolkit provides. + * + * As you see in this diagram: + * + * \dot + * digraph G { + * node [shape=record, fontname=Helvetica, fontsize=10]; + * edge [arrowhead="none", arrowtail="empty", dir="both", + * labelfontname=Helvetica, labelfontsize=10, color="#404040", + * labelfontcolor="#000080"]; + * fontname=Helvetica; fontsize=10; + * + * subgraph cluster_core { + * style="dashed"; color="#000080"; fontname=Helvetica; fontsize=10; + * label="dw::core"; + * + * subgraph cluster_ui { + * style="dashed"; color="#000080"; fontname=Helvetica; fontsize=10; + * label="dw::core::ui"; + * + * Embed [URL="\ref dw::core::ui::Embed"]; + * Resource [color="#a0a0a0", URL="\ref dw::core::ui::Resource"]; + * LabelButtonResource [color="#a0a0a0", + * URL="\ref dw::core::ui::LabelButtonResource"]; + * EntryResource [color="#a0a0a0", + * URL="\ref dw::core::ui::EntryResource"]; + * etc [color="#a0a0a0", label="..."]; + * } + * + * Widget [URL="\ref dw::core::Widget", color="#a0a0a0"]; + * } + * + * subgraph cluster_fltk { + * style="dashed"; color="#000080"; fontname=Helvetica; fontsize=10; + * label="dw::fltk::ui"; + * + * FltkLabelButtonResource + * [URL="\ref dw::fltk::ui::FltkLabelButtonResource"]; + * FltkEntryResource [URL="\ref dw::fltk::ui::FltkEntryResource"]; + * } + * + * Widget -> Embed; + * Embed -> Resource [arrowhead="open", arrowtail="none", + * headlabel="1", taillabel="1"]; + * Resource -> LabelButtonResource; + * Resource -> EntryResource; + * Resource -> etc; + * LabelButtonResource -> FltkLabelButtonResource; + * EntryResource -> FltkEntryResource; + * } + * \enddot + * + * <center>[\ref uml-legend "legend"]</center> + * + * there are several levels: + * + * <ol> + * <li> The Dw widget is dw::core::ui::Embed. It delegates most to + * dw::core::ui::Resource, which has similar methods like + * dw::core::Widget. + * + * <li> There are several sub interfaces of dw::core::ui::Resource, which + * may provide methods, as e.g. dw::core::ui::ListResource::addItem. In a + * platform independent context, you can cast the result of + * dw::core::ui::Embed::getResource to a specific sub class, if you + * know, which one is used. E.g., if you know, that a given instance + * dw::core::ui::Embed refers to a dw::core::ui::ListResource, you can + * write something like: + * + * \code + * dw::core::ui::Embed *embed; + * //... + * ((dw::core::ui::ListResource*)embed->getResource ())->addItem ("Hello!"); + * \endcode + * + * <li> These sub classes are then fully implemented in a platform specific + * way. For an example, look at dw::fltk::ui. + * </ol> + * + * There is a factory interface, dw::core::ui::ResourceFactory, which + * provides methods for creating common resources. By calling + * dw::core::Layout::getResourceFactory, which calls + * dw::core::Platform::getResourceFactory, you get the factory for the used + * platform. + * + * It is possible to define additional sub classes of + * dw::core::ui::Resource, but since they are not provided by + * dw::core::ui::ResourceFactory, you have to define some other + * abstractions, if you want to remain platform independent. + * + * + * <h3>...</h3> + * + * + * <h3>Resouces needed for HTML</h3> + * + * This chapter describes, how the form controls defined by HTML are + * implemented in Dw. Some of them do not refer to UI resources, but to + * other widgets, links to the respective documentations are provided + * here. + * + * <h4>Resouces created with \<INPUT\></h4> + * + * The HTML \<INPUT\> is always implemented by using UI + * resources. \<INPUT\> element has the following attributes: + * + * <table> + * <tr><th>Attribute <th>Implementation + * <tr><td>type <td>This defines the resource you have to instantiate. + * <tr><td>name <td>Not needed within Dw. + * <tr><td>value <td>The initial value is treated differently by different + * resources. + * <tr><td>checked <td>Parameter to + * dw::core::ui::ResourceFactory::createCheckButtonResource + * and dw::core::ui::ResourceFactory::createRadioButtonResource. + * <tr><td>disabled <td>This is provided for all resources by + * dw::core::ui::Resource::setEnabled. + * <tr><td>readonly <td>This is provided by + * dw::core::ui::TextResource::setEditable. + * <tr><td>size <td>This is handled by styles. + * <tr><td>maxlength <td>Parameter of + * dw::core::ui::ResourceFactory::createEntryResource. + * <tr><td>src <td>Handled by the caller (HTML parser). + * <tr><td>alt <td>Handled by the caller (HTML parser). + * <tr><td>usemap <td>Handled by the caller (HTML parser). + * <tr><td>ismap <td>Handled by the caller (HTML parser). + * <tr><td>tabindex <td>Not supported currently. + * <tr><td>accesskey <td>Not supported currently. + * <tr><td>onfocus <td>Not supported currently. + * <tr><td>onblur <td>Not supported currently. + * <tr><td>onselect <td>Not supported currently. + * <tr><td>onchange <td>Not supported currently. + * <tr><td>accept <td>Not supported currently. + * </table> + * + * For the different values of \em type, the following resources can be + * used: + * + * <table> + * <tr><th>Type <th>Resource + * <th>Factory Method + * <tr><td>text <td>dw::core::ui::EntryResource + * <td>dw::core::ui::ResourceFactory::createEntryResource + * <tr><td>password <td>dw::core::ui::EntryResource + * <td>dw::core::ui::ResourceFactory::createEntryResource + * <tr><td>checkbox <td>dw::core::ui::CheckButtonResource + * <td>dw::core::ui::ResourceFactory::createCheckButtonResource + * <tr><td>radio <td>dw::core::ui::RadioButtonResource + * <td>dw::core::ui::ResourceFactory::createRadioButtonResource + * <tr><td>submit <td>dw::core::ui::LabelButtonResource + * <td>dw::core::ui::ResourceFactory::createLabelButtonResource + * <tr><td>image <td>dw::core::ui::ComplexButtonResource + * <td>dw::core::ui::ResourceFactory::createComplexButtonResource, + * width a dw::Image inside and relief = false. + * <tr><td>reset <td>dw::core::ui::LabelButtonResource + * <td>dw::core::ui::ResourceFactory::createLabelButtonResource + * <tr><td>button <td>dw::core::ui::LabelButtonResource + * <td>dw::core::ui::ResourceFactory::createLabelButtonResource + * <tr><td>hidden <td>No rendering necessary. + * <td>- + * <tr><td>file <td>Not supported currently. + * <td>- + * </table> + * + * <h4>\<SELECT\>, \<OPTGROUP\>, and \<OPTION\></h4> + * + * \<SELECT\> is implemented either by dw::core::ui::OptionMenuResource + * (better suitable for \em size = 1 and single selection) or + * dw::core::ui::ListResource, which have a common base, + * dw::core::ui::SelectionResource. In the latter case, \em size must be + * specified via dw::core::style::Style. + * + * Factory methods are dw::core::ui::ResourceFactory::createListResource and + * dw::core::ui::ResourceFactory::createOptionMenuResource. + * + * \<OPTION\>'s are added via dw::core::ui::SelectionResource::addItem. + * + * \<OPTGROUP\> are created by using dw::core::ui::SelectionResource::pushGroup + * and dw::core::ui::SelectionResource::popGroup. + * + * For lists, the selection mode must be set in + * dw::core::ui::ResourceFactory::createListResource. + * + * <h4>\<TEXTAREA\></h4> + * + * \<TEXTAREA\> is implemented by dw::core::ui::MultiLineTextResource, + * the factory method is + * dw::core::ui::ResourceFactory::createMultiLineTextResource. + * dw::core::ui::TextResource::setEditable can be used, as for entries. + * + * <h4>\<BUTTON\></h4> + * + * For handling \<BUTTON\>, dw::core::ui::ComplexButtonResource should be used, + * with a dw::Textblock inside, and relief = true. The contents of \<BUTTON\> + * is then added to the dw::Textblock. + * + * \todo describe activation signal + */ +namespace ui { + +class Resource; + +/** + * \brief A widget for embedding UI widgets. + * + * \sa dw::core::ui + */ +class Embed: public Widget +{ + friend class Resource; + +private: + Resource *resource; + +protected: + void sizeRequestImpl (Requisition *requisition); + void getExtremesImpl (Extremes *extremes); + void sizeAllocateImpl (Allocation *allocation); + + int getAvailWidthOfChild (Widget *child, bool forceValue); + int getAvailHeightOfChild (Widget *child, bool forceValue); + void correctRequisitionOfChild (Widget *child, + Requisition *requisition, + void (*splitHeightFun) (int, int*, int*)); + void correctExtremesOfChild (Widget *child, Extremes *extremes); + + void containerSizeChangedForChildren (); + + void enterNotifyImpl (core::EventCrossing *event); + void leaveNotifyImpl (core::EventCrossing *event); + bool buttonPressImpl (core::EventButton *event); + +public: + static int CLASS_ID; + + Embed(Resource *resource); + ~Embed(); + + void setDisplayed (bool displayed); + void setEnabled (bool enabled); + void draw (View *view, Rectangle *area); + Iterator *iterator (Content::Type mask, bool atEnd); + void setStyle (style::Style *style); + + inline Resource *getResource () { return resource; } + + inline void correctReqWidthOfChildNoRec (Widget *child, + Requisition *requisition) + { Widget::correctReqWidthOfChild (child, requisition); } + + inline void correctReqHeightOfChildNoRec (Widget *child, + Requisition *requisition, + void (*splitHeightFun) (int, int*, + int*)) + { Widget::correctReqHeightOfChild (child, requisition, splitHeightFun); } + + virtual void correctExtremesOfChildNoRec (Widget *child, Extremes *extremes) + { Widget::correctExtremesOfChild (child, extremes); } +}; + +/** + * \brief Basic interface for all resources. + * + * \sa dw::core::ui + */ +class Resource +{ + friend class Embed; + +public: + /** + * \brief Receiver interface for the "activate" signal. + */ + class ActivateReceiver: public lout::signal::Receiver + { + public: + virtual void activate (Resource *resource) = 0; + virtual void enter (Resource *resource) = 0; + virtual void leave (Resource *resource) = 0; + }; + /** + * \brief Receiver interface for the "clicked" signal. + */ + class ClickedReceiver: public lout::signal::Receiver + { + public: + virtual void clicked (Resource *resource, EventButton *event) = 0; + }; + +private: + class ActivateEmitter: public lout::signal::Emitter + { + protected: + bool emitToReceiver (lout::signal::Receiver *receiver, int signalNo, + int argc, Object **argv); + public: + inline void connectActivate (ActivateReceiver *receiver) { + connect (receiver); } + void emitActivate (Resource *resource); + void emitEnter (Resource *resource); + void emitLeave (Resource *resource); + }; + + class ClickedEmitter: public lout::signal::Emitter + { + protected: + bool emitToReceiver (lout::signal::Receiver *receiver, int signalNo, + int argc, Object **argv); + public: + inline void connectClicked (ClickedReceiver *receiver) { + connect (receiver); } + void emitClicked (Resource *resource, EventButton *event); + }; + + Embed *embed; + ActivateEmitter activateEmitter; + ClickedEmitter clickedEmitter; + + void emitEnter (); + void emitLeave (); +protected: + inline void queueResize (bool extremesChanged) { + if (embed) embed->queueResize (0, extremesChanged); + } + + virtual Embed *getEmbed () { return embed; } + virtual void setEmbed (Embed *embed); + + inline void emitActivate () { + return activateEmitter.emitActivate (this); } + inline void emitClicked (EventButton *event) { + clickedEmitter.emitClicked (this, event); } + +public: + inline Resource () + { embed = NULL; DBG_OBJ_CREATE ("dw::core::ui::Resource"); } + + virtual ~Resource (); + + virtual void sizeRequest (Requisition *requisition) = 0; + virtual void getExtremes (Extremes *extremes); + virtual void sizeAllocate (Allocation *allocation); + + virtual int getAvailWidthOfChild (Widget *child, bool forceValue); + virtual int getAvailHeightOfChild (Widget *child, bool forceValue); + virtual void correctRequisitionOfChild (Widget *child, + Requisition *requisition, + void (*splitHeightFun) (int, int*, + int*)); + virtual void correctExtremesOfChild (Widget *child, Extremes *extremes); + virtual void containerSizeChangedForChildren (); + + virtual void setDisplayed (bool displayed); + virtual void draw (View *view, Rectangle *area); + virtual Iterator *iterator (Content::Type mask, bool atEnd) = 0; + virtual void setStyle (style::Style *style); + + virtual bool isEnabled () = 0; + virtual void setEnabled (bool enabled) = 0; + + inline void connectActivate (ActivateReceiver *receiver) { + activateEmitter.connectActivate (receiver); } + inline void connectClicked (ClickedReceiver *receiver) { + clickedEmitter.connectClicked (receiver); } +}; + + +class ButtonResource: public Resource +{}; + +/** + * \brief Interface for labelled buttons resources. + */ +class LabelButtonResource: public ButtonResource +{ +public: + Iterator *iterator (Content::Type mask, bool atEnd); + + virtual const char *getLabel () = 0; + virtual void setLabel (const char *label) = 0; +}; + +class ComplexButtonResource: public ButtonResource +{ +private: + class LayoutReceiver: public Layout::Receiver + { + public: + ComplexButtonResource *resource; + + void resizeQueued (bool extremesChanged); + }; + + friend class LayoutReceiver; + LayoutReceiver layoutReceiver; + + Widget *childWidget; + +protected: + Layout *layout; + int click_x, click_y; + + void setEmbed (Embed *embed); + + virtual Platform *createPlatform () = 0; + virtual void setLayout (Layout *layout) = 0; + + virtual int reliefXThickness () = 0; + virtual int reliefYThickness () = 0; + + void init (Widget *widget); + +public: + ComplexButtonResource (); + ~ComplexButtonResource (); + + void sizeRequest (Requisition *requisition); + void getExtremes (Extremes *extremes); + void sizeAllocate (Allocation *allocation); + + int getAvailWidthOfChild (Widget *child, bool forceValue); + int getAvailHeightOfChild (Widget *child, bool forceValue); + void correctRequisitionOfChild (Widget *child, + Requisition *requisition, + void (*splitHeightFun) (int, int*, int*)); + void correctExtremesOfChild (Widget *child, Extremes *extremes); + void containerSizeChangedForChildren (); + + Iterator *iterator (Content::Type mask, bool atEnd); + int getClickX () {return click_x;}; + int getClickY () {return click_y;}; +}; + +/** + * \brief Base interface for dw::core::ui::ListResource and + * dw::core::ui::OptionMenuResource. + */ +class SelectionResource: public Resource +{ +public: + virtual void addItem (const char *str, bool enabled, bool selected) = 0; + virtual void setItem (int index, bool selected) = 0; + virtual void pushGroup (const char *name, bool enabled) = 0; + virtual void popGroup () = 0; + + virtual int getNumberOfItems () = 0; + virtual bool isSelected (int index) = 0; +}; + +class ListResource: public SelectionResource +{ +public: + enum SelectionMode { + /** + * \brief Exactly one item is selected. + * + * If no item is selected initially, the first one is selected. + */ + SELECTION_EXACTLY_ONE, + + /** + * \brief Exactly one item is selected, except possibly at the beginning. + * + * If no item is selected initially, no one is selected automatically. + * The user may not unselect the only selected item. + */ + SELECTION_EXACTLY_ONE_BY_USER, + + /** + * \brief At most one item is selected. + * + * If no item is selected initially, no one is selected automatically. + * The user may unselect the only selected item. + */ + SELECTION_AT_MOST_ONE, + + /** + * \brief An arbitrary number of items may be selected. + */ + SELECTION_MULTIPLE + }; +}; + +class OptionMenuResource: public SelectionResource +{ +}; + +class TextResource: public Resource +{ +public: + Iterator *iterator (Content::Type mask, bool atEnd); + + virtual const char *getText () = 0; + virtual void setText (const char *text) = 0; + virtual bool isEditable () = 0; + virtual void setEditable (bool editable) = 0; +}; + +class EntryResource: public TextResource +{ +public: + enum { UNLIMITED_SIZE = -1 }; + virtual void setMaxLength (int maxlen) = 0; +}; + +class MultiLineTextResource: public TextResource +{ +}; + + +class ToggleButtonResource: public Resource +{ +public: + virtual bool isActivated () = 0; + virtual void setActivated (bool activated) = 0; +}; + +class CheckButtonResource: public ToggleButtonResource +{ +public: + Iterator *iterator (Content::Type mask, bool atEnd); +}; + +class RadioButtonResource: public ToggleButtonResource +{ +public: + class GroupIterator + { + protected: + GroupIterator () { } + virtual ~GroupIterator (); + + public: + virtual bool hasNext () = 0; + virtual RadioButtonResource *getNext () = 0; + virtual void unref () = 0; + }; + + /** + * \brief Return an iterator, to access all radio button resources + * within the group. + */ + virtual GroupIterator *groupIterator () = 0; + + Iterator *iterator (Content::Type mask, bool atEnd); +}; + + +/** + * \brief A factory for the common resource. + */ +class ResourceFactory: public lout::object::Object +{ +public: + virtual LabelButtonResource *createLabelButtonResource (const char *label) + = 0; + virtual ComplexButtonResource *createComplexButtonResource (Widget *widget, + bool relief) + = 0; + virtual ListResource *createListResource (ListResource::SelectionMode + selectionMode, int rows) = 0; + virtual OptionMenuResource *createOptionMenuResource () = 0; + virtual EntryResource *createEntryResource (int size, bool password, + const char *label) = 0; + virtual MultiLineTextResource *createMultiLineTextResource (int cols, + int rows) = 0; + virtual CheckButtonResource *createCheckButtonResource (bool activated) = 0; + virtual RadioButtonResource *createRadioButtonResource (RadioButtonResource + *groupedWith, + bool activated) = 0; +}; + +} // namespace ui +} // namespace core +} // namespace dw + +#endif // __DW_UI_HH__ diff --git a/dw/view.hh b/dw/view.hh new file mode 100644 index 0000000..8037dc6 --- /dev/null +++ b/dw/view.hh @@ -0,0 +1,211 @@ +#ifndef __DW_VIEW_HH__ +#define __DW_VIEW_HH__ + +#ifndef __INCLUDED_FROM_DW_CORE_HH__ +# error Do not include this file directly, use "core.hh" instead. +#endif + +namespace dw { +namespace core { + +/** + * \brief An interface to encapsulate platform dependent drawing. + * + * \sa\ref dw-overview, \ref dw-layout-views + */ +class View: public lout::object::Object +{ +public: + /* + * ---------------------------- + * Operations on the view + * ---------------------------- + */ + + /** + * \brief This methods notifies the view, that it has been attached to a + * layout. + */ + virtual void setLayout (Layout *layout) = 0; + + /** + * \brief Set the canvas size. + */ + virtual void setCanvasSize (int width, int ascent, int descent) = 0; + + /** + * \brief Set the cursor appearance. + */ + virtual void setCursor (style::Cursor cursor) = 0; + + /** + * \brief Set the background of the view. + */ + virtual void setBgColor (style::Color *color) = 0; + + /* + * --------------------------------------------------------- + * Scrolling and Related. Only usesViewport must be + * implemented, if it returns false, the other methods + * are never called. + * ------------------------------------------------------- + */ + + /** + * \brief Return, whether this view uses a viewport. + */ + virtual bool usesViewport () = 0; + + /** + * \brief Get the thickness of the horizontal scrollbar, when it is + * visible. + * + * Does not have to be implemented, when usesViewport returns false. + */ + virtual int getHScrollbarThickness () = 0; + + /** + * \brief Get the thickness of the vertical scrollbar, when it is + * visible. + * + * Does not have to be implemented, when usesViewport returns false. + */ + virtual int getVScrollbarThickness () = 0; + + /** + * \brief Scroll the vieport to the given position. + * + * Does not have to be implemented, when usesViewport returns false. + */ + virtual void scrollTo (int x, int y) = 0; + + /** + * \brief Scroll the viewport as commanded. + */ + virtual void scroll (ScrollCommand) { }; + + /** + * \brief Set the viewport size. + * + * Does not have to be implemented, when usesViewport returns false. + * + * This will normally imply a resize of the UI widget. Width and height are + * the dimensions of the new size, \em including the scrollbar thicknesses. + * + */ + virtual void setViewportSize (int width, int height, + int hScrollbarThickness, + int vScrollbarThickness) = 0; + + /* + * ----------------------- + * Drawing functions + * ----------------------- + */ + + /** + * \brief Called before drawing. + * + * All actual drawing operations will be enclosed into calls of + * dw::core:View::startDrawing and dw::core:View::finishDrawing. They + * may be implemented, e.g. when a backing + * pixmap is used, to prevent flickering. StartDrawing() will then + * initialize the backing pixmap, all other drawing operations will draw + * into it, and finishDrawing() will merge it into the window. + */ + virtual void startDrawing (Rectangle *area) = 0; + + /** + * \brief Called after drawing. + * + * \sa dw::core:View::startDrawing + */ + virtual void finishDrawing (Rectangle *area) = 0; + + /** + * \brief Queue a region, which is given in \em canvas coordinates, for + * drawing. + * + * The view implementation is responsible, that this region is drawn, either + * immediately, or (which is more typical, since more efficient) the areas + * are collected, combined (as far as possible), and the drawing is later + * done in an idle function. + */ + virtual void queueDraw (Rectangle *area) = 0; + + /** + * \brief Queue the total viewport for drawing. + * + * \sa dw::core::View::queueDraw + */ + virtual void queueDrawTotal () = 0; + + /** + * \brief Cancel a draw queue request. + * + * If dw::core::View::queueDraw or dw::core::View::queueDrawTotal have been + * called before, and the actual drawing was not processed yet, the actual + * drawing should be cancelled. Otherwise, the cancellation should be + * ignored. + */ + virtual void cancelQueueDraw () = 0; + + /* + * The following methods should be self-explaining. + */ + + virtual void drawPoint (style::Color *color, + style::Color::Shading shading, + int x, int y) = 0; + virtual void drawLine (style::Color *color, + style::Color::Shading shading, + int x1, int y1, int x2, int y2) = 0; + virtual void drawTypedLine (style::Color *color, + style::Color::Shading shading, + style::LineType type, int width, + int x1, int y1, int x2, int y2) = 0; + virtual void drawRectangle (style::Color *color, + style::Color::Shading shading, bool filled, + int x, int y, int width, int height) = 0; + virtual void drawArc (style::Color *color, + style::Color::Shading shading, bool filled, + int centerX, int centerY, int width, int height, + int angle1, int angle2) = 0; + virtual void drawPolygon (style::Color *color, + style::Color::Shading shading, + bool filled, bool convex, Point *points, + int npoints) = 0; + virtual void drawText (style::Font *font, + style::Color *color, + style::Color::Shading shading, + int x, int y, const char *text, int len) = 0; + virtual void drawSimpleWrappedText (style::Font *font, style::Color *color, + style::Color::Shading shading, + int x, int y, int w, int h, + const char *text) = 0; + virtual void drawImage (Imgbuf *imgbuf, int xRoot, int yRoot, + int x, int y, int width, int height) = 0; + + /* + * -------------- + * Clipping + * -------------- + */ + + /* + * To prevent drawing outside of a given area, a clipping view may be + * requested, which also implements this interface. The clipping view is + * related to the parent view (clipping views may be nested!), anything + * which is drawn into this clipping view, is later merged again into the + * parent view. An implementation will typically use additional pixmaps, + * which are later merged into the parent view pixmap/window. + */ + + virtual View *getClippingView (int x, int y, int width, int height) = 0; + virtual void mergeClippingView (View *clippingView) = 0; +}; + +} // namespace core +} // namespace dw + +#endif // __DW_VIEW_HH__ diff --git a/dw/widget.cc b/dw/widget.cc new file mode 100644 index 0000000..4ebce30 --- /dev/null +++ b/dw/widget.cc @@ -0,0 +1,1785 @@ +/* + * RTFL (originally part of dillo) + * + * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version; with the following exception: + * + * The copyright holders of RTFL give you permission to link this file + * statically or dynamically against all versions of the graphviz + * library, which are published by AT&T Corp. under one of the following + * licenses: + * + * - Common Public License version 1.0 as published by International + * Business Machines Corporation (IBM), or + * - Eclipse Public License version 1.0 as published by the Eclipse + * Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "core.hh" + +#include "../lout/msg.h" +#include "../lout/debug.hh" + +using namespace lout; +using namespace lout::object; + +namespace dw { +namespace core { + +// ---------------------------------------------------------------------- + +bool Widget::WidgetImgRenderer::readyToDraw () +{ + return widget->wasAllocated (); +} + +void Widget::WidgetImgRenderer::getBgArea (int *x, int *y, int *width, + int *height) +{ + widget->getPaddingArea (x, y, width, height); +} + +void Widget::WidgetImgRenderer::getRefArea (int *xRef, int *yRef, int *widthRef, + int *heightRef) +{ + widget->getPaddingArea (xRef, yRef, widthRef, heightRef); +} + +style::Style *Widget::WidgetImgRenderer::getStyle () +{ + return widget->getStyle (); +} + +void Widget::WidgetImgRenderer::draw (int x, int y, int width, int height) +{ + widget->queueDrawArea (x - widget->allocation.x, y - widget->allocation.y, + width, height); +} + +// ---------------------------------------------------------------------- + +bool Widget::adjustMinWidth = false; +int Widget::CLASS_ID = -1; + +Widget::Widget () +{ + DBG_OBJ_CREATE ("dw::core::Widget"); + registerName ("dw::core::Widget", &CLASS_ID); + + flags = (Flags)(NEEDS_RESIZE | EXTREMES_CHANGED); + parent = quasiParent = generator = container = NULL; + DBG_OBJ_SET_PTR ("container", container); + + layout = NULL; + + allocation.x = -1; + allocation.y = -1; + allocation.width = 1; + allocation.ascent = 1; + allocation.descent = 0; + + extraSpace.top = extraSpace.right = extraSpace.bottom = extraSpace.left = 0; + + style = NULL; + bgColor = NULL; + buttonSensitive = true; + buttonSensitiveSet = false; + + deleteCallbackData = NULL; + deleteCallbackFunc = NULL; + + widgetImgRenderer = NULL; +} + +Widget::~Widget () +{ + if (deleteCallbackFunc) + deleteCallbackFunc (deleteCallbackData); + + if (widgetImgRenderer) { + if (style && style->backgroundImage) + style->backgroundImage->removeExternalImgRenderer (widgetImgRenderer); + delete widgetImgRenderer; + } + + if (style) + style->unref (); + + if (parent) + parent->removeChild (this); + else if (layout) + layout->removeWidget (); + + DBG_OBJ_DELETE (); +} + + +/** + * \brief Calculates the intersection of widget->allocation and area, returned + * in intersection (in widget coordinates!). + * + * Typically used by containers when + * drawing their children. Returns whether intersection is not empty. + */ +bool Widget::intersects (Rectangle *area, Rectangle *intersection) +{ + Rectangle parentArea, childArea; + + parentArea = *area; + parentArea.x += parent->allocation.x; + parentArea.y += parent->allocation.y; + + childArea.x = allocation.x; + childArea.y = allocation.y; + childArea.width = allocation.width; + childArea.height = getHeight (); + + if (parentArea.intersectsWith (&childArea, intersection)) { + intersection->x -= allocation.x; + intersection->y -= allocation.y; + return true; + } else + return false; +} + +void Widget::setParent (Widget *parent) +{ + this->parent = parent; + layout = parent->layout; + + if (!buttonSensitiveSet) + buttonSensitive = parent->buttonSensitive; + + DBG_OBJ_ASSOC_PARENT (parent); + //printf ("The %s %p becomes a child of the %s %p\n", + // getClassName(), this, parent->getClassName(), parent); + + // Determine the container. Currently rather simple; will become + // more complicated when absolute and fixed positions are + // supported. + container = NULL; + for (Widget *widget = getParent (); widget != NULL && container == NULL; + widget = widget->getParent()) + if (widget->isPossibleContainer ()) + container = widget; + // If there is no possible container widget, there is + // (surprisingly!) also no container (i. e. the viewport is + // used). Does not occur in dillo, where the toplevel widget is a + // Textblock. + DBG_OBJ_SET_PTR ("container", container); + + notifySetParent(); +} + +void Widget::setQuasiParent (Widget *quasiParent) +{ + this->quasiParent = quasiParent; + + // More to do? Compare with setParent(). + + DBG_OBJ_SET_PTR ("quasiParent", quasiParent); +} + +void Widget::queueDrawArea (int x, int y, int width, int height) +{ + /** \todo Maybe only the intersection? */ + + DBG_OBJ_ENTER ("draw", 0, "queueDrawArea", "%d, %d, %d, %d", + x, y, width, height); + + _MSG("Widget::queueDrawArea alloc(%d %d %d %d) wid(%d %d %d %d)\n", + allocation.x, allocation.y, + allocation.width, allocation.ascent + allocation.descent, + x, y, width, height); + if (layout) + layout->queueDraw (x + allocation.x, y + allocation.y, width, height); + + DBG_OBJ_LEAVE (); +} + +/** + * \brief This method should be called, when a widget changes its size. + * + * A "fast" queueResize will ignore the anchestors, and furthermore + * not trigger the idle function. Used only within + * viewportSizeChanged, and not available outside Layout and Widget. + */ +void Widget::queueResize (int ref, bool extremesChanged, bool fast) +{ + DBG_OBJ_ENTER ("resize", 0, "queueResize", "%d, %s, %s", + ref, extremesChanged ? "true" : "false", + fast ? "true" : "false"); + + // queueResize() can be called recursively; calls are queued, so + // that actualQueueResize() is clean. + + if (queueResizeEntered ()) { + DBG_OBJ_MSG ("resize", 1, "put into queue"); + layout->queueQueueResizeList->pushUnder (new Layout::QueueResizeItem + (this, ref, extremesChanged, + fast)); + } else { + actualQueueResize (ref, extremesChanged, fast); + + DBG_IF_RTFL { + if (layout == NULL) + DBG_OBJ_MSG ("resize", 1, "layout is not set"); + else if (layout->queueQueueResizeList->size () == 0) + DBG_OBJ_MSG ("resize", 1, "queue item list is empty"); + } + + while (layout != NULL && layout->queueQueueResizeList->size () > 0) { + DBG_IF_RTFL { + DBG_OBJ_MSGF ("resize", 1, "queue item list has %d elements:", + layout->queueQueueResizeList->size ()); +#if 0 + // TODO This worked when queueQueueResizeList was a Vector; now, + // iterators should be used. + DBG_OBJ_MSG_START (); + for (int i = 0; i < layout->queueQueueResizeList->size (); i++) { + DBG_OBJ_MSGF + ("resize", 1, + "#%d: widget = %p, ref = %d, extremesChanged = %s, " + "fast = %s", + i, layout->queueQueueResizeList->get(i)->widget, + layout->queueQueueResizeList->get(i)->ref, + layout->queueQueueResizeList->get(i)->extremesChanged ? + "true" : "false", + layout->queueQueueResizeList->get(i)->fast ? + "true" : "false"); + } + DBG_OBJ_MSG_END (); + DBG_OBJ_MSG ("resize", 1, "taking #0 out of list"); +#endif + } + + Layout::QueueResizeItem *item = + layout->queueQueueResizeList->getTop (); + item->widget->actualQueueResize (item->ref, item->extremesChanged, + item->fast); + layout->queueQueueResizeList->pop (); + } + } + + DBG_OBJ_LEAVE (); +} + +void Widget::actualQueueResize (int ref, bool extremesChanged, bool fast) +{ + assert (!queueResizeEntered ()); + + DBG_OBJ_ENTER ("resize", 0, "actualQueueResize", "%d, %s, %s", + ref, extremesChanged ? "true" : "false", + fast ? "true" : "false"); + + enterQueueResize (); + + Widget *widget2, *child; + + Flags resizeFlag, extremesFlag; + + if (layout) { + // If RESIZE_QUEUED is set, this widget is already in the list. + if (!resizeQueued ()) + layout->queueResizeList->put (this); + + resizeFlag = RESIZE_QUEUED; + extremesFlag = EXTREMES_QUEUED; + } else { + resizeFlag = NEEDS_RESIZE; + extremesFlag = EXTREMES_CHANGED; + } + + setFlags (resizeFlag); + setFlags (ALLOCATE_QUEUED); + markSizeChange (ref); + + if (extremesChanged) { + setFlags (extremesFlag); + markExtremesChange (ref); + } + + if (fast) { + if (parent) { + // In this case, queueResize is called from top (may be a + // random entry point) to bottom, so markSizeChange and + // markExtremesChange have to be called explicitly for the + // parent. The tests (needsResize etc.) are uses to check + // whether queueResize has been called for the parent, or + // whether this widget is the enty point. + if (parent->needsResize () || parent->resizeQueued ()) + parent->markSizeChange (parentRef); + if (parent->extremesChanged () || parent->extremesQueued ()) + parent->markExtremesChange (parentRef); + } + } else { + for (widget2 = parent, child = this; widget2; + child = widget2, widget2 = widget2->parent) { + if (layout && !widget2->resizeQueued ()) + layout->queueResizeList->put (widget2); + + DBG_OBJ_MSGF ("resize", 2, "setting %s and ALLOCATE_QUEUED for %p", + resizeFlag == RESIZE_QUEUED ? + "RESIZE_QUEUED" : "NEEDS_RESIZE", + widget2); + + widget2->setFlags (resizeFlag); + widget2->markSizeChange (child->parentRef); + widget2->setFlags (ALLOCATE_QUEUED); + + if (extremesChanged) { + widget2->setFlags (extremesFlag); + widget2->markExtremesChange (child->parentRef); + } + } + + if (layout) + layout->queueResize (extremesChanged); + } + + leaveQueueResize (); + + DBG_OBJ_LEAVE (); +} + +void Widget::containerSizeChanged () +{ + DBG_OBJ_ENTER0 ("resize", 0, "containerSizeChanged"); + + // If there is a container widget (not the viewport), which has not + // changed its size (which can be determined by the respective + // flags: this method is called recursively), this widget will + // neither change its size. Also, the recursive iteration can be + // stopped, since the children of this widget will + if (container == NULL || + container->needsResize () || container->resizeQueued () || + container->extremesChanged () || container->extremesQueued ()) { + // Viewport (container == NULL) or container widget has changed + // its size. + if (affectedByContainerSizeChange ()) + queueResizeFast (0, true); + + // Even if *this* widget is not affected, children may be, so + // iterate over children. + containerSizeChangedForChildren (); + } + + DBG_OBJ_LEAVE (); +} + +bool Widget::affectedByContainerSizeChange () +{ + DBG_OBJ_ENTER0 ("resize", 0, "affectedByContainerSizeChange"); + + bool ret; + + // This standard implementation is suitable for all widgets which + // call correctRequisition() and correctExtremes(), even in the way + // how Textblock and Image do (see comments there). Has to be kept + // in sync. + + if (container == NULL) { + if (style::isAbsLength (getStyle()->width) && + style::isAbsLength (getStyle()->height)) + // Both absolute, i. e. fixed: no dependency. + ret = false; + else if (style::isPerLength (getStyle()->width) || + style::isPerLength (getStyle()->height)) { + // Any percentage: certainly dependenant. + ret = true; + } else + // One or both is "auto": depends ... + ret = + (getStyle()->width == style::LENGTH_AUTO ? + usesAvailWidth () : false) || + (getStyle()->height == style::LENGTH_AUTO ? + usesAvailHeight () : false); + } else + ret = container->affectsSizeChangeContainerChild (this); + + DBG_OBJ_MSGF ("resize", 1, "=> %s", ret ? "true" : "false"); + DBG_OBJ_LEAVE (); + return ret; +} + +bool Widget::affectsSizeChangeContainerChild (Widget *child) +{ + DBG_OBJ_ENTER ("resize", 0, "affectsSizeChangeContainerChild", "%p", child); + + bool ret; + + // From the point of view of the container. This standard + // implementation should be suitable for most (if not all) + // containers. + + if (style::isAbsLength (child->getStyle()->width) && + style::isAbsLength (child->getStyle()->height)) + // Both absolute, i. e. fixed: no dependency. + ret = false; + else if (style::isPerLength (child->getStyle()->width) || + style::isPerLength (child->getStyle()->height)) { + // Any percentage: certainly dependenant. + ret = true; + } else + // One or both is "auto": depends ... + ret = + (child->getStyle()->width == style::LENGTH_AUTO ? + child->usesAvailWidth () : false) || + (child->getStyle()->height == style::LENGTH_AUTO ? + child->usesAvailHeight () : false); + + DBG_OBJ_MSGF ("resize", 1, "=> %s", ret ? "true" : "false"); + DBG_OBJ_LEAVE (); + return ret; +} + +void Widget::containerSizeChangedForChildren () +{ + DBG_OBJ_ENTER0 ("resize", 0, "containerSizeChangedForChildren"); + + // Working, but inefficient standard implementation. + Iterator *it = iterator ((Content::Type)(Content::WIDGET_IN_FLOW | + Content::WIDGET_OOF_CONT), + false); + while (it->next ()) + it->getContent()->widget->containerSizeChanged (); + it->unref (); + + DBG_OBJ_LEAVE (); +} + +/** + * \brief Must be implemengted by a method returning true, when + * getAvailWidth() is called. + */ +bool Widget::usesAvailWidth () +{ + return false; +} + +/** + * \brief Must be implemengted by a method returning true, when + * getAvailHeight() is called. + */ +bool Widget::usesAvailHeight () +{ + return false; +} + +/** + * \brief This method is a wrapper for Widget::sizeRequestImpl(); it calls + * the latter only when needed. + */ +void Widget::sizeRequest (Requisition *requisition) +{ + assert (!queueResizeEntered ()); + + DBG_OBJ_ENTER0 ("resize", 0, "sizeRequest"); + + enterSizeRequest (); + + //printf ("The %stop-level %s %p with parentRef = %d: needsResize: %s, " + // "resizeQueued = %s\n", + // parent ? "non-" : "", getClassName(), this, parentRef, + // needsResize () ? "true" : "false", + // resizeQueued () ? "true" : "false"); + + if (resizeQueued ()) { + // This method is called outside of Layout::resizeIdle. + setFlags (NEEDS_RESIZE); + unsetFlags (RESIZE_QUEUED); + // The widget is not taken out of Layout::queueResizeList, since + // other *_QUEUED flags may still be set and processed in + // Layout::resizeIdle. + } + + if (needsResize ()) { + /** \todo Check requisition == &(this->requisition) and do what? */ + sizeRequestImpl (requisition); + this->requisition = *requisition; + unsetFlags (NEEDS_RESIZE); + + DBG_OBJ_SET_NUM ("requisition.width", requisition->width); + DBG_OBJ_SET_NUM ("requisition.ascent", requisition->ascent); + DBG_OBJ_SET_NUM ("requisition.descent", requisition->descent); + } else + *requisition = this->requisition; + + //printf (" ==> Result: %d x (%d + %d)\n", + // requisition->width, requisition->ascent, requisition->descent); + + leaveSizeRequest (); + + DBG_OBJ_LEAVE (); +} + +/** + * \brief Used to evaluate Widget::adjustMinWidth. + * + * If extremes == NULL, getExtremes is called. ForceValue is the same + * value passed to getAvailWidth etc.; if false, getExtremes is not + * called. + */ +int Widget::getMinWidth (Extremes *extremes, bool forceValue) +{ + DBG_OBJ_ENTER ("resize", 0, "getMinWidth", "..., %s", + forceValue ? "true" : "false"); + int minWidth; + + if (getAdjustMinWidth ()) { + Extremes extremes2; + if (extremes == NULL) { + if (forceValue) { + getExtremes (&extremes2); + extremes = &extremes2; + } + } + + // TODO Not completely clear whether this is feasable: Within + // the context of getAvailWidth(false) etc., getExtremes may not + // be called. We ignore the minimal width then. + minWidth = extremes ? extremes->minWidthIntrinsic : 0; + } else + minWidth = 0; + + DBG_OBJ_MSGF ("resize", 1, "=> %d", minWidth); + DBG_OBJ_LEAVE (); + + return minWidth; +} + +/** + * Return available width including margin/border/padding + * (extraSpace?), not only the content width. + */ +int Widget::getAvailWidth (bool forceValue) +{ + DBG_OBJ_ENTER ("resize", 0, "getAvailWidth", "%s", + forceValue ? "true" : "false"); + + int width; + + if (parent == NULL && quasiParent == NULL) { + DBG_OBJ_MSG ("resize", 1, "no parent, regarding viewport"); + DBG_OBJ_MSG_START (); + + // TODO Consider nested layouts (e. g. <button>). + + int viewportWidth = + layout->viewportWidth - (layout->canvasHeightGreater ? + layout->vScrollbarThickness : 0); + width = -1; + calcFinalWidth (getStyle (), viewportWidth, NULL, 0, forceValue, &width); + if (width == -1) + width = viewportWidth; + + DBG_OBJ_MSG_END (); + } else if (parent) { + DBG_OBJ_MSG ("resize", 1, "delegated to parent"); + DBG_OBJ_MSG_START (); + width = parent->getAvailWidthOfChild (this, forceValue); + DBG_OBJ_MSG_END (); + } else /* if (quasiParent) */ { + DBG_OBJ_MSG ("resize", 1, "delegated to quasiParent"); + DBG_OBJ_MSG_START (); + width = quasiParent->getAvailWidthOfChild (this, forceValue); + DBG_OBJ_MSG_END (); + } + + DBG_OBJ_MSGF ("resize", 1, "=> %d", width); + DBG_OBJ_LEAVE (); + + return width; +} + +/** + * Return available height including margin/border/padding + * (extraSpace?), not only the content height. + */ +int Widget::getAvailHeight (bool forceValue) +{ + // TODO Correct by ... not extremes, but ...? (Height extremes?) + + // TODO Consider 'min-height' and 'max-height'. (Minor priority, as long as + // "getAvailHeight (true)" is not used. + + DBG_OBJ_ENTER ("resize", 0, "getAvailHeight", "%s", + forceValue ? "true" : "false"); + + int height; + + if (parent == NULL && quasiParent == NULL) { + DBG_OBJ_MSG ("resize", 1, "no parent, regarding viewport"); + DBG_OBJ_MSG_START (); + + // TODO Consider nested layouts (e. g. <button>). + if (style::isAbsLength (getStyle()->height)) { + DBG_OBJ_MSGF ("resize", 1, "absolute height: %dpx", + style::absLengthVal (getStyle()->height)); + height = style::absLengthVal (getStyle()->height) + boxDiffHeight (); + } else if (style::isPerLength (getStyle()->height)) { + DBG_OBJ_MSGF ("resize", 1, "percentage height: %g%%", + 100 * style::perLengthVal_useThisOnlyForDebugging + (getStyle()->height)); + // Notice that here -- unlike getAvailWidth() -- + // layout->hScrollbarThickness is not considered here; + // something like canvasWidthGreater (analogue to + // canvasHeightGreater) would be complicated and lead to + // possibly contradictory self-references. + height = applyPerHeight (layout->viewportHeight, getStyle()->height); + } else { + DBG_OBJ_MSG ("resize", 1, "no specification"); + height = layout->viewportHeight; + } + + DBG_OBJ_MSG_END (); + } else if (parent) { + DBG_OBJ_MSG ("resize", 1, "delegated to parent"); + DBG_OBJ_MSG_START (); + height = parent->getAvailHeightOfChild (this, forceValue); + DBG_OBJ_MSG_END (); + } else /* if (quasiParent) */ { + DBG_OBJ_MSG ("resize", 1, "delegated to quasiParent"); + DBG_OBJ_MSG_START (); + height = quasiParent->getAvailHeightOfChild (this, forceValue); + DBG_OBJ_MSG_END (); + } + + DBG_OBJ_MSGF ("resize", 1, "=> %d", height); + DBG_OBJ_LEAVE (); + + return height; +} + +void Widget::correctRequisition (Requisition *requisition, + void (*splitHeightFun) (int, int *, int *)) +{ + // TODO Correct height by ... not extremes, but ...? (Height extremes?) + + DBG_OBJ_ENTER ("resize", 0, "correctRequisition", "%d * (%d + %d), ...", + requisition->width, requisition->ascent, + requisition->descent); + + if (parent == NULL && quasiParent == NULL) { + DBG_OBJ_MSG ("resize", 1, "no parent, regarding viewport"); + DBG_OBJ_MSG_START (); + + int limitMinWidth = getMinWidth (NULL, true); + int viewportWidth = + layout->viewportWidth - (layout->canvasHeightGreater ? + layout->vScrollbarThickness : 0); + calcFinalWidth (getStyle (), viewportWidth, NULL, limitMinWidth, false, + &requisition->width); + + // For layout->viewportHeight, see comment in getAvailHeight(). + int height = calcHeight (getStyle()->height, false, + layout->viewportHeight, NULL, false); + int minHeight = calcHeight (getStyle()->minHeight, false, + layout->viewportHeight, NULL, false); + int maxHeight = calcHeight (getStyle()->maxHeight, false, + layout->viewportHeight, NULL, false); + + // TODO Perhaps split first, then add box ascent and descent. + if (height != -1) + splitHeightFun (height, &requisition->ascent, &requisition->descent); + if (minHeight != -1 && + requisition->ascent + requisition->descent < minHeight) + splitHeightFun (minHeight, &requisition->ascent, + &requisition->descent); + if (maxHeight != -1 && + requisition->ascent + requisition->descent > maxHeight) + splitHeightFun (maxHeight, &requisition->ascent, + &requisition->descent); + + DBG_OBJ_MSG_END (); + } else if (parent) { + DBG_OBJ_MSG ("resize", 1, "delegated to parent"); + DBG_OBJ_MSG_START (); + parent->correctRequisitionOfChild (this, requisition, splitHeightFun); + DBG_OBJ_MSG_END (); + } else /* if (quasiParent) */ { + DBG_OBJ_MSG ("resize", 1, "delegated to quasiParent"); + DBG_OBJ_MSG_START (); + quasiParent->correctRequisitionOfChild (this, requisition, + splitHeightFun); + DBG_OBJ_MSG_END (); + } + + DBG_OBJ_MSGF ("resize", 1, "=> %d * (%d + %d)", + requisition->width, requisition->ascent, + requisition->descent); + DBG_OBJ_LEAVE (); +} + +void Widget::correctExtremes (Extremes *extremes) +{ + DBG_OBJ_ENTER ("resize", 0, "correctExtremes", "%d (%d) / %d (%d)", + extremes->minWidth, extremes->minWidthIntrinsic, + extremes->maxWidth, extremes->maxWidthIntrinsic); + + if (container == NULL && quasiParent == NULL) { + DBG_OBJ_MSG ("resize", 1, "no parent, regarding viewport"); + DBG_OBJ_MSG_START (); + + int limitMinWidth = getMinWidth (extremes, false); + int viewportWidth = + layout->viewportWidth - (layout->canvasHeightGreater ? + layout->vScrollbarThickness : 0); + + int width = calcWidth (getStyle()->width, viewportWidth, NULL, + limitMinWidth, false); + int minWidth = calcWidth (getStyle()->minWidth, viewportWidth, NULL, + limitMinWidth, false); + int maxWidth = calcWidth (getStyle()->maxWidth, viewportWidth, NULL, + limitMinWidth, false); + + DBG_OBJ_MSGF ("resize", 1, "width = %d, minWidth = %d, maxWidth = %d", + width, minWidth, maxWidth); + + if (width != -1) + extremes->minWidth = extremes->maxWidth = width; + if (minWidth != -1) + extremes->minWidth = minWidth; + if (maxWidth != -1) + extremes->maxWidth = maxWidth; + + DBG_OBJ_MSG_END (); + } else if (parent) { + DBG_OBJ_MSG ("resize", 1, "delegated to parent"); + DBG_OBJ_MSG_START (); + parent->correctExtremesOfChild (this, extremes); + DBG_OBJ_MSG_END (); + } else /* if (quasiParent) */ { + DBG_OBJ_MSG ("resize", 1, "delegated to quasiParent"); + DBG_OBJ_MSG_START (); + quasiParent->correctExtremesOfChild (this, extremes); + DBG_OBJ_MSG_END (); + } + + if (extremes->maxWidth < extremes->minWidth) + extremes->maxWidth = extremes->minWidth; + + DBG_OBJ_MSGF ("resize", 1, "=> %d / %d", + extremes->minWidth, extremes->maxWidth); + DBG_OBJ_LEAVE (); +} + +int Widget::calcWidth (style::Length cssValue, int refWidth, Widget *refWidget, + int limitMinWidth, bool forceValue) +{ + DBG_OBJ_ENTER ("resize", 0, "calcWidth", "0x%x, %d, %p, %d", + cssValue, refWidth, refWidget, limitMinWidth); + + assert (refWidth != -1 || refWidget != NULL); + + int width; + + if (style::isAbsLength (cssValue)) { + DBG_OBJ_MSGF ("resize", 1, "absolute width: %dpx", + style::absLengthVal (cssValue)); + width = misc::max (style::absLengthVal (cssValue) + boxDiffWidth (), + limitMinWidth); + } else if (style::isPerLength (cssValue)) { + DBG_OBJ_MSGF ("resize", 1, "percentage width: %g%%", + 100 * style::perLengthVal_useThisOnlyForDebugging + (cssValue)); + if (refWidth != -1) + width = misc::max (applyPerWidth (refWidth, cssValue), limitMinWidth); + else { + int availWidth = refWidget->getAvailWidth (forceValue); + if (availWidth != -1) { + int containerWidth = availWidth - refWidget->boxDiffWidth (); + width = misc::max (applyPerWidth (containerWidth, cssValue), + limitMinWidth); + } else + width = -1; + } + } else { + DBG_OBJ_MSG ("resize", 1, "not specified"); + width = -1; + } + + DBG_OBJ_MSGF ("resize", 1, "=> %d", width); + DBG_OBJ_LEAVE (); + return width; +} + +// *finalWidth may be -1. +void Widget::calcFinalWidth (style::Style *style, int refWidth, + Widget *refWidget, int limitMinWidth, + bool forceValue, int *finalWidth) +{ + DBG_OBJ_ENTER ("resize", 0, "calcFinalWidth", "..., %d, %p, %d, [%d]", + refWidth, refWidget, limitMinWidth, *finalWidth); + + int width = calcWidth (style->width, refWidth, refWidget, limitMinWidth, + forceValue); + int minWidth = calcWidth (style->minWidth, refWidth, refWidget, + limitMinWidth, forceValue); + int maxWidth = calcWidth (style->maxWidth, refWidth, refWidget, + limitMinWidth, forceValue); + + DBG_OBJ_MSGF ("resize", 1, "width = %d, minWidth = %d, maxWidth = %d", + width, minWidth, maxWidth); + + if (width != -1) + *finalWidth = width; + if (minWidth != -1 && *finalWidth != -1 && *finalWidth < minWidth) + *finalWidth = minWidth; + if (maxWidth != -1 && *finalWidth == -1 && *finalWidth > maxWidth) + *finalWidth = maxWidth; + + DBG_OBJ_MSGF ("resize", 1, "=> %d", *finalWidth); + DBG_OBJ_LEAVE (); +} + +int Widget::calcHeight (style::Length cssValue, bool usePercentage, + int refHeight, Widget *refWidget, bool forceValue) +{ + // TODO Search for usage of this method and check the value of + // "usePercentage"; this has to be clarified. + + DBG_OBJ_ENTER ("resize", 0, "calcHeight", "0x%x, %s, %d, %p", + cssValue, usePercentage ? "true" : "false", refHeight, + refWidget); + + assert (refHeight != -1 || refWidget != NULL); + + int height; + + if (style::isAbsLength (cssValue)) { + DBG_OBJ_MSGF ("resize", 1, "absolute height: %dpx", + style::absLengthVal (cssValue)); + height = + misc::max (style::absLengthVal (cssValue) + boxDiffHeight (), 0); + } else if (style::isPerLength (cssValue)) { + DBG_OBJ_MSGF ("resize", 1, "percentage height: %g%%", + 100 * + style::perLengthVal_useThisOnlyForDebugging (cssValue)); + if (usePercentage) { + if (refHeight != -1) + height = misc::max (applyPerHeight (refHeight, cssValue), 0); + else { + int availHeight = refWidget->getAvailHeight (forceValue); + if (availHeight != -1) { + int containerHeight = availHeight - refWidget->boxDiffHeight (); + height = + misc::max (applyPerHeight (containerHeight, cssValue), 0); + } else + height = -1; + } + } else + height = -1; + } else { + DBG_OBJ_MSG ("resize", 1, "not specified"); + height = -1; + } + + DBG_OBJ_MSGF ("resize", 1, "=> %d", height); + DBG_OBJ_LEAVE (); + return height; +} + +/** + * \brief Wrapper for Widget::getExtremesImpl(). + */ +void Widget::getExtremes (Extremes *extremes) +{ + assert (!queueResizeEntered ()); + + DBG_OBJ_ENTER0 ("resize", 0, "getExtremes"); + + enterGetExtremes (); + + if (extremesQueued ()) { + // This method is called outside of Layout::resizeIdle. + setFlags (EXTREMES_CHANGED); + unsetFlags (EXTREMES_QUEUED); + // The widget is not taken out of Layout::queueResizeList, since + // other *_QUEUED flags may still be set and processed in + // Layout::resizeIdle. + } + + if (extremesChanged ()) { + // For backward compatibility (part 1/2): + extremes->minWidthIntrinsic = extremes->maxWidthIntrinsic = -1; + + getExtremesImpl (extremes); + + // For backward compatibility (part 2/2): + if (extremes->minWidthIntrinsic == -1) + extremes->minWidthIntrinsic = extremes->minWidth; + if (extremes->maxWidthIntrinsic == -1) + extremes->maxWidthIntrinsic = extremes->maxWidth; + + this->extremes = *extremes; + unsetFlags (EXTREMES_CHANGED); + + DBG_OBJ_SET_NUM ("extremes.minWidth", extremes->minWidth); + DBG_OBJ_SET_NUM ("extremes.minWidthIntrinsic", + extremes->minWidthIntrinsic); + DBG_OBJ_SET_NUM ("extremes.maxWidth", extremes->maxWidth); + DBG_OBJ_SET_NUM ("extremes.maxWidthIntrinsic", + extremes->maxWidthIntrinsic); + } else + *extremes = this->extremes; + + leaveGetExtremes (); + + DBG_OBJ_LEAVE (); +} + +/** + * \brief Wrapper for Widget::sizeAllocateImpl, calls the latter only when + * needed. + */ +void Widget::sizeAllocate (Allocation *allocation) +{ + assert (!queueResizeEntered ()); + assert (!sizeRequestEntered ()); + assert (!getExtremesEntered ()); + assert (resizeIdleEntered ()); + + DBG_OBJ_ENTER ("resize", 0, "sizeAllocate", "%d, %d; %d * (%d + %d)", + allocation->x, allocation->y, allocation->width, + allocation->ascent, allocation->descent); + + DBG_OBJ_MSGF ("resize", 1, + "old allocation (%d, %d; %d * (%d + %d)); needsAllocate: %s", + this->allocation.x, this->allocation.y, this->allocation.width, + this->allocation.ascent, this->allocation.descent, + needsAllocate () ? "true" : "false"); + + enterSizeAllocate (); + + /*printf ("The %stop-level %s %p is allocated:\n", + parent ? "non-" : "", getClassName(), this); + printf (" old = (%d, %d, %d + (%d + %d))\n", + this->allocation.x, this->allocation.y, this->allocation.width, + this->allocation.ascent, this->allocation.descent); + printf (" new = (%d, %d, %d + (%d + %d))\n", + allocation->x, allocation->y, allocation->width, allocation->ascent, + allocation->descent); + printf (" NEEDS_ALLOCATE = %s\n", needsAllocate () ? "true" : "false");*/ + + if (needsAllocate () || + allocation->x != this->allocation.x || + allocation->y != this->allocation.y || + allocation->width != this->allocation.width || + allocation->ascent != this->allocation.ascent || + allocation->descent != this->allocation.descent) { + + if (wasAllocated ()) { + layout->queueDrawExcept ( + this->allocation.x, + this->allocation.y, + this->allocation.width, + this->allocation.ascent + this->allocation.descent, + allocation->x, + allocation->y, + allocation->width, + allocation->ascent + allocation->descent); + } + + sizeAllocateImpl (allocation); + + //DEBUG_MSG (DEBUG_ALLOC, "... to %d, %d, %d x %d x %d\n", + // widget->allocation.x, widget->allocation.y, + // widget->allocation.width, widget->allocation.ascent, + // widget->allocation.descent); + + this->allocation = *allocation; + unsetFlags (NEEDS_ALLOCATE); + setFlags (WAS_ALLOCATED); + + resizeDrawImpl (); + + DBG_OBJ_SET_NUM ("allocation.x", this->allocation.x); + DBG_OBJ_SET_NUM ("allocation.y", this->allocation.y); + DBG_OBJ_SET_NUM ("allocation.width", this->allocation.width); + DBG_OBJ_SET_NUM ("allocation.ascent", this->allocation.ascent); + DBG_OBJ_SET_NUM ("allocation.descent", this->allocation.descent); + } + + /*unsetFlags (NEEDS_RESIZE);*/ + + leaveSizeAllocate (); + + DBG_OBJ_LEAVE (); +} + +bool Widget::buttonPress (EventButton *event) +{ + return buttonPressImpl (event); +} + +bool Widget::buttonRelease (EventButton *event) +{ + return buttonReleaseImpl (event); +} + +bool Widget::motionNotify (EventMotion *event) +{ + return motionNotifyImpl (event); +} + +void Widget::enterNotify (EventCrossing *event) +{ + enterNotifyImpl (event); +} + +void Widget::leaveNotify (EventCrossing *event) +{ + leaveNotifyImpl (event); +} + +/** + * \brief Change the style of a widget. + * + * The old style is automatically unreferred, the new is referred. If this + * call causes the widget to change its size, dw::core::Widget::queueResize + * is called. + */ +void Widget::setStyle (style::Style *style) +{ + bool sizeChanged; + + if (widgetImgRenderer && this->style && this->style->backgroundImage) + this->style->backgroundImage->removeExternalImgRenderer + (widgetImgRenderer); + + style->ref (); + + if (this->style) { + sizeChanged = this->style->sizeDiffs (style); + this->style->unref (); + } else + sizeChanged = true; + + this->style = style; + + DBG_OBJ_ASSOC_CHILD (style); + + if (style && style->backgroundImage) { + // Create instance of WidgetImgRenderer when needed. Until this + // widget is deleted, "widgetImgRenderer" will be kept, since it + // is not specific to the style, but only to this widget. + if (widgetImgRenderer == NULL) + widgetImgRenderer = new WidgetImgRenderer (this); + style->backgroundImage->putExternalImgRenderer (widgetImgRenderer); + } + + if (layout != NULL) { + layout->updateCursor (); + } + + if (sizeChanged) + queueResize (0, true); + else + queueDraw (); + + // These should better be attributed to the style itself, and a + // script processing RTFL messages could transfer it to something + // equivalent: + + DBG_OBJ_SET_NUM ("style.margin.top", style->margin.top); + DBG_OBJ_SET_NUM ("style.margin.bottom", style->margin.bottom); + DBG_OBJ_SET_NUM ("style.margin.left", style->margin.left); + DBG_OBJ_SET_NUM ("style.margin.right", style->margin.right); + + DBG_OBJ_SET_NUM ("style.border-width.top", style->borderWidth.top); + DBG_OBJ_SET_NUM ("style.border-width.bottom", style->borderWidth.bottom); + DBG_OBJ_SET_NUM ("style.border-width.left", style->borderWidth.left); + DBG_OBJ_SET_NUM ("style.border-width.right", style->borderWidth.right); + + DBG_OBJ_SET_NUM ("style.padding.top", style->padding.top); + DBG_OBJ_SET_NUM ("style.padding.bottom", style->padding.bottom); + DBG_OBJ_SET_NUM ("style.padding.left", style->padding.left); + DBG_OBJ_SET_NUM ("style.padding.right", style->padding.right); + + DBG_OBJ_SET_NUM ("style.border-spacing (h)", style->hBorderSpacing); + DBG_OBJ_SET_NUM ("style.border-spacing (v)", style->vBorderSpacing); + + DBG_OBJ_SET_SYM ("style.display", + style->display == style::DISPLAY_BLOCK ? "block" : + style->display == style::DISPLAY_INLINE ? "inline" : + style->display == style::DISPLAY_INLINE_BLOCK ? + "inline-block" : + style->display == style::DISPLAY_LIST_ITEM ? "list-item" : + style->display == style::DISPLAY_NONE ? "none" : + style->display == style::DISPLAY_TABLE ? "table" : + style->display == style::DISPLAY_TABLE_ROW_GROUP ? + "table-row-group" : + style->display == style::DISPLAY_TABLE_HEADER_GROUP ? + "table-header-group" : + style->display == style::DISPLAY_TABLE_FOOTER_GROUP ? + "table-footer-group" : + style->display == style::DISPLAY_TABLE_ROW ? "table-row" : + style->display == style::DISPLAY_TABLE_CELL ? "table-cell" : + "???"); + + DBG_OBJ_SET_NUM ("style.width (raw)", style->width); + DBG_OBJ_SET_NUM ("style.min-width (raw)", style->minWidth); + DBG_OBJ_SET_NUM ("style.max-width (raw)", style->maxWidth); + DBG_OBJ_SET_NUM ("style.height (raw)", style->height); + DBG_OBJ_SET_NUM ("style.min-height (raw)", style->minHeight); + DBG_OBJ_SET_NUM ("style.max-height (raw)", style->maxHeight); +} + +/** + * \brief Set the background "behind" the widget, if it is not the + * background of the parent widget, e.g. the background of a table + * row. + */ +void Widget::setBgColor (style::Color *bgColor) +{ + this->bgColor = bgColor; +} + +/** + * \brief Get the actual background of a widget. + */ +style::Color *Widget::getBgColor () +{ + Widget *widget = this; + + while (widget != NULL) { + if (widget->style->backgroundColor) + return widget->style->backgroundColor; + if (widget->bgColor) + return widget->bgColor; + + widget = widget->parent; + } + + return layout->getBgColor (); +} + + +/** + * \brief Draw borders and background of a widget part, which allocation is + * given by (x, y, width, height) (widget coordinates). + * + * area is given in widget coordinates. + */ +void Widget::drawBox (View *view, style::Style *style, Rectangle *area, + int x, int y, int width, int height, bool inverse) +{ + Rectangle canvasArea; + canvasArea.x = area->x + allocation.x; + canvasArea.y = area->y + allocation.y; + canvasArea.width = area->width; + canvasArea.height = area->height; + + style::drawBorder (view, layout, &canvasArea, + allocation.x + x, allocation.y + y, + width, height, style, inverse); + + // This method is used for inline elements, where the CSS 2 specification + // does not define what here is called "reference area". To make it look + // smoothly, the widget padding box is used. + + // TODO Handle inverse drawing the same way as in drawWidgetBox? + // Maybe this method (drawBox) is anyway obsolete when extraSpace + // is fully supported (as in the "dillo_grows" repository). + + int xPad, yPad, widthPad, heightPad; + getPaddingArea (&xPad, &yPad, &widthPad, &heightPad); + style::drawBackground + (view, layout, &canvasArea, + allocation.x + x + style->margin.left + style->borderWidth.left, + allocation.y + y + style->margin.top + style->borderWidth.top, + width - style->margin.left - style->borderWidth.left + - style->margin.right - style->borderWidth.right, + height - style->margin.top - style->borderWidth.top + - style->margin.bottom - style->borderWidth.bottom, + xPad, yPad, widthPad, heightPad, style, style->backgroundColor, + inverse, false); +} + +/** + * \brief Draw borders and background of a widget. + * + * area is given in widget coordinates. + * + */ +void Widget::drawWidgetBox (View *view, Rectangle *area, bool inverse) +{ + Rectangle canvasArea; + canvasArea.x = area->x + allocation.x; + canvasArea.y = area->y + allocation.y; + canvasArea.width = area->width; + canvasArea.height = area->height; + + style::drawBorder (view, layout, &canvasArea, allocation.x, allocation.y, + allocation.width, getHeight (), style, inverse); + + int xPad, yPad, widthPad, heightPad; + getPaddingArea (&xPad, &yPad, &widthPad, &heightPad); + + style::Color *bgColor; + if (inverse && style->backgroundColor == NULL) { + // See style::drawBackground: for inverse drawing, we need a + // defined background color. Search through ancestors. + Widget *w = this; + while (w != NULL && w->style->backgroundColor == NULL) + w = w->parent; + + if (w != NULL && w->style->backgroundColor != NULL) + bgColor = w->style->backgroundColor; + else + bgColor = layout->getBgColor (); + } else + bgColor = style->backgroundColor; + + style::drawBackground (view, layout, &canvasArea, + xPad, yPad, widthPad, heightPad, + xPad, yPad, widthPad, heightPad, + style, bgColor, inverse, parent == NULL); +} + +/* + * This function is used by some widgets, when they are selected (as a whole). + * + * \todo This could be accelerated by using clipping bitmaps. Two important + * issues: + * + * (i) There should always been a pixel in the upper-left corner of the + * *widget*, so probably two different clipping bitmaps have to be + * used (10/01 and 01/10). + * + * (ii) Should a new GC always be created? + * + * \bug Not implemented. + */ +void Widget::drawSelected (View *view, Rectangle *area) +{ +} + + +void Widget::setButtonSensitive (bool buttonSensitive) +{ + this->buttonSensitive = buttonSensitive; + buttonSensitiveSet = true; +} + + +/** + * \brief Get the widget at the root of the tree, this widget is part from. + */ +Widget *Widget::getTopLevel () +{ + Widget *widget = this; + + while (widget->parent) + widget = widget->parent; + + return widget; +} + +/** + * \brief Get the level of the widget within the tree. + * + * The root widget has the level 0. + */ +int Widget::getLevel () +{ + Widget *widget = this; + int level = 0; + + while (widget->parent) { + level++; + widget = widget->parent; + } + + return level; +} + +/** + * \brief Get the level of the widget within the tree, regarting the + * generators, not the parents. + * + * The root widget has the level 0. + */ +int Widget::getGeneratorLevel () +{ + Widget *widget = this; + int level = 0; + + while (widget->getGenerator ()) { + level++; + widget = widget->getGenerator (); + } + + return level; +} + +/** + * \brief Get the widget with the highest level, which is a direct ancestor of + * widget1 and widget2. + */ +Widget *Widget::getNearestCommonAncestor (Widget *otherWidget) +{ + Widget *widget1 = this, *widget2 = otherWidget; + int level1 = widget1->getLevel (), level2 = widget2->getLevel(); + + /* Get both widgets onto the same level.*/ + while (level1 > level2) { + widget1 = widget1->parent; + level1--; + } + + while (level2 > level1) { + widget2 = widget2->parent; + level2--; + } + + /* Search upwards. */ + while (widget1 != widget2) { + if (widget1->parent == NULL) { + MSG_WARN("widgets in different trees\n"); + return NULL; + } + + widget1 = widget1->parent; + widget2 = widget2->parent; + } + + return widget1; +} + + +/** + * \brief Search recursively through widget. + * + * Used by dw::core::Layout:getWidgetAtPoint. + */ +Widget *Widget::getWidgetAtPoint (int x, int y, int level) +{ + Iterator *it; + Widget *childAtPoint; + + //printf ("%*s-> examining the %s %p (%d, %d, %d x (%d + %d))\n", + // 3 * level, "", getClassName (), this, allocation.x, allocation.y, + // allocation.width, allocation.ascent, allocation.descent); + + if (x >= allocation.x && + y >= allocation.y && + x <= allocation.x + allocation.width && + y <= allocation.y + getHeight ()) { + //_MSG ("%*s -> inside\n", 3 * level, ""); + /* + * Iterate over the children of this widget. Test recursively, whether + * the point is within the child (or one of its children...). If there + * is such a child, it is returned. Otherwise, this widget is returned. + */ + childAtPoint = NULL; + it = iterator ((Content::Type) + (Content::WIDGET_IN_FLOW | Content::WIDGET_OOF_CONT), + false); + + while (childAtPoint == NULL && it->next ()) { + Widget *child = it->getContent()->widget; + if (child->wasAllocated ()) + childAtPoint = child->getWidgetAtPoint (x, y, level + 1); + } + + it->unref (); + + if (childAtPoint) + return childAtPoint; + else + return this; + } else + return NULL; +} + + +void Widget::scrollTo (HPosition hpos, VPosition vpos) +{ + layout->scrollToWidget (hpos, vpos, this); +} + + +void Widget::scrollTo (HPosition hpos, VPosition vpos, + int x, int y, int width, int height) +{ + // TODO This should perhaps better be done by a new Layout::ScrollTarget. + layout->scrollTo (hpos, vpos, + x + allocation.x, y + allocation.y, width, height); +} + + +/** + * \brief Return the padding area (content plus padding). + * + * Used as "reference area" (ee comment of "style::drawBackground") + * for backgrounds. + */ +void Widget::getPaddingArea (int *xPad, int *yPad, int *widthPad, + int *heightPad) +{ + *xPad = allocation.x + style->margin.left + style->borderWidth.left; + *yPad = allocation.y + style->margin.top + style->borderWidth.top; + *widthPad = allocation.width - style->margin.left - style->borderWidth.left + - style->margin.right - style->borderWidth.right; + *heightPad = getHeight () - style->margin.top - style->borderWidth.top + - style->margin.bottom - style->borderWidth.bottom; +} + +void Widget::sizeAllocateImpl (Allocation *allocation) +{ +} + +void Widget::markSizeChange (int ref) +{ +} + +void Widget::markExtremesChange (int ref) +{ +} + +int Widget::applyPerWidth (int containerWidth, style::Length perWidth) +{ + return style::multiplyWithPerLength (containerWidth, perWidth) + + boxDiffWidth (); +} + +int Widget::applyPerHeight (int containerHeight, style::Length perHeight) +{ + return style::multiplyWithPerLength (containerHeight, perHeight) + + boxDiffHeight (); +} + +int Widget::getAvailWidthOfChild (Widget *child, bool forceValue) +{ + // This is a halfway suitable implementation for all + // containers. For simplification, this will be used during the + // development; then, a differentiation could be possible. + + DBG_OBJ_ENTER ("resize", 0, "getAvailWidthOfChild", "%p, %s", + child, forceValue ? "true" : "false"); + + int width; + + if (child->getStyle()->width == style::LENGTH_AUTO) { + DBG_OBJ_MSG ("resize", 1, "no specification"); + if (forceValue) + width = misc::max (getAvailWidth (true) - boxDiffWidth (), 0); + else + width = -1; + } else { + // In most cases, the toplevel widget should be a container, so + // the container is non-NULL when the parent is non-NULL. Just + // in case, regard also parent. And quasiParent. + Widget *effContainer = child->quasiParent ? child->quasiParent : + (child->container ? child->container : child->parent); + + if (effContainer == this) { + width = -1; + child->calcFinalWidth (child->getStyle(), -1, this, 0, forceValue, + &width); + } else { + DBG_OBJ_MSG ("resize", 1, "delegated to (effective) container"); + DBG_OBJ_MSG_START (); + width = effContainer->getAvailWidthOfChild (child, forceValue); + DBG_OBJ_MSG_END (); + } + } + + DBG_OBJ_MSGF ("resize", 1, "=> %d", width); + DBG_OBJ_LEAVE (); + + return width; +} + +int Widget::getAvailHeightOfChild (Widget *child, bool forceValue) +{ + // Again, a suitable implementation for all widgets (perhaps). + + // TODO Consider 'min-height' and 'max-height'. (Minor priority, as long as + // "getAvailHeight (true)" is not used. + + DBG_OBJ_ENTER ("resize", 0, "getAvailHeightOfChild", "%p, %s", + child, forceValue ? "true" : "false"); + + int height; + + if (child->getStyle()->height == style::LENGTH_AUTO) { + DBG_OBJ_MSG ("resize", 1, "no specification"); + if (forceValue) + height = misc::max (getAvailHeight (true) - boxDiffHeight (), 0); + else + height = -1; + } else { + // See comment in Widget::getAvailWidthOfChild. + Widget *effContainer = child->quasiParent ? child->quasiParent : + (child->container ? child->container : child->parent); + + if (effContainer == this) { + if (style::isAbsLength (child->getStyle()->height)) { + DBG_OBJ_MSGF ("resize", 1, "absolute height: %dpx", + style::absLengthVal (child->getStyle()->height)); + height = misc::max (style::absLengthVal (child->getStyle()->height) + + child->boxDiffHeight (), 0); + } else { + assert (style::isPerLength (child->getStyle()->height)); + DBG_OBJ_MSGF ("resize", 1, "percentage height: %g%%", + 100 * style::perLengthVal_useThisOnlyForDebugging + (child->getStyle()->height)); + + int availHeight = getAvailHeight (forceValue); + if (availHeight == -1) + height = -1; + else + height = + misc::max (child->applyPerHeight (availHeight - + boxDiffHeight (), + child->getStyle()->height), + 0); + } + } else { + DBG_OBJ_MSG ("resize", 1, "delegated to (effective) container"); + DBG_OBJ_MSG_START (); + height = effContainer->getAvailHeightOfChild (child, forceValue); + DBG_OBJ_MSG_END (); + } + } + + DBG_OBJ_MSGF ("resize", 1, "=> %d", height); + DBG_OBJ_LEAVE (); + + return height; +} + +void Widget::correctRequisitionOfChild (Widget *child, Requisition *requisition, + void (*splitHeightFun) (int, int*, + int*)) +{ + // Again, a suitable implementation for all widgets (perhaps). + + DBG_OBJ_ENTER ("resize", 0, "correctRequisitionOfChild", + "%p, %d * (%d + %d), ...", child, requisition->width, + requisition->ascent, requisition->descent); + + // See comment in Widget::getAvailWidthOfChild. + Widget *effContainer = child->quasiParent ? child->quasiParent : + (child->container ? child->container : child->parent); + + if (effContainer == this) { + correctReqWidthOfChild (child, requisition); + correctReqHeightOfChild (child, requisition, splitHeightFun); + } else { + DBG_OBJ_MSG ("resize", 1, "delegated to (effective) container"); + DBG_OBJ_MSG_START (); + effContainer->correctRequisitionOfChild (child, requisition, + splitHeightFun); + DBG_OBJ_MSG_END (); + } + + DBG_OBJ_MSGF ("resize", 1, "=> %d * (%d + %d)", + requisition->width, requisition->ascent, + requisition->descent); + DBG_OBJ_LEAVE (); +} + +void Widget::correctReqWidthOfChild (Widget *child, Requisition *requisition) +{ + DBG_OBJ_ENTER ("resize", 0, "correctReqWidthOfChild", "%p, %d * (%d + %d)", + child, requisition->width, requisition->ascent, + requisition->descent); + + assert (this == child->quasiParent || this == child->container); + + int limitMinWidth = child->getMinWidth (NULL, true); + child->calcFinalWidth (child->getStyle(), -1, this, limitMinWidth, false, + &requisition->width); + + DBG_OBJ_MSGF ("resize", 1, "=> %d * (%d + %d)", + requisition->width, requisition->ascent, + requisition->descent); + DBG_OBJ_LEAVE (); +} + +void Widget::correctReqHeightOfChild (Widget *child, Requisition *requisition, + void (*splitHeightFun) (int, int*, int*)) +{ + // TODO Correct height by extremes? (Height extemes?) + + assert (this == child->quasiParent || this == child->container); + + DBG_OBJ_ENTER ("resize", 0, "correctReqHeightOfChild", + "%p, %d * (%d + %d), ...", child, requisition->width, + requisition->ascent, requisition->descent); + + int height = child->calcHeight (child->getStyle()->height, false, -1, this, + false); + int minHeight = child->calcHeight (child->getStyle()->minHeight, false, -1, + this, false); + int maxHeight = child->calcHeight (child->getStyle()->maxHeight, false, -1, + this, false); + + // TODO Perhaps split first, then add box ascent and descent. + if (height != -1) + splitHeightFun (height, &requisition->ascent, &requisition->descent); + if (minHeight != -1 && + requisition->ascent + requisition->descent < minHeight) + splitHeightFun (minHeight, &requisition->ascent, + &requisition->descent); + if (maxHeight != -1 && + requisition->ascent + requisition->descent > maxHeight) + splitHeightFun (maxHeight, &requisition->ascent, + &requisition->descent); + + DBG_OBJ_MSGF ("resize", 1, "=> %d * (%d + %d)", + requisition->width, requisition->ascent, + requisition->descent); + DBG_OBJ_LEAVE (); +} + +void Widget::correctExtremesOfChild (Widget *child, Extremes *extremes) +{ + // See comment in correctRequisitionOfChild. + + DBG_OBJ_ENTER ("resize", 0, "correctExtremesOfChild", + "%p, %d (%d) / %d (%d)", + child, extremes->minWidth, extremes->minWidthIntrinsic, + extremes->maxWidth, extremes->maxWidthIntrinsic); + + // See comment in Widget::getAvailWidthOfChild. + Widget *effContainer = child->quasiParent ? child->quasiParent : + (child->container ? child->container : child->parent); + + if (effContainer == this) { + int limitMinWidth = child->getMinWidth (extremes, false); + int width = child->calcWidth (child->getStyle()->width, -1, this, + limitMinWidth, false); + int minWidth = child->calcWidth (child->getStyle()->minWidth, -1, this, + limitMinWidth, false); + int maxWidth = child->calcWidth (child->getStyle()->maxWidth, -1, this, + limitMinWidth, false); + + DBG_OBJ_MSGF ("resize", 1, "width = %d, minWidth = %d, maxWidth = %d", + width, minWidth, maxWidth); + + if (width != -1) + extremes->minWidth = extremes->maxWidth = width; + if (minWidth != -1) + extremes->minWidth = minWidth; + if (maxWidth != -1) + extremes->maxWidth = maxWidth; + } else { + DBG_OBJ_MSG ("resize", 1, "delegated to (effective) container"); + DBG_OBJ_MSG_START (); + effContainer->correctExtremesOfChild (child, extremes); + DBG_OBJ_MSG_END (); + } + + + DBG_OBJ_MSGF ("resize", 1, "=> %d / %d", + extremes->minWidth, extremes->maxWidth); + DBG_OBJ_LEAVE (); +} + +/** + * \brief This method is called after a widget has been set as the top of a + * widget tree. + * + * A widget may override this method when it is necessary to be notified. + */ +void Widget::notifySetAsTopLevel() +{ +} + +/** + * \brief This method is called after a widget has been added to a parent. + * + * A widget may override this method when it is necessary to be notified. + */ +void Widget::notifySetParent() +{ +} + +bool Widget::isBlockLevel () +{ + // Most widgets are not block-level. + return false; +} + +bool Widget::isPossibleContainer () +{ + // In most (all?) cases identical to: + return isBlockLevel (); +} + +bool Widget::buttonPressImpl (EventButton *event) +{ + return false; +} + +bool Widget::buttonReleaseImpl (EventButton *event) +{ + return false; +} + +bool Widget::motionNotifyImpl (EventMotion *event) +{ + return false; +} + +void Widget::enterNotifyImpl (EventCrossing *) +{ + style::Tooltip *tooltip = getStyle()->x_tooltip; + + if (tooltip) + tooltip->onEnter(); +} + +void Widget::leaveNotifyImpl (EventCrossing *) +{ + style::Tooltip *tooltip = getStyle()->x_tooltip; + + if (tooltip) + tooltip->onLeave(); +} + +void Widget::removeChild (Widget *child) +{ + // Should be implemented. + misc::assertNotReached (); +} + +// ---------------------------------------------------------------------- + +void splitHeightPreserveAscent (int height, int *ascent, int *descent) +{ + *descent = height - *ascent; + if (*descent < 0) { + *descent = 0; + *ascent = height; + } +} + +void splitHeightPreserveDescent (int height, int *ascent, int *descent) +{ + *ascent = height - *descent; + if (*ascent < 0) { + *ascent = 0; + *descent = height; + } +} + +} // namespace core +} // namespace dw diff --git a/dw/widget.hh b/dw/widget.hh new file mode 100644 index 0000000..d107fe1 --- /dev/null +++ b/dw/widget.hh @@ -0,0 +1,514 @@ +#ifndef __DW_WIDGET_HH__ +#define __DW_WIDGET_HH__ + +#ifndef __INCLUDED_FROM_DW_CORE_HH__ +# error Do not include this file directly, use "core.hh" instead. +#endif + +#include "../lout/identity.hh" + +/** + * \brief The type for callback functions. + */ +typedef void (*DW_Callback_t)(void *data); + +namespace dw { +namespace core { + +/** + * \brief The base class of all dillo widgets. + * + * \sa\ref dw-overview, \ref dw-layout-widgets + */ +class Widget: public lout::identity::IdentifiableObject +{ + friend class Layout; + +protected: + enum Flags { + /** + * \todo Comment this. + */ + RESIZE_QUEUED = 1 << 0, + + /** + * \todo Comment this. + */ + EXTREMES_QUEUED = 1 << 1, + + /** + * \brief Set, when dw::core::Widget::requisition is not up to date + * anymore. + * + * \todo Update, see RESIZE_QUEUED. + */ + NEEDS_RESIZE = 1 << 2, + + /** + * \brief Only used internally, set to enforce size allocation. + * + * In some cases, the size of a widget remains the same, but the + * children are allocated at different positions and in + * different sizes, so that a simple comparison of old and new + * allocation is insufficient. Therefore, this flag is set + * (indirectly, as ALLOCATE_QUEUED) in queueResize. + */ + NEEDS_ALLOCATE = 1 << 3, + + /** + * \todo Comment this. + */ + ALLOCATE_QUEUED = 1 << 4, + + /** + * \brief Set, when dw::core::Widget::extremes is not up to date + * anymore. + * + * \todo Update, see RESIZE_QUEUED. + */ + EXTREMES_CHANGED = 1 << 5, + + /** + * \brief Set, when a widget was already once allocated, + * + * The dw::Image widget uses this flag, see dw::Image::setBuffer. + */ + WAS_ALLOCATED = 1 << 6, + }; + + /** + * \brief Implementation which represents the whole widget. + * + * The only instance is set created needed. + */ + class WidgetImgRenderer: public style::StyleImage::ExternalWidgetImgRenderer + { + private: + Widget *widget; + + public: + inline WidgetImgRenderer (Widget *widget) { this->widget = widget; } + + bool readyToDraw (); + void getBgArea (int *x, int *y, int *width, int *height); + void getRefArea (int *xRef, int *yRef, int *widthRef, int *heightRef); + style::Style *getStyle (); + void draw (int x, int y, int width, int height); + }; + + WidgetImgRenderer *widgetImgRenderer; + +private: + static bool adjustMinWidth; + + /** + * \brief The parent widget, NULL for top-level widgets. + */ + Widget *parent; + + /** + * \brief ... + */ + Widget *quasiParent; + + /** + * \brief The generating widget, NULL for top-level widgets, or if + * not set; in the latter case, the effective generator (see + * getGenerator) is the parent. + */ + Widget *generator; + + /** + * \brief The containing widget, equivalent to the "containing + * block" defined by CSS. May be NULL, in this case the viewport + * is used. + */ + Widget *container; + + style::Style *style; + + Flags flags; + + /** + * \brief Size_request() stores the result of the last call of + * size_request_impl(). + * + * Do not read this directly, but call size_request(). + */ + Requisition requisition; + + /** + * \brief Analogue to dw::core::Widget::requisition. + */ + Extremes extremes; + + /** + * \brief See dw::core::Widget::setBgColor(). + */ + style::Color *bgColor; + + /** + * \brief See dw::core::Widget::setButtonSensitive(). + */ + bool buttonSensitive; + + /** + * \brief See dw::core::Widget::setButtonSensitive(). + */ + bool buttonSensitiveSet; + + void queueResize (int ref, bool extremesChanged, bool fast); + inline void queueResizeFast (int ref, bool extremesChanged) + { queueResize (ref, extremesChanged, true); } + void actualQueueResize (int ref, bool extremesChanged, bool fast); + +public: + /** + * \brief This value is defined by the parent widget, and used for + * incremential resizing. + * + * See documentation for an explanation. + */ + int parentRef; + +protected: + + /** + * \brief The current allocation: size and position, always relative to the + * canvas. + */ + Allocation allocation; + + inline int getHeight () { return allocation.ascent + allocation.descent; } + inline int getContentWidth() { return allocation.width + - style->boxDiffWidth (); } + inline int getContentHeight() { return getHeight () + - style->boxDiffHeight (); } + + Layout *layout; + + /** + * \brief Space around the margin box. Allocation is extraSpace + + * margin + border + padding + contents; + */ + style::Box extraSpace; + + /*inline void printFlags () { + DBG_IF_RTFL { + char buf[10 * 3 - 1 + 1]; + snprintf (buf, sizeof (buf), "%s:%s:%s:%s:%s:%s:%s", + (flags & RESIZE_QUEUED) ? "Rq" : "--", + (flags & EXTREMES_QUEUED) ? "Eq" : "--", + (flags & NEEDS_RESIZE) ? "nR" : "--", + (flags & NEEDS_ALLOCATE) ? "nA" : "--", + (flags & ALLOCATE_QUEUED) ? "Aq" : "--", + (flags & EXTREMES_CHANGED) ? "Ec" : "--", + (flags & WAS_ALLOCATED) ? "wA" : "--"); + DBG_OBJ_SET_SYM ("flags", buf); + } + }*/ + + inline void printFlag (Flags f) { + DBG_IF_RTFL { + switch (f) { + case RESIZE_QUEUED: + DBG_OBJ_SET_SYM ("flags.RESIZE_QUEUED", + (flags & RESIZE_QUEUED) ? "true" : "false"); + break; + + case EXTREMES_QUEUED: + DBG_OBJ_SET_SYM ("flags.EXTREMES_QUEUED", + (flags & EXTREMES_QUEUED) ? "true" : "false"); + break; + + case NEEDS_RESIZE: + DBG_OBJ_SET_SYM ("flags.NEEDS_RESIZE", + (flags & NEEDS_RESIZE) ? "true" : "false"); + break; + + case NEEDS_ALLOCATE: + DBG_OBJ_SET_SYM ("flags.NEEDS_ALLOCATE", + (flags & NEEDS_ALLOCATE) ? "true" : "false"); + break; + + case ALLOCATE_QUEUED: + DBG_OBJ_SET_SYM ("flags.ALLOCATE_QUEUED", + (flags & ALLOCATE_QUEUED) ? "true" : "false"); + break; + + case EXTREMES_CHANGED: + DBG_OBJ_SET_SYM ("flags.EXTREMES_CHANGED", + (flags & EXTREMES_CHANGED) ? "true" : "false"); + break; + + case WAS_ALLOCATED: + DBG_OBJ_SET_SYM ("flags.WAS_ALLOCATED", + (flags & WAS_ALLOCATED) ? "true" : "false"); + break; + } + } + } + + inline void setFlags (Flags f) + { flags = (Flags)(flags | f); printFlag (f); } + inline void unsetFlags (Flags f) + { flags = (Flags)(flags & ~f); printFlag (f); } + + + inline void queueDraw () + { queueDrawArea (0, 0, allocation.width, getHeight()); } + void queueDrawArea (int x, int y, int width, int height); + inline void queueResize (int ref, bool extremesChanged) + { queueResize (ref, extremesChanged, false); } + + /** + * \brief See \ref dw-widget-sizes. + */ + virtual void sizeRequestImpl (Requisition *requisition) = 0; + + /** + * \brief See \ref dw-widget-sizes. + */ + virtual void getExtremesImpl (Extremes *extremes) = 0; + + /** + * \brief See \ref dw-widget-sizes. + */ + virtual void sizeAllocateImpl (Allocation *allocation); + + /** + * \brief Called after sizeAllocateImpl() to redraw necessary areas. + * By default the whole widget is redrawn. + */ + virtual void resizeDrawImpl () { queueDraw (); }; + + /** + * \brief See \ref dw-widget-sizes. + */ + virtual void markSizeChange (int ref); + + /** + * \brief See \ref dw-widget-sizes. + */ + virtual void markExtremesChange (int ref); + + int getMinWidth (Extremes *extremes, bool forceValue); + + virtual int getAvailWidthOfChild (Widget *child, bool forceValue); + virtual int getAvailHeightOfChild (Widget *child, bool forceValue); + virtual void correctRequisitionOfChild (Widget *child, + Requisition *requisition, + void (*splitHeightFun) (int, int*, + int*)); + void correctReqWidthOfChild (Widget *child, Requisition *requisition); + void correctReqHeightOfChild (Widget *child, Requisition *requisition, + void (*splitHeightFun) (int, int*, int*)); + virtual void correctExtremesOfChild (Widget *child, Extremes *extremes); + + virtual void containerSizeChangedForChildren (); + + virtual bool affectedByContainerSizeChange (); + virtual bool affectsSizeChangeContainerChild (Widget *child); + virtual bool usesAvailWidth (); + virtual bool usesAvailHeight (); + + virtual void notifySetAsTopLevel(); + virtual void notifySetParent(); + + virtual bool buttonPressImpl (EventButton *event); + virtual bool buttonReleaseImpl (EventButton *event); + virtual bool motionNotifyImpl (EventMotion *event); + virtual void enterNotifyImpl (EventCrossing *event); + virtual void leaveNotifyImpl (EventCrossing *event); + + inline char *addAnchor (const char* name) + { return layout->addAnchor (this, name); } + + inline char *addAnchor (const char* name, int y) + { return layout->addAnchor (this, name, y); } + + inline void changeAnchor (char* name, int y) + { layout->changeAnchor (this, name, y); } + + inline void removeAnchor (char* name) + { if (layout) layout->removeAnchor (this, name); } + + //inline void updateBgColor () { layout->updateBgColor (); } + + inline void setCursor (style::Cursor cursor) + { layout->setCursor (cursor); } +#if 0 + inline bool selectionButtonPress (Iterator *it, int charPos, int linkNo, + EventButton *event, bool withinContent) + { return layout->selectionState.buttonPress (it, charPos, linkNo, event); } + + inline bool selectionButtonRelease (Iterator *it, int charPos, int linkNo, + EventButton *event, bool withinContent) + { return layout->selectionState.buttonRelease (it, charPos, linkNo, event);} + + inline bool selectionButtonMotion (Iterator *it, int charPos, int linkNo, + EventMotion *event, bool withinContent) + { return layout->selectionState.buttonMotion (it, charPos, linkNo, event); } +#endif + inline bool selectionHandleEvent (SelectionState::EventType eventType, + Iterator *it, int charPos, int linkNo, + MousePositionEvent *event) + { return layout->selectionState.handleEvent (eventType, it, charPos, linkNo, + event); } + +private: + void *deleteCallbackData; + DW_Callback_t deleteCallbackFunc; + +public: + inline void setDeleteCallback(DW_Callback_t func, void *data) + { deleteCallbackFunc = func; deleteCallbackData = data; } + +private: + bool resizeIdleEntered () { return layout && layout->resizeIdleCounter; } + + void enterQueueResize () { if (layout) layout->queueResizeCounter++; } + void leaveQueueResize () { if (layout) layout->queueResizeCounter--; } + bool queueResizeEntered () { return layout && layout->queueResizeCounter; } + + void enterSizeAllocate () { if (layout) layout->sizeAllocateCounter++; } + void leaveSizeAllocate () { if (layout) layout->sizeAllocateCounter--; } + bool sizeAllocateEntered () { return layout && layout->sizeAllocateCounter; } + + void enterSizeRequest () { if (layout) layout->sizeRequestCounter++; } + void leaveSizeRequest () { if (layout) layout->sizeRequestCounter--; } + bool sizeRequestEntered () { return layout && layout->sizeRequestCounter; } + + void enterGetExtremes () { if (layout) layout->getExtremesCounter++; } + void leaveGetExtremes () { if (layout) layout->getExtremesCounter--; } + bool getExtremesEntered () { return layout && layout->getExtremesCounter; } + + +public: + static int CLASS_ID; + + inline static void setAdjustMinWidth (bool adjustMinWidth) + { Widget::adjustMinWidth = adjustMinWidth; } + + Widget (); + ~Widget (); + + inline bool resizeQueued () { return flags & RESIZE_QUEUED; } + inline bool extremesQueued () { return flags & EXTREMES_QUEUED; } + inline bool needsResize () { return flags & NEEDS_RESIZE; } + inline bool needsAllocate () { return flags & NEEDS_ALLOCATE; } + inline bool allocateQueued () { return flags & ALLOCATE_QUEUED; } + inline bool extremesChanged () { return flags & EXTREMES_CHANGED; } + inline bool wasAllocated () { return flags & WAS_ALLOCATED; } + + void setParent (Widget *parent); + void setQuasiParent (Widget *quasiParent); + + void setGenerator (Widget *generator) { this->generator = generator; } + + inline style::Style *getStyle () { return style; } + /** \todo I do not like this. */ + inline Allocation *getAllocation () { return &allocation; } + + inline int boxOffsetX () + { return extraSpace.left + getStyle()->boxOffsetX (); } + inline int boxRestWidth () + { return extraSpace.right + getStyle()->boxRestWidth (); } + inline int boxDiffWidth () { return boxOffsetX () + boxRestWidth (); } + inline int boxOffsetY () + { return extraSpace.top + getStyle()->boxOffsetY (); } + inline int boxRestHeight () + { return extraSpace.bottom + getStyle()->boxRestHeight (); } + inline int boxDiffHeight () { return boxOffsetY () + boxRestHeight (); } + + void sizeRequest (Requisition *requisition); + void getExtremes (Extremes *extremes); + void sizeAllocate (Allocation *allocation); + + int getAvailWidth (bool forceValue); + int getAvailHeight (bool forceValue); + virtual bool getAdjustMinWidth () { return Widget::adjustMinWidth; } + void correctRequisition (Requisition *requisition, + void (*splitHeightFun) (int, int*, int*)); + void correctExtremes (Extremes *extremes); + int calcWidth (style::Length cssValue, int refWidth, Widget *refWidget, + int limitMinWidth, bool forceValue); + void calcFinalWidth (style::Style *style, int refWidth, Widget *refWidget, + int limitMinWidth, bool forceValue, int *finalWidth); + int calcHeight (style::Length cssValue, bool usePercentage, int refHeight, + Widget *refWidget, bool forceValue); + + virtual int applyPerWidth (int containerWidth, style::Length perWidth); + virtual int applyPerHeight (int containerHeight, style::Length perHeight); + + virtual bool isBlockLevel (); + virtual bool isPossibleContainer (); + + void containerSizeChanged (); + + bool intersects (Rectangle *area, Rectangle *intersection); + + /** Area is given in widget coordinates. */ + virtual void draw (View *view, Rectangle *area) = 0; + + bool buttonPress (EventButton *event); + bool buttonRelease (EventButton *event); + bool motionNotify (EventMotion *event); + void enterNotify (EventCrossing *event); + void leaveNotify (EventCrossing *event); + + virtual void setStyle (style::Style *style); + void setBgColor (style::Color *bgColor); + style::Color *getBgColor (); + + void drawBox (View *view, style::Style *style, Rectangle *area, + int x, int y, int width, int height, bool inverse); + void drawWidgetBox (View *view, Rectangle *area, bool inverse); + void drawSelected (View *view, Rectangle *area); + + void setButtonSensitive (bool buttonSensitive); + inline bool isButtonSensitive () { return buttonSensitive; } + + inline Widget *getParent () { return parent; } + inline Widget *getContainer () { return container; } + Widget *getTopLevel (); + int getLevel (); + int getGeneratorLevel (); + Widget *getNearestCommonAncestor (Widget *otherWidget); + + inline Widget *getGenerator () { return generator ? generator : parent; } + + inline Layout *getLayout () { return layout; } + + virtual Widget *getWidgetAtPoint (int x, int y, int level); + + void scrollTo (HPosition hpos, VPosition vpos); + void scrollTo (HPosition hpos, VPosition vpos, + int x, int y, int width, int height); + + void getPaddingArea (int *xPad, int *yPad, int *widthPad, int *heightPad); + + /** + * \brief Return an iterator for this widget. + * + * \em mask can narrow the types returned by the iterator, this can + * enhance performance quite much, e.g. when only searching for child + * widgets. + * + * With \em atEnd == false, the iterator starts \em before the beginning, + * i.e. the first call of dw::core::Iterator::next will let the iterator + * point on the first piece of contents. Likewise, With \em atEnd == true, + * the iterator starts \em after the last piece of contents, call + * dw::core::Iterator::prev in this case. + */ + virtual Iterator *iterator (Content::Type mask, bool atEnd) = 0; + virtual void removeChild (Widget *child); +}; + +void splitHeightPreserveAscent (int height, int *ascent, int *descent); +void splitHeightPreserveDescent (int height, int *ascent, int *descent); + +} // namespace core +} // namespace dw + +#endif // __DW_WIDGET_HH__ |