summaryrefslogtreecommitdiff
path: root/dw
diff options
context:
space:
mode:
authorjcid <devnull@localhost>2008-09-24 18:44:40 +0200
committerjcid <devnull@localhost>2008-09-24 18:44:40 +0200
commitc377e06400f138325a9a9d43d91a9272691867a1 (patch)
tree49f3ca1c46af11a058a68714899d4137ec717618 /dw
parent642f9b3e747859a7256ea12fab9f9ed50aa9253a (diff)
- Moved the dw2 tree into dillo2's tree.
Diffstat (limited to 'dw')
-rw-r--r--dw/Makefile.am70
-rw-r--r--dw/alignedtextblock.cc105
-rw-r--r--dw/alignedtextblock.hh61
-rw-r--r--dw/bullet.cc72
-rw-r--r--dw/bullet.hh27
-rw-r--r--dw/core.hh58
-rw-r--r--dw/events.hh83
-rw-r--r--dw/findtext.cc209
-rw-r--r--dw/findtext.hh82
-rw-r--r--dw/fltkcomplexbutton.cc282
-rw-r--r--dw/fltkcomplexbutton.hh59
-rw-r--r--dw/fltkcore.hh25
-rw-r--r--dw/fltkflatview.cc108
-rw-r--r--dw/fltkflatview.hh40
-rw-r--r--dw/fltkimgbuf.cc347
-rw-r--r--dw/fltkimgbuf.hh65
-rw-r--r--dw/fltkmisc.cc50
-rw-r--r--dw/fltkmisc.hh22
-rw-r--r--dw/fltkplatform.cc421
-rw-r--r--dw/fltkplatform.hh150
-rw-r--r--dw/fltkpreview.cc298
-rw-r--r--dw/fltkpreview.hh89
-rw-r--r--dw/fltkui.cc1202
-rw-r--r--dw/fltkui.hh554
-rw-r--r--dw/fltkviewbase.cc524
-rw-r--r--dw/fltkviewbase.hh108
-rw-r--r--dw/fltkviewport.cc496
-rw-r--r--dw/fltkviewport.hh77
-rw-r--r--dw/image.cc392
-rw-r--r--dw/image.hh161
-rw-r--r--dw/imgbuf.hh210
-rw-r--r--dw/iterator.cc797
-rw-r--r--dw/iterator.hh256
-rw-r--r--dw/layout.cc918
-rw-r--r--dw/layout.hh264
-rw-r--r--dw/listitem.cc72
-rw-r--r--dw/listitem.hh27
-rw-r--r--dw/platform.hh144
-rw-r--r--dw/preview.xbm5
-rw-r--r--dw/ruler.cc53
-rw-r--r--dw/ruler.hh30
-rw-r--r--dw/selection.cc514
-rw-r--r--dw/selection.hh275
-rw-r--r--dw/style.cc632
-rw-r--r--dw/style.hh669
-rw-r--r--dw/table.cc1192
-rw-r--r--dw/table.hh486
-rw-r--r--dw/tablecell.cc108
-rw-r--r--dw/tablecell.hh29
-rw-r--r--dw/textblock.cc2087
-rw-r--r--dw/textblock.hh387
-rw-r--r--dw/types.cc260
-rw-r--r--dw/types.hh204
-rw-r--r--dw/ui.cc364
-rw-r--r--dw/ui.hh564
-rw-r--r--dw/view.hh204
-rw-r--r--dw/widget.cc822
-rw-r--r--dw/widget.hh465
58 files changed, 18275 insertions, 0 deletions
diff --git a/dw/Makefile.am b/dw/Makefile.am
new file mode 100644
index 00000000..7150fce9
--- /dev/null
+++ b/dw/Makefile.am
@@ -0,0 +1,70 @@
+noinst_LIBRARIES = \
+ libDw-core.a \
+ libDw-fltk.a \
+ libDw-widgets.a
+
+libDw_core_a_SOURCES = \
+ core.hh \
+ events.hh \
+ findtext.cc \
+ findtext.hh \
+ imgbuf.hh \
+ 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
+
+libDw_fltk_a_SOURCES = \
+ fltkcomplexbutton.cc \
+ fltkcomplexbutton.hh \
+ fltkcore.hh \
+ fltkflatview.cc \
+ fltkflatview.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@
+
+libDw_widgets_a_SOURCES = \
+ alignedtextblock.cc \
+ alignedtextblock.hh \
+ bullet.cc \
+ bullet.hh \
+ image.cc \
+ image.hh \
+ listitem.cc \
+ listitem.hh \
+ ruler.cc \
+ ruler.hh \
+ table.cc \
+ table.hh \
+ tablecell.cc \
+ tablecell.hh \
+ textblock.cc \
+ textblock.hh
+
+EXTRA_DIST = preview.xbm
diff --git a/dw/alignedtextblock.cc b/dw/alignedtextblock.cc
new file mode 100644
index 00000000..bb24a3bc
--- /dev/null
+++ b/dw/alignedtextblock.cc
@@ -0,0 +1,105 @@
+/*
+ * Dillo Widget
+ *
+ * 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.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+
+
+#include "alignedtextblock.hh"
+#include <stdio.h>
+
+namespace dw {
+
+AlignedTextblock::List::List ()
+{
+ textblocks = new misc::SimpleVector <AlignedTextblock*> (4);
+ values = new misc::SimpleVector <int> (4);
+ maxValue = 0;
+ refCount = 0;
+}
+
+AlignedTextblock::List::~List ()
+{
+ delete textblocks;
+ delete values;
+}
+
+int AlignedTextblock::List::add(AlignedTextblock *textblock)
+{
+ textblocks->increase ();
+ values->increase ();
+ textblocks->set (textblocks->size () - 1, textblock);
+ refCount++;
+ return textblocks->size () - 1;
+}
+
+void AlignedTextblock::List::unref(int pos)
+{
+ assert (textblocks->get (pos) != NULL);
+ textblocks->set (pos, NULL);
+ refCount--;
+
+ if(refCount == 0)
+ delete this;
+}
+
+int AlignedTextblock::CLASS_ID = -1;
+
+AlignedTextblock::AlignedTextblock (bool limitTextWidth):
+ Textblock (limitTextWidth)
+{
+ registerName ("dw::AlignedTextblock", &CLASS_ID);
+}
+
+void AlignedTextblock::setRefTextblock (AlignedTextblock *ref)
+{
+ if(ref == NULL)
+ list = new List();
+ else
+ list = ref->list;
+
+ listPos = list->add (this);
+ updateValue ();
+}
+
+AlignedTextblock::~AlignedTextblock()
+{
+ list->unref (listPos);
+}
+
+void AlignedTextblock::updateValue ()
+{
+ if (list) {
+ list->setValue (listPos, getValue ());
+
+ if (list->getValue (listPos) > list->getMaxValue ()) {
+ // New value greater than current maximum -> apply it to others.
+ list->setMaxValue (list->getValue (listPos));
+
+ for (int i = 0; i < list->size (); i++)
+ if (list->getTextblock (i))
+ list->getTextblock (i)->setMaxValue (list->getMaxValue (),
+ list->getValue (i));
+ } else {
+ /* No change, apply old max_value only to this page. */
+ setMaxValue (list->getMaxValue (), list->getValue (listPos));
+ }
+ }
+}
+
+} // namespace dw
diff --git a/dw/alignedtextblock.hh b/dw/alignedtextblock.hh
new file mode 100644
index 00000000..e855f1cc
--- /dev/null
+++ b/dw/alignedtextblock.hh
@@ -0,0 +1,61 @@
+#ifndef __DW_ALIGNEDTEXTBLOCK_HH__
+#define __DW_ALIGNEDTEXTBLOCK_HH__
+
+#include "core.hh"
+#include "textblock.hh"
+
+namespace dw {
+
+/**
+ * \brief Base widget for all textblocks (sub classes of dw::Textblock), which
+ * are positioned vertically and aligned horizontally.
+ */
+class AlignedTextblock: public Textblock
+{
+private:
+ class List
+ {
+ private:
+ misc::SimpleVector <AlignedTextblock*> *textblocks;
+ misc::SimpleVector <int> *values;
+ int maxValue, refCount;
+
+ ~List ();
+
+ public:
+ List ();
+ inline int add (AlignedTextblock *textblock);
+ void unref (int pos);
+
+ inline int getMaxValue () { return maxValue; }
+ inline void setMaxValue (int maxValue) { this->maxValue = maxValue; }
+
+ inline int size () { return textblocks->size (); }
+ inline AlignedTextblock *getTextblock (int pos) {
+ return textblocks->get (pos); }
+ inline int getValue (int pos) {return values->get (pos); }
+ inline void setValue (int pos, int value) {
+ return values->set (pos, value); }
+ };
+
+ List *list;
+ int listPos;
+
+protected:
+ AlignedTextblock(bool limitTextWidth);
+
+ virtual int getValue () = 0;
+ virtual void setMaxValue (int maxValue, int value) = 0;
+
+ void setRefTextblock (AlignedTextblock *ref);
+ void updateValue ();
+
+public:
+ static int CLASS_ID;
+
+ ~AlignedTextblock();
+};
+
+} // namespace dw
+
+#endif // __DW_ALIGNEDTEXTBLOCK_HH__
diff --git a/dw/bullet.cc b/dw/bullet.cc
new file mode 100644
index 00000000..cc13867c
--- /dev/null
+++ b/dw/bullet.cc
@@ -0,0 +1,72 @@
+/*
+ * Dillo Widget
+ *
+ * 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.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+
+
+#include "bullet.hh"
+
+#include <stdio.h>
+
+namespace dw {
+
+Bullet::Bullet ()
+{
+}
+
+void Bullet::sizeRequestImpl (core::Requisition *requisition)
+{
+ requisition->width = lout::misc::max (getStyle()->font->xHeight * 4 / 5, 1);
+ requisition->ascent = lout::misc::max (getStyle()->font->xHeight, 1);
+ requisition->descent = 0;
+}
+
+void Bullet::draw (core::View *view, core::Rectangle *area)
+{
+ int x, y, l;
+ bool filled = true;
+
+ l = lout::misc::min (allocation.width, allocation.ascent);
+ x = allocation.x;
+ y = allocation.y + allocation.ascent - getStyle()->font->xHeight;
+
+ switch (getStyle()->listStyleType) {
+ case core::style::LIST_STYLE_TYPE_SQUARE:
+ view->drawRectangle (getStyle()->color,
+ core::style::Color::SHADING_NORMAL,
+ false, x, y, l, l);
+ break;
+ case core::style::LIST_STYLE_TYPE_CIRCLE:
+ filled = false;
+ // Fall Through
+ case core::style::LIST_STYLE_TYPE_DISC:
+ default:
+ view->drawArc (getStyle()->color, core::style::Color::SHADING_NORMAL,
+ filled, x, y, l, l, 0, 360);
+ }
+}
+
+core::Iterator *Bullet::iterator (core::Content::Type mask, bool atEnd)
+{
+ //return new core::TextIterator (this, mask, atEnd, "*");
+ /** \bug Not implemented. */
+ return new core::EmptyIterator (this, mask, atEnd);
+}
+
+} // namespace dw
diff --git a/dw/bullet.hh b/dw/bullet.hh
new file mode 100644
index 00000000..00912bd8
--- /dev/null
+++ b/dw/bullet.hh
@@ -0,0 +1,27 @@
+#ifndef __BULLET_HH__
+#define __BULLET_HH__
+
+#include "core.hh"
+
+namespace dw {
+
+/**
+ * \brief Displays different kind of bullets.
+ *
+ * Perhaps, in the future, Unicode characters are used for bullets, so this
+ * widget is not used anymore.
+ */
+class Bullet: public core::Widget
+{
+protected:
+ void sizeRequestImpl (core::Requisition *requisition);
+ void draw (core::View *view, core::Rectangle *area);
+ core::Iterator *iterator (core::Content::Type mask, bool atEnd);
+
+public:
+ Bullet ();
+};
+
+} // namespace dw
+
+#endif // __BULLET_HH__
diff --git a/dw/core.hh b/dw/core.hh
new file mode 100644
index 00000000..b6e18c10
--- /dev/null
+++ b/dw/core.hh
@@ -0,0 +1,58 @@
+#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;
+
+namespace ui {
+
+class ResourceFactory;
+
+} // namespace ui
+
+
+} // namespace dw
+} // namespace core
+
+#include "../lout/object.hh"
+#include "../lout/container.hh"
+#include "../lout/signal.hh"
+
+#include "types.hh"
+#include "events.hh"
+#include "imgbuf.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 00000000..860472ab
--- /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 independant 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 independant
+ * way.
+ */
+class Event: public 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 dw
+} // namespace core
+
+#endif // __DW_EVENTS_HH__
diff --git a/dw/findtext.cc b/dw/findtext.cc
new file mode 100644
index 00000000..15de9f16
--- /dev/null
+++ b/dw/findtext.cc
@@ -0,0 +1,209 @@
+/*
+ * Dillo Widget
+ *
+ * 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.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+
+
+#include "core.hh"
+
+namespace dw {
+namespace core {
+
+FindtextState::FindtextState ()
+{
+ key = NULL;
+ nexttab = NULL;
+ widget = NULL;
+ iterator = NULL;
+ hlIterator = NULL;
+}
+
+FindtextState::~FindtextState ()
+{
+ if (key)
+ delete key;
+ if (nexttab)
+ delete[] nexttab;
+ if (iterator)
+ delete iterator;
+ if (hlIterator)
+ delete hlIterator;
+}
+
+void FindtextState::setWidget (Widget *widget)
+{
+ this->widget = widget;
+
+ // A widget change will restart the search.
+ if (key)
+ delete 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)
+{
+ 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)
+ delete this->key;
+ this->key = strdup (key);
+ this->caseSens = caseSens;
+
+ if (nexttab)
+ delete[] nexttab;
+ nexttab = createNexttab (key, caseSens);
+
+ if (iterator)
+ delete iterator;
+ iterator = new CharIterator (widget);
+ iterator->next ();
+ } else
+ newKey = false;
+
+ bool firstTrial = !wasHighlighted || newKey;
+
+ if (search0 ()) {
+ // Highlighlighting 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);
+ iterator->next ();
+
+ // We expect a success.
+ Result result2 = search (key, caseSens);
+ assert (result2 == SUCCESS);
+ return RESTART;
+ }
+ }
+}
+
+/**
+ * \brief This method is called when the user closes the "find text" dialog.
+ */
+void FindtextState::resetSearch ()
+{
+ unhighlight ();
+
+ if (key)
+ delete key;
+ key = NULL;
+}
+
+int *FindtextState::createNexttab (const char *key, bool caseSens)
+{
+ 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);
+
+ 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 ()
+{
+ if (iterator->getChar () == CharIterator::END)
+ return false;
+
+ int j = 0;
+ bool nextit = true;
+ int l = strlen (key);
+
+ do {
+ if (j == -1 || charsEqual (iterator->getChar (), key[j], caseSens)) {
+ j++;
+ nextit = iterator->next ();
+ } else
+ j = nexttab[j];
+ } while (nextit && j < l);
+
+ if (j >= l) {
+ // Go back to where the word was found.
+ for (int i = 0; i < l; i++)
+ iterator->prev ();
+ return true;
+ } else
+ return false;
+}
+
+} // namespace dw
+} // namespace core
diff --git a/dw/findtext.hh b/dw/findtext.hh
new file mode 100644
index 00000000..d0c20206
--- /dev/null
+++ b/dw/findtext.hh
@@ -0,0 +1,82 @@
+#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 occurance of the pattern has been found. */
+ SUCCESS,
+
+ /**
+ * \brief There is no further occurance of the pattern, instead, the
+ * first occurance 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 int *createNexttab (const char *key, bool caseSens);
+ bool unhighlight ();
+ bool search0 ();
+
+ 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);
+ void resetSearch ();
+};
+
+} // namespace dw
+} // namespace core
+
+#endif // __DW_FINDTEXT_STATE_H__
diff --git a/dw/fltkcomplexbutton.cc b/dw/fltkcomplexbutton.cc
new file mode 100644
index 00000000..1649683f
--- /dev/null
+++ b/dw/fltkcomplexbutton.cc
@@ -0,0 +1,282 @@
+//
+//
+// Copyright 1998-2006 by Bill Spitzak and others.
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Library General Public
+// License as published by the Free Software Foundation; either
+// version 3 of the License, or (at your option) any later version.
+//
+// This library 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
+// Library General Public License for more details.
+//
+// You should have received a copy of the GNU Library General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+// USA.
+//
+// Please report all bugs and problems to "fltk-bugs@fltk.org".
+//
+
+#include <fltk/events.h>
+#include <fltk/damage.h>
+#include <fltk/Group.h>
+#include <fltk/Box.h>
+#include <stdlib.h>
+
+#include "fltkcomplexbutton.hh"
+
+using namespace fltk;
+using namespace dw::fltk::ui;
+
+/*! \class fltk::ComplexButton
+
+ ComplexButtons generate callbacks when they are clicked by the user. You
+ control exactly when and how by changing the values for when():
+ - fltk::WHEN_NEVER: The callback is not done, instead changed() is
+ turned on.
+ - fltk::WHEN_RELEASE: This is the default, the callback is done
+ after the user successfully clicks the button (i.e. they let it go
+ with the mouse still pointing at it), or when a shortcut is typed.
+ - fltk::WHEN_CHANGED : The callback is done each time the value()
+ changes (when the user pushes and releases the button, and as the
+ mouse is dragged around in and out of the button).
+
+ ComplexButtons can also generate callbacks in response to fltk::SHORTCUT
+ events. The button can either have an explicit shortcut() value or a
+ letter shortcut can be indicated in the label() with an '&'
+ character before it. For the label shortcut it does not matter if
+ Alt is held down, but if you have an input field in the same window,
+ the user will have to hold down the Alt key so that the input field
+ does not eat the event first as an fltk::KEY event.
+
+ \image html buttons.gif
+*/
+
+/*! \fn bool ComplexButton::value() const
+ The current value. True means it is pushed down, false means it is
+ not pushed down. The ToggleComplexButton subclass provides the ability for
+ the user to change this value permanently, otherwise it is just
+ temporary while the user is holding the button down.
+
+ This is the same as Widget::state().
+*/
+
+/*! \fn bool ComplexButton::value(bool)
+ Change the value(). Redraws the button and returns true if the new
+ value is different. This is the same function as Widget::state().
+ See also Widget::set(), Widget::clear(), and Widget::setonly().
+
+ If you turn it on, a normal button will draw pushed-in, until
+ the user clicks it and releases it.
+*/
+
+static bool initial_state;
+
+int ComplexButton::handle(int event) {
+ return handle(event, Rectangle(w(),h()));
+}
+
+int ComplexButton::handle(int event, const Rectangle& rectangle) {
+ switch (event) {
+ case ENTER:
+ case LEAVE:
+ redraw_highlight();
+ case MOVE:
+ return 1;
+ case PUSH:
+ if (pushed()) return 1; // ignore extra pushes on currently-pushed button
+ initial_state = state();
+ clear_flag(PUSHED);
+ do_callback();
+ case DRAG: {
+ bool inside = event_inside(rectangle);
+ if (inside) {
+ if (!flag(PUSHED)) {
+ set_flag(PUSHED);
+ redraw(DAMAGE_VALUE);
+ }
+ } else {
+ if (flag(PUSHED)) {
+ clear_flag(PUSHED);
+ redraw(DAMAGE_VALUE);
+ }
+ }
+ if (when() & WHEN_CHANGED) { // momentary button must record state()
+ if (state(inside ? !initial_state : initial_state))
+ do_callback();
+ }
+ return 1;}
+ case RELEASE:
+ if (!flag(PUSHED)) return 1;
+ clear_flag(PUSHED);
+ redraw(DAMAGE_VALUE);
+ if (type() == RADIO)
+ setonly();
+ else if (type()) // TOGGLE
+ state(!initial_state);
+ else {
+ state(initial_state);
+ if (when() & WHEN_CHANGED) {do_callback(); return 1;}
+ }
+ if (when() & WHEN_RELEASE) do_callback(); else set_changed();
+ return 1;
+ case FOCUS:
+ redraw(1); // minimal redraw to just add the focus box
+ // grab initial focus if we are an ReturnComplexButton:
+ return shortcut()==ReturnKey ? 2 : 1;
+ case UNFOCUS:
+ redraw(DAMAGE_HIGHLIGHT);
+ return 1;
+ case KEY:
+ if (event_key() == ' ' || event_key() == ReturnKey
+ || event_key() == KeypadEnter) goto EXECUTE;
+ return 0;
+ case SHORTCUT:
+ if (!test_shortcut()) return 0;
+ EXECUTE:
+ if (type() == RADIO) {
+ if (!state()) {
+ setonly();
+ if (when() & WHEN_CHANGED) do_callback(); else set_changed();
+ }
+ } else if (type()) { // TOGGLE
+ state(!state());
+ if (when() & WHEN_CHANGED) do_callback(); else set_changed();
+ }
+ if (when() & WHEN_RELEASE) do_callback();
+ return 1;
+ default:
+ return 0;
+ }
+}
+
+////////////////////////////////////////////////////////////////
+
+#include <fltk/draw.h>
+
+extern Widget* fl_did_clipping;
+
+/*!
+ This function provides a mess of back-compatabilty and Windows
+ emulation to subclasses of ComplexButton to draw with. It will draw the
+ button according to the current state of being pushed and it's
+ state(). If non-zero is passed for \a glyph_width then the glyph()
+ is drawn in that space on the left (or on the right if negative),
+ and it assummes the glyph indicates the state(), so the box is only
+ used to indicate the pushed state.
+*/
+void ComplexButton::draw(int glyph_width) const
+{
+ // For back-compatability, setting color() or box() directly on a plain
+ // button will cause it to act like buttoncolor() or buttonbox() are
+ // set:
+ Style localstyle;
+ const Style* style = this->style();
+ if (!glyph_width) {
+ localstyle = *style;
+ if (localstyle.color_) localstyle.buttoncolor_ = localstyle.color_;
+ if (localstyle.box_) localstyle.buttonbox_ = localstyle.box_;
+ if (localstyle.labelcolor_) localstyle.textcolor_ = localstyle.labelcolor_;
+ style = &localstyle;
+ }
+
+ Box* box = style->buttonbox();
+
+ Flags box_flags = flags() | OUTPUT;
+ Flags glyph_flags = box_flags & ~(HIGHLIGHT|OUTPUT);
+ if (glyph_width) box_flags &= ~STATE;
+
+ // only draw "inside" labels:
+ Rectangle r(0,0,w(),h());
+
+ if (box == NO_BOX) {
+ Color bg;
+ if (box_flags & HIGHLIGHT && (bg = style->highlight_color())) {
+ setcolor(bg);
+ fillrect(r);
+ } else if (label() || (damage()&(DAMAGE_EXPOSE|DAMAGE_HIGHLIGHT))) {
+ // erase the background so we can redraw the label in the new color:
+ draw_background();
+ }
+ // this allows these buttons to be put into browser/menus:
+ //fg = fl_item_labelcolor(this);
+ } else {
+ if ((damage()&(DAMAGE_EXPOSE|DAMAGE_HIGHLIGHT))
+ && !box->fills_rectangle()) {
+ // Erase the area behind non-square boxes
+ draw_background();
+ }
+ }
+
+ // Draw the box:
+ drawstyle(style,box_flags);
+ // For back-compatability we use any directly-set selection_color()
+ // to color the box:
+ if (!glyph_width && state() && style->selection_color_) {
+ setbgcolor(style->selection_color_);
+ setcolor(contrast(style->selection_textcolor(),style->selection_color_));
+ }
+ box->draw(r);
+ Rectangle r1(r); box->inset(r1);
+
+ if (glyph_width) {
+ int g = abs(glyph_width);
+ Rectangle lr(r1);
+ Rectangle gr(r1, g, g);
+ if (glyph_width < 0) {
+ lr.w(lr.w()-g-3);
+ gr.x(r1.r()-g-3);
+ } else {
+ lr.set_x(g+3);
+ gr.x(r1.x()+3);
+ }
+ this->draw_label(lr, box_flags);
+ drawstyle(style,glyph_flags);
+ this->glyph()->draw(gr);
+ drawstyle(style,box_flags);
+ } else {
+ this->draw_label(r1, box_flags);
+ }
+ box->draw_symbol_overlay(r);
+}
+
+void ComplexButton::draw() {
+ if (type() == HIDDEN) {
+ fl_did_clipping = this;
+ return;
+ }
+ draw(0);
+
+ // ComplexButton is a Group, draw its children
+ for (int i = children () - 1; i >= 0; i--)
+ draw_child (*child (i));
+}
+
+////////////////////////////////////////////////////////////////
+
+static NamedStyle style("ComplexButton", 0, &ComplexButton::default_style);
+NamedStyle* ComplexButton::default_style = &::style;
+
+ComplexButton::ComplexButton(int x,int y,int w,int h, const char *l) :
+ Group(x,y,w,h,l)
+{
+ style(default_style);
+ highlight_color(GRAY20);
+ //set_click_to_focus();
+}
+
+////////////////////////////////////////////////////////////////
+
+/*! \class fltk::ToggleComplexButton
+ This button turns the state() on and off each release of a click
+ inside of it.
+
+ You can also convert a regular button into this by doing
+ type(ComplexButton::TOGGLE) to it.
+*/
+
+//
+//
diff --git a/dw/fltkcomplexbutton.hh b/dw/fltkcomplexbutton.hh
new file mode 100644
index 00000000..8f80d5aa
--- /dev/null
+++ b/dw/fltkcomplexbutton.hh
@@ -0,0 +1,59 @@
+//
+//
+// Push button widget
+//
+// Copyright 2002 by Bill Spitzak and others.
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Library General Public
+// License as published by the Free Software Foundation; either
+// version 2 of the License, or (at your option) any later version.
+//
+// This library 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
+// Library General Public License for more details.
+//
+// You should have received a copy of the GNU Library General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+// USA.
+//
+// Please report all bugs and problems to "fltk-bugs@fltk.org".
+//
+
+#ifndef __FLTK_COMPLEX_BUTTON_HH__
+#define __FLTK_COMPLEX_BUTTON_HH__
+
+#include <fltk/Group.h>
+
+namespace dw {
+namespace fltk {
+namespace ui {
+
+class ComplexButton: public ::fltk::Group
+{
+public:
+ enum {HIDDEN=3}; // back-comptability value to hide the button
+
+ bool value() const { return state(); }
+ bool value(bool v) { return state(v); }
+
+ int handle(int);
+ int handle(int event, const Rectangle&);
+ ComplexButton(int,int,int,int,const char * = 0);
+ ~ComplexButton() { remove_all ();};
+ static ::fltk::NamedStyle* default_style;
+
+ virtual void draw();
+ void draw(int glyph_width) const;
+};
+
+} // namespace ui
+} // namespace fltk
+} // namespace dw
+
+#endif // __FLTK_COMPLEX_BUTTON_HH__
+
+//
+//
diff --git a/dw/fltkcore.hh b/dw/fltkcore.hh
new file mode 100644
index 00000000..fbff3fad
--- /dev/null
+++ b/dw/fltkcore.hh
@@ -0,0 +1,25 @@
+#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 core
+
+#include <fltk/Widget.h>
+
+#include "core.hh"
+#include "fltkimgbuf.hh"
+#include "fltkplatform.hh"
+#include "fltkui.hh"
+
+#undef __INCLUDED_FROM_DW_FLTK_CORE_HH__
+
+#endif // __DW_FLTK_CORE_HH__
diff --git a/dw/fltkflatview.cc b/dw/fltkflatview.cc
new file mode 100644
index 00000000..7f5f88a6
--- /dev/null
+++ b/dw/fltkflatview.cc
@@ -0,0 +1,108 @@
+/*
+ * Dillo Widget
+ *
+ * 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.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+
+
+#include "fltkflatview.hh"
+
+#include <fltk/draw.h>
+#include <fltk/events.h>
+
+#include <stdio.h>
+
+using namespace fltk;
+using namespace lout::container::typed;
+
+namespace dw {
+namespace fltk {
+
+FltkFlatView::FltkFlatView (int x, int y, int w, int h, const char *label):
+ FltkWidgetView (x, y, w, h, label)
+{
+}
+
+FltkFlatView::~FltkFlatView ()
+{
+}
+
+void FltkFlatView::setCanvasSize (int width, int ascent, int descent)
+{
+ /**
+ * \bug It has to be clarified, who is responsible for setting the
+ * FLTK widget size. In the only used context (complex buttons),
+ * it is done elsewhere.
+ */
+
+#if 0
+ FltkWidgetView::setCanvasSize (width, ascent, descent);
+
+ w (width);
+ h (ascent + descent);
+#endif
+}
+
+bool FltkFlatView::usesViewport ()
+{
+ return false;
+}
+
+int FltkFlatView::getHScrollbarThickness ()
+{
+ return 0;
+}
+
+int FltkFlatView::getVScrollbarThickness ()
+{
+ return 0;
+}
+
+void FltkFlatView::scrollTo (int x, int y)
+{
+}
+
+void FltkFlatView::setViewportSize (int width, int height,
+ int hScrollbarThickness,
+ int vScrollbarThickness)
+{
+}
+
+int FltkFlatView::translateViewXToCanvasX (int x)
+{
+ return x;
+}
+
+int FltkFlatView::translateViewYToCanvasY (int y)
+{
+ return y;
+}
+
+int FltkFlatView::translateCanvasXToViewX (int x)
+{
+ return x;
+}
+
+int FltkFlatView::translateCanvasYToViewY (int y)
+{
+ return y;
+}
+
+
+} // namespace fltk
+} // namespace dw
diff --git a/dw/fltkflatview.hh b/dw/fltkflatview.hh
new file mode 100644
index 00000000..dee5498f
--- /dev/null
+++ b/dw/fltkflatview.hh
@@ -0,0 +1,40 @@
+#ifndef __DW_FLTKFLATVIEW_HH__
+#define __DW_FLTKFLATVIEW_HH__
+
+#include <fltk/Group.h>
+#include <fltk/Scrollbar.h>
+
+#include "core.hh"
+#include "fltkcore.hh"
+#include "fltkviewbase.hh"
+
+namespace dw {
+namespace fltk {
+
+class FltkFlatView: public FltkWidgetView
+{
+protected:
+ int translateViewXToCanvasX (int x);
+ int translateViewYToCanvasY (int y);
+ int translateCanvasXToViewX (int x);
+ int translateCanvasYToViewY (int y);
+
+public:
+ FltkFlatView (int x, int y, int w, int h, const char *label = 0);
+ ~FltkFlatView ();
+
+ void setCanvasSize (int width, int ascent, int descent);
+
+ bool usesViewport ();
+ int getHScrollbarThickness ();
+ int getVScrollbarThickness ();
+ void scrollTo (int x, int y);
+ void setViewportSize (int width, int height,
+ int hScrollbarThickness, int vScrollbarThickness);
+};
+
+} // namespace fltk
+} // namespace dw
+
+#endif // __DW_FLTKFLATVIEW_HH__
+
diff --git a/dw/fltkimgbuf.cc b/dw/fltkimgbuf.cc
new file mode 100644
index 00000000..b8beb1cb
--- /dev/null
+++ b/dw/fltkimgbuf.cc
@@ -0,0 +1,347 @@
+/*
+ * Dillo Widget
+ *
+ * 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.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+
+
+#include "fltkcore.hh"
+#include "../lout/misc.hh"
+
+#include <fltk/draw.h>
+#include <fltk/Color.h>
+
+using namespace fltk;
+
+namespace dw {
+namespace fltk {
+
+using namespace container::typed;
+
+FltkImgbuf::FltkImgbuf (Type type, int width, int height)
+{
+ //printf("FltkImgbuf: new root %p\n", this);
+ init (type, width, height, NULL);
+}
+
+FltkImgbuf::FltkImgbuf (Type type, int width, int height, FltkImgbuf *root)
+{
+ //printf("FltkImgbuf: new scaled %p, root is %p\n", this, root);
+ init (type, width, height, root);
+}
+
+void FltkImgbuf::init (Type type, int width, int height, FltkImgbuf *root)
+{
+ this->root = root;
+ this->type = type;
+ this->width = width;
+ this->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;
+ }
+ //fprintf(stderr,"FltkImgbuf::init width=%d height=%d bpp=%d\n",
+ // width, height, bpp);
+ 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 misc::BitSet (height);
+
+ // The list is only used for root buffers.
+ if (isRoot())
+ scaledBuffers = new 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 ()
+{
+ //printf ("FltkImgbuf::~FltkImgbuf (%s)\n", isRoot() ? "root" : "scaled");
+
+ //if (root)
+ // printf("FltkImgbuf[scaled %p, root is %p]: deleted\n", this, root);
+ //else
+ // printf("FltkImgbuf[root %p]: deleted\n", this);
+
+ if (!isRoot())
+ root->detachScaledBuf (this);
+
+ delete[] rawdata;
+ delete copiedRows;
+
+ if (scaledBuffers)
+ delete scaledBuffers;
+}
+
+/**
+ * \brief This method is called for the root buffer, when a scaled buffer
+ * removed.
+ */
+void FltkImgbuf::detachScaledBuf (FltkImgbuf *scaledBuf)
+{
+ scaledBuffers->detachRef (scaledBuf);
+
+ //printf("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)
+{
+ 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);
+ }
+ }
+}
+
+void FltkImgbuf::copyRow (int row, const core::byte *data)
+{
+ assert (isRoot());
+
+ // 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 (root)
+ return root->getScaledBuf (width, 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;
+ }
+ }
+
+ /* This size is not yet used, so a new buffer has to be created. */
+ FltkImgbuf *sb = new FltkImgbuf (type, width, height, this);
+ scaledBuffers->append (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;
+ //fprintf(stderr,"::getRowArea: area x=%d y=%d width=%d height=%d\n",
+ // area->x, area->y, area->width, area->height);
+ } 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;
+ //fprintf(stderr,"::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;
+}
+
+void FltkImgbuf::ref ()
+{
+ refCount++;
+
+ //if (root)
+ // printf("FltkImgbuf[scaled %p, root is %p]: ref() => %d\n",
+ // this, root, refCount);
+ //else
+ // printf("FltkImgbuf[root %p]: ref() => %d\n", this, refCount);
+}
+
+void FltkImgbuf::unref ()
+{
+ //if (root)
+ // printf("FltkImgbuf[scaled %p, root is %p]: ref() => %d\n",
+ // this, root, refCount - 1);
+ //else
+ // printf("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
+ printf("FltkImgbuf[root %p]: not deleted\n", this);
+ } 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;
+}
+
+void FltkImgbuf::draw (::fltk::Widget *target, int xRoot, int yRoot,
+ int x, int y, int width, int height)
+{
+ // TODO (i): Implementation.
+ // TODO (ii): Clarify the question, whether "target" is the current widget
+ // (and so has not to be passed at all).
+
+/*
+ setcolor (0);
+
+ for (int row = y; row < y + height; row++) {
+ if (copiedRows->get (row)) {
+ ::fltk::Rectangle rect (x + xRoot, row + yRoot, width, 1);
+ fillrect (rect);
+ }
+ }
+*/
+
+ //fprintf(stderr,"::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 1
+ 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;
+ }
+
+ // almost OK for rows. For some unknown reason it trims the bottom and
+ // rightmost parts when scrolling.
+ ::fltk::Rectangle rect (xRoot + x, yRoot + y, width, height);
+ PixelType ptype = (type == RGBA) ? ::fltk::RGBA : ::fltk::RGB;
+ drawimage(rawdata+bpp*(y*this->width + x),ptype,rect,bpp*this->width);
+
+#else
+ // OK for full image.
+ ::fltk::Rectangle rect (xRoot, yRoot, this->width, this->height);
+ PixelType ptype = (type == RGBA) ? ::fltk::RGBA : ::fltk::RGB;
+ drawimage(rawdata,ptype,rect);
+#endif
+//}
+}
+
+} // namespace dw
+} // namespace fltk
diff --git a/dw/fltkimgbuf.hh b/dw/fltkimgbuf.hh
new file mode 100644
index 00000000..54d9ca34
--- /dev/null
+++ b/dw/fltkimgbuf.hh
@@ -0,0 +1,65 @@
+#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:
+ FltkImgbuf *root;
+ int refCount;
+ bool deleteOnUnref;
+ lout::container::typed::List <FltkImgbuf> *scaledBuffers;
+
+ int width, height;
+ Type type;
+
+//{
+ int bpp;
+ uchar *rawdata;
+//}
+
+ // This is just for testing drawing, it has to be replaced by
+ // the image buffer.
+ lout::misc::BitSet *copiedRows;
+
+ FltkImgbuf (Type type, int width, int height, FltkImgbuf *root);
+ void init (Type type, int width, int height, FltkImgbuf *root);
+ int scaledY(int ySrc);
+ int isRoot() { return (root == NULL); }
+ void detachScaledBuf (FltkImgbuf *scaledBuf);
+
+protected:
+ ~FltkImgbuf ();
+
+public:
+ FltkImgbuf (Type type, int width, int height);
+
+ void setCMap (int *colors, int num_colors);
+ inline void scaleRow (int row, const core::byte *data);
+ 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 ();
+ void ref ();
+ void unref ();
+
+ bool lastReference ();
+ void setDeleteOnUnref (bool deleteOnUnref);
+ bool isReferred ();
+
+ void draw (::fltk::Widget *target, int xRoot, int yRoot,
+ int x, int y, int width, int height);
+};
+
+} // namespace dw
+} // namespace fltk
+
+#endif // __DW_FLTK_IMGBUF_HH__
diff --git a/dw/fltkmisc.cc b/dw/fltkmisc.cc
new file mode 100644
index 00000000..08d75854
--- /dev/null
+++ b/dw/fltkmisc.cc
@@ -0,0 +1,50 @@
+/*
+ * Dillo Widget
+ *
+ * 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.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+
+
+#include "fltkmisc.hh"
+
+#include <fltk/events.h>
+#include <fltk/Monitor.h>
+#include <stdio.h>
+
+namespace dw {
+namespace fltk {
+namespace misc {
+
+int screenWidth ()
+{
+ return ::fltk::Monitor::all ().w ();
+}
+
+int screenHeight ()
+{
+ return ::fltk::Monitor::all ().h ();
+}
+
+void warpPointer (int x, int y)
+{
+ ::fltk::warp_mouse (x, y);
+}
+
+} // namespace misc
+} // namespace fltk
+} // namespace dw
diff --git a/dw/fltkmisc.hh b/dw/fltkmisc.hh
new file mode 100644
index 00000000..fc004318
--- /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 00000000..337f4dba
--- /dev/null
+++ b/dw/fltkplatform.cc
@@ -0,0 +1,421 @@
+/*
+ * Dillo Widget
+ *
+ * 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.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+
+
+#include "fltkcore.hh"
+
+#include <fltk/draw.h>
+#include <fltk/run.h>
+#include <fltk/events.h>
+#include <fltk/utf.h>
+#include <stdio.h>
+
+namespace dw {
+namespace fltk {
+
+using namespace ::fltk;
+
+/**
+ * \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);
+
+FltkFont::FltkFont (core::style::FontAttrs *attrs)
+{
+ copyAttrs (attrs);
+
+ int fa = 0;
+ if(weight >= 500)
+ fa |= BOLD;
+ if(style != core::style::FONT_STYLE_NORMAL)
+ fa |= ITALIC;
+
+ font = ::fltk::font(name, fa);
+ if(font == NULL) {
+ fprintf(stderr, "No font '%s', using default sans-serif font.\n", name);
+ /*
+ * If using xft, fltk::HELVETICA just means sans, fltk::COURIER
+ * means mono, and fltk::TIMES means serif.
+ */
+ font = HELVETICA;
+ }
+
+ setfont(font, size);
+ spaceWidth = (int)getwidth(" ");
+ int xw, xh;
+ measure("x", xw, xh);
+ xHeight = xh;
+ ascent = (int)getascent();
+ descent = (int)getdescent();
+
+ /**
+ * \bug The code above does not seem to work, so this workaround.
+ */
+ xHeight = ascent * 3 / 5;
+}
+
+FltkFont::~FltkFont ()
+{
+ fontsTable->remove (this);
+}
+
+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, core::style::Color::Type type):
+ Color (color, type)
+{
+ this->color = color;
+ this->type = type;
+
+ /*
+ * fltk/setcolor.cxx:
+ * "A Color of zero (fltk::NO_COLOR) will draw black but is
+ * ambiguous. It is returned as an error value or to indicate portions
+ * of a Style that should be inherited, and it is also used as the
+ * default label color for everything so that changing color zero can
+ * be used by the -fg switch. You should use fltk::BLACK (56) to get
+ * black."
+ *
+ * i.e., zero only works sometimes.
+ */
+
+ if (!(colors[SHADING_NORMAL] = shadeColor (color, SHADING_NORMAL) << 8))
+ colors[SHADING_NORMAL] = ::fltk::BLACK;
+ if (!(colors[SHADING_INVERSE] = shadeColor (color, SHADING_INVERSE) << 8))
+ colors[SHADING_INVERSE] = ::fltk::BLACK;
+
+ if(type == core::style::Color::TYPE_SHADED) {
+ if (!(colors[SHADING_DARK] = shadeColor (color, SHADING_DARK) << 8))
+ colors[SHADING_DARK] = ::fltk::BLACK;
+ if (!(colors[SHADING_LIGHT] = shadeColor (color, SHADING_LIGHT) << 8))
+ colors[SHADING_LIGHT] = ::fltk::BLACK;
+ }
+}
+
+FltkColor::~FltkColor ()
+{
+ colorsTable->remove (this);
+}
+
+FltkColor * FltkColor::create (int col, core::style::Color::Type type)
+{
+ ColorAttrs attrs(col, type);
+ FltkColor *color = colorsTable->get (&attrs);
+
+ if (color == NULL) {
+ color = new FltkColor (col, type);
+ colorsTable->put (color, color);
+ }
+
+ return color;
+}
+
+void FltkView::addFltkWidget (::fltk::Widget *widget,
+ core::Allocation *allocation)
+{
+}
+
+void FltkView::removeFltkWidget (::fltk::Widget *widget)
+{
+}
+
+void FltkView::allocateFltkWidget (::fltk::Widget *widget,
+ core::Allocation *allocation)
+{
+}
+
+void FltkView::drawFltkWidget (::fltk::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)
+{
+ return new ui::FltkComplexButtonResource (platform, widget, relief);
+}
+
+core::ui::ListResource *
+FltkPlatform::FltkResourceFactory::createListResource (core::ui
+ ::ListResource
+ ::SelectionMode
+ selectionMode)
+{
+ return new ui::FltkListResource (platform, selectionMode);
+}
+
+core::ui::OptionMenuResource *
+FltkPlatform::FltkResourceFactory::createOptionMenuResource ()
+{
+ return new ui::FltkOptionMenuResource (platform);
+}
+
+core::ui::EntryResource *
+FltkPlatform::FltkResourceFactory::createEntryResource (int maxLength,
+ bool password)
+{
+ return new ui::FltkEntryResource (platform, maxLength, password);
+}
+
+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 ()
+{
+ layout = NULL;
+ idleQueue = new container::typed::List <IdleFunc> (true);
+ idleFuncRunning = false;
+ idleFuncId = 0;
+
+ views = new container::typed::List <FltkView> (false);
+ resources = new container::typed::List <ui::FltkResource> (false);
+
+ resourceFactory.setPlatform (this);
+}
+
+FltkPlatform::~FltkPlatform ()
+{
+ if(idleFuncRunning)
+ remove_idle (generalStaticIdle, (void*)this);
+ delete idleQueue;
+ delete views;
+ delete resources;
+}
+
+void FltkPlatform::setLayout (core::Layout *layout)
+{
+ this->layout = layout;
+}
+
+
+void FltkPlatform::attachView (core::View *view)
+{
+ views->append ((FltkView*)view);
+
+ for (container::typed::Iterator <ui::FltkResource> it =
+ resources->iterator (); it.hasNext (); ) {
+ ui::FltkResource *resource = it.getNext ();
+ resource->attachView ((FltkView*)view);
+ }
+}
+
+
+void FltkPlatform::detachView (core::View *view)
+{
+ views->removeRef ((FltkView*)view);
+
+ for (container::typed::Iterator <ui::FltkResource> it =
+ resources->iterator (); it.hasNext (); ) {
+ ui::FltkResource *resource = it.getNext ();
+ resource->detachView ((FltkView*)view);
+ }
+}
+
+
+int FltkPlatform::textWidth (core::style::Font *font, const char *text,
+ int len)
+{
+ FltkFont *ff = (FltkFont*) font;
+ setfont (ff->font, ff->size);
+ return (int) getwidth (text, len);
+}
+
+int FltkPlatform::nextGlyph (const char *text, int idx)
+{
+ return utf8fwd (&text[idx + 1], text, &text[strlen (text)]) - text;
+}
+
+int FltkPlatform::prevGlyph (const char *text, int idx)
+{
+ return utf8back (&text[idx - 1], text, &text[strlen (text)]) - text;
+}
+
+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;
+ 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) {
+ 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())
+ remove_idle (generalStaticIdle, (void*)this);
+}
+
+core::style::Font *FltkPlatform::createFont (core::style::FontAttrs
+ *attrs,
+ bool tryEverything)
+{
+ return FltkFont::create (attrs);
+}
+
+core::style::Color *FltkPlatform::createSimpleColor (int color)
+{
+ return FltkColor::create (color, core::style::Color::TYPE_SIMPLE);
+}
+
+core::style::Color *FltkPlatform::createShadedColor (int color)
+{
+ return FltkColor::create (color, core::style::Color::TYPE_SHADED);
+}
+
+void FltkPlatform::copySelection(const char *text)
+{
+ fltk::copy(text, strlen(text), false);
+}
+
+core::Imgbuf *FltkPlatform::createImgbuf (core::Imgbuf::Type type,
+ int width, int height)
+{
+ return new FltkImgbuf (type, width, height);
+}
+
+core::ui::ResourceFactory *FltkPlatform::getResourceFactory ()
+{
+ return &resourceFactory;
+}
+
+
+void FltkPlatform::attachResource (ui::FltkResource *resource)
+{
+ resources->append (resource);
+
+ for (container::typed::Iterator <FltkView> it = views->iterator ();
+ it.hasNext (); ) {
+ FltkView *view = it.getNext ();
+ 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 00000000..7b3d3e73
--- /dev/null
+++ b/dw/fltkplatform.hh
@@ -0,0 +1,150 @@
+#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
+
+#include <fltk/Font.h>
+
+namespace dw {
+
+/**
+ * \brief This namespace contains FLTK implementations of Dw interfaces.
+ */
+namespace fltk {
+
+class FltkFont: public core::style::Font
+{
+ static lout::container::typed::HashTable <dw::core::style::FontAttrs,
+ FltkFont> *fontsTable;
+
+ FltkFont (core::style::FontAttrs *attrs);
+ ~FltkFont ();
+
+public:
+ ::fltk::Font *font;
+
+ static FltkFont *create (core::style::FontAttrs *attrs);
+};
+
+
+class FltkColor: public core::style::Color
+{
+ static lout::container::typed::HashTable <dw::core::style::ColorAttrs,
+ FltkColor> *colorsTable;
+
+ FltkColor (int color, core::style::Color::Type type);
+ ~FltkColor ();
+
+public:
+ int colors[SHADING_NUM];
+
+ static FltkColor *create(int color, core::style::Color::Type type);
+};
+
+
+/**
+ * \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 (::fltk::Widget *widget,
+ core::Allocation *allocation);
+ virtual void removeFltkWidget (::fltk::Widget *widget);
+ virtual void allocateFltkWidget (::fltk::Widget *widget,
+ core::Allocation *allocation);
+ virtual void drawFltkWidget (::fltk::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);
+ core::ui::OptionMenuResource *createOptionMenuResource ();
+ core::ui::EntryResource *createEntryResource (int maxLength,
+ bool password);
+ 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();
+
+ lout::container::typed::List <FltkView> *views;
+ 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);
+ int nextGlyph (const char *text, int idx);
+ int prevGlyph (const char *text, int idx);
+
+ int addIdle (void (core::Layout::*func) ());
+ void removeIdle (int idleId);
+
+ core::style::Font *createFont (core::style::FontAttrs *attrs,
+ bool tryEverything);
+ core::style::Color *createSimpleColor (int color);
+ core::style::Color *createShadedColor (int color);
+
+ core::Imgbuf *createImgbuf (core::Imgbuf::Type type, int width, int height);
+
+ 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 00000000..6bed7adf
--- /dev/null
+++ b/dw/fltkpreview.cc
@@ -0,0 +1,298 @@
+/*
+ * Dillo Widget
+ *
+ * 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.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+
+
+#include "fltkpreview.hh"
+#include "fltkmisc.hh"
+
+#include <fltk/events.h>
+#include <fltk/xbmImage.h>
+#include <fltk/draw.h>
+#include <stdio.h>
+
+#include "preview.xbm"
+
+using namespace ::fltk;
+
+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::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;
+ setfont(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
+ setcolor(((FltkColor*)color)->colors[shading]);
+ drawtext(text, len,
+ translateCanvasXToViewX (x), translateCanvasYToViewY (y));
+}
+
+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 (Widget *widget,
+ core::Rectangle *area)
+{
+}
+
+// ----------------------------------------------------------------------
+
+FltkPreviewWindow::FltkPreviewWindow (dw::core::Layout *layout):
+ MenuWindow (1, 1)
+{
+ box (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;
+ }
+
+ 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->w (w () - 2 * BORDER_WIDTH);
+ preview->h (h () - 2 * BORDER_WIDTH);
+}
+
+void FltkPreviewWindow::hideWindow ()
+{
+ 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):
+ Button (x, y, w, h, label)
+{
+ image (new xbmImage (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 PUSH:
+ window->showWindow ();
+ return Button::handle (event);
+
+ case DRAG:
+ if (window->visible ()) {
+ window->scrollTo (event_x_root (), event_y_root ());
+ return 1;
+ }
+ return Button::handle (event);
+
+ case RELEASE:
+ window->hideWindow ();
+ return Button::handle (event);
+
+ default:
+ return Button::handle (event);
+ }
+}
+
+} // namespace fltk
+} // namespace dw
diff --git a/dw/fltkpreview.hh b/dw/fltkpreview.hh
new file mode 100644
index 00000000..2464db89
--- /dev/null
+++ b/dw/fltkpreview.hh
@@ -0,0 +1,89 @@
+#ifndef __FlTKPREVIEW_HH__
+#define __FlTKPREVIEW_HH__
+
+#include <fltk/Button.h>
+#include <fltk/MenuWindow.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 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 drawImage (core::Imgbuf *imgbuf, int xRoot, int yRoot,
+ int x, int y, int width, int height);
+
+ bool usesFltkWidgets ();
+ void drawFltkWidget (::fltk::Widget *widget, core::Rectangle *area);
+};
+
+
+class FltkPreviewWindow: public ::fltk::MenuWindow
+{
+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 ::fltk::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 00000000..70ba1ef3
--- /dev/null
+++ b/dw/fltkui.cc
@@ -0,0 +1,1202 @@
+/*
+ * Dillo Widget
+ *
+ * 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.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+
+
+#include "fltkcore.hh"
+#include "fltkflatview.hh"
+#include "fltkcomplexbutton.hh"
+#include "../lout/misc.hh"
+
+#include <stdio.h>
+#include <fltk/Widget.h>
+#include <fltk/Group.h>
+#include <fltk/Input.h>
+#include <fltk/SecretInput.h>
+#include <fltk/TextEditor.h>
+#include <fltk/RadioButton.h>
+#include <fltk/CheckButton.h>
+#include <fltk/Choice.h>
+#include <fltk/Browser.h>
+#include <fltk/MultiBrowser.h>
+#include <fltk/Font.h>
+#include <fltk/draw.h>
+#include <fltk/Symbol.h>
+#include <fltk/Item.h>
+#include <fltk/ItemGroup.h>
+#include <fltk/events.h>
+
+namespace dw {
+namespace fltk {
+namespace ui {
+
+enum { RELIEF_X_THICKNESS = 5, RELIEF_Y_THICKNESS = 3 };
+
+using namespace object;
+using namespace container::typed;
+
+FltkResource::FltkResource (FltkPlatform *platform)
+{
+ this->platform = platform;
+
+ allocation.x = 0;
+ allocation.y = 0;
+ allocation.width = 1;
+ allocation.ascent = 1;
+ allocation.descent = 0;
+
+ style = NULL;
+}
+
+/**
+ * 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)
+{
+ viewsAndWidgets = new container::typed::List <ViewAndWidget> (true);
+ platform->attachResource (this);
+}
+
+FltkResource::~FltkResource ()
+{
+ platform->detachResource (this);
+ for (Iterator <ViewAndWidget> it = viewsAndWidgets->iterator ();
+ it.hasNext(); ) {
+ ViewAndWidget *viewAndWidget = it.getNext ();
+
+ if (viewAndWidget->widget) {
+ if (viewAndWidget->view) {
+ viewAndWidget->view->removeFltkWidget(viewAndWidget->widget);
+ }
+ delete viewAndWidget->widget;
+ }
+
+ }
+ delete viewsAndWidgets;
+ if(style)
+ style->unref ();
+}
+
+void FltkResource::attachView (FltkView *view)
+{
+ if (view->usesFltkWidgets ()) {
+ ViewAndWidget *viewAndWidget = new ViewAndWidget();
+ viewAndWidget->view = view;
+
+ viewAndWidget->widget = createNewWidget (&allocation);
+ viewAndWidget->view->addFltkWidget (viewAndWidget->widget, &allocation);
+ viewsAndWidgets->append (viewAndWidget);
+ if (style)
+ setWidgetStyle (viewAndWidget->widget, style);
+ }
+}
+
+void FltkResource::detachView (FltkView *view)
+{
+ for (Iterator <ViewAndWidget> it = viewsAndWidgets->iterator ();
+ it.hasNext(); ) {
+ ViewAndWidget *viewAndWidget = it.getNext ();
+ if (viewAndWidget->view == view) {
+ viewsAndWidgets->removeRef (viewAndWidget);
+ return;
+ }
+ }
+
+ fprintf (stderr, "FltkResource::detachView: View not found.");
+}
+
+void FltkResource::sizeAllocate (core::Allocation *allocation)
+{
+ this->allocation = *allocation;
+
+ for (Iterator <ViewAndWidget> it = viewsAndWidgets->iterator ();
+ it.hasNext(); ) {
+ ViewAndWidget *viewAndWidget = it.getNext ();
+ viewAndWidget->view->allocateFltkWidget (viewAndWidget->widget,
+ allocation);
+ }
+}
+
+void FltkResource::draw (core::View *view, core::Rectangle *area)
+{
+ FltkView *fltkView = (FltkView*)view;
+ if (fltkView->usesFltkWidgets ()) {
+ for (Iterator <ViewAndWidget> it = viewsAndWidgets->iterator ();
+ it.hasNext(); ) {
+ ViewAndWidget *viewAndWidget = it.getNext ();
+ if (viewAndWidget->view == fltkView) {
+ fltkView->drawFltkWidget (viewAndWidget->widget, area);
+ break;
+ }
+ }
+ }
+}
+
+void FltkResource::setStyle (core::style::Style *style)
+{
+ if(this->style)
+ this->style->unref ();
+
+ this->style = style;
+ style->ref ();
+
+ for (Iterator <ViewAndWidget> it = viewsAndWidgets->iterator ();
+ it.hasNext(); ) {
+ ViewAndWidget *viewAndWidget = it.getNext ();
+ setWidgetStyle (viewAndWidget->widget, style);
+ }
+}
+
+void FltkResource::setWidgetStyle (::fltk::Widget *widget,
+ core::style::Style *style)
+{
+ /** \bug label or text? */
+
+ FltkFont *font = (FltkFont*)style->font;
+ widget->labelsize (font->size);
+ widget->labelfont (font->font);
+ widget->textsize (font->size);
+ widget->textfont (font->font);
+
+ FltkColor *bg = (FltkColor*)style->backgroundColor;
+ if (bg) {
+ if (style->color) {
+ /*
+ * todo: if/when CSS is implemented, test whether style->color
+ * will consistently provide readable widgets.
+ */
+ int32_t c = bg->colors[FltkColor::SHADING_NORMAL];
+ int r = (c >> 24) & 0xff, g = (c >> 16) & 0xff, b = (c >> 8) & 0xff;
+ bool light = (r + g >= 0x150) || (r + g + b >= 0x180);
+
+ widget->labelcolor(light? ::fltk::BLACK : ::fltk::WHITE);
+ widget->textcolor(light? ::fltk::BLACK : ::fltk::WHITE);
+ widget->selection_color(light? ::fltk::BLACK : ::fltk::WHITE);
+ }
+
+ widget->color(bg->colors[FltkColor::SHADING_NORMAL]);
+ widget->buttoncolor(bg->colors[FltkColor::SHADING_NORMAL]);
+ widget->highlight_color(bg->colors[FltkColor::SHADING_LIGHT]);
+ widget->selection_textcolor(bg->colors[FltkColor::SHADING_NORMAL]);
+ }
+}
+
+bool FltkResource::isEnabled ()
+{
+ /** \bug Not implemented. */
+ return true;
+}
+
+void FltkResource::setEnabled (bool enabled)
+{
+ /** \bug Not implemented. */
+}
+
+// ----------------------------------------------------------------------
+
+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);
+}
+
+// ----------------------------------------------------------------------
+
+FltkLabelButtonResource::FltkLabelButtonResource (FltkPlatform *platform,
+ const char *label):
+ FltkSpecificResource <dw::core::ui::LabelButtonResource> (platform)
+{
+ this->label = strdup (label);
+ init (platform);
+}
+
+FltkLabelButtonResource::~FltkLabelButtonResource ()
+{
+ delete label;
+}
+
+::fltk::Widget *FltkLabelButtonResource::createNewWidget (core::Allocation
+ *allocation)
+{
+ ::fltk::Button *button =
+ new ::fltk::Button (allocation->x, allocation->y, allocation->width,
+ allocation->ascent + allocation->descent,
+ label);
+ button->callback (widgetCallback, this);
+ button->when (::fltk::WHEN_RELEASE);
+ return button;
+}
+
+void FltkLabelButtonResource::sizeRequest (core::Requisition *requisition)
+{
+ if (style) {
+ FltkFont *font = (FltkFont*)style->font;
+ ::fltk::setfont(font->font,font->size);
+ requisition->width =
+ (int)::fltk::getwidth (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;
+ }
+}
+
+void FltkLabelButtonResource::widgetCallback (::fltk::Widget *widget,
+ void *data)
+{
+ if (widget->when () & ::fltk::WHEN_RELEASE)
+ ((FltkLabelButtonResource*)data)->emitActivate ();
+}
+
+const char *FltkLabelButtonResource::getLabel ()
+{
+ return label;
+}
+
+
+void FltkLabelButtonResource::setLabel (const char *label)
+{
+ delete this->label;
+ this->label = strdup (label);
+
+ for (Iterator <ViewAndWidget> it = viewsAndWidgets->iterator ();
+ it.hasNext(); ) {
+ ViewAndWidget *viewAndWidget = it.getNext ();
+ viewAndWidget->widget->label (this->label);
+ }
+
+ queueResize (true);
+}
+
+// ----------------------------------------------------------------------
+
+FltkComplexButtonResource::FltkComplexButtonResource (FltkPlatform *platform,
+ dw::core::Widget
+ *widget, bool relief):
+ FltkSpecificResource <dw::core::ui::ComplexButtonResource> (platform)
+{
+ viewsAndViews = new container::typed::List <ViewAndView> (true);
+ this->relief = relief;
+ FltkResource::init (platform);
+ ComplexButtonResource::init (widget);
+}
+
+FltkComplexButtonResource::~FltkComplexButtonResource ()
+{
+ delete viewsAndViews;
+}
+
+void FltkComplexButtonResource::widgetCallback (::fltk::Widget *widget,
+ void *data)
+{
+ FltkComplexButtonResource *res = (FltkComplexButtonResource*)data;
+
+ /* would be best not to send click pos. if the image could not be loaded */
+ if (::fltk::event() == ::fltk::RELEASE &&
+ ::fltk::event_button() == ::fltk::LeftButton) {
+ res->click_x = ::fltk::event_x();
+ res->click_y = ::fltk::event_y();
+ res->emitActivate ();
+ } else {
+ ((FltkViewBase*)res->lastFlatView)->handle(::fltk::event());
+ }
+}
+
+dw::core::Platform *FltkComplexButtonResource::createPlatform ()
+{
+ return new FltkPlatform ();
+}
+
+void FltkComplexButtonResource::attachView (FltkView *view)
+{
+ FltkResource::attachView (view);
+
+ if (view->usesFltkWidgets ()) {
+ ViewAndView *viewAndView = new ViewAndView();
+ viewAndView->topView = view;
+ viewAndView->flatView = lastFlatView;
+ viewsAndViews->append (viewAndView);
+ }
+}
+
+void FltkComplexButtonResource::detachView (FltkView *view)
+{
+ FltkResource::detachView (view);
+
+ for (Iterator <ViewAndView> it = viewsAndViews->iterator ();
+ it.hasNext(); ) {
+ ViewAndView *viewAndView = it.getNext ();
+ if (viewAndView->topView == view) {
+ viewsAndViews->removeRef (viewAndView);
+ return;
+ }
+ }
+
+ fprintf (stderr,
+ "FltkComplexButtonResourceResource::detachView: View not "
+ "found.\n");
+}
+
+void FltkComplexButtonResource::sizeAllocate (core::Allocation *allocation)
+{
+ FltkResource::sizeAllocate (allocation);
+
+ for (Iterator <ViewAndView> it = viewsAndViews->iterator ();
+ it.hasNext(); ) {
+ ViewAndView *viewAndView = it.getNext ();
+ ((FltkFlatView*)viewAndView->flatView)->resize (
+ reliefXThickness (),
+ reliefYThickness (),
+ allocation->width - 2 * reliefXThickness (),
+ allocation->ascent + allocation->descent - 2 * reliefYThickness ());
+
+ ((FltkFlatView*)viewAndView->flatView)->parent ()->init_sizes ();
+ }
+}
+
+void FltkComplexButtonResource::setLayout (dw::core::Layout *layout)
+{
+ for (Iterator <ViewAndView> it = viewsAndViews->iterator ();
+ it.hasNext(); ) {
+ ViewAndView *viewAndView = it.getNext ();
+ layout->attachView (viewAndView->flatView);
+ }
+}
+
+int FltkComplexButtonResource::reliefXThickness ()
+{
+ return relief ? RELIEF_X_THICKNESS : 0;
+}
+
+int FltkComplexButtonResource::reliefYThickness ()
+{
+ return relief ? RELIEF_Y_THICKNESS : 0;
+}
+
+
+::fltk::Widget *FltkComplexButtonResource::createNewWidget (core::Allocation
+ *allocation)
+{
+ ComplexButton *button =
+ new ComplexButton (allocation->x, allocation->y, allocation->width,
+ allocation->ascent + allocation->descent);
+ button->callback (widgetCallback, this);
+ button->when (::fltk::WHEN_RELEASE);
+ if (!relief)
+ button->box(::fltk::FLAT_BOX);
+
+ FltkFlatView *flatView =
+ new FltkFlatView (allocation->x + reliefXThickness (),
+ allocation->y + reliefYThickness (),
+ allocation->width - 2 * reliefXThickness (),
+ allocation->ascent + allocation->descent
+ - 2 * reliefYThickness ());
+ button->add (flatView);
+
+ lastFlatView = flatView;
+
+ if (layout)
+ layout->attachView (lastFlatView);
+ return button;
+}
+
+// ----------------------------------------------------------------------
+
+FltkEntryResource::FltkEntryResource (FltkPlatform *platform, int maxLength,
+ bool password):
+ FltkSpecificResource <dw::core::ui::EntryResource> (platform)
+{
+ this->maxLength = maxLength;
+ this->password = password;
+
+ initText = NULL;
+ editable = false;
+
+ init (platform);
+}
+
+FltkEntryResource::~FltkEntryResource ()
+{
+ if (initText)
+ delete initText;
+}
+
+::fltk::Widget *FltkEntryResource::createNewWidget (core::Allocation
+ *allocation)
+{
+ ::fltk::Input *input =
+ password ?
+ new ::fltk::SecretInput (allocation->x, allocation->y,
+ allocation->width,
+ allocation->ascent + allocation->descent) :
+ new ::fltk::Input (allocation->x, allocation->y, allocation->width,
+ allocation->ascent + allocation->descent);
+ input->callback (widgetCallback, this);
+ input->when (::fltk::WHEN_ENTER_KEY_ALWAYS);
+
+ if (viewsAndWidgets->isEmpty ()) {
+ // First widget created, attach the set text.
+ if (initText)
+ input->value (initText);
+ } else
+ input->value
+ (((::fltk::Input*)viewsAndWidgets->getFirst()->widget)->value ());
+
+ return input;
+}
+
+void FltkEntryResource::sizeRequest (core::Requisition *requisition)
+{
+ if (style) {
+ FltkFont *font = (FltkFont*)style->font;
+ ::fltk::setfont(font->font,font->size);
+ requisition->width =
+ (int)::fltk::getwidth ("M", 1)
+ * (maxLength == UNLIMITED_MAX_LENGTH ? 10 : maxLength)
+ + 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;
+ }
+}
+
+void FltkEntryResource::widgetCallback (::fltk::Widget *widget,
+ void *data)
+{
+ /* The (::fltk::event_key() == ::fltk::ReturnKey) test
+ * is necessary because WHEN_ENTER_KEY also includes
+ * other events we're not interested in. For instance pressing
+ * The Back or Forward, buttons, or the first click on a rendered
+ * page. BUG: this must be investigated and reported to FLTK2 team
+ */
+ printf ("when = %d\n", widget->when ());
+ if ((widget->when () & ::fltk::WHEN_ENTER_KEY_ALWAYS) &&
+ (::fltk::event_key() == ::fltk::ReturnKey))
+ ((FltkEntryResource*)data)->emitActivate ();
+}
+
+const char *FltkEntryResource::getText ()
+{
+ if (viewsAndWidgets->isEmpty ())
+ return initText;
+ else
+ return ((::fltk::Input*)viewsAndWidgets->getFirst()->widget)->value ();
+}
+
+void FltkEntryResource::setText (const char *text)
+{
+ if (initText)
+ delete initText;
+ initText = strdup (text);
+
+ for (Iterator <ViewAndWidget> it = viewsAndWidgets->iterator ();
+ it.hasNext(); ) {
+ ViewAndWidget *viewAndWidget = it.getNext ();
+ ((::fltk::Input*)viewAndWidget->widget)->value (initText);
+ }
+}
+
+bool FltkEntryResource::isEditable ()
+{
+ return editable;
+}
+
+void FltkEntryResource::setEditable (bool editable)
+{
+ this->editable = editable;
+}
+
+// ----------------------------------------------------------------------
+
+FltkMultiLineTextResource::FltkMultiLineTextResource (FltkPlatform *platform,
+ int cols, int rows):
+ FltkSpecificResource <dw::core::ui::MultiLineTextResource> (platform)
+{
+ buffer = new ::fltk::TextBuffer;
+ editable = false;
+
+ numCols = cols;
+ numRows = rows;
+
+ // Check values. Upper bound check is left to the caller.
+ if (numCols < 1) {
+ fprintf (stderr, "WARNING: numCols = %d is set to 1.\n", numCols);
+ numCols = 1;
+ }
+ if (numRows < 1) {
+ fprintf (stderr, "WARNING: numRows = %d is set to 1.\n", numRows);
+ numRows = 1;
+ }
+
+ init (platform);
+}
+
+FltkMultiLineTextResource::~FltkMultiLineTextResource ()
+{
+ /* Free memory avoiding a double-free of text buffers */
+ for (Iterator <ViewAndWidget> it = viewsAndWidgets->iterator ();
+ it.hasNext(); ) {
+ ViewAndWidget *viewAndWidget = it.getNext ();
+ ((::fltk::TextEditor *) viewAndWidget->widget)->buffer (0);
+ }
+ delete buffer;
+}
+
+::fltk::Widget *FltkMultiLineTextResource::createNewWidget (core::Allocation
+ *allocation)
+{
+ ::fltk::TextEditor *text =
+ new ::fltk::TextEditor (allocation->x, allocation->y,
+ allocation->width,
+ allocation->ascent + allocation->descent);
+ text->buffer (buffer);
+ return text;
+}
+
+void FltkMultiLineTextResource::sizeRequest (core::Requisition *requisition)
+{
+ if (style) {
+ FltkFont *font = (FltkFont*)style->font;
+ ::fltk::setfont(font->font,font->size);
+ requisition->width =
+ (int)::fltk::getwidth ("X", 1) * numCols +
+ 2 * RELIEF_X_THICKNESS;
+ requisition->ascent =
+ font->ascent + RELIEF_Y_THICKNESS;
+ requisition->descent =
+ font->descent +
+ (font->ascent + font->descent) * (numRows - 1) +
+ RELIEF_Y_THICKNESS;
+ } else {
+ requisition->width = 1;
+ requisition->ascent = 1;
+ requisition->descent = 0;
+ }
+}
+
+const char *FltkMultiLineTextResource::getText ()
+{
+ return buffer->text ();
+}
+
+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>
+::fltk::Widget *FltkToggleButtonResource<I>::createNewWidget (core::Allocation
+ *allocation)
+{
+ ::fltk::Button *button = createNewButton (allocation);
+
+ if (this->viewsAndWidgets->isEmpty ())
+ button->value (initActivated);
+ else
+ button->value (((::fltk::Button*)this->viewsAndWidgets
+ ->getFirst()->widget)->value ());
+
+ return button;
+}
+
+
+template <class I>
+void FltkToggleButtonResource<I>::sizeRequest (core::Requisition *requisition)
+{
+ /** \bug Random values. */
+ requisition->width = 20;
+ requisition->ascent = 18;
+ requisition->descent = 5;
+}
+
+
+template <class I>
+bool FltkToggleButtonResource<I>::FltkToggleButtonResource::isActivated ()
+{
+ if (this->viewsAndWidgets->isEmpty ())
+ return initActivated;
+ else
+ return
+ ((::fltk::Button*)this->viewsAndWidgets->getFirst()->widget)
+ ->value ();
+}
+
+
+template <class I>
+void FltkToggleButtonResource<I>::setActivated (bool activated)
+{
+ initActivated = activated;
+
+ for (Iterator <FltkResource::ViewAndWidget> it =
+ this->viewsAndWidgets->iterator ();
+ it.hasNext(); ) {
+ FltkResource::ViewAndWidget *viewAndWidget = it.getNext ();
+ ((::fltk::Button*)viewAndWidget->widget)->value (initActivated);
+ }
+}
+
+// ----------------------------------------------------------------------
+
+FltkCheckButtonResource::FltkCheckButtonResource (FltkPlatform *platform,
+ bool activated):
+ FltkToggleButtonResource<dw::core::ui::CheckButtonResource> (platform,
+ activated)
+{
+ init (platform);
+}
+
+
+FltkCheckButtonResource::~FltkCheckButtonResource ()
+{
+}
+
+
+::fltk::Button *FltkCheckButtonResource::createNewButton (core::Allocation
+ *allocation)
+{
+ return
+ new ::fltk::CheckButton (allocation->x, allocation->y, allocation->width,
+ allocation->ascent + allocation->descent);
+}
+
+// ----------------------------------------------------------------------
+
+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 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 (::fltk::Widget *widget,
+ void *data)
+{
+ if (widget->when () & ::fltk::WHEN_CHANGED)
+ ((FltkRadioButtonResource*)data)->buttonClicked ();
+}
+
+void FltkRadioButtonResource::buttonClicked ()
+{
+ for (Iterator <FltkRadioButtonResource> it = group->iterator ();
+ it.hasNext (); ) {
+ FltkRadioButtonResource *other = it.getNext ();
+ other->setActivated (other == this);
+ }
+}
+
+::fltk::Button *FltkRadioButtonResource::createNewButton (core::Allocation
+ *allocation)
+{
+ /*
+ * Groups of fltk::RadioButton must be added to one fltk::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).
+ */
+
+ ::fltk::Button *button =
+ new ::fltk::RadioButton (allocation->x, allocation->y,
+ allocation->width,
+ allocation->ascent + allocation->descent);
+ button->when (::fltk::WHEN_CHANGED);
+ button->callback (widgetCallback, this);
+ button->type (::fltk::Button::TOGGLE);
+
+ return button;
+}
+
+// ----------------------------------------------------------------------
+
+template <class I> FltkSelectionResource<I>::Item::Item (Type type,
+ const char *name,
+ bool enabled,
+ bool selected)
+{
+ this->type = type;
+ this->name = name ? strdup (name) : NULL;
+ this->enabled = enabled;
+ initSelected = selected;
+}
+
+template <class I> FltkSelectionResource<I>::Item::~Item ()
+{
+ if (name)
+ delete name;
+}
+
+template <class I>
+::fltk::Item *FltkSelectionResource<I>::Item::createNewWidget (int index)
+{
+ ::fltk::Item *item = new ::fltk::Item (name);
+ item->user_data ((void *) index);
+ return item;
+}
+
+template <class I>
+::fltk::ItemGroup *
+FltkSelectionResource<I>::Item::createNewGroupWidget ()
+{
+ ::fltk::ItemGroup *itemGroup = new ::fltk::ItemGroup (name);
+ itemGroup->user_data ((void *) -1L);
+ return itemGroup;
+}
+
+
+template <class I>
+FltkSelectionResource<I>::WidgetStack::WidgetStack (::fltk::Menu *widget)
+{
+ this->widget = widget;
+ this->stack = new Stack <TypedPointer < ::fltk::Menu> > (true);
+}
+
+template <class I> FltkSelectionResource<I>::WidgetStack::~WidgetStack ()
+{
+ delete stack;
+}
+
+
+template <class I>
+FltkSelectionResource<I>::FltkSelectionResource (FltkPlatform *platform):
+ FltkSpecificResource<I> (platform)
+{
+ widgetStacks = new List <WidgetStack> (true);
+ allItems = new List <Item> (true);
+ items = new Vector <Item> (16, false);
+}
+
+template <class I> FltkSelectionResource<I>::~FltkSelectionResource ()
+{
+ delete widgetStacks;
+ delete allItems;
+ delete items;
+}
+
+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);
+}
+
+template <class I> ::fltk::Widget *
+FltkSelectionResource<I>::createNewWidget (core::Allocation *allocation)
+{
+ /** \todo Attributes (enabled, selected). */
+
+ ::fltk::Menu *menu = createNewMenu (allocation);
+ WidgetStack *widgetStack = new WidgetStack (menu);
+ widgetStack->stack->push (new TypedPointer < ::fltk::Menu> (menu));
+ widgetStacks->append (widgetStack);
+
+
+ ::fltk::Menu *itemGroup;
+ ::fltk::Item *itemWidget;
+
+ ::fltk::Group *currGroup = widgetStack->stack->getTop()->getTypedValue();
+
+ int index = 0;
+ for (Iterator <Item> it = allItems->iterator (); it.hasNext (); ) {
+ Item *item = it.getNext ();
+ switch (item->type) {
+ case Item::ITEM:
+ itemWidget = item->createNewWidget (index++);
+ currGroup->add (itemWidget);
+ break;
+
+ case Item::START:
+ itemGroup = item->createNewGroupWidget ();
+ currGroup->add (itemGroup);
+ widgetStack->stack->push (new TypedPointer < ::fltk::Menu> (menu));
+ currGroup = itemGroup;
+ break;
+
+ case Item::END:
+ widgetStack->stack->pop ();
+ currGroup = widgetStack->stack->getTop()->getTypedValue();
+ break;
+ }
+ }
+
+ return menu;
+}
+
+template <class I>
+typename FltkSelectionResource<I>::Item *
+FltkSelectionResource<I>::createNewItem (typename Item::Type type,
+ const char *name,
+ bool enabled,
+ bool selected) {
+ return new Item(type,name,enabled,selected);
+}
+
+template <class I> void FltkSelectionResource<I>::addItem (const char *str,
+ bool enabled,
+ bool selected)
+{
+ int index = items->size ();
+ Item *item = createNewItem (Item::ITEM, str, enabled, selected);
+ items->put (item);
+ allItems->append (item);
+
+ for (Iterator <WidgetStack> it = widgetStacks->iterator ();
+ it.hasNext(); ) {
+ WidgetStack *widgetStack = it.getNext ();
+ ::fltk::Item *itemWidget = item->createNewWidget (index);
+ widgetStack->stack->getTop()->getTypedValue()->add (itemWidget);
+
+ if (!enabled)
+ itemWidget->deactivate ();
+
+ if (selected) {
+ itemWidget->set_selected();
+ if (setSelectedItems ()) {
+ // Handle multiple item selection.
+ int pos[widgetStack->stack->size ()];
+ int i;
+ Iterator <TypedPointer < ::fltk::Menu> > it;
+ for (it = widgetStack->stack->iterator (),
+ i = widgetStack->stack->size () - 1;
+ it.hasNext ();
+ i--) {
+ TypedPointer < ::fltk::Menu> * p = it.getNext ();
+ pos[i] = p->getTypedValue()->children () - 1;
+ }
+ widgetStack->widget->set_item (pos, widgetStack->stack->size ());
+ }
+ }
+ }
+}
+
+template <class I> void FltkSelectionResource<I>::pushGroup (const char *name,
+ bool enabled)
+{
+ Item *item = createNewItem (Item::START, name, enabled);
+ allItems->append (item);
+
+ for (Iterator <WidgetStack> it = widgetStacks->iterator ();
+ it.hasNext(); ) {
+ WidgetStack *widgetStack = it.getNext ();
+ ::fltk::ItemGroup *group = item->createNewGroupWidget ();
+ widgetStack->stack->getTop()->getTypedValue()->add (group);
+ widgetStack->stack->push (new TypedPointer < ::fltk::Menu> (group));
+ if(!enabled)
+ group->deactivate ();
+ }
+}
+
+template <class I> void FltkSelectionResource<I>::popGroup ()
+{
+ Item *item = createNewItem (Item::END);
+ allItems->append (item);
+
+ for (Iterator <WidgetStack> it = widgetStacks->iterator ();
+ it.hasNext(); ) {
+ WidgetStack *widgetStack = it.getNext ();
+ widgetStack->stack->pop ();
+ }
+}
+
+template <class I> int FltkSelectionResource<I>::getNumberOfItems ()
+{
+ return items->size ();
+}
+
+template <class I> const char *FltkSelectionResource<I>::getItem (int index)
+{
+ return items->get(index)->name;
+}
+
+template <class I> int FltkSelectionResource<I>::getMaxStringWidth ()
+{
+ int width = 0, numberOfItems = getNumberOfItems ();
+ for (int i = 0; i < numberOfItems; i++) {
+ int len = (int)::fltk::getwidth (getItem(i));
+ if (len > width) width = len;
+ }
+ return width;
+}
+
+// ----------------------------------------------------------------------
+
+FltkOptionMenuResource::FltkOptionMenuResource (FltkPlatform *platform):
+ FltkSelectionResource <dw::core::ui::OptionMenuResource> (platform),
+ selection(-1)
+{
+ init (platform);
+}
+
+FltkOptionMenuResource::~FltkOptionMenuResource ()
+{
+}
+
+
+::fltk::Menu *FltkOptionMenuResource::createNewMenu (core::Allocation
+ *allocation)
+{
+ ::fltk::Menu *menu =
+ new ::fltk::Choice (allocation->x, allocation->y,
+ allocation->width,
+ allocation->ascent + allocation->descent);
+ menu->callback(widgetCallback,this);
+ return menu;
+}
+
+void FltkOptionMenuResource::widgetCallback (::fltk::Widget *widget,
+ void *data)
+{
+ ((FltkOptionMenuResource *) data)->selection =
+ (long) (((::fltk::Menu *) widget)->item()->user_data());
+}
+
+void FltkOptionMenuResource::sizeRequest (core::Requisition *requisition)
+{
+ if (style) {
+ FltkFont *font = (FltkFont*)style->font;
+ ::fltk::setfont(font->font,font->size);
+ int maxStringWidth = getMaxStringWidth ();
+ requisition->ascent = font->ascent + RELIEF_Y_THICKNESS;
+ requisition->descent = font->descent + RELIEF_Y_THICKNESS;
+ requisition->width = maxStringWidth
+ + (requisition->ascent + requisition->descent) * 4 / 5
+ + 2 * RELIEF_X_THICKNESS;
+ } else {
+ requisition->width = 1;
+ requisition->ascent = 1;
+ requisition->descent = 0;
+ }
+}
+
+void FltkOptionMenuResource::addItem (const char *str,
+ bool enabled, bool selected)
+{
+ FltkSelectionResource<dw::core::ui::OptionMenuResource>::addItem
+ (str,enabled,selected);
+ if (selected)
+ selection = (items->size ()) - 1;
+
+ queueResize (true);
+}
+
+bool FltkOptionMenuResource::isSelected (int index)
+{
+ return index == selection;
+}
+
+// ----------------------------------------------------------------------
+
+FltkListResource::FltkListResource (FltkPlatform *platform,
+ core::ui::ListResource::SelectionMode
+ selectionMode):
+ FltkSelectionResource <dw::core::ui::ListResource> (platform),
+ itemsSelected(8)
+{
+ init (platform);
+}
+
+FltkListResource::~FltkListResource ()
+{
+}
+
+
+::fltk::Menu *FltkListResource::createNewMenu (core::Allocation *allocation)
+{
+ ::fltk::Menu *menu =
+ new ::fltk::MultiBrowser (allocation->x, allocation->y,
+ allocation->width,
+ allocation->ascent + allocation->descent);
+ menu->callback(widgetCallback,this);
+ menu->when(::fltk::WHEN_CHANGED);
+ return menu;
+}
+
+void FltkListResource::widgetCallback (::fltk::Widget *widget, void *data)
+{
+ ::fltk::Widget *fltkItem = ((::fltk::Menu *) widget)->item ();
+ int index = (long) (fltkItem->user_data ());
+ if (index > -1) {
+ bool selected = fltkItem->selected ();
+ ((FltkListResource *) data)->itemsSelected.set (index, selected);
+ }
+}
+
+void FltkListResource::addItem (const char *str, bool enabled, bool selected)
+{
+ FltkSelectionResource<dw::core::ui::ListResource>::addItem
+ (str,enabled,selected);
+ int index = itemsSelected.size ();
+ itemsSelected.increase ();
+ itemsSelected.set (index,selected);
+ queueResize (true);
+}
+
+void FltkListResource::sizeRequest (core::Requisition *requisition)
+{
+ if (style) {
+ FltkFont *font = (FltkFont*)style->font;
+ ::fltk::setfont(font->font,font->size);
+ int maxStringWidth = getMaxStringWidth ();
+ requisition->ascent = font->ascent + RELIEF_Y_THICKNESS;
+ requisition->descent =
+ (getNumberOfItems () - 1) * (font->ascent) + font->descent;
+ requisition->width = maxStringWidth
+ + (font->ascent + font->descent) * 4 / 5
+ + 2 * RELIEF_X_THICKNESS;
+ } else {
+ requisition->width = 1;
+ requisition->ascent = 1;
+ requisition->descent = 0;
+ }
+}
+
+bool FltkListResource::isSelected (int index)
+{
+ return itemsSelected.get (index);
+}
+
+} // namespace ui
+} // namespace fltk
+} // namespace dw
+
diff --git a/dw/fltkui.hh b/dw/fltkui.hh
new file mode 100644
index 00000000..4de99d27
--- /dev/null
+++ b/dw/fltkui.hh
@@ -0,0 +1,554 @@
+#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 <fltk/Button.h>
+#include <fltk/Menu.h>
+#include <fltk/TextBuffer.h>
+#include <fltk/Item.h>
+#include <fltk/ItemGroup.h>
+
+namespace dw {
+namespace fltk {
+
+using namespace lout;
+
+/**
+ * \brief FLTK implementation of dw::core::ui.
+ *
+ * The design should be like this:
+ *
+ * \dot
+ * digraph G {
+ * node [shape=record, fontname=Helvetica, fontsize=10];
+ * edge [arrowhead="none", arrowtail="empty", 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 depencency 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 explicitely 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", 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", 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",
+ * style="dashed",
+ * color="#808000"];
+ * FltkSpecificResource -> FltkSpecificResource_entry [arrowhead="open",
+ * arrowtail="none",
+ * 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 object::Object
+{
+protected:
+ class ViewAndWidget: public object::Object
+ {
+ public:
+ FltkView *view;
+ ::fltk::Widget *widget;
+ };
+
+ container::typed::List <ViewAndWidget> *viewsAndWidgets;
+ core::Allocation allocation;
+ FltkPlatform *platform;
+
+ core::style::Style *style;
+
+ FltkResource (FltkPlatform *platform);
+ void init (FltkPlatform *platform);
+ virtual ::fltk::Widget *createNewWidget (core::Allocation *allocation) = 0;
+
+ void setWidgetStyle (::fltk::Widget *widget, core::style::Style *style);
+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:
+ inline FltkSpecificResource (FltkPlatform *platform) :
+ FltkResource (platform) { }
+
+ 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 (::fltk::Widget *widget, void *data);
+
+protected:
+ ::fltk::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);
+};
+
+
+class FltkComplexButtonResource:
+ public FltkSpecificResource <dw::core::ui::ComplexButtonResource>
+{
+private:
+ bool relief;
+
+ static void widgetCallback (::fltk::Widget *widget, void *data);
+
+protected:
+ class ViewAndView: public object::Object
+ {
+ public:
+ FltkView *topView, *flatView;
+ };
+
+ FltkView *lastFlatView;
+
+ container::typed::List <ViewAndView> *viewsAndViews;
+
+ void attachView (FltkView *view);
+ void detachView (FltkView *view);
+
+ void sizeAllocate (core::Allocation *allocation);
+
+ dw::core::Platform *createPlatform ();
+ void setLayout (dw::core::Layout *layout);
+
+ int reliefXThickness ();
+ int reliefYThickness ();
+
+ ::fltk::Widget *createNewWidget (core::Allocation *allocation);
+
+public:
+ FltkComplexButtonResource (FltkPlatform *platform, dw::core::Widget *widget,
+ bool relief);
+ ~FltkComplexButtonResource ();
+};
+
+
+/**
+ * \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 maxLength;
+ bool password;
+ const char *initText;
+ bool editable;
+
+ static void widgetCallback (::fltk::Widget *widget, void *data);
+
+protected:
+ ::fltk::Widget *createNewWidget (core::Allocation *allocation);
+
+public:
+ FltkEntryResource (FltkPlatform *platform, int maxLength, bool password);
+ ~FltkEntryResource ();
+
+ void sizeRequest (core::Requisition *requisition);
+
+ const char *getText ();
+ void setText (const char *text);
+ bool isEditable ();
+ void setEditable (bool editable);
+};
+
+
+class FltkMultiLineTextResource:
+ public FltkSpecificResource <dw::core::ui::MultiLineTextResource>
+{
+private:
+ ::fltk::TextBuffer *buffer;
+ bool editable;
+ int numCols, numRows;
+
+protected:
+ ::fltk::Widget *createNewWidget (core::Allocation *allocation);
+
+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 ::fltk::Button *createNewButton (core::Allocation *allocation) = 0;
+ ::fltk::Widget *createNewWidget (core::Allocation *allocation);
+
+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:
+ ::fltk::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:
+ container::typed::Iterator <FltkRadioButtonResource> it;
+
+ public:
+ inline FltkGroupIterator (container::typed::List
+ <FltkRadioButtonResource>
+ *list)
+ { it = list->iterator (); }
+
+ bool hasNext ();
+ dw::core::ui::RadioButtonResource *getNext ();
+ void unref ();
+ };
+
+ container::typed::List <FltkRadioButtonResource> *list;
+
+ protected:
+ ~Group ();
+
+ public:
+ Group (FltkRadioButtonResource *radioButtonResource);
+
+ inline 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 (::fltk::Widget *widget, void *data);
+ void buttonClicked ();
+
+protected:
+ ::fltk::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:
+ class Item: public object::Object
+ {
+ public:
+ enum Type { ITEM, START, END } type;
+
+ const char *name;
+ bool enabled, initSelected;
+
+ Item (Type type, const char *name = NULL, bool enabled = true,
+ bool selected = false);
+ ~Item ();
+
+ ::fltk::Item *createNewWidget (int index);
+ ::fltk::ItemGroup *createNewGroupWidget ();
+ };
+
+ class WidgetStack: public object::Object
+ {
+ public:
+ ::fltk::Menu *widget;
+ container::typed::Stack <object::TypedPointer < ::fltk::Menu> > *stack;
+
+ WidgetStack (::fltk::Menu *widget);
+ ~WidgetStack ();
+ };
+
+ container::typed::List <WidgetStack> *widgetStacks;
+ container::typed::List <Item> *allItems;
+ container::typed::Vector <Item> *items;
+
+ Item *createNewItem (typename Item::Type type,
+ const char *name = NULL,
+ bool enabled = true,
+ bool selected = false);
+
+ ::fltk::Widget *createNewWidget (core::Allocation *allocation);
+ virtual ::fltk::Menu *createNewMenu (core::Allocation *allocation) = 0;
+ virtual bool setSelectedItems() { return false; }
+
+ int getMaxStringWidth ();
+
+public:
+ FltkSelectionResource (FltkPlatform *platform);
+ ~FltkSelectionResource ();
+
+ dw::core::Iterator *iterator (dw::core::Content::Type mask, bool atEnd);
+
+ void addItem (const char *str, bool enabled, bool selected);
+
+ void pushGroup (const char *name, bool enabled);
+ void popGroup ();
+
+ int getNumberOfItems ();
+ const char *getItem (int index);
+};
+
+
+class FltkOptionMenuResource:
+ public FltkSelectionResource <dw::core::ui::OptionMenuResource>
+{
+protected:
+ ::fltk::Menu *createNewMenu (core::Allocation *allocation);
+ virtual bool setSelectedItems() { return true; }
+
+private:
+ static void widgetCallback (::fltk::Widget *widget, void *data);
+ int selection;
+
+public:
+ FltkOptionMenuResource (FltkPlatform *platform);
+ ~FltkOptionMenuResource ();
+
+ void addItem (const char *str, bool enabled, bool selected);
+
+ void sizeRequest (core::Requisition *requisition);
+ bool isSelected (int index);
+};
+
+class FltkListResource:
+ public FltkSelectionResource <dw::core::ui::ListResource>
+{
+protected:
+ ::fltk::Menu *createNewMenu (core::Allocation *allocation);
+
+private:
+ static void widgetCallback (::fltk::Widget *widget, void *data);
+ misc::SimpleVector <bool> itemsSelected;
+
+public:
+ FltkListResource (FltkPlatform *platform,
+ core::ui::ListResource::SelectionMode selectionMode);
+ ~FltkListResource ();
+
+ void addItem (const char *str, bool enabled, bool selected);
+
+ 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 00000000..fbbd15bd
--- /dev/null
+++ b/dw/fltkviewbase.cc
@@ -0,0 +1,524 @@
+/*
+ * Dillo Widget
+ *
+ * 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.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+
+
+#include "fltkviewport.hh"
+
+#include <fltk/draw.h>
+#include <fltk/damage.h>
+#include <fltk/layout.h>
+#include <fltk/events.h>
+#include <fltk/Cursor.h>
+#include <fltk/run.h>
+
+#include <stdio.h>
+
+using namespace fltk;
+using namespace lout::object;
+using namespace lout::container::typed;
+
+namespace dw {
+namespace fltk {
+
+::fltk::Image *FltkViewBase::backBuffer;
+bool FltkViewBase::backBufferInUse;
+
+FltkViewBase::FltkViewBase (int x, int y, int w, int h, const char *label):
+ Group (x, y, w, h, label)
+{
+ canvasWidth = 1;
+ canvasHeight = 1;
+ bgColor = WHITE;
+ lastDraw = time(0);
+ drawDelay = 2; /* in seconds */
+ mouse_x = mouse_y = 0;
+#ifndef NO_DOUBLEBUFFER
+ if (!backBuffer) {
+ backBuffer = new Image ();
+ }
+#endif
+}
+
+FltkViewBase::~FltkViewBase ()
+{
+ cancelQueueDraw ();
+}
+
+void FltkViewBase::draw ()
+{
+ int d = damage ();
+
+ if ((d & DAMAGE_VALUE) && !(d & DAMAGE_EXPOSE)) {
+ container::typed::Iterator <core::Rectangle> it;
+
+ for (it = drawRegion.rectangles (); it.hasNext (); ) {
+ drawRectangle (it.getNext (), true);
+ }
+
+ drawRegion.clear ();
+ d &= ~DAMAGE_VALUE;
+ }
+
+ if (d & DAMAGE_CHILD) {
+ drawChildWidgets ();
+ d &= ~DAMAGE_CHILD;
+ }
+
+ if (d) {
+ dw::core::Rectangle rect (
+ translateViewXToCanvasX (0),
+ translateViewYToCanvasY (0),
+ w (),
+ h ());
+
+ drawRectangle (&rect, false);
+
+ if (! (d & DAMAGE_SCROLL)) {
+ drawRegion.clear ();
+ }
+ }
+}
+
+void FltkViewBase::drawRectangle (const core::Rectangle *rect,
+ bool doubleBuffer)
+{
+ int offsetX = 0, offsetY = 0;
+
+ /* fltk-clipping does not use widget coordinates */
+ transform (offsetX, offsetY);
+
+ ::fltk::Rectangle viewRect (
+ translateCanvasXToViewX (rect->x) + offsetX,
+ translateCanvasYToViewY (rect->y) + offsetY,
+ rect->width, rect->height);
+
+ ::fltk::intersect_with_clip (viewRect);
+
+ viewRect.x (viewRect.x () - offsetX);
+ viewRect.y (viewRect.y () - offsetY);
+
+ if (! viewRect.empty ()) {
+ dw::core::Rectangle r (
+ translateViewXToCanvasX (viewRect.x ()),
+ translateViewYToCanvasY (viewRect.y ()),
+ viewRect.w (),
+ viewRect.h ());
+
+#ifdef NO_DOUBLEBUFFER
+ push_clip (viewRect);
+#endif
+
+ if (doubleBuffer && backBuffer && !backBufferInUse) {
+ backBufferInUse = true;
+ {
+ GSave gsave;
+
+ backBuffer->setsize (viewRect.w (), viewRect.h ());
+ backBuffer->make_current ();
+ translate (-viewRect.x (), -viewRect.y ());
+
+ setcolor (bgColor);
+ fillrect (viewRect);
+ theLayout->expose (this, &r);
+ }
+
+ backBuffer->draw (Rectangle (0, 0, viewRect.w (), viewRect.h ()),
+ viewRect);
+
+ backBufferInUse = false;
+ } else {
+ setcolor (bgColor);
+ fillrect (viewRect);
+ theLayout->expose (this, &r);
+ }
+
+#ifdef NO_DOUBLEBUFFER
+ pop_clip ();
+#endif
+ }
+}
+
+void FltkViewBase::drawChildWidgets () {
+ for (int i = children () - 1; i >= 0; i--) {
+ Widget& w = *child(i);
+ if (w.damage() & DAMAGE_CHILD_LABEL) {
+ draw_outside_label(w);
+ w.set_damage(w.damage() & ~DAMAGE_CHILD_LABEL);
+ }
+ update_child(w);
+ }
+}
+
+core::ButtonState getDwButtonState ()
+{
+ int s1 = event_state ();
+ int s2 = (core::ButtonState)0;
+
+ if(s1 & SHIFT) s2 |= core::SHIFT_MASK;
+ if(s1 & CTRL) s2 |= core::CONTROL_MASK;
+ if(s1 & ALT) s2 |= core::META_MASK;
+ if(s1 & BUTTON1) s2 |= core::BUTTON1_MASK;
+ if(s1 & BUTTON2) s2 |= core::BUTTON2_MASK;
+ if(s1 & BUTTON3) s2 |= core::BUTTON3_MASK;
+
+ return (core::ButtonState)s2;
+}
+
+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 PUSH:
+ processed =
+ theLayout->buttonPress (this, event_clicks () + 1,
+ translateViewXToCanvasX (event_x ()),
+ translateViewYToCanvasY (event_y ()),
+ getDwButtonState (), event_button ());
+ //printf ("PUSH => %s\n", processed ? "true" : "false");
+ return processed ? true : Group::handle (event);
+
+ case RELEASE:
+ processed =
+ theLayout->buttonRelease (this, event_clicks () + 1,
+ translateViewXToCanvasX (event_x ()),
+ translateViewYToCanvasY (event_y ()),
+ getDwButtonState (), event_button ());
+ //printf ("RELEASE => %s\n", processed ? "true" : "false");
+ return processed ? true : Group::handle (event);
+
+ case MOVE:
+ mouse_x = event_x();
+ mouse_y = event_y();
+ processed =
+ theLayout->motionNotify (this,
+ translateViewXToCanvasX (mouse_x),
+ translateViewYToCanvasY (mouse_y),
+ getDwButtonState ());
+ //printf ("MOVE => %s\n", processed ? "true" : "false");
+ return processed ? true : Group::handle (event);
+
+ case DRAG:
+ processed =
+ theLayout->motionNotify (this,
+ translateViewXToCanvasX (event_x ()),
+ translateViewYToCanvasY (event_y ()),
+ getDwButtonState ());
+ //printf ("DRAG => %s\n", processed ? "true" : "false");
+ return processed ? true : Group::handle (event);
+
+ case ENTER:
+ theLayout->enterNotify (this, translateViewXToCanvasX (event_x ()),
+ translateViewYToCanvasY (event_y ()),
+ getDwButtonState ());
+ return Group::handle (event);
+
+ case LEAVE:
+ theLayout->leaveNotify (this, getDwButtonState ());
+ return Group::handle (event);
+
+ default:
+ return Group::handle (event);
+ }
+}
+
+// ----------------------------------------------------------------------
+
+void FltkViewBase::setLayout (core::Layout *layout)
+{
+ theLayout = layout;
+}
+
+void FltkViewBase::setCanvasSize (int width, int ascent, int descent)
+{
+ canvasWidth = width;
+ canvasHeight = ascent + descent;
+}
+
+void FltkViewBase::setCursor (core::style::Cursor cursor)
+{
+ static Cursor *mapDwToFltk[] = {
+ CURSOR_CROSS,
+ CURSOR_ARROW,
+ CURSOR_HAND,
+ CURSOR_MOVE,
+ CURSOR_WE,
+ CURSOR_NESW,
+ CURSOR_NWSE,
+ CURSOR_NS,
+ CURSOR_NWSE,
+ CURSOR_NESW,
+ CURSOR_NS,
+ CURSOR_WE,
+ CURSOR_INSERT,
+ CURSOR_WAIT,
+ CURSOR_HELP
+ };
+
+ /*
+ static char *cursorName[] = {
+ "CURSOR_CROSS",
+ "CURSOR_ARROW",
+ "CURSOR_HAND",
+ "CURSOR_MOVE",
+ "CURSOR_WE",
+ "CURSOR_NESW",
+ "CURSOR_NWSE",
+ "CURSOR_NS",
+ "CURSOR_NWSE",
+ "CURSOR_NESW",
+ "CURSOR_NS",
+ "CURSOR_WE",
+ "CURSOR_INSERT",
+ "CURSOR_WAIT",
+ "CURSOR_HELP"
+ };
+
+ printf ("Cursor changes to '%s'.\n", cursorName[cursor]);
+ */
+
+ /** \bug Does not work */
+ this->cursor (mapDwToFltk[cursor]);
+}
+
+void FltkViewBase::setBgColor (core::style::Color *color)
+{
+ bgColor = color ?
+ ((FltkColor*)color)->colors[dw::core::style::Color::SHADING_NORMAL] :
+ WHITE;
+}
+
+void FltkViewBase::startDrawing (core::Rectangle *area)
+{
+}
+
+void FltkViewBase::finishDrawing (core::Rectangle *area)
+{
+}
+
+void FltkViewBase::queueDraw (core::Rectangle *area)
+{
+ drawRegion.addRectangle (area);
+ /** DAMAGE_VALUE is just an arbitrary value other than DAMAGE_EXPOSE here */
+ redraw (DAMAGE_VALUE);
+}
+
+static void drawTotalTimeout (void *data)
+{
+ FltkViewBase *view = (FltkViewBase*) data;
+ if (time(0) >= view->lastDraw + view->drawDelay) {
+ view->drawTotal ();
+ } else {
+ ::fltk::add_timeout (0.2f, drawTotalTimeout, data);
+ }
+}
+
+void FltkViewBase::drawTotal ()
+{
+ //static int calls = 0;
+ //printf(" FltkViewBase::drawTotal calls = %d\n", ++calls);
+ redraw (DAMAGE_EXPOSE);
+ lastDraw = time (0);
+ cancelQueueDraw ();
+}
+
+void FltkViewBase::queueDrawTotal ()
+{
+ drawTotal ();
+}
+
+void FltkViewBase::cancelQueueDraw ()
+{
+ ::fltk::remove_timeout (drawTotalTimeout, this);
+}
+
+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)
+{
+ setcolor(((FltkColor*)color)->colors[shading]);
+ drawline (translateCanvasXToViewX (x1), translateCanvasYToViewY (y1),
+ translateCanvasXToViewX (x2), translateCanvasYToViewY (y2));
+}
+
+void FltkViewBase::drawRectangle (core::style::Color *color,
+ core::style::Color::Shading shading,
+ bool filled,
+ int x, int y, int width, int height)
+{
+ setcolor(((FltkColor*)color)->colors[shading]);
+ int x1 = translateCanvasXToViewX (x);
+ int y1 = translateCanvasYToViewY (y);
+ int x2 = translateCanvasXToViewX (x + width);
+ int y2 = translateCanvasYToViewY (y + height);
+ ::fltk::Rectangle rect (x1, y1, x2 - x1, y2 - y1);
+ if (filled)
+ fillrect (rect);
+ else
+ strokerect (rect);
+}
+
+void FltkViewBase::drawArc (core::style::Color *color,
+ core::style::Color::Shading shading, bool filled,
+ int x, int y, int width, int height,
+ int angle1, int angle2)
+{
+ setcolor(((FltkColor*)color)->colors[shading]);
+ int x1 = translateCanvasXToViewX (x);
+ int y1 = translateCanvasYToViewY (y);
+ ::fltk::Rectangle rect (x1, y1, width, height);
+ addchord(rect, angle1, angle2);
+ closepath();
+ if (filled)
+ fillpath();
+ else
+ strokepath();
+}
+
+void FltkViewBase::drawPolygon (core::style::Color *color,
+ core::style::Color::Shading shading,
+ bool filled, int points[][2], int npoints)
+{
+ if (npoints > 0) {
+ for (int i = 0; i < npoints; i++) {
+ points[i][0] = translateCanvasXToViewX(points[i][0]);
+ points[i][1] = translateCanvasYToViewY(points[i][1]);
+ }
+ setcolor(((FltkColor*)color)->colors[shading]);
+ addvertices(npoints, points);
+ closepath();
+ if (filled)
+ fillpath();
+ else
+ strokepath();
+ }
+}
+
+core::View *FltkViewBase::getClippingView (int x, int y, int width, int height)
+{
+ push_clip (translateCanvasXToViewX (x), translateCanvasYToViewY (y),
+ width, height);
+ return this;
+}
+
+void FltkViewBase::mergeClippingView (core::View *clippingView)
+{
+ 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::layout () {
+ /**
+ * pass layout to child widgets. This is needed for complex fltk
+ * widgets as TextEditor.
+ * We can't use Group::layout() as that would rearrange the widgets.
+ */
+ for (int i = children () - 1; i >= 0; i--) {
+ ::fltk::Widget *widget = child (i);
+
+ if (widget->layout_damage ()) {
+ widget->layout ();
+ }
+ }
+}
+
+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)
+{
+ FltkFont *ff = (FltkFont*)font;
+ setfont(ff->font, ff->size);
+ setcolor(((FltkColor*)color)->colors[shading]);
+ drawtext(text, len,
+ translateCanvasXToViewX (x), translateCanvasYToViewY (y));
+}
+
+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 (::fltk::Widget *widget,
+ core::Allocation *allocation)
+{
+ allocateFltkWidget (widget, allocation);
+ add (widget);
+}
+
+void FltkWidgetView::removeFltkWidget (::fltk::Widget *widget)
+{
+ remove (widget);
+}
+
+void FltkWidgetView::allocateFltkWidget (::fltk::Widget *widget,
+ core::Allocation *allocation)
+{
+ widget->x (translateCanvasXToViewX (allocation->x));
+ widget->y (translateCanvasYToViewY (allocation->y));
+ widget->w (allocation->width);
+ widget->h (allocation->ascent + allocation->descent);
+
+ /* widgets created tiny and later resized need this flag to display */
+ uchar damage = widget->layout_damage ();
+ damage |= LAYOUT_XYWH;
+ widget->layout_damage (damage);
+}
+
+void FltkWidgetView::drawFltkWidget (::fltk::Widget *widget,
+ core::Rectangle *area)
+{
+ draw_child (*widget);
+}
+
+} // namespace fltk
+} // namespace dw
diff --git a/dw/fltkviewbase.hh b/dw/fltkviewbase.hh
new file mode 100644
index 00000000..09bcce39
--- /dev/null
+++ b/dw/fltkviewbase.hh
@@ -0,0 +1,108 @@
+#ifndef __DW_FLTKVIEWBASE_HH__
+#define __DW_FLTKVIEWBASE_HH__
+
+#include <time.h> // for time_t
+#include <sys/time.h> // for time_t in FreeBSD
+
+#include <fltk/Group.h>
+#include <fltk/Image.h>
+#include <fltk/Scrollbar.h>
+
+#include "fltkcore.hh"
+
+namespace dw {
+namespace fltk {
+
+class FltkViewBase: public FltkView, public ::fltk::Group
+{
+private:
+ int bgColor;
+ core::Region drawRegion;
+ static ::fltk::Image *backBuffer;
+ static bool backBufferInUse;
+
+ void drawRectangle (const core::Rectangle *rect, bool doubleBuffer);
+ void drawChildWidgets ();
+
+public:
+ time_t lastDraw;
+ time_t drawDelay;
+
+protected:
+ core::Layout *theLayout;
+ int canvasWidth, canvasHeight;
+ int mouse_x, mouse_y;
+
+ 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 drawTotal ();
+ 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 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 x, int y, int width, int height,
+ int angle1, int angle2);
+ void drawPolygon (core::style::Color *color,
+ core::style::Color::Shading shading,
+ bool filled, int points[][2], int npoints);
+
+ core::View *getClippingView (int x, int y, int width, int height);
+ void mergeClippingView (core::View *clippingView);
+};
+
+
+class FltkWidgetView: public FltkViewBase
+{
+public:
+ FltkWidgetView (int x, int y, int w, int h, const char *label = 0);
+ ~FltkWidgetView ();
+
+ void layout();
+
+ 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 drawImage (core::Imgbuf *imgbuf, int xRoot, int yRoot,
+ int x, int y, int width, int height);
+
+ bool usesFltkWidgets ();
+ void addFltkWidget (::fltk::Widget *widget, core::Allocation *allocation);
+ void removeFltkWidget (::fltk::Widget *widget);
+ void allocateFltkWidget (::fltk::Widget *widget,
+ core::Allocation *allocation);
+ void drawFltkWidget (::fltk::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 00000000..c999745a
--- /dev/null
+++ b/dw/fltkviewport.cc
@@ -0,0 +1,496 @@
+/*
+ * Dillo Widget
+ *
+ * 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.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+
+
+#include "fltkviewport.hh"
+
+#include <fltk/draw.h>
+#include <fltk/damage.h>
+#include <fltk/events.h>
+
+#include <stdio.h>
+
+using namespace fltk;
+using namespace lout::object;
+using namespace lout::container::typed;
+
+namespace dw {
+namespace fltk {
+
+FltkViewport::FltkViewport (int x, int y, int w, int h, const char *label):
+ FltkWidgetView (x, y, w, h, label)
+{
+ hscrollbar = new Scrollbar (0, 0, 1, 1);
+ hscrollbar->set_horizontal();
+ hscrollbar->callback (hscrollbarCallback, this);
+ add (hscrollbar);
+
+ vscrollbar = new Scrollbar (0, 0, 1, 1);
+ vscrollbar->set_vertical();
+ vscrollbar->callback (vscrollbarCallback, this);
+ add (vscrollbar);
+
+ scrollX = scrollY = scrollDX = scrollDY = 0;
+ 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 < ::fltk::Widget> >
+ (true);
+}
+
+FltkViewport::~FltkViewport ()
+{
+ delete gadgets;
+}
+
+void FltkViewport::adjustScrollbarsAndGadgetsAllocation ()
+{
+ int hdiff = 0, vdiff = 0;
+ int visibility = 0;
+
+ 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->x (0);
+ hscrollbar->y (0 + h () - SCROLLBAR_THICKNESS);
+ hscrollbar->w (w () - hdiff);
+ hscrollbar->h (SCROLLBAR_THICKNESS);
+
+ vscrollbar->x (0 + w () - SCROLLBAR_THICKNESS);
+ vscrollbar->y (0);
+ vscrollbar->h (h () - vdiff);
+ vscrollbar->w (SCROLLBAR_THICKNESS);
+
+ int x = w () - SCROLLBAR_THICKNESS, y = h () - SCROLLBAR_THICKNESS;
+ for(Iterator <TypedPointer < ::fltk::Widget> > it = gadgets->iterator ();
+ it.hasNext (); ) {
+ ::fltk::Widget *widget = it.getNext()->getTypedValue ();
+ widget->x (0);
+ widget->y (0);
+ widget->w (SCROLLBAR_THICKNESS);
+ widget->h (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 (Widget *vscrollbar, void *viewportPtr)
+{
+ ((FltkViewport*)viewportPtr)->vscrollbarChanged ();
+}
+
+void FltkViewport::hscrollbarCallback (Widget *hscrollbar, void *viewportPtr)
+{
+ ((FltkViewport*)viewportPtr)->hscrollbarChanged ();
+}
+
+// ----------------------------------------------------------------------
+
+void FltkViewport::layout ()
+{
+ theLayout->viewportSizeChanged (this, w(), h());
+ adjustScrollbarsAndGadgetsAllocation ();
+
+ FltkWidgetView::layout ();
+}
+
+void FltkViewport::draw_area (void *data, const Rectangle& cr )
+{
+ FltkViewport *vp = (FltkViewport*) data;
+ push_clip(cr);
+
+ vp->FltkWidgetView::draw ();
+
+ for(Iterator <TypedPointer < ::fltk::Widget> > it = vp->gadgets->iterator ();
+ it.hasNext (); ) {
+ ::fltk::Widget *widget = it.getNext()->getTypedValue ();
+ vp->draw_child (*widget);
+ }
+
+ pop_clip();
+
+}
+
+void FltkViewport::draw ()
+{
+ int hdiff = vscrollbar->visible () ? SCROLLBAR_THICKNESS : 0;
+ int vdiff = hscrollbar->visible () ? SCROLLBAR_THICKNESS : 0;
+ Rectangle cr (0, 0, w () - hdiff, h () - vdiff);
+ int d = damage();
+
+ if (d & DAMAGE_SCROLL) {
+ set_damage (DAMAGE_SCROLL);
+ scrollrect(cr, -scrollDX, -scrollDY, draw_area, this);
+ d &= ~DAMAGE_SCROLL;
+ set_damage (d);
+ }
+
+ if (d) {
+ draw_area(this, cr);
+
+ if (d == DAMAGE_CHILD) {
+ if (hscrollbar->damage ())
+ draw_child (*hscrollbar);
+ if (vscrollbar->damage ())
+ draw_child (*vscrollbar);
+ } else {
+ draw_child (*hscrollbar);
+ draw_child (*vscrollbar);
+ }
+ }
+
+ scrollDX = 0;
+ scrollDY = 0;
+}
+
+int FltkViewport::handle (int event)
+{
+ //printf("FltkViewport::handle %d\n", event);
+
+ if (hscrollbar->Rectangle::contains (event_x (), event_y ()) &&
+ !(event_state() & (SHIFT | CTRL | ALT)) &&
+ hscrollbar->send (event)) {
+ return 1;
+ }
+
+ if (vscrollbar->Rectangle::contains (event_x (), event_y ()) &&
+ vscrollbar->send (event)) {
+ return 1;
+ }
+
+ switch(event) {
+ case ::fltk::FOCUS:
+ /** \bug Draw focus box. */
+ return 1;
+
+ case ::fltk::UNFOCUS:
+ /** \bug Undraw focus box. */
+ return 1;
+
+ case ::fltk::PUSH:
+ take_focus();
+ if (::fltk::event_button() == ::fltk::MiddleButton) {
+ /* pass event so that middle click can open link in new window */
+ if (FltkWidgetView::handle (event) == 0) {
+ dragScrolling = 1;
+ dragX = ::fltk::event_x();
+ dragY = ::fltk::event_y();
+ setCursor (core::style::CURSOR_MOVE);
+ }
+ return 1;
+ }
+ break;
+
+ case ::fltk::DRAG:
+ if (::fltk::event_button() == ::fltk::MiddleButton) {
+ if (dragScrolling) {
+ scroll(dragX - ::fltk::event_x(), dragY - ::fltk::event_y());
+ dragX = ::fltk::event_x();
+ dragY = ::fltk::event_y();
+ return 1;
+ }
+ }
+ break;
+
+ case ::fltk:: MOUSEWHEEL:
+ return (event_dx() ? hscrollbar : vscrollbar)->handle(event);
+ break;
+
+ case ::fltk::RELEASE:
+ if (::fltk::event_button() == ::fltk::MiddleButton) {
+ dragScrolling = 0;
+ setCursor (core::style::CURSOR_DEFAULT);
+ }
+ break;
+
+ case ::fltk::ENTER:
+ /* could be the result of, e.g., closing another window. */
+ mouse_x = ::fltk::event_x();
+ mouse_y = ::fltk::event_y();
+ positionChanged();
+ break;
+
+ case ::fltk::LEAVE:
+ mouse_x = mouse_y = -1;
+ break;
+
+ case ::fltk::KEY:
+ /* tell fltk we want to receive these KEY events as SHORTCUT */
+ switch (::fltk::event_key()) {
+ case PageUpKey:
+ case PageDownKey:
+ case SpaceKey:
+ case DownKey:
+ case UpKey:
+ case RightKey:
+ case LeftKey:
+ case HomeKey:
+ case EndKey:
+ return 0;
+ }
+ break;
+
+ case ::fltk::SHORTCUT:
+ switch (::fltk::event_key()) {
+ case PageUpKey:
+ case 'b':
+ case 'B':
+ scroll (0, -vscrollbar->pagesize ());
+ return 1;
+
+ case PageDownKey:
+ case SpaceKey:
+ scroll (0, vscrollbar->pagesize ());
+ return 1;
+
+ case DownKey:
+ scroll (0, (int) vscrollbar->linesize ());
+ return 1;
+
+ case UpKey:
+ scroll (0, (int) -vscrollbar->linesize ());
+ return 1;
+
+ case RightKey:
+ scroll ((int) hscrollbar->linesize (), 0);
+ return 1;
+
+ case LeftKey:
+ scroll ((int) -hscrollbar->linesize (), 0);
+ return 1;
+
+ case HomeKey:
+ scrollTo (scrollX, 0);
+ return 1;
+
+ case EndKey:
+ scrollTo (scrollX, canvasHeight); /* gets adjusted in scrollTo () */
+ return 1;
+ }
+ }
+
+ 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 (mouse_x != -1)
+ (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 me 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 / scrollDY can therefore be non-zero here.
+ */
+ updateCanvasWidgets (x - scrollX, y - scrollY);
+ scrollDX += x - scrollX;
+ scrollDY += y - scrollY;
+
+ scrollX = x;
+ scrollY = y;
+
+ adjustScrollbarValues ();
+ redraw (DAMAGE_SCROLL);
+ theLayout->scrollPosChanged (this, scrollX, scrollY);
+ positionChanged();
+}
+
+void FltkViewport::scroll (int dx, int dy)
+{
+ scrollTo (scrollX + dx, scrollY + dy);
+}
+
+void FltkViewport::setViewportSize (int width, int height,
+ int hScrollbarThickness,
+ int vScrollbarThickness)
+{
+ if (hScrollbarThickness > 0)
+ hscrollbar->show ();
+ else
+ hscrollbar->hide ();
+ if (vScrollbarThickness > 0)
+ vscrollbar->show ();
+ else
+ vscrollbar->hide ();
+
+ /* If no scrollbar, go to the beginning */
+ scroll(hScrollbarThickness ? 0 : -scrollX,
+ vScrollbarThickness ? 0 : -scrollY);
+}
+
+void FltkViewport::updateCanvasWidgets (int dx, int dy)
+{
+ // scroll all child widgets except scroll bars
+ for (int i = children () - 1; i > 0; i--) {
+ ::fltk::Widget *widget = child (i);
+
+ if (widget == hscrollbar || widget == vscrollbar)
+ continue;
+
+ widget->x (widget->x () - dx);
+ widget->y (widget->y () - dy);
+ }
+}
+
+int FltkViewport::translateViewXToCanvasX (int x)
+{
+ return x + scrollX;
+}
+
+int FltkViewport::translateViewYToCanvasY (int y)
+{
+ return y + scrollY;
+}
+
+int FltkViewport::translateCanvasXToViewX (int x)
+{
+ return x - scrollX;
+}
+
+int FltkViewport::translateCanvasYToViewY (int y)
+{
+ return 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 (::fltk::Widget *gadget)
+{
+ /** \bug Reparent? */
+
+ gadgets->append (new TypedPointer < ::fltk::Widget> (gadget));
+ adjustScrollbarsAndGadgetsAllocation ();
+}
+
+
+} // namespace fltk
+} // namespace dw
diff --git a/dw/fltkviewport.hh b/dw/fltkviewport.hh
new file mode 100644
index 00000000..6af377d4
--- /dev/null
+++ b/dw/fltkviewport.hh
@@ -0,0 +1,77 @@
+#ifndef __DW_FLTKVIEWPORT_HH__
+#define __DW_FLTKVIEWPORT_HH__
+
+#include <fltk/Group.h>
+#include <fltk/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 dragScrolling, dragX, dragY;
+
+ ::fltk::Scrollbar *vscrollbar, *hscrollbar;
+
+ GadgetOrientation gadgetOrientation[4];
+ container::typed::List <object::TypedPointer < ::fltk::Widget> > *gadgets;
+
+ void adjustScrollbarsAndGadgetsAllocation ();
+ void adjustScrollbarValues ();
+ void hscrollbarChanged ();
+ void vscrollbarChanged ();
+ void positionChanged ();
+
+ static void hscrollbarCallback (Widget *hscrollbar, void *viewportPtr);
+ static void vscrollbarCallback (Widget *vscrollbar, void *viewportPtr);
+
+ void updateCanvasWidgets (int oldScrollX, int oldScrollY);
+ static void draw_area (void *data, const Rectangle& cr);
+
+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 layout();
+ 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 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 addGadget (::fltk::Widget *gadget);
+};
+
+} // namespace fltk
+} // namespace dw
+
+#endif // __DW_FLTKVIEWPORT_HH__
+
diff --git a/dw/image.cc b/dw/image.cc
new file mode 100644
index 00000000..499fc438
--- /dev/null
+++ b/dw/image.cc
@@ -0,0 +1,392 @@
+/*
+ * Dillo Widget
+ *
+ * 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.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+
+
+#include "image.hh"
+#include "../lout/misc.hh"
+
+namespace dw {
+
+using namespace lout;
+
+ImageMapsList::ImageMap::ImageMap ()
+{
+ shapesAndLinks = new container::typed::List <ShapeAndLink> (true);
+ defaultLink = -1;
+}
+
+ImageMapsList::ImageMap::~ImageMap ()
+{
+ delete shapesAndLinks;
+}
+
+void ImageMapsList::ImageMap::add (core::Shape *shape, int link) {
+ ShapeAndLink *shapeAndLink = new ShapeAndLink ();
+ shapeAndLink->shape = shape;
+ shapeAndLink->link = link;
+ shapesAndLinks->append (shapeAndLink);
+}
+
+int ImageMapsList::ImageMap::link (int x, int y) {
+ container::typed::Iterator <ShapeAndLink> it;
+ int link = defaultLink;
+
+ for (it = shapesAndLinks->iterator (); it.hasNext (); ) {
+ ShapeAndLink *shapeAndLink = it.getNext ();
+
+ if (shapeAndLink->shape->isPointWithin (x, y)) {
+ link = shapeAndLink->link;
+ break;
+ }
+ }
+
+ return link;
+}
+
+ImageMapsList::ImageMapsList ()
+{
+ imageMaps = new container::typed::HashTable <object::Object, ImageMap>
+ (true, true);
+ currentMap = NULL;
+}
+
+ImageMapsList::~ImageMapsList ()
+{
+ delete imageMaps;
+}
+
+/**
+ * \brief Start a new map and make it the current one.
+ *
+ * This has to be called before dw::ImageMapsList::addShapeToCurrentMap.
+ * "key" is owned by the image map list, so a copy should be passed, when
+ * necessary.
+ */
+void ImageMapsList::startNewMap (object::Object *key)
+{
+ currentMap = new ImageMap ();
+ imageMaps->put (key, currentMap);
+}
+
+/**
+ * \brief Add a shape to the current map-
+ *
+ * "shape" is owned by the image map list, so a copy should be passed, when
+ * necessary.
+ */
+void ImageMapsList::addShapeToCurrentMap (core::Shape *shape, int link)
+{
+ currentMap->add (shape, link);
+}
+
+/**
+ * \brief Set default link for current map-
+ */
+void ImageMapsList::setCurrentMapDefaultLink (int link)
+{
+ currentMap->setDefaultLink (link);
+}
+
+int ImageMapsList::link (object::Object *key, int x, int y)
+{
+ int link = -1;
+ ImageMap *map = imageMaps->get (key);
+
+ if (map)
+ link = map->link (x, y);
+
+ return link;
+}
+
+// ----------------------------------------------------------------------
+
+int Image::CLASS_ID = -1;
+
+Image::Image(const char *altText)
+{
+ registerName ("dw::Image", &CLASS_ID);
+ this->altText = altText ? strdup (altText) : NULL;
+ altTextWidth = -1; // not yet calculated
+ buffer = NULL;
+ clicking = false;
+ currLink = -1;
+ mapList = NULL;
+ mapKey = NULL;
+ isMap = false;
+}
+
+Image::~Image()
+{
+ if (altText)
+ delete altText;
+ if (buffer)
+ buffer->unref ();
+}
+
+void Image::sizeRequestImpl (core::Requisition *requisition)
+{
+ if (buffer) {
+ requisition->width = buffer->getRootWidth ();
+ requisition->ascent = buffer->getRootHeight ();
+ requisition->descent = 0;
+ } else {
+ if(altText && altText[0]) {
+ if (altTextWidth == -1)
+ altTextWidth =
+ layout->textWidth (getStyle()->font, altText, strlen (altText));
+
+ requisition->width = altTextWidth;
+ requisition->ascent = getStyle()->font->ascent;
+ requisition->descent = getStyle()->font->descent;
+ } else {
+ requisition->width = 0;
+ requisition->ascent = 0;
+ requisition->descent = 0;
+ }
+ }
+
+ requisition->width += getStyle()->boxDiffWidth ();
+ requisition->ascent += getStyle()->boxOffsetY ();
+ requisition->descent += getStyle()->boxRestHeight ();
+}
+
+void Image::sizeAllocateImpl (core::Allocation *allocation)
+{
+ core::Imgbuf *oldBuffer;
+ int dx, dy;
+
+ /* if image is moved only */
+ if (allocation->width == this->allocation.width &&
+ allocation->ascent + allocation->descent == getHeight ())
+ return;
+
+ dx = getStyle()->boxDiffWidth ();
+ dy = getStyle()->boxDiffHeight ();
+#if 0
+ printf("boxDiffHeight = %d + %d, buffer=%p\n",
+ getStyle()->boxOffsetY(), getStyle()->boxRestHeight(), buffer);
+ printf("getContentWidth() = allocation.width - style->boxDiffWidth ()"
+ " = %d - %d = %d\n",
+ this->allocation.width, getStyle()->boxDiffWidth(),
+ this->allocation.width - getStyle()->boxDiffWidth());
+ printf("getContentHeight() = getHeight() - style->boxDiffHeight ()"
+ " = %d - %d = %d\n", this->getHeight(), getStyle()->boxDiffHeight(),
+ this->getHeight() - getStyle()->boxDiffHeight());
+#endif
+ if (buffer != NULL &&
+ /* It may be, that the image is allocated at zero content size. In this
+ * case, we simply wait. */
+ getContentWidth () > 0 && getContentHeight () > 0) {
+ oldBuffer = buffer;
+ buffer = oldBuffer->getScaledBuf (allocation->width - dx,
+ allocation->ascent
+ + allocation->descent - dy);
+ oldBuffer->unref ();
+ }
+}
+
+void Image::enterNotifyImpl (core::EventCrossing *event)
+{
+ // BUG: this is wrong for image maps, but the cursor position is unknown.
+ currLink = getStyle()->x_link;
+
+ if (currLink != -1) {
+ (void) emitLinkEnter (currLink, -1, -1, -1);
+ }
+}
+
+void Image::leaveNotifyImpl (core::EventCrossing *event)
+{
+ clicking = false;
+
+ if (currLink != -1) {
+ currLink = -1;
+ (void) emitLinkEnter (-1, -1, -1, -1);
+ }
+}
+
+bool Image::motionNotifyImpl (core::EventMotion *event)
+{
+ if (mapList) {
+ /* client-side image map */
+ int newLink = mapList->link (mapKey, event->xWidget, event->yWidget);
+ if (newLink != currLink) {
+ currLink = newLink;
+ clicking = false;
+ setCursor(newLink == -1 ? core::style::CURSOR_DEFAULT :
+ core::style::CURSOR_POINTER);
+ (void) emitLinkEnter (newLink, -1, -1, -1);
+ }
+ } else if (isMap && currLink != -1) {
+ /* server-side image map */
+ (void) emitLinkEnter (currLink, -1, event->xWidget, event->yWidget);
+ }
+ return true;
+}
+
+bool Image::buttonPressImpl (core::EventButton *event)
+{
+ bool ret = false;
+ currLink = mapList ? mapList->link (mapKey, event->xWidget, event->yWidget):
+ getStyle()->x_link;
+ if (event->button == 3){
+ (void)emitLinkPress(currLink, getStyle()->x_img, -1,-1,event);
+ ret = true;
+ } else if (event->button == 1 || currLink != -1){
+ clicking = true;
+ ret = true;
+ }
+ return ret;
+}
+
+bool Image::buttonReleaseImpl (core::EventButton *event)
+{
+ currLink = mapList ? mapList->link (mapKey, event->xWidget, event->yWidget):
+ getStyle()->x_link;
+ if (clicking) {
+ int x = isMap ? event->xWidget : -1;
+ int y = isMap ? event->yWidget : -1;
+ clicking = false;
+ emitLinkClick (currLink, getStyle()->x_img, x, y, event);
+ return true;
+ }
+ return false;
+}
+
+void Image::draw (core::View *view, core::Rectangle *area)
+{
+ int dx, dy;
+ core::Rectangle content, intersection;
+
+ drawWidgetBox (view, area, false);
+
+ if (buffer) {
+ dx = getStyle()->boxOffsetX ();
+ dy = getStyle()->boxOffsetY ();
+ content.x = dx;
+ content.y = dy;
+ content.width = getContentWidth ();
+ content.height = getContentHeight ();
+
+ if (area->intersectsWith (&content, &intersection))
+ view->drawImage (buffer,
+ allocation.x + dx, allocation.y + dy,
+ intersection.x - dx, intersection.y - dy,
+ intersection.width, intersection.height);
+ } else {
+ if(altText && altText[0]) {
+ if (altTextWidth == -1)
+ altTextWidth =
+ layout->textWidth (getStyle()->font, altText, strlen (altText));
+
+ core::View *clippingView = NULL, *usedView = view;
+ if (allocation.width < altTextWidth ||
+ allocation.ascent < getStyle()->font->ascent ||
+ allocation.descent < getStyle()->font->descent) {
+ clippingView = usedView =
+ view->getClippingView (allocation.x + getStyle()->boxOffsetX (),
+ allocation.y + getStyle()->boxOffsetY (),
+ allocation.width
+ - getStyle()->boxDiffWidth (),
+ allocation.ascent + allocation.descent
+ - getStyle()->boxDiffHeight ());
+ }
+
+ usedView->drawText (getStyle()->font, getStyle()->color,
+ core::style::Color::SHADING_NORMAL,
+ allocation.x + getStyle()->boxOffsetX (),
+ allocation.y + getStyle()->boxOffsetY ()
+ + getStyle()->font->ascent,
+ altText, strlen(altText));
+
+ if(clippingView)
+ view->mergeClippingView (clippingView);
+ }
+ }
+
+ /** todo: draw selection */
+}
+
+core::Iterator *Image::iterator (core::Content::Type mask, bool atEnd)
+{
+ //return new core::TextIterator (this, mask, atEnd, altText);
+ /** \bug Not implemented. */
+ return new core::EmptyIterator (this, mask, atEnd);
+}
+
+void Image::setBuffer (core::Imgbuf *buffer, bool resize)
+{
+ core::Imgbuf *oldBuf = this->buffer;
+
+ if (resize)
+ queueResize (0, true);
+
+ // If the image has not yet been allocated, or is allocated at zero
+ // content size, the first part is useless.
+ if (wasAllocated () && getContentWidth () > 0 && getContentHeight () > 0) {
+ this->buffer =
+ buffer->getScaledBuf (getContentWidth (), getContentHeight ());
+ } else {
+ this->buffer = buffer;
+ buffer->ref ();
+ }
+
+ if (oldBuf)
+ oldBuf->unref ();
+}
+
+void Image::drawRow (int row)
+{
+ core::Rectangle area;
+
+ assert (buffer != NULL);
+
+ buffer->getRowArea (row, &area);
+ if (area.width && area.height)
+ queueDrawArea (area.x + getStyle()->boxOffsetX (),
+ area.y + getStyle()->boxOffsetY (),
+ area.width, area.height);
+}
+
+
+/**
+ * \brief Sets image as server side image map.
+ */
+void Image::setIsMap ()
+{
+ isMap = true;
+}
+
+
+/**
+ * \brief Sets image as client side image map.
+ *
+ * "list" is not owned by the image, the caller has to free it. "key"
+ * is owned by the image, if it is used by the caller afterwards, a copy
+ * should be passed.
+ */
+void Image::setUseMap (ImageMapsList *list, object::Object *key)
+{
+ mapList = list;
+ mapKey = key;
+}
+
+} // namespace dw
diff --git a/dw/image.hh b/dw/image.hh
new file mode 100644
index 00000000..37c27e0d
--- /dev/null
+++ b/dw/image.hh
@@ -0,0 +1,161 @@
+#ifndef __DW_IMAGE_HH__
+#define __DW_IMAGE_HH__
+
+#include "core.hh"
+
+namespace dw {
+
+/**
+ * \brief Represents a list of client-side image maps.
+ *
+ * All image maps of a HTML page (in the future, also image maps from
+ * different HTML pages) are stored in a list, which is passed to the
+ * image, so that it is possible to deal with maps, which are defined
+ * after the image within the HTML page.
+ *
+ * Maps are referred by instances of object::Object. These keys are
+ * typically URLs, so the type representing URLS should be derived from
+ * object::Object.
+ *
+ * \todo Some methods within the key class have to be implemented, this
+ * is not clear at this time.
+ */
+class ImageMapsList
+{
+private:
+ class ImageMap: public lout::object::Object {
+ private:
+ class ShapeAndLink: public lout::object::Object {
+ public:
+ core::Shape *shape;
+ int link;
+
+ ~ShapeAndLink () { if (shape) delete shape; };
+ };
+
+ lout::container::typed::List <ShapeAndLink> *shapesAndLinks;
+ int defaultLink;
+ public:
+ ImageMap ();
+ ~ImageMap ();
+
+ void add (core::Shape *shape, int link);
+ void setDefaultLink (int link) { defaultLink = link; };
+ int link (int x, int y);
+ };
+
+ lout::container::typed::HashTable <lout::object::Object, ImageMap>
+ *imageMaps;
+ ImageMap *currentMap;
+
+public:
+ ImageMapsList ();
+ ~ImageMapsList ();
+
+ void startNewMap (lout::object::Object *key);
+ void addShapeToCurrentMap (core::Shape *shape, int link);
+ void setCurrentMapDefaultLink (int link);
+ int link (lout::object::Object *key, int x, int y);
+};
+
+/**
+ * \brief Displays an instance of dw::core::Imgbuf.
+ *
+ * The dw::core::Imgbuf is automatically scaled, when needed, but dw::Image
+ * does not keep a reference on the root buffer.
+ *
+ *
+ * <h3>Signals</h3>
+ *
+ * For image maps, dw::Image uses the signals defined in
+ * dw::core::Widget::LinkReceiver. For client side image maps, -1 is
+ * passed for the coordinates, for server side image maps, the respective
+ * coordinates are used. See section "Image Maps" below.
+ *
+ *
+ * <h3>%Image Maps</h3>
+ *
+ * <h4>Client Side %Image Maps</h4>
+ *
+ * You must first create a list of image maps (dw::ImageMapList), which can
+ * be used for multiple images. The caller is responsible for freeing the
+ * dw::ImageMapList.
+ *
+ * Adding a map is done by dw::ImageMapsList::startNewMap. The key is an
+ * instance of a sub class of object::Object. In the context of HTML, this is
+ * a URL, which defines this map globally, by combining the URL of the
+ * document, this map is defined in, with the value of the attribute "name" of
+ * the \<MAP\> element, as a fragment.
+ *
+ * dw::ImageMapsList::addShapeToCurrentMap adds a shape to the current
+ * map. The \em link argument is a number, which is later passed to
+ * the dw::core::Widget::LinkReceiver.
+ *
+ * This map list is then, together with the key for the image, passed to
+ * dw::Image::setUseMap. For HTML, a URL with the value of the "ismap"
+ * attribute of \<IMG\> should be used.
+ *
+ * dw::Image will search the correct map, when needed. If it is not found
+ * at this time, but later defined, it will be found and used later. This is
+ * the case, when an HTML \<MAP\> is defined below the \<IMG\> in the
+ * document.
+ *
+ * Currently, only maps defined in the same document as the image may be
+ * used, since the dw::ImageMapsList is stored in the HTML link block, and
+ * contains only the image maps defined in the document.
+ *
+ * <h4>Server Side %Image Maps</h4>
+ *
+ * To use images for server side image maps, you must call
+ * dw::Image::setIsMap, and the dw::Image::style must contain a valid link
+ * (dw::core::style::Style::x_link). After this, motions and clicks are
+ * delegated to dw::core::Widget::LinkReceiver.
+ *
+ * \sa\ref dw-images-and-backgrounds
+ */
+class Image: public core::Widget
+{
+private:
+ char *altText;
+ core::Imgbuf *buffer;
+ int altTextWidth;
+ bool clicking;
+ int currLink;
+ ImageMapsList *mapList;
+ Object *mapKey;
+ bool isMap;
+
+protected:
+ void sizeRequestImpl (core::Requisition *requisition);
+ void sizeAllocateImpl (core::Allocation *allocation);
+
+ void draw (core::View *view, core::Rectangle *area);
+
+ bool buttonPressImpl (core::EventButton *event);
+ bool buttonReleaseImpl (core::EventButton *event);
+ void enterNotifyImpl (core::EventCrossing *event);
+ void leaveNotifyImpl (core::EventCrossing *event);
+ bool motionNotifyImpl (core::EventMotion *event);
+
+ //core::Iterator *iterator (Content::Type mask, bool atEnd);
+
+public:
+ static int CLASS_ID;
+
+ Image(const char *altText);
+ ~Image();
+
+ core::Iterator *iterator (core::Content::Type mask, bool atEnd);
+
+ inline core::Imgbuf *getBuffer () { return buffer; }
+ void setBuffer (core::Imgbuf *buffer, bool resize = false);
+
+ void drawRow (int row);
+
+ void setIsMap ();
+ void setUseMap (ImageMapsList *list, Object *key);
+};
+
+} // namespace dw
+
+#endif // __DW_IMAGE_HH__
diff --git a/dw/imgbuf.hh b/dw/imgbuf.hh
new file mode 100644
index 00000000..8948bbef
--- /dev/null
+++ b/dw/imgbuf.hh
@@ -0,0 +1,210 @@
+#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
+
+namespace dw {
+namespace core {
+
+using namespace lout;
+
+/**
+ * \brief The platform independant interface for image buffers.
+ *
+ * %Image buffers depend on the platform (see \ref dw-images-and-backgrounds),
+ * but have this general, platform independant 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 object::Object, public lout::signal::ObservedObject
+{
+public:
+ enum Type { RGB, RGBA, GRAY, INDEXED, INDEXED_ALPHA };
+
+ /*
+ * 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;
+
+ /*
+ * 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 dw
+} // namespace core
+
+#endif // __DW_IMGBUF_HH__
diff --git a/dw/iterator.cc b/dw/iterator.cc
new file mode 100644
index 00000000..39e09d41
--- /dev/null
+++ b/dw/iterator.cc
@@ -0,0 +1,797 @@
+/*
+ * Dillo Widget
+ *
+ * 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.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+
+
+#include "core.hh"
+#include <limits.h>
+
+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::Object (), misc::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);
+}
+
+/**
+ * \brief Delete the iterator.
+ *
+ * The desctructor 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
+ //
+ // Therefor, we 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);
+ }
+}
+
+// -------------------
+// 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 (misc::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 (misc::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::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::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::WIDGET);
+ it2 = it->cloneIterator ();
+
+ while (fromEnd ? it2->prev () : it2->next ()) {
+ if (it2->getContent()->type == Content::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 ();
+ if (it->getWidget()->getParent ()) {
+ it2 = it->getWidget()->getParent()->iterator (mask, false);
+ while (true) {
+ if (!it2->next ())
+ misc::assertNotReached ();
+
+ if (it2->getContent()->type == Content::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;
+}
+
+/**
+ * \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)
+{
+ //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::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.
+
+ // Construct the iterators.
+ int thisLevel = it->getWidget()->getLevel (), level;
+ Widget *w;
+ for (w = it->getWidget (), level = thisLevel; w->getParent() != NULL;
+ w = w->getParent (), level--) {
+ Iterator *it = w->getParent()->iterator (mask, false);
+ stack.put (it, level - 1);
+ while (true) {
+ bool hasNext = it->next();
+ assert (hasNext);
+
+ if (it->getContent()->type == Content::WIDGET &&
+ it->getContent()->widget == w)
+ break;
+ }
+ }
+
+ stack.put (it, thisLevel);
+ content = *(it->getContent());
+ }
+}
+
+
+DeepIterator::~DeepIterator ()
+{
+}
+
+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 (misc::Comparable *other)
+{
+ DeepIterator *otherDeepIterator = (DeepIterator*)other;
+
+ // Search the highest level, where the widgets are the same.
+ int level = 0;
+
+ while (stack.get(level)->getWidget ()
+ == otherDeepIterator->stack.get(level)->getWidget ()) {
+ if (level == stack.size() - 1 ||
+ level == otherDeepIterator->stack.size() - 1)
+ break;
+ level++;
+ }
+
+ while (stack.get(level)->getWidget ()
+ != otherDeepIterator->stack.get(level)->getWidget ())
+ 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::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::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;
+}
+
+CharIterator::CharIterator (Widget *widget)
+{
+ Iterator *i = widget->iterator (Content::SELECTION_CONTENT, 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(misc::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 dw
+} // namespace core
diff --git a/dw/iterator.hh b/dw/iterator.hh
new file mode 100644
index 00000000..605217ec
--- /dev/null
+++ b/dw/iterator.hh
@@ -0,0 +1,256 @@
+#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 object::Object, public misc::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);
+
+ 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);
+};
+
+
+/**
+ * \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);
+
+ object::Object *clone();
+ int compareTo(misc::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(misc::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 object::Object, public misc::Comparable
+{
+private:
+ class Stack: public container::typed::Vector<Iterator>
+ {
+ public:
+ inline Stack (): 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 () { }
+
+public:
+ DeepIterator(Iterator *it);
+ ~DeepIterator();
+
+ 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(misc::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 object::Object, public misc::Comparable
+{
+public:
+ enum { START = -1, END = -2 };
+
+private:
+ DeepIterator *it;
+ int pos, ch;
+
+ CharIterator ();
+
+public:
+ CharIterator (Widget *widget);
+ ~CharIterator ();
+
+ object::Object *clone();
+ int compareTo(misc::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 dw
+} // namespace core
+
+#endif // __ITERATOR_HH__
diff --git a/dw/layout.cc b/dw/layout.cc
new file mode 100644
index 00000000..e2f437a3
--- /dev/null
+++ b/dw/layout.cc
@@ -0,0 +1,918 @@
+/*
+ * Dillo Widget
+ *
+ * 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.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+
+
+#include "core.hh"
+
+#include "../lout/debug.hh"
+#include "../lout/misc.hh"
+
+using namespace lout::container;
+using namespace lout::object;
+
+namespace dw {
+namespace core {
+
+void Layout::Receiver::canvasSizeChanged (int width, int ascent, int descent)
+{
+}
+
+// ----------------------------------------------------------------------
+
+bool Layout::Emitter::emitToReceiver (lout::signal::Receiver *receiver,
+ int signalNo,
+ int argc, 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;
+
+ default:
+ misc::assertNotReached ();
+ }
+
+ return false;
+}
+
+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);
+}
+
+// ---------------------------------------------------------------------
+
+Layout::Anchor::~Anchor ()
+{
+ delete name;
+}
+
+// ---------------------------------------------------------------------
+
+Layout::Layout (Platform *platform)
+{
+ this->platform = platform;
+ views = new container::typed::List <View> (true);
+ topLevel = NULL;
+ widgetAtPoint = NULL;
+
+ DBG_OBJ_CREATE (this, "DwRenderLayout");
+
+ bgColor = NULL;
+ cursor = style::CURSOR_DEFAULT;
+
+ canvasWidth = canvasAscent = canvasDescent = 0;
+
+ usesViewport = false;
+ scrollX = scrollY = 0;
+
+ requestedAnchor = 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 (&findtextState, this);
+ DBG_OBJ_ASSOC (&selectionState, this);
+
+ platform->setLayout (this);
+
+ selectionState.setLayout(this);
+}
+
+Layout::~Layout ()
+{
+ if (scrollIdleId != -1)
+ platform->removeIdle (scrollIdleId);
+ if (resizeIdleId != -1)
+ platform->removeIdle (resizeIdleId);
+
+ if (topLevel)
+ delete topLevel;
+ delete platform;
+ delete views;
+ delete anchorsTable;
+ delete textZone;
+}
+
+void Layout::addWidget (Widget *widget)
+{
+ if (topLevel) {
+ fprintf (stderr, "widget already set\n");
+ return;
+ }
+
+ topLevel = widget;
+ widget->layout = this;
+
+ findtextState.setWidget (widget);
+
+ canvasHeightGreater = false;
+ setSizeHints ();
+ updateBgColor ();
+ queueResize ();
+}
+
+void Layout::removeWidget ()
+{
+ /**
+ * \bug Some more attributes must be reset here.
+ */
+ topLevel = NULL;
+ widgetAtPoint = NULL;
+ canvasWidth = canvasAscent = canvasDescent = 0;
+ scrollX = scrollY = 0;
+
+ for (typed::Iterator <View> it = views->iterator (); it.hasNext (); ) {
+ View *view = it.getNext ();
+ 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)
+{
+ if (topLevel)
+ delete topLevel;
+ widgetAtPoint = NULL;
+ 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)
+{
+ views->append (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 ();
+ }
+ }
+
+ /*
+ * 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)
+{
+ view->setLayout (NULL);
+ platform->detachView (view);
+
+ views->detachRef (view);
+
+ /**
+ * \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.
+ */
+}
+
+/**
+ * \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 (hpos, vpos, x, y, width, height, true);
+}
+
+void Layout::scrollTo0 (HPosition hpos, VPosition vpos,
+ int x, int y, int width, int height,
+ bool scrollingInterrupted)
+{
+ if (usesViewport) {
+ //printf ("scrollTo (%d, %d, %s)\n",
+ // x, y, scrollingInterrupted ? "true" : "false");
+
+ scrollTargetHpos = hpos;
+ scrollTargetVpos = vpos;
+ scrollTargetX = x;
+ scrollTargetY = y;
+ scrollTargetWidth = width;
+ scrollTargetHeight = height;
+
+ if (scrollIdleId == -1) {
+ scrollIdleId = platform->addIdle (&Layout::scrollIdle);
+ scrollIdleNotInterrupted = true;
+ }
+
+ scrollIdleNotInterrupted =
+ scrollIdleNotInterrupted || !scrollingInterrupted;
+ }
+}
+
+void Layout::scrollIdle ()
+{
+ bool xChanged = true;
+ switch (scrollTargetHpos) {
+ case HPOS_LEFT:
+ scrollX = scrollTargetX;
+ break;
+ case HPOS_CENTER:
+ scrollX =
+ scrollTargetX
+ - (viewportWidth - vScrollbarThickness - scrollTargetWidth) / 2;
+ break;
+ case HPOS_RIGHT:
+ scrollX =
+ scrollTargetX
+ - (viewportWidth - vScrollbarThickness - scrollTargetWidth);
+ break;
+ case HPOS_INTO_VIEW:
+ xChanged = calcScrollInto (scrollTargetX, scrollTargetWidth, &scrollX,
+ viewportWidth - vScrollbarThickness);
+ break;
+ case HPOS_NO_CHANGE:
+ xChanged = false;
+ break;
+ }
+
+ bool yChanged = true;
+ switch (scrollTargetVpos) {
+ case VPOS_TOP:
+ scrollY = scrollTargetY;
+ break;
+ case VPOS_CENTER:
+ scrollY =
+ scrollTargetY
+ - (viewportHeight - hScrollbarThickness - scrollTargetHeight) / 2;
+ break;
+ case VPOS_BOTTOM:
+ scrollY =
+ scrollTargetY
+ - (viewportHeight - hScrollbarThickness - scrollTargetHeight);
+ break;
+ case VPOS_INTO_VIEW:
+ yChanged = calcScrollInto (scrollTargetY, scrollTargetHeight, &scrollY,
+ viewportHeight - hScrollbarThickness);
+ break;
+ case VPOS_NO_CHANGE:
+ yChanged = false;
+ break;
+ }
+
+ if (xChanged || yChanged) {
+ adjustScrollPos ();
+ for (container::typed::Iterator <View> it = views->iterator ();
+ it.hasNext (); ) {
+ View *thisView = it.getNext();
+ thisView->scrollTo (scrollX, scrollY);
+ }
+ }
+
+ scrollIdleId = -1;
+}
+
+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);
+
+ //printf("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)
+{
+ Rectangle widgetArea, intersection, widgetDrawArea;
+
+ 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)) {
+ 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);
+ }
+ }
+}
+
+/**
+ * Sets the anchor to scroll to.
+ */
+void Layout::setAnchor (const char *anchor)
+{
+ //printf ("setAnchor (%s)\n", anchor);
+
+ if (requestedAnchor)
+ delete 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 (HPOS_NO_CHANGE, VPOS_TOP, 0, anchor->y, 0, 0, false);
+}
+
+void Layout::setCursor (style::Cursor cursor)
+{
+ if (cursor != this->cursor) {
+ this->cursor = cursor;
+
+ for (typed::Iterator <View> it = views->iterator (); it.hasNext (); ) {
+ View *view = it.getNext ();
+ view->setCursor (cursor);
+ }
+ }
+}
+
+void Layout::updateCursor ()
+{
+ if (widgetAtPoint && widgetAtPoint->style)
+ setCursor (widgetAtPoint->style->cursor);
+ else
+ setCursor (style::CURSOR_DEFAULT);
+}
+
+void Layout::updateBgColor ()
+{
+ /* The toplevel widget should always have a defined background color,
+ * except at the beginning. Searching a defined background is not
+ * necessary. */
+ if (topLevel && topLevel->getStyle() &&
+ topLevel->getStyle()->backgroundColor)
+ bgColor = topLevel->getStyle()->backgroundColor;
+ else
+ bgColor = NULL;
+
+ for (typed::Iterator <View> it = views->iterator (); it.hasNext (); ) {
+ View *view = it.getNext ();
+ view->setBgColor (bgColor);
+ }
+}
+
+void Layout::resizeIdle ()
+{
+ //static int calls = 0;
+ //printf(" Layout::resizeIdle calls = %d\n", ++calls);
+
+ while (resizeIdleId != -1) {
+ // Reset already here, since in this function, queueResize() may be
+ // called again.
+ resizeIdleId = -1;
+
+ if (topLevel) {
+ Requisition requisition;
+ Allocation allocation;
+
+ topLevel->sizeRequest (&requisition);
+
+ 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 views about the new world size.
+ for (typed::Iterator <View> it = views->iterator (); it.hasNext ();) {
+ View *view = it.getNext ();
+ view->setCanvasSize (canvasWidth, canvasAscent, canvasDescent);
+ // view->queueDrawTotal (false);
+ }
+
+ if (usesViewport) {
+ int actualHScrollbarThickness =
+ (canvasWidth > viewportWidth) ? hScrollbarThickness : 0;
+ int actualVScrollbarThickness =
+ (canvasAscent + canvasDescent > viewportHeight) ?
+ vScrollbarThickness : 0;
+
+ if (!canvasHeightGreater &&
+ canvasAscent + canvasDescent
+ > viewportHeight - actualHScrollbarThickness) {
+ canvasHeightGreater = true;
+ setSizeHints ();
+ /* May queue a new resize. */
+ }
+
+ // Set viewport sizes.
+ for (typed::Iterator <View> it = views->iterator ();
+ it.hasNext (); ) {
+ View *view = it.getNext ();
+ if (view->usesViewport ())
+ view->setViewportSize (viewportWidth, viewportHeight,
+ actualHScrollbarThickness,
+ actualVScrollbarThickness);
+ }
+ }
+ }
+
+ // views are redrawn via Widget::resizeDrawImpl ()
+
+ }
+
+ updateAnchor ();
+}
+
+void Layout::setSizeHints ()
+{
+ if (topLevel) {
+ topLevel->setWidth (viewportWidth
+ - (canvasHeightGreater ? vScrollbarThickness : 0));
+ topLevel->setAscent (viewportHeight - vScrollbarThickness);
+ topLevel->setDescent (0);
+ }
+}
+
+void Layout::queueDraw (int x, int y, int width, int height)
+{
+ Rectangle area;
+ area.x = x;
+ area.y = y;
+ area.width = width;
+ area.height = height;
+
+ if (area.isEmpty ()) return;
+
+ for (container::typed::Iterator <View> it = views->iterator ();
+ it.hasNext (); ) {
+ View *view = it.getNext ();
+ view->queueDraw (&area);
+ }
+}
+
+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 ()
+{
+ if (resizeIdleId == -1) {
+ for (container::typed::Iterator <View> it = views->iterator ();
+ it.hasNext (); ) {
+ View *view = it.getNext ();
+ view->cancelQueueDraw ();
+ }
+
+ resizeIdleId = platform->addIdle (&Layout::resizeIdle);
+ }
+}
+
+
+// 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, true);
+}
+
+/**
+ * \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, true);
+}
+
+/**
+ * \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)
+{
+ Widget *lastWidget;
+ EventCrossing event;
+
+ lastWidget = widgetAtPoint;
+ moveOutOfView (state);
+
+ if(lastWidget) {
+ event.state = state;
+ event.lastWidget = lastWidget;
+ event.currentWidget = widgetAtPoint;
+ lastWidget->leaveNotify (&event);
+ }
+}
+
+/*
+ * 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)
+ return topLevel->getWidgetAtPoint (x, y, 0);
+ else
+ return NULL;
+}
+
+
+/*
+ * Emit the necessary crossing events, when the mouse pointer has moved to
+ * the given widget.
+ */
+void Layout::moveToWidget (Widget *newWidgetAtPoint, ButtonState state)
+{
+ Widget *ancestor, *w;
+ Widget **track;
+ int trackLen, i;
+ EventCrossing crossingEvent;
+
+ 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;
+ track[i++] = ancestor;
+ if(newWidgetAtPoint) {
+ /* second part */
+ i = trackLen - 1;
+ for (w = newWidgetAtPoint; w != ancestor; w = w->getParent ())
+ track[i--] = w;
+ }
+
+ /* Send events to all events on the track */
+ for (i = 0; i < trackLen; i++) {
+ crossingEvent.state = state;
+ crossingEvent.currentWidget = widgetAtPoint; // ???
+ crossingEvent.lastWidget = widgetAtPoint; // ???
+
+ if (i != 0)
+ track[i]->enterNotify (&crossingEvent);
+ if (i != trackLen - 1)
+ track[i]->leaveNotify (&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, bool mayBeSuppressed)
+{
+ Widget *widget;
+
+ for (widget = widgetAtPoint; widget; widget = widget->getParent ()) {
+ if(!mayBeSuppressed || 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 ();
+ }
+ }
+ }
+
+ 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;
+
+ // Tell all views about the scrolling position, except the caller.
+ for (container::typed::Iterator <View> it = views->iterator ();
+ it.hasNext (); ) {
+ View *thisView = it.getNext();
+ if(view != thisView && thisView->usesViewport ())
+ thisView->scrollTo (scrollX, scrollY);
+ }
+
+ 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)
+{
+ //printf("Layout::viewportSizeChanged w=%d h=%d new_w=%d new_h=%d\n",
+ // viewportWidth, viewportHeight, 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;
+
+ /* if size changes, redraw this view.
+ * todo: this is a resize call (redraw/resize code needs a review). */
+ if (viewportWidth != width || viewportHeight != height)
+ queueResize();
+
+ viewportWidth = width;
+ viewportHeight = height;
+
+ setSizeHints ();
+
+ int actualHScrollbarThickness =
+ (canvasWidth > viewportWidth) ? hScrollbarThickness : 0;
+ int actualVScrollbarThickness =
+ (canvasAscent + canvasDescent > viewportWidth) ? vScrollbarThickness : 0;
+
+ /* Tell all views about the size, except the caller. */
+ for (container::typed::Iterator <View> it = views->iterator ();
+ it.hasNext (); ) {
+ View *thisView = it.getNext();
+ if(view != thisView && thisView->usesViewport ())
+ thisView->setViewportSize (viewportWidth, viewportHeight,
+ actualHScrollbarThickness,
+ actualVScrollbarThickness);
+ }
+}
+
+} // namespace dw
+} // namespace core
+
diff --git a/dw/layout.hh b/dw/layout.hh
new file mode 100644
index 00000000..13b8f312
--- /dev/null
+++ b/dw/layout.hh
@@ -0,0 +1,264 @@
+#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 object::Object
+{
+ friend class Widget;
+
+public:
+ /**
+ * \brief Receiver interface different signals.
+ *
+ * May be extended
+ */
+ class Receiver: public lout::signal::Receiver
+ {
+ public:
+ virtual void canvasSizeChanged (int width, int ascent, int descent);
+ };
+
+private:
+ class Emitter: public lout::signal::Emitter
+ {
+ private:
+ enum { CANVAS_SIZE_CHANGED };
+
+ protected:
+ bool emitToReceiver (lout::signal::Receiver *receiver, int signalNo,
+ int argc, Object **argv);
+
+ public:
+ inline void connectLayout (Receiver *receiver) { connect (receiver); }
+
+ void emitCanvasSizeChanged (int width, int ascent, int descent);
+ };
+
+ Emitter emitter;
+
+ class Anchor: public object::Object
+ {
+ public:
+ char *name;
+ Widget *widget;
+ int y;
+
+ ~Anchor ();
+ };
+
+ Platform *platform;
+ container::typed::List <View> *views;
+ Widget *topLevel, *widgetAtPoint;
+
+ /* The state, which must be projected into the views. */
+ style::Color *bgColor;
+ style::Cursor cursor;
+ int canvasWidth, canvasAscent, canvasDescent;
+
+ bool usesViewport;
+ int scrollX, scrollY, viewportWidth, viewportHeight;
+ bool canvasHeightGreater;
+ int hScrollbarThickness, vScrollbarThickness;
+
+ HPosition scrollTargetHpos;
+ VPosition scrollTargetVpos;
+ int scrollTargetX, scrollTargetY, scrollTargetWidth, scrollTargetHeight;
+
+ char *requestedAnchor;
+ int scrollIdleId, resizeIdleId;
+ bool scrollIdleNotInterrupted;
+
+ /* Anchors of the widget tree */
+ container::typed::HashTable <object::String, Anchor> *anchorsTable;
+
+ SelectionState selectionState;
+ FindtextState findtextState;
+
+ enum ButtonEventType { BUTTON_PRESS, BUTTON_RELEASE, MOTION_NOTIFY };
+
+ 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 mayBeSuppressed);
+ 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(HPosition hpos, VPosition vpos,
+ int x, int y, int width, int height,
+ bool scrollingInterrupted);
+ void scrollIdle ();
+ void adjustScrollPos ();
+ static bool calcScrollInto (int targetValue, int requestedSize,
+ int *value, int viewportSize);
+
+ 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 updateBgColor ();
+ 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 ();
+ void removeWidget ();
+
+public:
+ Layout (Platform *platform);
+ ~Layout ();
+
+ 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 setAnchor (const char *anchor);
+
+ /* View */
+
+ inline void expose (View *view, Rectangle *area) { draw (view, area); }
+
+ /**
+ * \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);
+ }
+
+ /**
+ * \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);
+
+ /* delegated */
+
+ inline int textWidth (style::Font *font, const char *text, int len)
+ {
+ return platform->textWidth (font, 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 style::Font *createFont (style::FontAttrs *attrs, bool tryEverything)
+ {
+ return platform->createFont (attrs, tryEverything);
+ }
+
+ inline style::Color *createSimpleColor (int color)
+ {
+ return platform->createSimpleColor (color);
+ }
+
+ inline style::Color *createShadedColor (int color)
+ {
+ return platform->createShadedColor (color);
+ }
+
+ inline Imgbuf *createImgbuf (Imgbuf::Type type, int width, int height)
+ {
+ return platform->createImgbuf (type, width, height);
+ }
+
+ 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)
+ { return findtextState.search (str, caseSens); }
+
+ /** \brief See dw::core::FindtextState::resetSearch. */
+ inline void resetSearch () { findtextState.resetSearch (); }
+};
+
+} // namespace dw
+} // namespace core
+
+#endif // __DW_LAYOUT_HH__
+
diff --git a/dw/listitem.cc b/dw/listitem.cc
new file mode 100644
index 00000000..ba960b46
--- /dev/null
+++ b/dw/listitem.cc
@@ -0,0 +1,72 @@
+/*
+ * Dillo Widget
+ *
+ * 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.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+
+
+#include "listitem.hh"
+#include <stdio.h>
+
+namespace dw {
+
+int ListItem::CLASS_ID = -1;
+
+ListItem::ListItem (ListItem *ref, bool limitTextWidth):
+ AlignedTextblock (limitTextWidth)
+{
+ registerName ("dw::ListItem", &CLASS_ID);
+ setRefTextblock (ref);
+}
+
+ListItem::~ListItem()
+{
+}
+
+void ListItem::initWithWidget (core::Widget *widget,
+ core::style::Style *style)
+{
+ addWidget (widget, style);
+ addSpace (style);
+ updateValue ();
+}
+
+void ListItem::initWithText (char *text, core::style::Style *style)
+{
+ addText (text, style);
+ addSpace (style);
+ updateValue ();
+}
+
+int ListItem::getValue ()
+{
+ if (words->size () == 0)
+ return 0;
+ else
+ return words->get(0).size.width + words->get(0).origSpace;
+}
+
+void ListItem::setMaxValue (int maxValue, int value)
+{
+ innerPadding = maxValue;
+ line1Offset = - value;
+ redrawY = 0;
+ queueResize (0, true);
+}
+
+} // namespace dw
diff --git a/dw/listitem.hh b/dw/listitem.hh
new file mode 100644
index 00000000..ea24af3e
--- /dev/null
+++ b/dw/listitem.hh
@@ -0,0 +1,27 @@
+#ifndef __DW_LISTITEM_HH__
+#define __DW_LISTITEM_HH__
+
+#include "core.hh"
+#include "alignedtextblock.hh"
+
+namespace dw {
+
+class ListItem: public AlignedTextblock
+{
+protected:
+ int getValue ();
+ void setMaxValue (int maxValue, int value);
+
+public:
+ static int CLASS_ID;
+
+ ListItem(ListItem *ref, bool limitTextWidth);
+ ~ListItem();
+
+ void initWithWidget (core::Widget *widget, core::style::Style *style);
+ void initWithText (char *texty, core::style::Style *style);
+};
+
+} // namespace dw
+
+#endif // __DW_LISTITEM_HH__
diff --git a/dw/platform.hh b/dw/platform.hh
new file mode 100644
index 00000000..0ae5d508
--- /dev/null
+++ b/dw/platform.hh
@@ -0,0 +1,144 @@
+#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 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 dependant 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 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;
+
+ /*
+ * ---------------------------------------------------------
+ * 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 dependant) font.
+ *
+ * Typically, within a platform, a sub class of dw::core::style::Font
+ * is defined, which holds more platform dependant 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;
+
+ /**
+ * \brief Create a simple color resource for a given 0xrrggbb value.
+ */
+ virtual style::Color *createSimpleColor (int color) = 0;
+
+ /**
+ * \brief Create a shaded color resource for a given 0xrrggbb value.
+ */
+ virtual style::Color *createShadedColor (int color) = 0;
+
+
+ /*
+ * --------------------
+ * Image Buffers
+ * --------------------
+ */
+ virtual Imgbuf *createImgbuf (Imgbuf::Type type, int width, int height) = 0;
+
+ /**
+ * \brief Copy selected text (0-terminated).
+ */
+ virtual void copySelection(const char *text) = 0;
+
+ /**
+ * ...
+ */
+ virtual ui::ResourceFactory *getResourceFactory () = 0;
+};
+
+} // namespace dw
+} // namespace core
+
+#endif // __DW_PLATFORM_HH__
diff --git a/dw/preview.xbm b/dw/preview.xbm
new file mode 100644
index 00000000..85ea829b
--- /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/ruler.cc b/dw/ruler.cc
new file mode 100644
index 00000000..abefa1bf
--- /dev/null
+++ b/dw/ruler.cc
@@ -0,0 +1,53 @@
+/*
+ * Dillo Widget
+ *
+ * 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.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+
+
+#include "ruler.hh"
+#include "../lout/misc.hh"
+
+#include <stdio.h>
+
+namespace dw {
+
+Ruler::Ruler ()
+{
+ unsetFlags (HAS_CONTENTS);
+}
+
+void Ruler::sizeRequestImpl (core::Requisition *requisition)
+{
+ requisition->width = getStyle()->boxDiffWidth ();
+ requisition->ascent = getStyle()->boxOffsetY ();
+ requisition->descent = getStyle()->boxRestHeight ();
+}
+
+void Ruler::draw (core::View *view, core::Rectangle *area)
+{
+ drawWidgetBox (view, area, false);
+}
+
+core::Iterator *Ruler::iterator (core::Content::Type mask, bool atEnd)
+{
+ /** \todo TextIterator? */
+ return new core::EmptyIterator (this, mask, atEnd);
+}
+
+} // namespace dw
diff --git a/dw/ruler.hh b/dw/ruler.hh
new file mode 100644
index 00000000..a1ae67ea
--- /dev/null
+++ b/dw/ruler.hh
@@ -0,0 +1,30 @@
+#ifndef __RULER_HH__
+#define __RULER_HH__
+
+#include "core.hh"
+
+namespace dw {
+
+/**
+ * \brief Widget for drawing (horizontal) rules.
+ *
+ * This is really an empty widget, the HTML parser puts a border
+ * around it, and drawing is done in dw::core::Widget::drawWidgetBox.
+ * The only remarkable point is that the HAS_CONTENT flag is
+ * cleared.
+ */
+class Ruler: public core::Widget
+{
+protected:
+ void sizeRequestImpl (core::Requisition *requisition);
+ void draw (core::View *view, core::Rectangle *area);
+
+public:
+ Ruler ();
+
+ core::Iterator *iterator (core::Content::Type mask, bool atEnd);
+};
+
+} // namespace dw
+
+#endif // __RULER_HH__
diff --git a/dw/selection.cc b/dw/selection.cc
new file mode 100644
index 00000000..3153576f
--- /dev/null
+++ b/dw/selection.cc
@@ -0,0 +1,514 @@
+/*
+ * Dillo Widget
+ *
+ * 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.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+
+
+#include "core.hh"
+
+#include <string.h>
+
+/*
+ * 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 ()
+{
+ layout = NULL;
+
+ selectionState = NONE;
+ from = NULL;
+ to = NULL;
+
+ linkState = LINK_NONE;
+ link = NULL;
+}
+
+SelectionState::~SelectionState ()
+{
+ reset ();
+}
+
+
+bool SelectionState::DoubleClickEmitter::emitToReceiver (lout::signal::Receiver
+ *receiver,
+ int signalNo,
+ int argc,
+ Object **argv)
+{
+ ((DoubleClickReceiver*)receiver)->doubleClick ();
+ return false;
+}
+
+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, bool withinContent)
+{
+ Widget *itWidget = it->getWidget ();
+ bool ret = false;
+
+ if (event && event->button == 1 &&
+ !withinContent && event->numPressed == 2) {
+ // When the user double-clicks on empty parts, emit the double click
+ // signal instead of normal processing. Used for full screen
+ // mode.
+ doubleClickEmitter.emitDoubleClick ();
+ // Reset everything, so that dw::core::Selection::buttonRelease will
+ // ignore the "release" event following soon.
+ highlight (false, 0);
+ reset ();
+ ret = true;
+ } else {
+ if (linkNo != -1) {
+ // link handling
+ if (event) {
+ // return value is ignored
+ itWidget->emitLinkPress (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 {
+ // normal selection handling
+ if (event && event->button == 1) {
+ 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;
+ } else {
+ if (event && event->button == 3) {
+ // menu popup
+ itWidget->emitLinkPress (-1, -1, -1, -1, event);
+ ret = true;
+ }
+ }
+ }
+ }
+
+ return ret;
+}
+
+bool SelectionState::buttonRelease (Iterator *it, int charPos, int linkNo,
+ EventButton *event, bool withinContent)
+{
+ Widget *itWidget = it->getWidget ();
+ bool ret = false;
+
+ if (linkState == LINK_PRESSED && event && event->button == linkButton) {
+ // link handling
+ ret = true;
+ if (linkNo != -1)
+ // return value is ignored
+ itWidget->emitLinkRelease (linkNo, -1, -1, -1, event);
+
+ // The link where the user clicked the mouse button?
+ if (linkNo == linkNumber) {
+ resetLink ();
+ // return value is ignored
+ itWidget->emitLinkClick (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, bool withinContent)
+{
+ 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,
+ bool withinContent)
+{
+ switch (eventType) {
+ case BUTTON_PRESS:
+ return buttonPress (it, charPos, linkNo, (EventButton*)event,
+ withinContent);
+
+ case BUTTON_RELEASE:
+ return buttonRelease (it, charPos, linkNo, (EventButton*)event,
+ withinContent);
+
+ case BUTTON_MOTION:
+ return buttonMotion (it, charPos, linkNo, (EventMotion*)event,
+ withinContent);
+
+
+ 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. Unighlight 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 dw
+} // namespace core
diff --git a/dw/selection.hh b/dw/selection.hh
new file mode 100644
index 00000000..9cc8d25f
--- /dev/null
+++ b/dw/selection.hh
@@ -0,0 +1,275 @@
+#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 {
+
+using namespace lout;
+
+/**
+ * \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::Widget::LinkReceiver),
+ * otherwise -1
+ * <tr><td>dw::core::EventButton *event <td>the event itself; only the button
+ * is used
+ * <tr><td>bool withinContent <td>true, if there is some selectable
+ * content unter the mouse cursor; if
+ * set to false, the "full screen"
+ * feature is used on double click.
+ * </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::Widget::LinkReceiver::press,
+ * dw::core::Widget::LinkReceiver::release and
+ * dw::core::Widget::LinkReceiver::click (but not
+ * dw::core::Widget::LinkReceiver::enter) are emitted by these methods, so
+ * that widgets which let dw::core::SelectionState handle links, should only
+ * emit dw::core::Widget::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 };
+
+ class DoubleClickReceiver: public lout::signal::Receiver
+ {
+ public:
+ virtual void doubleClick () = 0;
+ };
+
+private:
+ class DoubleClickEmitter: public lout::signal::Emitter
+ {
+ private:
+ enum { DOUBLE_CLICK };
+
+ protected:
+ bool emitToReceiver (lout::signal::Receiver *receiver, int signalNo,
+ int argc, Object **argv);
+
+ public:
+ inline void connectDoubleClick (DoubleClickReceiver *receiver)
+ { connect (receiver); }
+
+ inline void emitDoubleClick () { emitVoid (DOUBLE_CLICK, 0, NULL); }
+ };
+
+ DoubleClickEmitter doubleClickEmitter;
+
+ 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 ();
+ inline void connectDoubleClick (DoubleClickReceiver *receiver)
+ { doubleClickEmitter.connectDoubleClick (receiver); }
+
+ bool buttonPress (Iterator *it, int charPos, int linkNo,
+ EventButton *event, bool withinContent);
+ bool buttonRelease (Iterator *it, int charPos, int linkNo,
+ EventButton *event, bool withinContent);
+ bool buttonMotion (Iterator *it, int charPos, int linkNo,
+ EventMotion *event, bool withinContent);
+
+ bool handleEvent (EventType eventType, Iterator *it, int charPos,
+ int linkNo, MousePositionEvent *event,
+ bool withinContent);
+};
+
+} // namespace dw
+} // namespace core
+
+#endif // __DW_SELECTION_H__
diff --git a/dw/style.cc b/dw/style.cc
new file mode 100644
index 00000000..bdb04f25
--- /dev/null
+++ b/dw/style.cc
@@ -0,0 +1,632 @@
+/*
+ * Dillo Widget
+ *
+ * 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.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+
+
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <ctype.h>
+
+#include "core.hh"
+
+namespace dw {
+namespace core {
+namespace style {
+
+void StyleAttrs::initValues ()
+{
+ x_link = -1;
+ x_img = -1;
+ x_tooltip = NULL;
+ textDecoration = TEXT_DECORATION_NONE;
+ textAlign = TEXT_ALIGN_LEFT;
+ textAlignChar = '.';
+ listStyleType = LIST_STYLE_TYPE_DISC;
+ valign = VALIGN_MIDDLE;
+ backgroundColor = NULL;
+ width = LENGTH_AUTO;
+ height = LENGTH_AUTO;
+
+ margin.setVal (0);
+ borderWidth.setVal (0);
+ padding.setVal (0);
+ setBorderColor (NULL);
+ setBorderStyle (BORDER_NONE);
+ hBorderSpacing = 0;
+ vBorderSpacing = 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_link = -1;
+ x_img = -1;
+ x_tooltip = NULL;
+
+ textAlign = TEXT_ALIGN_LEFT; /* ??? */
+ valign = VALIGN_MIDDLE;
+ textAlignChar = '.';
+ backgroundColor = NULL;
+ width = LENGTH_AUTO;
+ height = LENGTH_AUTO;
+
+ margin.setVal (0);
+ borderWidth.setVal (0);
+ padding.setVal (0);
+ setBorderColor (NULL);
+ setBorderStyle (BORDER_NONE);
+ hBorderSpacing = 0;
+ vBorderSpacing = 0;
+
+ display = DISPLAY_INLINE;
+ whiteSpace = WHITE_SPACE_NORMAL;
+ cursor = CURSOR_DEFAULT; /** \todo Check CSS specification again. */
+}
+
+/**
+ * \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 &&
+ textAlign == otherAttrs->textAlign &&
+ valign == otherAttrs->valign &&
+ textAlignChar == otherAttrs->textAlignChar &&
+ hBorderSpacing == otherAttrs->hBorderSpacing &&
+ vBorderSpacing == otherAttrs->vBorderSpacing &&
+ width == otherAttrs->width &&
+ height == otherAttrs->height &&
+ margin.equals (&otherAttrs->margin) &&
+ borderWidth.equals (&otherAttrs->borderWidth) &&
+ padding.equals (&otherAttrs->padding) &&
+ 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 &&
+ listStyleType == otherAttrs->listStyleType &&
+ x_link == otherAttrs->x_link &&
+ x_img == otherAttrs->x_img &&
+ x_tooltip == otherAttrs->x_tooltip);
+}
+
+int StyleAttrs::hashValue () {
+ return (intptr_t) font +
+ textDecoration +
+ (intptr_t) color +
+ (intptr_t) backgroundColor +
+ textAlign +
+ valign +
+ textAlignChar +
+ hBorderSpacing +
+ vBorderSpacing +
+ width +
+ height +
+ margin.hashValue () +
+ borderWidth.hashValue () +
+ padding.hashValue () +
+ (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 +
+ listStyleType +
+ x_link +
+ 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)
+{
+ copyAttrs (attrs);
+
+ refCount = 1;
+
+ font->ref ();
+ if (color)
+ color->ref ();
+ if (backgroundColor)
+ backgroundColor->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 (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--;
+}
+
+void Style::copyAttrs (StyleAttrs *attrs)
+{
+ font = attrs->font;
+ textDecoration = attrs->textDecoration;
+ color = attrs->color;
+ backgroundColor = attrs->backgroundColor;
+ textAlign = attrs->textAlign;
+ valign = attrs->valign;
+ textAlignChar = attrs->textAlignChar;
+ hBorderSpacing = attrs->hBorderSpacing;
+ vBorderSpacing = attrs->vBorderSpacing;
+ width = attrs->width;
+ height = attrs->height;
+ margin = attrs->margin;
+ borderWidth = attrs->borderWidth;
+ padding = attrs->padding;
+ borderColor = attrs->borderColor;
+ borderStyle = attrs->borderStyle;
+ display = attrs->display;
+ whiteSpace = attrs->whiteSpace;
+ listStyleType = attrs->listStyleType;
+ cursor = attrs->cursor;
+ x_link = attrs->x_link;
+ 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 && 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;
+ return h;
+}
+
+Font::~Font ()
+{
+ delete name;
+}
+
+void Font::copyAttrs (FontAttrs *attrs)
+{
+ name = strdup (attrs->name);
+ size = attrs->size;
+ weight = attrs->weight;
+ style = attrs->style;
+}
+
+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);
+}
+
+Font *Font::createFromList (Layout *layout, FontAttrs *attrs,
+ char *defaultFamily)
+{
+ Font *font = NULL;
+ FontAttrs attrs2;
+ char *comma, *list, *current;
+
+ attrs2 = *attrs;
+ current = list = strdup (attrs->name);
+
+ while (current && (font == NULL)) {
+ comma = strchr (current, ',');
+ if (comma) *comma = 0;
+
+ attrs2.name = current;
+ font = create0 (layout, &attrs2, false);
+ if (font)
+ break;
+
+ if (comma) {
+ current = comma + 1;
+ while (isspace (*current)) current++;
+ } else
+ current = NULL;
+ }
+
+ delete list;
+
+ if (font == NULL) {
+ attrs2.name = defaultFamily;
+ font = create0 (layout, &attrs2, true);
+ }
+
+ if (font == NULL)
+ fprintf (stderr, "Could not find any font.\n");
+
+ return font;
+}
+
+// ----------------------------------------------------------------------
+
+bool ColorAttrs::equals(object::Object *other)
+{
+ ColorAttrs *oc = (ColorAttrs*)other;
+ return this == oc || (color == oc->color && type == oc->type);
+}
+
+int ColorAttrs::hashValue()
+{
+ return color ^ type;
+}
+
+Color::~Color ()
+{
+}
+
+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, Type type)
+{
+ ColorAttrs attrs(col, type);
+ Color *color = NULL;
+
+ switch (type) {
+ case TYPE_SIMPLE:
+ color = layout->createSimpleColor (col);
+ break;
+ case TYPE_SHADED:
+ color = layout->createShadedColor (col);
+ break;
+ }
+
+ return color;
+}
+
+// ----------------------------------------------------------------------
+
+/**
+ * \brief Draw a part of a border.
+ */
+static void drawPolygon (View *view, Color *color, Color::Shading shading,
+ int x1, int y1, int x2, int y2,
+ int width, int w1, int w2)
+{
+ int points[4][2];
+
+ if (width != 0) {
+ if (width == 1) {
+ if (x1 == x2)
+ view->drawLine (color, shading, x1, y1, x2, y2 - 1);
+ else
+ view->drawLine (color, shading, x1, y1, x2 - 1, y2);
+ } else if (width == -1) {
+ if (x1 == x2)
+ view->drawLine (color, shading, x1 - 1, y1, x2 - 1, y2 - 1);
+ else
+ view->drawLine (color, shading, x1, y1 - 1, x2 - 1, y2 - 1);
+ } else {
+ points[0][0] = x1;
+ points[0][1] = y1;
+ points[1][0] = x2;
+ points[1][1] = y2;
+
+ if (x1 == x2) {
+ points[2][0] = x1 + width;
+ points[2][1] = y2 + w2;
+ points[3][0] = x1 + width;
+ points[3][1] = y1 + w1;
+ } else {
+ points[2][0] = x2 + w2;
+ points[2][1] = y1 + width;
+ points[3][0] = x1 + w1;
+ points[3][1] = y1 + width;
+ }
+
+ /*
+ printf ("drawPolygon: (%d, %d) .. (%d, %d) .. (%d, %d) .. (%d, %d)\n",
+ points[0][0], points[0][1], points[1][0], points[1][1],
+ points[2][0], points[2][1], points[3][0], points[3][1]);
+ */
+ view->drawPolygon (color, shading, true, points, 4);
+ }
+ }
+}
+
+/**
+ * \brief Draw the border of a region in window, according to style.
+ *
+ * Used by dw::core::Widget::drawBox and dw::core::Widget::drawWidgetBox.
+ */
+void drawBorder (View *view, Rectangle *area,
+ int x, int y, int width, int height,
+ Style *style, bool inverse)
+{
+ /** \todo a lot! */
+ Color::Shading dark, light, normal;
+ Color::Shading top, right, bottom, left;
+ int xb1, yb1, xb2, yb2, xp1, yp1, xp2, yp2;
+
+ if (style->borderStyle.top == BORDER_NONE)
+ return;
+
+ xb1 = x + style->margin.left;
+ yb1 = y + style->margin.top;
+ xb2 = xb1 + width - style->margin.left - style->margin.right;
+ yb2 = yb1 + height - style->margin.top - style->margin.bottom;
+
+ xp1 = xb1 + style->borderWidth.top;
+ yp1 = yb1 + style->borderWidth.left;
+ xp2 = xb2 + style->borderWidth.bottom;
+ yp2 = yb2 + style->borderWidth.right;
+
+ light = inverse ? Color::SHADING_DARK : Color::SHADING_LIGHT;
+ dark = inverse ? Color::SHADING_LIGHT : Color::SHADING_DARK;
+ normal = inverse ? Color::SHADING_INVERSE : Color::SHADING_NORMAL;
+
+ switch (style->borderStyle.top) {
+ case BORDER_INSET:
+ top = left = dark;
+ right = bottom = light;
+ break;
+
+ case BORDER_OUTSET:
+ top = left = light;
+ right = bottom = dark;
+ break;
+
+ default:
+ top = right = bottom = left = normal;
+ break;
+ }
+
+ drawPolygon (view, style->borderColor.top, top, xb1, yb1, xb2, yb1,
+ style->borderWidth.top, style->borderWidth.left,
+ - style->borderWidth.right);
+ drawPolygon (view, style->borderColor.right, right, xb2, yb1, xb2, yb2,
+ - style->borderWidth.right, style->borderWidth.top,
+ - style->borderWidth.bottom);
+ drawPolygon (view, style->borderColor.bottom, bottom, xb1, yb2, xb2, yb2,
+ - style->borderWidth.bottom, style->borderWidth.left,
+ - style->borderWidth.right);
+ drawPolygon (view, style->borderColor.left, left, xb1, yb1, xb1, yb2,
+ style->borderWidth.left, style->borderWidth.top,
+ - style->borderWidth.bottom);
+}
+
+
+/**
+ * \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.
+ */
+void drawBackground (View *view, Rectangle *area,
+ int x, int y, int width, int height,
+ Style *style, bool inverse)
+{
+ Rectangle bgArea, intersection;
+
+ if (style->backgroundColor) {
+ bgArea.x = x + style->margin.left + style->borderWidth.left;
+ bgArea.y = y + style->margin.top + style->borderWidth.top;
+ bgArea.width =
+ width - style->margin.left - style->borderWidth.left -
+ style->margin.right - style->borderWidth.right;
+ bgArea.height =
+ height - style->margin.top - style->borderWidth.top -
+ style->margin.bottom - style->borderWidth.bottom;
+
+ if (area->intersectsWith (&bgArea, &intersection))
+ view->drawRectangle (style->backgroundColor,
+ inverse ?
+ Color::SHADING_INVERSE : Color::SHADING_NORMAL,
+ true, intersection.x, intersection.y,
+ intersection.width, intersection.height);
+ }
+}
+
+// ----------------------------------------------------------------------
+
+static const char
+ *roman_I0[] = { "","I","II","III","IV","V","VI","VII","VIII","IX" },
+ *roman_I1[] = { "","X","XX","XXX","XL","L","LX","LXX","LXXX","XC" },
+ *roman_I2[] = { "","C","CC","CCC","CD","D","DC","DCC","DCCC","CM" },
+ *roman_I3[] = { "","M","MM","MMM","MMMM" };
+
+void strtolower (char *s)
+{
+ for ( ; *s; s++)
+ *s = tolower (*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';
+
+ switch(listStyleType){
+ case LIST_STYLE_TYPE_LOWER_ALPHA:
+ start_ch = 'a';
+ case LIST_STYLE_TYPE_UPPER_ALPHA:
+ i0 = num - 1;
+ i1 = i0/26 - 1; i2 = i1/26 - 1;
+ if (i2 > 25) /* more than 26+26^2+26^3=18278 elements ? */
+ sprintf(buf, "****.");
+ else
+ sprintf(buf, "%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 ? */
+ sprintf(buf, "****.");
+ else
+ snprintf(buf, buflen, "%s%s%s%s.", roman_I3[i3], roman_I2[i2],
+ roman_I1[i1], roman_I0[i0]);
+ if (low)
+ strtolower(buf);
+ break;
+ case LIST_STYLE_TYPE_DECIMAL:
+ default:
+ sprintf(buf, "%d.", num);
+ break;
+ }
+}
+
+} // namespace style
+} // namespace dw
+} // namespace core
diff --git a/dw/style.hh b/dw/style.hh
new file mode 100644
index 00000000..492efd30
--- /dev/null
+++ b/dw/style.hh
@@ -0,0 +1,669 @@
+#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
+
+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 hythens (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 lenghts, 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_COSSHAIR,
+ 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 BorderStyle {
+ BORDER_NONE,
+ BORDER_HIDDEN,
+ BORDER_DOTTED,
+ BORDER_DASHED,
+ BORDER_SOLID,
+ BORDER_DOUBLE,
+ BORDER_GROOVE,
+ BORDER_RIDGE,
+ BORDER_INSET,
+ BORDER_OUTSET
+};
+
+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
+};
+
+/**
+ * \todo Incomplete. Has to be completed for a CSS implementation.
+ */
+enum DisplayType {
+ DISPLAY_BLOCK,
+ DISPLAY_INLINE,
+ DISPLAY_LIST_ITEM,
+ DISPLAY_TABLE,
+ DISPLAY_TABLE_ROW_GROUP,
+ DISPLAY_TABLE_HEADER_GROUP,
+ DISPLAY_TABLE_FOOTER_GROUP,
+ DISPLAY_TABLE_ROW,
+ DISPLAY_TABLE_CELL,
+ DISPLAY_LAST
+};
+
+
+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 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
+};
+
+/**
+ * \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" lenghths 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. */
+inline double perLengthVal(Length l) { return (double)(l & ~3) / (1 << 18); }
+
+/** \brief Returns the value of a relative length, as a float. */
+inline double relLengthVal(Length l) { return (double)(l & ~3) / (1 << 18); }
+
+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 Font;
+class Color;
+class Tooltip;
+
+/**
+ * \sa dw::core::style
+ */
+class StyleAttrs : public object::Object
+{
+public:
+ Font *font;
+ int textDecoration; /* No TextDecoration because of problems converting
+ * TextDecoration <-> int */
+ Color *color, *backgroundColor;
+
+ TextAlignType textAlign;
+ VAlignType valign;
+ char textAlignChar; /* In future, strings will be supported. */
+
+ int hBorderSpacing, vBorderSpacing;
+ Length width, height;
+
+ Box margin, borderWidth, padding;
+ struct { Color *top, *right, *bottom, *left; } borderColor;
+ struct { BorderStyle top, right, bottom, left; } borderStyle;
+
+ DisplayType display;
+ WhiteSpace whiteSpace;
+ ListStyleType listStyleType;
+ Cursor cursor;
+
+ int x_link;
+ int x_img;
+ Tooltip *x_tooltip;
+
+ 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; }
+
+ bool equals (object::Object *other);
+ int hashValue ();
+};
+
+
+/**
+ * \sa dw::core::style
+ */
+class Style: public StyleAttrs
+{
+private:
+ static int totalRef;
+ int refCount;
+ static container::typed::HashTable <StyleAttrs, Style> *styleTable;
+
+ Style (StyleAttrs *attrs);
+
+protected:
+ ~Style();
+
+ void copyAttrs (StyleAttrs *attrs);
+
+public:
+ inline static Style *create (Layout *layout, 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 object::String
+{
+public:
+ TooltipAttrs(const char *text): object::String(text) { }
+};
+
+/**
+ * \sa dw::core::style
+ */
+class Tooltip: public TooltipAttrs
+{
+private:
+ int refCount;
+
+ Tooltip (const char *text): TooltipAttrs(text) { refCount = 0; }
+
+public:
+ inline Tooltip *create (Layout *layout, const char *text)
+ { return new Tooltip (text); }
+
+ inline void ref () { refCount++; }
+ inline void unref ()
+ { if(--refCount == 0) delete this; }
+
+ inline void onEnter () { }
+ inline void onLeave () { }
+ inline void onMotion () { }
+};
+
+
+/**
+ * \sa dw::core::style
+ */
+class FontAttrs: public object::Object
+{
+public:
+ const char *name;
+ int size;
+ int weight;
+ FontStyle style;
+
+ bool equals(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 () { refCount = 0; }
+ virtual ~Font ();
+
+ void copyAttrs (FontAttrs *attrs);
+
+public:
+ int ascent, descent;
+ int spaceWidth;
+ int xHeight;
+
+ static Font *create (Layout *layout, FontAttrs *attrs);
+ static Font *createFromList (Layout *layout, FontAttrs *attrs,
+ char *defaultFamily);
+
+ inline void ref () { refCount++; }
+ inline void unref () { if(--refCount == 0) delete this; }
+};
+
+
+/**
+ * \sa dw::core::style
+ */
+class ColorAttrs: public object::Object
+{
+public:
+ enum Type { TYPE_SIMPLE, TYPE_SHADED };
+
+protected:
+ int color;
+ Type type;
+
+public:
+ inline ColorAttrs(int color, Type type)
+ {
+ this->color = color;
+ this->type = type;
+ }
+
+ inline int getColor () { return color; }
+ inline Type getType () { return type; }
+
+ bool equals(object::Object *other);
+ int hashValue();
+};
+
+
+/**
+ * \sa dw::core::style
+ */
+class Color: public ColorAttrs
+{
+private:
+ int refCount;
+
+ static Color *create (Layout *layout, int color, Type type);
+ void remove(dw::core::Layout *layout);
+ int shadeColor (int color, int d);
+
+protected:
+ inline Color (int color, Type type): ColorAttrs (color, type) {
+ 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:
+ inline static Color *createSimple (Layout *layout, int color)
+ {
+ return create (layout, color, TYPE_SIMPLE);
+ }
+
+ inline static Color *createShaded (Layout *layout, int color)
+ {
+ return create (layout, color, TYPE_SHADED);
+ }
+
+ inline void ref () { refCount++; }
+ inline void unref ()
+ { if(--refCount == 0) delete this; }
+};
+
+void drawBorder (View *view, Rectangle *area,
+ int x, int y, int width, int height,
+ Style *style, bool inverse);
+void drawBackground (View *view, Rectangle *area,
+ int x, int y, int width, int height,
+ Style *style, bool inverse);
+void numtostr (int num, char *buf, int buflen, ListStyleType listStyleType);
+
+} // namespace style
+} // namespace dw
+} // namespace core
+
+#endif // __DW_STYLE_HH__
+
diff --git a/dw/table.cc b/dw/table.cc
new file mode 100644
index 00000000..4135799b
--- /dev/null
+++ b/dw/table.cc
@@ -0,0 +1,1192 @@
+/*
+ * Dillo Widget
+ *
+ * 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.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+//#define DBG
+
+#include "table.hh"
+#include "../lout/misc.hh"
+
+#define MAX misc::max
+
+
+namespace dw {
+
+int Table::CLASS_ID = -1;
+
+Table::Table(bool limitTextWidth)
+{
+ registerName ("dw::Table", &CLASS_ID);
+ setFlags (USES_HINTS);
+
+ this->limitTextWidth = limitTextWidth;
+
+ rowClosed = false;
+
+ // random values
+ availWidth = 100;
+ availAscent = 100;
+ availDescent = 0;
+
+ numRows = 0;
+ numCols = 0;
+ curRow = -1;
+ curCol = 0;
+
+ children = new misc::SimpleVector <Child*> (16);
+ colExtremes = new misc::SimpleVector<core::Extremes> (8);
+ colWidths = new misc::SimpleVector <int> (8);
+ cumHeight = new misc::SimpleVector <int> (8);
+ rowSpanCells = new misc::SimpleVector <int> (8);
+ colSpanCells = new misc::SimpleVector <int> (8);
+ baseline = new misc::SimpleVector <int> (8);
+ rowStyle = new misc::SimpleVector <core::style::Style*> (8);
+
+ hasColPercent = 0;
+ colPercents = new misc::SimpleVector <float> (8);
+
+ redrawX = 0;
+ redrawY = 0;
+}
+
+
+Table::~Table()
+{
+ for (int i = 0; i < children->size (); i++) {
+ if (children->get(i)) {
+ switch (children->get(i)->type) {
+ case Child::CELL:
+ delete children->get(i)->cell.widget;
+ break;
+ case Child::SPAN_SPACE:
+ break;
+ }
+
+ delete children->get(i);
+ }
+ }
+
+ for (int i = 0; i < rowStyle->size (); i++)
+ if (rowStyle->get (i))
+ rowStyle->get(i)->unref ();
+
+ delete children;
+ delete colExtremes;
+ delete colWidths;
+ delete cumHeight;
+ delete rowSpanCells;
+ delete colSpanCells;
+ delete baseline;
+ delete rowStyle;
+ delete colPercents;
+}
+
+void Table::sizeRequestImpl (core::Requisition *requisition)
+{
+ forceCalcCellSizes ();
+
+ /**
+ * \bug Baselines are not regarded here.
+ */
+ requisition->width = getStyle()->boxDiffWidth ()
+ + (numCols + 1) * getStyle()->hBorderSpacing;
+ for (int col = 0; col < numCols; col++)
+ requisition->width += colWidths->get (col);
+
+ requisition->ascent =
+ getStyle()->boxDiffHeight () + cumHeight->get (numRows)
+ + getStyle()->vBorderSpacing;
+ requisition->descent = 0;
+
+}
+
+void Table::getExtremesImpl (core::Extremes *extremes)
+{
+ if (numCols == 0) {
+ extremes->minWidth = extremes->maxWidth = 0;
+ return;
+ }
+
+ forceCalcColumnExtremes ();
+
+ extremes->minWidth = extremes->maxWidth =
+ (numCols + 1) * getStyle()->hBorderSpacing
+ + getStyle()->boxDiffWidth ();
+ for (int col = 0; col < numCols; col++) {
+ extremes->minWidth += colExtremes->getRef(col)->minWidth;
+ extremes->maxWidth += colExtremes->getRef(col)->maxWidth;
+ }
+ if (core::style::isAbsLength (getStyle()->width)) {
+ extremes->minWidth =
+ MAX (extremes->minWidth,
+ core::style::absLengthVal(getStyle()->width));
+ extremes->maxWidth =
+ MAX (extremes->maxWidth,
+ core::style::absLengthVal(getStyle()->width));
+ }
+
+#ifdef DBG
+ printf(" Table::getExtremesImpl, {%d, %d} numCols=%d\n",
+ extremes->minWidth, extremes->maxWidth, numCols);
+#endif
+}
+
+void Table::sizeAllocateImpl (core::Allocation *allocation)
+{
+ calcCellSizes ();
+
+ /**
+ * \bug Baselines are not regarded here.
+ */
+
+ int offy =
+ allocation->y + getStyle()->boxOffsetY () + getStyle()->vBorderSpacing;
+ int x =
+ allocation->x + getStyle()->boxOffsetX () + getStyle()->hBorderSpacing;
+
+ for (int col = 0; col < numCols; col++) {
+ for (int row = 0; row < numRows; row++) {
+ int n = row * numCols + col;
+ if (childDefined (n)) {
+ int width =
+ (children->get(n)->cell.colspanEff - 1)
+ * getStyle()->hBorderSpacing;
+ for (int i = 0; i < children->get(n)->cell.colspanEff; i++)
+ width += colWidths->get (col + i);
+
+ core::Allocation childAllocation;
+ core::Requisition childRequisition;
+
+ children->get(n)->cell.widget->sizeRequest (&childRequisition);
+
+ childAllocation.x = x;
+ childAllocation.y = cumHeight->get (row) + offy;
+ childAllocation.width = width;
+ childAllocation.ascent = childRequisition.ascent;
+ childAllocation.descent =
+ cumHeight->get (row + children->get(n)->cell.rowspan)
+ - cumHeight->get (row) - getStyle()->vBorderSpacing
+ - childRequisition.ascent;
+ children->get(n)->cell.widget->sizeAllocate (&childAllocation);
+ }
+ }
+
+ x += colWidths->get (col) + getStyle()->hBorderSpacing;
+ }
+}
+
+void Table::resizeDrawImpl ()
+{
+ queueDrawArea (redrawX, 0, allocation.width - redrawX, getHeight ());
+ queueDrawArea (0, redrawY, allocation.width, getHeight () - redrawY);
+ redrawX = allocation.width;
+ redrawY = getHeight ();
+}
+
+void Table::setWidth (int width)
+{
+ // If limitTextWidth is set, a queueResize may also be necessary.
+ if (availWidth != width || limitTextWidth) {
+#ifdef DBG
+ printf(" Table::setWidth %d\n", width);
+#endif
+ availWidth = width;
+ queueResize (0, false);
+ }
+}
+
+void Table::setAscent (int ascent)
+{
+ if (availAscent != ascent) {
+ availAscent = ascent;
+ queueResize (0, false);
+ }
+}
+
+void Table::setDescent (int descent)
+{
+ if (availDescent != descent) {
+ availDescent = descent;
+ queueResize (0, false);
+ }
+}
+
+void Table::draw (core::View *view, core::Rectangle *area)
+{
+ // Can be optimized, by iterating on the lines in area.
+ drawWidgetBox (view, area, false);
+
+#if 0
+ int offx = getStyle()->boxOffsetX () + getStyle()->hBorderSpacing;
+ int offy = getStyle()->boxOffsetY () + getStyle()->vBorderSpacing;
+ int width = getContentWidth ();
+
+ // This part seems unnecessary. It also segfaulted sometimes when
+ // cumHeight size was less than numRows. --jcid
+ for (int row = 0; row < numRows; row++) {
+ if (rowStyle->get (row))
+ drawBox (view, rowStyle->get (row), area,
+ offx, offy + cumHeight->get (row),
+ width - 2*getStyle()->hBorderSpacing,
+ cumHeight->get (row + 1) - cumHeight->get (row)
+ - getStyle()->vBorderSpacing, false);
+ }
+#endif
+
+ for (int i = 0; i < children->size (); i++) {
+ if (childDefined (i)) {
+ Widget *child = children->get(i)->cell.widget;
+ core::Rectangle childArea;
+ if (child->intersects (area, &childArea))
+ child->draw (view, &childArea);
+ }
+ }
+}
+
+void Table::removeChild (Widget *child)
+{
+ /** \bug Not implemented. */
+}
+
+core::Iterator *Table::iterator (core::Content::Type mask, bool atEnd)
+{
+ return new TableIterator (this, mask, atEnd);
+}
+
+void Table::addCell (Widget *widget, int colspan, int rowspan)
+{
+ Child *child;
+ int colspanEff;
+
+ // We limit the values for colspan and rowspan to 50, to avoid
+ // attacks by malicious web pages.
+ if (colspan > 50 || colspan < 0) {
+ fprintf (stderr, "WARNING: colspan = %d is set to 50.\n", colspan);
+ colspan = 50;
+ }
+ if (rowspan > 50 || rowspan <= 0) {
+ fprintf (stderr, "WARNING: rowspan = %d is set to 50.\n", rowspan);
+ rowspan = 50;
+ }
+
+ if (numRows == 0) {
+ // to prevent a crash
+ fprintf (stderr, "WARNING: Cell without row.\n");
+ addRow (NULL);
+ }
+
+ if (rowClosed) {
+ fprintf (stderr, "WARNING: Last cell had colspan=0.\n");
+ addRow (NULL);
+ }
+
+ if (colspan == 0) {
+ colspanEff = MAX (numCols - curCol, 1);
+ rowClosed = true;
+ } else
+ colspanEff = colspan;
+
+ // Find next free cell-
+ while (curCol < numCols &&
+ (child = children->get(curRow * numCols + curCol)) != NULL &&
+ child->type == Child::SPAN_SPACE)
+ curCol++;
+
+#ifdef DBG
+ printf("Table::addCell numCols=%d,curCol=%d,colspan=%d,colspanEff=%d\n",
+ numCols, curCol, colspan, colspanEff);
+#endif
+ // Increase children array, when necessary.
+ if (curRow + rowspan > numRows)
+ reallocChildren (numCols, curRow + rowspan);
+ if (curCol + colspanEff > numCols)
+ reallocChildren (curCol + colspanEff, numRows);
+
+ // Fill span space.
+ for (int col = 0; col < colspanEff; col++)
+ for (int row = 0; row < rowspan; row++)
+ if (!(col == 0 && row == 0)) {
+ child = new Child ();
+ child->type = Child::SPAN_SPACE;
+ child->spanSpace.startCol = curCol;
+ child->spanSpace.startRow = curRow;
+ children->set ((curRow + row) * numCols + curCol + col, child);
+ }
+
+ // Set the "root" cell.
+ child = new Child ();
+ child->type = Child::CELL;
+ child->cell.widget = widget;
+ child->cell.colspanOrig = colspan;
+ child->cell.colspanEff = colspanEff;
+ child->cell.rowspan = rowspan;
+ children->set (curRow * numCols + curCol, child);
+
+ curCol += colspanEff;
+
+ widget->setParent (this);
+ if (rowStyle->get (curRow))
+ widget->setBgColor (rowStyle->get(curRow)->backgroundColor);
+ queueResize (0, true);
+
+#if 0
+ // show table structure in stdout
+ for (int row = 0; row < numRows; row++) {
+ for (int col = 0; col < numCols; col++) {
+ int n = row * numCols + col;
+ if (!(child = children->get (n))) {
+ printf("[null ] ");
+ } else if (children->get(n)->type == Child::CELL) {
+ printf("[CELL rs=%d] ", child->cell.rowspan);
+ } else if (children->get(n)->type == Child::SPAN_SPACE) {
+ printf("[SPAN rs=%d] ", child->cell.rowspan);
+ } else {
+ printf("[Unk. ] ");
+ }
+ }
+ printf("\n");
+ }
+ printf("\n");
+#endif
+}
+
+void Table::addRow (core::style::Style *style)
+{
+ curRow++;
+
+ if (curRow >= numRows)
+ reallocChildren (numCols, curRow + 1);
+
+ if (rowStyle->get (curRow))
+ rowStyle->get(curRow)->unref ();
+
+ rowStyle->set (curRow, style);
+ if (style)
+ style->ref ();
+
+ curCol = 0;
+ rowClosed = false;
+}
+
+TableCell *Table::getCellRef ()
+{
+ core::Widget *child;
+
+ for (int row = 0; row <= numRows; row++) {
+ int n = curCol + row * numCols;
+ if (childDefined (n)) {
+ child = children->get(n)->cell.widget;
+ if (child->instanceOf (TableCell::CLASS_ID))
+ return (TableCell*)child;
+ }
+ }
+
+ return NULL;
+}
+
+void Table::reallocChildren (int newNumCols, int newNumRows)
+{
+ assert (newNumCols >= numCols);
+ assert (newNumRows >= numRows);
+
+ children->setSize (newNumCols * newNumRows);
+
+ if (newNumCols > numCols) {
+ // Complicated case, array got also wider.
+ for (int row = newNumRows - 1; row >= 0; row--) {
+ int colspan0Col = -1, colspan0Row = -1;
+
+ // Copy old part.
+ for (int col = numCols - 1; col >= 0; col--) {
+ int n = row * newNumCols + col;
+ children->set (n, children->get (row * numCols + col));
+ if (children->get (n)) {
+ switch (children->get(n)->type) {
+ case Child::CELL:
+ if (children->get(n)->cell.colspanOrig == 0) {
+ colspan0Col = col;
+ colspan0Row = row;
+ children->get(n)->cell.colspanEff = newNumCols - col;
+ }
+ break;
+ case Child::SPAN_SPACE:
+ if (children->get(children->get(n)->spanSpace.startRow
+ * numCols +
+ children->get(n)->spanSpace.startCol)
+ ->cell.colspanOrig == 0) {
+ colspan0Col = children->get(n)->spanSpace.startCol;
+ colspan0Row = children->get(n)->spanSpace.startRow;
+ }
+ break;
+ }
+ }
+ }
+
+ // Fill rest of the column.
+ if (colspan0Col == -1) {
+ for (int col = numCols; col < newNumCols; col++)
+ children->set (row * newNumCols + col, NULL);
+ } else {
+ for (int col = numCols; col < newNumCols; col++) {
+ Child *child = new Child ();
+ child->type = Child::SPAN_SPACE;
+ child->spanSpace.startCol = colspan0Col;
+ child->spanSpace.startRow = colspan0Row;
+ children->set (row * newNumCols + col, child);
+ }
+ }
+ }
+ }
+
+ // Bottom part of the children array.
+ for (int row = numRows; row < newNumRows; row++)
+ for (int col = 0; col < newNumCols; col++)
+ children->set (row * newNumCols + col, NULL);
+
+ // Simple arrays.
+ rowStyle->setSize (newNumRows);
+ for (int row = numRows; row < newNumRows; row++)
+ rowStyle->set (row, NULL);
+ // Rest is increased, when needed.
+
+ numCols = newNumCols;
+ numRows = newNumRows;
+}
+
+// ----------------------------------------------------------------------
+
+void Table::calcCellSizes ()
+{
+ if (needsResize ())
+ forceCalcCellSizes ();
+}
+
+
+void Table::forceCalcCellSizes ()
+{
+ int totalWidth = 0, childHeight, forceTotalWidth = 1;
+ core::Extremes extremes;
+
+ // Will also call calcColumnExtremes(), when needed.
+ getExtremes (&extremes);
+
+ if (core::style::isAbsLength (getStyle()->width)) {
+ totalWidth = core::style::absLengthVal (getStyle()->width);
+ } else if (core::style::isPerLength (getStyle()->width)) {
+ /*
+ * If the width is > 100%, we use 100%, this prevents ugly
+ * results. (May be changed in future, when a more powerful
+ * rendering is implemented, to handle fixed positions etc.,
+ * as defined by CSS2.)
+ */
+ totalWidth =
+ (int)(availWidth
+ * misc::min (core::style::perLengthVal (getStyle()->width),
+ 1.0));
+ } else if (getStyle()->width == core::style::LENGTH_AUTO) {
+ totalWidth = availWidth;
+ forceTotalWidth = 0;
+ }
+#ifdef DBG
+ printf(" availWidth = %d\n", availWidth);
+ printf(" totalWidth1 = %d\n", totalWidth);
+#endif
+ if (totalWidth < extremes.minWidth)
+ totalWidth = extremes.minWidth;
+ totalWidth = totalWidth
+ - (numCols + 1) * getStyle()->hBorderSpacing
+ - getStyle()->boxDiffWidth ();
+#ifdef DBG
+ printf(" totalWidth2 = %d curCol=%d\n", totalWidth,curCol);
+#endif
+
+ colWidths->setSize (numCols, 0);
+ cumHeight->setSize (numRows + 1, 0);
+ rowSpanCells->setSize (0);
+ baseline->setSize (numRows);
+#ifdef DBG
+ printf(" extremes = %d,%d\n", extremes.minWidth, extremes.maxWidth);
+ printf(" getStyle()->boxDiffWidth() = %d\n", getStyle()->boxDiffWidth());
+ printf(" getStyle()->hBorderSpacing = %d\n", getStyle()->hBorderSpacing);
+#endif
+
+ apportion_percentages2 (totalWidth, forceTotalWidth);
+ if (!hasColPercent)
+ apportion2 (totalWidth, forceTotalWidth);
+
+ setCumHeight (0, 0);
+ for (int row = 0; row < numRows; row++) {
+ /**
+ * \bug dw::Table::baseline is not filled.
+ */
+ int rowHeight = 0;
+
+ for (int col = 0; col < numCols; col++) {
+ int n = row * numCols + col;
+ if (childDefined (n)) {
+ int width = (children->get(n)->cell.colspanEff - 1)
+ * getStyle()->hBorderSpacing;
+ for (int i = 0; i < children->get(n)->cell.colspanEff; i++)
+ width += colWidths->get (col + i);
+
+ core::Requisition childRequisition;
+ children->get(n)->cell.widget->setWidth (width);
+ children->get(n)->cell.widget->sizeRequest (&childRequisition);
+ childHeight = childRequisition.ascent + childRequisition.descent;
+ if (children->get(n)->cell.rowspan == 1) {
+ rowHeight = MAX (rowHeight, childHeight);
+ } else {
+ rowSpanCells->increase();
+ rowSpanCells->set(rowSpanCells->size()-1, n);
+ }
+ }
+ }/*for col*/
+
+ setCumHeight (row + 1,
+ cumHeight->get (row) + rowHeight + getStyle()->vBorderSpacing);
+
+ }/*for row*/
+
+ apportionRowSpan ();
+}
+
+void Table::apportionRowSpan ()
+{
+ int *rowHeight = NULL;
+
+ for (int c = 0; c < rowSpanCells->size(); ++c) {
+ int n = rowSpanCells->get(c);
+ int row = n / numCols;
+ int rs = children->get(n)->cell.rowspan;
+ int sumRows = cumHeight->get(row+rs) - cumHeight->get(row);
+ core::Requisition childRequisition;
+ children->get(n)->cell.widget->sizeRequest (&childRequisition);
+ int spanHeight = childRequisition.ascent + childRequisition.descent
+ + getStyle()->vBorderSpacing;
+ if (sumRows >= spanHeight)
+ continue;
+
+ // Cell size is too small.
+#ifdef DBG
+ printf("Short cell %d, sumRows=%d spanHeight=%d\n",
+ n,sumRows,spanHeight);
+#endif
+ // Fill height array
+ if (!rowHeight) {
+ rowHeight = new int[numRows];
+ for (int i = 0; i < numRows; i++)
+ rowHeight[i] = cumHeight->get(i+1) - cumHeight->get(i);
+ }
+#ifdef DBG
+ printf (" rowHeight { ");
+ for (int i = 0; i < numRows; i++)
+ printf ("%d ", rowHeight[i]);
+ printf ("}\n");
+#endif
+
+ // Calc new row sizes for this span.
+ int cumHnew_i = 0, cumh_i = 0, hnew_i;
+ for (int i = row; i < row + rs; ++i) {
+ hnew_i =
+ sumRows == 0 ? (int)((float)(spanHeight-cumHnew_i)/(row+rs-i)) :
+ (sumRows-cumh_i) <= 0 ? 0 :
+ (int)((float)(spanHeight-cumHnew_i)*rowHeight[i]/(sumRows-cumh_i));
+#ifdef DBG
+ printf (" i=%-3d h=%d hnew_i=%d =%d*%d/%d cumh_i=%d cumHnew_i=%d\n",
+ i,rowHeight[i],hnew_i,
+ spanHeight-cumHnew_i,rowHeight[i],sumRows-cumh_i,
+ cumh_i, cumHnew_i);
+#endif
+ cumHnew_i += hnew_i;
+ cumh_i += rowHeight[i];
+ rowHeight[i] = hnew_i;
+ }
+ // Update cumHeight
+ for (int i = 0; i < numRows; ++i)
+ setCumHeight (i+1, cumHeight->get(i) + rowHeight[i]);
+ }
+ delete[] rowHeight;
+}
+
+
+/**
+ * \brief Fills dw::Table::colExtremes, only if recalculation is necessary.
+ *
+ * \bug Some parts are missing.
+ */
+void Table::calcColumnExtremes ()
+{
+ if (extremesChanged ())
+ forceCalcColumnExtremes ();
+}
+
+
+/**
+ * \brief Fills dw::Table::colExtremes in all cases.
+ */
+void Table::forceCalcColumnExtremes ()
+{
+#ifdef DBG
+ printf(" Table::forceCalcColumnExtremes numCols=%d\n", numCols);
+#endif
+ if (numCols == 0)
+ return;
+
+ colExtremes->setSize (numCols);
+ colPercents->setSize (numCols);
+ colSpanCells->setSize (0);
+ /* 1. cells with colspan = 1 */
+ for (int col = 0; col < numCols; col++) {
+ colExtremes->getRef(col)->minWidth = 0;
+ colExtremes->getRef(col)->maxWidth = 0;
+ colPercents->set(col, LEN_AUTO);
+
+ for (int row = 0; row < numRows; row++) {
+ int n = row * numCols + col;
+ if (!childDefined (n))
+ continue;
+ if (children->get(n)->cell.colspanEff == 1) {
+ core::Extremes cellExtremes;
+ int cellMinW, cellMaxW, pbm;
+ core::style::Length width =
+ children->get(n)->cell.widget->getStyle()->width;
+ pbm = (numCols + 1) * getStyle()->hBorderSpacing
+ + children->get(n)->cell.widget->getStyle()->boxDiffWidth ();
+ children->get(n)->cell.widget->getExtremes (&cellExtremes);
+ if (core::style::isAbsLength (width)) {
+ // Fixed lengths include table padding, border and margin.
+ cellMinW = cellExtremes.minWidth;
+ cellMaxW = MAX (cellMinW,
+ core::style::absLengthVal(width) - pbm);
+ } else {
+ cellMinW = cellExtremes.minWidth;
+ cellMaxW = cellExtremes.maxWidth;
+ }
+#ifdef DBG
+ printf("FCCE, col%d colMin,colMax,cellMin,cellMax = %d,%d,%d,%d\n",
+ col,
+ colExtremes->getRef(col)->minWidth,
+ colExtremes->getRef(col)->maxWidth,
+ cellMinW, cellMaxW);
+#endif
+ colExtremes->getRef(col)->minWidth =
+ MAX (colExtremes->getRef(col)->minWidth, cellMinW);
+ colExtremes->getRef(col)->maxWidth =
+ MAX (colExtremes->getRef(col)->minWidth, MAX (
+ colExtremes->getRef(col)->maxWidth,
+ cellMaxW));
+
+ // Also fill the colPercents array in this pass
+ if (core::style::isPerLength (width)) {
+ hasColPercent = 1;
+ if (colPercents->get(col) == LEN_AUTO)
+ colPercents->set(col, core::style::perLengthVal(width));
+ } else if (core::style::isAbsLength (width)) {
+ // We treat LEN_ABS as a special case of LEN_AUTO.
+ /*
+ * if (colPercents->get(col) == LEN_AUTO)
+ * colPercents->set(col, LEN_ABS);
+ */
+ }
+ } else {
+ colSpanCells->increase();
+ colSpanCells->set(colSpanCells->size()-1, n);
+ }
+ }
+ }
+
+ /* 2. cells with colspan > 1 */
+ /* If needed, here we set proportionally apportioned col maximums */
+ for (int c = 0; c < colSpanCells->size(); ++c) {
+ core::Extremes cellExtremes;
+ int cellMinW, cellMaxW, pbm;
+ int n = colSpanCells->get(c);
+ int col = n % numCols;
+ int cs = children->get(n)->cell.colspanEff;
+ core::style::Length width =
+ children->get(n)->cell.widget->getStyle()->width;
+ pbm = (numCols + 1) * getStyle()->hBorderSpacing
+ + children->get(n)->cell.widget->getStyle()->boxDiffWidth ();
+ children->get(n)->cell.widget->getExtremes (&cellExtremes);
+ if (core::style::isAbsLength (width)) {
+ // Fixed lengths include table padding, border and margin.
+ cellMinW = cellExtremes.minWidth;
+ cellMaxW = MAX (cellMinW, core::style::absLengthVal(width) - pbm);
+ } else {
+ cellMinW = cellExtremes.minWidth;
+ cellMaxW = cellExtremes.maxWidth;
+ }
+ int minSumCols = 0, maxSumCols = 0;
+ for (int i = 0; i < cs; ++i) {
+ minSumCols += colExtremes->getRef(col+i)->minWidth;
+ maxSumCols += colExtremes->getRef(col+i)->maxWidth;
+ }
+#ifdef DBG
+ printf("cs=%d spanWidth=%d,%d sumCols=%d,%d\n",
+ cs,cellMinW,cellMaxW,minSumCols,maxSumCols);
+#endif
+ if (minSumCols >= cellMinW && maxSumCols >= cellMaxW)
+ continue;
+
+ // Cell size is too small; apportion {min,max} for this colspan.
+ int spanMinW = MAX (MAX(cs, minSumCols),
+ cellMinW - (cs-1) * getStyle()->hBorderSpacing),
+ spanMaxW = MAX (MAX(cs, maxSumCols),
+ cellMaxW - (cs-1) * getStyle()->hBorderSpacing);
+
+ if (minSumCols == 0) {
+ // No single cells defined for this span => pre-apportion equally
+ minSumCols = spanMinW; maxSumCols = spanMaxW;
+ int minW = spanMinW, maxW = spanMaxW;
+ for (int i = 0; i < cs; ++i) {
+ colExtremes->getRef(col+i)->minWidth = minW / (cs - i);
+ colExtremes->getRef(col+i)->maxWidth = maxW / (cs - i);
+ minW -= colExtremes->getRef(col+i)->minWidth;
+ maxW -= colExtremes->getRef(col+i)->maxWidth;
+ }
+ }
+
+ // This numbers will help if the span has percents.
+ int spanHasColPercent = 0;
+ int availSpanMinW = spanMinW;
+ float cumSpanPercent = 0.0f;
+ for (int i = col; i < col + cs; ++i) {
+ if (colPercents->get(i) > 0.0f) {
+ cumSpanPercent += colPercents->get(i);
+ ++spanHasColPercent;
+ } else
+ availSpanMinW -= colExtremes->getRef(i)->minWidth;
+ }
+
+ // Calculate weighted-apportion columns for this span.
+ int wMin = 0, wMax;
+ int cumMaxWnew = 0, cumMaxWold = 0, goalMaxW = spanMaxW;
+ int curAppW = maxSumCols;
+ int curExtraW = spanMinW - minSumCols;
+ for (int i = col; i < col + cs; ++i) {
+
+ if (!spanHasColPercent) {
+ int d_a = colExtremes->getRef(i)->maxWidth;
+ int d_w = curAppW > 0 ? (int)((float)curExtraW * d_a/curAppW) : 0;
+ if (d_a < 0||d_w < 0) {
+ printf("d_a=%d d_w=%d\n",d_a,d_w);
+ exit(1);
+ }
+ wMin = colExtremes->getRef(i)->minWidth + d_w;
+ colExtremes->getRef(i)->minWidth = wMin;
+ curExtraW -= d_w;
+ curAppW -= d_a;
+ } else {
+ if (colPercents->get(i) > 0.0f) {
+ wMin = MAX (colExtremes->getRef(i)->minWidth,
+ (int)(availSpanMinW
+ * colPercents->get(i)/cumSpanPercent));
+ colExtremes->getRef(i)->minWidth = wMin;
+ }
+ }
+
+ wMax = (goalMaxW-cumMaxWnew <= 0) ? 0 :
+ (int)((float)(goalMaxW-cumMaxWnew)
+ * colExtremes->getRef(i)->maxWidth
+ / (maxSumCols-cumMaxWold));
+ wMax = MAX (wMin, wMax);
+ cumMaxWnew += wMax;
+ cumMaxWold += colExtremes->getRef(i)->maxWidth;
+ colExtremes->getRef(i)->maxWidth = wMax;
+#ifdef DBG
+ printf ("i=%d, wMin=%d wMax=%d cumMaxWold=%d\n",
+ i,wMin,wMax,cumMaxWold);
+#endif
+ }
+#ifdef DBG
+ printf ("col min,max: [");
+ for (int i = 0; i < numCols; i++)
+ printf ("%d,%d ",
+ colExtremes->getRef(i)->minWidth,
+ colExtremes->getRef(i)->maxWidth);
+ printf ("]\n");
+ printf ("getStyle()->hBorderSpacing = %d\n", getStyle()->hBorderSpacing);
+#endif
+ }
+}
+
+/**
+ * \brief Apportionment function for AUTO-length columns.
+ * 'extremes' comes filled, 'result' comes defined for percentage columns.
+ */
+void Table::apportion2 (int totalWidth, int forceTotalWidth)
+{
+ if (colExtremes->size() == 0)
+ return;
+#ifdef DBG
+ printf("app2, availWidth=%d, totalWidth=%d, forceTotalWidth=%d\n",
+ availWidth, totalWidth, forceTotalWidth);
+ printf("app2, extremes: ( ");
+ for (int i = 0; i < colExtremes->size (); i++)
+ printf("%d,%d ",
+ colExtremes->get(i).minWidth, colExtremes->get(i).maxWidth);
+ printf(")\n");
+#endif
+ int minAutoWidth = 0, maxAutoWidth = 0, availAutoWidth = totalWidth;
+ for (int col = 0; col < numCols; col++) {
+ if (colPercents->get(col) == LEN_ABS) { // set absolute lengths
+ setColWidth (col, colExtremes->get(col).minWidth);
+ }
+ if (colPercents->get(col) == LEN_AUTO) {
+ maxAutoWidth += colExtremes->get(col).maxWidth;
+ minAutoWidth += colExtremes->get(col).minWidth;
+ } else
+ availAutoWidth -= colWidths->get(col);
+ }
+
+ if (!maxAutoWidth) // no LEN_AUTO cols!
+ return;
+
+ colWidths->setSize (colExtremes->size (), 0);
+
+ if (!forceTotalWidth && maxAutoWidth < availAutoWidth) {
+ // Enough space for the maximum table, don't widen past max.
+ availAutoWidth = maxAutoWidth;
+ }
+
+ // General case.
+ int curTargetWidth = MAX (availAutoWidth, minAutoWidth);
+ int curExtraWidth = curTargetWidth - minAutoWidth;
+ int curMaxWidth = maxAutoWidth;
+ int curNewWidth = minAutoWidth;
+ for (int col = 0; col < numCols; col++) {
+#ifdef DBG
+ printf("app2, col %d, minWidth=%d maxWidth=%d\n",
+ col,extremes->get(col).minWidth, colExtremes->get(col).maxWidth);
+#endif
+ if (colPercents->get(col) != LEN_AUTO)
+ continue;
+
+ int colMinWidth = colExtremes->getRef(col)->minWidth;
+ int colMaxWidth = colExtremes->getRef(col)->maxWidth;
+ int w = (curMaxWidth <= 0) ? 0 :
+ (int)((float)curTargetWidth * colMaxWidth/curMaxWidth);
+#ifdef DBG
+ printf("app2, curTargetWidth=%d colMaxWidth=%d curMaxWidth=%d "
+ "curNewWidth=%d ",
+ curTargetWidth, colMaxWidth,curMaxWidth,curNewWidth);
+ printf("w = %d, ", w);
+#endif
+ if (w <= colMinWidth)
+ w = colMinWidth;
+ else if (curNewWidth - colMinWidth + w > curTargetWidth)
+ w = colMinWidth + curExtraWidth;
+#ifdef DBG
+ printf("w = %d\n", w);
+#endif
+ curNewWidth -= colMinWidth;
+ curMaxWidth -= colMaxWidth;
+ curExtraWidth -= (w - colMinWidth);
+ curTargetWidth -= w;
+ setColWidth (col, w);
+ }
+#ifdef DBG
+ printf("app2, result: ( ");
+ for (int i = 0; i < colWidths->size (); i++)
+ printf("%d ", colWidths->get (i));
+ printf(")\n");
+#endif
+}
+
+void Table::apportion_percentages2(int totalWidth, int forceTotalWidth)
+{
+ int hasTablePercent = core::style::isPerLength (getStyle()->width) ? 1 : 0;
+
+ if (colExtremes->size() == 0 || (!hasTablePercent && !hasColPercent))
+ return;
+
+ // If there's a table-wide percentage, totalWidth comes already scaled.
+#ifdef DBG
+ printf("APP_P, availWidth=%d, totalWidth=%d, forceTotalWidth=%d\n",
+ availWidth, totalWidth, forceTotalWidth);
+#endif
+
+ if (!hasColPercent) {
+#ifdef DBG
+ printf("APP_P, only a table-wide percentage\n");
+ printf("APP_P, extremes = { ");
+ for (int col = 0; col < numCols; col++)
+ printf("%d,%d ", colExtremes->getRef(col)->minWidth,
+ colExtremes->getRef(col)->maxWidth);
+ printf("}\n");
+#endif
+ // It has only a table-wide percentage. Apportion non-absolute widths.
+ int sumMaxWidth = 0, perAvailWidth = totalWidth;
+ for (int col = 0; col < numCols; col++) {
+ if (colPercents->get(col) == LEN_ABS)
+ perAvailWidth -= colExtremes->getRef(col)->maxWidth;
+ else
+ sumMaxWidth += colExtremes->getRef(col)->maxWidth;
+ }
+#ifdef DBG
+ printf("APP_P, perAvailWidth=%d, sumMaxWidth=%d\n",
+ perAvailWidth, sumMaxWidth);
+#endif
+ for (int col = 0; col < numCols; col++) {
+ int max_wi = colExtremes->getRef(col)->maxWidth, new_wi;
+ if (colPercents->get(col) != LEN_ABS) {
+ new_wi = MAX (colExtremes->getRef(col)->minWidth,
+ (int)((float)max_wi * perAvailWidth/sumMaxWidth));
+ setColWidth (col, new_wi);
+ perAvailWidth -= new_wi;
+ sumMaxWidth -= max_wi;
+ }
+ }
+#ifdef DBG
+ printf("APP_P, result = { ");
+ for (int col = 0; col < numCols; col++)
+ printf("%d ", result->get(col));
+ printf("}\n");
+#endif
+
+ } else {
+ // we'll have to apportion...
+#ifdef DBG
+ printf("APP_P, we'll have to apportion...\n");
+#endif
+ // Calculate cumPercent and available space
+ float cumPercent = 0.0f;
+ int hasAutoCol = 0;
+ int sumMinWidth = 0, sumMaxWidth = 0, sumMinNonPer = 0, sumMaxNonPer = 0;
+ for (int col = 0; col < numCols; col++) {
+ if (colPercents->get(col) > 0.0f) {
+ cumPercent += colPercents->get(col);
+ } else {
+ sumMinNonPer += colExtremes->getRef(col)->minWidth;
+ sumMaxNonPer += colExtremes->getRef(col)->maxWidth;
+ hasAutoCol += (colPercents->get(col) == LEN_AUTO);
+ }
+ sumMinWidth += colExtremes->getRef(col)->minWidth;
+ sumMaxWidth += colExtremes->getRef(col)->maxWidth;
+#ifdef DBG
+ printf("APP_P, col %d minWidth=%d maxWidth=%d\n", col,
+ colExtremes->getRef(col)->minWidth,
+ colExtremes->getRef(col)->maxWidth);
+#endif
+ }
+ int oldTotalWidth = totalWidth;
+ if (!forceTotalWidth) {
+ if (sumMaxNonPer == 0 || cumPercent < 0.99f) {
+ // only percentage columns, or cumPercent < 100% => restrict width
+ int totW = (int)(sumMaxNonPer/(1.0f-cumPercent));
+ for (int col = 0; col < numCols; col++) {
+ totW = MAX (totW, (int)(colExtremes->getRef(col)->maxWidth
+ / colPercents->get(col)));
+ }
+ totalWidth = misc::min (totW, totalWidth);
+ }
+ }
+
+ // make sure there's enough space
+ totalWidth = MAX (totalWidth, sumMinWidth);
+ // extraWidth is always >= 0
+ int extraWidth = totalWidth - sumMinWidth;
+ int sumMinWidthPer = sumMinWidth - sumMinNonPer;
+ int curPerWidth = sumMinWidthPer;
+ // percentages refer to workingWidth
+ int workingWidth = totalWidth - sumMinNonPer;
+ if (cumPercent < 0.99f) {
+ // In this case, use the whole table width
+ workingWidth = totalWidth;
+ curPerWidth = sumMinWidth;
+ }
+#ifdef DBG
+ printf("APP_P, oldTotalWidth=%d totalWidth=%d"
+ " workingWidth=%d extraWidth=%d sumMinNonPer=%d\n",
+ oldTotalWidth,totalWidth,workingWidth,extraWidth,sumMinNonPer);
+#endif
+ for (int col = 0; col < numCols; col++) {
+ int colMinWidth = colExtremes->getRef(col)->minWidth;
+ if (colPercents->get(col) >= 0.0f) {
+ int w = (int)(workingWidth * colPercents->get(col));
+ if (w < colMinWidth)
+ w = colMinWidth;
+ else if (curPerWidth - colMinWidth + w > workingWidth)
+ w = colMinWidth + extraWidth;
+ extraWidth -= (w - colMinWidth);
+ curPerWidth += (w - colMinWidth);
+ setColWidth (col, w);
+ } else {
+ setColWidth (col, colMinWidth);
+ }
+ }
+
+ if (cumPercent < 0.99f) {
+ // Will have to apportion the other columns
+#ifdef DBG
+ printf("APP_P, extremes: ( ");
+ for (int i = 0; i < colExtremes->size (); i++)
+ printf("%d,%d ",
+ colExtremes->get(i).minWidth, colExtremes->get(i).maxWidth);
+ printf(")\n");
+#endif
+ curPerWidth -= sumMinNonPer;
+ int perWidth = (int)(curPerWidth/cumPercent);
+ totalWidth = MAX (totalWidth, perWidth);
+ totalWidth = misc::min (totalWidth, oldTotalWidth);
+#ifdef DBG
+ printf("APP_P, curPerWidth=%d perWidth=%d, totalWidth=%d\n",
+ curPerWidth, perWidth, totalWidth);
+#endif
+ if (hasAutoCol == 0) {
+ // Special case, cumPercent < 100% and no other columns to expand.
+ // We'll honor totalWidth by expanding the percentage cols.
+ int extraWidth = totalWidth - curPerWidth - sumMinNonPer;
+ for (int col = 0; col < numCols; col++) {
+ if (colPercents->get(col) >= 0.0f) {
+ int d = (int)(extraWidth * colPercents->get(col)/cumPercent);
+ setColWidth (col, colWidths->get(col) + d);
+ }
+ }
+ }
+ }
+#ifdef DBG
+ printf("APP_P, result ={ ");
+ for (int col = 0; col < numCols; col++)
+ printf("%d ", colWidths->get(col));
+ printf("}\n");
+#endif
+ apportion2 (totalWidth, 2);
+
+#ifdef DBG
+ printf("APP_P, percent={");
+ for (int col = 0; col < numCols; col++)
+ printf("%f ", colPercents->get(col));
+ printf("}\n");
+ printf("APP_P, result ={ ");
+ for (int col = 0; col < numCols; col++)
+ printf("%d ", colWidths->get(col));
+ printf("}\n");
+#endif
+ }
+}
+
+// ----------------------------------------------------------------------
+
+Table::TableIterator::TableIterator (Table *table,
+ core::Content::Type mask, bool atEnd):
+ core::Iterator (table, mask, atEnd)
+{
+ index = atEnd ? table->children->size () : -1;
+ content.type = atEnd ? core::Content::END : core::Content::START;
+}
+
+Table::TableIterator::TableIterator (Table *table,
+ core::Content::Type mask, int index):
+ core::Iterator (table, mask, false)
+{
+ this->index = index;
+
+ if (index < 0)
+ content.type = core::Content::START;
+ else if (index >= table->children->size ())
+ content.type = core::Content::END;
+ else {
+ content.type = core::Content::WIDGET;
+ content.widget = table->children->get(index)->cell.widget;
+ }
+}
+
+object::Object *Table::TableIterator::clone()
+{
+ return new TableIterator ((Table*)getWidget(), getMask(), index);
+}
+
+int Table::TableIterator::compareTo(misc::Comparable *other)
+{
+ return index - ((TableIterator*)other)->index;
+}
+
+bool Table::TableIterator::next ()
+{
+ Table *table = (Table*)getWidget();
+
+ if (content.type == core::Content::END)
+ return false;
+
+ // tables only contain widgets:
+ if ((getMask() & core::Content::WIDGET) == 0) {
+ content.type = core::Content::END;
+ return false;
+ }
+
+ do {
+ index++;
+ if (index >= table->children->size ()) {
+ content.type = core::Content::END;
+ return false;
+ }
+ } while (table->children->get(index) == NULL ||
+ table->children->get(index)->type != Child::CELL);
+
+ content.type = core::Content::WIDGET;
+ content.widget = table->children->get(index)->cell.widget;
+ return true;
+}
+
+bool Table::TableIterator::prev ()
+{
+ Table *table = (Table*)getWidget();
+
+ if (content.type == core::Content::START)
+ return false;
+
+ // tables only contain widgets:
+ if ((getMask() & core::Content::WIDGET) == 0) {
+ content.type = core::Content::START;
+ return false;
+ }
+
+ do {
+ index--;
+ if (index < 0) {
+ content.type = core::Content::START;
+ return false;
+ }
+ } while (table->children->get(index) == NULL ||
+ table->children->get(index)->type != Child::CELL);
+
+ content.type = core::Content::WIDGET;
+ content.widget = table->children->get(index)->cell.widget;
+ return true;
+}
+
+void Table::TableIterator::highlight (int start, int end,
+ core::HighlightLayer layer)
+{
+ /** todo Needs this an implementation? */
+}
+
+void Table::TableIterator::unhighlight (int direction,
+ core::HighlightLayer layer)
+{
+}
+
+void Table::TableIterator::getAllocation (int start, int end,
+ core::Allocation *allocation)
+{
+ /** \bug Not implemented. */
+}
+
+} // namespace dw
diff --git a/dw/table.hh b/dw/table.hh
new file mode 100644
index 00000000..ec2bacc8
--- /dev/null
+++ b/dw/table.hh
@@ -0,0 +1,486 @@
+#ifndef __DW_TABLE_HH__
+#define __DW_TABLE_HH__
+
+#include "core.hh"
+#include "tablecell.hh"
+#include "../lout/misc.hh"
+
+namespace dw {
+
+/**
+ * \brief A Widget for rendering tables.
+ *
+ * <h3>Introduction</h3>
+ *
+ * The dw::Table widget is used to render HTML tables.
+ *
+ * Each cell is itself an own widget. Any widget may be used, however, in
+ * dillo, only instances of dw::Textblock and dw::TableCell are used as
+ * children of dw::Table.
+ *
+ *
+ * <h3>Sizes</h3>
+ *
+ * <h4>General</h4>
+ *
+ * The following diagram shows the dependencies between the different
+ * functions, which are related to size calculation. Click on the boxes
+ * for more informations.
+ *
+ * \dot
+ * digraph G {
+ * node [shape=record, fontname=Helvetica, fontsize=10, color="#c0c0c0"];
+ * edge [arrowhead="open", arrowtail="none", labelfontname=Helvetica,
+ * labelfontsize=10, color="#404040", labelfontcolor="#000080",
+ * fontname=Helvetica, fontsize=10];
+ * fontname=Helvetica; fontsize=10;
+ *
+ * sizeRequestImpl [color="#0000ff", URL="\ref dw::Table::sizeRequestImpl"];
+ * sizeAllocateImpl [color="#0000ff",
+ * URL="\ref dw::Table::sizeAllocateImpl"];
+ * getExtremesImpl [color="#0000ff", URL="\ref dw::Table::getExtremesImpl"];
+ *
+ * subgraph cluster_sizes {
+ * style="dashed"; color="#8080c0";
+ * calcCellSizes [URL="\ref dw::Table::calcCellSizes"];
+ * forceCalcCellSizes [URL="\ref dw::Table::forceCalcCellSizes"];
+ * }
+ *
+ * subgraph cluster_extremes {
+ * style="dashed"; color="#8080c0";
+ * calcColumnExtremes [URL="\ref dw::Table::calcColumnExtremes"];
+ * forceCalcColumnExtremes[URL="\ref dw::Table::forceCalcColumnExtremes"];
+ * }
+ *
+ * sizeRequestImpl -> forceCalcCellSizes [label="[B]"];
+ * sizeAllocateImpl -> calcCellSizes [label="[A]"];
+ * getExtremesImpl -> forceCalcColumnExtremes [label="[B]"];
+ *
+ * forceCalcCellSizes -> calcColumnExtremes;
+ *
+ * calcCellSizes -> forceCalcCellSizes [style="dashed", label="[C]"];
+ * calcColumnExtremes -> forceCalcColumnExtremes [style="dashed",
+ * label="[C]"];
+ * }
+ * \enddot
+ *
+ * [A] In this case, the new calculation is \em not forced, but only
+ * done, when necessary.
+ *
+ * [B] In this case, the new calculation is allways necessary, since [C]
+ * is the case.
+ *
+ * [C] Whether this function is called, depends on NEEDS_RESIZE /
+ * EXTREMES_CHANGED.
+ *
+ *
+ * <h4>Apportionment</h4>
+ *
+ * \sa\ref rounding-errors
+ *
+ * Given two array \f$e_{i,\min}\f$ and \f$e_{i,\max}\f$, which
+ * represent the column minima and maxima, and a total width \f$W\f$, \em
+ * apportionment means to calculate column widths \f$w_{i}\f$, with
+ *
+ * \f[e_{i,\min} \le w_{i} \le e_{i,\max}\f]
+ *
+ * and
+ *
+ * \f[\sum w_{i} = W\f]
+ *
+ * There are different algorithms for apportionment, a simple one is
+ * recommended in the HTML 4.0.1 specification
+ * (http://www.w3.org/TR/REC-html40/appendix/notes.html#h-B.5.2.2):
+ *
+ * \f[w_{i} = e_{i,\min} +
+ * {e_{i,\max} - e_{i,\min}\over\sum e_{i,\max} - \sum e_{i,\min}}
+ * (W - \sum e_{i,\min})\f]
+ *
+ * This one is used currently, but another one will be used soon, which is
+ * described below. The rest of this chapter is independant of the exact
+ * apportionment algorithm.
+ *
+ * When referring to the apportionment function, we will call it
+ * \f$a_i (W, (e_{i,\min}), (e_{i,\min}))\f$ and write
+ * something like this:
+ *
+ * \f[w_{i} = a_i (W, (e_{i,\min}), (e_{i,\max})) \f]
+ *
+ * It is implemented by dw::Table::apportion.
+ *
+ * <h4>Column Extremes</h4>
+ *
+ * \sa\ref rounding-errors
+ *
+ * The sizes, which all other sizes depend on, are column extremes, which
+ * define, how wide a column may be at min and at max. They are
+ * calculated in the following way:
+ *
+ * <ol>
+ * <li> First, only cells with colspan = 1 are regarded:
+ *
+ * \f[ e_{\hbox{base},i,\min} = \max \{ e_{\hbox{cell},i,j,\min} \} \f]
+ * \f[ e_{\hbox{base},i,\max} = \max \{ e_{\hbox{cell},i,j,\max} \} \f]
+ *
+ * only for cells \f$(i, j)\f$ with colspan = 1.
+ *
+ * <li> Then,
+ * \f$e_{\hbox{span},i,\min}\f$ (but not \f$e_{\hbox{span},i,\max}\f$)
+ * are calculated from cells with colspan > 1. (In the following formulas,
+ * the cell at \f$(i_1, j)\f$ always span from \f$i_1\f$ to \f$i_2\f$.)
+ * If the minimal width of the column exeeds the sum of the column minima
+ * calculated in the last step:
+ *
+ * \f[e_{\hbox{cell},i_1,j,\min} >
+ * \sum_{i=i_1}^{i=i_2} e_{\hbox{base},i,\min}\f]
+ *
+ * then the minimal width of this cell is apportioned to the columns:
+ *
+ * <ul>
+ * <li> If the minimal width of this cell also exeeds the sum of the
+ * column maxima:
+ *
+ * \f[e_{\hbox{cell},i_1,j,\min} >
+ * \sum_{i=i_1}^{i=i_2} e_{\hbox{base},i,\max}\f]
+ *
+ * then \f$e_{\hbox{cell},i_1,j,\min}\f$ is apportioned in a simple
+ * way:
+ *
+ * \f[e_{\hbox{span},i,j,\min} =
+ * e_{\hbox{base},i,\max}
+ * {e_{\hbox{span},i,j,\min} \over
+ * \sum_{i=i_1}^{i=i_2} e_{\hbox{base},i,\max}}\f]
+ *
+ * <li> Otherwise, the apportionment function is used:
+ *
+ * \f[e_{\hbox{span},i,j,\min} =
+ * a_i (e_{\hbox{cell},i_1,j,\min},
+ * (e_{\hbox{cell},i_1,j,\min} \ldots
+ * e_{\hbox{cell},i_2,j,\min}),
+ * (e_{\hbox{cell},i_1,j,\max} \ldots
+ * e_{\hbox{cell},i_2,j,\max}))\f]
+ * </ul>
+ *
+ * After this, \f$e_{\hbox{span},i,\min}\f$ is then the maximum of all
+ * \f$e_{\hbox{span},i,j,\min}\f$.
+ *
+ * <li> Finally, the maximum of both is used.
+ * \f[ e_{i,\min} =
+ * \max \{ e_{\hbox{base},i,\min}, e_{\hbox{span},i,\min} \} \f]
+ * \f[ e_{i,\max} =
+ * \max \{ e_{\hbox{base},i,\max}, e_{i,\min} \} \f]
+ * For the maxima, there is no \f$e_{\hbox{span},i,\max}\f$, but it has to
+ * be assured, that the maximum is always greater or equal than/to the
+ * minimum.
+ * </ol>
+ *
+ * Generally, if absolute widths are speficied, they are, instead of the
+ * results of dw::core::Widget::getExtremes, taken for the minimal and
+ * maximal width of a cell (minus the box difference, i.e. the difference
+ * between content size and widget size). If the content width
+ * specification is smaller than the minimal content width of the widget
+ * (determined by dw::core::Widget::getExtremes), the latter is used
+ * instead.
+ *
+ * If percentage widths are specified, they are also collected, as column
+ * maxima. A similar method as for the extremes is used, for cells with
+ * colspan > 1:
+ *
+ * \f[w_{\hbox{span},i,j,\%} =
+ * a_i (w_{\hbox{cell},i_1,j,\%},
+ * (e_{\hbox{cell},i_1,j,\min} \ldots e_{\hbox{cell},i_2,j,\min}),
+ * (e_{\hbox{cell},i_1,j,\max} \ldots e_{\hbox{cell},i_2,j,\max}))\f]
+ *
+ * <h4>Cell Sizes</h4>
+ *
+ * <h5>Determining the Width of the Table</h5>
+ *
+ * The total width is
+ *
+ * <ul>
+ * <li> the specified absolute width of the table, when given, or
+ * <li> the available width (set by dw::Table::setWidth) times the specifies
+ * percentage width pf t(at max 100%), if the latter is given, or
+ * <li> otherwise the available width.
+ * </ul>
+ *
+ * In any case, it is corrected, if it is less than the minimal width
+ * (but not if it is greater than the maximal width).
+ *
+ * \bug The parantheses is not fully clear, look at the old code.
+ *
+ * Details on differences because of styles are omitted. Below, this
+ * total width is called \f$W\f$.
+ *
+ * <h5>Evaluating percentages</h5>
+ *
+ * The following algorithms are used to solve collisions between
+ * different size specifications (absolute and percentage). Generally,
+ * inherent sizes and specified absolute sizes are preferred.
+ *
+ * <ol>
+ * <li> First, calculate the sum of the minimal widths, for columns, where
+ * no percentage width has been specified. The difference to the total
+ * width is at max available to the columns with percentage width
+ * specifications:
+ *
+ * \f[W_{\hbox{columns}_\%,\hbox{available}} = W - \sum e_{i,\min}\f]
+ *
+ * with only those columns \f$i\f$ with no percentage width specification.
+ *
+ * <li> Then, calculate the sum of the widths, which the columns with
+ * percentage width specification would allocate, when fully adhering to
+ * then:
+ *
+ * \f[W_{\hbox{columns}_\%,\hbox{best}} = W \sum w_{i,\%}\f]
+ *
+ * with only those columns \f$i\f$ with a percentage width specification.
+ *
+ * <li> Two cases are distinguished:
+ *
+ * <ul>
+ * <li> \f$W_{\hbox{columns}_\%,\hbox{available}} \ge
+ * W_{\hbox{columns}_\%,\hbox{best}}\f$: In this case, the
+ * percentage widths can be used without any modification, by
+ * setting the extremes:
+ *
+ * \f[e_{i,\min} = e_{i,\max} = W w_{i,\%}\f]
+ *
+ * for only those columns \f$i\f$ with a percentage width
+ * specification.
+ *
+ * <li> \f$W_{\hbox{columns}_\%,\hbox{available}} <
+ * W_{\hbox{columns}_\%,\hbox{best}}\f$: In this case, the widths
+ * for these columns must be cut down:
+ *
+ * \f[e_{i,\min} = e_{i,\max} =
+ * w_{i,\%}
+ * {W_{\hbox{columns}_\%,\hbox{available}} \over
+ * w_{\hbox{total},\%}}\f]
+ *
+ * with
+ *
+ * \f[w_{\hbox{total},\%} = \sum w_{i,\%}\f]
+ *
+ * in both cases for only those columns \f$i\f$ with a percentage
+ * width specification.
+ * </ul>
+ * </ol>
+ *
+ * (\f$e_{i,\min}\f$ and \f$e_{i,\max}\f$ are set \em temporarily here,
+ * the notation should be a bit clearer.)
+ *
+ *
+ * <h5>Column Widths</h5>
+ *
+ * The column widths are now simply calculated by applying the
+ * apportenment function.
+ *
+ *
+ * <h5>Row Heights</h5>
+ *
+ * ...
+ *
+ * <h3>Alternative Apportionment Algorithm</h3>
+ *
+ * The algorithm described here tends to result in more homogeneous column
+ * widths.
+ *
+ * The following rule lead to well-defined \f$w_{i}\f$: All columns
+ * \f$i\f$ have have the same width \f$w\f$, except:
+ * <ul>
+ * <li> \f$w < e_{i,\min}\f$, or
+ * <li> \f$w > e_{i,\max}\f$.
+ * </ul>
+ *
+ * Furthermore, \f$w\f$ is
+ * <ul>
+ * <li> less than all \f$e_{i,\min}\f$ of columns not having \f$w\f$ as
+ * width, and
+ * <li> greater than all \f$e_{i,\min}\f$ of columns not having \f$w\f$ as
+ * width.
+ * </ul>
+ *
+ * Of course, \f$\sum w_{i} = W\f$ must be the case.
+ *
+ * Based on an initial value \f$w = {W\over n}\f$, \f$w\f$ can iteratively
+ * adjusted, based on these rules.
+ *
+ *
+ * <h3>Borders, Paddings, Spacing</h3>
+ *
+ * Currently, DwTable supports only the separated borders model (see CSS
+ * specification). Borders, paddings, spacing is done by creating
+ * dw::core::style::Style structures with values equivalent to following CSS:
+ *
+ * <pre>
+ * TABLE {
+ * border: outset \em table-border;
+ * border-collapse: separate;
+ * border-spacing: \em table-cellspacing;
+ * background-color: \em table-bgcolor;
+ * }
+ *
+ * TD TH {
+ * border: inset \em table-border;
+ * padding: \em table-cellspacing;
+ * background-color: \em td/th-bgcolor;
+ * }
+ * </pre>
+ *
+ * Here, \em foo-bar refers to the attribute \em bar of the tag \em foo foo.
+ * Look at the HTML parser for more details.
+ */
+class Table: public core::Widget
+{
+private:
+
+ struct Child
+ {
+ enum {
+ CELL, // cell starts here
+ SPAN_SPACE // part of a spanning cell
+ } type;
+ union {
+ struct {
+ core::Widget *widget;
+ int colspanOrig, colspanEff, rowspan;
+ } cell;
+ struct {
+ int startCol, startRow; // where the cell starts
+ } spanSpace;
+ };
+ };
+
+ class TableIterator: public core::Iterator
+ {
+ private:
+ int index;
+
+ public:
+ TableIterator (Table *table, core::Content::Type mask, bool atEnd);
+ TableIterator (Table *table, core::Content::Type mask, int index);
+
+ object::Object *clone();
+ int compareTo(misc::Comparable *other);
+
+ bool next ();
+ bool prev ();
+ void highlight (int start, int end, core::HighlightLayer layer);
+ void unhighlight (int direction, core::HighlightLayer layer);
+ void getAllocation (int start, int end, core::Allocation *allocation);
+ };
+
+ friend class TableIterator;
+
+ bool limitTextWidth, rowClosed;
+ int availWidth, availAscent, availDescent; // set by set...
+
+ int numRows, numCols, curRow, curCol;
+ misc::SimpleVector<Child*> *children;
+
+ int redrawX, redrawY;
+
+ /**
+ * \brief The extremes of all columns.
+ */
+ misc::SimpleVector<core::Extremes> *colExtremes;
+
+ /**
+ * \brief The widths of all columns.
+ */
+ misc::SimpleVector<int> *colWidths;
+
+ /**
+ * Row cumulative height array: cumHeight->size() is numRows + 1,
+ * cumHeight->get(0) is 0, cumHeight->get(numRows) is the total table
+ * height.
+ */
+ misc::SimpleVector<int> *cumHeight;
+ /**
+ * If a Cell has rowspan > 1, it goes into this array
+ */
+ misc::SimpleVector<int> *rowSpanCells;
+ /**
+ * If a Cell has colspan > 1, it goes into this array
+ */
+ misc::SimpleVector<int> *colSpanCells;
+ misc::SimpleVector<int> *baseline;
+
+ misc::SimpleVector<core::style::Style*> *rowStyle;
+
+ /**
+ * hasColPercent becomes true when any cell specifies a percentage width.
+ * A negative value in colPercents means LEN_AUTO or LEN_ABS.
+ */
+ enum { LEN_AUTO = -1, LEN_ABS = -2};
+ int hasColPercent;
+ misc::SimpleVector<float> *colPercents;
+
+ inline bool childDefined(int n)
+ {
+ return n < children->size() && children->get(n) != NULL &&
+ children->get(n)->type != Child::SPAN_SPACE;
+ }
+
+ void reallocChildren (int newNumCols, int newNumRows);
+
+ void calcCellSizes ();
+ void forceCalcCellSizes ();
+ void apportionRowSpan ();
+
+ void calcColumnExtremes ();
+ void forceCalcColumnExtremes ();
+
+ void apportion2 (int totalWidth, int forceTotalWidth);
+ void apportion_percentages2 (int totalWidth, int forceTotalWidth);
+
+ void setCumHeight (int row, int value)
+ {
+ if (value != cumHeight->get (row)) {
+ redrawY = misc::min ( redrawY, value );
+ cumHeight->set (row, value);
+ }
+ }
+
+ inline void setColWidth (int col, int value)
+ {
+ if (value != colWidths->get (col)) {
+ redrawX = misc::min (redrawX, value);
+ colWidths->set (col, value);
+ }
+ }
+
+protected:
+ void sizeRequestImpl (core::Requisition *requisition);
+ void getExtremesImpl (core::Extremes *extremes);
+ void sizeAllocateImpl (core::Allocation *allocation);
+ void resizeDrawImpl ();
+
+ void setWidth (int width);
+ void setAscent (int ascent);
+ void setDescent (int descent);
+ void draw (core::View *view, core::Rectangle *area);
+
+ //bool buttonPressImpl (core::EventButton *event);
+ //bool buttonReleaseImpl (core::EventButton *event);
+ //bool motionNotifyImpl (core::EventMotion *event);
+
+ void removeChild (Widget *child);
+
+public:
+ static int CLASS_ID;
+
+ Table(bool limitTextWidth);
+ ~Table();
+
+ core::Iterator *iterator (core::Content::Type mask, bool atEnd);
+
+ void addCell (Widget *widget, int colspan, int rowspan);
+ void addRow (core::style::Style *style);
+ TableCell *getCellRef ();
+};
+
+} // namespace dw
+
+#endif // __DW_TABLE_HH__
diff --git a/dw/tablecell.cc b/dw/tablecell.cc
new file mode 100644
index 00000000..b4d404f7
--- /dev/null
+++ b/dw/tablecell.cc
@@ -0,0 +1,108 @@
+/*
+ * Dillo Widget
+ *
+ * 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.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+
+
+#include "tablecell.hh"
+#include <stdio.h>
+
+namespace dw {
+
+int TableCell::CLASS_ID = -1;
+
+TableCell::TableCell (TableCell *ref, bool limitTextWidth):
+ AlignedTextblock (limitTextWidth)
+{
+ registerName ("dw::TableCell", &CLASS_ID);
+
+ /** \bug ignoreLine1OffsetSometimes does not work? */
+ //ignoreLine1OffsetSometimes = true;
+ charWordIndex = -1;
+ setRefTextblock (ref);
+}
+
+TableCell::~TableCell()
+{
+}
+
+void TableCell::wordWrap(int wordIndex)
+{
+ Textblock::Word *word;
+ char *p;
+
+ Textblock::wordWrap (wordIndex);
+
+ if (charWordIndex == -1) {
+ word = words->getRef (wordIndex);
+ if (word->content.type == core::Content::TEXT) {
+ if ((p = strchr (word->content.text,
+ word->style->textAlignChar))) {
+ charWordIndex = wordIndex;
+ charWordPos = p - word->content.text + 1;
+ } else if (word->style->textAlignChar == ' ' &&
+ word->content.space) {
+ charWordIndex = wordIndex + 1;
+ charWordPos = 0;
+ }
+ }
+ }
+
+ if (wordIndex == charWordIndex)
+ updateValue ();
+}
+
+int TableCell::getValue ()
+{
+ Textblock::Word *word;
+ int i, wordIndex;
+ int w;
+
+ if (charWordIndex == -1)
+ wordIndex = words->size () -1;
+ else
+ wordIndex = charWordIndex;
+
+ w = 0;
+ for (i = 0; i < wordIndex; i++) {
+ word = words->getRef (i);
+ w += word->size.width + word->origSpace;
+ }
+
+ if (charWordIndex == -1) {
+ if (words->size () > 0) {
+ word = words->getRef (words->size () - 1);
+ w += word->size.width;
+ }
+ } else {
+ word = words->getRef (charWordIndex);
+ w += layout->textWidth (word->style->font, word->content.text,
+ charWordPos);
+ }
+
+ return w;
+}
+
+void TableCell::setMaxValue (int maxValue, int value)
+{
+ line1Offset = maxValue - value;
+ queueResize (0, true);
+}
+
+} // namespace dw
diff --git a/dw/tablecell.hh b/dw/tablecell.hh
new file mode 100644
index 00000000..318d1f4e
--- /dev/null
+++ b/dw/tablecell.hh
@@ -0,0 +1,29 @@
+#ifndef __DW_TABLECELL_HH__
+#define __DW_TABLECELL_HH__
+
+#include "core.hh"
+#include "alignedtextblock.hh"
+
+namespace dw {
+
+class TableCell: public AlignedTextblock
+{
+private:
+ int charWordIndex, charWordPos;
+
+protected:
+ void wordWrap(int wordIndex);
+
+ int getValue ();
+ void setMaxValue (int maxValue, int value);
+
+public:
+ static int CLASS_ID;
+
+ TableCell(TableCell *ref, bool limitTextWidth);
+ ~TableCell();
+};
+
+} // namespace dw
+
+#endif // __DW_TABLECELL_HH__
diff --git a/dw/textblock.cc b/dw/textblock.cc
new file mode 100644
index 00000000..0ada8027
--- /dev/null
+++ b/dw/textblock.cc
@@ -0,0 +1,2087 @@
+/*
+ * Dillo Widget
+ *
+ * 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.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+
+
+#include "textblock.hh"
+#include "../lout/misc.hh"
+
+#include <stdio.h>
+#include <limits.h>
+
+namespace dw {
+
+int Textblock::CLASS_ID = -1;
+
+Textblock::Textblock (bool limitTextWidth)
+{
+ registerName ("dw::Textblock", &CLASS_ID);
+ setFlags (USES_HINTS);
+
+ listItem = false;
+ innerPadding = 0;
+ line1Offset = 0;
+ line1OffsetEff = 0;
+ ignoreLine1OffsetSometimes = false;
+ mustQueueResize = false;
+ redrawY = 0;
+ lastWordDrawn = 0;
+
+ /*
+ * The initial sizes of lines and words should not be
+ * too high, since this will waste much memory with tables
+ * containing many small cells. The few more calls to realloc
+ * should not decrease the speed considerably.
+ * (Current setting is for minimal memory usage. An interesting fact
+ * is that high values decrease speed due to memory handling overhead!)
+ * todo: Some tests would be useful.
+ */
+ lines = new misc::SimpleVector <Line> (1);
+ words = new misc::SimpleVector <Word> (1);
+
+ //DBG_OBJ_SET_NUM(page, "num_lines", num_lines);
+
+ lastLineWidth = 0;
+ lastLineParMin = 0;
+ lastLineParMax = 0;
+ wrapRef = -1;
+
+ //DBG_OBJ_SET_NUM(page, "last_line_width", last_line_width);
+ //DBG_OBJ_SET_NUM(page, "last_line_par_min", last_line_par_min);
+ //DBG_OBJ_SET_NUM(page, "last_line_par_max", last_line_par_max);
+ //DBG_OBJ_SET_NUM(page, "wrap_ref", wrap_ref);
+
+ hoverLink = -1;
+
+ // random values
+ availWidth = 100;
+ availAscent = 100;
+ availDescent = 0;
+
+ hoverTooltip = NULL;
+
+ this->limitTextWidth = limitTextWidth;
+
+ for (int layer = 0; layer < core::HIGHLIGHT_NUM_LAYERS; layer++) {
+ /* hlStart[layer].index > hlEnd[layer].index means no highlighting */
+ hlStart[layer].index = 1;
+ hlStart[layer].nChar = 0;
+ hlEnd[layer].index = 0;
+ hlEnd[layer].nChar = 0;
+ }
+}
+
+Textblock::~Textblock ()
+{
+ //_MSG ("Textblock::~Textblock\n");
+
+ for (int i = 0; i < words->size(); i++) {
+ Word *word = words->getRef (i);
+ if (word->content.type == core::Content::WIDGET)
+ delete word->content.widget;
+ else if (word->content.type == core::Content::ANCHOR)
+ /* This also frees the names (see removeAnchor() and related). */
+ removeAnchor(word->content.anchor);
+
+ word->style->unref ();
+ word->spaceStyle->unref ();
+ }
+
+ delete lines;
+ delete words;
+
+ /* Make sure we don't own widgets anymore. Necessary before call of
+ parent class destructor. (???) */
+ words = NULL;
+
+ //DBG_OBJ_SET_NUM(page, "num_lines", page->num_lines);
+}
+
+/**
+ * The ascent of a textblock is the ascent of the first line, plus
+ * padding/border/margin. This can be used to align the first lines
+ * of several textblocks in a horizontal line.
+ */
+void Textblock::sizeRequestImpl (core::Requisition *requisition)
+{
+ rewrap ();
+
+ if (lines->size () > 0) {
+ Line *lastLine = lines->getRef (lines->size () - 1);
+ requisition->width =
+ misc::max (lastLine->maxLineWidth, lastLineWidth);
+ /* Note: the break_space of the last line is ignored, so breaks
+ at the end of a textblock are not visible. */
+ requisition->ascent = lines->getRef(0)->ascent;
+ requisition->descent = lastLine->top
+ + lastLine->ascent + lastLine->descent - lines->getRef(0)->ascent;
+ } else {
+ requisition->width = lastLineWidth;
+ requisition->ascent = 0;
+ requisition->descent = 0;
+ }
+
+ requisition->width += innerPadding + getStyle()->boxDiffWidth ();
+ requisition->ascent += getStyle()->boxOffsetY ();
+ requisition->descent += getStyle()->boxRestHeight ();
+
+ if (requisition->width < availWidth)
+ requisition->width = availWidth;
+}
+
+/**
+ * Get the extremes of a word within a textblock.
+ */
+void Textblock::getWordExtremes (Word *word, core::Extremes *extremes)
+{
+ if (word->content.type == core::Content::WIDGET) {
+ if (word->content.widget->usesHints ())
+ word->content.widget->getExtremes (extremes);
+ else {
+ if (core::style::isPerLength
+ (word->content.widget->getStyle()->width)) {
+ extremes->minWidth = 0;
+ if (word->content.widget->hasContents ())
+ extremes->maxWidth = 1000000;
+ else
+ extremes->maxWidth = 0;
+ } else if (core::style::isAbsLength
+ (word->content.widget->getStyle()->width)) {
+ /* Fixed lengths are only applied to the content, so we have to
+ * add padding, border and margin. */
+ extremes->minWidth = extremes->maxWidth =
+ core::style::absLengthVal (word->content.widget->getStyle()
+ ->width)
+ + word->style->boxDiffWidth ();
+ } else
+ word->content.widget->getExtremes (extremes);
+ }
+ } else {
+ extremes->minWidth = word->size.width;
+ extremes->maxWidth = word->size.width;
+ }
+}
+
+void Textblock::getExtremesImpl (core::Extremes *extremes)
+{
+ core::Extremes wordExtremes;
+ Line *line;
+ Word *word;
+ int wordIndex, lineIndex;
+ int parMin, parMax;
+ bool nowrap;
+
+ //DBG_MSG (widget, "extremes", 0, "Dw_page_get_extremes");
+ //DBG_MSG_START (widget);
+
+ if (lines->size () == 0) {
+ /* empty page */
+ extremes->minWidth = 0;
+ extremes->maxWidth = 0;
+ } else if (wrapRef == -1) {
+ /* no rewrap necessary -> values in lines are up to date */
+ line = lines->getRef (lines->size () - 1);
+ /* Historical note: The former distinction between lines with and without
+ * words[first_word]->nowrap set is no longer necessary, since
+ * Dw_page_real_word_wrap sets max_word_min to the correct value in any
+ * case. */
+ extremes->minWidth = line->maxWordMin;
+ extremes->maxWidth = misc::max (line->maxParMax, lastLineParMax);
+ //DBG_MSG (widget, "extremes", 0, "simple case");
+ } else {
+ /* Calculate the extremes, based on the values in the line from
+ where a rewrap is necessary. */
+ //DBG_MSG (widget, "extremes", 0, "complex case");
+
+ if (wrapRef == 0) {
+ extremes->minWidth = 0;
+ extremes->maxWidth = 0;
+ parMin = 0;
+ parMax = 0;
+ } else {
+ line = lines->getRef (wrapRef);
+ extremes->minWidth = line->maxWordMin;
+ extremes->maxWidth = line->maxParMax;
+ parMin = line->parMin;
+ parMax = line->parMax;
+
+ //DBG_MSGF (widget, "extremes", 0, "parMin = %d", parMin);
+ }
+
+ //_MSG ("*** parMin = %d\n", parMin);
+
+ int prevWordSpace = 0;
+ for (lineIndex = wrapRef; lineIndex < lines->size (); lineIndex++) {
+ //DBG_MSGF (widget, "extremes", 0, "line %d", lineIndex);
+ //DBG_MSG_START (widget);
+
+ line = lines->getRef (lineIndex);
+ nowrap =
+ words->getRef(line->firstWord)->style->whiteSpace
+ != core::style::WHITE_SPACE_NORMAL;
+
+ //DEBUG_MSG (DEBUG_SIZE_LEVEL, " line %d (of %d), nowrap = %d\n",
+ // lineIndex, page->num_lines, nowrap);
+
+ for (wordIndex = line->firstWord; wordIndex < line->lastWord;
+ wordIndex++) {
+ word = words->getRef (wordIndex);
+ getWordExtremes (word, &wordExtremes);
+
+ /* For the first word, we simply add the line1_offset. */
+ if (ignoreLine1OffsetSometimes && wordIndex == 0) {
+ wordExtremes.minWidth += line1Offset;
+ //DEBUG_MSG (DEBUG_SIZE_LEVEL + 1,
+ // " (next plus %d)\n", page->line1_offset);
+ }
+
+ if (nowrap) {
+ parMin += prevWordSpace + wordExtremes.minWidth;
+ //DBG_MSGF (widget, "extremes", 0, "parMin = %d", parMin);
+ } else {
+ if (extremes->minWidth < wordExtremes.minWidth)
+ extremes->minWidth = wordExtremes.minWidth;
+ }
+
+ //printf("parMax = %d, wordMaxWidth=%d, prevWordSpace=%d\n",
+ // parMax, wordExtremes.maxWidth, prevWordSpace);
+ if (word->content.type != core::Content::BREAK)
+ parMax += prevWordSpace;
+ parMax += wordExtremes.maxWidth;
+ prevWordSpace = word->origSpace;
+
+ //DEBUG_MSG (DEBUG_SIZE_LEVEL + 1,
+ // " word %s: maxWidth = %d\n",
+ // a_Dw_content_text (&word->content),
+ // word_extremes.maxWidth);
+ }
+
+ if ((line->lastWord > line->firstWord &&
+ words->getRef(line->lastWord - 1)->content.type
+ == core::Content::BREAK ) ||
+ lineIndex == lines->size () - 1 ) {
+ word = words->getRef (line->lastWord - 1);
+
+ //DEBUG_MSG (DEBUG_SIZE_LEVEL + 2,
+ // " parMax = %d, after word %d (%s)\n",
+ // parMax, line->last_word - 1,
+ // a_Dw_content_text (&word->content));
+
+ if (extremes->maxWidth < parMax)
+ extremes->maxWidth = parMax;
+
+ if (nowrap) {
+ //DBG_MSGF (widget, "extremes", 0, "parMin = %d", parMin);
+ if (extremes->minWidth < parMin)
+ extremes->minWidth = parMin;
+
+ //DEBUG_MSG (DEBUG_SIZE_LEVEL + 2,
+ // " parMin = %d, after word %d (%s)\n",
+ // parMin, line->last_word - 1,
+ // a_Dw_content_text (&word->content));
+ }
+
+ prevWordSpace = 0;
+ parMin = 0;
+ parMax = 0;
+ }
+
+ //DBG_MSG_END (widget);
+ }
+
+ //DEBUG_MSG (DEBUG_SIZE_LEVEL + 3, " Result: %d, %d\n",
+ // extremes->minWidth, extremes->maxWidth);
+ }
+
+ //DBG_MSGF (widget, "extremes", 0, "width difference: %d + %d",
+ // page->inner_padding, p_Dw_style_box_diff_width (widget->style));
+
+ int diff = innerPadding + getStyle()->boxDiffWidth ();
+ extremes->minWidth += diff;
+ extremes->maxWidth += diff;
+
+ //DBG_MSG_END (widget);
+}
+
+
+void Textblock::sizeAllocateImpl (core::Allocation *allocation)
+{
+ int lineIndex, wordIndex;
+ Line *line;
+ Word *word;
+ int xCursor;
+ core::Allocation childAllocation;
+ core::Allocation *oldChildAllocation;
+ int wordInLine;
+
+ if (allocation->width != this->allocation.width) {
+ redrawY = 0;
+ }
+
+ for (lineIndex = 0; lineIndex < lines->size (); lineIndex++) {
+ line = lines->getRef (lineIndex);
+ xCursor = lineXOffsetWidget (line);
+
+ wordInLine = 0;
+ for (wordIndex = line->firstWord; wordIndex < line->lastWord;
+ wordIndex++) {
+ word = words->getRef (wordIndex);
+
+ if (wordIndex == lastWordDrawn) {
+ redrawY = misc::min (redrawY, lineYOffsetWidget (line));
+ }
+
+ switch (word->content.type) {
+ case core::Content::WIDGET:
+ /** \todo Justification within the line is done here. */
+ childAllocation.x = xCursor + allocation->x;
+ /* align=top:
+ childAllocation.y = line->top + allocation->y;
+ */
+
+ /* align=bottom (base line) */
+ /* Commented lines break the n2 and n3 test cases at
+ * http://www.dillo.org/test/img/ */
+ childAllocation.y =
+ lineYOffsetCanvasAllocation (line, allocation)
+ + (line->ascent - word->size.ascent);
+ // - word->content.widget->getStyle()->margin.top;
+ childAllocation.width = word->size.width;
+ childAllocation.ascent = word->size.ascent;
+ // + word->content.widget->getStyle()->margin.top;
+ childAllocation.descent = word->size.descent;
+ // + word->content.widget->getStyle()->margin.bottom;
+
+ oldChildAllocation = word->content.widget->getAllocation();
+
+ if (childAllocation.x != oldChildAllocation->x ||
+ childAllocation.y != oldChildAllocation->y ||
+ childAllocation.width != oldChildAllocation->width) {
+ /* The child widget has changed its position or its width
+ * so we need to redraw from this line onwards.
+ */
+ redrawY = misc::min (redrawY, lineYOffsetWidget (line));
+ if (word->content.widget->wasAllocated ()) {
+ redrawY = misc::min (redrawY,
+ oldChildAllocation->y - this->allocation.y);
+ }
+
+ } else if (childAllocation.ascent + childAllocation.descent !=
+ oldChildAllocation->ascent + oldChildAllocation->descent) {
+ /* The child widget has changed its height. We need to redraw
+ * from where it changed.
+ * It's important not to draw from the line base, because the
+ * child might be a table covering the whole page so we would
+ * end up redrawing the whole screen over and over.
+ * The drawing of the child content is left to the child itself.
+ */
+ int childChangedY =
+ misc::min(childAllocation.y - allocation->y +
+ childAllocation.ascent + childAllocation.descent,
+ oldChildAllocation->y - this->allocation.y +
+ oldChildAllocation->ascent + oldChildAllocation->descent);
+
+ redrawY = misc::min (redrawY, childChangedY);
+ }
+
+ word->content.widget->sizeAllocate (&childAllocation);
+ break;
+
+ case core::Content::ANCHOR:
+ changeAnchor (word->content.anchor,
+ lineYOffsetCanvasAllocation (line, allocation));
+ break;
+
+ default:
+ wordInLine++;
+ // make compiler happy
+ break;
+ }
+
+ xCursor += (word->size.width + word->effSpace);
+ }
+ }
+}
+
+void Textblock::resizeDrawImpl ()
+{
+ queueDrawArea (0, redrawY, allocation.width, getHeight () - redrawY);
+ if (lines->size () > 0) {
+ Line *lastLine = lines->getRef (lines->size () - 1);
+ /* Remember the last word that has been drawn so we can ensure to
+ * draw any new added words (see sizeAllocateImpl()).
+ */
+ lastWordDrawn = lastLine->lastWord;
+ }
+
+ redrawY = getHeight ();
+}
+
+void Textblock::markSizeChange (int ref)
+{
+ markChange (ref);
+}
+
+void Textblock::markExtremesChange (int ref)
+{
+ markChange (ref);
+}
+
+/*
+ * Implementation for both mark_size_change and mark_extremes_change.
+ */
+void Textblock::markChange (int ref)
+{
+ if (ref != -1) {
+ //DBG_MSGF (page, "wrap", 0, "Dw_page_mark_size_change (ref = %d)", ref);
+
+ if (wrapRef == -1)
+ wrapRef = ref;
+ else
+ wrapRef = misc::min (wrapRef, ref);
+
+ //DBG_OBJ_SET_NUM (page, "wrap_ref", page->wrap_ref);
+ }
+}
+
+void Textblock::setWidth (int width)
+{
+ /* If limitTextWidth is set to YES, a queue_resize may also be
+ * necessary. */
+ if (availWidth != width || limitTextWidth) {
+ //DEBUG_MSG(DEBUG_REWRAP_LEVEL,
+ // "Dw_page_set_width: Calling p_Dw_widget_queue_resize, "
+ // "in page with %d word(s)\n",
+ // page->num_words);
+
+ availWidth = width;
+ queueResize (0, false);
+ mustQueueResize = false;
+ redrawY = 0;
+ }
+}
+
+void Textblock::setAscent (int ascent)
+{
+ if (availAscent != ascent) {
+ //DEBUG_MSG(DEBUG_REWRAP_LEVEL,
+ // "Dw_page_set_ascent: Calling p_Dw_widget_queue_resize, "
+ // "in page with %d word(s)\n",
+ // page->num_words);
+
+ availAscent = ascent;
+ queueResize (0, false);
+ mustQueueResize = false;
+ }
+}
+
+void Textblock::setDescent (int descent)
+{
+ if (availDescent != descent) {
+ //DEBUG_MSG(DEBUG_REWRAP_LEVEL,
+ // "Dw_page_set_descent: Calling p_Dw_widget_queue_resize, "
+ // "in page with %d word(s)\n",
+ // page->num_words);
+
+ availDescent = descent;
+ queueResize (0, false);
+ mustQueueResize = false;
+ }
+}
+
+bool Textblock::buttonPressImpl (core::EventButton *event)
+{
+ return sendSelectionEvent (core::SelectionState::BUTTON_PRESS, event);
+}
+
+bool Textblock::buttonReleaseImpl (core::EventButton *event)
+{
+ return sendSelectionEvent (core::SelectionState::BUTTON_RELEASE, event);
+}
+
+bool Textblock::motionNotifyImpl (core::EventMotion *event)
+{
+ if (event->state & core::BUTTON1_MASK)
+ return sendSelectionEvent (core::SelectionState::BUTTON_MOTION, event);
+ else {
+ int linkOld, wordIndex;
+ core::style::Tooltip *tooltipOld;
+
+ wordIndex = findWord (event->xWidget, event->yWidget);
+
+ // cursor from word or widget style
+ if (wordIndex == -1)
+ setCursor (getStyle()->cursor);
+ else
+ setCursor (words->getRef(wordIndex)->style->cursor);
+
+ linkOld = hoverLink;
+ tooltipOld = hoverTooltip;
+
+ if (wordIndex == -1) {
+ hoverLink = -1;
+ hoverTooltip = NULL;
+ } else {
+ hoverLink = words->getRef(wordIndex)->style->x_link;
+ hoverTooltip = words->getRef(wordIndex)->style->x_tooltip;
+ }
+
+ // Show/hide tooltip
+ if (tooltipOld != hoverTooltip) {
+ if (tooltipOld)
+ tooltipOld->onLeave ();
+ if (hoverTooltip)
+ hoverTooltip->onEnter ();
+ } else if (hoverTooltip)
+ hoverTooltip->onMotion ();
+
+ if (hoverLink != linkOld)
+ return emitLinkEnter (hoverLink, -1, -1, -1);
+ else
+ return hoverLink != -1;
+ }
+}
+
+void Textblock::enterNotifyImpl (core::EventCrossing *event)
+{
+}
+
+void Textblock::leaveNotifyImpl (core::EventCrossing *event)
+{
+ hoverLink = -1;
+ (void) emitLinkEnter (hoverLink, -1, -1, -1);
+}
+
+/**
+ * \brief Send event to selection.
+ */
+bool Textblock::sendSelectionEvent (core::SelectionState::EventType eventType,
+ core::MousePositionEvent *event)
+{
+ core::Iterator *it;
+ Line *line, *lastLine;
+ int nextWordStartX, wordStartX, wordX, nextWordX, yFirst, yLast;
+ int charPos = 0, prevPos, wordIndex, lineIndex, link;
+ Word *word;
+ bool found, withinContent, r;
+
+ if (words->size () == 0)
+ // no contens at all
+ return false;
+
+ // In most cases true, so set here:
+ link = -1;
+ withinContent = true;
+
+ lastLine = lines->getRef (lines->size () - 1);
+ yFirst = lineYOffsetCanvasI (0);
+ yLast =
+ lineYOffsetCanvas (lastLine) + lastLine->ascent + lastLine->descent;
+ if (event->yCanvas < yFirst) {
+ // Above the first line: take the first word.
+ withinContent = false;
+ wordIndex = 0;
+ charPos = 0;
+ } else if (event->yCanvas >= yLast) {
+ // Below the last line: take the last word.
+ withinContent = false;
+ wordIndex = words->size () - 1;
+ word = words->getRef (wordIndex);
+ charPos = word->content.type == core::Content::TEXT ?
+ strlen (word->content.text) : 0;
+ } else {
+ lineIndex = findLineIndex (event->yWidget);
+ line = lines->getRef (lineIndex);
+
+ // Pointer within the break space?
+ if (event->yWidget >
+ (lineYOffsetWidget (line) + line->ascent + line->descent)) {
+ // Choose this break.
+ withinContent = false;
+ wordIndex = line->lastWord - 1;
+ charPos = 0;
+ } else if (event->xWidget < lineXOffsetWidget (line)) {
+ // Left of the first word in the line.
+ wordIndex = line->firstWord;
+ withinContent = false;
+ charPos = 0;
+ } else {
+ nextWordStartX = lineXOffsetWidget (line);
+ found = false;
+ for (wordIndex = line->firstWord;
+ !found && wordIndex < line->lastWord;
+ wordIndex++) {
+ word = words->getRef (wordIndex);
+ wordStartX = nextWordStartX;
+ nextWordStartX += word->size.width + word->effSpace;
+
+ if (event->xWidget >= wordStartX &&
+ event->xWidget < nextWordStartX) {
+ // We have found the word.
+ if (word->content.type == core::Content::TEXT) {
+ // Search the character the mouse pointer is in.
+ // nextWordX is the right side of this character.
+ charPos = 0;
+ while ((nextWordX = wordStartX +
+ layout->textWidth (word->style->font,
+ word->content.text, charPos))
+ <= event->xWidget)
+ charPos = layout->nextGlyph (word->content.text, charPos);
+
+ // The left side of this character.
+ prevPos = layout->prevGlyph (word->content.text, charPos);
+ wordX = wordStartX + layout->textWidth (word->style->font,
+ word->content.text,
+ prevPos);
+
+ // If the mouse pointer is left from the middle, use the left
+ // position, otherwise, use the right one.
+ if (event->xWidget <= (wordX + nextWordX) / 2)
+ charPos = prevPos;
+ } else {
+ // Depends on whether the pointer is within the left or
+ // right half of the (non-text) word.
+ if (event->xWidget >=
+ (wordStartX + nextWordStartX) / 2)
+ charPos = core::SelectionState::END_OF_WORD;
+ else
+ charPos = 0;
+ }
+
+ found = true;
+ link = word->style ? word->style->x_link : -1;
+ break;
+ }
+ }
+
+ if (!found) {
+ // No word found in this line (i.e. we are on the right side),
+ // take the last of this line.
+ withinContent = false;
+ wordIndex = line->lastWord - 1;
+ if (wordIndex >= words->size ())
+ wordIndex--;
+ word = words->getRef (wordIndex);
+ charPos = word->content.type == core::Content::TEXT ?
+ strlen (word->content.text) :
+ (int)core::SelectionState::END_OF_WORD;
+ }
+ }
+ }
+
+ it = new TextblockIterator (this, core::Content::SELECTION_CONTENT,
+ wordIndex);
+ r = selectionHandleEvent (eventType, it, charPos, link, event,
+ withinContent);
+ it->unref ();
+ return r;
+}
+
+void Textblock::removeChild (Widget *child)
+{
+ /** \bug Not implemented. */
+}
+
+core::Iterator *Textblock::iterator (core::Content::Type mask, bool atEnd)
+{
+ return new TextblockIterator (this, mask, atEnd);
+}
+
+/*
+ * ...
+ *
+ * availWidth is passed from wordWrap, to avoid calculating it twice.
+ */
+void Textblock::justifyLine (Line *line, int availWidth)
+{
+ /* To avoid rounding errors, the calculation is based on accumulated
+ * values (*_cum). */
+ int i;
+ int origSpaceSum, origSpaceCum;
+ int effSpaceDiffCum, lastEffSpaceDiffCum;
+ int diff;
+
+ diff = availWidth - lastLineWidth;
+ if (diff > 0) {
+ origSpaceSum = 0;
+ for (i = line->firstWord; i < line->lastWord - 1; i++)
+ origSpaceSum += words->getRef(i)->origSpace;
+
+ origSpaceCum = 0;
+ lastEffSpaceDiffCum = 0;
+ for (i = line->firstWord; i < line->lastWord - 1; i++) {
+ origSpaceCum += words->getRef(i)->origSpace;
+
+ if (origSpaceCum == 0)
+ effSpaceDiffCum = lastEffSpaceDiffCum;
+ else
+ effSpaceDiffCum = diff * origSpaceCum / origSpaceSum;
+
+ words->getRef(i)->effSpace = words->getRef(i)->origSpace +
+ (effSpaceDiffCum - lastEffSpaceDiffCum);
+ //DBG_OBJ_ARRSET_NUM (page, "words.%d.eff_space", i,
+ // page->words[i].eff_space);
+
+ lastEffSpaceDiffCum = effSpaceDiffCum;
+ }
+ }
+}
+
+
+void Textblock::addLine (int wordInd, bool newPar)
+{
+ Line *lastLine, *plastLine;
+
+ //DBG_MSG (page, "wrap", 0, "Dw_page_add_line");
+ //DBG_MSG_START (page);
+
+ lines->increase ();
+ //DBG_OBJ_SET_NUM(page, "num_lines", page->num_lines);
+
+ //DEBUG_MSG (DEBUG_REWRAP_LEVEL, "--- new line %d in %p, with word %d of %d"
+ // "\n", page->num_lines - 1, page, word_ind, page->num_words);
+
+ lastLine = lines->getRef (lines->size () - 1);
+
+ if (lines->size () == 1)
+ plastLine = NULL;
+ else
+ plastLine = lines->getRef (lines->size () - 2);
+
+ if (plastLine) {
+ /* second or more lines: copy values of last line */
+ lastLine->top =
+ plastLine->top + plastLine->ascent +
+ plastLine->descent + plastLine->breakSpace;
+ lastLine->maxLineWidth = plastLine->maxLineWidth;
+ lastLine->maxWordMin = plastLine->maxWordMin;
+ lastLine->maxParMax = plastLine->maxParMax;
+ lastLine->parMin = plastLine->parMin;
+ lastLine->parMax = plastLine->parMax;
+ } else {
+ /* first line: initialize values */
+ lastLine->top = 0;
+ lastLine->maxLineWidth = line1OffsetEff;
+ lastLine->maxWordMin = 0;
+ lastLine->maxParMax = 0;
+ lastLine->parMin = line1OffsetEff;
+ lastLine->parMax = line1OffsetEff;
+ }
+
+ //DBG_OBJ_ARRSET_NUM (page, "lines.%d.top", page->num_lines - 1,
+ // lastLine->top);
+ //DBG_OBJ_ARRSET_NUM (page, "lines.%d.maxLineWidth", page->num_lines - 1,
+ // lastLine->maxLineWidth);
+ //DBG_OBJ_ARRSET_NUM (page, "lines.%d.maxWordMin", page->num_lines - 1,
+ // lastLine->maxWordMin);
+ //DBG_OBJ_ARRSET_NUM (page, "lines.%d.maxParMax", page->num_lines - 1,
+ // lastLine->maxParMax);
+ //DBG_OBJ_ARRSET_NUM (page, "lines.%d.parMin", page->num_lines - 1,
+ // lastLine->parMin);
+ //DBG_OBJ_ARRSET_NUM (page, "lines.%d.parMax", page->num_lines - 1,
+ // lastLine->parMax);
+
+ lastLine->firstWord = wordInd;
+ lastLine->ascent = 0;
+ lastLine->descent = 0;
+ lastLine->marginDescent = 0;
+ lastLine->breakSpace = 0;
+ lastLine->leftOffset = 0;
+
+ //DBG_OBJ_ARRSET_NUM (page, "lines.%d.ascent", page->num_lines - 1,
+ // lastLine->ascent);
+ //DBG_OBJ_ARRSET_NUM (page, "lines.%d.descent", page->num_lines - 1,
+ // lastLine->descent);
+
+ /* update values in line */
+ lastLine->maxLineWidth = misc::max (lastLine->maxLineWidth, lastLineWidth);
+
+ if (lines->size () > 1)
+ lastLineWidth = 0;
+ else
+ lastLineWidth = line1OffsetEff;
+
+ if (newPar) {
+ lastLine->maxParMax = misc::max (lastLine->maxParMax, lastLineParMax);
+ //DBG_OBJ_ARRSET_NUM (page, "lines.%d.maxParMax", page->num_lines - 1,
+ // lastLine->maxParMax);
+
+ if (lines->size () > 1) {
+ lastLine->parMin = 0;
+ lastLine->parMax = 0;
+ } else {
+ lastLine->parMin = line1OffsetEff;
+ lastLine->parMax = line1OffsetEff;
+ }
+ lastLineParMin = 0;
+ lastLineParMax = 0;
+
+ //DBG_OBJ_SET_NUM(page, "lastLineParMin", page->lastLineParMin);
+ //DBG_OBJ_SET_NUM(page, "lastLineParMax", page->lastLineParMax);
+ }
+
+ lastLine->parMin = lastLineParMin;
+ lastLine->parMax = lastLineParMax;
+
+ //DBG_OBJ_ARRSET_NUM (page, "lines.%d.parMin", page->num_lines - 1,
+ // lastLine->parMin);
+ //DBG_OBJ_ARRSET_NUM (page, "lines.%d.parMax", page->num_lines - 1,
+ // lastLine->parMax);
+
+ //DBG_MSG_END (page);
+}
+
+/*
+ * This method is called in two cases: (i) when a word is added (by
+ * Dw_page_add_word), and (ii) when a page has to be (partially)
+ * rewrapped. It does word wrap, and adds new lines, if necesary.
+ */
+void Textblock::wordWrap(int wordIndex)
+{
+ Line *lastLine;
+ Word *word, *prevWord;
+ int availWidth, lastSpace, leftOffset;
+ bool newLine = false, newPar = false;
+ core::Extremes wordExtremes;
+
+ //DBG_MSGF (page, "wrap", 0, "Dw_page_real_word_wrap (%d): %s, width = %d",
+ // word_ind, a_Dw_content_html (&page->words[word_ind].content),
+ // page->words[word_ind].size.width);
+ //DBG_MSG_START (page);
+
+ availWidth = this->availWidth - getStyle()->boxDiffWidth() - innerPadding;
+ if (limitTextWidth &&
+ layout->getUsesViewport () &&
+ availWidth > layout->getWidthViewport () - 10)
+ availWidth = layout->getWidthViewport () - 10;
+
+ word = words->getRef (wordIndex);
+
+ if (lines->size () == 0) {
+ //DBG_MSG (page, "wrap", 0, "first line");
+ newLine = true;
+ newPar = true;
+ lastLine = NULL;
+ } else {
+ lastLine = lines->getRef (lines->size () - 1);
+
+ if (lines->size () > 0) {
+ prevWord = words->getRef (wordIndex - 1);
+ if (prevWord->content.type == core::Content::BREAK) {
+ //DBG_MSG (page, "wrap", 0, "after a break");
+ /* previous word is a break */
+ newLine = true;
+ newPar = true;
+ } else if (word->style->whiteSpace
+ != core::style::WHITE_SPACE_NORMAL) {
+ //DBG_MSGF (page, "wrap", 0, "no wrap (white_space = %d)",
+ // word->style->white_space);
+ newLine = false;
+ newPar = false;
+ } else {
+ if (lastLine->firstWord != wordIndex) {
+ /* Does new word fit into the last line? */
+ //DBG_MSGF (page, "wrap", 0,
+ // "word %d (%s) fits? (%d + %d + %d &lt;= %d)...",
+ // word_ind, a_Dw_content_html (&word->content),
+ // page->lastLine_width, prevWord->orig_space,
+ // word->size.width, availWidth);
+ newLine = (lastLineWidth + prevWord->origSpace
+ + word->size.width > availWidth);
+ //DBG_MSGF (page, "wrap", 0, "... %s.",
+ // newLine ? "No" : "Yes");
+ }
+ }
+ }
+ }
+
+ /* Has sometimes the wrong value. */
+ word->effSpace = word->origSpace;
+ //DBG_OBJ_ARRSET_NUM (page,"words.%d.eff_space", word_ind, word->eff_space);
+
+ /* Test, whether line1_offset can be used. */
+ if (wordIndex == 0) {
+ if (ignoreLine1OffsetSometimes) {
+ if (line1Offset + word->size.width > availWidth)
+ line1OffsetEff = 0;
+ else
+ line1OffsetEff = line1Offset;
+ } else
+ line1OffsetEff = line1Offset;
+ }
+
+ if (lastLine != NULL && newLine && !newPar &&
+ word->style->textAlign == core::style::TEXT_ALIGN_JUSTIFY)
+ justifyLine (lastLine, availWidth);
+
+ if (newLine) {
+ addLine (wordIndex, newPar);
+ lastLine = lines->getRef (lines->size () - 1);
+ }
+
+ lastLine->lastWord = wordIndex + 1;
+ lastLine->ascent = misc::max (lastLine->ascent, (int) word->size.ascent);
+ lastLine->descent = misc::max (lastLine->descent, (int) word->size.descent);
+
+ //DBG_OBJ_ARRSET_NUM (page, "lines.%d.ascent", page->num_lines - 1,
+ // lastLine->ascent);
+ //DBG_OBJ_ARRSET_NUM (page, "lines.%d.descent", page->num_lines - 1,
+ // lastLine->descent);
+
+ if (word->content.type == core::Content::WIDGET) {
+ lastLine->marginDescent =
+ misc::max (lastLine->marginDescent,
+ word->size.descent +
+ word->content.widget->getStyle()->margin.bottom);
+
+ //DBG_OBJ_ARRSET_NUM (page, "lines.%d.descent", page->num_lines - 1,
+ // lastLine->descent);
+
+ /* If the widget is not in the first line of the paragraph, its top
+ * margin may make the line higher.
+ */
+ if (lines->size () > 1) {
+ /* Here, we know already what the break and the bottom margin
+ * contributed to the space before this line.
+ */
+ lastLine->ascent =
+ misc::max (lastLine->ascent,
+ word->size.ascent
+ + word->content.widget->getStyle()->margin.top);
+
+ //DBG_OBJ_ARRSET_NUM (page, "lines.%d.ascent", page->num_lines - 1,
+ // lastLine->ascent);
+ }
+ } else
+ lastLine->marginDescent =
+ misc::max (lastLine->marginDescent, lastLine->descent);
+
+ getWordExtremes (word, &wordExtremes);
+ lastSpace = (wordIndex > 0) ? words->getRef(wordIndex - 1)->origSpace : 0;
+
+ if (word->content.type == core::Content::BREAK)
+ lastLine->breakSpace =
+ misc::max (word->content.breakSpace,
+ lastLine->marginDescent - lastLine->descent,
+ lastLine->breakSpace);
+
+ lastLineWidth += word->size.width;
+ if (!newLine)
+ lastLineWidth += lastSpace;
+
+ lastLineParMin += wordExtremes.maxWidth;
+ lastLineParMax += wordExtremes.maxWidth;
+ if (!newPar) {
+ lastLineParMin += lastSpace;
+ lastLineParMax += lastSpace;
+ }
+
+ if (word->style->whiteSpace != core::style::WHITE_SPACE_NORMAL) {
+ lastLine->parMin += wordExtremes.minWidth + lastSpace;
+ /* This may also increase the accumulated minimum word width. */
+ lastLine->maxWordMin =
+ misc::max (lastLine->maxWordMin, lastLine->parMin);
+ /* NOTE: Most code relies on that all values of nowrap are equal for all
+ * words within one line. */
+ } else
+ /* Simple case. */
+ lastLine->maxWordMin =
+ misc::max (lastLine->maxWordMin, wordExtremes.minWidth);
+
+ //DBG_OBJ_SET_NUM(page, "lastLine_par_min", page->lastLine_par_min);
+ //DBG_OBJ_SET_NUM(page, "lastLine_par_max", page->lastLine_par_max);
+ //DBG_OBJ_ARRSET_NUM (page, "lines.%d.par_min", page->num_lines - 1,
+ // lastLine->par_min);
+ //DBG_OBJ_ARRSET_NUM (page, "lines.%d.par_max", page->num_lines - 1,
+ // lastLine->par_max);
+ //DBG_OBJ_ARRSET_NUM (page, "lines.%d.max_word_min", page->num_lines - 1,
+ // lastLine->max_word_min);
+
+ /* Finally, justify the line. Breaks are ignored, since the HTML
+ * parser sometimes assignes the wrong style to them. (todo: ) */
+ if (word->content.type != core::Content::BREAK) {
+ switch (word->style->textAlign) {
+ case core::style::TEXT_ALIGN_LEFT:
+ case core::style::TEXT_ALIGN_JUSTIFY: /* see some lines above */
+ case core::style::TEXT_ALIGN_STRING: /* handled elsewhere (in the
+ * future) */
+ leftOffset = 0;
+ break;
+
+ case core::style::TEXT_ALIGN_RIGHT:
+ leftOffset = availWidth - lastLineWidth;
+ break;
+
+ case core::style::TEXT_ALIGN_CENTER:
+ leftOffset = (availWidth - lastLineWidth) / 2;
+ break;
+
+ default:
+ /* compiler happiness */
+ leftOffset = 0;
+ }
+
+ /* For large lines (images etc), which do not fit into the viewport: */
+ if (leftOffset < 0)
+ leftOffset = 0;
+
+ if (listItem && lastLine == lines->getRef (0)) {
+ /* List item markers are always on the left. */
+ lastLine->leftOffset = 0;
+ words->getRef(0)->effSpace = words->getRef(0)->origSpace + leftOffset;
+ //DBG_OBJ_ARRSET_NUM (page, "words.%d.eff_space", 0,
+ // page->words[0].eff_space);
+ } else
+ lastLine->leftOffset = leftOffset;
+ }
+
+ mustQueueResize = true;
+
+ //DBG_MSG_END (page);
+}
+
+
+/**
+ * Calculate the size of a widget within the page.
+ * (Subject of change in the near future!)
+ */
+void Textblock::calcWidgetSize (core::Widget *widget, core::Requisition *size)
+{
+ core::Requisition requisition;
+ int availWidth, availAscent, availDescent;
+
+ /* We ignore line1_offset[_eff]. */
+ availWidth = this->availWidth - getStyle()->boxDiffWidth () - innerPadding;
+ availAscent = this->availAscent - getStyle()->boxDiffHeight ();
+ availDescent = this->availDescent;
+
+ if (widget->usesHints ()) {
+ widget->setWidth (availWidth);
+ widget->setAscent (availAscent);
+ widget->setDescent (availDescent);
+ widget->sizeRequest (size);
+ size->ascent -= widget->getStyle()->margin.top;
+ size->descent -= widget->getStyle()->margin.bottom;
+ } else {
+ /* TODO: Use margin.{top|bottom} here, like above.
+ * (No harm for the next future.) */
+ if (widget->getStyle()->width == core::style::LENGTH_AUTO ||
+ widget->getStyle()->height == core::style::LENGTH_AUTO)
+ widget->sizeRequest (&requisition);
+
+ if (widget->getStyle()->width == core::style::LENGTH_AUTO)
+ size->width = requisition.width;
+ else if (core::style::isAbsLength (widget->getStyle()->width))
+ /* Fixed lengths are only applied to the content, so we have to
+ * add padding, border and margin. */
+ size->width = core::style::absLengthVal (widget->getStyle()->width)
+ + widget->getStyle()->boxDiffWidth ();
+ else
+ size->width =
+ (int) (core::style::perLengthVal (widget->getStyle()->width)
+ * availWidth);
+
+ if (widget->getStyle()->height == core::style::LENGTH_AUTO) {
+ size->ascent = requisition.ascent;
+ size->descent = requisition.descent;
+ } else if (core::style::isAbsLength (widget->getStyle()->height)) {
+ /* Fixed lengths are only applied to the content, so we have to
+ * add padding, border and margin. */
+ size->ascent =
+ core::style::absLengthVal (widget->getStyle()->height)
+ + widget->getStyle()->boxDiffHeight ();
+ size->descent = 0;
+ } else {
+ double len = core::style::perLengthVal (widget->getStyle()->height);
+ size->ascent = (int) (len * availAscent);
+ size->descent = (int) (len * availDescent);
+ }
+ }
+}
+
+/**
+ * Rewrap the page from the line from which this is necessary.
+ * There are basically two times we'll want to do this:
+ * either when the viewport is resized, or when the size changes on one
+ * of the child widgets.
+ */
+void Textblock::rewrap ()
+{
+ int i, wordIndex;
+ Word *word;
+ Line *lastLine;
+
+ if (wrapRef == -1)
+ /* page does not have to be rewrapped */
+ return;
+
+ //DBG_MSGF (page, "wrap", 0,
+ // "Dw_page_rewrap: page->wrap_ref = %d, in page with %d word(s)",
+ // page->wrap_ref, page->num_words);
+ //DBG_MSG_START (page);
+
+ /* All lines up from page->wrap_ref will be rebuild from the word list,
+ * the line list up from this position is rebuild. */
+ lines->setSize (wrapRef);
+ lastLineWidth = 0;
+ //DBG_OBJ_SET_NUM(page, "num_lines", page->num_lines);
+ //DBG_OBJ_SET_NUM(page, "lastLine_width", page->lastLine_width);
+
+ /* In the word list, we start at the last word, plus one (see definition
+ * of last_word), in the line before. */
+ if (wrapRef > 0) {
+ /* Note: In this case, Dw_page_real_word_wrap will immediately find
+ * the need to rewrap the line, since we start with the last one (plus
+ * one). This is also the reason, why page->lastLine_width is set
+ * to the length of the line. */
+ lastLine = lines->getRef (lines->size () - 1);
+
+ lastLineParMin = lastLine->parMin;
+ lastLineParMax = lastLine->parMax;
+
+ wordIndex = lastLine->lastWord;
+ for (i = lastLine->firstWord; i < lastLine->lastWord - 1; i++)
+ lastLineWidth += (words->getRef(i)->size.width +
+ words->getRef(i)->origSpace);
+ lastLineWidth += words->getRef(lastLine->lastWord - 1)->size.width;
+ } else {
+ lastLineParMin = 0;
+ lastLineParMax = 0;
+
+ wordIndex = 0;
+ }
+
+ for (; wordIndex < words->size (); wordIndex++) {
+ word = words->getRef (wordIndex);
+
+ if (word->content.type == core::Content::WIDGET)
+ calcWidgetSize (word->content.widget, &word->size);
+ wordWrap (wordIndex);
+
+ if (word->content.type == core::Content::WIDGET) {
+ word->content.widget->parentRef = lines->size () - 1;
+ //DBG_OBJ_SET_NUM (word->content.widget, "parent_ref",
+ // word->content.widget->parent_ref);
+ }
+
+ //DEBUG_MSG(DEBUG_REWRAP_LEVEL,
+ // "Assigning parent_ref = %d to rewrapped word %d, "
+ // "in page with %d word(s)\n",
+ // page->num_lines - 1, wordIndex, page->num_words);
+
+ /* todo_refactoring:
+ if (word->content.type == DW_CONTENT_ANCHOR)
+ p_Dw_gtk_viewport_change_anchor
+ (widget, word->content.anchor,
+ Dw_page_line_total_y_offset (page,
+ &page->lines[page->num_lines - 1]));
+ */
+ }
+
+ /* Next time, the page will not have to be rewrapped. */
+ wrapRef = -1;
+
+ //DBG_MSG_END (page);
+}
+
+/*
+ * Paint a line
+ * - x and y are toplevel dw coordinates (Question: what Dw? Changed. Test!)
+ * - area is used always (ev. set it to event->area)
+ * - event is only used when is_expose
+ */
+void Textblock::drawLine (Line *line, core::View *view, core::Rectangle *area)
+{
+ Word *word;
+ int wordIndex;
+ int xWidget, yWidget, xWorld, yWorld, yWorldBase;
+ int startHL, widthHL;
+ int wordLen;
+ int diff, effHLStart, effHLEnd, layer;
+ core::Widget *child;
+ core::Rectangle childArea;
+ core::style::Color *color, *thisBgColor, *wordBgColor;
+
+ /* Here's an idea on how to optimize this routine to minimize the number
+ * of calls to gdk_draw_string:
+ *
+ * Copy the text from the words into a buffer, adding a new word
+ * only if: the attributes match, and the spacing is either zero or
+ * equal to the width of ' '. In the latter case, copy a " " into
+ * the buffer. Then draw the buffer. */
+
+ thisBgColor = getBgColor ();
+
+ xWidget = lineXOffsetWidget(line);
+ xWorld = allocation.x + xWidget;
+ yWidget = lineYOffsetWidget (line);
+ yWorld = allocation.y + yWidget;
+ yWorldBase = yWorld + line->ascent;
+
+ for (wordIndex = line->firstWord; wordIndex < line->lastWord;
+ wordIndex++) {
+ word = words->getRef(wordIndex);
+ diff = 0;
+ color = word->style->color;
+
+ //DBG_OBJ_ARRSET_NUM (page, "words.%d.<i>drawn at</i>.x", wordIndex,
+ // xWidget);
+ //DBG_OBJ_ARRSET_NUM (page, "words.%d.<i>drawn at</i>.y", wordIndex,
+ // yWidget);
+
+ switch (word->content.type) {
+ case core::Content::TEXT:
+ if (word->style->backgroundColor)
+ wordBgColor = word->style->backgroundColor;
+ else
+ wordBgColor = thisBgColor;
+
+ /* Adjust the text baseline if the word is <SUP>-ed or <SUB>-ed. */
+ if (word->style->valign == core::style::VALIGN_SUB)
+ diff = word->size.ascent / 2;
+ else if (word->style->valign == core::style::VALIGN_SUPER)
+ diff -= word->size.ascent / 3;
+
+ /* Draw background (color, image), when given. */
+ if (word->style->hasBackground () && word->size.width > 0)
+ drawBox (view, word->style, area,
+ xWidget, yWidget + line->ascent - word->size.ascent,
+ word->size.width, word->size.ascent + word->size.descent,
+ false);
+
+ /* Draw space background (color, image), when given. */
+ if (word->spaceStyle->hasBackground () && word->effSpace > 0)
+ drawBox (view, word->spaceStyle, area,
+ xWidget + word->size.width,
+ yWidget + line->ascent - word->size.ascent,
+ word->effSpace, word->size.ascent + word->size.descent,
+ false);
+ view->drawText (word->style->font, color,
+ core::style::Color::SHADING_NORMAL,
+ xWorld, yWorldBase + diff,
+ word->content.text, strlen (word->content.text));
+
+ /* underline */
+ if (word->style->textDecoration &
+ core::style::TEXT_DECORATION_UNDERLINE)
+ view->drawLine (color, core::style::Color::SHADING_NORMAL,
+ xWorld, yWorldBase + 1 + diff,
+ xWorld + word->size.width - 1,
+ yWorldBase + 1 + diff);
+ if (wordIndex + 1 < line->lastWord &&
+ (word->spaceStyle->textDecoration
+ & core::style::TEXT_DECORATION_UNDERLINE))
+ view->drawLine (word->spaceStyle->color,
+ core::style::Color::SHADING_NORMAL,
+ xWorld + word->size.width,
+ yWorldBase + 1 + diff,
+ xWorld + word->size.width + word->effSpace - 1,
+ yWorldBase + 1 + diff);
+
+ /* strike-through */
+ if (word->style->textDecoration
+ & core::style::TEXT_DECORATION_LINE_THROUGH)
+ view->drawLine (color, core::style::Color::SHADING_NORMAL,
+ xWorld,
+ yWorldBase - word->size.ascent / 2 + diff,
+ xWorld + word->size.width - 1,
+ yWorldBase - word->size.ascent / 2 + diff);
+ if (wordIndex + 1 < line->lastWord &&
+ (word->spaceStyle->textDecoration
+ & core::style::TEXT_DECORATION_LINE_THROUGH))
+ view->drawLine (word->spaceStyle->color,
+ core::style::Color::SHADING_NORMAL,
+ xWorld + word->size.width,
+ yWorldBase - word->size.ascent / 2 + diff,
+ xWorld + word->size.width + word->effSpace - 1,
+ yWorldBase - word->size.ascent / 2 + diff);
+
+ for (layer = 0; layer < core::HIGHLIGHT_NUM_LAYERS; layer++) {
+ if (hlStart[layer].index <= wordIndex &&
+ hlEnd[layer].index >= wordIndex) {
+
+ wordLen = strlen (word->content.text);
+ effHLEnd = misc::min (wordLen, hlEnd[layer].nChar);
+ effHLStart = 0;
+ if (wordIndex == hlStart[layer].index)
+ effHLStart = misc::min (hlStart[layer].nChar, wordLen);
+
+ effHLEnd = wordLen;
+ if (wordIndex == hlEnd[layer].index)
+ effHLEnd = misc::min (hlEnd[layer].nChar, wordLen);
+
+ startHL = xWorld + layout->textWidth (word->style->font,
+ word->content.text,
+ effHLStart);
+ widthHL =
+ layout->textWidth (word->style->font,
+ word->content.text + effHLStart,
+ effHLEnd - effHLStart);
+
+ // If the space after this word highlighted, and this word
+ // is not the last one in this line, highlight also the
+ // space.
+ /** \todo This should also be done with spaces after non-text
+ * words, but this is not yet defined very well. */
+ if (wordIndex < hlEnd[layer].index &&
+ wordIndex < words->size () &&
+ wordIndex != line->lastWord - 1)
+ widthHL += word->effSpace;
+
+
+ if (widthHL != 0) {
+ /* Draw background for highlighted text. */
+ view->drawRectangle (wordBgColor,
+ core::style::Color::SHADING_INVERSE,
+ true, startHL,
+ yWorldBase - word->size.ascent,
+ widthHL,
+ word->size.ascent + word->size.descent);
+
+ /* Highlight the text. */
+ view->drawText (word->style->font,
+ color, core::style::Color::SHADING_INVERSE,
+ startHL, yWorldBase + diff,
+ word->content.text + effHLStart,
+ effHLEnd - effHLStart);
+
+ /* underline and strike-through */
+ if (word->style->textDecoration
+ & core::style::TEXT_DECORATION_UNDERLINE)
+ view->drawLine (color,
+ core::style::Color::SHADING_INVERSE,
+ startHL, yWorldBase + 1 + diff,
+ startHL + widthHL - 1,
+ yWorldBase + 1 + diff);
+ if (word->style->textDecoration
+ & core::style::TEXT_DECORATION_LINE_THROUGH)
+ view->drawLine (color,
+ core::style::Color::SHADING_INVERSE,
+ startHL,
+ yWorldBase - word->size.ascent / 2 + diff,
+ startHL + widthHL - 1,
+ yWorldBase - word->size.ascent / 2
+ + diff);
+ }
+ }
+ }
+ break;
+
+ case core::Content::WIDGET:
+ child = word->content.widget;
+ if (child->intersects (area, &childArea))
+ child->draw (view, &childArea);
+ break;
+
+ case core::Content::ANCHOR: case core::Content::BREAK:
+ /* nothing - an anchor/break isn't seen */
+ /*
+ * Historical note:
+ * > BUG: sometimes anchors have x_space;
+ * > we subtract that just in case --EG
+ * This is inconsistent with other parts of the code, so it should
+ * be tried to prevent this earlier.--SG
+ */
+ /*
+ * x_viewport -= word->size.width + word->eff_space;
+ * xWidget -= word->size.width + word->eff_space;
+ */
+#if 0
+ /* Useful for testing: draw breaks. */
+ if (word->content.type == DW_CONTENT_BREAK)
+ gdk_draw_rectangle (window, color, TRUE,
+ p_Dw_widget_xWorld_to_viewport (widget,
+ widget->allocation.x +
+ Dw_page_line_total_x_offset(page, line)),
+ y_viewport_base + line->descent,
+ DW_WIDGET_CONTENT_WIDTH(widget),
+ word->content.break_space);
+#endif
+ break;
+
+ default:
+ fprintf (stderr, "BUG!!! at (%d, %d).\n", xWorld, yWorldBase + diff);
+ break;
+ }
+
+ xWorld += word->size.width + word->effSpace;
+ xWidget += word->size.width + word->effSpace;
+ }
+}
+
+/**
+ * Find the first line index that includes y, relative to top of widget.
+ */
+int Textblock::findLineIndex (int y)
+{
+ int maxIndex = lines->size () - 1;
+ int step, index, low = 0;
+
+ step = (lines->size() + 1) >> 1;
+ while ( step > 1 ) {
+ index = low + step;
+ if (index <= maxIndex &&
+ lineYOffsetWidgetI (index) < y)
+ low = index;
+ step = (step + 1) >> 1;
+ }
+
+ if (low < maxIndex && lineYOffsetWidgetI (low + 1) < y)
+ low++;
+
+ /*
+ * This new routine returns the line number between (top) and
+ * (top + size.ascent + size.descent + break_space): the space
+ * _below_ the line is considered part of the line. Old routine
+ * returned line number between (top - previous_line->break_space)
+ * and (top + size.ascent + size.descent): the space _above_ the
+ * line was considered part of the line. This is important for
+ * Dw_page_find_link() --EG
+ * That function has now been inlined into Dw_page_motion_notify() --JV
+ */
+ return low;
+}
+
+/**
+ * \brief Find the line of word \em wordIndex.
+ */
+int Textblock::findLineOfWord (int wordIndex)
+{
+ int high = lines->size () - 1, index, low = 0;
+
+ //g_return_val_if_fail (word_index >= 0, -1);
+ //g_return_val_if_fail (word_index < page->num_words, -1);
+
+ while (true) {
+ index = (low + high) / 2;
+ if (wordIndex >= lines->getRef(index)->firstWord) {
+ if (wordIndex < lines->getRef(index)->lastWord)
+ return index;
+ else
+ low = index + 1;
+ } else
+ high = index - 1;
+ }
+}
+
+/**
+ * \brief Find the index of the word, or -1.
+ */
+int Textblock::findWord (int x, int y)
+{
+ int lineIndex, wordIndex;
+ int xCursor, lastXCursor;
+ Line *line;
+ Word *word;
+
+ if ((lineIndex = findLineIndex (y)) >= lines->size ())
+ return -1;
+ line = lines->getRef (lineIndex);
+ if (lineYOffsetWidget (line) + line->ascent + line->descent <= y)
+ return -1;
+
+ xCursor = lineXOffsetWidget (line);
+ for (wordIndex = line->firstWord; wordIndex < line->lastWord; wordIndex++) {
+ word = words->getRef (wordIndex);
+ lastXCursor = xCursor;
+ xCursor += word->size.width + word->effSpace;
+ if (lastXCursor <= x && xCursor > x)
+ return wordIndex;
+ }
+
+ return -1;
+}
+
+void Textblock::draw (core::View *view, core::Rectangle *area)
+{
+ int lineIndex;
+ Line *line;
+
+ drawWidgetBox (view, area, false);
+
+ lineIndex = findLineIndex (area->y);
+
+ for (; lineIndex < lines->size (); lineIndex++) {
+ line = lines->getRef (lineIndex);
+ if (lineYOffsetWidget (line) >= area->y + area->height)
+ break;
+
+ drawLine (line, view, area);
+ }
+}
+
+/**
+ * Add a new word (text, widget etc.) to a page.
+ */
+Textblock::Word *Textblock::addWord (int width, int ascent, int descent,
+ core::style::Style *style)
+{
+ Word *word;
+
+ words->increase ();
+
+ word = words->getRef (words->size() - 1);
+ word->size.width = width;
+ word->size.ascent = ascent;
+ word->size.descent = descent;
+ word->origSpace = 0;
+ word->effSpace = 0;
+ word->content.space = false;
+
+ //DBG_OBJ_ARRSET_NUM (page, "words.%d.size.width", page->num_words - 1,
+ // word->size.width);
+ //DBG_OBJ_ARRSET_NUM (page, "words.%d.size.descent", page->num_words - 1,
+ // word->size.descent);
+ //DBG_OBJ_ARRSET_NUM (page, "words.%d.size.ascent", page->num_words - 1,
+ // word->size.ascent);
+ //DBG_OBJ_ARRSET_NUM (page, "words.%d.orig_space", page->num_words - 1,
+ // word->orig_space);
+ //DBG_OBJ_ARRSET_NUM (page, "words.%d.eff_space", page->num_words - 1,
+ // word->eff_space);
+ //DBG_OBJ_ARRSET_NUM (page, "words.%d.content.space", page->num_words - 1,
+ // word->content.space);
+
+ word->style = style;
+ word->spaceStyle = style;
+ style->ref ();
+ style->ref ();
+
+ return word;
+}
+
+/**
+ * Calculate the size of a text word.
+ */
+void Textblock::calcTextSize (const char *text, core::style::Style *style,
+ core::Requisition *size)
+{
+ size->width =
+ layout->textWidth (style->font, text, strlen (text));
+ size->ascent = style->font->ascent;
+ size->descent = style->font->descent;
+
+ /* In case of a sub or super script we increase the word's height and
+ * potentially the line's height.
+ */
+ if (style->valign == core::style::VALIGN_SUB)
+ size->descent += (size->ascent / 2);
+ else if (style->valign == core::style::VALIGN_SUPER)
+ size->ascent += (size->ascent / 3);
+}
+
+
+/**
+ * Add a word to the page structure. Stashes the argument pointer in
+ * the page data structure so that it will be deallocated on destroy.
+ */
+void Textblock::addText (const char *text, core::style::Style *style)
+{
+ Word *word;
+ core::Requisition size;
+
+ calcTextSize (text, style, &size);
+ word = addWord (size.width, size.ascent, size.descent, style);
+ word->content.type = core::Content::TEXT;
+ word->content.text = layout->textZone->strdup(text);
+
+ //DBG_OBJ_ARRSET_STR (page, "words.%d.content.text", page->num_words - 1,
+ // word->content.text);
+
+ wordWrap (words->size () - 1);
+}
+
+/**
+ * Add a widget (word type) to the page.
+ */
+void Textblock::addWidget (core::Widget *widget, core::style::Style *style)
+{
+ Word *word;
+ core::Requisition size;
+
+ /* We first assign -1 as parent_ref, since the call of widget->size_request
+ * will otherwise let this DwPage be rewrapped from the beginning.
+ * (parent_ref is actually undefined, but likely has the value 0.) At the,
+ * end of this function, the correct value is assigned. */
+ widget->parentRef = -1;
+
+ widget->setParent (this);
+ widget->setStyle (style);
+
+ calcWidgetSize (widget, &size);
+ word = addWord (size.width, size.ascent, size.descent, style);
+
+ word->content.type = core::Content::WIDGET;
+ word->content.widget = widget;
+
+ //DBG_OBJ_ARRSET_PTR (page, "words.%d.content.widget", page->num_words - 1,
+ // word->content.widget);
+
+ wordWrap (words->size () - 1);
+ word->content.widget->parentRef = lines->size () - 1;
+ //DBG_OBJ_SET_NUM (word->content.widget, "parent_ref",
+ // word->content.widget->parent_ref);
+
+ //DEBUG_MSG(DEBUG_REWRAP_LEVEL,
+ // "Assigning parent_ref = %d to added word %d, "
+ // "in page with %d word(s)\n",
+ // page->num_lines - 1, page->num_words - 1, page->num_words);
+}
+
+
+/**
+ * Add an anchor to the page. "name" is copied, so no strdup is neccessary for
+ * the caller.
+ *
+ * Return true on success, and false, when this anchor had already been
+ * added to the widget tree.
+ */
+bool Textblock::addAnchor (const char *name, core::style::Style *style)
+{
+ Word *word;
+ char *copy;
+ int y;
+
+ // Since an anchor does not take any space, it is safe to call
+ // addAnchor already here.
+ if (wasAllocated ()) {
+ if (lines->size () == 0)
+ y = allocation.y;
+ else
+ y = allocation.y + lineYOffsetWidgetI (lines->size () - 1);
+ copy = Widget::addAnchor (name, y);
+ } else
+ copy = Widget::addAnchor (name);
+
+ if (copy == NULL)
+ /**
+ * \todo It may be neccessary for future uses to save the anchor in
+ * some way, e.g. when parts of the widget tree change.
+ */
+ return false;
+ else {
+ word = addWord (0, 0, 0, style);
+ word->content.type = core::Content::ANCHOR;
+ word->content.anchor = copy;
+ wordWrap (words->size () - 1);
+ return true;
+ }
+}
+
+
+/**
+ * ?
+ */
+void Textblock::addSpace (core::style::Style *style)
+{
+ int nl, nw;
+ int space;
+
+ nl = lines->size () - 1;
+ if (nl >= 0) {
+ nw = words->size () - 1;
+ if (nw >= 0) {
+ /* todo: remove this test case */
+ //if (page->words[nw].orig_space != 0) {
+ // _MSG(" a_Dw_page_add_space:: already existing space!!!\n");
+ //}
+
+ space = style->font->spaceWidth;
+ words->getRef(nw)->origSpace = space;
+ words->getRef(nw)->effSpace = space;
+ words->getRef(nw)->content.space = true;
+
+ //DBG_OBJ_ARRSET_NUM (page, "words.%d.orig_space", nw,
+ // page->words[nw].orig_space);
+ //DBG_OBJ_ARRSET_NUM (page, "words.%d.eff_space", nw,
+ // page->words[nw].eff_space);
+ //DBG_OBJ_ARRSET_NUM (page, "words.%d.content.space", nw,
+ // page->words[nw].content.space);
+
+ words->getRef(nw)->spaceStyle->unref ();
+ words->getRef(nw)->spaceStyle = style;
+ style->ref ();
+ }
+ }
+}
+
+
+/**
+ * Cause a paragraph break
+ */
+void Textblock::addParbreak (int space, core::style::Style *style)
+{
+ Word *word, *word2 = NULL; // Latter for compiler happiness, search!
+ bool isfirst;
+ Widget *widget;
+ int lineno;
+
+ /* A break may not be the first word of a page, or directly after
+ the bullet/number (which is the first word) in a list item. (See
+ also comment in Dw_page_size_request.) */
+ if (words->size () == 0 ||
+ (listItem && words->size () == 1)) {
+ /* This is a bit hackish: If a break is added as the
+ first/second word of a page, and the parent widget is also a
+ DwPage, and there is a break before -- this is the case when
+ a widget is used as a text box (lists, blockquotes, list
+ items etc) -- then we simply adjust the break before, in a
+ way that the space is in any case visible. */
+
+ /* Find the widget where to adjust the break_space. */
+ for (widget = this;
+ widget->getParent() &&
+ widget->getParent()->instanceOf (Textblock::CLASS_ID);
+ widget = widget->getParent ()) {
+ Textblock *textblock2 = (Textblock*)widget->getParent ();
+ if (textblock2->listItem)
+ isfirst = (textblock2->words->get(1).content.type
+ == core::Content::WIDGET
+ && textblock2->words->get(1).content.widget == widget);
+ else
+ isfirst = (textblock2->words->get(0).content.type
+ == core::Content::WIDGET
+ && textblock2->words->get(0).content.widget == widget);
+ if (!isfirst) {
+ /* The page we searched for has been found. */
+ lineno = widget->parentRef;
+ if (lineno > 0 &&
+ (word2 =
+ textblock2->words->getRef(textblock2->lines
+ ->get(lineno - 1).firstWord)) &&
+ word2->content.type == core::Content::BREAK) {
+ if (word2->content.breakSpace < space) {
+ word2->content.breakSpace = space;
+ textblock2->queueResize (lineno, false);
+ textblock2->mustQueueResize = false;
+ }
+ }
+ return;
+ }
+ /* Otherwise continue to examine parents. */
+ }
+ /* Return in any case. */
+ return;
+ }
+
+ /* Another break before? */
+ if ((word = words->getRef(words->size () - 1)) &&
+ word->content.type == core::Content::BREAK) {
+ Line *lastLine = lines->getRef (lines->size () - 1);
+
+ word->content.breakSpace =
+ misc::max (word->content.breakSpace, space);
+ lastLine->breakSpace =
+ misc::max (word->content.breakSpace,
+ lastLine->marginDescent - lastLine->descent,
+ lastLine->breakSpace);
+ return;
+ }
+
+ word = addWord (0, 0, 0, style);
+ word->content.type = core::Content::BREAK;
+ word->content.breakSpace = space;
+ wordWrap (words->size () - 1);
+}
+
+/*
+ * Cause a line break.
+ */
+void Textblock::addLinebreak (core::style::Style *style)
+{
+ Word *word;
+
+ if (words->size () == 0 ||
+ words->get(words->size () - 1).content.type == core::Content::BREAK)
+ // An <BR> in an empty line gets the height of the current font
+ // (why would someone else place it here?), ...
+ word = addWord (0, style->font->ascent, style->font->descent, style);
+ else
+ // ... otherwise, it has no size (and does not enlarge the line).
+ word = addWord (0, 0, 0, style);
+
+ word->content.type = core::Content::BREAK;
+ word->content.breakSpace = 0;
+ word->style = style;
+ wordWrap (words->size () - 1);
+}
+
+
+/**
+ * \brief Search recursively through widget.
+ *
+ * This is an optimized version of the general
+ * dw::core::Widget::getWidgetAtPoint method.
+ */
+core::Widget *Textblock::getWidgetAtPoint(int x, int y, int level)
+{
+ int lineIndex, wordIndex;
+ Line *line;
+
+ if (x < allocation.x ||
+ y < allocation.y ||
+ x > allocation.x + allocation.width ||
+ y > allocation.y + getHeight ()) {
+ return NULL;
+ }
+
+ lineIndex = findLineIndex (y - allocation.y);
+
+ if (lineIndex < 0 || lineIndex >= lines->size ()) {
+ return this;
+ }
+
+ line = lines->getRef (lineIndex);
+
+ for (wordIndex = line->firstWord; wordIndex < line->lastWord; wordIndex++) {
+ Word *word = words->getRef (wordIndex);
+
+ if (word->content.type == core::Content::WIDGET) {
+ core::Widget * childAtPoint;
+ childAtPoint = word->content.widget->getWidgetAtPoint (x, y,
+ level + 1);
+ if (childAtPoint) {
+ return childAtPoint;
+ }
+ }
+ }
+
+ return this;
+}
+
+
+/**
+ * This function "hands" the last break of a page "over" to a parent
+ * page. This is used for "collapsing spaces".
+ */
+void Textblock::handOverBreak (core::style::Style *style)
+{
+ #if 0
+ MISSING
+ DwPageLine *last_line;
+ DwWidget *parent;
+
+ if (page->num_lines == 0)
+ return;
+
+ last_line = &page->lines[page->num_lines - 1];
+ if (last_line->break_space != 0 &&
+ (parent = DW_WIDGET(page)->parent) && DW_IS_PAGE (parent))
+ a_Dw_page_add_parbreak (DW_PAGE (parent), last_line->break_space, style);
+#endif
+}
+
+/*
+ * Any words added by a_Dw_page_add_... are not immediately (queued to
+ * be) drawn, instead, this function must be called. This saves some
+ * calls to p_Dw_widget_queue_resize.
+ *
+ */
+void Textblock::flush ()
+{
+ if (mustQueueResize) {
+ queueResize (-1, true);
+ mustQueueResize = false;
+ }
+}
+
+
+// next: Dw_page_find_word
+
+void Textblock::changeLinkColor (int link, int newColor)
+{
+}
+
+void Textblock::changeWordStyle (int from, int to, core::style::Style *style,
+ bool includeFirstSpace, bool includeLastSpace)
+{
+}
+
+// ----------------------------------------------------------------------
+
+Textblock::TextblockIterator::TextblockIterator (Textblock *textblock,
+ core::Content::Type mask,
+ bool atEnd):
+ core::Iterator (textblock, mask, atEnd)
+{
+ index = atEnd ? textblock->words->size () : -1;
+ content.type = atEnd ? core::Content::END : core::Content::START;
+}
+
+Textblock::TextblockIterator::TextblockIterator (Textblock *textblock,
+ core::Content::Type mask,
+ int index):
+ core::Iterator (textblock, mask, false)
+{
+ this->index = index;
+
+ if (index < 0)
+ content.type = core::Content::START;
+ else if (index >= textblock->words->size ())
+ content.type = core::Content::END;
+ else
+ content = textblock->words->get(index).content;
+}
+
+object::Object *Textblock::TextblockIterator::clone()
+{
+ return new TextblockIterator ((Textblock*)getWidget(), getMask(), index);
+}
+
+int Textblock::TextblockIterator::compareTo(misc::Comparable *other)
+{
+ return index - ((TextblockIterator*)other)->index;
+}
+
+bool Textblock::TextblockIterator::next ()
+{
+ Textblock *textblock = (Textblock*)getWidget();
+
+ if (content.type == core::Content::END)
+ return false;
+
+ do {
+ index++;
+ if (index >= textblock->words->size ()) {
+ content.type = core::Content::END;
+ return false;
+ }
+ } while ((textblock->words->get(index).content.type & getMask()) == 0);
+
+ content = textblock->words->get(index).content;
+ return true;
+}
+
+bool Textblock::TextblockIterator::prev ()
+{
+ Textblock *textblock = (Textblock*)getWidget();
+
+ if (content.type == core::Content::START)
+ return false;
+
+ do {
+ index--;
+ if (index < 0) {
+ content.type = core::Content::START;
+ return false;
+ }
+ } while ((textblock->words->get(index).content.type & getMask()) == 0);
+
+ content = textblock->words->get(index).content;
+ return true;
+}
+
+void Textblock::TextblockIterator::highlight (int start, int end,
+ core::HighlightLayer layer)
+{
+ Textblock *textblock = (Textblock*)getWidget();
+ int index1 = index, index2 = index;
+
+ if (textblock->hlStart[layer].index > textblock->hlEnd[layer].index) {
+ /* nothing is highlighted */
+ textblock->hlStart[layer].index = index;
+ textblock->hlEnd[layer].index = index;
+ }
+
+ if (textblock->hlStart[layer].index >= index) {
+ index2 = textblock->hlStart[layer].index;
+ textblock->hlStart[layer].index = index;
+ textblock->hlStart[layer].nChar = start;
+ }
+
+ if (textblock->hlEnd[layer].index <= index) {
+ index2 = textblock->hlEnd[layer].index;
+ textblock->hlEnd[layer].index = index;
+ textblock->hlEnd[layer].nChar = end;
+ }
+
+ textblock->queueDrawRange (index1, index2);
+}
+
+void Textblock::TextblockIterator::unhighlight (int direction,
+ core::HighlightLayer layer)
+{
+ Textblock *textblock = (Textblock*)getWidget();
+ int index1 = index, index2 = index;
+
+ if (textblock->hlStart[layer].index > textblock->hlEnd[layer].index)
+ return;
+
+ if (direction == 0) {
+ index1 = textblock->hlStart[layer].index;
+ index2 = textblock->hlEnd[layer].index;
+ textblock->hlStart[layer].index = 1;
+ textblock->hlEnd[layer].index = 0;
+ } else if (direction > 0 && textblock->hlStart[layer].index <= index) {
+ index1 = textblock->hlStart[layer].index;
+ textblock->hlStart[layer].index = index + 1;
+ textblock->hlStart[layer].nChar = 0;
+ } else if (direction < 0 && textblock->hlEnd[layer].index >= index) {
+ index1 = textblock->hlEnd[layer].index;
+ textblock->hlEnd[layer].index = index - 1;
+ textblock->hlEnd[layer].nChar = INT_MAX;
+ }
+
+ textblock->queueDrawRange (index1, index2);
+}
+
+void Textblock::queueDrawRange (int index1, int index2)
+{
+ int from = misc::min (index1, index2);
+ int to = misc::max (index1, index2);
+
+ from = misc::min (from, words->size () - 1);
+ from = misc::max (from, 0);
+ to = misc::min (to, words->size () - 1);
+ to = misc::max (to, 0);
+
+ int line1 = findLineOfWord (from);
+ int line2 = findLineOfWord (to);
+
+ queueDrawArea (0,
+ lineYOffsetWidgetI (line1),
+ allocation.width,
+ lineYOffsetWidgetI (line2)
+ - lineYOffsetWidgetI (line1)
+ + lines->getRef (line2)->ascent
+ + lines->getRef (line2)->descent);
+}
+
+void Textblock::TextblockIterator::getAllocation (int start, int end,
+ core::Allocation *allocation)
+{
+ Textblock *textblock = (Textblock*)getWidget();
+ int lineIndex = textblock->findLineOfWord (index);
+ Line *line = textblock->lines->getRef (lineIndex);
+ Word *word = textblock->words->getRef (index);
+
+ allocation->x =
+ textblock->allocation.x + textblock->lineXOffsetWidget (line);
+ for (int i = line->firstWord; i < index; i++)
+ allocation->x += textblock->words->getRef(i)->size.width;
+
+ allocation->y =
+ textblock->allocation.y
+ + textblock->lineYOffsetWidget (line) + line->ascent - word->size.ascent;
+ allocation->width = word->size.width;
+ allocation->ascent = word->size.ascent;
+ allocation->descent = word->size.descent;
+}
+
+} // namespace dw
diff --git a/dw/textblock.hh b/dw/textblock.hh
new file mode 100644
index 00000000..b47c2e55
--- /dev/null
+++ b/dw/textblock.hh
@@ -0,0 +1,387 @@
+#ifndef __DW_TEXTBLOCK_HH__
+#define __DW_TEXTBLOCK_HH__
+
+#include "core.hh"
+#include "../lout/misc.hh"
+
+namespace dw {
+
+using namespace lout;
+
+/**
+ * \brief A Widget for rendering text blocks, i.e. paragraphs or sequences
+ * of paragraphs.
+ *
+ * <h3>Signals</h3>
+ *
+ * dw::Textblock uses the signals defined in
+ * dw::core::Widget::LinkReceiver, related to links. The coordinates are
+ * always -1.
+ *
+ *
+ * <h3>Collapsing Spaces</h3>
+ *
+ * The idea behind this is that every paragraph has a specific vertical
+ * space around and that they are combined to one space, according to
+ * rules stated below. A paragraph consists either of the lines between
+ * two paragraph breaks within a dw::Textblock, or of a dw::Textblock
+ * within a dw::Textblock, in a single line; the latter is used for
+ * indented boxes and list items.
+ *
+ * The rules:
+ *
+ * <ol>
+ * <li> If a paragraph is following by another, the space between them is the
+ * maximum of both box spaces:
+ *
+ * \image html dw-textblock-collapsing-spaces-1-1.png
+ *
+ * are combined like this:
+ *
+ * \image html dw-textblock-collapsing-spaces-1-2.png
+ *
+ * <li> a) If one paragraph is the first paragraph within another, the upper
+ * space of these paragraphs collapse. b) The analogue is the case for the
+ * last box:
+ *
+ * \image html dw-textblock-collapsing-spaces-2-1.png
+ *
+ * If B and C are put into A, the result is:
+ *
+ * \image html dw-textblock-collapsing-spaces-2-2.png
+ * </ol>
+ *
+ * For achieving this, there are some features of dw::Textblock:
+ *
+ * <ul>
+ * <li> Consequent breaks are automatically combined, according to
+ * rule 1. See the code of dw::Textblock::addParBreak for details.
+ *
+ * <li> If a break is added as the first word of the dw::Textblock within
+ * another dw::Textblock, collapsing according to rule 2a is done
+ * automatically. See the code of dw::Textblock::addParBreak.
+ *
+ * <li> To collapse spaces according to rule 2b,
+ * dw::Textblock::addParBreak::handOverBreak must be called for
+ * the \em inner widget. The HTML parser does this in
+ * Html_eventually_pop_dw.
+ * </ul>
+ *
+ *
+ * <h3>Collapsing Margins</h3>
+ *
+ * Collapsing margins, as defined in the CSS2 specification, are,
+ * supported in addition to collapsing spaces. Also, spaces and margins
+ * collapse themselves. I.e., the space between two paragraphs is the
+ * maximum of the space calculated as described in "Collapsing Spaces"
+ * and the space calculated according to the rules for collapsing margins.
+ *
+ * (This is an intermediate hybrid state, collapsing spaces are used in
+ * the current version of dillo, while I implemented collapsing margins
+ * for the CSS prototype and integrated it already into the main trunk. For
+ * a pure CSS-based dillo, collapsing spaces will not be needed anymore, and
+ * may be removed for simplicity.)
+ *
+ *
+ * <h3>Some Internals</h3>
+ *
+ * There are two lists, dw::Textblock::words and
+ * dw::Textblock::lines. The word list is quite static; only new words
+ * may be added. A word is either text, a widget, a break or an
+ * anchor. Anchors are stored in the text, because it may be necessary to
+ * correct the scroller positions at rewrapping.
+ *
+ * Lines refer to the word list (first and last), they are completely
+ * redundant, i.e., they can be rebuilt from the words. Lines can be
+ * rewrapped either completely or partially (see "Incremental Resizing"
+ * below). For the latter purpose, several values are accumulated in the
+ * lines. See dw::Textblock::Line for details.
+ *
+ *
+ * <h4>Incremental Resizing</h4>
+ *
+ * dw::Textblock makes use of incremental resizing as described in \ref
+ * dw-widget-sizes. The parentRef is, for children of a dw::Textblock, simply
+ * the number of the line.
+ *
+ * Generally, there are three cases which may change the size of the
+ * widget:
+ *
+ * <ul>
+ * <li> The available size of the widget has changed, e.g., because the
+ * user has changed the size of the browser window. In this case,
+ * it is necessary to rewrap all the lines.
+ *
+ * <li> A child widget has changed its size. In this case, only a rewrap
+ * down from the line where this widget is located is necessary.
+ *
+ * (This case is very important for tables. Tables are quite at the
+ * bottom, so that a partial rewrap is relevant. Otherwise, tables
+ * change their size quite often, so that this is necessary for a
+ * fast, non-blocking rendering)
+ *
+ * <li> A word (or widget, break etc.) is added to the text block. This
+ * makes it possible to reuse the old size by simply adjusting the
+ * current width and height, so no rewrapping is necessary.
+ * </ul>
+ *
+ * The state of the size calculation is stored in wrapRef within
+ * dw::Textblock, which has the value -1 if no rewrapping of lines
+ * necessary, or otherwise the line from which a rewrap is necessary.
+ *
+ */
+class Textblock: public core::Widget
+{
+protected:
+ struct Line
+ {
+ int firstWord; /* first-word's position in DwPageWord [0 based] */
+ int lastWord; /* last-word's position in DwPageWord [1 based] */
+
+ /* "top" is always relative to the top of the first line, i.e.
+ * page->lines[0].top is always 0. */
+ int top, ascent, descent, breakSpace, leftOffset;
+
+ /* This is similar to descent, but includes the bottom margins of the
+ * widgets within this line. */
+ int marginDescent;
+
+ /* The following members contain accumulated values, from the top
+ * down to the line before. */
+ int maxLineWidth; /* maximum of all line widths */
+ int maxWordMin; /* maximum of all word minima */
+ int maxParMax; /* maximum of all paragraph maxima */
+ int parMin; /* the minimal total width down from the last
+ * paragraph start, to the *beginning* of the
+ * line */
+ int parMax; /* the maximal total width down from the last
+ * paragraph start, to the *beginning* of the
+ * line */
+ };
+
+ struct Word
+ {
+ /* todo: perhaps add a xLeft? */
+ core::Requisition size;
+ /* Space after the word, only if it's not a break: */
+ unsigned short origSpace; /* from font, set by addSpace */
+ unsigned short effSpace; /* effective space, set by wordWrap,
+ * used for drawing etc. */
+ core::Content content;
+
+ core::style::Style *style;
+ core::style::Style *spaceStyle; /* initially the same as of the word,
+ later set by a_Dw_page_add_space */
+ };
+
+ class TextblockIterator: public core::Iterator
+ {
+ private:
+ int index;
+
+ public:
+ TextblockIterator (Textblock *textblock, core::Content::Type mask,
+ bool atEnd);
+ TextblockIterator (Textblock *textblock, core::Content::Type mask,
+ int index);
+
+ object::Object *clone();
+ int compareTo(misc::Comparable *other);
+
+ bool next ();
+ bool prev ();
+ void highlight (int start, int end, core::HighlightLayer layer);
+ void unhighlight (int direction, core::HighlightLayer layer);
+ void getAllocation (int start, int end, core::Allocation *allocation);
+ };
+
+ friend class TextblockIterator;
+
+ /* These fields provide some ad-hoc-functionality, used by sub-classes. */
+ bool listItem; /* If true, the first word of the page is treated
+ specially (search in source). */
+ int innerPadding; /* This is an additional padding on the left side
+ (used by ListItem). */
+ int line1Offset; /* This is an additional offset of the first line.
+ May be negative (shift to left) or positive
+ (shift to right). */
+ int line1OffsetEff; /* The "effective" value of line1_offset, may
+ differ from line1_offset when
+ ignoreLine1OffsetSometimes is set to true. */
+
+ /* The following is really hackish: It is used for DwTableCell (see
+ * comment in dw_table_cell.c), to avoid too wide table columns. If
+ * set to true, it has following effects:
+ *
+ * (i) line1_offset is ignored in calculating the minimal width
+ * (which is used by DwTable!), and
+ * (ii) line1_offset is ignored (line1_offset_eff is set to 0),
+ * when line1_offset plus the width of the first word is
+ * greater than the the available witdh.
+ *
+ * \todo Eliminate all these ad-hoc features by a new, simpler and
+ * more elegant design. ;-)
+ */
+ bool ignoreLine1OffsetSometimes;
+
+ bool mustQueueResize;
+
+ bool limitTextWidth; /* from preferences */
+
+ int redrawY;
+ int lastWordDrawn;
+
+ /* These values are set by set_... */
+ int availWidth, availAscent, availDescent;
+
+ int lastLineWidth;
+ int lastLineParMin;
+ int lastLineParMax;
+ int wrapRef; /* [0 based] */
+
+ misc::SimpleVector <Line> *lines;
+ misc::SimpleVector <Word> *words;
+
+ struct {int index, nChar;}
+ hlStart[core::HIGHLIGHT_NUM_LAYERS], hlEnd[core::HIGHLIGHT_NUM_LAYERS];
+
+ /* The word index of the link under a button press, and the char
+ * position */
+ int linkPressedIndex;
+ int link_pressedCharPos;
+
+ int hoverLink; /* The link under the button. */
+ core::style::Tooltip *hoverTooltip; /* The tooltip under the button. No ref
+ * hold. */
+
+
+ void queueDrawRange (int index1, int index2);
+ void getWordExtremes (Word *word, core::Extremes *extremes);
+ void markChange (int ref);
+ void justifyLine (Line *line, int availWidth);
+ void addLine (int wordInd, bool newPar);
+ void calcWidgetSize (core::Widget *widget, core::Requisition *size);
+ void rewrap ();
+ void drawLine (Line *line, core::View *view, core::Rectangle *area);
+ int findLineIndex (int y);
+ int findLineOfWord (int wordIndex);
+ int findWord (int x, int y);
+
+ Word *addWord (int width, int ascent, int descent,
+ core::style::Style *style);
+ void calcTextSize (const char *text, core::style::Style *style,
+ core::Requisition *size);
+
+
+ /**
+ * \brief Returns the x offset (the indentation plus any offset needed for
+ * centering or right justification) for the line.
+ *
+ * The offset returned is relative to the page *content* (i.e. without
+ * border etc.).
+ */
+ inline int lineXOffsetContents (Line *line)
+ {
+ return innerPadding + line->leftOffset +
+ (line == lines->getRef (0) ? line1OffsetEff : 0);
+ }
+
+ /**
+ * \brief Like lineXOffset, but relative to the allocation (i.e.
+ * including border etc.).
+ */
+ inline int lineXOffsetWidget (Line *line)
+ {
+ return lineXOffsetContents (line) + getStyle()->boxOffsetX ();
+ }
+
+ inline int lineYOffsetWidgetAllocation (Line *line,
+ core::Allocation *allocation)
+ {
+ return line->top + (allocation->ascent - lines->getRef(0)->ascent);
+ }
+
+ inline int lineYOffsetWidget (Line *line)
+ {
+ return lineYOffsetWidgetAllocation (line, &allocation);
+ }
+
+ /**
+ * Like lineYOffsetCanvas, but with the allocation as parameter.
+ */
+ inline int lineYOffsetCanvasAllocation (Line *line,
+ core::Allocation *allocation)
+ {
+ return allocation->y + lineYOffsetWidgetAllocation(line, allocation);
+ }
+
+ /**
+ * Returns the y offset (within the canvas) of a line.
+ */
+ inline int lineYOffsetCanvas (Line *line)
+ {
+ return lineYOffsetCanvasAllocation(line, &allocation);
+ }
+
+ inline int lineYOffsetWidgetI (int lineIndex)
+ {
+ return lineYOffsetWidget (lines->getRef (lineIndex));
+ }
+
+ inline int lineYOffsetCanvasI (int lineIndex)
+ {
+ return lineYOffsetCanvas (lines->getRef (lineIndex));
+ }
+
+ bool sendSelectionEvent (core::SelectionState::EventType eventType,
+ core::MousePositionEvent *event);
+
+ virtual void wordWrap(int wordIndex);
+
+ void sizeRequestImpl (core::Requisition *requisition);
+ void getExtremesImpl (core::Extremes *extremes);
+ void sizeAllocateImpl (core::Allocation *allocation);
+ void resizeDrawImpl ();
+
+ void markSizeChange (int ref);
+ void markExtremesChange (int ref);
+ void setWidth (int width);
+ void setAscent (int ascent);
+ void setDescent (int descent);
+ void draw (core::View *view, core::Rectangle *area);
+
+ bool buttonPressImpl (core::EventButton *event);
+ bool buttonReleaseImpl (core::EventButton *event);
+ bool motionNotifyImpl (core::EventMotion *event);
+ void enterNotifyImpl (core::EventCrossing *event);
+ void leaveNotifyImpl (core::EventCrossing *event);
+
+ void removeChild (Widget *child);
+
+public:
+ static int CLASS_ID;
+
+ Textblock(bool limitTextWidth);
+ ~Textblock();
+
+ core::Iterator *iterator (core::Content::Type mask, bool atEnd);
+
+ void flush ();
+
+ void addText (const char *text, core::style::Style *style);
+ void addWidget (core::Widget *widget, core::style::Style *style);
+ bool addAnchor (const char *name, core::style::Style *style);
+ void addSpace(core::style::Style *style);
+ void addParbreak (int space, core::style::Style *style);
+ void addLinebreak (core::style::Style *style);
+
+ core::Widget *getWidgetAtPoint (int x, int y, int level);
+ void handOverBreak (core::style::Style *style);
+ void changeLinkColor (int link, int newColor);
+ void changeWordStyle (int from, int to, core::style::Style *style,
+ bool includeFirstSpace, bool includeLastSpace);
+};
+
+} // namespace dw
+
+#endif // __DW_TEXTBLOCK_HH__
diff --git a/dw/types.cc b/dw/types.cc
new file mode 100644
index 00000000..94f95b42
--- /dev/null
+++ b/dw/types.cc
@@ -0,0 +1,260 @@
+/*
+ * Dillo Widget
+ *
+ * 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.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+
+
+#include "core.hh"
+
+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;
+}
+
+/**
+ * Return whether this rectangle and otherRect intersect. If yes,
+ * return the intersection rectangle in dest.
+ *
+ * \todo The function has been copied from gdktrectangle.c. Is this relevant
+ * for copyright?
+ */
+bool Rectangle::intersectsWith (Rectangle *otherRect, Rectangle *dest)
+{
+ Rectangle *src1 = this, *src2 = otherRect, *temp;
+ int src1_x2, src1_y2;
+ int src2_x2, src2_y2;
+ bool return_val;
+
+ return_val = false;
+
+ if (src2->x < src1->x) {
+ temp = src1;
+ src1 = src2;
+ src2 = temp;
+ }
+ dest->x = src2->x;
+
+ src1_x2 = src1->x + src1->width;
+ src2_x2 = src2->x + src2->width;
+
+ if (src2->x < src1_x2) {
+ if (src1_x2 < src2_x2)
+ dest->width = src1_x2 - dest->x;
+ else
+ dest->width = src2_x2 - dest->x;
+
+ if (src2->y < src1->y) {
+ temp = src1;
+ src1 = src2;
+ src2 = temp;
+ }
+ dest->y = src2->y;
+
+ src1_y2 = src1->y + src1->height;
+ src2_y2 = src2->y + src2->height;
+
+ if (src2->y < src1_y2) {
+ return_val = true;
+
+ if (src1_y2 < src2_y2)
+ dest->height = src1_y2 - dest->y;
+ else
+ dest->height = src2_y2 - dest->y;
+
+ if (dest->height == 0)
+ return_val = false;
+ if (dest->width == 0)
+ return_val = false;
+ }
+ }
+
+ return return_val;
+}
+
+/*
+ * 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;
+}
+
+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;
+}
+
+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.
+ return
+ zOfVectorProduct (ax1 - bx1, ay1 - by1, bx2 - bx1, by2 - by1) *
+ zOfVectorProduct (ax2 - bx1, ay2 - by1, bx2 - bx1, by2 - by1) <= 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);
+ //printf ("(%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);
+}
+
+} // namespace dw
+} // namespace core
diff --git a/dw/types.hh b/dw/types.hh
new file mode 100644
index 00000000..bdfca629
--- /dev/null
+++ b/dw/types.hh
@@ -0,0 +1,204 @@
+#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 {
+
+using namespace lout;
+
+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
+};
+
+
+/*
+ * 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 object::Object
+{
+public:
+ virtual bool isPointWithin (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);
+
+ 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);
+
+ bool isPointWithin (int x, int y);
+};
+
+/**
+ * \brief dw::core::Shape implemtation for polygons.
+ */
+class Polygon: public Shape
+{
+private:
+ 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 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:
+ container::typed::List <Rectangle> *rectangleList;
+
+public:
+ Region ();
+ ~Region ();
+
+ void clear () { rectangleList->clear (); };
+
+ void addRectangle (Rectangle *r);
+
+ 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;
+};
+
+struct Content
+{
+ enum Type {
+ START = 1 << 0,
+ END = 1 << 1,
+ TEXT = 1 << 2,
+ WIDGET = 1 << 3,
+ ANCHOR = 1 << 4,
+ BREAK = 1 << 5,
+ ALL = 0xff,
+ REAL_CONTENT = 0xff ^ (START | END),
+ SELECTION_CONTENT = TEXT | WIDGET | BREAK
+ };
+ /* Content is embedded in struct Word therefore we
+ * try to be space efficient.
+ */
+ short type;
+ bool space;
+ union {
+ const char *text;
+ Widget *widget;
+ char *anchor;
+ int breakSpace;
+ };
+};
+
+} // namespace dw
+} // namespace core
+
+#endif // __DW_TYPES_HH__
diff --git a/dw/ui.cc b/dw/ui.cc
new file mode 100644
index 00000000..f857e387
--- /dev/null
+++ b/dw/ui.cc
@@ -0,0 +1,364 @@
+/*
+ * Dillo Widget
+ *
+ * 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.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+
+
+#include "core.hh"
+
+#include <stdio.h>
+
+namespace dw {
+namespace core {
+namespace ui {
+
+using namespace object;
+
+int Embed::CLASS_ID = -1;
+
+Embed::Embed(Resource *resource)
+{
+ registerName ("dw::core::ui::Embed", &CLASS_ID);
+ this->resource = resource;
+ resource->setEmbed (this);
+}
+
+Embed::~Embed()
+{
+ delete resource;
+}
+
+void Embed::sizeRequestImpl (Requisition *requisition)
+{
+ resource->sizeRequest (requisition);
+}
+
+void Embed::getExtremesImpl (Extremes *extremes)
+{
+ resource->getExtremes (extremes);
+}
+
+void Embed::sizeAllocateImpl (Allocation *allocation)
+{
+ resource->sizeAllocate (allocation);
+}
+
+void Embed::enterNotifyImpl (core::EventCrossing *event)
+{
+ resource->emitEnter();
+}
+
+void Embed::leaveNotifyImpl (core::EventCrossing *event)
+{
+ resource->emitLeave();
+}
+
+void Embed::setWidth (int width)
+{
+ resource->setWidth (width);
+}
+
+void Embed::setAscent (int ascent)
+{
+ resource->setAscent (ascent);
+}
+
+void Embed::setDescent (int descent)
+{
+ resource->setDescent (descent);
+}
+
+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 ()
+{
+}
+
+void Resource::setEmbed (Embed *embed)
+{
+ this->embed = embed;
+}
+
+void Resource::getExtremes (Extremes *extremes)
+{
+ /* Simply return the requisition width */
+ Requisition requisition;
+ sizeRequest (&requisition);
+ extremes->minWidth = extremes->maxWidth = requisition.width;
+}
+
+void Resource::sizeAllocate (Allocation *allocation)
+{
+}
+
+void Resource::setWidth (int width)
+{
+}
+
+void Resource::setAscent (int ascent)
+{
+}
+
+void Resource::setDescent (int descent)
+{
+}
+
+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 ButtonResource::ClickedEmitter::emitToReceiver (lout::signal::Receiver
+ *receiver,
+ int signalNo,
+ int argc,
+ Object **argv)
+{
+ ((ClickedReceiver*)receiver)
+ ->clicked ((ButtonResource*)((Pointer*)argv[0])->getValue (),
+ ((Integer*)argv[1])->getValue (),
+ ((Integer*)argv[2])->getValue (),
+ ((Integer*)argv[3])->getValue ());
+ return false;
+}
+
+void ButtonResource::ClickedEmitter::emitClicked (ButtonResource *resource,
+ int buttonNo, int x, int y)
+{
+ Integer i1 (buttonNo);
+ Integer i2 (x);
+ Integer i3 (y);
+ Pointer p (resource);
+ Object *argv[4] = { &p, &i1, &i2, &i3 };
+ emitVoid (0, 4, 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::canvasSizeChanged (int width,
+ int ascent,
+ int descent)
+{
+ /**
+ * \todo The argument to queueResize is not always true. (But this works.)
+ */
+ resource->queueResize (true);
+}
+
+ComplexButtonResource::ComplexButtonResource ()
+{
+ layout = NULL;
+ layoutReceiver.resource = this;
+ click_x = click_y = -1;
+}
+
+void ComplexButtonResource::init (Widget *widget)
+{
+ this->widget = widget;
+
+ layout = new Layout (createPlatform ());
+ setLayout (layout);
+ layout->setWidget (widget);
+ layout->connect (&layoutReceiver);
+}
+
+void ComplexButtonResource::setEmbed (Embed *embed)
+{
+ ButtonResource::setEmbed (embed);
+
+ if (widget->usesHints ())
+ embed->setUsesHints ();
+}
+
+ComplexButtonResource::~ComplexButtonResource ()
+{
+ delete layout;
+}
+
+void ComplexButtonResource::sizeRequest (Requisition *requisition)
+{
+ Requisition widgetRequisition;
+ widget->sizeRequest (&widgetRequisition);
+ requisition->width = widgetRequisition.width + 2 * reliefXThickness ();
+ requisition->ascent = widgetRequisition.ascent + reliefYThickness ();
+ requisition->descent = widgetRequisition.descent + reliefYThickness ();
+}
+
+void ComplexButtonResource::getExtremes (Extremes *extremes)
+{
+ Extremes widgetExtremes;
+ widget->getExtremes (&widgetExtremes);
+ extremes->minWidth = widgetExtremes.minWidth + 2 * reliefXThickness ();
+ extremes->maxWidth = widgetExtremes.maxWidth + 2 * reliefXThickness ();
+}
+
+void ComplexButtonResource::sizeAllocate (Allocation *allocation)
+{
+}
+
+void ComplexButtonResource::setWidth (int width)
+{
+ widget->setWidth (width - 2 * reliefXThickness ());
+}
+
+void ComplexButtonResource::setAscent (int ascent)
+{
+ widget->setAscent (ascent - reliefYThickness ());
+}
+
+void ComplexButtonResource::setDescent (int descent)
+{
+ widget->setDescent (descent - reliefYThickness ());
+}
+
+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 core
+
diff --git a/dw/ui.hh b/dw/ui.hh
new file mode 100644
index 00000000..de3e1b2b
--- /dev/null
+++ b/dw/ui.hh
@@ -0,0 +1,564 @@
+#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", 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 instanciate.
+ * <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);
+ void enterNotifyImpl (core::EventCrossing *event);
+ void leaveNotifyImpl (core::EventCrossing *event);
+
+public:
+ static int CLASS_ID;
+
+ Embed(Resource *resource);
+ ~Embed();
+
+ void setWidth (int width);
+ void setAscent (int ascent);
+ void setDescent (int descent);
+ void draw (View *view, Rectangle *area);
+ Iterator *iterator (Content::Type mask, bool atEnd);
+ void setStyle (style::Style *style);
+
+ inline void setUsesHints () { setFlags (USES_HINTS); }
+
+ inline Resource *getResource () { return resource; }
+};
+
+/**
+ * \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;
+ };
+
+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);
+ };
+
+ Embed *embed;
+ ActivateEmitter activateEmitter;
+
+ 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); }
+
+public:
+ inline Resource () { embed = NULL; }
+
+ virtual ~Resource ();
+
+ virtual void sizeRequest (Requisition *requisition) = 0;
+ virtual void getExtremes (Extremes *extremes);
+ virtual void sizeAllocate (Allocation *allocation);
+ virtual void setWidth (int width);
+ virtual void setAscent (int ascent);
+ virtual void setDescent (int descent);
+ 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); }
+};
+
+
+class ButtonResource: public Resource
+{
+public:
+ /**
+ * \brief Receiver interface for the "clicked" signal.
+ */
+ class ClickedReceiver: public lout::signal::Receiver
+ {
+ public:
+ virtual void clicked (ButtonResource *resource, int buttonNo, int x,
+ int y) = 0;
+ };
+
+private:
+ 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 (ButtonResource *resource, int buttonNo, int x, int y);
+ };
+
+ ClickedEmitter clickedEmitter;
+
+protected:
+ inline void emitClicked (int buttonNo, int x, int y) {
+ return clickedEmitter.emitClicked (this, buttonNo, x, y); }
+
+public:
+ inline void connectClicked (ClickedReceiver *receiver) {
+ clickedEmitter.connectClicked (receiver); }
+};
+
+/**
+ * \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 canvasSizeChanged (int width, int ascent, int descent);
+ };
+
+ friend class LayoutReceiver;
+ LayoutReceiver layoutReceiver;
+
+ Widget *widget;
+
+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);
+ void setWidth (int width);
+ void setAscent (int ascent);
+ void setDescent (int descent);
+ 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 pushGroup (const char *name, bool enabled) = 0;
+ virtual void popGroup () = 0;
+
+ virtual int getNumberOfItems () = 0;
+ virtual const char *getItem (int index) = 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_MAX_LENGTH = -1 };
+};
+
+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 object::Object
+{
+public:
+ virtual LabelButtonResource *createLabelButtonResource (const char *label)
+ = 0;
+ virtual ComplexButtonResource *createComplexButtonResource (Widget *widget,
+ bool relief)
+ = 0;
+ virtual ListResource *createListResource (ListResource::SelectionMode
+ selectionMode) = 0;
+ virtual OptionMenuResource *createOptionMenuResource () = 0;
+ virtual EntryResource *createEntryResource (int maxLength,
+ bool password) = 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 00000000..ef604549
--- /dev/null
+++ b/dw/view.hh
@@ -0,0 +1,204 @@
+#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 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 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.
+ *
+ * \bug The rest of this comment needs to be updated.
+ *
+ * markerWidthDiff and markerHeightDiff are the respective dimensions of
+ * the viewport markers (see dw::core::getMarkerWidthDiff and
+ * dw::core::getMarkerHeightDiff), if they are 0, the respective
+ * marker should not be shown at all.
+ */
+ 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 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 x, int y, int width, int height,
+ int angle1, int angle2) = 0;
+ virtual void drawPolygon (style::Color *color,
+ style::Color::Shading shading,
+ bool filled, int points[][2], 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 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 dw
+} // namespace core
+
+#endif // __DW_VIEW_HH__
diff --git a/dw/widget.cc b/dw/widget.cc
new file mode 100644
index 00000000..e3ce8e3d
--- /dev/null
+++ b/dw/widget.cc
@@ -0,0 +1,822 @@
+/*
+ * Dillo Widget
+ *
+ * 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.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+
+
+#include "core.hh"
+
+#include "../lout/debug.hh"
+
+using namespace lout::object;
+
+namespace dw {
+namespace core {
+
+bool Widget::EventReceiver::buttonPress (Widget *widget, EventButton *event)
+{
+ return false;
+}
+
+bool Widget::EventReceiver::buttonRelease (Widget *widget, EventButton *event)
+{
+ return false;
+}
+
+bool Widget::EventReceiver::motionNotify (Widget *widget, EventMotion *event)
+{
+ return false;
+}
+
+void Widget::EventReceiver::enterNotify (Widget *widget, EventCrossing *event)
+{
+}
+
+void Widget::EventReceiver::leaveNotify (Widget *widget, EventCrossing *event)
+{
+}
+
+
+bool Widget::EventEmitter::emitToReceiver (lout::signal::Receiver *receiver,
+ int signalNo,
+ int argc, Object **argv)
+{
+ EventReceiver *eventReceiver = (EventReceiver*)receiver;
+
+ switch (signalNo) {
+ case BUTTON_PRESS:
+ return eventReceiver->buttonPress ((Widget*)argv[0],
+ (EventButton*)argv[1]);
+
+ case BUTTON_RELEASE:
+ return eventReceiver->buttonRelease ((Widget*)argv[0],
+ (EventButton*)argv[1]);
+
+ case MOTION_NOTIFY:
+ return eventReceiver->motionNotify ((Widget*)argv[0],
+ (EventMotion*)argv[1]);
+
+ case ENTER_NOTIFY:
+ eventReceiver->enterNotify ((Widget*)argv[0],
+ (EventCrossing*)argv[1]);
+ break;
+
+ case LEAVE_NOTIFY:
+ eventReceiver->leaveNotify ((Widget*)argv[1],
+ (EventCrossing*)argv[0]);
+ break;
+
+ default:
+ misc::assertNotReached ();
+ }
+
+ /* Compiler happiness. */
+ return false;
+}
+
+bool Widget::EventEmitter::emitButtonPress (Widget *widget, EventButton *event)
+{
+ Object *argv[2] = { widget, event };
+ return emitBool (BUTTON_PRESS, 2, argv);
+}
+
+bool Widget::EventEmitter::emitButtonRelease (Widget *widget,
+ EventButton *event)
+{
+ Object *argv[2] = { widget, event };
+ return emitBool (BUTTON_RELEASE, 2, argv);
+}
+
+bool Widget::EventEmitter::emitMotionNotify (Widget *widget,
+ EventMotion *event)
+{
+ Object *argv[2] = { widget, event };
+ return emitBool (MOTION_NOTIFY, 2, argv);
+}
+
+void Widget::EventEmitter::emitEnterNotify (Widget *widget,
+ EventCrossing *event)
+{
+ Object *argv[2] = { widget, event };
+ emitVoid (ENTER_NOTIFY, 2, argv);
+}
+
+void Widget::EventEmitter::emitLeaveNotify (Widget *widget,
+ EventCrossing *event)
+{
+ Object *argv[2] = { widget, event };
+ emitVoid (LEAVE_NOTIFY, 2, argv);
+}
+
+// ----------------------------------------------------------------------
+
+bool Widget::LinkReceiver::enter (Widget *widget, int link, int img,
+ int x, int y)
+{
+ return false;
+}
+
+bool Widget::LinkReceiver::press (Widget *widget, int link, int img,
+ int x, int y, EventButton *event)
+{
+ return false;
+}
+
+bool Widget::LinkReceiver::release (Widget *widget, int link, int img,
+ int x, int y, EventButton *event)
+{
+ return false;
+}
+
+bool Widget::LinkReceiver::click (Widget *widget, int link, int img,
+ int x, int y, EventButton *event)
+{
+ return false;
+}
+
+
+bool Widget::LinkEmitter::emitToReceiver (lout::signal::Receiver *receiver,
+ int signalNo,
+ int argc, 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 ();
+ }
+
+ /* Compiler happiness. */
+ return false;
+}
+
+bool Widget::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 Widget::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 Widget::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 Widget::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);
+}
+
+
+// ----------------------------------------------------------------------
+
+int Widget::CLASS_ID = -1;
+
+Widget::Widget ()
+{
+ registerName ("dw::core::Widget", &CLASS_ID);
+
+ flags = (Flags)(NEEDS_RESIZE | EXTREMES_CHANGED | HAS_CONTENTS);
+ parent = NULL;
+ layout = NULL;
+
+ allocation.x = -1;
+ allocation.y = -1;
+ allocation.width = 1;
+ allocation.ascent = 1;
+ allocation.descent = 0;
+
+ style = NULL;
+ bgColor = NULL;
+ buttonSensitive = true;
+ buttonSensitiveSet = false;
+
+ deleteCallbackData = NULL;
+ deleteCallbackFunc = NULL;
+}
+
+Widget::~Widget ()
+{
+ if (deleteCallbackFunc)
+ deleteCallbackFunc (deleteCallbackData);
+
+ if (style)
+ style->unref ();
+
+ if (parent)
+ parent->removeChild (this);
+ else
+ layout->removeWidget ();
+}
+
+
+/**
+ * \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 (widget, parent);
+}
+
+void Widget::queueDrawArea (int x, int y, int width, int height)
+{
+ /** \todo Maybe only the intersection? */
+ layout->queueDraw (x + allocation.x, y + allocation.y, width, height);
+ //printf("Widget::queueDrawArea x=%d y=%d w=%d h=%d\n", x, y, width, height);
+}
+
+/**
+ * \brief This method should be called, when a widget changes its size.
+ */
+void Widget::queueResize (int ref, bool extremesChanged)
+{
+ Widget *widget2, *child;
+
+ //DEBUG_MSG (DEBUG_SIZE,
+ // "a %stop-level %s with parent_ref = %d has changed its size\n",
+ // widget->parent ? "non-" : "",
+ // gtk_type_name (GTK_OBJECT_TYPE (widget)), widget->parent_ref);
+
+ setFlags (NEEDS_RESIZE);
+ setFlags (NEEDS_ALLOCATE);
+ markSizeChange (ref);
+
+ if (extremesChanged) {
+ setFlags (EXTREMES_CHANGED);
+ markExtremesChange (ref);
+ }
+
+ for (widget2 = parent, child = this;
+ widget2;
+ child = widget2, widget2 = widget2->parent) {
+ widget2->setFlags (NEEDS_RESIZE);
+ widget2->markSizeChange (child->parentRef);
+ widget2->setFlags (NEEDS_ALLOCATE);
+
+ //DEBUG_MSG (DEBUG_ALLOC,
+ // "setting DW_NEEDS_ALLOCATE for a %stop-level %s "
+ // "with parent_ref = %d\n",
+ // widget2->parent ? "non-" : "",
+ // gtk_type_name (GTK_OBJECT_TYPE (widget2)),
+ // widget2->parent_ref);
+
+ if (extremesChanged) {
+ widget2->setFlags (EXTREMES_CHANGED);
+ widget2->markExtremesChange (child->parentRef);
+ }
+ }
+
+ if (layout)
+ layout->queueResize ();
+}
+
+
+/**
+ * \brief This method is a wrapper for Widget::sizeRequestImpl(); it calls
+ * the latter only when needed.
+ */
+void Widget::sizeRequest (Requisition *requisition)
+{
+ if (needsResize ()) {
+ /** \todo Check requisition == &(this->requisition) and do what? */
+ sizeRequestImpl (requisition);
+ this->requisition = *requisition;
+ unsetFlags (NEEDS_RESIZE);
+
+ DBG_OBJ_SET_NUM (this, "requisition->width", requisition->width);
+ DBG_OBJ_SET_NUM (this, "requisition->ascent", requisition->ascent);
+ DBG_OBJ_SET_NUM (this, "requisition->descent", requisition->descent);
+ } else
+ *requisition = this->requisition;
+}
+
+/**
+ * \brief Wrapper for Widget::getExtremesImpl().
+ */
+void Widget::getExtremes (Extremes *extremes)
+{
+ if (extremesChanged ()) {
+ getExtremesImpl (extremes);
+ this->extremes = *extremes;
+ unsetFlags (EXTREMES_CHANGED);
+
+ DBG_OBJ_SET_NUM (this, "extremes->minWidth", extremes->minWidth);
+ DBG_OBJ_SET_NUM (this, "extremes->maxWidth", extremes->maxWidth);
+ } else
+ *extremes = this->extremes;
+}
+
+/**
+ * \brief Wrapper for Widget::sizeAllocateImpl, calls the latter only when
+ * needed.
+ */
+void Widget::sizeAllocate (Allocation *allocation)
+{
+ 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) {
+
+ //DEBUG_MSG (DEBUG_ALLOC,
+ // "a %stop-level %s with parent_ref = %d is newly allocated "
+ // "from %d, %d, %d x %d x %d ...\n",
+ // widget->parent ? "non-" : "",
+ // (GTK_OBJECT_TYPE_NAME (widget), widget->parent_ref,
+ // widget->allocation.x, widget->allocation.y,
+ // widget->allocation.width, widget->allocation.ascent,
+ // widget->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 (this, "allocation.x", this->allocation.x);
+ DBG_OBJ_SET_NUM (this, "allocation.y", this->allocation.y);
+ DBG_OBJ_SET_NUM (this, "allocation.width", this->allocation.width);
+ DBG_OBJ_SET_NUM (this, "allocation.ascent", this->allocation.ascent);
+ DBG_OBJ_SET_NUM (this, "allocation.descent", this->allocation.descent);
+ }
+
+ /*unsetFlags (NEEDS_RESIZE);*/
+}
+
+bool Widget::buttonPress (EventButton *event)
+{
+ bool b1 = buttonPressImpl (event);
+ bool b2 = eventEmitter.emitButtonPress (this, event);
+ return b1 || b2;
+}
+
+bool Widget::buttonRelease (EventButton *event)
+{
+ bool b1 = buttonReleaseImpl (event);
+ bool b2 = eventEmitter.emitButtonRelease (this, event);
+ return b1 || b2;
+}
+
+bool Widget::motionNotify (EventMotion *event)
+{
+ bool b1 = motionNotifyImpl (event);
+ bool b2 = eventEmitter.emitMotionNotify (this, event);
+ return b1 || b2;
+}
+
+void Widget::enterNotify (EventCrossing *event)
+{
+ enterNotifyImpl (event);
+ eventEmitter.emitEnterNotify (this, event);
+}
+
+void Widget::leaveNotify (EventCrossing *event)
+{
+ leaveNotifyImpl (event);
+ eventEmitter.emitLeaveNotify (this, 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 (this->style) {
+ sizeChanged = this->style->sizeDiffs (style);
+ this->style->unref ();
+ } else
+ sizeChanged = true;
+
+ style->ref ();
+ this->style = style;
+
+ if (layout != NULL) {
+ if (parent == NULL)
+ layout->updateBgColor ();
+ layout->updateCursor ();
+ }
+
+ if (sizeChanged)
+ queueResize (0, true);
+ else
+ queueDraw ();
+}
+
+/**
+ * \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;
+ }
+
+ fprintf (stderr, "No background color found!\n");
+ return NULL;
+
+}
+
+
+/**
+ * \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 viewArea;
+ viewArea.x = area->x + allocation.x;
+ viewArea.y = area->y + allocation.y;
+ viewArea.width = area->width;
+ viewArea.height = area->height;
+
+ style::drawBorder (view, &viewArea, allocation.x + x, allocation.y + y,
+ width, height, style, inverse);
+
+ /** \todo Background images? */
+ if (style->backgroundColor)
+ style::drawBackground (view, &viewArea,
+ allocation.x + x, allocation.y + y, width, height,
+ style, inverse);
+}
+
+/**
+ * \brief Draw borders and background of a widget.
+ *
+ * area is given in widget coordinates.
+ *
+ */
+void Widget::drawWidgetBox (View *view, Rectangle *area, bool inverse)
+{
+ Rectangle viewArea;
+ viewArea.x = area->x + allocation.x;
+ viewArea.y = area->y + allocation.y;
+ viewArea.width = area->width;
+ viewArea.height = area->height;
+
+ style::drawBorder (view, &viewArea, allocation.x, allocation.y,
+ allocation.width, getHeight (), style, inverse);
+
+ /** \todo Adjust following comment from the old dw sources. */
+ /*
+ * - Toplevel widget background colors are set as viewport
+ * background color. This is not crucial for the rendering, but
+ * looks a bit nicer when scrolling. Furthermore, the viewport
+ * does anything else in this case.
+ *
+ * - Since widgets are always drawn from top to bottom, it is
+ * *not* necessary to draw the background if
+ * widget->style->background_color is NULL (shining through).
+ */
+ /** \todo Background images? */
+ if (parent && style->backgroundColor)
+ style::drawBackground (view, &viewArea, allocation.x, allocation.y,
+ allocation.width, getHeight (), style, inverse);
+}
+
+/*
+ * 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 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) {
+ fprintf (stderr, "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;
+
+ //_MSG ("%*s-> examining the %s %p (%d, %d, %d x (%d + %d))\n",
+ // 3 * level, "", gtk_type_name (GTK_OBJECT_TYPE (widget)), widget,
+ // 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::WIDGET, false);
+
+ while (childAtPoint == NULL && it->next ())
+ childAtPoint = it->getContent()->widget->getWidgetAtPoint (x, y,
+ level + 1);
+
+ it->unref ();
+
+ if (childAtPoint)
+ return childAtPoint;
+ else
+ return this;
+ } else
+ return NULL;
+}
+
+
+void Widget::scrollTo (HPosition hpos, VPosition vpos,
+ int x, int y, int width, int height)
+{
+ layout->scrollTo (hpos, vpos,
+ x + allocation.x, y + allocation.y, width, height);
+}
+
+void Widget::getExtremesImpl (Extremes *extremes)
+{
+ /* Simply return the requisition width */
+ Requisition requisition;
+ sizeRequest (&requisition);
+ extremes->minWidth = extremes->maxWidth = requisition.width;
+}
+
+void Widget::sizeAllocateImpl (Allocation *allocation)
+{
+}
+
+void Widget::markSizeChange (int ref)
+{
+}
+
+void Widget::markExtremesChange (int ref)
+{
+}
+
+void Widget::setWidth (int width)
+{
+}
+
+void Widget::setAscent (int ascent)
+{
+}
+
+void Widget::setDescent (int descent)
+{
+}
+
+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 *event)
+{
+}
+
+void Widget::leaveNotifyImpl (EventCrossing *event)
+{
+}
+
+void Widget::removeChild (Widget *child)
+{
+ // Should be implemented.
+ misc::assertNotReached ();
+}
+
+
+
+} // namespace dw
+} // namespace core
diff --git a/dw/widget.hh b/dw/widget.hh
new file mode 100644
index 00000000..1bcfd032
--- /dev/null
+++ b/dw/widget.hh
@@ -0,0 +1,465 @@
+#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 identity::IdentifiableObject
+{
+ friend class Layout;
+
+public:
+ class EventReceiver: public lout::signal::Receiver
+ {
+ public:
+ virtual bool buttonPress (Widget *widget, EventButton *event);
+ virtual bool buttonRelease (Widget *widget, EventButton *event);
+ virtual bool motionNotify (Widget *widget, EventMotion *event);
+ virtual void enterNotify (Widget *widget, EventCrossing *event);
+ virtual void leaveNotify (Widget *widget, EventCrossing *event);
+ };
+
+ /**
+ * \brief This receiver is for signals related to HTML pages.
+ *
+ * The \em link argument to all signals defines a number, which has
+ * been passed before, e.g. by setting dw::core::style::Style::x_link.
+ * When defining this number (e.g in dw::core::style::Style::x_link),
+ * and when receiving the signal, the caller must interpret these numbers
+ * in a consistent way. In the HTML link block, this number is an index
+ * to an array of URLs.
+ *
+ * \em link = -1 represents an undefined link.
+ *
+ * The \em img argument to all signals defines a number which has
+ * been passed before, e.g. by setting dw::core::style::Style::x_img.
+ * When defining this number (e.g in dw::core::style::Style::x_img),
+ * and when receiving the signal, the caller must interpret these numbers
+ * in a consistent way. In the HTML link block, this number is an index
+ * to an array of structures containing image information.
+ *
+ * \em img = -1 represents an undefined image.
+ *
+ * \em x and \em y define the coordinates within the link area. They are
+ * only used for server-side image maps, see dw::Image.
+ *
+ * \sa dw::Image, dw::Textblock
+ */
+ 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);
+ };
+
+private:
+ class EventEmitter: public lout::signal::Emitter
+ {
+ private:
+ enum { BUTTON_PRESS, BUTTON_RELEASE, MOTION_NOTIFY, ENTER_NOTIFY,
+ LEAVE_NOTIFY };
+
+ protected:
+ bool emitToReceiver (lout::signal::Receiver *receiver, int signalNo,
+ int argc, Object **argv);
+
+ public:
+ inline void connectEvent (EventReceiver *receiver)
+ { connect (receiver); }
+
+ bool emitButtonPress (Widget *widget, EventButton *event);
+ bool emitButtonRelease (Widget *widget, EventButton *event);
+ bool emitMotionNotify (Widget *widget, EventMotion *event);
+ void emitEnterNotify (Widget *widget, EventCrossing *event);
+ void emitLeaveNotify (Widget *widget, EventCrossing *event);
+ };
+
+ class LinkEmitter: public lout::signal::Emitter
+ {
+ private:
+ enum { ENTER, PRESS, RELEASE, CLICK };
+
+ protected:
+ bool emitToReceiver (lout::signal::Receiver *receiver, int signalNo,
+ int argc, 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);
+ };
+
+ EventEmitter eventEmitter;
+
+ style::Style *style;
+
+
+protected:
+ enum Flags {
+ /**
+ * \brief Set, when dw::core::Widget::requisition is not up to date
+ * anymore.
+ */
+ NEEDS_RESIZE = 1 << 0,
+
+ /**
+ * \brief Only used internally, set to enforce size allocation.
+ *
+ * (I've forgotten the case, for which this is necessary.)
+ */
+ NEEDS_ALLOCATE = 1 << 1,
+
+ /**
+ * \brief Set, when dw::core::Widget::extremes is not up to date
+ * anymore.
+ */
+ EXTREMES_CHANGED = 1 << 2,
+
+ /**
+ * \brief Set by the widget itself (in the constructor), when set...
+ * methods are implemented.
+ *
+ * Will hopefully be removed, after redesigning the size model.
+ */
+ USES_HINTS = 1 << 3,
+
+ /**
+ * \brief Set by the widget itself (in the constructor), when it contains
+ * some contents, e.g. an image, as opposed to a horizontal ruler.
+ *
+ * Will hopefully be removed, after redesigning the size model.
+ */
+ HAS_CONTENTS = 1 << 4,
+
+ /**
+ * \brief Set, when a widget was already once allocated,
+ *
+ * The dw::Image widget uses this flag, see dw::Image::setBuffer.
+ */
+ WAS_ALLOCATED = 1 << 5,
+ };
+
+private:
+ /**
+ * \brief The parent widget, NULL for top-level widgets.
+ */
+ Widget *parent;
+
+ 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;
+
+public:
+ /**
+ * \brief This value is defined by the parent widget, and used for
+ * incremential resizing.
+ *
+ * See documentation for an explanation.
+ */
+ int parentRef;
+
+protected:
+ LinkEmitter linkEmitter;
+
+ /**
+ * \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;
+
+ inline void setFlags (Flags f) { flags = (Flags)(flags | f); }
+ inline void unsetFlags (Flags f) { flags = (Flags)(flags & ~f); }
+
+
+ inline void queueDraw ()
+ {
+ queueDrawArea (0, 0, allocation.width, getHeight());
+ }
+ void queueDrawArea (int x, int y, int width, int height);
+ void queueResize (int ref, bool extremesChanged);
+
+ /**
+ * \brief See \ref dw-widget-sizes.
+ */
+ virtual void sizeRequestImpl (Requisition *requisition) = 0;
+
+ /**
+ * \brief See \ref dw-widget-sizes.
+ */
+ virtual void getExtremesImpl (Extremes *extremes);
+
+ /**
+ * \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);
+
+ 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)
+ { layout->removeAnchor (this, name); }
+
+ //inline void updateBgColor () { layout->updateBgColor (); }
+
+ inline void setCursor (style::Cursor cursor)
+ { layout->setCursor (cursor); }
+
+ inline bool selectionButtonPress (Iterator *it, int charPos, int linkNo,
+ EventButton *event, bool withinContent)
+ { return layout->selectionState.buttonPress (it, charPos, linkNo, event,
+ withinContent); }
+
+ inline bool selectionButtonRelease (Iterator *it, int charPos, int linkNo,
+ EventButton *event, bool withinContent)
+ { return layout->selectionState.buttonRelease (it, charPos, linkNo, event,
+ withinContent); }
+
+ inline bool selectionButtonMotion (Iterator *it, int charPos, int linkNo,
+ EventMotion *event, bool withinContent)
+ { return layout->selectionState.buttonMotion (it, charPos, linkNo, event,
+ withinContent); }
+
+ inline bool selectionHandleEvent (SelectionState::EventType eventType,
+ Iterator *it, int charPos, int linkNo,
+ MousePositionEvent *event,
+ bool withinContent)
+ { return layout->selectionState.handleEvent (eventType, it, charPos, linkNo,
+ event, withinContent); }
+
+private:
+ void *deleteCallbackData;
+ DW_Callback_t deleteCallbackFunc;
+
+public:
+ inline void setDeleteCallback(DW_Callback_t func, void *data)
+ { deleteCallbackFunc = func; deleteCallbackData = data; }
+
+public:
+ static int CLASS_ID;
+
+ Widget ();
+ ~Widget ();
+
+ inline bool needsResize () { return flags & NEEDS_RESIZE; }
+ inline bool needsAllocate () { return flags & NEEDS_ALLOCATE; }
+ inline bool extremesChanged () { return flags & EXTREMES_CHANGED; }
+ inline bool wasAllocated () { return flags & WAS_ALLOCATED; }
+
+ inline void connectEvent (EventReceiver *receiver)
+ { eventEmitter.connectEvent (receiver); }
+
+ inline void connectLink (LinkReceiver *receiver)
+ { linkEmitter.connectLink (receiver); }
+
+ inline bool emitLinkEnter (int link, int img, int x, int y)
+ { return linkEmitter.emitEnter (this, link, img, x, y); }
+
+ inline bool emitLinkPress (int link, int img,
+ int x, int y, EventButton *event)
+ { return linkEmitter.emitPress (this, link, img, x, y, event); }
+
+ inline bool emitLinkRelease (int link, int img,
+ int x, int y, EventButton *event)
+ { return linkEmitter.emitRelease (this, link, img, x, y, event); }
+
+ inline bool emitLinkClick (int link, int img,
+ int x, int y, EventButton *event)
+ { return linkEmitter.emitClick (this, link, img, x, y, event); }
+
+ inline bool usesHints () { return flags & USES_HINTS; }
+ inline bool hasContents () { return flags & HAS_CONTENTS; }
+
+ void setParent (Widget *parent);
+
+ inline style::Style *getStyle () { return style; }
+ /** \todo I do not like this. */
+ inline Allocation *getAllocation () { return &allocation; }
+
+ void sizeRequest (Requisition *requisition);
+ void getExtremes (Extremes *extremes);
+ void sizeAllocate (Allocation *allocation);
+ virtual void setWidth (int width);
+ virtual void setAscent (int ascent);
+ virtual void setDescent (int descent);
+
+ 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; }
+ Widget *getTopLevel ();
+ int getLevel ();
+ Widget *getNearestCommonAncestor (Widget *otherWidget);
+
+ inline Layout *getLayout () { return layout; }
+
+ virtual Widget *getWidgetAtPoint (int x, int y, int level);
+
+ void scrollTo (HPosition hpos, VPosition vpos,
+ int x, int y, int width, int height);
+
+ /**
+ * \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);
+};
+
+} // namespace dw
+} // namespace core
+
+#endif // __DW_WIDGET_HH__