summaryrefslogtreecommitdiff
path: root/dw
diff options
context:
space:
mode:
Diffstat (limited to 'dw')
-rw-r--r--dw/Makefile.am57
-rw-r--r--dw/core.hh60
-rw-r--r--dw/events.hh83
-rw-r--r--dw/findtext.cc307
-rw-r--r--dw/findtext.hh84
-rw-r--r--dw/fltkcore.hh36
-rw-r--r--dw/fltkimgbuf.cc584
-rw-r--r--dw/fltkimgbuf.hh93
-rw-r--r--dw/fltkmisc.cc58
-rw-r--r--dw/fltkmisc.hh22
-rw-r--r--dw/fltkplatform.cc739
-rw-r--r--dw/fltkplatform.hh186
-rw-r--r--dw/fltkpreview.cc316
-rw-r--r--dw/fltkpreview.hh95
-rw-r--r--dw/fltkui.cc1395
-rw-r--r--dw/fltkui.hh505
-rw-r--r--dw/fltkviewbase.cc744
-rw-r--r--dw/fltkviewbase.hh141
-rw-r--r--dw/fltkviewport.cc558
-rw-r--r--dw/fltkviewport.hh84
-rw-r--r--dw/imgbuf.hh229
-rw-r--r--dw/imgrenderer.cc77
-rw-r--r--dw/imgrenderer.hh87
-rw-r--r--dw/iterator.cc920
-rw-r--r--dw/iterator.hh271
-rw-r--r--dw/layout.cc1445
-rw-r--r--dw/layout.hh532
-rw-r--r--dw/platform.hh171
-rw-r--r--dw/preview.xbm5
-rw-r--r--dw/selection.cc494
-rw-r--r--dw/selection.hh241
-rw-r--r--dw/style.cc1468
-rw-r--r--dw/style.hh907
-rw-r--r--dw/types.cc367
-rw-r--r--dw/types.hh238
-rw-r--r--dw/ui.cc519
-rw-r--r--dw/ui.hh592
-rw-r--r--dw/view.hh211
-rw-r--r--dw/widget.cc1785
-rw-r--r--dw/widget.hh514
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__