diff options
Diffstat (limited to 'dw')
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 <= %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__ |