diff options
Diffstat (limited to 'dw')
-rw-r--r-- | dw/Makefile.am | 6 | ||||
-rw-r--r-- | dw/findtext.cc | 4 | ||||
-rw-r--r-- | dw/fltkui.cc | 80 | ||||
-rw-r--r-- | dw/fltkui.hh | 4 | ||||
-rw-r--r-- | dw/iterator.cc | 146 | ||||
-rw-r--r-- | dw/iterator.hh | 15 | ||||
-rw-r--r-- | dw/layout.cc | 106 | ||||
-rw-r--r-- | dw/layout.hh | 29 | ||||
-rw-r--r-- | dw/outofflowmgr.cc | 2114 | ||||
-rw-r--r-- | dw/outofflowmgr.hh | 429 | ||||
-rw-r--r-- | dw/ruler.cc | 13 | ||||
-rw-r--r-- | dw/ruler.hh | 4 | ||||
-rw-r--r-- | dw/style.cc | 32 | ||||
-rw-r--r-- | dw/style.hh | 27 | ||||
-rw-r--r-- | dw/table.cc | 18 | ||||
-rw-r--r-- | dw/table.hh | 2 | ||||
-rw-r--r-- | dw/tablecell.cc | 2 | ||||
-rw-r--r-- | dw/textblock.cc | 943 | ||||
-rw-r--r-- | dw/textblock.hh | 240 | ||||
-rw-r--r-- | dw/textblock_iterator.cc | 331 | ||||
-rw-r--r-- | dw/textblock_linebreaking.cc | 1156 | ||||
-rw-r--r-- | dw/types.cc | 85 | ||||
-rw-r--r-- | dw/types.hh | 29 | ||||
-rw-r--r-- | dw/ui.cc | 58 | ||||
-rw-r--r-- | dw/ui.hh | 3 | ||||
-rw-r--r-- | dw/widget.cc | 217 | ||||
-rw-r--r-- | dw/widget.hh | 82 |
27 files changed, 5555 insertions, 620 deletions
diff --git a/dw/Makefile.am b/dw/Makefile.am index d0d56d2a..5306c5a5 100644 --- a/dw/Makefile.am +++ b/dw/Makefile.am @@ -1,7 +1,7 @@ AM_CPPFLAGS = \ -I$(top_srcdir) \ - -DDILLO_LIBDIR='"$(pkglibdir)/"' - + -DDILLO_LIBDIR='"$(pkglibdir)/"' \ + -DCUR_WORKING_DIR='"@BASE_CUR_WORKING_DIR@/dw"' noinst_LIBRARIES = \ libDw-core.a \ @@ -67,6 +67,8 @@ libDw_widgets_a_SOURCES = \ image.hh \ listitem.cc \ listitem.hh \ + outofflowmgr.cc \ + outofflowmgr.hh \ ruler.cc \ ruler.hh \ table.cc \ diff --git a/dw/findtext.cc b/dw/findtext.cc index cc57e991..e9384917 100644 --- a/dw/findtext.cc +++ b/dw/findtext.cc @@ -96,7 +96,7 @@ FindtextState::Result FindtextState::search (const char *key, bool caseSens, if (iterator) delete iterator; - iterator = new CharIterator (widget); + iterator = new CharIterator (widget, true); if (backwards) { /* Go to end */ @@ -128,7 +128,7 @@ FindtextState::Result FindtextState::search (const char *key, bool caseSens, } else { // Nothing found anymore, reset the state for the next trial. delete iterator; - iterator = new CharIterator (widget); + iterator = new CharIterator (widget, true); if (backwards) { /* Go to end */ while (iterator->next ()) ; diff --git a/dw/fltkui.cc b/dw/fltkui.cc index 58bb2c6f..bfc07528 100644 --- a/dw/fltkui.cc +++ b/dw/fltkui.cc @@ -145,6 +145,8 @@ using namespace lout::container::typed; FltkResource::FltkResource (FltkPlatform *platform) { + DBG_OBJ_CREATE ("dw::fltk::ui::FltkResource"); + this->platform = platform; allocation.x = 0; @@ -180,6 +182,8 @@ FltkResource::~FltkResource () } if (style) style->unref (); + + DBG_OBJ_DELETE (); } void FltkResource::attachView (FltkView *view) @@ -209,6 +213,10 @@ void FltkResource::detachView (FltkView *view) void FltkResource::sizeAllocate (core::Allocation *allocation) { + DBG_OBJ_MSGF ("resize", 0, "<b>sizeAllocate</b> (%d, %d; %d * (%d + %d))", + allocation->x, allocation->y, allocation->width, + allocation->ascent, allocation->descent); + this->allocation = *allocation; view->allocateFltkWidget (widget, allocation); } @@ -293,6 +301,20 @@ void FltkResource::setEnabled (bool enabled) // ---------------------------------------------------------------------- +template <class I> FltkSpecificResource<I>::FltkSpecificResource (FltkPlatform + *platform) : + FltkResource (platform) +{ + DBG_OBJ_CREATE ("dw::fltk::ui::FltkSpecificResource<>"); + DBG_OBJ_BASECLASS (I); + DBG_OBJ_BASECLASS (FltkResource); +} + +template <class I> FltkSpecificResource<I>::~FltkSpecificResource () +{ + DBG_OBJ_DELETE (); +} + template <class I> void FltkSpecificResource<I>::sizeAllocate (core::Allocation *allocation) { @@ -367,6 +389,9 @@ Fl_Widget *FltkLabelButtonResource::createNewWidget (core::Allocation void FltkLabelButtonResource::sizeRequest (core::Requisition *requisition) { + DBG_OBJ_MSG ("resize", 0, "<b>sizeRequest</b>"); + DBG_OBJ_MSG_START (); + if (style) { FltkFont *font = (FltkFont*)style->font; fl_font(font->font,font->size); @@ -380,6 +405,10 @@ void FltkLabelButtonResource::sizeRequest (core::Requisition *requisition) requisition->ascent = 1; requisition->descent = 0; } + + DBG_OBJ_MSGF ("resize", 1, "result: %d * (%d + %d)", + requisition->width, requisition->ascent, requisition->descent); + DBG_OBJ_MSG_END (); } /* @@ -513,6 +542,17 @@ void FltkComplexButtonResource::sizeAllocate (core::Allocation *allocation) { FltkResource::sizeAllocate (allocation); + DBG_OBJ_MSGF_O ("resize", 0, flatView, + "<b>resize</b> (%d %d, <i>%d - 2 * %d =</i> %d, " + "<i>%d + %d - 2 * %d =</i> %d)", + reliefXThickness (), reliefYThickness (), + allocation->width, reliefXThickness (), + allocation->width - 2 * reliefXThickness (), + allocation->ascent, allocation->descent, + reliefYThickness (), + allocation->ascent + allocation->descent + - 2 * reliefYThickness ()); + ((FltkFlatView*)flatView)->resize ( reliefXThickness (), reliefYThickness (), allocation->width - 2 * reliefXThickness (), @@ -634,6 +674,9 @@ void FltkEntryResource::setDisplayed(bool displayed) void FltkEntryResource::sizeRequest (core::Requisition *requisition) { + DBG_OBJ_MSG ("resize", 0, "<b>sizeRequest</b>"); + DBG_OBJ_MSG_START (); + if (displayed() && style) { FltkFont *font = (FltkFont*)style->font; fl_font(font->font,font->size); @@ -650,6 +693,10 @@ void FltkEntryResource::sizeRequest (core::Requisition *requisition) requisition->ascent = 0; requisition->descent = 0; } + + DBG_OBJ_MSGF ("resize", 1, "result: %d * (%d + %d)", + requisition->width, requisition->ascent, requisition->descent); + DBG_OBJ_MSG_END (); } void FltkEntryResource::sizeAllocate (core::Allocation *allocation) @@ -657,6 +704,11 @@ void FltkEntryResource::sizeAllocate (core::Allocation *allocation) if (!label) { FltkResource::sizeAllocate(allocation); } else { + DBG_OBJ_MSGF ("resize", 0, + "<b>sizeAllocate</b> (%d, %d; %d * (%d + %d))", + allocation->x, allocation->y, allocation->width, + allocation->ascent, allocation->descent); + this->allocation = *allocation; /* push the Fl_Input over to the right of the label */ @@ -779,6 +831,9 @@ void FltkMultiLineTextResource::setWidgetStyle (Fl_Widget *widget, void FltkMultiLineTextResource::sizeRequest (core::Requisition *requisition) { + DBG_OBJ_MSG ("resize", 0, "<b>sizeRequest</b>"); + DBG_OBJ_MSG_START (); + if (style) { FltkFont *font = (FltkFont*)style->font; fl_font(font->font,font->size); @@ -797,6 +852,10 @@ void FltkMultiLineTextResource::sizeRequest (core::Requisition *requisition) requisition->ascent = 1; requisition->descent = 0; } + + DBG_OBJ_MSGF ("resize", 1, "result: %d * (%d + %d)", + requisition->width, requisition->ascent, requisition->descent); + DBG_OBJ_MSG_END (); } const char *FltkMultiLineTextResource::getText () @@ -864,6 +923,9 @@ void FltkToggleButtonResource<I>::setWidgetStyle (Fl_Widget *widget, template <class I> void FltkToggleButtonResource<I>::sizeRequest (core::Requisition *requisition) { + DBG_OBJ_MSG ("resize", 0, "<b>sizeRequest</b>"); + DBG_OBJ_MSG_START (); + FltkFont *font = (FltkFont *) (this->FltkResource::style ? this->FltkResource::style->font : NULL); @@ -877,6 +939,10 @@ void FltkToggleButtonResource<I>::sizeRequest (core::Requisition *requisition) requisition->ascent = 1; requisition->descent = 0; } + + DBG_OBJ_MSGF ("resize", 1, "result: %d * (%d + %d)", + requisition->width, requisition->ascent, requisition->descent); + DBG_OBJ_MSG_END (); } @@ -1116,6 +1182,9 @@ int FltkOptionMenuResource::getMaxItemWidth() void FltkOptionMenuResource::sizeRequest (core::Requisition *requisition) { + DBG_OBJ_MSG ("resize", 0, "<b>sizeRequest</b>"); + DBG_OBJ_MSG_START (); + if (style) { FltkFont *font = (FltkFont*)style->font; fl_font(font->font, font->size); @@ -1130,6 +1199,10 @@ void FltkOptionMenuResource::sizeRequest (core::Requisition *requisition) requisition->ascent = 1; requisition->descent = 0; } + + DBG_OBJ_MSGF ("resize", 1, "result: %d * (%d + %d)", + requisition->width, requisition->ascent, requisition->descent); + DBG_OBJ_MSG_END (); } void FltkOptionMenuResource::enlargeMenu () @@ -1405,6 +1478,9 @@ int FltkListResource::getMaxItemWidth() void FltkListResource::sizeRequest (core::Requisition *requisition) { + DBG_OBJ_MSG ("resize", 0, "<b>sizeRequest</b>"); + DBG_OBJ_MSG_START (); + if (style) { CustBrowser *b = (CustBrowser *) widget; int height = b->full_height(); @@ -1425,6 +1501,10 @@ void FltkListResource::sizeRequest (core::Requisition *requisition) requisition->ascent = 1; requisition->descent = 0; } + + DBG_OBJ_MSGF ("resize", 1, "result: %d * (%d + %d)", + requisition->width, requisition->ascent, requisition->descent); + DBG_OBJ_MSG_END (); } int FltkListResource::getNumberOfItems() diff --git a/dw/fltkui.hh b/dw/fltkui.hh index 8667a0cb..a0d2d7b7 100644 --- a/dw/fltkui.hh +++ b/dw/fltkui.hh @@ -223,8 +223,8 @@ public: template <class I> class FltkSpecificResource: public I, public FltkResource { public: - inline FltkSpecificResource (FltkPlatform *platform) : - FltkResource (platform) { } + FltkSpecificResource (FltkPlatform *platform); + ~FltkSpecificResource (); void sizeAllocate (core::Allocation *allocation); void draw (core::View *view, core::Rectangle *area); diff --git a/dw/iterator.cc b/dw/iterator.cc index 18d7cd5a..18d62a49 100644 --- a/dw/iterator.cc +++ b/dw/iterator.cc @@ -55,6 +55,24 @@ bool Iterator::equals (Object *other) (getWidget() == otherIt->getWidget() && compareTo(otherIt) == 0); } +void Iterator::intoStringBuffer(misc::StringBuffer *sb) +{ + sb->append ("{ widget = "); + //widget->intoStringBuffer (sb); + sb->appendPointer (widget); + sb->append (" ("); + sb->append (widget->getClassName()); + sb->append (")>"); + + sb->append (", mask = "); + Content::maskIntoStringBuffer (mask, sb); + + sb->append (", content = "); + Content::intoStringBuffer (&content, sb); + + sb->append (" }"); +} + /** * \brief Delete the iterator. * @@ -186,6 +204,14 @@ void Iterator::scrollTo (Iterator *it1, Iterator *it2, int start, int end, } } + +void Iterator::print () +{ + misc::StringBuffer sb; + intoStringBuffer (&sb); + printf ("%s", sb.getChars ()); +} + // ------------------- // EmptyIterator // ------------------- @@ -343,7 +369,7 @@ Iterator *DeepIterator::searchDownward (Iterator *it, Content::Type mask, //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); + assert (it->getContent()->type & Content::ANY_WIDGET); it2 = it->getContent()->widget->iterator (mask, fromEnd); if (it2 == NULL) { @@ -356,7 +382,7 @@ Iterator *DeepIterator::searchDownward (Iterator *it, Content::Type mask, //DEBUG_MSG (1, "%*sexamining %s\n", // indent, "", a_Dw_iterator_text (it2)); - if (it2->getContent()->type == Content::WIDGET) { + if (it2->getContent()->type & Content::ANY_WIDGET) { // Another widget. Search in it downwards. it3 = searchDownward (it2, mask, fromEnd); if (it3 != NULL) { @@ -390,11 +416,11 @@ Iterator *DeepIterator::searchSideward (Iterator *it, Content::Type mask, //DEBUG_MSG (1, "%*smoving %swards from %s\n", // indent, "", from_end ? "back" : "for", a_Dw_iterator_text (it)); - assert (it->getContent()->type == Content::WIDGET); + assert (it->getContent()->type & Content::ANY_WIDGET); it2 = it->cloneIterator (); while (fromEnd ? it2->prev () : it2->next ()) { - if (it2->getContent()->type == Content::WIDGET) { + if (it2->getContent()->type & Content::ANY_WIDGET) { // Search downwards in this widget. it3 = searchDownward (it2, mask, fromEnd); if (it3 != NULL) { @@ -416,13 +442,14 @@ Iterator *DeepIterator::searchSideward (Iterator *it, Content::Type mask, /* Nothing found, go upwards in the tree (if possible). */ it2->unref (); - if (it->getWidget()->getParent ()) { - it2 = it->getWidget()->getParent()->iterator (mask, false); + Widget *respParent = getRespectiveParent (it->getWidget(), it->getMask()); + if (respParent) { + it2 = respParent->iterator (mask, false); while (true) { if (!it2->next ()) misc::assertNotReached (); - if (it2->getContent()->type == Content::WIDGET && + if (it2->getContent()->type & Content::ANY_WIDGET && it2->getContent()->widget == it->getWidget ()) { it3 = searchSideward (it2, mask, fromEnd); it2->unref (); @@ -440,6 +467,27 @@ Iterator *DeepIterator::searchSideward (Iterator *it, Content::Type mask, return NULL; } +Widget *DeepIterator::getRespectiveParent (Widget *widget, Content::Type mask) +{ + // Return, depending on which is requested indirectly (follow + // references or containments) the parent (container) or the + // generator. At this point, the type of the parent/generator is + // not known (since the parent/generator is not known), so we have + // to examine the mask. This is the reason why only one of + // WIDGET_OOF_CONT and WIDGET_OOF_REF is allowed. + + return (mask & Content::WIDGET_OOF_REF) ? + widget->getGenerator() : widget->getParent(); +} + +int DeepIterator::getRespectiveLevel (Widget *widget, Content::Type mask) +{ + // Similar to getRespectiveParent. + + return (mask & Content::WIDGET_OOF_REF) ? + widget->getGeneratorLevel() : widget->getLevel(); +} + /** * \brief Create a new deep iterator from an existing dw::core::Iterator. * @@ -456,6 +504,19 @@ Iterator *DeepIterator::searchSideward (Iterator *it, Content::Type mask, */ DeepIterator::DeepIterator (Iterator *it) { + //printf ("Starting creating DeepIterator %p ...\n", this); + //printf ("Initial iterator: "); + //it->print (); + //printf ("\n"); + + // Widgets out of flow are either followed widtin containers, or + // generators. Both (and also nothing at all) is not allowed. See + // also comment in getRespectiveParent. + int oofMask = + it->getMask() & (Content::WIDGET_OOF_CONT | Content::WIDGET_OOF_REF); + assert (oofMask == Content::WIDGET_OOF_CONT || + oofMask == Content::WIDGET_OOF_REF); + //DEBUG_MSG (1, "a_Dw_ext_iterator_new: %s\n", a_Dw_iterator_text (it)); // Clone input iterator, so the iterator passed as parameter @@ -467,7 +528,7 @@ DeepIterator::DeepIterator (Iterator *it) // 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) { + if (it->getContent()->type & Content::ANY_WIDGET) { Iterator *it2; // The second argument of searchDownward is actually a matter of @@ -494,31 +555,50 @@ DeepIterator::DeepIterator (Iterator *it) // \todo There may be a faster way instead of iterating through the // parent widgets. + //printf ("Starting with: "); + //it->print (); + //printf ("\n"); + // Construct the iterators. - int thisLevel = it->getWidget()->getLevel (), level; + int thisLevel = getRespectiveLevel (it->getWidget()), level; Widget *w; - for (w = it->getWidget (), level = thisLevel; w->getParent() != NULL; - w = w->getParent (), level--) { - Iterator *it = w->getParent()->iterator (mask, false); + for (w = it->getWidget (), level = thisLevel; + getRespectiveParent (w) != NULL; + w = getRespectiveParent (w), level--) { + Iterator *it = getRespectiveParent(w)->iterator (mask, false); + + //printf (" parent: %s %p\n", w->getClassName (), w); + stack.put (it, level - 1); while (true) { + //printf (" "); + //it->print (); + //printf ("\n"); + bool hasNext = it->next(); assert (hasNext); - if (it->getContent()->type == Content::WIDGET && + if (it->getContent()->type & Content::ANY_WIDGET && it->getContent()->widget == w) break; } + + //printf (" %d: ", level - 1); + //it->print (); + //printf ("\n"); } stack.put (it, thisLevel); content = *(it->getContent()); } + + //printf ("... done creating DeepIterator %p.\n", this); } DeepIterator::~DeepIterator () { + //printf ("Deleting DeepIterator %p ...\n", this); } object::Object *DeepIterator::clone () @@ -539,9 +619,29 @@ int DeepIterator::compareTo (object::Comparable *other) { DeepIterator *otherDeepIterator = (DeepIterator*)other; + //printf ("Compare: %s\n", stack.toString ()); + //printf (" to: %s\n", otherDeepIterator->stack.toString ()); + // Search the highest level, where the widgets are the same. int level = 0; + // The Comparable interface does not define "uncomparable". Deep + // iterators are only comparable if they belong to the same widget + // tree, so have the same widget at the bottom at the + // stack. If this is not the case, we abort. + + assert (stack.size() > 0); + assert (otherDeepIterator->stack.size() > 0); + + //printf ("Equal? The %s %p (of %p) and the %s %p (of %p)?\n", + // stack.get(0)->getWidget()->getClassName(), + // stack.get(0)->getWidget(), this, + // otherDeepIterator->stack.get(0)->getWidget()->getClassName(), + // otherDeepIterator->stack.get(0)->getWidget(), otherDeepIterator); + + assert (stack.get(0)->getWidget() + == otherDeepIterator->stack.get(level)->getWidget()); + while (stack.get(level)->getWidget () == otherDeepIterator->stack.get(level)->getWidget ()) { if (level == stack.size() - 1 || @@ -550,10 +650,14 @@ int DeepIterator::compareTo (object::Comparable *other) level++; } + //printf (" => level = %d (temorally)\n", level); + while (stack.get(level)->getWidget () != otherDeepIterator->stack.get(level)->getWidget ()) level--; + //printf (" => level = %d (finally)\n", level); + return stack.get(level)->compareTo (otherDeepIterator->stack.get(level)); } @@ -577,7 +681,7 @@ bool DeepIterator::next () Iterator *it = stack.getTop (); if (it->next ()) { - if (it->getContent()->type == Content::WIDGET) { + if (it->getContent()->type & Content::ANY_WIDGET) { // Widget: new iterator on stack, to search in this widget. stack.push (it->getContent()->widget->iterator (mask, false)); return next (); @@ -610,7 +714,7 @@ bool DeepIterator::prev () Iterator *it = stack.getTop (); if (it->prev ()) { - if (it->getContent()->type == Content::WIDGET) { + if (it->getContent()->type & Content::ANY_WIDGET) { // Widget: new iterator on stack, to search in this widget. stack.push (it->getContent()->widget->iterator (mask, true)); return prev (); @@ -642,9 +746,17 @@ CharIterator::CharIterator () it = NULL; } -CharIterator::CharIterator (Widget *widget) +/** + * \brief ... + * + * If followReferences is true, only the reference are followed, when + * the container and generator for a widget is different. If false, + * only the container is followed. + */ +CharIterator::CharIterator (Widget *widget, bool followReferences) { - Iterator *i = widget->iterator (Content::SELECTION_CONTENT, false); + Iterator *i = + widget->iterator (Content::maskForSelection (followReferences), false); it = new DeepIterator (i); i->unref (); ch = START; diff --git a/dw/iterator.hh b/dw/iterator.hh index d086721c..abf31d0b 100644 --- a/dw/iterator.hh +++ b/dw/iterator.hh @@ -31,6 +31,7 @@ private: public: bool equals (Object *other); + void intoStringBuffer(lout::misc::StringBuffer *sb); inline Widget *getWidget () { return widget; } inline Content *getContent () { return &content; } @@ -85,6 +86,8 @@ public: static void scrollTo (Iterator *it1, Iterator *it2, int start, int end, HPosition hpos, VPosition vpos); + + virtual void print (); }; @@ -168,6 +171,16 @@ private: inline DeepIterator () { } + static Widget *getRespectiveParent (Widget *widget, Content::Type mask); + inline Widget *getRespectiveParent (Widget *widget) { + return getRespectiveParent (widget, mask); + } + + static int getRespectiveLevel (Widget *widget, Content::Type mask); + inline int getRespectiveLevel (Widget *widget) { + return getRespectiveLevel (widget, mask); + } + public: DeepIterator(Iterator *it); ~DeepIterator(); @@ -230,7 +243,7 @@ private: CharIterator (); public: - CharIterator (Widget *widget); + CharIterator (Widget *widget, bool followReferences); ~CharIterator (); lout::object::Object *clone(); diff --git a/dw/layout.cc b/dw/layout.cc index 6f2e8d8b..289b204b 100644 --- a/dw/layout.cc +++ b/dw/layout.cc @@ -245,6 +245,9 @@ Layout::Layout (Platform *platform) topLevel = NULL; widgetAtPoint = NULL; + queueQueueResizeList = new typed::Vector<QueueResizeItem> (4, true); + queueResizeList = new typed::Vector<Widget> (4, false); + DBG_OBJ_CREATE ("dw::core::Layout"); bgColor = NULL; @@ -277,7 +280,13 @@ Layout::Layout (Platform *platform) selectionState.setLayout(this); + queueResizeCounter = sizeAllocateCounter = sizeRequestCounter = + getExtremesCounter = 0; + layoutImgRenderer = NULL; + + resizeIdleCounter = queueResizeCounter = sizeAllocateCounter + = sizeRequestCounter = getExtremesCounter = 0; } Layout::~Layout () @@ -299,10 +308,14 @@ Layout::~Layout () if (bgImage) bgImage->unref (); if (topLevel) { + detachWidget (topLevel); Widget *w = topLevel; topLevel = NULL; delete w; } + + delete queueQueueResizeList; + delete queueResizeList; delete platform; delete view; delete anchorsTable; @@ -311,6 +324,26 @@ Layout::~Layout () DBG_OBJ_DELETE (); } +void Layout::detachWidget (Widget *widget) +{ + // Called form ~Layout. Sometimes, the widgets (not only the toplevel widget) + // do some stuff after the layout has been deleted, so *all* widgets have to + // be detached, and check "layout != NULL" at relevant points. + + // Could be replaced by a virtual method in Widget, like getWidgetAtPoint, + // if performace were really a problem. + + widget->layout = NULL; + Iterator *it = + widget->iterator ((Content::Type) + (Content::WIDGET_IN_FLOW | Content::WIDGET_OOF_CONT), + false); + while (it->next ()) + detachWidget (it->getContent()->widget); + + it->unref (); +} + void Layout::addWidget (Widget *widget) { if (topLevel) { @@ -320,12 +353,18 @@ void Layout::addWidget (Widget *widget) topLevel = widget; widget->layout = this; + queueResizeList->clear (); + widget->notifySetAsTopLevel(); findtextState.setWidget (widget); canvasHeightGreater = false; setSizeHints (); - queueResize (); + + // Do not directly call Layout::queueResize(), but + // Widget::queueResize(), so that all flags are set properly, + // queueResizeList is filled, etc. + topLevel->queueResize (-1, false); } void Layout::removeWidget () @@ -334,6 +373,7 @@ void Layout::removeWidget () * \bug Some more attributes must be reset here. */ topLevel = NULL; + queueResizeList->clear (); widgetAtPoint = NULL; canvasWidth = canvasAscent = canvasDescent = 0; scrollX = scrollY = 0; @@ -776,11 +816,41 @@ void Layout::setBgImage (style::StyleImage *bgImage, void Layout::resizeIdle () { + DBG_OBJ_MSG ("resize", 0, "<b>resizeIdle</b>"); + DBG_OBJ_MSG_START (); + + enterResizeIdle (); + //static int calls = 0; - //MSG(" Layout::resizeIdle calls = %d\n", ++calls); + //printf ("Layout::resizeIdle calls = %d\n", ++calls); assert (resizeIdleId != -1); + for (typed::Iterator <Widget> it = queueResizeList->iterator(); + it.hasNext (); ) { + Widget *widget = it.getNext (); + + //printf (" the %stop-level %s %p was queued (extremes changed: %s)\n", + // widget->parent ? "non-" : "", widget->getClassName(), widget, + // widget->extremesQueued () ? "yes" : "no"); + + if (widget->resizeQueued ()) { + widget->setFlags (Widget::NEEDS_RESIZE); + widget->unsetFlags (Widget::RESIZE_QUEUED); + } + + if (widget->allocateQueued ()) { + widget->setFlags (Widget::NEEDS_ALLOCATE); + widget->unsetFlags (Widget::ALLOCATE_QUEUED); + } + + if (widget->extremesQueued ()) { + widget->setFlags (Widget::EXTREMES_CHANGED); + widget->unsetFlags (Widget::EXTREMES_QUEUED); + } + } + queueResizeList->clear (); + // Reset already here, since in this function, queueResize() may be // called again. resizeIdleId = -1; @@ -790,7 +860,15 @@ void Layout::resizeIdle () Allocation allocation; topLevel->sizeRequest (&requisition); - + DBG_OBJ_MSGF ("resize", 1, "toplevel size: %d * (%d + %d)", + requisition.width, requisition.ascent, requisition.descent); + + // This method is triggered by Widget::queueResize, which will, + // in any case, set NEEDS_ALLOCATE (indirectly, as + // ALLOCATE_QUEUED). This assertion helps to find + // inconsistences. + assert (topLevel->needsAllocate ()); + allocation.x = allocation.y = 0; allocation.width = requisition.width; allocation.ascent = requisition.ascent; @@ -825,10 +903,15 @@ void Layout::resizeIdle () } // views are redrawn via Widget::resizeDrawImpl () - } updateAnchor (); + + DBG_OBJ_MSGF ("resize", 1, + "after resizeIdle: resizeIdleId = %d", resizeIdleId); + DBG_OBJ_MSG_END (); + + leaveResizeIdle (); } void Layout::setSizeHints () @@ -879,11 +962,17 @@ void Layout::queueDrawExcept (int x, int y, int width, int height, void Layout::queueResize () { + DBG_OBJ_MSG ("resize", 0, "<b>queueResize</b>"); + DBG_OBJ_MSG_START (); + if (resizeIdleId == -1) { view->cancelQueueDraw (); resizeIdleId = platform->addIdle (&Layout::resizeIdle); + DBG_OBJ_MSGF ("resize", 1, "setting resizeIdleId = %d", resizeIdleId); } + + DBG_OBJ_MSG_END (); } @@ -1164,8 +1253,13 @@ void Layout::viewportSizeChanged (View *view, int width, int height) /* if size changes, redraw this view. * TODO: this is a resize call (redraw/resize code needs a review). */ - if (viewportWidth != width || viewportHeight != height) - queueResize(); + if (viewportWidth != width || viewportHeight != height) { + if (topLevel) + // similar to addWidget() + topLevel->queueResize (-1, false); + else + queueResize (); + } viewportWidth = width; viewportHeight = height; diff --git a/dw/layout.hh b/dw/layout.hh index 47554b42..6b6ceadb 100644 --- a/dw/layout.hh +++ b/dw/layout.hh @@ -150,9 +150,26 @@ private: ~Anchor (); }; + class QueueResizeItem: public lout::object::Object + { + public: + Widget *widget; + int ref; + bool extremesChanged; + + inline QueueResizeItem (Widget *widget, int ref, bool extremesChanged) + { + this->widget = widget; + this->ref = ref; + this->extremesChanged = extremesChanged; + } + }; + Platform *platform; View *view; Widget *topLevel, *widgetAtPoint; + lout::container::typed::Vector<QueueResizeItem> *queueQueueResizeList; + lout::container::typed::Vector<Widget> *queueResizeList; /* The state, which must be projected into the view. */ style::Color *bgColor; @@ -186,6 +203,8 @@ private: enum ButtonEventType { BUTTON_PRESS, BUTTON_RELEASE, MOTION_NOTIFY }; + void detachWidget (Widget *widget); + Widget *getWidgetAtPoint (int x, int y); void moveToWidget (Widget *newWidgetAtPoint, ButtonState state); @@ -236,6 +255,16 @@ private: void queueResize (); void removeWidget (); + /* For tests regarding the respective Layout and (mostly) Widget + methods. Accessed by respective methods (enter..., leave..., + ...Entered) defined here and in Widget. */ + + int resizeIdleCounter, queueResizeCounter, sizeAllocateCounter, + sizeRequestCounter, getExtremesCounter; + + void enterResizeIdle () { resizeIdleCounter++; } + void leaveResizeIdle () { resizeIdleCounter--; } + public: Layout (Platform *platform); ~Layout (); diff --git a/dw/outofflowmgr.cc b/dw/outofflowmgr.cc new file mode 100644 index 00000000..0c9a92be --- /dev/null +++ b/dw/outofflowmgr.cc @@ -0,0 +1,2114 @@ +/* + * Dillo Widget + * + * Copyright 2013 Sebastian Geerken <sgeerken@dillo.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#include "outofflowmgr.hh" +#include "textblock.hh" +#include "../lout/debug.hh" + +using namespace lout::object; +using namespace lout::container::typed; +using namespace lout::misc; +using namespace dw::core; +using namespace dw::core::style; + +namespace dw { + +OutOfFlowMgr::WidgetInfo::WidgetInfo (OutOfFlowMgr *oofm, Widget *widget) +{ + this->oofm = oofm; + this->widget = widget; + wasAllocated = false; + xCB = yCB = width = height = -1; +} + +void OutOfFlowMgr::WidgetInfo::update (bool wasAllocated, int xCB, int yCB, + int width, int height) +{ + DBG_OBJ_MSGF_O ("resize.oofm", 0, widget, + "<b>update</b> (%s, %d, %d, %d, %d)", + wasAllocated ? "true" : "false", xCB, yCB, width, height); + + this->wasAllocated = wasAllocated; + this->xCB = xCB; + this->yCB = yCB; + this->width = width; + this->height = height; + + DBG_OBJ_SET_NUM_O (widget, "<WidgetInfo>.xCB", xCB); + DBG_OBJ_SET_NUM_O (widget, "<WidgetInfo>.yCB", yCB); + DBG_OBJ_SET_NUM_O (widget, "<WidgetInfo>.width", width); + DBG_OBJ_SET_NUM_O (widget, "<WidgetInfo>.height", height); +} + +void OutOfFlowMgr::WidgetInfo::updateAllocation () +{ + DBG_OBJ_MSG_O ("resize.oofm", 0, widget, "<b>updateAllocation</b> ()"); + DBG_OBJ_MSG_START_O (widget); + + update (isNowAllocated (), getNewXCB (), getNewYCB (), getNewWidth (), + getNewHeight ()); + + DBG_OBJ_MSG_END_O (widget); +} + +// ---------------------------------------------------------------------- + +OutOfFlowMgr::Float::Float (OutOfFlowMgr *oofm, Widget *widget, + Textblock *generatingBlock, int externalIndex) : + WidgetInfo (oofm, widget) +{ + this->generatingBlock = generatingBlock; + this->externalIndex = externalIndex; + + yReq = yReal = size.width = size.ascent = size.descent = 0; + dirty = sizeChangedSinceLastAllocation = true; + inCBList = false; + + // Sometimes a float with widget = NULL is created as a key; this + // is not interesting for RTFL. + if (widget) { + DBG_OBJ_SET_NUM_O (widget, "<Float>.yReq", yReq); + DBG_OBJ_SET_NUM_O (widget, "<Float>.yReal", yReal); + DBG_OBJ_SET_NUM_O (widget, "<Float>.size.width", size.width); + DBG_OBJ_SET_NUM_O (widget, "<Float>.size.ascent", size.ascent); + DBG_OBJ_SET_NUM_O (widget, "<Float>.size.descent", size.descent); + } +} + +void OutOfFlowMgr::Float::intoStringBuffer(StringBuffer *sb) +{ + sb->append ("{ widget = "); + sb->appendPointer (getWidget ()); + + if (getWidget ()) { + sb->append (" ("); + sb->append (getWidget()->getClassName ()); + sb->append (")"); + } + + sb->append (", index = "); + sb->appendInt (index); + sb->append (", sideSpanningIndex = "); + sb->appendInt (sideSpanningIndex); + sb->append (", generatingBlock = "); + sb->appendPointer (generatingBlock); + sb->append (", yReq = "); + sb->appendInt (yReq); + sb->append (", yReal = "); + sb->appendInt (yReal); + sb->append (", size = { "); + sb->appendInt (size.width); + sb->append (" * "); + sb->appendInt (size.ascent); + sb->append (" + "); + sb->appendInt (size.descent); + sb->append (" }, dirty = "); + sb->appendBool (dirty); + sb->append (", sizeChangedSinceLastAllocation = "); + sb->appendBool (sizeChangedSinceLastAllocation); + sb->append (", inCBList = "); + sb->appendBool (inCBList); + sb->append (" }"); +} + +bool OutOfFlowMgr::Float::covers (Textblock *textblock, int y, int h) +{ + DBG_OBJ_MSGF_O ("border", 0, getOutOfFlowMgr (), + "<b>covers</b> (%p, %d, %d) [vloat: %p]", + textblock, y, h, getWidget ()); + DBG_OBJ_MSG_START_O (getOutOfFlowMgr ()); + + bool b; + + if (textblock == generatingBlock) { + int reqyGB = y; + int flyGB = yReal; + getOutOfFlowMgr()->ensureFloatSize (this); + int flh = size.ascent + size.descent; + b = flyGB + flh > reqyGB && flyGB < reqyGB + h; + + DBG_OBJ_MSGF_O ("border", 1, getOutOfFlowMgr (), + "for generator: reqyGB = %d, flyGB = %d, " + "flh = %d + %d = %d => %s", + reqyGB, flyGB, size.ascent, size.descent, flh, + b ? "true" : "false"); + } else { + assert (getOutOfFlowMgr()->wasAllocated (generatingBlock)); + assert (getOutOfFlowMgr()->wasAllocated (textblock)); + + if (!getWidget()->wasAllocated ()) { + DBG_OBJ_MSG_O ("border", 1, getOutOfFlowMgr (), + "not generator (not allocated) => false"); + b = false; + } else { + Allocation *tba = getOutOfFlowMgr()->getAllocation(textblock), + //*gba = getOutOfFlowMgr()->getAllocation(generatingBlock), + *fla = getWidget()->getAllocation (); + int reqyCanv = tba->y + y; + int flyCanv = fla->y; + int flh = fla->ascent + fla->descent; + b = flyCanv + flh > reqyCanv && flyCanv < reqyCanv + h; + + DBG_OBJ_MSGF_O ("border", 1, getOutOfFlowMgr (), + "not generator (allocated): reqyCanv = %d + %d = %d, " + "flyCanv = %d, flh = %d + %d = %d => %s", + tba->y, y, reqyCanv, flyCanv, + fla->ascent, fla->descent, flh, b ? "true" : "false"); + } + } + + DBG_OBJ_MSG_END_O (getOutOfFlowMgr ()); + + return b; +} + +int OutOfFlowMgr::Float::ComparePosition::compare (Object *o1, Object *o2) +{ + Float *fl1 = (Float*)o1, *fl2 = (Float*)o2; + int r; + + DBG_OBJ_MSGF_O ("border", 1, oofm, + "<b>ComparePosition::compare</b> (#%d, #%d) [refTB = %p]", + fl1->index, fl2->index, refTB); + DBG_OBJ_MSG_START_O (oofm); + + if (refTB == fl1->generatingBlock && refTB == fl2->generatingBlock) { + DBG_OBJ_MSG_O ("border", 2, oofm, "refTB is generating both floats"); + r = fl1->yReal - fl2->yReal; + } else { + DBG_OBJ_MSG_O ("border", 2, oofm, "refTB is not generating both floats"); + DBG_OBJ_MSG_START_O (oofm); + + assert (oofm->wasAllocated (fl1->generatingBlock)); + assert (oofm->wasAllocated (fl2->generatingBlock)); + + DBG_OBJ_MSGF_O ("border", 2, oofm, "generators are %p and %p", + fl1->generatingBlock, fl2->generatingBlock); + + // (i) Floats may not yet been allocated (although the + // generators are). Non-allocated floats do not have an effect + // yet, they are considered "at the end" of the list. + + // (ii) Float::widget for the key used for binary search. In + // this case, Float::yReal is used instead (which is set in + // SortedFloatsVector::find). + + bool a1 = fl1->getWidget () ? fl1->getWidget()->wasAllocated () : true; + bool a2 = fl2->getWidget () ? fl2->getWidget()->wasAllocated () : true; + + DBG_OBJ_MSGF_O ("border", 2, oofm, + "float 1 allocated: %s; float 2 allocated: %s", + a1 ? "yes" : "no", a2 ? "yes" : "no"); + + if (a1 && a2) { + int fly1 = fl1->getWidget() ? fl1->getWidget()->getAllocation()->y : + oofm->getAllocation(fl1->generatingBlock)->y + fl1->yReal; + int fly2 = fl2->getWidget() ? fl2->getWidget()->getAllocation()->y : + oofm->getAllocation(fl2->generatingBlock)->y + fl2->yReal; + DBG_OBJ_MSGF_O ("border", 2, oofm, "y diff = %d - %d", fly1, fly2); + r = fly1 - fly2; + } else if (a1 && !a2) + r = -1; + else if (!a1 && a2) + r = +1; + else // if (!a1 && !a2) + return 0; + + DBG_OBJ_MSG_END_O (oofm); + } + + DBG_OBJ_MSGF_O ("border", 1, oofm, "result: %d", r); + DBG_OBJ_MSG_END_O (oofm); + return r; +} + +int OutOfFlowMgr::Float::CompareSideSpanningIndex::compare (Object *o1, + Object *o2) +{ + return ((Float*)o1)->sideSpanningIndex - ((Float*)o2)->sideSpanningIndex; +} + +int OutOfFlowMgr::Float::CompareGBAndExtIndex::compare (Object *o1, Object *o2) +{ + Float *f1 = (Float*)o1, *f2 = (Float*)o2; + int r = -123; // Compiler happiness: GCC 4.7 does not handle this?; + + DBG_OBJ_MSGF_O ("border", 1, oofm, + "<b>CompareGBAndExtIndex::compare</b> (#%d -> %p/%d, " + "#%d -> %p/#%d)", + f1->index, f1->generatingBlock, f1->externalIndex, + f2->index, f2->generatingBlock, f2->externalIndex); + DBG_OBJ_MSG_START_O (oofm); + + if (f1->generatingBlock == f2->generatingBlock) { + r = f1->externalIndex - f2->externalIndex; + DBG_OBJ_MSGF_O ("border", 2, oofm, + "(a) generating blocks equal => %d - %d = %d", + f1->externalIndex, f2->externalIndex, r); + } else { + TBInfo *t1 = oofm->getTextblock (f1->generatingBlock), + *t2 = oofm->getTextblock (f2->generatingBlock); + bool rdef = false; + + for (TBInfo *t = t1; t != NULL; t = t->parent) + if (t->parent == t2) { + rdef = true; + r = t->parentExtIndex - f2->externalIndex; + DBG_OBJ_MSGF_O ("border", 2, oofm, + "(b) %p is an achestor of %p; direct child is " + "%p (%d) => %d - %d = %d\n", + t2->getTextblock (), t1->getTextblock (), + t->getTextblock (), t->parentExtIndex, + t->parentExtIndex, f2->externalIndex, r); + } + + for (TBInfo *t = t2; !rdef && t != NULL; t = t->parent) + if (t->parent == t1) { + r = f1->externalIndex - t->parentExtIndex; + rdef = true; + DBG_OBJ_MSGF_O ("border", 2, oofm, + "(c) %p is an achestor of %p; direct child is %p " + "(%d) => %d - %d = %d\n", + t1->getTextblock (), t2->getTextblock (), + t->getTextblock (), t->parentExtIndex, + f1->externalIndex, t->parentExtIndex, r); + } + + if (!rdef) { + r = t1->index - t2->index; + DBG_OBJ_MSGF_O ("border", 2, oofm, "(d) other => %d - %d = %d", + t1->index, t2->index, r); + } + } + + DBG_OBJ_MSGF_O ("border", 2, oofm, "result: %d", r); + DBG_OBJ_MSG_END_O (oofm); + return r; +} + +int OutOfFlowMgr::SortedFloatsVector::findFloatIndex (Textblock *lastGB, + int lastExtIndex) +{ + DBG_OBJ_MSGF_O ("border", 0, oofm, "<b>findFloatIndex</b> (%p, %d)", + lastGB, lastExtIndex); + DBG_OBJ_MSG_START_O (oofm); + + Float key (oofm, NULL, lastGB, lastExtIndex); + key.index = -1; // for debugging + Float::CompareGBAndExtIndex comparator (oofm); + int i = bsearch (&key, false, &comparator); + + // At position i is the next larger element, so element i should + // not included, but i - 1 returned; except if the exact element is + // found: then include it and so return i. + int r; + if (i == size()) + r = i - 1; + else { + Float *f = get (i); + if (comparator.compare (f, &key) == 0) + r = i; + else + r = i - 1; + } + + //printf ("[%p] findFloatIndex (%p, %d) => i = %d, r = %d (size = %d); " + // "in %s list %p on the %s side\n", + // oofm->containingBlock, lastGB, lastExtIndex, i, r, size (), + // type == GB ? "GB" : "CB", this, side == LEFT ? "left" : "right"); + + //for (int i = 0; i < size (); i++) { + // Float *f = get(i); + // TBInfo *t = oofm->getTextblock(f->generatingBlock); + // printf (" %d: (%p [%d, %p], %d)\n", i, f->generatingBlock, + // t->index, t->parent ? t->parent->textblock : NULL, + // get(i)->externalIndex); + //} + + DBG_OBJ_MSGF_O ("border", 1, oofm, "=> r = %d", r); + DBG_OBJ_MSG_END_O (oofm); + return r; +} + +int OutOfFlowMgr::SortedFloatsVector::find (Textblock *textblock, int y, + int start, int end) +{ + DBG_OBJ_MSGF_O ("border", 0, oofm, "<b>find</b> (%p, %d, %d, %d)", + textblock, y, start, end); + DBG_OBJ_MSG_START_O (oofm); + + Float key (oofm, NULL, NULL, 0); + key.generatingBlock = textblock; + key.yReal = y; + key.index = -1; // for debugging + Float::ComparePosition comparator (oofm, textblock); + int result = bsearch (&key, false, start, end, &comparator); + + DBG_OBJ_MSGF_O ("border", 1, oofm, "=> result = %d", result); + DBG_OBJ_MSG_END_O (oofm); + return result; +} + +int OutOfFlowMgr::SortedFloatsVector::findFirst (Textblock *textblock, + int y, int h, + Textblock *lastGB, + int lastExtIndex) +{ + DBG_OBJ_MSGF_O ("border", 0, oofm, "<b>findFirst</b> (%p, %d, %d, %p, %d)", + textblock, y, h, lastGB, lastExtIndex); + DBG_OBJ_MSG_START_O (oofm); + + DBG_IF_RTFL { + DBG_OBJ_MSG_O ("border", 2, oofm, "searching in list:"); + DBG_OBJ_MSG_START_O (oofm); + + for (int i = 0; i < size(); i++) { + Float *f = get(i); + DBG_OBJ_MSGF_O ("border", 2, oofm, + "%d: (%p, i = %d/%d, y = %d/%d, s = (%d * (%d + %d)), " + "%s, %s, ext = %d, GB = %p); widget at (%d, %d)", + i, f->getWidget (), f->index, f->sideSpanningIndex, + f->yReq, f->yReal, f->size.width, f->size.ascent, + f->size.descent, f->dirty ? "dirty" : "clean", + f->sizeChangedSinceLastAllocation ? "scsla" : "sNcsla", + f->externalIndex, f->generatingBlock, + f->getWidget()->getAllocation()->x, + f->getWidget()->getAllocation()->y); + } + + DBG_OBJ_MSG_END_O (oofm); + } + + int last = findFloatIndex (lastGB, lastExtIndex); + DBG_OBJ_MSGF_O ("border", 1, oofm, "last = %d", last); + assert (last < size()); + int i = find (textblock, y, 0, last), result; + DBG_OBJ_MSGF_O ("border", 1, oofm, "i = %d", i); + + // Note: The smallest value of "i" is 0, which means that "y" is before or + // equal to the first float. The largest value is "last + 1", which means + // that "y" is after the last float. In both cases, the first or last, + // respectively, float is a candidate. Generally, both floats, before and + // at the search position, are candidates. + + if (i > 0 && get(i - 1)->covers (textblock, y, h)) + result = i - 1; + else if (i <= last && get(i)->covers (textblock, y, h)) + result = i; + else + result = -1; + + DBG_OBJ_MSGF_O ("border", 1, oofm, "=> result = %d", result); + DBG_OBJ_MSG_END_O (oofm); + return result; +} + +int OutOfFlowMgr::SortedFloatsVector::findLastBeforeSideSpanningIndex + (int sideSpanningIndex) +{ + OutOfFlowMgr::Float::CompareSideSpanningIndex comparator; + Float key (NULL, NULL, NULL, 0); + key.sideSpanningIndex = sideSpanningIndex; + return bsearch (&key, false, &comparator) - 1; +} + +void OutOfFlowMgr::SortedFloatsVector::put (Float *vloat) +{ + lout::container::typed::Vector<Float>::put (vloat); + vloat->index = size() - 1; + vloat->inCBList = type == CB; +} + +OutOfFlowMgr::TBInfo::TBInfo (OutOfFlowMgr *oofm, Textblock *textblock, + TBInfo *parent, int parentExtIndex) : + WidgetInfo (oofm, textblock) +{ + this->parent = parent; + this->parentExtIndex = parentExtIndex; + + leftFloatsGB = new SortedFloatsVector (oofm, LEFT, SortedFloatsVector::GB); + rightFloatsGB = new SortedFloatsVector (oofm, RIGHT, SortedFloatsVector::GB); +} + +OutOfFlowMgr::TBInfo::~TBInfo () +{ + delete leftFloatsGB; + delete rightFloatsGB; +} + +OutOfFlowMgr::AbsolutelyPositioned::AbsolutelyPositioned (OutOfFlowMgr *oofm, + Widget *widget, + Textblock + *generatingBlock, + int externalIndex) +{ + this->widget = widget; + dirty = true; +} + +OutOfFlowMgr::OutOfFlowMgr (Textblock *containingBlock) +{ + DBG_OBJ_CREATE ("dw::OutOfFlowMgr"); + + this->containingBlock = containingBlock; + + leftFloatsCB = new SortedFloatsVector (this, LEFT, SortedFloatsVector::CB); + rightFloatsCB = new SortedFloatsVector (this, RIGHT, SortedFloatsVector::CB); + + leftFloatsAll = new Vector<Float> (1, true); + rightFloatsAll = new Vector<Float> (1, true); + + DBG_OBJ_SET_NUM ("leftFloatsAll.size", leftFloatsAll->size()); + DBG_OBJ_SET_NUM ("rightFloatsAll.size", rightFloatsAll->size()); + + floatsByWidget = new HashTable <TypedPointer <Widget>, Float> (true, false); + + tbInfos = new Vector<TBInfo> (1, false); + tbInfosByTextblock = + new HashTable <TypedPointer <Textblock>, TBInfo> (true, true); + + leftFloatsMark = rightFloatsMark = 0; + lastLeftTBIndex = lastRightTBIndex = 0; + + absolutelyPositioned = new Vector<AbsolutelyPositioned> (1, true); + + containingBlockWasAllocated = containingBlock->wasAllocated (); + if (containingBlockWasAllocated) + containingBlockAllocation = *(containingBlock->getAllocation()); + + addWidgetInFlow (containingBlock, NULL, 0); +} + +OutOfFlowMgr::~OutOfFlowMgr () +{ + //printf ("OutOfFlowMgr::~OutOfFlowMgr\n"); + + delete leftFloatsCB; + delete rightFloatsCB; + + // Order is important: tbInfosByTextblock is owner of the instances + // of TBInfo.tbInfosByTextblock + delete tbInfos; + delete tbInfosByTextblock; + + delete floatsByWidget; + + // Order is important, since the instances of Float are owned by + // leftFloatsAll and rightFloatsAll, so these should be deleted + // last. + delete leftFloatsAll; + delete rightFloatsAll; + + delete absolutelyPositioned; + + DBG_OBJ_DELETE (); +} + +void OutOfFlowMgr::sizeAllocateStart (Allocation *containingBlockAllocation) +{ + DBG_OBJ_MSG ("resize.oofm", 0, "<b>sizeAllocateStart</b>"); + this->containingBlockAllocation = *containingBlockAllocation; + containingBlockWasAllocated = true; +} + +void OutOfFlowMgr::sizeAllocateEnd () +{ + DBG_OBJ_MSG ("resize.oofm", 0, "<b>sizeAllocateEnd</b>"); + DBG_OBJ_MSG_START (); + + // Move floats from GB lists to the one CB list. + moveFromGBToCB (LEFT); + moveFromGBToCB (RIGHT); + + // Floats and absolutely positioned blocks have to be allocated + sizeAllocateFloats (LEFT); + sizeAllocateFloats (RIGHT); + sizeAllocateAbsolutelyPositioned (); + + // Textblocks have already been allocated here. + + // Check changes of both textblocks and floats allocation. (All is checked + // by hasRelationChanged (...).) + for (lout::container::typed::Iterator<TypedPointer <Textblock> > it = + tbInfosByTextblock->iterator (); + it.hasNext (); ) { + TypedPointer <Textblock> *key = it.getNext (); + TBInfo *tbInfo = tbInfosByTextblock->get (key); + Textblock *tb = key->getTypedValue(); + + int minFloatPos; + Widget *minFloat; + if (hasRelationChanged (tbInfo, &minFloatPos, &minFloat)) + tb->borderChanged (minFloatPos, minFloat); + } + + // Store some information for later use. + for (lout::container::typed::Iterator<TypedPointer <Textblock> > it = + tbInfosByTextblock->iterator (); + it.hasNext (); ) { + TypedPointer <Textblock> *key = it.getNext (); + TBInfo *tbInfo = tbInfosByTextblock->get (key); + Textblock *tb = key->getTypedValue(); + + tbInfo->updateAllocation (); + tbInfo->availWidth = tb->getAvailWidth (); + } + + // There are cases where some allocated floats (TODO: later also absolutely + // positioned elements?) exceed the CB allocation. + bool sizeChanged = doFloatsExceedCB (LEFT) || doFloatsExceedCB (RIGHT); + + // Similar for extremes. (TODO: here also absolutely positioned elements?) + bool extremesChanged = + haveExtremesChanged (LEFT) || haveExtremesChanged (RIGHT); + + for (int i = 0; i < leftFloatsCB->size(); i++) + leftFloatsCB->get(i)->updateAllocation (); + + for (int i = 0; i < rightFloatsCB->size(); i++) + rightFloatsCB->get(i)->updateAllocation (); + + if (sizeChanged || extremesChanged) + containingBlock->oofSizeChanged (extremesChanged); + + DBG_OBJ_MSG_END (); +} + +bool OutOfFlowMgr::hasRelationChanged (TBInfo *tbInfo, int *minFloatPos, + Widget **minFloat) +{ + DBG_OBJ_MSGF ("resize.oofm", 0, + "<b>hasRelationChanged</b> (<i>widget:</i> %p, ...)", + tbInfo->getWidget ()); + DBG_OBJ_MSG_START (); + + int leftMinPos, rightMinPos; + Widget *leftMinFloat, *rightMinFloat; + bool c1 = + hasRelationChanged (tbInfo, LEFT, &leftMinPos, &leftMinFloat); + bool c2 = + hasRelationChanged (tbInfo, RIGHT, &rightMinPos, &rightMinFloat); + if (c1 || c2) { + if (!c1) { + *minFloatPos = rightMinPos; + *minFloat = rightMinFloat; + } else if (!c2) { + *minFloatPos = leftMinPos; + *minFloat = leftMinFloat; + } else { + if (leftMinPos < rightMinPos) { + *minFloatPos = leftMinPos; + *minFloat = leftMinFloat; + } else{ + *minFloatPos = rightMinPos; + *minFloat = rightMinFloat; + } + } + } + + if (c1 || c2) + DBG_OBJ_MSGF ("resize.oofm", 1, + "has changed: minFloatPos = %d, minFloat = %p", + *minFloatPos, *minFloat); + else + DBG_OBJ_MSG ("resize.oofm", 1, "has not changed"); + + DBG_OBJ_MSG_END (); + return c1 || c2; +} + +bool OutOfFlowMgr::hasRelationChanged (TBInfo *tbInfo, Side side, + int *minFloatPos, Widget **minFloat) +{ + DBG_OBJ_MSGF ("resize.oofm", 0, + "<b>hasRelationChanged</b> (<i>widget:</i> %p, %s, ...)", + tbInfo->getWidget (), side == LEFT ? "LEFT" : "RIGHT"); + DBG_OBJ_MSG_START (); + + SortedFloatsVector *list = side == LEFT ? leftFloatsCB : rightFloatsCB; + bool changed = false; + + for (int i = 0; i < list->size(); i++) { + // TODO binary search? + Float *vloat = list->get(i); + int floatPos; + + if (tbInfo->getTextblock () == vloat->generatingBlock) + DBG_OBJ_MSGF ("resize.oofm", 1, + "not checking (generating!) textblock %p against float " + "%p", tbInfo->getWidget (), vloat->getWidget ()); + else { + Allocation *gba = getAllocation (vloat->generatingBlock); + + int newFlx = + calcFloatX (vloat, side, + gba->x - containingBlockAllocation.x, + gba->width, vloat->generatingBlock->getAvailWidth ()); + int newFly = vloat->generatingBlock->getAllocation()->y + - containingBlockAllocation.y + vloat->yReal; + + DBG_OBJ_MSGF ("resize.oofm", 1, + "checking textblock %p against float %p", + tbInfo->getWidget (), vloat->getWidget ()); + DBG_OBJ_MSG_START (); + + if (hasRelationChanged (tbInfo->wasThenAllocated (), + tbInfo->getOldXCB (), tbInfo->getOldYCB (), + tbInfo->getOldWidth (), + tbInfo->getOldHeight (), + tbInfo->getNewXCB (), tbInfo->getNewYCB (), + tbInfo->getNewWidth (), + tbInfo->getNewHeight (), + vloat->wasThenAllocated (), + // When not allocated before, these values + // are undefined, but this does not matter, + // since they are neither used. + vloat->getOldXCB (), vloat->getOldYCB (), + vloat->getOldWidth (), vloat->getOldHeight (), + newFlx, newFly, vloat->size.width, + vloat->size.ascent + vloat->size.descent, + side, &floatPos)) { + if (!changed || floatPos < *minFloatPos) { + *minFloatPos = floatPos; + *minFloat = vloat->getWidget (); + } + changed = true; + } else + DBG_OBJ_MSG ("resize.oofm", 0, "No."); + + DBG_OBJ_MSG_END (); + } + + // All floarts are searched, to find the minimum. TODO: Are + // floats sorted, so this can be shortened? (The first is the + // minimum?) + } + + if (changed) + DBG_OBJ_MSGF ("resize.oofm", 1, + "has changed: minFloatPos = %d, minFloat = %p", + *minFloatPos, *minFloat); + else + DBG_OBJ_MSG ("resize.oofm", 1, "has not changed"); + + DBG_OBJ_MSG_END (); + return changed; +} + +/** + * \brief ... + * + * All coordinates are given relative to the CB. *floatPos is relative + * to the TB, and may be negative. + */ +bool OutOfFlowMgr::hasRelationChanged (bool oldTBAlloc, + int oldTBx, int oldTBy, int oldTBw, + int oldTBh, int newTBx, int newTBy, + int newTBw, int newTBh, + bool oldFlAlloc, + int oldFlx, int oldFly, int oldFlw, + int oldFlh, int newFlx, int newFly, + int newFlw, int newFlh, + Side side, int *floatPos) +{ + DBG_OBJ_MSGF ("resize.oofm", 0, + "<b>hasRelationChanged</b> (<i>see below</i>, %s, ...)", + side == LEFT ? "LEFT" : "RIGHT"); + DBG_OBJ_MSG_START (); + + if (oldTBAlloc) + DBG_OBJ_MSGF ("resize.oofm", 1, "old TB: %d, %d; %d * %d", + oldTBx, oldTBy, oldTBw, oldTBh); + else + DBG_OBJ_MSG ("resize.oofm", 1, "old TB: undefined"); + DBG_OBJ_MSGF ("resize.oofm", 1, "new TB: %d, %d; %d * %d", + newTBx, newTBy, newTBw, newTBh); + + if (oldFlAlloc) + DBG_OBJ_MSGF ("resize.oofm", 1, "old Fl: %d, %d; %d * %d", + oldFlx, oldFly, oldFlw, oldFlh); + else + DBG_OBJ_MSG ("resize.oofm", 1, "old Fl: undefined"); + DBG_OBJ_MSGF ("resize.oofm", 1, "new Fl: %d, %d; %d * %d", + newFlx, newFly, newFlw, newFlh); + + bool result; + if (oldTBAlloc && oldFlAlloc) { + bool oldCov = oldFly + oldFlh > oldTBy && oldFly < oldTBy + oldTBh; + bool newCov = newFly + newFlh > newTBy && newFly < newTBy + newTBh; + + DBG_OBJ_MSGF ("resize.oofm", 1, "covered? then: %s, now: %s.", + oldCov ? "yes" : "no", newCov ? "yes" : "no"); + DBG_OBJ_MSG_START (); + + if (oldCov && newCov) { + int yOld = oldFly - oldTBy, yNew = newFly - newTBy; + if (yOld == yNew) { + DBG_OBJ_MSGF ("resize.oofm", 2, + "old (%d - %d) and new (%d - %d) position equal: %d", + oldFly, oldTBy, newFly, newTBy, yOld); + + // Float position has not changed, but perhaps the amout + // how far the float reaches into the TB. (TODO: + // Generally, not only here, it could be tested whether + // the float reaches into the TB at all.) + int wOld, wNew; + if (side == LEFT) { + wOld = oldFlx + oldFlw - oldTBx; + wNew = newFlx + newFlw - newTBx; + } else { + wOld = oldTBx + oldTBw - oldFlx; + wNew = newTBx + newTBw - newFlx; + } + + DBG_OBJ_MSGF ("resize.oofm", 2, "wOld = %d, wNew = %d\n", + wOld, wNew); + + if (wOld == wNew) { + if (oldFlh == newFlh) + result = false; + else { + // Only heights of floats changed. Relevant only + // from bottoms of float. + *floatPos = min (yOld + oldFlh, yNew + newFlh); + result = true; + } + } else { + *floatPos = yOld; + result = true; + } + } else { + DBG_OBJ_MSGF ("resize.oofm", 2, + "old (%d - %d = %d) and new (%d - %d = %d) position " + "different", + oldFly, oldTBy, yOld, newFly, newTBy, yNew); + *floatPos = min (yOld, yNew); + result = true; + } + } else if (oldCov) { + *floatPos = oldFly - oldTBy; + result = true; + DBG_OBJ_MSGF ("resize.oofm", 2, + "returning old position: %d - %d = %d", oldFly, oldTBy, + *floatPos); + } else if (newCov) { + *floatPos = newFly - newTBy; + result = true; + DBG_OBJ_MSGF ("resize.oofm", 2, + "returning new position: %d - %d = %d", newFly, newTBy, + *floatPos); + } else + result = false; + + DBG_OBJ_MSG_END (); + } else { + // Not allocated before: ignore all old values, only check whether + // TB is covered by Float. + if (newFly + newFlh > newTBy && newFly < newTBy + newTBh) { + *floatPos = newFly - newTBy; + result = true; + } else + result = false; + } + + if (result) + DBG_OBJ_MSGF ("resize.oofm", 1, "has changed: floatPos = %d", + *floatPos); + else + DBG_OBJ_MSG ("resize.oofm", 1, "has not changed"); + + DBG_OBJ_MSG_END (); + + return result; +} + +bool OutOfFlowMgr::doFloatsExceedCB (Side side) +{ + SortedFloatsVector *list = side == LEFT ? leftFloatsCB : rightFloatsCB; + bool exceeds = false; + + for (int i = 0; i < list->size () && !exceeds; i++) { + Float *vloat = list->get (i); + if (vloat->getWidget()->wasAllocated ()) { + Allocation *fla = vloat->getWidget()->getAllocation (); + if (fla->x + fla->width > + containingBlockAllocation.x + containingBlockAllocation.width || + fla->y + fla->ascent + fla->descent > + containingBlockAllocation.y + containingBlockAllocation.ascent + + containingBlockAllocation.descent) + exceeds = true; + } + } + + return exceeds; +} + +bool OutOfFlowMgr::haveExtremesChanged (Side side) +{ + // This is quite different from doFloatsExceedCB, since there is no + // counterpart to getExtremes, as sizeAllocate is a counterpart to + // sizeRequest. So we have to determine whether the allocation has + // changed the extremes, which is done by examining the part of the + // allocation which is part of the extremes calculation (see + // getFloatsExtremes). Changes of the extremes are handled by the + // normal queueResize mechanism. + + SortedFloatsVector *list = side == LEFT ? leftFloatsCB : rightFloatsCB; + bool changed = false; + + for (int i = 0; i < list->size () && !changed; i++) { + Float *vloat = list->get (i); + // When the GB is the CB, an allocation change does not play a + // role here. + if (vloat->generatingBlock != containingBlock) { + if (!vloat->wasThenAllocated () && vloat->isNowAllocated ()) + changed = true; + else { + // This method is called within sizeAllocateEnd, where + // containinBlock->getAllocation() (old value) and + // containinBlockAllocation (new value) are different. + + Allocation *oldCBA = containingBlock->getAllocation (); + Allocation *newCBA = &containingBlockAllocation; + + // Compare also to getFloatsExtremes. The box difference + // of the GB (from style) has not changed in this context, + // so it is ignored. + + int oldDiffLeft = vloat->getOldXCB (); + int newDiffLeft = vloat->getNewXCB (); + int oldDiffRight = + oldCBA->width - (vloat->getOldXCB () + vloat->getOldWidth ()); + int newDiffRight = + newCBA->width - (vloat->getNewXCB () + vloat->getNewWidth ()); + + if (// regarding minimum + (side == LEFT && oldDiffLeft != newDiffLeft) || + (side == RIGHT && oldDiffRight != newDiffRight) || + // regarding maximum + oldDiffLeft + oldDiffRight != newDiffLeft + newDiffRight) + changed = true; + } + } + } + + return changed; +} + +void OutOfFlowMgr::moveFromGBToCB (Side side) +{ + SortedFloatsVector *dest = side == LEFT ? leftFloatsCB : rightFloatsCB; + int *floatsMark = side == LEFT ? &leftFloatsMark : &rightFloatsMark; + + for (int mark = 0; mark <= *floatsMark; mark++) + for (lout::container::typed::Iterator<TBInfo> it = tbInfos->iterator (); + it.hasNext (); ) { + TBInfo *tbInfo = it.getNext (); + SortedFloatsVector *src = + side == LEFT ? tbInfo->leftFloatsGB : tbInfo->rightFloatsGB; + for (int i = 0; i < src->size (); i++) { + Float *vloat = src->get (i); + if (!vloat->inCBList && vloat->mark == mark) { + dest->put (vloat); + //printf("[%p] moving %s float %p (%s %p, mark %d) to CB list\n", + // containingBlock, side == LEFT ? "left" : "right", + // vloat, vloat->widget->getClassName(), vloat->widget, + // vloat->mark); + } + } + } + + *floatsMark = 0; + + /* Old code: GB lists do not have to be cleared, but their contents + are still useful after allocation. Soon to be deleted, not only + uncommented. + + for (lout::container::typed::Iterator<TBInfo> it = tbInfos->iterator (); + it.hasNext (); ) { + TBInfo *tbInfo = it.getNext (); + SortedFloatsVector *src = + side == LEFT ? tbInfo->leftFloatsGB : tbInfo->rightFloatsGB; + src->clear (); + } + */ + + //printf ("[%p] new %s list:\n", + // containingBlock, side == LEFT ? "left" : "right"); + //for (int i = 0; i < dest->size(); i++) + // printf (" %d: %s\n", i, dest->get(i)->toString()); +} + +void OutOfFlowMgr::sizeAllocateFloats (Side side) +{ + SortedFloatsVector *list = side == LEFT ? leftFloatsCB : rightFloatsCB; + + for (int i = 0; i < list->size(); i++) { + Float *vloat = list->get(i); + ensureFloatSize (vloat); + + Allocation *gbAllocation = getAllocation(vloat->generatingBlock); + Allocation *cbAllocation = getAllocation(containingBlock); + + Allocation childAllocation; + childAllocation.x = cbAllocation->x + + calcFloatX (vloat, side, gbAllocation->x - cbAllocation->x, + gbAllocation->width, + vloat->generatingBlock->getAvailWidth()); + childAllocation.y = gbAllocation->y + vloat->yReal; + childAllocation.width = vloat->size.width; + childAllocation.ascent = vloat->size.ascent; + childAllocation.descent = vloat->size.descent; + + vloat->getWidget()->sizeAllocate (&childAllocation); + } +} + + +/** + * \brief ... + * + * gbX is given relative to the CB, as is the return value. + */ +int OutOfFlowMgr::calcFloatX (Float *vloat, Side side, int gbX, int gbWidth, + int gbAvailWidth) +{ + int gbActualWidth; + + switch (side) { + case LEFT: + // Left floats are always aligned on the left side of the + // generator (content, not allocation). + return gbX + vloat->generatingBlock->getStyle()->boxOffsetX(); + break; + + case RIGHT: + // In some cases, the actual (allocated) width is too large; we + // use the "available" width here. + gbActualWidth = min (gbWidth, gbAvailWidth); + + // Similar for right floats, but in this case, floats are + // shifted to the right when they are too big (instead of + // shifting the generator to the right). + return max (gbX + gbActualWidth - vloat->size.width + - vloat->generatingBlock->getStyle()->boxRestWidth(), + // Do not exceed CB allocation: + 0); + + default: + assertNotReached (); + return 0; + } +} + + +void OutOfFlowMgr::draw (View *view, Rectangle *area) +{ + drawFloats (leftFloatsCB, view, area); + drawFloats (rightFloatsCB, view, area); + drawAbsolutelyPositioned (view, area); +} + +void OutOfFlowMgr::drawFloats (SortedFloatsVector *list, View *view, + Rectangle *area) +{ + // This could be improved, since the list is sorted: search the + // first float fitting into the area, and iterate until one is + // found below the area. + for (int i = 0; i < list->size(); i++) { + Float *vloat = list->get(i); + Rectangle childArea; + if (vloat->getWidget()->intersects (area, &childArea)) + vloat->getWidget()->draw (view, &childArea); + } +} + +void OutOfFlowMgr::drawAbsolutelyPositioned (View *view, Rectangle *area) +{ + for (int i = 0; i < absolutelyPositioned->size(); i++) { + AbsolutelyPositioned *abspos = absolutelyPositioned->get(i); + Rectangle childArea; + if (abspos->widget->intersects (area, &childArea)) + abspos->widget->draw (view, &childArea); + } +} + +/** + * This method consideres also the attributes not yet considered by + * dillo, so that the containing block is determined correctly, which + * leads sometimes to a cleaner rendering. + */ +bool OutOfFlowMgr::isWidgetOutOfFlow (Widget *widget) +{ + return + widget->getStyle()->vloat != FLOAT_NONE || + widget->getStyle()->position == POSITION_ABSOLUTE || + widget->getStyle()->position == POSITION_FIXED; +} + +bool OutOfFlowMgr::isWidgetHandledByOOFM (Widget *widget) +{ + // May be extended for fixed (and relative?) positions. + return isWidgetFloat (widget); + // TODO temporary disabled: || isWidgetAbsolutelyPositioned (widget); +} + +void OutOfFlowMgr::addWidgetInFlow (Textblock *textblock, + Textblock *parentBlock, int externalIndex) +{ + //printf ("[%p] addWidgetInFlow (%p, %p, %d)\n", + // containingBlock, textblock, parentBlock, externalIndex); + + TBInfo *tbInfo = + new TBInfo (this, textblock, + parentBlock ? getTextblock (parentBlock) : NULL, + externalIndex); + tbInfo->index = tbInfos->size(); + + tbInfos->put (tbInfo); + tbInfosByTextblock->put (new TypedPointer<Textblock> (textblock), tbInfo); +} + +void OutOfFlowMgr::addWidgetOOF (Widget *widget, Textblock *generatingBlock, + int externalIndex) +{ + DBG_OBJ_MSGF ("construct.oofm", 0, "<b>addWidgetOOF</b> (%p, %p, %d)", + widget, generatingBlock, externalIndex); + + if (isWidgetFloat (widget)) { + TBInfo *tbInfo = getTextblock (generatingBlock); + + Float *vloat = new Float (this, widget, generatingBlock, externalIndex); + + // Note: Putting the float first in the GB list, and then, + // possibly into the CB list (in that order) will trigger + // setting Float::inCBList to the right value. + + switch (widget->getStyle()->vloat) { + case FLOAT_LEFT: + leftFloatsAll->put (vloat); + DBG_OBJ_SET_NUM ("leftFloatsAll.size", leftFloatsAll->size()); + widget->parentRef = createRefLeftFloat (leftFloatsAll->size() - 1); + tbInfo->leftFloatsGB->put (vloat); + + if (wasAllocated (generatingBlock)) { + leftFloatsCB->put (vloat); + //printf ("[%p] adding left float %p (%s %p) to CB list\n", + // containingBlock, vloat, widget->getClassName(), widget); + } else { + if (tbInfo->index < lastLeftTBIndex) + leftFloatsMark++; + + vloat->mark = leftFloatsMark; + //printf ("[%p] adding left float %p (%s %p, mark %d) to GB list " + // "(index %d, last = %d)\n", + // containingBlock, vloat, widget->getClassName(), widget, + // vloat->mark, tbInfo->index, lastLeftTBIndex); + + lastLeftTBIndex = tbInfo->index; + } + break; + + case FLOAT_RIGHT: + rightFloatsAll->put (vloat); + DBG_OBJ_SET_NUM ("rightFloatsAll.size", rightFloatsAll->size()); + widget->parentRef = createRefRightFloat (rightFloatsAll->size() - 1); + tbInfo->rightFloatsGB->put (vloat); + + if (wasAllocated (generatingBlock)) { + rightFloatsCB->put (vloat); + //printf ("[%p] adding right float %p (%s %p) to CB list\n", + // containingBlock, vloat, widget->getClassName(), widget); + } else { + if (tbInfo->index < lastRightTBIndex) + rightFloatsMark++; + + vloat->mark = rightFloatsMark; + //printf ("[%p] adding right float %p (%s %p, mark %d) to GB list " + // "(index %d, last = %d)\n", + // containingBlock, vloat, widget->getClassName(), widget, + // vloat->mark, tbInfo->index, lastRightTBIndex); + + lastRightTBIndex = tbInfo->index; + } + + break; + + default: + assertNotReached(); + } + + // "sideSpanningIndex" is only compared, so this simple + // assignment is sufficient; differenciation between GB and CB + // lists is not neccessary. TODO: Can this also be applied to + // "index", to simplify the current code? Check: where is + // "index" used. + vloat->sideSpanningIndex = + leftFloatsAll->size() + rightFloatsAll->size() - 1; + + floatsByWidget->put (new TypedPointer<Widget> (widget), vloat); + } else if (isWidgetAbsolutelyPositioned (widget)) { + AbsolutelyPositioned *abspos = + new AbsolutelyPositioned (this, widget, generatingBlock, + externalIndex); + absolutelyPositioned->put (abspos); + widget->parentRef = + createRefAbsolutelyPositioned (absolutelyPositioned->size() - 1); + } else + // May be extended. + assertNotReached(); +} + +void OutOfFlowMgr::moveExternalIndices (Textblock *generatingBlock, + int oldStartIndex, int diff) +{ + TBInfo *tbInfo = getTextblock (generatingBlock); + moveExternalIndices (tbInfo->leftFloatsGB, oldStartIndex, diff); + moveExternalIndices (tbInfo->rightFloatsGB, oldStartIndex, diff); +} + +void OutOfFlowMgr::moveExternalIndices (SortedFloatsVector *list, + int oldStartIndex, int diff) +{ + // Could be faster with binary search, but the GB (not CB!) lists + // should be rather small. + for (int i = 0; i < list->size(); i++) { + Float *vloat = list->get(i); + if (vloat->externalIndex >= oldStartIndex) + vloat->externalIndex += diff; + } +} + +OutOfFlowMgr::Float *OutOfFlowMgr::findFloatByWidget (Widget *widget) +{ + TypedPointer <Widget> key (widget); + Float *vloat = floatsByWidget->get (&key); + assert (vloat != NULL); + return vloat; +} + +void OutOfFlowMgr::markSizeChange (int ref) +{ + DBG_OBJ_MSGF ("resize", 0, "<b>markSizeChange</b> (%d)", ref); + DBG_OBJ_MSG_START (); + + if (isRefFloat (ref)) { + Float *vloat; + + if (isRefLeftFloat (ref)) { + int i = getFloatIndexFromRef (ref); + vloat = leftFloatsAll->get (i); + //printf (" => left float %d\n", i); + } else if (isRefRightFloat (ref)) { + int i = getFloatIndexFromRef (ref); + vloat = rightFloatsAll->get (i); + //printf (" => right float %d\n", i); + } else { + assertNotReached(); + vloat = NULL; // compiler happiness + } + + vloat->dirty = vloat->sizeChangedSinceLastAllocation = true; + + // The generating block is told directly about this. (Others later, in + // sizeAllocateEnd.) Could be faster (cf. hasRelationChanged, which + // differentiates many special cases), but the size is not known yet, + vloat->generatingBlock->borderChanged (vloat->yReal, vloat->getWidget ()); + } else if (isRefAbsolutelyPositioned (ref)) { + int i = getAbsolutelyPositionedIndexFromRef (ref); + absolutelyPositioned->get(i)->dirty = true; + } else + assertNotReached(); + + DBG_OBJ_MSG_END (); +} + + +void OutOfFlowMgr::markExtremesChange (int ref) +{ + // Nothing to do here. +} + +Widget *OutOfFlowMgr::getWidgetAtPoint (int x, int y, int level) +{ + Widget *childAtPoint = getFloatWidgetAtPoint (leftFloatsCB, x, y, level); + if (childAtPoint == NULL) + childAtPoint = getFloatWidgetAtPoint (rightFloatsCB, x, y, level); + if (childAtPoint == NULL) + childAtPoint = getAbsolutelyPositionedWidgetAtPoint (x, y, level); + return childAtPoint; +} + +Widget *OutOfFlowMgr::getFloatWidgetAtPoint (SortedFloatsVector *list, + int x, int y, int level) +{ + for (int i = 0; i < list->size(); i++) { + // Could use binary search to be faster. + Float *vloat = list->get(i); + Widget *childAtPoint = + vloat->getWidget()->getWidgetAtPoint (x, y, level + 1); + if (childAtPoint) + return childAtPoint; + } + + return NULL; +} + +Widget *OutOfFlowMgr::getAbsolutelyPositionedWidgetAtPoint (int x, int y, + int level) +{ + for (int i = 0; i < absolutelyPositioned->size(); i++) { + AbsolutelyPositioned *abspos = absolutelyPositioned->get(i); + Widget *childAtPoint = abspos->widget->getWidgetAtPoint (x, y, level + 1); + if (childAtPoint) + return childAtPoint; + } + + return NULL; +} + +void OutOfFlowMgr::tellPosition (Widget *widget, int yReq) +{ + if (isWidgetFloat (widget)) + tellFloatPosition (widget, yReq); + + // Nothing to do for absolutely positioned blocks. +} + + +void OutOfFlowMgr::tellFloatPosition (Widget *widget, int yReq) +{ + DBG_OBJ_MSGF ("resize.oofm", 0, "<b>tellFloatPosition</b> (%p, %d)", + widget, yReq); + DBG_OBJ_MSG_START (); + + assert (yReq >= 0); + + Float *vloat = findFloatByWidget(widget); + + SortedFloatsVector *listSame, *listOpp; + Side side; + getFloatsListsAndSide (vloat, &listSame, &listOpp, &side); + ensureFloatSize (vloat); + + // "yReal" may change due to collisions (see below). + vloat->yReq = vloat->yReal = yReq; + + DBG_OBJ_SET_NUM_O (vloat->getWidget (), "<Float>.yReq", vloat->yReq); + DBG_OBJ_SET_NUM_O (vloat->getWidget (), "<Float>.yReal", vloat->yReal); + + // Test collisions (on this side). Only previous float is relevant. + int yRealNew; + if (vloat->index >= 1 && + collidesV (vloat, listSame->get (vloat->index - 1), &yRealNew)) { + vloat->yReal = yRealNew; + DBG_OBJ_SET_NUM_O (vloat->getWidget (), "<Float>.yReal", vloat->yReal); + } + + // Test collisions (on the opposite side). Search the last float on + // the other size before this float; only this is relevant. + int lastOppFloat = + listOpp->findLastBeforeSideSpanningIndex (vloat->sideSpanningIndex); + if (lastOppFloat >= 0 && + collidesH (vloat, listOpp->get (lastOppFloat), &yRealNew)) { + vloat->yReal = yRealNew; + DBG_OBJ_SET_NUM_O (vloat->getWidget (), "<Float>.yReal", vloat->yReal); + } + + DBG_OBJ_MSGF ("resize.oofm", 1, "vloat->yReq = %d, vloat->yReal = %d", + vloat->yReq, vloat->yReal); + + DBG_OBJ_MSG_END (); +} + +bool OutOfFlowMgr::collidesV (Float *vloat, Float *other, int *yReal) +{ + // Only checks vertical (possible) collisions, and only refers to + // vloat->yReal; never to vloat->allocation->y, even when the GBs are + // different. Used only in tellPosition. + + DBG_OBJ_MSGF ("resize.oofm", 0, + "<b>collides</b> (#%d [%p], #%d [%p], ...)", + vloat->index, vloat->getWidget (), other->index, + other->getWidget ()); + DBG_OBJ_MSG_START (); + + bool result; + + DBG_OBJ_MSGF ("resize.oofm", 1, "initial yReal = %d", vloat->yReal); + + if (vloat->generatingBlock == other->generatingBlock) { + ensureFloatSize (other); + int otherBottomGB = + other->yReal + other->size.ascent + other->size.descent; + + DBG_OBJ_MSGF ("resize.oofm", 1, + "same generators: otherBottomGB = %d + (%d + %d) = %d", + other->yReal, other->size.ascent, other->size.descent, + otherBottomGB); + + if (vloat->yReal < otherBottomGB) { + *yReal = otherBottomGB; + result = true; + } else + result = false; + } else { + assert (wasAllocated (vloat->generatingBlock)); + assert (wasAllocated (other->generatingBlock)); + + // If the other float is not allocated, there is no collision. The + // allocation of this float (vloat) is not used at all. + if (!other->getWidget()->wasAllocated ()) + result = false; + else { + Allocation *gba = getAllocation (vloat->generatingBlock), + *flaOther = other->getWidget()->getAllocation (); + int otherBottomGB = + flaOther->y + flaOther->ascent + flaOther->descent - gba->y; + + DBG_OBJ_MSGF ("resize.oofm", 1, + "different generators: " + "otherBottomGB = %d + (%d + %d) - %d = %d", + flaOther->y, flaOther->ascent, flaOther->descent, gba->y, + otherBottomGB); + + if (vloat->yReal < otherBottomGB) { + *yReal = otherBottomGB; + result = true; + } else + result = false; + } + } + + if (result) + DBG_OBJ_MSGF ("resize.oofm", 1, "collides: new yReal = %d", *yReal); + else + DBG_OBJ_MSG ("resize.oofm", 1, "does not collide"); + + DBG_OBJ_MSG_END (); + return result; +} + + +bool OutOfFlowMgr::collidesH (Float *vloat, Float *other, int *yReal) +{ + bool collidesH; + + if (!collidesV (vloat, other, yReal)) + collidesH = false; + else { + if (vloat->generatingBlock == other->generatingBlock) + collidesH = vloat->size.width + other->size.width + + vloat->generatingBlock->getStyle()->boxDiffWidth() + > vloat->generatingBlock->getAvailWidth(); + else { + assert (wasAllocated (vloat->generatingBlock)); + assert (wasAllocated (other->generatingBlock)); + + // Again, if the other float is not allocated, there is no + // collision. Compare to collidesV. (But vloat->size is used + // here.) + if (!other->getWidget()->wasAllocated ()) + collidesH = false; + else { + Allocation *gba = getAllocation (vloat->generatingBlock); + int vloatX = + calcFloatX (vloat, + vloat->getWidget()->getStyle()->vloat == FLOAT_LEFT ? + LEFT : RIGHT, + gba->x, gba->width, + vloat->generatingBlock->getAvailWidth ()); + + // Generally: right border of the left float > left border + // of the right float (all in canvas coordinates). + if (vloat->getWidget()->getStyle()->vloat == FLOAT_LEFT) + // "vloat" is left, "other" is right + collidesH = vloatX + vloat->size.width + > other->getWidget()->getAllocation()->x; + else + // "other" is left, "vloat" is right + collidesH = other->getWidget()->getAllocation()->x + + other->getWidget()->getAllocation()->width + > vloatX; + } + } + } + + return collidesH; +} + +void OutOfFlowMgr::getFloatsListsAndSide (Float *vloat, + SortedFloatsVector **listSame, + SortedFloatsVector **listOpp, + Side *side) +{ + TBInfo *tbInfo = getTextblock (vloat->generatingBlock); + + switch (vloat->getWidget()->getStyle()->vloat) { + case FLOAT_LEFT: + if (wasAllocated (vloat->generatingBlock)) { + if (listSame) *listSame = leftFloatsCB; + if (listOpp) *listOpp = rightFloatsCB; + } else { + if (listSame) *listSame = tbInfo->leftFloatsGB; + if (listOpp) *listOpp = tbInfo->rightFloatsGB; + } + if (side) *side = LEFT; + break; + + case FLOAT_RIGHT: + if (wasAllocated (vloat->generatingBlock)) { + if (listSame) *listSame = rightFloatsCB; + if (listOpp) *listOpp = leftFloatsCB; + } else { + if (listSame) *listSame = tbInfo->rightFloatsGB; + if (listOpp) *listOpp = tbInfo->leftFloatsGB; + } + if (side) *side = RIGHT; + break; + + default: + assertNotReached(); + } +} + +void OutOfFlowMgr::getSize (Requisition *cbReq, int *oofWidth, int *oofHeight) +{ + DBG_OBJ_MSG ("resize.oofm", 0, "<b>getSize</b> ()"); + DBG_OBJ_MSG_START (); + + int oofWidthAbsPos, oofHeightAbsPos; + getAbsolutelyPositionedSize (cbReq, &oofWidthAbsPos, &oofHeightAbsPos); + + int oofWidthtLeft, oofWidthRight, oofHeightLeft, oofHeightRight; + getFloatsSize (cbReq, LEFT, &oofWidthtLeft, &oofHeightLeft); + getFloatsSize (cbReq, RIGHT, &oofWidthRight, &oofHeightRight); + + *oofWidth = max (oofWidthtLeft, oofWidthRight, oofWidthAbsPos); + *oofHeight = max (oofHeightLeft, oofHeightRight, oofHeightAbsPos); + + DBG_OBJ_MSGF ("resize.oofm", 1, + "=> (a: %d, l: %d, r: %d => %d) * (a: %d, l: %d, r: %d => %d)", + oofWidthAbsPos, oofWidthtLeft, oofWidthRight, *oofWidth, + oofHeightAbsPos, oofHeightLeft, oofHeightRight, *oofHeight); + DBG_OBJ_MSG_END (); +} + +void OutOfFlowMgr::getFloatsSize (Requisition *cbReq, Side side, int *width, + int *height) +{ + DBG_OBJ_MSGF ("resize.oofm", 0, + "<b>getFloatsSize</b> ((%d * (%d + %d), %s, ...)", + cbReq->width, cbReq->ascent, cbReq->descent, + side == LEFT ? "LEFT" : "RIGHT"); + DBG_OBJ_MSG_START (); + + SortedFloatsVector *list = getFloatsListForTextblock (containingBlock, side); + + *width = *height = 0; + + for (int i = 0; i < list->size(); i++) { + Float *vloat = list->get(i); + + if (vloat->generatingBlock == containingBlock || + wasAllocated (vloat->generatingBlock)) { + ensureFloatSize (vloat); + int x, y; + + if (vloat->generatingBlock == containingBlock) { + x = calcFloatX (vloat, side, 0, cbReq->width, + vloat->generatingBlock->getAvailWidth ()); + y = vloat->yReal; + } else { + Allocation *gba = getAllocation(vloat->generatingBlock); + x = calcFloatX (vloat, side, + gba->x - containingBlockAllocation.x, gba->width, + vloat->generatingBlock->getAvailWidth ()); + y = gba->y - containingBlockAllocation.y + vloat->yReal; + } + + *width = max (*width, x + vloat->size.width); + *height = max (*height, y + vloat->size.ascent + vloat->size.descent); + + DBG_OBJ_MSGF ("resize.oofm", 1, + "considering float %p generated by %p: (%d + %d) * " + "(%d + (%d + %d)) => %d * %d", + vloat->getWidget (), vloat->generatingBlock, + x, vloat->size.width, + y, vloat->size.ascent, vloat->size.descent, + *width, *height); + } else + DBG_OBJ_MSGF ("resize.oofm", 1, + "considering float %p generated by %p: not allocated", + vloat->getWidget (), vloat->generatingBlock); + } + + DBG_OBJ_MSG_END (); +} + +void OutOfFlowMgr::getExtremes (Extremes *cbExtr, int *oofMinWidth, + int *oofMaxWidth) +{ + DBG_OBJ_MSG ("resize.oofm", 0, "<b>getExtremes</b> ()"); + DBG_OBJ_MSG_START (); + + int oofMinWidthAbsPos, oofMaxWidthAbsPos; + getAbsolutelyPositionedExtremes (cbExtr, &oofMinWidthAbsPos, + &oofMaxWidthAbsPos); + + int oofMinWidthtLeft, oofMinWidthRight, oofMaxWidthLeft, oofMaxWidthRight; + getFloatsExtremes (cbExtr, LEFT, &oofMinWidthtLeft, &oofMaxWidthLeft); + getFloatsExtremes (cbExtr, RIGHT, &oofMinWidthRight, &oofMaxWidthRight); + + *oofMinWidth = max (oofMinWidthtLeft, oofMinWidthRight, oofMinWidthAbsPos); + *oofMaxWidth = max (oofMaxWidthLeft, oofMaxWidthRight, oofMaxWidthAbsPos); + + DBG_OBJ_MSGF ("resize.oofm", 1, + "=> (a: %d, l: %d, r: %d => %d) * (a: %d, l: %d, r: %d => %d)", + oofMinWidthAbsPos, oofMinWidthtLeft, oofMinWidthRight, + *oofMinWidth, oofMaxWidthAbsPos, oofMaxWidthLeft, + oofMaxWidthRight, *oofMaxWidth); + DBG_OBJ_MSG_END (); +} + +void OutOfFlowMgr::getFloatsExtremes (Extremes *cbExtr, Side side, + int *minWidth, int *maxWidth) +{ + *minWidth = *maxWidth = 0; + + SortedFloatsVector *list = getFloatsListForTextblock (containingBlock, side); + + DBG_OBJ_MSGF ("resize", 0, "<b>getFloatsExtremes</b> ((%d / %d), %s, ...)", + cbExtr->minWidth, cbExtr->maxWidth, + side == LEFT ? "LEFT" : "RIGHT"); + DBG_OBJ_MSG_START (); + + for (int i = 0; i < list->size(); i++) { + Float *vloat = list->get(i); + + if (vloat->generatingBlock == containingBlock || + wasAllocated (vloat->generatingBlock)) { + Extremes extr; + vloat->getWidget()->getExtremes (&extr); + int leftDiff, rightDiff; + + DBG_OBJ_MSGF ("resize.oofm", 1, + "considering float %p generated by %p: %d / %d", + vloat->getWidget (), vloat->generatingBlock, + extr.minWidth, extr.maxWidth); + + if (vloat->generatingBlock == containingBlock) { + leftDiff = vloat->generatingBlock->getStyle()->boxOffsetX(); + rightDiff = vloat->generatingBlock->getStyle()->boxRestWidth(); + DBG_OBJ_MSGF ("resize.oofm", 1, + "GB == CB => leftDiff = %d, rightDiff = %d", + leftDiff, rightDiff); + } else { + Allocation *gba = getAllocation(vloat->generatingBlock); + leftDiff = gba->x - containingBlockAllocation.x + + vloat->generatingBlock->getStyle()->boxOffsetX(); + rightDiff = + (containingBlockAllocation.x + containingBlockAllocation.width) + - (gba->x + gba->width) + + vloat->generatingBlock->getStyle()->boxRestWidth(); + DBG_OBJ_MSGF ("resize.oofm", 1, + "GB != CB => leftDiff = %d - %d + %d = %d, " + "rightDiff = (%d + %d) - (%d + %d) + %d = %d", + gba->x, containingBlockAllocation.x, + vloat->generatingBlock->getStyle()->boxOffsetX(), + leftDiff, containingBlockAllocation.x, + containingBlockAllocation.width, gba->x, gba->width, + vloat->generatingBlock->getStyle()->boxRestWidth(), + rightDiff); + } + + // TODO: Or zero (instead of rightDiff) for right floats? + *minWidth = max (*minWidth, + extr.minWidth + side == LEFT ? leftDiff : rightDiff); + *maxWidth = max (*maxWidth, extr.maxWidth + leftDiff + rightDiff); + + DBG_OBJ_MSGF ("resize.oofm", 1, " => %d / %d", *minWidth, *maxWidth); + } else + DBG_OBJ_MSGF ("resize.oofm", 1, + "considering float %p generated by %p: not allocated", + vloat->getWidget (), vloat->generatingBlock); + } + + DBG_OBJ_MSG_END (); +} + +OutOfFlowMgr::TBInfo *OutOfFlowMgr::getTextblock (Textblock *textblock) +{ + TypedPointer<Textblock> key (textblock); + TBInfo *tbInfo = tbInfosByTextblock->get (&key); + assert (tbInfo); + return tbInfo; +} + +/** + * Get the left border for the vertical position of *y*, for a height + * of *h", based on floats; relative to the allocation of the calling + * textblock. + * + * The border includes marging/border/padding of the calling textblock + * but is 0 if there is no float, so a caller should also consider + * other borders. + */ +int OutOfFlowMgr::getLeftBorder (Textblock *textblock, int y, int h, + Textblock *lastGB, int lastExtIndex) +{ + int b = getBorder (textblock, LEFT, y, h, lastGB, lastExtIndex); + DBG_OBJ_MSGF ("border", 0, "left border (%p, %d, %d, %p, %d) => %d", + textblock, y, h, lastGB, lastExtIndex, b); + return b; +} + +/** + * Get the right border for the vertical position of *y*, for a height + * of *h*, based on floats. + * + * See also getLeftBorder(int, int); + */ +int OutOfFlowMgr::getRightBorder (Textblock *textblock, int y, int h, + Textblock *lastGB, int lastExtIndex) +{ + int b = getBorder (textblock, RIGHT, y, h, lastGB, lastExtIndex); + DBG_OBJ_MSGF ("border", 0, "right border (%p, %d, %d, %p, %d) => %d", + textblock, y, h, lastGB, lastExtIndex, b); + return b; +} + +int OutOfFlowMgr::getBorder (Textblock *textblock, Side side, int y, int h, + Textblock *lastGB, int lastExtIndex) +{ + DBG_OBJ_MSGF ("border", 0, "<b>getBorder</b> (%p, %s, %d, %d, %p, %d)", + textblock, side == LEFT ? "LEFT" : "RIGHT", y, h, + lastGB, lastExtIndex); + DBG_OBJ_MSG_START (); + + SortedFloatsVector *list = getFloatsListForTextblock (textblock, side); + int first = list->findFirst (textblock, y, h, lastGB, lastExtIndex); + + DBG_OBJ_MSGF ("border", 1, "first = %d", first); + + if (first == -1) { + // No float. + DBG_OBJ_MSG_END (); + return 0; + } else { + // It is not sufficient to find the first float, since a line + // (with height h) may cover the region of multiple float, of + // which the widest has to be choosen. + int border = 0; + bool covers = true; + // TODO Also check against lastGB and lastExtIndex + for (int i = first; covers && i < list->size(); i++) { + Float *vloat = list->get(i); + covers = vloat->covers (textblock, y, h); + DBG_OBJ_MSGF ("border", 1, "float %d (%p) covers? %s.", + i, vloat->getWidget(), covers ? "<b>yes</b>" : "no"); + + if (covers) { + int thisBorder; + if (vloat->generatingBlock == textblock) { + int borderIn = side == LEFT ? + vloat->generatingBlock->getStyle()->boxOffsetX() : + vloat->generatingBlock->getStyle()->boxRestWidth(); + thisBorder = vloat->size.width + borderIn; + DBG_OBJ_MSGF ("border", 1, "GB: thisBorder = %d + %d = %d", + vloat->size.width, borderIn, thisBorder); + } else { + assert (wasAllocated (vloat->generatingBlock)); + assert (vloat->getWidget()->wasAllocated ()); + + Allocation *tba = getAllocation(textblock), + *fla = vloat->getWidget()->getAllocation (); + if (side == LEFT) { + thisBorder = fla->x + fla->width - tba->x; + DBG_OBJ_MSGF ("border", 1, + "not GB: thisBorder = %d + %d - %d = %d", + fla->x, fla->width, tba->x, thisBorder); + } else { + // See also calcFloatX. + int tbAvWidth = textblock->getAvailWidth (); + thisBorder = tba->x + min (tba->width, tbAvWidth) - fla->x; + DBG_OBJ_MSGF ("border", 1, + "not GB: thisBorder = %d + min (%d, %d) - %d " + "= %d", + tba->x, tba->width, tbAvWidth, fla->x, + thisBorder); + } + } + + border = max (border, thisBorder); + DBG_OBJ_MSGF ("border", 1, "=> border = %d", border); + } + } + + DBG_OBJ_MSG_END (); + return border; + } +} + + +OutOfFlowMgr::SortedFloatsVector *OutOfFlowMgr::getFloatsListForTextblock + (Textblock *textblock, Side side) +{ + if (wasAllocated (textblock)) + return side == LEFT ? leftFloatsCB : rightFloatsCB; + else { + TBInfo *tbInfo = getTextblock (textblock); + return side == LEFT ? tbInfo->leftFloatsGB : tbInfo->rightFloatsGB; + } +} + + +bool OutOfFlowMgr::hasFloatLeft (Textblock *textblock, int y, int h, + Textblock *lastGB, int lastExtIndex) +{ + bool b = hasFloat (textblock, LEFT, y, h, lastGB, lastExtIndex); + DBG_OBJ_MSGF ("border", 0, "has float left (%p, %d, %d, %p, %d) => %s", + textblock, y, h, lastGB, lastExtIndex, b ? "true" : "false"); + return b; +} + +bool OutOfFlowMgr::hasFloatRight (Textblock *textblock, int y, int h, + Textblock *lastGB, int lastExtIndex) +{ + bool b = hasFloat (textblock, RIGHT, y, h, lastGB, lastExtIndex); + DBG_OBJ_MSGF ("border", 0, "has float right (%p, %d, %d, %p, %d) => %s", + textblock, y, h, lastGB, lastExtIndex, b ? "true" : "false"); + return b; +} + +bool OutOfFlowMgr::hasFloat (Textblock *textblock, Side side, int y, int h, + Textblock *lastGB, int lastExtIndex) +{ + DBG_OBJ_MSGF ("border", 0, "<b>hasFloat</b> (%p, %s, %d, %d, %p, %d)", + textblock, side == LEFT ? "LEFT" : "RIGHT", y, h, + lastGB, lastExtIndex); + DBG_OBJ_MSG_START (); + + SortedFloatsVector *list = getFloatsListForTextblock (textblock, side); + int first = list->findFirst (textblock, y, h, lastGB, lastExtIndex); + + DBG_OBJ_MSGF ("border", 1, "first = %d", first); + DBG_OBJ_MSG_END (); + return first != -1; +} + +/** + * Returns position relative to the textblock "tb". + */ +int OutOfFlowMgr::getClearPosition (Textblock *tb) +{ + if (tb->getStyle()) { + bool left = false, right = false; + switch (tb->getStyle()->clear) { + case CLEAR_NONE: break; + case CLEAR_LEFT: left = true; break; + case CLEAR_RIGHT: right = true; break; + case CLEAR_BOTH: left = right = true; break; + default: assertNotReached (); + } + + return max (left ? getClearPosition (tb, LEFT) : 0, + right ? getClearPosition (tb, RIGHT) : 0); + } else + return 0; +} + +int OutOfFlowMgr::getClearPosition (Textblock *tb, Side side) +{ + if (!wasAllocated (tb)) + // There is no relation yet to floats generated by other + // textblocks, and this textblocks floats are unimportant for + // the "clear" property. + return 0; + + SortedFloatsVector *list = side == LEFT ? leftFloatsCB : rightFloatsCB; + + int i = list->findFloatIndex (tb, 0); + if (i < 0) + return 0; + + Float *vloat = list->get(i); + // We pass this texblock and 0 (first word, or smallest external + // index, respectively), but search for the last float before this + // position. Therefore this check. + if (vloat->generatingBlock== tb) + i--; + if (i < 0) + return 0; + + vloat = list->get(i); + ensureFloatSize (vloat); + return + vloat->yReal + vloat->size.ascent + vloat->size.descent - + getAllocation(tb)->y; +} + +void OutOfFlowMgr::ensureFloatSize (Float *vloat) +{ + if (vloat->dirty || + // If the size of the containing block has changed (represented + // currently by the available width), a recalculation of a + // relative float width may also be necessary. + (isPerLength (vloat->getWidget()->getStyle()->width) && + vloat->cbAvailWidth != containingBlock->getAvailWidth ())) { + DBG_OBJ_MSGF ("resize.oofm", 0, + "<b>ensureFloatSize</b> (%p): recalculation", + vloat->getWidget ()); + DBG_OBJ_MSG_START (); + + Extremes extremes; + vloat->getWidget()->getExtremes (&extremes); + DBG_OBJ_MSGF ("resize.oofm", 1, "getExtremes => %d / %d", + extremes.minWidth, extremes.maxWidth); + + // TODO Ugly. Soon to be replaced by cleaner code? See also + // comment in Textblock::calcWidgetSize. + + if (vloat->getWidget()->usesHints ()) { + // For widths defined by CSS, similar adjustments (extremes + // etc.) like below are necessary, to prevent CPU hogging. + if (isAbsLength (vloat->getWidget()->getStyle()->width)) { + int width = absLengthVal (vloat->getWidget()->getStyle()->width); + DBG_OBJ_MSGF ("resize.oofm", 1, "about to set absolute width: %d", + width); + width = adjustFloatWidth (width, &extremes); + vloat->getWidget()->setWidth (width); + } else if (isPerLength (vloat->getWidget()->getStyle()->width)) { + int width = + multiplyWithPerLength (containingBlock->getAvailWidth(), + vloat->getWidget()->getStyle()->width); + DBG_OBJ_MSGF ("resize.oofm", 1, + "about to set percentage width: %d * %g = %d", + containingBlock->getAvailWidth(), + perLengthVal (vloat->getWidget()->getStyle()->width), + width); + width = adjustFloatWidth (width, &extremes); + vloat->getWidget()->setWidth (width); + } else + DBG_OBJ_MSG ("resize.oofm", 1, "setting no width: not defined"); + } else + DBG_OBJ_MSG ("resize.oofm", 1, "setting no width: uses no hints"); + + // This is a bit hackish: We first request the size, then set + // the available width (also considering the one of the + // containing block, and the extremes of the float), then + // request the size again, which may of course have a different + // result. This is a fix for the bug: + // + // Text in floats, which are wider because of an image, are + // broken at a too narrow width. Reproduce: + // test/floats2.html. After the image has been loaded, the + // text "Some text in a float." should not be broken + // anymore. + // + // If the call of setWidth not is neccessary, the second call + // will read the size from the cache, so no redundant + // calculation is necessary. + // + // Furthermore, extremes are considered; especially, floats are too + // wide, sometimes. + + vloat->getWidget()->sizeRequest (&vloat->size); + DBG_OBJ_MSGF ("resize.oofm", 1, "sizeRequest (1) => %d * (%d + %d)", + vloat->size.width, vloat->size.ascent, vloat->size.descent); + + // Set width ... + int width = vloat->size.width; + DBG_OBJ_MSGF ("resize.oofm", 1, "new width: %d", width); + width = adjustFloatWidth (width, &extremes); + vloat->getWidget()->setWidth (width); + vloat->getWidget()->sizeRequest (&vloat->size); + DBG_OBJ_MSGF ("resize.oofm", 1, "sizeRequest (2) => %d * (%d + %d)", + vloat->size.width, vloat->size.ascent, vloat->size.descent); + + vloat->cbAvailWidth = containingBlock->getAvailWidth (); + vloat->dirty = false; + + DBG_OBJ_MSGF ("resize.oofm", 1, "final size: %d * (%d + %d)", + vloat->size.width, vloat->size.ascent, vloat->size.descent); + + DBG_OBJ_SET_NUM_O (vloat->getWidget(), "<Float>.size.width", + vloat->size.width); + DBG_OBJ_SET_NUM_O (vloat->getWidget(), "<Float>.size.ascent", + vloat->size.ascent); + DBG_OBJ_SET_NUM_O (vloat->getWidget(), "<Float>.size.descent", + vloat->size.descent); + + // "sizeChangedSinceLastAllocation" is reset in sizeAllocateEnd() + + DBG_OBJ_MSG_END (); + } +} + +int OutOfFlowMgr::adjustFloatWidth (int width, Extremes *extremes) +{ + DBG_OBJ_MSGF ("resize.oofm", 0, + "<b>adjustFloatWidth</b> (%d, (%d, %d)) [CB->availWidth = %d]", + width, extremes->minWidth, extremes->maxWidth, + containingBlock->getAvailWidth()); + DBG_OBJ_MSG_START (); + + // Consider the available width of the containing block (when set): + if (width > containingBlock->getAvailWidth()) { + width = containingBlock->getAvailWidth(); + DBG_OBJ_MSGF ("resize.oofm", 1, "adjusted to availWidth: %d", width); + } + // Finally, consider extremes (as described above). + if (width < extremes->minWidth) { + width = extremes->minWidth; + DBG_OBJ_MSGF ("resize.oofm", 1, "adjusted to minWidth: %d", width); + } + if (width > extremes->maxWidth) { + width = extremes->maxWidth; + DBG_OBJ_MSGF ("resize.oofm", 1, "adjusted to maxWidth: %d", width); + } + + DBG_OBJ_MSGF ("resize.oofm", 1, "=> %d", width); + DBG_OBJ_MSG_END (); + + return width; +} + +void OutOfFlowMgr::getAbsolutelyPositionedSize (Requisition *cbReq, int *width, + int *height) +{ + // TODO + *width = *height = 0; +} + +void OutOfFlowMgr::getAbsolutelyPositionedExtremes (Extremes *cbExtr, + int *minWidth, + int *maxWidth) +{ + // TODO + *minWidth = *maxWidth = 0; +} + +void OutOfFlowMgr::ensureAbsolutelyPositionedSizeAndPosition + (AbsolutelyPositioned *abspos) +{ + // No work is done anymore on this, since widget sizes will be + // redesigned before absolute positions are finished. + + if (abspos->dirty) { + Style *style = abspos->widget->getStyle(); + int availWidth = containingBlock->getAvailWidth(); + int availHeight = + containingBlock->getAvailAscent() + containingBlock->getAvailDescent(); + + if (style->left == LENGTH_AUTO) + abspos->xCB = 0; + else + abspos->xCB = + calcValueForAbsolutelyPositioned (abspos, style->left, availWidth); + + if (style->top == LENGTH_AUTO) + abspos->yCB = 0; + else + abspos->yCB = + calcValueForAbsolutelyPositioned (abspos, style->top, availHeight); + + abspos->width = -1; // undefined + if (style->width != LENGTH_AUTO) + abspos->width = calcValueForAbsolutelyPositioned (abspos, style->width, + availWidth); + else if (style->right != LENGTH_AUTO) { + int right = calcValueForAbsolutelyPositioned (abspos, style->right, + availWidth); + abspos->width = max (0, availWidth - (abspos->xCB + right)); + } + + abspos->height = -1; // undefined + if (style->height != LENGTH_AUTO) + abspos->height = calcValueForAbsolutelyPositioned (abspos, + style->height, + availHeight); + else if (style->bottom != LENGTH_AUTO) { + int bottom = calcValueForAbsolutelyPositioned (abspos, style->bottom, + availHeight); + abspos->height = max (0, availHeight - (abspos->yCB + bottom)); + } + + if (abspos->width != -1) + abspos->widget->setWidth (abspos->width); + + if (abspos->height != -1) { + abspos->widget->setAscent (abspos->height); + abspos->widget->setDescent (0); // TODO + } + + if (abspos->width == -1 || abspos->height == -1) { + Requisition req; + abspos->widget->sizeRequest (&req); + + if (abspos->width == -1) + abspos->width = req.width; + + if (abspos->height == -1) + abspos->height = req.ascent + req.descent; + } + + abspos->dirty = false; + } +} + +int OutOfFlowMgr::calcValueForAbsolutelyPositioned + (AbsolutelyPositioned *abspos, Length styleLen, int refLen) +{ + assert (styleLen != LENGTH_AUTO); + if (isAbsLength (styleLen)) + return absLengthVal (styleLen); + else if (isPerLength (styleLen)) + return multiplyWithPerLength (refLen, styleLen); + else { + assertNotReached (); + return 0; // compiler happiness + } +} + +void OutOfFlowMgr::sizeAllocateAbsolutelyPositioned () +{ + for (int i = 0; i < absolutelyPositioned->size(); i++) { + Allocation *cbAllocation = getAllocation(containingBlock); + AbsolutelyPositioned *abspos = absolutelyPositioned->get (i); + ensureAbsolutelyPositionedSizeAndPosition (abspos); + + Allocation childAllocation; + childAllocation.x = cbAllocation->x + abspos->xCB; + childAllocation.y = cbAllocation->y + abspos->yCB; + childAllocation.width = abspos->width; + childAllocation.ascent = abspos->height; + childAllocation.descent = 0; // TODO + + abspos->widget->sizeAllocate (&childAllocation); + + printf ("[%p] allocating child %p at: (%d, %d), %d x (%d + %d)\n", + containingBlock, abspos->widget, childAllocation.x, + childAllocation.y, childAllocation.width, childAllocation.ascent, + childAllocation.descent); + } +} + +} // namespace dw diff --git a/dw/outofflowmgr.hh b/dw/outofflowmgr.hh new file mode 100644 index 00000000..c5c560c7 --- /dev/null +++ b/dw/outofflowmgr.hh @@ -0,0 +1,429 @@ +#ifndef __DW_OUTOFFLOWMGR_HH__ +#define __DW_OUTOFFLOWMGR_HH__ + +#include "core.hh" + +namespace dw { + +class Textblock; + +/** + * \brief Represents additional data for containing blocks. + */ +class OutOfFlowMgr +{ + friend class WidgetInfo; + +private: + enum Side { LEFT, RIGHT }; + + Textblock *containingBlock; + + // These two values are set by sizeAllocateStart(), and they are + // accessable also within sizeAllocateEnd(), and also for the + // containing block, for which allocation and WAS_ALLOCATED is set + // *after* sizeAllocateEnd(). See the two inline functions + // wasAllocated(Widget*) and getAllocation(Widget*) (further down) + // for usage. + core::Allocation containingBlockAllocation; + bool containingBlockWasAllocated; + + class WidgetInfo: public lout::object::Object + { + private: + bool wasAllocated; + int xCB, yCB; // relative to the containing block + int width, height; + + OutOfFlowMgr *oofm; + core::Widget *widget; + + protected: + OutOfFlowMgr *getOutOfFlowMgr () { return oofm; } + + public: + WidgetInfo (OutOfFlowMgr *oofm, core::Widget *widget); + + inline bool wasThenAllocated () { return wasAllocated; } + inline int getOldXCB () { return xCB; } + inline int getOldYCB () { return yCB; } + inline int getOldWidth () { return width; } + inline int getOldHeight () { return height; } + + inline bool isNowAllocated () { return widget->wasAllocated (); } + inline int getNewXCB () { return widget->getAllocation()->x - + oofm->containingBlockAllocation.x; } + inline int getNewYCB () { return widget->getAllocation()->y - + oofm->containingBlockAllocation.y; } + inline int getNewWidth () { return widget->getAllocation()->width; } + inline int getNewHeight () { return widget->getAllocation()->ascent + + widget->getAllocation()->descent; } + + void update (bool wasAllocated, int xCB, int yCB, int width, int height); + void updateAllocation (); + + inline core::Widget *getWidget () { return widget; } + }; + + class Float: public WidgetInfo + { + public: + class ComparePosition: public lout::object::Comparator + { + private: + OutOfFlowMgr *oofm; + Textblock *refTB; + + public: + ComparePosition (OutOfFlowMgr *oofm, Textblock *refTB) + { this->oofm = oofm; this->refTB = refTB; } + int compare(Object *o1, Object *o2); + }; + + class CompareSideSpanningIndex: public lout::object::Comparator + { + public: + int compare(Object *o1, Object *o2); + }; + + class CompareGBAndExtIndex: public lout::object::Comparator + { + private: + OutOfFlowMgr *oofm; + + public: + CompareGBAndExtIndex (OutOfFlowMgr *oofm) { this->oofm = oofm; } + int compare(Object *o1, Object *o2); + }; + + Textblock *generatingBlock; + int externalIndex; + int yReq, yReal; // relative to generator, not container + int index; /* When GB is not yet allocated: position + within TBInfo::leftFloatsGB or + TBInfo::rightFloatsGB, respectively. When GB + is allocated: position within leftFloatsCB + or rightFloatsCB, respectively, even when + the floats are still elements of + TBInfo::*FloatsGB. */ + int sideSpanningIndex, mark; + core::Requisition size; + int cbAvailWidth; /* On which the calculation of relative sizes + is based. Height not yet used, and probably + not added before size redesign. */ + bool dirty, sizeChangedSinceLastAllocation; + bool inCBList; /* Neccessary to prevent floats from being moved + twice from GB to CB list. */ + + Float (OutOfFlowMgr *oofm, core::Widget *widget, + Textblock *generatingBlock, int externalIndex); + + void intoStringBuffer(lout::misc::StringBuffer *sb); + + bool covers (Textblock *textblock, int y, int h); + }; + + /** + * This list is kept sorted. + * + * To prevent accessing methods of the base class in an + * uncontrolled way, the inheritance is private, not public; this + * means that all methods must be delegated (see iterator(), size() + * etc. below.) + * + * TODO Update comment: still sorted, but ... + * + * More: add() and change() may check order again. + */ + class SortedFloatsVector: private lout::container::typed::Vector<Float> + { + public: + enum Type { GB, CB } type; + + private: + OutOfFlowMgr *oofm; + Side side; + + public: + inline SortedFloatsVector (OutOfFlowMgr *oofm, Side side, Type type) : + lout::container::typed::Vector<Float> (1, false) + { this->oofm = oofm; this->side = side; this->type = type; } + + int findFloatIndex (Textblock *lastGB, int lastExtIndex); + int find (Textblock *textblock, int y, int start, int end); + int findFirst (Textblock *textblock, int y, int h, Textblock *lastGB, + int lastExtIndex); + int findLastBeforeSideSpanningIndex (int sideSpanningIndex); + void put (Float *vloat); + + inline lout::container::typed::Iterator<Float> iterator() + { return lout::container::typed::Vector<Float>::iterator (); } + inline int size () + { return lout::container::typed::Vector<Float>::size (); } + inline Float *get (int pos) + { return lout::container::typed::Vector<Float>::get (pos); } + inline void clear () + { lout::container::typed::Vector<Float>::clear (); } + }; + + class TBInfo: public WidgetInfo + { + public: + int availWidth; + int index; // position within "tbInfos" + + TBInfo *parent; + int parentExtIndex; + + // These two lists store all floats generated by this textblock, + // as long as this textblock is not allocates. + SortedFloatsVector *leftFloatsGB, *rightFloatsGB; + + TBInfo (OutOfFlowMgr *oofm, Textblock *textblock, + TBInfo *parent, int parentExtIndex); + ~TBInfo (); + + inline Textblock *getTextblock () { return (Textblock*)getWidget (); } + }; + + class AbsolutelyPositioned: public lout::object::Object + { + public: + core::Widget *widget; + int xCB, yCB; // relative to the containing block + int width, height; + bool dirty; + + AbsolutelyPositioned (OutOfFlowMgr *oofm, core::Widget *widget, + Textblock *generatingBlock, int externalIndex); + }; + + // These two lists store all floats, in the order in which they are + // defined. Only used for iterators. + lout::container::typed::Vector<Float> *leftFloatsAll, *rightFloatsAll; + + // These two lists store all floats whose generators are already + // allocated. + SortedFloatsVector *leftFloatsCB, *rightFloatsCB; + + lout::container::typed::HashTable<lout::object::TypedPointer + <dw::core::Widget>, Float> *floatsByWidget; + + lout::container::typed::Vector<TBInfo> *tbInfos; + lout::container::typed::HashTable<lout::object::TypedPointer <Textblock>, + TBInfo> *tbInfosByTextblock; + + lout::container::typed::Vector<AbsolutelyPositioned> *absolutelyPositioned; + + int lastLeftTBIndex, lastRightTBIndex, leftFloatsMark, rightFloatsMark; + + /** + * Variant of Widget::wasAllocated(), which can also be used within + * OOFM::sizeAllocateEnd(), and also for the generating block. + */ + inline bool wasAllocated (core::Widget *widget) { + return widget->wasAllocated () || + (widget == (core::Widget*)containingBlock && + containingBlockWasAllocated); } + + /** + * Variant of Widget::getAllocation(), which can also be used + * within OOFM::sizeAllocateEnd(), and also for the generating + * block. + */ + inline core::Allocation *getAllocation (core::Widget *widget) { + return widget == (core::Widget*)containingBlock ? + &containingBlockAllocation : widget->getAllocation (); } + + void moveExternalIndices (SortedFloatsVector *list, int oldStartIndex, + int diff); + Float *findFloatByWidget (core::Widget *widget); + + void moveFromGBToCB (Side side); + void sizeAllocateFloats (Side side); + int calcFloatX (Float *vloat, Side side, int gbX, int gbWidth, + int gbAvailWidth); + + bool hasRelationChanged (TBInfo *tbInfo,int *minFloatPos, + core::Widget **minFloat); + bool hasRelationChanged (TBInfo *tbInfo, Side side, int *minFloatPos, + core::Widget **minFloat); + bool hasRelationChanged (bool oldTBAlloc, + int oldTBx, int oldTBy, int oldTBw, int oldTBh, + int newTBx, int newTBy, int newTBw, int newTBh, + bool oldFlAlloc, + int oldFlx, int oldFly, int oldFlw, int oldFlh, + int newFlx, int newFly, int newFlw, int newFlh, + Side side, int *floatPos); + + bool doFloatsExceedCB (Side side); + bool haveExtremesChanged (Side side); + + void drawFloats (SortedFloatsVector *list, core::View *view, + core::Rectangle *area); + void drawAbsolutelyPositioned (core::View *view, core::Rectangle *area); + core::Widget *getFloatWidgetAtPoint (SortedFloatsVector *list, int x, int y, + int level); + core::Widget *getAbsolutelyPositionedWidgetAtPoint (int x, int y, int level); + + bool collidesV (Float *vloat, Float *other, int *yReal); + bool collidesH (Float *vloat, Float *other, int *yReal); + + void getFloatsListsAndSide (Float *vloat, SortedFloatsVector **listSame, + SortedFloatsVector **listOpp, Side *side); + + void getFloatsSize (core::Requisition *cbReq, Side side, int *width, + int *height); + void getFloatsExtremes (core::Extremes *cbExtr, Side side, int *minWidth, + int *maxWidth); + + TBInfo *getTextblock (Textblock *textblock); + int getBorder (Textblock *textblock, Side side, int y, int h, + Textblock *lastGB, int lastExtIndex); + SortedFloatsVector *getFloatsListForTextblock (Textblock *textblock, + Side side); + bool hasFloat (Textblock *textblock, Side side, int y, int h, + Textblock *lastGB, int lastExtIndex); + + int getClearPosition (Textblock *tb, Side side); + + void ensureFloatSize (Float *vloat); + int adjustFloatWidth (int width, core::Extremes *extremes); + + void tellFloatPosition (core::Widget *widget, int yReq); + + void getAbsolutelyPositionedSize (core::Requisition *cbReq, int *width, + int *height); + void getAbsolutelyPositionedExtremes (core::Extremes *cbExtr, int *minWidth, + int *maxWidth); + void ensureAbsolutelyPositionedSizeAndPosition (AbsolutelyPositioned + *abspos); + int calcValueForAbsolutelyPositioned (AbsolutelyPositioned *abspos, + core::style::Length styleLen, + int refLen); + void sizeAllocateAbsolutelyPositioned (); + + static inline bool isWidgetFloat (core::Widget *widget) + { return widget->getStyle()->vloat != core::style::FLOAT_NONE; } + static inline bool isWidgetAbsolutelyPositioned (core::Widget *widget) + { return widget->getStyle()->position == core::style::POSITION_ABSOLUTE; } + + /* + * Format for parent ref (see also below for isRefOutOfFlow, + * createRefNormalFlow, and getLineNoFromRef. + * + * Widget in flow: + * + * +---+ - - - +---+---+- - - - - -+---+---+---+---+ + * | line number | 0 | + * +---+ - - - +---+---+- - - - - -+---+---+---+---+ + * + * So, anything with the least signifant bit set to 1 is out of flow. + * + * Floats: + * + * +---+ - - - +---+---+- - - - - -+---+---+---+---+ + * | left float index | 0 | 0 | 1 | + * +---+ - - - +---+---+- - - - - -+---+---+---+---+ + * + * +---+ - - - +---+---+- - - - - -+---+---+---+---+ + * | right float index | 1 | 0 | 1 | + * +---+ - - - +---+---+- - - - - -+---+---+---+---+ + * + * Absolutely positioned blocks: + * + * +---+ - - - +---+---+- - - - - -+---+---+---+---+ + * | index | 1 | 1 | + * +---+ - - - +---+---+- - - - - -+---+---+---+---+ + */ + + inline static bool isRefFloat (int ref) + { return ref != -1 && (ref & 3) == 1; } + inline static bool isRefLeftFloat (int ref) + { return ref != -1 && (ref & 7) == 1; } + inline static bool isRefRightFloat (int ref) + { return ref != -1 && (ref & 7) == 5; } + inline static bool isRefAbsolutelyPositioned (int ref) + { return ref != -1 && (ref & 3) == 3; } + + inline static int createRefLeftFloat (int index) + { return (index << 3) | 1; } + inline static int createRefRightFloat (int index) + { return (index << 3) | 5; } + inline static int createRefAbsolutelyPositioned (int index) + { return (index << 2) | 3; } + + inline static int getFloatIndexFromRef (int ref) + { return ref == -1 ? ref : (ref >> 3); } + inline static int getAbsolutelyPositionedIndexFromRef (int ref) + { return ref == -1 ? ref : (ref >> 2); } + +public: + OutOfFlowMgr (Textblock *containingBlock); + ~OutOfFlowMgr (); + + void sizeAllocateStart (core::Allocation *containingBlockAllocation); + void sizeAllocateEnd (); + void draw (core::View *view, core::Rectangle *area); + + void markSizeChange (int ref); + void markExtremesChange (int ref); + core::Widget *getWidgetAtPoint (int x, int y, int level); + + static bool isWidgetOutOfFlow (core::Widget *widget); + static bool isWidgetHandledByOOFM (core::Widget *widget); + void addWidgetInFlow (Textblock *textblock, Textblock *parentBlock, + int externalIndex); + void addWidgetOOF (core::Widget *widget, Textblock *generatingBlock, + int externalIndex); + void moveExternalIndices (Textblock *generatingBlock, int oldStartIndex, + int diff); + + void tellPosition (core::Widget *widget, int yReq); + + void getSize (core::Requisition *cbReq, int *oofWidth, int *oofHeight); + void getExtremes (core::Extremes *cbExtr, + int *oofMinWidth, int *oofMaxWidth); + + int getLeftBorder (Textblock *textblock, int y, int h, Textblock *lastGB, + int lastExtIndex); + int getRightBorder (Textblock *textblock, int y, int h, Textblock *lastGB, + int lastExtIndex); + + bool hasFloatLeft (Textblock *textblock, int y, int h, Textblock *lastGB, + int lastExtIndex); + bool hasFloatRight (Textblock *textblock, int y, int h, Textblock *lastGB, + int lastExtIndex); + + int getClearPosition (Textblock *tb); + + inline static bool isRefOutOfFlow (int ref) + { return ref != -1 && (ref & 1) != 0; } + inline static int createRefNormalFlow (int lineNo) { return lineNo << 1; } + inline static int getLineNoFromRef (int ref) + { return ref == -1 ? ref : (ref >> 1); } + + // for iterators + inline int getNumWidgets () { + return leftFloatsAll->size() + rightFloatsAll->size() + + absolutelyPositioned->size(); } + + inline core::Widget *getWidget (int i) { + if (i < leftFloatsAll->size()) + return leftFloatsAll->get(i)->getWidget (); + else if (i < leftFloatsAll->size() + rightFloatsAll->size()) + return rightFloatsAll->get(i - leftFloatsAll->size())->getWidget (); + else + return absolutelyPositioned->get(i - (leftFloatsAll->size() + + rightFloatsAll->size()))->widget; + } + + inline bool affectsLeftBorder (core::Widget *widget) { + return widget->getStyle()->vloat == core::style::FLOAT_LEFT; } + inline bool affectsRightBorder (core::Widget *widget) { + return widget->getStyle()->vloat == core::style::FLOAT_RIGHT; } +}; + +} // namespace dw + +#endif // __DW_OUTOFFLOWMGR_HH__ diff --git a/dw/ruler.cc b/dw/ruler.cc index 115dfaa5..2b5288c2 100644 --- a/dw/ruler.cc +++ b/dw/ruler.cc @@ -28,17 +28,28 @@ namespace dw { Ruler::Ruler () { + setFlags (USES_HINTS); setFlags (BLOCK_LEVEL); unsetFlags (HAS_CONTENTS); + availWidth = 0; } void Ruler::sizeRequestImpl (core::Requisition *requisition) { - requisition->width = getStyle()->boxDiffWidth (); + requisition->width = + lout::misc::max (availWidth, getStyle()->boxDiffWidth ()); requisition->ascent = getStyle()->boxOffsetY (); requisition->descent = getStyle()->boxRestHeight (); } +void Ruler::setWidth (int width) +{ + if (availWidth != width) { + availWidth = width; + queueResize (0, false); + } +} + void Ruler::draw (core::View *view, core::Rectangle *area) { drawWidgetBox (view, area, false); diff --git a/dw/ruler.hh b/dw/ruler.hh index 32e859a1..863792dd 100644 --- a/dw/ruler.hh +++ b/dw/ruler.hh @@ -15,8 +15,12 @@ namespace dw { */ class Ruler: public core::Widget { +private: + int availWidth; + protected: void sizeRequestImpl (core::Requisition *requisition); + void setWidth (int width); void draw (core::View *view, core::Rectangle *area); public: diff --git a/dw/style.cc b/dw/style.cc index 8ec230a1..5edb7047 100644 --- a/dw/style.cc +++ b/dw/style.cc @@ -17,8 +17,6 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ - - #include <stdio.h> #include <string.h> #include <unistd.h> @@ -75,6 +73,10 @@ void StyleAttrs::initValues () backgroundPositionX = createPerLength (0); backgroundPositionY = createPerLength (0); width = height = lineHeight = LENGTH_AUTO; + vloat = FLOAT_NONE; + clear = CLEAR_NONE; + position = POSITION_STATIC; + top = bottom = left = right = LENGTH_AUTO; textIndent = 0; margin.setVal (0); borderWidth.setVal (0); @@ -101,6 +103,11 @@ void StyleAttrs::resetValues () valign = VALIGN_BASELINE; textAlignChar = '.'; + vloat = FLOAT_NONE; /** \todo Correct? Check specification. */ + clear = CLEAR_NONE; /** \todo Correct? Check specification. */ + position = POSITION_STATIC; /** \todo Correct? Check specification. */ + top = bottom = left = right = LENGTH_AUTO; /** \todo Correct? Check + specification. */ backgroundColor = NULL; backgroundImage = NULL; backgroundRepeat = BACKGROUND_REPEAT; @@ -156,6 +163,13 @@ bool StyleAttrs::equals (object::Object *other) { valign == otherAttrs->valign && textAlignChar == otherAttrs->textAlignChar && textTransform == otherAttrs->textTransform && + vloat == otherAttrs->vloat && + clear == otherAttrs->clear && + position == otherAttrs->position && + top == otherAttrs->top && + bottom == otherAttrs->bottom && + left == otherAttrs->left && + right == otherAttrs->right && hBorderSpacing == otherAttrs->hBorderSpacing && vBorderSpacing == otherAttrs->vBorderSpacing && wordSpacing == otherAttrs->wordSpacing && @@ -201,6 +215,13 @@ int StyleAttrs::hashValue () { valign + textAlignChar + textTransform + + vloat + + clear + + position + + top + + bottom + + left + + right + hBorderSpacing + vBorderSpacing + wordSpacing + @@ -316,6 +337,13 @@ void Style::copyAttrs (StyleAttrs *attrs) valign = attrs->valign; textAlignChar = attrs->textAlignChar; textTransform = attrs->textTransform; + vloat = attrs->vloat; + clear = attrs->clear; + position = attrs->position; + top = attrs->top; + bottom = attrs->bottom; + left = attrs->left; + right = attrs->right; hBorderSpacing = attrs->hBorderSpacing; vBorderSpacing = attrs->vBorderSpacing; wordSpacing = attrs->wordSpacing; diff --git a/dw/style.hh b/dw/style.hh index e0ce9d89..2cc258bf 100644 --- a/dw/style.hh +++ b/dw/style.hh @@ -296,7 +296,6 @@ enum ListStylePosition { LIST_STYLE_POSITION_INSIDE, LIST_STYLE_POSITION_OUTSIDE }; - enum ListStyleType { LIST_STYLE_TYPE_DISC, LIST_STYLE_TYPE_CIRCLE, @@ -332,6 +331,13 @@ enum FontVariant { FONT_VARIANT_SMALL_CAPS }; +enum Position { + POSITION_STATIC, + POSITION_RELATIVE, + POSITION_ABSOLUTE, + POSITION_FIXED, +}; + enum TextDecoration { TEXT_DECORATION_NONE = 0, TEXT_DECORATION_UNDERLINE = 1 << 0, @@ -348,6 +354,19 @@ enum WhiteSpace { WHITE_SPACE_PRE_LINE, }; +enum FloatType { + FLOAT_NONE, + FLOAT_LEFT, + FLOAT_RIGHT +}; + +enum ClearType { + CLEAR_LEFT, + CLEAR_RIGHT, + CLEAR_BOTH, + CLEAR_NONE +}; + /** * \brief Type for representing all lengths within dw::core::style. * @@ -503,6 +522,12 @@ public: VAlignType valign; char textAlignChar; /* In future, strings will be supported. */ TextTransform textTransform; + + FloatType vloat; /* "float" is a keyword. */ + ClearType clear; + + Position position; + Length top, bottom, left, right; int hBorderSpacing, vBorderSpacing, wordSpacing; Length width, height, lineHeight, textIndent; diff --git a/dw/table.cc b/dw/table.cc index b6f7209b..565dfc9e 100644 --- a/dw/table.cc +++ b/dw/table.cc @@ -482,7 +482,7 @@ void Table::reallocChildren (int newNumCols, int newNumRows) void Table::calcCellSizes () { - if (needsResize ()) + if (needsResize () || resizeQueued ()) forceCalcCellSizes (); } @@ -640,7 +640,7 @@ void Table::apportionRowSpan () */ void Table::calcColumnExtremes () { - if (extremesChanged ()) + if (extremesChanged () || extremesQueued ()) forceCalcColumnExtremes (); } @@ -1130,7 +1130,7 @@ Table::TableIterator::TableIterator (Table *table, else if (index >= table->children->size ()) content.type = core::Content::END; else { - content.type = core::Content::WIDGET; + content.type = core::Content::WIDGET_IN_FLOW; content.widget = table->children->get(index)->cell.widget; } } @@ -1152,8 +1152,8 @@ bool Table::TableIterator::next () if (content.type == core::Content::END) return false; - // tables only contain widgets: - if ((getMask() & core::Content::WIDGET) == 0) { + // tables only contain widgets (in flow): + if ((getMask() & core::Content::WIDGET_IN_FLOW) == 0) { content.type = core::Content::END; return false; } @@ -1167,7 +1167,7 @@ bool Table::TableIterator::next () } while (table->children->get(index) == NULL || table->children->get(index)->type != Child::CELL); - content.type = core::Content::WIDGET; + content.type = core::Content::WIDGET_IN_FLOW; content.widget = table->children->get(index)->cell.widget; return true; } @@ -1179,8 +1179,8 @@ bool Table::TableIterator::prev () if (content.type == core::Content::START) return false; - // tables only contain widgets: - if ((getMask() & core::Content::WIDGET) == 0) { + // tables only contain widgets (in flow): + if ((getMask() & core::Content::WIDGET_IN_FLOW) == 0) { content.type = core::Content::START; return false; } @@ -1194,7 +1194,7 @@ bool Table::TableIterator::prev () } while (table->children->get(index) == NULL || table->children->get(index)->type != Child::CELL); - content.type = core::Content::WIDGET; + content.type = core::Content::WIDGET_IN_FLOW; content.widget = table->children->get(index)->cell.widget; return true; } diff --git a/dw/table.hh b/dw/table.hh index 1d14ec07..6966a163 100644 --- a/dw/table.hh +++ b/dw/table.hh @@ -71,7 +71,7 @@ namespace dw { * is the case. * * [C] Whether this function is called, depends on NEEDS_RESIZE / - * EXTREMES_CHANGED. + * RESIZE_QUEUED / EXTREMES_CHANGED / EXTREMES_QUEUED. * * * <h4>Apportionment</h4> diff --git a/dw/tablecell.cc b/dw/tablecell.cc index 8612f620..67d6cf2d 100644 --- a/dw/tablecell.cc +++ b/dw/tablecell.cc @@ -107,7 +107,7 @@ int TableCell::getValue () void TableCell::setMaxValue (int maxValue, int value) { line1Offset = maxValue - value; - queueResize (0, true); + queueResize (OutOfFlowMgr::createRefNormalFlow (0), true); } } // namespace dw diff --git a/dw/textblock.cc b/dw/textblock.cc index 2c4ca20b..49030d26 100644 --- a/dw/textblock.cc +++ b/dw/textblock.cc @@ -25,13 +25,14 @@ #include "../lout/debug.hh" #include <stdio.h> -#include <math.h> +#include <math.h> // remove again? +#include <limits.h> /* * Local variables */ - /* The tooltip under mouse pointer in current textblock. No ref. hold. - * (having one per view looks not worth the extra clutter). */ +/* The tooltip under mouse pointer in current textblock. No ref. hold. + * (having one per view looks not worth the extra clutter). */ static dw::core::style::Tooltip *hoverTooltip = NULL; @@ -228,6 +229,7 @@ Textblock::Textblock (bool limitTextWidth) setFlags (USES_HINTS); setButtonSensitive(true); + containingBlock = NULL; hasListitemValue = false; innerPadding = 0; line1Offset = 0; @@ -250,15 +252,14 @@ Textblock::Textblock (bool limitTextWidth) nonTemporaryLines = 0; words = new misc::NotSoSimpleVector <Word> (1); anchors = new misc::SimpleVector <Anchor> (1); - - //DBG_OBJ_SET_NUM(this, "num_lines", num_lines); + outOfFlowMgr = NULL; wrapRefLines = wrapRefParagraphs = -1; - //DBG_OBJ_SET_NUM(this, "last_line_width", last_line_width); - //DBG_OBJ_SET_NUM(this, "last_line_par_min", last_line_par_min); - //DBG_OBJ_SET_NUM(this, "last_line_par_max", last_line_par_max); - //DBG_OBJ_SET_NUM(this, "wrap_ref", wrap_ref); + DBG_OBJ_SET_NUM ("lines.size", lines->size ()); + DBG_OBJ_SET_NUM ("words.size", words->size ()); + DBG_OBJ_SET_NUM ("wrapRefLines", wrapRefLines); + DBG_OBJ_SET_NUM ("wrapRefParagraphs", wrapRefParagraphs); hoverLink = -1; @@ -267,6 +268,12 @@ Textblock::Textblock (bool limitTextWidth) availAscent = 100; availDescent = 0; + DBG_OBJ_SET_NUM ("availWidth", availWidth); + DBG_OBJ_SET_NUM ("availAscent", availAscent); + DBG_OBJ_SET_NUM ("availDescent", availDescent); + + verticalOffset = 0; + this->limitTextWidth = limitTextWidth; for (int layer = 0; layer < core::HIGHLIGHT_NUM_LAYERS; layer++) { @@ -276,6 +283,8 @@ Textblock::Textblock (bool limitTextWidth) hlEnd[layer].index = 0; hlEnd[layer].nChar = 0; } + + initNewLine (); } Textblock::~Textblock () @@ -285,18 +294,8 @@ Textblock::~Textblock () /* make sure not to call a free'd tooltip (very fast overkill) */ hoverTooltip = NULL; - for (int i = 0; i < words->size(); i++) { - Word *word = words->getRef (i); - - if (word->content.type == core::Content::WIDGET) - delete word->content.widget; - - removeWordImgRenderer (i); - removeSpaceImgRenderer (i); - - word->style->unref (); - word->spaceStyle->unref (); - } + for (int i = 0; i < words->size(); i++) + cleanupWord (i); for (int i = 0; i < anchors->size(); i++) { Anchor *anchor = anchors->getRef (i); @@ -309,6 +308,16 @@ Textblock::~Textblock () delete words; delete anchors; + if(outOfFlowMgr) { + // I feel more comfortable by letting the textblock delete these + // widgets, instead of doing this in ~OutOfFlowMgr. + + for (int i = 0; i < outOfFlowMgr->getNumWidgets (); i++) + delete outOfFlowMgr->getWidget (i); + + delete outOfFlowMgr; + } + /* Make sure we don't own widgets anymore. Necessary before call of parent class destructor. (???) */ words = NULL; @@ -323,7 +332,8 @@ Textblock::~Textblock () */ void Textblock::sizeRequestImpl (core::Requisition *requisition) { - PRINTF ("[%p] SIZE_REQUEST: ...\n", this); + DBG_OBJ_MSG ("resize", 0, "<b>sizeRequestImpl</b> ()"); + DBG_OBJ_MSG_START (); rewrap (); showMissingLines (); @@ -331,18 +341,18 @@ void Textblock::sizeRequestImpl (core::Requisition *requisition) if (lines->size () > 0) { Line *lastLine = lines->getRef (lines->size () - 1); requisition->width = lastLine->maxLineWidth; - - PRINTF ("[%p] SIZE_REQUEST: lastLine->maxLineWidth = %d\n", - this, lastLine->maxLineWidth); - - PRINTF ("[%p] SIZE_REQUEST: lines[0]->boxAscent = %d\n", - this, lines->getRef(0)->boxAscent); - PRINTF ("[%p] SIZE_REQUEST: lines[%d]->top = %d\n", - this, lines->size () - 1, lastLine->top); - PRINTF ("[%p] SIZE_REQUEST: lines[%d]->boxAscent = %d\n", - this, lines->size () - 1, lastLine->boxAscent); - PRINTF ("[%p] SIZE_REQUEST: lines[%d]->boxDescent = %d\n", - this, lines->size () - 1, lastLine->boxDescent); + + DBG_OBJ_MSGF ("resize", 1, "lines[%d]->maxLineWidth = %d", + lines->size () - 1, lastLine->maxLineWidth); + + DBG_OBJ_MSGF ("resize", 1, "lines[0]->boxAscent = %d", + lines->getRef(0)->boxAscent); + DBG_OBJ_MSGF ("resize", 1, "lines[%d]->top = %d", + lines->size () - 1, lastLine->top); + DBG_OBJ_MSGF ("resize", 1, "lines[%d]->boxAscent = %d", + lines->size () - 1, lastLine->boxAscent); + DBG_OBJ_MSGF ("resize", 1, "lines[%d]->boxDescent = %d", + lines->size () - 1, lastLine->boxDescent); /* Note: the breakSpace of the last line is ignored, so breaks at the end of a textblock are not visible. */ @@ -356,18 +366,44 @@ void Textblock::sizeRequestImpl (core::Requisition *requisition) requisition->descent = 0; } - PRINTF ("[%p] SIZE_REQUEST: inner padding = %d, boxDiffWidth = %d\n", - this, innerPadding, getStyle()->boxDiffWidth ()); + DBG_OBJ_MSGF ("resize", 1, "inner padding = %d, boxDiffWidth = %d", + innerPadding, getStyle()->boxDiffWidth ()); requisition->width += innerPadding + getStyle()->boxDiffWidth (); - requisition->ascent += getStyle()->boxOffsetY (); + requisition->ascent += verticalOffset + getStyle()->boxOffsetY (); requisition->descent += getStyle()->boxRestHeight (); - if (requisition->width < availWidth) + // Dealing with parts out of flow, which may overlap the borders of + // the text block. Base lines are ignored here: they do not play a + // role (currently) and caring about them (for the future) would + // cause too much problems. + + DBG_OBJ_MSGF ("resize", 1, "before considering OOF widgets: %d * (%d + %d)", + requisition->width, requisition->ascent, requisition->descent); + + if (outOfFlowMgr) { + int oofWidth, oofHeight; + outOfFlowMgr->getSize (requisition, &oofWidth, &oofHeight); + requisition->width = misc::max (requisition->width, oofWidth); + if (oofHeight > requisition->ascent + requisition->descent) + requisition->descent = oofHeight - requisition->ascent; + } + + DBG_OBJ_MSGF ("resize", 1, + "before considering availWidth (= %d): %d * (%d + %d)", + availWidth, requisition->width, requisition->ascent, + requisition->descent); + + if (requisition->width < availWidth) { requisition->width = availWidth; + DBG_OBJ_MSGF ("resize", 1, "adjusting to availWidth => %d", + requisition->width); + } - PRINTF ("[%p] SIZE_REQUEST: %d x %d + %d\n", this, requisition->width, - requisition->ascent, requisition->descent); + DBG_OBJ_MSGF ("resize", 1, "=> %d * (%d + %d)", + requisition->width, requisition->ascent, requisition->descent); + + DBG_OBJ_MSG_END (); } /** @@ -375,7 +411,7 @@ void Textblock::sizeRequestImpl (core::Requisition *requisition) */ void Textblock::getWordExtremes (Word *word, core::Extremes *extremes) { - if (word->content.type == core::Content::WIDGET) { + if (word->content.type == core::Content::WIDGET_IN_FLOW) { if (word->content.widget->usesHints ()) word->content.widget->getExtremes (extremes); else { @@ -405,7 +441,8 @@ void Textblock::getWordExtremes (Word *word, core::Extremes *extremes) void Textblock::getExtremesImpl (core::Extremes *extremes) { - PRINTF ("[%p] GET_EXTREMES ...\n", this); + DBG_OBJ_MSG ("resize", 0, "<b>getExtremesImpl</b>"); + DBG_OBJ_MSG_START (); fillParagraphs (); @@ -417,14 +454,33 @@ void Textblock::getExtremesImpl (core::Extremes *extremes) Paragraph *lastPar = paragraphs->getLastRef (); extremes->minWidth = lastPar->maxParMin; extremes->maxWidth = lastPar->maxParMax; + + DBG_OBJ_MSGF ("resize", 1, "paragraphs[%d]->maxParMin = %d", + paragraphs->size () - 1, lastPar->maxParMin); + DBG_OBJ_MSGF ("resize", 1, "paragraphs[%d]->maxParMax = %d", + paragraphs->size () - 1, lastPar->maxParMax); } int diff = innerPadding + getStyle()->boxDiffWidth (); extremes->minWidth += diff; extremes->maxWidth += diff; - PRINTF ("[%p] GET_EXTREMES => %d / %d\n", - this, extremes->minWidth, extremes->maxWidth); + if (outOfFlowMgr) { + int oofMinWidth, oofMaxWidth; + outOfFlowMgr->getExtremes (extremes, &oofMinWidth, &oofMaxWidth); + + DBG_OBJ_MSGF ("resize", 1, "extremes: %d / %d, corrected: %d / %d", + extremes->minWidth, extremes->maxWidth, + oofMinWidth, oofMaxWidth); + + extremes->minWidth = misc::max (extremes->minWidth, oofMinWidth); + extremes->maxWidth = misc::max (extremes->maxWidth, oofMaxWidth); + } + + DBG_OBJ_MSGF ("resize", 1, "=> %d / %d", + extremes->minWidth, extremes->maxWidth); + + DBG_OBJ_MSG_END (); } @@ -436,6 +492,9 @@ void Textblock::sizeAllocateImpl (core::Allocation *allocation) showMissingLines (); + if(outOfFlowMgr) + outOfFlowMgr->sizeAllocateStart (allocation); + int lineIndex, wordIndex; Line *line; Word *word; @@ -449,7 +508,7 @@ void Textblock::sizeAllocateImpl (core::Allocation *allocation) for (lineIndex = 0; lineIndex < lines->size (); lineIndex++) { line = lines->getRef (lineIndex); - xCursor = lineXOffsetWidget (line); + xCursor = line->offsetCompleteWidget; for (wordIndex = line->firstWord; wordIndex <= line->lastWord; wordIndex++) { @@ -459,7 +518,7 @@ void Textblock::sizeAllocateImpl (core::Allocation *allocation) redrawY = misc::min (redrawY, lineYOffsetWidget (line)); } - if (word->content.type == core::Content::WIDGET) { + if (word->content.type == core::Content::WIDGET_IN_FLOW) { /** \todo Justification within the line is done here. */ childAllocation.x = xCursor + allocation->x; /* align=top: @@ -473,6 +532,7 @@ void Textblock::sizeAllocateImpl (core::Allocation *allocation) lineYOffsetCanvasAllocation (line, allocation) + (line->boxAscent - 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; @@ -531,11 +591,17 @@ void Textblock::sizeAllocateImpl (core::Allocation *allocation) } } + if(outOfFlowMgr) + outOfFlowMgr->sizeAllocateEnd (); + for (int i = 0; i < anchors->size(); i++) { Anchor *anchor = anchors->getRef(i); int y; - if (anchor->wordIndex >= words->size()) { + if (anchor->wordIndex >= words->size() || + // Also regard not-yet-existing lines. + lines->size () <= 0 || + anchor->wordIndex > lines->getLastRef()->lastWord) { y = allocation->y + allocation->ascent + allocation->descent; } else { Line *line = lines->getRef(findLineOfWord (anchor->wordIndex)); @@ -561,47 +627,122 @@ void Textblock::resizeDrawImpl () void Textblock::markSizeChange (int ref) { - PRINTF ("[%p] MARK_SIZE_CHANGE (%d): %d => ...\n", this, ref, wrapRefLines); + DBG_OBJ_MSGF ("resize", 0, "<b>markSizeChange</b> (%d)", ref); + DBG_OBJ_MSG_START (); - /* By the way: ref == -1 may have two different causes: (i) flush() - calls "queueResize (-1, true)", when no rewrapping is necessary; - and (ii) a word may have parentRef == -1 , when it is not yet - added to a line. In the latter case, nothing has to be done - now, but addLine(...) will do everything necessary. */ - if (ref != -1) { - if (wrapRefLines == -1) - wrapRefLines = ref; - else - wrapRefLines = misc::min (wrapRefLines, ref); - } + if (OutOfFlowMgr::isRefOutOfFlow (ref)) { + assert (outOfFlowMgr != NULL); + outOfFlowMgr->markSizeChange (ref); + } else { + PRINTF ("[%p] MARK_SIZE_CHANGE (%d): %d => ...\n", + this, ref, wrapRefLines); + + /* By the way: ref == -1 may have two different causes: (i) flush() + calls "queueResize (-1, true)", when no rewrapping is necessary; + and (ii) a word may have parentRef == -1 , when it is not yet + added to a line. In the latter case, nothing has to be done + now, but addLine(...) will do everything necessary. */ + if (ref != -1) { + if (wrapRefLines == -1) + wrapRefLines = OutOfFlowMgr::getLineNoFromRef (ref); + else + wrapRefLines = misc::min (wrapRefLines, + OutOfFlowMgr::getLineNoFromRef (ref)); + } - PRINTF (" ... => %d\n", wrapRefLine); + DBG_OBJ_SET_NUM ("wrapRefLines", wrapRefLines); + + // It seems that sometimes (even without floats) the lines + // structure is changed, so that wrapRefLines may refers to a + // line which does not exist anymore. Should be examined + // again. Until then, setting wrapRefLines to the same value is + // a workaround. + markExtremesChange (ref); + } - // It seems that sometimes the lines structure is changed, so that - // wrapRefLines may refers to a line which does not exist - // anymore. Should be examined again. Until then, setting - // wrapRefLines to the same value is a workaround. - markExtremesChange (ref); + DBG_OBJ_MSG_END (); } void Textblock::markExtremesChange (int ref) { - PRINTF ("[%p] MARK_EXTREMES_CHANGE (%d): %d => ...\n", - this, ref, wrapRefParagraphs); - - /* By the way: ref == -1 may have two different causes: (i) flush() - calls "queueResize (-1, true)", when no rewrapping is necessary; - and (ii) a word may have parentRef == -1 , when it is not yet - added to a line. In the latter case, nothing has to be done - now, but addLine(...) will do everything necessary. */ - if (ref != -1) { - if (wrapRefParagraphs == -1) - wrapRefParagraphs = ref; - else - wrapRefParagraphs = misc::min (wrapRefParagraphs, ref); + DBG_OBJ_MSGF ("resize", 1, "<b>markExtremesChange</b> (%d)", ref); + DBG_OBJ_MSG_START (); + + if (OutOfFlowMgr::isRefOutOfFlow (ref)) { + assert (outOfFlowMgr != NULL); + outOfFlowMgr->markExtremesChange (ref); + } else { + PRINTF ("[%p] MARK_EXTREMES_CHANGE (%d): %d => ...\n", + this, ref, wrapRefParagraphs); + + /* By the way: ref == -1 may have two different causes: (i) flush() + calls "queueResize (-1, true)", when no rewrapping is necessary; + and (ii) a word may have parentRef == -1 , when it is not yet + added to a line. In the latter case, nothing has to be done + now, but addLine(...) will do everything necessary. */ + if (ref != -1) { + if (wrapRefParagraphs == -1) + wrapRefParagraphs = OutOfFlowMgr::getLineNoFromRef (ref); + else + wrapRefParagraphs = + misc::min (wrapRefParagraphs, + OutOfFlowMgr::getLineNoFromRef (ref)); + } + + DBG_OBJ_SET_NUM ("wrapRefParagraphs", wrapRefParagraphs); } - PRINTF (" ... => %d\n", wrapRefParagraphs); + DBG_OBJ_MSG_END (); +} + +void Textblock::notifySetAsTopLevel() +{ + PRINTF ("%p becomes toplevel\n", this); + containingBlock = this; + PRINTF ("-> %p is its own containing block\n", this); +} + +bool Textblock::isContainingBlock (Widget *widget) +{ + return + // Of course, only textblocks are considered as containing + // blocks. + widget->instanceOf (Textblock::CLASS_ID) && + // The second condition: that this block is "out of flow", in a + // wider sense. + (// The toplevel widget is "out of flow", since there is no + // parent, and so no context. + widget->getParent() == NULL || + // A similar reasoning applies to a widget with another parent + // than a textblock (typical example: a table cell (this is + // also a text block) within a table widget). + !widget->getParent()->instanceOf (Textblock::CLASS_ID) || + // Finally, "out of flow" in a narrower sense: floats and + // absolute positions. + OutOfFlowMgr::isWidgetOutOfFlow (widget)); +} + +void Textblock::notifySetParent () +{ + PRINTF ("%p becomes a child of %p\n", this, getParent()); + + // Search for containing Box. + containingBlock = NULL; + + for (Widget *widget = this; widget != NULL && containingBlock == NULL; + widget = widget->getParent()) + if (isContainingBlock (widget)) { + containingBlock = (Textblock*)widget; + + if (containingBlock == this) { + PRINTF ("-> %p is its own containing block\n", this); + } else { + PRINTF ("-> %p becomes containing block of %p\n", + containingBlock, this); + } + } + + assert (containingBlock != NULL); } void Textblock::setWidth (int width) @@ -609,43 +750,46 @@ void Textblock::setWidth (int width) /* If limitTextWidth is set to YES, a queueResize() may also be * necessary. */ if (availWidth != width || limitTextWidth) { - //DEBUG_MSG(DEBUG_REWRAP_LEVEL, - // "setWidth: Calling queueResize, " - // "in page with %d word(s)\n", - // words->size()); + DBG_OBJ_MSGF ("resize", 0, "<b>setWidth</b> (%d)", width); + DBG_OBJ_MSG_START (); availWidth = width; - queueResize (0, false); + DBG_OBJ_SET_NUM ("availWidth", availWidth); + queueResize (OutOfFlowMgr::createRefNormalFlow (0), false); mustQueueResize = false; redrawY = 0; + + DBG_OBJ_MSG_END (); } } void Textblock::setAscent (int ascent) { if (availAscent != ascent) { - //DEBUG_MSG(DEBUG_REWRAP_LEVEL, - // "setAscent: Calling queueResize, " - // "in page with %d word(s)\n", - // words->size()); + DBG_OBJ_MSGF ("resize", 0, "<b>setAscent</b> (%d)", ascent); + DBG_OBJ_MSG_START (); availAscent = ascent; - queueResize (0, false); + DBG_OBJ_SET_NUM ("availAscent", availAscent); + queueResize (OutOfFlowMgr::createRefNormalFlow (0), false); mustQueueResize = false; + + DBG_OBJ_MSG_END (); } } void Textblock::setDescent (int descent) { if (availDescent != descent) { - //DEBUG_MSG(DEBUG_REWRAP_LEVEL, - // "setDescent: Calling queueResize, " - // "in page with %d word(s)\n", - // words->size()); + DBG_OBJ_MSGF ("resize", 0, "<b>setDescent</b> (%d)", descent); + DBG_OBJ_MSG_START (); availDescent = descent; - queueResize (0, false); + DBG_OBJ_SET_NUM ("availDescent", availDescent); + queueResize (OutOfFlowMgr::createRefNormalFlow (0), false); mustQueueResize = false; + + DBG_OBJ_MSG_END (); } } @@ -753,7 +897,8 @@ bool Textblock::sendSelectionEvent (core::SelectionState::EventType eventType, wordIndex = words->size () - 1; charPos = core::SelectionState::END_OF_WORD; } else { - Line *line = lines->getRef (findLineIndex (event->yWidget)); + Line *line = + lines->getRef (findLineIndexWhenAllocated (event->yWidget)); // Pointer within the break space? if (event->yWidget > @@ -761,11 +906,11 @@ bool Textblock::sendSelectionEvent (core::SelectionState::EventType eventType, // Choose this break. wordIndex = line->lastWord; charPos = core::SelectionState::END_OF_WORD; - } else if (event->xWidget < lineXOffsetWidget (line)) { + } else if (event->xWidget < line->offsetCompleteWidget) { // Left of the first word in the line. wordIndex = line->firstWord; } else { - int nextWordStartX = lineXOffsetWidget (line); + int nextWordStartX = line->offsetCompleteWidget; for (wordIndex = line->firstWord; wordIndex <= line->lastWord; @@ -860,8 +1005,9 @@ bool Textblock::sendSelectionEvent (core::SelectionState::EventType eventType, } } } - it = new TextblockIterator (this, core::Content::SELECTION_CONTENT, - wordIndex); + + it = new TextblockIterator (this, core::Content::maskForSelection (true), + false, wordIndex); r = selectionHandleEvent (eventType, it, charPos, link, event); it->unref (); return r; @@ -884,6 +1030,8 @@ core::Iterator *Textblock::iterator (core::Content::Type mask, bool atEnd) */ void Textblock::calcWidgetSize (core::Widget *widget, core::Requisition *size) { + DBG_OBJ_MSGF ("resize", 0, "<b>calcWidgetSize</b> (%p, ...)", widget); + core::Requisition requisition; int availWidth, availAscent, availDescent; core::style::Style *wstyle = widget->getStyle(); @@ -894,14 +1042,38 @@ void Textblock::calcWidgetSize (core::Widget *widget, core::Requisition *size) availDescent = this->availDescent; if (widget->usesHints ()) { - widget->setWidth (availWidth); + // This is a simplified version of calcAvailWidth (see there for + // more details). Until recently, the *attribute* availWidth was + // used, widthout any corrections. To limit the damage, only + // includde left and right border (by floats), until the Great + // Redesign Of Widget Sizes (GROWS). + int corrAvailWidth; + // Textblocks keep track of borders themselves, so they get the + // total available width. (Should once replaced by something + // like OOFAware.) + if (widget->instanceOf (Textblock::CLASS_ID)) + corrAvailWidth = availWidth; + else + corrAvailWidth = + misc::max (availWidth - (newLineLeftBorder + newLineRightBorder), + 0); + + DBG_OBJ_MSGF ("resize", 1, "setting hints: %d, %d, %d", + corrAvailWidth, availAscent, availDescent); + widget->setWidth (corrAvailWidth); widget->setAscent (availAscent); widget->setDescent (availDescent); widget->sizeRequest (size); + DBG_OBJ_MSGF ("resize", 1, "sizeRequest => %d * (%d + %d)", + size->width, size->ascent, size->descent); } else { if (wstyle->width == core::style::LENGTH_AUTO || - wstyle->height == core::style::LENGTH_AUTO) + wstyle->height == core::style::LENGTH_AUTO) { widget->sizeRequest (&requisition); + DBG_OBJ_MSGF ("resize", 1, "AUTO; sizeRequest => %d * (%d + %d)", + requisition.width, requisition.ascent, + requisition.descent); + } if (wstyle->width == core::style::LENGTH_AUTO) size->width = requisition.width; @@ -934,6 +1106,10 @@ void Textblock::calcWidgetSize (core::Widget *widget, core::Requisition *size) /* ascent and descent in words do not contain margins. */ size->ascent -= wstyle->margin.top; size->descent -= wstyle->margin.bottom; + + DBG_OBJ_MSGF ("resize", 1, "result: %d * (%d + %d)", + size->width, size->ascent, size->descent); + DBG_OBJ_MSG_END (); } /* @@ -1245,7 +1421,7 @@ void Textblock::drawSpace(int wordIndex, core::View *view, */ void Textblock::drawLine (Line *line, core::View *view, core::Rectangle *area) { - int xWidget = lineXOffsetWidget(line); + int xWidget = line->offsetCompleteWidget; int yWidgetBase = lineYOffsetWidget (line) + line->boxAscent; for (int wordIndex = line->firstWord; @@ -1256,10 +1432,10 @@ void Textblock::drawLine (Line *line, core::View *view, core::Rectangle *area) if (xWidget + wordSize + word->hyphenWidth + word->effSpace >= area->x) { if (word->content.type == core::Content::TEXT || - word->content.type == core::Content::WIDGET) { + word->content.type == core::Content::WIDGET_IN_FLOW) { if (word->size.width > 0) { - if (word->content.type == core::Content::WIDGET) { + if (word->content.type == core::Content::WIDGET_IN_FLOW) { core::Widget *child = word->content.widget; core::Rectangle childArea; @@ -1303,10 +1479,36 @@ void Textblock::drawLine (Line *line, core::View *view, core::Rectangle *area) } /** - * Find the first line index that includes y, relative to top of widget. + * Find the first line index that includes y, which is given in widget + * coordinates. */ int Textblock::findLineIndex (int y) { + return wasAllocated () ? + findLineIndexWhenAllocated (y) : findLineIndexWhenNotAllocated (y); +} + +int Textblock::findLineIndexWhenNotAllocated (int y) +{ + if (lines->size() == 0) + return -1; + else + return findLineIndex (y, + lines->getRef(0)->boxAscent + verticalOffset + + getStyle()->boxOffsetY()); +} + +int Textblock::findLineIndexWhenAllocated (int y) +{ + assert (wasAllocated ()); + return findLineIndex (y, allocation.ascent); +} + +int Textblock::findLineIndex (int y, int ascent) +{ + core::Allocation alloc; + alloc.ascent = ascent; // More is not needed. + int maxIndex = lines->size () - 1; int step, index, low = 0; @@ -1314,12 +1516,12 @@ int Textblock::findLineIndex (int y) while ( step > 1 ) { index = low + step; if (index <= maxIndex && - lineYOffsetWidgetI (index) <= y) + lineYOffsetWidgetIAllocation (index, &alloc) <= y) low = index; step = (step + 1) >> 1; } - if (low < maxIndex && lineYOffsetWidgetI (low + 1) <= y) + if (low < maxIndex && lineYOffsetWidgetIAllocation (low + 1, &alloc) <= y) low++; /* @@ -1340,12 +1542,13 @@ int Textblock::findLineIndex (int y) */ int Textblock::findLineOfWord (int wordIndex) { - int high = lines->size () - 1, index, low = 0; - - // TODO regard also not-yet-existing lines? - if (wordIndex < 0 || wordIndex >= words->size ()) + if (wordIndex < 0 || wordIndex >= words->size () || + // Also regard not-yet-existing lines. + lines->size () <= 0 || wordIndex > lines->getLastRef()->lastWord) return -1; + int high = lines->size () - 1, index, low = 0; + while (true) { index = (low + high) / 2; if (wordIndex >= lines->getRef(index)->firstWord) { @@ -1396,14 +1599,14 @@ Textblock::Word *Textblock::findWord (int x, int y, bool *inSpace) *inSpace = false; - if ((lineIndex = findLineIndex (y)) >= lines->size ()) + if ((lineIndex = findLineIndexWhenAllocated (y)) >= lines->size ()) return NULL; line = lines->getRef (lineIndex); yWidgetBase = lineYOffsetWidget (line) + line->boxAscent; if (yWidgetBase + line->boxDescent <= y) return NULL; - xCursor = lineXOffsetWidget (line); + xCursor = line->offsetCompleteWidget; for (wordIndex = line->firstWord; wordIndex <= line->lastWord;wordIndex++) { word = words->getRef (wordIndex); lastXCursor = xCursor; @@ -1438,9 +1641,17 @@ void Textblock::draw (core::View *view, core::Rectangle *area) int lineIndex; Line *line; - drawWidgetBox (view, area, false); + // Instead of drawWidgetBox, use drawBox to include verticalOffset. + if (getParent() == NULL) { + // The toplevel (parent == NULL) widget is a special case, which + // we leave to drawWidgetBox; verticalOffset will here always 0. + assert (verticalOffset == 0); + drawWidgetBox (view, area, false); + } else + drawBox (view, getStyle(), area, 0, verticalOffset, allocation.width, + getHeight() - verticalOffset, false); - lineIndex = findLineIndex (area->y); + lineIndex = findLineIndexWhenAllocated (area->y); for (; lineIndex < lines->size (); lineIndex++) { line = lines->getRef (lineIndex); @@ -1449,6 +1660,9 @@ void Textblock::draw (core::View *view, core::Rectangle *area) drawLine (line, view, area); } + + if(outOfFlowMgr) + outOfFlowMgr->draw(view, area); } /** @@ -1457,11 +1671,18 @@ void Textblock::draw (core::View *view, core::Rectangle *area) Textblock::Word *Textblock::addWord (int width, int ascent, int descent, short flags, core::style::Style *style) { + DBG_OBJ_MSGF ("construct.word", 0, "<b>addWord</b> (%d * (%d + %d), %d, %p)", + width, ascent, descent, flags, style); + DBG_OBJ_MSG_START (); + words->increase (); + DBG_OBJ_SET_NUM ("words.size", words->size ()); int wordNo = words->size () - 1; initWord (wordNo); fillWord (wordNo, width, ascent, descent, flags, style); - return words->getRef (wordNo);; + + DBG_OBJ_MSG_END (); + return words->getRef (wordNo); } /** @@ -1476,6 +1697,21 @@ void Textblock::initWord (int wordNo) word->spaceImgRenderer = NULL; } +void Textblock::cleanupWord (int wordNo) +{ + Word *word = words->getRef (wordNo); + + if (word->content.type == core::Content::WIDGET_IN_FLOW) + delete word->content.widget; + /** \todo Widget references? What about texts? */ + + removeWordImgRenderer (wordNo); + removeSpaceImgRenderer (wordNo); + + word->style->unref (); + word->spaceStyle->unref (); +} + void Textblock::removeWordImgRenderer (int wordNo) { Word *word = words->getRef (wordNo); @@ -1936,6 +2172,16 @@ void Textblock::addText0 (const char *text, size_t len, short flags, word->content.type = core::Content::TEXT; word->content.text = layout->textZone->strndup(text, len); + DBG_OBJ_ARRATTRSET_SYM ("words", words->size () - 1, "type", "TEXT"); + DBG_OBJ_ARRATTRSET_STR ("words", words->size () - 1, + "text/widget/breakSpace", word->content.text); + + // The following debug message may be useful to identify the + // different textblocks. + + //if (words->size() == 1) + // printf ("[%p] first word: '%s'\n", this, text); + processWord (words->size () - 1); } @@ -1944,28 +2190,62 @@ void Textblock::addText0 (const char *text, size_t len, short flags, */ 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 Textblock 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; - PRINTF ("%p becomes child of %p\n", widget, this); - - widget->setParent (this); widget->setStyle (style); - calcWidgetSize (widget, &size); - word = addWord (size.width, size.ascent, size.descent, 0, style); + PRINTF ("adding the %s %p to %p (word %d) ...\n", + widget->getClassName(), widget, this, words->size()); - word->content.type = core::Content::WIDGET; - word->content.widget = widget; + if (containingBlock->outOfFlowMgr == NULL) { + containingBlock->outOfFlowMgr = new OutOfFlowMgr (containingBlock); + DBG_OBJ_ASSOC (containingBlock, containingBlock->outOfFlowMgr); + } + + if (OutOfFlowMgr::isWidgetHandledByOOFM (widget)) { + PRINTF (" -> out of flow.\n"); + + widget->setParent (containingBlock); + widget->setGenerator (this); + containingBlock->outOfFlowMgr->addWidgetOOF (widget, this, + words->size ()); + Word *word = addWord (0, 0, 0, 0, style); + word->content.type = core::Content::WIDGET_OOF_REF; + word->content.widget = widget; + + // After a out-of-flow reference, breaking is allowed. (This avoids some + // problems with breaking near float definitions.) + setBreakOption (word, style, 0, 0, false); - //DBG_OBJ_ARRSET_PTR (page, "words.%d.content.widget", words->size() - 1, - // word->content.widget); + DBG_OBJ_ARRATTRSET_SYM ("words", words->size () - 1, "type", + "WIDGET_OOF_REF"); + DBG_OBJ_ARRATTRSET_PTR ("words", words->size () - 1, + "text/widget/breakSpace", word->content.widget); + } else { + PRINTF (" -> within flow.\n"); + + widget->setParent (this); + + // TODO Replace (perhaps) later "textblock" by "OOF aware widget". + if (widget->instanceOf (Textblock::CLASS_ID)) + containingBlock->outOfFlowMgr->addWidgetInFlow ((Textblock*)widget, + this, words->size ()); + + core::Requisition size; + calcWidgetSize (widget, &size); + Word *word = addWord (size.width, size.ascent, size.descent, 0, style); + word->content.type = core::Content::WIDGET_IN_FLOW; + word->content.widget = widget; + + DBG_OBJ_ARRATTRSET_SYM ("words", words->size () - 1, "type", + "WIDGET_IN_FLOW"); + DBG_OBJ_ARRATTRSET_PTR ("words", words->size () - 1, + "text/widget/breakSpace", word->content.widget); + } processWord (words->size () - 1); //DBG_OBJ_SET_NUM (word->content.widget, "parent_ref", @@ -2033,7 +2313,7 @@ void Textblock::addSpace (core::style::Style *style) /** * Add a break option (see setBreakOption() for details). Used instead - * of addStyle for ideographic characters. + * of addSpace for ideographic characters. * * When "forceBreak" is true, a break is even possible within PRE etc. */ @@ -2067,8 +2347,10 @@ void Textblock::fillSpace (int wordNo, core::style::Style *style) // TODO: This line does not work: addBreakOption (word, style); - // Do not override a previously set break penalty. - if (!word->content.space) { + if (// Do not override a previously set break penalty: + !word->content.space && + // OOF references are considered specially, and must not have a space: + word->content.type != core::Content::WIDGET_OOF_REF) { setBreakOption (word, style, 0, 0, false); word->content.space = true; @@ -2140,7 +2422,7 @@ void Textblock::addParbreak (int space, core::style::Style *style) /* 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.) */ + also comment in sizeRequest.) */ if (words->size () == 0 || (hasListitemValue && words->size () == 1)) { /* This is a bit hackish: If a break is added as the @@ -2149,23 +2431,23 @@ void Textblock::addParbreak (int space, core::style::Style *style) 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. */ - Widget *widget; - - /* Find the widget where to adjust the breakSpace. */ - for (widget = this; - widget->getParent() && - widget->getParent()->instanceOf (Textblock::CLASS_ID); + /* Find the widget where to adjust the breakSpace. (Only + consider normal flow, no floats etc.) */ + for (Widget *widget = this; + widget->getParent() != NULL && + widget->getParent()->instanceOf (Textblock::CLASS_ID) && + !OutOfFlowMgr::isRefOutOfFlow (widget->parentRef); widget = widget->getParent ()) { Textblock *textblock2 = (Textblock*)widget->getParent (); int index = textblock2->hasListitemValue ? 1 : 0; bool isfirst = (textblock2->words->getRef(index)->content.type - == core::Content::WIDGET + == core::Content::WIDGET_IN_FLOW && textblock2->words->getRef(index)->content.widget == widget); if (!isfirst) { - /* The page we searched for has been found. */ + /* The text block we searched for has been found. */ Word *word2; - int lineno = widget->parentRef; + int lineno = OutOfFlowMgr::getLineNoFromRef (widget->parentRef); if (lineno > 0 && (word2 = @@ -2174,7 +2456,8 @@ void Textblock::addParbreak (int space, core::style::Style *style) word2->content.type == core::Content::BREAK) { if (word2->content.breakSpace < space) { word2->content.breakSpace = space; - textblock2->queueResize (lineno, false); + textblock2->queueResize + (OutOfFlowMgr::createRefNormalFlow (lineno), false); textblock2->mustQueueResize = false; } } @@ -2182,6 +2465,7 @@ void Textblock::addParbreak (int space, core::style::Style *style) } /* Otherwise continue to examine parents. */ } + /* Return in any case. */ return; } @@ -2205,6 +2489,12 @@ void Textblock::addParbreak (int space, core::style::Style *style) word->content.type = core::Content::BREAK; word->badnessAndPenalty.setPenalty (PENALTY_FORCE_BREAK); word->content.breakSpace = space; + + DBG_OBJ_ARRATTRSET_SYM ("words", words->size () - 1, "type", "BREAK"); + DBG_OBJ_ARRATTRSET_NUM ("words", words->size () - 1, + "text/widget/breakSpace", word->content.breakSpace); + + breakAdded (); processWord (words->size () - 1); } @@ -2230,9 +2520,41 @@ void Textblock::addLinebreak (core::style::Style *style) word->content.type = core::Content::BREAK; word->badnessAndPenalty.setPenalty (PENALTY_FORCE_BREAK); word->content.breakSpace = 0; + + DBG_OBJ_ARRATTRSET_SYM ("words", words->size () - 1, "type", "BREAK"); + DBG_OBJ_ARRATTRSET_NUM ("words", words->size () - 1, + "text/widget/breakSpace", word->content.breakSpace); + + breakAdded (); processWord (words->size () - 1); } +/** + * Called directly after a (line or paragraph) break has been added. + */ +void Textblock::breakAdded () +{ + assert (words->size () >= 1); + assert (words->getRef(words->size () - 1)->content.type + == core::Content::BREAK); + + // Any space before is removed. It is not used; on the other hand, + // this snippet (an example from a real-world debugging session) + // would cause problems: + // + // <input style="width: 100%" .../> + // <button ...>...</button> + // + // (Notice the space between <input> and <button>, and also that + // the HTML parser will insert a BREAK between them.) The <input> + // would be given the available width ("width: 100%"), but the + // actual width (Word::totalWidth) would include the space, so that + // the width of the line is larger than the available width. + + if (words->size () >= 2) + words->getRef(words->size () - 2)->origSpace = + words->getRef(words->size () - 2)->effSpace = 0; +} /** * \brief Search recursively through widget. @@ -2242,6 +2564,10 @@ void Textblock::addLinebreak (core::style::Style *style) */ core::Widget *Textblock::getWidgetAtPoint(int x, int y, int level) { + //printf ("%*s-> examining the %s %p (%d, %d, %d x (%d + %d))\n", + // 3 * level, "", getClassName (), this, allocation.x, allocation.y, + // allocation.width, allocation.ascent, allocation.descent); + int lineIndex, wordIndex; Line *line; @@ -2252,7 +2578,15 @@ core::Widget *Textblock::getWidgetAtPoint(int x, int y, int level) return NULL; } - lineIndex = findLineIndex (y - allocation.y); + // First, search for widgets out of flow, notably floats, since + // there are cases where they overlap child textblocks. Should + // later be refined using z-index. + Widget *oofWidget = + outOfFlowMgr ? outOfFlowMgr->getWidgetAtPoint (x, y, level) : NULL; + if (oofWidget) + return oofWidget; + + lineIndex = findLineIndexWhenAllocated (y - allocation.y); if (lineIndex < 0 || lineIndex >= lines->size ()) { return this; @@ -2263,7 +2597,7 @@ core::Widget *Textblock::getWidgetAtPoint(int x, int y, int level) for (wordIndex = line->firstWord; wordIndex <= line->lastWord;wordIndex++) { Word *word = words->getRef (wordIndex); - if (word->content.type == core::Content::WIDGET) { + if (word->content.type == core::Content::WIDGET_IN_FLOW) { core::Widget * childAtPoint; childAtPoint = word->content.widget->getWidgetAtPoint (x, y, level + 1); @@ -2303,12 +2637,14 @@ void Textblock::handOverBreak (core::style::Style *style) */ void Textblock::flush () { - PRINTF ("[%p] FLUSH => %s (parentRef = %d)\n", - this, mustQueueResize ? "true" : "false", parentRef); - if (mustQueueResize) { + DBG_OBJ_MSG ("resize", 0, "<b>flush</b> (mustQueueResize set)"); + DBG_OBJ_MSG_START (); + queueResize (-1, true); mustQueueResize = false; + + DBG_OBJ_MSG_END (); } } @@ -2344,7 +2680,7 @@ void Textblock::changeLinkColor (int link, int newColor) old_style->unref(); break; } - case core::Content::WIDGET: + case core::Content::WIDGET_IN_FLOW: { core::Widget *widget = word->content.widget; styleAttrs = *widget->getStyle(); styleAttrs.color = core::style::Color::create (layout, @@ -2396,4 +2732,315 @@ void Textblock::queueDrawRange (int index1, int index2) } } +void Textblock::setVerticalOffset (int verticalOffset) +{ + if (this->verticalOffset != verticalOffset) { + this->verticalOffset = verticalOffset; + mustQueueResize = true; + queueDraw (); // Could perhaps be optimized. + } +} + +/** + * Called by dw::OutOfFlowMgr when the border has changed due to a + * float (or some floats). + * + * "y", which given in widget coordinates, denotes the minimal + * position (when more than one float caused this), "vloat" the + * floating widget belonging to "y". + */ +void Textblock::borderChanged (int y, Widget *vloat) +{ + DBG_OBJ_MSGF ("resize", 0, "<b>borderChanged</b> (%d, %p)", y, vloat); + DBG_OBJ_MSG_START (); + + int lineIndex = findLineIndex (y); + DBG_OBJ_MSGF ("resize", 1, "Line index: %d (of %d).", + lineIndex, lines->size ()); + + // Nothing to do at all, when lineIndex >= lines->size (), + // i. e. the change is below the bottom of this widget. + if (lineIndex < lines->size ()) { + int wrapLineIndex; + if (lineIndex < 0) + // Rewrap all. + wrapLineIndex = 0; + else + wrapLineIndex = lineIndex; + + DBG_OBJ_MSGF ("resize", 1, "Rewrapping from line %d (of %d).", + wrapLineIndex, lines->size ()); + + if (vloat->getGenerator() == this) { + bool found = false; + // Sometimes, the respective word is not yet part of a + // line. Nothing to do, but because of the assertion below + // (and also for performace reasons) this should be + // considered. TODO: Integrate this below. + for (int wordIndex = + lines->size() > 0 ? lines->getLastRef()->lastWord + 1 : 0; + !found && wordIndex < words->size(); wordIndex++) { + Word *word = words->getRef (wordIndex); + if (word->content.type == core::Content::WIDGET_OOF_REF && + word->content.widget == vloat) + found = true; + } + + // We search for the line of the float reference. There are + // two cases when this is not the line corresponsing to y: + // + // (1) When the float was moved down, due to collisions with + // other floats: in this case, the line number gets + // smaller (since the float reference is before). + // + // (2) In some cases, the line number may become larger, due + // to the per-line optimization of the words: initially, + // lines->size() - 1 is assigned, but it may happen that + // the float reference is put into another line. + // + // Only in the first case, a correction is neccessary, but a + // test for the second case is useful. (TODO: I've forgotten + // why a correction is neccessary.) + // + // Searched is done in the following order: + // + // - wrapLineIndex, + // - wrapLineIndex - 1, + // - wrapLineIndex + 1, + // - wrapLineIndex - 2, + // - wrapLineIndex + 2, + // + // etc. until either the float reference has been found or + // all lines have been searched (the latter triggers an + // abortion). + + bool exceedsBeginning = false, exceedsEnd = false; + for (int i = 0; !found; i++) { + bool exceeds; + int lineIndex2; + if (i % 2 == 0) { + // even: +0, +1, +2, ... + lineIndex2 = wrapLineIndex + i / 2; + if (i > 0) + exceeds = exceedsEnd = lineIndex2 >= lines->size (); + else + exceeds = exceedsEnd = false; + } else { + // odd: -1, -2, ... + lineIndex2 = wrapLineIndex - (i + 1) / 2; + exceeds = exceedsBeginning = lineIndex2 < 0; + } + + if (exceedsBeginning && exceedsEnd) + break; + + if (!exceeds) { + Line *line = lines->getRef (lineIndex2); + for (int wordIndex = line->firstWord; + !found && wordIndex <= line->lastWord; wordIndex++) { + Word *word = words->getRef (wordIndex); + if (word->content.type == core::Content::WIDGET_OOF_REF && + word->content.widget == vloat) { + found = true; + // Correct only by smaller values (case (1) above): + wrapLineIndex = misc::min (wrapLineIndex, lineIndex2); + } + } + } + } + + if (!found) + printBorderChangedErrorAndAbort (y, vloat, wrapLineIndex); + } + + DBG_OBJ_MSGF ("resize", 1, "Corrected to line %d.", wrapLineIndex); + + queueResize (OutOfFlowMgr::createRefNormalFlow (wrapLineIndex), true); + + // Notice that the line no. wrapLineIndex may not exist yet. + if (wrapLineIndex == 0) + lastWordDrawn = misc::min (lastWordDrawn, -1); + else + lastWordDrawn = misc::min (lastWordDrawn, + lines->getRef(wrapLineIndex - 1)->lastWord); + } + + DBG_OBJ_MSG_END (); +} + +void Textblock::printBorderChangedErrorAndAbort (int y, Widget *vloat, + int wrapLineIndex) +{ + // TODO Should be adjusted to recent changes in borderChanged(). + + printf ("*** This call failed! ***\n"); + printf ("[%p] borderChanged (%d, %p (%s))\n", + this, y, vloat, vloat->getClassName()); + printf (" Initial wrapLineIndex = line %d (of %d lines). " + "Has to be corrected.\n", wrapLineIndex, lines->size ()); + + printf (" search non-existing line (from %d to %d):\n", + lines->size() > 0 ? lines->getLastRef()->lastWord + 1 : 0, + words->size() - 1); + for (int wordIndex = + lines->size() > 0 ? lines->getLastRef()->lastWord + 1 : 0; + wordIndex < words->size(); wordIndex++) { + printf (" word %d: ", wordIndex); + printWordShort (words->getRef (wordIndex)); + printf ("\n"); + } + + for (int lineIndex2 = wrapLineIndex; lineIndex2 >= 0; + lineIndex2--) { + Line *line = lines->getRef (lineIndex2); + printf (" searching existing line %d (from %d to %d):\n", + lineIndex2, line->firstWord, line->lastWord); + for (int wordIndex = line->firstWord; + wordIndex <= line->lastWord; wordIndex++) { + printf (" word %d: ", wordIndex); + printWordShort (words->getRef (wordIndex)); + printf ("\n"); + } + } + + bool found2 = false; + for (int lineIndex2 = wrapLineIndex + 1; + !found2 && lineIndex2 < lines->size (); lineIndex2++) { + Line *line = lines->getRef (lineIndex2); + printf (" could have searched existing line %d " + "(from %d to %d):\n", + lineIndex2, line->firstWord, line->lastWord); + for (int wordIndex = line->firstWord; + !found2 && wordIndex <= line->lastWord; wordIndex++) { + Word *word = words->getRef (wordIndex); + if (word->content.type == core::Content::WIDGET_OOF_REF && + word->content.widget == vloat) { + found2 = true; + printf (" word %d: ", wordIndex); + printWordShort (word); + printf ("\n"); + } + } + } + + lout::misc::assertNotReached (); +} + +Textblock *Textblock::getTextblockForLine (Line *line) +{ + return getTextblockForLine (line->firstWord, line->lastWord); +} + +Textblock *Textblock::getTextblockForLine (int lineNo) +{ + int firstWord = lineNo == 0 ? 0 :lines->getRef(lineNo - 1)->lastWord + 1; + int lastWord = lineNo < lines->size() ? + lines->getRef(lineNo)->lastWord : words->size() - 1; + return getTextblockForLine (firstWord, lastWord); +} + +Textblock *Textblock::getTextblockForLine (int firstWord, int lastWord) +{ + if (firstWord < words->size ()) { + //printf ("[%p] GET_TEXTBLOCK_FOR_LINE (%d, %d)\n", + // this, firstWord, lastWord); + + // A textblock is always between two line breaks, and so the + // first word of the line. + Word *word = words->getRef (firstWord); + + if (word->content.type == core::Content::WIDGET_IN_FLOW && + word->content.widget->instanceOf (Textblock::CLASS_ID)) { + //printf (" word %d: ", firstWord); + //printWordShort (word); + //printf ("\n"); + + return (Textblock*)word->content.widget; + } + } + + //printf (" nothing\n"); + return NULL; +} + +/** + * Includes margin, border, and padding. + */ +int Textblock::yOffsetOfPossiblyMissingLine (int lineNo) +{ + DBG_OBJ_MSGF ("line.yoffset", 0, + "<b>yOffsetOfPossiblyMissingLine</b> (%d <i>of %d</i>)", + lineNo, lines->size()); + DBG_OBJ_MSG_START (); + + int result; + + if (lineNo == 0) { + result = verticalOffset + getStyle()->boxOffsetY(); + DBG_OBJ_MSGF ("line.yoffset", 1, "first line: %d + %d = %d", + verticalOffset, getStyle()->boxOffsetY(), result); + } else { + Line *prevLine = lines->getRef (lineNo - 1); + result = verticalOffset + getStyle()->boxOffsetY() + + prevLine->top + prevLine->boxAscent + prevLine->boxDescent + + prevLine->breakSpace; + DBG_OBJ_MSGF ("line.yoffset", 1, + "other line: %d + %d + %d + (%d + %d) + %d = %d", + verticalOffset, getStyle()->boxOffsetY(), + prevLine->top, prevLine->boxAscent, prevLine->boxDescent, + prevLine->breakSpace, result); + } + + DBG_OBJ_MSG_END (); + + return result; +} + +int Textblock::heightOfPossiblyMissingLine (int lineNo) +{ + DBG_OBJ_MSGF ("line.height", 0, + "<b>heightOfPossiblyMissingLine</b> (%d <i>of %d</i>)", + lineNo, lines->size()); + DBG_OBJ_MSG_START (); + + int result; + + if (lineNo < lines->size()) { + // An existing line. + + Line *line = lines->getRef (lineNo); + + // This is sometimes called within addLine, so that the + // condition above is true, but line->boxAscent and + // line->boxDescent are not set appropriately. We have to + // accumulate the heights then. + + if (line->finished) { + DBG_OBJ_MSGF ("line.height", 1, + "exists and is finished; height = %d + %d = %d", + line->boxAscent, line->boxDescent, + line->boxAscent + line->boxDescent); + result = line->boxAscent + line->boxDescent; + } else { + DBG_OBJ_MSG ("line.height", 1, "exist but is not finished"); + result = misc::max (1, newLineAscent + newLineDescent); + } + } else if (lineNo == lines->size()) { + // The line to be constructed: some words exist, but not the + // line. Accumulate the word heights. + + // Old comment: Furthermore, this is in some cases incomplete: + // see doc/dw-out-of-flow.doc. -- Still the case? + + DBG_OBJ_MSG ("line.height", 1, "does not exist"); + result = misc::max (1, newLineAscent + newLineDescent); + } else + result = 1; + + DBG_OBJ_MSGF ("line.height", 0, "result = %d", result); + DBG_OBJ_MSG_END (); + + return result; +} + } // namespace dw diff --git a/dw/textblock.hh b/dw/textblock.hh index b85937ba..f792b779 100644 --- a/dw/textblock.hh +++ b/dw/textblock.hh @@ -4,13 +4,18 @@ #include <limits.h> #include "core.hh" +#include "outofflowmgr.hh" #include "../lout/misc.hh" -// These were used when improved line breaking and hyphenation were -// implemented. Should be cleaned up; perhaps reactivate RTFL again. +// These were used when improved line breaking and hyphenation were implemented. +// Should be, bit by bit, replaced by RTFL (see ../lout/debug.hh). #define PRINTF(fmt, ...) #define PUTCHAR(ch) +#ifdef DBG_RTFL +# define DEBUG +#endif + namespace dw { /** @@ -18,10 +23,11 @@ namespace dw { * of paragraphs. * * <div style="border: 2px solid #ffff00; margin-top: 0.5em; - * margin-bottom: 0.5em; padding: 0.5em 1em; - * background-color: #ffffe0"><b>Info:</b> The recent changes (line - * breaking and hyphenation) have not yet been incorporated into this - * documentation. See \ref dw-line-breaking.</div> + * margin-bottom: 0.5em; padding: 0.5em 1em; background-color: + * #ffffe0"><b>Info:</b> The recent changes (line breaking and + * hyphenation on one hand, floats on the other hand) have not yet + * been incorporated into this documentation. See \ref + * dw-line-breaking and \ref dw-out-of-flow.</div> * * <h3>Signals</h3> * @@ -47,9 +53,9 @@ namespace dw { * * \image html dw-textblock-collapsing-spaces-1-1.png * - * are combined like this: + * are combined like this: * - * \image html dw-textblock-collapsing-spaces-1-2.png + * \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 @@ -73,9 +79,9 @@ namespace dw { * 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. + * dw::Textblock::addParBreak::handOverBreak must be called for + * the \em inner widget. The HTML parser does this in + * Html_eventually_pop_dw. * </ul> * * @@ -218,6 +224,7 @@ private: bool lineCanBeBroken (int penaltyIndex); int compareTo (int penaltyIndex, BadnessAndPenalty *other); + void intoStringBuffer(lout::misc::StringBuffer *sb); void print (); }; @@ -236,6 +243,9 @@ private: static const char *hyphenDrawChar; + Textblock *containingBlock; + OutOfFlowMgr *outOfFlowMgr; + protected: /** * \brief Implementation used for words. @@ -308,10 +318,21 @@ protected: int firstWord; /* first word's index in word vector */ int lastWord; /* last word's index in word vector */ - /* "top" is always relative to the top of the first line, i.e. - * page->lines[0].top is always 0. */ - int top, boxAscent, boxDescent, contentAscent, contentDescent, - breakSpace, leftOffset; + + int top; /* "top" is always relative to the top + of the first line, i.e. + page->lines[0].top is always 0. */ + int boxAscent; /* Maximum of all ascents of the words + in this line. This is the actual + ascent of the line. */ + int boxDescent; /* Maximum of all decents of the words + in this line. This is the actual + descent of the line. */ + int contentAscent; /* ??? */ + int contentDescent; /* ??? */ + int breakSpace; /* Space between this line and the next one. */ + int leftOffset; /* ??? */ + int offsetCompleteWidget; /* ??? */ /* This is similar to descent, but includes the bottom margins of the * widgets within this line. */ @@ -323,6 +344,20 @@ protected: * changed line breaking), the values were accumulated up to the * last line, not this line.*/ int maxLineWidth; + + /* Set to false at the beginning of addLine(), and to true at + * the end. Should be checked by some methods which are called + * by addLine(). */ + bool finished; + + /* The word index of the last OOF reference (most importantly: + * float) whic is positioned before this line, or -1, if there + * is no OOF reference positioned before. + * + * **Important:** These references may still be part of this or + * even a following line, when positioned before (this is the + * reason this attribute exists); see \ref dw-out-of-flow. */ + int lastOofRefPositionedBeforeThisLine; }; struct Word @@ -409,13 +444,14 @@ protected: class TextblockIterator: public core::Iterator { private: + bool oofm; int index; public: TextblockIterator (Textblock *textblock, core::Content::Type mask, bool atEnd); TextblockIterator (Textblock *textblock, core::Content::Type mask, - int index); + bool oofm, int index); lout::object::Object *clone(); int compareTo(lout::object::Comparable *other); @@ -425,6 +461,7 @@ protected: 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); + void print (); }; friend class TextblockIterator; @@ -479,7 +516,31 @@ protected: /* These values are set by set_... */ int availWidth, availAscent, availDescent; - int wrapRefLines, wrapRefParagraphs; /* [0 based] */ + // Additional vertical offset, used for the "clear" attribute. + int verticalOffset; + + int wrapRefLines, wrapRefParagraphs; /* 0-based. Important: Both + are the line numbers, not + the value stored in + parentRef. */ + + // These four values are calculated by containingBlock->outOfFlowMgr + // (when defined; otherwise, they are false, or 0, respectively), for + // the newly constructed line, only when needed: when a new line is + // added, or if something in the line currently constucted has + // changed, e. g. a float has been added. + + bool newLineHasFloatLeft, newLineHasFloatRight; + int newLineLeftBorder, newLineRightBorder; /* As returned by + outOfFlowMgr->get...Border, + or 0, if outOfFlowMgr + is NULL */ + + // Ascent and descent of the newly constructed line, i. e. maximum + // of all words ascent/descent since the end of the last line. Not + // neccessary the ascent and descent of the newly added line, since + // not all words are added to it. + int newLineAscent, newLineDescent; lout::misc::SimpleVector <Line> *lines; lout::misc::SimpleVector <Paragraph> *paragraphs; @@ -492,16 +553,19 @@ protected: int hoverLink; /* The link under the mouse pointer */ - void queueDrawRange (int index1, int index2); void getWordExtremes (Word *word, core::Extremes *extremes); void justifyLine (Line *line, int diff); - Line *addLine (int firstWord, int lastWord, bool temporary); + Line *addLine (int firstWord, int lastWord, int newLastOofPos, + bool temporary); void calcWidgetSize (core::Widget *widget, core::Requisition *size); void rewrap (); void fillParagraphs (); + void initNewLine (); + void calcBorders (int lastOofRef, int height); void showMissingLines (); void removeTemporaryLines (); + void setVerticalOffset (int verticalOffset); void decorateText (core::View *view, core::style::Style *style, core::style::Color::Shading shading, @@ -520,13 +584,18 @@ protected: int xWidget, int yWidgetBase); void drawLine (Line *line, core::View *view, core::Rectangle *area); int findLineIndex (int y); + int findLineIndexWhenNotAllocated (int y); + int findLineIndexWhenAllocated (int y); + int findLineIndex (int y, int ascent); int findLineOfWord (int wordIndex); int findParagraphOfWord (int wordIndex); Word *findWord (int x, int y, bool *inSpace); Word *addWord (int width, int ascent, int descent, short flags, core::style::Style *style); + void breakAdded (); void initWord (int wordNo); + void cleanupWord (int wordNo); void removeWordImgRenderer (int wordNo); void setWordImgRenderer (int wordNo); void removeSpaceImgRenderer (int wordNo); @@ -543,25 +612,17 @@ protected: core::Requisition *size, bool isStart, bool isEnd); /** - * \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.). + * Of nested text blocks, only the most inner one must regard the + * borders of floats. */ - inline int lineXOffsetContents (Line *line) + inline bool mustBorderBeRegarded (Line *line) { - return innerPadding + line->leftOffset + - (line == lines->getFirstRef() ? line1OffsetEff : 0); + return getTextblockForLine (line) == NULL; } - /** - * \brief Like lineXOffset, but relative to the allocation (i.e. - * including border etc.). - */ - inline int lineXOffsetWidget (Line *line) + inline bool mustBorderBeRegarded (int lineNo) { - return lineXOffsetContents (line) + getStyle()->boxOffsetX (); + return getTextblockForLine (lineNo) == NULL; } inline int lineYOffsetWidgetAllocation (Line *line, @@ -581,7 +642,7 @@ protected: inline int lineYOffsetCanvasAllocation (Line *line, core::Allocation *allocation) { - return allocation->y + lineYOffsetWidgetAllocation(line, allocation); + return allocation->y + lineYOffsetWidgetAllocation (line, allocation); } /** @@ -597,6 +658,13 @@ protected: return lineYOffsetWidget (lines->getRef (lineIndex)); } + inline int lineYOffsetWidgetIAllocation (int lineIndex, + core::Allocation *allocation) + { + return lineYOffsetWidgetAllocation (lines->getRef (lineIndex), + allocation); + } + inline int lineYOffsetCanvasI (int lineIndex) { return lineYOffsetCanvas (lines->getRef (lineIndex)); @@ -606,12 +674,26 @@ protected: { if (lines->size() == 0) return 0; - else - return - (words->getRef(lines->getLastRef()->lastWord)->flags & - (Word::DIV_CHAR_AT_EOL | Word::PERM_DIV_CHAR)) ? 1 : 0; + else { + Line *line = lines->getLastRef(); + if (line->firstWord <= line->lastWord) + return + (words->getRef(line->lastWord)->flags & + (Word::DIV_CHAR_AT_EOL | Word::PERM_DIV_CHAR)) ? 1 : 0; + else + // empty line + return 0; + } } + Textblock *getTextblockForLine (Line *line); + Textblock *getTextblockForLine (int lineNo); + Textblock *getTextblockForLine (int firstWord, int lastWord); + void printBorderChangedErrorAndAbort (int y, Widget *vloat, + int wrapLineIndex); + int yOffsetOfPossiblyMissingLine (int lineNo); + int heightOfPossiblyMissingLine (int lineNo); + bool sendSelectionEvent (core::SelectionState::EventType eventType, core::MousePositionEvent *event); @@ -619,11 +701,25 @@ protected: int *maxOfMinWidth, int *sumOfMaxWidth); void processWord (int wordIndex); virtual bool wordWrap (int wordIndex, bool wrapAll); + bool wrapWordInFlow (int wordIndex, bool wrapAll); + void balanceBreakPosAndHeight (int wordIndex, int firstIndex, + int *searchUntil, bool tempNewLine, + int penaltyIndex, bool borderIsCalculated, + bool *thereWillBeMoreSpace, bool wrapAll, + bool *wordListChanged, int *wordIndexEnd, + int lastFloatPos, bool regardBorder, + int *height, int *breakPos); + int searchBreakPos (int wordIndex, int firstIndex, int *searchUntil, + bool tempNewLine, int penaltyIndex, + bool thereWillBeMoreSpace, bool wrapAll, + bool *wordListChanged, int *wordIndexEnd); int searchMinBap (int firstWord, int lastWordm, int penaltyIndex, - bool correctAtEnd); + bool thereWillBeMoreSpace, bool correctAtEnd); int considerHyphenation (int firstIndex, int breakPos); bool isHyphenationCandidate (Word *word); - + int calcLinePartHeight (int firstWord, int lastWord); + + void handleWordExtremes (int wordIndex); void correctLastWordExtremes (); @@ -632,6 +728,7 @@ protected: static int getLineShrinkability(Word *lastWord); static int getLineStretchability(Word *lastWord); int hyphenateWord (int wordIndex); + void moveWordIndices (int wordIndex, int num); void accumulateWordForLine (int lineIndex, int wordIndex); void accumulateWordData (int wordIndex); int calcAvailWidth (int lineIndex); @@ -645,6 +742,8 @@ protected: void markSizeChange (int ref); void markExtremesChange (int ref); + void notifySetAsTopLevel(); + void notifySetParent(); void setWidth (int width); void setAscent (int ascent); void setDescent (int descent); @@ -664,6 +763,7 @@ protected: core::style::Style *style, int numBreaks, int *breakPos, core::Requisition *wordSize); + static bool isContainingBlock (Widget *widget); public: static int CLASS_ID; @@ -699,8 +799,68 @@ public: void changeLinkColor (int link, int newColor); void changeWordStyle (int from, int to, core::style::Style *style, bool includeFirstSpace, bool includeLastSpace); + + void borderChanged (int y, core::Widget *vloat); + inline void oofSizeChanged (bool extremesChanged) + { queueResize (-1, extremesChanged); } + inline int getAvailWidth () { return availWidth; } + inline int getAvailAscent () { return availAscent; } + inline int getAvailDescent () { return availDescent; } }; +#define DBG_SET_WORD(n) \ + D_STMT_START { \ + switch (words->getRef(n)->content.type) { \ + case ::dw::core::Content::TEXT: \ + DBG_OBJ_ARRATTRSET_SYM ("words", n, "type", "TEXT"); \ + DBG_OBJ_ARRATTRSET_STR ("words", n, "text/widget/breakSpace", \ + words->getRef(n)->content.text); \ + break; \ + case ::dw::core::Content::WIDGET_IN_FLOW: \ + DBG_OBJ_ARRATTRSET_SYM ("words", n, "type", "WIDGET_IN_FLOW"); \ + DBG_OBJ_ARRATTRSET_PTR ("words", n, "text/widget/breakSpace", \ + words->getRef(n)->content.widget); \ + break; \ + case ::dw::core::Content::WIDGET_OOF_REF: \ + DBG_OBJ_ARRATTRSET_SYM ("words", n, "type", "WIDGET_OOF_REF"); \ + DBG_OBJ_ARRATTRSET_PTR ("words", n, "text/widget/breakSpace", \ + words->getRef(n)->content.widget); \ + break; \ + case ::dw::core::Content::BREAK: \ + DBG_OBJ_ARRATTRSET_SYM ("words", n, "type", "TEXT"); \ + DBG_OBJ_ARRATTRSET_NUM ("words", n, "text/widget/breakSpace", \ + words->getRef(n)->content.breakSpace); \ + break; \ + default: \ + DBG_OBJ_ARRATTRSET_SYM ("words", n, "type", "???"); \ + DBG_OBJ_ARRATTRSET_SYM ("words", n, "text/widget/breakSpace", "???"); \ + } \ + } D_STMT_END + +#define DBG_MSG_WORD(aspect, prio, prefix, n, suffix) \ + D_STMT_START { \ + switch (words->getRef(n)->content.type) { \ + case ::dw::core::Content::TEXT: \ + DBG_OBJ_MSGF (aspect, prio, prefix "TEXT / \"%s\"" suffix, \ + words->getRef(n)->content.text); \ + break; \ + case ::dw::core::Content::WIDGET_IN_FLOW: \ + DBG_OBJ_MSGF (aspect, prio, prefix "WIDGET_IN_FLOW / %p" suffix, \ + words->getRef(n)->content.widget); \ + break; \ + case ::dw::core::Content::WIDGET_OOF_REF: \ + DBG_OBJ_MSGF (aspect, prio, prefix "WIDGET_OOF_REF / %p" suffix, \ + words->getRef(n)->content.widget); \ + break; \ + case ::dw::core::Content::BREAK: \ + DBG_OBJ_MSGF (aspect, prio, prefix "BREAK / %d" suffix, \ + words->getRef(n)->content.breakSpace); \ + break; \ + default: \ + DBG_OBJ_MSG (aspect, prio, prefix "??? / ???"); \ + } \ + } D_STMT_END + } // namespace dw #endif // __DW_TEXTBLOCK_HH__ diff --git a/dw/textblock_iterator.cc b/dw/textblock_iterator.cc index 7531ecd5..c26b7f6e 100644 --- a/dw/textblock_iterator.cc +++ b/dw/textblock_iterator.cc @@ -35,20 +35,34 @@ Textblock::TextblockIterator::TextblockIterator (Textblock *textblock, bool atEnd): core::Iterator (textblock, mask, atEnd) { - index = atEnd ? textblock->words->size () : -1; + if (atEnd) { + if (textblock->outOfFlowMgr) { + oofm = true; + index = textblock->outOfFlowMgr->getNumWidgets(); + } else { + oofm = false; + index = textblock->words->size(); + } + } else { + oofm = false; + index = -1; + } + content.type = atEnd ? core::Content::END : core::Content::START; } Textblock::TextblockIterator::TextblockIterator (Textblock *textblock, core::Content::Type mask, - int index): + bool oofm, int index): core::Iterator (textblock, mask, false) { + this->oofm = oofm; this->index = index; + // TODO To be completely exact, oofm should be considered here. if (index < 0) content.type = core::Content::START; - else if (index >= textblock->words->size ()) + else if (index >= textblock->words->size()) content.type = core::Content::END; else content = textblock->words->getRef(index)->content; @@ -56,12 +70,20 @@ Textblock::TextblockIterator::TextblockIterator (Textblock *textblock, object::Object *Textblock::TextblockIterator::clone() { - return new TextblockIterator ((Textblock*)getWidget(), getMask(), index); + return + new TextblockIterator ((Textblock*)getWidget(), getMask(), oofm, index); } int Textblock::TextblockIterator::compareTo(object::Comparable *other) { - return index - ((TextblockIterator*)other)->index; + TextblockIterator *otherTI = (TextblockIterator*)other; + + if (oofm && !otherTI->oofm) + return +1; + else if (!oofm && otherTI->oofm) + return -1; + else + return index - otherTI->index; } bool Textblock::TextblockIterator::next () @@ -71,15 +93,52 @@ bool Textblock::TextblockIterator::next () if (content.type == core::Content::END) return false; + short type; + do { index++; - if (index >= textblock->words->size ()) { - content.type = core::Content::END; - return false; + + if (oofm) { + // Iterating over OOFM. + if (index >= textblock->outOfFlowMgr->getNumWidgets()) { + // End of OOFM list reached. + content.type = core::Content::END; + return false; + } + type = core::Content::WIDGET_OOF_CONT; + } else { + // Iterating over words list. + if (index < textblock->words->size ()) + // Still words left. + type = textblock->words->getRef(index)->content.type; + else { + // End of words list reached. + if (textblock->outOfFlowMgr) { + oofm = true; + index = 0; + if (textblock->outOfFlowMgr->getNumWidgets() > 0) + // Start with OOFM widgets. + type = core::Content::WIDGET_OOF_CONT; + else { + // No OOFM widgets (number is 0). + content.type = core::Content::END; + return false; + } + } else { + // No OOFM widgets (no OOFM agt all). + content.type = core::Content::END; + return false; + } + } } - } while ((textblock->words->getRef(index)->content.type & getMask()) == 0); + } while ((type & getMask()) == 0); + + if (oofm) { + content.type = core::Content::WIDGET_OOF_CONT; + content.widget = textblock->outOfFlowMgr->getWidget (index); + } else + content = textblock->words->getRef(index)->content; - content = textblock->words->getRef(index)->content; return true; } @@ -90,132 +149,188 @@ bool Textblock::TextblockIterator::prev () if (content.type == core::Content::START) return false; + short type; + do { index--; - if (index < 0) { - content.type = core::Content::START; - return false; + + if (oofm) { + // Iterating over OOFM. + if (index >= 0) + // Still widgets left. + type = core::Content::WIDGET_OOF_CONT; + else { + // Beginning of OOFM list reached. Continue with words. + oofm = false; + index = textblock->words->size() - 1; + if (index < 0) { + // There are no words. (Actually, this case should not + // happen: When there are OOF widgets, ther must be OOF + // references, or widgets in flow, which contain + // references. + content.type = core::Content::END; + return false; + } + type = textblock->words->getRef(index)->content.type; + } + } else { + // Iterating over words list. + if (index < 0) { + // Beginning of words list reached. + content.type = core::Content::START; + return false; + } + type = textblock->words->getRef(index)->content.type; } - } while ((textblock->words->getRef(index)->content.type & getMask()) == 0); + } while ((type & getMask()) == 0); + + if (oofm) { + content.type = core::Content::WIDGET_OOF_CONT; + content.type = false; + content.widget = textblock->outOfFlowMgr->getWidget (index); + } else + content = textblock->words->getRef(index)->content; - content = textblock->words->getRef(index)->content; return true; } void Textblock::TextblockIterator::highlight (int start, int end, core::HighlightLayer layer) { - Textblock *textblock = (Textblock*)getWidget(); - int index1 = index, index2 = index; - - int oldStartIndex = textblock->hlStart[layer].index; - int oldStartChar = textblock->hlStart[layer].nChar; - int oldEndIndex = textblock->hlEnd[layer].index; - int oldEndChar = textblock->hlEnd[layer].nChar; - - 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; + if (!oofm) { + Textblock *textblock = (Textblock*)getWidget(); + int index1 = index, index2 = index; + + int oldStartIndex = textblock->hlStart[layer].index; + int oldStartChar = textblock->hlStart[layer].nChar; + int oldEndIndex = textblock->hlEnd[layer].index; + int oldEndChar = textblock->hlEnd[layer].nChar; + + 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; + } + + if (oldStartIndex != textblock->hlStart[layer].index || + oldStartChar != textblock->hlStart[layer].nChar || + oldEndIndex != textblock->hlEnd[layer].index || + oldEndChar != textblock->hlEnd[layer].nChar) + textblock->queueDrawRange (index1, index2); } - if (oldStartIndex != textblock->hlStart[layer].index || - oldStartChar != textblock->hlStart[layer].nChar || - oldEndIndex != textblock->hlEnd[layer].index || - oldEndChar != textblock->hlEnd[layer].nChar) - textblock->queueDrawRange (index1, index2); + // TODO What about OOF widgets? } 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; - - int oldStartIndex = textblock->hlStart[layer].index; - int oldStartChar = textblock->hlStart[layer].nChar; - int oldEndIndex = textblock->hlEnd[layer].index; - int oldEndChar = textblock->hlEnd[layer].nChar; - - 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; + if (!oofm) { + Textblock *textblock = (Textblock*)getWidget(); + int index1 = index, index2 = index; + + if (textblock->hlStart[layer].index > textblock->hlEnd[layer].index) + return; + + int oldStartIndex = textblock->hlStart[layer].index; + int oldStartChar = textblock->hlStart[layer].nChar; + int oldEndIndex = textblock->hlEnd[layer].index; + int oldEndChar = textblock->hlEnd[layer].nChar; + + 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; + } + + if (oldStartIndex != textblock->hlStart[layer].index || + oldStartChar != textblock->hlStart[layer].nChar || + oldEndIndex != textblock->hlEnd[layer].index || + oldEndChar != textblock->hlEnd[layer].nChar) + textblock->queueDrawRange (index1, index2); } - if (oldStartIndex != textblock->hlStart[layer].index || - oldStartChar != textblock->hlStart[layer].nChar || - oldEndIndex != textblock->hlEnd[layer].index || - oldEndChar != textblock->hlEnd[layer].nChar) - textblock->queueDrawRange (index1, index2); + // TODO What about OOF widgets? } 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++) { - Word *w = textblock->words->getRef(i); - allocation->x += w->size.width + w->effSpace; - } - if (start > 0 && word->content.type == core::Content::TEXT) { - allocation->x += textblock->textWidth (word->content.text, 0, start, - word->style, - word->flags & Word::WORD_START, - (word->flags & Word::WORD_END) - && word->content.text[start] == 0); - } - allocation->y = textblock->lineYOffsetCanvas (line) + line->boxAscent - - word->size.ascent; - - allocation->width = word->size.width; - if (word->content.type == core::Content::TEXT) { - int wordEnd = strlen(word->content.text); - - if (start > 0 || end < wordEnd) { - end = misc::min(end, wordEnd); /* end could be INT_MAX */ - allocation->width = - textblock->textWidth (word->content.text, start, end - start, - word->style, - (word->flags & Word::WORD_START) - && start == 0, - (word->flags & Word::WORD_END) - && word->content.text[end] == 0); + if (oofm) { + // TODO Consider start and end? + *allocation = + *(textblock->outOfFlowMgr->getWidget(index)->getAllocation()); + } else { + int lineIndex = textblock->findLineOfWord (index); + Line *line = textblock->lines->getRef (lineIndex); + Word *word = textblock->words->getRef (index); + + allocation->x = + textblock->allocation.x + line->offsetCompleteWidget; + + for (int i = line->firstWord; i < index; i++) { + Word *w = textblock->words->getRef(i); + allocation->x += w->size.width + w->effSpace; + } + if (start > 0 && word->content.type == core::Content::TEXT) { + allocation->x += textblock->textWidth (word->content.text, 0, start, + word->style, + word->flags & Word::WORD_START, + (word->flags & Word::WORD_END) + && word->content.text[start] + == 0); + } + allocation->y = textblock->lineYOffsetCanvas (line) + line->boxAscent - + word->size.ascent; + + allocation->width = word->size.width; + if (word->content.type == core::Content::TEXT) { + int wordEnd = strlen(word->content.text); + + if (start > 0 || end < wordEnd) { + end = misc::min(end, wordEnd); /* end could be INT_MAX */ + allocation->width = + textblock->textWidth (word->content.text, start, end - start, + word->style, + (word->flags & Word::WORD_START) + && start == 0, + (word->flags & Word::WORD_END) + && word->content.text[end] == 0); + } } + allocation->ascent = word->size.ascent; + allocation->descent = word->size.descent; } - allocation->ascent = word->size.ascent; - allocation->descent = word->size.descent; +} + +void Textblock::TextblockIterator::print () +{ + Iterator::print (); + printf (", oofm = %s, index = %d", oofm ? "true" : "false", index); + } } // namespace dw diff --git a/dw/textblock_linebreaking.cc b/dw/textblock_linebreaking.cc index 32e400fa..c5143dcf 100644 --- a/dw/textblock_linebreaking.cc +++ b/dw/textblock_linebreaking.cc @@ -23,6 +23,7 @@ #include "textblock.hh" #include "hyphenator.hh" #include "../lout/msg.h" +#include "../lout/debug.hh" #include "../lout/misc.hh" #include <stdio.h> @@ -192,61 +193,63 @@ int Textblock::BadnessAndPenalty::compareTo (int penaltyIndex, void Textblock::BadnessAndPenalty::print () { + misc::StringBuffer sb; + intoStringBuffer(&sb); + printf ("%s", sb.getChars ()); +} + +void Textblock::BadnessAndPenalty::intoStringBuffer(misc::StringBuffer *sb) +{ switch (badnessState) { case NOT_STRETCHABLE: - printf ("not stretchable"); + sb->append ("not stretchable"); break; case TOO_TIGHT: - printf ("too tight"); + sb->append ("too tight"); break; case QUITE_LOOSE: - printf ("quite loose (ratio = %d)", ratio); + sb->append ("quite loose (ratio = "); + sb->appendInt (ratio); + sb->append (")"); break; case BADNESS_VALUE: - printf ("%d", badness); + sb->appendInt (badness); break; } #ifdef DEBUG - printf (" [%d + %d - %d vs. %d]", - totalWidth, totalStretchability, totalShrinkability, idealWidth); + sb->append (" ["); + sb->appendInt (totalWidth); + sb->append (" + "); + sb->appendInt (totalStretchability); + sb->append (" - "); + sb->appendInt (totalShrinkability); + sb->append (" vs. "); + sb->appendInt (idealWidth); + sb->append ("]"); #endif - printf (" + ("); + sb->append (" + ("); for (int i = 0; i < 2; i++) { if (penalty[i] == INT_MIN) - printf ("-inf"); + sb->append ("-inf"); else if (penalty[i] == INT_MAX) - printf ("inf"); + sb->append ("inf"); else - printf ("%d", penalty[i]); + sb->appendInt (penalty[i]); if (i == 0) - printf (", "); + sb->append (", "); } - printf (")"); + sb->append (")"); } void Textblock::printWordShort (Word *word) { - switch(word->content.type) { - case core::Content::TEXT: - printf ("\"%s\"", word->content.text); - break; - case core::Content::WIDGET: - printf ("<widget: %p (%s)>", - word->content.widget, word->content.widget->getClassName()); - break; - case core::Content::BREAK: - printf ("<break>"); - break; - default: - printf ("<?>"); - break; - } + core::Content::print (&(word->content)); } void Textblock::printWordFlags (short flags) @@ -287,8 +290,11 @@ void Textblock::printWord (Word *word) */ void Textblock::justifyLine (Line *line, int diff) { - /* To avoid rounding errors, the calculation is based on accumulated - * values. */ + DBG_OBJ_MSGF ("construct.line", 0, "<b>justifyLine</b> (..., %d)", diff); + DBG_OBJ_MSG_START (); + + // To avoid rounding errors, the calculation is based on accumulated + // values. See doc/rounding-errors.doc. if (diff > 0) { int spaceStretchabilitySum = 0; @@ -306,8 +312,8 @@ void Textblock::justifyLine (Line *line, int diff) - spaceDiffCum; spaceDiffCum += spaceDiff; - PRINTF (" %d (of %d): diff = %d\n", i, words->size (), - spaceDiff); + DBG_OBJ_MSGF ("construct.line", 1, "%d (of %d): diff = %d", + i, words->size (), spaceDiff); word->effSpace = word->origSpace + spaceDiff; } @@ -328,38 +334,57 @@ void Textblock::justifyLine (Line *line, int diff) - spaceDiffCum; spaceDiffCum += spaceDiff; + DBG_OBJ_MSGF ("construct.line", 1, "%d (of %d): diff = %d", + i, words->size (), spaceDiff); + word->effSpace = word->origSpace + spaceDiff; } } } + + DBG_OBJ_MSG_END (); } Textblock::Line *Textblock::addLine (int firstWord, int lastWord, - bool temporary) + int newLastOofPos, bool temporary) { - PRINTF ("[%p] ADD_LINE (%d, %d) => %d\n", - this, firstWord, lastWord, lines->size ()); + DBG_OBJ_MSGF ("construct.line", 0, "<b>addLine</b> (%d, %d) => %d", + firstWord, lastWord, lines->size ()); + DBG_OBJ_MSG_START (); + + int lineWidth; + if (lastWord >= firstWord) { + DBG_MSG_WORD ("construct.line", 1, "<i>first word:</i> ", firstWord, ""); + DBG_MSG_WORD ("construct.line", 1, "<i>last word:</i> ", lastWord, ""); + + Word *lastWordOfLine = words->getRef(lastWord); + // Word::totalWidth includes the hyphen (which is what we want here). + lineWidth = lastWordOfLine->totalWidth; + DBG_OBJ_MSGF ("construct.line", 1, "lineWidth (from last word): %d", + lineWidth); + } else { + // empty line + lineWidth = 0; + DBG_OBJ_MSGF ("construct.line", 1, "lineWidth (empty line): %d", + lineWidth); + } - Word *lastWordOfLine = words->getRef(lastWord); - // Word::totalWidth includes the hyphen (which is what we want here). - int lineWidth = lastWordOfLine->totalWidth; // "lineWidth" is relative to leftOffset, so we may have to add // "line1OffsetEff" (remember: this is, for list items, negative). - if (lines->size () == 0) + if (lines->size () == 0) { lineWidth += line1OffsetEff; + DBG_OBJ_MSGF ("construct.line", 1, "lineWidth (line1OffsetEff): %d", + lineWidth); + } int maxOfMinWidth, sumOfMaxWidth; accumulateWordExtremes (firstWord, lastWord, &maxOfMinWidth, &sumOfMaxWidth); - PRINTF (" words[%d]->totalWidth = %d\n", lastWord, - lastWordOfLine->totalWidth); - - PRINTF ("[%p] ##### LINE ADDED: %d, from %d to %d #####\n", - this, lines->size (), firstWord, lastWord); - lines->increase (); + DBG_OBJ_SET_NUM ("lines.size", lines->size ()); + if(!temporary) { // If the last line was temporary, this will be temporary, too, even // if not requested. @@ -379,38 +404,59 @@ Textblock::Line *Textblock::addLine (int firstWord, int lastWord, line->marginDescent = 0; line->breakSpace = 0; line->leftOffset = 0; + line->finished = false; alignLine (lineIndex); + for (int i = line->firstWord; i < line->lastWord; i++) { Word *word = words->getRef (i); lineWidth += (word->effSpace - word->origSpace); + DBG_OBJ_MSGF ("construct.line", 1, + "lineWidth [corrected space (%d - %d) after word %d]: %d", + word->effSpace, word->origSpace, i, lineWidth); } if (lines->size () == 1) { // first line line->top = 0; line->maxLineWidth = lineWidth; + line->lastOofRefPositionedBeforeThisLine = -1; } else { Line *prevLine = lines->getRef (lines->size () - 2); line->top = prevLine->top + prevLine->boxAscent + prevLine->boxDescent + prevLine->breakSpace; line->maxLineWidth = misc::max (lineWidth, prevLine->maxLineWidth); + line->lastOofRefPositionedBeforeThisLine = + prevLine->lastOofRefPositionedBeforeThisLine; } for(int i = line->firstWord; i <= line->lastWord; i++) accumulateWordForLine (lineIndex, i); - PRINTF (" line[%d].top = %d\n", lines->size () - 1, line->top); - PRINTF (" line[%d].boxAscent = %d\n", lines->size () - 1, line->boxAscent); - PRINTF (" line[%d].boxDescent = %d\n", - lines->size () - 1, line->boxDescent); - PRINTF (" line[%d].contentAscent = %d\n", lines->size () - 1, - line->contentAscent); - PRINTF (" line[%d].contentDescent = %d\n", - lines->size () - 1, line->contentDescent); - - PRINTF (" line[%d].maxLineWidth = %d\n", - lines->size () - 1, line->maxLineWidth); + // Especially empty lines (possible when there are floats) have + // zero height, which may cause endless loops. For this reasons, + // the height should be positive. + line->boxAscent = misc::max (line->boxAscent, 1); + + // Calculate offsetCompleteWidget, which includes also floats. + int leftBorder = mustBorderBeRegarded (line) ? newLineLeftBorder : 0; + line->offsetCompleteWidget = + misc::max (leftBorder, + getStyle()->boxOffsetX() + innerPadding + + (lineIndex == 0 ? line1OffsetEff : 0)) + + line->leftOffset; + + DBG_OBJ_MSGF ("construct.line", 1, "top = %d\n", line->top); + DBG_OBJ_MSGF ("construct.line", 1, "boxAscent = %d\n", line->boxAscent); + DBG_OBJ_MSGF ("construct.line", 1, "boxDescent = %d\n", line->boxDescent); + DBG_OBJ_MSGF ("construct.line", 1, "contentAscent = %d\n", + line->contentAscent); + DBG_OBJ_MSGF ("construct.line", 1, "contentDescent = %d\n", + line->contentDescent); + DBG_OBJ_MSGF ("construct.line", 1, "maxLineWidth = %d (lineWidth = %d)\n", + line->maxLineWidth, lineWidth); + DBG_OBJ_MSGF ("construct.line", 1, "offsetCompleteWidget = %d\n", + line->offsetCompleteWidget); mustQueueResize = true; @@ -421,7 +467,7 @@ Textblock::Line *Textblock::addLine (int firstWord, int lastWord, //words->getRef(line->lastWord)->badnessAndPenalty.print (); //printf ("\n"); - int xWidget = lineXOffsetWidget(line); + int xWidget = line->offsetCompleteWidget; for (int i = firstWord; i <= lastWord; i++) { Word *word = words->getRef (i); if (word->wordImgRenderer) @@ -431,6 +477,15 @@ Textblock::Line *Textblock::addLine (int firstWord, int lastWord, xWidget += word->size.width + word->effSpace; } + line->finished = true; + line->lastOofRefPositionedBeforeThisLine = + misc::max (line->lastOofRefPositionedBeforeThisLine, newLastOofPos); + DBG_OBJ_SET_NUM ("lastLine.lastOofRefPositionedBeforeThisLine", + line->lastOofRefPositionedBeforeThisLine); + + initNewLine (); + + DBG_OBJ_MSG_END (); return line; } @@ -507,18 +562,18 @@ void Textblock::processWord (int wordIndex) */ bool Textblock::wordWrap (int wordIndex, bool wrapAll) { - PRINTF ("[%p] WORD_WRAP (%d, %s)\n", - this, wordIndex, wrapAll ? "true" : "false"); + DBG_OBJ_MSGF ("construct.word", 0, "<b>wordWrap</b> (%d, %s)", + wordIndex, wrapAll ? "true" : "false"); + DBG_OBJ_MSG_START (); - Word *word; - bool wordListChanged = false; + DBG_MSG_WORD ("construct.word", 1, "<i>wrapped word:</i> ", wordIndex, ""); if (!wrapAll) removeTemporaryLines (); initLine1Offset (wordIndex); - word = words->getRef (wordIndex); + Word *word = words->getRef (wordIndex); word->effSpace = word->origSpace; accumulateWordData (wordIndex); @@ -527,43 +582,122 @@ bool Textblock::wordWrap (int wordIndex, bool wrapAll) //printWord (word); //printf ("\n"); + bool b; + if (word->content.type == core::Content::WIDGET_OOF_REF) + b = false; + else + b = wrapWordInFlow (wordIndex, wrapAll); + + DBG_OBJ_MSG_END (); + + return b; +} + +bool Textblock::wrapWordInFlow (int wordIndex, bool wrapAll) +{ + DBG_OBJ_MSGF ("construct.word", 0, "<b>wrapWordInFlow</b> (%d, %s)", + wordIndex, wrapAll ? "true" : "false"); + DBG_OBJ_MSG_START (); + + Word *word = words->getRef (wordIndex); + bool wordListChanged = false; + int penaltyIndex = calcPenaltyIndexForNewLine (); bool newLine; do { + // This variable, thereWillBeMoreSpace, is set to true, if, due + // to floats, this line is smaller than following lines will be + // (and, at the end, there will be surely lines without + // floats). If this is the case, lines may, in an extreme case, + // be left empty. + + // (In other cases, lines are never left empty, even if this means + // that the contents is wider than the available witdh. Leaving + // lines empty does not make sense without floats, since there will + // be no possibility with more space anymore.) + + bool regardBorder = mustBorderBeRegarded (lines->size ()); + bool thereWillBeMoreSpace = regardBorder ? + newLineHasFloatLeft || newLineHasFloatRight : false; + + DBG_OBJ_MSGF ("construct.word", 1, + "thereWillBeMoreSpace = %s ? %s || %s : false = %s", + regardBorder ? "true" : "false", + newLineHasFloatLeft ? "true" : "false", + newLineHasFloatRight ? "true" : "false", + thereWillBeMoreSpace ? "true" : "false"); + + bool tempNewLine = false; int firstIndex = lines->size() == 0 ? 0 : lines->getLastRef()->lastWord + 1; int searchUntil; - if (wrapAll && wordIndex >= firstIndex && wordIndex == words->size() -1) { + if (wordIndex < firstIndex) + // Current word is already part of a line (ending with + // firstIndex - 1), so no new line has to be added. + newLine = false; + else if (wrapAll && wordIndex >= firstIndex && + wordIndex == words->size() -1) { newLine = true; searchUntil = wordIndex; tempNewLine = true; - PRINTF (" NEW LINE: last word\n"); + DBG_OBJ_MSG ("construct.word", 1, "<b>new line:</b> last word"); } else if (wordIndex >= firstIndex && // TODO: lineMustBeBroken should be independent of // the penalty index? word->badnessAndPenalty.lineMustBeBroken (penaltyIndex)) { newLine = true; searchUntil = wordIndex; - PRINTF (" NEW LINE: forced break\n"); + DBG_OBJ_MSG ("construct.word", 1, "<b>new line:</b> forced break"); } else { // Break the line when too tight, but only when there is a // possible break point so far. (TODO: I've forgotten the // original bug which is fixed by this.) + + // Exception of the latter rule: thereWillBeMoreSpace; see + // above, where it is defined. + + DBG_OBJ_MSGF ("construct.word", 1, + "possible line break between %d and %d?", + firstIndex, wordIndex - 1); + DBG_OBJ_MSG_START (); + bool possibleLineBreak = false; - for (int i = firstIndex; !possibleLineBreak && i <= wordIndex - 1; i++) + for (int i = firstIndex; + !(thereWillBeMoreSpace || possibleLineBreak) + && i <= wordIndex - 1; + i++) { + DBG_OBJ_MSGF ("construct.word", 2, "examining word %d", i); if (words->getRef(i)->badnessAndPenalty - .lineCanBeBroken (penaltyIndex)) + .lineCanBeBroken (penaltyIndex)) { + DBG_MSG_WORD ("construct.word", 2, "break possible for word:", + i, ""); possibleLineBreak = true; + } + } + + DBG_OBJ_MSG_END (); + DBG_OBJ_MSGF ("construct.word", 1, "=> %s", + possibleLineBreak ? "true" : "false"); - if (possibleLineBreak && word->badnessAndPenalty.lineTooTight ()) { + DBG_OBJ_MSGF ("construct.word", 1, "word->... too tight: %s", + word->badnessAndPenalty.lineTooTight () ? + "true" : "false"); + + if ((thereWillBeMoreSpace || possibleLineBreak) + && word->badnessAndPenalty.lineTooTight ()) { + // TODO Should hyphenation be considered here? (Here, + // because it is to late in searchBreakPos?) newLine = true; searchUntil = wordIndex - 1; - PRINTF (" NEW LINE: line too tight\n"); - } else + DBG_OBJ_MSG ("construct.word", 1, + "<b>new line:</b> line too tight"); + } else { + DBG_OBJ_MSG ("construct.word", 1, "no <b>new line</b>"); newLine = false; + } } if(!newLine && !wrapAll) @@ -575,65 +709,98 @@ bool Textblock::wordWrap (int wordIndex, bool wrapAll) // newLine is calculated as "true". mustQueueResize = true; - if(newLine) { + PRINTF ("[%p] special case? newLine = %s, wrapAll = %s => " + "mustQueueResize = %s\n", this, newLine ? "true" : "false", + wrapAll ? "true" : "false", mustQueueResize ? "true" : "false"); + + if (newLine) { accumulateWordData (wordIndex); + int wordIndexEnd = wordIndex; + int height = 1; // assumed by calcBorders before (see there) + int breakPos; + + balanceBreakPosAndHeight (wordIndex, firstIndex, &searchUntil, + tempNewLine, penaltyIndex, true, + &thereWillBeMoreSpace, wrapAll, + &wordListChanged, &wordIndexEnd, + lines->size() > 0 ? + lines->getLastRef() + ->lastOofRefPositionedBeforeThisLine : -1, + regardBorder, &height, &breakPos); + + int lastFloatPos = lines->size() > 0 ? + lines->getLastRef()->lastOofRefPositionedBeforeThisLine : -1; + bool floatHandled; + int yNewLine = yOffsetOfPossiblyMissingLine (lines->size ()); - bool lineAdded; do { - int breakPos = - searchMinBap (firstIndex, searchUntil, penaltyIndex, wrapAll); - int hyphenatedWord = considerHyphenation (firstIndex, breakPos); - - //printf ("[%p] breakPos = %d (", this, breakPos); - //printWordShort (words->getRef (breakPos)); - //printf ("), hyphenatedWord = %d", hyphenatedWord); - //if (hyphenatedWord != -1) { - // printf (" ("); - // printWordShort (words->getRef (hyphenatedWord)); - // printf (")"); - //} - //printf ("\n"); - - if(hyphenatedWord == -1) { - addLine (firstIndex, breakPos, tempNewLine); - PRINTF ("[%p] new line %d (%s), from %d to %d\n", - this, lines->size() - 1, - tempNewLine ? "temporally" : "permanently", - firstIndex, breakPos); - lineAdded = true; - penaltyIndex = calcPenaltyIndexForNewLine (); - } else { - // TODO hyphenateWord() should return whether something has - // changed at all. So that a second run, with - // !word->canBeHyphenated, is unnecessary. - // TODO Update: for this, searchUntil == 0 should be checked. - PRINTF ("[%p] old searchUntil = %d ...\n", this, searchUntil); - int n = hyphenateWord (hyphenatedWord); - searchUntil += n; - if (hyphenatedWord <= wordIndex) - wordIndexEnd += n; - PRINTF ("[%p] -> new searchUntil = %d ...\n", this, searchUntil); - lineAdded = false; - - // update word pointer as hyphenateWord() can trigger a - // reorganization of the words structure - word = words->getRef (wordIndex); - - if (n > 0 && hyphenatedWord <= wordIndex) - wordListChanged = true; - } + DBG_OBJ_MSG ("construct.word", 1, "<i>floatHandled loop cycle</i>"); + DBG_OBJ_MSG_START (); + + DBG_OBJ_MSGF ("construct.word", 2, + "breakPos = %d, height = %d, lastFloatPos = %d", + breakPos, height, lastFloatPos); - PRINTF ("[%p] accumulating again from %d to %d\n", - this, breakPos + 1, wordIndexEnd); - for(int i = breakPos + 1; i <= wordIndexEnd; i++) - accumulateWordData (i); + int startSearch = misc::max (firstIndex, lastFloatPos + 1); + int newFloatPos = -1; + + // Step 1: search for the next float. + DBG_OBJ_MSGF ("construct.word", 2, "searching from %d to %d", + startSearch, breakPos); + for (int i = startSearch; newFloatPos == -1 && i <= breakPos; i++) { + core::Content *content = &(words->getRef(i)->content); + if (content->type == core::Content::WIDGET_OOF_REF && + // Later, absolutepositioned elements (which do not affect + // borders) can be ignored at this point. + (containingBlock->outOfFlowMgr->affectsLeftBorder + (content->widget) || + containingBlock->outOfFlowMgr->affectsRightBorder + (content->widget))) + newFloatPos = i; + } + + DBG_OBJ_MSGF ("construct.word", 2, "newFloatPos = %d", newFloatPos); + + if (newFloatPos == -1) + floatHandled = false; + else { + floatHandled = true; - } while(!lineAdded); + // Step 2: position the float and re-calculate the line. + lastFloatPos = newFloatPos; + + containingBlock->outOfFlowMgr->tellPosition + (words->getRef(lastFloatPos)->content.widget, yNewLine); + + balanceBreakPosAndHeight (wordIndex, firstIndex, &searchUntil, + tempNewLine, penaltyIndex, false, + &thereWillBeMoreSpace, wrapAll, + &wordListChanged, &wordIndexEnd, + lastFloatPos, regardBorder, &height, + &breakPos); + } + + DBG_OBJ_MSG_END (); + } while (floatHandled); + + addLine (firstIndex, breakPos, lastFloatPos, tempNewLine); + + DBG_OBJ_MSGF ("construct.word", 1, + "accumulating again from %d to %d\n", + breakPos + 1, wordIndexEnd); + for(int i = breakPos + 1; i <= wordIndexEnd; i++) + accumulateWordData (i); + + // update word pointer as hyphenateWord() can trigger a + // reorganization of the words structure + word = words->getRef (wordIndex); + + penaltyIndex = calcPenaltyIndexForNewLine (); } } while (newLine); - if(word->content.type == core::Content::WIDGET) { + if(word->content.type == core::Content::WIDGET_IN_FLOW) { // Set parentRef for the child, when necessary. // // parentRef is set for the child already, when a line is @@ -650,41 +817,247 @@ bool Textblock::wordWrap (int wordIndex, bool wrapAll) firstWordWithoutLine = lines->getLastRef()->lastWord + 1; if (wordIndex >= firstWordWithoutLine) { - word->content.widget->parentRef = lines->size (); + word->content.widget->parentRef = + OutOfFlowMgr::createRefNormalFlow (lines->size ()); PRINTF ("The %s %p is assigned parentRef = %d.\n", word->content.widget->getClassName(), word->content.widget, word->content.widget->parentRef); } } + DBG_OBJ_MSG_END (); + return wordListChanged; } +// *height must be initialized, but not *breakPos. +// *wordIndexEnd must be initialized (initially to wordIndex) +void Textblock::balanceBreakPosAndHeight (int wordIndex, int firstIndex, + int *searchUntil, bool tempNewLine, + int penaltyIndex, + bool borderIsCalculated, + bool *thereWillBeMoreSpace, + bool wrapAll, bool *wordListChanged, + int *wordIndexEnd, int lastFloatPos, + bool regardBorder, int *height, + int *breakPos) +{ + DBG_OBJ_MSGF ("construct.word", 0, + "<b>balanceBreakPosAndHeight</b> (%d, %d. %d, %s, %d, %s, " + "..., %s, ..., %d, %s, %d, ...)", + wordIndex, firstIndex, *searchUntil, + tempNewLine ? "true" : "false", penaltyIndex, + borderIsCalculated ? "true" : "false", + wrapAll ? "true" : "false", lastFloatPos, + regardBorder ? "true" : "false", *height); + DBG_OBJ_MSG_START (); + + // The height of this part of the line (until the new break + // position) may change with the break position, but the break + // position may depend on the height. We try to let these values + // converge. + // + // The height, as a function of the break position, is + // monotonically (but not strictly) increasing, since more words + // may make the line higher (but not flatter). The break position, + // as a function of the height, is, however, monotonically (but not + // strictly) *de*creasing, since flatter lines may fit easier + // between floats (although this is a rare case). So a convergence + // is not necessary. + // + // For this reason, we iterate only as long as the height does not + // increase again, and stop if it remains the same. As the minimum + // is 1, this approach will force the iteration to stop. + // + // (As a side effect, this will lead to a larger break position, + // and so place as much words as possible in the line.) + + int runNo = 1; + while (true) { + if (!(borderIsCalculated && runNo == 1)) { + // borderIsCalculated is, of course, only valid in the first run + calcBorders (lastFloatPos, *height); + *thereWillBeMoreSpace = regardBorder ? + newLineHasFloatLeft || newLineHasFloatRight : false; + + for(int i = firstIndex; i <= *wordIndexEnd; i++) + accumulateWordData (i); + } + + DBG_OBJ_MSGF ("construct.word", 1, "thereWillBeMoreSpace = %s", + *thereWillBeMoreSpace ? "true" : "false"); + + int newBreakPos = + searchBreakPos (wordIndex, firstIndex, searchUntil, + tempNewLine, penaltyIndex, + *thereWillBeMoreSpace, wrapAll, + wordListChanged, wordIndexEnd); + int newHeight = calcLinePartHeight (firstIndex, newBreakPos); + + DBG_OBJ_MSGF ("construct.word", 1, + "runNo = %d, newBreakPos = %d, newHeight = %d", + runNo, newBreakPos, newHeight); + if (runNo == 1) + DBG_OBJ_MSGF ("construct.word", 1, + "old: height = %d, breakPos undefined", *height); + else + DBG_OBJ_MSGF ("construct.word", 1, + "old: height = %d, breakPos = %d", *height, *breakPos); + + if (runNo != 1 /* Since *some* value are needed, the results + from the first run are never discarded. */ + && newHeight >= *height) { + if (newHeight == *height) { + // newHeight == height: convergence, stop here. The new break + // position is, nevertheless, adopted. + DBG_OBJ_MSG ("construct.word", 1, "stopping, adopting new values"); + *breakPos = newBreakPos; + } else + // newHeight > height: do not proceed, discard new values, + // which are less desirable than the old ones (see above). + DBG_OBJ_MSG ("construct.word", 1, + "stopping, discarding new values"); + break; + } else { + DBG_OBJ_MSG ("construct.word", 1, "adopting new values, continuing"); + *height = newHeight; + *breakPos = newBreakPos; + } + + runNo++; + } + + DBG_OBJ_MSG_END (); +} + +// *wordIndexEnd must be initialized (initially to wordIndex) +int Textblock::searchBreakPos (int wordIndex, int firstIndex, int *searchUntil, + bool tempNewLine, int penaltyIndex, + bool thereWillBeMoreSpace, bool wrapAll, + bool *wordListChanged, int *wordIndexEnd) +{ + DBG_OBJ_MSGF ("construct.word", 0, + "<b>searchBreakPos</b> (%d, %d. %d, %s, %d, %s, %s, ...)", + wordIndex, firstIndex, *searchUntil, + tempNewLine ? "true" : "false", penaltyIndex, + thereWillBeMoreSpace ? "true" : "false", + wrapAll ? "true" : "false"); + DBG_OBJ_MSG_START (); + + int result; + bool lineAdded; + + do { + DBG_OBJ_MSG ("construct.word", 1, "<i>searchBreakPos loop cycle</i>"); + DBG_OBJ_MSG_START (); + + if (firstIndex > *searchUntil) { + // empty line + DBG_OBJ_MSG ("construct.word", 1, "empty line"); + assert (*searchUntil == firstIndex - 1); + result = firstIndex - 1; + lineAdded = true; + } else if (thereWillBeMoreSpace && + words->getRef(firstIndex)->badnessAndPenalty.lineTooTight ()) { + // TODO Consider hyphenation. + DBG_OBJ_MSG ("construct.word", 1, "too tight => empty line"); + result = firstIndex - 1; + lineAdded = true; + } else { + DBG_OBJ_MSG ("construct.word", 1, "non-empty line"); + + int breakPos = + searchMinBap (firstIndex, *searchUntil, penaltyIndex, + thereWillBeMoreSpace, wrapAll); + int hyphenatedWord = considerHyphenation (firstIndex, breakPos); + + DBG_OBJ_MSGF ("construct.word", 1, "breakPos = %d", breakPos); + DBG_MSG_WORD ("construct.word", 1, "<i>break at word:</i> ", + breakPos, ""); + DBG_OBJ_MSGF ("construct.word", 1, "hyphenatedWord = %d", + hyphenatedWord); + if (hyphenatedWord != -1) + DBG_MSG_WORD ("construct.word", 1, + "<i>hyphenate at word:</i> ", + hyphenatedWord, ""); + + if(hyphenatedWord == -1) { + result = breakPos; + lineAdded = true; + } else { + // TODO hyphenateWord() should return whether something + // has changed at all. So that a second run, with + // !word->canBeHyphenated, is unnecessary. + // TODO Update: The return value of hyphenateWord() should + // be checked. + DBG_OBJ_MSGF ("construct.word", 1, "old searchUntil = %d", + *searchUntil); + int n = hyphenateWord (hyphenatedWord); + *searchUntil += n; + if (hyphenatedWord <= wordIndex) + *wordIndexEnd += n; + DBG_OBJ_MSGF ("construct.word", 1, "new searchUntil = %d", + *searchUntil); + lineAdded = false; + result = 1000000000; // compiler happiness + + if (n > 0 && hyphenatedWord <= wordIndex) + *wordListChanged = true; + + DBG_OBJ_MSGF ("construct.word", 1, + "accumulating again from %d to %d\n", + breakPos + 1, *wordIndexEnd); + for(int i = breakPos + 1; i <= *wordIndexEnd; i++) + accumulateWordData (i); + } + } + + DBG_OBJ_MSG_END (); + } while(!lineAdded); + + DBG_OBJ_MSGF ("construct.word", 1, "=> %d", result); + DBG_OBJ_MSG_END (); + + return result; +} + int Textblock::searchMinBap (int firstWord, int lastWord, int penaltyIndex, - bool correctAtEnd) + bool thereWillBeMoreSpace, bool correctAtEnd) { - PRINTF (" searching from %d to %d\n", firstWord, lastWord); + DBG_OBJ_MSGF ("construct.word", 0, + "<b>searchMinBap</b> (%d, %d, %d, %s, %s)", + firstWord, lastWord, penaltyIndex, + thereWillBeMoreSpace ? "true" : "false", + correctAtEnd ? "true" : "false"); + DBG_OBJ_MSG_START (); int pos = -1; + DBG_OBJ_MSG_START (); for (int i = firstWord; i <= lastWord; i++) { Word *w = words->getRef(i); - //printf (" %d (of %d): ", i, words->size ()); - //printWord (w); - //printf ("\n"); - + DBG_IF_RTFL { + misc::StringBuffer sb; + w->badnessAndPenalty.intoStringBuffer (&sb); + DBG_OBJ_MSGF ("construct.word", 2, "%d (of %d): b+p: %s", + i, words->size (), sb.getChars ()); + DBG_MSG_WORD ("construct.word", 2, "(<i>i. e.:</i> ", i, ")"); + } + + // "<=" instead of "<" in the next lines (see also + // "correctedBap.compareTo ...) tends to result in more words + // per line -- theoretically. Practically, the case "==" will + // never occur. if (pos == -1 || w->badnessAndPenalty.compareTo (penaltyIndex, &words->getRef(pos) ->badnessAndPenalty) <= 0) - // "<=" instead of "<" in the next lines tends to result in - // more words per line -- theoretically. Practically, the - // case "==" will never occur. pos = i; } + DBG_OBJ_MSG_END (); - PRINTF (" found at %d\n", pos); + DBG_OBJ_MSGF ("construct.word", 1, "found at %d\n", pos); if (correctAtEnd && lastWord == words->size () - 1) { // Since no break and no space is added, the last word will have @@ -699,21 +1072,24 @@ int Textblock::searchMinBap (int firstWord, int lastWord, int penaltyIndex, BadnessAndPenalty correctedBap = w->badnessAndPenalty; correctedBap.setPenalty (0); - //printf (" corrected bap: "); - //correctedBap.print (); - //printf ("\n"); - + DBG_IF_RTFL { + misc::StringBuffer sb; + correctedBap.intoStringBuffer (&sb); + DBG_OBJ_MSGF ("construct.word", 1, "corrected b+p: %s", + sb.getChars ()); + } + if (correctedBap.compareTo(penaltyIndex, &words->getRef(pos)->badnessAndPenalty) <= 0) { pos = lastWord; - PRINTF (" corrected => %d\n", pos); + DBG_OBJ_MSGF ("construct.word", 1, "corrected: %d\n", pos); } } + DBG_OBJ_MSG_END (); return pos; } - /** * Suggest a word to hyphenate, when breaking at breakPos is * planned. Return a word index or -1, when hyphenation makes no @@ -764,6 +1140,19 @@ bool Textblock::isHyphenationCandidate (Word *word) } +int Textblock::calcLinePartHeight (int firstWord, int lastWord) +{ + int ascent = 0, descent = 0; + + for (int i = firstWord; i <= lastWord; i++) { + Word *word = words->getRef (i); + ascent = misc::max (ascent, word->size.ascent); + descent = misc::max (descent, word->size.descent); + } + + return misc::max (ascent + descent, 1); +} + /** * Counter part to wordWrap(), but for extremes, not size calculation. */ @@ -771,13 +1160,18 @@ void Textblock::handleWordExtremes (int wordIndex) { // TODO Overall, clarify penalty index. + DBG_OBJ_MSGF ("construct.paragraph", 0, + "<b>handleWordExtremes</b> (%d)", wordIndex); + DBG_OBJ_MSG_START (); + Word *word = words->getRef (wordIndex); + DBG_MSG_WORD ("construct.paragraph", 1, + "<i>handled word:</i> ", wordIndex, ""); + core::Extremes wordExtremes; getWordExtremes (word, &wordExtremes); - - //printf ("[%p] HANDLE_WORD_EXTREMES (%d): ", this, wordIndex); - //printWordWithFlags (word); - //printf (" => %d / %d\n", wordExtremes.minWidth, wordExtremes.maxWidth); + DBG_OBJ_MSGF ("construct.paragraph", 1, "extremes: %d / %d", + wordExtremes.minWidth, wordExtremes.maxWidth); if (wordIndex == 0) { wordExtremes.minWidth += line1Offset; @@ -802,10 +1196,12 @@ void Textblock::handleWordExtremes (int wordIndex) } else par->maxParMin = par->maxParMax = 0; - PRINTF (" new par: %d\n", paragraphs->size() - 1); + DBG_OBJ_MSGF ("construct.paragraph", 1, "new par: %d", + paragraphs->size() - 1); } - PRINTF (" last par: %d\n", paragraphs->size() - 1); + DBG_OBJ_MSGF ("construct.paragraph", 1, "last par: %d", + paragraphs->size() - 1); Paragraph *lastPar = paragraphs->getLastRef(); int corrDiffMin, corrDiffMax; @@ -821,8 +1217,10 @@ void Textblock::handleWordExtremes (int wordIndex) } else corrDiffMin = corrDiffMax = 0; - PRINTF (" (lastPar from %d to %d; corrDiffMin = %d, corDiffMax = %d)\n", - lastPar->firstWord, lastPar->lastWord, corrDiffMin, corrDiffMax); + DBG_OBJ_MSGF ("construct.paragraph", 1, + "(lastPar from %d to %d; corrDiffMin = %d, corDiffMax = %d)", + lastPar->firstWord, lastPar->lastWord, corrDiffMin, + corrDiffMax); // Minimum: between two *possible* breaks. // Shrinkability could be considered, but really does not play a role. @@ -836,11 +1234,13 @@ void Textblock::handleWordExtremes (int wordIndex) lastPar->parMax += wordExtremes.maxWidth + word->hyphenWidth + corrDiffMax; lastPar->maxParMax = misc::max (lastPar->maxParMax, lastPar->parMax); - PRINTF (" => parMin = %d (max = %d), parMax = %d (max = %d)\n", - lastPar->parMin, lastPar->maxParMin, lastPar->parMax, - lastPar->maxParMax); + DBG_OBJ_MSGF ("construct.paragraph", 1, + "=> parMin = %d (max = %d), parMax = %d (max = %d)", + lastPar->parMin, lastPar->maxParMin, lastPar->parMax, + lastPar->maxParMax); lastPar->lastWord = wordIndex; + DBG_OBJ_MSG_END (); } /** @@ -882,10 +1282,18 @@ int Textblock::hyphenateWord (int wordIndex) PRINTF ("[%p] %d words ...\n", this, words->size ()); words->insert (wordIndex, numBreaks); + + DBG_IF_RTFL { + for (int i = wordIndex + numBreaks; i < words->size (); i++) + DBG_SET_WORD (i); + } + for (int i = 0; i < numBreaks; i++) initWord (wordIndex + i); PRINTF ("[%p] ... => %d words\n", this, words->size ()); + moveWordIndices (wordIndex, numBreaks); + // Adjust anchor indexes. for (int i = 0; i < anchors->size (); i++) { Anchor *anchor = anchors->getRef (i); @@ -909,6 +1317,10 @@ int Textblock::hyphenateWord (int wordIndex) end - start); PRINTF (" [%d] -> '%s'\n", wordIndex + i, w->content.text); + DBG_OBJ_ARRATTRSET_SYM ("words", wordIndex + i, "type", "TEXT"); + DBG_OBJ_ARRATTRSET_STR ("words", wordIndex + i, + "text/widget/breakSpace", w->content.text); + // Note: there are numBreaks + 1 word parts. if (i == 0) w->flags |= Word::WORD_START; @@ -941,13 +1353,20 @@ int Textblock::hyphenateWord (int wordIndex) } } - accumulateWordData (wordIndex + i); - //printf ("[%p] %d: hyphenated word part: ", this, wordIndex + i); //printWordWithFlags (w); //printf ("\n"); } + // AccumulateWordData() will calculate the width, which depends + // on the borders (possibly limited by floats), which depends on + // the widgeds so far. For this reason, it is important to first + // make all words consistent before calling + // accumulateWordData(); therefore the second loop. + + for (int i = 0; i < numBreaks + 1; i++) + accumulateWordData (wordIndex + i); + PRINTF (" finished\n"); //delete origword->content.text; TODO: Via textZone? @@ -961,13 +1380,33 @@ int Textblock::hyphenateWord (int wordIndex) return numBreaks; } +void Textblock::moveWordIndices (int wordIndex, int num) +{ + if (containingBlock->outOfFlowMgr) + containingBlock->outOfFlowMgr->moveExternalIndices (this, wordIndex, num); + + for (int i = lines->size () - 1; i >= 0; i--) { + Line *line = lines->getRef (i); + if (line->lastOofRefPositionedBeforeThisLine < wordIndex) + // Since lastOofRefPositionedBeforeThisLine are ascending, + // the search can be stopped here. + break; + else + line->lastOofRefPositionedBeforeThisLine += num; + } +} + void Textblock::accumulateWordForLine (int lineIndex, int wordIndex) { Line *line = lines->getRef (lineIndex); Word *word = words->getRef (wordIndex); - PRINTF (" %d + %d / %d + %d\n", line->boxAscent, line->boxDescent, + PRINTF ("[%p] ACCUMULATE_WORD_FOR_LINE (%d, %d): %d + %d / %d + %d\n", + this, lineIndex, wordIndex, line->boxAscent, line->boxDescent, word->size.ascent, word->size.descent); + //printf (" "); + //printWord (word); + //printf ("\n"); line->boxAscent = misc::max (line->boxAscent, word->size.ascent); line->boxDescent = misc::max (line->boxDescent, word->size.descent); @@ -982,7 +1421,7 @@ void Textblock::accumulateWordForLine (int lineIndex, int wordIndex) len += word->style->font->ascent / 3; line->contentDescent = misc::max (line->contentDescent, len); - if (word->content.type == core::Content::WIDGET) { + if (word->content.type == core::Content::WIDGET_IN_FLOW) { int collapseMarginTop = 0; line->marginDescent = @@ -1006,7 +1445,8 @@ void Textblock::accumulateWordForLine (int lineIndex, int wordIndex) + word->content.widget->getStyle()->margin.top - collapseMarginTop); - word->content.widget->parentRef = lineIndex; + word->content.widget->parentRef = + OutOfFlowMgr::createRefNormalFlow (lineIndex); } else { line->marginDescent = misc::max (line->marginDescent, line->boxDescent); @@ -1021,6 +1461,10 @@ void Textblock::accumulateWordForLine (int lineIndex, int wordIndex) void Textblock::accumulateWordData (int wordIndex) { + DBG_OBJ_MSGF ("construct.word", 1, "<b>accumulateWordData</b> (%d)", + wordIndex); + DBG_OBJ_MSG_START (); + // Typically, the word in question is in the last line; in any case // quite at the end of the text, so that linear search is actually // the fastest option. @@ -1035,14 +1479,14 @@ void Textblock::accumulateWordData (int wordIndex) firstWordOfLine = lines->getRef(lineIndex - 1)->lastWord + 1; Word *word = words->getRef (wordIndex); - PRINTF ("[%p] ACCUMULATE_WORD_DATA (%d); lineIndex = %d: ...\n", - this, wordIndex, lineIndex); + DBG_OBJ_MSGF ("construct.word", 2, "lineIndex = %d", lineIndex); int availWidth = calcAvailWidth (lineIndex); - PRINTF (" (%s existing line %d starts with word %d)\n", - lineIndex < lines->size () ? "already" : "not yet", - lineIndex, firstWordOfLine); + DBG_OBJ_MSGF ("construct.word", 2, + "(%s existing line %d starts with word %d; availWidth = %d)", + lineIndex < lines->size () ? "already" : "not yet", + lineIndex, firstWordOfLine, availWidth); if (wordIndex == firstWordOfLine) { // first word of the (not neccessarily yet existing) line @@ -1051,6 +1495,11 @@ void Textblock::accumulateWordData (int wordIndex) word->maxDescent = word->size.descent; word->totalSpaceStretchability = 0; word->totalSpaceShrinkability = 0; + + DBG_OBJ_MSGF ("construct.word", 1, + "first word of line: words[%d].totalWidth = %d + %d = %d", + wordIndex, word->size.width, word->hyphenWidth, + word->totalWidth); } else { Word *prevWord = words->getRef (wordIndex - 1); @@ -1063,6 +1512,13 @@ void Textblock::accumulateWordData (int wordIndex) prevWord->totalSpaceStretchability + getSpaceStretchability(prevWord); word->totalSpaceShrinkability = prevWord->totalSpaceShrinkability + getSpaceShrinkability(prevWord); + + DBG_OBJ_MSGF ("construct.word", 1, + "not first word of line: words[%d].totalWidth = %d + %d - " + "%d + %d + %d = %d", + wordIndex, prevWord->totalWidth, prevWord->origSpace, + prevWord->hyphenWidth, word->size.width, + word->hyphenWidth, word->totalWidth); } int totalStretchability = @@ -1073,26 +1529,48 @@ void Textblock::accumulateWordData (int wordIndex) totalStretchability, totalShrinkability); - //printf (" => "); - //printWord (word); - //printf ("\n"); + DBG_IF_RTFL { + misc::StringBuffer sb; + word->badnessAndPenalty.intoStringBuffer (&sb); + DBG_OBJ_MSGF ("construct.word", 1, "b+p: %s", sb.getChars ()); + } + + DBG_OBJ_MSG_END (); } int Textblock::calcAvailWidth (int lineIndex) { - int availWidth = - this->availWidth - getStyle()->boxDiffWidth() - innerPadding; + DBG_OBJ_MSGF ("construct.word", 1, "<b>calcAvailWidth</b> (%d <i>of %d</i>)", + lineIndex, lines->size()); + DBG_OBJ_MSG_START (); + + int availWidth = this->availWidth - innerPadding; if (limitTextWidth && layout->getUsesViewport () && - availWidth > layout->getWidthViewport () - 10) + // margin/border/padding will be subtracted later, via OOFM. + availWidth - getStyle()->boxDiffWidth() + > layout->getWidthViewport () - 10) availWidth = layout->getWidthViewport () - 10; if (lineIndex == 0) availWidth -= line1OffsetEff; - //PRINTF("[%p] CALC_AVAIL_WIDTH => %d - %d - %d = %d\n", - // this, this->availWidth, getStyle()->boxDiffWidth(), innerPadding, - // availWidth); + int leftBorder, rightBorder; + if (mustBorderBeRegarded (lineIndex)) { + leftBorder = newLineLeftBorder; + rightBorder = newLineRightBorder; + } else + leftBorder = rightBorder = 0; + + leftBorder = misc::max (leftBorder, getStyle()->boxOffsetX()); + rightBorder = misc::max (rightBorder, getStyle()->boxRestWidth()); + + availWidth -= (leftBorder + rightBorder); + DBG_OBJ_MSGF ("construct.word", 2, "=> %d - %d - (%d + %d) = %d\n", + this->availWidth, innerPadding, leftBorder, rightBorder, + availWidth); + + DBG_OBJ_MSG_END (); return availWidth; } @@ -1108,7 +1586,7 @@ void Textblock::initLine1Offset (int wordIndex) } else { int indent = 0; - if (word->content.type == core::Content::WIDGET && + if (word->content.type == core::Content::WIDGET_IN_FLOW && word->content.widget->blockLevel() == true) { /* don't use text-indent when nesting blocks */ } else { @@ -1131,49 +1609,70 @@ void Textblock::initLine1Offset (int wordIndex) */ void Textblock::alignLine (int lineIndex) { + DBG_OBJ_MSGF ("construct.line", 0, "<b>alignLine</b> (%d)", lineIndex); + DBG_OBJ_MSG_START (); + Line *line = lines->getRef (lineIndex); int availWidth = calcAvailWidth (lineIndex); - Word *firstWord = words->getRef (line->firstWord); - Word *lastWord = words->getRef (line->lastWord); - - for (int i = line->firstWord; i < line->lastWord; i++) - words->getRef(i)->origSpace = words->getRef(i)->effSpace; - - if (firstWord->content.type != core::Content::BREAK) { - switch (firstWord->style->textAlign) { - case core::style::TEXT_ALIGN_LEFT: - case core::style::TEXT_ALIGN_STRING: /* handled elsewhere (in the - * future)? */ - line->leftOffset = 0; - break; - case core::style::TEXT_ALIGN_JUSTIFY: /* see some lines above */ - line->leftOffset = 0; - // Do not justify the last line of a paragraph (which ends on a - // BREAK or with the last word of the page). - if(!(lastWord->content.type == core::Content::BREAK || - line->lastWord == words->size () - 1) || - // In some cases, however, an unjustified line would be too wide: - // when the line would be shrunken otherwise. (This solution is - // far from perfect, but a better solution would make changes in - // the line breaking algorithm necessary.) - availWidth < lastWord->totalWidth) - justifyLine (line, availWidth - lastWord->totalWidth); - break; - case core::style::TEXT_ALIGN_RIGHT: - line->leftOffset = availWidth - lastWord->totalWidth; - break; - case core::style::TEXT_ALIGN_CENTER: - line->leftOffset = (availWidth - lastWord->totalWidth) / 2; - break; - default: - /* compiler happiness */ - line->leftOffset = 0; + if (line->firstWord <= line->lastWord) { + Word *firstWord = words->getRef (line->firstWord); + Word *lastWord = words->getRef (line->lastWord); + + for (int i = line->firstWord; i < line->lastWord; i++) + words->getRef(i)->origSpace = words->getRef(i)->effSpace; + + if (firstWord->content.type != core::Content::BREAK) { + switch (firstWord->style->textAlign) { + case core::style::TEXT_ALIGN_LEFT: + DBG_OBJ_MSG ("construct.line", 1, + "first word has 'text-align: left'"); + line->leftOffset = 0; + break; + case core::style::TEXT_ALIGN_STRING: /* handled elsewhere (in the + * future)? */ + DBG_OBJ_MSG ("construct.line", 1, + "first word has 'text-align: string'"); + line->leftOffset = 0; + break; + case core::style::TEXT_ALIGN_JUSTIFY: /* see some lines above */ + line->leftOffset = 0; + DBG_OBJ_MSG ("construct.line", 1, + "first word has 'text-align: justify'"); + // Do not justify the last line of a paragraph (which ends on a + // BREAK or with the last word of the page). + if(!(lastWord->content.type == core::Content::BREAK || + line->lastWord == words->size () - 1) || + // In some cases, however, an unjustified line would be too wide: + // when the line would be shrunken otherwise. (This solution is + // far from perfect, but a better solution would make changes in + // the line breaking algorithm necessary.) + availWidth < lastWord->totalWidth) + justifyLine (line, availWidth - lastWord->totalWidth); + break; + case core::style::TEXT_ALIGN_RIGHT: + DBG_OBJ_MSG ("construct.line", 1, + "first word has 'text-align: right'"); + line->leftOffset = availWidth - lastWord->totalWidth; + break; + case core::style::TEXT_ALIGN_CENTER: + DBG_OBJ_MSG ("construct.line", 1, + "first word has 'text-align: center'"); + line->leftOffset = (availWidth - lastWord->totalWidth) / 2; + break; + default: + /* compiler happiness */ + line->leftOffset = 0; + } + + /* For large lines (images etc), which do not fit into the viewport: */ + if (line->leftOffset < 0) + line->leftOffset = 0; } + } else + // empty line + line->leftOffset = 0; - /* For large lines (images etc), which do not fit into the viewport: */ - if (line->leftOffset < 0) - line->leftOffset = 0; - } + DBG_OBJ_MSG_END (); } /** @@ -1184,43 +1683,53 @@ void Textblock::alignLine (int lineIndex) */ void Textblock::rewrap () { - PRINTF ("[%p] REWRAP: wrapRef = %d\n", this, wrapRef); + DBG_OBJ_MSG ("construct.line", 0, "<b>rewrap</b> ()"); + DBG_OBJ_MSG_START (); if (wrapRefLines == -1) - /* page does not have to be rewrapped */ - return; - - /* All lines up from wrapRef will be rebuild from the word list, - * the line list up from this position is rebuild. */ - lines->setSize (wrapRefLines); - nonTemporaryLines = misc::min (nonTemporaryLines, wrapRefLines); - - int firstWord; - if (lines->size () > 0) - firstWord = lines->getLastRef()->lastWord + 1; - else - firstWord = 0; + DBG_OBJ_MSG ("construct.line", 0, "does not have to be rewrapped"); + else { + // All lines up from wrapRef will be rebuild from the word list, + // the line list up from this position is rebuild. + lines->setSize (wrapRefLines); + DBG_OBJ_SET_NUM ("lines.size", lines->size ()); + nonTemporaryLines = misc::min (nonTemporaryLines, wrapRefLines); + + initNewLine (); + + int firstWord; + if (lines->size () > 0) { + Line *lastLine = lines->getLastRef(); + firstWord = lastLine->lastWord + 1; + } else + firstWord = 0; + + DBG_OBJ_MSGF ("construct.line", 0, "starting with word %d", firstWord); - for (int i = firstWord; i < words->size (); i++) { - Word *word = words->getRef (i); + for (int i = firstWord; i < words->size (); i++) { + Word *word = words->getRef (i); - if (word->content.type == core::Content::WIDGET) - calcWidgetSize (word->content.widget, &word->size); - - wordWrap (i, false); + if (word->content.type == core::Content::WIDGET_IN_FLOW) + calcWidgetSize (word->content.widget, &word->size); + + wordWrap (i, false); + + // Somewhat historical, but still important, note: + // + // For the case that something else is done with this word, it + // is important that wordWrap() may insert some new words; since + // NotSoSimpleVector is used for the words list, the internal + // structure may have changed, so getRef() must be called again. + // + // So this is necessary: word = words->getRef (i); + } - // Somewhat historical, but still important, note: - // - // For the case that something else is done with this word, it - // is important that wordWrap() may insert some new words; since - // NotSoSimpleVector is used for the words list, the internal - // structure may have changed, so getRef() must be called again. - // - // So this is necessary: word = words->getRef (i); + // Next time, the page will not have to be rewrapped. + wrapRefLines = -1; + DBG_OBJ_SET_NUM ("wrapRefLines", wrapRefLines); } - - /* Next time, the page will not have to be rewrapped. */ - wrapRefLines = -1; + + DBG_OBJ_MSG_END (); } /** @@ -1228,14 +1737,20 @@ void Textblock::rewrap () */ void Textblock::fillParagraphs () { + DBG_OBJ_MSG ("resize", 0, "<b>fillParagraphs</b>"); + DBG_OBJ_MSG_START (); + if (wrapRefParagraphs == -1) return; // Notice that wrapRefParagraphs refers to the lines, not to the paragraphs. int firstWordOfLine; - if (lines->size () > 0 && wrapRefParagraphs > 0) - firstWordOfLine = lines->getRef(wrapRefParagraphs - 1)->lastWord + 1; - else + if (lines->size () > 0 && wrapRefParagraphs > 0) { + // Sometimes, wrapRefParagraphs is larger than lines->size(), due to + // floats? (Has to be clarified.) + int lineNo = misc::min (wrapRefParagraphs, lines->size ()) - 1; + firstWordOfLine = lines->getRef(lineNo)->lastWord + 1; + } else firstWordOfLine = 0; int parNo; @@ -1268,22 +1783,175 @@ void Textblock::fillParagraphs () handleWordExtremes (i); wrapRefParagraphs = -1; + DBG_OBJ_SET_NUM ("wrapRefParagraphs", wrapRefParagraphs); + + DBG_OBJ_MSG_END (); +} + +void Textblock::initNewLine () +{ + DBG_OBJ_MSG ("construct.line", 0, "<b>initNewLine</b> ()"); + DBG_OBJ_MSG_START (); + + // At the very beginning, in Textblock::Textblock, where this + // method is called, containingBlock is not yet defined. + + if (containingBlock && containingBlock->outOfFlowMgr) { + if (lines->size () == 0) { + int clearPosition = + containingBlock->outOfFlowMgr->getClearPosition (this); + setVerticalOffset (misc::max (clearPosition, 0)); + } + } + + calcBorders (lines->size() > 0 ? + lines->getLastRef()->lastOofRefPositionedBeforeThisLine : -1, + 1); + + newLineAscent = newLineDescent = 0; + + DBG_OBJ_SET_NUM ("newLineAscent", newLineAscent); + DBG_OBJ_SET_NUM ("newLineDescent", newLineDescent); + + DBG_OBJ_MSG_END (); +} + +void Textblock::calcBorders (int lastOofRef, int height) +{ + DBG_OBJ_MSGF ("construct.line", 0, "<b>calcBorders</b> (%d, %d)", + lastOofRef, height); + DBG_OBJ_MSG_START (); + + if (containingBlock && containingBlock->outOfFlowMgr) { + // Consider the example: + // + // <div> + // Some text A ... + // <p> Some text B ... <img style="float:right" ...> </p> + // Some more text C ... + // </div> + // + // If the image is large enough, it should float around the last + // paragraph, "Some more text C ...": + // + // Some more text A ... + // + // Some more ,---------. + // text B ... | | + // | <img> | + // Some more | | <---- Consider this line! + // text C ... '---------' + // + // Since this float is generated in the <p> element, not in the- + // <div> element, and since they are represented by different + // instances of dw::Textblock, lastOofRefPositionedBeforeThisLine, + // and so lastOofRef, is -1 for the line marked with an arrow; + // this would result in ignoring the float, because -1 is + // equivalent to the very beginning of the <div> element ("Some + // more text A ..."), which is not affected by the float. + // + // On the other hand, the only relevant values of + // Line::lastOofRefPositionedBeforeThisLine are those greater + // than the first word of the new line, so a solution is to use + // the maximum of both. + + + int firstWordOfLine = lines->size() > 0 ? + lines->getLastRef()->lastWord + 1 : 0; + int effOofRef = misc::max (lastOofRef, firstWordOfLine); + + int y = yOffsetOfPossiblyMissingLine (lines->size ()); + + newLineHasFloatLeft = + containingBlock->outOfFlowMgr->hasFloatLeft (this, y, height, this, + effOofRef); + newLineHasFloatRight = + containingBlock->outOfFlowMgr->hasFloatRight (this, y, height, this, + effOofRef); + newLineLeftBorder = + containingBlock->outOfFlowMgr->getLeftBorder (this, y, height, this, + effOofRef); + newLineRightBorder = + containingBlock->outOfFlowMgr->getRightBorder (this, y, height, this, + effOofRef); + + DBG_OBJ_MSGF ("construct.line", 0, + "%d (%s) / %d (%s), at %d (%d), until %d = max (%d, %d)\n", + newLineLeftBorder, newLineHasFloatLeft ? "true" : "false", + newLineRightBorder, newLineHasFloatRight ? "true" : "false", + y, height, effOofRef, lastOofRef, firstWordOfLine); + } else { + newLineHasFloatLeft = newLineHasFloatRight = false; + newLineLeftBorder = newLineRightBorder = 0; + + DBG_OBJ_MSG ("construct.line", 0, "<i>no CB of OOFM</i>"); + } + + DBG_OBJ_MSG_END (); } void Textblock::showMissingLines () { - int firstWordToWrap = lines->size () > 0 ? - lines->getRef(lines->size () - 1)->lastWord + 1 : 0; - PRINTF ("[%p] SHOW_MISSING_LINES: wrap from %d to %d\n", - this, firstWordToWrap, words->size () - 1); + DBG_OBJ_MSG ("construct.line", 0, "<b>showMissingLines</b> ()"); + DBG_OBJ_MSG_START (); + + // "Temporary word": when the last word is an OOF reference, it is + // not processed, and not part of any line. For this reason, we + // introduce a "temporary word", which is in flow, after this last + // OOF reference, and later removed again. + bool tempWord = words->size () > 0 && + words->getLastRef()->content.type == core::Content::WIDGET_OOF_REF; + int firstWordToWrap = + lines->size () > 0 ? lines->getLastRef()->lastWord + 1 : 0; + + DBG_OBJ_MSGF ("construct.line", 1, + "words->size() = %d, firstWordToWrap = %d, tempWord = %s", + words->size (), firstWordToWrap, tempWord ? "true" : "false"); + + if (tempWord) { + core::Requisition size = { 0, 0, 0 }; + addText0 ("", 0, Word::WORD_START | Word::WORD_END, getStyle (), &size); + } + for (int i = firstWordToWrap; i < words->size (); i++) wordWrap (i, true); + + // Remove temporary word again. The only reference should be the line. + if (tempWord) { + cleanupWord (words->size () - 1); + words->setSize (words->size () - 1); + if (lines->getLastRef()->lastWord > words->size () - 1) + lines->getLastRef()->lastWord = words->size () - 1; + } + + // The following old code should not be necessary anymore, after + // the introduction of the "virtual word". Instead, test the + // condition. + assert (lines->size () == 0 || + lines->getLastRef()->lastWord == words->size () - 1); + /* + // In some cases, there are some words of type WIDGET_OOF_REF left, which + // are not added to line, since addLine() is only called within + // wrapWordInFlow(), but not within wrapWordOofRef(). The missing line + // is created here, so it is ensured that the last line ends with the last + // word. + + int firstWordNotInLine = + lines->size () > 0 ? lines->getLastRef()->lastWord + 1: 0; + DBG_OBJ_MSGF ("construct.line", 1, "firstWordNotInLine = %d (of %d)", + firstWordNotInLine, words->size ()); + if (firstWordNotInLine < words->size ()) + addLine (firstWordNotInLine, words->size () - 1, -1, true); + */ + + DBG_OBJ_MSG_END (); } void Textblock::removeTemporaryLines () { lines->setSize (nonTemporaryLines); + DBG_OBJ_SET_NUM ("lines.size", lines->size ()); } int Textblock::getSpaceShrinkability(struct Word *word) diff --git a/dw/types.cc b/dw/types.cc index 86836bc1..56af66d1 100644 --- a/dw/types.cc +++ b/dw/types.cc @@ -268,5 +268,90 @@ void Region::addRectangle (Rectangle *rPointer) rectangleList->append (r); } +Content::Type Content::maskForSelection (bool followReferences) +{ + Content::Type widgetMask = (Content::Type) + (Content::WIDGET_IN_FLOW | + (followReferences ? Content::WIDGET_OOF_REF : Content::WIDGET_OOF_CONT)); + return (Content::Type)(Content::SELECTION_CONTENT | widgetMask); +} + +void Content::intoStringBuffer(Content *content, misc::StringBuffer *sb) +{ + switch(content->type) { + case START: + sb->append ("<start>"); + break; + case END: + sb->append ("<end>"); + break; + case TEXT: + sb->append ("\""); + sb->append (content->text); + sb->append ("\""); + break; + case WIDGET_IN_FLOW: + sb->append ("<widget in flow: "); + sb->appendPointer (content->widget); + sb->append (" ("); + sb->append (content->widget->getClassName()); + sb->append (")>"); + break; + case WIDGET_OOF_REF: + sb->append ("<widget oof ref: "); + sb->appendPointer (content->widget); + sb->append (" ("); + sb->append (content->widget->getClassName()); + sb->append (")>"); + break; + case WIDGET_OOF_CONT: + sb->append ("<widget oof cont: "); + sb->appendPointer (content->widget); + sb->append (" ("); + sb->append (content->widget->getClassName()); + sb->append (")>"); + break; + case BREAK: + sb->append ("<break>"); + break; + default: + sb->append ("<"); + sb->appendInt (content->type); + sb->append ("?>"); + break; + } +} + +void Content::maskIntoStringBuffer(Type mask, misc::StringBuffer *sb) +{ + sb->append ((mask & START) ? "st" : "--"); + sb->append (":"); + sb->append ((mask & END) ? "en" : "--"); + sb->append (":"); + sb->append ((mask & TEXT) ? "tx" : "--"); + sb->append (":"); + sb->append ((mask & WIDGET_IN_FLOW) ? "wf" : "--"); + sb->append (":"); + sb->append ((mask & WIDGET_OOF_REF) ? "Wr" : "--"); + sb->append (":"); + sb->append ((mask & WIDGET_OOF_CONT) ? "Wc" : "--"); + sb->append (":"); + sb->append ((mask & BREAK) ? "br" : "--"); +} + +void Content::print (Content *content) +{ + misc::StringBuffer sb; + intoStringBuffer (content, &sb); + printf ("%s", sb.getChars ()); +} + +void Content::printMask (Type mask) +{ + misc::StringBuffer sb; + maskIntoStringBuffer (mask, &sb); + printf ("%s", sb.getChars ()); +} + } // namespace core } // namespace dw diff --git a/dw/types.hh b/dw/types.hh index f04fc138..e910d296 100644 --- a/dw/types.hh +++ b/dw/types.hh @@ -188,11 +188,27 @@ struct Content START = 1 << 0, END = 1 << 1, TEXT = 1 << 2, - WIDGET = 1 << 3, - BREAK = 1 << 4, + + /** \brief widget in normal flow, so that _this_ widget + (containing this content) is both container (parent) and + generator */ + WIDGET_IN_FLOW = 1 << 3, + + /** \brief widget out of flow (OOF); _this_ widget (containing + this content) is only the container (parent), but _not_ + generator */ + WIDGET_OOF_CONT = 1 << 4, + + /** \brief reference to a widget out of flow (OOF); _this_ + widget (containing this content) is only the generator + (parent), but _not_ container */ + WIDGET_OOF_REF = 1 << 5, + BREAK = 1 << 6, + ALL = 0xff, REAL_CONTENT = 0xff ^ (START | END), - SELECTION_CONTENT = TEXT | WIDGET | BREAK + SELECTION_CONTENT = TEXT | BREAK, // WIDGET_* must be set additionally + ANY_WIDGET = WIDGET_IN_FLOW | WIDGET_OOF_CONT | WIDGET_OOF_REF, }; /* Content is embedded in struct Word therefore we @@ -205,6 +221,13 @@ struct Content Widget *widget; int breakSpace; }; + + static Content::Type maskForSelection (bool followReferences); + + static void intoStringBuffer(Content *content, lout::misc::StringBuffer *sb); + static void maskIntoStringBuffer(Type mask, lout::misc::StringBuffer *sb); + static void print (Content *content); + static void printMask (Type mask); }; } // namespace core @@ -39,6 +39,7 @@ Embed::Embed(Resource *resource) registerName ("dw::core::ui::Embed", &CLASS_ID); this->resource = resource; resource->setEmbed (this); + DBG_OBJ_ASSOC_CHILD (resource); } Embed::~Embed() @@ -89,17 +90,32 @@ bool Embed::buttonPressImpl (core::EventButton *event) void Embed::setWidth (int width) { + DBG_OBJ_MSGF ("resize", 0, "<b>setWidth</b> (%d)", width); + DBG_OBJ_MSG_START (); + resource->setWidth (width); + + DBG_OBJ_MSG_END (); } void Embed::setAscent (int ascent) { + DBG_OBJ_MSGF ("resize", 0, "<b>setAscent</b> (%d)", ascent); + DBG_OBJ_MSG_START (); + resource->setAscent (ascent); + + DBG_OBJ_MSG_END (); } void Embed::setDescent (int descent) { + DBG_OBJ_MSGF ("resize", 0, "<b>setDescent</b> (%d)", descent); + DBG_OBJ_MSG_START (); + resource->setDescent (descent); + + DBG_OBJ_MSG_END (); } void Embed::setDisplayed (bool displayed) @@ -180,6 +196,7 @@ void Resource::ActivateEmitter::emitLeave (Resource *resource) Resource::~Resource () { + DBG_OBJ_DELETE (); } void Resource::setEmbed (Embed *embed) @@ -189,10 +206,17 @@ void Resource::setEmbed (Embed *embed) void Resource::getExtremes (Extremes *extremes) { + DBG_OBJ_MSG ("resize", 0, "<b>getExtremes</b>"); + DBG_OBJ_MSG_START (); + /* Simply return the requisition width */ Requisition requisition; sizeRequest (&requisition); extremes->minWidth = extremes->maxWidth = requisition.width; + + DBG_OBJ_MSGF ("resize", 1, "result: %d / %d", + extremes->minWidth, extremes->maxWidth); + DBG_OBJ_MSG_END (); } void Resource::sizeAllocate (Allocation *allocation) @@ -271,11 +295,12 @@ void ComplexButtonResource::LayoutReceiver::canvasSizeChanged (int width, /** * \todo Verify that this is correct. */ - resource->queueResize (resource->childWidget->extremesChanged ()); + resource->queueResize (resource->childWidget->extremesQueued ()); } ComplexButtonResource::ComplexButtonResource () { + DBG_OBJ_CREATE ("dw::core::ui::ComplexButtonResource"); layout = NULL; layoutReceiver.resource = this; click_x = click_y = -1; @@ -287,6 +312,7 @@ void ComplexButtonResource::init (Widget *widget) layout = new Layout (createPlatform ()); setLayout (layout); + DBG_OBJ_ASSOC_CHILD (layout); layout->setWidget (widget); layout->connect (&layoutReceiver); } @@ -302,23 +328,38 @@ void ComplexButtonResource::setEmbed (Embed *embed) ComplexButtonResource::~ComplexButtonResource () { delete layout; + DBG_OBJ_DELETE (); } void ComplexButtonResource::sizeRequest (Requisition *requisition) { + DBG_OBJ_MSG ("resize", 0, "<b>sizeRequest</b>"); + DBG_OBJ_MSG_START (); + Requisition widgetRequisition; childWidget->sizeRequest (&widgetRequisition); requisition->width = widgetRequisition.width + 2 * reliefXThickness (); requisition->ascent = widgetRequisition.ascent + reliefYThickness (); requisition->descent = widgetRequisition.descent + reliefYThickness (); + + DBG_OBJ_MSGF ("resize", 1, "result: %d * (%d + %d)", + requisition->width, requisition->ascent, requisition->descent); + DBG_OBJ_MSG_END (); } void ComplexButtonResource::getExtremes (Extremes *extremes) { + DBG_OBJ_MSG ("resize", 0, "<b>getExtremes</b>"); + DBG_OBJ_MSG_START (); + Extremes widgetExtremes; childWidget->getExtremes (&widgetExtremes); extremes->minWidth = widgetExtremes.minWidth + 2 * reliefXThickness (); extremes->maxWidth = widgetExtremes.maxWidth + 2 * reliefXThickness (); + + DBG_OBJ_MSGF ("resize", 1, "result: %d / %d", + extremes->minWidth, extremes->maxWidth); + DBG_OBJ_MSG_END (); } void ComplexButtonResource::sizeAllocate (Allocation *allocation) @@ -327,17 +368,32 @@ void ComplexButtonResource::sizeAllocate (Allocation *allocation) void ComplexButtonResource::setWidth (int width) { + DBG_OBJ_MSGF ("resize", 0, "<b>setWidth</b> (%d)", width); + DBG_OBJ_MSG_START (); + childWidget->setWidth (width - 2 * reliefXThickness ()); + + DBG_OBJ_MSG_END (); } void ComplexButtonResource::setAscent (int ascent) { + DBG_OBJ_MSGF ("resize", 0, "<b>setAscent</b> (%d)", ascent); + DBG_OBJ_MSG_START (); + childWidget->setAscent (ascent - reliefYThickness ()); + + DBG_OBJ_MSG_END (); } void ComplexButtonResource::setDescent (int descent) { + DBG_OBJ_MSGF ("resize", 0, "<b>setDescent</b> (%d)", descent); + DBG_OBJ_MSG_START (); + childWidget->setDescent (descent - reliefYThickness ()); + + DBG_OBJ_MSG_END (); } Iterator *ComplexButtonResource::iterator (Content::Type mask, bool atEnd) @@ -329,7 +329,8 @@ protected: clickedEmitter.emitClicked (this, event); } public: - inline Resource () { embed = NULL; } + inline Resource () + { embed = NULL; DBG_OBJ_CREATE ("dw::core::ui::Resource"); } virtual ~Resource (); diff --git a/dw/widget.cc b/dw/widget.cc index 385bdb97..ba49c158 100644 --- a/dw/widget.cc +++ b/dw/widget.cc @@ -70,7 +70,7 @@ Widget::Widget () registerName ("dw::core::Widget", &CLASS_ID); flags = (Flags)(NEEDS_RESIZE | EXTREMES_CHANGED | HAS_CONTENTS); - parent = NULL; + parent = generator = NULL; layout = NULL; allocation.x = -1; @@ -106,7 +106,7 @@ Widget::~Widget () if (parent) parent->removeChild (this); - else + else if (layout) layout->removeWidget (); DBG_OBJ_DELETE (); @@ -150,15 +150,17 @@ void Widget::setParent (Widget *parent) buttonSensitive = parent->buttonSensitive; DBG_OBJ_ASSOC_PARENT (parent); - //printf ("The %s %p becomes a child of the %s %p\n", // getClassName(), this, parent->getClassName(), parent); + + notifySetParent(); } 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); + if (layout) + layout->queueDraw (x + allocation.x, y + allocation.y, width, height); _MSG("Widget::queueDrawArea x=%d y=%d w=%d h=%d\n", x, y, width, height); } @@ -167,40 +169,98 @@ void Widget::queueDrawArea (int x, int y, int width, int height) */ void Widget::queueResize (int ref, bool extremesChanged) { + DBG_OBJ_MSGF ("resize", 0, "<b>queueResize</b> (%d, %s)", + ref, extremesChanged ? "true" : "false"); + DBG_OBJ_MSG_START (); + + // queueResize() can be called recursively; calls are queued, so + // that actualQueueResize() is clean. + + if (queueResizeEntered ()) { + DBG_OBJ_MSG ("resize", 1, "put into queue"); + layout->queueQueueResizeList->put (new Layout::QueueResizeItem + (this, ref, extremesChanged)); + } else { + actualQueueResize (ref, extremesChanged); + + while (layout != NULL && layout->queueQueueResizeList->size () > 0) { + Layout::QueueResizeItem *item = layout->queueQueueResizeList->get (0); + DBG_OBJ_MSGF ("resize", 1, "taken out of queue queue (size = %d)", + layout->queueQueueResizeList->size ()); + item->widget->actualQueueResize (item->ref, item->extremesChanged); + layout->queueQueueResizeList->remove (0); // hopefully not too large + } + } + + DBG_OBJ_MSG_END (); +} + +void Widget::actualQueueResize (int ref, bool extremesChanged) +{ + assert (!queueResizeEntered ()); + + DBG_OBJ_MSGF ("resize", 0, "<b>actualQueueResize</b> (%d, %s)", + ref, extremesChanged ? "true" : "false"); + DBG_OBJ_MSG_START (); + + enterQueueResize (); + Widget *widget2, *child; - //printf("The %stop-level %s %p with parentRef = %d has changed its size.\n", - // parent ? "non-" : "", getClassName(), this, parentRef); + //printf("The %stop-level %s %p with parentRef = %d has changed its size. " + // "Layout = %p.\n", + // parent ? "non-" : "", getClassName(), this, parentRef, layout); - setFlags (NEEDS_RESIZE); - setFlags (NEEDS_ALLOCATE); - markSizeChange (ref); + Flags resizeFlag, extremesFlag; + if (layout) { + // If RESIZE_QUEUED is set, this widget is already in the list. + if (!resizeQueued ()) + layout->queueResizeList->put (this); + + resizeFlag = RESIZE_QUEUED; + extremesFlag = EXTREMES_QUEUED; + } else { + resizeFlag = NEEDS_RESIZE; + extremesFlag = EXTREMES_CHANGED; + } + + setFlags (resizeFlag); + setFlags (ALLOCATE_QUEUED); + markSizeChange (ref); + if (extremesChanged) { - setFlags (EXTREMES_CHANGED); + setFlags (extremesFlag); 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); - - //printf (" Setting DW_NEEDS_RESIZE and NEEDS_ALLOCATE for the " + + for (widget2 = parent, child = this; widget2; + child = widget2, widget2 = widget2->parent) { + //printf (" Setting %s and ALLOCATE_QUEUED for the " // "%stop-level %s %p with parentRef = %d\n", + // resizeFlag == RESIZE_QUEUED ? "RESIZE_QUEUED" : "NEEDS_RESIZE", // widget2->parent ? "non-" : "", widget2->getClassName(), widget2, // widget2->parentRef); + if (layout && !widget2->resizeQueued ()) + layout->queueResizeList->put (widget2); + + widget2->setFlags (resizeFlag); + widget2->markSizeChange (child->parentRef); + widget2->setFlags (ALLOCATE_QUEUED); + if (extremesChanged) { - widget2->setFlags (EXTREMES_CHANGED); + widget2->setFlags (extremesFlag); widget2->markExtremesChange (child->parentRef); } } if (layout) layout->queueResize (); + + leaveQueueResize (); + + DBG_OBJ_MSG_END (); } @@ -210,6 +270,28 @@ void Widget::queueResize (int ref, bool extremesChanged) */ void Widget::sizeRequest (Requisition *requisition) { + assert (!queueResizeEntered ()); + + DBG_OBJ_MSG ("resize", 0, "<b>sizeRequest</b>"); + DBG_OBJ_MSG_START (); + + enterSizeRequest (); + + //printf ("The %stop-level %s %p with parentRef = %d: needsResize: %s, " + // "resizeQueued = %s\n", + // parent ? "non-" : "", getClassName(), this, parentRef, + // needsResize () ? "true" : "false", + // resizeQueued () ? "true" : "false"); + + if (resizeQueued ()) { + // This method is called outside of Layout::resizeIdle. + setFlags (NEEDS_RESIZE); + unsetFlags (RESIZE_QUEUED); + // The widget is not taken out of Layout::queueResizeList, since + // other *_QUEUED flags may still be set and processed in + // Layout::resizeIdle. + } + if (needsResize ()) { /** \todo Check requisition == &(this->requisition) and do what? */ sizeRequestImpl (requisition); @@ -221,6 +303,13 @@ void Widget::sizeRequest (Requisition *requisition) DBG_OBJ_SET_NUM ("requisition.descent", requisition->descent); } else *requisition = this->requisition; + + //printf (" ==> Result: %d x (%d + %d)\n", + // requisition->width, requisition->ascent, requisition->descent); + + leaveSizeRequest (); + + DBG_OBJ_MSG_END (); } /** @@ -228,6 +317,22 @@ void Widget::sizeRequest (Requisition *requisition) */ void Widget::getExtremes (Extremes *extremes) { + assert (!queueResizeEntered ()); + + DBG_OBJ_MSG ("resize", 0, "<b>getExtremes</b>"); + DBG_OBJ_MSG_START (); + + enterGetExtremes (); + + if (extremesQueued ()) { + // This method is called outside of Layout::resizeIdle. + setFlags (EXTREMES_CHANGED); + unsetFlags (EXTREMES_QUEUED); + // The widget is not taken out of Layout::queueResizeList, since + // other *_QUEUED flags may still be set and processed in + // Layout::resizeIdle. + } + if (extremesChanged ()) { getExtremesImpl (extremes); this->extremes = *extremes; @@ -237,6 +342,10 @@ void Widget::getExtremes (Extremes *extremes) DBG_OBJ_SET_NUM ("extremes.maxWidth", extremes->maxWidth); } else *extremes = this->extremes; + + leaveGetExtremes (); + + DBG_OBJ_MSG_END (); } /** @@ -245,6 +354,28 @@ void Widget::getExtremes (Extremes *extremes) */ void Widget::sizeAllocate (Allocation *allocation) { + assert (!queueResizeEntered ()); + assert (!sizeRequestEntered ()); + assert (!getExtremesEntered ()); + assert (resizeIdleEntered ()); + + DBG_OBJ_MSGF ("resize", 0, "<b>sizeAllocate</b> (%d, %d; %d * (%d + %d))", + allocation->x, allocation->y, allocation->width, + allocation->ascent, allocation->descent); + DBG_OBJ_MSG_START (); + + enterSizeAllocate (); + + /*printf ("The %stop-level %s %p is allocated:\n", + parent ? "non-" : "", getClassName(), this); + printf (" old = (%d, %d, %d + (%d + %d))\n", + this->allocation.x, this->allocation.y, this->allocation.width, + this->allocation.ascent, this->allocation.descent); + printf (" new = (%d, %d, %d + (%d + %d))\n", + allocation->x, allocation->y, allocation->width, allocation->ascent, + allocation->descent); + printf (" NEEDS_ALLOCATE = %s\n", needsAllocate () ? "true" : "false");*/ + if (needsAllocate () || allocation->x != this->allocation.x || allocation->y != this->allocation.y || @@ -285,6 +416,10 @@ void Widget::sizeAllocate (Allocation *allocation) } /*unsetFlags (NEEDS_RESIZE);*/ + + leaveSizeAllocate (); + + DBG_OBJ_MSG_END (); } bool Widget::buttonPress (EventButton *event) @@ -507,6 +642,25 @@ int Widget::getLevel () } /** + * \brief Get the level of the widget within the tree, regarting the + * generators, not the parents. + * + * The root widget has the level 0. + */ +int Widget::getGeneratorLevel () +{ + Widget *widget = this; + int level = 0; + + while (widget->getGenerator ()) { + level++; + widget = widget->getGenerator (); + } + + return level; +} + +/** * \brief Get the widget with the highest level, which is a direct ancestor of * widget1 and widget2. */ @@ -566,7 +720,9 @@ Widget *Widget::getWidgetAtPoint (int x, int y, int level) * is such a child, it is returned. Otherwise, this widget is returned. */ childAtPoint = NULL; - it = iterator (Content::WIDGET, false); + it = iterator ((Content::Type) + (Content::WIDGET_IN_FLOW | Content::WIDGET_OOF_CONT), + false); while (childAtPoint == NULL && it->next ()) childAtPoint = it->getContent()->widget->getWidgetAtPoint (x, y, @@ -627,6 +783,25 @@ void Widget::markExtremesChange (int ref) { } +/** + * \brief This method is called after a widget has been set as the top of a + * widget tree. + * + * A widget may override this method when it is necessary to be notified. + */ +void Widget::notifySetAsTopLevel() +{ +} + +/** + * \brief This method is called after a widget has been added to a parent. + * + * A widget may override this method when it is necessary to be notified. + */ +void Widget::notifySetParent() +{ +} + void Widget::setWidth (int width) { } diff --git a/dw/widget.hh b/dw/widget.hh index 34b35efa..e7ac7f55 100644 --- a/dw/widget.hh +++ b/dw/widget.hh @@ -27,23 +27,46 @@ class Widget: public lout::identity::IdentifiableObject protected: enum Flags { /** + * \todo Comment this. + */ + RESIZE_QUEUED = 1 << 0, + + /** + * \todo Comment this. + */ + EXTREMES_QUEUED = 1 << 1, + + /** * \brief Set, when dw::core::Widget::requisition is not up to date * anymore. + * + * \todo Update, see RESIZE_QUEUED. */ - NEEDS_RESIZE = 1 << 0, + NEEDS_RESIZE = 1 << 2, /** * \brief Only used internally, set to enforce size allocation. * - * (I've forgotten the case, for which this is necessary.) + * In some cases, the size of a widget remains the same, but the + * children are allocated at different positions and in + * different sizes, so that a simple comparison of old and new + * allocation is insufficient. Therefore, this flag is set + * (indirectly, as ALLOCATE_QUEUED) in queueResize. */ - NEEDS_ALLOCATE = 1 << 1, + NEEDS_ALLOCATE = 1 << 3, + + /** + * \todo Comment this. + */ + ALLOCATE_QUEUED = 1 << 4, /** * \brief Set, when dw::core::Widget::extremes is not up to date * anymore. + * + * \todo Update, see RESIZE_QUEUED. */ - EXTREMES_CHANGED = 1 << 2, + EXTREMES_CHANGED = 1 << 5, /** * \brief Set by the widget itself (in the constructor), when set... @@ -51,7 +74,7 @@ protected: * * Will hopefully be removed, after redesigning the size model. */ - USES_HINTS = 1 << 3, + USES_HINTS = 1 << 6, /** * \brief Set by the widget itself (in the constructor), when it contains @@ -59,19 +82,19 @@ protected: * * Will hopefully be removed, after redesigning the size model. */ - HAS_CONTENTS = 1 << 4, + HAS_CONTENTS = 1 << 7, /** * \brief Set, when a widget was already once allocated, * * The dw::Image widget uses this flag, see dw::Image::setBuffer. */ - WAS_ALLOCATED = 1 << 5, + WAS_ALLOCATED = 1 << 8, /** * \brief Set for block-level widgets (as opposed to inline widgets) */ - BLOCK_LEVEL = 1 << 6, + BLOCK_LEVEL = 1 << 9, }; /** @@ -101,6 +124,14 @@ private: * \brief The parent widget, NULL for top-level widgets. */ Widget *parent; + + /** + * \brief The generating widget, NULL for top-level widgets, or if + * not set; in the latter case, the effective generator (see + * getGenerator) is the parent. + */ + Widget *generator; + style::Style *style; Flags flags; @@ -133,6 +164,8 @@ private: */ bool buttonSensitiveSet; + void actualQueueResize (int ref, bool extremesChanged); + public: /** * \brief This value is defined by the parent widget, and used for @@ -200,6 +233,9 @@ protected: */ virtual void markExtremesChange (int ref); + virtual void notifySetAsTopLevel(); + virtual void notifySetParent(); + virtual bool buttonPressImpl (EventButton *event); virtual bool buttonReleaseImpl (EventButton *event); virtual bool motionNotifyImpl (EventMotion *event); @@ -216,7 +252,7 @@ protected: { layout->changeAnchor (this, name, y); } inline void removeAnchor (char* name) - { layout->removeAnchor (this, name); } + { if (layout) layout->removeAnchor (this, name); } //inline void updateBgColor () { layout->updateBgColor (); } @@ -249,14 +285,37 @@ public: inline void setDeleteCallback(DW_Callback_t func, void *data) { deleteCallbackFunc = func; deleteCallbackData = data; } +private: + bool resizeIdleEntered () { return layout && layout->resizeIdleCounter; } + + void enterQueueResize () { if (layout) layout->queueResizeCounter++; } + void leaveQueueResize () { if (layout) layout->queueResizeCounter--; } + bool queueResizeEntered () { return layout && layout->queueResizeCounter; } + + void enterSizeAllocate () { if (layout) layout->sizeAllocateCounter++; } + void leaveSizeAllocate () { if (layout) layout->sizeAllocateCounter--; } + bool sizeAllocateEntered () { return layout && layout->sizeAllocateCounter; } + + void enterSizeRequest () { if (layout) layout->sizeRequestCounter++; } + void leaveSizeRequest () { if (layout) layout->sizeRequestCounter--; } + bool sizeRequestEntered () { return layout && layout->sizeRequestCounter; } + + void enterGetExtremes () { if (layout) layout->getExtremesCounter++; } + void leaveGetExtremes () { if (layout) layout->getExtremesCounter--; } + bool getExtremesEntered () { return layout && layout->getExtremesCounter; } + + public: static int CLASS_ID; Widget (); ~Widget (); + inline bool resizeQueued () { return flags & RESIZE_QUEUED; } + inline bool extremesQueued () { return flags & EXTREMES_QUEUED; } inline bool needsResize () { return flags & NEEDS_RESIZE; } inline bool needsAllocate () { return flags & NEEDS_ALLOCATE; } + inline bool allocateQueued () { return flags & ALLOCATE_QUEUED; } inline bool extremesChanged () { return flags & EXTREMES_CHANGED; } inline bool wasAllocated () { return flags & WAS_ALLOCATED; } inline bool usesHints () { return flags & USES_HINTS; } @@ -265,6 +324,8 @@ public: void setParent (Widget *parent); + void setGenerator (Widget *generator) { this->generator = generator; } + inline style::Style *getStyle () { return style; } /** \todo I do not like this. */ inline Allocation *getAllocation () { return &allocation; } @@ -302,8 +363,11 @@ public: inline Widget *getParent () { return parent; } Widget *getTopLevel (); int getLevel (); + int getGeneratorLevel (); Widget *getNearestCommonAncestor (Widget *otherWidget); + inline Widget *getGenerator () { return generator ? generator : parent; } + inline Layout *getLayout () { return layout; } virtual Widget *getWidgetAtPoint (int x, int y, int level); |