aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog129
-rw-r--r--Doxyfile1143
-rw-r--r--Makefile.am4
-rw-r--r--configure.in10
-rw-r--r--doc/Makefile.am61
-rw-r--r--doc/dw-changes.doc109
-rw-r--r--doc/dw-images-and-backgrounds.doc146
-rw-r--r--doc/dw-layout-views.doc270
-rw-r--r--doc/dw-layout-widgets.doc270
-rw-r--r--doc/dw-map.doc59
-rw-r--r--doc/dw-overview.doc157
-rw-r--r--doc/dw-usage.doc373
-rw-r--r--doc/dw-widget-sizes.doc186
-rw-r--r--doc/fltk-problems.doc219
-rw-r--r--doc/index.doc48
-rw-r--r--doc/lout.doc94
-rw-r--r--doc/rounding-errors.doc24
-rw-r--r--doc/uml-legend.doc192
-rw-r--r--dw/Makefile.am70
-rw-r--r--dw/alignedtextblock.cc105
-rw-r--r--dw/alignedtextblock.hh61
-rw-r--r--dw/bullet.cc72
-rw-r--r--dw/bullet.hh27
-rw-r--r--dw/core.hh58
-rw-r--r--dw/events.hh83
-rw-r--r--dw/findtext.cc209
-rw-r--r--dw/findtext.hh82
-rw-r--r--dw/fltkcomplexbutton.cc282
-rw-r--r--dw/fltkcomplexbutton.hh59
-rw-r--r--dw/fltkcore.hh25
-rw-r--r--dw/fltkflatview.cc108
-rw-r--r--dw/fltkflatview.hh40
-rw-r--r--dw/fltkimgbuf.cc347
-rw-r--r--dw/fltkimgbuf.hh65
-rw-r--r--dw/fltkmisc.cc50
-rw-r--r--dw/fltkmisc.hh22
-rw-r--r--dw/fltkplatform.cc421
-rw-r--r--dw/fltkplatform.hh150
-rw-r--r--dw/fltkpreview.cc298
-rw-r--r--dw/fltkpreview.hh89
-rw-r--r--dw/fltkui.cc1202
-rw-r--r--dw/fltkui.hh554
-rw-r--r--dw/fltkviewbase.cc524
-rw-r--r--dw/fltkviewbase.hh108
-rw-r--r--dw/fltkviewport.cc496
-rw-r--r--dw/fltkviewport.hh77
-rw-r--r--dw/image.cc392
-rw-r--r--dw/image.hh161
-rw-r--r--dw/imgbuf.hh210
-rw-r--r--dw/iterator.cc797
-rw-r--r--dw/iterator.hh256
-rw-r--r--dw/layout.cc918
-rw-r--r--dw/layout.hh264
-rw-r--r--dw/listitem.cc72
-rw-r--r--dw/listitem.hh27
-rw-r--r--dw/platform.hh144
-rw-r--r--dw/preview.xbm5
-rw-r--r--dw/ruler.cc53
-rw-r--r--dw/ruler.hh30
-rw-r--r--dw/selection.cc514
-rw-r--r--dw/selection.hh275
-rw-r--r--dw/style.cc632
-rw-r--r--dw/style.hh669
-rw-r--r--dw/table.cc1192
-rw-r--r--dw/table.hh486
-rw-r--r--dw/tablecell.cc108
-rw-r--r--dw/tablecell.hh29
-rw-r--r--dw/textblock.cc2087
-rw-r--r--dw/textblock.hh387
-rw-r--r--dw/types.cc260
-rw-r--r--dw/types.hh204
-rw-r--r--dw/ui.cc364
-rw-r--r--dw/ui.hh564
-rw-r--r--dw/view.hh204
-rw-r--r--dw/widget.cc822
-rw-r--r--dw/widget.hh465
-rw-r--r--lout/Makefile.am14
-rw-r--r--lout/container.cc558
-rw-r--r--lout/container.hh441
-rw-r--r--lout/debug.hh152
-rw-r--r--lout/identity.cc114
-rw-r--r--lout/identity.hh148
-rw-r--r--lout/misc.cc237
-rw-r--r--lout/misc.hh320
-rw-r--r--lout/object.cc311
-rw-r--r--lout/object.hh161
-rw-r--r--lout/signal.cc171
-rw-r--r--lout/signal.hh310
-rw-r--r--src/Makefile.am12
-rw-r--r--test/Makefile.am153
-rw-r--r--test/dw_anchors_test.cc162
-rw-r--r--test/dw_border_test.cc125
-rw-r--r--test/dw_example.cc103
-rw-r--r--test/dw_find_test.cc151
-rw-r--r--test/dw_images_scaled.cc153
-rw-r--r--test/dw_images_scaled2.cc148
-rw-r--r--test/dw_images_simple.cc151
-rw-r--r--test/dw_imgbuf_mem_test.cc114
-rw-r--r--test/dw_links.cc158
-rw-r--r--test/dw_links2.cc186
-rw-r--r--test/dw_lists.cc134
-rw-r--r--test/dw_resource_test.cc98
-rw-r--r--test/dw_table.cc115
-rw-r--r--test/dw_table_aligned.cc119
-rw-r--r--test/dw_ui_test.cc241
-rw-r--r--test/fltk_browser.cc47
-rw-r--r--test/form.cc264
-rw-r--r--test/form.hh164
-rw-r--r--test/shapes.cc41
109 files changed, 27518 insertions, 27 deletions
diff --git a/ChangeLog b/ChangeLog
index 9cf8ad83..3a554998 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -181,6 +181,135 @@ dillo-fltk2
- test no_proxy (set a list in dillorc).
-----------------------------------------------------------------------------
+dw
+
+0.0.43
+ - Fixed bug in dw::core::ExtIterator (wrong mask, see also Jorge's
+ patch "createvar.diff" from Nov 08).
+ - Applied Jorge's patch for dw::core::AlignedTextblock
+ ("lists.diff" in mail from Nov 08).
+ - Applied Jorge's patch for dw::core::Textblock ("links.diff" in
+ mail from Nov 08).
+ - Applied Jorge's patch for configure.in ("conf.diff" in mail from
+ Nov 08).
+ - Renamed ExtIterator to DeepIterator.
+ - Implemented CharIterator (as an alternative to word iterators).
+ - Implemented text search (simple KMP based on CharIterator).
+ - Completed scrolling.
+ Patches: Sebastian Geerken
+
+ + Implemented drag scrolling with mouse's middle button.
+ - Enabled commented out partial image redraw, adding some checks.
+ - Enabled clipped redraws (avoids some flickering).
+ - Improvement: avoid complete redraws for child widget updates.
+ - Added code to really delete fltk2 widgets embedded in dw2.
+ - Fixed partial redraws and scrolling interference.
+ - Added combination of drawing rectangles into a larger one.
+ - Bug fix: a newly added rectangle may contain others.
+ - Made draw() check whether a rectangle is visible at drawing time.
+ - The background is now cleared properly on partial redraws.
+ - Made getWidgetAtPoint() a virtual method of widget and implemented a
+ custom one for TextBlock, reducing CPU usage on pages full of links.
+ - Added a style reference and an initialization to mustQueueResize.
+ - Replaced prepareCreateFltkWidget with an explicit call to add().
+ - Fixed an assertion-exit bug in DeepIterator.
+ - Fixed two viewport bugs: in drawing and scrolling.
+ - Made scrollbars really children of FltkViewport.
+ - Avoided multiple redraws when Layout::resizeIdle() queues itself.
+ - Set FltkViewBase::draw to intersect with view area for expose.
+ - Set cursor shape to CURSOR_MOVE on drag. Disabled drag over links.
+ - Added Layout::queueDrawExcept(), it reduces flickering by avoiding
+ a redraw when another rectangle is added.
+ - Fixed a scrollIdleId test to properly compare against -1.
+ - Set FltkPlatform::removeIdle to use removeRef() instead of remove().
+ - Cleaned up scroll code and moved updateCanvasWidgets() out of draw().
+ - Switched begin-end pairs with add() calls (fixes side-effect bugs!).
+ - Fixed checks in adjustScrollPos() to not allow wild values.
+ - Added double buffering for partial redraws!
+ - Implemented ComplexButton.
+ - Fixed find text so it works for phrases and PRE-wrapped text.
+ - Fixed a bug in DeepIterator::prev.
+ - Added the "lout" namespace.
+ - Reduced memory usage in 30% by reusing styles, reducing the size
+ of struct Content, and not preallocating in SimpleVector. !
+ - Made fontsTable and colorsTable static members of Font and Color.
+ - Moved highlighting information from struct Word into Textblock
+ to save memory.
+ - Reduced memory usage 10% with a custom memory handler in Textblock.
+ - Fixed a segfault when searching for single characters.
+ - Fixed memory leaks by s/delete/delete[]/ where necessary.
+ - Fixed three iterator memory leaks in Iterator::scrollTo().
+ - Changed DeepIterator to always clone its parameter (segfault bug).
+ - Implemented selection of multibyte glyphs (UTF-8).
+ - Removed the canvasWidgets list (fltk's children seem enough).
+ - Switched misc:assert() to the standard assert() call.
+ Patches: Johannes Hofmann
+ + Fixed a segfault-on-empty-strings bug in ConstString::hashValue.
+ - Fixed a segfault in reallocChildren (colspan/rowspan related).
+ - Fixed another assertion-exit bug in DeepIterator.
+ - Added the dw::fltk::ui::FltkMultiLineTextResource class.
+ - Implemented TEXTAREA using fltk::TextEditor.
+ - Bugfix: added the missing fltk::setfont calls before ::getwidth.
+ - Bugfix: initialized scrollIdleNotInterrupted variable.
+ - Commented out obsolete DEBUG_MSG lines in widget.cc.
+ - Fixed rowspan apportion when no single rowspan=1 row is found.
+ - Fixed allocateFltkWidget to handle and display FltkListResource.
+ - Fixed a slithery BUG in lout::misc::Stringbuffer.
+ - Implemented multiple item selection in FltkSelectionResource.
+ Patches: Jeremy Henty
+ + Added an extra argument in the link signals
+ (I recommended that instead of an array of image handlers --jcid)
+ - Aded an x_img camp to style (an image array index, like x_link).
+ - Added the same workaround in ui.cc for WHEN_ENTER_KEY_ALWAYS.
+ - Fixed shading (style.cc) and implemented FltkViewBase::drawPolygon().
+ - Implemented Circle and Disk bullet drawing.
+ - Fixed a bug in FltkViewBase::getClippingView.
+ - Made FltkColor::FltkColor use ::fltk::BLACK (bugfix).
+ - Fixed a bug with dissappearing widgets when scrolling with low CPU.
+ - Fixed a bug with the canvas offset of scrolling bars.
+ - Fixed a typo bug in scrollIdle() and a typo in processMouseEvent().
+ - Fixed an offset arithmetic bug with widgets inside textblock.
+ - Fixed RTFL debugging messages.
+ - Switched ComplexButton to use "Activate" instead of "Clicked" signal.
+ - Made the ComplexButton resource remember its click x,y.
+ - Added "enter" and "leave" signals into class Resource.
+ Patches: place
+ + Enabled mouse wheel scrolling.
+ FltkViewport::setScrollStep() sets how many points at a time.
+ - Added setDeleteCallback(DW_Callback_t func, void *data) to widget.
+ This allows to hook a callback when the widget is destroyed.
+ - Implemented a weighted apportionment algorithm for table rowspan.
+ - Implemented a weighted apportionment algorithm for table colspan.
+ - Implemented percentage widths in tables (better rendering!).
+ - Fixed an initialization bug in hruler.
+ - Fixed a bug in the textblock's wrapping algorithm.
+ - Fixed a bug in table cellpadding.
+ - Fixed a bug in getContentHeight().
+ - Changed the table-apportion algorithms + bug fixes. Big work!
+ - Fixed a mistake in the CSS-box-model PNG image (style-box-model.png).
+ - Added initialization for scrollX and scrollY.
+ - Fixed a typo bug in adjustScrollPos().
+ - Fixed two typo bugs in Textblock::drawLine().
+ - Changed Textblock::addText() to internally allocate its text string,
+ making the memory handling opaque to the caller.
+ Patches: Jorge Arellano Cid
+ + Added actual text selection.
+ Patch: Sebastian Geerken, place
+ + Implemented dw::fltk::ui::FltkOptionMenuResource::isSelected(),
+ added Item::createNewGroupWidget(), Item::createNewWidget().
+ Patch: Jeremy Henty, Johannes Hofmann
+ + Implemented the necessary base for image maps.
+ Patch: Johannes Hofmann, place
+
+0.0.42
+ - Fixed event handling in FLTK views. (Fixes links and several
+ problems with UI resources.)
+ - Implemented clipping views. (dw::Image used this already in
+ version 0.0.41.)
+ - Added "activated" signals to UI resources.
+ Patches: Sebastian Geerken
+
+-----------------------------------------------------------------------------
0.8.5-pre-dw-redesign-1 [internal]
diff --git a/Doxyfile b/Doxyfile
new file mode 100644
index 00000000..8aae5d7f
--- /dev/null
+++ b/Doxyfile
@@ -0,0 +1,1143 @@
+# Doxyfile 1.3.7
+
+# This file describes the settings to be used by the documentation system
+# doxygen (www.doxygen.org) for a project
+#
+# All text after a hash (#) is considered a comment and will be ignored
+# The format is:
+# TAG = value [value, ...]
+# For lists items can also be appended using:
+# TAG += value [value, ...]
+# Values that contain spaces should be placed between quotes (" ")
+
+#---------------------------------------------------------------------------
+# Project related configuration options
+#---------------------------------------------------------------------------
+
+# The PROJECT_NAME tag is a single word (or a sequence of words surrounded
+# by quotes) that should identify the project.
+
+PROJECT_NAME = Dillo
+
+# The PROJECT_NUMBER tag can be used to enter a project or revision number.
+# This could be handy for archiving the generated documentation or
+# if some version control system is used.
+
+PROJECT_NUMBER =
+
+# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute)
+# base path where the generated documentation will be put.
+# If a relative path is entered, it will be relative to the location
+# where doxygen was started. If left blank the current directory will be used.
+
+OUTPUT_DIRECTORY =
+
+# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create
+# 2 levels of 10 sub-directories under the output directory of each output
+# format and will distribute the generated files over these directories.
+# Enabling this option can be useful when feeding doxygen a huge amount of source
+# files, where putting all generated files in the same directory would otherwise
+# cause performance problems for the file system.
+
+CREATE_SUBDIRS = NO
+
+# The OUTPUT_LANGUAGE tag is used to specify the language in which all
+# documentation generated by doxygen is written. Doxygen will use this
+# information to generate all constant output in the proper language.
+# The default language is English, other supported languages are:
+# Brazilian, Catalan, Chinese, Chinese-Traditional, Croatian, Czech, Danish, Dutch,
+# Finnish, French, German, Greek, Hungarian, Italian, Japanese, Japanese-en
+# (Japanese with English messages), Korean, Korean-en, Norwegian, Polish, Portuguese,
+# Romanian, Russian, Serbian, Slovak, Slovene, Spanish, Swedish, and Ukrainian.
+
+OUTPUT_LANGUAGE = English
+
+# This tag can be used to specify the encoding used in the generated output.
+# The encoding is not always determined by the language that is chosen,
+# but also whether or not the output is meant for Windows or non-Windows users.
+# In case there is a difference, setting the USE_WINDOWS_ENCODING tag to YES
+# forces the Windows encoding (this is the default for the Windows binary),
+# whereas setting the tag to NO uses a Unix-style encoding (the default for
+# all platforms other than Windows).
+
+USE_WINDOWS_ENCODING = NO
+
+# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will
+# include brief member descriptions after the members that are listed in
+# the file and class documentation (similar to JavaDoc).
+# Set to NO to disable this.
+
+BRIEF_MEMBER_DESC = YES
+
+# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend
+# the brief description of a member or function before the detailed description.
+# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the
+# brief descriptions will be completely suppressed.
+
+REPEAT_BRIEF = YES
+
+# This tag implements a quasi-intelligent brief description abbreviator
+# that is used to form the text in various listings. Each string
+# in this list, if found as the leading text of the brief description, will be
+# stripped from the text and the result after processing the whole list, is used
+# as the annotated text. Otherwise, the brief description is used as-is. If left
+# blank, the following values are used ("$name" is automatically replaced with the
+# name of the entity): "The $name class" "The $name widget" "The $name file"
+# "is" "provides" "specifies" "contains" "represents" "a" "an" "the"
+
+ABBREVIATE_BRIEF =
+
+# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then
+# Doxygen will generate a detailed section even if there is only a brief
+# description.
+
+ALWAYS_DETAILED_SEC = NO
+
+# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all inherited
+# members of a class in the documentation of that class as if those members were
+# ordinary class members. Constructors, destructors and assignment operators of
+# the base classes will not be shown.
+
+INLINE_INHERITED_MEMB = NO
+
+# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full
+# path before files name in the file list and in the header files. If set
+# to NO the shortest path that makes the file name unique will be used.
+
+FULL_PATH_NAMES = YES
+
+# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag
+# can be used to strip a user-defined part of the path. Stripping is
+# only done if one of the specified strings matches the left-hand part of
+# the path. The tag can be used to show relative paths in the file list.
+# If left blank the directory from which doxygen is run is used as the
+# path to strip.
+
+STRIP_FROM_PATH =
+
+# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of
+# the path mentioned in the documentation of a class, which tells
+# the reader which header file to include in order to use a class.
+# If left blank only the name of the header file containing the class
+# definition is used. Otherwise one should specify the include paths that
+# are normally passed to the compiler using the -I flag.
+
+STRIP_FROM_INC_PATH =
+
+# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter
+# (but less readable) file names. This can be useful is your file systems
+# doesn't support long names like on DOS, Mac, or CD-ROM.
+
+SHORT_NAMES = NO
+
+# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen
+# will interpret the first line (until the first dot) of a JavaDoc-style
+# comment as the brief description. If set to NO, the JavaDoc
+# comments will behave just like the Qt-style comments (thus requiring an
+# explicit @brief command for a brief description.
+
+JAVADOC_AUTOBRIEF = NO
+
+# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen
+# treat a multi-line C++ special comment block (i.e. a block of //! or ///
+# comments) as a brief description. This used to be the default behaviour.
+# The new default is to treat a multi-line C++ comment block as a detailed
+# description. Set this tag to YES if you prefer the old behaviour instead.
+
+MULTILINE_CPP_IS_BRIEF = NO
+
+# If the DETAILS_AT_TOP tag is set to YES then Doxygen
+# will output the detailed description near the top, like JavaDoc.
+# If set to NO, the detailed description appears after the member
+# documentation.
+
+DETAILS_AT_TOP = YES
+
+# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented
+# member inherits the documentation from any documented member that it
+# re-implements.
+
+INHERIT_DOCS = YES
+
+# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC
+# tag is set to YES, then doxygen will reuse the documentation of the first
+# member in the group (if any) for the other members of the group. By default
+# all members of a group must be documented explicitly.
+
+DISTRIBUTE_GROUP_DOC = NO
+
+# The TAB_SIZE tag can be used to set the number of spaces in a tab.
+# Doxygen uses this value to replace tabs by spaces in code fragments.
+
+TAB_SIZE = 8
+
+# This tag can be used to specify a number of aliases that acts
+# as commands in the documentation. An alias has the form "name=value".
+# For example adding "sideeffect=\par Side Effects:\n" will allow you to
+# put the command \sideeffect (or @sideeffect) in the documentation, which
+# will result in a user-defined paragraph with heading "Side Effects:".
+# You can put \n's in the value part of an alias to insert newlines.
+
+ALIASES =
+
+# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources
+# only. Doxygen will then generate output that is more tailored for C.
+# For instance, some of the names that are used will be different. The list
+# of all members will be omitted, etc.
+
+OPTIMIZE_OUTPUT_FOR_C = NO
+
+# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java sources
+# only. Doxygen will then generate output that is more tailored for Java.
+# For instance, namespaces will be presented as packages, qualified scopes
+# will look different, etc.
+
+OPTIMIZE_OUTPUT_JAVA = NO
+
+# Set the SUBGROUPING tag to YES (the default) to allow class member groups of
+# the same type (for instance a group of public functions) to be put as a
+# subgroup of that type (e.g. under the Public Functions section). Set it to
+# NO to prevent subgrouping. Alternatively, this can be done per class using
+# the \nosubgrouping command.
+
+SUBGROUPING = YES
+
+#---------------------------------------------------------------------------
+# Build related configuration options
+#---------------------------------------------------------------------------
+
+# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in
+# documentation are documented, even if no documentation was available.
+# Private class members and static file members will be hidden unless
+# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES
+
+EXTRACT_ALL = YES
+
+# If the EXTRACT_PRIVATE tag is set to YES all private members of a class
+# will be included in the documentation.
+
+EXTRACT_PRIVATE = YES
+
+# If the EXTRACT_STATIC tag is set to YES all static members of a file
+# will be included in the documentation.
+
+EXTRACT_STATIC = YES
+
+# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs)
+# defined locally in source files will be included in the documentation.
+# If set to NO only classes defined in header files are included.
+
+EXTRACT_LOCAL_CLASSES = NO
+
+# This flag is only useful for Objective-C code. When set to YES local
+# methods, which are defined in the implementation section but not in
+# the interface are included in the documentation.
+# If set to NO (the default) only methods in the interface are included.
+
+EXTRACT_LOCAL_METHODS = NO
+
+# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all
+# undocumented members of documented classes, files or namespaces.
+# If set to NO (the default) these members will be included in the
+# various overviews, but no documentation section is generated.
+# This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_MEMBERS = NO
+
+# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all
+# undocumented classes that are normally visible in the class hierarchy.
+# If set to NO (the default) these classes will be included in the various
+# overviews. This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_CLASSES = NO
+
+# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all
+# friend (class|struct|union) declarations.
+# If set to NO (the default) these declarations will be included in the
+# documentation.
+
+HIDE_FRIEND_COMPOUNDS = NO
+
+# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any
+# documentation blocks found inside the body of a function.
+# If set to NO (the default) these blocks will be appended to the
+# function's detailed documentation block.
+
+HIDE_IN_BODY_DOCS = NO
+
+# The INTERNAL_DOCS tag determines if documentation
+# that is typed after a \internal command is included. If the tag is set
+# to NO (the default) then the documentation will be excluded.
+# Set it to YES to include the internal documentation.
+
+INTERNAL_DOCS = NO
+
+# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate
+# file names in lower-case letters. If set to YES upper-case letters are also
+# allowed. This is useful if you have classes or files whose names only differ
+# in case and if your file system supports case sensitive file names. Windows
+# users are advised to set this option to NO.
+
+CASE_SENSE_NAMES = YES
+
+# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen
+# will show members with their full class and namespace scopes in the
+# documentation. If set to YES the scope will be hidden.
+
+HIDE_SCOPE_NAMES = NO
+
+# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen
+# will put a list of the files that are included by a file in the documentation
+# of that file.
+
+SHOW_INCLUDE_FILES = YES
+
+# If the INLINE_INFO tag is set to YES (the default) then a tag [inline]
+# is inserted in the documentation for inline members.
+
+INLINE_INFO = YES
+
+# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen
+# will sort the (detailed) documentation of file and class members
+# alphabetically by member name. If set to NO the members will appear in
+# declaration order.
+
+SORT_MEMBER_DOCS = YES
+
+# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the
+# brief documentation of file, namespace and class members alphabetically
+# by member name. If set to NO (the default) the members will appear in
+# declaration order.
+
+SORT_BRIEF_DOCS = NO
+
+# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be
+# sorted by fully-qualified names, including namespaces. If set to
+# NO (the default), the class list will be sorted only by class name,
+# not including the namespace part.
+# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.
+# Note: This option applies only to the class list, not to the
+# alphabetical list.
+
+SORT_BY_SCOPE_NAME = NO
+
+# The GENERATE_TODOLIST tag can be used to enable (YES) or
+# disable (NO) the todo list. This list is created by putting \todo
+# commands in the documentation.
+
+GENERATE_TODOLIST = YES
+
+# The GENERATE_TESTLIST tag can be used to enable (YES) or
+# disable (NO) the test list. This list is created by putting \test
+# commands in the documentation.
+
+GENERATE_TESTLIST = YES
+
+# The GENERATE_BUGLIST tag can be used to enable (YES) or
+# disable (NO) the bug list. This list is created by putting \bug
+# commands in the documentation.
+
+GENERATE_BUGLIST = YES
+
+# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or
+# disable (NO) the deprecated list. This list is created by putting
+# \deprecated commands in the documentation.
+
+GENERATE_DEPRECATEDLIST= YES
+
+# The ENABLED_SECTIONS tag can be used to enable conditional
+# documentation sections, marked by \if sectionname ... \endif.
+
+ENABLED_SECTIONS =
+
+# The MAX_INITIALIZER_LINES tag determines the maximum number of lines
+# the initial value of a variable or define consists of for it to appear in
+# the documentation. If the initializer consists of more lines than specified
+# here it will be hidden. Use a value of 0 to hide initializers completely.
+# The appearance of the initializer of individual variables and defines in the
+# documentation can be controlled using \showinitializer or \hideinitializer
+# command in the documentation regardless of this setting.
+
+MAX_INITIALIZER_LINES = 30
+
+# Set the SHOW_USED_FILES tag to NO to disable the list of files generated
+# at the bottom of the documentation of classes and structs. If set to YES the
+# list will mention the files that were used to generate the documentation.
+
+SHOW_USED_FILES = YES
+
+#---------------------------------------------------------------------------
+# configuration options related to warning and progress messages
+#---------------------------------------------------------------------------
+
+# The QUIET tag can be used to turn on/off the messages that are generated
+# by doxygen. Possible values are YES and NO. If left blank NO is used.
+
+QUIET = NO
+
+# The WARNINGS tag can be used to turn on/off the warning messages that are
+# generated by doxygen. Possible values are YES and NO. If left blank
+# NO is used.
+
+WARNINGS = YES
+
+# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings
+# for undocumented members. If EXTRACT_ALL is set to YES then this flag will
+# automatically be disabled.
+
+WARN_IF_UNDOCUMENTED = YES
+
+# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for
+# potential errors in the documentation, such as not documenting some
+# parameters in a documented function, or documenting parameters that
+# don't exist or using markup commands wrongly.
+
+WARN_IF_DOC_ERROR = YES
+
+# The WARN_FORMAT tag determines the format of the warning messages that
+# doxygen can produce. The string should contain the $file, $line, and $text
+# tags, which will be replaced by the file and line number from which the
+# warning originated and the warning text.
+
+WARN_FORMAT = "$file:$line: $text"
+
+# The WARN_LOGFILE tag can be used to specify a file to which warning
+# and error messages should be written. If left blank the output is written
+# to stderr.
+
+WARN_LOGFILE =
+
+#---------------------------------------------------------------------------
+# configuration options related to the input files
+#---------------------------------------------------------------------------
+
+# The INPUT tag can be used to specify the files and/or directories that contain
+# documented source files. You may enter file names like "myfile.cpp" or
+# directories like "/usr/src/myproject". Separate the files or directories
+# with spaces.
+
+INPUT =
+
+# If the value of the INPUT tag contains directories, you can use the
+# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp
+# and *.h) to filter out the source-files in the directories. If left
+# blank the following patterns are tested:
+# *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx *.hpp
+# *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm
+
+FILE_PATTERNS = *.cc *.hh *.doc
+
+# The RECURSIVE tag can be used to turn specify whether or not subdirectories
+# should be searched for input files as well. Possible values are YES and NO.
+# If left blank NO is used.
+
+RECURSIVE = YES
+
+# The EXCLUDE tag can be used to specify files and/or directories that should
+# excluded from the INPUT source files. This way you can easily exclude a
+# subdirectory from a directory tree whose root is specified with the INPUT tag.
+
+EXCLUDE = dlib dpi dpid dpip src test
+
+# The EXCLUDE_SYMLINKS tag can be used select whether or not files or directories
+# that are symbolic links (a Unix filesystem feature) are excluded from the input.
+
+EXCLUDE_SYMLINKS = NO
+
+# If the value of the INPUT tag contains directories, you can use the
+# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude
+# certain files from those directories.
+
+EXCLUDE_PATTERNS =
+
+# The EXAMPLE_PATH tag can be used to specify one or more files or
+# directories that contain example code fragments that are included (see
+# the \include command).
+
+EXAMPLE_PATH =
+
+# If the value of the EXAMPLE_PATH tag contains directories, you can use the
+# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp
+# and *.h) to filter out the source-files in the directories. If left
+# blank all files are included.
+
+EXAMPLE_PATTERNS =
+
+# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be
+# searched for input files to be used with the \include or \dontinclude
+# commands irrespective of the value of the RECURSIVE tag.
+# Possible values are YES and NO. If left blank NO is used.
+
+EXAMPLE_RECURSIVE = NO
+
+# The IMAGE_PATH tag can be used to specify one or more files or
+# directories that contain image that are included in the documentation (see
+# the \image command).
+
+IMAGE_PATH = doc
+
+# The INPUT_FILTER tag can be used to specify a program that doxygen should
+# invoke to filter for each input file. Doxygen will invoke the filter program
+# by executing (via popen()) the command <filter> <input-file>, where <filter>
+# is the value of the INPUT_FILTER tag, and <input-file> is the name of an
+# input file. Doxygen will then use the output that the filter program writes
+# to standard output.
+
+INPUT_FILTER =
+
+# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using
+# INPUT_FILTER) will be used to filter the input files when producing source
+# files to browse (i.e. when SOURCE_BROWSER is set to YES).
+
+FILTER_SOURCE_FILES = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to source browsing
+#---------------------------------------------------------------------------
+
+# If the SOURCE_BROWSER tag is set to YES then a list of source files will
+# be generated. Documented entities will be cross-referenced with these sources.
+# Note: To get rid of all source code in the generated output, make sure also
+# VERBATIM_HEADERS is set to NO.
+
+SOURCE_BROWSER = NO
+
+# Setting the INLINE_SOURCES tag to YES will include the body
+# of functions and classes directly in the documentation.
+
+INLINE_SOURCES = NO
+
+# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct
+# doxygen to hide any special comment blocks from generated source code
+# fragments. Normal C and C++ comments will always remain visible.
+
+STRIP_CODE_COMMENTS = YES
+
+# If the REFERENCED_BY_RELATION tag is set to YES (the default)
+# then for each documented function all documented
+# functions referencing it will be listed.
+
+REFERENCED_BY_RELATION = YES
+
+# If the REFERENCES_RELATION tag is set to YES (the default)
+# then for each documented function all documented entities
+# called/used by that function will be listed.
+
+REFERENCES_RELATION = YES
+
+# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen
+# will generate a verbatim copy of the header file for each class for
+# which an include is specified. Set to NO to disable this.
+
+VERBATIM_HEADERS = YES
+
+#---------------------------------------------------------------------------
+# configuration options related to the alphabetical class index
+#---------------------------------------------------------------------------
+
+# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index
+# of all compounds will be generated. Enable this if the project
+# contains a lot of classes, structs, unions or interfaces.
+
+ALPHABETICAL_INDEX = YES
+
+# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then
+# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns
+# in which this list will be split (can be a number in the range [1..20])
+
+COLS_IN_ALPHA_INDEX = 3
+
+# In case all classes in a project start with a common prefix, all
+# classes will be put under the same header in the alphabetical index.
+# The IGNORE_PREFIX tag can be used to specify one or more prefixes that
+# should be ignored while generating the index headers.
+
+IGNORE_PREFIX =
+
+#---------------------------------------------------------------------------
+# configuration options related to the HTML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_HTML tag is set to YES (the default) Doxygen will
+# generate HTML output.
+
+GENERATE_HTML = YES
+
+# The HTML_OUTPUT tag is used to specify where the HTML docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `html' will be used as the default path.
+
+HTML_OUTPUT = html
+
+# The HTML_FILE_EXTENSION tag can be used to specify the file extension for
+# each generated HTML page (for example: .htm,.php,.asp). If it is left blank
+# doxygen will generate files with .html extension.
+
+HTML_FILE_EXTENSION = .html
+
+# The HTML_HEADER tag can be used to specify a personal HTML header for
+# each generated HTML page. If it is left blank doxygen will generate a
+# standard header.
+
+HTML_HEADER =
+
+# The HTML_FOOTER tag can be used to specify a personal HTML footer for
+# each generated HTML page. If it is left blank doxygen will generate a
+# standard footer.
+
+HTML_FOOTER =
+
+# The HTML_STYLESHEET tag can be used to specify a user-defined cascading
+# style sheet that is used by each HTML page. It can be used to
+# fine-tune the look of the HTML output. If the tag is left blank doxygen
+# will generate a default style sheet. Note that doxygen will try to copy
+# the style sheet file to the HTML output directory, so don't put your own
+# stylesheet in the HTML output directory as well, or it will be erased!
+
+HTML_STYLESHEET =
+
+# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes,
+# files or namespaces will be aligned in HTML using tables. If set to
+# NO a bullet list will be used.
+
+HTML_ALIGN_MEMBERS = YES
+
+# If the GENERATE_HTMLHELP tag is set to YES, additional index files
+# will be generated that can be used as input for tools like the
+# Microsoft HTML help workshop to generate a compressed HTML help file (.chm)
+# of the generated HTML documentation.
+
+GENERATE_HTMLHELP = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can
+# be used to specify the file name of the resulting .chm file. You
+# can add a path in front of the file if the result should not be
+# written to the html output directory.
+
+CHM_FILE =
+
+# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can
+# be used to specify the location (absolute path including file name) of
+# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run
+# the HTML help compiler on the generated index.hhp.
+
+HHC_LOCATION =
+
+# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag
+# controls if a separate .chi index file is generated (YES) or that
+# it should be included in the master .chm file (NO).
+
+GENERATE_CHI = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag
+# controls whether a binary table of contents is generated (YES) or a
+# normal table of contents (NO) in the .chm file.
+
+BINARY_TOC = NO
+
+# The TOC_EXPAND flag can be set to YES to add extra items for group members
+# to the contents of the HTML help documentation and to the tree view.
+
+TOC_EXPAND = NO
+
+# The DISABLE_INDEX tag can be used to turn on/off the condensed index at
+# top of each HTML page. The value NO (the default) enables the index and
+# the value YES disables it.
+
+DISABLE_INDEX = NO
+
+# This tag can be used to set the number of enum values (range [1..20])
+# that doxygen will group on one line in the generated HTML documentation.
+
+ENUM_VALUES_PER_LINE = 4
+
+# If the GENERATE_TREEVIEW tag is set to YES, a side panel will be
+# generated containing a tree-like index structure (just like the one that
+# is generated for HTML Help). For this to work a browser that supports
+# JavaScript, DHTML, CSS and frames is required (for instance Mozilla 1.0+,
+# Netscape 6.0+, Internet explorer 5.0+, or Konqueror). Windows users are
+# probably better off using the HTML help feature.
+
+GENERATE_TREEVIEW = NO
+
+# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be
+# used to set the initial width (in pixels) of the frame in which the tree
+# is shown.
+
+TREEVIEW_WIDTH = 250
+
+#---------------------------------------------------------------------------
+# configuration options related to the LaTeX output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will
+# generate Latex output.
+
+GENERATE_LATEX = NO
+
+# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `latex' will be used as the default path.
+
+LATEX_OUTPUT = latex
+
+# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be
+# invoked. If left blank `latex' will be used as the default command name.
+
+LATEX_CMD_NAME = latex
+
+# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to
+# generate index for LaTeX. If left blank `makeindex' will be used as the
+# default command name.
+
+MAKEINDEX_CMD_NAME = makeindex
+
+# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact
+# LaTeX documents. This may be useful for small projects and may help to
+# save some trees in general.
+
+COMPACT_LATEX = NO
+
+# The PAPER_TYPE tag can be used to set the paper type that is used
+# by the printer. Possible values are: a4, a4wide, letter, legal and
+# executive. If left blank a4wide will be used.
+
+PAPER_TYPE = a4wide
+
+# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX
+# packages that should be included in the LaTeX output.
+
+EXTRA_PACKAGES =
+
+# The LATEX_HEADER tag can be used to specify a personal LaTeX header for
+# the generated latex document. The header should contain everything until
+# the first chapter. If it is left blank doxygen will generate a
+# standard header. Notice: only use this tag if you know what you are doing!
+
+LATEX_HEADER =
+
+# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated
+# is prepared for conversion to pdf (using ps2pdf). The pdf file will
+# contain links (just like the HTML output) instead of page references
+# This makes the output suitable for online browsing using a pdf viewer.
+
+PDF_HYPERLINKS = NO
+
+# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of
+# plain latex in the generated Makefile. Set this option to YES to get a
+# higher quality PDF documentation.
+
+USE_PDFLATEX = NO
+
+# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode.
+# command to the generated LaTeX files. This will instruct LaTeX to keep
+# running if errors occur, instead of asking the user for help.
+# This option is also used when generating formulas in HTML.
+
+LATEX_BATCHMODE = NO
+
+# If LATEX_HIDE_INDICES is set to YES then doxygen will not
+# include the index chapters (such as File Index, Compound Index, etc.)
+# in the output.
+
+LATEX_HIDE_INDICES = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the RTF output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output
+# The RTF output is optimized for Word 97 and may not look very pretty with
+# other RTF readers or editors.
+
+GENERATE_RTF = NO
+
+# The RTF_OUTPUT tag is used to specify where the RTF docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `rtf' will be used as the default path.
+
+RTF_OUTPUT = rtf
+
+# If the COMPACT_RTF tag is set to YES Doxygen generates more compact
+# RTF documents. This may be useful for small projects and may help to
+# save some trees in general.
+
+COMPACT_RTF = NO
+
+# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated
+# will contain hyperlink fields. The RTF file will
+# contain links (just like the HTML output) instead of page references.
+# This makes the output suitable for online browsing using WORD or other
+# programs which support those fields.
+# Note: wordpad (write) and others do not support links.
+
+RTF_HYPERLINKS = NO
+
+# Load stylesheet definitions from file. Syntax is similar to doxygen's
+# config file, i.e. a series of assignments. You only have to provide
+# replacements, missing definitions are set to their default value.
+
+RTF_STYLESHEET_FILE =
+
+# Set optional variables used in the generation of an rtf document.
+# Syntax is similar to doxygen's config file.
+
+RTF_EXTENSIONS_FILE =
+
+#---------------------------------------------------------------------------
+# configuration options related to the man page output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_MAN tag is set to YES (the default) Doxygen will
+# generate man pages
+
+GENERATE_MAN = NO
+
+# The MAN_OUTPUT tag is used to specify where the man pages will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `man' will be used as the default path.
+
+MAN_OUTPUT = man
+
+# The MAN_EXTENSION tag determines the extension that is added to
+# the generated man pages (default is the subroutine's section .3)
+
+MAN_EXTENSION = .3
+
+# If the MAN_LINKS tag is set to YES and Doxygen generates man output,
+# then it will generate one additional man file for each entity
+# documented in the real man page(s). These additional files
+# only source the real man page, but without them the man command
+# would be unable to find the correct page. The default is NO.
+
+MAN_LINKS = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the XML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_XML tag is set to YES Doxygen will
+# generate an XML file that captures the structure of
+# the code including all documentation.
+
+GENERATE_XML = NO
+
+# The XML_OUTPUT tag is used to specify where the XML pages will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `xml' will be used as the default path.
+
+XML_OUTPUT = xml
+
+# The XML_SCHEMA tag can be used to specify an XML schema,
+# which can be used by a validating XML parser to check the
+# syntax of the XML files.
+
+XML_SCHEMA =
+
+# The XML_DTD tag can be used to specify an XML DTD,
+# which can be used by a validating XML parser to check the
+# syntax of the XML files.
+
+XML_DTD =
+
+# If the XML_PROGRAMLISTING tag is set to YES Doxygen will
+# dump the program listings (including syntax highlighting
+# and cross-referencing information) to the XML output. Note that
+# enabling this will significantly increase the size of the XML output.
+
+XML_PROGRAMLISTING = YES
+
+#---------------------------------------------------------------------------
+# configuration options for the AutoGen Definitions output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will
+# generate an AutoGen Definitions (see autogen.sf.net) file
+# that captures the structure of the code including all
+# documentation. Note that this feature is still experimental
+# and incomplete at the moment.
+
+GENERATE_AUTOGEN_DEF = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the Perl module output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_PERLMOD tag is set to YES Doxygen will
+# generate a Perl module file that captures the structure of
+# the code including all documentation. Note that this
+# feature is still experimental and incomplete at the
+# moment.
+
+GENERATE_PERLMOD = NO
+
+# If the PERLMOD_LATEX tag is set to YES Doxygen will generate
+# the necessary Makefile rules, Perl scripts and LaTeX code to be able
+# to generate PDF and DVI output from the Perl module output.
+
+PERLMOD_LATEX = NO
+
+# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be
+# nicely formatted so it can be parsed by a human reader. This is useful
+# if you want to understand what is going on. On the other hand, if this
+# tag is set to NO the size of the Perl module output will be much smaller
+# and Perl will parse it just the same.
+
+PERLMOD_PRETTY = YES
+
+# The names of the make variables in the generated doxyrules.make file
+# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX.
+# This is useful so different doxyrules.make files included by the same
+# Makefile don't overwrite each other's variables.
+
+PERLMOD_MAKEVAR_PREFIX =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the preprocessor
+#---------------------------------------------------------------------------
+
+# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will
+# evaluate all C-preprocessor directives found in the sources and include
+# files.
+
+ENABLE_PREPROCESSING = YES
+
+# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro
+# names in the source code. If set to NO (the default) only conditional
+# compilation will be performed. Macro expansion can be done in a controlled
+# way by setting EXPAND_ONLY_PREDEF to YES.
+
+MACRO_EXPANSION = NO
+
+# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES
+# then the macro expansion is limited to the macros specified with the
+# PREDEFINED and EXPAND_AS_PREDEFINED tags.
+
+EXPAND_ONLY_PREDEF = NO
+
+# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files
+# in the INCLUDE_PATH (see below) will be search if a #include is found.
+
+SEARCH_INCLUDES = YES
+
+# The INCLUDE_PATH tag can be used to specify one or more directories that
+# contain include files that are not input files but should be processed by
+# the preprocessor.
+
+INCLUDE_PATH =
+
+# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard
+# patterns (like *.h and *.hpp) to filter out the header-files in the
+# directories. If left blank, the patterns specified with FILE_PATTERNS will
+# be used.
+
+INCLUDE_FILE_PATTERNS =
+
+# The PREDEFINED tag can be used to specify one or more macro names that
+# are defined before the preprocessor is started (similar to the -D option of
+# gcc). The argument of the tag is a list of macros of the form: name
+# or name=definition (no spaces). If the definition and the = are
+# omitted =1 is assumed.
+
+PREDEFINED =
+
+# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then
+# this tag can be used to specify a list of macro names that should be expanded.
+# The macro definition that is found in the sources will be used.
+# Use the PREDEFINED tag if you want to use a different macro definition.
+
+EXPAND_AS_DEFINED =
+
+# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then
+# doxygen's preprocessor will remove all function-like macros that are alone
+# on a line, have an all uppercase name, and do not end with a semicolon. Such
+# function macros are typically used for boiler-plate code, and will confuse the
+# parser if not removed.
+
+SKIP_FUNCTION_MACROS = YES
+
+#---------------------------------------------------------------------------
+# Configuration::additions related to external references
+#---------------------------------------------------------------------------
+
+# The TAGFILES option can be used to specify one or more tagfiles.
+# Optionally an initial location of the external documentation
+# can be added for each tagfile. The format of a tag file without
+# this location is as follows:
+# TAGFILES = file1 file2 ...
+# Adding location for the tag files is done as follows:
+# TAGFILES = file1=loc1 "file2 = loc2" ...
+# where "loc1" and "loc2" can be relative or absolute paths or
+# URLs. If a location is present for each tag, the installdox tool
+# does not have to be run to correct the links.
+# Note that each tag file must have a unique name
+# (where the name does NOT include the path)
+# If a tag file is not located in the directory in which doxygen
+# is run, you must also specify the path to the tagfile here.
+
+TAGFILES =
+
+# When a file name is specified after GENERATE_TAGFILE, doxygen will create
+# a tag file that is based on the input files it reads.
+
+GENERATE_TAGFILE =
+
+# If the ALLEXTERNALS tag is set to YES all external classes will be listed
+# in the class index. If set to NO only the inherited external classes
+# will be listed.
+
+ALLEXTERNALS = NO
+
+# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed
+# in the modules index. If set to NO, only the current project's groups will
+# be listed.
+
+EXTERNAL_GROUPS = YES
+
+# The PERL_PATH should be the absolute path and name of the perl script
+# interpreter (i.e. the result of `which perl').
+
+PERL_PATH = /usr/bin/perl
+
+#---------------------------------------------------------------------------
+# Configuration options related to the dot tool
+#---------------------------------------------------------------------------
+
+# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will
+# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base or
+# super classes. Setting the tag to NO turns the diagrams off. Note that this
+# option is superseded by the HAVE_DOT option below. This is only a fallback. It is
+# recommended to install and use dot, since it yields more powerful graphs.
+
+CLASS_DIAGRAMS = YES
+
+# If set to YES, the inheritance and collaboration graphs will hide
+# inheritance and usage relations if the target is undocumented
+# or is not a class.
+
+HIDE_UNDOC_RELATIONS = YES
+
+# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is
+# available from the path. This tool is part of Graphviz, a graph visualization
+# toolkit from AT&T and Lucent Bell Labs. The other options in this section
+# have no effect if this option is set to NO (the default)
+
+HAVE_DOT = YES
+#HAVE_DOT = NO
+
+# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for each documented class showing the direct and
+# indirect inheritance relations. Setting this tag to YES will force the
+# the CLASS_DIAGRAMS tag to NO.
+
+CLASS_GRAPH = NO
+
+# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for each documented class showing the direct and
+# indirect implementation dependencies (inheritance, containment, and
+# class references variables) of the class with other documented classes.
+
+COLLABORATION_GRAPH = NO
+
+# If the UML_LOOK tag is set to YES doxygen will generate inheritance and
+# collaboration diagrams in a style similar to the OMG's Unified Modeling
+# Language.
+
+UML_LOOK = YES
+
+# If set to YES, the inheritance and collaboration graphs will show the
+# relations between templates and their instances.
+
+TEMPLATE_RELATIONS = NO
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT
+# tags are set to YES then doxygen will generate a graph for each documented
+# file showing the direct and indirect include dependencies of the file with
+# other documented files.
+
+INCLUDE_GRAPH = YES
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and
+# HAVE_DOT tags are set to YES then doxygen will generate a graph for each
+# documented header file showing the documented files that directly or
+# indirectly include this file.
+
+INCLUDED_BY_GRAPH = YES
+
+# If the CALL_GRAPH and HAVE_DOT tags are set to YES then doxygen will
+# generate a call dependency graph for every global function or class method.
+# Note that enabling this option will significantly increase the time of a run.
+# So in most cases it will be better to enable call graphs for selected
+# functions only using the \callgraph command.
+
+CALL_GRAPH = NO
+
+# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen
+# will graphical hierarchy of all classes instead of a textual one.
+
+GRAPHICAL_HIERARCHY = NO
+
+# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
+# generated by dot. Possible values are png, jpg, or gif
+# If left blank png will be used.
+
+DOT_IMAGE_FORMAT = png
+
+# The tag DOT_PATH can be used to specify the path where the dot tool can be
+# found. If left blank, it is assumed the dot tool can be found on the path.
+
+DOT_PATH =
+
+# The DOTFILE_DIRS tag can be used to specify one or more directories that
+# contain dot files that are included in the documentation (see the
+# \dotfile command).
+
+DOTFILE_DIRS =
+
+# The MAX_DOT_GRAPH_WIDTH tag can be used to set the maximum allowed width
+# (in pixels) of the graphs generated by dot. If a graph becomes larger than
+# this value, doxygen will try to truncate the graph, so that it fits within
+# the specified constraint. Beware that most browsers cannot cope with very
+# large images.
+
+MAX_DOT_GRAPH_WIDTH = 1024
+
+# The MAX_DOT_GRAPH_HEIGHT tag can be used to set the maximum allows height
+# (in pixels) of the graphs generated by dot. If a graph becomes larger than
+# this value, doxygen will try to truncate the graph, so that it fits within
+# the specified constraint. Beware that most browsers cannot cope with very
+# large images.
+
+MAX_DOT_GRAPH_HEIGHT = 20480
+
+# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the
+# graphs generated by dot. A depth value of 3 means that only nodes reachable
+# from the root by following a path via at most 3 edges will be shown. Nodes that
+# lay further from the root node will be omitted. Note that setting this option to
+# 1 or 2 may greatly reduce the computation time needed for large code bases. Also
+# note that a graph may be further truncated if the graph's image dimensions are
+# not sufficient to fit the graph (see MAX_DOT_GRAPH_WIDTH and MAX_DOT_GRAPH_HEIGHT).
+# If 0 is used for the depth value (the default), the graph is not depth-constrained.
+
+MAX_DOT_GRAPH_DEPTH = 0
+
+# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will
+# generate a legend page explaining the meaning of the various boxes and
+# arrows in the dot generated graphs.
+
+GENERATE_LEGEND = YES
+
+# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will
+# remove the intermediate dot files that are used to generate
+# the various graphs.
+
+DOT_CLEANUP = YES
+
+#---------------------------------------------------------------------------
+# Configuration::additions related to the search engine
+#---------------------------------------------------------------------------
+
+# The SEARCHENGINE tag specifies whether or not a search engine should be
+# used. If set to NO the values of all tags below this one will be ignored.
+
+SEARCHENGINE = NO
diff --git a/Makefile.am b/Makefile.am
index a740cac1..d2177ae5 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1,5 +1,5 @@
-SUBDIRS = doc dlib dpip src dpid dpi
+SUBDIRS = lout dw dlib dpip src doc dpid dpi test
-EXTRA_DIST = ChangeLog.old dillo2rc install-dpi-local README-port
+EXTRA_DIST = ChangeLog.old Doxyfile dillo2rc install-dpi-local README-port
sysconf_DATA = dillo2rc
diff --git a/configure.in b/configure.in
index 420f84e9..f37a0f29 100644
--- a/configure.in
+++ b/configure.in
@@ -34,6 +34,8 @@ AC_ARG_ENABLE(ssl, [ --disable-ssl Disable ssl features (eg. https
enable_ssl=$enableval, enable_ssl=yes)
AC_ARG_ENABLE(threaded-dns,[ --disable-threaded-dns Disable the advantage of a reentrant resolver library],
enable_threaded_dns=$enableval, enable_threaded_dns=yes)
+AC_ARG_ENABLE(rtfl, [ --enable-rtfl Build with rtfl messages])
+AC_ARG_ENABLE(doublebuffer, [ --disable-doublebuffer Disable double buffering for drawing], , enable_doublebuffer=yes)
AC_PROG_CC
AC_PROG_CXX
@@ -425,6 +427,12 @@ fi
if test "x$enable_threaded_dns" = "xyes" ; then
CFLAGS="$CFLAGS -DD_DNS_THREADED"
fi
+if test "x$enable_rtfl" = "xyes" ; then
+ CXXFLAGS="$CXXFLAGS -DDBG_RTFL"
+fi
+if test "x$enable_doublebuffer" = "xno" ; then
+ CXXFLAGS="$CXXFLAGS -DNO_DOUBLEBUFFER"
+fi
dnl -----------------------
dnl Checks for header files
@@ -487,5 +495,5 @@ AC_SUBST(LIBFLTK_LIBS)
AC_SUBST(LIBICONV_LIBS)
AC_SUBST(datadir)
-AC_OUTPUT(Makefile dlib/Makefile dpip/Makefile dpid/Makefile dpi/Makefile doc/Makefile src/Makefile src/IO/Makefile)
+AC_OUTPUT(Makefile dlib/Makefile dpip/Makefile dpid/Makefile dpi/Makefile doc/Makefile dw/Makefile lout/Makefile src/Makefile src/IO/Makefile test/Makefile)
diff --git a/doc/Makefile.am b/doc/Makefile.am
index 9142fa3a..faea2b6c 100644
--- a/doc/Makefile.am
+++ b/doc/Makefile.am
@@ -1,19 +1,44 @@
EXTRA_DIST = \
- Cache.txt \
- Cookies.txt \
- Dillo.txt \
- Dw.txt \
- DwImage.txt \
- DwPage.txt \
- DwRender.txt \
- DwStyle.txt \
- DwTable.txt \
- DwWidget.txt \
- HtmlParser.txt \
- IO.txt \
- Images.txt \
- Imgbuf.txt \
- NC_design.txt \
- Selection.txt \
- Dpid.txt \
- README
+ index.doc \
+ lout.doc \
+ dw-map.doc \
+ dw-overview.doc \
+ dw-usage.doc \
+ dw-layout-views.doc \
+ dw-layout-widgets.doc \
+ dw-widget-sizes.doc \
+ dw-changes.doc \
+ dw-images-and-backgrounds.doc \
+ fltk-problems.doc \
+ rounding-errors.doc \
+ uml-legend.doc \
+ dw-example-screenshot.png \
+ dw-viewport-without-scrollbar.png \
+ dw-viewport-with-scrollbar.png \
+ dw-size-of-widget.png \
+ dw-style-box-model.png \
+ dw-style-length-absolute.png \
+ dw-style-length-percentage.png \
+ dw-style-length-relative.png \
+ dw-textblock-collapsing-spaces-1-1.png \
+ dw-textblock-collapsing-spaces-1-2.png \
+ dw-textblock-collapsing-spaces-2-1.png \
+ dw-textblock-collapsing-spaces-2-2.png \
+ Cache.txt \
+ Cookies.txt \
+ Dillo.txt \
+ Dw.txt \
+ DwImage.txt \
+ DwPage.txt \
+ DwRender.txt \
+ DwStyle.txt \
+ DwTable.txt \
+ DwWidget.txt \
+ HtmlParser.txt \
+ IO.txt \
+ Images.txt \
+ Imgbuf.txt \
+ NC_design.txt \
+ Selection.txt \
+ Dpid.txt \
+ README
diff --git a/doc/dw-changes.doc b/doc/dw-changes.doc
new file mode 100644
index 00000000..b76e9433
--- /dev/null
+++ b/doc/dw-changes.doc
@@ -0,0 +1,109 @@
+/** \page dw-changes Changes to the GTK+-based Release Version
+
+<h2>Changes in Dw</h2>
+
+Related to the FLTK port, there have been many changes, this is a
+(hopefully complete) list:
+
+<ul>
+<li> Rendering abstraction, read \ref dw-overview and \ref dw-layout-views
+ for details. Some important changes:
+
+ <ul>
+ <li> The underlying platform (e.g. the UI toolkit) is fully abstract,
+ there are several platform independent structures replacing
+ GTK+ structures, e.g. dw::core::Event.
+
+ <li> The central class managing the widget tree is not anymore
+ GtkDwViewport, but dw::core::Layout.
+
+ <li> There are multiple views (implementations of dw::core::View).
+ Typically, you keep a reference on dw::core::Layout and a "main"
+ view (a viewport).
+
+ <li> Drawing is done via dw::core::View, a pointer is passed to
+ dw::core::Widget::draw.
+
+ <li> The distinction between viewport coordinates and canvas
+ coordinates (formerly world coordinates) has been mostly
+ removed. (Only for views, it sometimes plays a role, see
+ \ref dw-layout-views).
+</ul>
+
+<li> Cursors have been moved to dw::core::style, see
+ dw::core::style::Style::cursor. dw::core::Widget::setCursor is now
+ protected (and so only called by widget implementations).
+
+<li> World coordinates are now called canvas coordinates.
+
+<li> There is now a distinction between dw::core::style::StyleAttrs and
+ dw::core::style::Style.
+
+<li> There is no base class for container widgets anymore. The former
+ DwContainer::for_all has been removed, instead this functionality
+ is now done via iterators (dw::core::Widget::iterator,
+ dw::core::Iterator).
+
+<li> DwPage is now called dw::Textblock, and DwAlignedPage
+ dw::AlignedTextblock.
+
+<li> dw::Textblock, all sub classes of it, and dw::Table do not read
+ "limit_text_width" from the preferences, but get it as an argument.
+ (May change again.)
+
+<li> dw::Table has been rewritten.
+
+<li> Instead of border_spacing in the old DwStyle, there are two attributes,
+ dw::core::style::Style::hBorderSpacing and
+ dw::core::style::Style::vBorderSpacing, since CSS allowes to specify
+ two values. Without CSS, both attributes should have the same value.
+
+<li> Images are handled differently, see \ref dw-images-and-backgrounds.
+
+<li> Embedded UI widgets (formerly GtkWidget's) are handled differently,
+ see dw::core::ui.
+
+<li> DwButton has been removed, instead, embedded UI widgets are used. See
+ dw::core::ui and dw::core::ui::ComplexButtonResource.
+</ul>
+
+Dw is now written C++, the transition should be obvious. All "Dw"
+prefixes have been removed, instead, namespaces are used now:
+
+<ul>
+<li>dw::core contains the core,
+<li>dw::core::style styles,
+<li>dw::core::ui embedded UI resources,
+<li>dw::fltk classes related to FLTK, and
+<li>::dw the widgets.
+</ul>
+
+<h2>Documentation</h2>
+
+The old documentation has been moved to:
+
+<table>
+<tr><th colspan="2">Old <th>New
+<tr><td rowspan="2">Dw.txt
+ <td>general part <td>\ref dw-overview, \ref dw-usage,
+ \ref dw-layout-widgets,
+ \ref dw-widget-sizes
+<tr><td>remarks on specific widgets <td>respective source files: dw::Bullet,
+ dw::core::ui::Embed
+<tr><td rowspan="2">DwImage.txt
+ <td>signals <td>dw::core::Widget::LinkReceiver
+<tr><td>rest <td>dw::Image,
+ \ref dw-images-and-backgrounds
+<tr><td colspan="2">Imgbuf.txt <td>dw::core::Imgbuf,
+ \ref dw-images-and-backgrounds
+<tr><td colspan="2">DwPage.txt <td>dw::Textblock
+<tr><td colspan="2">DwRender.txt <td>\ref dw-overview, \ref dw-layout-views,
+ dw::core::ui
+<tr><td colspan="2">DwStyle.txt <td>dw::core::style
+<tr><td colspan="2">DwTable.txt <td>dw::Table
+<tr><td colspan="2">DwWidget.txt <td>dw::core::Widget, \ref dw-layout-widgets,
+ \ref dw-widget-sizes
+<tr><td colspan="2">Selection.txt <td>dw::core::SelectionState
+</table>
+
+*/ \ No newline at end of file
diff --git a/doc/dw-images-and-backgrounds.doc b/doc/dw-images-and-backgrounds.doc
new file mode 100644
index 00000000..c4991807
--- /dev/null
+++ b/doc/dw-images-and-backgrounds.doc
@@ -0,0 +1,146 @@
+/** \page dw-images-and-backgrounds Images and Backgrounds in Dw
+
+<h2>General</h2>
+
+Representation of the image data is delegated to dw::core::Imgbuf, see
+there for details. Drawing is delegated to dw::core::View
+(dw::core::View::drawImgbuf).
+
+Since dw::core::Imgbuf provides memory management based on reference
+counting, there may be an 1-to-n relation from image renderers (image
+widgets or backgrounds, see below) and dw::core::Imgbuf. Since
+dw::core::Imgbuf does not know about renderers, but just provides
+rendering functionality, the caller must (typically after calling
+dw::core::Imgbuf::copyRow) notify all renderers connected to the
+buffer.
+
+
+<h2>Images</h2>
+
+This is the simplest renderer, displaying an image. For each row to be
+drawn,
+
+<ol>
+<li> first dw::core::Imgbuf::copyRow, and then
+<li> for each dw::Image, dw::Image::drawRow must be called, with the same
+ argument (no scaling is necessary).
+</ol>
+
+dw::Image automatically scales the dw::core::Imgbuf, the root buffer
+should be passed to dw::Image::setBuffer.
+
+\see dw::Image for more details.
+
+<h2>Future Extensions</h2>
+
+(This is not implemented yet.) Rendering itself (image widgets and
+background) will be abstracted, by a new interface
+dw::core::ImageRenderer. In the current code for image decoding, this
+interface will replace references to dw::Image, which implements
+dw::core::ImageRenderer, in most cases.
+
+<h2>Backgrounds</h2>
+
+(This is based on future extensions described above.) Since background
+are style resources, they are associated with
+dw::core::style::Style. For backgrounds, another level is needed,
+because of the 1-to-n relation from dw::core::style::Style to
+dw::core::Widget:
+
+\dot
+digraph G {
+ node [shape=record, fontname=Helvetica, fontsize=10];
+ edge [arrowhead="open", arrowtail="none", labelfontname=Helvetica,
+ labelfontsize=10, color="#404040", labelfontcolor="#000080"];
+ fontname=Helvetica; fontsize=10;
+
+ "background renderer (as a part of style)" -> "image buffer" [headlabel="*",
+ taillabel="1"];
+ "widget (or a part of it)" -> "background renderer (as a part of style)"
+ [headlabel="*", taillabel="1"];
+}
+\enddot
+
+Unlike dw::Image, dw::core::style::BgRenderer is not associated with a
+certain rectangle on the canvas. Instead, widgets, or parts of widgets
+take this role. This is generally represented by an implementation of
+the interface dw::core::style::BgAllocation, which is implemented by
+dw::core::Widget, but also by all parts of widget implementation,
+which may have an own background image.
+
+The following diagram gives a total overview:
+
+\dot
+digraph G {
+ node [shape=record, fontname=Helvetica, fontsize=10];
+ edge [arrowhead="open", arrowtail="none", labelfontname=Helvetica,
+ labelfontsize=10, color="#404040", labelfontcolor="#000080"];
+ fontname=Helvetica; fontsize=10;
+
+ "DICache Entry";
+
+ subgraph cluster_dw_images {
+ style="dashed"; color="#000080"; fontname=Helvetica; fontsize=10;
+ label="Dw Images";
+
+ ImageRenderer [URL="\ref dw::core::ImageRenderer", color="#ff8080"];
+ Imgbuf [URL="\ref dw::core::Imgbuf", color="#ff8080"];
+ }
+
+ subgraph cluster_widgets {
+ style="dashed"; color="#000080"; fontname=Helvetica; fontsize=10;
+ label="Widgets";
+
+ Widget [URL="\ref dw::core::Widget", color="#a0a0a0"];
+ Textblock [URL="\ref dw::Textblock"];
+ "Textblock::Word" [URL="\ref dw::Textblock::Word"];
+ Table [URL="\ref dw::Table"];
+ "Table::Row" [URL="\ref dw::Table::Row"];
+ Image [URL="\ref dw::Image"];
+ }
+
+ subgraph cluster_style {
+ style="dashed"; color="#000080"; fontname=Helvetica; fontsize=10;
+ label="dw::core::style";
+
+ Style [URL="\ref dw::core::style::Style"];
+ BgRenderer [URL="\ref dw::core::style::BgRenderer"];
+ BgAllocation [URL="\ref dw::core::style::BgAllocation", color="#ff8080"];
+ }
+
+ "DICache Entry" -> ImageRenderer [headlabel="*", taillabel="1"];
+ "DICache Entry" -> Imgbuf [headlabel="1", taillabel="1"];
+
+ BgRenderer -> Imgbuf [headlabel="1", taillabel="*"];
+ BgRenderer -> BgAllocation [headlabel="*", taillabel="1"];
+ ImageRenderer -> BgRenderer [arrowhead="none", arrowtail="empty",
+ style="dashed"];
+ ImageRenderer -> Image [arrowhead="none", arrowtail="empty",
+ style="dashed"];
+
+ Style -> BgRenderer [headlabel="0..1", taillabel="1"];
+
+ Widget -> Textblock [arrowhead="none", arrowtail="empty"];
+ Textblock -> "Textblock::Word" [headlabel="*", taillabel="1"];
+ Widget -> Table [arrowhead="none", arrowtail="empty"];
+ Table -> "Table::Row" [headlabel="*", taillabel="1"];
+ Widget -> Image [arrowhead="none", arrowtail="empty"];
+
+ BgAllocation -> Widget [arrowhead="none", arrowtail="empty",
+ style="dashed"];
+ BgAllocation -> "Textblock::Word" [arrowhead="none", arrowtail="empty",
+ style="dashed"];
+ BgAllocation -> "Table::Row" [arrowhead="none", arrowtail="empty",
+ style="dashed"];
+}
+\enddot
+
+<center>[\ref uml-legend "legend"]</center>
+
+
+<h2>Integration into dillo</h2>
+
+\todo Add some references.
+
+
+*/
diff --git a/doc/dw-layout-views.doc b/doc/dw-layout-views.doc
new file mode 100644
index 00000000..f064c671
--- /dev/null
+++ b/doc/dw-layout-views.doc
@@ -0,0 +1,270 @@
+/** \page dw-layout-views Layout and Views
+
+Rendering of Dw is done in a way resembling the model-view pattern, at
+least formally. Actually, the counterpart of the model, the layout
+(dw::core::Layout), does a bit more than a typical model, namely the
+layouting (delegated to the widget tree, see \ref dw-layout-widgets),
+and the views do a bit less than a typical view, i.e. only the actual
+drawing.
+
+Additionally, there is a structure representing common properties of
+the platform, views generally work only together with one specific
+platform. A platform is typically related to the underlying UI
+toolkit, but other uses may be thought of.
+
+This design helps to archieve three important goals:
+
+<ul>
+<li> It makes different views of the same document simple, e.g. the
+ normal viewport and the preview window.
+
+<li> Abstraction of the actual drawing, by different implementations
+ of dw::core::View. Most important, there must be a viewport, but
+ some other views are possible, e.g. a preview window.
+
+<li> It makes portability simple.
+</ul>
+
+
+<h2>Viewports</h2>
+
+Although the design implies that the usage of viewports should be
+fully transparent to the layout module, this cannot be fully archived,
+for the following reasons:
+
+<ul>
+<li> Some features, which are used on the level of dw::core::Widget,
+ e.g. anchors, refer to scrolling positions.
+
+<li> Size hints (see \ref dw-layout-widgets) depend on the viewport
+ sizes, e.g. when the user changes the window size, and so also
+ the size of a viewport, the text within should be rewrapped.
+</ul>
+
+Therefor, dw::core::Layout keeps track of the viewport size, the
+viewport position, and even the thickness of the scrollbars, they are
+relevant, see below for more details. These sizes are always equal in
+all views. However, a given view may not use viewports at all, and
+there may be the case, that no view related to a layout uses
+viewports, in this case, the viewport size is not defined at all.
+
+(The case, that there is no viewport at all, is currently not well
+defined, but this case does not occur currently within dillo.)
+
+Whether a given dw::core::View implementation is a viewport or not, is
+defined by the return value of dw::core::View::usesViewport. If this
+method returns false, the following methods need not to be implemented
+at all:
+
+<ul>
+<li> dw::core::View::getHScrollbarThickness,
+<li> dw::core::View::getVScrollbarThickness,
+<li> dw::core::View::scrollTo, and
+<li> dw::core::View::setViewportSize.
+</ul>
+
+<h3>Scrolling Positions</h3>
+
+The scrolling position is the canvas position at the upper left corner
+of the viewport. Views using viewports must
+
+<ol>
+<li> change this value on request (dw::core::View::scrollTo), and
+<li> tell other changes to the layout, e.g. caused by user events
+ (dw::core::Layout::scrollPosChanged).
+</ol>
+
+Applications of scrolling positions (anchors, test search etc.) are
+handled by the layout, in a way fully transparent to the views.
+
+<h3>Scrollbars</h3>
+
+A feature of the viewport size model are scrollbars. There may be a
+vertical scrollbar and a horizontal scrollbar, displaying the
+relationship between canvas and viewport height or width,
+respectively. If they are not needed, they are hidden, to save screen
+space.
+
+Since scrollbars decrease the usable space of a view, dw::core::Layout
+must know how much space they take. Each view returns, via
+dw::core::View::getHScrollbarThickness and
+dw::core::View::getVScrollbarThickness, how thick they will be, when
+visible. The total space difference is then the maximum of all values,
+which the views return.
+
+Viewport sizes, which denote the size of the viewport widgets, include
+scrollbar thicknesses. When referring to the viewport \em excluding
+the scrollbars space, we will call it "usable viewport size", this is
+the area, which is used to display the canvas.
+
+<h2>Drawing</h2>
+
+A view must implement several drawing methods, which work on the whole
+canvas. If it is neccesary to convert them (e.g. into
+dw::fltk::FltkViewport), this is done in a way fully transparent to
+dw::core::Widget and dw::core::Layout, instead, this is done by the
+view implementation.
+
+There exist following situations:
+
+<ul>
+<li> A view gets an expose event: It will delegate this to the
+ layout (dw::core::Layout::draw), which will then pass it to the
+ widgets (dw::core::Widget::draw), with the view as a parameter.
+ Eventually, the widgets will call drawing methods of the view.
+
+<li> A widget requests a redraw: In this case, the widget will
+ delegate this to the layout (dw::core::Layout::queueDraw), which
+ delegates it to all views (dw::core::View::queueDraw).
+ Typically, the views will queue these requests, for efficiency.
+
+<li> A widget requests a resize: This case is described below, in short,
+ dw::core::View::queueDrawTotal is called for the view.
+</ul>
+
+If the draw method of a widget is implemented in a way that it may
+draw outside of the widget's allocation, it should draw into a
+<i>clipping view.</i> A clipping view is a view related to the actual
+view, which guarantees that the parts drawn outside are discarded. At
+the end, the clipping view is merged into the actual view. Sample
+code:
+
+\code
+void Foo::draw (dw::core::View *view, dw::core::Rectangle *area)
+{
+ // 1. Create a clipping view.
+ dw::core::View clipView =
+ view->getClippingView (allocation.x, allocation.y,
+ allocation.width, getHeight ());
+
+ // 2. Draw into clip_view
+ clipView->doSomeDrawing (...);
+
+ // 3. Draw the children, they receive the clipping view as argument.
+ dw::core::Rectangle *childArea
+ for (<all relevant children>) {
+ if (child->intersects (area, &childArea))
+ child->draw (clipView, childArea);
+ }
+
+ // 4. Merge
+ view->mergeClippingView (clipView);
+}
+\endcode
+
+A drawing process is always embedded into calls of
+dw::core::View::startDrawing and dw::core::View::finishDrawing. An
+implementation of this may e.g. use backing pixmaps, to prevent
+flickering.
+
+
+<h2>Sizes</h2>
+
+Generally, all views show the same layout, which has a given size
+(canvas size). In the simplest case, views do not have an influence on
+the canvas size, so that they are just told about changes of the
+canvas size, by a call to dw::core::View::setCanvasSize. This happens
+in the following situations:
+
+<ul>
+<li> dw::core::Layout::addWidget,
+<li> dw::core::Layout::removeWidget (called by dw::core::Widget::~Widget),
+ and
+<li> dw::core::Layout::queueResize (called by
+ dw::core::Widget::queueResize, when a widget itself requests a size
+ change).
+</ul>
+
+<h3>Viewports</h3>
+
+Furthermore, viewport sizes and scrollbar thicknesses are always the
+same. There are two cases, in which the viewport size changes:
+
+<ul>
+<li> As an reaction on a user event, e.g. when the user changes the
+ window size. In this case, the affected view delegates this
+ change to the layout, by calling
+ dw::core::Layout::viewportSizeChanged. All other views are
+ told about this, by calling dw::core::Layout::setViewportSize.
+
+<li> The viewport size may also depend on the visibility of UI
+ widgets, which depend on the world size, e.g scrollbars,
+ generally called "viewport markers". This is described in an own
+ section.
+</ul>
+
+After the creation of the layout, the viewport size is undefined. When
+a view is attached to a layout, and this view is already to be able to
+define its viewport size, it may already call
+dw::core::Layout::viewportSizeChanged within the implementation of
+dw::core::Layout::setLayout. If not, it may do this, as soon as the
+viewport size gets known.
+
+Generally, the scrollbars have to be considered. If e.g. an HTML page
+is rather small, it looks like this:
+
+\image html dw-viewport-without-scrollbar.png
+
+If some more data is retrieved, so that the height exceeds the
+viewport size, the text has to be rewrapped, since the available width
+gets smaller, due to the vertical scrollbar:
+
+\image html dw-viewport-with-scrollbar.png
+
+Notice the different line breaks.
+
+This means circular dependencies between these different sizes:
+
+<ol>
+<li> Whether the scrollbars are visible or not, determines the
+ usable space of the viewport.
+
+<li> From the usable space of the viewport, the size hints for the
+ toplevel are calculated.
+
+<li> The size hints for the toplevel widgets may have an effect on its
+ size, which is actually the canvas size.
+
+<li> The canvas size determines the visibility of the scrollbarss.
+</ol>
+
+To make an implementation simpler, we simplify the model:
+
+<ol>
+<li> For the calls to dw::core::Widget::setAscent and
+ dw::core::Widget::setDescent, we will always exclude the
+ horizontal scrollbar thickness (i.e. assume the horizontal
+ scrollbar is used, although the visibility is determined correctly).
+
+<li> For the calls to dw::core::Widget::setWidth, we will calculate
+ the usable viewport width, but with the general assumption, that
+ the widget generally gets higher.
+</ol>
+
+This results in the following rules:
+
+<ol>
+<li> Send always (when it changes) dw::core::Layout::viewportHeight
+ minus the maximal value of dw::core::View::getHScrollbarThickness as
+ argument to dw::core::Widget::setAscent, and 0 as argument to
+ dw::core::Widget::setDescent.
+
+<li> There is a flag, dw::core::Layout::canvasHeightGreater, which is set
+ to false in the following cases:
+
+ <ul>
+ <li> dw::core::Layout::addWidget,
+ <li> dw::core::Layout::removeWidget (called by dw::core::Widget::~Widget),
+ and
+ <li> dw::core::Layout::viewportSizeChanged.
+ </ul>
+
+ Whenever the canvas size is calculated (dw::core::Layout::resizeIdle),
+ and dw::core::Layout::canvasHeightGreater is false, a test is made,
+ whether the widget has in the meantime grown that high, that the second
+ argument should be set to true (i.e. the vertical scrollbar gets visible).
+ As soon as and dw::core::Layout::canvasHeightGreater is true, no such test
+ is done anymore.
+</ol>
+
+*/ \ No newline at end of file
diff --git a/doc/dw-layout-widgets.doc b/doc/dw-layout-widgets.doc
new file mode 100644
index 00000000..f5b0ed06
--- /dev/null
+++ b/doc/dw-layout-widgets.doc
@@ -0,0 +1,270 @@
+/** \page dw-layout-widgets Layout and Widgets
+
+Both, the layouting and the drawing is delegated to a tree of
+widgets. A widget represents a given part of the document, e.g. a text
+block, a table, or an image. Widgets may be nested, so layouting and
+drawing may be delegated by one widget to its child widgets.
+
+Where do define the borders of a widget, wheather to combine different
+widgets to one, or to split one widget into multiple ones, should be
+considered based on different concerns:
+
+<ul>
+<li> First, there are some restrictions of Dw:
+
+ <ul>
+ <li> The allocation (this is the space a widget allocates at
+ a time) of a dillo widget is always rectangular, and
+ <li> the allocation of a child widget must be a within the allocation
+ of the parent widget.
+ </ul>
+
+<li> Since some widgets are already rather complex, an important goal
+ is to keep the implementation of the widget simple.
+
+<li> Furthermore, the granularity should not be to fine, because of the
+ overhead each single widget adds.
+</ul>
+
+For CSS, there will be a document tree on top of Dw, this will be
+flexible enough when mapping the document structure on the widget
+structure, so you should not have the document structure in mind.
+
+<h2>Sizes</h2>
+
+\ref dw-widget-sizes
+
+
+<h2>Styles</h2>
+
+Each widget is assigned a style, see dw::core::style for more
+informations.
+
+
+<h2>Iterators</h2>
+
+Widgets must implement dw::core::Widget::iterator. There are several
+common iterators:
+
+<ul>
+<li>dw::core::EmptyIterator, and
+<li>dw::core::TextIterator.
+</ul>
+
+Both hide the constructor, use the \em create method.
+
+These simple iterators only iterate through one widget, it does not
+have to iterate recursively through child widgets. Instead, the type
+dw::core::Content::WIDGET is returned, and the next call of
+dw::core::Iterator::next will return the piece of contents \em after
+(not within) this child widget.
+
+This makes implementation much simpler, for recursive iteration, there
+is dw::core::DeepIterator.
+
+
+<h2>Anchors and Scrolling</h2>
+
+\todo This section is not implemented yet, after the implementation,
+ the documentation should be reviewed.
+
+Here is a description, what is to be done for a widget
+implementation. How to jump to anchors, set scrolling positions
+etc. is described in \ref dw-usage.
+
+<h3>Anchors</h3>
+
+Anchors are position markers, which are identified by a name, which is
+unique in the widget tree. The widget must care about anchors in three
+different situations:
+
+<ol>
+<li> Adding an anchor is inititiated by a specific widget method, e.g.
+ dw::Textblock::addAnchor. Here, dw::core::Widget::addAnchor must be
+ called,
+
+<li> Whenever the position of an anchor is changed,
+ dw::core::Widget::changeAnchor is called (typically, this is done
+ in the implementation of dw::core::Widget::sizeAllocateImpl).
+
+<li> When a widget is destroyed, the anchor must be removed, by calling
+ dw::core::Widget::removeAnchor.
+</ol>
+
+All these methods are delegated to dw::core::Layout, which manages
+anchors centrally. If the anchor in question has been set to jump to,
+the viewport position is automatically adjusted, see \ref
+dw-usage.
+
+
+<h2>Drawing</h2>
+
+In two cases, a widget has to be drawn:
+
+<ol>
+<li> as a reaction on an expose event,
+<li> if the widget content has changed and it needs to be redrawn.
+</ol>
+
+In both cases, drawing is done by the implementation of
+dw::core::Widget::draw. Generally, a widget draws into different views
+(see \ref dw-layout-views), the view to draw into is passed as the
+first argument. In the first case, only the view, which causes the
+expose event, is passed, in the second case, dw::core::Widget::draw is
+called multiple times, once for each view connected to the layout.
+
+Each view provides some primitive methods for drawing, most should be
+obvious. Notice that the views do not know anything about dillo
+widgets, and so coordinates have to be passed as canvas coordinates.
+
+A widget may only draw in its own allocation. If this cannot be
+achieved, a <i>clipping view</i> can be used, this is described in
+\ref dw-layout-views. Generally, drawing should then look like:
+
+\code
+void Foo::draw (dw::core::View *view, dw::core::Rectangle *area)
+{
+ // 1. Create a clipping view.
+ dw::core::View clipView =
+ view->getClippingView (allocation.x, allocation.y,
+ allocation.width, getHeight ());
+
+ // 2. Draw into clip_view
+ clipView->doSomeDrawing (...);
+
+ // 3. Draw the children, they receive the clipping view as argument.
+ dw::core::Rectangle *childArea
+ for (<all relevant children>) {
+ if (child->intersects (area, &childArea))
+ child->draw (clipView, childArea);
+ }
+
+ // 4. Merge
+ view->mergeClippingView (clipView);
+}
+\endcode
+
+Clipping views are expensive, so they should be avoided, when possible.
+
+The second argument to dw::core::Widget::draw is the region, which has
+to be drawn. This may (but needs not) be used for optimization.
+
+If a widget contains child widgets, it must explicitly draw these
+children (see also code example above). For this, there is the useful
+method dw::core::Widget::intersects, which returns, which area of the
+child must be drawn.
+
+<h3>Explicit Redrawing</h3>
+
+If a widget changes its contents, so that it must be redrawn, it must
+call dw::core::Widget::queueDrawArea or
+dw::core::Widget::queueDraw. The first variant expects a region within
+the widget, the second will cause the whole widget to be redrawn. This
+will cause an asynchronous call of dw::core::Widget::draw.
+
+If only the size changes, a call to dw::core::Widget::queueResize is
+sufficient, this will also queue a complete redraw (see \ref
+dw-widget-sizes.)
+
+
+<h2>Mouse Events</h2>
+
+A widget may process mouse events. The views (\ref dw-layout-views)
+pass mouse events to the layout, which then passes them to the
+widgtes. There are two kinds of mouse events:
+
+<ul>
+<li>events returning bool, and
+<li>events returning nothing (void).
+</ul>
+
+The first group consists of:
+
+<ul>
+<li>dw::core::Widget::buttonPressImpl,
+<li>dw::core::Widget::buttonReleaseImpl, and
+<li>dw::core::Widget::motionNotifyImpl.
+</ul>
+
+For these events, a widget returns a boolean value, which denotes,
+whether the widget has processed this event (true) or not (false). In
+the latter case, the event is delegated according to the following
+rules:
+
+<ol>
+<li> First, this event is passed to the bottom-most widget, in which
+ allocation the mouse position is in.
+<li> If the widget does not process this event (returning false), it is
+ passed to the parent, and so on.
+<li> The final result (whether \em any widget has processed this event) is
+ returned to the view.
+</ol>
+
+The view may return this to the UI toolkit, which then interprets this
+in a similar way (whether the viewport, a UI widget, has processed
+this event).
+
+These events return nothing:
+
+<ul>
+<li>dw::core::Widget::enterNotifyImpl and
+<li>dw::core::Widget::leaveNotifyImpl.
+</ul>
+
+since they are bound to a widget.
+
+When processing mouse events, the layout always deals with two
+widgets: the widget, the mouse pointer was in, when the previous mouse
+event was processed, (below called the "old widget") and the widget,
+in which the mouse pointer is now ("new widget").
+
+The following paths are calculated:
+
+<ol>
+<li> the path from the old widget to the nearest common anchestor of the old
+ and the new widget, and
+<li> the path from this anchestor to the new widget.
+</ol>
+
+For the widgets along these paths, dw::core::Widget::enterNotifyImpl
+and dw::core::Widget::leaveNotifyImpl are called.
+
+<h3>Signals</h3>
+
+If a caller outside of the widget is interested in these events, he
+can connect a dw::core::Widget::EventReceiver. For those events with a
+boolean return value, the results of the signal emission is regarded,
+i.e. the delegation of an event to the parent of the widget can be
+stopped by a signal receiver returning true, even if the widget method
+returns false.
+
+First, the widget method is called, then (in any case) the signal is
+emitted.
+
+<h3>Selection</h3>
+
+If your widget has selectable contents, it should delegate the events
+to dw::core::SelectionState (dw::core::Layout::selectionState).
+
+
+<h2>Miscellaneous</h2>
+
+<h3>Cursors</h3>
+
+Each widget has a cursor, which is set by
+dw::core::Widget::setCursor. If a cursor is assigned to a part of a
+widget, this widget must process mouse events, and call
+dw::core::Widget::setCursor explicitly.
+
+(This will change, cursors should become part of
+dw::core::style::Style.)
+
+<h3>Background</h3>
+
+Backgrounds are part of styles
+(dw::core::style::Style::backgroundColor). If a widget assigns
+background colors to parts of a widget (as dw::Table does for rows),
+it must call dw::core::Widget::setBgColor for the children inside this
+part.
+
+*/ \ No newline at end of file
diff --git a/doc/dw-map.doc b/doc/dw-map.doc
new file mode 100644
index 00000000..aebeb7da
--- /dev/null
+++ b/doc/dw-map.doc
@@ -0,0 +1,59 @@
+/** \page dw-map Dillo Widget Documentation Map
+
+This maps includes special documentations as well as longer comments
+in the sources. Arrows denote references between the documents.
+
+\dot
+digraph G {
+ rankdir=LR;
+ node [shape=record, fontname=Helvetica, fontsize=8];
+ fontname=Helvetica; fontsize=8;
+
+ dw_overview [label="Dillo Widget Overview", URL="\ref dw-overview"];
+ dw_usage [label="Dillo Widget Usage", URL="\ref dw-usage"];
+ dw_layout_views [label="Layout and Views", URL="\ref dw-layout-views"];
+ dw_layout_widgets [label="Layout and Widgets",
+ URL="\ref dw-layout-widgets"];
+ dw_widget_sizes [label="Sizes of Dillo Widgets",
+ URL="\ref dw-widget-sizes"];
+ dw_changes [label="Changes to the GTK+-based Release Version",
+ URL="\ref dw-changes"];
+ dw_images_and_backgrounds [label="Images and Backgrounds in Dw",
+ URL="\ref dw-images-and-backgrounds"];
+ dw_Image [label="dw::Image", URL="\ref dw::Image"];
+ dw_core_Imgbuf [label="dw::core::Imgbuf", URL="\ref dw::core::Imgbuf"];
+ dw_core_SelectionState [label="dw::core::SelectionState",
+ URL="\ref dw::core::SelectionState"];
+ dw_core_style [label="dw::core::style", URL="\ref dw::core::style"];
+ dw_Table [label="dw::Table", URL="\ref dw::Table"];
+ dw_Textblock [label="dw::Textblock", URL="\ref dw::Textblock"];
+ dw_core_ui [label="dw::core::ui", URL="\ref dw::core::ui"];
+
+ dw_overview -> dw_changes;
+ dw_overview -> dw_usage;
+ dw_overview -> dw_core_style;
+ dw_overview -> dw_core_ui;
+ dw_overview -> dw_images_and_backgrounds;
+ dw_overview -> dw_layout_widgets;
+ dw_overview -> dw_widget_sizes;
+ dw_overview -> dw_layout_views;
+
+ dw_usage -> dw_Table;
+ dw_usage -> dw_Textblock;
+ dw_usage -> dw_core_style;
+ dw_usage -> dw_core_ui;
+ dw_usage -> dw_images_and_backgrounds;
+
+ dw_layout_widgets -> dw_widget_sizes;
+ dw_layout_widgets -> dw_core_SelectionState;
+
+ dw_widget_sizes -> dw_Table;
+ dw_widget_sizes -> dw_Textblock;
+
+ dw_images_and_backgrounds -> dw_core_Imgbuf;
+ dw_images_and_backgrounds -> dw_Image;
+
+ dw_core_style -> dw_Textblock;
+}
+\enddot
+*/ \ No newline at end of file
diff --git a/doc/dw-overview.doc b/doc/dw-overview.doc
new file mode 100644
index 00000000..2414a5da
--- /dev/null
+++ b/doc/dw-overview.doc
@@ -0,0 +1,157 @@
+/** \page dw-overview Dillo Widget Overview
+
+Note: If you are already familiar with the Gtk+-based version of Dw,
+read \ref dw-changes.
+
+
+The module Dw (Dillo Widget) is responsible for the low-level rendering of
+all resources, e.g. images, plain text, and HTML pages (this is the
+most complex type). Dw is \em not responsible for parsing HTML, or
+decoding image data. Furthermore, the document tree, which is planned
+for CSS, is neither a part of Dw, instead, it is a new module on top
+of Dw.
+
+The rendering, as done by Dw, is split into two phases:
+
+<ul>
+<li> the \em layouting, this means calculating the exact positions of
+ words, lines, etc. (in pixel position), and
+<li> the \em drawing, i.e. making the result of the layouting visible
+ on the screen.
+</ul>
+
+The result of the layouting allocates an area, which is called
+\em canvas.
+
+<h2>Structure</h2>
+
+The whole Dw module can be split into the following parts:
+
+\dot
+digraph G {
+ node [shape=record, fontname=Helvetica, fontsize=10];
+ edge [arrowhead="open", fontname=Helvetica, fontsize=10,
+ labelfontname=Helvetica, labelfontsize=10,
+ color="#404040", labelfontcolor="#000080"];
+
+ subgraph cluster_core {
+ style="dashed"; color="#000080"; fontname=Helvetica; fontsize=10;
+ label="Platform independent core";
+
+ Layout [URL="\ref dw::core::Layout"];
+ Platform [URL="\ref dw::core::Platform", color="#ff8080"];
+ View [URL="\ref dw::core::View", color="#ff8080"];
+ Widget [URL="\ref dw::core::Widget", color="#a0a0a0"];
+ }
+
+ subgraph cluster_fltk {
+ style="dashed"; color="#000080"; fontname=Helvetica; fontsize=10;
+ label="FLTK specific part (as an\nexample for the platform specific\n\
+implementations)";
+
+ subgraph cluster_fltkcore {
+ style="dashed"; color="#000080"; fontname=Helvetica; fontsize=10;
+ label="FLTK core";
+
+ FltkPlatform [URL="\ref dw::fltk::FltkPlatform"];
+ FltkView [URL="\ref dw::fltk::FltkView", color="#ff8080"];
+ }
+
+ FltkViewport [URL="\ref dw::fltk::FltkViewport"];
+ FltkPreview [URL="\ref dw::fltk::FltkPreview"];
+ }
+
+ subgraph cluster_widgets {
+ style="dashed"; color="#000080"; fontname=Helvetica; fontsize=10;
+ label="Platform independent widgets";
+
+ Textblock [URL="\ref dw::Textblock"];
+ AlignedTextblock [URL="\ref dw::AlignedTextblock", color="#a0a0a0"];
+ Table [URL="\ref dw::Table"];
+ Image [URL="\ref dw::Image"];
+ etc1 [label="..."];
+ etc2 [label="..."];
+ }
+
+ Layout -> Platform [headlabel="1", taillabel="1"];
+ Layout -> View [headlabel="*", taillabel="1"];
+
+ Layout -> Widget [headlabel="1", taillabel="1", label="topLevel"];
+ Widget -> Widget [headlabel="*", taillabel="1", label="children"];
+
+ Widget -> Textblock [arrowhead="none", arrowtail="empty"];
+ Widget -> Table [arrowhead="none", arrowtail="empty"];
+ Widget -> Image [arrowhead="none", arrowtail="empty"];
+ Widget -> etc1 [arrowhead="none", arrowtail="empty"];
+ Textblock -> AlignedTextblock [arrowhead="none", arrowtail="empty"];
+ AlignedTextblock -> etc2 [arrowhead="none", arrowtail="empty"];
+
+ Platform -> FltkPlatform [arrowhead="none", arrowtail="empty",
+ style="dashed"];
+ FltkPlatform -> FltkView [headlabel="*", taillabel="1"];
+
+ View -> FltkView [arrowhead="none", arrowtail="empty"];
+ FltkView -> FltkViewport [arrowhead="none", arrowtail="empty",
+ style="dashed"];
+ FltkView -> FltkPreview [arrowhead="none", arrowtail="empty",
+ style="dashed"];
+}
+\enddot
+
+<center>[\ref uml-legend "legend"]</center>
+
+\em Platform means in most cases the underlying UI toolkit
+(e.g. FLTK). A layout is bound to a specific platform, but multiple
+platforms may be handled in one program.
+
+A short overview:
+
+<ul>
+<li> dw::core::Layout is the central class, it manages the widgets and the
+ views, and provides delegation methods for the platform.
+
+<li> The layouting is done by a tree of widgets (details are described in
+ \ref dw-layout-widgets), also the drawing, which is finally delegated
+ to the views.
+
+<li> The views (implementations of dw::core::View) provide primitive methods
+ for drawing, but also have an influence on
+ the canvas size (via size hints). See \ref dw-layout-views for details.
+
+<li> Some platform dependencies are handled by implementations
+ of dw::core::Platform.
+</ul>
+
+
+<h3>Header Files</h3>
+
+The structures mentioned above can be found in the following header
+files:
+
+<ul>
+<li> Anything from the Dw core in core.hh. Do not include the single files.
+
+<li> The single widgets can be found in the respective header files, e.g.
+ image.hh for dw::Image.
+
+<li> The core of the FLTK implementation is defined in fltkcore.hh. This
+ includes dw::fltk::FltkPlatform, dw::fltk::FltkView, but not the concrete
+ view implementations.
+
+<li> The views can be found in single header files, e.g fltkviewport.hh for
+ dw::fltk::FltkViewport.
+</ul>
+
+
+<h2>Further Documentations</h2>
+
+A complete map can be found at \ref dw-map.
+
+<ul>
+<li> For learning, how to use Dw, read \ref dw-usage and related documents,
+ dw::core::style, dw::core::ui and \ref dw-images-and-backgrounds.
+<li> Advanced topics are described in \ref dw-layout-widgets,
+ \ref dw-widget-sizes and \ref dw-layout-views.
+</ul>
+
+*/ \ No newline at end of file
diff --git a/doc/dw-usage.doc b/doc/dw-usage.doc
new file mode 100644
index 00000000..d0aa8d36
--- /dev/null
+++ b/doc/dw-usage.doc
@@ -0,0 +1,373 @@
+/** \page dw-usage Dillo Widget Usage
+
+This document describes the usage of Dw, without going too much into
+detail.
+
+
+<h2>Getting Started</h2>
+
+In this section, a small runnable example is described, based on the
+FLTK implementation.
+
+As described in \ref dw-overview, the following objects are needed:
+
+<ul>
+<li> dw::core::Layout,
+<li> an implementation of dw::core::Platform (we will use
+ dw::fltk::FltkPlatform),
+<li> at least one implementation of dw::core::View (dw::fltk::FltkViewport),
+ and
+<li> some widgets (for this example, only a simple dw::Textblock).
+</ul>
+
+First of all, the necessary #include's:
+
+\code
+#include <fltk/Window.h>
+#include <fltk/run.h>
+
+#include "dw/core.hh"
+#include "dw/fltkcore.hh"
+#include "dw/fltkviewport.hh"
+#include "dw/textblock.hh"
+\endcode
+
+Everything is put into one function:
+
+\code
+int main(int argc, char **argv)
+{
+\endcode
+
+As the first object, the platform is instanciated:
+
+\code
+ dw::fltk::FltkPlatform *platform = new dw::fltk::FltkPlatform ();
+\endcode
+
+Then, the layout is created, with the platform attached:
+
+\code
+ dw::core::Layout *layout = new dw::core::Layout (platform);
+\endcode
+
+For the view, we first need a FLTK window:
+
+\code
+ fltk::Window *window = new fltk::Window(200, 300, "Dw Example");
+ window->begin();
+\endcode
+
+After this, we can create a viewport, and attach it to the layout:
+
+\code
+ dw::fltk::FltkViewport *viewport =
+ new dw::fltk::FltkViewport (0, 0, 200, 300);
+ layout->attachView (viewport);
+\endcode
+
+Each widget needs a style (dw::core::style::Style, see dw::core::style),
+so we construct it here. For this, we need to fill a
+dw::core::style::StyleAttrs structure with values, and call
+dw::core::style::Style::create (latter is done further below):
+
+\code
+ dw::core::style::StyleAttrs styleAttrs;
+ styleAttrs.initValues ();
+ styleAttrs.margin.setVal (5);
+\endcode
+
+dw::core::style::StyleAttrs::initValues sets several default
+values. The last line sets a margin of 5 pixels. Next, we need a
+font. Fonts are created in a similar way, first, the attributes are
+defined:
+
+\code
+ dw::core::style::FontAttrs fontAttrs;
+ fontAttrs.name = "Bitstream Charter";
+ fontAttrs.size = 14;
+ fontAttrs.weight = 400;
+ fontAttrs.style = dw::core::style::FONT_STYLE_NORMAL;
+\endcode
+
+Now, the font can be created:
+
+\code
+ styleAttrs.font = dw::core::style::Font::create (layout, &fontAttrs);
+\endcode
+
+As the last attributes, the background and forground colors are
+defined, here dw::core::style::Color::createSimple must be called:
+
+\code
+ styleAttrs.color =
+ dw::core::style::Color::createSimple (layout, 0x000000);
+ styleAttrs.backgroundColor =
+ dw::core::style::Color::createSimple (layout, 0xffffff);
+\endcode
+
+Finally, the style for the widget is created:
+
+\code
+ dw::core::style::Style *widgetStyle =
+ dw::core::style::Style::create (layout, &styleAttrs);
+\endcode
+
+Now, we create a widget, assign a style to it, and set it as the
+toplevel widget of the layout:
+
+\code
+ dw::Textblock *textblock = new dw::Textblock (false);
+ textblock->setStyle (widgetStyle);
+ layout->setWidget (textblock);
+\endcode
+
+The style is not needed anymore (a reference is added in
+dw::core::Widget::setStyle), so it should be unreferred:
+
+\code
+ widgetStyle->unref();
+\endcode
+
+Now, some text should be added to the textblock. For this, we first
+need another style. \em styleAttrs can still be used for this. We set
+the margin to 0, and the background color to "transparent":
+
+\code
+ styleAttrs.margin.setVal (0);
+ styleAttrs.backgroundColor = NULL;
+
+ dw::core::style::Style *wordStyle =
+ dw::core::style::Style::create (layout, &styleAttrs);
+\endcode
+
+This loop adds some paragraphs:
+
+\code
+ for(int i = 1; i <= 10; i++) {
+ char buf[4];
+ sprintf(buf, "%d.", i);
+
+ char *words[] = { "This", "is", "the", buf, "paragraph.",
+ "Here", "comes", "some", "more", "text",
+ "to", "demonstrate", "word", "wrapping.",
+ NULL };
+
+ for(int j = 0; words[j]; j++) {
+ textblock->addText(strdup(words[j]), wordStyle);
+\endcode
+
+Notice the \em strdup, dw::Textblock::addText will feel responsible
+for the string, and free the text at the end. (This has been done to
+avoid some overhead in the HTML parser.)
+
+The rest is simple, it also includes spaces (which also have styles):
+
+\code
+ textblock->addSpace(wordStyle);
+ }
+\endcode
+
+Finally, a paragraph break is added, which is 10 pixels high:
+
+\code
+ textblock->addParbreak(10, wordStyle);
+ }
+\endcode
+
+Again, this style should be unreferred:
+
+\code
+ wordStyle->unref();
+\endcode
+
+After adding text, this method should always be called (for faster
+adding large text blocks):
+
+\code
+ textblock->flush ();
+\endcode
+
+Some FLTK stuff to finally show the window:
+
+\code
+ window->resizable(viewport);
+ window->show();
+ int errorCode = fltk::run();
+\endcode
+
+For cleaning up, it is sufficient to destroy the layout:
+
+\code
+ delete layout;
+\endcode
+
+And the rest
+
+\code
+ return errorCode;
+}
+\endcode
+
+If you compile and start the program, you should see the following:
+
+\image html dw-example-screenshot.png
+
+Try to scroll, or to resize the window, you will see, that everything
+is done automatically.
+
+Of course, creating new widgets, adding text to widgets etc. can also
+be done while the program is running, i.e. after fltk::run has been
+called, within timeouts, idles, I/O functions etc. Notice that Dw is
+not thread safe, so that everything should be done within one thread.
+
+With the exception, that you have to call dw::Textblock::flush,
+everything gets imediately visible, within reasonable times; Dw has
+been optimized for frequent updates.
+
+
+<h2>List of all Widgets</h2>
+
+These widgets are used within dillo:
+
+<ul>
+<li>dw::core::ui::Embed
+<li>dw::AlignedTextblock
+<li>dw::Bullet
+<li>dw::Ruler
+<li>dw::Image
+<li>dw::ListItem
+<li>dw::Table
+<li>dw::TableCell
+<li>dw::Textblock
+</ul>
+
+If you want to create a new widget, refer to \ref dw-layout-widgets.
+
+
+<h2>List of Views</h2>
+
+There are three dw::core::View implementations for FLTK:
+
+<ul>
+<li> dw::fltk::FltkViewport implements a viewport, which is used in the
+ example above.
+
+<li> dw::fltk::FltkPreview implements a preview window, together with
+ dw::fltk::FltkPreviewButton, it is possible to have a scaled down
+ overview of the whole canvas.
+
+<li> dw::fltk::FltkFlatView is a "flat" view, i.e. it does not support
+ scrolling. It is used for HTML buttons, see
+ dw::fltk::ui::FltkComplexButtonResource and especially
+ dw::fltk::ui::FltkComplexButtonResource::createNewWidget for details.
+</ul>
+
+More informations about views in general can be found in \ref
+dw-layout-views.
+
+
+<h2>Iterators</h2>
+
+For examining generally the contents of widgets, there are iterators
+(dw::core::Iterator), created by the method
+dw::core::Widget::iterator (see there for more details).
+
+These simple iterators only iterate through one widget, and return
+child widgets as dw::core::Content::WIDGET. The next call of
+dw::core::Iterator::next will return the piece of contents \em after
+(not within) this child widget.
+
+If you want to iterate through the whole widget trees, there are two
+possibilities:
+
+<ol>
+<li> Use a recursive function. Of course, with this approach, you are
+ limited by the program flow.
+
+<li> Maintain a stack of iterators, so you can freely pass this stack
+ around. This is already implemented, as dw::core::DeepIterator.
+</ol>
+
+As an example, dw::core::SelectionState represents the selected region
+as two instances of dw::core::DeepIterator.
+
+
+<h2>Finding Text</h2>
+
+See dw::core::Layout::findtextState and dw::core::FindtextState
+(details in the latter). There are delegation methods:
+
+<ul>
+<li> dw::core::Layout::search and
+<li> dw::core::Layout::resetSearch.
+</ul>
+
+
+<h2>Anchors and Scrolling</h2>
+
+In some cases, it is necessary to scroll to a given position, or to
+an anchor, programmatically.
+
+<h3>Anchors</h3>
+
+Anchors are defined by widgets, e.g. dw::Textblock defines them, when
+dw::Textblock::addAnchor is called. To jump to a specific anchor
+within the current widget tree, use dw::core::Layout::setAnchor.
+
+This can be done immediately after assignig a toplevel widget, even
+when the anchor has not yet been defined. The layout will remember the
+anchor, and jump to the respective position, as soon as possible. Even
+if the anchor position changes (e.g., when an anchor is moved
+downwards, since some space is needed for an image in the text above),
+the position is corrected.
+
+As soon as the user scrolls the viewport, this correction is not done
+anymore. If in dillo, the user request a page with an anchor, which is
+quite at the bottom of the page, he may be get interested in the text
+at the beginning of the page, and so scrolling down. If then, after
+the anchor has been read and added to the dw::Textblock, this anchor
+would be jumped at, the user would become confused.
+
+The anchor is dismissed, too, when the toplevel widget is removed
+again.
+
+\todo Currently, anchors only define vertical positions.
+
+<h3>Scrolling</h3>
+
+To scroll to a given position, use the method
+dw::core::Layout::scrollTo. It expects several parameters:
+
+<ul>
+<li>a horizontal adjustment parameter, defined by dw::core::HPosition,
+<li>a vertical adjustment parameter, defined by dw::core::VPosition, and
+<li>a rectangle (\em x, \em y, \em width and \em heigh) of the region
+ to be adjusted.
+</ul>
+
+If you just want to move the canvas coordinate (\em x, \em y) into the
+upper left corner of the viewport, you can call:
+
+\code
+dw::core::Layout *layout;
+// ...
+layout->scrollTo(dw::core::HPOS_LEFT, dw::core::VPOS_TOP, 0, 0, 0, 0);
+\endcode
+
+By using dw::core::HPOS_NO_CHANGE or dw::core::VPOS_NO_CHANGE, you can
+change only one dimension. dw::core::HPOS_INTO_VIEW and
+dw::core::VPOS_INTO_VIEW will cause the viewport to move as much as
+necessary, that the region is visible in the viewport (this is
+e.g. used for finding text).
+
+
+<h2>Further Documentations</h2>
+
+<ul>
+<li> dw::core::style
+<li> dw::core::ui
+<li> \ref dw-images-and-backgrounds
+</ul>
+
+*/
diff --git a/doc/dw-widget-sizes.doc b/doc/dw-widget-sizes.doc
new file mode 100644
index 00000000..989eb83c
--- /dev/null
+++ b/doc/dw-widget-sizes.doc
@@ -0,0 +1,186 @@
+/** \page dw-widget-sizes Sizes of Dillo Widgets
+
+<h2>Allocation</h2>
+
+Each widget has an \em allocation at a given time, this includes
+
+<ul>
+<li> the position (\em x, \em y) relative to the upper left corner of the
+ canvas, and
+<li> the size (\em width, \em ascent, \em descent).
+</ul>
+
+The \em canvas is the whole area available for the widgets, in most
+cases, only a part is seen in a viewport. The allocation of the toplevel widget is exactly the allocation of the canvas, i.e.
+
+<ul>
+<li> the position of the toplevel widget is always (0, 0), and
+<li> the canvas size is defined by the size of the toplevel widget.
+</ul>
+
+The size of a widget is not simply defined by the width and the
+height, instead, widgets may have a base line, and so are vertically
+divided into an ascender (which height is called \em ascent), and a
+descender (which height is called \em descent). The total height is so
+the sum of \em ascent and \em descent.
+
+Sizes of zero are allowed. The upper limit for the size of a widget is
+defined by the limits of the C++ type \em int.
+
+\image html dw-size-of-widget.png Allocation of a Widget
+
+In the example in the image, the widget has the following allocation:
+
+<ul>
+<li>\em x = 50
+<li>\em y = 50
+<li>\em width = 150
+<li>\em ascent = 150
+<li>\em descent = 100
+</ul>
+
+The current allocation of a widget is hold in
+dw::core::Widget::allocation. It can be set from outside by
+callcalling dw::core::Widget::sizeAllocate. This is a concrete method,
+which will call dw::core::Widget::sizeAllocateImpl (see code of
+dw::core::Widget::sizeAllocate for details).
+
+For trivial widgets (like dw::Bullet),
+dw::core::Widget::sizeAllocateImpl does not need to be
+implemented. For more complex widgets, the implementation should call
+dw::core::Widget::sizeAllocate (not
+dw::core::Widget::sizeAllocateImpl) on all child widgets, with
+appropriate child allocations. dw::core::Widget::allocation should not
+be changed here, this is already done in
+dw::core::Widget::sizeAllocate.
+
+<h2>Requisitions</h2>
+
+A widget may prefer a given size for the allocation. This size, the
+\em requisition, should be returned by the method
+dw::core::Widget::sizeRequestImpl. In the simplest case, this is
+independent of the context, e.g. for an
+image. dw::Image::sizeRequestImpl returns the following size:
+
+<ul>
+<li> If no buffer has yet been assigned (see dw::Image for more details),
+ the size necessary for the alternative text is returned. If no
+ alternative text has been set, zero is returned.
+
+<li> If a buffer has been assigned (by dw::Image::setBuffer), the root
+ size is returned (i.e. the original size of the image to display).
+</ul>
+
+This is a bit simplified, dw::Image::sizeRequestImpl should also deal
+with margins, borders and paddings, see dw::core::style.
+
+From the outside, dw::Image::sizeRequest should be called, which does
+a bit of optimization. Notice that in dw::Image::sizeRequestImpl, no
+optimization like lazy evaluation is necessary, this is already done
+in dw::Image::sizeRequest.
+
+A widget, which has children, will likely call dw::Image::sizeRequest
+on its children, to calculate the total requisition.
+
+The caller (this is either the dw::core::Layout, or the parent
+widget), may, but also may not consider the requisition. Instead, a
+widget must deal with any allocation. (For example, dw::Image scales
+the image buffer when allocated at another size.)
+
+<h2>Size Hints</h2>
+
+Some widgets do not have an inherent size, but depend on the context,
+e.g. the viewport size. These widgets should adhere to <i>size hints</i>,
+i.e. implement the methods dw::core::Widget::setWidth,
+dw::core::Widget::setAscent and dw::core::Widget::setDescent. The values
+passed to the calles are
+
+<ul>
+<li> the viewport size (ascent is the heigt here, while descent is 0) for
+ the toplevel widget, and
+<li> determined by the parent for its child widgets.
+</ul>
+
+Generally, the values should define the available space for the
+widget.
+
+A widget, which depends on size hints, should call
+dw::core::Widget::queueResize, when apropriate.
+
+\todo There should be a definition of "available space".
+
+<h2>Width Extremes</h2>
+
+dw::Table uses width extremes for fast calculation of column
+widths. The structure dw::core::Extremes represents the minimal and
+maximal width of a widget, as defined by:
+
+<ul>
+<li> the minimal width is the smallest width, at which a widget can still
+ display contents, and
+<li> the maximal width is the largest width, above which increasing the width
+ does not make any sense.
+</ul>
+
+Especially the latter is vaguely defined, here are some examples:
+
+<ul>
+<li> For those widgets, which do not depend on size hints, the minimal and
+ the maximal width is the inherent width (the one returned by
+ dw::core::Widget::sizeRequest).
+
+<li> For a textblock, the minimal width is the width of the widest
+ (unbreakable) word, the maximal width is the width of the total
+ paragraph (stretching a paragraph further would only waste space).
+ Actually, the implementation of dw::Textblock::getExtremesImpl is
+ a bit more complex.
+
+<li> dw::Table is an example, where the width extremes are calculated
+ from the width extremes of the children.
+</ul>
+
+Handling width extremes is similar to handling requisitions, a widget
+must implement dw::core::Widget::getExtremesImpl, but a caller will
+use dw::core::Widget::getExtremes.
+
+
+<h2>Resizing</h2>
+
+When the widget changes its size (requisition), it should call
+dw::core::Widget::queueResize. The next call of
+dw::core::Widget::sizeRequestImpl should then return the new
+size. See dw::Image::setBuffer as an example.
+
+Interna are described in the code of dw::core::Widget::queueResize.
+
+<h3>Incremental Resizing</h3>
+
+A widget may calculate its size based on size calculations already
+done before. In this case, a widget must exactly know the reasons, why
+a call of dw::core::Widget::sizeRequestImpl is necessary. To make use
+of this, a widget must implement the following:
+
+<ol>
+<li> There is a member dw::core::Widget::parentRef, which is
+ totally under control of the parent widget (and so sometimes not
+ used at all). It is necessary to define how parentRef is used
+ by a specific parent widget, and it has to be set to the correct
+ value whenever necessary.
+
+<li> The widget must implement dw::core::Widget::markSizeChange and
+ dw::core::Widget::markExtremesChange, these methods are called in
+ two cases:
+
+ <ol>
+ <li> directly after dw::core::Widget::queueResize, with the argument
+ ref was passed to dw::core::Widget::queueResize, and
+ <li> if a child widget has called dw::core::Widget::queueResize,
+ with the value of the parent_ref member of this child.
+ </ol>
+</ol>
+
+This way, a widget can exactly keep track on size changes, and so
+implement resizing in a faster way. A good example on how to use this
+is dw::Textblock.
+
+*/ \ No newline at end of file
diff --git a/doc/fltk-problems.doc b/doc/fltk-problems.doc
new file mode 100644
index 00000000..3e8c401f
--- /dev/null
+++ b/doc/fltk-problems.doc
@@ -0,0 +1,219 @@
+/** \page fltk-problems Problems with FLTK
+
+<h2>dw::fltk::FltkViewport</h2>
+
+Current problems:
+
+<ul>
+<li> dw::fltk::FltkViewport::draw should only draw the region, for which e.g.
+ an expose event was received.
+
+<li> dw::fltk::FltkViewport::queueDraw will collect data, which has to be
+ redrawn. Currently, it calls redraw (DAMAGE_EXPOSE), can this be changed,
+ so that dw::fltk::FltkViewport::draw will distinguish between the two
+ cases?
+
+<li> For Scrolling, something similar applies, only parts of the viewport
+ have to be redrawn.
+
+<li> Also for Scrolling, it is necessary to copy parts of the window.
+
+<li> How should dw::fltk::FltkViewport::cancelQueueDraw be implemented?
+
+<li> If the value of a scrollbar is changed by the program, not the user,
+ the callback seems not to be called. Can this be assured?
+
+<li> The same for dw::fltk::FltkViewport::layout?
+
+<li> Also, the problems with the widgets seems to work. Also sure?
+
+<li> When drawing, clipping of 32 bit values seems to work.
+
+<li> Who is responsable for clearing before drawing?
+
+<li> The embedded buttons are not redrawn, when pressing the mouse button
+ on them.
+
+<li> The item group within a selection widget (menu) should not be selectable.
+</ul>
+
+
+<h2>dw::fltk::FltkPlatform</h2>
+
+<ul>
+<li> There is the problem, that fltk::font always returns a font, the
+ required one, or a replacements. The latter is not wanted in all
+ cases, e.g. when several fonts are tested. Perhaps, this could be
+ solved by searching in the font list.
+
+<li> In dw::fltk::FltkFont::FltkFont, fltk::measure does not seem to work
+ for the calculation of dw::core::style::Font::xHeight.
+
+<li> Distinction between italics and oblique would be nice
+ (dw::fltk::FltkFont::FltkFont).
+</ul>
+
+
+<h2>dw::fltk::ui::FltkCheckButtonResource</h2>
+
+Groups of fltk::RadioButton must be added to one fltk::Group, which is
+not possible in this context. There are two alternatives:
+
+<ol>
+<li>there is a more flexible way to group radio buttons, or
+<li>radio buttons are not grouped, instead, grouping (especially
+ unchecking other buttons) is done by the application.
+</ol>
+
+(This is mostly solved.)
+
+<h2>dw::fltk::FltkImgbuf</h2>
+
+Alpha transparency should be best abstracted by FLTK itself. If not,
+perhaps different implementations for different window systems could
+be used. Then, it is for X necessary to use GCs with clipping masks.
+
+
+<h2>Lower Priority</h2>
+
+There needs to be an XEmbed implementation.
+
+
+<h2>dw::fltk::ui::ComplexButton</h2>
+
+Unfortunately, FLTK does not provide a button with Group as parent, so
+that children may be added to the button. dw::fltk::ui::ComplexButton does
+exactly this, and is, in an ugly way, a modified copy of the FLTK
+button.
+
+It would be nice, if this is merged with the standard FLTK
+button. Furthermore, setting the type is strange.
+
+If the files do not compile, it may be useful to create a new one from
+the FLTK source:
+
+<ol>
+<li> Copy fltk/Button.h from FLTK to dw/fltkcomplexbutton.hh and
+ src/Button.cxx to dw/fltkcomplexbutton.cc.
+
+<li> In both files, rename "Button" to "ComplexButton". Automatic replacing
+ should work.
+
+<li> Apply the changes below.
+</ol>
+
+The following changes should be applied manually.
+
+<h3>Changes in fltkcomplexbutton.hh</h3>
+
+First of all, the #define's for avoiding multiple includes:
+
+\code
+-#ifndef fltk_ComplexButton_h // fltk_Button_h formerly
+-#define fltk_ComplexButton_h
++#ifndef __FLTK_COMPLEX_BUTTON_HH__
++#define __FLTK_COMPLEX_BUTTON_HH__
+\endcode
+
+at the beginning and
+
+\code
+-#endif
++#endif // __FLTK_COMPLEX_BUTTON_HH__
+\endcode
+
+at the end. Then, the namespace is changed:
+
+\code
+-namespace fltk {
++namespace dw {
++namespace fltk {
++namespace ui {
+\endcode
+
+at the beginning and
+
+\code
+-}
++} // namespace ui
++} // namespace fltk
++} // namespace dw
+\endcode
+
+at the end. Most important, the base class is changed:
+
+\code
+-#ifndef fltk_Widget_h
+-#include "Widget.h"
+-#endif
++#include <fltk/Group.h>
+\endcode
+
+and
+
+\code
+-class FL_API ComplexButton : public Widget {
++class ComplexButton: public ::fltk::Group
++{
+\endcode
+
+Finally, for dw::fltk::ui::ComplexButton::default_style, there is a
+namespace conflict:
+
+\code
+- static NamedStyle* default_style;
++ static ::fltk::NamedStyle* default_style;
+\endcode
+
+<h3>Changes in fltkcomplexbutton.cc</h3>
+
+First, #include's:
+
+\code
+
+ #include <fltk/events.h>
+ #include <fltk/damage.h>
+-#include <fltk/ComplexButton.h> // <fltk/Button.h> formerly
+ #include <fltk/Group.h>
+ #include <fltk/Box.h>
+ #include <stdlib.h>
++
++#include "fltkcomplexbutton.hh"
+\endcode
+
+Second, namespaces:
+
+\code
++using namespace dw::fltk::ui;
+ using namespace fltk;
+\endcode
+
+Since the base class is now Group, the constructor must be changed:
+
+\code
+-ComplexButton::ComplexButton(int x,int y,int w,int h, const char *l) : Widget(x,y,w,h,l) {
++ComplexButton::ComplexButton(int x,int y,int w,int h, const char *l) :
++ Group(x,y,w,h,l)
++{
+\endcode
+
+At the end of the constructor,
+
+\code
++ type (NORMAL);
+ }
+\endcode
+
+must be added (I've forgotten, what this is for).
+
+Finally, the button must draw its children (end of
+dw::fltk::ui::ComplexButton::draw()):
+
+\code
++
++ for (int i = 0; i < children (); i++)
++ draw_child (*child (i));
+ }
+\endcode
+
+*/ \ No newline at end of file
diff --git a/doc/index.doc b/doc/index.doc
new file mode 100644
index 00000000..9892f177
--- /dev/null
+++ b/doc/index.doc
@@ -0,0 +1,48 @@
+/** \mainpage
+
+<h2>Overview</h2>
+
+This is a list of documents to start with:
+
+<ul>
+<li> \ref lout
+<li> \ref dw-overview (map at \ref dw-map)
+</ul>
+
+Currently, a document \ref fltk-problems is maintained, ideally, it
+will be removed soon.
+
+<h2>Historical</h2>
+
+<h3>Replacements for GTK+ and GLib</h3>
+
+There are several classes etc., which are used for tasks formerly (in the GTK+
+version of dillo) achieved by GtkObject (in 1.2.x, this is part of Gtk+) and
+GLib. For an overview on all this, take a look at \ref lout.
+
+GtkObject is replaced by the following:
+
+<ul>
+<li> object::Object is a common base class for many classes used dillo. In
+ the namespace ::object, there are also some more common classes and
+ interfaces.
+
+<li> A sub class of object::Object is identity::IdentifiableObject, which
+ allows to determine the class at run-time (equivalent to GTK_CHECK_CAST
+ in GtkObject).
+
+<li> For signals, there is the namespace ::signal.
+</ul>
+
+Hash tables, linked lists etc. can be found in the ::container namespace,
+several useful macros from GLib have been implemented as inline functions
+in the ::misc namespace.
+
+As an alternative to the macros defined in list.h, there is also a template
+class, misc::SimpleVector, which does the same.
+
+<h3>Changes in Dw</h3>
+
+If you have been familiar with Dw before, take a look at \ref dw-changes.
+
+*/
diff --git a/doc/lout.doc b/doc/lout.doc
new file mode 100644
index 00000000..0d5be679
--- /dev/null
+++ b/doc/lout.doc
@@ -0,0 +1,94 @@
+/** \page lout Lots of Useful Tools
+
+In the "lout" directory, there are some common base functionality for
+C++. Most is described as doxygen comments, this text gives an
+overview.
+
+<h2>Common Base Class</h2>
+
+Many classes are derived from object::Object, which defines some
+general methods. See there for more information.
+
+For the case, that you need primitive C++ types, there are some
+wrappers:
+
+<table>
+<tr><th>C++ Type <th>Wrapper Class
+<tr><td>void* <td>object::Pointer
+<tr><td>specific pointer <td>object::TypedPointer (template class)
+<tr><td>int <td>object::Integer
+<tr><td>const char* <td>object::ConstString
+<tr><td>char* <td>object::String
+</table>
+
+
+<h2>Containers</h2>
+
+In the namespace ::container, several container classes are defined,
+which all deal with instances of object::Object.
+
+<h3>Untyped Containers</h3>
+
+In container::untyped, there are the following containers:
+
+<ul>
+<li>container::untyped::Vector, a dynamically increases array,
+<li>container::untyped::List, a linked list,
+<li>container::untyped::HashTable, a hash table, and
+<li>container::untyped::Stack, a stack.
+</ul>
+
+All provide specific methods, but since they have a common base class,
+container::untyped::Collection, they all provide iterators, by the
+method container::untyped::Collection::iterator.
+
+<h3>Typed Containers</h3>
+
+container::typed provides wrappers for the container classes defined
+in container::untyped, which are more type safe, by using C++
+templates.
+
+
+<h2>Signals</h2>
+
+For how to connect objects at run-time (to reduce dependencies), take a
+look at the ::signal namespace.
+
+There is also a base class signal::ObservedObject, which implements
+signals for deletion.
+
+
+<h2>Debugging</h2>
+
+In debug.hh, there are some some useful macros for debugging messages,
+see the file for mor informations.
+
+
+<h2>Identifying Classes at Runtime</h2>
+
+If the class of an object must be identified at runtime,
+identity::IdentifiableObject should be used as the base class, see
+there for more details.
+
+
+<h2>Miscellaneous</h2>
+
+The ::misc namespace provides several miscellaneous stuff:
+
+<ul>
+<li> In some contexts, it is necessary to compare objects
+ (less/greater), for this, also misc::Comparable must be
+ implemented. For example., container::untyped::Vector::sort and
+ container::typed::Vector::sort cast the elements to
+ misc::Comparable. This can be mixed with object::Object.
+<li> misc::SimpleVector, a simple, template based vector class (not
+ depending on object::Object),
+<li> misc::StringBuffer, class for fast concatenation of a large number
+ of strings,
+<li> misc::BitSet implements a bitset.
+<li> useful (template) functions (misc::min, misc::max), and
+<li> some functions useful for runtime checks (misc::assert,
+ misc::assertNotReached).
+</ul>
+
+*/
diff --git a/doc/rounding-errors.doc b/doc/rounding-errors.doc
new file mode 100644
index 00000000..433d6ed9
--- /dev/null
+++ b/doc/rounding-errors.doc
@@ -0,0 +1,24 @@
+/** \page rounding-errors How to Avoid Rounding Errors
+
+(Probably, this is a standard algorithm, so if someone knows the name,
+drop me a note.)
+
+If something like
+
+\f[y_i = {x_i a \over b}\f]
+
+is to be calculated, and all numbers are integers, a naive
+implementation would result in something, for which
+
+\f[\sum y_i \ne {(\sum x_i) a \over b}\f]
+
+because of rounding errors, due to the integer division. This can be
+avoided by transforming the formula into
+
+\f[y_i = {(\sum_{j=0}^{j=i} x_j) a \over b} - \sum_{j=0}^{j=i} y_j\f]
+
+Of corse, when all \f$y_i\f$ are calculated in a sequence,
+\f$\sum_{j=0}^{j=i} x_j\f$ and \f$\sum_{j=0}^{j=i} y_j\f$ can be
+accumulated in the same loop.
+
+*/ \ No newline at end of file
diff --git a/doc/uml-legend.doc b/doc/uml-legend.doc
new file mode 100644
index 00000000..14a64e7d
--- /dev/null
+++ b/doc/uml-legend.doc
@@ -0,0 +1,192 @@
+/** \page uml-legend UML Legend
+
+This page describes the notation for several diagrams used in the
+documentation, which is a slight variation of UML.
+
+
+<h2>Classes</h2>
+
+Classes are represented by boxes, containing there names:
+
+\dot
+digraph G {
+ node [shape=record, fontname=Helvetica, fontsize=10];
+ fontname=Helvetica; fontsize=8;
+ "Concrete Class";
+ "Abstract Class" [color="#a0a0a0"];
+ Interface [color="#ff8080"];
+}
+\enddot
+
+(In most cases, the attributes and operations are left away, for
+better readibility. Just click on it, to get to the detailed
+description.)
+
+Of course, in C++, there are no interfaces, but here, we call a class,
+which has only virtual abstract methods, and so does not provide any
+functionality, an interface.
+
+Templates get a yellow background color:
+
+\dot
+digraph G {
+ node [shape=record, fontname=Helvetica, fontsize=10,
+ fillcolor="#ffffc0", style="filled"];
+ fontname=Helvetica; fontsize=8;
+ "Concrete Class Template";
+ "Abstract Class Template" [color="#a0a0a0"];
+ "Interface Template" [color="#ff8080"];
+}
+\enddot
+
+
+<h2>Objects</h2>
+
+In some cases, an examle for a concrete constellation of objects is
+shown. An object is represented by a box containing a name and the
+class, separated by a colon.
+
+\dot
+digraph G {
+ node [shape=record, fontname=Helvetica, fontsize=10];
+ edge [arrowhead="open", labelfontname=Helvetica, labelfontsize=10,
+ color="#404040", labelfontcolor="#000080"];
+ fontname=Helvetica; fontsize=10;
+
+ "x: A" -> "y1: B";
+ "x: A" -> "y2: B";
+}
+\enddot
+
+The names (\em x, \em y, and \em z) are only meant within the context
+of the diagram, there needs not to be a relation to the actual names
+in the program. They should be unique within the diagram.
+
+Classes and objects may be mixed in one diagram.
+
+
+<h2>Associations</h2>
+
+\dot
+digraph G {
+ node [shape=record, fontname=Helvetica, fontsize=10];
+ edge [arrowhead="open", labelfontname=Helvetica, labelfontsize=10,
+ color="#404040", labelfontcolor="#000080",
+ fontname=Helvetica, fontsize=10, fontcolor="#000080"];
+ fontname=Helvetica; fontsize=10;
+ A -> B [headlabel="*", taillabel="1", label="x"];
+}
+\enddot
+
+In this example, one instance of A refers to an arbitrary number of B
+instances (denoted by the "*"), and each instance of B is referred by
+exactly one ("1") A. The label \em x is the name of the association,
+in most cases the name of the field, e.g. A::x.
+
+Possible other values for the \em multiplicity:
+
+<ul>
+<li> a concrete number, in most cases "1",
+<li> a range, e.g. "0..1",
+<li> "*", denoting an arbitrary number.
+</ul>
+
+
+<h2>Implementations and Inheritance</h2>
+
+\dot
+digraph G {
+ node [shape=record, fontname=Helvetica, fontsize=10];
+ edge [arrowhead="none", arrowtail="empty", labelfontname=Helvetica,
+ labelfontsize=10, color="#404040", labelfontcolor="#000080"];
+ fontname=Helvetica; fontsize=10;
+ A[color="#ff8080"];
+ B[color="#ff8080"];
+ C;
+ D;
+ A -> B;
+ A -> C [style="dashed"];
+ C -> D;
+}
+\enddot
+
+In this example,
+
+<ul>
+<li> the interface B extends the interface A,
+<li> the class C implements the interface A, and
+<li> the class D extends the class C.
+</ul>
+
+
+<h2>Template Instanciations</h2>
+
+Template instanciations are shown as own classes/interfaces, the
+instanciation by the template is shown by a yellow dashed arrow:
+
+\dot
+digraph G {
+ node [shape=record, fontname=Helvetica, fontsize=10];
+ edge [arrowhead="none", arrowtail="empty", labelfontname=Helvetica,
+ labelfontsize=10, color="#404040", labelfontcolor="#000080"];
+ fontname=Helvetica; fontsize=10;
+
+ A[color="#ff8080"];
+ B[color="#ff8080"];
+ C[color="#ff8080", fillcolor="#ffffc0", style="filled"];
+ C_A[color="#ff8080", label="C \<A\>"];
+ C_B[color="#ff8080", label="C \<A\>"];
+ D;
+
+ C -> C_A [arrowhead="open", arrowtail="none", style="dashed",
+ color="#808000"];
+ C -> C_B [arrowhead="open", arrowtail="none", style="dashed",
+ color="#808000"];
+ A -> C_A;
+ B -> C_B;
+ C_A -> D [style="dashed"];
+}
+\enddot
+
+In this example, the interface template C uses the template argument
+as super interface.
+
+
+<h2>Packages</h2>
+
+Packages are presented by dashed rectangles:
+
+\dot
+digraph G {
+ node [shape=record, fontname=Helvetica, fontsize=10];
+ edge [arrowhead="none", arrowtail="empty", labelfontname=Helvetica,
+ labelfontsize=10, color="#404040", labelfontcolor="#000080"];
+ fontname=Helvetica; fontsize=10;
+
+ subgraph cluster_1 {
+ style="dashed"; color="#000080"; fontname=Helvetica; fontsize=10;
+ label="package 1";
+
+ A;
+ B [color="#a0a0a0"];
+ }
+
+ subgraph cluster_2 {
+ style="dashed"; color="#000080"; fontname=Helvetica; fontsize=10;
+ label="package 2";
+
+ C;
+ D [color="#a0a0a0"];
+ E
+ }
+
+ A -> C;
+ B -> D;
+ D -> E;
+ E -> A [arrowhead="open", arrowtail="none"];
+}
+\enddot
+
+Packages may be nested.
+
+*/ \ No newline at end of file
diff --git a/dw/Makefile.am b/dw/Makefile.am
new file mode 100644
index 00000000..7150fce9
--- /dev/null
+++ b/dw/Makefile.am
@@ -0,0 +1,70 @@
+noinst_LIBRARIES = \
+ libDw-core.a \
+ libDw-fltk.a \
+ libDw-widgets.a
+
+libDw_core_a_SOURCES = \
+ core.hh \
+ events.hh \
+ findtext.cc \
+ findtext.hh \
+ imgbuf.hh \
+ iterator.cc \
+ iterator.hh \
+ layout.cc \
+ layout.hh \
+ platform.hh \
+ selection.hh \
+ selection.cc \
+ style.cc \
+ style.hh \
+ types.cc \
+ types.hh \
+ ui.cc \
+ ui.hh \
+ view.hh \
+ widget.cc \
+ widget.hh
+
+libDw_fltk_a_SOURCES = \
+ fltkcomplexbutton.cc \
+ fltkcomplexbutton.hh \
+ fltkcore.hh \
+ fltkflatview.cc \
+ fltkflatview.hh \
+ fltkimgbuf.cc \
+ fltkimgbuf.hh \
+ fltkmisc.cc \
+ fltkmisc.hh \
+ fltkplatform.cc \
+ fltkplatform.hh \
+ fltkpreview.hh \
+ fltkpreview.cc \
+ fltkui.cc \
+ fltkui.hh \
+ fltkviewbase.cc \
+ fltkviewbase.hh \
+ fltkviewport.cc \
+ fltkviewport.hh
+
+libDw_fltk_a_CXXFLAGS = @LIBFLTK_CXXFLAGS@
+
+libDw_widgets_a_SOURCES = \
+ alignedtextblock.cc \
+ alignedtextblock.hh \
+ bullet.cc \
+ bullet.hh \
+ image.cc \
+ image.hh \
+ listitem.cc \
+ listitem.hh \
+ ruler.cc \
+ ruler.hh \
+ table.cc \
+ table.hh \
+ tablecell.cc \
+ tablecell.hh \
+ textblock.cc \
+ textblock.hh
+
+EXTRA_DIST = preview.xbm
diff --git a/dw/alignedtextblock.cc b/dw/alignedtextblock.cc
new file mode 100644
index 00000000..bb24a3bc
--- /dev/null
+++ b/dw/alignedtextblock.cc
@@ -0,0 +1,105 @@
+/*
+ * Dillo Widget
+ *
+ * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+
+
+#include "alignedtextblock.hh"
+#include <stdio.h>
+
+namespace dw {
+
+AlignedTextblock::List::List ()
+{
+ textblocks = new misc::SimpleVector <AlignedTextblock*> (4);
+ values = new misc::SimpleVector <int> (4);
+ maxValue = 0;
+ refCount = 0;
+}
+
+AlignedTextblock::List::~List ()
+{
+ delete textblocks;
+ delete values;
+}
+
+int AlignedTextblock::List::add(AlignedTextblock *textblock)
+{
+ textblocks->increase ();
+ values->increase ();
+ textblocks->set (textblocks->size () - 1, textblock);
+ refCount++;
+ return textblocks->size () - 1;
+}
+
+void AlignedTextblock::List::unref(int pos)
+{
+ assert (textblocks->get (pos) != NULL);
+ textblocks->set (pos, NULL);
+ refCount--;
+
+ if(refCount == 0)
+ delete this;
+}
+
+int AlignedTextblock::CLASS_ID = -1;
+
+AlignedTextblock::AlignedTextblock (bool limitTextWidth):
+ Textblock (limitTextWidth)
+{
+ registerName ("dw::AlignedTextblock", &CLASS_ID);
+}
+
+void AlignedTextblock::setRefTextblock (AlignedTextblock *ref)
+{
+ if(ref == NULL)
+ list = new List();
+ else
+ list = ref->list;
+
+ listPos = list->add (this);
+ updateValue ();
+}
+
+AlignedTextblock::~AlignedTextblock()
+{
+ list->unref (listPos);
+}
+
+void AlignedTextblock::updateValue ()
+{
+ if (list) {
+ list->setValue (listPos, getValue ());
+
+ if (list->getValue (listPos) > list->getMaxValue ()) {
+ // New value greater than current maximum -> apply it to others.
+ list->setMaxValue (list->getValue (listPos));
+
+ for (int i = 0; i < list->size (); i++)
+ if (list->getTextblock (i))
+ list->getTextblock (i)->setMaxValue (list->getMaxValue (),
+ list->getValue (i));
+ } else {
+ /* No change, apply old max_value only to this page. */
+ setMaxValue (list->getMaxValue (), list->getValue (listPos));
+ }
+ }
+}
+
+} // namespace dw
diff --git a/dw/alignedtextblock.hh b/dw/alignedtextblock.hh
new file mode 100644
index 00000000..e855f1cc
--- /dev/null
+++ b/dw/alignedtextblock.hh
@@ -0,0 +1,61 @@
+#ifndef __DW_ALIGNEDTEXTBLOCK_HH__
+#define __DW_ALIGNEDTEXTBLOCK_HH__
+
+#include "core.hh"
+#include "textblock.hh"
+
+namespace dw {
+
+/**
+ * \brief Base widget for all textblocks (sub classes of dw::Textblock), which
+ * are positioned vertically and aligned horizontally.
+ */
+class AlignedTextblock: public Textblock
+{
+private:
+ class List
+ {
+ private:
+ misc::SimpleVector <AlignedTextblock*> *textblocks;
+ misc::SimpleVector <int> *values;
+ int maxValue, refCount;
+
+ ~List ();
+
+ public:
+ List ();
+ inline int add (AlignedTextblock *textblock);
+ void unref (int pos);
+
+ inline int getMaxValue () { return maxValue; }
+ inline void setMaxValue (int maxValue) { this->maxValue = maxValue; }
+
+ inline int size () { return textblocks->size (); }
+ inline AlignedTextblock *getTextblock (int pos) {
+ return textblocks->get (pos); }
+ inline int getValue (int pos) {return values->get (pos); }
+ inline void setValue (int pos, int value) {
+ return values->set (pos, value); }
+ };
+
+ List *list;
+ int listPos;
+
+protected:
+ AlignedTextblock(bool limitTextWidth);
+
+ virtual int getValue () = 0;
+ virtual void setMaxValue (int maxValue, int value) = 0;
+
+ void setRefTextblock (AlignedTextblock *ref);
+ void updateValue ();
+
+public:
+ static int CLASS_ID;
+
+ ~AlignedTextblock();
+};
+
+} // namespace dw
+
+#endif // __DW_ALIGNEDTEXTBLOCK_HH__
diff --git a/dw/bullet.cc b/dw/bullet.cc
new file mode 100644
index 00000000..cc13867c
--- /dev/null
+++ b/dw/bullet.cc
@@ -0,0 +1,72 @@
+/*
+ * Dillo Widget
+ *
+ * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+
+
+#include "bullet.hh"
+
+#include <stdio.h>
+
+namespace dw {
+
+Bullet::Bullet ()
+{
+}
+
+void Bullet::sizeRequestImpl (core::Requisition *requisition)
+{
+ requisition->width = lout::misc::max (getStyle()->font->xHeight * 4 / 5, 1);
+ requisition->ascent = lout::misc::max (getStyle()->font->xHeight, 1);
+ requisition->descent = 0;
+}
+
+void Bullet::draw (core::View *view, core::Rectangle *area)
+{
+ int x, y, l;
+ bool filled = true;
+
+ l = lout::misc::min (allocation.width, allocation.ascent);
+ x = allocation.x;
+ y = allocation.y + allocation.ascent - getStyle()->font->xHeight;
+
+ switch (getStyle()->listStyleType) {
+ case core::style::LIST_STYLE_TYPE_SQUARE:
+ view->drawRectangle (getStyle()->color,
+ core::style::Color::SHADING_NORMAL,
+ false, x, y, l, l);
+ break;
+ case core::style::LIST_STYLE_TYPE_CIRCLE:
+ filled = false;
+ // Fall Through
+ case core::style::LIST_STYLE_TYPE_DISC:
+ default:
+ view->drawArc (getStyle()->color, core::style::Color::SHADING_NORMAL,
+ filled, x, y, l, l, 0, 360);
+ }
+}
+
+core::Iterator *Bullet::iterator (core::Content::Type mask, bool atEnd)
+{
+ //return new core::TextIterator (this, mask, atEnd, "*");
+ /** \bug Not implemented. */
+ return new core::EmptyIterator (this, mask, atEnd);
+}
+
+} // namespace dw
diff --git a/dw/bullet.hh b/dw/bullet.hh
new file mode 100644
index 00000000..00912bd8
--- /dev/null
+++ b/dw/bullet.hh
@@ -0,0 +1,27 @@
+#ifndef __BULLET_HH__
+#define __BULLET_HH__
+
+#include "core.hh"
+
+namespace dw {
+
+/**
+ * \brief Displays different kind of bullets.
+ *
+ * Perhaps, in the future, Unicode characters are used for bullets, so this
+ * widget is not used anymore.
+ */
+class Bullet: public core::Widget
+{
+protected:
+ void sizeRequestImpl (core::Requisition *requisition);
+ void draw (core::View *view, core::Rectangle *area);
+ core::Iterator *iterator (core::Content::Type mask, bool atEnd);
+
+public:
+ Bullet ();
+};
+
+} // namespace dw
+
+#endif // __BULLET_HH__
diff --git a/dw/core.hh b/dw/core.hh
new file mode 100644
index 00000000..b6e18c10
--- /dev/null
+++ b/dw/core.hh
@@ -0,0 +1,58 @@
+#ifndef __DW_CORE_HH__
+#define __DW_CORE_HH__
+
+#define __INCLUDED_FROM_DW_CORE_HH__
+
+/**
+ * \brief Dw is in this namespace, or sub namespaces of this one.
+ *
+ * The core can be found in dw::core, widgets are defined directly here.
+ *
+ * \sa \ref dw-overview
+ */
+namespace dw {
+
+/**
+ * \brief The core of Dw is defined in this namespace.
+ *
+ * \sa \ref dw-overview
+ */
+namespace core {
+
+typedef unsigned char byte;
+
+class Layout;
+class View;
+class Widget;
+class Iterator;
+
+namespace ui {
+
+class ResourceFactory;
+
+} // namespace ui
+
+
+} // namespace dw
+} // namespace core
+
+#include "../lout/object.hh"
+#include "../lout/container.hh"
+#include "../lout/signal.hh"
+
+#include "types.hh"
+#include "events.hh"
+#include "imgbuf.hh"
+#include "style.hh"
+#include "view.hh"
+#include "platform.hh"
+#include "iterator.hh"
+#include "findtext.hh"
+#include "selection.hh"
+#include "layout.hh"
+#include "widget.hh"
+#include "ui.hh"
+
+#undef __INCLUDED_FROM_DW_CORE_HH__
+
+#endif // __DW_CORE_HH__
diff --git a/dw/events.hh b/dw/events.hh
new file mode 100644
index 00000000..860472ab
--- /dev/null
+++ b/dw/events.hh
@@ -0,0 +1,83 @@
+#ifndef __DW_EVENTS_HH__
+#define __DW_EVENTS_HH__
+
+#ifndef __INCLUDED_FROM_DW_CORE_HH__
+# error Do not include this file directly, use "core.hh" instead.
+#endif
+
+namespace dw {
+namespace core {
+
+/**
+ * \brief Platform independant representation.
+ */
+enum ButtonState
+{
+ /* We won't use more than these ones. */
+ SHIFT_MASK = 1 << 0,
+ CONTROL_MASK = 1 << 1,
+ META_MASK = 1 << 2,
+ BUTTON1_MASK = 1 << 3,
+ BUTTON2_MASK = 1 << 4,
+ BUTTON3_MASK = 1 << 5
+};
+
+/**
+ * \brief Base class for all events.
+ *
+ * The dw::core::Event hierarchy describes events in a platform independant
+ * way.
+ */
+class Event: public object::Object
+{
+public:
+};
+
+/**
+ * \brief Base class for all mouse events.
+ */
+class MouseEvent: public Event
+{
+public:
+ ButtonState state;
+};
+
+/**
+ * \brief Base class for all mouse events related to a specific position.
+ */
+class MousePositionEvent: public MouseEvent
+{
+public:
+ int xCanvas, yCanvas, xWidget, yWidget;
+};
+
+/**
+ * \brief Represents a button press or release event.
+ */
+class EventButton: public MousePositionEvent
+{
+public:
+ int numPressed; /* 1 for simple click, 2 for double click, etc. */
+ int button;
+};
+
+/**
+ * \brief Represents a mouse motion event.
+ */
+class EventMotion: public MousePositionEvent
+{
+};
+
+/**
+ * \brief Represents a enter or leave notify event.
+ */
+class EventCrossing: public MouseEvent
+{
+public:
+ Widget *lastWidget, *currentWidget;
+};
+
+} // namespace dw
+} // namespace core
+
+#endif // __DW_EVENTS_HH__
diff --git a/dw/findtext.cc b/dw/findtext.cc
new file mode 100644
index 00000000..15de9f16
--- /dev/null
+++ b/dw/findtext.cc
@@ -0,0 +1,209 @@
+/*
+ * Dillo Widget
+ *
+ * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+
+
+#include "core.hh"
+
+namespace dw {
+namespace core {
+
+FindtextState::FindtextState ()
+{
+ key = NULL;
+ nexttab = NULL;
+ widget = NULL;
+ iterator = NULL;
+ hlIterator = NULL;
+}
+
+FindtextState::~FindtextState ()
+{
+ if (key)
+ delete key;
+ if (nexttab)
+ delete[] nexttab;
+ if (iterator)
+ delete iterator;
+ if (hlIterator)
+ delete hlIterator;
+}
+
+void FindtextState::setWidget (Widget *widget)
+{
+ this->widget = widget;
+
+ // A widget change will restart the search.
+ if (key)
+ delete key;
+ key = NULL;
+ if (nexttab)
+ delete[] nexttab;
+ nexttab = NULL;
+
+ if (iterator)
+ delete iterator;
+ iterator = NULL;
+ if (hlIterator)
+ delete hlIterator;
+ hlIterator = NULL;
+}
+
+FindtextState::Result FindtextState::search (const char *key, bool caseSens)
+{
+ if (!widget || *key == 0) // empty keys are not found
+ return NOT_FOUND;
+
+ bool wasHighlighted = unhighlight ();
+ bool newKey;
+
+ // If the key (or the widget) changes (including case sensitivity),
+ // the search is started from the beginning.
+ if (this->key == NULL || this->caseSens != caseSens ||
+ strcmp (this->key, key) != 0) {
+ newKey = true;
+ if (this->key)
+ delete this->key;
+ this->key = strdup (key);
+ this->caseSens = caseSens;
+
+ if (nexttab)
+ delete[] nexttab;
+ nexttab = createNexttab (key, caseSens);
+
+ if (iterator)
+ delete iterator;
+ iterator = new CharIterator (widget);
+ iterator->next ();
+ } else
+ newKey = false;
+
+ bool firstTrial = !wasHighlighted || newKey;
+
+ if (search0 ()) {
+ // Highlighlighting is done with a clone.
+ hlIterator = iterator->cloneCharIterator ();
+ for (int i = 0; key[i]; i++)
+ hlIterator->next ();
+ CharIterator::highlight (iterator, hlIterator, HIGHLIGHT_FINDTEXT);
+ CharIterator::scrollTo (iterator, hlIterator,
+ HPOS_INTO_VIEW, VPOS_CENTER);
+
+ // The search will continue from the word after the found position.
+ iterator->next ();
+ return SUCCESS;
+ } else {
+ if (firstTrial)
+ return NOT_FOUND;
+ else {
+ // Nothing found anymore, reset the state for the next trial.
+ delete iterator;
+ iterator = new CharIterator (widget);
+ iterator->next ();
+
+ // We expect a success.
+ Result result2 = search (key, caseSens);
+ assert (result2 == SUCCESS);
+ return RESTART;
+ }
+ }
+}
+
+/**
+ * \brief This method is called when the user closes the "find text" dialog.
+ */
+void FindtextState::resetSearch ()
+{
+ unhighlight ();
+
+ if (key)
+ delete key;
+ key = NULL;
+}
+
+int *FindtextState::createNexttab (const char *key, bool caseSens)
+{
+ int i = 0;
+ int j = -1;
+ int l = strlen (key);
+ int *nexttab = new int[l + 1]; // + 1 is necessary for l == 1 case
+ nexttab[0] = -1;
+
+ do {
+ if (j == -1 || charsEqual (key[i], key[j], caseSens)) {
+ i++;
+ j++;
+ nexttab[i] = j;
+ //_MSG ("nexttab[%d] = %d\n", i, j);
+ } else
+ j = nexttab[j];
+ } while (i < l - 1);
+
+ return nexttab;
+}
+
+/**
+ * \brief Unhighlight, and return whether a region was highlighted.
+ */
+bool FindtextState::unhighlight ()
+{
+ if (hlIterator) {
+ CharIterator *start = hlIterator->cloneCharIterator ();
+ for (int i = 0; key[i]; i++)
+ start->prev ();
+
+ CharIterator::unhighlight (start, hlIterator, HIGHLIGHT_FINDTEXT);
+ delete start;
+ delete hlIterator;
+ hlIterator = NULL;
+
+ return true;
+ } else
+ return false;
+}
+
+bool FindtextState::search0 ()
+{
+ if (iterator->getChar () == CharIterator::END)
+ return false;
+
+ int j = 0;
+ bool nextit = true;
+ int l = strlen (key);
+
+ do {
+ if (j == -1 || charsEqual (iterator->getChar (), key[j], caseSens)) {
+ j++;
+ nextit = iterator->next ();
+ } else
+ j = nexttab[j];
+ } while (nextit && j < l);
+
+ if (j >= l) {
+ // Go back to where the word was found.
+ for (int i = 0; i < l; i++)
+ iterator->prev ();
+ return true;
+ } else
+ return false;
+}
+
+} // namespace dw
+} // namespace core
diff --git a/dw/findtext.hh b/dw/findtext.hh
new file mode 100644
index 00000000..d0c20206
--- /dev/null
+++ b/dw/findtext.hh
@@ -0,0 +1,82 @@
+#ifndef __DW_FINDTEXT_STATE_H__
+#define __DW_FINDTEXT_STATE_H__
+
+#ifndef __INCLUDED_FROM_DW_CORE_HH__
+# error Do not include this file directly, use "core.hh" instead.
+#endif
+
+#include <ctype.h>
+
+namespace dw {
+namespace core {
+
+class FindtextState
+{
+public:
+ typedef enum {
+ /** \brief The next occurance of the pattern has been found. */
+ SUCCESS,
+
+ /**
+ * \brief There is no further occurance of the pattern, instead, the
+ * first occurance has been selected.
+ */
+ RESTART,
+
+ /** \brief The patten does not at all occur in the text. */
+ NOT_FOUND
+ } Result;
+
+private:
+ /**
+ * \brief The key used for the last search.
+ *
+ * If dw::core::Findtext::search is called with the same key, the search
+ * is continued, otherwise it is restarted.
+ */
+ char *key;
+
+ /** \brief Whether the last search was case sensitive. */
+ bool caseSens;
+
+ /** \brief The table used for KMP search. */
+ int *nexttab;
+
+ /** \brief The top of the widget tree, in which the search is done.
+ *
+ * From this, the iterator will be constructed. Set by
+ * dw::core::Findtext::widget
+ */
+ Widget *widget;
+
+ /** \brief The position from where the next search will start. */
+ CharIterator *iterator;
+
+ /**
+ * \brief The position from where the characters are highlighted.
+ *
+ * NULL, when no text is highlighted.
+ */
+ CharIterator *hlIterator;
+
+ static int *createNexttab (const char *key, bool caseSens);
+ bool unhighlight ();
+ bool search0 ();
+
+ inline static bool charsEqual (char c1, char c2, bool caseSens)
+ { return caseSens ? c1 == c2 : tolower (c1) == tolower (c2) ||
+ isspace (c1) && isspace (c2); }
+
+public:
+ FindtextState ();
+ ~FindtextState ();
+
+ void setWidget (Widget *widget);
+ Result search (const char *key, bool caseSens);
+ void resetSearch ();
+};
+
+} // namespace dw
+} // namespace core
+
+#endif // __DW_FINDTEXT_STATE_H__
diff --git a/dw/fltkcomplexbutton.cc b/dw/fltkcomplexbutton.cc
new file mode 100644
index 00000000..1649683f
--- /dev/null
+++ b/dw/fltkcomplexbutton.cc
@@ -0,0 +1,282 @@
+//
+//
+// Copyright 1998-2006 by Bill Spitzak and others.
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Library General Public
+// License as published by the Free Software Foundation; either
+// version 3 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Library General Public License for more details.
+//
+// You should have received a copy of the GNU Library General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+// USA.
+//
+// Please report all bugs and problems to "fltk-bugs@fltk.org".
+//
+
+#include <fltk/events.h>
+#include <fltk/damage.h>
+#include <fltk/Group.h>
+#include <fltk/Box.h>
+#include <stdlib.h>
+
+#include "fltkcomplexbutton.hh"
+
+using namespace fltk;
+using namespace dw::fltk::ui;
+
+/*! \class fltk::ComplexButton
+
+ ComplexButtons generate callbacks when they are clicked by the user. You
+ control exactly when and how by changing the values for when():
+ - fltk::WHEN_NEVER: The callback is not done, instead changed() is
+ turned on.
+ - fltk::WHEN_RELEASE: This is the default, the callback is done
+ after the user successfully clicks the button (i.e. they let it go
+ with the mouse still pointing at it), or when a shortcut is typed.
+ - fltk::WHEN_CHANGED : The callback is done each time the value()
+ changes (when the user pushes and releases the button, and as the
+ mouse is dragged around in and out of the button).
+
+ ComplexButtons can also generate callbacks in response to fltk::SHORTCUT
+ events. The button can either have an explicit shortcut() value or a
+ letter shortcut can be indicated in the label() with an '&'
+ character before it. For the label shortcut it does not matter if
+ Alt is held down, but if you have an input field in the same window,
+ the user will have to hold down the Alt key so that the input field
+ does not eat the event first as an fltk::KEY event.
+
+ \image html buttons.gif
+*/
+
+/*! \fn bool ComplexButton::value() const
+ The current value. True means it is pushed down, false means it is
+ not pushed down. The ToggleComplexButton subclass provides the ability for
+ the user to change this value permanently, otherwise it is just
+ temporary while the user is holding the button down.
+
+ This is the same as Widget::state().
+*/
+
+/*! \fn bool ComplexButton::value(bool)
+ Change the value(). Redraws the button and returns true if the new
+ value is different. This is the same function as Widget::state().
+ See also Widget::set(), Widget::clear(), and Widget::setonly().
+
+ If you turn it on, a normal button will draw pushed-in, until
+ the user clicks it and releases it.
+*/
+
+static bool initial_state;
+
+int ComplexButton::handle(int event) {
+ return handle(event, Rectangle(w(),h()));
+}
+
+int ComplexButton::handle(int event, const Rectangle& rectangle) {
+ switch (event) {
+ case ENTER:
+ case LEAVE:
+ redraw_highlight();
+ case MOVE:
+ return 1;
+ case PUSH:
+ if (pushed()) return 1; // ignore extra pushes on currently-pushed button
+ initial_state = state();
+ clear_flag(PUSHED);
+ do_callback();
+ case DRAG: {
+ bool inside = event_inside(rectangle);
+ if (inside) {
+ if (!flag(PUSHED)) {
+ set_flag(PUSHED);
+ redraw(DAMAGE_VALUE);
+ }
+ } else {
+ if (flag(PUSHED)) {
+ clear_flag(PUSHED);
+ redraw(DAMAGE_VALUE);
+ }
+ }
+ if (when() & WHEN_CHANGED) { // momentary button must record state()
+ if (state(inside ? !initial_state : initial_state))
+ do_callback();
+ }
+ return 1;}
+ case RELEASE:
+ if (!flag(PUSHED)) return 1;
+ clear_flag(PUSHED);
+ redraw(DAMAGE_VALUE);
+ if (type() == RADIO)
+ setonly();
+ else if (type()) // TOGGLE
+ state(!initial_state);
+ else {
+ state(initial_state);
+ if (when() & WHEN_CHANGED) {do_callback(); return 1;}
+ }
+ if (when() & WHEN_RELEASE) do_callback(); else set_changed();
+ return 1;
+ case FOCUS:
+ redraw(1); // minimal redraw to just add the focus box
+ // grab initial focus if we are an ReturnComplexButton:
+ return shortcut()==ReturnKey ? 2 : 1;
+ case UNFOCUS:
+ redraw(DAMAGE_HIGHLIGHT);
+ return 1;
+ case KEY:
+ if (event_key() == ' ' || event_key() == ReturnKey
+ || event_key() == KeypadEnter) goto EXECUTE;
+ return 0;
+ case SHORTCUT:
+ if (!test_shortcut()) return 0;
+ EXECUTE:
+ if (type() == RADIO) {
+ if (!state()) {
+ setonly();
+ if (when() & WHEN_CHANGED) do_callback(); else set_changed();
+ }
+ } else if (type()) { // TOGGLE
+ state(!state());
+ if (when() & WHEN_CHANGED) do_callback(); else set_changed();
+ }
+ if (when() & WHEN_RELEASE) do_callback();
+ return 1;
+ default:
+ return 0;
+ }
+}
+
+////////////////////////////////////////////////////////////////
+
+#include <fltk/draw.h>
+
+extern Widget* fl_did_clipping;
+
+/*!
+ This function provides a mess of back-compatabilty and Windows
+ emulation to subclasses of ComplexButton to draw with. It will draw the
+ button according to the current state of being pushed and it's
+ state(). If non-zero is passed for \a glyph_width then the glyph()
+ is drawn in that space on the left (or on the right if negative),
+ and it assummes the glyph indicates the state(), so the box is only
+ used to indicate the pushed state.
+*/
+void ComplexButton::draw(int glyph_width) const
+{
+ // For back-compatability, setting color() or box() directly on a plain
+ // button will cause it to act like buttoncolor() or buttonbox() are
+ // set:
+ Style localstyle;
+ const Style* style = this->style();
+ if (!glyph_width) {
+ localstyle = *style;
+ if (localstyle.color_) localstyle.buttoncolor_ = localstyle.color_;
+ if (localstyle.box_) localstyle.buttonbox_ = localstyle.box_;
+ if (localstyle.labelcolor_) localstyle.textcolor_ = localstyle.labelcolor_;
+ style = &localstyle;
+ }
+
+ Box* box = style->buttonbox();
+
+ Flags box_flags = flags() | OUTPUT;
+ Flags glyph_flags = box_flags & ~(HIGHLIGHT|OUTPUT);
+ if (glyph_width) box_flags &= ~STATE;
+
+ // only draw "inside" labels:
+ Rectangle r(0,0,w(),h());
+
+ if (box == NO_BOX) {
+ Color bg;
+ if (box_flags & HIGHLIGHT && (bg = style->highlight_color())) {
+ setcolor(bg);
+ fillrect(r);
+ } else if (label() || (damage()&(DAMAGE_EXPOSE|DAMAGE_HIGHLIGHT))) {
+ // erase the background so we can redraw the label in the new color:
+ draw_background();
+ }
+ // this allows these buttons to be put into browser/menus:
+ //fg = fl_item_labelcolor(this);
+ } else {
+ if ((damage()&(DAMAGE_EXPOSE|DAMAGE_HIGHLIGHT))
+ && !box->fills_rectangle()) {
+ // Erase the area behind non-square boxes
+ draw_background();
+ }
+ }
+
+ // Draw the box:
+ drawstyle(style,box_flags);
+ // For back-compatability we use any directly-set selection_color()
+ // to color the box:
+ if (!glyph_width && state() && style->selection_color_) {
+ setbgcolor(style->selection_color_);
+ setcolor(contrast(style->selection_textcolor(),style->selection_color_));
+ }
+ box->draw(r);
+ Rectangle r1(r); box->inset(r1);
+
+ if (glyph_width) {
+ int g = abs(glyph_width);
+ Rectangle lr(r1);
+ Rectangle gr(r1, g, g);
+ if (glyph_width < 0) {
+ lr.w(lr.w()-g-3);
+ gr.x(r1.r()-g-3);
+ } else {
+ lr.set_x(g+3);
+ gr.x(r1.x()+3);
+ }
+ this->draw_label(lr, box_flags);
+ drawstyle(style,glyph_flags);
+ this->glyph()->draw(gr);
+ drawstyle(style,box_flags);
+ } else {
+ this->draw_label(r1, box_flags);
+ }
+ box->draw_symbol_overlay(r);
+}
+
+void ComplexButton::draw() {
+ if (type() == HIDDEN) {
+ fl_did_clipping = this;
+ return;
+ }
+ draw(0);
+
+ // ComplexButton is a Group, draw its children
+ for (int i = children () - 1; i >= 0; i--)
+ draw_child (*child (i));
+}
+
+////////////////////////////////////////////////////////////////
+
+static NamedStyle style("ComplexButton", 0, &ComplexButton::default_style);
+NamedStyle* ComplexButton::default_style = &::style;
+
+ComplexButton::ComplexButton(int x,int y,int w,int h, const char *l) :
+ Group(x,y,w,h,l)
+{
+ style(default_style);
+ highlight_color(GRAY20);
+ //set_click_to_focus();
+}
+
+////////////////////////////////////////////////////////////////
+
+/*! \class fltk::ToggleComplexButton
+ This button turns the state() on and off each release of a click
+ inside of it.
+
+ You can also convert a regular button into this by doing
+ type(ComplexButton::TOGGLE) to it.
+*/
+
+//
+//
diff --git a/dw/fltkcomplexbutton.hh b/dw/fltkcomplexbutton.hh
new file mode 100644
index 00000000..8f80d5aa
--- /dev/null
+++ b/dw/fltkcomplexbutton.hh
@@ -0,0 +1,59 @@
+//
+//
+// Push button widget
+//
+// Copyright 2002 by Bill Spitzak and others.
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Library General Public
+// License as published by the Free Software Foundation; either
+// version 2 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Library General Public License for more details.
+//
+// You should have received a copy of the GNU Library General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+// USA.
+//
+// Please report all bugs and problems to "fltk-bugs@fltk.org".
+//
+
+#ifndef __FLTK_COMPLEX_BUTTON_HH__
+#define __FLTK_COMPLEX_BUTTON_HH__
+
+#include <fltk/Group.h>
+
+namespace dw {
+namespace fltk {
+namespace ui {
+
+class ComplexButton: public ::fltk::Group
+{
+public:
+ enum {HIDDEN=3}; // back-comptability value to hide the button
+
+ bool value() const { return state(); }
+ bool value(bool v) { return state(v); }
+
+ int handle(int);
+ int handle(int event, const Rectangle&);
+ ComplexButton(int,int,int,int,const char * = 0);
+ ~ComplexButton() { remove_all ();};
+ static ::fltk::NamedStyle* default_style;
+
+ virtual void draw();
+ void draw(int glyph_width) const;
+};
+
+} // namespace ui
+} // namespace fltk
+} // namespace dw
+
+#endif // __FLTK_COMPLEX_BUTTON_HH__
+
+//
+//
diff --git a/dw/fltkcore.hh b/dw/fltkcore.hh
new file mode 100644
index 00000000..fbff3fad
--- /dev/null
+++ b/dw/fltkcore.hh
@@ -0,0 +1,25 @@
+#ifndef __DW_FLTK_CORE_HH__
+#define __DW_FLTK_CORE_HH__
+
+#define __INCLUDED_FROM_DW_FLTK_CORE_HH__
+
+namespace dw {
+namespace fltk {
+namespace ui {
+
+class FltkResource;
+
+} // namespace ui
+} // namespace fltk
+} // namespace core
+
+#include <fltk/Widget.h>
+
+#include "core.hh"
+#include "fltkimgbuf.hh"
+#include "fltkplatform.hh"
+#include "fltkui.hh"
+
+#undef __INCLUDED_FROM_DW_FLTK_CORE_HH__
+
+#endif // __DW_FLTK_CORE_HH__
diff --git a/dw/fltkflatview.cc b/dw/fltkflatview.cc
new file mode 100644
index 00000000..7f5f88a6
--- /dev/null
+++ b/dw/fltkflatview.cc
@@ -0,0 +1,108 @@
+/*
+ * Dillo Widget
+ *
+ * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+
+
+#include "fltkflatview.hh"
+
+#include <fltk/draw.h>
+#include <fltk/events.h>
+
+#include <stdio.h>
+
+using namespace fltk;
+using namespace lout::container::typed;
+
+namespace dw {
+namespace fltk {
+
+FltkFlatView::FltkFlatView (int x, int y, int w, int h, const char *label):
+ FltkWidgetView (x, y, w, h, label)
+{
+}
+
+FltkFlatView::~FltkFlatView ()
+{
+}
+
+void FltkFlatView::setCanvasSize (int width, int ascent, int descent)
+{
+ /**
+ * \bug It has to be clarified, who is responsible for setting the
+ * FLTK widget size. In the only used context (complex buttons),
+ * it is done elsewhere.
+ */
+
+#if 0
+ FltkWidgetView::setCanvasSize (width, ascent, descent);
+
+ w (width);
+ h (ascent + descent);
+#endif
+}
+
+bool FltkFlatView::usesViewport ()
+{
+ return false;
+}
+
+int FltkFlatView::getHScrollbarThickness ()
+{
+ return 0;
+}
+
+int FltkFlatView::getVScrollbarThickness ()
+{
+ return 0;
+}
+
+void FltkFlatView::scrollTo (int x, int y)
+{
+}
+
+void FltkFlatView::setViewportSize (int width, int height,
+ int hScrollbarThickness,
+ int vScrollbarThickness)
+{
+}
+
+int FltkFlatView::translateViewXToCanvasX (int x)
+{
+ return x;
+}
+
+int FltkFlatView::translateViewYToCanvasY (int y)
+{
+ return y;
+}
+
+int FltkFlatView::translateCanvasXToViewX (int x)
+{
+ return x;
+}
+
+int FltkFlatView::translateCanvasYToViewY (int y)
+{
+ return y;
+}
+
+
+} // namespace fltk
+} // namespace dw
diff --git a/dw/fltkflatview.hh b/dw/fltkflatview.hh
new file mode 100644
index 00000000..dee5498f
--- /dev/null
+++ b/dw/fltkflatview.hh
@@ -0,0 +1,40 @@
+#ifndef __DW_FLTKFLATVIEW_HH__
+#define __DW_FLTKFLATVIEW_HH__
+
+#include <fltk/Group.h>
+#include <fltk/Scrollbar.h>
+
+#include "core.hh"
+#include "fltkcore.hh"
+#include "fltkviewbase.hh"
+
+namespace dw {
+namespace fltk {
+
+class FltkFlatView: public FltkWidgetView
+{
+protected:
+ int translateViewXToCanvasX (int x);
+ int translateViewYToCanvasY (int y);
+ int translateCanvasXToViewX (int x);
+ int translateCanvasYToViewY (int y);
+
+public:
+ FltkFlatView (int x, int y, int w, int h, const char *label = 0);
+ ~FltkFlatView ();
+
+ void setCanvasSize (int width, int ascent, int descent);
+
+ bool usesViewport ();
+ int getHScrollbarThickness ();
+ int getVScrollbarThickness ();
+ void scrollTo (int x, int y);
+ void setViewportSize (int width, int height,
+ int hScrollbarThickness, int vScrollbarThickness);
+};
+
+} // namespace fltk
+} // namespace dw
+
+#endif // __DW_FLTKFLATVIEW_HH__
+
diff --git a/dw/fltkimgbuf.cc b/dw/fltkimgbuf.cc
new file mode 100644
index 00000000..b8beb1cb
--- /dev/null
+++ b/dw/fltkimgbuf.cc
@@ -0,0 +1,347 @@
+/*
+ * Dillo Widget
+ *
+ * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+
+
+#include "fltkcore.hh"
+#include "../lout/misc.hh"
+
+#include <fltk/draw.h>
+#include <fltk/Color.h>
+
+using namespace fltk;
+
+namespace dw {
+namespace fltk {
+
+using namespace container::typed;
+
+FltkImgbuf::FltkImgbuf (Type type, int width, int height)
+{
+ //printf("FltkImgbuf: new root %p\n", this);
+ init (type, width, height, NULL);
+}
+
+FltkImgbuf::FltkImgbuf (Type type, int width, int height, FltkImgbuf *root)
+{
+ //printf("FltkImgbuf: new scaled %p, root is %p\n", this, root);
+ init (type, width, height, root);
+}
+
+void FltkImgbuf::init (Type type, int width, int height, FltkImgbuf *root)
+{
+ this->root = root;
+ this->type = type;
+ this->width = width;
+ this->height = height;
+
+ // TODO: Maybe this is only for root buffers
+ switch (type) {
+ case RGBA: bpp = 4; break;
+ case RGB: bpp = 3; break;
+ default: bpp = 1; break;
+ }
+ //fprintf(stderr,"FltkImgbuf::init width=%d height=%d bpp=%d\n",
+ // width, height, bpp);
+ rawdata = new uchar[bpp * width * height];
+ // Set light-gray as interim background color.
+ memset(rawdata, 222, width*height*bpp);
+
+ refCount = 1;
+ deleteOnUnref = true;
+ copiedRows = new misc::BitSet (height);
+
+ // The list is only used for root buffers.
+ if (isRoot())
+ scaledBuffers = new container::typed::List <FltkImgbuf> (true);
+ else
+ scaledBuffers = NULL;
+
+ if (!isRoot()) {
+ // Scaling
+ for (int row = 0; row < root->height; row++) {
+ if (root->copiedRows->get (row))
+ scaleRow (row, root->rawdata + row*root->width*root->bpp);
+ }
+ }
+}
+
+FltkImgbuf::~FltkImgbuf ()
+{
+ //printf ("FltkImgbuf::~FltkImgbuf (%s)\n", isRoot() ? "root" : "scaled");
+
+ //if (root)
+ // printf("FltkImgbuf[scaled %p, root is %p]: deleted\n", this, root);
+ //else
+ // printf("FltkImgbuf[root %p]: deleted\n", this);
+
+ if (!isRoot())
+ root->detachScaledBuf (this);
+
+ delete[] rawdata;
+ delete copiedRows;
+
+ if (scaledBuffers)
+ delete scaledBuffers;
+}
+
+/**
+ * \brief This method is called for the root buffer, when a scaled buffer
+ * removed.
+ */
+void FltkImgbuf::detachScaledBuf (FltkImgbuf *scaledBuf)
+{
+ scaledBuffers->detachRef (scaledBuf);
+
+ //printf("FltkImgbuf[root %p]: scaled buffer %p is detached, %d left\n",
+ // this, scaledBuf, scaledBuffers->size ());
+
+ if (refCount == 0 && scaledBuffers->isEmpty () && deleteOnUnref)
+ // If the root buffer is not used anymore, but this is the last scaled
+ // buffer.
+ // See also: FltkImgbuf::unref().
+ delete this;
+}
+
+void FltkImgbuf::setCMap (int *colors, int num_colors)
+{
+}
+
+inline void FltkImgbuf::scaleRow (int row, const core::byte *data)
+{
+ int sr1 = scaledY (row);
+ int sr2 = scaledY (row + 1);
+
+ for(int sr = sr1; sr < sr2; sr++) {
+ // Avoid multiple passes.
+ if (copiedRows->get(sr)) continue;
+
+ copiedRows->set (sr, true);
+ if (sr == sr1) {
+ for(int px = 0; px < root->width; px++) {
+ int px1 = px * width / root->width;
+ int px2 = (px+1) * width / root->width;
+ for(int sp = px1; sp < px2; sp++) {
+ memcpy(rawdata + (sr*width + sp)*bpp, data + px*bpp, bpp);
+ }
+ }
+ } else {
+ memcpy(rawdata + sr*width*bpp, rawdata + sr1*width*bpp, width*bpp);
+ }
+ }
+}
+
+void FltkImgbuf::copyRow (int row, const core::byte *data)
+{
+ assert (isRoot());
+
+ // Flag the row done and copy its data.
+ copiedRows->set (row, true);
+ memcpy(rawdata + row * width * bpp, data, width * bpp);
+
+ // Update all the scaled buffers of this root image.
+ for (Iterator <FltkImgbuf> it = scaledBuffers->iterator(); it.hasNext(); ) {
+ FltkImgbuf *sb = it.getNext ();
+ sb->scaleRow(row, data);
+ }
+}
+
+void FltkImgbuf::newScan ()
+{
+ if (isRoot()) {
+ for (Iterator<FltkImgbuf> it = scaledBuffers->iterator(); it.hasNext();){
+ FltkImgbuf *sb = it.getNext ();
+ sb->copiedRows->clear();
+ }
+ }
+}
+
+core::Imgbuf* FltkImgbuf::getScaledBuf (int width, int height)
+{
+ if (root)
+ return root->getScaledBuf (width, height);
+
+ if (width == this->width && height == this->height) {
+ ref ();
+ return this;
+ }
+
+ for (Iterator <FltkImgbuf> it = scaledBuffers->iterator(); it.hasNext(); ) {
+ FltkImgbuf *sb = it.getNext ();
+ if (sb->width == width && sb->height == height) {
+ sb->ref ();
+ return sb;
+ }
+ }
+
+ /* This size is not yet used, so a new buffer has to be created. */
+ FltkImgbuf *sb = new FltkImgbuf (type, width, height, this);
+ scaledBuffers->append (sb);
+ return sb;
+}
+
+void FltkImgbuf::getRowArea (int row, dw::core::Rectangle *area)
+{
+ // TODO: May have to be adjusted.
+
+ if (isRoot()) {
+ /* root buffer */
+ area->x = 0;
+ area->y = row;
+ area->width = width;
+ area->height = 1;
+ //fprintf(stderr,"::getRowArea: area x=%d y=%d width=%d height=%d\n",
+ // area->x, area->y, area->width, area->height);
+ } else {
+ // scaled buffer
+ int sr1 = scaledY (row);
+ int sr2 = scaledY (row + 1);
+
+ area->x = 0;
+ area->y = sr1;
+ area->width = width;
+ area->height = sr2 - sr1;
+ //fprintf(stderr,"::getRowArea: area x=%d y=%d width=%d height=%d\n",
+ // area->x, area->y, area->width, area->height);
+ }
+}
+
+int FltkImgbuf::getRootWidth ()
+{
+ return root ? root->width : width;
+}
+
+int FltkImgbuf::getRootHeight ()
+{
+ return root ? root->height : height;
+}
+
+void FltkImgbuf::ref ()
+{
+ refCount++;
+
+ //if (root)
+ // printf("FltkImgbuf[scaled %p, root is %p]: ref() => %d\n",
+ // this, root, refCount);
+ //else
+ // printf("FltkImgbuf[root %p]: ref() => %d\n", this, refCount);
+}
+
+void FltkImgbuf::unref ()
+{
+ //if (root)
+ // printf("FltkImgbuf[scaled %p, root is %p]: ref() => %d\n",
+ // this, root, refCount - 1);
+ //else
+ // printf("FltkImgbuf[root %p]: ref() => %d\n", this, refCount - 1);
+
+ if (--refCount == 0) {
+ if (isRoot ()) {
+ // Root buffer, it must be ensured that no scaled buffers are left.
+ // See also FltkImgbuf::detachScaledBuf().
+ if (scaledBuffers->isEmpty () && deleteOnUnref)
+ delete this;
+ else
+ printf("FltkImgbuf[root %p]: not deleted\n", this);
+ } else
+ // Scaled buffer buffer, simply delete it.
+ delete this;
+ }
+}
+
+bool FltkImgbuf::lastReference ()
+{
+ return refCount == 1 &&
+ (scaledBuffers == NULL || scaledBuffers->isEmpty ());
+}
+
+void FltkImgbuf::setDeleteOnUnref (bool deleteOnUnref)
+{
+ assert (isRoot ());
+ this->deleteOnUnref = deleteOnUnref;
+}
+
+bool FltkImgbuf::isReferred ()
+{
+ return refCount != 0 ||
+ (scaledBuffers != NULL && !scaledBuffers->isEmpty ());
+}
+
+
+int FltkImgbuf::scaledY(int ySrc)
+{
+ // TODO: May have to be adjusted.
+ assert (root != NULL);
+ return ySrc * height / root->height;
+}
+
+void FltkImgbuf::draw (::fltk::Widget *target, int xRoot, int yRoot,
+ int x, int y, int width, int height)
+{
+ // TODO (i): Implementation.
+ // TODO (ii): Clarify the question, whether "target" is the current widget
+ // (and so has not to be passed at all).
+
+/*
+ setcolor (0);
+
+ for (int row = y; row < y + height; row++) {
+ if (copiedRows->get (row)) {
+ ::fltk::Rectangle rect (x + xRoot, row + yRoot, width, 1);
+ fillrect (rect);
+ }
+ }
+*/
+
+ //fprintf(stderr,"::draw: xRoot=%d x=%d yRoot=%d y=%d width=%d height=%d\n"
+ // " this->width=%d this->height=%d\n",
+ // xRoot, x, yRoot, y, width, height, this->width, this->height);
+
+//{
+#if 1
+ if (x > this->width || y > this->height) {
+ return;
+ }
+
+ if (x + width > this->width) {
+ width = this->width - x;
+ }
+
+ if (y + height > this->height) {
+ height = this->height - y;
+ }
+
+ // almost OK for rows. For some unknown reason it trims the bottom and
+ // rightmost parts when scrolling.
+ ::fltk::Rectangle rect (xRoot + x, yRoot + y, width, height);
+ PixelType ptype = (type == RGBA) ? ::fltk::RGBA : ::fltk::RGB;
+ drawimage(rawdata+bpp*(y*this->width + x),ptype,rect,bpp*this->width);
+
+#else
+ // OK for full image.
+ ::fltk::Rectangle rect (xRoot, yRoot, this->width, this->height);
+ PixelType ptype = (type == RGBA) ? ::fltk::RGBA : ::fltk::RGB;
+ drawimage(rawdata,ptype,rect);
+#endif
+//}
+}
+
+} // namespace dw
+} // namespace fltk
diff --git a/dw/fltkimgbuf.hh b/dw/fltkimgbuf.hh
new file mode 100644
index 00000000..54d9ca34
--- /dev/null
+++ b/dw/fltkimgbuf.hh
@@ -0,0 +1,65 @@
+#ifndef __DW_FLTKIMGBUF_HH__
+#define __DW_FLTKIMGBUF_HH__
+
+#ifndef __INCLUDED_FROM_DW_FLTK_CORE_HH__
+# error Do not include this file directly, use "fltkcore.hh" instead.
+#endif
+
+namespace dw {
+namespace fltk {
+
+class FltkImgbuf: public core::Imgbuf
+{
+private:
+ FltkImgbuf *root;
+ int refCount;
+ bool deleteOnUnref;
+ lout::container::typed::List <FltkImgbuf> *scaledBuffers;
+
+ int width, height;
+ Type type;
+
+//{
+ int bpp;
+ uchar *rawdata;
+//}
+
+ // This is just for testing drawing, it has to be replaced by
+ // the image buffer.
+ lout::misc::BitSet *copiedRows;
+
+ FltkImgbuf (Type type, int width, int height, FltkImgbuf *root);
+ void init (Type type, int width, int height, FltkImgbuf *root);
+ int scaledY(int ySrc);
+ int isRoot() { return (root == NULL); }
+ void detachScaledBuf (FltkImgbuf *scaledBuf);
+
+protected:
+ ~FltkImgbuf ();
+
+public:
+ FltkImgbuf (Type type, int width, int height);
+
+ void setCMap (int *colors, int num_colors);
+ inline void scaleRow (int row, const core::byte *data);
+ void newScan ();
+ void copyRow (int row, const core::byte *data);
+ core::Imgbuf* getScaledBuf (int width, int height);
+ void getRowArea (int row, dw::core::Rectangle *area);
+ int getRootWidth ();
+ int getRootHeight ();
+ void ref ();
+ void unref ();
+
+ bool lastReference ();
+ void setDeleteOnUnref (bool deleteOnUnref);
+ bool isReferred ();
+
+ void draw (::fltk::Widget *target, int xRoot, int yRoot,
+ int x, int y, int width, int height);
+};
+
+} // namespace dw
+} // namespace fltk
+
+#endif // __DW_FLTK_IMGBUF_HH__
diff --git a/dw/fltkmisc.cc b/dw/fltkmisc.cc
new file mode 100644
index 00000000..08d75854
--- /dev/null
+++ b/dw/fltkmisc.cc
@@ -0,0 +1,50 @@
+/*
+ * Dillo Widget
+ *
+ * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+
+
+#include "fltkmisc.hh"
+
+#include <fltk/events.h>
+#include <fltk/Monitor.h>
+#include <stdio.h>
+
+namespace dw {
+namespace fltk {
+namespace misc {
+
+int screenWidth ()
+{
+ return ::fltk::Monitor::all ().w ();
+}
+
+int screenHeight ()
+{
+ return ::fltk::Monitor::all ().h ();
+}
+
+void warpPointer (int x, int y)
+{
+ ::fltk::warp_mouse (x, y);
+}
+
+} // namespace misc
+} // namespace fltk
+} // namespace dw
diff --git a/dw/fltkmisc.hh b/dw/fltkmisc.hh
new file mode 100644
index 00000000..fc004318
--- /dev/null
+++ b/dw/fltkmisc.hh
@@ -0,0 +1,22 @@
+#ifndef __FLTKMISC_HH__
+#define __FLTKMISC_HH__
+
+namespace dw {
+namespace fltk {
+
+/**
+ * \brief Miscellaneous FLTK stuff.
+ */
+namespace misc {
+
+int screenWidth ();
+int screenHeight ();
+
+void warpPointer (int x, int y);
+
+} // namespace misc
+} // namespace fltk
+} // namespace dw
+
+
+#endif // __FLTKMISC_HH__
diff --git a/dw/fltkplatform.cc b/dw/fltkplatform.cc
new file mode 100644
index 00000000..337f4dba
--- /dev/null
+++ b/dw/fltkplatform.cc
@@ -0,0 +1,421 @@
+/*
+ * Dillo Widget
+ *
+ * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+
+
+#include "fltkcore.hh"
+
+#include <fltk/draw.h>
+#include <fltk/run.h>
+#include <fltk/events.h>
+#include <fltk/utf.h>
+#include <stdio.h>
+
+namespace dw {
+namespace fltk {
+
+using namespace ::fltk;
+
+/**
+ * \todo Distinction between italics and oblique would be nice.
+ */
+
+container::typed::HashTable <dw::core::style::FontAttrs,
+ FltkFont> *FltkFont::fontsTable =
+ new container::typed::HashTable <dw::core::style::FontAttrs,
+ FltkFont> (false, false);
+
+FltkFont::FltkFont (core::style::FontAttrs *attrs)
+{
+ copyAttrs (attrs);
+
+ int fa = 0;
+ if(weight >= 500)
+ fa |= BOLD;
+ if(style != core::style::FONT_STYLE_NORMAL)
+ fa |= ITALIC;
+
+ font = ::fltk::font(name, fa);
+ if(font == NULL) {
+ fprintf(stderr, "No font '%s', using default sans-serif font.\n", name);
+ /*
+ * If using xft, fltk::HELVETICA just means sans, fltk::COURIER
+ * means mono, and fltk::TIMES means serif.
+ */
+ font = HELVETICA;
+ }
+
+ setfont(font, size);
+ spaceWidth = (int)getwidth(" ");
+ int xw, xh;
+ measure("x", xw, xh);
+ xHeight = xh;
+ ascent = (int)getascent();
+ descent = (int)getdescent();
+
+ /**
+ * \bug The code above does not seem to work, so this workaround.
+ */
+ xHeight = ascent * 3 / 5;
+}
+
+FltkFont::~FltkFont ()
+{
+ fontsTable->remove (this);
+}
+
+FltkFont*
+FltkFont::create (core::style::FontAttrs *attrs)
+{
+ FltkFont *font = fontsTable->get (attrs);
+
+ if (font == NULL) {
+ font = new FltkFont (attrs);
+ fontsTable->put (font, font);
+ }
+
+ return font;
+}
+
+container::typed::HashTable <dw::core::style::ColorAttrs,
+ FltkColor>
+ *FltkColor::colorsTable =
+ new container::typed::HashTable <dw::core::style::ColorAttrs,
+ FltkColor> (false, false);
+
+FltkColor::FltkColor (int color, core::style::Color::Type type):
+ Color (color, type)
+{
+ this->color = color;
+ this->type = type;
+
+ /*
+ * fltk/setcolor.cxx:
+ * "A Color of zero (fltk::NO_COLOR) will draw black but is
+ * ambiguous. It is returned as an error value or to indicate portions
+ * of a Style that should be inherited, and it is also used as the
+ * default label color for everything so that changing color zero can
+ * be used by the -fg switch. You should use fltk::BLACK (56) to get
+ * black."
+ *
+ * i.e., zero only works sometimes.
+ */
+
+ if (!(colors[SHADING_NORMAL] = shadeColor (color, SHADING_NORMAL) << 8))
+ colors[SHADING_NORMAL] = ::fltk::BLACK;
+ if (!(colors[SHADING_INVERSE] = shadeColor (color, SHADING_INVERSE) << 8))
+ colors[SHADING_INVERSE] = ::fltk::BLACK;
+
+ if(type == core::style::Color::TYPE_SHADED) {
+ if (!(colors[SHADING_DARK] = shadeColor (color, SHADING_DARK) << 8))
+ colors[SHADING_DARK] = ::fltk::BLACK;
+ if (!(colors[SHADING_LIGHT] = shadeColor (color, SHADING_LIGHT) << 8))
+ colors[SHADING_LIGHT] = ::fltk::BLACK;
+ }
+}
+
+FltkColor::~FltkColor ()
+{
+ colorsTable->remove (this);
+}
+
+FltkColor * FltkColor::create (int col, core::style::Color::Type type)
+{
+ ColorAttrs attrs(col, type);
+ FltkColor *color = colorsTable->get (&attrs);
+
+ if (color == NULL) {
+ color = new FltkColor (col, type);
+ colorsTable->put (color, color);
+ }
+
+ return color;
+}
+
+void FltkView::addFltkWidget (::fltk::Widget *widget,
+ core::Allocation *allocation)
+{
+}
+
+void FltkView::removeFltkWidget (::fltk::Widget *widget)
+{
+}
+
+void FltkView::allocateFltkWidget (::fltk::Widget *widget,
+ core::Allocation *allocation)
+{
+}
+
+void FltkView::drawFltkWidget (::fltk::Widget *widget, core::Rectangle *area)
+{
+}
+
+
+core::ui::LabelButtonResource *
+FltkPlatform::FltkResourceFactory::createLabelButtonResource (const char
+ *label)
+{
+ return new ui::FltkLabelButtonResource (platform, label);
+}
+
+core::ui::ComplexButtonResource *
+FltkPlatform::FltkResourceFactory::createComplexButtonResource (core::Widget
+ *widget,
+ bool relief)
+{
+ return new ui::FltkComplexButtonResource (platform, widget, relief);
+}
+
+core::ui::ListResource *
+FltkPlatform::FltkResourceFactory::createListResource (core::ui
+ ::ListResource
+ ::SelectionMode
+ selectionMode)
+{
+ return new ui::FltkListResource (platform, selectionMode);
+}
+
+core::ui::OptionMenuResource *
+FltkPlatform::FltkResourceFactory::createOptionMenuResource ()
+{
+ return new ui::FltkOptionMenuResource (platform);
+}
+
+core::ui::EntryResource *
+FltkPlatform::FltkResourceFactory::createEntryResource (int maxLength,
+ bool password)
+{
+ return new ui::FltkEntryResource (platform, maxLength, password);
+}
+
+core::ui::MultiLineTextResource *
+FltkPlatform::FltkResourceFactory::createMultiLineTextResource (int cols,
+ int rows)
+{
+ return new ui::FltkMultiLineTextResource (platform, cols, rows);
+}
+
+core::ui::CheckButtonResource *
+FltkPlatform::FltkResourceFactory::createCheckButtonResource (bool activated)
+{
+ return new ui::FltkCheckButtonResource (platform, activated);
+}
+
+core::ui::RadioButtonResource
+*FltkPlatform::FltkResourceFactory::createRadioButtonResource
+(core::ui::RadioButtonResource *groupedWith, bool activated)
+{
+ return
+ new ui::FltkRadioButtonResource (platform,
+ (ui::FltkRadioButtonResource*)
+ groupedWith,
+ activated);
+}
+
+// ----------------------------------------------------------------------
+
+FltkPlatform::FltkPlatform ()
+{
+ layout = NULL;
+ idleQueue = new container::typed::List <IdleFunc> (true);
+ idleFuncRunning = false;
+ idleFuncId = 0;
+
+ views = new container::typed::List <FltkView> (false);
+ resources = new container::typed::List <ui::FltkResource> (false);
+
+ resourceFactory.setPlatform (this);
+}
+
+FltkPlatform::~FltkPlatform ()
+{
+ if(idleFuncRunning)
+ remove_idle (generalStaticIdle, (void*)this);
+ delete idleQueue;
+ delete views;
+ delete resources;
+}
+
+void FltkPlatform::setLayout (core::Layout *layout)
+{
+ this->layout = layout;
+}
+
+
+void FltkPlatform::attachView (core::View *view)
+{
+ views->append ((FltkView*)view);
+
+ for (container::typed::Iterator <ui::FltkResource> it =
+ resources->iterator (); it.hasNext (); ) {
+ ui::FltkResource *resource = it.getNext ();
+ resource->attachView ((FltkView*)view);
+ }
+}
+
+
+void FltkPlatform::detachView (core::View *view)
+{
+ views->removeRef ((FltkView*)view);
+
+ for (container::typed::Iterator <ui::FltkResource> it =
+ resources->iterator (); it.hasNext (); ) {
+ ui::FltkResource *resource = it.getNext ();
+ resource->detachView ((FltkView*)view);
+ }
+}
+
+
+int FltkPlatform::textWidth (core::style::Font *font, const char *text,
+ int len)
+{
+ FltkFont *ff = (FltkFont*) font;
+ setfont (ff->font, ff->size);
+ return (int) getwidth (text, len);
+}
+
+int FltkPlatform::nextGlyph (const char *text, int idx)
+{
+ return utf8fwd (&text[idx + 1], text, &text[strlen (text)]) - text;
+}
+
+int FltkPlatform::prevGlyph (const char *text, int idx)
+{
+ return utf8back (&text[idx - 1], text, &text[strlen (text)]) - text;
+}
+
+void FltkPlatform::generalStaticIdle (void *data)
+{
+ ((FltkPlatform*)data)->generalIdle();
+}
+
+void FltkPlatform::generalIdle ()
+{
+ IdleFunc *idleFunc;
+
+ if (!idleQueue->isEmpty ()) {
+ /* Execute the first function in the list. */
+ idleFunc = idleQueue->getFirst ();
+ (layout->*(idleFunc->func)) ();
+
+ /* Remove this function. */
+ idleQueue->removeRef(idleFunc);
+ }
+
+ if(idleQueue->isEmpty()) {
+ idleFuncRunning = false;
+ remove_idle (generalStaticIdle, (void*)this);
+ }
+}
+
+/**
+ * \todo Incomplete comments.
+ */
+int FltkPlatform::addIdle (void (core::Layout::*func) ())
+{
+ /*
+ * Since ... (todo) we have to wrap around fltk_add_idle. There is only one
+ * idle function, the passed idle function is put into a queue.
+ */
+ if(!idleFuncRunning) {
+ add_idle (generalStaticIdle, (void*)this);
+ idleFuncRunning = true;
+ }
+
+ idleFuncId++;
+
+ IdleFunc *idleFunc = new IdleFunc();
+ idleFunc->id = idleFuncId;
+ idleFunc->func = func;
+ idleQueue->append (idleFunc);
+
+ return idleFuncId;
+}
+
+void FltkPlatform::removeIdle (int idleId)
+{
+ bool found;
+ container::typed::Iterator <IdleFunc> it;
+ IdleFunc *idleFunc;
+
+ for(found = false, it = idleQueue->iterator(); !found && it.hasNext(); ) {
+ idleFunc = it.getNext();
+ if(idleFunc->id == idleId) {
+ idleQueue->removeRef (idleFunc);
+ found = true;
+ }
+ }
+
+ if(idleFuncRunning && idleQueue->isEmpty())
+ remove_idle (generalStaticIdle, (void*)this);
+}
+
+core::style::Font *FltkPlatform::createFont (core::style::FontAttrs
+ *attrs,
+ bool tryEverything)
+{
+ return FltkFont::create (attrs);
+}
+
+core::style::Color *FltkPlatform::createSimpleColor (int color)
+{
+ return FltkColor::create (color, core::style::Color::TYPE_SIMPLE);
+}
+
+core::style::Color *FltkPlatform::createShadedColor (int color)
+{
+ return FltkColor::create (color, core::style::Color::TYPE_SHADED);
+}
+
+void FltkPlatform::copySelection(const char *text)
+{
+ fltk::copy(text, strlen(text), false);
+}
+
+core::Imgbuf *FltkPlatform::createImgbuf (core::Imgbuf::Type type,
+ int width, int height)
+{
+ return new FltkImgbuf (type, width, height);
+}
+
+core::ui::ResourceFactory *FltkPlatform::getResourceFactory ()
+{
+ return &resourceFactory;
+}
+
+
+void FltkPlatform::attachResource (ui::FltkResource *resource)
+{
+ resources->append (resource);
+
+ for (container::typed::Iterator <FltkView> it = views->iterator ();
+ it.hasNext (); ) {
+ FltkView *view = it.getNext ();
+ resource->attachView (view);
+ }
+}
+
+void FltkPlatform::detachResource (ui::FltkResource *resource)
+{
+ resources->removeRef (resource);
+}
+
+} // namespace fltk
+} // namespace dw
diff --git a/dw/fltkplatform.hh b/dw/fltkplatform.hh
new file mode 100644
index 00000000..7b3d3e73
--- /dev/null
+++ b/dw/fltkplatform.hh
@@ -0,0 +1,150 @@
+#ifndef __DW_FLTKPLATFORM_HH__
+#define __DW_FLTKPLATFORM_HH__
+
+#ifndef __INCLUDED_FROM_DW_FLTK_CORE_HH__
+# error Do not include this file directly, use "fltkcore.hh" instead.
+#endif
+
+#include <fltk/Font.h>
+
+namespace dw {
+
+/**
+ * \brief This namespace contains FLTK implementations of Dw interfaces.
+ */
+namespace fltk {
+
+class FltkFont: public core::style::Font
+{
+ static lout::container::typed::HashTable <dw::core::style::FontAttrs,
+ FltkFont> *fontsTable;
+
+ FltkFont (core::style::FontAttrs *attrs);
+ ~FltkFont ();
+
+public:
+ ::fltk::Font *font;
+
+ static FltkFont *create (core::style::FontAttrs *attrs);
+};
+
+
+class FltkColor: public core::style::Color
+{
+ static lout::container::typed::HashTable <dw::core::style::ColorAttrs,
+ FltkColor> *colorsTable;
+
+ FltkColor (int color, core::style::Color::Type type);
+ ~FltkColor ();
+
+public:
+ int colors[SHADING_NUM];
+
+ static FltkColor *create(int color, core::style::Color::Type type);
+};
+
+
+/**
+ * \brief This interface adds some more methods for all flkt-based views.
+ */
+class FltkView: public core::View
+{
+public:
+ virtual bool usesFltkWidgets () = 0;
+
+ virtual void addFltkWidget (::fltk::Widget *widget,
+ core::Allocation *allocation);
+ virtual void removeFltkWidget (::fltk::Widget *widget);
+ virtual void allocateFltkWidget (::fltk::Widget *widget,
+ core::Allocation *allocation);
+ virtual void drawFltkWidget (::fltk::Widget *widget, core::Rectangle *area);
+};
+
+
+class FltkPlatform: public core::Platform
+{
+private:
+ class FltkResourceFactory: public core::ui::ResourceFactory
+ {
+ private:
+ FltkPlatform *platform;
+
+ public:
+ inline void setPlatform (FltkPlatform *platform) {
+ this->platform = platform; }
+
+ core::ui::LabelButtonResource *createLabelButtonResource (const char
+ *label);
+ core::ui::ComplexButtonResource *
+ createComplexButtonResource (core::Widget *widget, bool relief);
+ core::ui::ListResource *
+ createListResource (core::ui::ListResource::SelectionMode selectionMode);
+ core::ui::OptionMenuResource *createOptionMenuResource ();
+ core::ui::EntryResource *createEntryResource (int maxLength,
+ bool password);
+ core::ui::MultiLineTextResource *createMultiLineTextResource (int cols,
+ int rows);
+ core::ui::CheckButtonResource *createCheckButtonResource (bool
+ activated);
+ core::ui::RadioButtonResource *
+ createRadioButtonResource (core::ui::RadioButtonResource
+ *groupedWith, bool activated);
+ };
+
+ FltkResourceFactory resourceFactory;
+
+ class IdleFunc: public lout::object::Object
+ {
+ public:
+ int id;
+ void (core::Layout::*func) ();
+ };
+
+ core::Layout *layout;
+
+ lout::container::typed::List <IdleFunc> *idleQueue;
+ bool idleFuncRunning;
+ int idleFuncId;
+
+ static void generalStaticIdle(void *data);
+ void generalIdle();
+
+ lout::container::typed::List <FltkView> *views;
+ lout::container::typed::List <ui::FltkResource> *resources;
+
+public:
+ FltkPlatform ();
+ ~FltkPlatform ();
+
+ void setLayout (core::Layout *layout);
+
+ void attachView (core::View *view);
+
+ void detachView (core::View *view);
+
+ int textWidth (core::style::Font *font, const char *text, int len);
+ int nextGlyph (const char *text, int idx);
+ int prevGlyph (const char *text, int idx);
+
+ int addIdle (void (core::Layout::*func) ());
+ void removeIdle (int idleId);
+
+ core::style::Font *createFont (core::style::FontAttrs *attrs,
+ bool tryEverything);
+ core::style::Color *createSimpleColor (int color);
+ core::style::Color *createShadedColor (int color);
+
+ core::Imgbuf *createImgbuf (core::Imgbuf::Type type, int width, int height);
+
+ void copySelection(const char *text);
+
+ core::ui::ResourceFactory *getResourceFactory ();
+
+ void attachResource (ui::FltkResource *resource);
+ void detachResource (ui::FltkResource *resource);
+};
+
+} // namespace fltk
+} // namespace dw
+
+#endif // __DW_FLTKPLATFORM_HH__
diff --git a/dw/fltkpreview.cc b/dw/fltkpreview.cc
new file mode 100644
index 00000000..6bed7adf
--- /dev/null
+++ b/dw/fltkpreview.cc
@@ -0,0 +1,298 @@
+/*
+ * Dillo Widget
+ *
+ * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+
+
+#include "fltkpreview.hh"
+#include "fltkmisc.hh"
+
+#include <fltk/events.h>
+#include <fltk/xbmImage.h>
+#include <fltk/draw.h>
+#include <stdio.h>
+
+#include "preview.xbm"
+
+using namespace ::fltk;
+
+namespace dw {
+namespace fltk {
+
+FltkPreview::FltkPreview (int x, int y, int w, int h,
+ dw::core::Layout *layout, const char *label):
+ FltkViewBase (x, y, w, h, label)
+{
+ layout->attachView (this);
+
+ scrollX = 0;
+ scrollY = 0;
+ scrollWidth = 1;
+ scrollHeight = 1;
+}
+
+FltkPreview::~FltkPreview ()
+{
+}
+
+int FltkPreview::handle (int event)
+{
+ return FltkViewBase::handle (event);
+}
+
+int FltkPreview::translateViewXToCanvasX (int x)
+{
+ return x * canvasWidth / w ();
+}
+
+int FltkPreview::translateViewYToCanvasY (int y)
+{
+ return y * canvasHeight / h ();
+}
+
+int FltkPreview::translateCanvasXToViewX (int x)
+{
+ return x * w () / canvasWidth;
+}
+
+int FltkPreview::translateCanvasYToViewY (int y)
+{
+ return y * h () / canvasHeight;
+}
+
+void FltkPreview::setCanvasSize (int width, int ascent, int descent)
+{
+ FltkViewBase::setCanvasSize (width, ascent, descent);
+ if (parent() && parent()->visible ())
+ ((FltkPreviewWindow*)parent())->reallocate ();
+}
+
+bool FltkPreview::usesViewport ()
+{
+ return true;
+}
+
+int FltkPreview::getHScrollbarThickness ()
+{
+ return 0;
+}
+
+int FltkPreview::getVScrollbarThickness ()
+{
+ return 0;
+}
+
+void FltkPreview::scrollTo (int x, int y)
+{
+ scrollX = x;
+ scrollY = y;
+}
+
+void FltkPreview::setViewportSize (int width, int height,
+ int hScrollbarThickness,
+ int vScrollbarThickness)
+{
+ scrollWidth = width - vScrollbarThickness;
+ scrollHeight = height - hScrollbarThickness;
+}
+
+void FltkPreview::drawText (core::style::Font *font,
+ core::style::Color *color,
+ core::style::Color::Shading shading,
+ int x, int y, const char *text, int len)
+{
+ /*
+ * We must call setfont() before calling getwidth() (or anything
+ * else that measures text).
+ */
+ FltkFont *ff = (FltkFont*)font;
+ setfont(ff->font, translateCanvasXToViewX (ff->size));
+#if 0
+ /**
+ * \todo Normally, this should already be known, maybe it
+ * should be passed?
+ */
+ int width = (int)getwidth (text, len);
+ int height = font->ascent; // No descent, this would look to "bold".
+
+ int x1 = translateCanvasXToViewX (x);
+ int y1 = translateCanvasYToViewY (y);
+ int x2 = translateCanvasXToViewX (x + width);
+ int y2 = translateCanvasYToViewY (y + height);
+ Rectangle rect (x1, y1, x2 - x1, y2 - y1);
+
+ setcolor(((FltkColor*)color)->colors[shading]);
+ fillrect (rect);
+#endif
+ setcolor(((FltkColor*)color)->colors[shading]);
+ drawtext(text, len,
+ translateCanvasXToViewX (x), translateCanvasYToViewY (y));
+}
+
+void FltkPreview::drawImage (core::Imgbuf *imgbuf, int xRoot, int yRoot,
+ int x, int y, int width, int height)
+{
+}
+
+bool FltkPreview::usesFltkWidgets ()
+{
+ return false;
+}
+
+void FltkPreview::drawFltkWidget (Widget *widget,
+ core::Rectangle *area)
+{
+}
+
+// ----------------------------------------------------------------------
+
+FltkPreviewWindow::FltkPreviewWindow (dw::core::Layout *layout):
+ MenuWindow (1, 1)
+{
+ box (EMBOSSED_BOX);
+
+ begin ();
+ preview = new FltkPreview (BORDER_WIDTH, BORDER_WIDTH, 1, 1, layout);
+ end ();
+
+ hide ();
+}
+
+FltkPreviewWindow::~FltkPreviewWindow ()
+{
+}
+
+void FltkPreviewWindow::showWindow ()
+{
+ reallocate ();
+ show ();
+}
+
+void FltkPreviewWindow::reallocate ()
+{
+ int maxWidth = misc::screenWidth () / 2;
+ int maxHeight = misc::screenHeight () * 4 / 5;
+ int mx, my, width, height;
+ bool warp = false;
+
+ if(preview->canvasHeight * maxWidth > maxHeight * preview->canvasWidth) {
+ // Expand to maximal height (most likely case).
+ width = preview->canvasWidth * maxHeight / preview->canvasHeight;
+ height = maxHeight;
+ } else {
+ // Expand to maximal width.
+ width = maxWidth;
+ height = preview->canvasHeight * maxWidth / preview->canvasWidth;
+ }
+
+ get_mouse(mx, my);
+
+ posX = mx - preview->translateCanvasXToViewX (preview->scrollX
+ + preview->scrollWidth / 2);
+ posY = my - preview->translateCanvasYToViewY (preview->scrollY
+ + preview->scrollHeight / 2);
+
+ if (posX < 0) {
+ mx -= posX;
+ posX = 0;
+ warp = true;
+ } else if (posX + width > misc::screenWidth ()) {
+ mx -= (posX - (misc::screenWidth () - width));
+ posX = misc::screenWidth () - width;
+ warp = true;
+ }
+
+ if (posY < 0) {
+ my -= posY;
+ posY = 0;
+ warp = true;
+ } else if (posY + height > misc::screenHeight ()) {
+ my -= (posY - (misc::screenHeight () - height));
+ posY = misc::screenHeight () - height;
+ warp = true;
+ }
+
+ if (warp)
+ misc::warpPointer (mx, my);
+
+ resize (posX, posY, width, height);
+
+ preview->w (w () - 2 * BORDER_WIDTH);
+ preview->h (h () - 2 * BORDER_WIDTH);
+}
+
+void FltkPreviewWindow::hideWindow ()
+{
+ Window::hide ();
+}
+
+void FltkPreviewWindow::scrollTo (int mouseX, int mouseY)
+{
+ preview->scrollX =
+ preview->translateViewXToCanvasX (mouseX - posX - BORDER_WIDTH)
+ - preview->scrollWidth / 2;
+ preview->scrollY =
+ preview->translateViewYToCanvasY (mouseY - posY - BORDER_WIDTH)
+ - preview->scrollHeight / 2;
+ preview->theLayout->scrollPosChanged (preview,
+ preview->scrollX, preview->scrollY);
+}
+
+// ----------------------------------------------------------------------
+
+FltkPreviewButton::FltkPreviewButton (int x, int y, int w, int h,
+ dw::core::Layout *layout,
+ const char *label):
+ Button (x, y, w, h, label)
+{
+ image (new xbmImage (preview_bits, preview_width, preview_height));
+ window = new FltkPreviewWindow (layout);
+}
+
+FltkPreviewButton::~FltkPreviewButton ()
+{
+}
+
+int FltkPreviewButton::handle (int event)
+{
+ /** \bug Some parts are missing. */
+
+ switch (event) {
+ case PUSH:
+ window->showWindow ();
+ return Button::handle (event);
+
+ case DRAG:
+ if (window->visible ()) {
+ window->scrollTo (event_x_root (), event_y_root ());
+ return 1;
+ }
+ return Button::handle (event);
+
+ case RELEASE:
+ window->hideWindow ();
+ return Button::handle (event);
+
+ default:
+ return Button::handle (event);
+ }
+}
+
+} // namespace fltk
+} // namespace dw
diff --git a/dw/fltkpreview.hh b/dw/fltkpreview.hh
new file mode 100644
index 00000000..2464db89
--- /dev/null
+++ b/dw/fltkpreview.hh
@@ -0,0 +1,89 @@
+#ifndef __FlTKPREVIEW_HH__
+#define __FlTKPREVIEW_HH__
+
+#include <fltk/Button.h>
+#include <fltk/MenuWindow.h>
+#include "fltkviewbase.hh"
+
+namespace dw {
+namespace fltk {
+
+class FltkPreview: public FltkViewBase
+{
+ friend class FltkPreviewWindow;
+
+private:
+ int scrollX, scrollY, scrollWidth, scrollHeight;
+
+protected:
+ int translateViewXToCanvasX (int x);
+ int translateViewYToCanvasY (int y);
+ int translateCanvasXToViewX (int x);
+ int translateCanvasYToViewY (int y);
+
+public:
+ FltkPreview (int x, int y, int w, int h, dw::core::Layout *layout,
+ const char *label = 0);
+ ~FltkPreview ();
+
+ int handle (int event);
+
+ void setCanvasSize (int width, int ascent, int descent);
+
+ bool usesViewport ();
+ int getHScrollbarThickness ();
+ int getVScrollbarThickness ();
+ void scrollTo (int x, int y);
+ void setViewportSize (int width, int height,
+ int hScrollbarThickness, int vScrollbarThickness);
+
+ void drawText (core::style::Font *font,
+ core::style::Color *color,
+ core::style::Color::Shading shading,
+ int x, int y, const char *text, int len);
+ void drawImage (core::Imgbuf *imgbuf, int xRoot, int yRoot,
+ int x, int y, int width, int height);
+
+ bool usesFltkWidgets ();
+ void drawFltkWidget (::fltk::Widget *widget, core::Rectangle *area);
+};
+
+
+class FltkPreviewWindow: public ::fltk::MenuWindow
+{
+private:
+ enum { BORDER_WIDTH = 2 };
+
+ FltkPreview *preview;
+ int posX, posY;
+
+public:
+ FltkPreviewWindow (dw::core::Layout *layout);
+ ~FltkPreviewWindow ();
+
+ void reallocate ();
+
+ void showWindow ();
+ void hideWindow ();
+
+ void scrollTo (int mouseX, int mouseY);
+};
+
+
+class FltkPreviewButton: public ::fltk::Button
+{
+private:
+ FltkPreviewWindow *window;
+
+public:
+ FltkPreviewButton (int x, int y, int w, int h,
+ dw::core::Layout *layout, const char *label = 0);
+ ~FltkPreviewButton ();
+
+ int handle (int event);
+};
+
+} // namespace fltk
+} // namespace dw
+
+#endif // __FlTKPREVIEW_HH__
diff --git a/dw/fltkui.cc b/dw/fltkui.cc
new file mode 100644
index 00000000..70ba1ef3
--- /dev/null
+++ b/dw/fltkui.cc
@@ -0,0 +1,1202 @@
+/*
+ * Dillo Widget
+ *
+ * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+
+
+#include "fltkcore.hh"
+#include "fltkflatview.hh"
+#include "fltkcomplexbutton.hh"
+#include "../lout/misc.hh"
+
+#include <stdio.h>
+#include <fltk/Widget.h>
+#include <fltk/Group.h>
+#include <fltk/Input.h>
+#include <fltk/SecretInput.h>
+#include <fltk/TextEditor.h>
+#include <fltk/RadioButton.h>
+#include <fltk/CheckButton.h>
+#include <fltk/Choice.h>
+#include <fltk/Browser.h>
+#include <fltk/MultiBrowser.h>
+#include <fltk/Font.h>
+#include <fltk/draw.h>
+#include <fltk/Symbol.h>
+#include <fltk/Item.h>
+#include <fltk/ItemGroup.h>
+#include <fltk/events.h>
+
+namespace dw {
+namespace fltk {
+namespace ui {
+
+enum { RELIEF_X_THICKNESS = 5, RELIEF_Y_THICKNESS = 3 };
+
+using namespace object;
+using namespace container::typed;
+
+FltkResource::FltkResource (FltkPlatform *platform)
+{
+ this->platform = platform;
+
+ allocation.x = 0;
+ allocation.y = 0;
+ allocation.width = 1;
+ allocation.ascent = 1;
+ allocation.descent = 0;
+
+ style = NULL;
+}
+
+/**
+ * This is not a constructor, since it calls some virtual methods, which
+ * should not be done in a C++ base constructor.
+ */
+void FltkResource::init (FltkPlatform *platform)
+{
+ viewsAndWidgets = new container::typed::List <ViewAndWidget> (true);
+ platform->attachResource (this);
+}
+
+FltkResource::~FltkResource ()
+{
+ platform->detachResource (this);
+ for (Iterator <ViewAndWidget> it = viewsAndWidgets->iterator ();
+ it.hasNext(); ) {
+ ViewAndWidget *viewAndWidget = it.getNext ();
+
+ if (viewAndWidget->widget) {
+ if (viewAndWidget->view) {
+ viewAndWidget->view->removeFltkWidget(viewAndWidget->widget);
+ }
+ delete viewAndWidget->widget;
+ }
+
+ }
+ delete viewsAndWidgets;
+ if(style)
+ style->unref ();
+}
+
+void FltkResource::attachView (FltkView *view)
+{
+ if (view->usesFltkWidgets ()) {
+ ViewAndWidget *viewAndWidget = new ViewAndWidget();
+ viewAndWidget->view = view;
+
+ viewAndWidget->widget = createNewWidget (&allocation);
+ viewAndWidget->view->addFltkWidget (viewAndWidget->widget, &allocation);
+ viewsAndWidgets->append (viewAndWidget);
+ if (style)
+ setWidgetStyle (viewAndWidget->widget, style);
+ }
+}
+
+void FltkResource::detachView (FltkView *view)
+{
+ for (Iterator <ViewAndWidget> it = viewsAndWidgets->iterator ();
+ it.hasNext(); ) {
+ ViewAndWidget *viewAndWidget = it.getNext ();
+ if (viewAndWidget->view == view) {
+ viewsAndWidgets->removeRef (viewAndWidget);
+ return;
+ }
+ }
+
+ fprintf (stderr, "FltkResource::detachView: View not found.");
+}
+
+void FltkResource::sizeAllocate (core::Allocation *allocation)
+{
+ this->allocation = *allocation;
+
+ for (Iterator <ViewAndWidget> it = viewsAndWidgets->iterator ();
+ it.hasNext(); ) {
+ ViewAndWidget *viewAndWidget = it.getNext ();
+ viewAndWidget->view->allocateFltkWidget (viewAndWidget->widget,
+ allocation);
+ }
+}
+
+void FltkResource::draw (core::View *view, core::Rectangle *area)
+{
+ FltkView *fltkView = (FltkView*)view;
+ if (fltkView->usesFltkWidgets ()) {
+ for (Iterator <ViewAndWidget> it = viewsAndWidgets->iterator ();
+ it.hasNext(); ) {
+ ViewAndWidget *viewAndWidget = it.getNext ();
+ if (viewAndWidget->view == fltkView) {
+ fltkView->drawFltkWidget (viewAndWidget->widget, area);
+ break;
+ }
+ }
+ }
+}
+
+void FltkResource::setStyle (core::style::Style *style)
+{
+ if(this->style)
+ this->style->unref ();
+
+ this->style = style;
+ style->ref ();
+
+ for (Iterator <ViewAndWidget> it = viewsAndWidgets->iterator ();
+ it.hasNext(); ) {
+ ViewAndWidget *viewAndWidget = it.getNext ();
+ setWidgetStyle (viewAndWidget->widget, style);
+ }
+}
+
+void FltkResource::setWidgetStyle (::fltk::Widget *widget,
+ core::style::Style *style)
+{
+ /** \bug label or text? */
+
+ FltkFont *font = (FltkFont*)style->font;
+ widget->labelsize (font->size);
+ widget->labelfont (font->font);
+ widget->textsize (font->size);
+ widget->textfont (font->font);
+
+ FltkColor *bg = (FltkColor*)style->backgroundColor;
+ if (bg) {
+ if (style->color) {
+ /*
+ * todo: if/when CSS is implemented, test whether style->color
+ * will consistently provide readable widgets.
+ */
+ int32_t c = bg->colors[FltkColor::SHADING_NORMAL];
+ int r = (c >> 24) & 0xff, g = (c >> 16) & 0xff, b = (c >> 8) & 0xff;
+ bool light = (r + g >= 0x150) || (r + g + b >= 0x180);
+
+ widget->labelcolor(light? ::fltk::BLACK : ::fltk::WHITE);
+ widget->textcolor(light? ::fltk::BLACK : ::fltk::WHITE);
+ widget->selection_color(light? ::fltk::BLACK : ::fltk::WHITE);
+ }
+
+ widget->color(bg->colors[FltkColor::SHADING_NORMAL]);
+ widget->buttoncolor(bg->colors[FltkColor::SHADING_NORMAL]);
+ widget->highlight_color(bg->colors[FltkColor::SHADING_LIGHT]);
+ widget->selection_textcolor(bg->colors[FltkColor::SHADING_NORMAL]);
+ }
+}
+
+bool FltkResource::isEnabled ()
+{
+ /** \bug Not implemented. */
+ return true;
+}
+
+void FltkResource::setEnabled (bool enabled)
+{
+ /** \bug Not implemented. */
+}
+
+// ----------------------------------------------------------------------
+
+template <class I> void FltkSpecificResource<I>::sizeAllocate (core::Allocation
+ *allocation)
+{
+ FltkResource::sizeAllocate (allocation);
+}
+
+template <class I> void FltkSpecificResource<I>::draw (core::View *view,
+ core::Rectangle *area)
+{
+ FltkResource::draw (view, area);
+}
+
+template <class I> void FltkSpecificResource<I>::setStyle (core::style::Style
+ *style)
+{
+ FltkResource::setStyle (style);
+}
+
+template <class I> bool FltkSpecificResource<I>::isEnabled ()
+{
+ return FltkResource::isEnabled ();
+}
+
+template <class I> void FltkSpecificResource<I>::setEnabled (bool enabled)
+{
+ FltkResource::setEnabled (enabled);
+}
+
+// ----------------------------------------------------------------------
+
+FltkLabelButtonResource::FltkLabelButtonResource (FltkPlatform *platform,
+ const char *label):
+ FltkSpecificResource <dw::core::ui::LabelButtonResource> (platform)
+{
+ this->label = strdup (label);
+ init (platform);
+}
+
+FltkLabelButtonResource::~FltkLabelButtonResource ()
+{
+ delete label;
+}
+
+::fltk::Widget *FltkLabelButtonResource::createNewWidget (core::Allocation
+ *allocation)
+{
+ ::fltk::Button *button =
+ new ::fltk::Button (allocation->x, allocation->y, allocation->width,
+ allocation->ascent + allocation->descent,
+ label);
+ button->callback (widgetCallback, this);
+ button->when (::fltk::WHEN_RELEASE);
+ return button;
+}
+
+void FltkLabelButtonResource::sizeRequest (core::Requisition *requisition)
+{
+ if (style) {
+ FltkFont *font = (FltkFont*)style->font;
+ ::fltk::setfont(font->font,font->size);
+ requisition->width =
+ (int)::fltk::getwidth (label, strlen (label))
+ + 2 * RELIEF_X_THICKNESS;
+ requisition->ascent = font->ascent + RELIEF_Y_THICKNESS;
+ requisition->descent = font->descent + RELIEF_Y_THICKNESS;
+ } else {
+ requisition->width = 1;
+ requisition->ascent = 1;
+ requisition->descent = 0;
+ }
+}
+
+void FltkLabelButtonResource::widgetCallback (::fltk::Widget *widget,
+ void *data)
+{
+ if (widget->when () & ::fltk::WHEN_RELEASE)
+ ((FltkLabelButtonResource*)data)->emitActivate ();
+}
+
+const char *FltkLabelButtonResource::getLabel ()
+{
+ return label;
+}
+
+
+void FltkLabelButtonResource::setLabel (const char *label)
+{
+ delete this->label;
+ this->label = strdup (label);
+
+ for (Iterator <ViewAndWidget> it = viewsAndWidgets->iterator ();
+ it.hasNext(); ) {
+ ViewAndWidget *viewAndWidget = it.getNext ();
+ viewAndWidget->widget->label (this->label);
+ }
+
+ queueResize (true);
+}
+
+// ----------------------------------------------------------------------
+
+FltkComplexButtonResource::FltkComplexButtonResource (FltkPlatform *platform,
+ dw::core::Widget
+ *widget, bool relief):
+ FltkSpecificResource <dw::core::ui::ComplexButtonResource> (platform)
+{
+ viewsAndViews = new container::typed::List <ViewAndView> (true);
+ this->relief = relief;
+ FltkResource::init (platform);
+ ComplexButtonResource::init (widget);
+}
+
+FltkComplexButtonResource::~FltkComplexButtonResource ()
+{
+ delete viewsAndViews;
+}
+
+void FltkComplexButtonResource::widgetCallback (::fltk::Widget *widget,
+ void *data)
+{
+ FltkComplexButtonResource *res = (FltkComplexButtonResource*)data;
+
+ /* would be best not to send click pos. if the image could not be loaded */
+ if (::fltk::event() == ::fltk::RELEASE &&
+ ::fltk::event_button() == ::fltk::LeftButton) {
+ res->click_x = ::fltk::event_x();
+ res->click_y = ::fltk::event_y();
+ res->emitActivate ();
+ } else {
+ ((FltkViewBase*)res->lastFlatView)->handle(::fltk::event());
+ }
+}
+
+dw::core::Platform *FltkComplexButtonResource::createPlatform ()
+{
+ return new FltkPlatform ();
+}
+
+void FltkComplexButtonResource::attachView (FltkView *view)
+{
+ FltkResource::attachView (view);
+
+ if (view->usesFltkWidgets ()) {
+ ViewAndView *viewAndView = new ViewAndView();
+ viewAndView->topView = view;
+ viewAndView->flatView = lastFlatView;
+ viewsAndViews->append (viewAndView);
+ }
+}
+
+void FltkComplexButtonResource::detachView (FltkView *view)
+{
+ FltkResource::detachView (view);
+
+ for (Iterator <ViewAndView> it = viewsAndViews->iterator ();
+ it.hasNext(); ) {
+ ViewAndView *viewAndView = it.getNext ();
+ if (viewAndView->topView == view) {
+ viewsAndViews->removeRef (viewAndView);
+ return;
+ }
+ }
+
+ fprintf (stderr,
+ "FltkComplexButtonResourceResource::detachView: View not "
+ "found.\n");
+}
+
+void FltkComplexButtonResource::sizeAllocate (core::Allocation *allocation)
+{
+ FltkResource::sizeAllocate (allocation);
+
+ for (Iterator <ViewAndView> it = viewsAndViews->iterator ();
+ it.hasNext(); ) {
+ ViewAndView *viewAndView = it.getNext ();
+ ((FltkFlatView*)viewAndView->flatView)->resize (
+ reliefXThickness (),
+ reliefYThickness (),
+ allocation->width - 2 * reliefXThickness (),
+ allocation->ascent + allocation->descent - 2 * reliefYThickness ());
+
+ ((FltkFlatView*)viewAndView->flatView)->parent ()->init_sizes ();
+ }
+}
+
+void FltkComplexButtonResource::setLayout (dw::core::Layout *layout)
+{
+ for (Iterator <ViewAndView> it = viewsAndViews->iterator ();
+ it.hasNext(); ) {
+ ViewAndView *viewAndView = it.getNext ();
+ layout->attachView (viewAndView->flatView);
+ }
+}
+
+int FltkComplexButtonResource::reliefXThickness ()
+{
+ return relief ? RELIEF_X_THICKNESS : 0;
+}
+
+int FltkComplexButtonResource::reliefYThickness ()
+{
+ return relief ? RELIEF_Y_THICKNESS : 0;
+}
+
+
+::fltk::Widget *FltkComplexButtonResource::createNewWidget (core::Allocation
+ *allocation)
+{
+ ComplexButton *button =
+ new ComplexButton (allocation->x, allocation->y, allocation->width,
+ allocation->ascent + allocation->descent);
+ button->callback (widgetCallback, this);
+ button->when (::fltk::WHEN_RELEASE);
+ if (!relief)
+ button->box(::fltk::FLAT_BOX);
+
+ FltkFlatView *flatView =
+ new FltkFlatView (allocation->x + reliefXThickness (),
+ allocation->y + reliefYThickness (),
+ allocation->width - 2 * reliefXThickness (),
+ allocation->ascent + allocation->descent
+ - 2 * reliefYThickness ());
+ button->add (flatView);
+
+ lastFlatView = flatView;
+
+ if (layout)
+ layout->attachView (lastFlatView);
+ return button;
+}
+
+// ----------------------------------------------------------------------
+
+FltkEntryResource::FltkEntryResource (FltkPlatform *platform, int maxLength,
+ bool password):
+ FltkSpecificResource <dw::core::ui::EntryResource> (platform)
+{
+ this->maxLength = maxLength;
+ this->password = password;
+
+ initText = NULL;
+ editable = false;
+
+ init (platform);
+}
+
+FltkEntryResource::~FltkEntryResource ()
+{
+ if (initText)
+ delete initText;
+}
+
+::fltk::Widget *FltkEntryResource::createNewWidget (core::Allocation
+ *allocation)
+{
+ ::fltk::Input *input =
+ password ?
+ new ::fltk::SecretInput (allocation->x, allocation->y,
+ allocation->width,
+ allocation->ascent + allocation->descent) :
+ new ::fltk::Input (allocation->x, allocation->y, allocation->width,
+ allocation->ascent + allocation->descent);
+ input->callback (widgetCallback, this);
+ input->when (::fltk::WHEN_ENTER_KEY_ALWAYS);
+
+ if (viewsAndWidgets->isEmpty ()) {
+ // First widget created, attach the set text.
+ if (initText)
+ input->value (initText);
+ } else
+ input->value
+ (((::fltk::Input*)viewsAndWidgets->getFirst()->widget)->value ());
+
+ return input;
+}
+
+void FltkEntryResource::sizeRequest (core::Requisition *requisition)
+{
+ if (style) {
+ FltkFont *font = (FltkFont*)style->font;
+ ::fltk::setfont(font->font,font->size);
+ requisition->width =
+ (int)::fltk::getwidth ("M", 1)
+ * (maxLength == UNLIMITED_MAX_LENGTH ? 10 : maxLength)
+ + 2 * RELIEF_X_THICKNESS;
+ requisition->ascent = font->ascent + RELIEF_Y_THICKNESS;
+ requisition->descent = font->descent + RELIEF_Y_THICKNESS;
+ } else {
+ requisition->width = 1;
+ requisition->ascent = 1;
+ requisition->descent = 0;
+ }
+}
+
+void FltkEntryResource::widgetCallback (::fltk::Widget *widget,
+ void *data)
+{
+ /* The (::fltk::event_key() == ::fltk::ReturnKey) test
+ * is necessary because WHEN_ENTER_KEY also includes
+ * other events we're not interested in. For instance pressing
+ * The Back or Forward, buttons, or the first click on a rendered
+ * page. BUG: this must be investigated and reported to FLTK2 team
+ */
+ printf ("when = %d\n", widget->when ());
+ if ((widget->when () & ::fltk::WHEN_ENTER_KEY_ALWAYS) &&
+ (::fltk::event_key() == ::fltk::ReturnKey))
+ ((FltkEntryResource*)data)->emitActivate ();
+}
+
+const char *FltkEntryResource::getText ()
+{
+ if (viewsAndWidgets->isEmpty ())
+ return initText;
+ else
+ return ((::fltk::Input*)viewsAndWidgets->getFirst()->widget)->value ();
+}
+
+void FltkEntryResource::setText (const char *text)
+{
+ if (initText)
+ delete initText;
+ initText = strdup (text);
+
+ for (Iterator <ViewAndWidget> it = viewsAndWidgets->iterator ();
+ it.hasNext(); ) {
+ ViewAndWidget *viewAndWidget = it.getNext ();
+ ((::fltk::Input*)viewAndWidget->widget)->value (initText);
+ }
+}
+
+bool FltkEntryResource::isEditable ()
+{
+ return editable;
+}
+
+void FltkEntryResource::setEditable (bool editable)
+{
+ this->editable = editable;
+}
+
+// ----------------------------------------------------------------------
+
+FltkMultiLineTextResource::FltkMultiLineTextResource (FltkPlatform *platform,
+ int cols, int rows):
+ FltkSpecificResource <dw::core::ui::MultiLineTextResource> (platform)
+{
+ buffer = new ::fltk::TextBuffer;
+ editable = false;
+
+ numCols = cols;
+ numRows = rows;
+
+ // Check values. Upper bound check is left to the caller.
+ if (numCols < 1) {
+ fprintf (stderr, "WARNING: numCols = %d is set to 1.\n", numCols);
+ numCols = 1;
+ }
+ if (numRows < 1) {
+ fprintf (stderr, "WARNING: numRows = %d is set to 1.\n", numRows);
+ numRows = 1;
+ }
+
+ init (platform);
+}
+
+FltkMultiLineTextResource::~FltkMultiLineTextResource ()
+{
+ /* Free memory avoiding a double-free of text buffers */
+ for (Iterator <ViewAndWidget> it = viewsAndWidgets->iterator ();
+ it.hasNext(); ) {
+ ViewAndWidget *viewAndWidget = it.getNext ();
+ ((::fltk::TextEditor *) viewAndWidget->widget)->buffer (0);
+ }
+ delete buffer;
+}
+
+::fltk::Widget *FltkMultiLineTextResource::createNewWidget (core::Allocation
+ *allocation)
+{
+ ::fltk::TextEditor *text =
+ new ::fltk::TextEditor (allocation->x, allocation->y,
+ allocation->width,
+ allocation->ascent + allocation->descent);
+ text->buffer (buffer);
+ return text;
+}
+
+void FltkMultiLineTextResource::sizeRequest (core::Requisition *requisition)
+{
+ if (style) {
+ FltkFont *font = (FltkFont*)style->font;
+ ::fltk::setfont(font->font,font->size);
+ requisition->width =
+ (int)::fltk::getwidth ("X", 1) * numCols +
+ 2 * RELIEF_X_THICKNESS;
+ requisition->ascent =
+ font->ascent + RELIEF_Y_THICKNESS;
+ requisition->descent =
+ font->descent +
+ (font->ascent + font->descent) * (numRows - 1) +
+ RELIEF_Y_THICKNESS;
+ } else {
+ requisition->width = 1;
+ requisition->ascent = 1;
+ requisition->descent = 0;
+ }
+}
+
+const char *FltkMultiLineTextResource::getText ()
+{
+ return buffer->text ();
+}
+
+void FltkMultiLineTextResource::setText (const char *text)
+{
+ buffer->text (text);
+}
+
+bool FltkMultiLineTextResource::isEditable ()
+{
+ return editable;
+}
+
+void FltkMultiLineTextResource::setEditable (bool editable)
+{
+ this->editable = editable;
+}
+
+// ----------------------------------------------------------------------
+
+template <class I>
+FltkToggleButtonResource<I>::FltkToggleButtonResource (FltkPlatform *platform,
+ bool activated):
+ FltkSpecificResource <I> (platform)
+{
+ initActivated = activated;
+}
+
+
+template <class I>
+FltkToggleButtonResource<I>::~FltkToggleButtonResource ()
+{
+}
+
+
+template <class I>
+::fltk::Widget *FltkToggleButtonResource<I>::createNewWidget (core::Allocation
+ *allocation)
+{
+ ::fltk::Button *button = createNewButton (allocation);
+
+ if (this->viewsAndWidgets->isEmpty ())
+ button->value (initActivated);
+ else
+ button->value (((::fltk::Button*)this->viewsAndWidgets
+ ->getFirst()->widget)->value ());
+
+ return button;
+}
+
+
+template <class I>
+void FltkToggleButtonResource<I>::sizeRequest (core::Requisition *requisition)
+{
+ /** \bug Random values. */
+ requisition->width = 20;
+ requisition->ascent = 18;
+ requisition->descent = 5;
+}
+
+
+template <class I>
+bool FltkToggleButtonResource<I>::FltkToggleButtonResource::isActivated ()
+{
+ if (this->viewsAndWidgets->isEmpty ())
+ return initActivated;
+ else
+ return
+ ((::fltk::Button*)this->viewsAndWidgets->getFirst()->widget)
+ ->value ();
+}
+
+
+template <class I>
+void FltkToggleButtonResource<I>::setActivated (bool activated)
+{
+ initActivated = activated;
+
+ for (Iterator <FltkResource::ViewAndWidget> it =
+ this->viewsAndWidgets->iterator ();
+ it.hasNext(); ) {
+ FltkResource::ViewAndWidget *viewAndWidget = it.getNext ();
+ ((::fltk::Button*)viewAndWidget->widget)->value (initActivated);
+ }
+}
+
+// ----------------------------------------------------------------------
+
+FltkCheckButtonResource::FltkCheckButtonResource (FltkPlatform *platform,
+ bool activated):
+ FltkToggleButtonResource<dw::core::ui::CheckButtonResource> (platform,
+ activated)
+{
+ init (platform);
+}
+
+
+FltkCheckButtonResource::~FltkCheckButtonResource ()
+{
+}
+
+
+::fltk::Button *FltkCheckButtonResource::createNewButton (core::Allocation
+ *allocation)
+{
+ return
+ new ::fltk::CheckButton (allocation->x, allocation->y, allocation->width,
+ allocation->ascent + allocation->descent);
+}
+
+// ----------------------------------------------------------------------
+
+bool FltkRadioButtonResource::Group::FltkGroupIterator::hasNext ()
+{
+ return it.hasNext ();
+}
+
+dw::core::ui::RadioButtonResource
+*FltkRadioButtonResource::Group::FltkGroupIterator::getNext ()
+{
+ return (dw::core::ui::RadioButtonResource*)it.getNext ();
+}
+
+void FltkRadioButtonResource::Group::FltkGroupIterator::unref ()
+{
+ delete this;
+}
+
+
+FltkRadioButtonResource::Group::Group (FltkRadioButtonResource
+ *radioButtonResource)
+{
+ list = new container::typed::List <FltkRadioButtonResource> (false);
+ connect (radioButtonResource);
+}
+
+FltkRadioButtonResource::Group::~Group ()
+{
+ delete list;
+}
+
+void FltkRadioButtonResource::Group::connect (FltkRadioButtonResource
+ *radioButtonResource)
+{
+ list->append (radioButtonResource);
+}
+
+void FltkRadioButtonResource::Group::unconnect (FltkRadioButtonResource
+ *radioButtonResource)
+{
+ list->removeRef (radioButtonResource);
+ if (list->isEmpty ())
+ delete this;
+}
+
+
+FltkRadioButtonResource::FltkRadioButtonResource (FltkPlatform *platform,
+ FltkRadioButtonResource
+ *groupedWith,
+ bool activated):
+ FltkToggleButtonResource<dw::core::ui::RadioButtonResource> (platform,
+ activated)
+{
+ init (platform);
+
+ if (groupedWith) {
+ group = groupedWith->group;
+ group->connect (this);
+ } else
+ group = new Group (this);
+}
+
+
+FltkRadioButtonResource::~FltkRadioButtonResource ()
+{
+ group->unconnect (this);
+}
+
+dw::core::ui::RadioButtonResource::GroupIterator
+*FltkRadioButtonResource::groupIterator ()
+{
+ return group->groupIterator ();
+}
+
+void FltkRadioButtonResource::widgetCallback (::fltk::Widget *widget,
+ void *data)
+{
+ if (widget->when () & ::fltk::WHEN_CHANGED)
+ ((FltkRadioButtonResource*)data)->buttonClicked ();
+}
+
+void FltkRadioButtonResource::buttonClicked ()
+{
+ for (Iterator <FltkRadioButtonResource> it = group->iterator ();
+ it.hasNext (); ) {
+ FltkRadioButtonResource *other = it.getNext ();
+ other->setActivated (other == this);
+ }
+}
+
+::fltk::Button *FltkRadioButtonResource::createNewButton (core::Allocation
+ *allocation)
+{
+ /*
+ * Groups of fltk::RadioButton must be added to one fltk::Group, which is
+ * not possible in this context. For this, we do the grouping ourself,
+ * based on FltkRadioButtonResource::Group.
+ *
+ * What we actually need for this, is a widget, which behaves like a
+ * check button, but looks like a radio button. The first depends on the
+ * type, the second on the style. Since the type is simpler to change
+ * than the style, we create a radio button, and then change the type
+ * (instead of creating a check button, and changing the style).
+ */
+
+ ::fltk::Button *button =
+ new ::fltk::RadioButton (allocation->x, allocation->y,
+ allocation->width,
+ allocation->ascent + allocation->descent);
+ button->when (::fltk::WHEN_CHANGED);
+ button->callback (widgetCallback, this);
+ button->type (::fltk::Button::TOGGLE);
+
+ return button;
+}
+
+// ----------------------------------------------------------------------
+
+template <class I> FltkSelectionResource<I>::Item::Item (Type type,
+ const char *name,
+ bool enabled,
+ bool selected)
+{
+ this->type = type;
+ this->name = name ? strdup (name) : NULL;
+ this->enabled = enabled;
+ initSelected = selected;
+}
+
+template <class I> FltkSelectionResource<I>::Item::~Item ()
+{
+ if (name)
+ delete name;
+}
+
+template <class I>
+::fltk::Item *FltkSelectionResource<I>::Item::createNewWidget (int index)
+{
+ ::fltk::Item *item = new ::fltk::Item (name);
+ item->user_data ((void *) index);
+ return item;
+}
+
+template <class I>
+::fltk::ItemGroup *
+FltkSelectionResource<I>::Item::createNewGroupWidget ()
+{
+ ::fltk::ItemGroup *itemGroup = new ::fltk::ItemGroup (name);
+ itemGroup->user_data ((void *) -1L);
+ return itemGroup;
+}
+
+
+template <class I>
+FltkSelectionResource<I>::WidgetStack::WidgetStack (::fltk::Menu *widget)
+{
+ this->widget = widget;
+ this->stack = new Stack <TypedPointer < ::fltk::Menu> > (true);
+}
+
+template <class I> FltkSelectionResource<I>::WidgetStack::~WidgetStack ()
+{
+ delete stack;
+}
+
+
+template <class I>
+FltkSelectionResource<I>::FltkSelectionResource (FltkPlatform *platform):
+ FltkSpecificResource<I> (platform)
+{
+ widgetStacks = new List <WidgetStack> (true);
+ allItems = new List <Item> (true);
+ items = new Vector <Item> (16, false);
+}
+
+template <class I> FltkSelectionResource<I>::~FltkSelectionResource ()
+{
+ delete widgetStacks;
+ delete allItems;
+ delete items;
+}
+
+template <class I> dw::core::Iterator *
+FltkSelectionResource<I>::iterator (dw::core::Content::Type mask, bool atEnd)
+{
+ /** \bug Implementation. */
+ return new core::EmptyIterator (this->getEmbed (), mask, atEnd);
+}
+
+template <class I> ::fltk::Widget *
+FltkSelectionResource<I>::createNewWidget (core::Allocation *allocation)
+{
+ /** \todo Attributes (enabled, selected). */
+
+ ::fltk::Menu *menu = createNewMenu (allocation);
+ WidgetStack *widgetStack = new WidgetStack (menu);
+ widgetStack->stack->push (new TypedPointer < ::fltk::Menu> (menu));
+ widgetStacks->append (widgetStack);
+
+
+ ::fltk::Menu *itemGroup;
+ ::fltk::Item *itemWidget;
+
+ ::fltk::Group *currGroup = widgetStack->stack->getTop()->getTypedValue();
+
+ int index = 0;
+ for (Iterator <Item> it = allItems->iterator (); it.hasNext (); ) {
+ Item *item = it.getNext ();
+ switch (item->type) {
+ case Item::ITEM:
+ itemWidget = item->createNewWidget (index++);
+ currGroup->add (itemWidget);
+ break;
+
+ case Item::START:
+ itemGroup = item->createNewGroupWidget ();
+ currGroup->add (itemGroup);
+ widgetStack->stack->push (new TypedPointer < ::fltk::Menu> (menu));
+ currGroup = itemGroup;
+ break;
+
+ case Item::END:
+ widgetStack->stack->pop ();
+ currGroup = widgetStack->stack->getTop()->getTypedValue();
+ break;
+ }
+ }
+
+ return menu;
+}
+
+template <class I>
+typename FltkSelectionResource<I>::Item *
+FltkSelectionResource<I>::createNewItem (typename Item::Type type,
+ const char *name,
+ bool enabled,
+ bool selected) {
+ return new Item(type,name,enabled,selected);
+}
+
+template <class I> void FltkSelectionResource<I>::addItem (const char *str,
+ bool enabled,
+ bool selected)
+{
+ int index = items->size ();
+ Item *item = createNewItem (Item::ITEM, str, enabled, selected);
+ items->put (item);
+ allItems->append (item);
+
+ for (Iterator <WidgetStack> it = widgetStacks->iterator ();
+ it.hasNext(); ) {
+ WidgetStack *widgetStack = it.getNext ();
+ ::fltk::Item *itemWidget = item->createNewWidget (index);
+ widgetStack->stack->getTop()->getTypedValue()->add (itemWidget);
+
+ if (!enabled)
+ itemWidget->deactivate ();
+
+ if (selected) {
+ itemWidget->set_selected();
+ if (setSelectedItems ()) {
+ // Handle multiple item selection.
+ int pos[widgetStack->stack->size ()];
+ int i;
+ Iterator <TypedPointer < ::fltk::Menu> > it;
+ for (it = widgetStack->stack->iterator (),
+ i = widgetStack->stack->size () - 1;
+ it.hasNext ();
+ i--) {
+ TypedPointer < ::fltk::Menu> * p = it.getNext ();
+ pos[i] = p->getTypedValue()->children () - 1;
+ }
+ widgetStack->widget->set_item (pos, widgetStack->stack->size ());
+ }
+ }
+ }
+}
+
+template <class I> void FltkSelectionResource<I>::pushGroup (const char *name,
+ bool enabled)
+{
+ Item *item = createNewItem (Item::START, name, enabled);
+ allItems->append (item);
+
+ for (Iterator <WidgetStack> it = widgetStacks->iterator ();
+ it.hasNext(); ) {
+ WidgetStack *widgetStack = it.getNext ();
+ ::fltk::ItemGroup *group = item->createNewGroupWidget ();
+ widgetStack->stack->getTop()->getTypedValue()->add (group);
+ widgetStack->stack->push (new TypedPointer < ::fltk::Menu> (group));
+ if(!enabled)
+ group->deactivate ();
+ }
+}
+
+template <class I> void FltkSelectionResource<I>::popGroup ()
+{
+ Item *item = createNewItem (Item::END);
+ allItems->append (item);
+
+ for (Iterator <WidgetStack> it = widgetStacks->iterator ();
+ it.hasNext(); ) {
+ WidgetStack *widgetStack = it.getNext ();
+ widgetStack->stack->pop ();
+ }
+}
+
+template <class I> int FltkSelectionResource<I>::getNumberOfItems ()
+{
+ return items->size ();
+}
+
+template <class I> const char *FltkSelectionResource<I>::getItem (int index)
+{
+ return items->get(index)->name;
+}
+
+template <class I> int FltkSelectionResource<I>::getMaxStringWidth ()
+{
+ int width = 0, numberOfItems = getNumberOfItems ();
+ for (int i = 0; i < numberOfItems; i++) {
+ int len = (int)::fltk::getwidth (getItem(i));
+ if (len > width) width = len;
+ }
+ return width;
+}
+
+// ----------------------------------------------------------------------
+
+FltkOptionMenuResource::FltkOptionMenuResource (FltkPlatform *platform):
+ FltkSelectionResource <dw::core::ui::OptionMenuResource> (platform),
+ selection(-1)
+{
+ init (platform);
+}
+
+FltkOptionMenuResource::~FltkOptionMenuResource ()
+{
+}
+
+
+::fltk::Menu *FltkOptionMenuResource::createNewMenu (core::Allocation
+ *allocation)
+{
+ ::fltk::Menu *menu =
+ new ::fltk::Choice (allocation->x, allocation->y,
+ allocation->width,
+ allocation->ascent + allocation->descent);
+ menu->callback(widgetCallback,this);
+ return menu;
+}
+
+void FltkOptionMenuResource::widgetCallback (::fltk::Widget *widget,
+ void *data)
+{
+ ((FltkOptionMenuResource *) data)->selection =
+ (long) (((::fltk::Menu *) widget)->item()->user_data());
+}
+
+void FltkOptionMenuResource::sizeRequest (core::Requisition *requisition)
+{
+ if (style) {
+ FltkFont *font = (FltkFont*)style->font;
+ ::fltk::setfont(font->font,font->size);
+ int maxStringWidth = getMaxStringWidth ();
+ requisition->ascent = font->ascent + RELIEF_Y_THICKNESS;
+ requisition->descent = font->descent + RELIEF_Y_THICKNESS;
+ requisition->width = maxStringWidth
+ + (requisition->ascent + requisition->descent) * 4 / 5
+ + 2 * RELIEF_X_THICKNESS;
+ } else {
+ requisition->width = 1;
+ requisition->ascent = 1;
+ requisition->descent = 0;
+ }
+}
+
+void FltkOptionMenuResource::addItem (const char *str,
+ bool enabled, bool selected)
+{
+ FltkSelectionResource<dw::core::ui::OptionMenuResource>::addItem
+ (str,enabled,selected);
+ if (selected)
+ selection = (items->size ()) - 1;
+
+ queueResize (true);
+}
+
+bool FltkOptionMenuResource::isSelected (int index)
+{
+ return index == selection;
+}
+
+// ----------------------------------------------------------------------
+
+FltkListResource::FltkListResource (FltkPlatform *platform,
+ core::ui::ListResource::SelectionMode
+ selectionMode):
+ FltkSelectionResource <dw::core::ui::ListResource> (platform),
+ itemsSelected(8)
+{
+ init (platform);
+}
+
+FltkListResource::~FltkListResource ()
+{
+}
+
+
+::fltk::Menu *FltkListResource::createNewMenu (core::Allocation *allocation)
+{
+ ::fltk::Menu *menu =
+ new ::fltk::MultiBrowser (allocation->x, allocation->y,
+ allocation->width,
+ allocation->ascent + allocation->descent);
+ menu->callback(widgetCallback,this);
+ menu->when(::fltk::WHEN_CHANGED);
+ return menu;
+}
+
+void FltkListResource::widgetCallback (::fltk::Widget *widget, void *data)
+{
+ ::fltk::Widget *fltkItem = ((::fltk::Menu *) widget)->item ();
+ int index = (long) (fltkItem->user_data ());
+ if (index > -1) {
+ bool selected = fltkItem->selected ();
+ ((FltkListResource *) data)->itemsSelected.set (index, selected);
+ }
+}
+
+void FltkListResource::addItem (const char *str, bool enabled, bool selected)
+{
+ FltkSelectionResource<dw::core::ui::ListResource>::addItem
+ (str,enabled,selected);
+ int index = itemsSelected.size ();
+ itemsSelected.increase ();
+ itemsSelected.set (index,selected);
+ queueResize (true);
+}
+
+void FltkListResource::sizeRequest (core::Requisition *requisition)
+{
+ if (style) {
+ FltkFont *font = (FltkFont*)style->font;
+ ::fltk::setfont(font->font,font->size);
+ int maxStringWidth = getMaxStringWidth ();
+ requisition->ascent = font->ascent + RELIEF_Y_THICKNESS;
+ requisition->descent =
+ (getNumberOfItems () - 1) * (font->ascent) + font->descent;
+ requisition->width = maxStringWidth
+ + (font->ascent + font->descent) * 4 / 5
+ + 2 * RELIEF_X_THICKNESS;
+ } else {
+ requisition->width = 1;
+ requisition->ascent = 1;
+ requisition->descent = 0;
+ }
+}
+
+bool FltkListResource::isSelected (int index)
+{
+ return itemsSelected.get (index);
+}
+
+} // namespace ui
+} // namespace fltk
+} // namespace dw
+
diff --git a/dw/fltkui.hh b/dw/fltkui.hh
new file mode 100644
index 00000000..4de99d27
--- /dev/null
+++ b/dw/fltkui.hh
@@ -0,0 +1,554 @@
+#ifndef __DW_FLTK_UI_HH__
+#define __DW_FLTK_UI_HH__
+
+#ifndef __INCLUDED_FROM_DW_FLTK_CORE_HH__
+# error Do not include this file directly, use "fltkcore.hh" instead.
+#endif
+
+#include <fltk/Button.h>
+#include <fltk/Menu.h>
+#include <fltk/TextBuffer.h>
+#include <fltk/Item.h>
+#include <fltk/ItemGroup.h>
+
+namespace dw {
+namespace fltk {
+
+using namespace lout;
+
+/**
+ * \brief FLTK implementation of dw::core::ui.
+ *
+ * The design should be like this:
+ *
+ * \dot
+ * digraph G {
+ * node [shape=record, fontname=Helvetica, fontsize=10];
+ * edge [arrowhead="none", arrowtail="empty", labelfontname=Helvetica,
+ * labelfontsize=10, color="#404040", labelfontcolor="#000080"];
+ * fontname=Helvetica; fontsize=10;
+ *
+ * subgraph cluster_core {
+ * style="dashed"; color="#000080"; fontname=Helvetica; fontsize=10;
+ * label="dw::core::ui";
+ *
+ * Resource [color="#a0a0a0", URL="\ref dw::core::ui::Resource"];
+ * LabelButtonResource [color="#a0a0a0",
+ * URL="\ref dw::core::ui::LabelButtonResource"];
+ * EntryResource [color="#a0a0a0",
+ * URL="\ref dw::core::ui::EntryResource"];
+ * }
+ *
+ * subgraph cluster_fltk {
+ * style="dashed"; color="#000080"; fontname=Helvetica; fontsize=10;
+ * label="dw::fltk::ui";
+ *
+ * FltkResource [color="#a0a0a0", URL="\ref dw::fltk::ui::FltkResource"];
+ * FltkLabelButtonResource
+ * [URL="\ref dw::fltk::ui::FltkLabelButtonResource"];
+ * FltkEntryResource [URL="\ref dw::fltk::ui::FltkEntryResource"];
+ * }
+ *
+ * Resource -> LabelButtonResource;
+ * Resource -> EntryResource;
+ * FltkResource -> FltkLabelButtonResource;
+ * FltkResource -> FltkEntryResource;
+ * Resource -> FltkResource;
+ * LabelButtonResource -> FltkLabelButtonResource;
+ * EntryResource -> FltkEntryResource;
+ * }
+ * \enddot
+ *
+ * <center>[\ref uml-legend "legend"]</center>
+ *
+ * where dw::fltk::ui::FltkResource provides some base funtionality for all
+ * conctrete FLTK implementations of sub-interfaces of dw::core::ui::Resource.
+ * However, this is not directly possible in C++, since the base class
+ * dw::core::ui::Resource is ambiguous for
+ * dw::fltk::ui::FltkLabelButtonResource.
+ *
+ * To solve this, we have to remove the depencency between
+ * dw::fltk::ui::FltkResource and dw::core::ui::Resource, instead, the part
+ * of dw::core::ui::Resource, which is implemented in
+ * dw::fltk::ui::FltkResource, must be explicitely delegated from
+ * dw::fltk::ui::FltkLabelButtonResourceto dw::fltk::ui::FltkResource:
+ *
+ * \dot
+ * digraph G {
+ * node [shape=record, fontname=Helvetica, fontsize=10];
+ * edge [arrowhead="none", arrowtail="empty", labelfontname=Helvetica,
+ * labelfontsize=10, color="#404040", labelfontcolor="#000080"];
+ * fontname=Helvetica; fontsize=10;
+ *
+ * subgraph cluster_core {
+ * style="dashed"; color="#000080"; fontname=Helvetica; fontsize=10;
+ * label="dw::core::ui";
+ *
+ * Resource [color="#a0a0a0", URL="\ref dw::core::ui::Resource"];
+ * LabelButtonResource [color="#a0a0a0",
+ * URL="\ref dw::core::ui::LabelButtonResource"];
+ * EntryResource [color="#a0a0a0",
+ * URL="\ref dw::core::ui::EntryResource"];
+ * }
+ *
+ * subgraph cluster_fltk {
+ * style="dashed"; color="#000080"; fontname=Helvetica; fontsize=10;
+ * label="dw::fltk::ui";
+ *
+ * FltkResource [color="#a0a0a0", URL="\ref dw::fltk::ui::FltkResource"];
+ * FltkLabelButtonResource
+ * [URL="\ref dw::fltk::ui::FltkLabelButtonResource"];
+ * FltkEntryResource [URL="\ref dw::fltk::ui::FltkEntryResource"];
+ * }
+ *
+ * Resource -> LabelButtonResource;
+ * Resource -> EntryResource;
+ * FltkResource -> FltkLabelButtonResource;
+ * FltkResource -> FltkEntryResource;
+ * LabelButtonResource -> FltkLabelButtonResource;
+ * EntryResource -> FltkEntryResource;
+ * }
+ * \enddot
+ *
+ * <center>[\ref uml-legend "legend"]</center>
+ *
+ * To make this a bit simpler, we use templates:
+ *
+ * \dot
+ * digraph G {
+ * node [shape=record, fontname=Helvetica, fontsize=10];
+ * edge [arrowhead="none", arrowtail="empty", labelfontname=Helvetica,
+ * labelfontsize=10, color="#404040", labelfontcolor="#000080"];
+ * fontname=Helvetica; fontsize=10;
+ *
+ * subgraph cluster_core {
+ * style="dashed"; color="#000080"; fontname=Helvetica; fontsize=10;
+ * label="dw::core::ui";
+ *
+ * Resource [color="#a0a0a0", URL="\ref dw::core::ui::Resource"];
+ * LabelButtonResource [color="#a0a0a0",
+ * URL="\ref dw::core::ui::LabelButtonResource"];
+ * EntryResource [color="#a0a0a0",
+ * URL="\ref dw::core::ui::EntryResource"];
+ * }
+ *
+ * subgraph cluster_fltk {
+ * style="dashed"; color="#000080"; fontname=Helvetica; fontsize=10;
+ * label="dw::fltk::ui";
+ *
+ * FltkResource [color="#a0a0a0", URL="\ref dw::fltk::ui::FltkResource"];
+ * FltkSpecificResource [color="#a0a0a0",
+ * fillcolor="#ffffc0", style="filled"
+ * URL="\ref dw::fltk::ui::FltkSpecificResource"];
+ * FltkSpecificResource_button [color="#a0a0a0",
+ * label="FltkSpecificResource \<LabelButtonResource\>"];
+ * FltkSpecificResource_entry [color="#a0a0a0",
+ * label="FltkSpecificResource \<EntryResource\>"];
+ * FltkEntryResource [URL="\ref dw::fltk::ui::FltkEntryResource"];
+ * FltkLabelButtonResource
+ * [URL="\ref dw::fltk::ui::FltkLabelButtonResource"];
+ * }
+ *
+ * Resource -> LabelButtonResource;
+ * Resource -> EntryResource;
+ * FltkResource -> FltkSpecificResource;
+ * FltkSpecificResource -> FltkSpecificResource_button [arrowhead="open",
+ * arrowtail="none",
+ * style="dashed",
+ * color="#808000"];
+ * FltkSpecificResource -> FltkSpecificResource_entry [arrowhead="open",
+ * arrowtail="none",
+ * style="dashed",
+ * color="#808000"];
+ * LabelButtonResource -> FltkSpecificResource_button;
+ * EntryResource -> FltkSpecificResource_entry;
+ * FltkSpecificResource_button -> FltkLabelButtonResource;
+ * FltkSpecificResource_entry -> FltkEntryResource;
+ * }
+ * \enddot
+ *
+ * <center>[\ref uml-legend "legend"]</center>
+ */
+namespace ui {
+
+/**
+ * ...
+ */
+class FltkResource: public object::Object
+{
+protected:
+ class ViewAndWidget: public object::Object
+ {
+ public:
+ FltkView *view;
+ ::fltk::Widget *widget;
+ };
+
+ container::typed::List <ViewAndWidget> *viewsAndWidgets;
+ core::Allocation allocation;
+ FltkPlatform *platform;
+
+ core::style::Style *style;
+
+ FltkResource (FltkPlatform *platform);
+ void init (FltkPlatform *platform);
+ virtual ::fltk::Widget *createNewWidget (core::Allocation *allocation) = 0;
+
+ void setWidgetStyle (::fltk::Widget *widget, core::style::Style *style);
+public:
+ ~FltkResource ();
+
+ virtual void attachView (FltkView *view);
+ virtual void detachView (FltkView *view);
+
+ void sizeAllocate (core::Allocation *allocation);
+ void draw (core::View *view, core::Rectangle *area);
+
+ void setStyle (core::style::Style *style);
+
+ bool isEnabled ();
+ void setEnabled (bool enabled);
+};
+
+
+template <class I> class FltkSpecificResource: public I, public FltkResource
+{
+public:
+ inline FltkSpecificResource (FltkPlatform *platform) :
+ FltkResource (platform) { }
+
+ void sizeAllocate (core::Allocation *allocation);
+ void draw (core::View *view, core::Rectangle *area);
+ void setStyle (core::style::Style *style);
+
+ bool isEnabled ();
+ void setEnabled (bool enabled);
+};
+
+
+class FltkLabelButtonResource:
+ public FltkSpecificResource <dw::core::ui::LabelButtonResource>
+{
+private:
+ const char *label;
+
+ static void widgetCallback (::fltk::Widget *widget, void *data);
+
+protected:
+ ::fltk::Widget *createNewWidget (core::Allocation *allocation);
+
+public:
+ FltkLabelButtonResource (FltkPlatform *platform, const char *label);
+ ~FltkLabelButtonResource ();
+
+ void sizeRequest (core::Requisition *requisition);
+
+ const char *getLabel ();
+ void setLabel (const char *label);
+};
+
+
+class FltkComplexButtonResource:
+ public FltkSpecificResource <dw::core::ui::ComplexButtonResource>
+{
+private:
+ bool relief;
+
+ static void widgetCallback (::fltk::Widget *widget, void *data);
+
+protected:
+ class ViewAndView: public object::Object
+ {
+ public:
+ FltkView *topView, *flatView;
+ };
+
+ FltkView *lastFlatView;
+
+ container::typed::List <ViewAndView> *viewsAndViews;
+
+ void attachView (FltkView *view);
+ void detachView (FltkView *view);
+
+ void sizeAllocate (core::Allocation *allocation);
+
+ dw::core::Platform *createPlatform ();
+ void setLayout (dw::core::Layout *layout);
+
+ int reliefXThickness ();
+ int reliefYThickness ();
+
+ ::fltk::Widget *createNewWidget (core::Allocation *allocation);
+
+public:
+ FltkComplexButtonResource (FltkPlatform *platform, dw::core::Widget *widget,
+ bool relief);
+ ~FltkComplexButtonResource ();
+};
+
+
+/**
+ * \bug Maximal length not supported yet.
+ * \todo Text values are not synchronized (not needed in dillo).
+ */
+class FltkEntryResource:
+ public FltkSpecificResource <dw::core::ui::EntryResource>
+{
+private:
+ int maxLength;
+ bool password;
+ const char *initText;
+ bool editable;
+
+ static void widgetCallback (::fltk::Widget *widget, void *data);
+
+protected:
+ ::fltk::Widget *createNewWidget (core::Allocation *allocation);
+
+public:
+ FltkEntryResource (FltkPlatform *platform, int maxLength, bool password);
+ ~FltkEntryResource ();
+
+ void sizeRequest (core::Requisition *requisition);
+
+ const char *getText ();
+ void setText (const char *text);
+ bool isEditable ();
+ void setEditable (bool editable);
+};
+
+
+class FltkMultiLineTextResource:
+ public FltkSpecificResource <dw::core::ui::MultiLineTextResource>
+{
+private:
+ ::fltk::TextBuffer *buffer;
+ bool editable;
+ int numCols, numRows;
+
+protected:
+ ::fltk::Widget *createNewWidget (core::Allocation *allocation);
+
+public:
+ FltkMultiLineTextResource (FltkPlatform *platform, int cols, int rows);
+ ~FltkMultiLineTextResource ();
+
+ void sizeRequest (core::Requisition *requisition);
+
+ const char *getText ();
+ void setText (const char *text);
+ bool isEditable ();
+ void setEditable (bool editable);
+};
+
+
+template <class I> class FltkToggleButtonResource:
+ public FltkSpecificResource <I>
+{
+private:
+ bool initActivated;
+
+protected:
+ virtual ::fltk::Button *createNewButton (core::Allocation *allocation) = 0;
+ ::fltk::Widget *createNewWidget (core::Allocation *allocation);
+
+public:
+ FltkToggleButtonResource (FltkPlatform *platform,
+ bool activated);
+ ~FltkToggleButtonResource ();
+
+ void sizeRequest (core::Requisition *requisition);
+
+ bool isActivated ();
+ void setActivated (bool activated);
+};
+
+
+class FltkCheckButtonResource:
+ public FltkToggleButtonResource <dw::core::ui::CheckButtonResource>
+{
+protected:
+ ::fltk::Button *createNewButton (core::Allocation *allocation);
+
+public:
+ FltkCheckButtonResource (FltkPlatform *platform,
+ bool activated);
+ ~FltkCheckButtonResource ();
+};
+
+
+class FltkRadioButtonResource:
+ public FltkToggleButtonResource <dw::core::ui::RadioButtonResource>
+{
+private:
+ class Group
+ {
+ private:
+ class FltkGroupIterator:
+ public dw::core::ui::RadioButtonResource::GroupIterator
+ {
+ private:
+ container::typed::Iterator <FltkRadioButtonResource> it;
+
+ public:
+ inline FltkGroupIterator (container::typed::List
+ <FltkRadioButtonResource>
+ *list)
+ { it = list->iterator (); }
+
+ bool hasNext ();
+ dw::core::ui::RadioButtonResource *getNext ();
+ void unref ();
+ };
+
+ container::typed::List <FltkRadioButtonResource> *list;
+
+ protected:
+ ~Group ();
+
+ public:
+ Group (FltkRadioButtonResource *radioButtonResource);
+
+ inline container::typed::Iterator <FltkRadioButtonResource> iterator ()
+ {
+ return list->iterator ();
+ }
+
+ inline dw::core::ui::RadioButtonResource::GroupIterator
+ *groupIterator ()
+ {
+ return new FltkGroupIterator (list);
+ }
+
+ void connect (FltkRadioButtonResource *radioButtonResource);
+ void unconnect (FltkRadioButtonResource *radioButtonResource);
+ };
+
+ Group *group;
+
+ static void widgetCallback (::fltk::Widget *widget, void *data);
+ void buttonClicked ();
+
+protected:
+ ::fltk::Button *createNewButton (core::Allocation *allocation);
+
+public:
+ FltkRadioButtonResource (FltkPlatform *platform,
+ FltkRadioButtonResource *groupedWith,
+ bool activated);
+ ~FltkRadioButtonResource ();
+
+ GroupIterator *groupIterator ();
+};
+
+
+template <class I> class FltkSelectionResource:
+ public FltkSpecificResource <I>
+{
+protected:
+ class Item: public object::Object
+ {
+ public:
+ enum Type { ITEM, START, END } type;
+
+ const char *name;
+ bool enabled, initSelected;
+
+ Item (Type type, const char *name = NULL, bool enabled = true,
+ bool selected = false);
+ ~Item ();
+
+ ::fltk::Item *createNewWidget (int index);
+ ::fltk::ItemGroup *createNewGroupWidget ();
+ };
+
+ class WidgetStack: public object::Object
+ {
+ public:
+ ::fltk::Menu *widget;
+ container::typed::Stack <object::TypedPointer < ::fltk::Menu> > *stack;
+
+ WidgetStack (::fltk::Menu *widget);
+ ~WidgetStack ();
+ };
+
+ container::typed::List <WidgetStack> *widgetStacks;
+ container::typed::List <Item> *allItems;
+ container::typed::Vector <Item> *items;
+
+ Item *createNewItem (typename Item::Type type,
+ const char *name = NULL,
+ bool enabled = true,
+ bool selected = false);
+
+ ::fltk::Widget *createNewWidget (core::Allocation *allocation);
+ virtual ::fltk::Menu *createNewMenu (core::Allocation *allocation) = 0;
+ virtual bool setSelectedItems() { return false; }
+
+ int getMaxStringWidth ();
+
+public:
+ FltkSelectionResource (FltkPlatform *platform);
+ ~FltkSelectionResource ();
+
+ dw::core::Iterator *iterator (dw::core::Content::Type mask, bool atEnd);
+
+ void addItem (const char *str, bool enabled, bool selected);
+
+ void pushGroup (const char *name, bool enabled);
+ void popGroup ();
+
+ int getNumberOfItems ();
+ const char *getItem (int index);
+};
+
+
+class FltkOptionMenuResource:
+ public FltkSelectionResource <dw::core::ui::OptionMenuResource>
+{
+protected:
+ ::fltk::Menu *createNewMenu (core::Allocation *allocation);
+ virtual bool setSelectedItems() { return true; }
+
+private:
+ static void widgetCallback (::fltk::Widget *widget, void *data);
+ int selection;
+
+public:
+ FltkOptionMenuResource (FltkPlatform *platform);
+ ~FltkOptionMenuResource ();
+
+ void addItem (const char *str, bool enabled, bool selected);
+
+ void sizeRequest (core::Requisition *requisition);
+ bool isSelected (int index);
+};
+
+class FltkListResource:
+ public FltkSelectionResource <dw::core::ui::ListResource>
+{
+protected:
+ ::fltk::Menu *createNewMenu (core::Allocation *allocation);
+
+private:
+ static void widgetCallback (::fltk::Widget *widget, void *data);
+ misc::SimpleVector <bool> itemsSelected;
+
+public:
+ FltkListResource (FltkPlatform *platform,
+ core::ui::ListResource::SelectionMode selectionMode);
+ ~FltkListResource ();
+
+ void addItem (const char *str, bool enabled, bool selected);
+
+ void sizeRequest (core::Requisition *requisition);
+ bool isSelected (int index);
+};
+
+
+} // namespace ui
+} // namespace fltk
+} // namespace dw
+
+
+#endif // __DW_FLTK_UI_HH__
diff --git a/dw/fltkviewbase.cc b/dw/fltkviewbase.cc
new file mode 100644
index 00000000..fbbd15bd
--- /dev/null
+++ b/dw/fltkviewbase.cc
@@ -0,0 +1,524 @@
+/*
+ * Dillo Widget
+ *
+ * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+
+
+#include "fltkviewport.hh"
+
+#include <fltk/draw.h>
+#include <fltk/damage.h>
+#include <fltk/layout.h>
+#include <fltk/events.h>
+#include <fltk/Cursor.h>
+#include <fltk/run.h>
+
+#include <stdio.h>
+
+using namespace fltk;
+using namespace lout::object;
+using namespace lout::container::typed;
+
+namespace dw {
+namespace fltk {
+
+::fltk::Image *FltkViewBase::backBuffer;
+bool FltkViewBase::backBufferInUse;
+
+FltkViewBase::FltkViewBase (int x, int y, int w, int h, const char *label):
+ Group (x, y, w, h, label)
+{
+ canvasWidth = 1;
+ canvasHeight = 1;
+ bgColor = WHITE;
+ lastDraw = time(0);
+ drawDelay = 2; /* in seconds */
+ mouse_x = mouse_y = 0;
+#ifndef NO_DOUBLEBUFFER
+ if (!backBuffer) {
+ backBuffer = new Image ();
+ }
+#endif
+}
+
+FltkViewBase::~FltkViewBase ()
+{
+ cancelQueueDraw ();
+}
+
+void FltkViewBase::draw ()
+{
+ int d = damage ();
+
+ if ((d & DAMAGE_VALUE) && !(d & DAMAGE_EXPOSE)) {
+ container::typed::Iterator <core::Rectangle> it;
+
+ for (it = drawRegion.rectangles (); it.hasNext (); ) {
+ drawRectangle (it.getNext (), true);
+ }
+
+ drawRegion.clear ();
+ d &= ~DAMAGE_VALUE;
+ }
+
+ if (d & DAMAGE_CHILD) {
+ drawChildWidgets ();
+ d &= ~DAMAGE_CHILD;
+ }
+
+ if (d) {
+ dw::core::Rectangle rect (
+ translateViewXToCanvasX (0),
+ translateViewYToCanvasY (0),
+ w (),
+ h ());
+
+ drawRectangle (&rect, false);
+
+ if (! (d & DAMAGE_SCROLL)) {
+ drawRegion.clear ();
+ }
+ }
+}
+
+void FltkViewBase::drawRectangle (const core::Rectangle *rect,
+ bool doubleBuffer)
+{
+ int offsetX = 0, offsetY = 0;
+
+ /* fltk-clipping does not use widget coordinates */
+ transform (offsetX, offsetY);
+
+ ::fltk::Rectangle viewRect (
+ translateCanvasXToViewX (rect->x) + offsetX,
+ translateCanvasYToViewY (rect->y) + offsetY,
+ rect->width, rect->height);
+
+ ::fltk::intersect_with_clip (viewRect);
+
+ viewRect.x (viewRect.x () - offsetX);
+ viewRect.y (viewRect.y () - offsetY);
+
+ if (! viewRect.empty ()) {
+ dw::core::Rectangle r (
+ translateViewXToCanvasX (viewRect.x ()),
+ translateViewYToCanvasY (viewRect.y ()),
+ viewRect.w (),
+ viewRect.h ());
+
+#ifdef NO_DOUBLEBUFFER
+ push_clip (viewRect);
+#endif
+
+ if (doubleBuffer && backBuffer && !backBufferInUse) {
+ backBufferInUse = true;
+ {
+ GSave gsave;
+
+ backBuffer->setsize (viewRect.w (), viewRect.h ());
+ backBuffer->make_current ();
+ translate (-viewRect.x (), -viewRect.y ());
+
+ setcolor (bgColor);
+ fillrect (viewRect);
+ theLayout->expose (this, &r);
+ }
+
+ backBuffer->draw (Rectangle (0, 0, viewRect.w (), viewRect.h ()),
+ viewRect);
+
+ backBufferInUse = false;
+ } else {
+ setcolor (bgColor);
+ fillrect (viewRect);
+ theLayout->expose (this, &r);
+ }
+
+#ifdef NO_DOUBLEBUFFER
+ pop_clip ();
+#endif
+ }
+}
+
+void FltkViewBase::drawChildWidgets () {
+ for (int i = children () - 1; i >= 0; i--) {
+ Widget& w = *child(i);
+ if (w.damage() & DAMAGE_CHILD_LABEL) {
+ draw_outside_label(w);
+ w.set_damage(w.damage() & ~DAMAGE_CHILD_LABEL);
+ }
+ update_child(w);
+ }
+}
+
+core::ButtonState getDwButtonState ()
+{
+ int s1 = event_state ();
+ int s2 = (core::ButtonState)0;
+
+ if(s1 & SHIFT) s2 |= core::SHIFT_MASK;
+ if(s1 & CTRL) s2 |= core::CONTROL_MASK;
+ if(s1 & ALT) s2 |= core::META_MASK;
+ if(s1 & BUTTON1) s2 |= core::BUTTON1_MASK;
+ if(s1 & BUTTON2) s2 |= core::BUTTON2_MASK;
+ if(s1 & BUTTON3) s2 |= core::BUTTON3_MASK;
+
+ return (core::ButtonState)s2;
+}
+
+int FltkViewBase::handle (int event)
+{
+ bool processed;
+
+ /**
+ * \todo Consider, whether this from the FLTK documentation has any
+ * impacts: "To receive fltk::RELEASE events you must return non-zero
+ * when passed a fltk::PUSH event. "
+ */
+ switch(event) {
+ case PUSH:
+ processed =
+ theLayout->buttonPress (this, event_clicks () + 1,
+ translateViewXToCanvasX (event_x ()),
+ translateViewYToCanvasY (event_y ()),
+ getDwButtonState (), event_button ());
+ //printf ("PUSH => %s\n", processed ? "true" : "false");
+ return processed ? true : Group::handle (event);
+
+ case RELEASE:
+ processed =
+ theLayout->buttonRelease (this, event_clicks () + 1,
+ translateViewXToCanvasX (event_x ()),
+ translateViewYToCanvasY (event_y ()),
+ getDwButtonState (), event_button ());
+ //printf ("RELEASE => %s\n", processed ? "true" : "false");
+ return processed ? true : Group::handle (event);
+
+ case MOVE:
+ mouse_x = event_x();
+ mouse_y = event_y();
+ processed =
+ theLayout->motionNotify (this,
+ translateViewXToCanvasX (mouse_x),
+ translateViewYToCanvasY (mouse_y),
+ getDwButtonState ());
+ //printf ("MOVE => %s\n", processed ? "true" : "false");
+ return processed ? true : Group::handle (event);
+
+ case DRAG:
+ processed =
+ theLayout->motionNotify (this,
+ translateViewXToCanvasX (event_x ()),
+ translateViewYToCanvasY (event_y ()),
+ getDwButtonState ());
+ //printf ("DRAG => %s\n", processed ? "true" : "false");
+ return processed ? true : Group::handle (event);
+
+ case ENTER:
+ theLayout->enterNotify (this, translateViewXToCanvasX (event_x ()),
+ translateViewYToCanvasY (event_y ()),
+ getDwButtonState ());
+ return Group::handle (event);
+
+ case LEAVE:
+ theLayout->leaveNotify (this, getDwButtonState ());
+ return Group::handle (event);
+
+ default:
+ return Group::handle (event);
+ }
+}
+
+// ----------------------------------------------------------------------
+
+void FltkViewBase::setLayout (core::Layout *layout)
+{
+ theLayout = layout;
+}
+
+void FltkViewBase::setCanvasSize (int width, int ascent, int descent)
+{
+ canvasWidth = width;
+ canvasHeight = ascent + descent;
+}
+
+void FltkViewBase::setCursor (core::style::Cursor cursor)
+{
+ static Cursor *mapDwToFltk[] = {
+ CURSOR_CROSS,
+ CURSOR_ARROW,
+ CURSOR_HAND,
+ CURSOR_MOVE,
+ CURSOR_WE,
+ CURSOR_NESW,
+ CURSOR_NWSE,
+ CURSOR_NS,
+ CURSOR_NWSE,
+ CURSOR_NESW,
+ CURSOR_NS,
+ CURSOR_WE,
+ CURSOR_INSERT,
+ CURSOR_WAIT,
+ CURSOR_HELP
+ };
+
+ /*
+ static char *cursorName[] = {
+ "CURSOR_CROSS",
+ "CURSOR_ARROW",
+ "CURSOR_HAND",
+ "CURSOR_MOVE",
+ "CURSOR_WE",
+ "CURSOR_NESW",
+ "CURSOR_NWSE",
+ "CURSOR_NS",
+ "CURSOR_NWSE",
+ "CURSOR_NESW",
+ "CURSOR_NS",
+ "CURSOR_WE",
+ "CURSOR_INSERT",
+ "CURSOR_WAIT",
+ "CURSOR_HELP"
+ };
+
+ printf ("Cursor changes to '%s'.\n", cursorName[cursor]);
+ */
+
+ /** \bug Does not work */
+ this->cursor (mapDwToFltk[cursor]);
+}
+
+void FltkViewBase::setBgColor (core::style::Color *color)
+{
+ bgColor = color ?
+ ((FltkColor*)color)->colors[dw::core::style::Color::SHADING_NORMAL] :
+ WHITE;
+}
+
+void FltkViewBase::startDrawing (core::Rectangle *area)
+{
+}
+
+void FltkViewBase::finishDrawing (core::Rectangle *area)
+{
+}
+
+void FltkViewBase::queueDraw (core::Rectangle *area)
+{
+ drawRegion.addRectangle (area);
+ /** DAMAGE_VALUE is just an arbitrary value other than DAMAGE_EXPOSE here */
+ redraw (DAMAGE_VALUE);
+}
+
+static void drawTotalTimeout (void *data)
+{
+ FltkViewBase *view = (FltkViewBase*) data;
+ if (time(0) >= view->lastDraw + view->drawDelay) {
+ view->drawTotal ();
+ } else {
+ ::fltk::add_timeout (0.2f, drawTotalTimeout, data);
+ }
+}
+
+void FltkViewBase::drawTotal ()
+{
+ //static int calls = 0;
+ //printf(" FltkViewBase::drawTotal calls = %d\n", ++calls);
+ redraw (DAMAGE_EXPOSE);
+ lastDraw = time (0);
+ cancelQueueDraw ();
+}
+
+void FltkViewBase::queueDrawTotal ()
+{
+ drawTotal ();
+}
+
+void FltkViewBase::cancelQueueDraw ()
+{
+ ::fltk::remove_timeout (drawTotalTimeout, this);
+}
+
+void FltkViewBase::drawPoint (core::style::Color *color,
+ core::style::Color::Shading shading,
+ int x, int y)
+{
+}
+
+void FltkViewBase::drawLine (core::style::Color *color,
+ core::style::Color::Shading shading,
+ int x1, int y1, int x2, int y2)
+{
+ setcolor(((FltkColor*)color)->colors[shading]);
+ drawline (translateCanvasXToViewX (x1), translateCanvasYToViewY (y1),
+ translateCanvasXToViewX (x2), translateCanvasYToViewY (y2));
+}
+
+void FltkViewBase::drawRectangle (core::style::Color *color,
+ core::style::Color::Shading shading,
+ bool filled,
+ int x, int y, int width, int height)
+{
+ setcolor(((FltkColor*)color)->colors[shading]);
+ int x1 = translateCanvasXToViewX (x);
+ int y1 = translateCanvasYToViewY (y);
+ int x2 = translateCanvasXToViewX (x + width);
+ int y2 = translateCanvasYToViewY (y + height);
+ ::fltk::Rectangle rect (x1, y1, x2 - x1, y2 - y1);
+ if (filled)
+ fillrect (rect);
+ else
+ strokerect (rect);
+}
+
+void FltkViewBase::drawArc (core::style::Color *color,
+ core::style::Color::Shading shading, bool filled,
+ int x, int y, int width, int height,
+ int angle1, int angle2)
+{
+ setcolor(((FltkColor*)color)->colors[shading]);
+ int x1 = translateCanvasXToViewX (x);
+ int y1 = translateCanvasYToViewY (y);
+ ::fltk::Rectangle rect (x1, y1, width, height);
+ addchord(rect, angle1, angle2);
+ closepath();
+ if (filled)
+ fillpath();
+ else
+ strokepath();
+}
+
+void FltkViewBase::drawPolygon (core::style::Color *color,
+ core::style::Color::Shading shading,
+ bool filled, int points[][2], int npoints)
+{
+ if (npoints > 0) {
+ for (int i = 0; i < npoints; i++) {
+ points[i][0] = translateCanvasXToViewX(points[i][0]);
+ points[i][1] = translateCanvasYToViewY(points[i][1]);
+ }
+ setcolor(((FltkColor*)color)->colors[shading]);
+ addvertices(npoints, points);
+ closepath();
+ if (filled)
+ fillpath();
+ else
+ strokepath();
+ }
+}
+
+core::View *FltkViewBase::getClippingView (int x, int y, int width, int height)
+{
+ push_clip (translateCanvasXToViewX (x), translateCanvasYToViewY (y),
+ width, height);
+ return this;
+}
+
+void FltkViewBase::mergeClippingView (core::View *clippingView)
+{
+ pop_clip ();
+}
+
+// ----------------------------------------------------------------------
+
+FltkWidgetView::FltkWidgetView (int x, int y, int w, int h,
+ const char *label):
+ FltkViewBase (x, y, w, h, label)
+{
+}
+
+FltkWidgetView::~FltkWidgetView ()
+{
+}
+
+void FltkWidgetView::layout () {
+ /**
+ * pass layout to child widgets. This is needed for complex fltk
+ * widgets as TextEditor.
+ * We can't use Group::layout() as that would rearrange the widgets.
+ */
+ for (int i = children () - 1; i >= 0; i--) {
+ ::fltk::Widget *widget = child (i);
+
+ if (widget->layout_damage ()) {
+ widget->layout ();
+ }
+ }
+}
+
+void FltkWidgetView::drawText (core::style::Font *font,
+ core::style::Color *color,
+ core::style::Color::Shading shading,
+ int x, int y, const char *text, int len)
+{
+ FltkFont *ff = (FltkFont*)font;
+ setfont(ff->font, ff->size);
+ setcolor(((FltkColor*)color)->colors[shading]);
+ drawtext(text, len,
+ translateCanvasXToViewX (x), translateCanvasYToViewY (y));
+}
+
+void FltkWidgetView::drawImage (core::Imgbuf *imgbuf, int xRoot, int yRoot,
+ int x, int y, int width, int height)
+{
+ ((FltkImgbuf*)imgbuf)->draw (this,
+ translateCanvasXToViewX (xRoot),
+ translateCanvasYToViewY (yRoot),
+ x, y, width, height);
+}
+
+bool FltkWidgetView::usesFltkWidgets ()
+{
+ return true;
+}
+
+void FltkWidgetView::addFltkWidget (::fltk::Widget *widget,
+ core::Allocation *allocation)
+{
+ allocateFltkWidget (widget, allocation);
+ add (widget);
+}
+
+void FltkWidgetView::removeFltkWidget (::fltk::Widget *widget)
+{
+ remove (widget);
+}
+
+void FltkWidgetView::allocateFltkWidget (::fltk::Widget *widget,
+ core::Allocation *allocation)
+{
+ widget->x (translateCanvasXToViewX (allocation->x));
+ widget->y (translateCanvasYToViewY (allocation->y));
+ widget->w (allocation->width);
+ widget->h (allocation->ascent + allocation->descent);
+
+ /* widgets created tiny and later resized need this flag to display */
+ uchar damage = widget->layout_damage ();
+ damage |= LAYOUT_XYWH;
+ widget->layout_damage (damage);
+}
+
+void FltkWidgetView::drawFltkWidget (::fltk::Widget *widget,
+ core::Rectangle *area)
+{
+ draw_child (*widget);
+}
+
+} // namespace fltk
+} // namespace dw
diff --git a/dw/fltkviewbase.hh b/dw/fltkviewbase.hh
new file mode 100644
index 00000000..09bcce39
--- /dev/null
+++ b/dw/fltkviewbase.hh
@@ -0,0 +1,108 @@
+#ifndef __DW_FLTKVIEWBASE_HH__
+#define __DW_FLTKVIEWBASE_HH__
+
+#include <time.h> // for time_t
+#include <sys/time.h> // for time_t in FreeBSD
+
+#include <fltk/Group.h>
+#include <fltk/Image.h>
+#include <fltk/Scrollbar.h>
+
+#include "fltkcore.hh"
+
+namespace dw {
+namespace fltk {
+
+class FltkViewBase: public FltkView, public ::fltk::Group
+{
+private:
+ int bgColor;
+ core::Region drawRegion;
+ static ::fltk::Image *backBuffer;
+ static bool backBufferInUse;
+
+ void drawRectangle (const core::Rectangle *rect, bool doubleBuffer);
+ void drawChildWidgets ();
+
+public:
+ time_t lastDraw;
+ time_t drawDelay;
+
+protected:
+ core::Layout *theLayout;
+ int canvasWidth, canvasHeight;
+ int mouse_x, mouse_y;
+
+ virtual int translateViewXToCanvasX (int x) = 0;
+ virtual int translateViewYToCanvasY (int y) = 0;
+ virtual int translateCanvasXToViewX (int x) = 0;
+ virtual int translateCanvasYToViewY (int y) = 0;
+
+public:
+ FltkViewBase (int x, int y, int w, int h, const char *label = 0);
+ ~FltkViewBase ();
+
+ void draw();
+ int handle (int event);
+
+ void setLayout (core::Layout *layout);
+ void setCanvasSize (int width, int ascent, int descent);
+ void setCursor (core::style::Cursor cursor);
+ void setBgColor (core::style::Color *color);
+
+ void startDrawing (core::Rectangle *area);
+ void finishDrawing (core::Rectangle *area);
+ void queueDraw (core::Rectangle *area);
+ void queueDrawTotal ();
+ void drawTotal ();
+ void cancelQueueDraw ();
+ void drawPoint (core::style::Color *color,
+ core::style::Color::Shading shading,
+ int x, int y);
+ void drawLine (core::style::Color *color,
+ core::style::Color::Shading shading,
+ int x1, int y1, int x2, int y2);
+ void drawRectangle (core::style::Color *color,
+ core::style::Color::Shading shading, bool filled,
+ int x, int y, int width, int height);
+ void drawArc (core::style::Color *color,
+ core::style::Color::Shading shading, bool filled,
+ int x, int y, int width, int height,
+ int angle1, int angle2);
+ void drawPolygon (core::style::Color *color,
+ core::style::Color::Shading shading,
+ bool filled, int points[][2], int npoints);
+
+ core::View *getClippingView (int x, int y, int width, int height);
+ void mergeClippingView (core::View *clippingView);
+};
+
+
+class FltkWidgetView: public FltkViewBase
+{
+public:
+ FltkWidgetView (int x, int y, int w, int h, const char *label = 0);
+ ~FltkWidgetView ();
+
+ void layout();
+
+ void drawText (core::style::Font *font,
+ core::style::Color *color,
+ core::style::Color::Shading shading,
+ int x, int y, const char *text, int len);
+ void drawImage (core::Imgbuf *imgbuf, int xRoot, int yRoot,
+ int x, int y, int width, int height);
+
+ bool usesFltkWidgets ();
+ void addFltkWidget (::fltk::Widget *widget, core::Allocation *allocation);
+ void removeFltkWidget (::fltk::Widget *widget);
+ void allocateFltkWidget (::fltk::Widget *widget,
+ core::Allocation *allocation);
+ void drawFltkWidget (::fltk::Widget *widget, core::Rectangle *area);
+};
+
+} // namespace fltk
+} // namespace dw
+
+#endif // __DW_FLTKVIEWBASE_HH__
+
diff --git a/dw/fltkviewport.cc b/dw/fltkviewport.cc
new file mode 100644
index 00000000..c999745a
--- /dev/null
+++ b/dw/fltkviewport.cc
@@ -0,0 +1,496 @@
+/*
+ * Dillo Widget
+ *
+ * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+
+
+#include "fltkviewport.hh"
+
+#include <fltk/draw.h>
+#include <fltk/damage.h>
+#include <fltk/events.h>
+
+#include <stdio.h>
+
+using namespace fltk;
+using namespace lout::object;
+using namespace lout::container::typed;
+
+namespace dw {
+namespace fltk {
+
+FltkViewport::FltkViewport (int x, int y, int w, int h, const char *label):
+ FltkWidgetView (x, y, w, h, label)
+{
+ hscrollbar = new Scrollbar (0, 0, 1, 1);
+ hscrollbar->set_horizontal();
+ hscrollbar->callback (hscrollbarCallback, this);
+ add (hscrollbar);
+
+ vscrollbar = new Scrollbar (0, 0, 1, 1);
+ vscrollbar->set_vertical();
+ vscrollbar->callback (vscrollbarCallback, this);
+ add (vscrollbar);
+
+ scrollX = scrollY = scrollDX = scrollDY = 0;
+ dragScrolling = 0;
+
+ gadgetOrientation[0] = GADGET_HORIZONTAL;
+ gadgetOrientation[1] = GADGET_HORIZONTAL;
+ gadgetOrientation[2] = GADGET_VERTICAL;
+ gadgetOrientation[3] = GADGET_HORIZONTAL;
+
+ gadgets =
+ new container::typed::List <object::TypedPointer < ::fltk::Widget> >
+ (true);
+}
+
+FltkViewport::~FltkViewport ()
+{
+ delete gadgets;
+}
+
+void FltkViewport::adjustScrollbarsAndGadgetsAllocation ()
+{
+ int hdiff = 0, vdiff = 0;
+ int visibility = 0;
+
+ if (hscrollbar->visible ())
+ visibility |= 1;
+ if (vscrollbar->visible ())
+ visibility |= 2;
+
+ if (gadgets->size () > 0) {
+ switch (gadgetOrientation [visibility]) {
+ case GADGET_VERTICAL:
+ hdiff = SCROLLBAR_THICKNESS;
+ vdiff = SCROLLBAR_THICKNESS * gadgets->size ();
+ break;
+
+ case GADGET_HORIZONTAL:
+ hdiff = SCROLLBAR_THICKNESS * gadgets->size ();
+ vdiff = SCROLLBAR_THICKNESS;
+ break;
+ }
+ } else {
+ hdiff = vscrollbar->visible () ? SCROLLBAR_THICKNESS : 0;
+ vdiff = hscrollbar->visible () ? SCROLLBAR_THICKNESS : 0;
+ }
+
+ hscrollbar->x (0);
+ hscrollbar->y (0 + h () - SCROLLBAR_THICKNESS);
+ hscrollbar->w (w () - hdiff);
+ hscrollbar->h (SCROLLBAR_THICKNESS);
+
+ vscrollbar->x (0 + w () - SCROLLBAR_THICKNESS);
+ vscrollbar->y (0);
+ vscrollbar->h (h () - vdiff);
+ vscrollbar->w (SCROLLBAR_THICKNESS);
+
+ int x = w () - SCROLLBAR_THICKNESS, y = h () - SCROLLBAR_THICKNESS;
+ for(Iterator <TypedPointer < ::fltk::Widget> > it = gadgets->iterator ();
+ it.hasNext (); ) {
+ ::fltk::Widget *widget = it.getNext()->getTypedValue ();
+ widget->x (0);
+ widget->y (0);
+ widget->w (SCROLLBAR_THICKNESS);
+ widget->h (SCROLLBAR_THICKNESS);
+
+ switch (gadgetOrientation [visibility]) {
+ case GADGET_VERTICAL:
+ y -= SCROLLBAR_THICKNESS;
+ break;
+
+ case GADGET_HORIZONTAL:
+ x -= SCROLLBAR_THICKNESS;
+ break;
+ }
+ }
+}
+
+void FltkViewport::adjustScrollbarValues ()
+{
+ hscrollbar->value (scrollX, hscrollbar->w (), 0, canvasWidth);
+ vscrollbar->value (scrollY, vscrollbar->h (), 0, canvasHeight);
+}
+
+void FltkViewport::hscrollbarChanged ()
+{
+ scroll (hscrollbar->value () - scrollX, 0);
+}
+
+void FltkViewport::vscrollbarChanged ()
+{
+ scroll (0, vscrollbar->value () - scrollY);
+}
+
+void FltkViewport::vscrollbarCallback (Widget *vscrollbar, void *viewportPtr)
+{
+ ((FltkViewport*)viewportPtr)->vscrollbarChanged ();
+}
+
+void FltkViewport::hscrollbarCallback (Widget *hscrollbar, void *viewportPtr)
+{
+ ((FltkViewport*)viewportPtr)->hscrollbarChanged ();
+}
+
+// ----------------------------------------------------------------------
+
+void FltkViewport::layout ()
+{
+ theLayout->viewportSizeChanged (this, w(), h());
+ adjustScrollbarsAndGadgetsAllocation ();
+
+ FltkWidgetView::layout ();
+}
+
+void FltkViewport::draw_area (void *data, const Rectangle& cr )
+{
+ FltkViewport *vp = (FltkViewport*) data;
+ push_clip(cr);
+
+ vp->FltkWidgetView::draw ();
+
+ for(Iterator <TypedPointer < ::fltk::Widget> > it = vp->gadgets->iterator ();
+ it.hasNext (); ) {
+ ::fltk::Widget *widget = it.getNext()->getTypedValue ();
+ vp->draw_child (*widget);
+ }
+
+ pop_clip();
+
+}
+
+void FltkViewport::draw ()
+{
+ int hdiff = vscrollbar->visible () ? SCROLLBAR_THICKNESS : 0;
+ int vdiff = hscrollbar->visible () ? SCROLLBAR_THICKNESS : 0;
+ Rectangle cr (0, 0, w () - hdiff, h () - vdiff);
+ int d = damage();
+
+ if (d & DAMAGE_SCROLL) {
+ set_damage (DAMAGE_SCROLL);
+ scrollrect(cr, -scrollDX, -scrollDY, draw_area, this);
+ d &= ~DAMAGE_SCROLL;
+ set_damage (d);
+ }
+
+ if (d) {
+ draw_area(this, cr);
+
+ if (d == DAMAGE_CHILD) {
+ if (hscrollbar->damage ())
+ draw_child (*hscrollbar);
+ if (vscrollbar->damage ())
+ draw_child (*vscrollbar);
+ } else {
+ draw_child (*hscrollbar);
+ draw_child (*vscrollbar);
+ }
+ }
+
+ scrollDX = 0;
+ scrollDY = 0;
+}
+
+int FltkViewport::handle (int event)
+{
+ //printf("FltkViewport::handle %d\n", event);
+
+ if (hscrollbar->Rectangle::contains (event_x (), event_y ()) &&
+ !(event_state() & (SHIFT | CTRL | ALT)) &&
+ hscrollbar->send (event)) {
+ return 1;
+ }
+
+ if (vscrollbar->Rectangle::contains (event_x (), event_y ()) &&
+ vscrollbar->send (event)) {
+ return 1;
+ }
+
+ switch(event) {
+ case ::fltk::FOCUS:
+ /** \bug Draw focus box. */
+ return 1;
+
+ case ::fltk::UNFOCUS:
+ /** \bug Undraw focus box. */
+ return 1;
+
+ case ::fltk::PUSH:
+ take_focus();
+ if (::fltk::event_button() == ::fltk::MiddleButton) {
+ /* pass event so that middle click can open link in new window */
+ if (FltkWidgetView::handle (event) == 0) {
+ dragScrolling = 1;
+ dragX = ::fltk::event_x();
+ dragY = ::fltk::event_y();
+ setCursor (core::style::CURSOR_MOVE);
+ }
+ return 1;
+ }
+ break;
+
+ case ::fltk::DRAG:
+ if (::fltk::event_button() == ::fltk::MiddleButton) {
+ if (dragScrolling) {
+ scroll(dragX - ::fltk::event_x(), dragY - ::fltk::event_y());
+ dragX = ::fltk::event_x();
+ dragY = ::fltk::event_y();
+ return 1;
+ }
+ }
+ break;
+
+ case ::fltk:: MOUSEWHEEL:
+ return (event_dx() ? hscrollbar : vscrollbar)->handle(event);
+ break;
+
+ case ::fltk::RELEASE:
+ if (::fltk::event_button() == ::fltk::MiddleButton) {
+ dragScrolling = 0;
+ setCursor (core::style::CURSOR_DEFAULT);
+ }
+ break;
+
+ case ::fltk::ENTER:
+ /* could be the result of, e.g., closing another window. */
+ mouse_x = ::fltk::event_x();
+ mouse_y = ::fltk::event_y();
+ positionChanged();
+ break;
+
+ case ::fltk::LEAVE:
+ mouse_x = mouse_y = -1;
+ break;
+
+ case ::fltk::KEY:
+ /* tell fltk we want to receive these KEY events as SHORTCUT */
+ switch (::fltk::event_key()) {
+ case PageUpKey:
+ case PageDownKey:
+ case SpaceKey:
+ case DownKey:
+ case UpKey:
+ case RightKey:
+ case LeftKey:
+ case HomeKey:
+ case EndKey:
+ return 0;
+ }
+ break;
+
+ case ::fltk::SHORTCUT:
+ switch (::fltk::event_key()) {
+ case PageUpKey:
+ case 'b':
+ case 'B':
+ scroll (0, -vscrollbar->pagesize ());
+ return 1;
+
+ case PageDownKey:
+ case SpaceKey:
+ scroll (0, vscrollbar->pagesize ());
+ return 1;
+
+ case DownKey:
+ scroll (0, (int) vscrollbar->linesize ());
+ return 1;
+
+ case UpKey:
+ scroll (0, (int) -vscrollbar->linesize ());
+ return 1;
+
+ case RightKey:
+ scroll ((int) hscrollbar->linesize (), 0);
+ return 1;
+
+ case LeftKey:
+ scroll ((int) -hscrollbar->linesize (), 0);
+ return 1;
+
+ case HomeKey:
+ scrollTo (scrollX, 0);
+ return 1;
+
+ case EndKey:
+ scrollTo (scrollX, canvasHeight); /* gets adjusted in scrollTo () */
+ return 1;
+ }
+ }
+
+ return FltkWidgetView::handle (event);
+}
+
+// ----------------------------------------------------------------------
+
+void FltkViewport::setCanvasSize (int width, int ascent, int descent)
+{
+ FltkWidgetView::setCanvasSize (width, ascent, descent);
+ adjustScrollbarValues ();
+}
+
+/*
+ * This is used to simulate mouse motion (e.g., when scrolling).
+ */
+void FltkViewport::positionChanged ()
+{
+ if (mouse_x != -1)
+ (void)theLayout->motionNotify (this,
+ translateViewXToCanvasX (mouse_x),
+ translateViewYToCanvasY (mouse_y),
+ (core::ButtonState)0);
+}
+
+/*
+ * For scrollbars, this currently sets the same step to both vertical and
+ * horizontal. It may me differentiated if necessary.
+ */
+void FltkViewport::setScrollStep(int step)
+{
+ vscrollbar->linesize(step);
+ hscrollbar->linesize(step);
+}
+
+bool FltkViewport::usesViewport ()
+{
+ return true;
+}
+
+int FltkViewport::getHScrollbarThickness ()
+{
+ return SCROLLBAR_THICKNESS;
+}
+
+int FltkViewport::getVScrollbarThickness ()
+{
+ return SCROLLBAR_THICKNESS;
+}
+
+void FltkViewport::scrollTo (int x, int y)
+{
+ int hdiff = vscrollbar->visible () ? SCROLLBAR_THICKNESS : 0;
+ int vdiff = hscrollbar->visible () ? SCROLLBAR_THICKNESS : 0;
+
+ x = misc::min (x, canvasWidth - w() + hdiff);
+ x = misc::max (x, 0);
+
+ y = misc::min (y, canvasHeight - h() + vdiff);
+ y = misc::max (y, 0);
+
+ if (x == scrollX && y == scrollY) {
+ return;
+ }
+
+ /* multiple calls to scroll can happen before a redraw occurs.
+ * scrollDX / scrollDY can therefore be non-zero here.
+ */
+ updateCanvasWidgets (x - scrollX, y - scrollY);
+ scrollDX += x - scrollX;
+ scrollDY += y - scrollY;
+
+ scrollX = x;
+ scrollY = y;
+
+ adjustScrollbarValues ();
+ redraw (DAMAGE_SCROLL);
+ theLayout->scrollPosChanged (this, scrollX, scrollY);
+ positionChanged();
+}
+
+void FltkViewport::scroll (int dx, int dy)
+{
+ scrollTo (scrollX + dx, scrollY + dy);
+}
+
+void FltkViewport::setViewportSize (int width, int height,
+ int hScrollbarThickness,
+ int vScrollbarThickness)
+{
+ if (hScrollbarThickness > 0)
+ hscrollbar->show ();
+ else
+ hscrollbar->hide ();
+ if (vScrollbarThickness > 0)
+ vscrollbar->show ();
+ else
+ vscrollbar->hide ();
+
+ /* If no scrollbar, go to the beginning */
+ scroll(hScrollbarThickness ? 0 : -scrollX,
+ vScrollbarThickness ? 0 : -scrollY);
+}
+
+void FltkViewport::updateCanvasWidgets (int dx, int dy)
+{
+ // scroll all child widgets except scroll bars
+ for (int i = children () - 1; i > 0; i--) {
+ ::fltk::Widget *widget = child (i);
+
+ if (widget == hscrollbar || widget == vscrollbar)
+ continue;
+
+ widget->x (widget->x () - dx);
+ widget->y (widget->y () - dy);
+ }
+}
+
+int FltkViewport::translateViewXToCanvasX (int x)
+{
+ return x + scrollX;
+}
+
+int FltkViewport::translateViewYToCanvasY (int y)
+{
+ return y + scrollY;
+}
+
+int FltkViewport::translateCanvasXToViewX (int x)
+{
+ return x - scrollX;
+}
+
+int FltkViewport::translateCanvasYToViewY (int y)
+{
+ return y - scrollY;
+}
+
+// ----------------------------------------------------------------------
+
+void FltkViewport::setGadgetOrientation (bool hscrollbarVisible,
+ bool vscrollbarVisible,
+ FltkViewport::GadgetOrientation
+ gadgetOrientation)
+{
+ this->gadgetOrientation[(hscrollbarVisible ? 0 : 1) |
+ (vscrollbarVisible ? 0 : 2)] = gadgetOrientation;
+ adjustScrollbarsAndGadgetsAllocation ();
+}
+
+void FltkViewport::addGadget (::fltk::Widget *gadget)
+{
+ /** \bug Reparent? */
+
+ gadgets->append (new TypedPointer < ::fltk::Widget> (gadget));
+ adjustScrollbarsAndGadgetsAllocation ();
+}
+
+
+} // namespace fltk
+} // namespace dw
diff --git a/dw/fltkviewport.hh b/dw/fltkviewport.hh
new file mode 100644
index 00000000..6af377d4
--- /dev/null
+++ b/dw/fltkviewport.hh
@@ -0,0 +1,77 @@
+#ifndef __DW_FLTKVIEWPORT_HH__
+#define __DW_FLTKVIEWPORT_HH__
+
+#include <fltk/Group.h>
+#include <fltk/Scrollbar.h>
+
+#include "core.hh"
+#include "fltkcore.hh"
+#include "fltkviewbase.hh"
+
+namespace dw {
+namespace fltk {
+
+class FltkViewport: public FltkWidgetView
+{
+public:
+ enum GadgetOrientation { GADGET_VERTICAL, GADGET_HORIZONTAL };
+
+private:
+ enum { SCROLLBAR_THICKNESS = 15 };
+
+ int scrollX, scrollY;
+ int scrollDX, scrollDY;
+ int dragScrolling, dragX, dragY;
+
+ ::fltk::Scrollbar *vscrollbar, *hscrollbar;
+
+ GadgetOrientation gadgetOrientation[4];
+ container::typed::List <object::TypedPointer < ::fltk::Widget> > *gadgets;
+
+ void adjustScrollbarsAndGadgetsAllocation ();
+ void adjustScrollbarValues ();
+ void hscrollbarChanged ();
+ void vscrollbarChanged ();
+ void positionChanged ();
+
+ static void hscrollbarCallback (Widget *hscrollbar, void *viewportPtr);
+ static void vscrollbarCallback (Widget *vscrollbar, void *viewportPtr);
+
+ void updateCanvasWidgets (int oldScrollX, int oldScrollY);
+ static void draw_area (void *data, const Rectangle& cr);
+
+protected:
+ int translateViewXToCanvasX (int x);
+ int translateViewYToCanvasY (int y);
+ int translateCanvasXToViewX (int x);
+ int translateCanvasYToViewY (int y);
+
+public:
+ FltkViewport (int x, int y, int w, int h, const char *label = 0);
+ ~FltkViewport ();
+
+ void layout();
+ void draw ();
+ int handle (int event);
+
+ void setCanvasSize (int width, int ascent, int descent);
+
+ bool usesViewport ();
+ int getHScrollbarThickness ();
+ int getVScrollbarThickness ();
+ void scroll(int dx, int dy);
+ void scrollTo (int x, int y);
+ void setViewportSize (int width, int height,
+ int hScrollbarThickness, int vScrollbarThickness);
+ void setScrollStep(int step);
+
+ void setGadgetOrientation (bool hscrollbarVisible, bool vscrollbarVisible,
+ GadgetOrientation gadgetOrientation);
+ void addGadget (::fltk::Widget *gadget);
+};
+
+} // namespace fltk
+} // namespace dw
+
+#endif // __DW_FLTKVIEWPORT_HH__
+
diff --git a/dw/image.cc b/dw/image.cc
new file mode 100644
index 00000000..499fc438
--- /dev/null
+++ b/dw/image.cc
@@ -0,0 +1,392 @@
+/*
+ * Dillo Widget
+ *
+ * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+
+
+#include "image.hh"
+#include "../lout/misc.hh"
+
+namespace dw {
+
+using namespace lout;
+
+ImageMapsList::ImageMap::ImageMap ()
+{
+ shapesAndLinks = new container::typed::List <ShapeAndLink> (true);
+ defaultLink = -1;
+}
+
+ImageMapsList::ImageMap::~ImageMap ()
+{
+ delete shapesAndLinks;
+}
+
+void ImageMapsList::ImageMap::add (core::Shape *shape, int link) {
+ ShapeAndLink *shapeAndLink = new ShapeAndLink ();
+ shapeAndLink->shape = shape;
+ shapeAndLink->link = link;
+ shapesAndLinks->append (shapeAndLink);
+}
+
+int ImageMapsList::ImageMap::link (int x, int y) {
+ container::typed::Iterator <ShapeAndLink> it;
+ int link = defaultLink;
+
+ for (it = shapesAndLinks->iterator (); it.hasNext (); ) {
+ ShapeAndLink *shapeAndLink = it.getNext ();
+
+ if (shapeAndLink->shape->isPointWithin (x, y)) {
+ link = shapeAndLink->link;
+ break;
+ }
+ }
+
+ return link;
+}
+
+ImageMapsList::ImageMapsList ()
+{
+ imageMaps = new container::typed::HashTable <object::Object, ImageMap>
+ (true, true);
+ currentMap = NULL;
+}
+
+ImageMapsList::~ImageMapsList ()
+{
+ delete imageMaps;
+}
+
+/**
+ * \brief Start a new map and make it the current one.
+ *
+ * This has to be called before dw::ImageMapsList::addShapeToCurrentMap.
+ * "key" is owned by the image map list, so a copy should be passed, when
+ * necessary.
+ */
+void ImageMapsList::startNewMap (object::Object *key)
+{
+ currentMap = new ImageMap ();
+ imageMaps->put (key, currentMap);
+}
+
+/**
+ * \brief Add a shape to the current map-
+ *
+ * "shape" is owned by the image map list, so a copy should be passed, when
+ * necessary.
+ */
+void ImageMapsList::addShapeToCurrentMap (core::Shape *shape, int link)
+{
+ currentMap->add (shape, link);
+}
+
+/**
+ * \brief Set default link for current map-
+ */
+void ImageMapsList::setCurrentMapDefaultLink (int link)
+{
+ currentMap->setDefaultLink (link);
+}
+
+int ImageMapsList::link (object::Object *key, int x, int y)
+{
+ int link = -1;
+ ImageMap *map = imageMaps->get (key);
+
+ if (map)
+ link = map->link (x, y);
+
+ return link;
+}
+
+// ----------------------------------------------------------------------
+
+int Image::CLASS_ID = -1;
+
+Image::Image(const char *altText)
+{
+ registerName ("dw::Image", &CLASS_ID);
+ this->altText = altText ? strdup (altText) : NULL;
+ altTextWidth = -1; // not yet calculated
+ buffer = NULL;
+ clicking = false;
+ currLink = -1;
+ mapList = NULL;
+ mapKey = NULL;
+ isMap = false;
+}
+
+Image::~Image()
+{
+ if (altText)
+ delete altText;
+ if (buffer)
+ buffer->unref ();
+}
+
+void Image::sizeRequestImpl (core::Requisition *requisition)
+{
+ if (buffer) {
+ requisition->width = buffer->getRootWidth ();
+ requisition->ascent = buffer->getRootHeight ();
+ requisition->descent = 0;
+ } else {
+ if(altText && altText[0]) {
+ if (altTextWidth == -1)
+ altTextWidth =
+ layout->textWidth (getStyle()->font, altText, strlen (altText));
+
+ requisition->width = altTextWidth;
+ requisition->ascent = getStyle()->font->ascent;
+ requisition->descent = getStyle()->font->descent;
+ } else {
+ requisition->width = 0;
+ requisition->ascent = 0;
+ requisition->descent = 0;
+ }
+ }
+
+ requisition->width += getStyle()->boxDiffWidth ();
+ requisition->ascent += getStyle()->boxOffsetY ();
+ requisition->descent += getStyle()->boxRestHeight ();
+}
+
+void Image::sizeAllocateImpl (core::Allocation *allocation)
+{
+ core::Imgbuf *oldBuffer;
+ int dx, dy;
+
+ /* if image is moved only */
+ if (allocation->width == this->allocation.width &&
+ allocation->ascent + allocation->descent == getHeight ())
+ return;
+
+ dx = getStyle()->boxDiffWidth ();
+ dy = getStyle()->boxDiffHeight ();
+#if 0
+ printf("boxDiffHeight = %d + %d, buffer=%p\n",
+ getStyle()->boxOffsetY(), getStyle()->boxRestHeight(), buffer);
+ printf("getContentWidth() = allocation.width - style->boxDiffWidth ()"
+ " = %d - %d = %d\n",
+ this->allocation.width, getStyle()->boxDiffWidth(),
+ this->allocation.width - getStyle()->boxDiffWidth());
+ printf("getContentHeight() = getHeight() - style->boxDiffHeight ()"
+ " = %d - %d = %d\n", this->getHeight(), getStyle()->boxDiffHeight(),
+ this->getHeight() - getStyle()->boxDiffHeight());
+#endif
+ if (buffer != NULL &&
+ /* It may be, that the image is allocated at zero content size. In this
+ * case, we simply wait. */
+ getContentWidth () > 0 && getContentHeight () > 0) {
+ oldBuffer = buffer;
+ buffer = oldBuffer->getScaledBuf (allocation->width - dx,
+ allocation->ascent
+ + allocation->descent - dy);
+ oldBuffer->unref ();
+ }
+}
+
+void Image::enterNotifyImpl (core::EventCrossing *event)
+{
+ // BUG: this is wrong for image maps, but the cursor position is unknown.
+ currLink = getStyle()->x_link;
+
+ if (currLink != -1) {
+ (void) emitLinkEnter (currLink, -1, -1, -1);
+ }
+}
+
+void Image::leaveNotifyImpl (core::EventCrossing *event)
+{
+ clicking = false;
+
+ if (currLink != -1) {
+ currLink = -1;
+ (void) emitLinkEnter (-1, -1, -1, -1);
+ }
+}
+
+bool Image::motionNotifyImpl (core::EventMotion *event)
+{
+ if (mapList) {
+ /* client-side image map */
+ int newLink = mapList->link (mapKey, event->xWidget, event->yWidget);
+ if (newLink != currLink) {
+ currLink = newLink;
+ clicking = false;
+ setCursor(newLink == -1 ? core::style::CURSOR_DEFAULT :
+ core::style::CURSOR_POINTER);
+ (void) emitLinkEnter (newLink, -1, -1, -1);
+ }
+ } else if (isMap && currLink != -1) {
+ /* server-side image map */
+ (void) emitLinkEnter (currLink, -1, event->xWidget, event->yWidget);
+ }
+ return true;
+}
+
+bool Image::buttonPressImpl (core::EventButton *event)
+{
+ bool ret = false;
+ currLink = mapList ? mapList->link (mapKey, event->xWidget, event->yWidget):
+ getStyle()->x_link;
+ if (event->button == 3){
+ (void)emitLinkPress(currLink, getStyle()->x_img, -1,-1,event);
+ ret = true;
+ } else if (event->button == 1 || currLink != -1){
+ clicking = true;
+ ret = true;
+ }
+ return ret;
+}
+
+bool Image::buttonReleaseImpl (core::EventButton *event)
+{
+ currLink = mapList ? mapList->link (mapKey, event->xWidget, event->yWidget):
+ getStyle()->x_link;
+ if (clicking) {
+ int x = isMap ? event->xWidget : -1;
+ int y = isMap ? event->yWidget : -1;
+ clicking = false;
+ emitLinkClick (currLink, getStyle()->x_img, x, y, event);
+ return true;
+ }
+ return false;
+}
+
+void Image::draw (core::View *view, core::Rectangle *area)
+{
+ int dx, dy;
+ core::Rectangle content, intersection;
+
+ drawWidgetBox (view, area, false);
+
+ if (buffer) {
+ dx = getStyle()->boxOffsetX ();
+ dy = getStyle()->boxOffsetY ();
+ content.x = dx;
+ content.y = dy;
+ content.width = getContentWidth ();
+ content.height = getContentHeight ();
+
+ if (area->intersectsWith (&content, &intersection))
+ view->drawImage (buffer,
+ allocation.x + dx, allocation.y + dy,
+ intersection.x - dx, intersection.y - dy,
+ intersection.width, intersection.height);
+ } else {
+ if(altText && altText[0]) {
+ if (altTextWidth == -1)
+ altTextWidth =
+ layout->textWidth (getStyle()->font, altText, strlen (altText));
+
+ core::View *clippingView = NULL, *usedView = view;
+ if (allocation.width < altTextWidth ||
+ allocation.ascent < getStyle()->font->ascent ||
+ allocation.descent < getStyle()->font->descent) {
+ clippingView = usedView =
+ view->getClippingView (allocation.x + getStyle()->boxOffsetX (),
+ allocation.y + getStyle()->boxOffsetY (),
+ allocation.width
+ - getStyle()->boxDiffWidth (),
+ allocation.ascent + allocation.descent
+ - getStyle()->boxDiffHeight ());
+ }
+
+ usedView->drawText (getStyle()->font, getStyle()->color,
+ core::style::Color::SHADING_NORMAL,
+ allocation.x + getStyle()->boxOffsetX (),
+ allocation.y + getStyle()->boxOffsetY ()
+ + getStyle()->font->ascent,
+ altText, strlen(altText));
+
+ if(clippingView)
+ view->mergeClippingView (clippingView);
+ }
+ }
+
+ /** todo: draw selection */
+}
+
+core::Iterator *Image::iterator (core::Content::Type mask, bool atEnd)
+{
+ //return new core::TextIterator (this, mask, atEnd, altText);
+ /** \bug Not implemented. */
+ return new core::EmptyIterator (this, mask, atEnd);
+}
+
+void Image::setBuffer (core::Imgbuf *buffer, bool resize)
+{
+ core::Imgbuf *oldBuf = this->buffer;
+
+ if (resize)
+ queueResize (0, true);
+
+ // If the image has not yet been allocated, or is allocated at zero
+ // content size, the first part is useless.
+ if (wasAllocated () && getContentWidth () > 0 && getContentHeight () > 0) {
+ this->buffer =
+ buffer->getScaledBuf (getContentWidth (), getContentHeight ());
+ } else {
+ this->buffer = buffer;
+ buffer->ref ();
+ }
+
+ if (oldBuf)
+ oldBuf->unref ();
+}
+
+void Image::drawRow (int row)
+{
+ core::Rectangle area;
+
+ assert (buffer != NULL);
+
+ buffer->getRowArea (row, &area);
+ if (area.width && area.height)
+ queueDrawArea (area.x + getStyle()->boxOffsetX (),
+ area.y + getStyle()->boxOffsetY (),
+ area.width, area.height);
+}
+
+
+/**
+ * \brief Sets image as server side image map.
+ */
+void Image::setIsMap ()
+{
+ isMap = true;
+}
+
+
+/**
+ * \brief Sets image as client side image map.
+ *
+ * "list" is not owned by the image, the caller has to free it. "key"
+ * is owned by the image, if it is used by the caller afterwards, a copy
+ * should be passed.
+ */
+void Image::setUseMap (ImageMapsList *list, object::Object *key)
+{
+ mapList = list;
+ mapKey = key;
+}
+
+} // namespace dw
diff --git a/dw/image.hh b/dw/image.hh
new file mode 100644
index 00000000..37c27e0d
--- /dev/null
+++ b/dw/image.hh
@@ -0,0 +1,161 @@
+#ifndef __DW_IMAGE_HH__
+#define __DW_IMAGE_HH__
+
+#include "core.hh"
+
+namespace dw {
+
+/**
+ * \brief Represents a list of client-side image maps.
+ *
+ * All image maps of a HTML page (in the future, also image maps from
+ * different HTML pages) are stored in a list, which is passed to the
+ * image, so that it is possible to deal with maps, which are defined
+ * after the image within the HTML page.
+ *
+ * Maps are referred by instances of object::Object. These keys are
+ * typically URLs, so the type representing URLS should be derived from
+ * object::Object.
+ *
+ * \todo Some methods within the key class have to be implemented, this
+ * is not clear at this time.
+ */
+class ImageMapsList
+{
+private:
+ class ImageMap: public lout::object::Object {
+ private:
+ class ShapeAndLink: public lout::object::Object {
+ public:
+ core::Shape *shape;
+ int link;
+
+ ~ShapeAndLink () { if (shape) delete shape; };
+ };
+
+ lout::container::typed::List <ShapeAndLink> *shapesAndLinks;
+ int defaultLink;
+ public:
+ ImageMap ();
+ ~ImageMap ();
+
+ void add (core::Shape *shape, int link);
+ void setDefaultLink (int link) { defaultLink = link; };
+ int link (int x, int y);
+ };
+
+ lout::container::typed::HashTable <lout::object::Object, ImageMap>
+ *imageMaps;
+ ImageMap *currentMap;
+
+public:
+ ImageMapsList ();
+ ~ImageMapsList ();
+
+ void startNewMap (lout::object::Object *key);
+ void addShapeToCurrentMap (core::Shape *shape, int link);
+ void setCurrentMapDefaultLink (int link);
+ int link (lout::object::Object *key, int x, int y);
+};
+
+/**
+ * \brief Displays an instance of dw::core::Imgbuf.
+ *
+ * The dw::core::Imgbuf is automatically scaled, when needed, but dw::Image
+ * does not keep a reference on the root buffer.
+ *
+ *
+ * <h3>Signals</h3>
+ *
+ * For image maps, dw::Image uses the signals defined in
+ * dw::core::Widget::LinkReceiver. For client side image maps, -1 is
+ * passed for the coordinates, for server side image maps, the respective
+ * coordinates are used. See section "Image Maps" below.
+ *
+ *
+ * <h3>%Image Maps</h3>
+ *
+ * <h4>Client Side %Image Maps</h4>
+ *
+ * You must first create a list of image maps (dw::ImageMapList), which can
+ * be used for multiple images. The caller is responsible for freeing the
+ * dw::ImageMapList.
+ *
+ * Adding a map is done by dw::ImageMapsList::startNewMap. The key is an
+ * instance of a sub class of object::Object. In the context of HTML, this is
+ * a URL, which defines this map globally, by combining the URL of the
+ * document, this map is defined in, with the value of the attribute "name" of
+ * the \<MAP\> element, as a fragment.
+ *
+ * dw::ImageMapsList::addShapeToCurrentMap adds a shape to the current
+ * map. The \em link argument is a number, which is later passed to
+ * the dw::core::Widget::LinkReceiver.
+ *
+ * This map list is then, together with the key for the image, passed to
+ * dw::Image::setUseMap. For HTML, a URL with the value of the "ismap"
+ * attribute of \<IMG\> should be used.
+ *
+ * dw::Image will search the correct map, when needed. If it is not found
+ * at this time, but later defined, it will be found and used later. This is
+ * the case, when an HTML \<MAP\> is defined below the \<IMG\> in the
+ * document.
+ *
+ * Currently, only maps defined in the same document as the image may be
+ * used, since the dw::ImageMapsList is stored in the HTML link block, and
+ * contains only the image maps defined in the document.
+ *
+ * <h4>Server Side %Image Maps</h4>
+ *
+ * To use images for server side image maps, you must call
+ * dw::Image::setIsMap, and the dw::Image::style must contain a valid link
+ * (dw::core::style::Style::x_link). After this, motions and clicks are
+ * delegated to dw::core::Widget::LinkReceiver.
+ *
+ * \sa\ref dw-images-and-backgrounds
+ */
+class Image: public core::Widget
+{
+private:
+ char *altText;
+ core::Imgbuf *buffer;
+ int altTextWidth;
+ bool clicking;
+ int currLink;
+ ImageMapsList *mapList;
+ Object *mapKey;
+ bool isMap;
+
+protected:
+ void sizeRequestImpl (core::Requisition *requisition);
+ void sizeAllocateImpl (core::Allocation *allocation);
+
+ void draw (core::View *view, core::Rectangle *area);
+
+ bool buttonPressImpl (core::EventButton *event);
+ bool buttonReleaseImpl (core::EventButton *event);
+ void enterNotifyImpl (core::EventCrossing *event);
+ void leaveNotifyImpl (core::EventCrossing *event);
+ bool motionNotifyImpl (core::EventMotion *event);
+
+ //core::Iterator *iterator (Content::Type mask, bool atEnd);
+
+public:
+ static int CLASS_ID;
+
+ Image(const char *altText);
+ ~Image();
+
+ core::Iterator *iterator (core::Content::Type mask, bool atEnd);
+
+ inline core::Imgbuf *getBuffer () { return buffer; }
+ void setBuffer (core::Imgbuf *buffer, bool resize = false);
+
+ void drawRow (int row);
+
+ void setIsMap ();
+ void setUseMap (ImageMapsList *list, Object *key);
+};
+
+} // namespace dw
+
+#endif // __DW_IMAGE_HH__
diff --git a/dw/imgbuf.hh b/dw/imgbuf.hh
new file mode 100644
index 00000000..8948bbef
--- /dev/null
+++ b/dw/imgbuf.hh
@@ -0,0 +1,210 @@
+#ifndef __DW_IMGBUF_HH__
+#define __DW_IMGBUF_HH__
+
+#ifndef __INCLUDED_FROM_DW_CORE_HH__
+# error Do not include this file directly, use "core.hh" instead.
+#endif
+
+namespace dw {
+namespace core {
+
+using namespace lout;
+
+/**
+ * \brief The platform independant interface for image buffers.
+ *
+ * %Image buffers depend on the platform (see \ref dw-images-and-backgrounds),
+ * but have this general, platform independant interface. The purpose of
+ * an image buffer is
+ *
+ * <ol>
+ * <li> storing the image data,
+ * <li> handling scaled versions of this buffer, and
+ * <li> drawing.
+ * </ol>
+ *
+ * The latter must be done independently from the window.
+ *
+ * <h3>Creating</h3>
+ *
+ * %Image buffers are created by calling dw::core::Platform::createImgbuf.
+ *
+ * <h3>Storing %Image Data</h3>
+ *
+ * dw::core::Imgbuf supports five image types, which are listed in the table
+ * below. The representation defines, how the colors are stored within
+ * the data, which is passed to dw::core::Imgbuf::copyRow.
+ *
+ * <table>
+ * <tr><th>Type (dw::core::Imgbuf::Type) <th>Bytes per
+ * Pixel <th>Representation
+ * <tr><td>dw::core::Imgbuf::RGB <td>3 <td>red, green, blue
+ * <tr><td>dw::core::Imgbuf::RGBA <td>4 <td>red, green, blue, alpha
+ * <tr><td>dw::core::Imgbuf::GRAY <td>1 <td>gray value
+ * <tr><td>dw::core::Imgbuf::INDEXED <td>1 <td>index to colormap
+ * <tr><td>dw::core::Imgbuf::INDEXED_ALPHA <td>1 <td>index to colormap
+ * </table>
+ *
+ * The last two types need a colormap, which is set by
+ * dw::core::Imgbuf::setCMap, which must be called before
+ * dw::core::Imgbuf::copyRow. This function expects the colors as 32 bit
+ * unsigned integers, which have the format 0xrrbbgg (for indexed
+ * images), or 0xaarrggbb (for indexed alpha), respectively.
+ *
+ *
+ * <h3>Scaling</h3>
+ *
+ * The buffer with the original size, which was created by
+ * dw::core::Platform::createImgbuf, is called root buffer. Imgbuf provides
+ * the ability to scale buffers. Generally, both root buffers, as well as
+ * scaled buffers, may be shared, memory management is done by reference
+ * counters.
+ *
+ * Via dw::core::Imgbuf::getScaledBuf, you can retrieve a scaled buffer.
+ * Generally, something like this must work always, in an efficient way:
+ *
+ * \code
+ * dw::core::Imgbuf *curBuf, *oldBuf;
+ * int width, heigt,
+ * // ...
+ * oldBuf = curBuf;
+ * curBuf = oldBuf->getScaledBuf(oldBuf, width, height);
+ * oldBuf->unref();
+ * \endcode
+ *
+ * \em oldBuf may both be a root buffer, or a scaled buffer.
+ *
+ * The root buffer keeps a list of all children, and all methods
+ * operating on the image data (dw::core::Imgbuf::copyRow and
+ * dw::core::Imgbuf::setCMap) are delegated to the scaled buffers, when
+ * processed, and inherited, when a new scaled buffer is created. This
+ * means, that they must only be performed for the root buffer.
+ *
+ * A possible implementation could be (dw::fltk::FltkImgbuf does it this way):
+ *
+ * <ul>
+ * <li> If the method is called with an already scaled image buffer, this is
+ * delegated to the root buffer.
+ *
+ * <li> If the given size is the original size, the root buffer is
+ * returned, with an increased reference counter.
+ *
+ * <li> Otherwise, if this buffer has already been scaled to the given
+ * size, return this scaled buffer, with an increased reference
+ * counter.
+ *
+ * <li> Otherwise, return a new scaled buffer with reference counter 1.
+ * </ul>
+ *
+ * Special care is to be taken, when the root buffer is not used anymore,
+ * i.e. after dw::core::Imgbuf::unref the reference counter is 0, but there
+ * are still scaled buffers. Since all methods operating on the image data
+ * (dw::core::Imgbuf::copyRow and dw::core::Imgbuf::setCMap) are called for
+ * the root buffer, the root buffer is still needed, and so must not be
+ * deleted at this point. This is, how dw::fltk::FltkImgbuf solves this
+ * problem:
+ *
+ * <ul>
+ * <li> dw::fltk::FltkImgbuf::unref does, for root buffers, check, not only
+ * whether dw::fltk::FltkImgbuf::refCount is 0, but also, whether
+ * there are children left. When the latter is the case, the buffer
+ * is not deleted.
+ *
+ * <li> There is an additional check in dw::fltk::FltkImgbuf::detachScaledBuf,
+ * which deals with the case, that dw::fltk::FltkImgbuf::refCount is 0,
+ * and the last scaled buffer is removed.
+ * </ul>
+ *
+ * In the following example:
+ *
+ * \code
+ * dw::fltk::FltkPlatform *platform = new dw::fltk::FltkPlatform ();
+ * dw::core::Layout *layout = new dw::core::Layout (platform);
+ *
+ * dw::core::Imgbuf *rootbuf =
+ * layout->createImgbuf (dw::core::Imgbuf::RGB, 100, 100);
+ * dw::core::Imgbuf *scaledbuf = rootbuf->getScaledBuf (50, 50);
+ * rootbuf->unref ();
+ * scaledbuf->unref ();
+ * \endcode
+ *
+ * the root buffer is not deleted, when dw::core::Imgbuf::unref is called,
+ * since a scaled buffer is left. After calling dw::core::Imgbuf::unref for
+ * the scaled buffer, it is deleted, and after it, the root buffer.
+ *
+ * <h3>Drawing</h3>
+ *
+ * dw::core::Imgbuf provides no methods for drawing, instead, this is
+ * done by the views (implementation of dw::core::View).
+ *
+ * There are two situations, when drawing is necessary:
+ *
+ * <ol>
+ * <li> To react on expose events, the function dw::core::View::drawImage
+ * should be used, with the following parameters:
+ * <ul>
+ * <li> of course, the image buffer,
+ * <li> where the root of the image would be displayed (as \em xRoot
+ * and \em yRoot), and
+ * <li> the region within the image, which should be displayed (\em x,
+ * \em y, \em width, \em height).
+ * </ul>
+ *
+ * <li> When a row has been copied, it has to be drawn. To determine the
+ * area, which has to be drawn, the dw::core::Imgbuf::getRowArea
+ * should be used. The result can then passed
+ * to dw::core::View::drawImage.
+ * </ol>
+ *
+ * \sa \ref dw-images-and-backgrounds
+ */
+class Imgbuf: public object::Object, public lout::signal::ObservedObject
+{
+public:
+ enum Type { RGB, RGBA, GRAY, INDEXED, INDEXED_ALPHA };
+
+ /*
+ * Methods called from the image decoding
+ */
+
+ virtual void setCMap (int *colors, int num_colors) = 0;
+ virtual void copyRow (int row, const byte *data) = 0;
+ virtual void newScan () = 0;
+
+ /*
+ * Methods called from dw::Image
+ */
+
+ virtual Imgbuf* getScaledBuf (int width, int height) = 0;
+ virtual void getRowArea (int row, dw::core::Rectangle *area) = 0;
+ virtual int getRootWidth () = 0;
+ virtual int getRootHeight () = 0;
+
+ /*
+ * Reference counting.
+ */
+
+ virtual void ref () = 0;
+ virtual void unref () = 0;
+
+ /**
+ * \todo Comment
+ */
+ virtual bool lastReference () = 0;
+
+
+ /**
+ * \todo Comment
+ */
+ virtual void setDeleteOnUnref (bool deleteOnUnref) = 0;
+
+ /**
+ * \todo Comment
+ */
+ virtual bool isReferred () = 0;
+};
+
+} // namespace dw
+} // namespace core
+
+#endif // __DW_IMGBUF_HH__
diff --git a/dw/iterator.cc b/dw/iterator.cc
new file mode 100644
index 00000000..39e09d41
--- /dev/null
+++ b/dw/iterator.cc
@@ -0,0 +1,797 @@
+/*
+ * Dillo Widget
+ *
+ * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+
+
+#include "core.hh"
+#include <limits.h>
+
+namespace dw {
+namespace core {
+
+// --------------
+// Iterator
+// --------------
+
+Iterator::Iterator(Widget *widget, Content::Type mask, bool atEnd)
+{
+ this->widget = widget;
+ this->mask = mask;
+}
+
+Iterator::Iterator(Iterator &it): object::Object (), misc::Comparable ()
+{
+ widget = it.widget;
+ content = it.content;
+}
+
+Iterator::~Iterator()
+{
+}
+
+bool Iterator::equals (Object *other)
+{
+ Iterator *otherIt = (Iterator*)other;
+ return
+ this == otherIt ||
+ (getWidget() == otherIt->getWidget() && compareTo(otherIt) == 0);
+}
+
+/**
+ * \brief Delete the iterator.
+ *
+ * The desctructor is hidden, implementations may use optimizations for
+ * the allocation. (Will soon be the case for dw::core::EmptyIteratorFactory.)
+ */
+void Iterator::unref ()
+{
+ delete this;
+}
+
+/**
+ * \brief Scrolls the viewport, so that the region between \em it1 and
+ * \em it2 is seen, according to \em hpos and \em vpos.
+ *
+ * The parameters \em start and \em end have the same meaning as in
+ * dw::core::Iterator::getAllocation, \em start refers
+ * to \em it1, while \em end rerers to \em it2.
+ *
+ * If \em it1 and \em it2 point to the same location (see code), only
+ * \em it1 is regarded, and both belowstart and belowend refer to it.
+ */
+void Iterator::scrollTo (Iterator *it1, Iterator *it2, int start, int end,
+ HPosition hpos, VPosition vpos)
+{
+ Allocation alloc1, alloc2, alloc;
+ int x1, x2, y1, y2;
+ DeepIterator *eit1, *eit2, *eit3;
+ int curStart, curEnd, cmp;
+ bool atStart;
+
+ if (it1->equals(it2)) {
+ it1->getAllocation (start, end, &alloc);
+ it1->getWidget()->getLayout()->scrollTo (hpos, vpos, alloc.x, alloc.y,
+ alloc.width,
+ alloc.ascent + alloc.descent);
+ } else {
+ // First, determine the rectangle all iterators from it1 and it2
+ // allocate, i.e. the smallest rectangle containing all allocations of
+ // these iterators.
+ eit1 = new DeepIterator (it1);
+ eit2 = new DeepIterator (it2);
+
+ x1 = INT_MAX;
+ x2 = INT_MIN;
+ y1 = INT_MAX;
+ y2 = INT_MIN;
+
+ for (eit3 = (DeepIterator*)eit1->clone (), atStart = true;
+ (cmp = eit3->compareTo (eit2)) <= 0;
+ eit3->next (), atStart = false) {
+ if (atStart)
+ curStart = start;
+ else
+ curStart = 0;
+
+ if (cmp == 0)
+ curEnd = end;
+ else
+ curEnd = INT_MAX;
+
+ eit3->getAllocation (curStart, curEnd, &alloc);
+ x1 = misc::min (x1, alloc.x);
+ x2 = misc::max (x2, alloc.x + alloc.width);
+ y1 = misc::min (y1, alloc.y);
+ y2 = misc::max (y2, alloc.y + alloc.ascent + alloc.descent);
+ }
+
+ delete eit3;
+ delete eit2;
+ delete eit1;
+
+ it1->getAllocation (start, INT_MAX, &alloc1);
+ it2->getAllocation (0, end, &alloc2);
+
+ if (alloc1.x > alloc2.x) {
+ //
+ // This is due to a line break within the region. When the line is
+ // longer than the viewport, and the region is actually quite short,
+ // the user would not see anything of the region, as in this figure
+ // (with region marked as "#"):
+ //
+ // +----------+ ,-- alloc1
+ // | | V
+ // | | ### ###
+ // ### ### | |
+ // ^ | | <-- viewport
+ // | +----------+
+ // `-- alloc2
+ // |----------------------------|
+ // width
+ //
+ // Therefor, we the region smaller, so that the region will be
+ // displayed like this:
+ //
+ // ,-- alloc1
+ // +----|-----+
+ // | V |
+ // | ### ###|
+ // ### ### | |
+ // ^ | | <-- viewport
+ // `-- alloc2 +----------+
+ // |----------|
+ // width
+ //
+
+ /** \todo Changes in the viewport size, until the idle function is
+ * called, are not regarded. */
+
+ if (it1->getWidget()->getLayout()->getUsesViewport() &&
+ x2 - x1 > it1->getWidget()->getLayout()->getWidthViewport()) {
+ x1 = x2 - it1->getWidget()->getLayout()->getWidthViewport();
+ x2 = x1 + it1->getWidget()->getLayout()->getWidthViewport();
+ }
+ }
+
+ if (alloc1.y > alloc2.y) {
+ // This is similar to the case above, e.g. if the region ends in
+ // another table column.
+ if (it1->getWidget()->getLayout()->getUsesViewport() &&
+ y2 - y1 > it1->getWidget()->getLayout()->getHeightViewport()) {
+ y1 = y2 - it1->getWidget()->getLayout()->getHeightViewport();
+ y2 = y1 + it1->getWidget()->getLayout()->getHeightViewport();
+ }
+ }
+
+ it1->getWidget()->getLayout()->scrollTo (hpos, vpos,
+ x1, y1, x2 - x1, y2 - y1);
+ }
+}
+
+// -------------------
+// EmptyIterator
+// -------------------
+
+EmptyIterator::EmptyIterator (Widget *widget, Content::Type mask, bool atEnd):
+ Iterator (widget, mask, atEnd)
+{
+ this->content.type = (atEnd ? Content::END : Content::START);
+}
+
+EmptyIterator::EmptyIterator (EmptyIterator &it): Iterator (it)
+{
+}
+
+object::Object *EmptyIterator::clone ()
+{
+ return new EmptyIterator (*this);
+}
+
+int EmptyIterator::compareTo (misc::Comparable *other)
+{
+ EmptyIterator *otherIt = (EmptyIterator*)other;
+
+ if (content.type == otherIt->content.type)
+ return 0;
+ else if(content.type == Content::START)
+ return -1;
+ else
+ return +1;
+}
+
+bool EmptyIterator::next ()
+{
+ content.type = Content::END;
+ return false;
+}
+
+bool EmptyIterator::prev ()
+{
+ content.type = Content::START;
+ return false;
+}
+
+void EmptyIterator::highlight (int start, int end, HighlightLayer layer)
+{
+}
+
+void EmptyIterator::unhighlight (int direction, HighlightLayer layer)
+{
+}
+
+void EmptyIterator::getAllocation (int start, int end, Allocation *allocation)
+{
+}
+
+// ------------------
+// TextIterator
+// ------------------
+
+TextIterator::TextIterator (Widget *widget, Content::Type mask, bool atEnd,
+ const char *text): Iterator (widget, mask, atEnd)
+{
+ this->content.type = (atEnd ? Content::END : Content::START);
+ this->text = (mask & Content::TEXT) ? text : NULL;
+}
+
+TextIterator::TextIterator (TextIterator &it): Iterator (it)
+{
+ text = it.text;
+}
+
+int TextIterator::compareTo (misc::Comparable *other)
+{
+ TextIterator *otherIt = (TextIterator*)other;
+
+ if (content.type == otherIt->content.type)
+ return 0;
+
+ switch (content.type) {
+ case Content::START:
+ return -1;
+
+ case Content::TEXT:
+ if (otherIt->content.type == Content::START)
+ return +1;
+ else
+ return -1;
+
+ case Content::END:
+ return +1;
+
+ default:
+ misc::assertNotReached();
+ return 0;
+ }
+}
+
+bool TextIterator::next ()
+{
+ if (content.type == Content::START && text != NULL) {
+ content.type = Content::TEXT;
+ content.text = text;
+ return true;
+ } else {
+ content.type = Content::END;
+ return false;
+ }
+}
+
+bool TextIterator::prev ()
+{
+ if (content.type == Content::END && text != NULL) {
+ content.type = Content::TEXT;
+ content.text = text;
+ return true;
+ } else {
+ content.type = Content::START;
+ return false;
+ }
+}
+
+void TextIterator::getAllocation (int start, int end, Allocation *allocation)
+{
+ // Return the allocation of the widget.
+ *allocation = *(getWidget()->getAllocation ());
+}
+
+// ------------------
+// DeepIterator
+// ------------------
+
+DeepIterator::Stack::~Stack ()
+{
+ for (int i = 0; i < size (); i++)
+ get(i)->unref ();
+}
+
+/*
+ * The following two methods are used by dw::core::DeepIterator::DeepIterator,
+ * when the passed dw::core::Iterator points to a widget. Since a
+ * dw::core::DeepIterator never returns a widget, the dw::core::Iterator has
+ * to be corrected, by searching for the next content downwards (within the
+ * widget pointed to), forwards, and backwards (in the traversed tree).
+ */
+
+/*
+ * Search downwards. If fromEnd is true, start search at the end,
+ * otherwise at the beginning.
+ */
+Iterator *DeepIterator::searchDownward (Iterator *it, Content::Type mask,
+ bool fromEnd)
+{
+ Iterator *it2, *it3;
+
+ //DEBUG_MSG (1, "%*smoving down (%swards) from %s\n",
+ // indent, "", from_end ? "back" : "for", a_Dw_iterator_text (it));
+
+ assert (it->getContent()->type == Content::WIDGET);
+ it2 = it->getContent()->widget->iterator (mask, fromEnd);
+
+ if (it2 == NULL) {
+ // Moving downwards failed.
+ //DEBUG_MSG (1, "%*smoving down failed\n", indent, "");
+ return NULL;
+ }
+
+ while (fromEnd ? it2->prev () : it2->next ()) {
+ //DEBUG_MSG (1, "%*sexamining %s\n",
+ // indent, "", a_Dw_iterator_text (it2));
+
+ if (it2->getContent()->type == Content::WIDGET) {
+ // Another widget. Search in it downwards.
+ it3 = searchDownward (it2, mask, fromEnd);
+ if (it3 != NULL) {
+ it2->unref ();
+ return it3;
+ }
+ // Else continue in this widget.
+ } else {
+ // Success!
+ //DEBUG_MSG (1, "%*smoving down succeeded: %s\n",
+ // indent, "", a_Dw_iterator_text (it2));
+ return it2;
+ }
+ }
+
+ // Nothing found.
+ it2->unref ();
+ //DEBUG_MSG (1, "%*smoving down failed (nothing found)\n", indent, "");
+ return NULL;
+}
+
+/*
+ * Search sidewards. fromEnd specifies the direction, false means forwards,
+ * true means backwards.
+ */
+Iterator *DeepIterator::searchSideward (Iterator *it, Content::Type mask,
+ bool fromEnd)
+{
+ Iterator *it2, *it3;
+
+ //DEBUG_MSG (1, "%*smoving %swards from %s\n",
+ // indent, "", from_end ? "back" : "for", a_Dw_iterator_text (it));
+
+ assert (it->getContent()->type == Content::WIDGET);
+ it2 = it->cloneIterator ();
+
+ while (fromEnd ? it2->prev () : it2->next ()) {
+ if (it2->getContent()->type == Content::WIDGET) {
+ // Search downwards in this widget.
+ it3 = searchDownward (it2, mask, fromEnd);
+ if (it3 != NULL) {
+ it2->unref ();
+ //DEBUG_MSG (1, "%*smoving %swards succeeded: %s\n",
+ // indent, "", from_end ? "back" : "for",
+ // a_Dw_iterator_text (it3));
+ return it3;
+ }
+ // Else continue in this widget.
+ } else {
+ // Success!
+ // DEBUG_MSG (1, "%*smoving %swards succeeded: %s\n",
+ // indent, "", from_end ? "back" : "for",
+ // a_Dw_iterator_text (it2));
+ return it2;
+ }
+ }
+
+ /* Nothing found, go upwards in the tree (if possible). */
+ it2->unref ();
+ if (it->getWidget()->getParent ()) {
+ it2 = it->getWidget()->getParent()->iterator (mask, false);
+ while (true) {
+ if (!it2->next ())
+ misc::assertNotReached ();
+
+ if (it2->getContent()->type == Content::WIDGET &&
+ it2->getContent()->widget == it->getWidget ()) {
+ it3 = searchSideward (it2, mask, fromEnd);
+ it2->unref ();
+ //DEBUG_MSG (1, "%*smoving %swards succeeded: %s\n",
+ // indent, "", from_end ? "back" : "for",
+ // a_Dw_iterator_text (it3));
+ return it3;
+ }
+ }
+ }
+
+ // Nothing found at all.
+ // DEBUG_MSG (1, "%*smoving %swards failed (nothing found)\n",
+ // indent, "", from_end ? "back" : "for");
+ return NULL;
+}
+
+/**
+ * \brief Create a new deep iterator from an existing dw::core::Iterator.
+ *
+ * The content of the return value will be the content of \em it. If within
+ * the widget tree, there is no non-widget content, the resulting deep
+ * iterator is empty (denoted by dw::core::DeepIterator::stack == NULL).
+ *
+ * Notes:
+ *
+ * <ol>
+ * <li> The mask of \em i" must include DW_CONTENT_WIDGET, but
+ * dw::core::DeepIterator::next will never return widgets.
+ * </ol>
+ */
+DeepIterator::DeepIterator (Iterator *it)
+{
+ //DEBUG_MSG (1, "a_Dw_ext_iterator_new: %s\n", a_Dw_iterator_text (it));
+
+ // Clone input iterator, so the iterator passed as parameter
+ // remains untouched.
+ it = it->cloneIterator ();
+ this->mask = it->getMask ();
+
+ hasContents = true;
+
+ // If it points to a widget, find a near non-widget content,
+ // since an DeepIterator should never return widgets.
+ if (it->getContent()->type == Content::WIDGET) {
+ Iterator *it2;
+
+ // The second argument of searchDownward is actually a matter of
+ // taste :-)
+ if ((it2 = searchDownward (it, mask, false)) ||
+ (it2 = searchSideward (it, mask, false)) ||
+ (it2 = searchSideward (it, mask, true))) {
+ it->unref ();
+ it = it2;
+ } else {
+ // This may happen, when a page does not contain any non-widget
+ // content.
+ //DEBUG_MSG (1, "a_Dw_ext_iterator_new got totally helpless!\n");
+ it->unref ();
+ hasContents = false;
+ }
+ }
+
+ //DEBUG_MSG (1, " => %s\n", a_Dw_iterator_text (it));
+
+ if(hasContents) {
+ // If this widget has parents, we must construct appropriate iterators.
+ //
+ // \todo There may be a faster way instead of iterating through the
+ // parent widgets.
+
+ // Construct the iterators.
+ int thisLevel = it->getWidget()->getLevel (), level;
+ Widget *w;
+ for (w = it->getWidget (), level = thisLevel; w->getParent() != NULL;
+ w = w->getParent (), level--) {
+ Iterator *it = w->getParent()->iterator (mask, false);
+ stack.put (it, level - 1);
+ while (true) {
+ bool hasNext = it->next();
+ assert (hasNext);
+
+ if (it->getContent()->type == Content::WIDGET &&
+ it->getContent()->widget == w)
+ break;
+ }
+ }
+
+ stack.put (it, thisLevel);
+ content = *(it->getContent());
+ }
+}
+
+
+DeepIterator::~DeepIterator ()
+{
+}
+
+object::Object *DeepIterator::clone ()
+{
+ DeepIterator *it = new DeepIterator ();
+
+ for (int i = 0; i < stack.size (); i++)
+ it->stack.put (stack.get(i)->cloneIterator (), i);
+
+ it->mask = mask;
+ it->content = content;
+ it->hasContents = hasContents;
+
+ return it;
+}
+
+int DeepIterator::compareTo (misc::Comparable *other)
+{
+ DeepIterator *otherDeepIterator = (DeepIterator*)other;
+
+ // Search the highest level, where the widgets are the same.
+ int level = 0;
+
+ while (stack.get(level)->getWidget ()
+ == otherDeepIterator->stack.get(level)->getWidget ()) {
+ if (level == stack.size() - 1 ||
+ level == otherDeepIterator->stack.size() - 1)
+ break;
+ level++;
+ }
+
+ while (stack.get(level)->getWidget ()
+ != otherDeepIterator->stack.get(level)->getWidget ())
+ level--;
+
+ return stack.get(level)->compareTo (otherDeepIterator->stack.get(level));
+}
+
+DeepIterator *DeepIterator::createVariant(Iterator *it)
+{
+ /** \todo Not yet implemented, and actually not yet needed very much. */
+ return new DeepIterator (it);
+}
+
+bool DeepIterator::isEmpty () {
+ return !hasContents;
+}
+
+/**
+ * \brief Move iterator forward and store content it.
+ *
+ * Returns true on success.
+ */
+bool DeepIterator::next ()
+{
+ Iterator *it = stack.getTop ();
+
+ if (it->next ()) {
+ if (it->getContent()->type == Content::WIDGET) {
+ // Widget: new iterator on stack, to search in this widget.
+ stack.push (it->getContent()->widget->iterator (mask, false));
+ return next ();
+ } else {
+ // Simply return the content of the iterartor.
+ content = *(it->getContent ());
+ return true;
+ }
+ } else {
+ // No more data in the top-most widget.
+ if (stack.size () > 1) {
+ // Pop iterator from stack, and move to next item in the old one.
+ stack.pop ();
+ return next ();
+ } else {
+ // Stack is empty.
+ content.type = Content::END;
+ return false;
+ }
+ }
+}
+
+/**
+ * \brief Move iterator backward and store content it.
+ *
+ * Returns true on success.
+ */
+bool DeepIterator::prev ()
+{
+ Iterator *it = stack.getTop ();
+
+ if (it->prev ()) {
+ if (it->getContent()->type == Content::WIDGET) {
+ // Widget: new iterator on stack, to search in this widget.
+ stack.push (it->getContent()->widget->iterator (mask, true));
+ return prev ();
+ } else {
+ // Simply return the content of the iterartor.
+ content = *(it->getContent ());
+ return true;
+ }
+ } else {
+ // No more data in the top-most widget.
+ if (stack.size () > 1) {
+ // Pop iterator from stack, and move to next item in the old one.
+ stack.pop ();
+ return prev ();
+ } else {
+ // Stack is empty.
+ content.type = Content::START;
+ return false;
+ }
+ }
+}
+
+// -----------------
+// CharIterator
+// -----------------
+
+CharIterator::CharIterator ()
+{
+ it = NULL;
+}
+
+CharIterator::CharIterator (Widget *widget)
+{
+ Iterator *i = widget->iterator (Content::SELECTION_CONTENT, false);
+ it = new DeepIterator (i);
+ i->unref ();
+ ch = START;
+}
+
+CharIterator::~CharIterator ()
+{
+ if (it)
+ delete it;
+}
+
+object::Object *CharIterator::clone()
+{
+ CharIterator *cloned = new CharIterator ();
+ cloned->it = it->cloneDeepIterator ();
+ cloned->ch = ch;
+ cloned->pos = pos;
+ return cloned;
+}
+
+int CharIterator::compareTo(misc::Comparable *other)
+{
+ CharIterator *otherIt = (CharIterator*)other;
+ int c = it->compareTo(otherIt->it);
+ if (c != 0)
+ return c;
+ else
+ return pos - otherIt->pos;
+}
+
+bool CharIterator::next ()
+{
+ if (ch == START || it->getContent()->type == Content::BREAK ||
+ (it->getContent()->type == Content::TEXT &&
+ it->getContent()->text[pos] == 0)) {
+ if(it->next()) {
+ if (it->getContent()->type == Content::BREAK)
+ ch = '\n';
+ else { // if (it->getContent()->type == Content::TEXT)
+ pos = 0;
+ ch = it->getContent()->text[pos];
+ if (ch == 0)
+ // should not happen, actually
+ return next ();
+ }
+ return true;
+ }
+ else {
+ ch = END;
+ return false;
+ }
+ } else if(ch == END)
+ return false;
+ else {
+ // at this point, it->getContent()->type == Content::TEXT
+ pos++;
+ ch = it->getContent()->text[pos];
+ if (ch == 0) {
+ if (it->getContent()->space) {
+ ch = ' ';
+ } else {
+ return next ();
+ }
+ }
+
+ return true;
+ }
+}
+
+bool CharIterator::prev ()
+{
+ if (ch == END || it->getContent()->type == Content::BREAK ||
+ (it->getContent()->type == Content::TEXT && pos == 0)) {
+ if(it->prev()) {
+ if (it->getContent()->type == Content::BREAK)
+ ch = '\n';
+ else { // if (it->getContent()->type == Content::TEXT)
+ if (it->getContent()->text[0] == 0)
+ return prev ();
+ else {
+ pos = strlen (it->getContent()->text);
+ if (it->getContent()->space) {
+ ch = ' ';
+ } else {
+ pos--;
+ ch = it->getContent()->text[pos];
+ }
+ }
+ }
+ return true;
+ }
+ else {
+ ch = START;
+ return false;
+ }
+ } else if(ch == START)
+ return false;
+ else {
+ // at this point, it->getContent()->type == Content::TEXT
+ pos--;
+ ch = it->getContent()->text[pos];
+ return true;
+ }
+}
+
+void CharIterator::highlight (CharIterator *it1, CharIterator *it2,
+ HighlightLayer layer)
+{
+ if (it2->getChar () == CharIterator::END)
+ it2->prev ();
+
+ if (it1->it->compareTo (it2->it) == 0)
+ // Only one content => highlight part of it.
+ it1->it->highlight (it1->pos, it2->pos, layer);
+ else {
+ DeepIterator *it = it1->it->cloneDeepIterator ();
+ int c;
+ bool start;
+ for (start = true;
+ (c = it->compareTo (it2->it)) <= 0;
+ it->next (), start = false) {
+ int endOfWord =
+ it->getContent()->type == Content::TEXT ?
+ strlen (it->getContent()->text) : 1;
+ if (start) // first iteration
+ it->highlight (it1->pos, endOfWord, layer);
+ else if (c == 0) // last iteration
+ it->highlight (0, it2->pos, layer);
+ else
+ it->highlight (0, endOfWord, layer);
+ }
+ delete it;
+ }
+}
+
+void CharIterator::unhighlight (CharIterator *it1, CharIterator *it2,
+ HighlightLayer layer)
+{
+ if (it1->it->compareTo (it2->it) == 0)
+ // Only one content => unhighlight it (only for efficiency).
+ it1->it->unhighlight (0, layer);
+ else {
+ DeepIterator *it = it1->it->cloneDeepIterator ();
+ for (; it->compareTo (it2->it) <= 0; it->next ())
+ it->unhighlight (-1, layer);
+ delete it;
+ }
+}
+
+} // namespace dw
+} // namespace core
diff --git a/dw/iterator.hh b/dw/iterator.hh
new file mode 100644
index 00000000..605217ec
--- /dev/null
+++ b/dw/iterator.hh
@@ -0,0 +1,256 @@
+#ifndef __ITERATOR_HH__
+#define __ITERATOR_HH__
+
+#ifndef __INCLUDED_FROM_DW_CORE_HH__
+# error Do not include this file directly, use "core.hh" instead.
+#endif
+
+namespace dw {
+namespace core {
+
+/**
+ * \brief Iterators are used to iterate through the contents of a widget.
+ *
+ * When using iterators, you should care about the results of
+ * dw::core::Widget::hasContents.
+ *
+ * \sa dw::core::Widget::iterator
+ */
+class Iterator: public object::Object, public misc::Comparable
+{
+protected:
+ Iterator(Widget *widget, Content::Type mask, bool atEnd);
+ Iterator(Iterator &it);
+ ~Iterator();
+
+ Content content;
+
+private:
+ Widget *widget;
+ Content::Type mask;
+
+public:
+ bool equals (Object *other);
+
+ inline Widget *getWidget () { return widget; }
+ inline Content *getContent () { return &content; }
+ inline Content::Type getMask () { return mask; }
+
+ virtual void unref ();
+
+ /**
+ * \brief Move iterator forward and store content it.
+ *
+ * Returns true on success.
+ */
+ virtual bool next () = 0;
+
+ /**
+ * \brief Move iterator backward and store content it.
+ *
+ * Returns true on success.
+ */
+ virtual bool prev () = 0;
+
+ /**
+ * \brief Extend highlighted region to contain part of the current content.
+ *
+ * For text, start and end define the
+ * characters, otherwise, the shape is defined as [0, 1], i.e. for
+ * highlighting a whole dw::core::Content, pass 0 and >= 1.
+ * To unhighlight see also dw::core::Iterator::unhighlight.
+ */
+ virtual void highlight (int start, int end, HighlightLayer layer) = 0;
+
+ /**
+ * \brief Shrink highlighted region to no longer contain the
+ * current content.
+ *
+ * The direction parameter indicates whether the highlighted region should be
+ * reduced from the start (direction > 0) or from the end (direction < 0).
+ * If direction is 0 all content is unhighlighted.
+ */
+ virtual void unhighlight (int direction, HighlightLayer layer) = 0;
+
+ /**
+ * \brief Return the shape, which a part of the item, the iterator points
+ * on, allocates.
+ *
+ * The parameters start and end have the same meaning as in
+ * DwIterator::highlight().
+ */
+ virtual void getAllocation (int start, int end, Allocation *allocation) = 0;
+
+ inline Iterator *cloneIterator () { return (Iterator*)clone(); }
+
+ static void scrollTo (Iterator *it1, Iterator *it2, int start, int end,
+ HPosition hpos, VPosition vpos);
+};
+
+
+/**
+ * \brief This implementation of dw::core::Iterator can be used by widgets
+ * with no contents.
+ */
+class EmptyIterator: public Iterator
+{
+private:
+ EmptyIterator (EmptyIterator &it);
+
+public:
+ EmptyIterator (Widget *widget, Content::Type mask, bool atEnd);
+
+ object::Object *clone();
+ int compareTo(misc::Comparable *other);
+ bool next ();
+ bool prev ();
+ void highlight (int start, int end, HighlightLayer layer);
+ void unhighlight (int direction, HighlightLayer layer);
+ void getAllocation (int start, int end, Allocation *allocation);
+};
+
+
+/**
+ * \brief This implementation of dw::core::Iterator can be used by widgets
+ * having one text word as contents
+ */
+class TextIterator: public Iterator
+{
+private:
+ /** May be NULL, in this case, the next is skipped. */
+ const char *text;
+
+ TextIterator (TextIterator &it);
+
+public:
+ TextIterator (Widget *widget, Content::Type mask, bool atEnd,
+ const char *text);
+
+ int compareTo(misc::Comparable *other);
+
+ bool next ();
+ bool prev ();
+ void getAllocation (int start, int end, Allocation *allocation);
+};
+
+
+/**
+ * \brief A stack of iterators, to iterate recursively through a widget tree.
+ *
+ * This class is similar to dw::core::Iterator, but not
+ * created by a widget, but explicitly from another iterator. Deep
+ * iterators do not have the limitation, that iteration is only done within
+ * a widget, instead, child widgets are iterated through recursively.
+ */
+class DeepIterator: public object::Object, public misc::Comparable
+{
+private:
+ class Stack: public container::typed::Vector<Iterator>
+ {
+ public:
+ inline Stack (): container::typed::Vector<Iterator> (4, false) { }
+ ~Stack ();
+ inline Iterator *getTop () { return get (size () - 1); }
+ inline void push (Iterator *it) { put(it, -1); }
+ inline void pop() { getTop()->unref (); remove (size () - 1); }
+ };
+
+ Stack stack;
+
+ static Iterator *searchDownward (Iterator *it, Content::Type mask,
+ bool fromEnd);
+ static Iterator *searchSideward (Iterator *it, Content::Type mask,
+ bool fromEnd);
+
+ Content::Type mask;
+ Content content;
+ bool hasContents;
+
+ inline DeepIterator () { }
+
+public:
+ DeepIterator(Iterator *it);
+ ~DeepIterator();
+
+ object::Object *clone ();
+
+ DeepIterator *createVariant(Iterator *it);
+ inline Iterator *getTopIterator () { return stack.getTop(); }
+ inline Content *getContent () { return &content; }
+
+ bool isEmpty ();
+
+ bool next ();
+ bool prev ();
+ inline DeepIterator *cloneDeepIterator() { return (DeepIterator*)clone(); }
+ int compareTo(misc::Comparable *other);
+
+ /**
+ * \brief Highlight a part of the current content.
+ *
+ * Unhighlight the current content by passing -1 as start (see also
+ * (dw::core::Iterator::unhighlight). For text, start and end define the
+ * characters, otherwise, the shape is defined as [0, 1], i.e. for
+ * highlighting a whole dw::core::Content, pass 0 and >= 1.
+ */
+ inline void highlight (int start, int end, HighlightLayer layer)
+ { stack.getTop()->highlight (start, end, layer); }
+
+ /**
+ * \brief Return the shape, which a part of the item, the iterator points
+ * on, allocates.
+ *
+ * The parameters start and end have the same meaning as in
+ * DwIterator::highlight().
+ */
+ inline void getAllocation (int start, int end, Allocation *allocation)
+ { stack.getTop()->getAllocation (start, end, allocation); }
+
+ inline void unhighlight (int direction, HighlightLayer layer)
+ { stack.getTop()->unhighlight (direction, layer); }
+
+ inline static void scrollTo (DeepIterator *it1, DeepIterator *it2,
+ int start, int end,
+ HPosition hpos, VPosition vpos)
+ { Iterator::scrollTo(it1->stack.getTop(), it2->stack.getTop(),
+ start, end, hpos, vpos); }
+};
+
+class CharIterator: public object::Object, public misc::Comparable
+{
+public:
+ enum { START = -1, END = -2 };
+
+private:
+ DeepIterator *it;
+ int pos, ch;
+
+ CharIterator ();
+
+public:
+ CharIterator (Widget *widget);
+ ~CharIterator ();
+
+ object::Object *clone();
+ int compareTo(misc::Comparable *other);
+
+ bool next ();
+ bool prev ();
+ inline int getChar() { return ch; }
+ inline CharIterator *cloneCharIterator() { return (CharIterator*)clone(); }
+
+ static void highlight (CharIterator *it1, CharIterator *it2,
+ HighlightLayer layer);
+ static void unhighlight (CharIterator *it1, CharIterator *it2,
+ HighlightLayer layer);
+
+ inline static void scrollTo (CharIterator *it1, CharIterator *it2,
+ HPosition hpos, VPosition vpos)
+ { DeepIterator::scrollTo(it1->it, it2->it, it1->pos, it2->pos,
+ hpos, vpos); }
+};
+
+} // namespace dw
+} // namespace core
+
+#endif // __ITERATOR_HH__
diff --git a/dw/layout.cc b/dw/layout.cc
new file mode 100644
index 00000000..e2f437a3
--- /dev/null
+++ b/dw/layout.cc
@@ -0,0 +1,918 @@
+/*
+ * Dillo Widget
+ *
+ * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+
+
+#include "core.hh"
+
+#include "../lout/debug.hh"
+#include "../lout/misc.hh"
+
+using namespace lout::container;
+using namespace lout::object;
+
+namespace dw {
+namespace core {
+
+void Layout::Receiver::canvasSizeChanged (int width, int ascent, int descent)
+{
+}
+
+// ----------------------------------------------------------------------
+
+bool Layout::Emitter::emitToReceiver (lout::signal::Receiver *receiver,
+ int signalNo,
+ int argc, Object **argv)
+{
+ Receiver *layoutReceiver = (Receiver*)receiver;
+
+ switch (signalNo) {
+ case CANVAS_SIZE_CHANGED:
+ layoutReceiver->canvasSizeChanged (((Integer*)argv[0])->getValue (),
+ ((Integer*)argv[1])->getValue (),
+ ((Integer*)argv[2])->getValue ());
+ break;
+
+ default:
+ misc::assertNotReached ();
+ }
+
+ return false;
+}
+
+void Layout::Emitter::emitCanvasSizeChanged (int width,
+ int ascent, int descent)
+{
+ Integer w (width), a (ascent), d (descent);
+ Object *argv[3] = { &w, &a, &d };
+ emitVoid (CANVAS_SIZE_CHANGED, 3, argv);
+}
+
+// ---------------------------------------------------------------------
+
+Layout::Anchor::~Anchor ()
+{
+ delete name;
+}
+
+// ---------------------------------------------------------------------
+
+Layout::Layout (Platform *platform)
+{
+ this->platform = platform;
+ views = new container::typed::List <View> (true);
+ topLevel = NULL;
+ widgetAtPoint = NULL;
+
+ DBG_OBJ_CREATE (this, "DwRenderLayout");
+
+ bgColor = NULL;
+ cursor = style::CURSOR_DEFAULT;
+
+ canvasWidth = canvasAscent = canvasDescent = 0;
+
+ usesViewport = false;
+ scrollX = scrollY = 0;
+
+ requestedAnchor = NULL;
+ scrollIdleId = -1;
+ scrollIdleNotInterrupted = false;
+
+ anchorsTable =
+ new container::typed::HashTable <object::String, Anchor> (true, true);
+
+ resizeIdleId = -1;
+
+ textZone = new misc::ZoneAllocator (16 * 1024);
+
+ DBG_OBJ_ASSOC (&findtextState, this);
+ DBG_OBJ_ASSOC (&selectionState, this);
+
+ platform->setLayout (this);
+
+ selectionState.setLayout(this);
+}
+
+Layout::~Layout ()
+{
+ if (scrollIdleId != -1)
+ platform->removeIdle (scrollIdleId);
+ if (resizeIdleId != -1)
+ platform->removeIdle (resizeIdleId);
+
+ if (topLevel)
+ delete topLevel;
+ delete platform;
+ delete views;
+ delete anchorsTable;
+ delete textZone;
+}
+
+void Layout::addWidget (Widget *widget)
+{
+ if (topLevel) {
+ fprintf (stderr, "widget already set\n");
+ return;
+ }
+
+ topLevel = widget;
+ widget->layout = this;
+
+ findtextState.setWidget (widget);
+
+ canvasHeightGreater = false;
+ setSizeHints ();
+ updateBgColor ();
+ queueResize ();
+}
+
+void Layout::removeWidget ()
+{
+ /**
+ * \bug Some more attributes must be reset here.
+ */
+ topLevel = NULL;
+ widgetAtPoint = NULL;
+ canvasWidth = canvasAscent = canvasDescent = 0;
+ scrollX = scrollY = 0;
+
+ for (typed::Iterator <View> it = views->iterator (); it.hasNext (); ) {
+ View *view = it.getNext ();
+ view->setCanvasSize (canvasWidth, canvasAscent, canvasDescent);
+ if (view->usesViewport ())
+ view->setViewportSize (viewportWidth, viewportHeight, 0, 0);
+ view->queueDrawTotal ();
+ }
+
+ setAnchor (NULL);
+ updateAnchor ();
+
+ emitter.emitCanvasSizeChanged (canvasWidth, canvasAscent, canvasDescent);
+
+ findtextState.setWidget (NULL);
+ selectionState.reset ();
+
+ updateCursor ();
+}
+
+void Layout::setWidget (Widget *widget)
+{
+ if (topLevel)
+ delete topLevel;
+ widgetAtPoint = NULL;
+ textZone->zoneFree ();
+ addWidget (widget);
+
+ updateCursor ();
+}
+
+/**
+ * \brief Attach a view to the layout.
+ *
+ * It will become a child of the layout,
+ * and so it will be destroyed, when the layout will be destroyed.
+ */
+void Layout::attachView (View *view)
+{
+ views->append (view);
+ platform->attachView (view);
+
+ /*
+ * The layout of the view is set later, first, we "project" the current
+ * state of the layout into the new view. A view must handle this without
+ * a layout. See also at the end of this function.
+ */
+ if (bgColor)
+ view->setBgColor (bgColor);
+ view->setCursor (cursor);
+ view->setCanvasSize (canvasWidth, canvasAscent, canvasDescent);
+
+ if (view->usesViewport ()) {
+ if (usesViewport) {
+ view->scrollTo (scrollX, scrollY);
+ view->setViewportSize (viewportWidth, viewportHeight,
+ hScrollbarThickness, vScrollbarThickness);
+ hScrollbarThickness = misc::max (hScrollbarThickness,
+ view->getHScrollbarThickness ());
+ vScrollbarThickness = misc::max (vScrollbarThickness,
+ view->getVScrollbarThickness ());
+ }
+ else {
+ usesViewport = true;
+ scrollX = scrollY = 0;
+ viewportWidth = viewportHeight = 100; // random values
+ hScrollbarThickness = view->getHScrollbarThickness ();
+ vScrollbarThickness = view->getVScrollbarThickness ();
+ }
+ }
+
+ /*
+ * This is the last call within this function, so that it is safe for
+ * the implementation of dw::core::View::setLayout, to call methods
+ * of dw::core::Layout.
+ */
+ view->setLayout (this);
+}
+
+void Layout::detachView (View *view)
+{
+ view->setLayout (NULL);
+ platform->detachView (view);
+
+ views->detachRef (view);
+
+ /**
+ * \todo Actually, viewportMarkerWidthDiff and
+ * viewportMarkerHeightDiff have to be recalculated here, since the
+ * effective (i.e. maximal) values may change, after the view has been
+ * detached. Same applies to the usage of viewports.
+ */
+}
+
+/**
+ * \brief Scrolls all viewports, so that the region [x, y, width, height]
+ * is seen, according to hpos and vpos.
+ */
+void Layout::scrollTo (HPosition hpos, VPosition vpos,
+ int x, int y, int width, int height)
+{
+ scrollTo0 (hpos, vpos, x, y, width, height, true);
+}
+
+void Layout::scrollTo0 (HPosition hpos, VPosition vpos,
+ int x, int y, int width, int height,
+ bool scrollingInterrupted)
+{
+ if (usesViewport) {
+ //printf ("scrollTo (%d, %d, %s)\n",
+ // x, y, scrollingInterrupted ? "true" : "false");
+
+ scrollTargetHpos = hpos;
+ scrollTargetVpos = vpos;
+ scrollTargetX = x;
+ scrollTargetY = y;
+ scrollTargetWidth = width;
+ scrollTargetHeight = height;
+
+ if (scrollIdleId == -1) {
+ scrollIdleId = platform->addIdle (&Layout::scrollIdle);
+ scrollIdleNotInterrupted = true;
+ }
+
+ scrollIdleNotInterrupted =
+ scrollIdleNotInterrupted || !scrollingInterrupted;
+ }
+}
+
+void Layout::scrollIdle ()
+{
+ bool xChanged = true;
+ switch (scrollTargetHpos) {
+ case HPOS_LEFT:
+ scrollX = scrollTargetX;
+ break;
+ case HPOS_CENTER:
+ scrollX =
+ scrollTargetX
+ - (viewportWidth - vScrollbarThickness - scrollTargetWidth) / 2;
+ break;
+ case HPOS_RIGHT:
+ scrollX =
+ scrollTargetX
+ - (viewportWidth - vScrollbarThickness - scrollTargetWidth);
+ break;
+ case HPOS_INTO_VIEW:
+ xChanged = calcScrollInto (scrollTargetX, scrollTargetWidth, &scrollX,
+ viewportWidth - vScrollbarThickness);
+ break;
+ case HPOS_NO_CHANGE:
+ xChanged = false;
+ break;
+ }
+
+ bool yChanged = true;
+ switch (scrollTargetVpos) {
+ case VPOS_TOP:
+ scrollY = scrollTargetY;
+ break;
+ case VPOS_CENTER:
+ scrollY =
+ scrollTargetY
+ - (viewportHeight - hScrollbarThickness - scrollTargetHeight) / 2;
+ break;
+ case VPOS_BOTTOM:
+ scrollY =
+ scrollTargetY
+ - (viewportHeight - hScrollbarThickness - scrollTargetHeight);
+ break;
+ case VPOS_INTO_VIEW:
+ yChanged = calcScrollInto (scrollTargetY, scrollTargetHeight, &scrollY,
+ viewportHeight - hScrollbarThickness);
+ break;
+ case VPOS_NO_CHANGE:
+ yChanged = false;
+ break;
+ }
+
+ if (xChanged || yChanged) {
+ adjustScrollPos ();
+ for (container::typed::Iterator <View> it = views->iterator ();
+ it.hasNext (); ) {
+ View *thisView = it.getNext();
+ thisView->scrollTo (scrollX, scrollY);
+ }
+ }
+
+ scrollIdleId = -1;
+}
+
+void Layout::adjustScrollPos ()
+{
+ scrollX = misc::min (scrollX,
+ canvasWidth - (viewportWidth - vScrollbarThickness));
+ scrollX = misc::max (scrollX, 0);
+
+ scrollY = misc::min (scrollY,
+ canvasAscent + canvasDescent - (viewportHeight - hScrollbarThickness));
+ scrollY = misc::max (scrollY, 0);
+
+ //printf("adjustScrollPos: scrollX=%d scrollY=%d\n", scrollX, scrollY);
+}
+
+bool Layout::calcScrollInto (int requestedValue, int requestedSize,
+ int *value, int viewportSize)
+{
+ if (requestedSize > viewportSize) {
+ // The viewport size is smaller than the size of the region which will
+ // be shown. If the region is already visible, do not change the
+ // position. Otherwise, show the left/upper border, this is most likely
+ // what is needed.
+ if (*value >= requestedValue &&
+ *value + viewportSize < requestedValue + requestedSize)
+ return false;
+ else
+ requestedSize = viewportSize;
+ }
+
+ if (requestedValue < *value) {
+ *value = requestedValue;
+ return true;
+ } else if (requestedValue + requestedSize > *value + viewportSize) {
+ *value = requestedValue - viewportSize + requestedSize;
+ return true;
+ } else
+ return false;
+}
+
+void Layout::draw (View *view, Rectangle *area)
+{
+ Rectangle widgetArea, intersection, widgetDrawArea;
+
+ if (topLevel) {
+ /* Draw the top level widget. */
+ widgetArea.x = topLevel->allocation.x;
+ widgetArea.y = topLevel->allocation.y;
+ widgetArea.width = topLevel->allocation.width;
+ widgetArea.height = topLevel->getHeight ();
+
+ if (area->intersectsWith (&widgetArea, &intersection)) {
+ view->startDrawing (&intersection);
+
+ /* Intersection in widget coordinates. */
+ widgetDrawArea.x = intersection.x - topLevel->allocation.x;
+ widgetDrawArea.y = intersection.y - topLevel->allocation.y;
+ widgetDrawArea.width = intersection.width;
+ widgetDrawArea.height = intersection.height;
+
+ topLevel->draw (view, &widgetDrawArea);
+
+ view->finishDrawing (&intersection);
+ }
+ }
+}
+
+/**
+ * Sets the anchor to scroll to.
+ */
+void Layout::setAnchor (const char *anchor)
+{
+ //printf ("setAnchor (%s)\n", anchor);
+
+ if (requestedAnchor)
+ delete requestedAnchor;
+ requestedAnchor = anchor ? strdup (anchor) : NULL;
+ updateAnchor ();
+}
+
+/**
+ * Used, when the widget is not allocated yet.
+ */
+char *Layout::addAnchor (Widget *widget, const char* name)
+{
+ return addAnchor (widget, name, -1);
+}
+
+char *Layout::addAnchor (Widget *widget, const char* name, int y)
+{
+ String key (name);
+ if (anchorsTable->contains (&key))
+ return NULL;
+ else {
+ Anchor *anchor = new Anchor ();
+ anchor->name = strdup (name);
+ anchor->widget = widget;
+ anchor->y = y;
+
+ anchorsTable->put (new String (name), anchor);
+ updateAnchor ();
+
+ return anchor->name;
+ }
+}
+
+void Layout::changeAnchor (Widget *widget, char* name, int y)
+{
+ String key (name);
+ Anchor *anchor = anchorsTable->get (&key);
+ assert (anchor);
+ assert (anchor->widget == widget);
+ anchor->y = y;
+ updateAnchor ();
+}
+
+void Layout::removeAnchor (Widget *widget, char* name)
+{
+ String key (name);
+ anchorsTable->remove (&key);
+}
+
+void Layout::updateAnchor ()
+{
+ Anchor *anchor;
+ if (requestedAnchor) {
+ String key (requestedAnchor);
+ anchor = anchorsTable->get (&key);
+ } else
+ anchor = NULL;
+
+ if (anchor == NULL) {
+ /** \todo Copy comment from old docs. */
+ if (scrollIdleId != -1 && !scrollIdleNotInterrupted) {
+ platform->removeIdle (scrollIdleId);
+ scrollIdleId = -1;
+ }
+ } else
+ if (anchor->y != -1)
+ scrollTo0 (HPOS_NO_CHANGE, VPOS_TOP, 0, anchor->y, 0, 0, false);
+}
+
+void Layout::setCursor (style::Cursor cursor)
+{
+ if (cursor != this->cursor) {
+ this->cursor = cursor;
+
+ for (typed::Iterator <View> it = views->iterator (); it.hasNext (); ) {
+ View *view = it.getNext ();
+ view->setCursor (cursor);
+ }
+ }
+}
+
+void Layout::updateCursor ()
+{
+ if (widgetAtPoint && widgetAtPoint->style)
+ setCursor (widgetAtPoint->style->cursor);
+ else
+ setCursor (style::CURSOR_DEFAULT);
+}
+
+void Layout::updateBgColor ()
+{
+ /* The toplevel widget should always have a defined background color,
+ * except at the beginning. Searching a defined background is not
+ * necessary. */
+ if (topLevel && topLevel->getStyle() &&
+ topLevel->getStyle()->backgroundColor)
+ bgColor = topLevel->getStyle()->backgroundColor;
+ else
+ bgColor = NULL;
+
+ for (typed::Iterator <View> it = views->iterator (); it.hasNext (); ) {
+ View *view = it.getNext ();
+ view->setBgColor (bgColor);
+ }
+}
+
+void Layout::resizeIdle ()
+{
+ //static int calls = 0;
+ //printf(" Layout::resizeIdle calls = %d\n", ++calls);
+
+ while (resizeIdleId != -1) {
+ // Reset already here, since in this function, queueResize() may be
+ // called again.
+ resizeIdleId = -1;
+
+ if (topLevel) {
+ Requisition requisition;
+ Allocation allocation;
+
+ topLevel->sizeRequest (&requisition);
+
+ allocation.x = allocation.y = 0;
+ allocation.width = requisition.width;
+ allocation.ascent = requisition.ascent;
+ allocation.descent = requisition.descent;
+ topLevel->sizeAllocate (&allocation);
+
+ canvasWidth = requisition.width;
+ canvasAscent = requisition.ascent;
+ canvasDescent = requisition.descent;
+
+ emitter.emitCanvasSizeChanged (
+ canvasWidth, canvasAscent, canvasDescent);
+
+ // Tell the views about the new world size.
+ for (typed::Iterator <View> it = views->iterator (); it.hasNext ();) {
+ View *view = it.getNext ();
+ view->setCanvasSize (canvasWidth, canvasAscent, canvasDescent);
+ // view->queueDrawTotal (false);
+ }
+
+ if (usesViewport) {
+ int actualHScrollbarThickness =
+ (canvasWidth > viewportWidth) ? hScrollbarThickness : 0;
+ int actualVScrollbarThickness =
+ (canvasAscent + canvasDescent > viewportHeight) ?
+ vScrollbarThickness : 0;
+
+ if (!canvasHeightGreater &&
+ canvasAscent + canvasDescent
+ > viewportHeight - actualHScrollbarThickness) {
+ canvasHeightGreater = true;
+ setSizeHints ();
+ /* May queue a new resize. */
+ }
+
+ // Set viewport sizes.
+ for (typed::Iterator <View> it = views->iterator ();
+ it.hasNext (); ) {
+ View *view = it.getNext ();
+ if (view->usesViewport ())
+ view->setViewportSize (viewportWidth, viewportHeight,
+ actualHScrollbarThickness,
+ actualVScrollbarThickness);
+ }
+ }
+ }
+
+ // views are redrawn via Widget::resizeDrawImpl ()
+
+ }
+
+ updateAnchor ();
+}
+
+void Layout::setSizeHints ()
+{
+ if (topLevel) {
+ topLevel->setWidth (viewportWidth
+ - (canvasHeightGreater ? vScrollbarThickness : 0));
+ topLevel->setAscent (viewportHeight - vScrollbarThickness);
+ topLevel->setDescent (0);
+ }
+}
+
+void Layout::queueDraw (int x, int y, int width, int height)
+{
+ Rectangle area;
+ area.x = x;
+ area.y = y;
+ area.width = width;
+ area.height = height;
+
+ if (area.isEmpty ()) return;
+
+ for (container::typed::Iterator <View> it = views->iterator ();
+ it.hasNext (); ) {
+ View *view = it.getNext ();
+ view->queueDraw (&area);
+ }
+}
+
+void Layout::queueDrawExcept (int x, int y, int width, int height,
+ int ex, int ey, int ewidth, int eheight) {
+
+ if (x == ex && y == ey && width == ewidth && height == eheight)
+ return;
+
+ // queueDraw() the four rectangles within rectangle (x, y, width, height)
+ // around rectangle (ex, ey, ewidth, eheight).
+ // Some or all of these may be empty.
+
+ // upper left corner of the intersection rectangle
+ int ix1 = misc::max (x, ex);
+ int iy1 = misc::max (y, ey);
+ // lower right corner of the intersection rectangle
+ int ix2 = misc::min (x + width, ex + ewidth);
+ int iy2 = misc::min (y + height, ey + eheight);
+
+ queueDraw (x, y, width, iy1 - y);
+ queueDraw (x, iy2, width, y + height - iy2);
+ queueDraw (x, iy1, ix1 - x, iy2 - iy1);
+ queueDraw (ix2, iy1, x + width - ix2, iy2 - iy1);
+}
+
+void Layout::queueResize ()
+{
+ if (resizeIdleId == -1) {
+ for (container::typed::Iterator <View> it = views->iterator ();
+ it.hasNext (); ) {
+ View *view = it.getNext ();
+ view->cancelQueueDraw ();
+ }
+
+ resizeIdleId = platform->addIdle (&Layout::resizeIdle);
+ }
+}
+
+
+// Views
+
+bool Layout::buttonEvent (ButtonEventType type, View *view, int numPressed,
+ int x, int y, ButtonState state, int button)
+
+{
+ EventButton event;
+
+ moveToWidgetAtPoint (x, y, state);
+
+ event.xCanvas = x;
+ event.yCanvas = y;
+ event.state = state;
+ event.button = button;
+ event.numPressed = numPressed;
+
+ return processMouseEvent (&event, type, true);
+}
+
+/**
+ * \brief This function is called by a view, to delegate a motion notify
+ * event.
+ *
+ * Arguments are similar to dw::core::Layout::buttonPress.
+ */
+bool Layout::motionNotify (View *view, int x, int y, ButtonState state)
+{
+ EventButton event;
+
+ moveToWidgetAtPoint (x, y, state);
+
+ event.xCanvas = x;
+ event.yCanvas = y;
+ event.state = state;
+
+ return processMouseEvent (&event, MOTION_NOTIFY, true);
+}
+
+/**
+ * \brief This function is called by a view, to delegate a enter notify event.
+ *
+ * Arguments are similar to dw::core::Layout::buttonPress.
+ */
+void Layout::enterNotify (View *view, int x, int y, ButtonState state)
+{
+ Widget *lastWidget;
+ EventCrossing event;
+
+ lastWidget = widgetAtPoint;
+ moveToWidgetAtPoint (x, y, state);
+
+ if(widgetAtPoint) {
+ event.state = state;
+ event.lastWidget = lastWidget;
+ event.currentWidget = widgetAtPoint;
+ widgetAtPoint->enterNotify (&event);
+ }
+}
+
+/**
+ * \brief This function is called by a view, to delegate a leave notify event.
+ *
+ * Arguments are similar to dw::core::Layout::buttonPress.
+ */
+void Layout::leaveNotify (View *view, ButtonState state)
+{
+ Widget *lastWidget;
+ EventCrossing event;
+
+ lastWidget = widgetAtPoint;
+ moveOutOfView (state);
+
+ if(lastWidget) {
+ event.state = state;
+ event.lastWidget = lastWidget;
+ event.currentWidget = widgetAtPoint;
+ lastWidget->leaveNotify (&event);
+ }
+}
+
+/*
+ * Return the widget at position (x, y). Return NULL, if there is no widget.
+ */
+Widget *Layout::getWidgetAtPoint (int x, int y)
+{
+ //_MSG ("------------------------------------------------------------\n");
+ //_MSG ("widget at (%d, %d)\n", x, y);
+ if (topLevel)
+ return topLevel->getWidgetAtPoint (x, y, 0);
+ else
+ return NULL;
+}
+
+
+/*
+ * Emit the necessary crossing events, when the mouse pointer has moved to
+ * the given widget.
+ */
+void Layout::moveToWidget (Widget *newWidgetAtPoint, ButtonState state)
+{
+ Widget *ancestor, *w;
+ Widget **track;
+ int trackLen, i;
+ EventCrossing crossingEvent;
+
+ if (newWidgetAtPoint != widgetAtPoint) {
+ // The mouse pointer has been moved into another widget.
+ if (newWidgetAtPoint && widgetAtPoint)
+ ancestor =
+ newWidgetAtPoint->getNearestCommonAncestor (widgetAtPoint);
+ else if(newWidgetAtPoint)
+ ancestor = newWidgetAtPoint->getTopLevel ();
+ else
+ ancestor = widgetAtPoint->getTopLevel ();
+
+ // Construct the track.
+ trackLen = 0;
+ if (widgetAtPoint)
+ // first part
+ for (w = widgetAtPoint; w != ancestor; w = w->getParent ())
+ trackLen++;
+ trackLen++; // for the ancestor
+ if(newWidgetAtPoint)
+ // second part
+ for (w = newWidgetAtPoint; w != ancestor; w = w->getParent ())
+ trackLen++;
+
+ track = new Widget* [trackLen];
+ i = 0;
+ if (widgetAtPoint)
+ /* first part */
+ for (w = widgetAtPoint; w != ancestor; w = w->getParent ())
+ track[i++] = w;
+ track[i++] = ancestor;
+ if(newWidgetAtPoint) {
+ /* second part */
+ i = trackLen - 1;
+ for (w = newWidgetAtPoint; w != ancestor; w = w->getParent ())
+ track[i--] = w;
+ }
+
+ /* Send events to all events on the track */
+ for (i = 0; i < trackLen; i++) {
+ crossingEvent.state = state;
+ crossingEvent.currentWidget = widgetAtPoint; // ???
+ crossingEvent.lastWidget = widgetAtPoint; // ???
+
+ if (i != 0)
+ track[i]->enterNotify (&crossingEvent);
+ if (i != trackLen - 1)
+ track[i]->leaveNotify (&crossingEvent);
+ }
+
+ delete[] track;
+
+ widgetAtPoint = newWidgetAtPoint;
+ updateCursor ();
+ }
+}
+
+/**
+ * \brief Common processing of press, release and motion events.
+ *
+ * This function depends on that move_to_widget_at_point()
+ * has been called before.
+ */
+bool Layout::processMouseEvent (MousePositionEvent *event,
+ ButtonEventType type, bool mayBeSuppressed)
+{
+ Widget *widget;
+
+ for (widget = widgetAtPoint; widget; widget = widget->getParent ()) {
+ if(!mayBeSuppressed || widget->isButtonSensitive ()) {
+ event->xWidget = event->xCanvas - widget->getAllocation()->x;
+ event->yWidget = event->yCanvas - widget->getAllocation()->y;
+
+ switch (type) {
+ case BUTTON_PRESS:
+ return widget->buttonPress ((EventButton*)event);
+
+ case BUTTON_RELEASE:
+ return widget->buttonRelease ((EventButton*)event);
+
+ case MOTION_NOTIFY:
+ return widget->motionNotify ((EventMotion*)event);
+
+ default:
+ misc::assertNotReached ();
+ }
+ }
+ }
+
+ return false;
+}
+
+/*
+ * This function must be called by a view, when the user has manually changed
+ * the viewport position. It is *not* called, when the layout has requested the
+ * position change.
+ */
+void Layout::scrollPosChanged (View *view, int x, int y)
+{
+ if (x != scrollX || y != scrollY) {
+ scrollX = x;
+ scrollY = y;
+
+ // Tell all views about the scrolling position, except the caller.
+ for (container::typed::Iterator <View> it = views->iterator ();
+ it.hasNext (); ) {
+ View *thisView = it.getNext();
+ if(view != thisView && thisView->usesViewport ())
+ thisView->scrollTo (scrollX, scrollY);
+ }
+
+ setAnchor (NULL);
+ updateAnchor ();
+ }
+}
+
+/*
+ * This function must be called by a viewport view, when its viewport size has
+ * changed. It is *not* called, when the layout has requested the size change.
+ */
+void Layout::viewportSizeChanged (View *view, int width, int height)
+{
+ //printf("Layout::viewportSizeChanged w=%d h=%d new_w=%d new_h=%d\n",
+ // viewportWidth, viewportHeight, width, height);
+
+ /* If the width has become higher, we test again, whether the vertical
+ * scrollbar (so to speak) can be hidden again. */
+ if(usesViewport && width > viewportWidth)
+ canvasHeightGreater = false;
+
+ /* if size changes, redraw this view.
+ * todo: this is a resize call (redraw/resize code needs a review). */
+ if (viewportWidth != width || viewportHeight != height)
+ queueResize();
+
+ viewportWidth = width;
+ viewportHeight = height;
+
+ setSizeHints ();
+
+ int actualHScrollbarThickness =
+ (canvasWidth > viewportWidth) ? hScrollbarThickness : 0;
+ int actualVScrollbarThickness =
+ (canvasAscent + canvasDescent > viewportWidth) ? vScrollbarThickness : 0;
+
+ /* Tell all views about the size, except the caller. */
+ for (container::typed::Iterator <View> it = views->iterator ();
+ it.hasNext (); ) {
+ View *thisView = it.getNext();
+ if(view != thisView && thisView->usesViewport ())
+ thisView->setViewportSize (viewportWidth, viewportHeight,
+ actualHScrollbarThickness,
+ actualVScrollbarThickness);
+ }
+}
+
+} // namespace dw
+} // namespace core
+
diff --git a/dw/layout.hh b/dw/layout.hh
new file mode 100644
index 00000000..13b8f312
--- /dev/null
+++ b/dw/layout.hh
@@ -0,0 +1,264 @@
+#ifndef __DW_LAYOUT_HH__
+#define __DW_LAYOUT_HH__
+
+#ifndef __INCLUDED_FROM_DW_CORE_HH__
+# error Do not include this file directly, use "core.hh" instead.
+#endif
+
+namespace dw {
+namespace core {
+
+/**
+ * \brief The central class for managing and drawing a widget tree.
+ *
+ * \sa\ref dw-overview, \ref dw-layout-widgets, \ref dw-layout-views
+ */
+class Layout: public object::Object
+{
+ friend class Widget;
+
+public:
+ /**
+ * \brief Receiver interface different signals.
+ *
+ * May be extended
+ */
+ class Receiver: public lout::signal::Receiver
+ {
+ public:
+ virtual void canvasSizeChanged (int width, int ascent, int descent);
+ };
+
+private:
+ class Emitter: public lout::signal::Emitter
+ {
+ private:
+ enum { CANVAS_SIZE_CHANGED };
+
+ protected:
+ bool emitToReceiver (lout::signal::Receiver *receiver, int signalNo,
+ int argc, Object **argv);
+
+ public:
+ inline void connectLayout (Receiver *receiver) { connect (receiver); }
+
+ void emitCanvasSizeChanged (int width, int ascent, int descent);
+ };
+
+ Emitter emitter;
+
+ class Anchor: public object::Object
+ {
+ public:
+ char *name;
+ Widget *widget;
+ int y;
+
+ ~Anchor ();
+ };
+
+ Platform *platform;
+ container::typed::List <View> *views;
+ Widget *topLevel, *widgetAtPoint;
+
+ /* The state, which must be projected into the views. */
+ style::Color *bgColor;
+ style::Cursor cursor;
+ int canvasWidth, canvasAscent, canvasDescent;
+
+ bool usesViewport;
+ int scrollX, scrollY, viewportWidth, viewportHeight;
+ bool canvasHeightGreater;
+ int hScrollbarThickness, vScrollbarThickness;
+
+ HPosition scrollTargetHpos;
+ VPosition scrollTargetVpos;
+ int scrollTargetX, scrollTargetY, scrollTargetWidth, scrollTargetHeight;
+
+ char *requestedAnchor;
+ int scrollIdleId, resizeIdleId;
+ bool scrollIdleNotInterrupted;
+
+ /* Anchors of the widget tree */
+ container::typed::HashTable <object::String, Anchor> *anchorsTable;
+
+ SelectionState selectionState;
+ FindtextState findtextState;
+
+ enum ButtonEventType { BUTTON_PRESS, BUTTON_RELEASE, MOTION_NOTIFY };
+
+ Widget *getWidgetAtPoint (int x, int y);
+ void moveToWidget (Widget *newWidgetAtPoint, ButtonState state);
+
+ /**
+ * \brief Emit the necessary crossing events, when the mouse pointer has
+ * moved to position (\em x, \em );
+ */
+ void moveToWidgetAtPoint (int x, int y, ButtonState state)
+ { moveToWidget (getWidgetAtPoint (x, y), state); }
+
+ /**
+ * \brief Emit the necessary crossing events, when the mouse pointer
+ * has moved out of the view.
+ */
+ void moveOutOfView (ButtonState state) { moveToWidget (NULL, state); }
+
+ bool processMouseEvent (MousePositionEvent *event, ButtonEventType type,
+ bool mayBeSuppressed);
+ bool buttonEvent (ButtonEventType type, View *view,
+ int numPressed, int x, int y, ButtonState state,
+ int button);
+ void resizeIdle ();
+ void setSizeHints ();
+ void draw (View *view, Rectangle *area);
+
+ void scrollTo0(HPosition hpos, VPosition vpos,
+ int x, int y, int width, int height,
+ bool scrollingInterrupted);
+ void scrollIdle ();
+ void adjustScrollPos ();
+ static bool calcScrollInto (int targetValue, int requestedSize,
+ int *value, int viewportSize);
+
+ void updateAnchor ();
+
+ /* Widget */
+
+ char *addAnchor (Widget *widget, const char* name);
+ char *addAnchor (Widget *widget, const char* name, int y);
+ void changeAnchor (Widget *widget, char* name, int y);
+ void removeAnchor (Widget *widget, char* name);
+ void setCursor (style::Cursor cursor);
+ void updateCursor ();
+ void updateBgColor ();
+ void queueDraw (int x, int y, int width, int height);
+ void queueDrawExcept (int x, int y, int width, int height,
+ int ex, int ey, int ewidth, int eheight);
+ void queueResize ();
+ void removeWidget ();
+
+public:
+ Layout (Platform *platform);
+ ~Layout ();
+
+ misc::ZoneAllocator *textZone;
+
+ void addWidget (Widget *widget);
+ void setWidget (Widget *widget);
+
+ void attachView (View *view);
+ void detachView (View *view);
+
+ inline bool getUsesViewport () { return usesViewport; }
+ inline int getWidthViewport () { return viewportWidth; }
+ inline int getHeightViewport () { return viewportHeight; }
+ inline int getScrollPosX () { return scrollX; }
+ inline int getScrollPosY () { return scrollY; }
+
+ /* public */
+
+ void scrollTo (HPosition hpos, VPosition vpos,
+ int x, int y, int width, int height);
+ void setAnchor (const char *anchor);
+
+ /* View */
+
+ inline void expose (View *view, Rectangle *area) { draw (view, area); }
+
+ /**
+ * \brief This function is called by a view, to delegate a button press
+ * event.
+ *
+ * \em numPressed is 1 for simple presses, 2 for double presses etc. (more
+ * that 2 is never needed), \em x and \em y the world coordinates, and
+ * \em button the number of the button pressed.
+ */
+ inline bool buttonPress (View *view, int numPressed, int x, int y,
+ ButtonState state, int button)
+ {
+ return buttonEvent (BUTTON_PRESS, view, numPressed, x, y, state, button);
+ }
+
+ /**
+ * \brief This function is called by a view, to delegate a button press
+ * event.
+ *
+ * Arguments are similar to dw::core::Layout::buttonPress.
+ */
+ inline bool buttonRelease (View *view, int numPressed, int x, int y,
+ ButtonState state, int button)
+ {
+ return buttonEvent (BUTTON_RELEASE, view, numPressed, x, y, state,
+ button);
+ }
+
+ bool motionNotify (View *view, int x, int y, ButtonState state);
+ void enterNotify (View *view, int x, int y, ButtonState state);
+ void leaveNotify (View *view, ButtonState state);
+
+ void scrollPosChanged (View *view, int x, int y);
+ void viewportSizeChanged (View *view, int width, int height);
+
+ /* delegated */
+
+ inline int textWidth (style::Font *font, const char *text, int len)
+ {
+ return platform->textWidth (font, text, len);
+ }
+
+ inline int nextGlyph (const char *text, int idx)
+ {
+ return platform->nextGlyph (text, idx);
+ }
+
+ inline int prevGlyph (const char *text, int idx)
+ {
+ return platform->prevGlyph (text, idx);
+ }
+
+ inline style::Font *createFont (style::FontAttrs *attrs, bool tryEverything)
+ {
+ return platform->createFont (attrs, tryEverything);
+ }
+
+ inline style::Color *createSimpleColor (int color)
+ {
+ return platform->createSimpleColor (color);
+ }
+
+ inline style::Color *createShadedColor (int color)
+ {
+ return platform->createShadedColor (color);
+ }
+
+ inline Imgbuf *createImgbuf (Imgbuf::Type type, int width, int height)
+ {
+ return platform->createImgbuf (type, width, height);
+ }
+
+ inline void copySelection(const char *text)
+ {
+ platform->copySelection(text);
+ }
+
+ inline ui::ResourceFactory *getResourceFactory ()
+ {
+ return platform->getResourceFactory ();
+ }
+
+ inline void connect (Receiver *receiver) {
+ emitter.connectLayout (receiver); }
+
+ /** \brief See dw::core::FindtextState::search. */
+ inline FindtextState::Result search (const char *str, bool caseSens)
+ { return findtextState.search (str, caseSens); }
+
+ /** \brief See dw::core::FindtextState::resetSearch. */
+ inline void resetSearch () { findtextState.resetSearch (); }
+};
+
+} // namespace dw
+} // namespace core
+
+#endif // __DW_LAYOUT_HH__
+
diff --git a/dw/listitem.cc b/dw/listitem.cc
new file mode 100644
index 00000000..ba960b46
--- /dev/null
+++ b/dw/listitem.cc
@@ -0,0 +1,72 @@
+/*
+ * Dillo Widget
+ *
+ * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+
+
+#include "listitem.hh"
+#include <stdio.h>
+
+namespace dw {
+
+int ListItem::CLASS_ID = -1;
+
+ListItem::ListItem (ListItem *ref, bool limitTextWidth):
+ AlignedTextblock (limitTextWidth)
+{
+ registerName ("dw::ListItem", &CLASS_ID);
+ setRefTextblock (ref);
+}
+
+ListItem::~ListItem()
+{
+}
+
+void ListItem::initWithWidget (core::Widget *widget,
+ core::style::Style *style)
+{
+ addWidget (widget, style);
+ addSpace (style);
+ updateValue ();
+}
+
+void ListItem::initWithText (char *text, core::style::Style *style)
+{
+ addText (text, style);
+ addSpace (style);
+ updateValue ();
+}
+
+int ListItem::getValue ()
+{
+ if (words->size () == 0)
+ return 0;
+ else
+ return words->get(0).size.width + words->get(0).origSpace;
+}
+
+void ListItem::setMaxValue (int maxValue, int value)
+{
+ innerPadding = maxValue;
+ line1Offset = - value;
+ redrawY = 0;
+ queueResize (0, true);
+}
+
+} // namespace dw
diff --git a/dw/listitem.hh b/dw/listitem.hh
new file mode 100644
index 00000000..ea24af3e
--- /dev/null
+++ b/dw/listitem.hh
@@ -0,0 +1,27 @@
+#ifndef __DW_LISTITEM_HH__
+#define __DW_LISTITEM_HH__
+
+#include "core.hh"
+#include "alignedtextblock.hh"
+
+namespace dw {
+
+class ListItem: public AlignedTextblock
+{
+protected:
+ int getValue ();
+ void setMaxValue (int maxValue, int value);
+
+public:
+ static int CLASS_ID;
+
+ ListItem(ListItem *ref, bool limitTextWidth);
+ ~ListItem();
+
+ void initWithWidget (core::Widget *widget, core::style::Style *style);
+ void initWithText (char *texty, core::style::Style *style);
+};
+
+} // namespace dw
+
+#endif // __DW_LISTITEM_HH__
diff --git a/dw/platform.hh b/dw/platform.hh
new file mode 100644
index 00000000..0ae5d508
--- /dev/null
+++ b/dw/platform.hh
@@ -0,0 +1,144 @@
+#ifndef __DW_PLATFORM_HH__
+#define __DW_PLATFORM_HH__
+
+#ifndef __INCLUDED_FROM_DW_CORE_HH__
+# error Do not include this file directly, use "core.hh" instead.
+#endif
+
+namespace dw {
+namespace core {
+
+/**
+ * \brief An interface to encapsulate some platform dependencies.
+ *
+ * \sa\ref dw-overview
+ */
+class Platform: public object::Object
+{
+public:
+ /*
+ * -----------------------------------
+ * General
+ * -----------------------------------
+ */
+
+ /**
+ * \brief This methods notifies the platform, that it has been attached to
+ * a layout.
+ */
+ virtual void setLayout (Layout *layout) = 0;
+
+ /*
+ * -------------------------
+ * Operations on views
+ * -------------------------
+ */
+
+ /**
+ * \brief This methods notifies the platform, that a view has been attached
+ * to the related layout.
+ */
+ virtual void attachView (View *view) = 0;
+
+ /**
+ * \brief This methods notifies the platform, that a view has been detached
+ * from the related layout.
+ */
+ virtual void detachView (View *view) = 0;
+
+ /*
+ * -----------------------------------
+ * Platform dependant properties
+ * -----------------------------------
+ */
+
+ /**
+ * \brief Return the width of a text, with a given length and font.
+ */
+ virtual int textWidth (style::Font *font, const char *text, int len) = 0;
+
+ /**
+ * \brief Return the index of the next glyph in string text.
+ */
+ virtual int nextGlyph (const char *text, int idx) = 0;
+
+ /**
+ * \brief Return the index of the previous glyph in string text.
+ */
+ virtual int prevGlyph (const char *text, int idx) = 0;
+
+ /*
+ * ---------------------------------------------------------
+ * These are to encapsulate some platform dependencies
+ * ---------------------------------------------------------
+ */
+
+ /**
+ * \brief Add an idle function.
+ *
+ * An idle function is called once, when no other
+ * tasks are to be done (e.g. there are no events to process), and then
+ * removed from the queue. The return value is a number, which can be
+ * used in removeIdle below.
+ */
+ virtual int addIdle (void (Layout::*func) ()) = 0;
+
+ /**
+ * \brief Remove an idle function, which has not been processed yet.
+ */
+ virtual void removeIdle (int idleId) = 0;
+
+ /*
+ * ---------------------
+ * Style Resources
+ * ---------------------
+ */
+
+ /**
+ * \brief Create a (platform dependant) font.
+ *
+ * Typically, within a platform, a sub class of dw::core::style::Font
+ * is defined, which holds more platform dependant data.
+ *
+ * Also, this method must fill the attributes "font" (when needed),
+ * "ascent", "descent", "spaceSidth" and "xHeight". If "tryEverything"
+ * is true, several methods should be used to use another font, when
+ * the requested font is not available. Passing false is typically done,
+ * if the caller wants to test different variations.
+ */
+ virtual style::Font *createFont (style::FontAttrs *attrs,
+ bool tryEverything) = 0;
+
+ /**
+ * \brief Create a simple color resource for a given 0xrrggbb value.
+ */
+ virtual style::Color *createSimpleColor (int color) = 0;
+
+ /**
+ * \brief Create a shaded color resource for a given 0xrrggbb value.
+ */
+ virtual style::Color *createShadedColor (int color) = 0;
+
+
+ /*
+ * --------------------
+ * Image Buffers
+ * --------------------
+ */
+ virtual Imgbuf *createImgbuf (Imgbuf::Type type, int width, int height) = 0;
+
+ /**
+ * \brief Copy selected text (0-terminated).
+ */
+ virtual void copySelection(const char *text) = 0;
+
+ /**
+ * ...
+ */
+ virtual ui::ResourceFactory *getResourceFactory () = 0;
+};
+
+} // namespace dw
+} // namespace core
+
+#endif // __DW_PLATFORM_HH__
diff --git a/dw/preview.xbm b/dw/preview.xbm
new file mode 100644
index 00000000..85ea829b
--- /dev/null
+++ b/dw/preview.xbm
@@ -0,0 +1,5 @@
+#define preview_width 11
+#define preview_height 11
+static unsigned char preview_bits[] = {
+ 0x20, 0x00, 0x70, 0x00, 0x20, 0x00, 0x20, 0x00, 0x22, 0x02, 0xff, 0x07,
+ 0x22, 0x02, 0x20, 0x00, 0x20, 0x00, 0x70, 0x00, 0x20, 0x00};
diff --git a/dw/ruler.cc b/dw/ruler.cc
new file mode 100644
index 00000000..abefa1bf
--- /dev/null
+++ b/dw/ruler.cc
@@ -0,0 +1,53 @@
+/*
+ * Dillo Widget
+ *
+ * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+
+
+#include "ruler.hh"
+#include "../lout/misc.hh"
+
+#include <stdio.h>
+
+namespace dw {
+
+Ruler::Ruler ()
+{
+ unsetFlags (HAS_CONTENTS);
+}
+
+void Ruler::sizeRequestImpl (core::Requisition *requisition)
+{
+ requisition->width = getStyle()->boxDiffWidth ();
+ requisition->ascent = getStyle()->boxOffsetY ();
+ requisition->descent = getStyle()->boxRestHeight ();
+}
+
+void Ruler::draw (core::View *view, core::Rectangle *area)
+{
+ drawWidgetBox (view, area, false);
+}
+
+core::Iterator *Ruler::iterator (core::Content::Type mask, bool atEnd)
+{
+ /** \todo TextIterator? */
+ return new core::EmptyIterator (this, mask, atEnd);
+}
+
+} // namespace dw
diff --git a/dw/ruler.hh b/dw/ruler.hh
new file mode 100644
index 00000000..a1ae67ea
--- /dev/null
+++ b/dw/ruler.hh
@@ -0,0 +1,30 @@
+#ifndef __RULER_HH__
+#define __RULER_HH__
+
+#include "core.hh"
+
+namespace dw {
+
+/**
+ * \brief Widget for drawing (horizontal) rules.
+ *
+ * This is really an empty widget, the HTML parser puts a border
+ * around it, and drawing is done in dw::core::Widget::drawWidgetBox.
+ * The only remarkable point is that the HAS_CONTENT flag is
+ * cleared.
+ */
+class Ruler: public core::Widget
+{
+protected:
+ void sizeRequestImpl (core::Requisition *requisition);
+ void draw (core::View *view, core::Rectangle *area);
+
+public:
+ Ruler ();
+
+ core::Iterator *iterator (core::Content::Type mask, bool atEnd);
+};
+
+} // namespace dw
+
+#endif // __RULER_HH__
diff --git a/dw/selection.cc b/dw/selection.cc
new file mode 100644
index 00000000..3153576f
--- /dev/null
+++ b/dw/selection.cc
@@ -0,0 +1,514 @@
+/*
+ * Dillo Widget
+ *
+ * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+
+
+#include "core.hh"
+
+#include <string.h>
+
+/*
+ * strndup() is a GNU extension.
+ */
+extern "C" char *strndup(const char *s, size_t size)
+{
+ char *r = (char *) malloc (size + 1);
+
+ if (r) {
+ strncpy (r, s, size);
+ r[size] = 0;
+ }
+
+ return r;
+}
+
+namespace dw {
+namespace core {
+
+SelectionState::SelectionState ()
+{
+ layout = NULL;
+
+ selectionState = NONE;
+ from = NULL;
+ to = NULL;
+
+ linkState = LINK_NONE;
+ link = NULL;
+}
+
+SelectionState::~SelectionState ()
+{
+ reset ();
+}
+
+
+bool SelectionState::DoubleClickEmitter::emitToReceiver (lout::signal::Receiver
+ *receiver,
+ int signalNo,
+ int argc,
+ Object **argv)
+{
+ ((DoubleClickReceiver*)receiver)->doubleClick ();
+ return false;
+}
+
+void SelectionState::reset ()
+{
+ resetSelection ();
+ resetLink ();
+}
+
+void SelectionState::resetSelection ()
+{
+ if (from)
+ delete from;
+ from = NULL;
+ if (to)
+ delete to;
+ to = NULL;
+ selectionState = NONE;
+}
+
+
+void SelectionState::resetLink ()
+{
+ if (link)
+ delete link;
+ link = NULL;
+ linkState = LINK_NONE;
+}
+
+bool SelectionState::buttonPress (Iterator *it, int charPos, int linkNo,
+ EventButton *event, bool withinContent)
+{
+ Widget *itWidget = it->getWidget ();
+ bool ret = false;
+
+ if (event && event->button == 1 &&
+ !withinContent && event->numPressed == 2) {
+ // When the user double-clicks on empty parts, emit the double click
+ // signal instead of normal processing. Used for full screen
+ // mode.
+ doubleClickEmitter.emitDoubleClick ();
+ // Reset everything, so that dw::core::Selection::buttonRelease will
+ // ignore the "release" event following soon.
+ highlight (false, 0);
+ reset ();
+ ret = true;
+ } else {
+ if (linkNo != -1) {
+ // link handling
+ if (event) {
+ // return value is ignored
+ itWidget->emitLinkPress (linkNo, -1, -1, -1, event);
+ resetLink ();
+ linkState = LINK_PRESSED;
+ linkButton = event->button;
+ DeepIterator *newLink = new DeepIterator (it);
+ if (newLink->isEmpty ()) {
+ delete newLink;
+ resetLink ();
+ } else {
+ link = newLink;
+ // It may be that the user has pressed on something activatable
+ // (linkNo != -1), but there is no contents, e.g. with images
+ // without ALTernative text.
+ if (link) {
+ linkChar = correctCharPos (link, charPos);
+ linkNumber = linkNo;
+ }
+ }
+ // We do not return the value of the signal method,
+ // but we do actually process this event.
+ ret = true;
+ }
+ } else {
+ // normal selection handling
+ if (event && event->button == 1) {
+ highlight (false, 0);
+ resetSelection ();
+
+ selectionState = SELECTING;
+ DeepIterator *newFrom = new DeepIterator (it);
+ if (newFrom->isEmpty ()) {
+ delete newFrom;
+ resetSelection ();
+ } else {
+ from = newFrom;
+ fromChar = correctCharPos (from, charPos);
+ to = from->cloneDeepIterator ();
+ toChar = correctCharPos (to, charPos);
+ }
+ ret = true;
+ } else {
+ if (event && event->button == 3) {
+ // menu popup
+ itWidget->emitLinkPress (-1, -1, -1, -1, event);
+ ret = true;
+ }
+ }
+ }
+ }
+
+ return ret;
+}
+
+bool SelectionState::buttonRelease (Iterator *it, int charPos, int linkNo,
+ EventButton *event, bool withinContent)
+{
+ Widget *itWidget = it->getWidget ();
+ bool ret = false;
+
+ if (linkState == LINK_PRESSED && event && event->button == linkButton) {
+ // link handling
+ ret = true;
+ if (linkNo != -1)
+ // return value is ignored
+ itWidget->emitLinkRelease (linkNo, -1, -1, -1, event);
+
+ // The link where the user clicked the mouse button?
+ if (linkNo == linkNumber) {
+ resetLink ();
+ // return value is ignored
+ itWidget->emitLinkClick (linkNo, -1, -1, -1, event);
+ } else {
+ if (event->button == 1)
+ // Reset links and switch to selection mode. The selection
+ // state will be set to SELECTING, which is handled some lines
+ // below.
+ switchLinkToSelection (it, charPos);
+ }
+ }
+
+ if (selectionState == SELECTING && event && event->button == 1) {
+ // normal selection
+ ret = true;
+ adjustSelection (it, charPos);
+
+ if (from->compareTo (to) == 0 && fromChar == toChar)
+ // nothing selected
+ resetSelection ();
+ else {
+ copy ();
+ selectionState = SELECTED;
+ }
+ }
+
+ return ret;
+}
+
+bool SelectionState::buttonMotion (Iterator *it, int charPos, int linkNo,
+ EventMotion *event, bool withinContent)
+{
+ if (linkState == LINK_PRESSED) {
+ //link handling
+ if (linkNo != linkNumber)
+ // No longer the link where the user clicked the mouse button.
+ // Reset links and switch to selection mode.
+ switchLinkToSelection (it, charPos);
+ // Still in link: do nothing.
+ } else if (selectionState == SELECTING) {
+ // selection
+ adjustSelection (it, charPos);
+ }
+
+ return true;
+}
+
+/**
+ * \brief General form of dw::core::SelectionState::buttonPress,
+ * dw::core::SelectionState::buttonRelease and
+ * dw::core::SelectionState::buttonMotion.
+ */
+bool SelectionState::handleEvent (EventType eventType, Iterator *it,
+ int charPos, int linkNo,
+ MousePositionEvent *event,
+ bool withinContent)
+{
+ switch (eventType) {
+ case BUTTON_PRESS:
+ return buttonPress (it, charPos, linkNo, (EventButton*)event,
+ withinContent);
+
+ case BUTTON_RELEASE:
+ return buttonRelease (it, charPos, linkNo, (EventButton*)event,
+ withinContent);
+
+ case BUTTON_MOTION:
+ return buttonMotion (it, charPos, linkNo, (EventMotion*)event,
+ withinContent);
+
+
+ default:
+ misc::assertNotReached ();
+ }
+
+ return false;
+}
+
+
+/**
+ * \brief This method is called when the user decides not to activate a link,
+ * but instead select text.
+ */
+void SelectionState::switchLinkToSelection (Iterator *it, int charPos)
+{
+ // It may be that selection->link is NULL, see a_Selection_button_press.
+ if (link) {
+ // Reset old selection.
+ highlight (false, 0);
+ resetSelection ();
+
+ // Transfer link state into selection state.
+ from = link->cloneDeepIterator ();
+ fromChar = linkChar;
+ to = from->createVariant (it);
+ toChar = correctCharPos (to, charPos);
+ selectionState = SELECTING;
+
+ // Reset link status.
+ resetLink ();
+
+ highlight (true, 0);
+
+ } else {
+ // A link was pressed on, but there is nothing to select. Reset
+ // everything.
+ resetSelection ();
+ resetLink ();
+ }
+}
+
+/**
+ * \brief This method is used by core::dw::SelectionState::buttonMotion and
+ * core::dw::SelectionState::buttonRelease, and changes the second limit of
+ * the already existing selection region.
+ */
+void SelectionState::adjustSelection (Iterator *it, int charPos)
+{
+ DeepIterator *newTo;
+ int newToChar, cmpOld, cmpNew, cmpDiff, len;
+ bool bruteHighlighting = false;
+
+ newTo = to->createVariant (it);
+ newToChar = correctCharPos (newTo, charPos);
+
+ cmpOld = to->compareTo (from);
+ cmpNew = newTo->compareTo (from);
+
+ if (cmpOld == 0 || cmpNew == 0) {
+ // Either before, or now, the limits differ only by the character
+ // position.
+ bruteHighlighting = true;
+ } else if (cmpOld * cmpNew < 0) {
+ // The selection order has changed, i.e. the user moved the selection
+ // end again beyond the position he started.
+ bruteHighlighting = true;
+ } else {
+ // Here, cmpOld and cmpNew are equivalent and != 0.
+ cmpDiff = newTo->compareTo (to);
+
+ if (cmpOld * cmpDiff > 0) {
+ // The user has enlarged the selection. Highlight the difference.
+ if (cmpDiff < 0) {
+ len = correctCharPos (to, END_OF_WORD);
+ highlight0 (true, newTo, newToChar, to, len + 1, 1);
+ } else {
+ highlight0 (true, to, 0, newTo, newToChar, -1);
+ }
+ } else {
+ if (cmpOld * cmpDiff < 0) {
+ // The user has reduced the selection. Unighlight the difference.
+ highlight0 (false, to, 0, newTo, 0, cmpDiff);
+ }
+
+ // Otherwise, the user has changed the position only slightly.
+ // In both cases, re-highlight the new position.
+ if (cmpOld < 0) {
+ len = correctCharPos (newTo, END_OF_WORD);
+ newTo->highlight (newToChar, len + 1, HIGHLIGHT_SELECTION);
+ } else
+ newTo->highlight (0, newToChar, HIGHLIGHT_SELECTION);
+ }
+ }
+
+ if (bruteHighlighting)
+ highlight (false, 0);
+
+ delete to;
+ to = newTo;
+ toChar = newToChar;
+
+ if (bruteHighlighting)
+ highlight (true, 0);
+}
+
+/**
+ * \brief This method deals especially with the case that a widget passes
+ * dw::core::SelectionState::END_OF_WORD.
+ */
+int SelectionState::correctCharPos (DeepIterator *it, int charPos)
+{
+ Iterator *top = it->getTopIterator ();
+ int len;
+
+ if (top->getContent()->type == Content::TEXT)
+ len = strlen(top->getContent()->text);
+ else
+ len = 1;
+
+ return misc::min(charPos, len);
+}
+
+void SelectionState::highlight0 (bool fl, DeepIterator *from, int fromChar,
+ DeepIterator *to, int toChar, int dir)
+{
+ DeepIterator *a, *b, *i;
+ int cmp, aChar, bChar;
+ bool start;
+
+ if (from && to) {
+ cmp = from->compareTo (to);
+ if (cmp == 0) {
+ if (fl) {
+ if (fromChar < toChar)
+ from->highlight (fromChar, toChar, HIGHLIGHT_SELECTION);
+ else
+ from->highlight (toChar, fromChar, HIGHLIGHT_SELECTION);
+ } else
+ from->unhighlight (0, HIGHLIGHT_SELECTION);
+ return;
+ }
+
+ if (cmp < 0) {
+ a = from;
+ aChar = fromChar;
+ b = to;
+ bChar = toChar;
+ } else {
+ a = to;
+ aChar = toChar;
+ b = from;
+ bChar = fromChar;
+ }
+
+ for (i = a->cloneDeepIterator (), start = true;
+ (cmp = i->compareTo (b)) <= 0;
+ i->next (), start = false) {
+ if (i->getContent()->type == Content::TEXT) {
+ if (fl) {
+ if (start) {
+ i->highlight (aChar, strlen (i->getContent()->text) + 1,
+ HIGHLIGHT_SELECTION);
+ } else if (cmp == 0) {
+ // the end
+ i->highlight (0, bChar, HIGHLIGHT_SELECTION);
+ } else {
+ i->highlight (0, strlen (i->getContent()->text) + 1,
+ HIGHLIGHT_SELECTION);
+ }
+ } else {
+ i->unhighlight (dir, HIGHLIGHT_SELECTION);
+ }
+ }
+ }
+ delete i;
+ }
+}
+
+void SelectionState::copy()
+{
+ if (from && to) {
+ Iterator *si;
+ DeepIterator *a, *b, *i;
+ int cmp, aChar, bChar;
+ bool start;
+ char *tmp;
+ misc::StringBuffer strbuf;
+
+ cmp = from->compareTo (to);
+ if (cmp == 0) {
+ if (from->getContent()->type == Content::TEXT) {
+ si = from->getTopIterator ();
+ if (fromChar < toChar)
+ tmp = strndup (si->getContent()->text + fromChar,
+ toChar - fromChar);
+ else
+ tmp = strndup (si->getContent()->text + toChar,
+ fromChar - toChar);
+ strbuf.appendNoCopy (tmp);
+ }
+ } else {
+ if (cmp < 0) {
+ a = from;
+ aChar = fromChar;
+ b = to;
+ bChar = toChar;
+ } else {
+ a = to;
+ aChar = toChar;
+ b = from;
+ bChar = fromChar;
+ }
+
+ for (i = a->cloneDeepIterator (), start = true;
+ (cmp = i->compareTo (b)) <= 0;
+ i->next (), start = false) {
+ si = i->getTopIterator ();
+ switch (si->getContent()->type) {
+ case Content::TEXT:
+ if (start) {
+ tmp = strndup (si->getContent()->text + aChar,
+ strlen (i->getContent()->text) - aChar);
+ strbuf.appendNoCopy (tmp);
+ } else if (cmp == 0) {
+ // the end
+ tmp = strndup (si->getContent()->text, bChar);
+ strbuf.appendNoCopy (tmp);
+ } else
+ strbuf.append (si->getContent()->text);
+
+ if (si->getContent()->space && cmp != 0)
+ strbuf.append (" ");
+
+ break;
+
+ case Content::BREAK:
+ if (si->getContent()->breakSpace > 0)
+ strbuf.append ("\n\n");
+ else
+ strbuf.append ("\n");
+ break;
+ default:
+ // Make pedantic compilers happy. Especially
+ // DW_CONTENT_WIDGET is never returned by a DwDeepIterator.
+ break;
+ }
+ }
+ delete i;
+ }
+
+ layout->copySelection(strbuf.getChars());
+ }
+}
+
+} // namespace dw
+} // namespace core
diff --git a/dw/selection.hh b/dw/selection.hh
new file mode 100644
index 00000000..9cc8d25f
--- /dev/null
+++ b/dw/selection.hh
@@ -0,0 +1,275 @@
+#ifndef __DW_SELECTION_H__
+#define __DW_SELECTION_H__
+
+#ifndef __INCLUDED_FROM_DW_CORE_HH__
+# error Do not include this file directly, use "core.hh" instead.
+#endif
+
+namespace dw {
+namespace core {
+
+using namespace lout;
+
+/**
+ * \brief This class handles selections, as well as activation of links,
+ * which is closely related.
+ *
+ * <h3>General Overview</h3>
+ *
+ * dw::core::SelectionState is associated with dw::core::Layout. The selection
+ * state is controlled by "abstract events", which are sent by single
+ * widgets by calling one of the following methods:
+ *
+ * <ul>
+ * <li> dw::core::SelectionState::buttonPress for button press events,
+ * <li> dw::core::SelectionState::buttonRelease for button release events, and
+ * <li> dw::core::SelectionState::buttonMotion for motion events (with pressed
+ * mouse button).
+ * </ul>
+ *
+ * The widget must construct simple iterators (dw::core::Iterator), which will
+ * be transferred to deep iterators (dw::core::DeepIterator), see below for
+ * more details. All event handling methods have the same signature, the
+ * arguments in detail are:
+ *
+ * <table>
+ * <tr><td>dw::core::Iterator *it <td>the iterator pointing on the item
+ * under the mouse pointer; this
+ * iterator \em must be created with
+ * dw::core::Content::SELECTION_CONTENT
+ * as mask
+ * <tr><td>int charPos <td>the exact (character) position
+ * within the iterator,
+ * <tr><td>int linkNo <td>if this item is associated with a
+ * link, its number (see
+ * dw::core::Widget::LinkReceiver),
+ * otherwise -1
+ * <tr><td>dw::core::EventButton *event <td>the event itself; only the button
+ * is used
+ * <tr><td>bool withinContent <td>true, if there is some selectable
+ * content unter the mouse cursor; if
+ * set to false, the "full screen"
+ * feature is used on double click.
+ * </table>
+ *
+ * Look also at dw::core::SelectionState::handleEvent, which may be useful
+ * in some circumstances.
+ *
+ * In some cases, \em charPos would be difficult to determine. E.g., when
+ * the dw::Textblock widget decides that the user is pointing on a position
+ * <i>at the end</i> of an image (DwImage), it constructs a simple iterator
+ * pointing on this image widget. In a simple iterator, that fact that
+ * the pointer is at the end, would be represented by \em charPos == 1. But
+ * when transferring this simple iterator into an deep iterator, this
+ * simple iterator is discarded and instead the stack has an iterator
+ * pointing to text at the top. As a result, only the first letter of the
+ * ALT text would be copied.
+ *
+ * To avoid this problem, widgets should in this case pass
+ * dw::core::SelectionState::END_OF_WORD as \em charPos, which is then
+ * automatically reduced to the actual length of the deep(!) iterator.
+ *
+ * The return value is the same as in DwWidget event handling methods.
+ * I.e., in most cases, they should simply return it. The events
+ * dw::core::Widget::LinkReceiver::press,
+ * dw::core::Widget::LinkReceiver::release and
+ * dw::core::Widget::LinkReceiver::click (but not
+ * dw::core::Widget::LinkReceiver::enter) are emitted by these methods, so
+ * that widgets which let dw::core::SelectionState handle links, should only
+ * emit dw::core::Widget::LinkReceiver::enter for themselves.
+ *
+ * <h3>Selection State</h3>
+ *
+ * Selection interferes with handling the activation of links, so the
+ * latter is also handled by the dw::core::SelectionState. Details are based on
+ * following guidelines:
+ *
+ * <ol>
+ * <li> It should be simple to select links and to start selection in
+ * links. The rule to distinguish between link activation and
+ * selection is that the selection starts as soon as the user leaves
+ * the link. (This is, IMO, a useful feature. Even after drag and
+ * drop has been implemented in dillo, this should be somehow
+ * preserved.)
+ *
+ * <li> The selection should stay as long as possible, i.e., the old
+ * selection is only cleared when a new selection is started.
+ * </ol>
+ *
+ * The latter leads to a model with two states: the selection state and
+ * the link handling state.
+ *
+ * The general selection works, for events not pointing on links, like
+ * this (numbers in parantheses after the event denote the button, "n"
+ * means arbitrary button):
+ *
+ * \dot
+ * digraph G {
+ * node [shape=ellipse, fontname=Helvetica, fontsize=10];
+ * edge [arrowhead="open", labelfontname=Helvetica, labelfontsize=10,
+ * color="#404040", labelfontcolor="#000080",
+ * fontname=Helvetica, fontsize=10, fontcolor="#000080"];
+ * fontname=Helvetica; fontsize=10;
+ *
+ * NONE;
+ * SELECTING;
+ * q [label="Anything selected?", shape=plaintext];
+ * SELECTED;
+ *
+ * NONE -> SELECTING [label="press(1)\non non-link"];
+ * SELECTING -> SELECTING [label="motion(1)"];
+ * SELECTING -> q [label="release(1)"];
+ * q -> SELECTED [label="yes"];
+ * q -> NONE [label="no"];
+ * SELECTED -> SELECTING [label="press(1)"];
+ *
+ * }
+ * \enddot
+ *
+ * The selected region is represented by two instances of
+ * dw::core::DeepIterator.
+ *
+ * Links are handled by a different state machine:
+ *
+ * \dot
+ * digraph G {
+ * node [shape=ellipse, fontname=Helvetica, fontsize=10];
+ * edge [arrowhead="open", labelfontname=Helvetica, labelfontsize=10,
+ * color="#404040", labelfontcolor="#000080",
+ * fontname=Helvetica, fontsize=10, fontcolor="#000080"];
+ * fontname=Helvetica; fontsize=10;
+ *
+ * LINK_NONE;
+ * LINK_PRESSED;
+ * click [label="Emit \"click\" signal.", shape=record];
+ * q11 [label="Still the same link?", shape=plaintext];
+ * q21 [label="Still the same link?", shape=plaintext];
+ * q22 [label="n == 1?", shape=plaintext];
+ * SELECTED [label="Switch selection\nto SELECTED", shape=record];
+ * q12 [label="n == 1?", shape=plaintext];
+ * SELECTING [label="Switch selection\nto SELECTING", shape=record];
+ *
+ * LINK_NONE -> LINK_PRESSED [label="press(n)\non link"];
+ * LINK_PRESSED -> q11 [label="motion(n)"];
+ * q11 -> LINK_PRESSED [label="yes"];
+ * q11 -> q12 [label="no"];
+ * q12 -> SELECTING [label="yes"];
+ * SELECTING -> LINK_NONE;
+ * q12 -> LINK_NONE [label="no"];
+ * LINK_PRESSED -> q21 [label="release(n)"];
+ * q21 -> click [label="yes"];
+ * click -> LINK_NONE;
+ * q21 -> q22 [label="no"];
+ * q22 -> SELECTED [label="yes"];
+ * SELECTED -> LINK_NONE;
+ * q22 -> LINK_NONE [label="no"];
+ * }
+ * \enddot
+ *
+ * Switching selection simply means that the selection state will
+ * eventually be SELECTED/SELECTING, with the original and the current
+ * position making up the selection region. This happens for button 1,
+ * events with buttons other than 1 do not affect selection at all.
+ *
+ *
+ * \todo dw::core::SelectionState::buttonMotion currently always assumes
+ * that button 1 has been pressed (since otherwise it would not do
+ * anything). This should be made a bit cleaner.
+ *
+ * \todo The selection should be cleared, when the user selects something
+ * somewhere else (perhaps switched into "non-active" mode, as e.g. Gtk+
+ * does).
+ *
+ */
+class SelectionState
+{
+public:
+ enum { END_OF_WORD = 1 << 30 };
+
+ class DoubleClickReceiver: public lout::signal::Receiver
+ {
+ public:
+ virtual void doubleClick () = 0;
+ };
+
+private:
+ class DoubleClickEmitter: public lout::signal::Emitter
+ {
+ private:
+ enum { DOUBLE_CLICK };
+
+ protected:
+ bool emitToReceiver (lout::signal::Receiver *receiver, int signalNo,
+ int argc, Object **argv);
+
+ public:
+ inline void connectDoubleClick (DoubleClickReceiver *receiver)
+ { connect (receiver); }
+
+ inline void emitDoubleClick () { emitVoid (DOUBLE_CLICK, 0, NULL); }
+ };
+
+ DoubleClickEmitter doubleClickEmitter;
+
+ Layout *layout;
+
+ // selection
+ enum {
+ NONE,
+ SELECTING,
+ SELECTED
+ } selectionState;
+
+ DeepIterator *from, *to;
+ int fromChar, toChar;
+
+ // link handling
+ enum {
+ LINK_NONE,
+ LINK_PRESSED
+ } linkState;
+
+ int linkButton;
+ DeepIterator *link;
+ int linkChar, linkNumber;
+
+ void resetSelection ();
+ void resetLink ();
+ void switchLinkToSelection (Iterator *it, int charPos);
+ void adjustSelection (Iterator *it, int charPos);
+ static int correctCharPos (DeepIterator *it, int charPos);
+
+ void highlight (bool fl, int dir)
+ { highlight0 (fl, from, fromChar, to, toChar, dir); }
+
+ void highlight0 (bool fl, DeepIterator *from, int fromChar,
+ DeepIterator *to, int toChar, int dir);
+ void copy ();
+
+public:
+ enum EventType { BUTTON_PRESS, BUTTON_RELEASE, BUTTON_MOTION };
+
+ SelectionState ();
+ ~SelectionState ();
+
+ inline void setLayout (Layout *layout) { this->layout = layout; }
+ void reset ();
+ inline void connectDoubleClick (DoubleClickReceiver *receiver)
+ { doubleClickEmitter.connectDoubleClick (receiver); }
+
+ bool buttonPress (Iterator *it, int charPos, int linkNo,
+ EventButton *event, bool withinContent);
+ bool buttonRelease (Iterator *it, int charPos, int linkNo,
+ EventButton *event, bool withinContent);
+ bool buttonMotion (Iterator *it, int charPos, int linkNo,
+ EventMotion *event, bool withinContent);
+
+ bool handleEvent (EventType eventType, Iterator *it, int charPos,
+ int linkNo, MousePositionEvent *event,
+ bool withinContent);
+};
+
+} // namespace dw
+} // namespace core
+
+#endif // __DW_SELECTION_H__
diff --git a/dw/style.cc b/dw/style.cc
new file mode 100644
index 00000000..bdb04f25
--- /dev/null
+++ b/dw/style.cc
@@ -0,0 +1,632 @@
+/*
+ * Dillo Widget
+ *
+ * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+
+
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <ctype.h>
+
+#include "core.hh"
+
+namespace dw {
+namespace core {
+namespace style {
+
+void StyleAttrs::initValues ()
+{
+ x_link = -1;
+ x_img = -1;
+ x_tooltip = NULL;
+ textDecoration = TEXT_DECORATION_NONE;
+ textAlign = TEXT_ALIGN_LEFT;
+ textAlignChar = '.';
+ listStyleType = LIST_STYLE_TYPE_DISC;
+ valign = VALIGN_MIDDLE;
+ backgroundColor = NULL;
+ width = LENGTH_AUTO;
+ height = LENGTH_AUTO;
+
+ margin.setVal (0);
+ borderWidth.setVal (0);
+ padding.setVal (0);
+ setBorderColor (NULL);
+ setBorderStyle (BORDER_NONE);
+ hBorderSpacing = 0;
+ vBorderSpacing = 0;
+
+ display = DISPLAY_INLINE;
+ whiteSpace = WHITE_SPACE_NORMAL;
+ cursor = CURSOR_DEFAULT;
+}
+
+/**
+ * \brief Reset those style attributes to their standard values, which are
+ * not inherited, according to CSS.
+ */
+void StyleAttrs::resetValues ()
+{
+ x_link = -1;
+ x_img = -1;
+ x_tooltip = NULL;
+
+ textAlign = TEXT_ALIGN_LEFT; /* ??? */
+ valign = VALIGN_MIDDLE;
+ textAlignChar = '.';
+ backgroundColor = NULL;
+ width = LENGTH_AUTO;
+ height = LENGTH_AUTO;
+
+ margin.setVal (0);
+ borderWidth.setVal (0);
+ padding.setVal (0);
+ setBorderColor (NULL);
+ setBorderStyle (BORDER_NONE);
+ hBorderSpacing = 0;
+ vBorderSpacing = 0;
+
+ display = DISPLAY_INLINE;
+ whiteSpace = WHITE_SPACE_NORMAL;
+ cursor = CURSOR_DEFAULT; /** \todo Check CSS specification again. */
+}
+
+/**
+ * \brief This method returns whether something may change its size, when
+ * its style changes from this style to \em otherStyle.
+ *
+ * It is mainly for optimizing style changes where only colors etc change
+ * (where false would be returned), in some cases it may return true, although
+ * a size change does not actually happen (e.g. when in a certain
+ * context a particular attribute is ignored).
+ *
+ * \todo Should for CSS implemented properly. Currently, size changes are
+ * not needed, so always false is returned. See also
+ * dw::core::Widget::setStyle.
+ */
+bool StyleAttrs::sizeDiffs (StyleAttrs *otherStyle)
+{
+ return false;
+}
+
+bool StyleAttrs::equals (object::Object *other) {
+ StyleAttrs *otherAttrs = (StyleAttrs *) other;
+
+ return this == otherAttrs ||
+ (font == otherAttrs->font &&
+ textDecoration == otherAttrs->textDecoration &&
+ color == otherAttrs->color &&
+ backgroundColor == otherAttrs->backgroundColor &&
+ textAlign == otherAttrs->textAlign &&
+ valign == otherAttrs->valign &&
+ textAlignChar == otherAttrs->textAlignChar &&
+ hBorderSpacing == otherAttrs->hBorderSpacing &&
+ vBorderSpacing == otherAttrs->vBorderSpacing &&
+ width == otherAttrs->width &&
+ height == otherAttrs->height &&
+ margin.equals (&otherAttrs->margin) &&
+ borderWidth.equals (&otherAttrs->borderWidth) &&
+ padding.equals (&otherAttrs->padding) &&
+ borderColor.top == otherAttrs->borderColor.top &&
+ borderColor.right == otherAttrs->borderColor.right &&
+ borderColor.bottom == otherAttrs->borderColor.bottom &&
+ borderColor.left == otherAttrs->borderColor.left &&
+ borderStyle.top == otherAttrs->borderStyle.top &&
+ borderStyle.right == otherAttrs->borderStyle.right &&
+ borderStyle.bottom == otherAttrs->borderStyle.bottom &&
+ borderStyle.left == otherAttrs->borderStyle.left &&
+ display == otherAttrs->display &&
+ whiteSpace == otherAttrs->whiteSpace &&
+ listStyleType == otherAttrs->listStyleType &&
+ x_link == otherAttrs->x_link &&
+ x_img == otherAttrs->x_img &&
+ x_tooltip == otherAttrs->x_tooltip);
+}
+
+int StyleAttrs::hashValue () {
+ return (intptr_t) font +
+ textDecoration +
+ (intptr_t) color +
+ (intptr_t) backgroundColor +
+ textAlign +
+ valign +
+ textAlignChar +
+ hBorderSpacing +
+ vBorderSpacing +
+ width +
+ height +
+ margin.hashValue () +
+ borderWidth.hashValue () +
+ padding.hashValue () +
+ (intptr_t) borderColor.top +
+ (intptr_t) borderColor.right +
+ (intptr_t) borderColor.bottom +
+ (intptr_t) borderColor.left +
+ borderStyle.top +
+ borderStyle.right +
+ borderStyle.bottom +
+ borderStyle.left +
+ display +
+ whiteSpace +
+ listStyleType +
+ x_link +
+ x_img +
+ (intptr_t) x_tooltip;
+}
+
+int Style::totalRef = 0;
+container::typed::HashTable <StyleAttrs, Style> * Style::styleTable =
+ new container::typed::HashTable <StyleAttrs, Style> (false, false, 1024);
+
+Style::Style (StyleAttrs *attrs)
+{
+ copyAttrs (attrs);
+
+ refCount = 1;
+
+ font->ref ();
+ if (color)
+ color->ref ();
+ if (backgroundColor)
+ backgroundColor->ref ();
+ if (borderColor.top)
+ borderColor.top->ref();
+ if (borderColor.bottom)
+ borderColor.bottom->ref();
+ if (borderColor.left)
+ borderColor.left->ref();
+ if (borderColor.right)
+ borderColor.right->ref();
+ if (x_tooltip)
+ x_tooltip->ref();
+
+ totalRef++;
+}
+
+Style::~Style ()
+{
+ font->unref ();
+
+ if (color)
+ color->unref ();
+ if (backgroundColor)
+ backgroundColor->unref ();
+ if (borderColor.top)
+ borderColor.top->unref();
+ if (borderColor.bottom)
+ borderColor.bottom->unref();
+ if (borderColor.left)
+ borderColor.left->unref();
+ if (borderColor.right)
+ borderColor.right->unref();
+ if (x_tooltip)
+ x_tooltip->unref();
+
+ styleTable->remove (this);
+ totalRef--;
+}
+
+void Style::copyAttrs (StyleAttrs *attrs)
+{
+ font = attrs->font;
+ textDecoration = attrs->textDecoration;
+ color = attrs->color;
+ backgroundColor = attrs->backgroundColor;
+ textAlign = attrs->textAlign;
+ valign = attrs->valign;
+ textAlignChar = attrs->textAlignChar;
+ hBorderSpacing = attrs->hBorderSpacing;
+ vBorderSpacing = attrs->vBorderSpacing;
+ width = attrs->width;
+ height = attrs->height;
+ margin = attrs->margin;
+ borderWidth = attrs->borderWidth;
+ padding = attrs->padding;
+ borderColor = attrs->borderColor;
+ borderStyle = attrs->borderStyle;
+ display = attrs->display;
+ whiteSpace = attrs->whiteSpace;
+ listStyleType = attrs->listStyleType;
+ cursor = attrs->cursor;
+ x_link = attrs->x_link;
+ x_img = attrs->x_img;
+ x_tooltip = attrs->x_tooltip;
+}
+
+// ----------------------------------------------------------------------
+
+bool FontAttrs::equals(object::Object *other)
+{
+ FontAttrs *otherAttrs = (FontAttrs*)other;
+ return
+ this == otherAttrs ||
+ (size == otherAttrs->size && weight == otherAttrs->weight &&
+ style == otherAttrs->style && strcmp (name, otherAttrs->name) == 0);
+}
+
+int FontAttrs::hashValue()
+{
+ int h = object::String::hashValue (name);
+ h = (h << 5) - h + size;
+ h = (h << 5) - h + weight;
+ h = (h << 5) - h + style;
+ return h;
+}
+
+Font::~Font ()
+{
+ delete name;
+}
+
+void Font::copyAttrs (FontAttrs *attrs)
+{
+ name = strdup (attrs->name);
+ size = attrs->size;
+ weight = attrs->weight;
+ style = attrs->style;
+}
+
+Font *Font::create0 (Layout *layout, FontAttrs *attrs,
+ bool tryEverything)
+{
+ return layout->createFont (attrs, tryEverything);
+}
+
+Font *Font::create (Layout *layout, FontAttrs *attrs)
+{
+ return create0 (layout, attrs, false);
+}
+
+Font *Font::createFromList (Layout *layout, FontAttrs *attrs,
+ char *defaultFamily)
+{
+ Font *font = NULL;
+ FontAttrs attrs2;
+ char *comma, *list, *current;
+
+ attrs2 = *attrs;
+ current = list = strdup (attrs->name);
+
+ while (current && (font == NULL)) {
+ comma = strchr (current, ',');
+ if (comma) *comma = 0;
+
+ attrs2.name = current;
+ font = create0 (layout, &attrs2, false);
+ if (font)
+ break;
+
+ if (comma) {
+ current = comma + 1;
+ while (isspace (*current)) current++;
+ } else
+ current = NULL;
+ }
+
+ delete list;
+
+ if (font == NULL) {
+ attrs2.name = defaultFamily;
+ font = create0 (layout, &attrs2, true);
+ }
+
+ if (font == NULL)
+ fprintf (stderr, "Could not find any font.\n");
+
+ return font;
+}
+
+// ----------------------------------------------------------------------
+
+bool ColorAttrs::equals(object::Object *other)
+{
+ ColorAttrs *oc = (ColorAttrs*)other;
+ return this == oc || (color == oc->color && type == oc->type);
+}
+
+int ColorAttrs::hashValue()
+{
+ return color ^ type;
+}
+
+Color::~Color ()
+{
+}
+
+int Color::shadeColor (int color, int d)
+{
+ int red = (color >> 16) & 255;
+ int green = (color >> 8) & 255;
+ int blue = color & 255;
+
+ double oldLightness = ((double) misc::max (red, green, blue)) / 255;
+ double newLightness;
+
+ if (oldLightness > 0.8) {
+ if (d > 0)
+ newLightness = oldLightness - 0.2;
+ else
+ newLightness = oldLightness - 0.4;
+ } else if (oldLightness < 0.2) {
+ if (d > 0)
+ newLightness = oldLightness + 0.4;
+ else
+ newLightness = oldLightness + 0.2;
+ } else
+ newLightness = oldLightness + d * 0.2;
+
+ if (oldLightness) {
+ double f = (newLightness / oldLightness);
+ red = (int)(red * f);
+ green = (int)(green * f);
+ blue = (int)(blue * f);
+ } else {
+ red = green = blue = (int)(newLightness * 255);
+ }
+
+ return (red << 16) | (green << 8) | blue;
+}
+
+int Color::shadeColor (int color, Shading shading)
+{
+ switch (shading) {
+ case SHADING_NORMAL:
+ return color;
+
+ case SHADING_LIGHT:
+ return shadeColor(color, +1);
+
+ case SHADING_INVERSE:
+ return color ^ 0xffffff;
+
+ case SHADING_DARK:
+ return shadeColor(color, -1);
+
+ default:
+ // compiler happiness
+ misc::assertNotReached ();
+ return -1;
+ }
+}
+
+
+Color *Color::create (Layout *layout, int col, Type type)
+{
+ ColorAttrs attrs(col, type);
+ Color *color = NULL;
+
+ switch (type) {
+ case TYPE_SIMPLE:
+ color = layout->createSimpleColor (col);
+ break;
+ case TYPE_SHADED:
+ color = layout->createShadedColor (col);
+ break;
+ }
+
+ return color;
+}
+
+// ----------------------------------------------------------------------
+
+/**
+ * \brief Draw a part of a border.
+ */
+static void drawPolygon (View *view, Color *color, Color::Shading shading,
+ int x1, int y1, int x2, int y2,
+ int width, int w1, int w2)
+{
+ int points[4][2];
+
+ if (width != 0) {
+ if (width == 1) {
+ if (x1 == x2)
+ view->drawLine (color, shading, x1, y1, x2, y2 - 1);
+ else
+ view->drawLine (color, shading, x1, y1, x2 - 1, y2);
+ } else if (width == -1) {
+ if (x1 == x2)
+ view->drawLine (color, shading, x1 - 1, y1, x2 - 1, y2 - 1);
+ else
+ view->drawLine (color, shading, x1, y1 - 1, x2 - 1, y2 - 1);
+ } else {
+ points[0][0] = x1;
+ points[0][1] = y1;
+ points[1][0] = x2;
+ points[1][1] = y2;
+
+ if (x1 == x2) {
+ points[2][0] = x1 + width;
+ points[2][1] = y2 + w2;
+ points[3][0] = x1 + width;
+ points[3][1] = y1 + w1;
+ } else {
+ points[2][0] = x2 + w2;
+ points[2][1] = y1 + width;
+ points[3][0] = x1 + w1;
+ points[3][1] = y1 + width;
+ }
+
+ /*
+ printf ("drawPolygon: (%d, %d) .. (%d, %d) .. (%d, %d) .. (%d, %d)\n",
+ points[0][0], points[0][1], points[1][0], points[1][1],
+ points[2][0], points[2][1], points[3][0], points[3][1]);
+ */
+ view->drawPolygon (color, shading, true, points, 4);
+ }
+ }
+}
+
+/**
+ * \brief Draw the border of a region in window, according to style.
+ *
+ * Used by dw::core::Widget::drawBox and dw::core::Widget::drawWidgetBox.
+ */
+void drawBorder (View *view, Rectangle *area,
+ int x, int y, int width, int height,
+ Style *style, bool inverse)
+{
+ /** \todo a lot! */
+ Color::Shading dark, light, normal;
+ Color::Shading top, right, bottom, left;
+ int xb1, yb1, xb2, yb2, xp1, yp1, xp2, yp2;
+
+ if (style->borderStyle.top == BORDER_NONE)
+ return;
+
+ xb1 = x + style->margin.left;
+ yb1 = y + style->margin.top;
+ xb2 = xb1 + width - style->margin.left - style->margin.right;
+ yb2 = yb1 + height - style->margin.top - style->margin.bottom;
+
+ xp1 = xb1 + style->borderWidth.top;
+ yp1 = yb1 + style->borderWidth.left;
+ xp2 = xb2 + style->borderWidth.bottom;
+ yp2 = yb2 + style->borderWidth.right;
+
+ light = inverse ? Color::SHADING_DARK : Color::SHADING_LIGHT;
+ dark = inverse ? Color::SHADING_LIGHT : Color::SHADING_DARK;
+ normal = inverse ? Color::SHADING_INVERSE : Color::SHADING_NORMAL;
+
+ switch (style->borderStyle.top) {
+ case BORDER_INSET:
+ top = left = dark;
+ right = bottom = light;
+ break;
+
+ case BORDER_OUTSET:
+ top = left = light;
+ right = bottom = dark;
+ break;
+
+ default:
+ top = right = bottom = left = normal;
+ break;
+ }
+
+ drawPolygon (view, style->borderColor.top, top, xb1, yb1, xb2, yb1,
+ style->borderWidth.top, style->borderWidth.left,
+ - style->borderWidth.right);
+ drawPolygon (view, style->borderColor.right, right, xb2, yb1, xb2, yb2,
+ - style->borderWidth.right, style->borderWidth.top,
+ - style->borderWidth.bottom);
+ drawPolygon (view, style->borderColor.bottom, bottom, xb1, yb2, xb2, yb2,
+ - style->borderWidth.bottom, style->borderWidth.left,
+ - style->borderWidth.right);
+ drawPolygon (view, style->borderColor.left, left, xb1, yb1, xb1, yb2,
+ style->borderWidth.left, style->borderWidth.top,
+ - style->borderWidth.bottom);
+}
+
+
+/**
+ * \brief Draw the background (content plus padding) of a region in window,
+ * according to style.
+ *
+ * Used by dw::core::Widget::drawBox and dw::core::Widget::drawWidgetBox.
+ */
+void drawBackground (View *view, Rectangle *area,
+ int x, int y, int width, int height,
+ Style *style, bool inverse)
+{
+ Rectangle bgArea, intersection;
+
+ if (style->backgroundColor) {
+ bgArea.x = x + style->margin.left + style->borderWidth.left;
+ bgArea.y = y + style->margin.top + style->borderWidth.top;
+ bgArea.width =
+ width - style->margin.left - style->borderWidth.left -
+ style->margin.right - style->borderWidth.right;
+ bgArea.height =
+ height - style->margin.top - style->borderWidth.top -
+ style->margin.bottom - style->borderWidth.bottom;
+
+ if (area->intersectsWith (&bgArea, &intersection))
+ view->drawRectangle (style->backgroundColor,
+ inverse ?
+ Color::SHADING_INVERSE : Color::SHADING_NORMAL,
+ true, intersection.x, intersection.y,
+ intersection.width, intersection.height);
+ }
+}
+
+// ----------------------------------------------------------------------
+
+static const char
+ *roman_I0[] = { "","I","II","III","IV","V","VI","VII","VIII","IX" },
+ *roman_I1[] = { "","X","XX","XXX","XL","L","LX","LXX","LXXX","XC" },
+ *roman_I2[] = { "","C","CC","CCC","CD","D","DC","DCC","DCCC","CM" },
+ *roman_I3[] = { "","M","MM","MMM","MMMM" };
+
+void strtolower (char *s)
+{
+ for ( ; *s; s++)
+ *s = tolower (*s);
+}
+
+/**
+ * \brief Convert a number into a string, in a given list style.
+ *
+ * Used for ordered lists.
+ */
+void numtostr (int num, char *buf, int buflen, ListStyleType listStyleType)
+{
+ int i3, i2, i1, i0;
+ bool low = false;
+ int start_ch = 'A';
+
+ switch(listStyleType){
+ case LIST_STYLE_TYPE_LOWER_ALPHA:
+ start_ch = 'a';
+ case LIST_STYLE_TYPE_UPPER_ALPHA:
+ i0 = num - 1;
+ i1 = i0/26 - 1; i2 = i1/26 - 1;
+ if (i2 > 25) /* more than 26+26^2+26^3=18278 elements ? */
+ sprintf(buf, "****.");
+ else
+ sprintf(buf, "%c%c%c.",
+ i2<0 ? ' ' : start_ch + i2%26,
+ i1<0 ? ' ' : start_ch + i1%26,
+ i0<0 ? ' ' : start_ch + i0%26);
+ break;
+ case LIST_STYLE_TYPE_LOWER_ROMAN:
+ low = true;
+ case LIST_STYLE_TYPE_UPPER_ROMAN:
+ i0 = num;
+ i1 = i0/10; i2 = i1/10; i3 = i2/10;
+ i0 %= 10; i1 %= 10; i2 %= 10;
+ if (num < 0 || i3 > 4) /* more than 4999 elements ? */
+ sprintf(buf, "****.");
+ else
+ snprintf(buf, buflen, "%s%s%s%s.", roman_I3[i3], roman_I2[i2],
+ roman_I1[i1], roman_I0[i0]);
+ if (low)
+ strtolower(buf);
+ break;
+ case LIST_STYLE_TYPE_DECIMAL:
+ default:
+ sprintf(buf, "%d.", num);
+ break;
+ }
+}
+
+} // namespace style
+} // namespace dw
+} // namespace core
diff --git a/dw/style.hh b/dw/style.hh
new file mode 100644
index 00000000..492efd30
--- /dev/null
+++ b/dw/style.hh
@@ -0,0 +1,669 @@
+#ifndef __DW_STYLE_HH__
+#define __DW_STYLE_HH__
+
+#include <stdint.h>
+
+#ifndef __INCLUDED_FROM_DW_CORE_HH__
+# error Do not include this file directly, use "core.hh" instead.
+#endif
+
+namespace dw {
+namespace core {
+
+/**
+ * \brief Anything related to Dillo %Widget styles is defined here.
+ *
+ * <h3>Overview</h3>
+ *
+ * dw::core::style::Style provides some resources and attributes for
+ * drawing widgets, as well as for parts of a widget (e.g., dw::Textblock
+ * uses styles for its words). Creating a style is done by filling a
+ * dw::core::style::StyleAttrs with the attributes and calling
+ * dw::core::style::Style::create:
+ *
+ * \code
+ * dw::core::style::Style styleAttrs;
+ * dw::core::style::Style *style;
+ * dw::core::Layout *layout;
+ *
+ * // ...
+ *
+ * styleAttrs.foo = bar;
+ * // etc.
+ * style = dw::core::style::Style::create (&styleAttrs, layout);
+ * // do something with style
+ * \endcode
+ *
+ * After this, the attributes of a dw::core::style::Style should not be
+ * changed anymore, since styles are often shared between different
+ * widgets etc. (see below). Most times, you simply copy the attributes
+ * of another style (possible, since dw::core::style::Style is a sub
+ * class of dw::core::style::StyleAttrs), modify them and create a new
+ * style:
+ *
+ * \code
+ * styleAttrs = *anotherStyle;
+ * styleAttrs.foo = baz;
+ * style = dw::core::style::Style::create (&styleAttrs, layout);
+ * \endcode
+ *
+ * The dw::core::style::Font structure can be created by
+ * dw::core::style::Font::create, in a similar, with
+ * dw::core::style::FontAttrs, and colors by
+ * dw::core::style::Color::create, passing 0xrrggbb as an
+ * argument. Furthermore, there is dw::core::style::Tooltip, created by
+ * dw::core::style::Tooltip::create.
+ *
+ * Notice that fonts, colors and tooltips are only intended to be used in
+ * conjunction with dw::core::style::Style.
+ *
+ *
+ * <h3>Naming</h3>
+ *
+ * dw::core::style::Style will become important for CSS, each CSS
+ * attribute, which is supported by dillo, will refer to an attribute in
+ * dw::core::style::Style. For this reason, the attributes in
+ * dw::core::style::Style get the names from the CSS attributes, with
+ * "camelCase" instead of hythens (e.g. "background-color" becomes
+ * "backgroundColor").
+ *
+ * However, dw::core::style::Style will be extended by some more
+ * attributes, which are not defined by CSS. To distinguish them, they
+ * get the prefix "x_", e.g. dw::core::style::Style::x_link.
+ *
+ *
+ * <h3>Lengths and Percentages</h3>
+ *
+ * dw::core::style::Length is a simple data type for lengths and
+ * percentages:
+ *
+ * <ul>
+ * <li> A length refers to an absolute measurement. It is used to
+ * represent the HTML type %Pixels; and the CSS type \<length\>.
+ *
+ * For CSS lenghts, there are two units: (i) pixels and absolute
+ * units, which have to be converted to pixels (a pixel is, unlike
+ * in the CSS specification, treated as absolute unit), and (ii) the
+ * relative units "em" and "ex" (see below).
+ *
+ * <li> A percentage refers to a value relative to another value. It is
+ * used for the HTML type %Length; (except %Pixels;), and the CSS
+ * type \<percentage\>.
+ *
+ * <li> A relative length can be used in lists of HTML MultiLengths.
+ * </ul>
+ *
+ * Since many values in CSS may be either lengths or percentages, a
+ * single type is very useful.
+ *
+ * <h4>Useful Functions</h4>
+ *
+ * Creating lengths:
+ *
+ * <ul>
+ * <li> dw::core::style::createAbsLength
+ * <li> dw::core::style::createPerLength
+ * <li> dw::core::style::createRelLength
+ * </ul>
+ *
+ * Examine lengths:
+ *
+ * <ul>
+ * <li> dw::core::style::isAbsLength
+ * <li> dw::core::style::isPerLength
+ * <li> dw::core::style::isRelLength
+ * <li> dw::core::style::absLengthVal
+ * <li> dw::core::style::perLengthVal
+ * <li> dw::core::style::relLengthVal
+ * </ul>
+ *
+ *
+ * <h3>Boxes</h3>
+ *
+ * <h4>The CSS %Box Model</h4>
+ *
+ * For borders, margins etc., the box model defined by CSS2 is
+ * used. dw::core::style::Style contains some members defining these
+ * attributes. A dw::core::Widget must use these values for any
+ * calculation of sizes. There are some helper functions (see
+ * dw/style.hh). A dw::core::style::Style box looks quite similar to a
+ * CSS box:
+ *
+ * \image html dw-style-box-model.png
+ *
+ * <h4>Background colors</h4>
+ *
+ * The background color is stored in
+ * dw::core::style::Style::backgroundColor, which may be NULL (the
+ * background color of the parent widget is shining through).
+ *
+ * For toplevel widgets, this color is set as the background color of the
+ * views (dw::core::View::setBgColor), for other widgets, a filled
+ * rectangle is drawn, covering the content and padding. (This is
+ * compliant with CSS2, the background color of the toplevel element
+ * covers the whole canvas.)
+ *
+ * <h4>Drawing</h4>
+ *
+ * The following methods may be useful:
+ *
+ * <ul>
+ * <li> dw::core::Widget::drawWidgetBox for drawing the box of a widget
+ * (typically at the beginning of the implementation of
+ * dw::core::Widget::draw), and
+ *
+ * <li> dw::core::Widget::drawBox, for drawing parts of a widget (e.g.
+ * dw::Textblock::Word, which has its own dw::Textblock::Word::style).
+ * </ul>
+ *
+ *
+ * <h3>Notes on Memory Management</h3>
+ *
+ * Memory management is done by reference counting,
+ * dw::core::style::Style::create returns a pointer to
+ * dw::core::style::Style with an increased reference counter, so you
+ * should care about calling dw::core::style::Style::unref if it is not
+ * used anymore. You do \em not need to care about the reference counters
+ * of fonts and styles.
+ *
+ * In detail:
+ *
+ * <ul>
+ * <li> dw::core::style::Style::ref is called in
+ *
+ * <ul>
+ * <li> dw::core::Widget::setStyle to assign a style to a widget,
+ * <li> dw::Textblock::addText, dw::Textblock::addWidget,
+ * dw::Textblock::addAnchor, dw::Textblock::addSpace,
+ * dw::Textblock::addParbreak and dw::Textblock::addLinebreak,
+ * to assign a style to a dw::Textblock::Word, and
+ * <li> by the HTML parser, when pushing an element on the stack.
+ * </ul>
+ *
+ * <li> dw::core::style::Style::unref is called in
+ *
+ * <ul>
+ * <li> dw::core::Widget::~Widget, dw::Textblock::~Textblock, by the
+ * HTML parser, when popping an element fom the stack, and
+ * <li> dw::core::Widget::setStyle, dw::Textblock::addText etc.,
+ * these methods overwrite an existing style.
+ * </ul>
+ * </ul>
+ */
+namespace style {
+
+enum Cursor {
+ CURSOR_COSSHAIR,
+ CURSOR_DEFAULT,
+ CURSOR_POINTER,
+ CURSOR_MOVE,
+ CURSOR_E_RESIZE,
+ CURSOR_NE_RESIZE,
+ CURSOR_NW_RESIZE,
+ CURSOR_N_RESIZE,
+ CURSOR_SE_RESIZE,
+ CURSOR_SW_RESIZE,
+ CURSOR_S_RESIZE,
+ CURSOR_W_RESIZE,
+ CURSOR_TEXT,
+ CURSOR_WAIT,
+ CURSOR_HELP
+};
+
+enum BorderStyle {
+ BORDER_NONE,
+ BORDER_HIDDEN,
+ BORDER_DOTTED,
+ BORDER_DASHED,
+ BORDER_SOLID,
+ BORDER_DOUBLE,
+ BORDER_GROOVE,
+ BORDER_RIDGE,
+ BORDER_INSET,
+ BORDER_OUTSET
+};
+
+enum TextAlignType {
+ TEXT_ALIGN_LEFT,
+ TEXT_ALIGN_RIGHT,
+ TEXT_ALIGN_CENTER,
+ TEXT_ALIGN_JUSTIFY,
+ TEXT_ALIGN_STRING
+};
+
+enum VAlignType {
+ VALIGN_TOP,
+ VALIGN_BOTTOM,
+ VALIGN_MIDDLE,
+ VALIGN_BASELINE,
+ VALIGN_SUB,
+ VALIGN_SUPER
+};
+
+/**
+ * \todo Incomplete. Has to be completed for a CSS implementation.
+ */
+enum DisplayType {
+ DISPLAY_BLOCK,
+ DISPLAY_INLINE,
+ DISPLAY_LIST_ITEM,
+ DISPLAY_TABLE,
+ DISPLAY_TABLE_ROW_GROUP,
+ DISPLAY_TABLE_HEADER_GROUP,
+ DISPLAY_TABLE_FOOTER_GROUP,
+ DISPLAY_TABLE_ROW,
+ DISPLAY_TABLE_CELL,
+ DISPLAY_LAST
+};
+
+
+enum ListStyleType {
+ LIST_STYLE_TYPE_DISC,
+ LIST_STYLE_TYPE_CIRCLE,
+ LIST_STYLE_TYPE_SQUARE,
+ LIST_STYLE_TYPE_DECIMAL,
+ LIST_STYLE_TYPE_DECIMAL_LEADING_ZERO,
+ LIST_STYLE_TYPE_LOWER_ROMAN,
+ LIST_STYLE_TYPE_UPPER_ROMAN,
+ LIST_STYLE_TYPE_LOWER_GREEK,
+ LIST_STYLE_TYPE_LOWER_ALPHA,
+ LIST_STYLE_TYPE_LOWER_LATIN,
+ LIST_STYLE_TYPE_UPPER_ALPHA,
+ LIST_STYLE_TYPE_UPPER_LATIN,
+ LIST_STYLE_TYPE_HEBREW,
+ LIST_STYLE_TYPE_ARMENIAN,
+ LIST_STYLE_TYPE_GEORGIAN,
+ LIST_STYLE_TYPE_CJK_IDEOGRAPHIC,
+ LIST_STYLE_TYPE_HIRAGANA,
+ LIST_STYLE_TYPE_KATAKANA,
+ LIST_STYLE_TYPE_HIRAGANA_IROHA,
+ LIST_STYLE_TYPE_KATAKANA_IROHA,
+ LIST_STYLE_TYPE_NONE
+};
+
+enum FontStyle {
+ FONT_STYLE_NORMAL,
+ FONT_STYLE_ITALIC,
+ FONT_STYLE_OBLIQUE
+};
+
+enum TextDecoration {
+ TEXT_DECORATION_NONE = 0,
+ TEXT_DECORATION_UNDERLINE = 1 << 0,
+ TEXT_DECORATION_OVERLINE = 1 << 1,
+ TEXT_DECORATION_LINE_THROUGH = 1 << 2,
+ TEXT_DECORATION_BLINK = 1 << 3
+};
+
+enum WhiteSpace {
+ WHITE_SPACE_NORMAL,
+ WHITE_SPACE_PRE,
+ WHITE_SPACE_NOWRAP
+};
+
+/**
+ * \brief Type for representing all lengths within dw::core::style.
+ *
+ * Lengths are int's. Absolute lengths are represented in the following way:
+ *
+ * \image html dw-style-length-absolute.png
+ *
+ * Percentages:
+ *
+ * \image html dw-style-length-percentage.png
+ *
+ * Relative lengths (only used in HTML):
+ *
+ * \image html dw-style-length-relative.png
+ *
+ * This is an implementation detail, use one of the following functions:
+ *
+ * Creating lengths:
+ *
+ * <ul>
+ * <li> dw::core::style::createAbsLength
+ * <li> dw::core::style::createPerLength
+ * <li> dw::core::style::createRelLength
+ * </ul>
+ *
+ * Examine lengths:
+ *
+ * <ul>
+ * <li> dw::core::style::isAbsLength
+ * <li> dw::core::style::isPerLength
+ * <li> dw::core::style::isRelLength
+ * <li> dw::core::style::absLengthVal
+ * <li> dw::core::style::perLengthVal
+ * <li> dw::core::style::relLengthVal
+ * </ul>
+ *
+ * "auto" lenghths are represented as dw::core::style::LENGTH_AUTO.
+ */
+typedef int Length;
+
+/** \brief Returns a length of \em n pixels. */
+inline Length createAbsLength(int n) { return (n << 2) | 1; }
+
+/** \brief Returns a percentage, \em v is relative to 1, not to 100. */
+inline Length createPerLength(double v) {
+ return (int)(v * (1 << 18)) & ~3 | 2; }
+
+/** \brief Returns a relative length. */
+inline Length createRelLength(double v) {
+ return (int)(v * (1 << 18)) & ~3 | 3; }
+
+/** \brief Returns true if \em l is an absolute length. */
+inline bool isAbsLength(Length l) { return (l & 3) == 1; }
+
+/** \brief Returns true if \em l is a percentage. */
+inline bool isPerLength(Length l) { return (l & 3) == 2; }
+
+/** \brief Returns true if \em l is a relative length. */
+inline bool isRelLength(Length l) { return (l & 3) == 3; }
+
+/** \brief Returns the value of a length in pixels, as an integer. */
+inline int absLengthVal(Length l) { return l >> 2; }
+
+/** \brief Returns the value of a percentage, relative to 1, as a double. */
+inline double perLengthVal(Length l) { return (double)(l & ~3) / (1 << 18); }
+
+/** \brief Returns the value of a relative length, as a float. */
+inline double relLengthVal(Length l) { return (double)(l & ~3) / (1 << 18); }
+
+enum {
+ /** \brief Represents "auto" lengths. */
+ LENGTH_AUTO = 0
+};
+
+/**
+ * \brief Represents a dimension box according to the CSS box model.
+ *
+ * Used for dw::core::style::Style::margin,
+ * dw::core::style::Style::borderWidth, and dw::core::style::Style::padding.
+ */
+class Box
+{
+public:
+ /* in future also percentages */
+ int top, right, bottom, left;
+
+ inline void setVal(int val) { top = right = bottom = left = val; }
+ inline bool equals (Box *other) {
+ return top == other->top &&
+ right == other->right &&
+ bottom == other->bottom &&
+ left == other->left;
+ }
+ inline int hashValue () {
+ return top + right + bottom + left;
+ }
+};
+
+class Font;
+class Color;
+class Tooltip;
+
+/**
+ * \sa dw::core::style
+ */
+class StyleAttrs : public object::Object
+{
+public:
+ Font *font;
+ int textDecoration; /* No TextDecoration because of problems converting
+ * TextDecoration <-> int */
+ Color *color, *backgroundColor;
+
+ TextAlignType textAlign;
+ VAlignType valign;
+ char textAlignChar; /* In future, strings will be supported. */
+
+ int hBorderSpacing, vBorderSpacing;
+ Length width, height;
+
+ Box margin, borderWidth, padding;
+ struct { Color *top, *right, *bottom, *left; } borderColor;
+ struct { BorderStyle top, right, bottom, left; } borderStyle;
+
+ DisplayType display;
+ WhiteSpace whiteSpace;
+ ListStyleType listStyleType;
+ Cursor cursor;
+
+ int x_link;
+ int x_img;
+ Tooltip *x_tooltip;
+
+ void initValues ();
+ void resetValues ();
+
+ bool sizeDiffs (StyleAttrs *otherStyleAttrs);
+
+ inline void setBorderColor(Color *val) {
+ borderColor.top = borderColor.right = borderColor.bottom
+ = borderColor.left = val; }
+ inline void setBorderStyle(BorderStyle val) {
+ borderStyle.top = borderStyle.right = borderStyle.bottom
+ = borderStyle.left = val; }
+
+ inline int boxOffsetX ()
+ {
+ return margin.left + borderWidth.left + padding.left;
+ }
+ inline int boxRestWidth ()
+ {
+ return margin.right + borderWidth.right + padding.right;
+ }
+ inline int boxDiffWidth () { return boxOffsetX () + boxRestWidth (); }
+ inline int boxOffsetY ()
+ {
+ return margin.top + borderWidth.top + padding.top;
+ }
+ inline int boxRestHeight ()
+ {
+ return margin.bottom + borderWidth.bottom + padding.bottom;
+ }
+ inline int boxDiffHeight () { return boxOffsetY () + boxRestHeight (); }
+
+ inline bool hasBackground () { return backgroundColor != NULL; }
+
+ bool equals (object::Object *other);
+ int hashValue ();
+};
+
+
+/**
+ * \sa dw::core::style
+ */
+class Style: public StyleAttrs
+{
+private:
+ static int totalRef;
+ int refCount;
+ static container::typed::HashTable <StyleAttrs, Style> *styleTable;
+
+ Style (StyleAttrs *attrs);
+
+protected:
+ ~Style();
+
+ void copyAttrs (StyleAttrs *attrs);
+
+public:
+ inline static Style *create (Layout *layout, StyleAttrs *attrs)
+ {
+ Style *style = styleTable->get (attrs);
+ if (style) {
+ style->ref ();
+ } else {
+ style = new Style (attrs);
+ styleTable->put(style, style);
+ }
+ return style;
+ }
+
+ inline void ref () { refCount++; }
+ inline void unref () { if(--refCount == 0) delete this; }
+};
+
+
+/**
+ * \sa dw::core::style
+ */
+class TooltipAttrs: public object::String
+{
+public:
+ TooltipAttrs(const char *text): object::String(text) { }
+};
+
+/**
+ * \sa dw::core::style
+ */
+class Tooltip: public TooltipAttrs
+{
+private:
+ int refCount;
+
+ Tooltip (const char *text): TooltipAttrs(text) { refCount = 0; }
+
+public:
+ inline Tooltip *create (Layout *layout, const char *text)
+ { return new Tooltip (text); }
+
+ inline void ref () { refCount++; }
+ inline void unref ()
+ { if(--refCount == 0) delete this; }
+
+ inline void onEnter () { }
+ inline void onLeave () { }
+ inline void onMotion () { }
+};
+
+
+/**
+ * \sa dw::core::style
+ */
+class FontAttrs: public object::Object
+{
+public:
+ const char *name;
+ int size;
+ int weight;
+ FontStyle style;
+
+ bool equals(object::Object *other);
+ int hashValue();
+};
+
+
+/**
+ * \sa dw::core::style
+ */
+class Font: public FontAttrs
+{
+private:
+ int refCount;
+
+ static Font *create0 (Layout *layout, FontAttrs *attrs, bool tryEverything);
+
+protected:
+ inline Font () { refCount = 0; }
+ virtual ~Font ();
+
+ void copyAttrs (FontAttrs *attrs);
+
+public:
+ int ascent, descent;
+ int spaceWidth;
+ int xHeight;
+
+ static Font *create (Layout *layout, FontAttrs *attrs);
+ static Font *createFromList (Layout *layout, FontAttrs *attrs,
+ char *defaultFamily);
+
+ inline void ref () { refCount++; }
+ inline void unref () { if(--refCount == 0) delete this; }
+};
+
+
+/**
+ * \sa dw::core::style
+ */
+class ColorAttrs: public object::Object
+{
+public:
+ enum Type { TYPE_SIMPLE, TYPE_SHADED };
+
+protected:
+ int color;
+ Type type;
+
+public:
+ inline ColorAttrs(int color, Type type)
+ {
+ this->color = color;
+ this->type = type;
+ }
+
+ inline int getColor () { return color; }
+ inline Type getType () { return type; }
+
+ bool equals(object::Object *other);
+ int hashValue();
+};
+
+
+/**
+ * \sa dw::core::style
+ */
+class Color: public ColorAttrs
+{
+private:
+ int refCount;
+
+ static Color *create (Layout *layout, int color, Type type);
+ void remove(dw::core::Layout *layout);
+ int shadeColor (int color, int d);
+
+protected:
+ inline Color (int color, Type type): ColorAttrs (color, type) {
+ refCount = 0; }
+ virtual ~Color ();
+
+public:
+ enum Shading { SHADING_NORMAL, SHADING_INVERSE, SHADING_DARK, SHADING_LIGHT,
+ SHADING_NUM };
+
+protected:
+ int shadeColor (int color, Shading shading);
+
+public:
+ inline static Color *createSimple (Layout *layout, int color)
+ {
+ return create (layout, color, TYPE_SIMPLE);
+ }
+
+ inline static Color *createShaded (Layout *layout, int color)
+ {
+ return create (layout, color, TYPE_SHADED);
+ }
+
+ inline void ref () { refCount++; }
+ inline void unref ()
+ { if(--refCount == 0) delete this; }
+};
+
+void drawBorder (View *view, Rectangle *area,
+ int x, int y, int width, int height,
+ Style *style, bool inverse);
+void drawBackground (View *view, Rectangle *area,
+ int x, int y, int width, int height,
+ Style *style, bool inverse);
+void numtostr (int num, char *buf, int buflen, ListStyleType listStyleType);
+
+} // namespace style
+} // namespace dw
+} // namespace core
+
+#endif // __DW_STYLE_HH__
+
diff --git a/dw/table.cc b/dw/table.cc
new file mode 100644
index 00000000..4135799b
--- /dev/null
+++ b/dw/table.cc
@@ -0,0 +1,1192 @@
+/*
+ * Dillo Widget
+ *
+ * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+//#define DBG
+
+#include "table.hh"
+#include "../lout/misc.hh"
+
+#define MAX misc::max
+
+
+namespace dw {
+
+int Table::CLASS_ID = -1;
+
+Table::Table(bool limitTextWidth)
+{
+ registerName ("dw::Table", &CLASS_ID);
+ setFlags (USES_HINTS);
+
+ this->limitTextWidth = limitTextWidth;
+
+ rowClosed = false;
+
+ // random values
+ availWidth = 100;
+ availAscent = 100;
+ availDescent = 0;
+
+ numRows = 0;
+ numCols = 0;
+ curRow = -1;
+ curCol = 0;
+
+ children = new misc::SimpleVector <Child*> (16);
+ colExtremes = new misc::SimpleVector<core::Extremes> (8);
+ colWidths = new misc::SimpleVector <int> (8);
+ cumHeight = new misc::SimpleVector <int> (8);
+ rowSpanCells = new misc::SimpleVector <int> (8);
+ colSpanCells = new misc::SimpleVector <int> (8);
+ baseline = new misc::SimpleVector <int> (8);
+ rowStyle = new misc::SimpleVector <core::style::Style*> (8);
+
+ hasColPercent = 0;
+ colPercents = new misc::SimpleVector <float> (8);
+
+ redrawX = 0;
+ redrawY = 0;
+}
+
+
+Table::~Table()
+{
+ for (int i = 0; i < children->size (); i++) {
+ if (children->get(i)) {
+ switch (children->get(i)->type) {
+ case Child::CELL:
+ delete children->get(i)->cell.widget;
+ break;
+ case Child::SPAN_SPACE:
+ break;
+ }
+
+ delete children->get(i);
+ }
+ }
+
+ for (int i = 0; i < rowStyle->size (); i++)
+ if (rowStyle->get (i))
+ rowStyle->get(i)->unref ();
+
+ delete children;
+ delete colExtremes;
+ delete colWidths;
+ delete cumHeight;
+ delete rowSpanCells;
+ delete colSpanCells;
+ delete baseline;
+ delete rowStyle;
+ delete colPercents;
+}
+
+void Table::sizeRequestImpl (core::Requisition *requisition)
+{
+ forceCalcCellSizes ();
+
+ /**
+ * \bug Baselines are not regarded here.
+ */
+ requisition->width = getStyle()->boxDiffWidth ()
+ + (numCols + 1) * getStyle()->hBorderSpacing;
+ for (int col = 0; col < numCols; col++)
+ requisition->width += colWidths->get (col);
+
+ requisition->ascent =
+ getStyle()->boxDiffHeight () + cumHeight->get (numRows)
+ + getStyle()->vBorderSpacing;
+ requisition->descent = 0;
+
+}
+
+void Table::getExtremesImpl (core::Extremes *extremes)
+{
+ if (numCols == 0) {
+ extremes->minWidth = extremes->maxWidth = 0;
+ return;
+ }
+
+ forceCalcColumnExtremes ();
+
+ extremes->minWidth = extremes->maxWidth =
+ (numCols + 1) * getStyle()->hBorderSpacing
+ + getStyle()->boxDiffWidth ();
+ for (int col = 0; col < numCols; col++) {
+ extremes->minWidth += colExtremes->getRef(col)->minWidth;
+ extremes->maxWidth += colExtremes->getRef(col)->maxWidth;
+ }
+ if (core::style::isAbsLength (getStyle()->width)) {
+ extremes->minWidth =
+ MAX (extremes->minWidth,
+ core::style::absLengthVal(getStyle()->width));
+ extremes->maxWidth =
+ MAX (extremes->maxWidth,
+ core::style::absLengthVal(getStyle()->width));
+ }
+
+#ifdef DBG
+ printf(" Table::getExtremesImpl, {%d, %d} numCols=%d\n",
+ extremes->minWidth, extremes->maxWidth, numCols);
+#endif
+}
+
+void Table::sizeAllocateImpl (core::Allocation *allocation)
+{
+ calcCellSizes ();
+
+ /**
+ * \bug Baselines are not regarded here.
+ */
+
+ int offy =
+ allocation->y + getStyle()->boxOffsetY () + getStyle()->vBorderSpacing;
+ int x =
+ allocation->x + getStyle()->boxOffsetX () + getStyle()->hBorderSpacing;
+
+ for (int col = 0; col < numCols; col++) {
+ for (int row = 0; row < numRows; row++) {
+ int n = row * numCols + col;
+ if (childDefined (n)) {
+ int width =
+ (children->get(n)->cell.colspanEff - 1)
+ * getStyle()->hBorderSpacing;
+ for (int i = 0; i < children->get(n)->cell.colspanEff; i++)
+ width += colWidths->get (col + i);
+
+ core::Allocation childAllocation;
+ core::Requisition childRequisition;
+
+ children->get(n)->cell.widget->sizeRequest (&childRequisition);
+
+ childAllocation.x = x;
+ childAllocation.y = cumHeight->get (row) + offy;
+ childAllocation.width = width;
+ childAllocation.ascent = childRequisition.ascent;
+ childAllocation.descent =
+ cumHeight->get (row + children->get(n)->cell.rowspan)
+ - cumHeight->get (row) - getStyle()->vBorderSpacing
+ - childRequisition.ascent;
+ children->get(n)->cell.widget->sizeAllocate (&childAllocation);
+ }
+ }
+
+ x += colWidths->get (col) + getStyle()->hBorderSpacing;
+ }
+}
+
+void Table::resizeDrawImpl ()
+{
+ queueDrawArea (redrawX, 0, allocation.width - redrawX, getHeight ());
+ queueDrawArea (0, redrawY, allocation.width, getHeight () - redrawY);
+ redrawX = allocation.width;
+ redrawY = getHeight ();
+}
+
+void Table::setWidth (int width)
+{
+ // If limitTextWidth is set, a queueResize may also be necessary.
+ if (availWidth != width || limitTextWidth) {
+#ifdef DBG
+ printf(" Table::setWidth %d\n", width);
+#endif
+ availWidth = width;
+ queueResize (0, false);
+ }
+}
+
+void Table::setAscent (int ascent)
+{
+ if (availAscent != ascent) {
+ availAscent = ascent;
+ queueResize (0, false);
+ }
+}
+
+void Table::setDescent (int descent)
+{
+ if (availDescent != descent) {
+ availDescent = descent;
+ queueResize (0, false);
+ }
+}
+
+void Table::draw (core::View *view, core::Rectangle *area)
+{
+ // Can be optimized, by iterating on the lines in area.
+ drawWidgetBox (view, area, false);
+
+#if 0
+ int offx = getStyle()->boxOffsetX () + getStyle()->hBorderSpacing;
+ int offy = getStyle()->boxOffsetY () + getStyle()->vBorderSpacing;
+ int width = getContentWidth ();
+
+ // This part seems unnecessary. It also segfaulted sometimes when
+ // cumHeight size was less than numRows. --jcid
+ for (int row = 0; row < numRows; row++) {
+ if (rowStyle->get (row))
+ drawBox (view, rowStyle->get (row), area,
+ offx, offy + cumHeight->get (row),
+ width - 2*getStyle()->hBorderSpacing,
+ cumHeight->get (row + 1) - cumHeight->get (row)
+ - getStyle()->vBorderSpacing, false);
+ }
+#endif
+
+ for (int i = 0; i < children->size (); i++) {
+ if (childDefined (i)) {
+ Widget *child = children->get(i)->cell.widget;
+ core::Rectangle childArea;
+ if (child->intersects (area, &childArea))
+ child->draw (view, &childArea);
+ }
+ }
+}
+
+void Table::removeChild (Widget *child)
+{
+ /** \bug Not implemented. */
+}
+
+core::Iterator *Table::iterator (core::Content::Type mask, bool atEnd)
+{
+ return new TableIterator (this, mask, atEnd);
+}
+
+void Table::addCell (Widget *widget, int colspan, int rowspan)
+{
+ Child *child;
+ int colspanEff;
+
+ // We limit the values for colspan and rowspan to 50, to avoid
+ // attacks by malicious web pages.
+ if (colspan > 50 || colspan < 0) {
+ fprintf (stderr, "WARNING: colspan = %d is set to 50.\n", colspan);
+ colspan = 50;
+ }
+ if (rowspan > 50 || rowspan <= 0) {
+ fprintf (stderr, "WARNING: rowspan = %d is set to 50.\n", rowspan);
+ rowspan = 50;
+ }
+
+ if (numRows == 0) {
+ // to prevent a crash
+ fprintf (stderr, "WARNING: Cell without row.\n");
+ addRow (NULL);
+ }
+
+ if (rowClosed) {
+ fprintf (stderr, "WARNING: Last cell had colspan=0.\n");
+ addRow (NULL);
+ }
+
+ if (colspan == 0) {
+ colspanEff = MAX (numCols - curCol, 1);
+ rowClosed = true;
+ } else
+ colspanEff = colspan;
+
+ // Find next free cell-
+ while (curCol < numCols &&
+ (child = children->get(curRow * numCols + curCol)) != NULL &&
+ child->type == Child::SPAN_SPACE)
+ curCol++;
+
+#ifdef DBG
+ printf("Table::addCell numCols=%d,curCol=%d,colspan=%d,colspanEff=%d\n",
+ numCols, curCol, colspan, colspanEff);
+#endif
+ // Increase children array, when necessary.
+ if (curRow + rowspan > numRows)
+ reallocChildren (numCols, curRow + rowspan);
+ if (curCol + colspanEff > numCols)
+ reallocChildren (curCol + colspanEff, numRows);
+
+ // Fill span space.
+ for (int col = 0; col < colspanEff; col++)
+ for (int row = 0; row < rowspan; row++)
+ if (!(col == 0 && row == 0)) {
+ child = new Child ();
+ child->type = Child::SPAN_SPACE;
+ child->spanSpace.startCol = curCol;
+ child->spanSpace.startRow = curRow;
+ children->set ((curRow + row) * numCols + curCol + col, child);
+ }
+
+ // Set the "root" cell.
+ child = new Child ();
+ child->type = Child::CELL;
+ child->cell.widget = widget;
+ child->cell.colspanOrig = colspan;
+ child->cell.colspanEff = colspanEff;
+ child->cell.rowspan = rowspan;
+ children->set (curRow * numCols + curCol, child);
+
+ curCol += colspanEff;
+
+ widget->setParent (this);
+ if (rowStyle->get (curRow))
+ widget->setBgColor (rowStyle->get(curRow)->backgroundColor);
+ queueResize (0, true);
+
+#if 0
+ // show table structure in stdout
+ for (int row = 0; row < numRows; row++) {
+ for (int col = 0; col < numCols; col++) {
+ int n = row * numCols + col;
+ if (!(child = children->get (n))) {
+ printf("[null ] ");
+ } else if (children->get(n)->type == Child::CELL) {
+ printf("[CELL rs=%d] ", child->cell.rowspan);
+ } else if (children->get(n)->type == Child::SPAN_SPACE) {
+ printf("[SPAN rs=%d] ", child->cell.rowspan);
+ } else {
+ printf("[Unk. ] ");
+ }
+ }
+ printf("\n");
+ }
+ printf("\n");
+#endif
+}
+
+void Table::addRow (core::style::Style *style)
+{
+ curRow++;
+
+ if (curRow >= numRows)
+ reallocChildren (numCols, curRow + 1);
+
+ if (rowStyle->get (curRow))
+ rowStyle->get(curRow)->unref ();
+
+ rowStyle->set (curRow, style);
+ if (style)
+ style->ref ();
+
+ curCol = 0;
+ rowClosed = false;
+}
+
+TableCell *Table::getCellRef ()
+{
+ core::Widget *child;
+
+ for (int row = 0; row <= numRows; row++) {
+ int n = curCol + row * numCols;
+ if (childDefined (n)) {
+ child = children->get(n)->cell.widget;
+ if (child->instanceOf (TableCell::CLASS_ID))
+ return (TableCell*)child;
+ }
+ }
+
+ return NULL;
+}
+
+void Table::reallocChildren (int newNumCols, int newNumRows)
+{
+ assert (newNumCols >= numCols);
+ assert (newNumRows >= numRows);
+
+ children->setSize (newNumCols * newNumRows);
+
+ if (newNumCols > numCols) {
+ // Complicated case, array got also wider.
+ for (int row = newNumRows - 1; row >= 0; row--) {
+ int colspan0Col = -1, colspan0Row = -1;
+
+ // Copy old part.
+ for (int col = numCols - 1; col >= 0; col--) {
+ int n = row * newNumCols + col;
+ children->set (n, children->get (row * numCols + col));
+ if (children->get (n)) {
+ switch (children->get(n)->type) {
+ case Child::CELL:
+ if (children->get(n)->cell.colspanOrig == 0) {
+ colspan0Col = col;
+ colspan0Row = row;
+ children->get(n)->cell.colspanEff = newNumCols - col;
+ }
+ break;
+ case Child::SPAN_SPACE:
+ if (children->get(children->get(n)->spanSpace.startRow
+ * numCols +
+ children->get(n)->spanSpace.startCol)
+ ->cell.colspanOrig == 0) {
+ colspan0Col = children->get(n)->spanSpace.startCol;
+ colspan0Row = children->get(n)->spanSpace.startRow;
+ }
+ break;
+ }
+ }
+ }
+
+ // Fill rest of the column.
+ if (colspan0Col == -1) {
+ for (int col = numCols; col < newNumCols; col++)
+ children->set (row * newNumCols + col, NULL);
+ } else {
+ for (int col = numCols; col < newNumCols; col++) {
+ Child *child = new Child ();
+ child->type = Child::SPAN_SPACE;
+ child->spanSpace.startCol = colspan0Col;
+ child->spanSpace.startRow = colspan0Row;
+ children->set (row * newNumCols + col, child);
+ }
+ }
+ }
+ }
+
+ // Bottom part of the children array.
+ for (int row = numRows; row < newNumRows; row++)
+ for (int col = 0; col < newNumCols; col++)
+ children->set (row * newNumCols + col, NULL);
+
+ // Simple arrays.
+ rowStyle->setSize (newNumRows);
+ for (int row = numRows; row < newNumRows; row++)
+ rowStyle->set (row, NULL);
+ // Rest is increased, when needed.
+
+ numCols = newNumCols;
+ numRows = newNumRows;
+}
+
+// ----------------------------------------------------------------------
+
+void Table::calcCellSizes ()
+{
+ if (needsResize ())
+ forceCalcCellSizes ();
+}
+
+
+void Table::forceCalcCellSizes ()
+{
+ int totalWidth = 0, childHeight, forceTotalWidth = 1;
+ core::Extremes extremes;
+
+ // Will also call calcColumnExtremes(), when needed.
+ getExtremes (&extremes);
+
+ if (core::style::isAbsLength (getStyle()->width)) {
+ totalWidth = core::style::absLengthVal (getStyle()->width);
+ } else if (core::style::isPerLength (getStyle()->width)) {
+ /*
+ * If the width is > 100%, we use 100%, this prevents ugly
+ * results. (May be changed in future, when a more powerful
+ * rendering is implemented, to handle fixed positions etc.,
+ * as defined by CSS2.)
+ */
+ totalWidth =
+ (int)(availWidth
+ * misc::min (core::style::perLengthVal (getStyle()->width),
+ 1.0));
+ } else if (getStyle()->width == core::style::LENGTH_AUTO) {
+ totalWidth = availWidth;
+ forceTotalWidth = 0;
+ }
+#ifdef DBG
+ printf(" availWidth = %d\n", availWidth);
+ printf(" totalWidth1 = %d\n", totalWidth);
+#endif
+ if (totalWidth < extremes.minWidth)
+ totalWidth = extremes.minWidth;
+ totalWidth = totalWidth
+ - (numCols + 1) * getStyle()->hBorderSpacing
+ - getStyle()->boxDiffWidth ();
+#ifdef DBG
+ printf(" totalWidth2 = %d curCol=%d\n", totalWidth,curCol);
+#endif
+
+ colWidths->setSize (numCols, 0);
+ cumHeight->setSize (numRows + 1, 0);
+ rowSpanCells->setSize (0);
+ baseline->setSize (numRows);
+#ifdef DBG
+ printf(" extremes = %d,%d\n", extremes.minWidth, extremes.maxWidth);
+ printf(" getStyle()->boxDiffWidth() = %d\n", getStyle()->boxDiffWidth());
+ printf(" getStyle()->hBorderSpacing = %d\n", getStyle()->hBorderSpacing);
+#endif
+
+ apportion_percentages2 (totalWidth, forceTotalWidth);
+ if (!hasColPercent)
+ apportion2 (totalWidth, forceTotalWidth);
+
+ setCumHeight (0, 0);
+ for (int row = 0; row < numRows; row++) {
+ /**
+ * \bug dw::Table::baseline is not filled.
+ */
+ int rowHeight = 0;
+
+ for (int col = 0; col < numCols; col++) {
+ int n = row * numCols + col;
+ if (childDefined (n)) {
+ int width = (children->get(n)->cell.colspanEff - 1)
+ * getStyle()->hBorderSpacing;
+ for (int i = 0; i < children->get(n)->cell.colspanEff; i++)
+ width += colWidths->get (col + i);
+
+ core::Requisition childRequisition;
+ children->get(n)->cell.widget->setWidth (width);
+ children->get(n)->cell.widget->sizeRequest (&childRequisition);
+ childHeight = childRequisition.ascent + childRequisition.descent;
+ if (children->get(n)->cell.rowspan == 1) {
+ rowHeight = MAX (rowHeight, childHeight);
+ } else {
+ rowSpanCells->increase();
+ rowSpanCells->set(rowSpanCells->size()-1, n);
+ }
+ }
+ }/*for col*/
+
+ setCumHeight (row + 1,
+ cumHeight->get (row) + rowHeight + getStyle()->vBorderSpacing);
+
+ }/*for row*/
+
+ apportionRowSpan ();
+}
+
+void Table::apportionRowSpan ()
+{
+ int *rowHeight = NULL;
+
+ for (int c = 0; c < rowSpanCells->size(); ++c) {
+ int n = rowSpanCells->get(c);
+ int row = n / numCols;
+ int rs = children->get(n)->cell.rowspan;
+ int sumRows = cumHeight->get(row+rs) - cumHeight->get(row);
+ core::Requisition childRequisition;
+ children->get(n)->cell.widget->sizeRequest (&childRequisition);
+ int spanHeight = childRequisition.ascent + childRequisition.descent
+ + getStyle()->vBorderSpacing;
+ if (sumRows >= spanHeight)
+ continue;
+
+ // Cell size is too small.
+#ifdef DBG
+ printf("Short cell %d, sumRows=%d spanHeight=%d\n",
+ n,sumRows,spanHeight);
+#endif
+ // Fill height array
+ if (!rowHeight) {
+ rowHeight = new int[numRows];
+ for (int i = 0; i < numRows; i++)
+ rowHeight[i] = cumHeight->get(i+1) - cumHeight->get(i);
+ }
+#ifdef DBG
+ printf (" rowHeight { ");
+ for (int i = 0; i < numRows; i++)
+ printf ("%d ", rowHeight[i]);
+ printf ("}\n");
+#endif
+
+ // Calc new row sizes for this span.
+ int cumHnew_i = 0, cumh_i = 0, hnew_i;
+ for (int i = row; i < row + rs; ++i) {
+ hnew_i =
+ sumRows == 0 ? (int)((float)(spanHeight-cumHnew_i)/(row+rs-i)) :
+ (sumRows-cumh_i) <= 0 ? 0 :
+ (int)((float)(spanHeight-cumHnew_i)*rowHeight[i]/(sumRows-cumh_i));
+#ifdef DBG
+ printf (" i=%-3d h=%d hnew_i=%d =%d*%d/%d cumh_i=%d cumHnew_i=%d\n",
+ i,rowHeight[i],hnew_i,
+ spanHeight-cumHnew_i,rowHeight[i],sumRows-cumh_i,
+ cumh_i, cumHnew_i);
+#endif
+ cumHnew_i += hnew_i;
+ cumh_i += rowHeight[i];
+ rowHeight[i] = hnew_i;
+ }
+ // Update cumHeight
+ for (int i = 0; i < numRows; ++i)
+ setCumHeight (i+1, cumHeight->get(i) + rowHeight[i]);
+ }
+ delete[] rowHeight;
+}
+
+
+/**
+ * \brief Fills dw::Table::colExtremes, only if recalculation is necessary.
+ *
+ * \bug Some parts are missing.
+ */
+void Table::calcColumnExtremes ()
+{
+ if (extremesChanged ())
+ forceCalcColumnExtremes ();
+}
+
+
+/**
+ * \brief Fills dw::Table::colExtremes in all cases.
+ */
+void Table::forceCalcColumnExtremes ()
+{
+#ifdef DBG
+ printf(" Table::forceCalcColumnExtremes numCols=%d\n", numCols);
+#endif
+ if (numCols == 0)
+ return;
+
+ colExtremes->setSize (numCols);
+ colPercents->setSize (numCols);
+ colSpanCells->setSize (0);
+ /* 1. cells with colspan = 1 */
+ for (int col = 0; col < numCols; col++) {
+ colExtremes->getRef(col)->minWidth = 0;
+ colExtremes->getRef(col)->maxWidth = 0;
+ colPercents->set(col, LEN_AUTO);
+
+ for (int row = 0; row < numRows; row++) {
+ int n = row * numCols + col;
+ if (!childDefined (n))
+ continue;
+ if (children->get(n)->cell.colspanEff == 1) {
+ core::Extremes cellExtremes;
+ int cellMinW, cellMaxW, pbm;
+ core::style::Length width =
+ children->get(n)->cell.widget->getStyle()->width;
+ pbm = (numCols + 1) * getStyle()->hBorderSpacing
+ + children->get(n)->cell.widget->getStyle()->boxDiffWidth ();
+ children->get(n)->cell.widget->getExtremes (&cellExtremes);
+ if (core::style::isAbsLength (width)) {
+ // Fixed lengths include table padding, border and margin.
+ cellMinW = cellExtremes.minWidth;
+ cellMaxW = MAX (cellMinW,
+ core::style::absLengthVal(width) - pbm);
+ } else {
+ cellMinW = cellExtremes.minWidth;
+ cellMaxW = cellExtremes.maxWidth;
+ }
+#ifdef DBG
+ printf("FCCE, col%d colMin,colMax,cellMin,cellMax = %d,%d,%d,%d\n",
+ col,
+ colExtremes->getRef(col)->minWidth,
+ colExtremes->getRef(col)->maxWidth,
+ cellMinW, cellMaxW);
+#endif
+ colExtremes->getRef(col)->minWidth =
+ MAX (colExtremes->getRef(col)->minWidth, cellMinW);
+ colExtremes->getRef(col)->maxWidth =
+ MAX (colExtremes->getRef(col)->minWidth, MAX (
+ colExtremes->getRef(col)->maxWidth,
+ cellMaxW));
+
+ // Also fill the colPercents array in this pass
+ if (core::style::isPerLength (width)) {
+ hasColPercent = 1;
+ if (colPercents->get(col) == LEN_AUTO)
+ colPercents->set(col, core::style::perLengthVal(width));
+ } else if (core::style::isAbsLength (width)) {
+ // We treat LEN_ABS as a special case of LEN_AUTO.
+ /*
+ * if (colPercents->get(col) == LEN_AUTO)
+ * colPercents->set(col, LEN_ABS);
+ */
+ }
+ } else {
+ colSpanCells->increase();
+ colSpanCells->set(colSpanCells->size()-1, n);
+ }
+ }
+ }
+
+ /* 2. cells with colspan > 1 */
+ /* If needed, here we set proportionally apportioned col maximums */
+ for (int c = 0; c < colSpanCells->size(); ++c) {
+ core::Extremes cellExtremes;
+ int cellMinW, cellMaxW, pbm;
+ int n = colSpanCells->get(c);
+ int col = n % numCols;
+ int cs = children->get(n)->cell.colspanEff;
+ core::style::Length width =
+ children->get(n)->cell.widget->getStyle()->width;
+ pbm = (numCols + 1) * getStyle()->hBorderSpacing
+ + children->get(n)->cell.widget->getStyle()->boxDiffWidth ();
+ children->get(n)->cell.widget->getExtremes (&cellExtremes);
+ if (core::style::isAbsLength (width)) {
+ // Fixed lengths include table padding, border and margin.
+ cellMinW = cellExtremes.minWidth;
+ cellMaxW = MAX (cellMinW, core::style::absLengthVal(width) - pbm);
+ } else {
+ cellMinW = cellExtremes.minWidth;
+ cellMaxW = cellExtremes.maxWidth;
+ }
+ int minSumCols = 0, maxSumCols = 0;
+ for (int i = 0; i < cs; ++i) {
+ minSumCols += colExtremes->getRef(col+i)->minWidth;
+ maxSumCols += colExtremes->getRef(col+i)->maxWidth;
+ }
+#ifdef DBG
+ printf("cs=%d spanWidth=%d,%d sumCols=%d,%d\n",
+ cs,cellMinW,cellMaxW,minSumCols,maxSumCols);
+#endif
+ if (minSumCols >= cellMinW && maxSumCols >= cellMaxW)
+ continue;
+
+ // Cell size is too small; apportion {min,max} for this colspan.
+ int spanMinW = MAX (MAX(cs, minSumCols),
+ cellMinW - (cs-1) * getStyle()->hBorderSpacing),
+ spanMaxW = MAX (MAX(cs, maxSumCols),
+ cellMaxW - (cs-1) * getStyle()->hBorderSpacing);
+
+ if (minSumCols == 0) {
+ // No single cells defined for this span => pre-apportion equally
+ minSumCols = spanMinW; maxSumCols = spanMaxW;
+ int minW = spanMinW, maxW = spanMaxW;
+ for (int i = 0; i < cs; ++i) {
+ colExtremes->getRef(col+i)->minWidth = minW / (cs - i);
+ colExtremes->getRef(col+i)->maxWidth = maxW / (cs - i);
+ minW -= colExtremes->getRef(col+i)->minWidth;
+ maxW -= colExtremes->getRef(col+i)->maxWidth;
+ }
+ }
+
+ // This numbers will help if the span has percents.
+ int spanHasColPercent = 0;
+ int availSpanMinW = spanMinW;
+ float cumSpanPercent = 0.0f;
+ for (int i = col; i < col + cs; ++i) {
+ if (colPercents->get(i) > 0.0f) {
+ cumSpanPercent += colPercents->get(i);
+ ++spanHasColPercent;
+ } else
+ availSpanMinW -= colExtremes->getRef(i)->minWidth;
+ }
+
+ // Calculate weighted-apportion columns for this span.
+ int wMin = 0, wMax;
+ int cumMaxWnew = 0, cumMaxWold = 0, goalMaxW = spanMaxW;
+ int curAppW = maxSumCols;
+ int curExtraW = spanMinW - minSumCols;
+ for (int i = col; i < col + cs; ++i) {
+
+ if (!spanHasColPercent) {
+ int d_a = colExtremes->getRef(i)->maxWidth;
+ int d_w = curAppW > 0 ? (int)((float)curExtraW * d_a/curAppW) : 0;
+ if (d_a < 0||d_w < 0) {
+ printf("d_a=%d d_w=%d\n",d_a,d_w);
+ exit(1);
+ }
+ wMin = colExtremes->getRef(i)->minWidth + d_w;
+ colExtremes->getRef(i)->minWidth = wMin;
+ curExtraW -= d_w;
+ curAppW -= d_a;
+ } else {
+ if (colPercents->get(i) > 0.0f) {
+ wMin = MAX (colExtremes->getRef(i)->minWidth,
+ (int)(availSpanMinW
+ * colPercents->get(i)/cumSpanPercent));
+ colExtremes->getRef(i)->minWidth = wMin;
+ }
+ }
+
+ wMax = (goalMaxW-cumMaxWnew <= 0) ? 0 :
+ (int)((float)(goalMaxW-cumMaxWnew)
+ * colExtremes->getRef(i)->maxWidth
+ / (maxSumCols-cumMaxWold));
+ wMax = MAX (wMin, wMax);
+ cumMaxWnew += wMax;
+ cumMaxWold += colExtremes->getRef(i)->maxWidth;
+ colExtremes->getRef(i)->maxWidth = wMax;
+#ifdef DBG
+ printf ("i=%d, wMin=%d wMax=%d cumMaxWold=%d\n",
+ i,wMin,wMax,cumMaxWold);
+#endif
+ }
+#ifdef DBG
+ printf ("col min,max: [");
+ for (int i = 0; i < numCols; i++)
+ printf ("%d,%d ",
+ colExtremes->getRef(i)->minWidth,
+ colExtremes->getRef(i)->maxWidth);
+ printf ("]\n");
+ printf ("getStyle()->hBorderSpacing = %d\n", getStyle()->hBorderSpacing);
+#endif
+ }
+}
+
+/**
+ * \brief Apportionment function for AUTO-length columns.
+ * 'extremes' comes filled, 'result' comes defined for percentage columns.
+ */
+void Table::apportion2 (int totalWidth, int forceTotalWidth)
+{
+ if (colExtremes->size() == 0)
+ return;
+#ifdef DBG
+ printf("app2, availWidth=%d, totalWidth=%d, forceTotalWidth=%d\n",
+ availWidth, totalWidth, forceTotalWidth);
+ printf("app2, extremes: ( ");
+ for (int i = 0; i < colExtremes->size (); i++)
+ printf("%d,%d ",
+ colExtremes->get(i).minWidth, colExtremes->get(i).maxWidth);
+ printf(")\n");
+#endif
+ int minAutoWidth = 0, maxAutoWidth = 0, availAutoWidth = totalWidth;
+ for (int col = 0; col < numCols; col++) {
+ if (colPercents->get(col) == LEN_ABS) { // set absolute lengths
+ setColWidth (col, colExtremes->get(col).minWidth);
+ }
+ if (colPercents->get(col) == LEN_AUTO) {
+ maxAutoWidth += colExtremes->get(col).maxWidth;
+ minAutoWidth += colExtremes->get(col).minWidth;
+ } else
+ availAutoWidth -= colWidths->get(col);
+ }
+
+ if (!maxAutoWidth) // no LEN_AUTO cols!
+ return;
+
+ colWidths->setSize (colExtremes->size (), 0);
+
+ if (!forceTotalWidth && maxAutoWidth < availAutoWidth) {
+ // Enough space for the maximum table, don't widen past max.
+ availAutoWidth = maxAutoWidth;
+ }
+
+ // General case.
+ int curTargetWidth = MAX (availAutoWidth, minAutoWidth);
+ int curExtraWidth = curTargetWidth - minAutoWidth;
+ int curMaxWidth = maxAutoWidth;
+ int curNewWidth = minAutoWidth;
+ for (int col = 0; col < numCols; col++) {
+#ifdef DBG
+ printf("app2, col %d, minWidth=%d maxWidth=%d\n",
+ col,extremes->get(col).minWidth, colExtremes->get(col).maxWidth);
+#endif
+ if (colPercents->get(col) != LEN_AUTO)
+ continue;
+
+ int colMinWidth = colExtremes->getRef(col)->minWidth;
+ int colMaxWidth = colExtremes->getRef(col)->maxWidth;
+ int w = (curMaxWidth <= 0) ? 0 :
+ (int)((float)curTargetWidth * colMaxWidth/curMaxWidth);
+#ifdef DBG
+ printf("app2, curTargetWidth=%d colMaxWidth=%d curMaxWidth=%d "
+ "curNewWidth=%d ",
+ curTargetWidth, colMaxWidth,curMaxWidth,curNewWidth);
+ printf("w = %d, ", w);
+#endif
+ if (w <= colMinWidth)
+ w = colMinWidth;
+ else if (curNewWidth - colMinWidth + w > curTargetWidth)
+ w = colMinWidth + curExtraWidth;
+#ifdef DBG
+ printf("w = %d\n", w);
+#endif
+ curNewWidth -= colMinWidth;
+ curMaxWidth -= colMaxWidth;
+ curExtraWidth -= (w - colMinWidth);
+ curTargetWidth -= w;
+ setColWidth (col, w);
+ }
+#ifdef DBG
+ printf("app2, result: ( ");
+ for (int i = 0; i < colWidths->size (); i++)
+ printf("%d ", colWidths->get (i));
+ printf(")\n");
+#endif
+}
+
+void Table::apportion_percentages2(int totalWidth, int forceTotalWidth)
+{
+ int hasTablePercent = core::style::isPerLength (getStyle()->width) ? 1 : 0;
+
+ if (colExtremes->size() == 0 || (!hasTablePercent && !hasColPercent))
+ return;
+
+ // If there's a table-wide percentage, totalWidth comes already scaled.
+#ifdef DBG
+ printf("APP_P, availWidth=%d, totalWidth=%d, forceTotalWidth=%d\n",
+ availWidth, totalWidth, forceTotalWidth);
+#endif
+
+ if (!hasColPercent) {
+#ifdef DBG
+ printf("APP_P, only a table-wide percentage\n");
+ printf("APP_P, extremes = { ");
+ for (int col = 0; col < numCols; col++)
+ printf("%d,%d ", colExtremes->getRef(col)->minWidth,
+ colExtremes->getRef(col)->maxWidth);
+ printf("}\n");
+#endif
+ // It has only a table-wide percentage. Apportion non-absolute widths.
+ int sumMaxWidth = 0, perAvailWidth = totalWidth;
+ for (int col = 0; col < numCols; col++) {
+ if (colPercents->get(col) == LEN_ABS)
+ perAvailWidth -= colExtremes->getRef(col)->maxWidth;
+ else
+ sumMaxWidth += colExtremes->getRef(col)->maxWidth;
+ }
+#ifdef DBG
+ printf("APP_P, perAvailWidth=%d, sumMaxWidth=%d\n",
+ perAvailWidth, sumMaxWidth);
+#endif
+ for (int col = 0; col < numCols; col++) {
+ int max_wi = colExtremes->getRef(col)->maxWidth, new_wi;
+ if (colPercents->get(col) != LEN_ABS) {
+ new_wi = MAX (colExtremes->getRef(col)->minWidth,
+ (int)((float)max_wi * perAvailWidth/sumMaxWidth));
+ setColWidth (col, new_wi);
+ perAvailWidth -= new_wi;
+ sumMaxWidth -= max_wi;
+ }
+ }
+#ifdef DBG
+ printf("APP_P, result = { ");
+ for (int col = 0; col < numCols; col++)
+ printf("%d ", result->get(col));
+ printf("}\n");
+#endif
+
+ } else {
+ // we'll have to apportion...
+#ifdef DBG
+ printf("APP_P, we'll have to apportion...\n");
+#endif
+ // Calculate cumPercent and available space
+ float cumPercent = 0.0f;
+ int hasAutoCol = 0;
+ int sumMinWidth = 0, sumMaxWidth = 0, sumMinNonPer = 0, sumMaxNonPer = 0;
+ for (int col = 0; col < numCols; col++) {
+ if (colPercents->get(col) > 0.0f) {
+ cumPercent += colPercents->get(col);
+ } else {
+ sumMinNonPer += colExtremes->getRef(col)->minWidth;
+ sumMaxNonPer += colExtremes->getRef(col)->maxWidth;
+ hasAutoCol += (colPercents->get(col) == LEN_AUTO);
+ }
+ sumMinWidth += colExtremes->getRef(col)->minWidth;
+ sumMaxWidth += colExtremes->getRef(col)->maxWidth;
+#ifdef DBG
+ printf("APP_P, col %d minWidth=%d maxWidth=%d\n", col,
+ colExtremes->getRef(col)->minWidth,
+ colExtremes->getRef(col)->maxWidth);
+#endif
+ }
+ int oldTotalWidth = totalWidth;
+ if (!forceTotalWidth) {
+ if (sumMaxNonPer == 0 || cumPercent < 0.99f) {
+ // only percentage columns, or cumPercent < 100% => restrict width
+ int totW = (int)(sumMaxNonPer/(1.0f-cumPercent));
+ for (int col = 0; col < numCols; col++) {
+ totW = MAX (totW, (int)(colExtremes->getRef(col)->maxWidth
+ / colPercents->get(col)));
+ }
+ totalWidth = misc::min (totW, totalWidth);
+ }
+ }
+
+ // make sure there's enough space
+ totalWidth = MAX (totalWidth, sumMinWidth);
+ // extraWidth is always >= 0
+ int extraWidth = totalWidth - sumMinWidth;
+ int sumMinWidthPer = sumMinWidth - sumMinNonPer;
+ int curPerWidth = sumMinWidthPer;
+ // percentages refer to workingWidth
+ int workingWidth = totalWidth - sumMinNonPer;
+ if (cumPercent < 0.99f) {
+ // In this case, use the whole table width
+ workingWidth = totalWidth;
+ curPerWidth = sumMinWidth;
+ }
+#ifdef DBG
+ printf("APP_P, oldTotalWidth=%d totalWidth=%d"
+ " workingWidth=%d extraWidth=%d sumMinNonPer=%d\n",
+ oldTotalWidth,totalWidth,workingWidth,extraWidth,sumMinNonPer);
+#endif
+ for (int col = 0; col < numCols; col++) {
+ int colMinWidth = colExtremes->getRef(col)->minWidth;
+ if (colPercents->get(col) >= 0.0f) {
+ int w = (int)(workingWidth * colPercents->get(col));
+ if (w < colMinWidth)
+ w = colMinWidth;
+ else if (curPerWidth - colMinWidth + w > workingWidth)
+ w = colMinWidth + extraWidth;
+ extraWidth -= (w - colMinWidth);
+ curPerWidth += (w - colMinWidth);
+ setColWidth (col, w);
+ } else {
+ setColWidth (col, colMinWidth);
+ }
+ }
+
+ if (cumPercent < 0.99f) {
+ // Will have to apportion the other columns
+#ifdef DBG
+ printf("APP_P, extremes: ( ");
+ for (int i = 0; i < colExtremes->size (); i++)
+ printf("%d,%d ",
+ colExtremes->get(i).minWidth, colExtremes->get(i).maxWidth);
+ printf(")\n");
+#endif
+ curPerWidth -= sumMinNonPer;
+ int perWidth = (int)(curPerWidth/cumPercent);
+ totalWidth = MAX (totalWidth, perWidth);
+ totalWidth = misc::min (totalWidth, oldTotalWidth);
+#ifdef DBG
+ printf("APP_P, curPerWidth=%d perWidth=%d, totalWidth=%d\n",
+ curPerWidth, perWidth, totalWidth);
+#endif
+ if (hasAutoCol == 0) {
+ // Special case, cumPercent < 100% and no other columns to expand.
+ // We'll honor totalWidth by expanding the percentage cols.
+ int extraWidth = totalWidth - curPerWidth - sumMinNonPer;
+ for (int col = 0; col < numCols; col++) {
+ if (colPercents->get(col) >= 0.0f) {
+ int d = (int)(extraWidth * colPercents->get(col)/cumPercent);
+ setColWidth (col, colWidths->get(col) + d);
+ }
+ }
+ }
+ }
+#ifdef DBG
+ printf("APP_P, result ={ ");
+ for (int col = 0; col < numCols; col++)
+ printf("%d ", colWidths->get(col));
+ printf("}\n");
+#endif
+ apportion2 (totalWidth, 2);
+
+#ifdef DBG
+ printf("APP_P, percent={");
+ for (int col = 0; col < numCols; col++)
+ printf("%f ", colPercents->get(col));
+ printf("}\n");
+ printf("APP_P, result ={ ");
+ for (int col = 0; col < numCols; col++)
+ printf("%d ", colWidths->get(col));
+ printf("}\n");
+#endif
+ }
+}
+
+// ----------------------------------------------------------------------
+
+Table::TableIterator::TableIterator (Table *table,
+ core::Content::Type mask, bool atEnd):
+ core::Iterator (table, mask, atEnd)
+{
+ index = atEnd ? table->children->size () : -1;
+ content.type = atEnd ? core::Content::END : core::Content::START;
+}
+
+Table::TableIterator::TableIterator (Table *table,
+ core::Content::Type mask, int index):
+ core::Iterator (table, mask, false)
+{
+ this->index = index;
+
+ if (index < 0)
+ content.type = core::Content::START;
+ else if (index >= table->children->size ())
+ content.type = core::Content::END;
+ else {
+ content.type = core::Content::WIDGET;
+ content.widget = table->children->get(index)->cell.widget;
+ }
+}
+
+object::Object *Table::TableIterator::clone()
+{
+ return new TableIterator ((Table*)getWidget(), getMask(), index);
+}
+
+int Table::TableIterator::compareTo(misc::Comparable *other)
+{
+ return index - ((TableIterator*)other)->index;
+}
+
+bool Table::TableIterator::next ()
+{
+ Table *table = (Table*)getWidget();
+
+ if (content.type == core::Content::END)
+ return false;
+
+ // tables only contain widgets:
+ if ((getMask() & core::Content::WIDGET) == 0) {
+ content.type = core::Content::END;
+ return false;
+ }
+
+ do {
+ index++;
+ if (index >= table->children->size ()) {
+ content.type = core::Content::END;
+ return false;
+ }
+ } while (table->children->get(index) == NULL ||
+ table->children->get(index)->type != Child::CELL);
+
+ content.type = core::Content::WIDGET;
+ content.widget = table->children->get(index)->cell.widget;
+ return true;
+}
+
+bool Table::TableIterator::prev ()
+{
+ Table *table = (Table*)getWidget();
+
+ if (content.type == core::Content::START)
+ return false;
+
+ // tables only contain widgets:
+ if ((getMask() & core::Content::WIDGET) == 0) {
+ content.type = core::Content::START;
+ return false;
+ }
+
+ do {
+ index--;
+ if (index < 0) {
+ content.type = core::Content::START;
+ return false;
+ }
+ } while (table->children->get(index) == NULL ||
+ table->children->get(index)->type != Child::CELL);
+
+ content.type = core::Content::WIDGET;
+ content.widget = table->children->get(index)->cell.widget;
+ return true;
+}
+
+void Table::TableIterator::highlight (int start, int end,
+ core::HighlightLayer layer)
+{
+ /** todo Needs this an implementation? */
+}
+
+void Table::TableIterator::unhighlight (int direction,
+ core::HighlightLayer layer)
+{
+}
+
+void Table::TableIterator::getAllocation (int start, int end,
+ core::Allocation *allocation)
+{
+ /** \bug Not implemented. */
+}
+
+} // namespace dw
diff --git a/dw/table.hh b/dw/table.hh
new file mode 100644
index 00000000..ec2bacc8
--- /dev/null
+++ b/dw/table.hh
@@ -0,0 +1,486 @@
+#ifndef __DW_TABLE_HH__
+#define __DW_TABLE_HH__
+
+#include "core.hh"
+#include "tablecell.hh"
+#include "../lout/misc.hh"
+
+namespace dw {
+
+/**
+ * \brief A Widget for rendering tables.
+ *
+ * <h3>Introduction</h3>
+ *
+ * The dw::Table widget is used to render HTML tables.
+ *
+ * Each cell is itself an own widget. Any widget may be used, however, in
+ * dillo, only instances of dw::Textblock and dw::TableCell are used as
+ * children of dw::Table.
+ *
+ *
+ * <h3>Sizes</h3>
+ *
+ * <h4>General</h4>
+ *
+ * The following diagram shows the dependencies between the different
+ * functions, which are related to size calculation. Click on the boxes
+ * for more informations.
+ *
+ * \dot
+ * digraph G {
+ * node [shape=record, fontname=Helvetica, fontsize=10, color="#c0c0c0"];
+ * edge [arrowhead="open", arrowtail="none", labelfontname=Helvetica,
+ * labelfontsize=10, color="#404040", labelfontcolor="#000080",
+ * fontname=Helvetica, fontsize=10];
+ * fontname=Helvetica; fontsize=10;
+ *
+ * sizeRequestImpl [color="#0000ff", URL="\ref dw::Table::sizeRequestImpl"];
+ * sizeAllocateImpl [color="#0000ff",
+ * URL="\ref dw::Table::sizeAllocateImpl"];
+ * getExtremesImpl [color="#0000ff", URL="\ref dw::Table::getExtremesImpl"];
+ *
+ * subgraph cluster_sizes {
+ * style="dashed"; color="#8080c0";
+ * calcCellSizes [URL="\ref dw::Table::calcCellSizes"];
+ * forceCalcCellSizes [URL="\ref dw::Table::forceCalcCellSizes"];
+ * }
+ *
+ * subgraph cluster_extremes {
+ * style="dashed"; color="#8080c0";
+ * calcColumnExtremes [URL="\ref dw::Table::calcColumnExtremes"];
+ * forceCalcColumnExtremes[URL="\ref dw::Table::forceCalcColumnExtremes"];
+ * }
+ *
+ * sizeRequestImpl -> forceCalcCellSizes [label="[B]"];
+ * sizeAllocateImpl -> calcCellSizes [label="[A]"];
+ * getExtremesImpl -> forceCalcColumnExtremes [label="[B]"];
+ *
+ * forceCalcCellSizes -> calcColumnExtremes;
+ *
+ * calcCellSizes -> forceCalcCellSizes [style="dashed", label="[C]"];
+ * calcColumnExtremes -> forceCalcColumnExtremes [style="dashed",
+ * label="[C]"];
+ * }
+ * \enddot
+ *
+ * [A] In this case, the new calculation is \em not forced, but only
+ * done, when necessary.
+ *
+ * [B] In this case, the new calculation is allways necessary, since [C]
+ * is the case.
+ *
+ * [C] Whether this function is called, depends on NEEDS_RESIZE /
+ * EXTREMES_CHANGED.
+ *
+ *
+ * <h4>Apportionment</h4>
+ *
+ * \sa\ref rounding-errors
+ *
+ * Given two array \f$e_{i,\min}\f$ and \f$e_{i,\max}\f$, which
+ * represent the column minima and maxima, and a total width \f$W\f$, \em
+ * apportionment means to calculate column widths \f$w_{i}\f$, with
+ *
+ * \f[e_{i,\min} \le w_{i} \le e_{i,\max}\f]
+ *
+ * and
+ *
+ * \f[\sum w_{i} = W\f]
+ *
+ * There are different algorithms for apportionment, a simple one is
+ * recommended in the HTML 4.0.1 specification
+ * (http://www.w3.org/TR/REC-html40/appendix/notes.html#h-B.5.2.2):
+ *
+ * \f[w_{i} = e_{i,\min} +
+ * {e_{i,\max} - e_{i,\min}\over\sum e_{i,\max} - \sum e_{i,\min}}
+ * (W - \sum e_{i,\min})\f]
+ *
+ * This one is used currently, but another one will be used soon, which is
+ * described below. The rest of this chapter is independant of the exact
+ * apportionment algorithm.
+ *
+ * When referring to the apportionment function, we will call it
+ * \f$a_i (W, (e_{i,\min}), (e_{i,\min}))\f$ and write
+ * something like this:
+ *
+ * \f[w_{i} = a_i (W, (e_{i,\min}), (e_{i,\max})) \f]
+ *
+ * It is implemented by dw::Table::apportion.
+ *
+ * <h4>Column Extremes</h4>
+ *
+ * \sa\ref rounding-errors
+ *
+ * The sizes, which all other sizes depend on, are column extremes, which
+ * define, how wide a column may be at min and at max. They are
+ * calculated in the following way:
+ *
+ * <ol>
+ * <li> First, only cells with colspan = 1 are regarded:
+ *
+ * \f[ e_{\hbox{base},i,\min} = \max \{ e_{\hbox{cell},i,j,\min} \} \f]
+ * \f[ e_{\hbox{base},i,\max} = \max \{ e_{\hbox{cell},i,j,\max} \} \f]
+ *
+ * only for cells \f$(i, j)\f$ with colspan = 1.
+ *
+ * <li> Then,
+ * \f$e_{\hbox{span},i,\min}\f$ (but not \f$e_{\hbox{span},i,\max}\f$)
+ * are calculated from cells with colspan > 1. (In the following formulas,
+ * the cell at \f$(i_1, j)\f$ always span from \f$i_1\f$ to \f$i_2\f$.)
+ * If the minimal width of the column exeeds the sum of the column minima
+ * calculated in the last step:
+ *
+ * \f[e_{\hbox{cell},i_1,j,\min} >
+ * \sum_{i=i_1}^{i=i_2} e_{\hbox{base},i,\min}\f]
+ *
+ * then the minimal width of this cell is apportioned to the columns:
+ *
+ * <ul>
+ * <li> If the minimal width of this cell also exeeds the sum of the
+ * column maxima:
+ *
+ * \f[e_{\hbox{cell},i_1,j,\min} >
+ * \sum_{i=i_1}^{i=i_2} e_{\hbox{base},i,\max}\f]
+ *
+ * then \f$e_{\hbox{cell},i_1,j,\min}\f$ is apportioned in a simple
+ * way:
+ *
+ * \f[e_{\hbox{span},i,j,\min} =
+ * e_{\hbox{base},i,\max}
+ * {e_{\hbox{span},i,j,\min} \over
+ * \sum_{i=i_1}^{i=i_2} e_{\hbox{base},i,\max}}\f]
+ *
+ * <li> Otherwise, the apportionment function is used:
+ *
+ * \f[e_{\hbox{span},i,j,\min} =
+ * a_i (e_{\hbox{cell},i_1,j,\min},
+ * (e_{\hbox{cell},i_1,j,\min} \ldots
+ * e_{\hbox{cell},i_2,j,\min}),
+ * (e_{\hbox{cell},i_1,j,\max} \ldots
+ * e_{\hbox{cell},i_2,j,\max}))\f]
+ * </ul>
+ *
+ * After this, \f$e_{\hbox{span},i,\min}\f$ is then the maximum of all
+ * \f$e_{\hbox{span},i,j,\min}\f$.
+ *
+ * <li> Finally, the maximum of both is used.
+ * \f[ e_{i,\min} =
+ * \max \{ e_{\hbox{base},i,\min}, e_{\hbox{span},i,\min} \} \f]
+ * \f[ e_{i,\max} =
+ * \max \{ e_{\hbox{base},i,\max}, e_{i,\min} \} \f]
+ * For the maxima, there is no \f$e_{\hbox{span},i,\max}\f$, but it has to
+ * be assured, that the maximum is always greater or equal than/to the
+ * minimum.
+ * </ol>
+ *
+ * Generally, if absolute widths are speficied, they are, instead of the
+ * results of dw::core::Widget::getExtremes, taken for the minimal and
+ * maximal width of a cell (minus the box difference, i.e. the difference
+ * between content size and widget size). If the content width
+ * specification is smaller than the minimal content width of the widget
+ * (determined by dw::core::Widget::getExtremes), the latter is used
+ * instead.
+ *
+ * If percentage widths are specified, they are also collected, as column
+ * maxima. A similar method as for the extremes is used, for cells with
+ * colspan > 1:
+ *
+ * \f[w_{\hbox{span},i,j,\%} =
+ * a_i (w_{\hbox{cell},i_1,j,\%},
+ * (e_{\hbox{cell},i_1,j,\min} \ldots e_{\hbox{cell},i_2,j,\min}),
+ * (e_{\hbox{cell},i_1,j,\max} \ldots e_{\hbox{cell},i_2,j,\max}))\f]
+ *
+ * <h4>Cell Sizes</h4>
+ *
+ * <h5>Determining the Width of the Table</h5>
+ *
+ * The total width is
+ *
+ * <ul>
+ * <li> the specified absolute width of the table, when given, or
+ * <li> the available width (set by dw::Table::setWidth) times the specifies
+ * percentage width pf t(at max 100%), if the latter is given, or
+ * <li> otherwise the available width.
+ * </ul>
+ *
+ * In any case, it is corrected, if it is less than the minimal width
+ * (but not if it is greater than the maximal width).
+ *
+ * \bug The parantheses is not fully clear, look at the old code.
+ *
+ * Details on differences because of styles are omitted. Below, this
+ * total width is called \f$W\f$.
+ *
+ * <h5>Evaluating percentages</h5>
+ *
+ * The following algorithms are used to solve collisions between
+ * different size specifications (absolute and percentage). Generally,
+ * inherent sizes and specified absolute sizes are preferred.
+ *
+ * <ol>
+ * <li> First, calculate the sum of the minimal widths, for columns, where
+ * no percentage width has been specified. The difference to the total
+ * width is at max available to the columns with percentage width
+ * specifications:
+ *
+ * \f[W_{\hbox{columns}_\%,\hbox{available}} = W - \sum e_{i,\min}\f]
+ *
+ * with only those columns \f$i\f$ with no percentage width specification.
+ *
+ * <li> Then, calculate the sum of the widths, which the columns with
+ * percentage width specification would allocate, when fully adhering to
+ * then:
+ *
+ * \f[W_{\hbox{columns}_\%,\hbox{best}} = W \sum w_{i,\%}\f]
+ *
+ * with only those columns \f$i\f$ with a percentage width specification.
+ *
+ * <li> Two cases are distinguished:
+ *
+ * <ul>
+ * <li> \f$W_{\hbox{columns}_\%,\hbox{available}} \ge
+ * W_{\hbox{columns}_\%,\hbox{best}}\f$: In this case, the
+ * percentage widths can be used without any modification, by
+ * setting the extremes:
+ *
+ * \f[e_{i,\min} = e_{i,\max} = W w_{i,\%}\f]
+ *
+ * for only those columns \f$i\f$ with a percentage width
+ * specification.
+ *
+ * <li> \f$W_{\hbox{columns}_\%,\hbox{available}} <
+ * W_{\hbox{columns}_\%,\hbox{best}}\f$: In this case, the widths
+ * for these columns must be cut down:
+ *
+ * \f[e_{i,\min} = e_{i,\max} =
+ * w_{i,\%}
+ * {W_{\hbox{columns}_\%,\hbox{available}} \over
+ * w_{\hbox{total},\%}}\f]
+ *
+ * with
+ *
+ * \f[w_{\hbox{total},\%} = \sum w_{i,\%}\f]
+ *
+ * in both cases for only those columns \f$i\f$ with a percentage
+ * width specification.
+ * </ul>
+ * </ol>
+ *
+ * (\f$e_{i,\min}\f$ and \f$e_{i,\max}\f$ are set \em temporarily here,
+ * the notation should be a bit clearer.)
+ *
+ *
+ * <h5>Column Widths</h5>
+ *
+ * The column widths are now simply calculated by applying the
+ * apportenment function.
+ *
+ *
+ * <h5>Row Heights</h5>
+ *
+ * ...
+ *
+ * <h3>Alternative Apportionment Algorithm</h3>
+ *
+ * The algorithm described here tends to result in more homogeneous column
+ * widths.
+ *
+ * The following rule lead to well-defined \f$w_{i}\f$: All columns
+ * \f$i\f$ have have the same width \f$w\f$, except:
+ * <ul>
+ * <li> \f$w < e_{i,\min}\f$, or
+ * <li> \f$w > e_{i,\max}\f$.
+ * </ul>
+ *
+ * Furthermore, \f$w\f$ is
+ * <ul>
+ * <li> less than all \f$e_{i,\min}\f$ of columns not having \f$w\f$ as
+ * width, and
+ * <li> greater than all \f$e_{i,\min}\f$ of columns not having \f$w\f$ as
+ * width.
+ * </ul>
+ *
+ * Of course, \f$\sum w_{i} = W\f$ must be the case.
+ *
+ * Based on an initial value \f$w = {W\over n}\f$, \f$w\f$ can iteratively
+ * adjusted, based on these rules.
+ *
+ *
+ * <h3>Borders, Paddings, Spacing</h3>
+ *
+ * Currently, DwTable supports only the separated borders model (see CSS
+ * specification). Borders, paddings, spacing is done by creating
+ * dw::core::style::Style structures with values equivalent to following CSS:
+ *
+ * <pre>
+ * TABLE {
+ * border: outset \em table-border;
+ * border-collapse: separate;
+ * border-spacing: \em table-cellspacing;
+ * background-color: \em table-bgcolor;
+ * }
+ *
+ * TD TH {
+ * border: inset \em table-border;
+ * padding: \em table-cellspacing;
+ * background-color: \em td/th-bgcolor;
+ * }
+ * </pre>
+ *
+ * Here, \em foo-bar refers to the attribute \em bar of the tag \em foo foo.
+ * Look at the HTML parser for more details.
+ */
+class Table: public core::Widget
+{
+private:
+
+ struct Child
+ {
+ enum {
+ CELL, // cell starts here
+ SPAN_SPACE // part of a spanning cell
+ } type;
+ union {
+ struct {
+ core::Widget *widget;
+ int colspanOrig, colspanEff, rowspan;
+ } cell;
+ struct {
+ int startCol, startRow; // where the cell starts
+ } spanSpace;
+ };
+ };
+
+ class TableIterator: public core::Iterator
+ {
+ private:
+ int index;
+
+ public:
+ TableIterator (Table *table, core::Content::Type mask, bool atEnd);
+ TableIterator (Table *table, core::Content::Type mask, int index);
+
+ object::Object *clone();
+ int compareTo(misc::Comparable *other);
+
+ bool next ();
+ bool prev ();
+ void highlight (int start, int end, core::HighlightLayer layer);
+ void unhighlight (int direction, core::HighlightLayer layer);
+ void getAllocation (int start, int end, core::Allocation *allocation);
+ };
+
+ friend class TableIterator;
+
+ bool limitTextWidth, rowClosed;
+ int availWidth, availAscent, availDescent; // set by set...
+
+ int numRows, numCols, curRow, curCol;
+ misc::SimpleVector<Child*> *children;
+
+ int redrawX, redrawY;
+
+ /**
+ * \brief The extremes of all columns.
+ */
+ misc::SimpleVector<core::Extremes> *colExtremes;
+
+ /**
+ * \brief The widths of all columns.
+ */
+ misc::SimpleVector<int> *colWidths;
+
+ /**
+ * Row cumulative height array: cumHeight->size() is numRows + 1,
+ * cumHeight->get(0) is 0, cumHeight->get(numRows) is the total table
+ * height.
+ */
+ misc::SimpleVector<int> *cumHeight;
+ /**
+ * If a Cell has rowspan > 1, it goes into this array
+ */
+ misc::SimpleVector<int> *rowSpanCells;
+ /**
+ * If a Cell has colspan > 1, it goes into this array
+ */
+ misc::SimpleVector<int> *colSpanCells;
+ misc::SimpleVector<int> *baseline;
+
+ misc::SimpleVector<core::style::Style*> *rowStyle;
+
+ /**
+ * hasColPercent becomes true when any cell specifies a percentage width.
+ * A negative value in colPercents means LEN_AUTO or LEN_ABS.
+ */
+ enum { LEN_AUTO = -1, LEN_ABS = -2};
+ int hasColPercent;
+ misc::SimpleVector<float> *colPercents;
+
+ inline bool childDefined(int n)
+ {
+ return n < children->size() && children->get(n) != NULL &&
+ children->get(n)->type != Child::SPAN_SPACE;
+ }
+
+ void reallocChildren (int newNumCols, int newNumRows);
+
+ void calcCellSizes ();
+ void forceCalcCellSizes ();
+ void apportionRowSpan ();
+
+ void calcColumnExtremes ();
+ void forceCalcColumnExtremes ();
+
+ void apportion2 (int totalWidth, int forceTotalWidth);
+ void apportion_percentages2 (int totalWidth, int forceTotalWidth);
+
+ void setCumHeight (int row, int value)
+ {
+ if (value != cumHeight->get (row)) {
+ redrawY = misc::min ( redrawY, value );
+ cumHeight->set (row, value);
+ }
+ }
+
+ inline void setColWidth (int col, int value)
+ {
+ if (value != colWidths->get (col)) {
+ redrawX = misc::min (redrawX, value);
+ colWidths->set (col, value);
+ }
+ }
+
+protected:
+ void sizeRequestImpl (core::Requisition *requisition);
+ void getExtremesImpl (core::Extremes *extremes);
+ void sizeAllocateImpl (core::Allocation *allocation);
+ void resizeDrawImpl ();
+
+ void setWidth (int width);
+ void setAscent (int ascent);
+ void setDescent (int descent);
+ void draw (core::View *view, core::Rectangle *area);
+
+ //bool buttonPressImpl (core::EventButton *event);
+ //bool buttonReleaseImpl (core::EventButton *event);
+ //bool motionNotifyImpl (core::EventMotion *event);
+
+ void removeChild (Widget *child);
+
+public:
+ static int CLASS_ID;
+
+ Table(bool limitTextWidth);
+ ~Table();
+
+ core::Iterator *iterator (core::Content::Type mask, bool atEnd);
+
+ void addCell (Widget *widget, int colspan, int rowspan);
+ void addRow (core::style::Style *style);
+ TableCell *getCellRef ();
+};
+
+} // namespace dw
+
+#endif // __DW_TABLE_HH__
diff --git a/dw/tablecell.cc b/dw/tablecell.cc
new file mode 100644
index 00000000..b4d404f7
--- /dev/null
+++ b/dw/tablecell.cc
@@ -0,0 +1,108 @@
+/*
+ * Dillo Widget
+ *
+ * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+
+
+#include "tablecell.hh"
+#include <stdio.h>
+
+namespace dw {
+
+int TableCell::CLASS_ID = -1;
+
+TableCell::TableCell (TableCell *ref, bool limitTextWidth):
+ AlignedTextblock (limitTextWidth)
+{
+ registerName ("dw::TableCell", &CLASS_ID);
+
+ /** \bug ignoreLine1OffsetSometimes does not work? */
+ //ignoreLine1OffsetSometimes = true;
+ charWordIndex = -1;
+ setRefTextblock (ref);
+}
+
+TableCell::~TableCell()
+{
+}
+
+void TableCell::wordWrap(int wordIndex)
+{
+ Textblock::Word *word;
+ char *p;
+
+ Textblock::wordWrap (wordIndex);
+
+ if (charWordIndex == -1) {
+ word = words->getRef (wordIndex);
+ if (word->content.type == core::Content::TEXT) {
+ if ((p = strchr (word->content.text,
+ word->style->textAlignChar))) {
+ charWordIndex = wordIndex;
+ charWordPos = p - word->content.text + 1;
+ } else if (word->style->textAlignChar == ' ' &&
+ word->content.space) {
+ charWordIndex = wordIndex + 1;
+ charWordPos = 0;
+ }
+ }
+ }
+
+ if (wordIndex == charWordIndex)
+ updateValue ();
+}
+
+int TableCell::getValue ()
+{
+ Textblock::Word *word;
+ int i, wordIndex;
+ int w;
+
+ if (charWordIndex == -1)
+ wordIndex = words->size () -1;
+ else
+ wordIndex = charWordIndex;
+
+ w = 0;
+ for (i = 0; i < wordIndex; i++) {
+ word = words->getRef (i);
+ w += word->size.width + word->origSpace;
+ }
+
+ if (charWordIndex == -1) {
+ if (words->size () > 0) {
+ word = words->getRef (words->size () - 1);
+ w += word->size.width;
+ }
+ } else {
+ word = words->getRef (charWordIndex);
+ w += layout->textWidth (word->style->font, word->content.text,
+ charWordPos);
+ }
+
+ return w;
+}
+
+void TableCell::setMaxValue (int maxValue, int value)
+{
+ line1Offset = maxValue - value;
+ queueResize (0, true);
+}
+
+} // namespace dw
diff --git a/dw/tablecell.hh b/dw/tablecell.hh
new file mode 100644
index 00000000..318d1f4e
--- /dev/null
+++ b/dw/tablecell.hh
@@ -0,0 +1,29 @@
+#ifndef __DW_TABLECELL_HH__
+#define __DW_TABLECELL_HH__
+
+#include "core.hh"
+#include "alignedtextblock.hh"
+
+namespace dw {
+
+class TableCell: public AlignedTextblock
+{
+private:
+ int charWordIndex, charWordPos;
+
+protected:
+ void wordWrap(int wordIndex);
+
+ int getValue ();
+ void setMaxValue (int maxValue, int value);
+
+public:
+ static int CLASS_ID;
+
+ TableCell(TableCell *ref, bool limitTextWidth);
+ ~TableCell();
+};
+
+} // namespace dw
+
+#endif // __DW_TABLECELL_HH__
diff --git a/dw/textblock.cc b/dw/textblock.cc
new file mode 100644
index 00000000..0ada8027
--- /dev/null
+++ b/dw/textblock.cc
@@ -0,0 +1,2087 @@
+/*
+ * Dillo Widget
+ *
+ * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+
+
+#include "textblock.hh"
+#include "../lout/misc.hh"
+
+#include <stdio.h>
+#include <limits.h>
+
+namespace dw {
+
+int Textblock::CLASS_ID = -1;
+
+Textblock::Textblock (bool limitTextWidth)
+{
+ registerName ("dw::Textblock", &CLASS_ID);
+ setFlags (USES_HINTS);
+
+ listItem = false;
+ innerPadding = 0;
+ line1Offset = 0;
+ line1OffsetEff = 0;
+ ignoreLine1OffsetSometimes = false;
+ mustQueueResize = false;
+ redrawY = 0;
+ lastWordDrawn = 0;
+
+ /*
+ * The initial sizes of lines and words should not be
+ * too high, since this will waste much memory with tables
+ * containing many small cells. The few more calls to realloc
+ * should not decrease the speed considerably.
+ * (Current setting is for minimal memory usage. An interesting fact
+ * is that high values decrease speed due to memory handling overhead!)
+ * todo: Some tests would be useful.
+ */
+ lines = new misc::SimpleVector <Line> (1);
+ words = new misc::SimpleVector <Word> (1);
+
+ //DBG_OBJ_SET_NUM(page, "num_lines", num_lines);
+
+ lastLineWidth = 0;
+ lastLineParMin = 0;
+ lastLineParMax = 0;
+ wrapRef = -1;
+
+ //DBG_OBJ_SET_NUM(page, "last_line_width", last_line_width);
+ //DBG_OBJ_SET_NUM(page, "last_line_par_min", last_line_par_min);
+ //DBG_OBJ_SET_NUM(page, "last_line_par_max", last_line_par_max);
+ //DBG_OBJ_SET_NUM(page, "wrap_ref", wrap_ref);
+
+ hoverLink = -1;
+
+ // random values
+ availWidth = 100;
+ availAscent = 100;
+ availDescent = 0;
+
+ hoverTooltip = NULL;
+
+ this->limitTextWidth = limitTextWidth;
+
+ for (int layer = 0; layer < core::HIGHLIGHT_NUM_LAYERS; layer++) {
+ /* hlStart[layer].index > hlEnd[layer].index means no highlighting */
+ hlStart[layer].index = 1;
+ hlStart[layer].nChar = 0;
+ hlEnd[layer].index = 0;
+ hlEnd[layer].nChar = 0;
+ }
+}
+
+Textblock::~Textblock ()
+{
+ //_MSG ("Textblock::~Textblock\n");
+
+ for (int i = 0; i < words->size(); i++) {
+ Word *word = words->getRef (i);
+ if (word->content.type == core::Content::WIDGET)
+ delete word->content.widget;
+ else if (word->content.type == core::Content::ANCHOR)
+ /* This also frees the names (see removeAnchor() and related). */
+ removeAnchor(word->content.anchor);
+
+ word->style->unref ();
+ word->spaceStyle->unref ();
+ }
+
+ delete lines;
+ delete words;
+
+ /* Make sure we don't own widgets anymore. Necessary before call of
+ parent class destructor. (???) */
+ words = NULL;
+
+ //DBG_OBJ_SET_NUM(page, "num_lines", page->num_lines);
+}
+
+/**
+ * The ascent of a textblock is the ascent of the first line, plus
+ * padding/border/margin. This can be used to align the first lines
+ * of several textblocks in a horizontal line.
+ */
+void Textblock::sizeRequestImpl (core::Requisition *requisition)
+{
+ rewrap ();
+
+ if (lines->size () > 0) {
+ Line *lastLine = lines->getRef (lines->size () - 1);
+ requisition->width =
+ misc::max (lastLine->maxLineWidth, lastLineWidth);
+ /* Note: the break_space of the last line is ignored, so breaks
+ at the end of a textblock are not visible. */
+ requisition->ascent = lines->getRef(0)->ascent;
+ requisition->descent = lastLine->top
+ + lastLine->ascent + lastLine->descent - lines->getRef(0)->ascent;
+ } else {
+ requisition->width = lastLineWidth;
+ requisition->ascent = 0;
+ requisition->descent = 0;
+ }
+
+ requisition->width += innerPadding + getStyle()->boxDiffWidth ();
+ requisition->ascent += getStyle()->boxOffsetY ();
+ requisition->descent += getStyle()->boxRestHeight ();
+
+ if (requisition->width < availWidth)
+ requisition->width = availWidth;
+}
+
+/**
+ * Get the extremes of a word within a textblock.
+ */
+void Textblock::getWordExtremes (Word *word, core::Extremes *extremes)
+{
+ if (word->content.type == core::Content::WIDGET) {
+ if (word->content.widget->usesHints ())
+ word->content.widget->getExtremes (extremes);
+ else {
+ if (core::style::isPerLength
+ (word->content.widget->getStyle()->width)) {
+ extremes->minWidth = 0;
+ if (word->content.widget->hasContents ())
+ extremes->maxWidth = 1000000;
+ else
+ extremes->maxWidth = 0;
+ } else if (core::style::isAbsLength
+ (word->content.widget->getStyle()->width)) {
+ /* Fixed lengths are only applied to the content, so we have to
+ * add padding, border and margin. */
+ extremes->minWidth = extremes->maxWidth =
+ core::style::absLengthVal (word->content.widget->getStyle()
+ ->width)
+ + word->style->boxDiffWidth ();
+ } else
+ word->content.widget->getExtremes (extremes);
+ }
+ } else {
+ extremes->minWidth = word->size.width;
+ extremes->maxWidth = word->size.width;
+ }
+}
+
+void Textblock::getExtremesImpl (core::Extremes *extremes)
+{
+ core::Extremes wordExtremes;
+ Line *line;
+ Word *word;
+ int wordIndex, lineIndex;
+ int parMin, parMax;
+ bool nowrap;
+
+ //DBG_MSG (widget, "extremes", 0, "Dw_page_get_extremes");
+ //DBG_MSG_START (widget);
+
+ if (lines->size () == 0) {
+ /* empty page */
+ extremes->minWidth = 0;
+ extremes->maxWidth = 0;
+ } else if (wrapRef == -1) {
+ /* no rewrap necessary -> values in lines are up to date */
+ line = lines->getRef (lines->size () - 1);
+ /* Historical note: The former distinction between lines with and without
+ * words[first_word]->nowrap set is no longer necessary, since
+ * Dw_page_real_word_wrap sets max_word_min to the correct value in any
+ * case. */
+ extremes->minWidth = line->maxWordMin;
+ extremes->maxWidth = misc::max (line->maxParMax, lastLineParMax);
+ //DBG_MSG (widget, "extremes", 0, "simple case");
+ } else {
+ /* Calculate the extremes, based on the values in the line from
+ where a rewrap is necessary. */
+ //DBG_MSG (widget, "extremes", 0, "complex case");
+
+ if (wrapRef == 0) {
+ extremes->minWidth = 0;
+ extremes->maxWidth = 0;
+ parMin = 0;
+ parMax = 0;
+ } else {
+ line = lines->getRef (wrapRef);
+ extremes->minWidth = line->maxWordMin;
+ extremes->maxWidth = line->maxParMax;
+ parMin = line->parMin;
+ parMax = line->parMax;
+
+ //DBG_MSGF (widget, "extremes", 0, "parMin = %d", parMin);
+ }
+
+ //_MSG ("*** parMin = %d\n", parMin);
+
+ int prevWordSpace = 0;
+ for (lineIndex = wrapRef; lineIndex < lines->size (); lineIndex++) {
+ //DBG_MSGF (widget, "extremes", 0, "line %d", lineIndex);
+ //DBG_MSG_START (widget);
+
+ line = lines->getRef (lineIndex);
+ nowrap =
+ words->getRef(line->firstWord)->style->whiteSpace
+ != core::style::WHITE_SPACE_NORMAL;
+
+ //DEBUG_MSG (DEBUG_SIZE_LEVEL, " line %d (of %d), nowrap = %d\n",
+ // lineIndex, page->num_lines, nowrap);
+
+ for (wordIndex = line->firstWord; wordIndex < line->lastWord;
+ wordIndex++) {
+ word = words->getRef (wordIndex);
+ getWordExtremes (word, &wordExtremes);
+
+ /* For the first word, we simply add the line1_offset. */
+ if (ignoreLine1OffsetSometimes && wordIndex == 0) {
+ wordExtremes.minWidth += line1Offset;
+ //DEBUG_MSG (DEBUG_SIZE_LEVEL + 1,
+ // " (next plus %d)\n", page->line1_offset);
+ }
+
+ if (nowrap) {
+ parMin += prevWordSpace + wordExtremes.minWidth;
+ //DBG_MSGF (widget, "extremes", 0, "parMin = %d", parMin);
+ } else {
+ if (extremes->minWidth < wordExtremes.minWidth)
+ extremes->minWidth = wordExtremes.minWidth;
+ }
+
+ //printf("parMax = %d, wordMaxWidth=%d, prevWordSpace=%d\n",
+ // parMax, wordExtremes.maxWidth, prevWordSpace);
+ if (word->content.type != core::Content::BREAK)
+ parMax += prevWordSpace;
+ parMax += wordExtremes.maxWidth;
+ prevWordSpace = word->origSpace;
+
+ //DEBUG_MSG (DEBUG_SIZE_LEVEL + 1,
+ // " word %s: maxWidth = %d\n",
+ // a_Dw_content_text (&word->content),
+ // word_extremes.maxWidth);
+ }
+
+ if ((line->lastWord > line->firstWord &&
+ words->getRef(line->lastWord - 1)->content.type
+ == core::Content::BREAK ) ||
+ lineIndex == lines->size () - 1 ) {
+ word = words->getRef (line->lastWord - 1);
+
+ //DEBUG_MSG (DEBUG_SIZE_LEVEL + 2,
+ // " parMax = %d, after word %d (%s)\n",
+ // parMax, line->last_word - 1,
+ // a_Dw_content_text (&word->content));
+
+ if (extremes->maxWidth < parMax)
+ extremes->maxWidth = parMax;
+
+ if (nowrap) {
+ //DBG_MSGF (widget, "extremes", 0, "parMin = %d", parMin);
+ if (extremes->minWidth < parMin)
+ extremes->minWidth = parMin;
+
+ //DEBUG_MSG (DEBUG_SIZE_LEVEL + 2,
+ // " parMin = %d, after word %d (%s)\n",
+ // parMin, line->last_word - 1,
+ // a_Dw_content_text (&word->content));
+ }
+
+ prevWordSpace = 0;
+ parMin = 0;
+ parMax = 0;
+ }
+
+ //DBG_MSG_END (widget);
+ }
+
+ //DEBUG_MSG (DEBUG_SIZE_LEVEL + 3, " Result: %d, %d\n",
+ // extremes->minWidth, extremes->maxWidth);
+ }
+
+ //DBG_MSGF (widget, "extremes", 0, "width difference: %d + %d",
+ // page->inner_padding, p_Dw_style_box_diff_width (widget->style));
+
+ int diff = innerPadding + getStyle()->boxDiffWidth ();
+ extremes->minWidth += diff;
+ extremes->maxWidth += diff;
+
+ //DBG_MSG_END (widget);
+}
+
+
+void Textblock::sizeAllocateImpl (core::Allocation *allocation)
+{
+ int lineIndex, wordIndex;
+ Line *line;
+ Word *word;
+ int xCursor;
+ core::Allocation childAllocation;
+ core::Allocation *oldChildAllocation;
+ int wordInLine;
+
+ if (allocation->width != this->allocation.width) {
+ redrawY = 0;
+ }
+
+ for (lineIndex = 0; lineIndex < lines->size (); lineIndex++) {
+ line = lines->getRef (lineIndex);
+ xCursor = lineXOffsetWidget (line);
+
+ wordInLine = 0;
+ for (wordIndex = line->firstWord; wordIndex < line->lastWord;
+ wordIndex++) {
+ word = words->getRef (wordIndex);
+
+ if (wordIndex == lastWordDrawn) {
+ redrawY = misc::min (redrawY, lineYOffsetWidget (line));
+ }
+
+ switch (word->content.type) {
+ case core::Content::WIDGET:
+ /** \todo Justification within the line is done here. */
+ childAllocation.x = xCursor + allocation->x;
+ /* align=top:
+ childAllocation.y = line->top + allocation->y;
+ */
+
+ /* align=bottom (base line) */
+ /* Commented lines break the n2 and n3 test cases at
+ * http://www.dillo.org/test/img/ */
+ childAllocation.y =
+ lineYOffsetCanvasAllocation (line, allocation)
+ + (line->ascent - word->size.ascent);
+ // - word->content.widget->getStyle()->margin.top;
+ childAllocation.width = word->size.width;
+ childAllocation.ascent = word->size.ascent;
+ // + word->content.widget->getStyle()->margin.top;
+ childAllocation.descent = word->size.descent;
+ // + word->content.widget->getStyle()->margin.bottom;
+
+ oldChildAllocation = word->content.widget->getAllocation();
+
+ if (childAllocation.x != oldChildAllocation->x ||
+ childAllocation.y != oldChildAllocation->y ||
+ childAllocation.width != oldChildAllocation->width) {
+ /* The child widget has changed its position or its width
+ * so we need to redraw from this line onwards.
+ */
+ redrawY = misc::min (redrawY, lineYOffsetWidget (line));
+ if (word->content.widget->wasAllocated ()) {
+ redrawY = misc::min (redrawY,
+ oldChildAllocation->y - this->allocation.y);
+ }
+
+ } else if (childAllocation.ascent + childAllocation.descent !=
+ oldChildAllocation->ascent + oldChildAllocation->descent) {
+ /* The child widget has changed its height. We need to redraw
+ * from where it changed.
+ * It's important not to draw from the line base, because the
+ * child might be a table covering the whole page so we would
+ * end up redrawing the whole screen over and over.
+ * The drawing of the child content is left to the child itself.
+ */
+ int childChangedY =
+ misc::min(childAllocation.y - allocation->y +
+ childAllocation.ascent + childAllocation.descent,
+ oldChildAllocation->y - this->allocation.y +
+ oldChildAllocation->ascent + oldChildAllocation->descent);
+
+ redrawY = misc::min (redrawY, childChangedY);
+ }
+
+ word->content.widget->sizeAllocate (&childAllocation);
+ break;
+
+ case core::Content::ANCHOR:
+ changeAnchor (word->content.anchor,
+ lineYOffsetCanvasAllocation (line, allocation));
+ break;
+
+ default:
+ wordInLine++;
+ // make compiler happy
+ break;
+ }
+
+ xCursor += (word->size.width + word->effSpace);
+ }
+ }
+}
+
+void Textblock::resizeDrawImpl ()
+{
+ queueDrawArea (0, redrawY, allocation.width, getHeight () - redrawY);
+ if (lines->size () > 0) {
+ Line *lastLine = lines->getRef (lines->size () - 1);
+ /* Remember the last word that has been drawn so we can ensure to
+ * draw any new added words (see sizeAllocateImpl()).
+ */
+ lastWordDrawn = lastLine->lastWord;
+ }
+
+ redrawY = getHeight ();
+}
+
+void Textblock::markSizeChange (int ref)
+{
+ markChange (ref);
+}
+
+void Textblock::markExtremesChange (int ref)
+{
+ markChange (ref);
+}
+
+/*
+ * Implementation for both mark_size_change and mark_extremes_change.
+ */
+void Textblock::markChange (int ref)
+{
+ if (ref != -1) {
+ //DBG_MSGF (page, "wrap", 0, "Dw_page_mark_size_change (ref = %d)", ref);
+
+ if (wrapRef == -1)
+ wrapRef = ref;
+ else
+ wrapRef = misc::min (wrapRef, ref);
+
+ //DBG_OBJ_SET_NUM (page, "wrap_ref", page->wrap_ref);
+ }
+}
+
+void Textblock::setWidth (int width)
+{
+ /* If limitTextWidth is set to YES, a queue_resize may also be
+ * necessary. */
+ if (availWidth != width || limitTextWidth) {
+ //DEBUG_MSG(DEBUG_REWRAP_LEVEL,
+ // "Dw_page_set_width: Calling p_Dw_widget_queue_resize, "
+ // "in page with %d word(s)\n",
+ // page->num_words);
+
+ availWidth = width;
+ queueResize (0, false);
+ mustQueueResize = false;
+ redrawY = 0;
+ }
+}
+
+void Textblock::setAscent (int ascent)
+{
+ if (availAscent != ascent) {
+ //DEBUG_MSG(DEBUG_REWRAP_LEVEL,
+ // "Dw_page_set_ascent: Calling p_Dw_widget_queue_resize, "
+ // "in page with %d word(s)\n",
+ // page->num_words);
+
+ availAscent = ascent;
+ queueResize (0, false);
+ mustQueueResize = false;
+ }
+}
+
+void Textblock::setDescent (int descent)
+{
+ if (availDescent != descent) {
+ //DEBUG_MSG(DEBUG_REWRAP_LEVEL,
+ // "Dw_page_set_descent: Calling p_Dw_widget_queue_resize, "
+ // "in page with %d word(s)\n",
+ // page->num_words);
+
+ availDescent = descent;
+ queueResize (0, false);
+ mustQueueResize = false;
+ }
+}
+
+bool Textblock::buttonPressImpl (core::EventButton *event)
+{
+ return sendSelectionEvent (core::SelectionState::BUTTON_PRESS, event);
+}
+
+bool Textblock::buttonReleaseImpl (core::EventButton *event)
+{
+ return sendSelectionEvent (core::SelectionState::BUTTON_RELEASE, event);
+}
+
+bool Textblock::motionNotifyImpl (core::EventMotion *event)
+{
+ if (event->state & core::BUTTON1_MASK)
+ return sendSelectionEvent (core::SelectionState::BUTTON_MOTION, event);
+ else {
+ int linkOld, wordIndex;
+ core::style::Tooltip *tooltipOld;
+
+ wordIndex = findWord (event->xWidget, event->yWidget);
+
+ // cursor from word or widget style
+ if (wordIndex == -1)
+ setCursor (getStyle()->cursor);
+ else
+ setCursor (words->getRef(wordIndex)->style->cursor);
+
+ linkOld = hoverLink;
+ tooltipOld = hoverTooltip;
+
+ if (wordIndex == -1) {
+ hoverLink = -1;
+ hoverTooltip = NULL;
+ } else {
+ hoverLink = words->getRef(wordIndex)->style->x_link;
+ hoverTooltip = words->getRef(wordIndex)->style->x_tooltip;
+ }
+
+ // Show/hide tooltip
+ if (tooltipOld != hoverTooltip) {
+ if (tooltipOld)
+ tooltipOld->onLeave ();
+ if (hoverTooltip)
+ hoverTooltip->onEnter ();
+ } else if (hoverTooltip)
+ hoverTooltip->onMotion ();
+
+ if (hoverLink != linkOld)
+ return emitLinkEnter (hoverLink, -1, -1, -1);
+ else
+ return hoverLink != -1;
+ }
+}
+
+void Textblock::enterNotifyImpl (core::EventCrossing *event)
+{
+}
+
+void Textblock::leaveNotifyImpl (core::EventCrossing *event)
+{
+ hoverLink = -1;
+ (void) emitLinkEnter (hoverLink, -1, -1, -1);
+}
+
+/**
+ * \brief Send event to selection.
+ */
+bool Textblock::sendSelectionEvent (core::SelectionState::EventType eventType,
+ core::MousePositionEvent *event)
+{
+ core::Iterator *it;
+ Line *line, *lastLine;
+ int nextWordStartX, wordStartX, wordX, nextWordX, yFirst, yLast;
+ int charPos = 0, prevPos, wordIndex, lineIndex, link;
+ Word *word;
+ bool found, withinContent, r;
+
+ if (words->size () == 0)
+ // no contens at all
+ return false;
+
+ // In most cases true, so set here:
+ link = -1;
+ withinContent = true;
+
+ lastLine = lines->getRef (lines->size () - 1);
+ yFirst = lineYOffsetCanvasI (0);
+ yLast =
+ lineYOffsetCanvas (lastLine) + lastLine->ascent + lastLine->descent;
+ if (event->yCanvas < yFirst) {
+ // Above the first line: take the first word.
+ withinContent = false;
+ wordIndex = 0;
+ charPos = 0;
+ } else if (event->yCanvas >= yLast) {
+ // Below the last line: take the last word.
+ withinContent = false;
+ wordIndex = words->size () - 1;
+ word = words->getRef (wordIndex);
+ charPos = word->content.type == core::Content::TEXT ?
+ strlen (word->content.text) : 0;
+ } else {
+ lineIndex = findLineIndex (event->yWidget);
+ line = lines->getRef (lineIndex);
+
+ // Pointer within the break space?
+ if (event->yWidget >
+ (lineYOffsetWidget (line) + line->ascent + line->descent)) {
+ // Choose this break.
+ withinContent = false;
+ wordIndex = line->lastWord - 1;
+ charPos = 0;
+ } else if (event->xWidget < lineXOffsetWidget (line)) {
+ // Left of the first word in the line.
+ wordIndex = line->firstWord;
+ withinContent = false;
+ charPos = 0;
+ } else {
+ nextWordStartX = lineXOffsetWidget (line);
+ found = false;
+ for (wordIndex = line->firstWord;
+ !found && wordIndex < line->lastWord;
+ wordIndex++) {
+ word = words->getRef (wordIndex);
+ wordStartX = nextWordStartX;
+ nextWordStartX += word->size.width + word->effSpace;
+
+ if (event->xWidget >= wordStartX &&
+ event->xWidget < nextWordStartX) {
+ // We have found the word.
+ if (word->content.type == core::Content::TEXT) {
+ // Search the character the mouse pointer is in.
+ // nextWordX is the right side of this character.
+ charPos = 0;
+ while ((nextWordX = wordStartX +
+ layout->textWidth (word->style->font,
+ word->content.text, charPos))
+ <= event->xWidget)
+ charPos = layout->nextGlyph (word->content.text, charPos);
+
+ // The left side of this character.
+ prevPos = layout->prevGlyph (word->content.text, charPos);
+ wordX = wordStartX + layout->textWidth (word->style->font,
+ word->content.text,
+ prevPos);
+
+ // If the mouse pointer is left from the middle, use the left
+ // position, otherwise, use the right one.
+ if (event->xWidget <= (wordX + nextWordX) / 2)
+ charPos = prevPos;
+ } else {
+ // Depends on whether the pointer is within the left or
+ // right half of the (non-text) word.
+ if (event->xWidget >=
+ (wordStartX + nextWordStartX) / 2)
+ charPos = core::SelectionState::END_OF_WORD;
+ else
+ charPos = 0;
+ }
+
+ found = true;
+ link = word->style ? word->style->x_link : -1;
+ break;
+ }
+ }
+
+ if (!found) {
+ // No word found in this line (i.e. we are on the right side),
+ // take the last of this line.
+ withinContent = false;
+ wordIndex = line->lastWord - 1;
+ if (wordIndex >= words->size ())
+ wordIndex--;
+ word = words->getRef (wordIndex);
+ charPos = word->content.type == core::Content::TEXT ?
+ strlen (word->content.text) :
+ (int)core::SelectionState::END_OF_WORD;
+ }
+ }
+ }
+
+ it = new TextblockIterator (this, core::Content::SELECTION_CONTENT,
+ wordIndex);
+ r = selectionHandleEvent (eventType, it, charPos, link, event,
+ withinContent);
+ it->unref ();
+ return r;
+}
+
+void Textblock::removeChild (Widget *child)
+{
+ /** \bug Not implemented. */
+}
+
+core::Iterator *Textblock::iterator (core::Content::Type mask, bool atEnd)
+{
+ return new TextblockIterator (this, mask, atEnd);
+}
+
+/*
+ * ...
+ *
+ * availWidth is passed from wordWrap, to avoid calculating it twice.
+ */
+void Textblock::justifyLine (Line *line, int availWidth)
+{
+ /* To avoid rounding errors, the calculation is based on accumulated
+ * values (*_cum). */
+ int i;
+ int origSpaceSum, origSpaceCum;
+ int effSpaceDiffCum, lastEffSpaceDiffCum;
+ int diff;
+
+ diff = availWidth - lastLineWidth;
+ if (diff > 0) {
+ origSpaceSum = 0;
+ for (i = line->firstWord; i < line->lastWord - 1; i++)
+ origSpaceSum += words->getRef(i)->origSpace;
+
+ origSpaceCum = 0;
+ lastEffSpaceDiffCum = 0;
+ for (i = line->firstWord; i < line->lastWord - 1; i++) {
+ origSpaceCum += words->getRef(i)->origSpace;
+
+ if (origSpaceCum == 0)
+ effSpaceDiffCum = lastEffSpaceDiffCum;
+ else
+ effSpaceDiffCum = diff * origSpaceCum / origSpaceSum;
+
+ words->getRef(i)->effSpace = words->getRef(i)->origSpace +
+ (effSpaceDiffCum - lastEffSpaceDiffCum);
+ //DBG_OBJ_ARRSET_NUM (page, "words.%d.eff_space", i,
+ // page->words[i].eff_space);
+
+ lastEffSpaceDiffCum = effSpaceDiffCum;
+ }
+ }
+}
+
+
+void Textblock::addLine (int wordInd, bool newPar)
+{
+ Line *lastLine, *plastLine;
+
+ //DBG_MSG (page, "wrap", 0, "Dw_page_add_line");
+ //DBG_MSG_START (page);
+
+ lines->increase ();
+ //DBG_OBJ_SET_NUM(page, "num_lines", page->num_lines);
+
+ //DEBUG_MSG (DEBUG_REWRAP_LEVEL, "--- new line %d in %p, with word %d of %d"
+ // "\n", page->num_lines - 1, page, word_ind, page->num_words);
+
+ lastLine = lines->getRef (lines->size () - 1);
+
+ if (lines->size () == 1)
+ plastLine = NULL;
+ else
+ plastLine = lines->getRef (lines->size () - 2);
+
+ if (plastLine) {
+ /* second or more lines: copy values of last line */
+ lastLine->top =
+ plastLine->top + plastLine->ascent +
+ plastLine->descent + plastLine->breakSpace;
+ lastLine->maxLineWidth = plastLine->maxLineWidth;
+ lastLine->maxWordMin = plastLine->maxWordMin;
+ lastLine->maxParMax = plastLine->maxParMax;
+ lastLine->parMin = plastLine->parMin;
+ lastLine->parMax = plastLine->parMax;
+ } else {
+ /* first line: initialize values */
+ lastLine->top = 0;
+ lastLine->maxLineWidth = line1OffsetEff;
+ lastLine->maxWordMin = 0;
+ lastLine->maxParMax = 0;
+ lastLine->parMin = line1OffsetEff;
+ lastLine->parMax = line1OffsetEff;
+ }
+
+ //DBG_OBJ_ARRSET_NUM (page, "lines.%d.top", page->num_lines - 1,
+ // lastLine->top);
+ //DBG_OBJ_ARRSET_NUM (page, "lines.%d.maxLineWidth", page->num_lines - 1,
+ // lastLine->maxLineWidth);
+ //DBG_OBJ_ARRSET_NUM (page, "lines.%d.maxWordMin", page->num_lines - 1,
+ // lastLine->maxWordMin);
+ //DBG_OBJ_ARRSET_NUM (page, "lines.%d.maxParMax", page->num_lines - 1,
+ // lastLine->maxParMax);
+ //DBG_OBJ_ARRSET_NUM (page, "lines.%d.parMin", page->num_lines - 1,
+ // lastLine->parMin);
+ //DBG_OBJ_ARRSET_NUM (page, "lines.%d.parMax", page->num_lines - 1,
+ // lastLine->parMax);
+
+ lastLine->firstWord = wordInd;
+ lastLine->ascent = 0;
+ lastLine->descent = 0;
+ lastLine->marginDescent = 0;
+ lastLine->breakSpace = 0;
+ lastLine->leftOffset = 0;
+
+ //DBG_OBJ_ARRSET_NUM (page, "lines.%d.ascent", page->num_lines - 1,
+ // lastLine->ascent);
+ //DBG_OBJ_ARRSET_NUM (page, "lines.%d.descent", page->num_lines - 1,
+ // lastLine->descent);
+
+ /* update values in line */
+ lastLine->maxLineWidth = misc::max (lastLine->maxLineWidth, lastLineWidth);
+
+ if (lines->size () > 1)
+ lastLineWidth = 0;
+ else
+ lastLineWidth = line1OffsetEff;
+
+ if (newPar) {
+ lastLine->maxParMax = misc::max (lastLine->maxParMax, lastLineParMax);
+ //DBG_OBJ_ARRSET_NUM (page, "lines.%d.maxParMax", page->num_lines - 1,
+ // lastLine->maxParMax);
+
+ if (lines->size () > 1) {
+ lastLine->parMin = 0;
+ lastLine->parMax = 0;
+ } else {
+ lastLine->parMin = line1OffsetEff;
+ lastLine->parMax = line1OffsetEff;
+ }
+ lastLineParMin = 0;
+ lastLineParMax = 0;
+
+ //DBG_OBJ_SET_NUM(page, "lastLineParMin", page->lastLineParMin);
+ //DBG_OBJ_SET_NUM(page, "lastLineParMax", page->lastLineParMax);
+ }
+
+ lastLine->parMin = lastLineParMin;
+ lastLine->parMax = lastLineParMax;
+
+ //DBG_OBJ_ARRSET_NUM (page, "lines.%d.parMin", page->num_lines - 1,
+ // lastLine->parMin);
+ //DBG_OBJ_ARRSET_NUM (page, "lines.%d.parMax", page->num_lines - 1,
+ // lastLine->parMax);
+
+ //DBG_MSG_END (page);
+}
+
+/*
+ * This method is called in two cases: (i) when a word is added (by
+ * Dw_page_add_word), and (ii) when a page has to be (partially)
+ * rewrapped. It does word wrap, and adds new lines, if necesary.
+ */
+void Textblock::wordWrap(int wordIndex)
+{
+ Line *lastLine;
+ Word *word, *prevWord;
+ int availWidth, lastSpace, leftOffset;
+ bool newLine = false, newPar = false;
+ core::Extremes wordExtremes;
+
+ //DBG_MSGF (page, "wrap", 0, "Dw_page_real_word_wrap (%d): %s, width = %d",
+ // word_ind, a_Dw_content_html (&page->words[word_ind].content),
+ // page->words[word_ind].size.width);
+ //DBG_MSG_START (page);
+
+ availWidth = this->availWidth - getStyle()->boxDiffWidth() - innerPadding;
+ if (limitTextWidth &&
+ layout->getUsesViewport () &&
+ availWidth > layout->getWidthViewport () - 10)
+ availWidth = layout->getWidthViewport () - 10;
+
+ word = words->getRef (wordIndex);
+
+ if (lines->size () == 0) {
+ //DBG_MSG (page, "wrap", 0, "first line");
+ newLine = true;
+ newPar = true;
+ lastLine = NULL;
+ } else {
+ lastLine = lines->getRef (lines->size () - 1);
+
+ if (lines->size () > 0) {
+ prevWord = words->getRef (wordIndex - 1);
+ if (prevWord->content.type == core::Content::BREAK) {
+ //DBG_MSG (page, "wrap", 0, "after a break");
+ /* previous word is a break */
+ newLine = true;
+ newPar = true;
+ } else if (word->style->whiteSpace
+ != core::style::WHITE_SPACE_NORMAL) {
+ //DBG_MSGF (page, "wrap", 0, "no wrap (white_space = %d)",
+ // word->style->white_space);
+ newLine = false;
+ newPar = false;
+ } else {
+ if (lastLine->firstWord != wordIndex) {
+ /* Does new word fit into the last line? */
+ //DBG_MSGF (page, "wrap", 0,
+ // "word %d (%s) fits? (%d + %d + %d &lt;= %d)...",
+ // word_ind, a_Dw_content_html (&word->content),
+ // page->lastLine_width, prevWord->orig_space,
+ // word->size.width, availWidth);
+ newLine = (lastLineWidth + prevWord->origSpace
+ + word->size.width > availWidth);
+ //DBG_MSGF (page, "wrap", 0, "... %s.",
+ // newLine ? "No" : "Yes");
+ }
+ }
+ }
+ }
+
+ /* Has sometimes the wrong value. */
+ word->effSpace = word->origSpace;
+ //DBG_OBJ_ARRSET_NUM (page,"words.%d.eff_space", word_ind, word->eff_space);
+
+ /* Test, whether line1_offset can be used. */
+ if (wordIndex == 0) {
+ if (ignoreLine1OffsetSometimes) {
+ if (line1Offset + word->size.width > availWidth)
+ line1OffsetEff = 0;
+ else
+ line1OffsetEff = line1Offset;
+ } else
+ line1OffsetEff = line1Offset;
+ }
+
+ if (lastLine != NULL && newLine && !newPar &&
+ word->style->textAlign == core::style::TEXT_ALIGN_JUSTIFY)
+ justifyLine (lastLine, availWidth);
+
+ if (newLine) {
+ addLine (wordIndex, newPar);
+ lastLine = lines->getRef (lines->size () - 1);
+ }
+
+ lastLine->lastWord = wordIndex + 1;
+ lastLine->ascent = misc::max (lastLine->ascent, (int) word->size.ascent);
+ lastLine->descent = misc::max (lastLine->descent, (int) word->size.descent);
+
+ //DBG_OBJ_ARRSET_NUM (page, "lines.%d.ascent", page->num_lines - 1,
+ // lastLine->ascent);
+ //DBG_OBJ_ARRSET_NUM (page, "lines.%d.descent", page->num_lines - 1,
+ // lastLine->descent);
+
+ if (word->content.type == core::Content::WIDGET) {
+ lastLine->marginDescent =
+ misc::max (lastLine->marginDescent,
+ word->size.descent +
+ word->content.widget->getStyle()->margin.bottom);
+
+ //DBG_OBJ_ARRSET_NUM (page, "lines.%d.descent", page->num_lines - 1,
+ // lastLine->descent);
+
+ /* If the widget is not in the first line of the paragraph, its top
+ * margin may make the line higher.
+ */
+ if (lines->size () > 1) {
+ /* Here, we know already what the break and the bottom margin
+ * contributed to the space before this line.
+ */
+ lastLine->ascent =
+ misc::max (lastLine->ascent,
+ word->size.ascent
+ + word->content.widget->getStyle()->margin.top);
+
+ //DBG_OBJ_ARRSET_NUM (page, "lines.%d.ascent", page->num_lines - 1,
+ // lastLine->ascent);
+ }
+ } else
+ lastLine->marginDescent =
+ misc::max (lastLine->marginDescent, lastLine->descent);
+
+ getWordExtremes (word, &wordExtremes);
+ lastSpace = (wordIndex > 0) ? words->getRef(wordIndex - 1)->origSpace : 0;
+
+ if (word->content.type == core::Content::BREAK)
+ lastLine->breakSpace =
+ misc::max (word->content.breakSpace,
+ lastLine->marginDescent - lastLine->descent,
+ lastLine->breakSpace);
+
+ lastLineWidth += word->size.width;
+ if (!newLine)
+ lastLineWidth += lastSpace;
+
+ lastLineParMin += wordExtremes.maxWidth;
+ lastLineParMax += wordExtremes.maxWidth;
+ if (!newPar) {
+ lastLineParMin += lastSpace;
+ lastLineParMax += lastSpace;
+ }
+
+ if (word->style->whiteSpace != core::style::WHITE_SPACE_NORMAL) {
+ lastLine->parMin += wordExtremes.minWidth + lastSpace;
+ /* This may also increase the accumulated minimum word width. */
+ lastLine->maxWordMin =
+ misc::max (lastLine->maxWordMin, lastLine->parMin);
+ /* NOTE: Most code relies on that all values of nowrap are equal for all
+ * words within one line. */
+ } else
+ /* Simple case. */
+ lastLine->maxWordMin =
+ misc::max (lastLine->maxWordMin, wordExtremes.minWidth);
+
+ //DBG_OBJ_SET_NUM(page, "lastLine_par_min", page->lastLine_par_min);
+ //DBG_OBJ_SET_NUM(page, "lastLine_par_max", page->lastLine_par_max);
+ //DBG_OBJ_ARRSET_NUM (page, "lines.%d.par_min", page->num_lines - 1,
+ // lastLine->par_min);
+ //DBG_OBJ_ARRSET_NUM (page, "lines.%d.par_max", page->num_lines - 1,
+ // lastLine->par_max);
+ //DBG_OBJ_ARRSET_NUM (page, "lines.%d.max_word_min", page->num_lines - 1,
+ // lastLine->max_word_min);
+
+ /* Finally, justify the line. Breaks are ignored, since the HTML
+ * parser sometimes assignes the wrong style to them. (todo: ) */
+ if (word->content.type != core::Content::BREAK) {
+ switch (word->style->textAlign) {
+ case core::style::TEXT_ALIGN_LEFT:
+ case core::style::TEXT_ALIGN_JUSTIFY: /* see some lines above */
+ case core::style::TEXT_ALIGN_STRING: /* handled elsewhere (in the
+ * future) */
+ leftOffset = 0;
+ break;
+
+ case core::style::TEXT_ALIGN_RIGHT:
+ leftOffset = availWidth - lastLineWidth;
+ break;
+
+ case core::style::TEXT_ALIGN_CENTER:
+ leftOffset = (availWidth - lastLineWidth) / 2;
+ break;
+
+ default:
+ /* compiler happiness */
+ leftOffset = 0;
+ }
+
+ /* For large lines (images etc), which do not fit into the viewport: */
+ if (leftOffset < 0)
+ leftOffset = 0;
+
+ if (listItem && lastLine == lines->getRef (0)) {
+ /* List item markers are always on the left. */
+ lastLine->leftOffset = 0;
+ words->getRef(0)->effSpace = words->getRef(0)->origSpace + leftOffset;
+ //DBG_OBJ_ARRSET_NUM (page, "words.%d.eff_space", 0,
+ // page->words[0].eff_space);
+ } else
+ lastLine->leftOffset = leftOffset;
+ }
+
+ mustQueueResize = true;
+
+ //DBG_MSG_END (page);
+}
+
+
+/**
+ * Calculate the size of a widget within the page.
+ * (Subject of change in the near future!)
+ */
+void Textblock::calcWidgetSize (core::Widget *widget, core::Requisition *size)
+{
+ core::Requisition requisition;
+ int availWidth, availAscent, availDescent;
+
+ /* We ignore line1_offset[_eff]. */
+ availWidth = this->availWidth - getStyle()->boxDiffWidth () - innerPadding;
+ availAscent = this->availAscent - getStyle()->boxDiffHeight ();
+ availDescent = this->availDescent;
+
+ if (widget->usesHints ()) {
+ widget->setWidth (availWidth);
+ widget->setAscent (availAscent);
+ widget->setDescent (availDescent);
+ widget->sizeRequest (size);
+ size->ascent -= widget->getStyle()->margin.top;
+ size->descent -= widget->getStyle()->margin.bottom;
+ } else {
+ /* TODO: Use margin.{top|bottom} here, like above.
+ * (No harm for the next future.) */
+ if (widget->getStyle()->width == core::style::LENGTH_AUTO ||
+ widget->getStyle()->height == core::style::LENGTH_AUTO)
+ widget->sizeRequest (&requisition);
+
+ if (widget->getStyle()->width == core::style::LENGTH_AUTO)
+ size->width = requisition.width;
+ else if (core::style::isAbsLength (widget->getStyle()->width))
+ /* Fixed lengths are only applied to the content, so we have to
+ * add padding, border and margin. */
+ size->width = core::style::absLengthVal (widget->getStyle()->width)
+ + widget->getStyle()->boxDiffWidth ();
+ else
+ size->width =
+ (int) (core::style::perLengthVal (widget->getStyle()->width)
+ * availWidth);
+
+ if (widget->getStyle()->height == core::style::LENGTH_AUTO) {
+ size->ascent = requisition.ascent;
+ size->descent = requisition.descent;
+ } else if (core::style::isAbsLength (widget->getStyle()->height)) {
+ /* Fixed lengths are only applied to the content, so we have to
+ * add padding, border and margin. */
+ size->ascent =
+ core::style::absLengthVal (widget->getStyle()->height)
+ + widget->getStyle()->boxDiffHeight ();
+ size->descent = 0;
+ } else {
+ double len = core::style::perLengthVal (widget->getStyle()->height);
+ size->ascent = (int) (len * availAscent);
+ size->descent = (int) (len * availDescent);
+ }
+ }
+}
+
+/**
+ * Rewrap the page from the line from which this is necessary.
+ * There are basically two times we'll want to do this:
+ * either when the viewport is resized, or when the size changes on one
+ * of the child widgets.
+ */
+void Textblock::rewrap ()
+{
+ int i, wordIndex;
+ Word *word;
+ Line *lastLine;
+
+ if (wrapRef == -1)
+ /* page does not have to be rewrapped */
+ return;
+
+ //DBG_MSGF (page, "wrap", 0,
+ // "Dw_page_rewrap: page->wrap_ref = %d, in page with %d word(s)",
+ // page->wrap_ref, page->num_words);
+ //DBG_MSG_START (page);
+
+ /* All lines up from page->wrap_ref will be rebuild from the word list,
+ * the line list up from this position is rebuild. */
+ lines->setSize (wrapRef);
+ lastLineWidth = 0;
+ //DBG_OBJ_SET_NUM(page, "num_lines", page->num_lines);
+ //DBG_OBJ_SET_NUM(page, "lastLine_width", page->lastLine_width);
+
+ /* In the word list, we start at the last word, plus one (see definition
+ * of last_word), in the line before. */
+ if (wrapRef > 0) {
+ /* Note: In this case, Dw_page_real_word_wrap will immediately find
+ * the need to rewrap the line, since we start with the last one (plus
+ * one). This is also the reason, why page->lastLine_width is set
+ * to the length of the line. */
+ lastLine = lines->getRef (lines->size () - 1);
+
+ lastLineParMin = lastLine->parMin;
+ lastLineParMax = lastLine->parMax;
+
+ wordIndex = lastLine->lastWord;
+ for (i = lastLine->firstWord; i < lastLine->lastWord - 1; i++)
+ lastLineWidth += (words->getRef(i)->size.width +
+ words->getRef(i)->origSpace);
+ lastLineWidth += words->getRef(lastLine->lastWord - 1)->size.width;
+ } else {
+ lastLineParMin = 0;
+ lastLineParMax = 0;
+
+ wordIndex = 0;
+ }
+
+ for (; wordIndex < words->size (); wordIndex++) {
+ word = words->getRef (wordIndex);
+
+ if (word->content.type == core::Content::WIDGET)
+ calcWidgetSize (word->content.widget, &word->size);
+ wordWrap (wordIndex);
+
+ if (word->content.type == core::Content::WIDGET) {
+ word->content.widget->parentRef = lines->size () - 1;
+ //DBG_OBJ_SET_NUM (word->content.widget, "parent_ref",
+ // word->content.widget->parent_ref);
+ }
+
+ //DEBUG_MSG(DEBUG_REWRAP_LEVEL,
+ // "Assigning parent_ref = %d to rewrapped word %d, "
+ // "in page with %d word(s)\n",
+ // page->num_lines - 1, wordIndex, page->num_words);
+
+ /* todo_refactoring:
+ if (word->content.type == DW_CONTENT_ANCHOR)
+ p_Dw_gtk_viewport_change_anchor
+ (widget, word->content.anchor,
+ Dw_page_line_total_y_offset (page,
+ &page->lines[page->num_lines - 1]));
+ */
+ }
+
+ /* Next time, the page will not have to be rewrapped. */
+ wrapRef = -1;
+
+ //DBG_MSG_END (page);
+}
+
+/*
+ * Paint a line
+ * - x and y are toplevel dw coordinates (Question: what Dw? Changed. Test!)
+ * - area is used always (ev. set it to event->area)
+ * - event is only used when is_expose
+ */
+void Textblock::drawLine (Line *line, core::View *view, core::Rectangle *area)
+{
+ Word *word;
+ int wordIndex;
+ int xWidget, yWidget, xWorld, yWorld, yWorldBase;
+ int startHL, widthHL;
+ int wordLen;
+ int diff, effHLStart, effHLEnd, layer;
+ core::Widget *child;
+ core::Rectangle childArea;
+ core::style::Color *color, *thisBgColor, *wordBgColor;
+
+ /* Here's an idea on how to optimize this routine to minimize the number
+ * of calls to gdk_draw_string:
+ *
+ * Copy the text from the words into a buffer, adding a new word
+ * only if: the attributes match, and the spacing is either zero or
+ * equal to the width of ' '. In the latter case, copy a " " into
+ * the buffer. Then draw the buffer. */
+
+ thisBgColor = getBgColor ();
+
+ xWidget = lineXOffsetWidget(line);
+ xWorld = allocation.x + xWidget;
+ yWidget = lineYOffsetWidget (line);
+ yWorld = allocation.y + yWidget;
+ yWorldBase = yWorld + line->ascent;
+
+ for (wordIndex = line->firstWord; wordIndex < line->lastWord;
+ wordIndex++) {
+ word = words->getRef(wordIndex);
+ diff = 0;
+ color = word->style->color;
+
+ //DBG_OBJ_ARRSET_NUM (page, "words.%d.<i>drawn at</i>.x", wordIndex,
+ // xWidget);
+ //DBG_OBJ_ARRSET_NUM (page, "words.%d.<i>drawn at</i>.y", wordIndex,
+ // yWidget);
+
+ switch (word->content.type) {
+ case core::Content::TEXT:
+ if (word->style->backgroundColor)
+ wordBgColor = word->style->backgroundColor;
+ else
+ wordBgColor = thisBgColor;
+
+ /* Adjust the text baseline if the word is <SUP>-ed or <SUB>-ed. */
+ if (word->style->valign == core::style::VALIGN_SUB)
+ diff = word->size.ascent / 2;
+ else if (word->style->valign == core::style::VALIGN_SUPER)
+ diff -= word->size.ascent / 3;
+
+ /* Draw background (color, image), when given. */
+ if (word->style->hasBackground () && word->size.width > 0)
+ drawBox (view, word->style, area,
+ xWidget, yWidget + line->ascent - word->size.ascent,
+ word->size.width, word->size.ascent + word->size.descent,
+ false);
+
+ /* Draw space background (color, image), when given. */
+ if (word->spaceStyle->hasBackground () && word->effSpace > 0)
+ drawBox (view, word->spaceStyle, area,
+ xWidget + word->size.width,
+ yWidget + line->ascent - word->size.ascent,
+ word->effSpace, word->size.ascent + word->size.descent,
+ false);
+ view->drawText (word->style->font, color,
+ core::style::Color::SHADING_NORMAL,
+ xWorld, yWorldBase + diff,
+ word->content.text, strlen (word->content.text));
+
+ /* underline */
+ if (word->style->textDecoration &
+ core::style::TEXT_DECORATION_UNDERLINE)
+ view->drawLine (color, core::style::Color::SHADING_NORMAL,
+ xWorld, yWorldBase + 1 + diff,
+ xWorld + word->size.width - 1,
+ yWorldBase + 1 + diff);
+ if (wordIndex + 1 < line->lastWord &&
+ (word->spaceStyle->textDecoration
+ & core::style::TEXT_DECORATION_UNDERLINE))
+ view->drawLine (word->spaceStyle->color,
+ core::style::Color::SHADING_NORMAL,
+ xWorld + word->size.width,
+ yWorldBase + 1 + diff,
+ xWorld + word->size.width + word->effSpace - 1,
+ yWorldBase + 1 + diff);
+
+ /* strike-through */
+ if (word->style->textDecoration
+ & core::style::TEXT_DECORATION_LINE_THROUGH)
+ view->drawLine (color, core::style::Color::SHADING_NORMAL,
+ xWorld,
+ yWorldBase - word->size.ascent / 2 + diff,
+ xWorld + word->size.width - 1,
+ yWorldBase - word->size.ascent / 2 + diff);
+ if (wordIndex + 1 < line->lastWord &&
+ (word->spaceStyle->textDecoration
+ & core::style::TEXT_DECORATION_LINE_THROUGH))
+ view->drawLine (word->spaceStyle->color,
+ core::style::Color::SHADING_NORMAL,
+ xWorld + word->size.width,
+ yWorldBase - word->size.ascent / 2 + diff,
+ xWorld + word->size.width + word->effSpace - 1,
+ yWorldBase - word->size.ascent / 2 + diff);
+
+ for (layer = 0; layer < core::HIGHLIGHT_NUM_LAYERS; layer++) {
+ if (hlStart[layer].index <= wordIndex &&
+ hlEnd[layer].index >= wordIndex) {
+
+ wordLen = strlen (word->content.text);
+ effHLEnd = misc::min (wordLen, hlEnd[layer].nChar);
+ effHLStart = 0;
+ if (wordIndex == hlStart[layer].index)
+ effHLStart = misc::min (hlStart[layer].nChar, wordLen);
+
+ effHLEnd = wordLen;
+ if (wordIndex == hlEnd[layer].index)
+ effHLEnd = misc::min (hlEnd[layer].nChar, wordLen);
+
+ startHL = xWorld + layout->textWidth (word->style->font,
+ word->content.text,
+ effHLStart);
+ widthHL =
+ layout->textWidth (word->style->font,
+ word->content.text + effHLStart,
+ effHLEnd - effHLStart);
+
+ // If the space after this word highlighted, and this word
+ // is not the last one in this line, highlight also the
+ // space.
+ /** \todo This should also be done with spaces after non-text
+ * words, but this is not yet defined very well. */
+ if (wordIndex < hlEnd[layer].index &&
+ wordIndex < words->size () &&
+ wordIndex != line->lastWord - 1)
+ widthHL += word->effSpace;
+
+
+ if (widthHL != 0) {
+ /* Draw background for highlighted text. */
+ view->drawRectangle (wordBgColor,
+ core::style::Color::SHADING_INVERSE,
+ true, startHL,
+ yWorldBase - word->size.ascent,
+ widthHL,
+ word->size.ascent + word->size.descent);
+
+ /* Highlight the text. */
+ view->drawText (word->style->font,
+ color, core::style::Color::SHADING_INVERSE,
+ startHL, yWorldBase + diff,
+ word->content.text + effHLStart,
+ effHLEnd - effHLStart);
+
+ /* underline and strike-through */
+ if (word->style->textDecoration
+ & core::style::TEXT_DECORATION_UNDERLINE)
+ view->drawLine (color,
+ core::style::Color::SHADING_INVERSE,
+ startHL, yWorldBase + 1 + diff,
+ startHL + widthHL - 1,
+ yWorldBase + 1 + diff);
+ if (word->style->textDecoration
+ & core::style::TEXT_DECORATION_LINE_THROUGH)
+ view->drawLine (color,
+ core::style::Color::SHADING_INVERSE,
+ startHL,
+ yWorldBase - word->size.ascent / 2 + diff,
+ startHL + widthHL - 1,
+ yWorldBase - word->size.ascent / 2
+ + diff);
+ }
+ }
+ }
+ break;
+
+ case core::Content::WIDGET:
+ child = word->content.widget;
+ if (child->intersects (area, &childArea))
+ child->draw (view, &childArea);
+ break;
+
+ case core::Content::ANCHOR: case core::Content::BREAK:
+ /* nothing - an anchor/break isn't seen */
+ /*
+ * Historical note:
+ * > BUG: sometimes anchors have x_space;
+ * > we subtract that just in case --EG
+ * This is inconsistent with other parts of the code, so it should
+ * be tried to prevent this earlier.--SG
+ */
+ /*
+ * x_viewport -= word->size.width + word->eff_space;
+ * xWidget -= word->size.width + word->eff_space;
+ */
+#if 0
+ /* Useful for testing: draw breaks. */
+ if (word->content.type == DW_CONTENT_BREAK)
+ gdk_draw_rectangle (window, color, TRUE,
+ p_Dw_widget_xWorld_to_viewport (widget,
+ widget->allocation.x +
+ Dw_page_line_total_x_offset(page, line)),
+ y_viewport_base + line->descent,
+ DW_WIDGET_CONTENT_WIDTH(widget),
+ word->content.break_space);
+#endif
+ break;
+
+ default:
+ fprintf (stderr, "BUG!!! at (%d, %d).\n", xWorld, yWorldBase + diff);
+ break;
+ }
+
+ xWorld += word->size.width + word->effSpace;
+ xWidget += word->size.width + word->effSpace;
+ }
+}
+
+/**
+ * Find the first line index that includes y, relative to top of widget.
+ */
+int Textblock::findLineIndex (int y)
+{
+ int maxIndex = lines->size () - 1;
+ int step, index, low = 0;
+
+ step = (lines->size() + 1) >> 1;
+ while ( step > 1 ) {
+ index = low + step;
+ if (index <= maxIndex &&
+ lineYOffsetWidgetI (index) < y)
+ low = index;
+ step = (step + 1) >> 1;
+ }
+
+ if (low < maxIndex && lineYOffsetWidgetI (low + 1) < y)
+ low++;
+
+ /*
+ * This new routine returns the line number between (top) and
+ * (top + size.ascent + size.descent + break_space): the space
+ * _below_ the line is considered part of the line. Old routine
+ * returned line number between (top - previous_line->break_space)
+ * and (top + size.ascent + size.descent): the space _above_ the
+ * line was considered part of the line. This is important for
+ * Dw_page_find_link() --EG
+ * That function has now been inlined into Dw_page_motion_notify() --JV
+ */
+ return low;
+}
+
+/**
+ * \brief Find the line of word \em wordIndex.
+ */
+int Textblock::findLineOfWord (int wordIndex)
+{
+ int high = lines->size () - 1, index, low = 0;
+
+ //g_return_val_if_fail (word_index >= 0, -1);
+ //g_return_val_if_fail (word_index < page->num_words, -1);
+
+ while (true) {
+ index = (low + high) / 2;
+ if (wordIndex >= lines->getRef(index)->firstWord) {
+ if (wordIndex < lines->getRef(index)->lastWord)
+ return index;
+ else
+ low = index + 1;
+ } else
+ high = index - 1;
+ }
+}
+
+/**
+ * \brief Find the index of the word, or -1.
+ */
+int Textblock::findWord (int x, int y)
+{
+ int lineIndex, wordIndex;
+ int xCursor, lastXCursor;
+ Line *line;
+ Word *word;
+
+ if ((lineIndex = findLineIndex (y)) >= lines->size ())
+ return -1;
+ line = lines->getRef (lineIndex);
+ if (lineYOffsetWidget (line) + line->ascent + line->descent <= y)
+ return -1;
+
+ xCursor = lineXOffsetWidget (line);
+ for (wordIndex = line->firstWord; wordIndex < line->lastWord; wordIndex++) {
+ word = words->getRef (wordIndex);
+ lastXCursor = xCursor;
+ xCursor += word->size.width + word->effSpace;
+ if (lastXCursor <= x && xCursor > x)
+ return wordIndex;
+ }
+
+ return -1;
+}
+
+void Textblock::draw (core::View *view, core::Rectangle *area)
+{
+ int lineIndex;
+ Line *line;
+
+ drawWidgetBox (view, area, false);
+
+ lineIndex = findLineIndex (area->y);
+
+ for (; lineIndex < lines->size (); lineIndex++) {
+ line = lines->getRef (lineIndex);
+ if (lineYOffsetWidget (line) >= area->y + area->height)
+ break;
+
+ drawLine (line, view, area);
+ }
+}
+
+/**
+ * Add a new word (text, widget etc.) to a page.
+ */
+Textblock::Word *Textblock::addWord (int width, int ascent, int descent,
+ core::style::Style *style)
+{
+ Word *word;
+
+ words->increase ();
+
+ word = words->getRef (words->size() - 1);
+ word->size.width = width;
+ word->size.ascent = ascent;
+ word->size.descent = descent;
+ word->origSpace = 0;
+ word->effSpace = 0;
+ word->content.space = false;
+
+ //DBG_OBJ_ARRSET_NUM (page, "words.%d.size.width", page->num_words - 1,
+ // word->size.width);
+ //DBG_OBJ_ARRSET_NUM (page, "words.%d.size.descent", page->num_words - 1,
+ // word->size.descent);
+ //DBG_OBJ_ARRSET_NUM (page, "words.%d.size.ascent", page->num_words - 1,
+ // word->size.ascent);
+ //DBG_OBJ_ARRSET_NUM (page, "words.%d.orig_space", page->num_words - 1,
+ // word->orig_space);
+ //DBG_OBJ_ARRSET_NUM (page, "words.%d.eff_space", page->num_words - 1,
+ // word->eff_space);
+ //DBG_OBJ_ARRSET_NUM (page, "words.%d.content.space", page->num_words - 1,
+ // word->content.space);
+
+ word->style = style;
+ word->spaceStyle = style;
+ style->ref ();
+ style->ref ();
+
+ return word;
+}
+
+/**
+ * Calculate the size of a text word.
+ */
+void Textblock::calcTextSize (const char *text, core::style::Style *style,
+ core::Requisition *size)
+{
+ size->width =
+ layout->textWidth (style->font, text, strlen (text));
+ size->ascent = style->font->ascent;
+ size->descent = style->font->descent;
+
+ /* In case of a sub or super script we increase the word's height and
+ * potentially the line's height.
+ */
+ if (style->valign == core::style::VALIGN_SUB)
+ size->descent += (size->ascent / 2);
+ else if (style->valign == core::style::VALIGN_SUPER)
+ size->ascent += (size->ascent / 3);
+}
+
+
+/**
+ * Add a word to the page structure. Stashes the argument pointer in
+ * the page data structure so that it will be deallocated on destroy.
+ */
+void Textblock::addText (const char *text, core::style::Style *style)
+{
+ Word *word;
+ core::Requisition size;
+
+ calcTextSize (text, style, &size);
+ word = addWord (size.width, size.ascent, size.descent, style);
+ word->content.type = core::Content::TEXT;
+ word->content.text = layout->textZone->strdup(text);
+
+ //DBG_OBJ_ARRSET_STR (page, "words.%d.content.text", page->num_words - 1,
+ // word->content.text);
+
+ wordWrap (words->size () - 1);
+}
+
+/**
+ * Add a widget (word type) to the page.
+ */
+void Textblock::addWidget (core::Widget *widget, core::style::Style *style)
+{
+ Word *word;
+ core::Requisition size;
+
+ /* We first assign -1 as parent_ref, since the call of widget->size_request
+ * will otherwise let this DwPage be rewrapped from the beginning.
+ * (parent_ref is actually undefined, but likely has the value 0.) At the,
+ * end of this function, the correct value is assigned. */
+ widget->parentRef = -1;
+
+ widget->setParent (this);
+ widget->setStyle (style);
+
+ calcWidgetSize (widget, &size);
+ word = addWord (size.width, size.ascent, size.descent, style);
+
+ word->content.type = core::Content::WIDGET;
+ word->content.widget = widget;
+
+ //DBG_OBJ_ARRSET_PTR (page, "words.%d.content.widget", page->num_words - 1,
+ // word->content.widget);
+
+ wordWrap (words->size () - 1);
+ word->content.widget->parentRef = lines->size () - 1;
+ //DBG_OBJ_SET_NUM (word->content.widget, "parent_ref",
+ // word->content.widget->parent_ref);
+
+ //DEBUG_MSG(DEBUG_REWRAP_LEVEL,
+ // "Assigning parent_ref = %d to added word %d, "
+ // "in page with %d word(s)\n",
+ // page->num_lines - 1, page->num_words - 1, page->num_words);
+}
+
+
+/**
+ * Add an anchor to the page. "name" is copied, so no strdup is neccessary for
+ * the caller.
+ *
+ * Return true on success, and false, when this anchor had already been
+ * added to the widget tree.
+ */
+bool Textblock::addAnchor (const char *name, core::style::Style *style)
+{
+ Word *word;
+ char *copy;
+ int y;
+
+ // Since an anchor does not take any space, it is safe to call
+ // addAnchor already here.
+ if (wasAllocated ()) {
+ if (lines->size () == 0)
+ y = allocation.y;
+ else
+ y = allocation.y + lineYOffsetWidgetI (lines->size () - 1);
+ copy = Widget::addAnchor (name, y);
+ } else
+ copy = Widget::addAnchor (name);
+
+ if (copy == NULL)
+ /**
+ * \todo It may be neccessary for future uses to save the anchor in
+ * some way, e.g. when parts of the widget tree change.
+ */
+ return false;
+ else {
+ word = addWord (0, 0, 0, style);
+ word->content.type = core::Content::ANCHOR;
+ word->content.anchor = copy;
+ wordWrap (words->size () - 1);
+ return true;
+ }
+}
+
+
+/**
+ * ?
+ */
+void Textblock::addSpace (core::style::Style *style)
+{
+ int nl, nw;
+ int space;
+
+ nl = lines->size () - 1;
+ if (nl >= 0) {
+ nw = words->size () - 1;
+ if (nw >= 0) {
+ /* todo: remove this test case */
+ //if (page->words[nw].orig_space != 0) {
+ // _MSG(" a_Dw_page_add_space:: already existing space!!!\n");
+ //}
+
+ space = style->font->spaceWidth;
+ words->getRef(nw)->origSpace = space;
+ words->getRef(nw)->effSpace = space;
+ words->getRef(nw)->content.space = true;
+
+ //DBG_OBJ_ARRSET_NUM (page, "words.%d.orig_space", nw,
+ // page->words[nw].orig_space);
+ //DBG_OBJ_ARRSET_NUM (page, "words.%d.eff_space", nw,
+ // page->words[nw].eff_space);
+ //DBG_OBJ_ARRSET_NUM (page, "words.%d.content.space", nw,
+ // page->words[nw].content.space);
+
+ words->getRef(nw)->spaceStyle->unref ();
+ words->getRef(nw)->spaceStyle = style;
+ style->ref ();
+ }
+ }
+}
+
+
+/**
+ * Cause a paragraph break
+ */
+void Textblock::addParbreak (int space, core::style::Style *style)
+{
+ Word *word, *word2 = NULL; // Latter for compiler happiness, search!
+ bool isfirst;
+ Widget *widget;
+ int lineno;
+
+ /* A break may not be the first word of a page, or directly after
+ the bullet/number (which is the first word) in a list item. (See
+ also comment in Dw_page_size_request.) */
+ if (words->size () == 0 ||
+ (listItem && words->size () == 1)) {
+ /* This is a bit hackish: If a break is added as the
+ first/second word of a page, and the parent widget is also a
+ DwPage, and there is a break before -- this is the case when
+ a widget is used as a text box (lists, blockquotes, list
+ items etc) -- then we simply adjust the break before, in a
+ way that the space is in any case visible. */
+
+ /* Find the widget where to adjust the break_space. */
+ for (widget = this;
+ widget->getParent() &&
+ widget->getParent()->instanceOf (Textblock::CLASS_ID);
+ widget = widget->getParent ()) {
+ Textblock *textblock2 = (Textblock*)widget->getParent ();
+ if (textblock2->listItem)
+ isfirst = (textblock2->words->get(1).content.type
+ == core::Content::WIDGET
+ && textblock2->words->get(1).content.widget == widget);
+ else
+ isfirst = (textblock2->words->get(0).content.type
+ == core::Content::WIDGET
+ && textblock2->words->get(0).content.widget == widget);
+ if (!isfirst) {
+ /* The page we searched for has been found. */
+ lineno = widget->parentRef;
+ if (lineno > 0 &&
+ (word2 =
+ textblock2->words->getRef(textblock2->lines
+ ->get(lineno - 1).firstWord)) &&
+ word2->content.type == core::Content::BREAK) {
+ if (word2->content.breakSpace < space) {
+ word2->content.breakSpace = space;
+ textblock2->queueResize (lineno, false);
+ textblock2->mustQueueResize = false;
+ }
+ }
+ return;
+ }
+ /* Otherwise continue to examine parents. */
+ }
+ /* Return in any case. */
+ return;
+ }
+
+ /* Another break before? */
+ if ((word = words->getRef(words->size () - 1)) &&
+ word->content.type == core::Content::BREAK) {
+ Line *lastLine = lines->getRef (lines->size () - 1);
+
+ word->content.breakSpace =
+ misc::max (word->content.breakSpace, space);
+ lastLine->breakSpace =
+ misc::max (word->content.breakSpace,
+ lastLine->marginDescent - lastLine->descent,
+ lastLine->breakSpace);
+ return;
+ }
+
+ word = addWord (0, 0, 0, style);
+ word->content.type = core::Content::BREAK;
+ word->content.breakSpace = space;
+ wordWrap (words->size () - 1);
+}
+
+/*
+ * Cause a line break.
+ */
+void Textblock::addLinebreak (core::style::Style *style)
+{
+ Word *word;
+
+ if (words->size () == 0 ||
+ words->get(words->size () - 1).content.type == core::Content::BREAK)
+ // An <BR> in an empty line gets the height of the current font
+ // (why would someone else place it here?), ...
+ word = addWord (0, style->font->ascent, style->font->descent, style);
+ else
+ // ... otherwise, it has no size (and does not enlarge the line).
+ word = addWord (0, 0, 0, style);
+
+ word->content.type = core::Content::BREAK;
+ word->content.breakSpace = 0;
+ word->style = style;
+ wordWrap (words->size () - 1);
+}
+
+
+/**
+ * \brief Search recursively through widget.
+ *
+ * This is an optimized version of the general
+ * dw::core::Widget::getWidgetAtPoint method.
+ */
+core::Widget *Textblock::getWidgetAtPoint(int x, int y, int level)
+{
+ int lineIndex, wordIndex;
+ Line *line;
+
+ if (x < allocation.x ||
+ y < allocation.y ||
+ x > allocation.x + allocation.width ||
+ y > allocation.y + getHeight ()) {
+ return NULL;
+ }
+
+ lineIndex = findLineIndex (y - allocation.y);
+
+ if (lineIndex < 0 || lineIndex >= lines->size ()) {
+ return this;
+ }
+
+ line = lines->getRef (lineIndex);
+
+ for (wordIndex = line->firstWord; wordIndex < line->lastWord; wordIndex++) {
+ Word *word = words->getRef (wordIndex);
+
+ if (word->content.type == core::Content::WIDGET) {
+ core::Widget * childAtPoint;
+ childAtPoint = word->content.widget->getWidgetAtPoint (x, y,
+ level + 1);
+ if (childAtPoint) {
+ return childAtPoint;
+ }
+ }
+ }
+
+ return this;
+}
+
+
+/**
+ * This function "hands" the last break of a page "over" to a parent
+ * page. This is used for "collapsing spaces".
+ */
+void Textblock::handOverBreak (core::style::Style *style)
+{
+ #if 0
+ MISSING
+ DwPageLine *last_line;
+ DwWidget *parent;
+
+ if (page->num_lines == 0)
+ return;
+
+ last_line = &page->lines[page->num_lines - 1];
+ if (last_line->break_space != 0 &&
+ (parent = DW_WIDGET(page)->parent) && DW_IS_PAGE (parent))
+ a_Dw_page_add_parbreak (DW_PAGE (parent), last_line->break_space, style);
+#endif
+}
+
+/*
+ * Any words added by a_Dw_page_add_... are not immediately (queued to
+ * be) drawn, instead, this function must be called. This saves some
+ * calls to p_Dw_widget_queue_resize.
+ *
+ */
+void Textblock::flush ()
+{
+ if (mustQueueResize) {
+ queueResize (-1, true);
+ mustQueueResize = false;
+ }
+}
+
+
+// next: Dw_page_find_word
+
+void Textblock::changeLinkColor (int link, int newColor)
+{
+}
+
+void Textblock::changeWordStyle (int from, int to, core::style::Style *style,
+ bool includeFirstSpace, bool includeLastSpace)
+{
+}
+
+// ----------------------------------------------------------------------
+
+Textblock::TextblockIterator::TextblockIterator (Textblock *textblock,
+ core::Content::Type mask,
+ bool atEnd):
+ core::Iterator (textblock, mask, atEnd)
+{
+ index = atEnd ? textblock->words->size () : -1;
+ content.type = atEnd ? core::Content::END : core::Content::START;
+}
+
+Textblock::TextblockIterator::TextblockIterator (Textblock *textblock,
+ core::Content::Type mask,
+ int index):
+ core::Iterator (textblock, mask, false)
+{
+ this->index = index;
+
+ if (index < 0)
+ content.type = core::Content::START;
+ else if (index >= textblock->words->size ())
+ content.type = core::Content::END;
+ else
+ content = textblock->words->get(index).content;
+}
+
+object::Object *Textblock::TextblockIterator::clone()
+{
+ return new TextblockIterator ((Textblock*)getWidget(), getMask(), index);
+}
+
+int Textblock::TextblockIterator::compareTo(misc::Comparable *other)
+{
+ return index - ((TextblockIterator*)other)->index;
+}
+
+bool Textblock::TextblockIterator::next ()
+{
+ Textblock *textblock = (Textblock*)getWidget();
+
+ if (content.type == core::Content::END)
+ return false;
+
+ do {
+ index++;
+ if (index >= textblock->words->size ()) {
+ content.type = core::Content::END;
+ return false;
+ }
+ } while ((textblock->words->get(index).content.type & getMask()) == 0);
+
+ content = textblock->words->get(index).content;
+ return true;
+}
+
+bool Textblock::TextblockIterator::prev ()
+{
+ Textblock *textblock = (Textblock*)getWidget();
+
+ if (content.type == core::Content::START)
+ return false;
+
+ do {
+ index--;
+ if (index < 0) {
+ content.type = core::Content::START;
+ return false;
+ }
+ } while ((textblock->words->get(index).content.type & getMask()) == 0);
+
+ content = textblock->words->get(index).content;
+ return true;
+}
+
+void Textblock::TextblockIterator::highlight (int start, int end,
+ core::HighlightLayer layer)
+{
+ Textblock *textblock = (Textblock*)getWidget();
+ int index1 = index, index2 = index;
+
+ if (textblock->hlStart[layer].index > textblock->hlEnd[layer].index) {
+ /* nothing is highlighted */
+ textblock->hlStart[layer].index = index;
+ textblock->hlEnd[layer].index = index;
+ }
+
+ if (textblock->hlStart[layer].index >= index) {
+ index2 = textblock->hlStart[layer].index;
+ textblock->hlStart[layer].index = index;
+ textblock->hlStart[layer].nChar = start;
+ }
+
+ if (textblock->hlEnd[layer].index <= index) {
+ index2 = textblock->hlEnd[layer].index;
+ textblock->hlEnd[layer].index = index;
+ textblock->hlEnd[layer].nChar = end;
+ }
+
+ textblock->queueDrawRange (index1, index2);
+}
+
+void Textblock::TextblockIterator::unhighlight (int direction,
+ core::HighlightLayer layer)
+{
+ Textblock *textblock = (Textblock*)getWidget();
+ int index1 = index, index2 = index;
+
+ if (textblock->hlStart[layer].index > textblock->hlEnd[layer].index)
+ return;
+
+ if (direction == 0) {
+ index1 = textblock->hlStart[layer].index;
+ index2 = textblock->hlEnd[layer].index;
+ textblock->hlStart[layer].index = 1;
+ textblock->hlEnd[layer].index = 0;
+ } else if (direction > 0 && textblock->hlStart[layer].index <= index) {
+ index1 = textblock->hlStart[layer].index;
+ textblock->hlStart[layer].index = index + 1;
+ textblock->hlStart[layer].nChar = 0;
+ } else if (direction < 0 && textblock->hlEnd[layer].index >= index) {
+ index1 = textblock->hlEnd[layer].index;
+ textblock->hlEnd[layer].index = index - 1;
+ textblock->hlEnd[layer].nChar = INT_MAX;
+ }
+
+ textblock->queueDrawRange (index1, index2);
+}
+
+void Textblock::queueDrawRange (int index1, int index2)
+{
+ int from = misc::min (index1, index2);
+ int to = misc::max (index1, index2);
+
+ from = misc::min (from, words->size () - 1);
+ from = misc::max (from, 0);
+ to = misc::min (to, words->size () - 1);
+ to = misc::max (to, 0);
+
+ int line1 = findLineOfWord (from);
+ int line2 = findLineOfWord (to);
+
+ queueDrawArea (0,
+ lineYOffsetWidgetI (line1),
+ allocation.width,
+ lineYOffsetWidgetI (line2)
+ - lineYOffsetWidgetI (line1)
+ + lines->getRef (line2)->ascent
+ + lines->getRef (line2)->descent);
+}
+
+void Textblock::TextblockIterator::getAllocation (int start, int end,
+ core::Allocation *allocation)
+{
+ Textblock *textblock = (Textblock*)getWidget();
+ int lineIndex = textblock->findLineOfWord (index);
+ Line *line = textblock->lines->getRef (lineIndex);
+ Word *word = textblock->words->getRef (index);
+
+ allocation->x =
+ textblock->allocation.x + textblock->lineXOffsetWidget (line);
+ for (int i = line->firstWord; i < index; i++)
+ allocation->x += textblock->words->getRef(i)->size.width;
+
+ allocation->y =
+ textblock->allocation.y
+ + textblock->lineYOffsetWidget (line) + line->ascent - word->size.ascent;
+ allocation->width = word->size.width;
+ allocation->ascent = word->size.ascent;
+ allocation->descent = word->size.descent;
+}
+
+} // namespace dw
diff --git a/dw/textblock.hh b/dw/textblock.hh
new file mode 100644
index 00000000..b47c2e55
--- /dev/null
+++ b/dw/textblock.hh
@@ -0,0 +1,387 @@
+#ifndef __DW_TEXTBLOCK_HH__
+#define __DW_TEXTBLOCK_HH__
+
+#include "core.hh"
+#include "../lout/misc.hh"
+
+namespace dw {
+
+using namespace lout;
+
+/**
+ * \brief A Widget for rendering text blocks, i.e. paragraphs or sequences
+ * of paragraphs.
+ *
+ * <h3>Signals</h3>
+ *
+ * dw::Textblock uses the signals defined in
+ * dw::core::Widget::LinkReceiver, related to links. The coordinates are
+ * always -1.
+ *
+ *
+ * <h3>Collapsing Spaces</h3>
+ *
+ * The idea behind this is that every paragraph has a specific vertical
+ * space around and that they are combined to one space, according to
+ * rules stated below. A paragraph consists either of the lines between
+ * two paragraph breaks within a dw::Textblock, or of a dw::Textblock
+ * within a dw::Textblock, in a single line; the latter is used for
+ * indented boxes and list items.
+ *
+ * The rules:
+ *
+ * <ol>
+ * <li> If a paragraph is following by another, the space between them is the
+ * maximum of both box spaces:
+ *
+ * \image html dw-textblock-collapsing-spaces-1-1.png
+ *
+ * are combined like this:
+ *
+ * \image html dw-textblock-collapsing-spaces-1-2.png
+ *
+ * <li> a) If one paragraph is the first paragraph within another, the upper
+ * space of these paragraphs collapse. b) The analogue is the case for the
+ * last box:
+ *
+ * \image html dw-textblock-collapsing-spaces-2-1.png
+ *
+ * If B and C are put into A, the result is:
+ *
+ * \image html dw-textblock-collapsing-spaces-2-2.png
+ * </ol>
+ *
+ * For achieving this, there are some features of dw::Textblock:
+ *
+ * <ul>
+ * <li> Consequent breaks are automatically combined, according to
+ * rule 1. See the code of dw::Textblock::addParBreak for details.
+ *
+ * <li> If a break is added as the first word of the dw::Textblock within
+ * another dw::Textblock, collapsing according to rule 2a is done
+ * automatically. See the code of dw::Textblock::addParBreak.
+ *
+ * <li> To collapse spaces according to rule 2b,
+ * dw::Textblock::addParBreak::handOverBreak must be called for
+ * the \em inner widget. The HTML parser does this in
+ * Html_eventually_pop_dw.
+ * </ul>
+ *
+ *
+ * <h3>Collapsing Margins</h3>
+ *
+ * Collapsing margins, as defined in the CSS2 specification, are,
+ * supported in addition to collapsing spaces. Also, spaces and margins
+ * collapse themselves. I.e., the space between two paragraphs is the
+ * maximum of the space calculated as described in "Collapsing Spaces"
+ * and the space calculated according to the rules for collapsing margins.
+ *
+ * (This is an intermediate hybrid state, collapsing spaces are used in
+ * the current version of dillo, while I implemented collapsing margins
+ * for the CSS prototype and integrated it already into the main trunk. For
+ * a pure CSS-based dillo, collapsing spaces will not be needed anymore, and
+ * may be removed for simplicity.)
+ *
+ *
+ * <h3>Some Internals</h3>
+ *
+ * There are two lists, dw::Textblock::words and
+ * dw::Textblock::lines. The word list is quite static; only new words
+ * may be added. A word is either text, a widget, a break or an
+ * anchor. Anchors are stored in the text, because it may be necessary to
+ * correct the scroller positions at rewrapping.
+ *
+ * Lines refer to the word list (first and last), they are completely
+ * redundant, i.e., they can be rebuilt from the words. Lines can be
+ * rewrapped either completely or partially (see "Incremental Resizing"
+ * below). For the latter purpose, several values are accumulated in the
+ * lines. See dw::Textblock::Line for details.
+ *
+ *
+ * <h4>Incremental Resizing</h4>
+ *
+ * dw::Textblock makes use of incremental resizing as described in \ref
+ * dw-widget-sizes. The parentRef is, for children of a dw::Textblock, simply
+ * the number of the line.
+ *
+ * Generally, there are three cases which may change the size of the
+ * widget:
+ *
+ * <ul>
+ * <li> The available size of the widget has changed, e.g., because the
+ * user has changed the size of the browser window. In this case,
+ * it is necessary to rewrap all the lines.
+ *
+ * <li> A child widget has changed its size. In this case, only a rewrap
+ * down from the line where this widget is located is necessary.
+ *
+ * (This case is very important for tables. Tables are quite at the
+ * bottom, so that a partial rewrap is relevant. Otherwise, tables
+ * change their size quite often, so that this is necessary for a
+ * fast, non-blocking rendering)
+ *
+ * <li> A word (or widget, break etc.) is added to the text block. This
+ * makes it possible to reuse the old size by simply adjusting the
+ * current width and height, so no rewrapping is necessary.
+ * </ul>
+ *
+ * The state of the size calculation is stored in wrapRef within
+ * dw::Textblock, which has the value -1 if no rewrapping of lines
+ * necessary, or otherwise the line from which a rewrap is necessary.
+ *
+ */
+class Textblock: public core::Widget
+{
+protected:
+ struct Line
+ {
+ int firstWord; /* first-word's position in DwPageWord [0 based] */
+ int lastWord; /* last-word's position in DwPageWord [1 based] */
+
+ /* "top" is always relative to the top of the first line, i.e.
+ * page->lines[0].top is always 0. */
+ int top, ascent, descent, breakSpace, leftOffset;
+
+ /* This is similar to descent, but includes the bottom margins of the
+ * widgets within this line. */
+ int marginDescent;
+
+ /* The following members contain accumulated values, from the top
+ * down to the line before. */
+ int maxLineWidth; /* maximum of all line widths */
+ int maxWordMin; /* maximum of all word minima */
+ int maxParMax; /* maximum of all paragraph maxima */
+ int parMin; /* the minimal total width down from the last
+ * paragraph start, to the *beginning* of the
+ * line */
+ int parMax; /* the maximal total width down from the last
+ * paragraph start, to the *beginning* of the
+ * line */
+ };
+
+ struct Word
+ {
+ /* todo: perhaps add a xLeft? */
+ core::Requisition size;
+ /* Space after the word, only if it's not a break: */
+ unsigned short origSpace; /* from font, set by addSpace */
+ unsigned short effSpace; /* effective space, set by wordWrap,
+ * used for drawing etc. */
+ core::Content content;
+
+ core::style::Style *style;
+ core::style::Style *spaceStyle; /* initially the same as of the word,
+ later set by a_Dw_page_add_space */
+ };
+
+ class TextblockIterator: public core::Iterator
+ {
+ private:
+ int index;
+
+ public:
+ TextblockIterator (Textblock *textblock, core::Content::Type mask,
+ bool atEnd);
+ TextblockIterator (Textblock *textblock, core::Content::Type mask,
+ int index);
+
+ object::Object *clone();
+ int compareTo(misc::Comparable *other);
+
+ bool next ();
+ bool prev ();
+ void highlight (int start, int end, core::HighlightLayer layer);
+ void unhighlight (int direction, core::HighlightLayer layer);
+ void getAllocation (int start, int end, core::Allocation *allocation);
+ };
+
+ friend class TextblockIterator;
+
+ /* These fields provide some ad-hoc-functionality, used by sub-classes. */
+ bool listItem; /* If true, the first word of the page is treated
+ specially (search in source). */
+ int innerPadding; /* This is an additional padding on the left side
+ (used by ListItem). */
+ int line1Offset; /* This is an additional offset of the first line.
+ May be negative (shift to left) or positive
+ (shift to right). */
+ int line1OffsetEff; /* The "effective" value of line1_offset, may
+ differ from line1_offset when
+ ignoreLine1OffsetSometimes is set to true. */
+
+ /* The following is really hackish: It is used for DwTableCell (see
+ * comment in dw_table_cell.c), to avoid too wide table columns. If
+ * set to true, it has following effects:
+ *
+ * (i) line1_offset is ignored in calculating the minimal width
+ * (which is used by DwTable!), and
+ * (ii) line1_offset is ignored (line1_offset_eff is set to 0),
+ * when line1_offset plus the width of the first word is
+ * greater than the the available witdh.
+ *
+ * \todo Eliminate all these ad-hoc features by a new, simpler and
+ * more elegant design. ;-)
+ */
+ bool ignoreLine1OffsetSometimes;
+
+ bool mustQueueResize;
+
+ bool limitTextWidth; /* from preferences */
+
+ int redrawY;
+ int lastWordDrawn;
+
+ /* These values are set by set_... */
+ int availWidth, availAscent, availDescent;
+
+ int lastLineWidth;
+ int lastLineParMin;
+ int lastLineParMax;
+ int wrapRef; /* [0 based] */
+
+ misc::SimpleVector <Line> *lines;
+ misc::SimpleVector <Word> *words;
+
+ struct {int index, nChar;}
+ hlStart[core::HIGHLIGHT_NUM_LAYERS], hlEnd[core::HIGHLIGHT_NUM_LAYERS];
+
+ /* The word index of the link under a button press, and the char
+ * position */
+ int linkPressedIndex;
+ int link_pressedCharPos;
+
+ int hoverLink; /* The link under the button. */
+ core::style::Tooltip *hoverTooltip; /* The tooltip under the button. No ref
+ * hold. */
+
+
+ void queueDrawRange (int index1, int index2);
+ void getWordExtremes (Word *word, core::Extremes *extremes);
+ void markChange (int ref);
+ void justifyLine (Line *line, int availWidth);
+ void addLine (int wordInd, bool newPar);
+ void calcWidgetSize (core::Widget *widget, core::Requisition *size);
+ void rewrap ();
+ void drawLine (Line *line, core::View *view, core::Rectangle *area);
+ int findLineIndex (int y);
+ int findLineOfWord (int wordIndex);
+ int findWord (int x, int y);
+
+ Word *addWord (int width, int ascent, int descent,
+ core::style::Style *style);
+ void calcTextSize (const char *text, core::style::Style *style,
+ core::Requisition *size);
+
+
+ /**
+ * \brief Returns the x offset (the indentation plus any offset needed for
+ * centering or right justification) for the line.
+ *
+ * The offset returned is relative to the page *content* (i.e. without
+ * border etc.).
+ */
+ inline int lineXOffsetContents (Line *line)
+ {
+ return innerPadding + line->leftOffset +
+ (line == lines->getRef (0) ? line1OffsetEff : 0);
+ }
+
+ /**
+ * \brief Like lineXOffset, but relative to the allocation (i.e.
+ * including border etc.).
+ */
+ inline int lineXOffsetWidget (Line *line)
+ {
+ return lineXOffsetContents (line) + getStyle()->boxOffsetX ();
+ }
+
+ inline int lineYOffsetWidgetAllocation (Line *line,
+ core::Allocation *allocation)
+ {
+ return line->top + (allocation->ascent - lines->getRef(0)->ascent);
+ }
+
+ inline int lineYOffsetWidget (Line *line)
+ {
+ return lineYOffsetWidgetAllocation (line, &allocation);
+ }
+
+ /**
+ * Like lineYOffsetCanvas, but with the allocation as parameter.
+ */
+ inline int lineYOffsetCanvasAllocation (Line *line,
+ core::Allocation *allocation)
+ {
+ return allocation->y + lineYOffsetWidgetAllocation(line, allocation);
+ }
+
+ /**
+ * Returns the y offset (within the canvas) of a line.
+ */
+ inline int lineYOffsetCanvas (Line *line)
+ {
+ return lineYOffsetCanvasAllocation(line, &allocation);
+ }
+
+ inline int lineYOffsetWidgetI (int lineIndex)
+ {
+ return lineYOffsetWidget (lines->getRef (lineIndex));
+ }
+
+ inline int lineYOffsetCanvasI (int lineIndex)
+ {
+ return lineYOffsetCanvas (lines->getRef (lineIndex));
+ }
+
+ bool sendSelectionEvent (core::SelectionState::EventType eventType,
+ core::MousePositionEvent *event);
+
+ virtual void wordWrap(int wordIndex);
+
+ void sizeRequestImpl (core::Requisition *requisition);
+ void getExtremesImpl (core::Extremes *extremes);
+ void sizeAllocateImpl (core::Allocation *allocation);
+ void resizeDrawImpl ();
+
+ void markSizeChange (int ref);
+ void markExtremesChange (int ref);
+ void setWidth (int width);
+ void setAscent (int ascent);
+ void setDescent (int descent);
+ void draw (core::View *view, core::Rectangle *area);
+
+ bool buttonPressImpl (core::EventButton *event);
+ bool buttonReleaseImpl (core::EventButton *event);
+ bool motionNotifyImpl (core::EventMotion *event);
+ void enterNotifyImpl (core::EventCrossing *event);
+ void leaveNotifyImpl (core::EventCrossing *event);
+
+ void removeChild (Widget *child);
+
+public:
+ static int CLASS_ID;
+
+ Textblock(bool limitTextWidth);
+ ~Textblock();
+
+ core::Iterator *iterator (core::Content::Type mask, bool atEnd);
+
+ void flush ();
+
+ void addText (const char *text, core::style::Style *style);
+ void addWidget (core::Widget *widget, core::style::Style *style);
+ bool addAnchor (const char *name, core::style::Style *style);
+ void addSpace(core::style::Style *style);
+ void addParbreak (int space, core::style::Style *style);
+ void addLinebreak (core::style::Style *style);
+
+ core::Widget *getWidgetAtPoint (int x, int y, int level);
+ void handOverBreak (core::style::Style *style);
+ void changeLinkColor (int link, int newColor);
+ void changeWordStyle (int from, int to, core::style::Style *style,
+ bool includeFirstSpace, bool includeLastSpace);
+};
+
+} // namespace dw
+
+#endif // __DW_TEXTBLOCK_HH__
diff --git a/dw/types.cc b/dw/types.cc
new file mode 100644
index 00000000..94f95b42
--- /dev/null
+++ b/dw/types.cc
@@ -0,0 +1,260 @@
+/*
+ * Dillo Widget
+ *
+ * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+
+
+#include "core.hh"
+
+namespace dw {
+namespace core {
+
+Rectangle::Rectangle (int x, int y, int width, int height)
+{
+ this->x = x;
+ this->y = y;
+ this->width = width;
+ this->height = height;
+}
+
+/**
+ * Return whether this rectangle and otherRect intersect. If yes,
+ * return the intersection rectangle in dest.
+ *
+ * \todo The function has been copied from gdktrectangle.c. Is this relevant
+ * for copyright?
+ */
+bool Rectangle::intersectsWith (Rectangle *otherRect, Rectangle *dest)
+{
+ Rectangle *src1 = this, *src2 = otherRect, *temp;
+ int src1_x2, src1_y2;
+ int src2_x2, src2_y2;
+ bool return_val;
+
+ return_val = false;
+
+ if (src2->x < src1->x) {
+ temp = src1;
+ src1 = src2;
+ src2 = temp;
+ }
+ dest->x = src2->x;
+
+ src1_x2 = src1->x + src1->width;
+ src2_x2 = src2->x + src2->width;
+
+ if (src2->x < src1_x2) {
+ if (src1_x2 < src2_x2)
+ dest->width = src1_x2 - dest->x;
+ else
+ dest->width = src2_x2 - dest->x;
+
+ if (src2->y < src1->y) {
+ temp = src1;
+ src1 = src2;
+ src2 = temp;
+ }
+ dest->y = src2->y;
+
+ src1_y2 = src1->y + src1->height;
+ src2_y2 = src2->y + src2->height;
+
+ if (src2->y < src1_y2) {
+ return_val = true;
+
+ if (src1_y2 < src2_y2)
+ dest->height = src1_y2 - dest->y;
+ else
+ dest->height = src2_y2 - dest->y;
+
+ if (dest->height == 0)
+ return_val = false;
+ if (dest->width == 0)
+ return_val = false;
+ }
+ }
+
+ return return_val;
+}
+
+/*
+ * Return whether this is a subset of otherRect.
+ */
+bool Rectangle::isSubsetOf (Rectangle *otherRect)
+{
+ return
+ x >= otherRect->x &&
+ y >= otherRect->y &&
+ x + width <= otherRect->x + otherRect->width &&
+ y + height <= otherRect->y + otherRect->height;
+}
+
+bool Rectangle::isPointWithin (int x, int y)
+{
+ return
+ x >= this->x && y >= this->y &&
+ x < this->x + width && y < this->y + height;
+}
+
+// ----------------------------------------------------------------------
+
+Circle::Circle (int x, int y, int radius)
+{
+ this->x = x;
+ this->y = y;
+ this->radius = radius;
+}
+
+bool Circle::isPointWithin (int x, int y)
+{
+ return
+ (x - this->x) * (x - this->x) + (y - this->y) * (y - this->y)
+ <= radius * radius;
+}
+
+// ----------------------------------------------------------------------
+
+Polygon::Polygon ()
+{
+ points = new misc::SimpleVector<Point> (8);
+ minx = miny = 0xffffff;
+ maxx = maxy = -0xffffff;
+}
+
+Polygon::~Polygon ()
+{
+ delete points;
+}
+
+void Polygon::addPoint (int x, int y)
+{
+ points->increase ();
+ points->getRef(points->size () - 1)->x = x;
+ points->getRef(points->size () - 1)->y = y;
+
+ minx = misc::min(minx, x);
+ miny = misc::min(miny, y);
+ maxx = misc::max(maxx, x);
+ maxy = misc::max(maxy, y);
+}
+
+/**
+ * \brief Return, whether the line, limited by (ax1, ay1) and (ax2, ay2),
+ * crosses the unlimited line, determined by two points (bx1, by1) and
+ * (bx2, by2).
+ */
+bool Polygon::linesCross0(int ax1, int ay1, int ax2, int ay2,
+ int bx1, int by1, int bx2, int by2)
+{
+ /** TODO Some more description */
+ // If the scalar product is 0, it means that one point is on the second
+ // line, so we check for <= 0, not < 0.
+ return
+ zOfVectorProduct (ax1 - bx1, ay1 - by1, bx2 - bx1, by2 - by1) *
+ zOfVectorProduct (ax2 - bx1, ay2 - by1, bx2 - bx1, by2 - by1) <= 0;
+}
+
+/**
+ * \brief Return, whether the line, limited by (ax1, ay1) and (ax2, ay2),
+ * crosses the line, limited by (bx1, by1) and (bx2, by2).
+ */
+bool Polygon::linesCross(int ax1, int ay1, int ax2, int ay2,
+ int bx1, int by1, int bx2, int by2)
+{
+ bool cross =
+ linesCross0 (ax1, ay1, ax2, ay2, bx1, by1, bx2, by2) &&
+ linesCross0 (bx1, by1, bx2, by2, ax1, ay1, ax2, ay2);
+ //printf ("(%d, %d) - (%d, %d) and (%d, %d) - (%d, %d) cross? %s.\n",
+ // ax1, ay1, ax2, ay2, bx1, by1, bx2, by2, cross ? "Yes" : "No");
+ return cross;
+}
+
+bool Polygon::isPointWithin (int x, int y)
+{
+ if (points->size () < 3 ||
+ (x < minx || x > maxx || y < miny || y >= maxy))
+ return false;
+ else {
+ int numCrosses = 0;
+ for (int i = 0; i < points->size () - 1; i++) {
+ if (linesCross (minx - 1, miny - 1, x, y,
+ points->getRef(i)->x, points->getRef(i)->y,
+ points->getRef(i + 1)->x, points->getRef(i + 1)->y))
+ numCrosses++;
+ }
+ if (linesCross (minx - 1, miny - 1, x, y,
+ points->getRef(points->size () - 1)->x,
+ points->getRef(points->size () - 1)->y,
+ points->getRef(0)->x, points->getRef(0)->y))
+ numCrosses++;
+
+ return numCrosses % 2 == 1;
+ }
+}
+
+Region::Region()
+{
+ rectangleList = new container::typed::List <Rectangle> (true);
+}
+
+Region::~Region()
+{
+ delete rectangleList;
+}
+
+/**
+ * \brief Add a rectangle to the region and combine it with
+ * existing rectangles if possible.
+ * The number of rectangles is forced to be less than 16
+ * by combining excessive rectangles.
+ */
+void Region::addRectangle (Rectangle *rPointer)
+{
+ container::typed::Iterator <Rectangle> it;
+ Rectangle *r = new Rectangle (rPointer->x, rPointer->y,
+ rPointer->width, rPointer->height);
+
+ for (it = rectangleList->iterator (); it.hasNext (); ) {
+ Rectangle *ownRect = it.getNext ();
+
+ int combinedHeight =
+ misc::max(r->y + r->height, ownRect->y + ownRect->height) -
+ misc::min(r->y, ownRect->y);
+ int combinedWidth =
+ misc::max(r->x + r->width, ownRect->x + ownRect->width) -
+ misc::min(r->x, ownRect->x);
+
+ if (rectangleList->size() >= 16 ||
+ combinedWidth * combinedHeight <=
+ ownRect->width * ownRect->height + r->width * r->height) {
+
+ r->x = misc::min(r->x, ownRect->x);
+ r->y = misc::min(r->y, ownRect->y);
+ r->width = combinedWidth;
+ r->height = combinedHeight;
+
+ rectangleList->removeRef (ownRect);
+ }
+ }
+
+ rectangleList->append (r);
+}
+
+} // namespace dw
+} // namespace core
diff --git a/dw/types.hh b/dw/types.hh
new file mode 100644
index 00000000..bdfca629
--- /dev/null
+++ b/dw/types.hh
@@ -0,0 +1,204 @@
+#ifndef __DW_TYPES_HH__
+#define __DW_TYPES_HH__
+
+#ifndef __INCLUDED_FROM_DW_CORE_HH__
+# error Do not include this file directly, use "core.hh" instead.
+#endif
+
+namespace dw {
+namespace core {
+
+using namespace lout;
+
+enum HPosition
+{
+ HPOS_LEFT,
+ HPOS_CENTER,
+ HPOS_RIGHT,
+ HPOS_INTO_VIEW, /* scroll only, until the content in question comes
+ * into view */
+ HPOS_NO_CHANGE
+};
+
+enum VPosition
+{
+ VPOS_TOP,
+ VPOS_CENTER,
+ VPOS_BOTTOM,
+ VPOS_INTO_VIEW, /* scroll only, until the content in question comes
+ * into view */
+ VPOS_NO_CHANGE
+};
+
+
+/*
+ * Different "layers" may be highlighted in a widget.
+ */
+enum HighlightLayer
+{
+ HIGHLIGHT_SELECTION,
+ HIGHLIGHT_FINDTEXT,
+ HIGHLIGHT_NUM_LAYERS
+};
+
+struct Point
+{
+ int x;
+ int y;
+};
+
+/**
+ * \brief Abstract interface for different shapes.
+ */
+class Shape: public object::Object
+{
+public:
+ virtual bool isPointWithin (int x, int y) = 0;
+};
+
+/**
+ * \brief dw::core::Shape implemtation for simple rectangles.
+ */
+class Rectangle: public Shape
+{
+public:
+ int x;
+ int y;
+ int width;
+ int height;
+
+ inline Rectangle () { }
+ Rectangle (int x, int y, int width, int height);
+
+ bool intersectsWith (Rectangle *otherRect, Rectangle *dest);
+ bool isSubsetOf (Rectangle *otherRect);
+ bool isPointWithin (int x, int y);
+ bool isEmpty () { return width <= 0 || height <= 0; };
+};
+
+/**
+ * \brief dw::core::Shape implemtation for simple circles.
+ */
+class Circle: public Shape
+{
+public:
+ int x, y, radius;
+
+ Circle (int x, int y, int radius);
+
+ bool isPointWithin (int x, int y);
+};
+
+/**
+ * \brief dw::core::Shape implemtation for polygons.
+ */
+class Polygon: public Shape
+{
+private:
+ misc::SimpleVector<Point> *points;
+ int minx, miny, maxx, maxy;
+
+ /**
+ * \brief Return the z-coordinate of the vector product of two
+ * vectors, whose z-coordinate is 0 (so that x and y of
+ * the vector product is 0, too).
+ */
+ inline int zOfVectorProduct(int x1, int y1, int x2, int y2) {
+ return x1 * y2 - x2 * y1;
+ }
+
+ bool linesCross0(int ax1, int ay1, int ax2, int ay2,
+ int bx1, int by1, int bx2, int by2);
+ bool linesCross(int ax1, int ay1, int ax2, int ay2,
+ int bx1, int by1, int bx2, int by2);
+
+public:
+ Polygon ();
+ ~Polygon ();
+
+ void addPoint (int x, int y);
+ bool isPointWithin (int x, int y);
+};
+
+/**
+ * Implementation for a point set.
+ * Currently represented as a set of rectangles not containing
+ * each other.
+ * It is guaranteed that the rectangles returned by rectangles ()
+ * cover all rectangles that were added with addRectangle ().
+ */
+class Region
+{
+private:
+ container::typed::List <Rectangle> *rectangleList;
+
+public:
+ Region ();
+ ~Region ();
+
+ void clear () { rectangleList->clear (); };
+
+ void addRectangle (Rectangle *r);
+
+ container::typed::Iterator <Rectangle> rectangles ()
+ {
+ return rectangleList->iterator ();
+ };
+};
+
+/**
+ * \brief Represents the allocation, i.e. actual position and size of a
+ * dw::core::Widget.
+ */
+struct Allocation
+{
+ int x;
+ int y;
+ int width;
+ int ascent;
+ int descent;
+};
+
+struct Requisition
+{
+ int width;
+ int ascent;
+ int descent;
+};
+
+struct Extremes
+{
+ int minWidth;
+ int maxWidth;
+};
+
+struct Content
+{
+ enum Type {
+ START = 1 << 0,
+ END = 1 << 1,
+ TEXT = 1 << 2,
+ WIDGET = 1 << 3,
+ ANCHOR = 1 << 4,
+ BREAK = 1 << 5,
+ ALL = 0xff,
+ REAL_CONTENT = 0xff ^ (START | END),
+ SELECTION_CONTENT = TEXT | WIDGET | BREAK
+ };
+ /* Content is embedded in struct Word therefore we
+ * try to be space efficient.
+ */
+ short type;
+ bool space;
+ union {
+ const char *text;
+ Widget *widget;
+ char *anchor;
+ int breakSpace;
+ };
+};
+
+} // namespace dw
+} // namespace core
+
+#endif // __DW_TYPES_HH__
diff --git a/dw/ui.cc b/dw/ui.cc
new file mode 100644
index 00000000..f857e387
--- /dev/null
+++ b/dw/ui.cc
@@ -0,0 +1,364 @@
+/*
+ * Dillo Widget
+ *
+ * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+
+
+#include "core.hh"
+
+#include <stdio.h>
+
+namespace dw {
+namespace core {
+namespace ui {
+
+using namespace object;
+
+int Embed::CLASS_ID = -1;
+
+Embed::Embed(Resource *resource)
+{
+ registerName ("dw::core::ui::Embed", &CLASS_ID);
+ this->resource = resource;
+ resource->setEmbed (this);
+}
+
+Embed::~Embed()
+{
+ delete resource;
+}
+
+void Embed::sizeRequestImpl (Requisition *requisition)
+{
+ resource->sizeRequest (requisition);
+}
+
+void Embed::getExtremesImpl (Extremes *extremes)
+{
+ resource->getExtremes (extremes);
+}
+
+void Embed::sizeAllocateImpl (Allocation *allocation)
+{
+ resource->sizeAllocate (allocation);
+}
+
+void Embed::enterNotifyImpl (core::EventCrossing *event)
+{
+ resource->emitEnter();
+}
+
+void Embed::leaveNotifyImpl (core::EventCrossing *event)
+{
+ resource->emitLeave();
+}
+
+void Embed::setWidth (int width)
+{
+ resource->setWidth (width);
+}
+
+void Embed::setAscent (int ascent)
+{
+ resource->setAscent (ascent);
+}
+
+void Embed::setDescent (int descent)
+{
+ resource->setDescent (descent);
+}
+
+void Embed::draw (View *view, Rectangle *area)
+{
+ drawWidgetBox (view, area, false);
+ resource->draw (view, area);
+}
+
+Iterator *Embed::iterator (Content::Type mask, bool atEnd)
+{
+ return resource->iterator (mask, atEnd);
+}
+
+void Embed::setStyle (style::Style *style)
+{
+ resource->setStyle (style);
+ Widget::setStyle (style);
+}
+
+// ----------------------------------------------------------------------
+
+bool Resource::ActivateEmitter::emitToReceiver (lout::signal::Receiver
+ *receiver,
+ int signalNo,
+ int argc, Object **argv)
+{
+ ActivateReceiver *ar = (ActivateReceiver*)receiver;
+ Resource *res = (Resource*)((Pointer*)argv[0])->getValue ();
+
+ switch (signalNo) {
+ case 0:
+ ar->activate (res);
+ break;
+ case 1:
+ ar->enter (res);
+ break;
+ case 2:
+ ar->leave (res);
+ break;
+ default:
+ misc::assertNotReached ();
+ }
+ return false;
+}
+
+void Resource::ActivateEmitter::emitActivate (Resource *resource)
+{
+ Pointer p (resource);
+ Object *argv[1] = { &p };
+ emitVoid (0, 1, argv);
+}
+
+void Resource::ActivateEmitter::emitEnter (Resource *resource)
+{
+ Pointer p (resource);
+ Object *argv[1] = { &p };
+ emitVoid (1, 1, argv);
+}
+
+void Resource::ActivateEmitter::emitLeave (Resource *resource)
+{
+ Pointer p (resource);
+ Object *argv[1] = { &p };
+ emitVoid (2, 1, argv);
+}
+
+// ----------------------------------------------------------------------
+
+Resource::~Resource ()
+{
+}
+
+void Resource::setEmbed (Embed *embed)
+{
+ this->embed = embed;
+}
+
+void Resource::getExtremes (Extremes *extremes)
+{
+ /* Simply return the requisition width */
+ Requisition requisition;
+ sizeRequest (&requisition);
+ extremes->minWidth = extremes->maxWidth = requisition.width;
+}
+
+void Resource::sizeAllocate (Allocation *allocation)
+{
+}
+
+void Resource::setWidth (int width)
+{
+}
+
+void Resource::setAscent (int ascent)
+{
+}
+
+void Resource::setDescent (int descent)
+{
+}
+
+void Resource::draw (View *view, Rectangle *area)
+{
+}
+
+void Resource::setStyle (style::Style *style)
+{
+}
+
+void Resource::emitEnter ()
+{
+ activateEmitter.emitEnter(this);
+}
+
+void Resource::emitLeave ()
+{
+ activateEmitter.emitLeave(this);
+}
+
+// ----------------------------------------------------------------------
+
+bool ButtonResource::ClickedEmitter::emitToReceiver (lout::signal::Receiver
+ *receiver,
+ int signalNo,
+ int argc,
+ Object **argv)
+{
+ ((ClickedReceiver*)receiver)
+ ->clicked ((ButtonResource*)((Pointer*)argv[0])->getValue (),
+ ((Integer*)argv[1])->getValue (),
+ ((Integer*)argv[2])->getValue (),
+ ((Integer*)argv[3])->getValue ());
+ return false;
+}
+
+void ButtonResource::ClickedEmitter::emitClicked (ButtonResource *resource,
+ int buttonNo, int x, int y)
+{
+ Integer i1 (buttonNo);
+ Integer i2 (x);
+ Integer i3 (y);
+ Pointer p (resource);
+ Object *argv[4] = { &p, &i1, &i2, &i3 };
+ emitVoid (0, 4, argv);
+}
+
+// ----------------------------------------------------------------------
+
+Iterator *LabelButtonResource::iterator (Content::Type mask, bool atEnd)
+{
+ /** \todo Perhaps in brackets? */
+ // return new TextIterator (getEmbed (), mask, atEnd, getLabel ());
+ /** \bug Not implemented. */
+ return new EmptyIterator (getEmbed (), mask, atEnd);
+}
+
+// ----------------------------------------------------------------------
+
+void ComplexButtonResource::LayoutReceiver::canvasSizeChanged (int width,
+ int ascent,
+ int descent)
+{
+ /**
+ * \todo The argument to queueResize is not always true. (But this works.)
+ */
+ resource->queueResize (true);
+}
+
+ComplexButtonResource::ComplexButtonResource ()
+{
+ layout = NULL;
+ layoutReceiver.resource = this;
+ click_x = click_y = -1;
+}
+
+void ComplexButtonResource::init (Widget *widget)
+{
+ this->widget = widget;
+
+ layout = new Layout (createPlatform ());
+ setLayout (layout);
+ layout->setWidget (widget);
+ layout->connect (&layoutReceiver);
+}
+
+void ComplexButtonResource::setEmbed (Embed *embed)
+{
+ ButtonResource::setEmbed (embed);
+
+ if (widget->usesHints ())
+ embed->setUsesHints ();
+}
+
+ComplexButtonResource::~ComplexButtonResource ()
+{
+ delete layout;
+}
+
+void ComplexButtonResource::sizeRequest (Requisition *requisition)
+{
+ Requisition widgetRequisition;
+ widget->sizeRequest (&widgetRequisition);
+ requisition->width = widgetRequisition.width + 2 * reliefXThickness ();
+ requisition->ascent = widgetRequisition.ascent + reliefYThickness ();
+ requisition->descent = widgetRequisition.descent + reliefYThickness ();
+}
+
+void ComplexButtonResource::getExtremes (Extremes *extremes)
+{
+ Extremes widgetExtremes;
+ widget->getExtremes (&widgetExtremes);
+ extremes->minWidth = widgetExtremes.minWidth + 2 * reliefXThickness ();
+ extremes->maxWidth = widgetExtremes.maxWidth + 2 * reliefXThickness ();
+}
+
+void ComplexButtonResource::sizeAllocate (Allocation *allocation)
+{
+}
+
+void ComplexButtonResource::setWidth (int width)
+{
+ widget->setWidth (width - 2 * reliefXThickness ());
+}
+
+void ComplexButtonResource::setAscent (int ascent)
+{
+ widget->setAscent (ascent - reliefYThickness ());
+}
+
+void ComplexButtonResource::setDescent (int descent)
+{
+ widget->setDescent (descent - reliefYThickness ());
+}
+
+Iterator *ComplexButtonResource::iterator (Content::Type mask, bool atEnd)
+{
+ /**
+ * \bug Implementation.
+ * This is a bit more complicated: We have two layouts here.
+ */
+ return new EmptyIterator (getEmbed (), mask, atEnd);
+}
+
+// ----------------------------------------------------------------------
+
+Iterator *TextResource::iterator (Content::Type mask, bool atEnd)
+{
+ // return new TextIterator (getEmbed (), mask, atEnd, getText ());
+ /** \bug Not implemented. */
+ return new EmptyIterator (getEmbed (), mask, atEnd);
+}
+
+// ----------------------------------------------------------------------
+
+Iterator *CheckButtonResource::iterator (Content::Type mask, bool atEnd)
+{
+ //return new TextIterator (getEmbed (), mask, atEnd,
+ // isActivated () ? "[X]" : "[ ]");
+ /** \bug Not implemented. */
+ return new EmptyIterator (getEmbed (), mask, atEnd);
+}
+
+// ----------------------------------------------------------------------
+
+RadioButtonResource::GroupIterator::~GroupIterator ()
+{
+}
+
+Iterator *RadioButtonResource::iterator (Content::Type mask, bool atEnd)
+{
+ //return new TextIterator (getEmbed (), mask, atEnd,
+ // isActivated () ? "(*)" : "( )");
+ /** \bug Not implemented. */
+ return new EmptyIterator (getEmbed (), mask, atEnd);
+}
+
+} // namespace ui
+} // namespace core
+} // namespace core
+
diff --git a/dw/ui.hh b/dw/ui.hh
new file mode 100644
index 00000000..de3e1b2b
--- /dev/null
+++ b/dw/ui.hh
@@ -0,0 +1,564 @@
+#ifndef __DW_UI_HH__
+#define __DW_UI_HH__
+
+#ifndef __INCLUDED_FROM_DW_CORE_HH__
+# error Do not include this file directly, use "core.hh" instead.
+#endif
+
+namespace dw {
+namespace core {
+
+/**
+ * \brief Anything related to embedded UI widgets is defined here.
+ *
+ * UI resources are another abstraction for Dw widgets, which are not
+ * fully implemented in a platform-independent way. Typically, they
+ * involve creating widgets, which the underlying UI toolkit provides.
+ *
+ * As you see in this diagram:
+ *
+ * \dot
+ * digraph G {
+ * node [shape=record, fontname=Helvetica, fontsize=10];
+ * edge [arrowhead="none", arrowtail="empty", labelfontname=Helvetica,
+ * labelfontsize=10, color="#404040", labelfontcolor="#000080"];
+ * fontname=Helvetica; fontsize=10;
+ *
+ * subgraph cluster_core {
+ * style="dashed"; color="#000080"; fontname=Helvetica; fontsize=10;
+ * label="dw::core";
+ *
+ * subgraph cluster_ui {
+ * style="dashed"; color="#000080"; fontname=Helvetica; fontsize=10;
+ * label="dw::core::ui";
+ *
+ * Embed [URL="\ref dw::core::ui::Embed"];
+ * Resource [color="#a0a0a0", URL="\ref dw::core::ui::Resource"];
+ * LabelButtonResource [color="#a0a0a0",
+ * URL="\ref dw::core::ui::LabelButtonResource"];
+ * EntryResource [color="#a0a0a0",
+ * URL="\ref dw::core::ui::EntryResource"];
+ * etc [color="#a0a0a0", label="..."];
+ * }
+ *
+ * Widget [URL="\ref dw::core::Widget", color="#a0a0a0"];
+ * }
+ *
+ * subgraph cluster_fltk {
+ * style="dashed"; color="#000080"; fontname=Helvetica; fontsize=10;
+ * label="dw::fltk::ui";
+ *
+ * FltkLabelButtonResource
+ * [URL="\ref dw::fltk::ui::FltkLabelButtonResource"];
+ * FltkEntryResource [URL="\ref dw::fltk::ui::FltkEntryResource"];
+ * }
+ *
+ * Widget -> Embed;
+ * Embed -> Resource [arrowhead="open", arrowtail="none",
+ * headlabel="1", taillabel="1"];
+ * Resource -> LabelButtonResource;
+ * Resource -> EntryResource;
+ * Resource -> etc;
+ * LabelButtonResource -> FltkLabelButtonResource;
+ * EntryResource -> FltkEntryResource;
+ * }
+ * \enddot
+ *
+ * <center>[\ref uml-legend "legend"]</center>
+ *
+ * there are several levels:
+ *
+ * <ol>
+ * <li> The Dw widget is dw::core::ui::Embed. It delegates most to
+ * dw::core::ui::Resource, which has similar methods like
+ * dw::core::Widget.
+ *
+ * <li> There are several sub interfaces of dw::core::ui::Resource, which
+ * may provide methods, as e.g. dw::core::ui::ListResource::addItem. In a
+ * platform independent context, you can cast the result of
+ * dw::core::ui::Embed::getResource to a specific sub class, if you
+ * know, which one is used. E.g., if you know, that a given instance
+ * dw::core::ui::Embed refers to a dw::core::ui::ListResource, you can
+ * write something like:
+ *
+ * \code
+ * dw::core::ui::Embed *embed;
+ * //...
+ * ((dw::core::ui::ListResource*)embed->getResource ())->addItem ("Hello!");
+ * \endcode
+ *
+ * <li> These sub classes are then fully implemented in a platform specific
+ * way. For an example, look at dw::fltk::ui.
+ * </ol>
+ *
+ * There is a factory interface, dw::core::ui::ResourceFactory, which
+ * provides methods for creating common resources. By calling
+ * dw::core::Layout::getResourceFactory, which calls
+ * dw::core::Platform::getResourceFactory, you get the factory for the used
+ * platform.
+ *
+ * It is possible to define additional sub classes of
+ * dw::core::ui::Resource, but since they are not provided by
+ * dw::core::ui::ResourceFactory, you have to define some other
+ * abstractions, if you want to remain platform independent.
+ *
+ *
+ * <h3>...</h3>
+ *
+ *
+ * <h3>Resouces needed for HTML</h3>
+ *
+ * This chapter describes, how the form controls defined by HTML are
+ * implemented in Dw. Some of them do not refer to UI resources, but to
+ * other widgets, links to the respective documentations are provided
+ * here.
+ *
+ * <h4>Resouces created with \<INPUT\></h4>
+ *
+ * The HTML \<INPUT\> is always implemented by using UI
+ * resources. \<INPUT\> element has the following attributes:
+ *
+ * <table>
+ * <tr><th>Attribute <th>Implementation
+ * <tr><td>type <td>This defines the resource you have to instanciate.
+ * <tr><td>name <td>Not needed within Dw.
+ * <tr><td>value <td>The initial value is treated differently by different
+ * resources.
+ * <tr><td>checked <td>Parameter to
+ * dw::core::ui::ResourceFactory::createCheckButtonResource
+ * and dw::core::ui::ResourceFactory::createRadioButtonResource.
+ * <tr><td>disabled <td>This is provided for all resources by
+ * dw::core::ui::Resource::setEnabled.
+ * <tr><td>readonly <td>This is provided by
+ * dw::core::ui::TextResource::setEditable.
+ * <tr><td>size <td>This is handled by styles.
+ * <tr><td>maxlength <td>Parameter of
+ * dw::core::ui::ResourceFactory::createEntryResource.
+ * <tr><td>src <td>Handled by the caller (HTML parser).
+ * <tr><td>alt <td>Handled by the caller (HTML parser).
+ * <tr><td>usemap <td>Handled by the caller (HTML parser).
+ * <tr><td>ismap <td>Handled by the caller (HTML parser).
+ * <tr><td>tabindex <td>Not supported currently.
+ * <tr><td>accesskey <td>Not supported currently.
+ * <tr><td>onfocus <td>Not supported currently.
+ * <tr><td>onblur <td>Not supported currently.
+ * <tr><td>onselect <td>Not supported currently.
+ * <tr><td>onchange <td>Not supported currently.
+ * <tr><td>accept <td>Not supported currently.
+ * </table>
+ *
+ * For the different values of \em type, the following resources can be
+ * used:
+ *
+ * <table>
+ * <tr><th>Type <th>Resource
+ * <th>Factory Method
+ * <tr><td>text <td>dw::core::ui::EntryResource
+ * <td>dw::core::ui::ResourceFactory::createEntryResource
+ * <tr><td>password <td>dw::core::ui::EntryResource
+ * <td>dw::core::ui::ResourceFactory::createEntryResource
+ * <tr><td>checkbox <td>dw::core::ui::CheckButtonResource
+ * <td>dw::core::ui::ResourceFactory::createCheckButtonResource
+ * <tr><td>radio <td>dw::core::ui::RadioButtonResource
+ * <td>dw::core::ui::ResourceFactory::createRadioButtonResource
+ * <tr><td>submit <td>dw::core::ui::LabelButtonResource
+ * <td>dw::core::ui::ResourceFactory::createLabelButtonResource
+ * <tr><td>image <td>dw::core::ui::ComplexButtonResource
+ * <td>dw::core::ui::ResourceFactory::createComplexButtonResource,
+ * width a dw::Image inside and relief = false.
+ * <tr><td>reset <td>dw::core::ui::LabelButtonResource
+ * <td>dw::core::ui::ResourceFactory::createLabelButtonResource
+ * <tr><td>button <td>dw::core::ui::LabelButtonResource
+ * <td>dw::core::ui::ResourceFactory::createLabelButtonResource
+ * <tr><td>hidden <td>No rendering necessary.
+ * <td>-
+ * <tr><td>file <td>Not supported currently.
+ * <td>-
+ * </table>
+ *
+ * <h4>\<SELECT\>, \<OPTGROUP\>, and \<OPTION\></h4>
+ *
+ * \<SELECT\> is implemented either by dw::core::ui::OptionMenuResource
+ * (better suitable for \em size = 1 and single selection) or
+ * dw::core::ui::ListResource, which have a common base,
+ * dw::core::ui::SelectionResource. In the latter case, \em size must be
+ * specified via dw::core::style::Style.
+ *
+ * Factory methods are dw::core::ui::ResourceFactory::createListResource and
+ * dw::core::ui::ResourceFactory::createOptionMenuResource.
+ *
+ * \<OPTION\>'s are added via dw::core::ui::SelectionResource::addItem.
+ *
+ * \<OPTGROUP\> are created by using dw::core::ui::SelectionResource::pushGroup
+ * and dw::core::ui::SelectionResource::popGroup.
+ *
+ * For lists, the selection mode must be set in
+ * dw::core::ui::ResourceFactory::createListResource.
+ *
+ * <h4>\<TEXTAREA\></h4>
+ *
+ * \<TEXTAREA\> is implemented by dw::core::ui::MultiLineTextResource,
+ * the factory method is
+ * dw::core::ui::ResourceFactory::createMultiLineTextResource.
+ * dw::core::ui::TextResource::setEditable can be used, as for entries.
+ *
+ * <h4>\<BUTTON\></h4>
+ *
+ * For handling \<BUTTON\>, dw::core::ui::ComplexButtonResource should be used,
+ * with a dw::Textblock inside, and relief = true. The contents of \<BUTTON\>
+ * is then added to the dw::Textblock.
+ *
+ * \todo describe activation signal
+ */
+namespace ui {
+
+class Resource;
+
+/**
+ * \brief A widget for embedding UI widgets.
+ *
+ * \sa dw::core::ui
+ */
+class Embed: public Widget
+{
+ friend class Resource;
+
+private:
+ Resource *resource;
+
+protected:
+ void sizeRequestImpl (Requisition *requisition);
+ void getExtremesImpl (Extremes *extremes);
+ void sizeAllocateImpl (Allocation *allocation);
+ void enterNotifyImpl (core::EventCrossing *event);
+ void leaveNotifyImpl (core::EventCrossing *event);
+
+public:
+ static int CLASS_ID;
+
+ Embed(Resource *resource);
+ ~Embed();
+
+ void setWidth (int width);
+ void setAscent (int ascent);
+ void setDescent (int descent);
+ void draw (View *view, Rectangle *area);
+ Iterator *iterator (Content::Type mask, bool atEnd);
+ void setStyle (style::Style *style);
+
+ inline void setUsesHints () { setFlags (USES_HINTS); }
+
+ inline Resource *getResource () { return resource; }
+};
+
+/**
+ * \brief Basic interface for all resources.
+ *
+ * \sa dw::core::ui
+ */
+class Resource
+{
+ friend class Embed;
+
+public:
+ /**
+ * \brief Receiver interface for the "activate" signal.
+ */
+ class ActivateReceiver: public lout::signal::Receiver
+ {
+ public:
+ virtual void activate (Resource *resource) = 0;
+ virtual void enter (Resource *resource) = 0;
+ virtual void leave (Resource *resource) = 0;
+ };
+
+private:
+ class ActivateEmitter: public lout::signal::Emitter
+ {
+ protected:
+ bool emitToReceiver (lout::signal::Receiver *receiver, int signalNo,
+ int argc, Object **argv);
+ public:
+ inline void connectActivate (ActivateReceiver *receiver) {
+ connect (receiver); }
+ void emitActivate (Resource *resource);
+ void emitEnter (Resource *resource);
+ void emitLeave (Resource *resource);
+ };
+
+ Embed *embed;
+ ActivateEmitter activateEmitter;
+
+ void emitEnter ();
+ void emitLeave ();
+protected:
+ inline void queueResize (bool extremesChanged) {
+ if (embed) embed->queueResize (0, extremesChanged);
+ }
+
+ virtual Embed *getEmbed () { return embed; }
+ virtual void setEmbed (Embed *embed);
+
+ inline void emitActivate () {
+ return activateEmitter.emitActivate (this); }
+
+public:
+ inline Resource () { embed = NULL; }
+
+ virtual ~Resource ();
+
+ virtual void sizeRequest (Requisition *requisition) = 0;
+ virtual void getExtremes (Extremes *extremes);
+ virtual void sizeAllocate (Allocation *allocation);
+ virtual void setWidth (int width);
+ virtual void setAscent (int ascent);
+ virtual void setDescent (int descent);
+ virtual void draw (View *view, Rectangle *area);
+ virtual Iterator *iterator (Content::Type mask, bool atEnd) = 0;
+ virtual void setStyle (style::Style *style);
+
+ virtual bool isEnabled () = 0;
+ virtual void setEnabled (bool enabled) = 0;
+
+ inline void connectActivate (ActivateReceiver *receiver) {
+ activateEmitter.connectActivate (receiver); }
+};
+
+
+class ButtonResource: public Resource
+{
+public:
+ /**
+ * \brief Receiver interface for the "clicked" signal.
+ */
+ class ClickedReceiver: public lout::signal::Receiver
+ {
+ public:
+ virtual void clicked (ButtonResource *resource, int buttonNo, int x,
+ int y) = 0;
+ };
+
+private:
+ class ClickedEmitter: public lout::signal::Emitter
+ {
+ protected:
+ bool emitToReceiver (lout::signal::Receiver *receiver, int signalNo,
+ int argc, Object **argv);
+ public:
+ inline void connectClicked (ClickedReceiver *receiver) {
+ connect (receiver); }
+ void emitClicked (ButtonResource *resource, int buttonNo, int x, int y);
+ };
+
+ ClickedEmitter clickedEmitter;
+
+protected:
+ inline void emitClicked (int buttonNo, int x, int y) {
+ return clickedEmitter.emitClicked (this, buttonNo, x, y); }
+
+public:
+ inline void connectClicked (ClickedReceiver *receiver) {
+ clickedEmitter.connectClicked (receiver); }
+};
+
+/**
+ * \brief Interface for labelled buttons resources.
+ */
+class LabelButtonResource: public ButtonResource
+{
+public:
+ Iterator *iterator (Content::Type mask, bool atEnd);
+
+ virtual const char *getLabel () = 0;
+ virtual void setLabel (const char *label) = 0;
+};
+
+class ComplexButtonResource: public ButtonResource
+{
+private:
+ class LayoutReceiver: public Layout::Receiver
+ {
+ public:
+ ComplexButtonResource *resource;
+
+ void canvasSizeChanged (int width, int ascent, int descent);
+ };
+
+ friend class LayoutReceiver;
+ LayoutReceiver layoutReceiver;
+
+ Widget *widget;
+
+protected:
+ Layout *layout;
+ int click_x, click_y;
+
+ void setEmbed (Embed *embed);
+
+ virtual Platform *createPlatform () = 0;
+ virtual void setLayout (Layout *layout) = 0;
+
+ virtual int reliefXThickness () = 0;
+ virtual int reliefYThickness () = 0;
+
+ void init (Widget *widget);
+
+public:
+ ComplexButtonResource ();
+ ~ComplexButtonResource ();
+
+ void sizeRequest (Requisition *requisition);
+ void getExtremes (Extremes *extremes);
+ void sizeAllocate (Allocation *allocation);
+ void setWidth (int width);
+ void setAscent (int ascent);
+ void setDescent (int descent);
+ Iterator *iterator (Content::Type mask, bool atEnd);
+ int getClickX () {return click_x;};
+ int getClickY () {return click_y;};
+};
+
+/**
+ * \brief Base interface for dw::core::ui::ListResource and
+ * dw::core::ui::OptionMenuResource.
+ */
+class SelectionResource: public Resource
+{
+public:
+ virtual void addItem (const char *str, bool enabled, bool selected) = 0;
+
+ virtual void pushGroup (const char *name, bool enabled) = 0;
+ virtual void popGroup () = 0;
+
+ virtual int getNumberOfItems () = 0;
+ virtual const char *getItem (int index) = 0;
+ virtual bool isSelected (int index) = 0;
+};
+
+class ListResource: public SelectionResource
+{
+public:
+ enum SelectionMode {
+ /**
+ * \brief Exactly one item is selected.
+ *
+ * If no item is selected initially, the first one is selected.
+ */
+ SELECTION_EXACTLY_ONE,
+
+ /**
+ * \brief Exactly one item is selected, except possibly at the beginning.
+ *
+ * If no item is selected initially, no one is selected automatically.
+ * The user may not unselect the only selected item.
+ */
+ SELECTION_EXACTLY_ONE_BY_USER,
+
+ /**
+ * \brief At most one item is selected.
+ *
+ * If no item is selected initially, no one is selected automatically.
+ * The user may unselect the only selected item.
+ */
+ SELECTION_AT_MOST_ONE,
+
+ /**
+ * \brief An arbitrary number of items may be selected.
+ */
+ SELECTION_MULTIPLE
+ };
+};
+
+class OptionMenuResource: public SelectionResource
+{
+};
+
+class TextResource: public Resource
+{
+public:
+ Iterator *iterator (Content::Type mask, bool atEnd);
+
+ virtual const char *getText () = 0;
+ virtual void setText (const char *text) = 0;
+ virtual bool isEditable () = 0;
+ virtual void setEditable (bool editable) = 0;
+};
+
+class EntryResource: public TextResource
+{
+public:
+ enum { UNLIMITED_MAX_LENGTH = -1 };
+};
+
+class MultiLineTextResource: public TextResource
+{
+};
+
+
+class ToggleButtonResource: public Resource
+{
+public:
+ virtual bool isActivated () = 0;
+ virtual void setActivated (bool activated) = 0;
+};
+
+class CheckButtonResource: public ToggleButtonResource
+{
+public:
+ Iterator *iterator (Content::Type mask, bool atEnd);
+};
+
+class RadioButtonResource: public ToggleButtonResource
+{
+public:
+ class GroupIterator
+ {
+ protected:
+ GroupIterator () { }
+ virtual ~GroupIterator ();
+
+ public:
+ virtual bool hasNext () = 0;
+ virtual RadioButtonResource *getNext () = 0;
+ virtual void unref () = 0;
+ };
+
+ /**
+ * \brief Return an iterator, to access all radio button resources
+ * within the group.
+ */
+ virtual GroupIterator *groupIterator () = 0;
+
+ Iterator *iterator (Content::Type mask, bool atEnd);
+};
+
+
+/**
+ * \brief A factory for the common resource.
+ */
+class ResourceFactory: public object::Object
+{
+public:
+ virtual LabelButtonResource *createLabelButtonResource (const char *label)
+ = 0;
+ virtual ComplexButtonResource *createComplexButtonResource (Widget *widget,
+ bool relief)
+ = 0;
+ virtual ListResource *createListResource (ListResource::SelectionMode
+ selectionMode) = 0;
+ virtual OptionMenuResource *createOptionMenuResource () = 0;
+ virtual EntryResource *createEntryResource (int maxLength,
+ bool password) = 0;
+ virtual MultiLineTextResource *createMultiLineTextResource (int cols,
+ int rows) = 0;
+ virtual CheckButtonResource *createCheckButtonResource (bool activated) = 0;
+ virtual RadioButtonResource *createRadioButtonResource (RadioButtonResource
+ *groupedWith,
+ bool activated) = 0;
+};
+
+} // namespace ui
+} // namespace core
+} // namespace dw
+
+#endif // __DW_UI_HH__
diff --git a/dw/view.hh b/dw/view.hh
new file mode 100644
index 00000000..ef604549
--- /dev/null
+++ b/dw/view.hh
@@ -0,0 +1,204 @@
+#ifndef __DW_VIEW_HH__
+#define __DW_VIEW_HH__
+
+#ifndef __INCLUDED_FROM_DW_CORE_HH__
+# error Do not include this file directly, use "core.hh" instead.
+#endif
+
+namespace dw {
+namespace core {
+
+/**
+ * \brief An interface to encapsulate platform dependent drawing.
+ *
+ * \sa\ref dw-overview, \ref dw-layout-views
+ */
+class View: public object::Object
+{
+public:
+ /*
+ * ----------------------------
+ * Operations on the view
+ * ----------------------------
+ */
+
+ /**
+ * \brief This methods notifies the view, that it has been attached to a
+ * layout.
+ */
+ virtual void setLayout (Layout *layout) = 0;
+
+ /**
+ * \brief Set the canvas size.
+ */
+ virtual void setCanvasSize (int width, int ascent, int descent) = 0;
+
+ /**
+ * \brief Set the cursor appearance.
+ */
+ virtual void setCursor (style::Cursor cursor) = 0;
+
+ /**
+ * \brief Set the background of the view.
+ */
+ virtual void setBgColor (style::Color *color) = 0;
+
+ /*
+ * ---------------------------------------------------------
+ * Scrolling and Related. Only usesViewport must be
+ * implemented, if it returns false, the other methods
+ * are never called.
+ * ---------------­-----------­-----------------------------
+ */
+
+ /**
+ * \brief Return, whether this view uses a viewport.
+ */
+ virtual bool usesViewport () = 0;
+
+ /**
+ * \brief Get the thickness of the horizontal scrollbar, when it is
+ * visible.
+ *
+ * Does not have to be implemented, when usesViewport returns false.
+ */
+ virtual int getHScrollbarThickness () = 0;
+
+ /**
+ * \brief Get the thickness of the vertical scrollbar, when it is
+ * visible.
+ *
+ * Does not have to be implemented, when usesViewport returns false.
+ */
+ virtual int getVScrollbarThickness () = 0;
+
+ /**
+ * \brief Scroll the vieport to the given position.
+ *
+ * Does not have to be implemented, when usesViewport returns false.
+ */
+ virtual void scrollTo (int x, int y) = 0;
+
+ /**
+ * \brief Set the viewport size.
+ *
+ * Does not have to be implemented, when usesViewport returns false.
+ *
+ * This will normally imply a resize of the UI widget. Width and height are
+ * the dimensions of the new size, \em including the scrollbar thicknesses.
+ *
+ * \bug The rest of this comment needs to be updated.
+ *
+ * markerWidthDiff and markerHeightDiff are the respective dimensions of
+ * the viewport markers (see dw::core::getMarkerWidthDiff and
+ * dw::core::getMarkerHeightDiff), if they are 0, the respective
+ * marker should not be shown at all.
+ */
+ virtual void setViewportSize (int width, int height,
+ int hScrollbarThickness,
+ int vScrollbarThickness) = 0;
+
+ /*
+ * -----------------------
+ * Drawing functions
+ * -----------------------
+ */
+
+ /**
+ * \brief Called before drawing.
+ *
+ * All actual drawing operations will be enclosed into calls of
+ * dw::core:View::startDrawing and dw::core:View::finishDrawing. They
+ * may be implemented, e.g. when a backing
+ * pixmap is used, to prevent flickering. StartDrawing() will then
+ * initialize the backing pixmap, all other drawing operations will draw
+ * into it, and finishDrawing() will merge it into the window.
+ */
+ virtual void startDrawing (Rectangle *area) = 0;
+
+ /**
+ * \brief Called after drawing.
+ *
+ * \sa dw::core:View::startDrawing
+ */
+ virtual void finishDrawing (Rectangle *area) = 0;
+
+ /**
+ * \brief Queue a region, which is given in \em canvas coordinates, for
+ * drawing.
+ *
+ * The view implementation is responsible, that this region is drawn, either
+ * immediately, or (which is more typical, since more efficient) the areas
+ * are collected, combined (as far as possible), and the drawing is later
+ * done in an idle function.
+ */
+ virtual void queueDraw (Rectangle *area) = 0;
+
+ /**
+ * \brief Queue the total viewport for drawing.
+ *
+ * \sa dw::core::View::queueDraw
+ */
+ virtual void queueDrawTotal () = 0;
+
+ /**
+ * \brief Cancel a draw queue request.
+ *
+ * If dw::core::View::queueDraw or dw::core::View::queueDrawTotal have been
+ * called before, and the actual drawing was not processed yet, the actual
+ * drawing should be cancelled. Otherwise, the cancellation should be
+ * ignored.
+ */
+ virtual void cancelQueueDraw () = 0;
+
+ /*
+ * The following methods should be self-explaining.
+ */
+
+ virtual void drawPoint (style::Color *color,
+ style::Color::Shading shading,
+ int x, int y) = 0;
+ virtual void drawLine (style::Color *color,
+ style::Color::Shading shading,
+ int x1, int y1, int x2, int y2) = 0;
+ virtual void drawRectangle (style::Color *color,
+ style::Color::Shading shading, bool filled,
+ int x, int y, int width, int height) = 0;
+ virtual void drawArc (style::Color *color,
+ style::Color::Shading shading, bool filled,
+ int x, int y, int width, int height,
+ int angle1, int angle2) = 0;
+ virtual void drawPolygon (style::Color *color,
+ style::Color::Shading shading,
+ bool filled, int points[][2], int npoints) = 0;
+ virtual void drawText (style::Font *font,
+ style::Color *color,
+ style::Color::Shading shading,
+ int x, int y, const char *text, int len) = 0;
+
+ virtual void drawImage (Imgbuf *imgbuf, int xRoot, int yRoot,
+ int x, int y, int width, int height) = 0;
+
+ /*
+ * --------------
+ * Clipping
+ * --------------
+ */
+
+ /*
+ * To prevent drawing outside of a given area, a clipping view may be
+ * requested, which also implements this interface. The clipping view is
+ * related to the parent view (clipping views may be nested!), anything
+ * which is drawn into this clipping view, is later merged again into the
+ * parent view. An implementation will typically use additional pixmaps,
+ * which are later merged into the parent view pixmap/window.
+ */
+
+ virtual View *getClippingView (int x, int y, int width, int height) = 0;
+ virtual void mergeClippingView (View *clippingView) = 0;
+};
+
+} // namespace dw
+} // namespace core
+
+#endif // __DW_VIEW_HH__
diff --git a/dw/widget.cc b/dw/widget.cc
new file mode 100644
index 00000000..e3ce8e3d
--- /dev/null
+++ b/dw/widget.cc
@@ -0,0 +1,822 @@
+/*
+ * Dillo Widget
+ *
+ * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+
+
+#include "core.hh"
+
+#include "../lout/debug.hh"
+
+using namespace lout::object;
+
+namespace dw {
+namespace core {
+
+bool Widget::EventReceiver::buttonPress (Widget *widget, EventButton *event)
+{
+ return false;
+}
+
+bool Widget::EventReceiver::buttonRelease (Widget *widget, EventButton *event)
+{
+ return false;
+}
+
+bool Widget::EventReceiver::motionNotify (Widget *widget, EventMotion *event)
+{
+ return false;
+}
+
+void Widget::EventReceiver::enterNotify (Widget *widget, EventCrossing *event)
+{
+}
+
+void Widget::EventReceiver::leaveNotify (Widget *widget, EventCrossing *event)
+{
+}
+
+
+bool Widget::EventEmitter::emitToReceiver (lout::signal::Receiver *receiver,
+ int signalNo,
+ int argc, Object **argv)
+{
+ EventReceiver *eventReceiver = (EventReceiver*)receiver;
+
+ switch (signalNo) {
+ case BUTTON_PRESS:
+ return eventReceiver->buttonPress ((Widget*)argv[0],
+ (EventButton*)argv[1]);
+
+ case BUTTON_RELEASE:
+ return eventReceiver->buttonRelease ((Widget*)argv[0],
+ (EventButton*)argv[1]);
+
+ case MOTION_NOTIFY:
+ return eventReceiver->motionNotify ((Widget*)argv[0],
+ (EventMotion*)argv[1]);
+
+ case ENTER_NOTIFY:
+ eventReceiver->enterNotify ((Widget*)argv[0],
+ (EventCrossing*)argv[1]);
+ break;
+
+ case LEAVE_NOTIFY:
+ eventReceiver->leaveNotify ((Widget*)argv[1],
+ (EventCrossing*)argv[0]);
+ break;
+
+ default:
+ misc::assertNotReached ();
+ }
+
+ /* Compiler happiness. */
+ return false;
+}
+
+bool Widget::EventEmitter::emitButtonPress (Widget *widget, EventButton *event)
+{
+ Object *argv[2] = { widget, event };
+ return emitBool (BUTTON_PRESS, 2, argv);
+}
+
+bool Widget::EventEmitter::emitButtonRelease (Widget *widget,
+ EventButton *event)
+{
+ Object *argv[2] = { widget, event };
+ return emitBool (BUTTON_RELEASE, 2, argv);
+}
+
+bool Widget::EventEmitter::emitMotionNotify (Widget *widget,
+ EventMotion *event)
+{
+ Object *argv[2] = { widget, event };
+ return emitBool (MOTION_NOTIFY, 2, argv);
+}
+
+void Widget::EventEmitter::emitEnterNotify (Widget *widget,
+ EventCrossing *event)
+{
+ Object *argv[2] = { widget, event };
+ emitVoid (ENTER_NOTIFY, 2, argv);
+}
+
+void Widget::EventEmitter::emitLeaveNotify (Widget *widget,
+ EventCrossing *event)
+{
+ Object *argv[2] = { widget, event };
+ emitVoid (LEAVE_NOTIFY, 2, argv);
+}
+
+// ----------------------------------------------------------------------
+
+bool Widget::LinkReceiver::enter (Widget *widget, int link, int img,
+ int x, int y)
+{
+ return false;
+}
+
+bool Widget::LinkReceiver::press (Widget *widget, int link, int img,
+ int x, int y, EventButton *event)
+{
+ return false;
+}
+
+bool Widget::LinkReceiver::release (Widget *widget, int link, int img,
+ int x, int y, EventButton *event)
+{
+ return false;
+}
+
+bool Widget::LinkReceiver::click (Widget *widget, int link, int img,
+ int x, int y, EventButton *event)
+{
+ return false;
+}
+
+
+bool Widget::LinkEmitter::emitToReceiver (lout::signal::Receiver *receiver,
+ int signalNo,
+ int argc, Object **argv)
+{
+ LinkReceiver *linkReceiver = (LinkReceiver*)receiver;
+
+ switch (signalNo) {
+ case ENTER:
+ return linkReceiver->enter ((Widget*)argv[0],
+ ((Integer*)argv[1])->getValue (),
+ ((Integer*)argv[2])->getValue (),
+ ((Integer*)argv[3])->getValue (),
+ ((Integer*)argv[4])->getValue ());
+
+ case PRESS:
+ return linkReceiver->press ((Widget*)argv[0],
+ ((Integer*)argv[1])->getValue (),
+ ((Integer*)argv[2])->getValue (),
+ ((Integer*)argv[3])->getValue (),
+ ((Integer*)argv[4])->getValue (),
+ (EventButton*)argv[5]);
+
+ case RELEASE:
+ return linkReceiver->release ((Widget*)argv[0],
+ ((Integer*)argv[1])->getValue (),
+ ((Integer*)argv[2])->getValue (),
+ ((Integer*)argv[3])->getValue (),
+ ((Integer*)argv[4])->getValue (),
+ (EventButton*)argv[5]);
+
+ case CLICK:
+ return linkReceiver->click ((Widget*)argv[0],
+ ((Integer*)argv[1])->getValue (),
+ ((Integer*)argv[2])->getValue (),
+ ((Integer*)argv[3])->getValue (),
+ ((Integer*)argv[4])->getValue (),
+ (EventButton*)argv[5]);
+
+ default:
+ misc::assertNotReached ();
+ }
+
+ /* Compiler happiness. */
+ return false;
+}
+
+bool Widget::LinkEmitter::emitEnter (Widget *widget, int link, int img,
+ int x, int y)
+{
+ Integer ilink (link), iimg (img), ix (x), iy (y);
+ Object *argv[5] = { widget, &ilink, &iimg, &ix, &iy };
+ return emitBool (ENTER, 5, argv);
+}
+
+bool Widget::LinkEmitter::emitPress (Widget *widget, int link, int img,
+ int x, int y, EventButton *event)
+{
+ Integer ilink (link), iimg (img), ix (x), iy (y);
+ Object *argv[6] = { widget, &ilink, &iimg, &ix, &iy, event };
+ return emitBool (PRESS, 6, argv);
+}
+
+bool Widget::LinkEmitter::emitRelease (Widget *widget, int link, int img,
+ int x, int y, EventButton *event)
+{
+ Integer ilink (link), iimg (img), ix (x), iy (y);
+ Object *argv[6] = { widget, &ilink, &iimg, &ix, &iy, event };
+ return emitBool (RELEASE, 6, argv);
+}
+
+bool Widget::LinkEmitter::emitClick (Widget *widget, int link, int img,
+ int x, int y, EventButton *event)
+{
+ Integer ilink (link), iimg (img), ix (x), iy (y);
+ Object *argv[6] = { widget, &ilink, &iimg, &ix, &iy, event };
+ return emitBool (CLICK, 6, argv);
+}
+
+
+// ----------------------------------------------------------------------
+
+int Widget::CLASS_ID = -1;
+
+Widget::Widget ()
+{
+ registerName ("dw::core::Widget", &CLASS_ID);
+
+ flags = (Flags)(NEEDS_RESIZE | EXTREMES_CHANGED | HAS_CONTENTS);
+ parent = NULL;
+ layout = NULL;
+
+ allocation.x = -1;
+ allocation.y = -1;
+ allocation.width = 1;
+ allocation.ascent = 1;
+ allocation.descent = 0;
+
+ style = NULL;
+ bgColor = NULL;
+ buttonSensitive = true;
+ buttonSensitiveSet = false;
+
+ deleteCallbackData = NULL;
+ deleteCallbackFunc = NULL;
+}
+
+Widget::~Widget ()
+{
+ if (deleteCallbackFunc)
+ deleteCallbackFunc (deleteCallbackData);
+
+ if (style)
+ style->unref ();
+
+ if (parent)
+ parent->removeChild (this);
+ else
+ layout->removeWidget ();
+}
+
+
+/**
+ * \brief Calculates the intersection of widget->allocation and area, returned
+ * in intersection (in widget coordinates!).
+ *
+ * Typically used by containers when
+ * drawing their children. Returns whether intersection is not empty.
+ */
+bool Widget::intersects (Rectangle *area, Rectangle *intersection)
+{
+ Rectangle parentArea, childArea;
+
+ parentArea = *area;
+ parentArea.x += parent->allocation.x;
+ parentArea.y += parent->allocation.y;
+
+ childArea.x = allocation.x;
+ childArea.y = allocation.y;
+ childArea.width = allocation.width;
+ childArea.height = getHeight ();
+
+ if (parentArea.intersectsWith (&childArea, intersection)) {
+ intersection->x -= allocation.x;
+ intersection->y -= allocation.y;
+ return true;
+ } else
+ return false;
+}
+
+void Widget::setParent (Widget *parent)
+{
+ this->parent = parent;
+ layout = parent->layout;
+
+ if (!buttonSensitiveSet)
+ buttonSensitive = parent->buttonSensitive;
+
+ //DBG_OBJ_ASSOC (widget, parent);
+}
+
+void Widget::queueDrawArea (int x, int y, int width, int height)
+{
+ /** \todo Maybe only the intersection? */
+ layout->queueDraw (x + allocation.x, y + allocation.y, width, height);
+ //printf("Widget::queueDrawArea x=%d y=%d w=%d h=%d\n", x, y, width, height);
+}
+
+/**
+ * \brief This method should be called, when a widget changes its size.
+ */
+void Widget::queueResize (int ref, bool extremesChanged)
+{
+ Widget *widget2, *child;
+
+ //DEBUG_MSG (DEBUG_SIZE,
+ // "a %stop-level %s with parent_ref = %d has changed its size\n",
+ // widget->parent ? "non-" : "",
+ // gtk_type_name (GTK_OBJECT_TYPE (widget)), widget->parent_ref);
+
+ setFlags (NEEDS_RESIZE);
+ setFlags (NEEDS_ALLOCATE);
+ markSizeChange (ref);
+
+ if (extremesChanged) {
+ setFlags (EXTREMES_CHANGED);
+ markExtremesChange (ref);
+ }
+
+ for (widget2 = parent, child = this;
+ widget2;
+ child = widget2, widget2 = widget2->parent) {
+ widget2->setFlags (NEEDS_RESIZE);
+ widget2->markSizeChange (child->parentRef);
+ widget2->setFlags (NEEDS_ALLOCATE);
+
+ //DEBUG_MSG (DEBUG_ALLOC,
+ // "setting DW_NEEDS_ALLOCATE for a %stop-level %s "
+ // "with parent_ref = %d\n",
+ // widget2->parent ? "non-" : "",
+ // gtk_type_name (GTK_OBJECT_TYPE (widget2)),
+ // widget2->parent_ref);
+
+ if (extremesChanged) {
+ widget2->setFlags (EXTREMES_CHANGED);
+ widget2->markExtremesChange (child->parentRef);
+ }
+ }
+
+ if (layout)
+ layout->queueResize ();
+}
+
+
+/**
+ * \brief This method is a wrapper for Widget::sizeRequestImpl(); it calls
+ * the latter only when needed.
+ */
+void Widget::sizeRequest (Requisition *requisition)
+{
+ if (needsResize ()) {
+ /** \todo Check requisition == &(this->requisition) and do what? */
+ sizeRequestImpl (requisition);
+ this->requisition = *requisition;
+ unsetFlags (NEEDS_RESIZE);
+
+ DBG_OBJ_SET_NUM (this, "requisition->width", requisition->width);
+ DBG_OBJ_SET_NUM (this, "requisition->ascent", requisition->ascent);
+ DBG_OBJ_SET_NUM (this, "requisition->descent", requisition->descent);
+ } else
+ *requisition = this->requisition;
+}
+
+/**
+ * \brief Wrapper for Widget::getExtremesImpl().
+ */
+void Widget::getExtremes (Extremes *extremes)
+{
+ if (extremesChanged ()) {
+ getExtremesImpl (extremes);
+ this->extremes = *extremes;
+ unsetFlags (EXTREMES_CHANGED);
+
+ DBG_OBJ_SET_NUM (this, "extremes->minWidth", extremes->minWidth);
+ DBG_OBJ_SET_NUM (this, "extremes->maxWidth", extremes->maxWidth);
+ } else
+ *extremes = this->extremes;
+}
+
+/**
+ * \brief Wrapper for Widget::sizeAllocateImpl, calls the latter only when
+ * needed.
+ */
+void Widget::sizeAllocate (Allocation *allocation)
+{
+ if (needsAllocate () ||
+ allocation->x != this->allocation.x ||
+ allocation->y != this->allocation.y ||
+ allocation->width != this->allocation.width ||
+ allocation->ascent != this->allocation.ascent ||
+ allocation->descent != this->allocation.descent) {
+
+ //DEBUG_MSG (DEBUG_ALLOC,
+ // "a %stop-level %s with parent_ref = %d is newly allocated "
+ // "from %d, %d, %d x %d x %d ...\n",
+ // widget->parent ? "non-" : "",
+ // (GTK_OBJECT_TYPE_NAME (widget), widget->parent_ref,
+ // widget->allocation.x, widget->allocation.y,
+ // widget->allocation.width, widget->allocation.ascent,
+ // widget->allocation.descent);
+
+ if (wasAllocated ()) {
+ layout->queueDrawExcept (
+ this->allocation.x,
+ this->allocation.y,
+ this->allocation.width,
+ this->allocation.ascent + this->allocation.descent,
+ allocation->x,
+ allocation->y,
+ allocation->width,
+ allocation->ascent + allocation->descent);
+ }
+
+ sizeAllocateImpl (allocation);
+
+ //DEBUG_MSG (DEBUG_ALLOC, "... to %d, %d, %d x %d x %d\n",
+ // widget->allocation.x, widget->allocation.y,
+ // widget->allocation.width, widget->allocation.ascent,
+ // widget->allocation.descent);
+
+ this->allocation = *allocation;
+ unsetFlags (NEEDS_ALLOCATE);
+ setFlags (WAS_ALLOCATED);
+
+ resizeDrawImpl ();
+
+ DBG_OBJ_SET_NUM (this, "allocation.x", this->allocation.x);
+ DBG_OBJ_SET_NUM (this, "allocation.y", this->allocation.y);
+ DBG_OBJ_SET_NUM (this, "allocation.width", this->allocation.width);
+ DBG_OBJ_SET_NUM (this, "allocation.ascent", this->allocation.ascent);
+ DBG_OBJ_SET_NUM (this, "allocation.descent", this->allocation.descent);
+ }
+
+ /*unsetFlags (NEEDS_RESIZE);*/
+}
+
+bool Widget::buttonPress (EventButton *event)
+{
+ bool b1 = buttonPressImpl (event);
+ bool b2 = eventEmitter.emitButtonPress (this, event);
+ return b1 || b2;
+}
+
+bool Widget::buttonRelease (EventButton *event)
+{
+ bool b1 = buttonReleaseImpl (event);
+ bool b2 = eventEmitter.emitButtonRelease (this, event);
+ return b1 || b2;
+}
+
+bool Widget::motionNotify (EventMotion *event)
+{
+ bool b1 = motionNotifyImpl (event);
+ bool b2 = eventEmitter.emitMotionNotify (this, event);
+ return b1 || b2;
+}
+
+void Widget::enterNotify (EventCrossing *event)
+{
+ enterNotifyImpl (event);
+ eventEmitter.emitEnterNotify (this, event);
+}
+
+void Widget::leaveNotify (EventCrossing *event)
+{
+ leaveNotifyImpl (event);
+ eventEmitter.emitLeaveNotify (this, event);
+}
+
+/**
+ * \brief Change the style of a widget.
+ *
+ * The old style is automatically unreferred, the new is referred. If this
+ * call causes the widget to change its size, dw::core::Widget::queueResize
+ * is called.
+ */
+void Widget::setStyle (style::Style *style)
+{
+ bool sizeChanged;
+
+ if (this->style) {
+ sizeChanged = this->style->sizeDiffs (style);
+ this->style->unref ();
+ } else
+ sizeChanged = true;
+
+ style->ref ();
+ this->style = style;
+
+ if (layout != NULL) {
+ if (parent == NULL)
+ layout->updateBgColor ();
+ layout->updateCursor ();
+ }
+
+ if (sizeChanged)
+ queueResize (0, true);
+ else
+ queueDraw ();
+}
+
+/**
+ * \brief Set the background "behind" the widget, if it is not the
+ * background of the parent widget, e.g. the background of a table
+ * row.
+ */
+void Widget::setBgColor (style::Color *bgColor)
+{
+ this->bgColor = bgColor;
+}
+
+/**
+ * \brief Get the actual background of a widget.
+ */
+style::Color *Widget::getBgColor ()
+{
+ Widget *widget = this;
+
+ while (widget != NULL) {
+ if (widget->style->backgroundColor)
+ return widget->style->backgroundColor;
+ if (widget->bgColor)
+ return widget->bgColor;
+
+ widget = widget->parent;
+ }
+
+ fprintf (stderr, "No background color found!\n");
+ return NULL;
+
+}
+
+
+/**
+ * \brief Draw borders and background of a widget part, which allocation is
+ * given by (x, y, width, height) (widget coordinates).
+ *
+ * area is given in widget coordinates.
+ */
+void Widget::drawBox (View *view, style::Style *style, Rectangle *area,
+ int x, int y, int width, int height, bool inverse)
+{
+ Rectangle viewArea;
+ viewArea.x = area->x + allocation.x;
+ viewArea.y = area->y + allocation.y;
+ viewArea.width = area->width;
+ viewArea.height = area->height;
+
+ style::drawBorder (view, &viewArea, allocation.x + x, allocation.y + y,
+ width, height, style, inverse);
+
+ /** \todo Background images? */
+ if (style->backgroundColor)
+ style::drawBackground (view, &viewArea,
+ allocation.x + x, allocation.y + y, width, height,
+ style, inverse);
+}
+
+/**
+ * \brief Draw borders and background of a widget.
+ *
+ * area is given in widget coordinates.
+ *
+ */
+void Widget::drawWidgetBox (View *view, Rectangle *area, bool inverse)
+{
+ Rectangle viewArea;
+ viewArea.x = area->x + allocation.x;
+ viewArea.y = area->y + allocation.y;
+ viewArea.width = area->width;
+ viewArea.height = area->height;
+
+ style::drawBorder (view, &viewArea, allocation.x, allocation.y,
+ allocation.width, getHeight (), style, inverse);
+
+ /** \todo Adjust following comment from the old dw sources. */
+ /*
+ * - Toplevel widget background colors are set as viewport
+ * background color. This is not crucial for the rendering, but
+ * looks a bit nicer when scrolling. Furthermore, the viewport
+ * does anything else in this case.
+ *
+ * - Since widgets are always drawn from top to bottom, it is
+ * *not* necessary to draw the background if
+ * widget->style->background_color is NULL (shining through).
+ */
+ /** \todo Background images? */
+ if (parent && style->backgroundColor)
+ style::drawBackground (view, &viewArea, allocation.x, allocation.y,
+ allocation.width, getHeight (), style, inverse);
+}
+
+/*
+ * This function is used by some widgets, when they are selected (as a whole).
+ *
+ * \todo This could be accelerated by using clipping bitmaps. Two important
+ * issues:
+ *
+ * (i) There should always been a pixel in the upper-left corner of the
+ * *widget*, so probably two different clipping bitmaps have to be
+ * used (10/01 and 01/10).
+ *
+ * (ii) Should a new GC always be created?
+ *
+ * \bug Not implemented.
+ */
+void Widget::drawSelected (View *view, Rectangle *area)
+{
+}
+
+
+void Widget::setButtonSensitive (bool buttonSensitive)
+{
+ this->buttonSensitive = buttonSensitive;
+ buttonSensitiveSet = true;
+}
+
+
+/**
+ * \brief Get the widget at the root of the tree, this widget is part from.
+ */
+Widget *Widget::getTopLevel ()
+{
+ Widget *widget = this;
+
+ while (widget->parent)
+ widget = widget->parent;
+
+ return widget;
+}
+
+/**
+ * \brief Get the level of the widget within the tree.
+ *
+ * The root widget has the level 0.
+ */
+int Widget::getLevel ()
+{
+ Widget *widget = this;
+ int level = 0;
+
+ while (widget->parent) {
+ level++;
+ widget = widget->parent;
+ }
+
+ return level;
+}
+
+/**
+ * \brief Get the widget with the highest level, which is a direct ancestor of
+ * widget1 and widget2.
+ */
+Widget *Widget::getNearestCommonAncestor (Widget *otherWidget)
+{
+ Widget *widget1 = this, *widget2 = otherWidget;
+ int level1 = widget1->getLevel (), level2 = widget2->getLevel();
+
+ /* Get both widgets onto the same level.*/
+ while (level1 > level2) {
+ widget1 = widget1->parent;
+ level1--;
+ }
+
+ while (level2 > level1) {
+ widget2 = widget2->parent;
+ level2--;
+ }
+
+ /* Search upwards. */
+ while (widget1 != widget2) {
+ if (widget1->parent == NULL) {
+ fprintf (stderr, "widgets in different trees\n");
+ return NULL;
+ }
+
+ widget1 = widget1->parent;
+ widget2 = widget2->parent;
+ }
+
+ return widget1;
+}
+
+
+/**
+ * \brief Search recursively through widget.
+ *
+ * Used by dw::core::Layout:getWidgetAtPoint.
+ */
+Widget *Widget::getWidgetAtPoint (int x, int y, int level)
+{
+ Iterator *it;
+ Widget *childAtPoint;
+
+ //_MSG ("%*s-> examining the %s %p (%d, %d, %d x (%d + %d))\n",
+ // 3 * level, "", gtk_type_name (GTK_OBJECT_TYPE (widget)), widget,
+ // allocation.x, allocation.y,
+ // allocation.width, allocation.ascent,
+ // allocation.descent);
+
+ if (x >= allocation.x &&
+ y >= allocation.y &&
+ x <= allocation.x + allocation.width &&
+ y <= allocation.y + getHeight ()) {
+ //_MSG ("%*s -> inside\n", 3 * level, "");
+ /*
+ * Iterate over the children of this widget. Test recursively, whether
+ * the point is within the child (or one of its children...). If there
+ * is such a child, it is returned. Otherwise, this widget is returned.
+ */
+ childAtPoint = NULL;
+ it = iterator (Content::WIDGET, false);
+
+ while (childAtPoint == NULL && it->next ())
+ childAtPoint = it->getContent()->widget->getWidgetAtPoint (x, y,
+ level + 1);
+
+ it->unref ();
+
+ if (childAtPoint)
+ return childAtPoint;
+ else
+ return this;
+ } else
+ return NULL;
+}
+
+
+void Widget::scrollTo (HPosition hpos, VPosition vpos,
+ int x, int y, int width, int height)
+{
+ layout->scrollTo (hpos, vpos,
+ x + allocation.x, y + allocation.y, width, height);
+}
+
+void Widget::getExtremesImpl (Extremes *extremes)
+{
+ /* Simply return the requisition width */
+ Requisition requisition;
+ sizeRequest (&requisition);
+ extremes->minWidth = extremes->maxWidth = requisition.width;
+}
+
+void Widget::sizeAllocateImpl (Allocation *allocation)
+{
+}
+
+void Widget::markSizeChange (int ref)
+{
+}
+
+void Widget::markExtremesChange (int ref)
+{
+}
+
+void Widget::setWidth (int width)
+{
+}
+
+void Widget::setAscent (int ascent)
+{
+}
+
+void Widget::setDescent (int descent)
+{
+}
+
+bool Widget::buttonPressImpl (EventButton *event)
+{
+ return false;
+}
+
+bool Widget::buttonReleaseImpl (EventButton *event)
+{
+ return false;
+}
+
+bool Widget::motionNotifyImpl (EventMotion *event)
+{
+ return false;
+}
+
+void Widget::enterNotifyImpl (EventCrossing *event)
+{
+}
+
+void Widget::leaveNotifyImpl (EventCrossing *event)
+{
+}
+
+void Widget::removeChild (Widget *child)
+{
+ // Should be implemented.
+ misc::assertNotReached ();
+}
+
+
+
+} // namespace dw
+} // namespace core
diff --git a/dw/widget.hh b/dw/widget.hh
new file mode 100644
index 00000000..1bcfd032
--- /dev/null
+++ b/dw/widget.hh
@@ -0,0 +1,465 @@
+#ifndef __DW_WIDGET_HH__
+#define __DW_WIDGET_HH__
+
+#ifndef __INCLUDED_FROM_DW_CORE_HH__
+# error Do not include this file directly, use "core.hh" instead.
+#endif
+
+#include "../lout/identity.hh"
+
+/**
+ * \brief The type for callback functions.
+ */
+typedef void (*DW_Callback_t)(void *data);
+
+namespace dw {
+namespace core {
+
+/**
+ * \brief The base class of all dillo widgets.
+ *
+ * \sa\ref dw-overview, \ref dw-layout-widgets
+ */
+class Widget: public identity::IdentifiableObject
+{
+ friend class Layout;
+
+public:
+ class EventReceiver: public lout::signal::Receiver
+ {
+ public:
+ virtual bool buttonPress (Widget *widget, EventButton *event);
+ virtual bool buttonRelease (Widget *widget, EventButton *event);
+ virtual bool motionNotify (Widget *widget, EventMotion *event);
+ virtual void enterNotify (Widget *widget, EventCrossing *event);
+ virtual void leaveNotify (Widget *widget, EventCrossing *event);
+ };
+
+ /**
+ * \brief This receiver is for signals related to HTML pages.
+ *
+ * The \em link argument to all signals defines a number, which has
+ * been passed before, e.g. by setting dw::core::style::Style::x_link.
+ * When defining this number (e.g in dw::core::style::Style::x_link),
+ * and when receiving the signal, the caller must interpret these numbers
+ * in a consistent way. In the HTML link block, this number is an index
+ * to an array of URLs.
+ *
+ * \em link = -1 represents an undefined link.
+ *
+ * The \em img argument to all signals defines a number which has
+ * been passed before, e.g. by setting dw::core::style::Style::x_img.
+ * When defining this number (e.g in dw::core::style::Style::x_img),
+ * and when receiving the signal, the caller must interpret these numbers
+ * in a consistent way. In the HTML link block, this number is an index
+ * to an array of structures containing image information.
+ *
+ * \em img = -1 represents an undefined image.
+ *
+ * \em x and \em y define the coordinates within the link area. They are
+ * only used for server-side image maps, see dw::Image.
+ *
+ * \sa dw::Image, dw::Textblock
+ */
+ class LinkReceiver: public lout::signal::Receiver
+ {
+ public:
+ /**
+ * \brief Called, when a link is entered, left, or the position has
+ * changed.
+ *
+ * When a link is entered, this method is called with the respective
+ * arguments. When a link is left, this method is called with all
+ * three arguments (\em link, \em x, \em y) set to -1.
+ *
+ * When coordinates are supported, a change of the coordinates also
+ * causes emitting this signal.
+ */
+ virtual bool enter (Widget *widget, int link, int img, int x, int y);
+
+ /**
+ * \brief Called, when the user has pressed the mouse button on a
+ * link (but not yet released).
+ *
+ * The causing event is passed as \em event.
+ */
+ virtual bool press (Widget *widget, int link, int img, int x, int y,
+ EventButton *event);
+
+ /**
+ * \brief Called, when the user has released the mouse button on a
+ * link.
+ *
+ * The causing event is passed as \em event.
+ */
+ virtual bool release (Widget *widget, int link, int img, int x, int y,
+ EventButton *event);
+
+ /**
+ * \brief Called, when the user has clicked on a link.
+ *
+ * For mouse interaction, this is equivalent to "press" and "release"
+ * on the same link. In this case, \em event contains the "release"
+ * event.
+ *
+ * When activating links via keyboard is supported, only a "clicked"
+ * signal will be emitted, and \em event will be NULL.
+ */
+ virtual bool click (Widget *widget, int link, int img, int x, int y,
+ EventButton *event);
+ };
+
+private:
+ class EventEmitter: public lout::signal::Emitter
+ {
+ private:
+ enum { BUTTON_PRESS, BUTTON_RELEASE, MOTION_NOTIFY, ENTER_NOTIFY,
+ LEAVE_NOTIFY };
+
+ protected:
+ bool emitToReceiver (lout::signal::Receiver *receiver, int signalNo,
+ int argc, Object **argv);
+
+ public:
+ inline void connectEvent (EventReceiver *receiver)
+ { connect (receiver); }
+
+ bool emitButtonPress (Widget *widget, EventButton *event);
+ bool emitButtonRelease (Widget *widget, EventButton *event);
+ bool emitMotionNotify (Widget *widget, EventMotion *event);
+ void emitEnterNotify (Widget *widget, EventCrossing *event);
+ void emitLeaveNotify (Widget *widget, EventCrossing *event);
+ };
+
+ class LinkEmitter: public lout::signal::Emitter
+ {
+ private:
+ enum { ENTER, PRESS, RELEASE, CLICK };
+
+ protected:
+ bool emitToReceiver (lout::signal::Receiver *receiver, int signalNo,
+ int argc, Object **argv);
+
+ public:
+ inline void connectLink (LinkReceiver *receiver) { connect (receiver); }
+
+ bool emitEnter (Widget *widget, int link, int img, int x, int y);
+ bool emitPress (Widget *widget, int link, int img, int x, int y,
+ EventButton *event);
+ bool emitRelease (Widget *widget, int link, int img, int x, int y,
+ EventButton *event);
+ bool emitClick (Widget *widget, int link, int img, int x, int y,
+ EventButton *event);
+ };
+
+ EventEmitter eventEmitter;
+
+ style::Style *style;
+
+
+protected:
+ enum Flags {
+ /**
+ * \brief Set, when dw::core::Widget::requisition is not up to date
+ * anymore.
+ */
+ NEEDS_RESIZE = 1 << 0,
+
+ /**
+ * \brief Only used internally, set to enforce size allocation.
+ *
+ * (I've forgotten the case, for which this is necessary.)
+ */
+ NEEDS_ALLOCATE = 1 << 1,
+
+ /**
+ * \brief Set, when dw::core::Widget::extremes is not up to date
+ * anymore.
+ */
+ EXTREMES_CHANGED = 1 << 2,
+
+ /**
+ * \brief Set by the widget itself (in the constructor), when set...
+ * methods are implemented.
+ *
+ * Will hopefully be removed, after redesigning the size model.
+ */
+ USES_HINTS = 1 << 3,
+
+ /**
+ * \brief Set by the widget itself (in the constructor), when it contains
+ * some contents, e.g. an image, as opposed to a horizontal ruler.
+ *
+ * Will hopefully be removed, after redesigning the size model.
+ */
+ HAS_CONTENTS = 1 << 4,
+
+ /**
+ * \brief Set, when a widget was already once allocated,
+ *
+ * The dw::Image widget uses this flag, see dw::Image::setBuffer.
+ */
+ WAS_ALLOCATED = 1 << 5,
+ };
+
+private:
+ /**
+ * \brief The parent widget, NULL for top-level widgets.
+ */
+ Widget *parent;
+
+ Flags flags;
+
+ /**
+ * \brief Size_request() stores the result of the last call of
+ * size_request_impl().
+ *
+ * Do not read this directly, but call size_request().
+ */
+ Requisition requisition;
+
+ /**
+ * \brief Analogue to dw::core::Widget::requisition.
+ */
+ Extremes extremes;
+
+ /**
+ * \brief See dw::core::Widget::setBgColor().
+ */
+ style::Color *bgColor;
+
+ /**
+ * \brief See dw::core::Widget::setButtonSensitive().
+ */
+ bool buttonSensitive;
+
+ /**
+ * \brief See dw::core::Widget::setButtonSensitive().
+ */
+ bool buttonSensitiveSet;
+
+public:
+ /**
+ * \brief This value is defined by the parent widget, and used for
+ * incremential resizing.
+ *
+ * See documentation for an explanation.
+ */
+ int parentRef;
+
+protected:
+ LinkEmitter linkEmitter;
+
+ /**
+ * \brief The current allocation: size and position, always relative to the
+ * canvas.
+ */
+ Allocation allocation;
+
+ inline int getHeight () { return allocation.ascent + allocation.descent; }
+ inline int getContentWidth() { return allocation.width
+ - style->boxDiffWidth (); }
+ inline int getContentHeight() { return getHeight ()
+ - style->boxDiffHeight (); }
+
+ Layout *layout;
+
+ inline void setFlags (Flags f) { flags = (Flags)(flags | f); }
+ inline void unsetFlags (Flags f) { flags = (Flags)(flags & ~f); }
+
+
+ inline void queueDraw ()
+ {
+ queueDrawArea (0, 0, allocation.width, getHeight());
+ }
+ void queueDrawArea (int x, int y, int width, int height);
+ void queueResize (int ref, bool extremesChanged);
+
+ /**
+ * \brief See \ref dw-widget-sizes.
+ */
+ virtual void sizeRequestImpl (Requisition *requisition) = 0;
+
+ /**
+ * \brief See \ref dw-widget-sizes.
+ */
+ virtual void getExtremesImpl (Extremes *extremes);
+
+ /**
+ * \brief See \ref dw-widget-sizes.
+ */
+ virtual void sizeAllocateImpl (Allocation *allocation);
+
+ /**
+ * \brief Called after sizeAllocateImpl() to redraw necessary areas.
+ * By default the whole widget is redrawn.
+ */
+ virtual void resizeDrawImpl () { queueDraw (); };
+
+ /**
+ * \brief See \ref dw-widget-sizes.
+ */
+ virtual void markSizeChange (int ref);
+
+ /**
+ * \brief See \ref dw-widget-sizes.
+ */
+ virtual void markExtremesChange (int ref);
+
+ virtual bool buttonPressImpl (EventButton *event);
+ virtual bool buttonReleaseImpl (EventButton *event);
+ virtual bool motionNotifyImpl (EventMotion *event);
+ virtual void enterNotifyImpl (EventCrossing *event);
+ virtual void leaveNotifyImpl (EventCrossing *event);
+
+ inline char *addAnchor (const char* name)
+ { return layout->addAnchor (this, name); }
+
+ inline char *addAnchor (const char* name, int y)
+ { return layout->addAnchor (this, name, y); }
+
+ inline void changeAnchor (char* name, int y)
+ { layout->changeAnchor (this, name, y); }
+
+ inline void removeAnchor (char* name)
+ { layout->removeAnchor (this, name); }
+
+ //inline void updateBgColor () { layout->updateBgColor (); }
+
+ inline void setCursor (style::Cursor cursor)
+ { layout->setCursor (cursor); }
+
+ inline bool selectionButtonPress (Iterator *it, int charPos, int linkNo,
+ EventButton *event, bool withinContent)
+ { return layout->selectionState.buttonPress (it, charPos, linkNo, event,
+ withinContent); }
+
+ inline bool selectionButtonRelease (Iterator *it, int charPos, int linkNo,
+ EventButton *event, bool withinContent)
+ { return layout->selectionState.buttonRelease (it, charPos, linkNo, event,
+ withinContent); }
+
+ inline bool selectionButtonMotion (Iterator *it, int charPos, int linkNo,
+ EventMotion *event, bool withinContent)
+ { return layout->selectionState.buttonMotion (it, charPos, linkNo, event,
+ withinContent); }
+
+ inline bool selectionHandleEvent (SelectionState::EventType eventType,
+ Iterator *it, int charPos, int linkNo,
+ MousePositionEvent *event,
+ bool withinContent)
+ { return layout->selectionState.handleEvent (eventType, it, charPos, linkNo,
+ event, withinContent); }
+
+private:
+ void *deleteCallbackData;
+ DW_Callback_t deleteCallbackFunc;
+
+public:
+ inline void setDeleteCallback(DW_Callback_t func, void *data)
+ { deleteCallbackFunc = func; deleteCallbackData = data; }
+
+public:
+ static int CLASS_ID;
+
+ Widget ();
+ ~Widget ();
+
+ inline bool needsResize () { return flags & NEEDS_RESIZE; }
+ inline bool needsAllocate () { return flags & NEEDS_ALLOCATE; }
+ inline bool extremesChanged () { return flags & EXTREMES_CHANGED; }
+ inline bool wasAllocated () { return flags & WAS_ALLOCATED; }
+
+ inline void connectEvent (EventReceiver *receiver)
+ { eventEmitter.connectEvent (receiver); }
+
+ inline void connectLink (LinkReceiver *receiver)
+ { linkEmitter.connectLink (receiver); }
+
+ inline bool emitLinkEnter (int link, int img, int x, int y)
+ { return linkEmitter.emitEnter (this, link, img, x, y); }
+
+ inline bool emitLinkPress (int link, int img,
+ int x, int y, EventButton *event)
+ { return linkEmitter.emitPress (this, link, img, x, y, event); }
+
+ inline bool emitLinkRelease (int link, int img,
+ int x, int y, EventButton *event)
+ { return linkEmitter.emitRelease (this, link, img, x, y, event); }
+
+ inline bool emitLinkClick (int link, int img,
+ int x, int y, EventButton *event)
+ { return linkEmitter.emitClick (this, link, img, x, y, event); }
+
+ inline bool usesHints () { return flags & USES_HINTS; }
+ inline bool hasContents () { return flags & HAS_CONTENTS; }
+
+ void setParent (Widget *parent);
+
+ inline style::Style *getStyle () { return style; }
+ /** \todo I do not like this. */
+ inline Allocation *getAllocation () { return &allocation; }
+
+ void sizeRequest (Requisition *requisition);
+ void getExtremes (Extremes *extremes);
+ void sizeAllocate (Allocation *allocation);
+ virtual void setWidth (int width);
+ virtual void setAscent (int ascent);
+ virtual void setDescent (int descent);
+
+ bool intersects (Rectangle *area, Rectangle *intersection);
+
+ /** Area is given in widget coordinates. */
+ virtual void draw (View *view, Rectangle *area) = 0;
+
+ bool buttonPress (EventButton *event);
+ bool buttonRelease (EventButton *event);
+ bool motionNotify (EventMotion *event);
+ void enterNotify (EventCrossing *event);
+ void leaveNotify (EventCrossing *event);
+
+ virtual void setStyle (style::Style *style);
+ void setBgColor (style::Color *bgColor);
+ style::Color *getBgColor ();
+
+ void drawBox (View *view, style::Style *style, Rectangle *area,
+ int x, int y, int width, int height, bool inverse);
+ void drawWidgetBox (View *view, Rectangle *area, bool inverse);
+ void drawSelected (View *view, Rectangle *area);
+
+ void setButtonSensitive (bool buttonSensitive);
+ inline bool isButtonSensitive () { return buttonSensitive; }
+
+ inline Widget *getParent () { return parent; }
+ Widget *getTopLevel ();
+ int getLevel ();
+ Widget *getNearestCommonAncestor (Widget *otherWidget);
+
+ inline Layout *getLayout () { return layout; }
+
+ virtual Widget *getWidgetAtPoint (int x, int y, int level);
+
+ void scrollTo (HPosition hpos, VPosition vpos,
+ int x, int y, int width, int height);
+
+ /**
+ * \brief Return an iterator for this widget.
+ *
+ * \em mask can narrow the types returned by the iterator, this can
+ * enhance performance quite much, e.g. when only searching for child
+ * widgets.
+ *
+ * With \em atEnd == false, the iterator starts \em before the beginning,
+ * i.e. the first call of dw::core::Iterator::next will let the iterator
+ * point on the first piece of contents. Likewise, With \em atEnd == true,
+ * the iterator starts \em after the last piece of contents, call
+ * dw::core::Iterator::prev in this case.
+ */
+ virtual Iterator *iterator (Content::Type mask, bool atEnd) = 0;
+ virtual void removeChild (Widget *child);
+};
+
+} // namespace dw
+} // namespace core
+
+#endif // __DW_WIDGET_HH__
diff --git a/lout/Makefile.am b/lout/Makefile.am
new file mode 100644
index 00000000..18e00cf2
--- /dev/null
+++ b/lout/Makefile.am
@@ -0,0 +1,14 @@
+noinst_LIBRARIES = liblout.a
+
+liblout_a_SOURCES = \
+ container.cc \
+ container.hh \
+ debug.hh \
+ identity.cc \
+ identity.hh \
+ misc.cc \
+ misc.hh \
+ object.cc \
+ object.hh \
+ signal.cc \
+ signal.hh
diff --git a/lout/container.cc b/lout/container.cc
new file mode 100644
index 00000000..0b00c195
--- /dev/null
+++ b/lout/container.cc
@@ -0,0 +1,558 @@
+/*
+ * Dillo Widget
+ *
+ * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+
+
+#include "container.hh"
+#include "misc.hh"
+
+namespace lout {
+
+using namespace object;
+
+namespace container {
+
+namespace untyped {
+
+// -------------
+// Iterator
+// -------------
+
+Iterator::Iterator()
+{
+ impl = NULL;
+}
+
+Iterator::Iterator(const Iterator &it2)
+{
+ impl = it2.impl;
+ if(impl)
+ impl->ref();
+}
+
+Iterator::Iterator(Iterator &it2)
+{
+ impl = it2.impl;
+ if(impl)
+ impl->ref();
+}
+
+Iterator &Iterator::operator=(const Iterator &it2)
+{
+ if(impl)
+ impl->unref();
+ impl = it2.impl;
+ if(impl)
+ impl->ref();
+ return *this;
+}
+
+Iterator &Iterator::operator=(Iterator &it2)
+{
+ if(impl)
+ impl->unref();
+ impl = it2.impl;
+ if(impl)
+ impl->ref();
+ return *this;
+}
+
+Iterator::~Iterator()
+{
+ if(impl)
+ impl->unref();
+}
+
+// ----------------
+// Collection
+// ----------------
+
+void Collection::intoStringBuffer(misc::StringBuffer *sb)
+{
+ sb->append("{ ");
+ bool first = true;
+ for(Iterator it = iterator(); it.hasNext(); ) {
+ if(!first)
+ sb->append(", ");
+ it.getNext()->intoStringBuffer(sb);
+ first = false;
+ }
+ sb->append(" }");
+}
+
+// ------------
+// Vector
+// ------------
+
+Vector::Vector(int initSize, bool ownerOfObjects)
+{
+ numAlloc = initSize == 0 ? 1 : initSize;
+ this->ownerOfObjects = ownerOfObjects;
+ numElements = 0;
+ array = (Object**)malloc(numAlloc * sizeof(Object*));
+}
+
+Vector::~Vector()
+{
+ clear();
+ free(array);
+}
+
+void Vector::put(Object *newElement, int newPos)
+{
+ if(newPos == -1)
+ newPos = numElements;
+
+ // Old entry is overwritten.
+ if(newPos < numElements) {
+ if(ownerOfObjects && array[newPos])
+ delete array[newPos];
+ }
+
+ // Allocated memory has to be increased.
+ if(newPos >= numAlloc) {
+ while (newPos >= numAlloc)
+ numAlloc *= 2;
+ array = (Object**)realloc(array, numAlloc * sizeof(Object*));
+ }
+
+ // Insert NULL's into possible gap before new position.
+ for(int i = numElements; i < newPos; i++)
+ array[i] = NULL;
+
+ if(newPos >= numElements)
+ numElements = newPos + 1;
+
+ array[newPos] = newElement;
+}
+
+void Vector::clear()
+{
+ if (ownerOfObjects) {
+ for(int i = 0; i < numElements; i++)
+ if(array[i])
+ delete array[i];
+ }
+
+ numElements = 0;
+}
+
+void Vector::insert(Object *newElement, int pos)
+{
+ if(pos >= numElements)
+ put(newElement, pos);
+ else {
+ numElements++;
+
+ // Allocated memory has to be increased.
+ if(numElements >= numAlloc) {
+ numAlloc *= 2;
+ array = (Object**)realloc(array, numAlloc * sizeof(Object*));
+ }
+
+ for(int i = numElements - 1; i > pos; i--)
+ array[i] = array[i - 1];
+
+ array[pos] = newElement;
+ }
+}
+
+void Vector::remove(int pos)
+{
+ if(ownerOfObjects && array[pos])
+ delete array[pos];
+
+ for(int i = pos + 1; i < numElements; i++)
+ array[i - 1] = array[i];
+
+ numElements--;
+}
+
+/**
+ * Sort the elements in the vector. Assumes that all elements are Comparable's.
+ */
+void Vector::sort()
+{
+ qsort(array, numElements, sizeof(Object*), misc::Comparable::compareFun);
+}
+
+
+/**
+ * \bug Not implemented.
+ */
+Collection0::AbstractIterator* Vector::createIterator()
+{
+ return NULL;
+}
+
+// ------------
+// List
+// ------------
+
+List::List(bool ownerOfObjects)
+{
+ this->ownerOfObjects = ownerOfObjects;
+ first = last = NULL;
+ numElements = 0;
+}
+
+List::~List()
+{
+ clear();
+}
+
+void List::clear()
+{
+ while(first) {
+ if(ownerOfObjects && first->object)
+ delete first->object;
+ Node *next = first->next;
+ delete first;
+ first = next;
+ }
+
+ last = NULL;
+ numElements = 0;
+}
+
+void List::append(Object *element)
+{
+ Node *newLast = new Node;
+ newLast->next = NULL;
+ newLast->object = element;
+
+ if(last) {
+ last->next = newLast;
+ last = newLast;
+ } else
+ first = last = newLast;
+
+ numElements++;
+}
+
+
+bool List::remove0(Object *element, bool compare, bool doNotDeleteAtAll)
+{
+ Node *beforeCur, *cur;
+
+ for(beforeCur = NULL, cur = first; cur; beforeCur = cur, cur = cur->next) {
+ if (compare ?
+ (cur->object && element->equals(cur->object)) :
+ element == cur->object) {
+ if(beforeCur) {
+ beforeCur->next = cur->next;
+ if(cur->next == NULL)
+ last = beforeCur;
+ } else {
+ first = cur->next;
+ if(first == NULL)
+ last = NULL;
+ }
+
+ if(ownerOfObjects && cur->object && !doNotDeleteAtAll)
+ delete cur->object;
+ delete cur;
+
+ numElements--;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+Object *List::ListIterator::getNext()
+{
+ Object *object;
+
+ if(current) {
+ object = current->object;
+ current = current->next;
+ } else
+ object = NULL;
+
+ return object;
+}
+
+bool List::ListIterator::hasNext()
+{
+ return current != NULL;
+}
+
+Collection0::AbstractIterator* List::createIterator()
+{
+ return new ListIterator(first);
+}
+
+
+// ---------------
+// HashTable
+// ---------------
+
+HashTable::HashTable(bool ownerOfKeys, bool ownerOfValues, int tableSize)
+{
+ this->ownerOfKeys = ownerOfKeys;
+ this->ownerOfValues = ownerOfValues;
+ this->tableSize = tableSize;
+
+ table = new Node*[tableSize];
+ for(int i = 0; i < tableSize; i++)
+ table[i] = NULL;
+}
+
+HashTable::~HashTable()
+{
+ for(int i = 0; i < tableSize; i++) {
+ Node *n1 = table[i];
+ while(n1) {
+ Node *n2 = n1->next;
+
+ if(ownerOfValues && n1->value)
+ delete n1->value;
+ if(ownerOfKeys)
+ delete n1->key;
+ delete n1;
+
+ n1 = n2;
+ }
+ }
+
+ delete[] table;
+}
+
+void HashTable::intoStringBuffer(misc::StringBuffer *sb)
+{
+ sb->append("{ ");
+
+ bool first = true;
+ for(int i = 0; i < tableSize; i++) {
+ for(Node *node = table[i]; node; node = node->next) {
+ if(!first)
+ sb->append(", ");
+ node->key->intoStringBuffer(sb);
+ sb->append(" => ");
+ node->value->intoStringBuffer(sb);
+ first = false;
+ }
+ }
+
+ sb->append(" }");
+}
+
+void HashTable::put(Object *key, Object *value)
+{
+ int h = calcHashValue(key);
+ Node *n = new Node;
+ n->key = key;
+ n->value = value;
+ n->next = table[h];
+ table[h] = n;
+}
+
+bool HashTable::contains(Object *key)
+{
+ int h = calcHashValue(key);
+ for(Node *n = table[h]; n; n = n->next) {
+ if (key->equals(n->key))
+ return true;
+ }
+
+ return false;
+}
+
+Object *HashTable::get(Object *key)
+{
+ int h = calcHashValue(key);
+ for(Node *n = table[h]; n; n = n->next) {
+ if (key->equals(n->key))
+ return n->value;
+ }
+
+ return NULL;
+}
+
+bool HashTable::remove(Object *key)
+{
+ int h = calcHashValue(key);
+ Node *last, *cur;
+
+ for(last = NULL, cur = table[h]; cur; last = cur, cur = cur->next) {
+ if (key->equals(cur->key)) {
+ if(last)
+ last->next = cur->next;
+ else
+ table[h] = cur->next;
+
+ if(ownerOfValues && cur->value)
+ delete cur->value;
+ if(ownerOfKeys)
+ delete cur->key;
+ delete cur;
+
+ return true;
+ }
+ }
+
+ return false;
+}
+
+Object *HashTable::getKey (Object *key)
+{
+ int h = calcHashValue(key);
+ for(Node *n = table[h]; n; n = n->next) {
+ if (key->equals(n->key))
+ return n->key;
+ }
+
+ return NULL;
+}
+
+HashTable::HashTableIterator::HashTableIterator(HashTable *table)
+{
+ this->table = table;
+ node = NULL;
+ pos = -1;
+ gotoNext();
+}
+
+void HashTable::HashTableIterator::gotoNext()
+{
+ if(node)
+ node = node->next;
+
+ while(node == NULL) {
+ if(pos >= table->tableSize - 1)
+ return;
+ pos++;
+ node = table->table[pos];
+ }
+}
+
+
+Object *HashTable::HashTableIterator::getNext()
+{
+ Object *result;
+ if(node)
+ result = node->key;
+ else
+ result = NULL;
+
+ gotoNext();
+ return result;
+}
+
+bool HashTable::HashTableIterator::hasNext()
+{
+ return node != NULL;
+}
+
+Collection0::AbstractIterator* HashTable::createIterator()
+{
+ return new HashTableIterator(this);
+}
+
+// -----------
+// Stack
+// -----------
+
+Stack::Stack (bool ownerOfObjects)
+{
+ this->ownerOfObjects = ownerOfObjects;
+ bottom = top = NULL;
+ numElements = 0;
+}
+
+Stack::~Stack()
+{
+ while (top)
+ pop ();
+}
+
+void Stack::push (object::Object *object)
+{
+ Node *newTop = new Node ();
+ newTop->object = object;
+ newTop->prev = top;
+
+ top = newTop;
+ if(bottom == NULL)
+ bottom = top;
+
+ numElements++;
+}
+
+void Stack::pushUnder (object::Object *object)
+{
+ Node *newBottom = new Node ();
+ newBottom->object = object;
+ newBottom->prev = NULL;
+ if(bottom != NULL)
+ bottom->prev = newBottom;
+
+ bottom = newBottom;
+ if(top == NULL)
+ top = bottom;
+
+ numElements++;
+}
+
+void Stack::pop ()
+{
+ Node *newTop = top->prev;
+
+ if (ownerOfObjects)
+ delete top->object;
+ delete top;
+
+ top = newTop;
+ if(top == NULL)
+ bottom = NULL;
+
+ numElements--;
+}
+
+Object *Stack::StackIterator::getNext()
+{
+ Object *object;
+
+ if(current) {
+ object = current->object;
+ current = current->prev;
+ } else
+ object = NULL;
+
+ return object;
+}
+
+bool Stack::StackIterator::hasNext()
+{
+ return current != NULL;
+}
+
+Collection0::AbstractIterator* Stack::createIterator()
+{
+ return new StackIterator(top);
+}
+
+} // namespace untyped
+
+} // namespace container
+
+} // namespace lout
diff --git a/lout/container.hh b/lout/container.hh
new file mode 100644
index 00000000..26803e23
--- /dev/null
+++ b/lout/container.hh
@@ -0,0 +1,441 @@
+#ifndef __LOUT_CONTAINER_HH_
+#define __LOUT_CONTAINER_HH_
+
+#include "object.hh"
+
+/**
+ * \brief This namespace contains a framework for container classes, which
+ * members are instances of object::Object.
+ *
+ * A common problem in languanges without garbage collection is, where the
+ * children belong to, and so, who is responsible to delete them (instanciation
+ * is always done by the caller). This information is here told to the
+ * collections, each container has a constructor with the parameter
+ * "ownerOfObjects" (HashTable has two such parameters, for keys and values).
+ *
+ * \sa container::untyped, container::typed
+ */
+namespace lout {
+
+namespace container {
+
+/**
+ * \brief The container classes defined here contain instances of
+ * object::Object.
+ *
+ * Different sub-classes may be mixed, and you have to care about casting,
+ * there is no type-safety.
+ */
+namespace untyped {
+
+/**
+ * \brief ...
+ */
+class Collection0: public object::Object
+{
+ friend class Iterator;
+
+protected:
+ /**
+ * \brief The base class for all iterators, as created by
+ * container::untyped::Collection::createIterator.
+ */
+ class AbstractIterator: public object::Object
+ {
+ private:
+ int refcount;
+
+ public:
+ AbstractIterator() { refcount = 1; }
+
+ void ref () { refcount++; }
+ void unref () { refcount--; if (refcount == 0) delete this; }
+
+ virtual bool hasNext () = 0;
+ virtual Object *getNext () = 0;
+ };
+
+protected:
+ virtual AbstractIterator* createIterator() = 0;
+};
+
+/**
+ * \brief This is a small wrapper for AbstractIterator, which may be used
+ * directly, not as a pointer, to makes memory management simpler.
+ */
+class Iterator
+{
+ friend class Collection;
+
+private:
+ Collection0::AbstractIterator *impl;
+
+ // Should not instanciated directly.
+ inline Iterator(Collection0::AbstractIterator *impl) { this->impl = impl; }
+
+public:
+ Iterator();
+ Iterator(const Iterator &it2);
+ Iterator(Iterator &it2);
+ ~Iterator();
+ Iterator &operator=(const Iterator &it2);
+ Iterator &operator=(Iterator &it2);
+
+ inline bool hasNext() { return impl->hasNext(); }
+ inline object::Object *getNext() { return impl->getNext(); }
+};
+
+/**
+ * \brief Abstract base class for all container objects in container::untyped.
+ */
+class Collection: public Collection0
+{
+public:
+ void intoStringBuffer(misc::StringBuffer *sb);
+ inline Iterator iterator() { Iterator it(createIterator()); return it; }
+};
+
+
+/**
+ * \brief Container, which is implemented by an array, which is
+ * dynamically resized.
+ */
+class Vector: public Collection
+{
+private:
+ object::Object **array;
+ int numAlloc, numElements;
+ bool ownerOfObjects;
+
+protected:
+ AbstractIterator* createIterator();
+
+public:
+ Vector(int initSize, bool ownerOfObjects);
+ ~Vector();
+
+ void put(object::Object *newElement, int newPos = -1);
+ void insert(object::Object *newElement, int pos);
+ void remove(int pos);
+ inline object::Object *get(int pos)
+ { return (pos >= 0 && pos < numElements) ? array[pos] : NULL; }
+ inline int size() { return numElements; }
+ void clear();
+ void sort();
+};
+
+
+/**
+ * \brief A single-linked list.
+ */
+class List: public Collection
+{
+ friend class ListIterator;
+
+private:
+ struct Node
+ {
+ public:
+ object::Object *object;
+ Node *next;
+ };
+
+ class ListIterator: public AbstractIterator
+ {
+ private:
+ List::Node *current;
+ public:
+ ListIterator(List::Node *node) { current = node; }
+ bool hasNext();
+ Object *getNext();
+ };
+
+ Node *first, *last;
+ bool ownerOfObjects;
+ int numElements;
+
+ bool remove0(object::Object *element, bool compare, bool doNotDeleteAtAll);
+
+protected:
+ AbstractIterator* createIterator();
+
+public:
+ List(bool ownerOfObjects);
+ ~List();
+
+ void clear();
+ void append(object::Object *element);
+ inline bool removeRef(object::Object *element)
+ { return remove0(element, false, false); }
+ inline bool remove(object::Object *element)
+ { return remove0(element, true, false); }
+ inline bool detachRef(object::Object *element)
+ { return remove0(element, false, true); }
+ inline int size() { return numElements; }
+ inline bool isEmpty() { return numElements == 0; }
+ inline object::Object *getFirst() { return first->object; }
+ inline object::Object *getLast() { return last->object; }
+};
+
+
+/**
+ * \brief A hash table.
+ */
+class HashTable: public Collection
+{
+ friend class HashTableIterator;
+
+private:
+ struct Node
+ {
+ object::Object *key, *value;
+ Node *next;
+ };
+
+ class HashTableIterator: public Collection0::AbstractIterator
+ {
+ private:
+ HashTable *table;
+ HashTable::Node *node;
+ int pos;
+
+ void gotoNext();
+
+ public:
+ HashTableIterator(HashTable *table);
+ bool hasNext();
+ Object *getNext();
+ };
+
+ Node **table;
+ int tableSize;
+ bool ownerOfKeys, ownerOfValues;
+
+private:
+ inline int calcHashValue(object::Object *key)
+ {
+ return abs(key->hashValue()) % tableSize;
+ }
+
+protected:
+ AbstractIterator* createIterator();
+
+public:
+ HashTable(bool ownerOfKeys, bool ownerOfValues, int tableSize = 251);
+ ~HashTable();
+
+ void intoStringBuffer(misc::StringBuffer *sb);
+
+ void put (object::Object *key, object::Object *value);
+ bool contains (object::Object *key);
+ Object *get (object::Object *key);
+ bool remove (object::Object *key);
+ Object *getKey (Object *key);
+};
+
+/**
+ * \brief A stack (LIFO).
+ *
+ * Note that the iterator returns all elements in the reversed order they have
+ * been put on the stack.
+ */
+class Stack: public Collection
+{
+ friend class StackIterator;
+
+private:
+ class Node
+ {
+ public:
+ object::Object *object;
+ Node *prev;
+ };
+
+ class StackIterator: public AbstractIterator
+ {
+ private:
+ Stack::Node *current;
+ public:
+ StackIterator(Stack::Node *node) { current = node; }
+ bool hasNext();
+ Object *getNext();
+ };
+
+ Node *bottom, *top;
+ bool ownerOfObjects;
+ int numElements;
+
+protected:
+ AbstractIterator* createIterator();
+
+public:
+ Stack (bool ownerOfObjects);
+ ~Stack();
+
+ void push (object::Object *object);
+ void pushUnder (object::Object *object);
+ inline object::Object *getTop () { return top ? top->object : NULL; }
+ void pop ();
+ inline int size() { return numElements; }
+};
+
+} // namespace untyped
+
+/**
+ * \brief This namespace provides thin wrappers, implemented as C++ templates,
+ * to gain type-safety.
+ *
+ * Notice, that nevertheless, all objects still have to be instances of
+ * object::Object.
+ */
+namespace typed {
+
+template <class T> class Collection;
+
+/**
+ * \brief Typed version of container::untyped::Iterator.
+ */
+template <class T> class Iterator
+{
+ friend class Collection<T>;
+
+private:
+ untyped::Iterator base;
+
+public:
+ inline Iterator() { }
+ inline Iterator(const Iterator<T> &it2) { this->base = it2.base; }
+ inline Iterator(Iterator<T> &it2) { this->base = it2.base; }
+ ~Iterator() { }
+ inline Iterator &operator=(const Iterator<T> &it2)
+ { this->base = it2.base; return *this; }
+ inline Iterator &operator=(Iterator<T> &it2)
+ { this->base = it2.base; return *this; }
+
+ inline bool hasNext() { return this->base.hasNext(); }
+ inline T *getNext() { return (T*)this->base.getNext(); }
+};
+
+/**
+ * \brief Abstract base class template for all container objects in
+ * container::typed.
+ *
+ * Actually, a wrapper for container::untyped::Collection.
+ */
+template <class T> class Collection: public object::Object
+{
+protected:
+ untyped::Collection *base;
+
+public:
+ void intoStringBuffer(misc::StringBuffer *sb)
+ { this->base->intoStringBuffer(sb); }
+
+ inline Iterator<T> iterator() {
+ Iterator<T> it; it.base = this->base->iterator(); return it; }
+};
+
+
+/**
+ * \brief Typed version of container::untyped::Vector.
+ */
+template <class T> class Vector: public Collection <T>
+{
+public:
+ inline Vector(int initSize, bool ownerOfObjects) {
+ this->base = new untyped::Vector(initSize, ownerOfObjects); }
+ ~Vector() { delete this->base; }
+
+ inline void put(T *newElement, int newPos = -1)
+ { ((untyped::Vector*)this->base)->put(newElement, newPos); }
+ inline void insert(T *newElement, int pos)
+ { ((untyped::Vector*)this->base)->insert(newElement, pos); }
+ inline void remove(int pos) { ((untyped::Vector*)this->base)->remove(pos); }
+ inline T *get(int pos)
+ { return (T*)((untyped::Vector*)this->base)->get(pos); }
+ inline int size() { return ((untyped::Vector*)this->base)->size(); }
+ inline void clear() { ((untyped::Vector*)this->base)->clear(); }
+ inline void sort() { ((untyped::Vector*)this->base)->sort(); }
+};
+
+
+/**
+ * \brief Typed version of container::untyped::List.
+ */
+template <class T> class List: public Collection <T>
+{
+public:
+ inline List(bool ownerOfObjects)
+ { this->base = new untyped::List(ownerOfObjects); }
+ ~List() { delete this->base; }
+
+ inline void clear() { ((untyped::List*)this->base)->clear(); }
+ inline void append(T *element)
+ { ((untyped::List*)this->base)->append(element); }
+ inline bool removeRef(T *element) {
+ return ((untyped::List*)this->base)->removeRef(element); }
+ inline bool remove(T *element) {
+ return ((untyped::List*)this->base)->remove(element); }
+ inline bool detachRef(T *element) {
+ return ((untyped::List*)this->base)->detachRef(element); }
+
+ inline int size() { return ((untyped::List*)this->base)->size(); }
+ inline bool isEmpty()
+ { return ((untyped::List*)this->base)->isEmpty(); }
+ inline T *getFirst()
+ { return (T*)((untyped::List*)this->base)->getFirst(); }
+ inline T *getLast()
+ { return (T*)((untyped::List*)this->base)->getLast(); }
+};
+
+
+/**
+ * \brief Typed version of container::untyped::HashTable.
+ */
+template <class K, class V> class HashTable: public Collection <K>
+{
+public:
+ inline HashTable(bool ownerOfKeys, bool ownerOfValues, int tableSize = 251)
+ { this->base = new untyped::HashTable(ownerOfKeys, ownerOfValues,
+ tableSize); }
+ ~HashTable() { delete this->base; }
+
+ inline void put(K *key, V *value)
+ { return ((untyped::HashTable*)this->base)->put(key, value); }
+ inline bool contains(K *key)
+ { return ((untyped::HashTable*)this->base)->contains(key); }
+ inline V *get(K *key)
+ { return (V*)((untyped::HashTable*)this->base)->get(key); }
+ inline bool remove(K *key)
+ { return ((untyped::HashTable*)this->base)->remove(key); }
+ inline K *getKey(K *key)
+ { return (K*)((untyped::HashTable*)this->base)->getKey(key); }
+};
+
+/**
+ * \brief Typed version of container::untyped::Stack.
+ */
+template <class T> class Stack: public Collection <T>
+{
+public:
+ inline Stack (bool ownerOfObjects)
+ { this->base = new untyped::Stack (ownerOfObjects); }
+ ~Stack() { delete this->base; }
+
+ inline void push (T *object) {
+ ((untyped::Stack*)this->base)->push (object); }
+ inline void pushUnder (T *object)
+ { ((untyped::Stack*)this->base)->pushUnder (object); }
+ inline T *getTop ()
+ { return (T*)((untyped::Stack*)this->base)->getTop (); }
+ inline void pop () { ((untyped::Stack*)this->base)->pop (); }
+ inline int size() { return ((untyped::Stack*)this->base)->size(); }
+};
+
+} // namespace untyped
+
+} // namespace container
+
+} // namespace lout
+
+#endif // __LOUT_CONTAINER_HH_
diff --git a/lout/debug.hh b/lout/debug.hh
new file mode 100644
index 00000000..a2c08393
--- /dev/null
+++ b/lout/debug.hh
@@ -0,0 +1,152 @@
+#ifndef __LOUT_DEBUG_H__
+#define __LOUT_DEBUG_H__
+
+/*
+ * Simple debug messages. Add:
+ *
+ * #define DEBUG_LEVEL <n>
+ * #include "debug.h"
+ *
+ * to the file you are working on, or let DEBUG_LEVEL undefined to
+ * disable all messages. A higher level denotes a greater importance
+ * of the message.
+ */
+
+#include <stdio.h>
+
+#define D_STMT_START do
+#define D_STMT_END while (0)
+
+# ifdef DEBUG_LEVEL
+# define DEBUG_MSG(level, ...) \
+ D_STMT_START { \
+ if (DEBUG_LEVEL && (level) >= DEBUG_LEVEL) \
+ printf(__VA_ARGS__); \
+ } D_STMT_END
+# else
+# define DEBUG_MSG(level, ...)
+# endif /* DEBUG_LEVEL */
+
+
+
+/*
+ * Following is experimental, and will be explained soon.
+ */
+
+#ifdef DBG_RTFL
+
+#include <unistd.h>
+#include <stdio.h>
+
+#define DBG_MSG(obj, aspect, prio, msg) \
+ D_STMT_START { \
+ printf ("[rtfl]%s:%d:%d:msg:%p:%s:%d:%s\n", \
+ __FILE__, __LINE__, getpid(), obj, aspect, prio, msg); \
+ fflush (stdout); \
+ } D_STMT_END
+
+#define DBG_MSGF(obj, aspect, prio, fmt, ...) \
+ D_STMT_START { \
+ printf ("[rtfl]%s:%d:%d:msg:%p:%s:%d:" fmt "\n", \
+ __FILE__, __LINE__, getpid(), obj, aspect, prio, __VA_ARGS__); \
+ fflush (stdout); \
+ } D_STMT_END
+
+#define DBG_MSG_START(obj) \
+ D_STMT_START { \
+ printf ("[rtfl]%s:%d:%d:msg-start:%p\n", \
+ __FILE__, __LINE__, getpid(), obj); \
+ fflush (stdout); \
+ } D_STMT_END
+
+#define DBG_MSG_END(obj) \
+ D_STMT_START { \
+ printf ("[rtfl]%s:%d:%d:msg-end:%p\n", \
+ __FILE__, __LINE__, getpid(), obj); \
+ fflush (stdout); \
+ } D_STMT_END
+
+#define DBG_OBJ_CREATE(obj, klass) \
+ D_STMT_START { \
+ printf ("[rtfl]%s:%d:%d:obj-create:%p:%s\n", \
+ __FILE__, __LINE__, getpid(), obj, klass); \
+ fflush (stdout); \
+ } D_STMT_END
+
+#define DBG_OBJ_ASSOC(child, parent) \
+ D_STMT_START { \
+ printf ("[rtfl]%s:%d:%d:obj-assoc:%p:%p\n", \
+ __FILE__, __LINE__, getpid(), child, parent); \
+ fflush (stdout); \
+ } D_STMT_END
+
+#define DBG_OBJ_SET_NUM(obj, var, val) \
+ D_STMT_START { \
+ printf ("[rtfl]%s:%d:%d:obj-set:%p:%s:%d\n", \
+ __FILE__, __LINE__, getpid(), obj, var, val); \
+ fflush (stdout); \
+ } D_STMT_END
+
+#define DBG_OBJ_SET_STR(obj, var, val) \
+ D_STMT_START { \
+ printf ("[rtfl]%s:%d:%d:obj-set:%p:%s:%s\n", \
+ __FILE__, __LINE__, getpid(), obj, var, val); \
+ fflush (stdout); \
+ } D_STMT_END
+
+#define DBG_OBJ_SET_PTR(obj, var, val) \
+ D_STMT_START { \
+ printf ("[rtfl]%s:%d:%d:obj-set:%p:%s:%p\n", \
+ __FILE__, __LINE__, getpid(), obj, var, val); \
+ fflush (stdout); \
+ } D_STMT_END
+
+#define DBG_OBJ_ARRSET_NUM(obj, var, ind, val) \
+ D_STMT_START { \
+ printf ("[rtfl]%s:%d:%d:obj-set:%p:" var ":%d\n", \
+ __FILE__, __LINE__, getpid(), obj, ind, val); \
+ fflush (stdout); \
+ } D_STMT_END
+
+#define DBG_OBJ_ARRSET_STR(obj, var, ind, val) \
+ D_STMT_START { \
+ printf ("[rtfl]%s:%d:%d:obj-set:%p:" var ":%s\n", \
+ __FILE__, __LINE__, getpid(), obj, ind, val); \
+ fflush (stdout); \
+ } D_STMT_END
+
+#define DBG_OBJ_ARRSET_PTR(obj, var, ind, val) \
+ D_STMT_START { \
+ printf ("[rtfl]%s:%d:%d:obj-set:%p:" var ":%p\n", \
+ __FILE__, __LINE__, getpid(), obj, ind, val); \
+ fflush (stdout); \
+ } D_STMT_END
+
+#define DBG_OBJ_COLOR(klass, color) \
+ D_STMT_START { \
+ printf ("[rtfl]%s:%d:%d:obj-color:%s:%s\n", \
+ __FILE__, __LINE__, getpid(), klass, color); \
+ fflush (stdout); \
+ } D_STMT_END
+
+#else /* DBG_RTFL */
+
+#define DBG_MSG(obj, aspect, prio, msg)
+#define DBG_MSGF(obj, aspect, prio, fmt, ...)
+#define DBG_MSG_START(obj)
+#define DBG_MSG_END(obj)
+#define DBG_OBJ_CREATE(obj, klass)
+#define DBG_OBJ_ASSOC(child, parent)
+#define DBG_OBJ_SET_NUM(obj, var, val)
+#define DBG_OBJ_SET_STR(obj, var, val)
+#define DBG_OBJ_SET_PTR(obj, var, val)
+#define DBG_OBJ_ARRSET_NUM(obj, var, ind, val)
+#define DBG_OBJ_ARRSET_STR(obj, var, ind, val)
+#define DBG_OBJ_ARRSET_PTR(obj, var, ind, val)
+#define DBG_OBJ_COLOR(klass, color)
+
+#endif /* DBG_RTFL */
+
+#endif /* __LOUT_DEBUG_H__ */
+
+
diff --git a/lout/identity.cc b/lout/identity.cc
new file mode 100644
index 00000000..5e4965f1
--- /dev/null
+++ b/lout/identity.cc
@@ -0,0 +1,114 @@
+/*
+ * Dillo Widget
+ *
+ * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+
+
+#include "identity.hh"
+
+#include <stdio.h>
+
+namespace lout {
+namespace identity {
+
+// ------------------------
+// IdentifiableObject
+// ------------------------
+
+using namespace object;
+using namespace container::typed;
+
+IdentifiableObject::Class::Class (IdentifiableObject::Class *parent, int id,
+ const char *className)
+{
+ this->parent = parent;
+ this->id = id;
+ this->className = className;
+}
+
+HashTable <ConstString, IdentifiableObject::Class>
+ *IdentifiableObject::classesByName =
+ new HashTable<ConstString, IdentifiableObject::Class> (true, true);
+Vector <IdentifiableObject::Class> *IdentifiableObject::classesById =
+ new Vector <IdentifiableObject::Class> (16, false);
+IdentifiableObject::Class *IdentifiableObject::currentlyConstructedClass;
+
+IdentifiableObject::IdentifiableObject ()
+{
+ currentlyConstructedClass = NULL;
+}
+
+void IdentifiableObject::intoStringBuffer(misc::StringBuffer *sb)
+{
+ sb->append("<instance of ");
+ sb->append(getClassName());
+ sb->append(">");
+}
+
+/**
+ * \brief This method must be called in the constructor for the sub class.
+ * See class comment for details.
+ */
+void IdentifiableObject::registerName (const char *className, int *classId)
+{
+ ConstString str (className);
+ Class *klass = classesByName->get (&str);
+ if (klass == NULL) {
+ klass = new Class (currentlyConstructedClass, classesById->size (),
+ className);
+ ConstString *key = new ConstString (className);
+ classesByName->put (key, klass);
+ classesById->put (klass);
+ }
+
+ this->classId = klass->id;
+ currentlyConstructedClass = klass;
+}
+
+/**
+ * \brief Returns, whether this class is an instance of the class, given by
+ * \em otherClassId, or of a sub class of this class.
+ */
+bool IdentifiableObject::instanceOf (int otherClassId)
+{
+ if (otherClassId == -1)
+ // Other class has not been registered yet, while it should have been,
+ // if this class is an instance of it or of a sub-class.
+ return false;
+
+ Class *otherClass = classesById->get (otherClassId);
+
+ if (otherClass == NULL) {
+ fprintf (stderr,
+ "WARNING: Something got wrong here, it seems that a "
+ "CLASS_ID was not initialized properly.\n");
+ return false;
+ }
+
+ for (Class *klass = classesById->get (classId); klass != NULL;
+ klass = klass->parent) {
+ if (klass == otherClass)
+ return true;
+ }
+
+ return false;
+}
+
+} // namespace identity
+} // namespace lout
diff --git a/lout/identity.hh b/lout/identity.hh
new file mode 100644
index 00000000..7dcdbac4
--- /dev/null
+++ b/lout/identity.hh
@@ -0,0 +1,148 @@
+#ifndef __LOUT_OBJECTX_HH__
+#define __LOUT_OBJECTX_HH__
+
+#include "object.hh"
+#include "container.hh"
+#include "signal.hh"
+
+/**
+ * \brief Some stuff to identify classes of objects at run-time.
+ */
+
+namespace lout {
+
+namespace identity {
+
+/**
+ * \brief Instances of classes, which are sub classes of this class, may
+ * be identified at run-time.
+ *
+ * <h3>Testing the class</h3>
+ *
+ * Since e.g. dw::Textblock is a sub class of IdentifiableObject, and
+ * implemented in the correct way (as described below), for any given
+ * IdentifiableObject the following test can be done:
+ *
+ * \code
+ * identity::IdentifiableObject *o;
+ * // ...
+ * bool isATextblock = o->instanceOf(dw::Textblock::CLASS_ID);
+ * \endcode
+ *
+ * \em isATextblock is true, when \em o is an instance of dw::Textblock,
+ * or of a sub class of dw::Textblock. Otherwise, \em isATextblock is false.
+ *
+ * It is also possible to get the class identifier of an
+ * identity::IdentifiableObject, e.g.
+ *
+ * \code
+ * bool isOnlyATextblock = o->getClassId() == dw::Textblock::CLASS_ID;
+ * \endcode
+ *
+ * would result in true, if o is an instance of dw::Textblock, but not an
+ * instance of a sub class of dw::Textblock.
+ *
+ * <h3>Defining Sub Classes</h3>
+ *
+ * Each direct or indirect sub class of IdentifiableObject must
+ *
+ * <ul>
+ * <li> add a static int CLASS_ID with -1 as initial value, and
+ * <li> call registerName (\em name, &CLASS_ID) in the constructor, where
+ * \em name should be unique, e.g. the fully qualified class name.
+ * </ul>
+ *
+ * After this, <i>class</i>::CLASS_ID refers to a number, which denotes the
+ * class. (If this is still -1, since the class has not yet been instanciated,
+ * any test will fail, which is correct.)
+ *
+ * <h3>Notes on implementation</h3>
+ *
+ * If there are some classes like this:
+ *
+ * \dot
+ * digraph G {
+ * node [shape=record, fontname=Helvetica, fontsize=10];
+ * edge [arrowhead="none", arrowtail="empty", labelfontname=Helvetica,
+ * labelfontsize=10, color="#404040", labelfontcolor="#000080"];
+ * fontname=Helvetica; fontsize=10;
+ * IdentifiableObject [color="#a0a0a0"];
+ * A;
+ * B [color="#a0a0a0"];
+ * C;
+ * IdentifiableObject -> A;
+ * IdentifiableObject -> B;
+ * B -> C;
+ * }
+ * \enddot
+ *
+ * <center>[\ref uml-legend "legend"]</center>
+ *
+ * and first, an instance of A, and then an instance of C is created, there
+ * will be the following calls of functions and constructors:
+ *
+ * <ol>
+ * <li> %IdentifiableObject ();
+ * <li> %registerName ("A", &A::CLASS_ID);
+ * <li> %IdentifiableObject ();
+ * <li> %registerName ("B", &B::CLASS_ID);
+ * <li> %registerName ("C", &C::CLASS_ID);
+ * </ol>
+ *
+ * From this, the class hierarchy above can easily constructed, and stored
+ * in identity::IdentifiableObject::classesByName and
+ * in identity::IdentifiableObject::classesById. See the code for details.
+ *
+ * N.b. Multiple inheritance is not supported, the construction of the
+ * tree would become confused.
+ */
+class IdentifiableObject: public object::Object
+{
+private:
+ class Class: public object::Object
+ {
+ public:
+ Class *parent;
+ int id;
+ const char *className;
+
+ Class (Class *parent, int id, const char *className);
+ };
+
+ static container::typed::HashTable <object::ConstString,
+ Class> *classesByName;
+ static container::typed::Vector <Class> *classesById;
+ static Class *currentlyConstructedClass;
+
+ int classId;
+
+protected:
+ void registerName (const char *className, int *classId);
+
+public:
+ IdentifiableObject ();
+
+ virtual void intoStringBuffer(misc::StringBuffer *sb);
+
+ /**
+ * \brief Returns the class identifier.
+ *
+ * This is rarely used, rather, tests with
+ * identity::IdentifiableObject::instanceOf are done.
+ */
+ int getClassId () { return classId; }
+
+ /**
+ * \brief Return the name, under which the class of this object was
+ * registered.
+ */
+ const char *getClassName() { return classesById->get(classId)->className; }
+
+ bool instanceOf (int otherClassId);
+};
+
+} // namespace identity
+
+} // namespace lout
+
+#endif // __LOUT_OBJECTX_HH__
diff --git a/lout/misc.cc b/lout/misc.cc
new file mode 100644
index 00000000..2008737b
--- /dev/null
+++ b/lout/misc.cc
@@ -0,0 +1,237 @@
+/*
+ * Dillo Widget
+ *
+ * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+
+
+#include "misc.hh"
+
+#include <ctype.h>
+#include <config.h>
+
+#define PRGNAME PACKAGE "/" VERSION
+
+namespace lout {
+
+namespace misc {
+
+const char *prgName = PRGNAME;
+
+void init (int argc, char *argv[])
+{
+ prgName = strdup (argv[0]);
+}
+
+void chop (char *s)
+{
+ char *p = s + strlen (s) - 1;
+ while (*p == '\n') {
+ *p = 0;
+ p--;
+ }
+}
+
+char *strip (char *s)
+{
+ while (isspace (*s))
+ s++;
+
+ char *p = s + strlen (s) - 1;
+ while (isspace (*p)) {
+ *p = 0;
+ p--;
+ }
+
+ return s;
+}
+
+// ----------------
+// Comparable
+// ----------------
+
+Comparable::~Comparable()
+{
+}
+
+/**
+ * \brief This static method may be used as compare function for qsort(3), for
+ * an array of Object* (Object*[] or Object**).
+ */
+int Comparable::compareFun(const void *p1, const void *p2)
+{
+ Comparable **c1 = (Comparable**)p1;
+ Comparable **c2 = (Comparable**)p2;
+ if(c1 && c2)
+ return ((*c1)->compareTo(*c2));
+ else if(c1)
+ return 1;
+ else if(c2)
+ return -1;
+ else
+ return 0;
+}
+
+
+// ------------------
+// StringBuffer
+// ------------------
+
+
+StringBuffer::StringBuffer()
+{
+ firstNode = lastNode = NULL;
+ numChars = 0;
+ str = NULL;
+ strValid = false;
+}
+
+StringBuffer::~StringBuffer()
+{
+ clear ();
+ if(str)
+ delete[] str;
+}
+
+/**
+ * \brief Append a NUL-terminated string to the buffer, without copying.
+ *
+ * No copy is made, so this method should only be used in cases, where
+ * the string would otherwise be freed again. (This method may then
+ * save some CPU cycles.)
+ */
+void StringBuffer::appendNoCopy(char *str)
+{
+ Node *node = new Node();
+ node->data = str;
+ node->next = NULL;
+
+ if(firstNode == NULL) {
+ firstNode = node;
+ lastNode = node;
+ } else {
+ lastNode->next = node;
+ lastNode = node;
+ }
+
+ numChars += strlen(str);
+ strValid = false;
+}
+
+/**
+ * \brief Return a NUL-terminated strings containing all appended strings.
+ *
+ * The caller does not have to free the string, this is done in
+ * misc::StringBuffer::~StringBuffer.
+ */
+const char *StringBuffer::getChars()
+{
+ if(strValid)
+ return str;
+
+ if(str)
+ delete[] str;
+ str = new char[numChars + 1];
+ char *p = str;
+
+ for(Node *node = firstNode; node; node = node->next) {
+ int l = strlen(node->data);
+ memcpy(p, node->data, l * sizeof(char));
+ p += l;
+ }
+
+ *p = 0;
+ strValid = true;
+ return str;
+}
+
+/**
+ * \brief Remove all strings appended to the string buffer.
+ */
+void StringBuffer::clear ()
+{
+ Node *node, *nextNode;
+ for(node = firstNode; node; node = nextNode) {
+ nextNode = node->next;
+ delete node->data;
+ delete node;
+ }
+ firstNode = lastNode = NULL;
+ numChars = 0;
+ strValid = false;
+}
+
+
+// ------------
+// BitSet
+// ------------
+
+BitSet::BitSet(int initBits)
+{
+ numBytes = bytesForBits(initBits);
+ bits = (unsigned char*)malloc(numBytes * sizeof(unsigned char));
+ clear();
+}
+
+BitSet::~BitSet()
+{
+ free(bits);
+}
+
+void BitSet::intoStringBuffer(misc::StringBuffer *sb)
+{
+ sb->append("[");
+ for(int i = 0; i < numBytes; i++)
+ sb->append(get(i) ? "1" : "0");
+ sb->append("]");
+}
+
+bool BitSet::get(int i)
+{
+ if(8 * i >= numBytes)
+ return false;
+ else
+ return bits[i / 8] & (1 << (i % 8));
+}
+
+void BitSet::set(int i, bool val)
+{
+ if(8 * i >= numBytes) {
+ int newNumBytes = numBytes;
+ while(8 * i >= newNumBytes)
+ newNumBytes *= 2;
+ bits =
+ (unsigned char*)realloc(bits, newNumBytes * sizeof(unsigned char));
+ memset(bits + numBytes, 0, newNumBytes - numBytes);
+ numBytes = newNumBytes;
+ }
+
+ if(val)
+ bits[i / 8] |= (1 << (i % 8));
+ else
+ bits[i / 8] &= ~(1 << (i % 8));
+}
+
+void BitSet::clear()
+{
+ memset(bits, 0, numBytes);
+}
+
+} // namespace misc
+
+} // namespace lout
diff --git a/lout/misc.hh b/lout/misc.hh
new file mode 100644
index 00000000..f9184bf3
--- /dev/null
+++ b/lout/misc.hh
@@ -0,0 +1,320 @@
+#ifndef __LOUT_MISC_HH__
+#define __LOUT_MISC_HH__
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+/**
+ * \brief Miscellaneous stuff, which does not fit anywhere else.
+ *
+ * Actually, the other parts, beginning with ::object, depend on this.
+ */
+namespace lout {
+
+namespace misc {
+
+template <class T> inline T min (T a, T b) { return a < b ? a : b; }
+template <class T> inline T max (T a, T b) { return a > b ? a : b; }
+
+template <class T> inline T min (T a, T b, T c)
+{
+ return (min (a, min (b, c)));
+}
+template <class T> inline T max (T a, T b, T c)
+{
+ return (max (a, max (b, c)));
+}
+
+extern const char *prgName;
+
+void init (int argc, char *argv[]);
+void chop (char *s);
+char *strip (char *s);
+
+
+inline void assertNotReached ()
+{
+ fprintf (stderr, "*** [%s] This should not happen! ***\n", prgName);
+ abort ();
+}
+
+/**
+ * \brief Instances of a sub class of this interface may be compared (less,
+ * greater).
+ *
+ * Used for sorting etc.
+ */
+class Comparable
+{
+public:
+ virtual ~Comparable();
+
+ /**
+ * \brief Compare two objects c1 and c2.
+ *
+ * return a value < 0, when c1 is less than c2, a value > 0, when c1
+ * is greater than c2, or 0, when c1 and c2 are equal.
+ *
+ * If also object::Object is implemented, and if c1.equals(c2),
+ * c1.compareTo(c2) must be 0, but, unlike you may expect,
+ * the reversed is not necessarily true. This method returns 0, if,
+ * according to the rules for sorting, there is no difference, but there
+ * may still be differences (not relevant for sorting), which "equals" will
+ * care about.
+ */
+ virtual int compareTo(Comparable *other) = 0;
+
+ static int compareFun(const void *p1, const void *p2);
+};
+
+/**
+ * \brief Simple (simpler than container::untyped::Vector and
+ * container::typed::Vector) template based vector.
+ */
+template <class T> class SimpleVector
+{
+private:
+ enum {
+ /**
+ * \brief Edit this for debugging. Should be optimized by the compiler.
+ */
+ BOUND_CHECKING = 1
+ };
+
+ T *array;
+ int num, numAlloc;
+
+ inline void resize ()
+ {
+ /* This algorithm was tunned for memory&speed with this huge page:
+ * http://downloads.mysql.com/docs/refman-6.0-en.html.tar.gz
+ */
+ if (array == NULL) {
+ this->numAlloc = 1;
+ this->array = (T*) malloc (sizeof (T));
+ }
+ if (this->numAlloc < this->num) {
+ this->numAlloc = (this->num < 100) ?
+ this->num : this->num + this->num/10;
+ this->array =
+ (T*) realloc(this->array, (this->numAlloc * sizeof (T)));
+ }
+ }
+
+public:
+ inline SimpleVector (int initAlloc)
+ {
+ this->num = 0;
+ this->numAlloc = initAlloc;
+ this->array = NULL;
+ }
+
+ inline ~SimpleVector ()
+ {
+ if (this->array)
+ free (this->array);
+ }
+
+ /**
+ * \brief Return the number of elements put into this vector.
+ */
+ inline int size() { return this->num; }
+
+ inline T* getArray() { return array; }
+
+ /**
+ * \brief Increase the vector size by one.
+ *
+ * May be necessary before calling misc::SimpleVector::set.
+ */
+ inline void increase() { this->num++; this->resize (); }
+
+ /**
+ * \brief Set the size explicitely.
+ *
+ * May be necessary before called before misc::SimpleVector::set.
+ */
+ inline void setSize(int newSize) { this->num = newSize; this->resize (); }
+
+ /**
+ * \brief Set the size explicitely and initialize new values.
+ *
+ * May be necessary before called before misc::SimpleVector::set.
+ */
+ inline void setSize (int newSize, T t) {
+ int oldSize = this->num;
+ setSize (newSize);
+ for (int i = oldSize; i < newSize; i++)
+ set (i, t);
+ }
+
+ /**
+ * \brief Return the reference of one element.
+ *
+ * \sa misc::SimpleVector::get
+ */
+ inline T* getRef (int i) {
+ if (BOUND_CHECKING)
+ assert (i >= 0 && i < this->num);
+ return array + i;
+ }
+
+ /**
+ * \brief Return the one element, explicitety.
+ *
+ * The element is copied, so for complex elements, you should rather used
+ * misc::SimpleVector::getRef.
+ */
+ inline T get (int i) {
+ if (BOUND_CHECKING)
+ assert (i >= 0 && i < this->num);
+ return this->array[i];
+ }
+
+ /**
+ * \brief Store an object in the vector.
+ *
+ * Unlike in container::untyped::Vector and container::typed::Vector,
+ * you have to care about the size, so a call to
+ * misc::SimpleVector::increase or misc::SimpleVector::setSize may
+ * be necessary before.
+ */
+ inline void set (int i, T t) {
+ if (BOUND_CHECKING)
+ assert (i >= 0 && i < this->num);
+ this->array[i] = t;
+ }
+};
+
+
+/**
+ * \brief A class for fast concatenation of a large number of strings.
+ */
+class StringBuffer
+{
+private:
+ struct Node
+ {
+ char *data;
+ Node *next;
+ };
+
+ Node *firstNode, *lastNode;
+ int numChars;
+ char *str;
+ bool strValid;
+
+public:
+ StringBuffer();
+ ~StringBuffer();
+
+ /**
+ * \brief Append a NUL-terminated string to the buffer, with copying.
+ *
+ * A copy is kept in the buffer, so the caller does not have to care
+ * about memory management.
+ */
+ inline void append(const char *str) { appendNoCopy(strdup(str)); }
+ void appendNoCopy(char *str);
+ const char *getChars();
+ void clear ();
+};
+
+
+/**
+ * \brief A bit set, which automatically reallocates when needed.
+ */
+class BitSet
+{
+private:
+ unsigned char *bits;
+ int numBytes;
+
+ inline int bytesForBits(int bits) { return bits == 0 ? 1 : (bits + 7) / 8; }
+
+public:
+ BitSet(int initBits);
+ ~BitSet();
+ void intoStringBuffer(misc::StringBuffer *sb);
+ bool get(int i);
+ void set(int i, bool val);
+ void clear();
+};
+
+/**
+ * \brief A simple allocator optimized to handle many small chunks of memory.
+ * The chunks can not be free'd individually. Instead the whole zone must be
+ * free'd with zoneFree().
+ */
+class ZoneAllocator
+{
+private:
+ size_t poolSize, poolLimit, freeIdx;
+ SimpleVector <char*> *pools;
+ SimpleVector <char*> *bulk;
+
+public:
+ ZoneAllocator (size_t poolSize) {
+ this->poolSize = poolSize;
+ this->poolLimit = poolSize / 4;
+ this->freeIdx = poolSize;
+ this->pools = new SimpleVector <char*> (1);
+ this->bulk = new SimpleVector <char*> (1);
+ };
+
+ ~ZoneAllocator () {
+ zoneFree ();
+ delete pools;
+ delete bulk;
+ }
+
+ inline void * zoneAlloc (size_t t) {
+ void *ret;
+
+ if (t > poolLimit) {
+ bulk->increase ();
+ bulk->set (bulk->size () - 1, (char*) malloc (t));
+ return bulk->get (bulk->size () - 1);
+ }
+
+ if (t > poolSize - freeIdx) {
+ pools->increase ();
+ pools->set (pools->size () - 1, (char*) malloc (poolSize));
+ freeIdx = 0;
+ }
+
+ ret = pools->get (pools->size () - 1) + freeIdx;
+ freeIdx += t;
+ return ret;
+ }
+
+ inline void zoneFree () {
+ for (int i = 0; i < pools->size (); i++)
+ free (pools->get (i));
+ pools->setSize (0);
+ for (int i = 0; i < bulk->size (); i++)
+ free (bulk->get (i));
+ bulk->setSize (0);
+ freeIdx = poolSize;
+ }
+
+ inline const char *strndup (const char *str, size_t t) {
+ char *new_str = (char *) zoneAlloc (t + 1);
+ memcpy (new_str, str, t);
+ new_str[t] = '\0';
+ return new_str;
+ }
+
+ inline const char *strdup (const char *str) {
+ return strndup (str, strlen (str));
+ }
+};
+
+} // namespace misc
+
+} // namespace lout
+
+#endif // __LOUT_MISC_HH__
diff --git a/lout/object.cc b/lout/object.cc
new file mode 100644
index 00000000..7da124fa
--- /dev/null
+++ b/lout/object.cc
@@ -0,0 +1,311 @@
+/*
+ * Dillo Widget
+ *
+ * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+
+
+#include "object.hh"
+#include <stdio.h>
+
+namespace lout {
+
+namespace object {
+
+// ------------
+// Object
+// ------------
+
+/**
+ * \brief The destructor is defined as virtual (but not abstract), so that
+ * destruction of Object's works properly.
+ */
+Object::~Object()
+{
+}
+
+/**
+ * \brief Returns, whether two objects are equal.
+ *
+ * The caller should ensure, that this and the object have the same class;
+ * this makes casting of "other" safe. Typically, an implementation should
+ * check this == other first, the caller can assume a fast implementation.
+ */
+bool Object::equals(Object *other)
+{
+ misc::assertNotReached ();
+ return false;
+}
+
+/**
+ * \brief Return a hash value for the object.
+ */
+int Object::hashValue()
+{
+ fprintf (stderr, "Object::hashValue() should be implemented.\n");
+ return 0;
+}
+
+/**
+ * \brief Return an exact copy of the object.
+ */
+Object *Object::clone()
+{
+ misc::assertNotReached ();
+ return NULL;
+}
+
+/**
+ * \brief Use object::Object::intoStringBuffer to return a textual
+ * representation of the object.
+ *
+ * The caller does not have to free the memory, object::Object is responsible
+ * for this.
+ */
+const char *Object::toString()
+{
+ /** \todo garbage! */
+ misc::StringBuffer sb;
+ intoStringBuffer(&sb);
+ char *s = strdup(sb.getChars());
+ return s;
+}
+
+/**
+ * \brief Store a textual representation of the object in a misc::StringBuffer.
+ *
+ * This is used by object::Object::toString.
+ */
+void Object::intoStringBuffer(misc::StringBuffer *sb)
+{
+ sb->append("<not further specified object>");
+}
+
+/**
+ * \brief Return the number of bytes, this object totally uses.
+ */
+size_t Object::sizeOf()
+{
+ fprintf (stderr, "Object::sizeOf() should be implemented.\n");
+ return sizeof(Object*);
+}
+
+// -------------
+// Pointer
+// -------------
+
+bool Pointer::equals(Object *other)
+{
+ return value == ((Pointer*)other)->value;
+}
+
+int Pointer::hashValue()
+{
+/* For some unknown reason, this doesn't compile on some 64bit platforms:
+ *
+ * if (sizeof (int) == sizeof (void*))
+ * return (int)value;
+ * else
+ * return ((int*)value)[0] ^ ((int*)value)[1];
+ */
+#if SIZEOF_VOID_P == 4
+ return (int)value;
+#else
+ return ((int*)value)[0] ^ ((int*)value)[1];
+#endif
+}
+
+void Pointer::intoStringBuffer(misc::StringBuffer *sb)
+{
+ char buf[64];
+ snprintf(buf, sizeof(buf), "0x%p", value);
+ sb->append(buf);
+}
+
+// -------------
+// Integer
+// -------------
+
+bool Integer::equals(Object *other)
+{
+ return value == ((Integer*)other)->value;
+}
+
+int Integer::hashValue()
+{
+ return (int)value;
+}
+
+void Integer::intoStringBuffer(misc::StringBuffer *sb)
+{
+ char buf[64];
+ sprintf(buf, "%d", value);
+ sb->append(buf);
+}
+
+int Integer::compareTo(Comparable *other)
+{
+ return value - ((Integer*)other)->value;
+}
+
+// -----------------
+// ConstString
+// -----------------
+
+bool ConstString::equals(Object *other)
+{
+ ConstString *otherString = (ConstString*)other;
+ return
+ this == other ||
+ (str == NULL && otherString->str == NULL) ||
+ (str != NULL && otherString->str != NULL &&
+ strcmp(str, otherString->str) == 0);
+}
+
+int ConstString::hashValue()
+{
+ return hashValue(str);
+}
+
+
+int ConstString::compareTo(Comparable *other)
+{
+ String *otherString = (String*)other;
+ if(str && otherString->str)
+ return strcmp(str, otherString->str);
+ else if(str)
+ return 1;
+ else if(otherString->str)
+ return -1;
+ else
+ return 0;
+}
+
+
+int ConstString::hashValue(const char *str)
+{
+ if(str) {
+ int h = 0;
+ for (int i = 0; str[i]; i++)
+ h = (h * 256 + str[i]);
+ return h;
+ } else
+ return 0;
+}
+
+void ConstString::intoStringBuffer(misc::StringBuffer *sb)
+{
+ sb->append(str);
+}
+
+// ------------
+// String
+// ------------
+
+String::String (const char *str): ConstString (str ? strdup(str) : NULL)
+{
+}
+
+String::~String ()
+{
+ if(str)
+ delete str;
+}
+
+// ------------
+// Pair
+// ------------
+
+PairBase::PairBase(Object *first, Object *second)
+{
+ this->first = first;
+ this->second = second;
+}
+
+PairBase::~PairBase()
+{
+ if(first)
+ delete first;
+ if(second)
+ delete second;
+}
+
+bool PairBase::equals(Object *other)
+{
+ PairBase *otherPair = (PairBase*)other;
+
+ return
+ // Identical?
+ this == other ||
+ (// Both first parts are NULL, ...
+ (first == NULL && otherPair->first == NULL) ||
+ // ... or both first parts are not NULL and equal
+ (first != NULL && otherPair->first != NULL
+ && first->equals (otherPair->first))) &&
+ // Same with second part.
+ ((second == NULL && otherPair->second == NULL) ||
+ (second != NULL && otherPair->second != NULL
+ && second->equals (otherPair->second)));
+}
+
+int PairBase::hashValue()
+{
+ int value = 0;
+
+ if(first)
+ value ^= first->hashValue();
+ if(second)
+ value ^= second->hashValue();
+
+ return value;
+}
+
+void PairBase::intoStringBuffer(misc::StringBuffer *sb)
+{
+ sb->append("<pair: ");
+
+ if(first)
+ first->intoStringBuffer(sb);
+ else
+ sb->append("(nil)");
+
+ sb->append(",");
+
+ if(second)
+ second->intoStringBuffer(sb);
+ else
+ sb->append("(nil)");
+
+ sb->append(">");
+}
+
+size_t PairBase::sizeOf()
+{
+ size_t size = 0;
+
+ if(first)
+ size += first->sizeOf();
+ if(second)
+ size += second->sizeOf();
+
+ return size;
+}
+
+} // namespace object
+
+} // namespace lout
diff --git a/lout/object.hh b/lout/object.hh
new file mode 100644
index 00000000..7d505c99
--- /dev/null
+++ b/lout/object.hh
@@ -0,0 +1,161 @@
+#ifndef __LOUT_OBJECT_HH__
+#define __LOUT_OBJECT_HH__
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "misc.hh"
+
+/**
+ * \brief Here, some common classes (or interfaces) are defined, to standardize
+ * the access to other classes.
+ */
+namespace lout {
+
+namespace object {
+
+/**
+ * \brief This is the base class for many other classes, which defines very
+ * common virtual methods.
+ *
+ * For convenience, none of them are abstract, but they
+ * must be defined, when they are needed, especially for containers.
+ */
+class Object
+{
+public:
+ virtual ~Object();
+ virtual bool equals(Object *other);
+ virtual int hashValue();
+ virtual Object *clone();
+ virtual void intoStringBuffer(misc::StringBuffer *sb);
+ const char *toString();
+ virtual size_t sizeOf();
+};
+
+/**
+ * \brief An object::Object wrapper for void pointers.
+ */
+class Pointer: public Object
+{
+private:
+ void *value;
+
+public:
+ Pointer(void *value) { this->value = value; }
+ bool equals(Object *other);
+ int hashValue();
+ void intoStringBuffer(misc::StringBuffer *sb);
+ inline void *getValue() { return value; }
+};
+
+/**
+ * \brief A typed version of object::Pointer.
+ */
+template <class T> class TypedPointer: public Pointer
+{
+public:
+ inline TypedPointer(T *value) : Pointer ((void*)value) { }
+ inline T *getTypedValue() { return (T*)getValue(); }
+};
+
+
+/**
+ * \brief An object::Object wrapper for int's.
+ */
+class Integer: public Object, misc::Comparable
+{
+ int value;
+
+public:
+ Integer(int value) { this->value = value; }
+ bool equals(Object *other);
+ int hashValue();
+ void intoStringBuffer(misc::StringBuffer *sb);
+ int compareTo(Comparable *other);
+ inline int getValue() { return value; }
+};
+
+
+/**
+ * \brief An object::Object wrapper for constant strings (char*).
+ *
+ * As opposed to object::String, the char array is not copied.
+ */
+class ConstString: public Object, misc::Comparable
+{
+protected:
+ const char *str;
+
+public:
+ ConstString(const char *str) { this->str = str; }
+ bool equals(Object *other);
+ int hashValue();
+ int compareTo(Comparable *other);
+ void intoStringBuffer(misc::StringBuffer *sb);
+
+ inline const char *chars() { return str; }
+
+ static int hashValue(const char *str);
+};
+
+
+/**
+ * \brief An object::Object wrapper for strings (char*).
+ *
+ * As opposed to object::ConstantString, the char array is copied.
+ */
+class String: public ConstString
+{
+public:
+ String(const char *str);
+ ~String();
+};
+
+/**
+ * \todo Comment
+ */
+class PairBase: public Object
+{
+protected:
+ Object *first, *second;
+
+public:
+ PairBase(Object *first, Object *second);
+ ~PairBase();
+
+ bool equals(Object *other);
+ int hashValue();
+ void intoStringBuffer(misc::StringBuffer *sb);
+ size_t sizeOf();
+};
+
+/**
+ * \todo Comment
+ */
+class Pair: public PairBase
+{
+public:
+ Pair(Object *first, Object *second): PairBase (first, second) { }
+
+ inline Object *getFirst () { return first; }
+ inline Object *getSecond () { return second; }
+};
+
+/**
+ * \todo Comment
+ */
+template <class F, class S> class TypedPair: public PairBase
+{
+public:
+ TypedPair(F *first, S *second): PairBase (first, second) { }
+
+ inline F *getFirst () { return first; }
+ inline S *getSecond () { return second; }
+};
+
+} // namespace object
+
+} // namespace lout
+
+#endif // __LOUT_OBJECT_HH__
diff --git a/lout/signal.cc b/lout/signal.cc
new file mode 100644
index 00000000..46aae626
--- /dev/null
+++ b/lout/signal.cc
@@ -0,0 +1,171 @@
+/*
+ * Dillo Widget
+ *
+ * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+
+
+#include "signal.hh"
+
+namespace lout {
+namespace signal {
+
+using namespace container::typed;
+
+// ------------
+// Emitter
+// ------------
+
+Emitter::Emitter ()
+{
+ receivers = new List <Receiver> (false);
+}
+
+Emitter::~Emitter ()
+{
+ for(Iterator<Receiver> it = receivers->iterator (); it.hasNext (); ) {
+ Receiver *receiver = it.getNext ();
+ receiver->unconnectFrom (this);
+ }
+ delete receivers;
+}
+
+void Emitter::intoStringBuffer(misc::StringBuffer *sb)
+{
+ sb->append ("<emitter: ");
+ receivers->intoStringBuffer (sb);
+ sb->append (">");
+}
+
+void Emitter::unconnect (Receiver *receiver)
+{
+ receivers->removeRef (receiver);
+}
+
+/**
+ * \brief Connect a receiver to the emitter.
+ *
+ * This is protected, a sub class should define a wrapper, with the respective
+ * receiver as an argument, to gain type safety.
+ */
+void Emitter::connect (Receiver *receiver)
+{
+ receivers->append (receiver);
+ receiver->connectTo (this);
+}
+
+/**
+ * \brief Emit a void signal.
+ *
+ * This method should be called by a wrapper (return value void), which
+ * \em folds the signal, and delegates the emission to here.
+ */
+void Emitter::emitVoid (int signalNo, int argc, Object **argv)
+{
+ for(Iterator <Receiver> it = receivers->iterator (); it.hasNext (); ) {
+ Receiver *receiver = it.getNext();
+ emitToReceiver (receiver, signalNo, argc, argv);
+ }
+}
+
+/**
+ * \brief Emit a boolean signal.
+ *
+ * This method should be called by a wrapper, which \em folds the signal,
+ * delegates the emission to here, and returns the same boolean value.
+ */
+bool Emitter::emitBool (int signalNo, int argc, Object **argv)
+{
+ bool b = false, bt;
+
+ for(Iterator <Receiver> it = receivers->iterator (); it.hasNext (); ) {
+ Receiver *receiver = it.getNext();
+ // Note: All receivers are called, even if one returns true.
+ // Therefore, something like
+ // b = b || emitToReceiver (receiver, signalNo, argc, argv);
+ // does not work.
+ bt = emitToReceiver (receiver, signalNo, argc, argv);
+ b = b || bt;
+ }
+
+ return b;
+}
+
+
+// --------------
+// Receiver
+// --------------
+
+Receiver::Receiver()
+{
+ emitters = new List <Emitter> (false);
+}
+
+Receiver::~Receiver()
+{
+ for(Iterator<Emitter> it = emitters->iterator(); it.hasNext(); ) {
+ Emitter *emitter = it.getNext();
+ emitter->unconnect (this);
+ }
+ delete emitters;
+}
+
+void Receiver::intoStringBuffer(misc::StringBuffer *sb)
+{
+ // emitters are not listed, to prevent recursion
+ sb->append ("<receiver>");
+}
+
+void Receiver::connectTo(Emitter *emitter)
+{
+ emitters->append (emitter);
+}
+
+void Receiver::unconnectFrom(Emitter *emitter)
+{
+ emitters->removeRef (emitter);
+}
+
+// ------------------------
+// ObservedObject
+// ------------------------
+
+bool ObservedObject::DeletionEmitter::emitToReceiver (Receiver *receiver,
+ int signalNo,
+ int argc, Object **argv)
+{
+ object::TypedPointer <ObservedObject> *p =
+ (object::TypedPointer<ObservedObject>*)argv[0];
+ ((DeletionReceiver*)receiver)->deleted (p->getTypedValue ());
+ return false;
+}
+
+void ObservedObject::DeletionEmitter::emitDeletion (ObservedObject *obj)
+{
+ object::TypedPointer <ObservedObject> p(obj);
+ object::Object *argv[1] = { &p };
+ emitVoid (0, 1, argv);
+}
+
+ObservedObject::~ObservedObject()
+{
+ deletionEmitter.emitDeletion (this);
+}
+
+} // namespace signal
+} // namespace lout
diff --git a/lout/signal.hh b/lout/signal.hh
new file mode 100644
index 00000000..c96247be
--- /dev/null
+++ b/lout/signal.hh
@@ -0,0 +1,310 @@
+#ifndef __LOUT_SIGNALS_HH__
+#define __LOUT_SIGNALS_HH__
+
+#include "object.hh"
+#include "container.hh"
+
+/**
+ * \brief This namespace provides base classes to define signals.
+ *
+ * By using signals, objects may be connected at run-time, e.g. a general
+ * button widget may be connected to another application-specific object,
+ * which reacts on the operations on the button by the user. In this case,
+ * the button e.g. defines a signal "clicked", which is "emitted" each
+ * time the user clicks on the button. After the application-specific
+ * object has been connected to this signal, a specific method of it will
+ * be called each time, this button emits the "clicked" signal.
+ *
+ * Below, we will call the level, on which signals are defined, the
+ * "general level", and the level, on which the signals are connected,
+ * the "caller level".
+ *
+ * <h3>Defining Signals</h3>
+ *
+ * Typically, signals are grouped. To define a signal group \em bar for your
+ * class \em Foo, you have to define two classes, the emitter and the
+ * receiver (BarEmitter and BarReceiver), and instanciate the emitter:
+ *
+ * \dot
+ * digraph G {
+ * node [shape=record, fontname=Helvetica, fontsize=10];
+ * edge [arrowhead="none", arrowtail="empty", labelfontname=Helvetica,
+ * labelfontsize=10, color="#404040", labelfontcolor="#000080"];
+ * fontname=Helvetica; fontsize=10;
+ *
+ * subgraph cluster_signal {
+ * style="dashed"; color="#000080"; fontname=Helvetica; fontsize=10;
+ * label="signal";
+ *
+ * Emitter [color="#a0a0a0", URL="\ref signal::Emitter"];
+ * Receiver [color="#a0a0a0", URL="\ref signal::Receiver"];
+ * }
+ *
+ * subgraph cluster_foo {
+ * style="dashed"; color="#000080"; fontname=Helvetica; fontsize=10;
+ * label="General (foo)";
+ *
+ * Foo;
+ * BarEmitter;
+ * BarReceiver [color="#a0a0a0"];
+ * }
+ *
+ * Emitter -> BarEmitter;
+ * Receiver -> BarReceiver;
+ * Foo -> BarEmitter [arrowhead="open", arrowtail="none",
+ * headlabel="1", taillabel="1"];
+ * }
+ * \enddot
+ *
+ * <center>[\ref uml-legend "legend"]</center>
+ *
+ * BarEmitter (class and instance) may be kept private, but BarReceiver must
+ * be public, since the caller of Foo must create a sub class of it. For
+ * BarEmitter, several methods must be implemented, see signal::Emitter for
+ * details. In BarReceiver, only some virtual abstract methods are defined,
+ * which the caller must implement. In this case, it is recommended to define
+ * a connectBar(BarReceiver*) method in Foo, which is delegated to the
+ * BarEmitter.
+ *
+ * <h3>Connecting to Signals</h3>
+ *
+ * A caller, which wants to connect to a signal, must define a sub class of
+ * the receiver, and implement the virtual methods. A typical design looks
+ * like this:
+ *
+ * \dot
+ * digraph G {
+ * node [shape=record, fontname=Helvetica, fontsize=10];
+ * edge [arrowhead="open", arrowtail="none", labelfontname=Helvetica,
+ * labelfontsize=10, color="#404040", labelfontcolor="#000080"];
+ * fontname=Helvetica; fontsize=10;
+ *
+ * subgraph cluster_foo {
+ * style="dashed"; color="#000080"; fontname=Helvetica; fontsize=10;
+ * label="Generall (foo)";
+ *
+ * BarReceiver [color="#a0a0a0"];
+ * }
+ *
+ * subgraph cluster_qix {
+ * style="dashed"; color="#000080"; fontname=Helvetica; fontsize=10;
+ * label="Caller (qix)";
+ *
+ * Qix;
+ * QixBarReceiver;
+ * }
+ *
+ * BarReceiver -> QixBarReceiver [arrowhead="none", arrowtail="empty"];
+ * QixBarReceiver -> Qix [headlabel="1", taillabel="*"];
+ * }
+ * \enddot
+ *
+ * <center>[\ref uml-legend "legend"]</center>
+ *
+ * (We skip "baz" in the canon, for better readability.)
+ *
+ * Here, the QixBarReceiver is connected to the Qix, so that the signals can
+ * be delegated to the Qix. Notice that the receiver gets automatically
+ * disconnected, when deleted (see signal::Receiver::~Receiver).
+ *
+ * <h3>Void and Boolean Signals</h3>
+ *
+ * In the simplest case, signal emitting involves calling a list of
+ * signal receivers (void signals). For boolean signals, the receivers return
+ * a boolean value, and the result of the signal emission (the return value of
+ * signal::Emitter::emitBool) returns the disjunction of the values returned
+ * by the receivers. Typically, a receiver states with its return value,
+ * whether the signal was used in any way, the resulting return value so
+ * indicates, whether at least one receiver has used the signal.
+ *
+ * In Dw, events are processed this way. In the simplest case, they are
+ * delegated to the parent widget, if the widget does not process them (by
+ * returning false). As an addition, signals are emitted, and if a receiver
+ * processes the event, this is handled the same way, as if the widget itself
+ * would have processed it.
+ *
+ * Notice, that also for boolean signals, all receivers are called, even
+ * after one receiver has already returned true.
+ *
+ * <h3>Memory Management</h3>
+ *
+ * <h4>Emitters</h4>
+ *
+ * Emitters are typically instanciated one, for one object emitting the
+ * signals. In the example above, the class Foo will contain a field
+ * "BarEmitter barEmitter" (not as a pointer, "BarEmitter *barEmitter").
+ *
+ * <h4>Receivers</h4>
+ *
+ * It is important, that a emitter never deletes a receiver, it just removes
+ * them from the receivers list. Likewise, when a receiver is deleted, it
+ * unconnects itself from all emitters. (The same receiver instance can indeed
+ * be connected to multiple emitters.) So, the caller has to care about
+ * deleting receivers.
+ *
+ * In the example above, something like that will work:
+ *
+ * \code
+ * class Qix
+ * {
+ * private:
+ * class QixBarReceiver
+ * {
+ * public:
+ * Qix *qix;
+ * // ...
+ * };
+ *
+ * QixBarReceiver barReceiver;
+ *
+ * // ...
+ * };
+ * \endcode
+ *
+ * The constructor of Qix should then set \em qix:
+ *
+ * \code
+ * Qix::Qix ()
+ * {
+ * barReceiver.qix = this.
+ * // ...
+ * }
+ * \endcode
+ *
+ * After this, &\em barReceiver can be connected to all instances of
+ * BarEmitter, also multiple times.
+ */
+namespace lout {
+
+namespace signal {
+
+class Receiver;
+
+/**
+ * \brief The base class for signal emitters.
+ *
+ * If defining a signal group, a sub class of this class must be defined,
+ * with
+ *
+ * <ul>
+ * <li> a definition of the different signals (as enumeration),
+ * <li> an implementation of signal::Emitter::emitToReceiver,
+ * <li> wrappers for signal::Emitter::emitVoid and signal::Emitter::emitBool,
+ * respectively (one for each signal), and
+ * <li> a wrapper for signal::Emitter::connect.
+ * </ul>
+ *
+ * There are two representations of signals:
+ *
+ * <ul>
+ * <li> In the \em unfolded representation, the signal itself is represented
+ * by the method itself (in the emitter or the receiver), and the
+ * arguments are represented as normal C++ types.
+ *
+ * <li> \em Folding signals means to represent the signal itself by an integer
+ * number (enumeration), and translate the arguments in an object::Object*
+ * array. (If a given argument is not an instance of object::Object*,
+ * the wrappers in ::object can be used.)
+ * </ul>
+ *
+ * \sa ::signal
+ */
+class Emitter: public object::Object
+{
+ friend class Receiver;
+
+private:
+ container::typed::List <Receiver> *receivers;
+
+ void unconnect (Receiver *receiver);
+
+protected:
+ void emitVoid (int signalNo, int argc, Object **argv);
+ bool emitBool (int signalNo, int argc, Object **argv);
+ void connect(Receiver *receiver);
+
+ /**
+ * \brief A sub class must implement this for a call to a single
+ * receiver.
+ *
+ * This methods gets the signal in a \em folded representation, it has
+ * to unfold it, and pass it to a single receiver. For boolean signals,
+ * the return value of the receiver must be returned, for void signals,
+ * the return value is discarded.
+ */
+ virtual bool emitToReceiver (Receiver *receiver, int signalNo,
+ int argc, Object **argv) = 0;
+
+public:
+ Emitter();
+ ~Emitter();
+
+ void intoStringBuffer(misc::StringBuffer *sb);
+};
+
+/**
+ * \brief The base class for signal receiver base classes.
+ *
+ * If defining a signal group, a sub class of this class must be defined,
+ * in which only the abstract signal methods must be defined.
+ *
+ * \sa ::signal
+ */
+class Receiver: public object::Object
+{
+ friend class Emitter;
+
+private:
+ container::typed::List<Emitter> *emitters;
+
+ void connectTo(Emitter *emitter);
+ void unconnectFrom(Emitter *emitter);
+
+public:
+ Receiver();
+ ~Receiver();
+
+ void intoStringBuffer(misc::StringBuffer *sb);
+};
+
+/**
+ * \brief An observed object has a signal emitter, which tells the
+ * receivers, when the object is deleted.
+ */
+class ObservedObject
+{
+public:
+ class DeletionReceiver: public signal::Receiver
+ {
+ public:
+ virtual void deleted (ObservedObject *object) = 0;
+ };
+
+private:
+ class DeletionEmitter: public signal::Emitter
+ {
+ protected:
+ bool emitToReceiver (signal::Receiver *receiver, int signalNo,
+ int argc, Object **argv);
+
+ public:
+ inline void connectDeletion (DeletionReceiver *receiver)
+ { connect (receiver); }
+
+ void emitDeletion (ObservedObject *obj);
+ };
+
+ DeletionEmitter deletionEmitter;
+
+public:
+ virtual ~ObservedObject();
+
+ inline void connectDeletion (DeletionReceiver *receiver)
+ { deletionEmitter.connectDeletion (receiver); }
+};
+
+} // namespace signal
+
+} // namespace lout
+
+#endif // __LOUT_SIGNALS_HH__
diff --git a/src/Makefile.am b/src/Makefile.am
index c037a699..f5699da6 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1,6 +1,6 @@
AM_CPPFLAGS=-DDILLORC_SYS='"$(sysconfdir)/dillo2rc"' @LIBJPEG_CPPFLAGS@
-AM_CFLAGS = -I../../dw-testbed @LIBPNG_CFLAGS@
-AM_CXXFLAGS = -I../../dw-testbed @LIBPNG_CFLAGS@ @LIBFLTK_CXXFLAGS@
+AM_CFLAGS = @LIBPNG_CFLAGS@
+AM_CXXFLAGS = -I.. @LIBPNG_CFLAGS@ @LIBFLTK_CXXFLAGS@
SUBDIRS = IO
@@ -10,10 +10,10 @@ dillo_fltk_LDADD = \
../dlib/libDlib.a \
../dpip/libDpip.a \
IO/libDiof.a \
- ../../dw-testbed/dw/libDw-widgets.a \
- ../../dw-testbed/dw/libDw-fltk.a \
- ../../dw-testbed/dw/libDw-core.a \
- ../../dw-testbed/lout/liblout.a \
+ ../dw/libDw-widgets.a \
+ ../dw/libDw-fltk.a \
+ ../dw/libDw-core.a \
+ ../lout/liblout.a \
@LIBJPEG_LIBS@ @LIBPNG_LIBS@ @LIBFLTK_LIBS@ @LIBZ_LIBS@ @LIBICONV_LIBS@
dillo_fltk_SOURCES = \
diff --git a/test/Makefile.am b/test/Makefile.am
new file mode 100644
index 00000000..bde2b0f8
--- /dev/null
+++ b/test/Makefile.am
@@ -0,0 +1,153 @@
+AM_CFLAGS = @LIBFLTK_CFLAGS@
+AM_CXXFLAGS = @LIBFLTK_CXXFLAGS@
+
+noinst_PROGRAMS = \
+ dw-anchors-test \
+ dw-example \
+ dw-find-test \
+ dw-links \
+ dw-links2 \
+ dw-images-simple \
+ dw-images-scaled \
+ dw-images-scaled2 \
+ dw-lists \
+ dw-table-aligned \
+ dw-table \
+ dw-border-test \
+ dw-imgbuf-mem-test \
+ dw-resource-test \
+ dw-ui-test \
+ fltk-browser \
+ shapes
+
+dw_anchors_test_SOURCES = dw_anchors_test.cc
+dw_anchors_test_LDADD = \
+ ../dw/libDw-widgets.a \
+ ../dw/libDw-fltk.a \
+ ../dw/libDw-core.a \
+ ../lout/liblout.a \
+ @LIBFLTK_LIBS@
+
+dw_example_SOURCES = dw_example.cc
+dw_example_LDADD = \
+ ../dw/libDw-widgets.a \
+ ../dw/libDw-fltk.a \
+ ../dw/libDw-core.a \
+ ../lout/liblout.a \
+ @LIBFLTK_LIBS@
+
+dw_find_test_SOURCES = dw_find_test.cc
+dw_find_test_LDADD = \
+ ../dw/libDw-widgets.a \
+ ../dw/libDw-fltk.a \
+ ../dw/libDw-core.a \
+ ../lout/liblout.a \
+ @LIBFLTK_LIBS@
+
+dw_links_SOURCES = dw_links.cc
+dw_links_LDADD = \
+ ../dw/libDw-widgets.a \
+ ../dw/libDw-fltk.a \
+ ../dw/libDw-core.a \
+ ../lout/liblout.a \
+ @LIBFLTK_LIBS@
+
+dw_links2_SOURCES = dw_links2.cc
+dw_links2_LDADD = \
+ ../dw/libDw-widgets.a \
+ ../dw/libDw-fltk.a \
+ ../dw/libDw-core.a \
+ ../lout/liblout.a \
+ @LIBFLTK_LIBS@
+
+dw_images_simple_SOURCES = dw_images_simple.cc
+dw_images_simple_LDADD = \
+ ../dw/libDw-widgets.a \
+ ../dw/libDw-fltk.a \
+ ../dw/libDw-core.a \
+ ../lout/liblout.a \
+ @LIBFLTK_LIBS@
+
+dw_images_scaled_SOURCES = dw_images_scaled.cc
+dw_images_scaled_LDADD = \
+ ../dw/libDw-widgets.a \
+ ../dw/libDw-fltk.a \
+ ../dw/libDw-core.a \
+ ../lout/liblout.a \
+ @LIBFLTK_LIBS@
+
+dw_images_scaled2_SOURCES = dw_images_scaled2.cc
+dw_images_scaled2_LDADD = \
+ ../dw/libDw-widgets.a \
+ ../dw/libDw-fltk.a \
+ ../dw/libDw-core.a \
+ ../lout/liblout.a \
+ @LIBFLTK_LIBS@
+
+dw_lists_SOURCES = dw_lists.cc
+dw_lists_LDADD = \
+ ../dw/libDw-widgets.a \
+ ../dw/libDw-fltk.a \
+ ../dw/libDw-core.a \
+ ../lout/liblout.a \
+ @LIBFLTK_LIBS@
+
+dw_table_aligned_SOURCES = dw_table_aligned.cc
+dw_table_aligned_LDADD = \
+ ../dw/libDw-widgets.a \
+ ../dw/libDw-fltk.a \
+ ../dw/libDw-core.a \
+ ../lout/liblout.a \
+ @LIBFLTK_LIBS@
+
+dw_table_SOURCES = dw_table.cc
+dw_table_LDADD = \
+ ../dw/libDw-widgets.a \
+ ../dw/libDw-fltk.a \
+ ../dw/libDw-core.a \
+ ../lout/liblout.a \
+ @LIBFLTK_LIBS@
+
+dw_border_test_SOURCES = dw_border_test.cc
+dw_border_test_LDADD = \
+ ../dw/libDw-widgets.a \
+ ../dw/libDw-fltk.a \
+ ../dw/libDw-core.a \
+ ../lout/liblout.a \
+ @LIBFLTK_LIBS@
+
+
+dw_imgbuf_mem_test_SOURCES = dw_imgbuf_mem_test.cc
+dw_imgbuf_mem_test_LDADD = \
+ ../dw/libDw-widgets.a \
+ ../dw/libDw-fltk.a \
+ ../dw/libDw-core.a \
+ ../lout/liblout.a \
+ @LIBFLTK_LIBS@
+
+dw_resource_test_SOURCES = dw_resource_test.cc
+dw_resource_test_LDADD = \
+ ../dw/libDw-widgets.a \
+ ../dw/libDw-fltk.a \
+ ../dw/libDw-core.a \
+ ../lout/liblout.a \
+ @LIBFLTK_LIBS@
+
+dw_ui_test_SOURCES = \
+ dw_ui_test.cc \
+ form.cc \
+ form.hh
+dw_ui_test_LDADD = \
+ ../dw/libDw-widgets.a \
+ ../dw/libDw-fltk.a \
+ ../dw/libDw-core.a \
+ ../lout/liblout.a \
+ @LIBFLTK_LIBS@
+
+fltk_browser_SOURCES = fltk_browser.cc
+fltk_browser_LDADD = @LIBFLTK_LIBS@
+
+shapes_SOURCES = shapes.cc
+shapes_LDADD = \
+ ../dw/libDw-core.a \
+ ../lout/liblout.a
diff --git a/test/dw_anchors_test.cc b/test/dw_anchors_test.cc
new file mode 100644
index 00000000..8615439e
--- /dev/null
+++ b/test/dw_anchors_test.cc
@@ -0,0 +1,162 @@
+/*
+ * Dillo Widget
+ *
+ * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+
+
+#include <ctype.h>
+#include <fltk/Window.h>
+#include <fltk/run.h>
+
+#include "../dw/core.hh"
+#include "../dw/fltkcore.hh"
+#include "../dw/fltkviewport.hh"
+#include "../dw/textblock.hh"
+
+using namespace lout::container::typed;
+using namespace dw;
+using namespace dw::core;
+using namespace dw::core::style;
+using namespace dw::fltk;
+
+static FltkPlatform *platform;
+static Layout *layout;
+static ::fltk::Window *window;
+static FltkViewport *viewport;
+static Style *topWidgetStyle, *widgetStyle, *wordStyle, *headingStyle;
+static Textblock *topTextblock = NULL;
+static int textblockNo = 0;
+
+static const char *numbers[10] = {
+ "one", "two", "three", "four", "five",
+ "six", "seven", "eight", "nine", "ten"
+};
+
+static void anchorCallback (::fltk::Widget *widget, void *data)
+{
+ layout->setAnchor (numbers[(long)data]);
+}
+
+static void textTimeout (void *data)
+{
+ Textblock *oldTop = topTextblock;
+ topTextblock = new Textblock (false);
+
+ if (oldTop) {
+ oldTop->addLinebreak (wordStyle);
+ oldTop->addWidget (topTextblock, widgetStyle);
+ } else {
+ topTextblock->setStyle (topWidgetStyle);
+ layout->setWidget (topTextblock);
+ }
+
+ topTextblock->addAnchor (numbers[textblockNo], headingStyle);
+
+ char buf[16];
+ strcpy (buf, numbers[textblockNo]);
+ buf[0] = toupper (buf[0]);
+ topTextblock->addText (buf, headingStyle);
+ topTextblock->addParbreak (5, headingStyle);
+
+ for (int i = 0; i < 30; i++) {
+ strcpy (buf, numbers[textblockNo]);
+ if (i == 0)
+ buf[0] = toupper (buf[0]);
+ strcat (buf, i == 29 ? "." : ",");
+
+ topTextblock->addText (buf, wordStyle);
+ topTextblock->addSpace (wordStyle);
+ }
+
+ topTextblock->flush ();
+
+ textblockNo++;
+ if (textblockNo < 10)
+ ::fltk::repeat_timeout (1, textTimeout, NULL);
+
+}
+
+int main(int argc, char **argv)
+{
+ char *buttonLabel[10];
+
+ platform = new FltkPlatform ();
+ layout = new Layout (platform);
+
+ window = new ::fltk::Window(250, 200, "Dw Find Test");
+ window->begin();
+
+ viewport = new FltkViewport (50, 0, 200, 200);
+ layout->attachView (viewport);
+
+ for (int i = 0; i < 10; i++) {
+ char buf[16];
+ strcpy (buf, numbers[i]);
+ buf[0] = toupper (buf[0]);
+ buttonLabel[i] = strdup(buf);
+ ::fltk::Button *button =
+ new ::fltk::Button(0, 20 * i, 50, 20, buttonLabel[i]);
+ button->callback (anchorCallback, (void*)i);
+ button->when (::fltk::WHEN_RELEASE);
+ }
+
+ FontAttrs fontAttrs;
+ fontAttrs.name = "Bitstream Charter";
+ fontAttrs.size = 14;
+ fontAttrs.weight = 400;
+ fontAttrs.style = FONT_STYLE_NORMAL;
+
+ StyleAttrs styleAttrs;
+ styleAttrs.initValues ();
+ styleAttrs.font = Font::create (layout, &fontAttrs);
+ styleAttrs.margin.setVal (5);
+ styleAttrs.color = Color::createSimple (layout, 0x000000);
+ styleAttrs.backgroundColor = Color::createSimple (layout, 0xffffff);
+ topWidgetStyle = Style::create (layout, &styleAttrs);
+
+ styleAttrs.margin.left = 20;
+ styleAttrs.margin.right = 0;
+ styleAttrs.backgroundColor = NULL;
+ widgetStyle = Style::create (layout, &styleAttrs);
+
+ styleAttrs.margin.left = 0;
+ wordStyle = Style::create (layout, &styleAttrs);
+
+ fontAttrs.size = 28;
+ fontAttrs.weight = 700;
+ styleAttrs.font = Font::create (layout, &fontAttrs);
+ headingStyle = Style::create (layout, &styleAttrs);
+
+ ::fltk::add_timeout (0, textTimeout, NULL);
+
+ window->resizable(viewport);
+ window->show();
+
+ int errorCode = ::fltk::run();
+
+ topWidgetStyle->unref ();
+ widgetStyle->unref ();
+ wordStyle->unref ();
+ headingStyle->unref ();
+ for (int i = 0; i < 10; i++)
+ delete buttonLabel[i];
+ delete layout;
+
+ return errorCode;
+}
diff --git a/test/dw_border_test.cc b/test/dw_border_test.cc
new file mode 100644
index 00000000..0c2b7783
--- /dev/null
+++ b/test/dw_border_test.cc
@@ -0,0 +1,125 @@
+/*
+ * Dillo Widget
+ *
+ * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+
+
+#include <fltk/Window.h>
+#include <fltk/run.h>
+
+#include "../dw/core.hh"
+#include "../dw/fltkcore.hh"
+#include "../dw/fltkviewport.hh"
+#include "../dw/textblock.hh"
+#include "../dw/listitem.hh"
+
+using namespace dw;
+using namespace dw::core;
+using namespace dw::core::style;
+using namespace dw::fltk;
+
+int main(int argc, char **argv)
+{
+ FltkPlatform *platform = new FltkPlatform ();
+ Layout *layout = new Layout (platform);
+
+ ::fltk::Window *window = new ::fltk::Window(200, 300, "Dw Border Test");
+ window->begin();
+
+ FltkViewport *viewport = new FltkViewport (0, 0, 200, 300);
+ layout->attachView (viewport);
+
+ StyleAttrs styleAttrs;
+ styleAttrs.initValues ();
+ styleAttrs.margin.setVal (5);
+ styleAttrs.borderWidth.setVal (2);
+ styleAttrs.setBorderColor (Color::createShaded (layout, 0xffffff));
+ styleAttrs.setBorderStyle (BORDER_INSET);
+ styleAttrs.padding.setVal (5);
+
+ FontAttrs fontAttrs;
+ fontAttrs.name = "Bitstream Charter";
+ fontAttrs.size = 14;
+ fontAttrs.weight = 400;
+ fontAttrs.style = FONT_STYLE_NORMAL;
+ styleAttrs.font = Font::create (layout, &fontAttrs);
+
+ styleAttrs.color = Color::createSimple (layout, 0x000000);
+ styleAttrs.backgroundColor = Color::createSimple (layout, 0xffffff);
+
+ Style *widgetStyle1 = Style::create (layout, &styleAttrs);
+
+ styleAttrs.backgroundColor = Color::createSimple (layout, 0xffff80);
+ styleAttrs.margin.setVal (0);
+ styleAttrs.borderWidth.setVal (1);
+ styleAttrs.setBorderColor (Color::createSimple (layout, 0x4040ff));
+ styleAttrs.setBorderStyle (BORDER_SOLID);
+ styleAttrs.padding.setVal (1);
+
+ Style *widgetStyle2 = Style::create (layout, &styleAttrs);
+
+ Textblock *textblock1 = new Textblock (false);
+ textblock1->setStyle (widgetStyle1);
+ layout->setWidget (textblock1);
+
+ widgetStyle1->unref();
+
+ styleAttrs.borderWidth.setVal (0);
+ styleAttrs.padding.setVal (0);
+ styleAttrs.backgroundColor = NULL;
+ styleAttrs.cursor = CURSOR_TEXT;
+
+ Style *wordStyle = Style::create (layout, &styleAttrs);
+
+ const char *words1[] = { "Some", "random", "text.", NULL };
+ const char *words2[] = { "A", "nested", "paragraph.", NULL };
+
+ for(int i = 0; words1[i]; i++) {
+ if(i != 0)
+ textblock1->addSpace (wordStyle);
+ textblock1->addText (words1[i], wordStyle);
+ }
+
+ for(int i = 0; i < 1; i++) {
+ textblock1->addParbreak(0, wordStyle);
+
+ Textblock *textblock2 = new Textblock (false);
+ textblock1->addWidget (textblock2, widgetStyle2);
+
+ for(int j = 0; words2[j]; j++) {
+ if(j != 0)
+ textblock2->addSpace (wordStyle);
+ textblock2->addText (words2[j], wordStyle);
+ }
+
+ textblock2->flush ();
+ }
+
+ textblock1->flush ();
+
+ window->resizable(viewport);
+ window->show();
+ int errorCode = ::fltk::run();
+
+ widgetStyle2->unref();
+ wordStyle->unref();
+ delete layout;
+
+ return errorCode;
+}
diff --git a/test/dw_example.cc b/test/dw_example.cc
new file mode 100644
index 00000000..75b891e2
--- /dev/null
+++ b/test/dw_example.cc
@@ -0,0 +1,103 @@
+/*
+ * Dillo Widget
+ *
+ * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+
+
+#include <fltk/Window.h>
+#include <fltk/run.h>
+
+#include "../dw/core.hh"
+#include "../dw/fltkcore.hh"
+#include "../dw/fltkviewport.hh"
+#include "../dw/textblock.hh"
+
+
+int main(int argc, char **argv)
+{
+ dw::fltk::FltkPlatform *platform = new dw::fltk::FltkPlatform ();
+ dw::core::Layout *layout = new dw::core::Layout (platform);
+
+ fltk::Window *window = new fltk::Window(200, 300, "Dw Example");
+ window->begin();
+
+ dw::fltk::FltkViewport *viewport =
+ new dw::fltk::FltkViewport (0, 0, 200, 300);
+ layout->attachView (viewport);
+
+ dw::core::style::StyleAttrs styleAttrs;
+ styleAttrs.initValues ();
+ styleAttrs.margin.setVal (5);
+
+ dw::core::style::FontAttrs fontAttrs;
+ fontAttrs.name = "Bitstream Charter";
+ fontAttrs.size = 14;
+ fontAttrs.weight = 400;
+ fontAttrs.style = dw::core::style::FONT_STYLE_NORMAL;
+ styleAttrs.font = dw::core::style::Font::create (layout, &fontAttrs);
+
+ styleAttrs.color =
+ dw::core::style::Color::createSimple (layout, 0x000000);
+ styleAttrs.backgroundColor =
+ dw::core::style::Color::createSimple (layout, 0xffffff);
+
+ dw::core::style::Style *widgetStyle =
+ dw::core::style::Style::create (layout, &styleAttrs);
+
+ dw::Textblock *textblock = new dw::Textblock (false);
+ textblock->setStyle (widgetStyle);
+ layout->setWidget (textblock);
+
+ widgetStyle->unref();
+
+ styleAttrs.margin.setVal (0);
+ styleAttrs.backgroundColor = NULL;
+
+ dw::core::style::Style *wordStyle =
+ dw::core::style::Style::create (layout, &styleAttrs);
+
+ for(int i = 1; i <= 10; i++) {
+ char buf[4];
+ sprintf(buf, "%d.", i);
+
+ const char *words[] = { "This", "is", "the", buf, "paragraph.",
+ "Here", "comes", "some", "more", "text",
+ "to", "demonstrate", "word", "wrapping.",
+ NULL };
+
+ for(int j = 0; words[j]; j++) {
+ textblock->addText(words[j], wordStyle);
+ textblock->addSpace(wordStyle);
+ }
+
+ textblock->addParbreak(10, wordStyle);
+ }
+
+ wordStyle->unref();
+
+ textblock->flush ();
+
+ window->resizable(viewport);
+ window->show();
+ int errorCode = fltk::run();
+
+ delete layout;
+
+ return errorCode;
+}
diff --git a/test/dw_find_test.cc b/test/dw_find_test.cc
new file mode 100644
index 00000000..a43b4e59
--- /dev/null
+++ b/test/dw_find_test.cc
@@ -0,0 +1,151 @@
+/*
+ * Dillo Widget
+ *
+ * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+
+
+#include <fltk/Window.h>
+#include <fltk/run.h>
+
+#include "../dw/core.hh"
+#include "../dw/fltkcore.hh"
+#include "../dw/fltkviewport.hh"
+#include "../dw/textblock.hh"
+
+using namespace lout::container::typed;
+using namespace dw;
+using namespace dw::core;
+using namespace dw::core::style;
+using namespace dw::fltk;
+
+static FltkPlatform *platform;
+static Layout *layout;
+static ::fltk::Window *window;
+static FltkViewport *viewport;
+static ::fltk::Button *findButton, *resetButton;
+static ::fltk::Widget *resultLabel;
+
+static void findCallback (::fltk::Widget *widget, void *data)
+{
+ //switch(layout->search ("worm", true)) {
+ switch(layout->search ("WORM", false)) {
+ case FindtextState::SUCCESS:
+ resultLabel->label("SUCCESS");
+ break;
+
+ case FindtextState::RESTART:
+ resultLabel->label("RESTART");
+ break;
+
+ case FindtextState::NOT_FOUND:
+ resultLabel->label("NOT_FOUND");
+ break;
+ }
+
+ resultLabel->redraw ();
+}
+
+static void resetCallback (::fltk::Widget *widget, void *data)
+{
+ layout->resetSearch ();
+ resultLabel->label("---");
+ resultLabel->redraw ();
+}
+
+int main(int argc, char **argv)
+{
+ platform = new FltkPlatform ();
+ layout = new Layout (platform);
+
+ window = new ::fltk::Window(200, 300, "Dw Find Test");
+ window->begin();
+
+ viewport = new FltkViewport (0, 0, 200, 280);
+ layout->attachView (viewport);
+
+ findButton = new ::fltk::Button(0, 280, 50, 20, "Find");
+ findButton->callback (findCallback, NULL);
+ findButton->when (::fltk::WHEN_RELEASE);
+
+ resetButton = new ::fltk::Button(50, 280, 50, 20, "Reset");
+ resetButton->callback (resetCallback, NULL);
+ resetButton->when (::fltk::WHEN_RELEASE);
+
+ resultLabel = new ::fltk::Widget(100, 280, 100, 20, "---");
+
+ FontAttrs fontAttrs;
+ fontAttrs.name = "Bitstream Charter";
+ fontAttrs.size = 14;
+ fontAttrs.weight = 400;
+ fontAttrs.style = FONT_STYLE_NORMAL;
+
+ StyleAttrs styleAttrs;
+ styleAttrs.initValues ();
+ styleAttrs.font = Font::create (layout, &fontAttrs);
+ styleAttrs.margin.setVal (10);
+ styleAttrs.color = Color::createSimple (layout, 0x000000);
+ styleAttrs.backgroundColor = Color::createSimple (layout, 0xffffff);
+ Style *topWidgetStyle = Style::create (layout, &styleAttrs);
+
+ styleAttrs.margin.setVal (0);
+ styleAttrs.margin.left = 30;
+ styleAttrs.backgroundColor = NULL;
+ Style *widgetStyle = Style::create (layout, &styleAttrs);
+
+ styleAttrs.margin.left = 0;
+ Style *wordStyle = Style::create (layout, &styleAttrs);
+
+ Textblock *textblock = new Textblock (false);
+ textblock->setStyle (topWidgetStyle);
+ layout->setWidget (textblock);
+
+ Stack <Textblock> *stack = new Stack <Textblock> (false);
+ stack->push (textblock);
+
+ for(int i = 0; i < 10; i++)
+ for(int j = 0; j < 10; j++) {
+ Textblock *current;
+ if(j < 5) {
+ current = new Textblock (false);
+ stack->getTop()->addWidget (current, widgetStyle);
+ stack->push (current);
+ } else {
+ stack->getTop()->flush ();
+ stack->pop ();
+ current = stack->getTop ();
+ }
+
+ current->addText ((i == j ? "worm" : "apple"), wordStyle);
+ current->addLinebreak (wordStyle);
+ }
+
+ stack->getTop()->flush ();
+
+ topWidgetStyle->unref ();
+ widgetStyle->unref ();
+ wordStyle->unref ();
+
+ window->resizable(viewport);
+ window->show();
+ int errorCode = ::fltk::run();
+
+ delete layout;
+
+ return errorCode;
+}
diff --git a/test/dw_images_scaled.cc b/test/dw_images_scaled.cc
new file mode 100644
index 00000000..dfaf57b9
--- /dev/null
+++ b/test/dw_images_scaled.cc
@@ -0,0 +1,153 @@
+/*
+ * Dillo Widget
+ *
+ * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+
+
+#include <fltk/Window.h>
+#include <fltk/run.h>
+
+#include "../dw/core.hh"
+#include "../dw/fltkcore.hh"
+#include "../dw/fltkviewport.hh"
+#include "../dw/textblock.hh"
+#include "../dw/image.hh"
+
+using namespace dw;
+using namespace dw::core;
+using namespace dw::core::style;
+using namespace dw::fltk;
+
+static Layout *layout;
+static Image *image;
+static core::Imgbuf *imgbuf = NULL;
+static int imgRow = 0;
+
+static void imageInitTimeout (void *data)
+{
+ //imgbuf = layout->createImgbuf (Imgbuf::RGBA, 400, 200);
+ imgbuf = layout->createImgbuf (Imgbuf::RGB, 400, 200);
+ image->setBuffer (imgbuf);
+}
+
+/*
+static void imageDrawTimeout (void *data)
+{
+ if (imgbuf) {
+ for (int i = 0; i < 1; i++) {
+ byte buf[4 * 400];
+ for(int x = 0; x < 400; x++) {
+ buf[4 * x + 0] = x * 255 / 399;
+ buf[4 * x + 1] = (399 - x) * 255 / 399;
+ buf[4 * x + 2] = imgRow * 255 / 199;
+ buf[4 * x + 3] = (199 - imgRow) * 255 / 199;
+ }
+
+ imgbuf->copyRow (imgRow, buf);
+ image->drawRow (imgRow);
+ imgRow++;
+ }
+ }
+
+ if(imgRow < 200)
+ ::fltk::repeat_timeout (0.5, imageDrawTimeout, NULL);
+}
+*/
+
+static void imageDrawTimeout (void *data)
+{
+ if (imgbuf) {
+ for (int i = 0; i < 1; i++) {
+ byte buf[3 * 400];
+ for(int x = 0; x < 400; x++) {
+ buf[3 * x + 0] = x * 255 / 399;
+ buf[3 * x + 1] = (399 - x) * 255 / 399;
+ buf[3 * x + 2] = imgRow * 255 / 199;
+ }
+
+ imgbuf->copyRow (imgRow, buf);
+ image->drawRow (imgRow);
+ imgRow++;
+ }
+ }
+
+ if(imgRow < 200)
+ ::fltk::repeat_timeout (0.5, imageDrawTimeout, NULL);
+}
+
+int main(int argc, char **argv)
+{
+ FltkPlatform *platform = new FltkPlatform ();
+ layout = new Layout (platform);
+
+ ::fltk::Window *window = new ::fltk::Window(410, 210, "Dw Scaled Image");
+ window->begin();
+
+ FltkViewport *viewport = new FltkViewport (0, 0, 410, 210);
+ layout->attachView (viewport);
+
+ StyleAttrs styleAttrs;
+ styleAttrs.initValues ();
+ styleAttrs.margin.setVal (5);
+ styleAttrs.width = createPerLength (1.0);
+ styleAttrs.height = createPerLength (1.0);
+
+ FontAttrs fontAttrs;
+ fontAttrs.name = "Bitstream Charter";
+ fontAttrs.size = 14;
+ fontAttrs.weight = 400;
+ fontAttrs.style = FONT_STYLE_NORMAL;
+ styleAttrs.font = Font::create (layout, &fontAttrs);
+
+ styleAttrs.color = Color::createSimple (layout, 0x000000);
+ styleAttrs.backgroundColor = Color::createSimple (layout, 0xffffff);
+
+ Style *widgetStyle = Style::create (layout, &styleAttrs);
+
+ Textblock *textblock = new Textblock (false);
+ textblock->setStyle (widgetStyle);
+ layout->setWidget (textblock);
+
+ widgetStyle->unref();
+
+ styleAttrs.margin.setVal (0);
+ styleAttrs.backgroundColor = NULL;
+
+ Style *imageStyle = Style::create (layout, &styleAttrs);
+
+ image = new dw::Image ("");
+ textblock->addWidget (image, imageStyle);
+ textblock->addSpace (imageStyle);
+
+ imageStyle->unref();
+
+ textblock->flush ();
+
+ window->resizable(viewport);
+ window->show();
+
+ ::fltk::add_timeout (2.0, imageInitTimeout, NULL);
+ ::fltk::add_timeout (0.1, imageDrawTimeout, NULL);
+
+ int errorCode = ::fltk::run();
+
+ delete layout;
+
+ return errorCode;
+}
diff --git a/test/dw_images_scaled2.cc b/test/dw_images_scaled2.cc
new file mode 100644
index 00000000..39c55046
--- /dev/null
+++ b/test/dw_images_scaled2.cc
@@ -0,0 +1,148 @@
+/*
+ * Dillo Widget
+ *
+ * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+
+
+#include <fltk/Window.h>
+#include <fltk/run.h>
+
+#include "../dw/core.hh"
+#include "../dw/fltkcore.hh"
+#include "../dw/fltkviewport.hh"
+#include "../dw/textblock.hh"
+#include "../dw/image.hh"
+
+using namespace dw;
+using namespace dw::core;
+using namespace dw::core::style;
+using namespace dw::fltk;
+
+static Layout *layout;
+static Image *image1, *image2;
+static core::Imgbuf *imgbuf = NULL;
+static int imgRow = 0;
+
+static void imageInitTimeout (void *data)
+{
+ imgbuf = layout->createImgbuf (Imgbuf::RGB, 400, 200);
+ image1->setBuffer (imgbuf);
+ image2->setBuffer (imgbuf);
+}
+
+static void imageDrawTimeout (void *data)
+{
+ if (imgbuf) {
+ for (int i = 0; i < 1; i++) {
+ byte buf[3 * 400];
+ for(int x = 0; x < 400; x++) {
+ buf[3 * x + 0] = x * 255 / 399;
+ buf[3 * x + 1] = (399 - x) * 255 / 399;
+ buf[3 * x + 2] = imgRow * 255 / 199;
+ }
+
+ imgbuf->copyRow (imgRow, buf);
+ image1->drawRow (imgRow);
+ image2->drawRow (imgRow);
+ imgRow++;
+ }
+ }
+
+ if(imgRow < 200)
+ ::fltk::repeat_timeout (0.5, imageDrawTimeout, NULL);
+}
+
+int main(int argc, char **argv)
+{
+ FltkPlatform *platform = new FltkPlatform ();
+ layout = new Layout (platform);
+
+ ::fltk::Window *window = new ::fltk::Window(410, 210, "Dw Scaled Image 2");
+ window->begin();
+
+ FltkViewport *viewport = new FltkViewport (0, 0, 410, 210);
+ layout->attachView (viewport);
+
+ StyleAttrs styleAttrs;
+ styleAttrs.initValues ();
+ styleAttrs.margin.setVal (5);
+
+ FontAttrs fontAttrs;
+ fontAttrs.name = "Bitstream Charter";
+ fontAttrs.size = 14;
+ fontAttrs.weight = 400;
+ fontAttrs.style = FONT_STYLE_NORMAL;
+ styleAttrs.font = Font::create (layout, &fontAttrs);
+
+ styleAttrs.color = Color::createSimple (layout, 0x000000);
+ styleAttrs.backgroundColor = Color::createSimple (layout, 0xffffff);
+
+ Style *widgetStyle = Style::create (layout, &styleAttrs);
+
+ Textblock *textblock = new Textblock (false);
+ textblock->setStyle (widgetStyle);
+ layout->setWidget (textblock);
+
+ widgetStyle->unref();
+
+ styleAttrs.margin.setVal (0);
+ styleAttrs.borderWidth.setVal (0);
+ styleAttrs.padding.setVal (0);
+ styleAttrs.backgroundColor = NULL;
+
+ Style *wordStyle = Style::create (layout, &styleAttrs);
+
+ styleAttrs.borderWidth.setVal (1);
+ styleAttrs.setBorderColor (Color::createShaded (layout, 0x000080));
+ styleAttrs.setBorderStyle (BORDER_SOLID);
+ styleAttrs.padding.setVal (1);
+ styleAttrs.backgroundColor = NULL;
+ styleAttrs.width = createPerLength (0.5);
+ styleAttrs.height = createPerLength (0.5);
+
+ Style *imageStyle1 = Style::create (layout, &styleAttrs);
+ image1 = new dw::Image ("A longer ALT Text to demonstrate clipping.");
+ textblock->addWidget (image1, imageStyle1);
+ imageStyle1->unref();
+
+ textblock->addParbreak (10, wordStyle);
+
+ styleAttrs.width = LENGTH_AUTO;
+ styleAttrs.height = LENGTH_AUTO;
+
+ Style *imageStyle2 = Style::create (layout, &styleAttrs);
+ image2 = new dw::Image ("A longer ALT Text to demonstrate clipping.");
+ textblock->addWidget (image2, imageStyle2);
+ imageStyle2->unref();
+
+ wordStyle->unref ();
+ textblock->flush ();
+
+ window->resizable(viewport);
+ window->show();
+
+ ::fltk::add_timeout (3.0, imageInitTimeout, NULL);
+ ::fltk::add_timeout (0.1, imageDrawTimeout, NULL);
+
+ int errorCode = ::fltk::run();
+
+ delete layout;
+
+ return errorCode;
+}
diff --git a/test/dw_images_simple.cc b/test/dw_images_simple.cc
new file mode 100644
index 00000000..78629d55
--- /dev/null
+++ b/test/dw_images_simple.cc
@@ -0,0 +1,151 @@
+/*
+ * Dillo Widget
+ *
+ * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+
+
+#include <fltk/Window.h>
+#include <fltk/run.h>
+
+#include "../dw/core.hh"
+#include "../dw/fltkcore.hh"
+#include "../dw/fltkviewport.hh"
+#include "../dw/textblock.hh"
+#include "../dw/image.hh"
+
+using namespace dw;
+using namespace dw::core;
+using namespace dw::core::style;
+using namespace dw::fltk;
+
+static Layout *layout;
+static Image *image;
+static core::Imgbuf *imgbuf = NULL;
+static int imgRow = 0;
+
+static void imageInitTimeout (void *data)
+{
+ //imgbuf = layout->createImgbuf (Imgbuf::RGBA, 400, 200);
+ imgbuf = layout->createImgbuf (Imgbuf::RGB, 400, 200);
+ image->setBuffer (imgbuf);
+}
+
+/*
+static void imageDrawTimeout (void *data)
+{
+ if (imgbuf) {
+ for (int i = 0; i < 1; i++) {
+ byte buf[4 * 400];
+ for(int x = 0; x < 400; x++) {
+ buf[4 * x + 0] = x * 255 / 399;
+ buf[4 * x + 1] = (399 - x) * 255 / 399;
+ buf[4 * x + 2] = imgRow * 255 / 199;
+ buf[4 * x + 3] = (199 - imgRow) * 255 / 199;
+ }
+
+ imgbuf->copyRow (imgRow, buf);
+ image->drawRow (imgRow);
+ imgRow++;
+ }
+ }
+
+ if(imgRow < 200)
+ ::fltk::repeat_timeout (0.5, imageDrawTimeout, NULL);
+}
+*/
+
+static void imageDrawTimeout (void *data)
+{
+ if (imgbuf) {
+ for (int i = 0; i < 1; i++) {
+ byte buf[3 * 400];
+ for(int x = 0; x < 400; x++) {
+ buf[3 * x + 0] = x * 255 / 399;
+ buf[3 * x + 1] = (399 - x) * 255 / 399;
+ buf[3 * x + 2] = imgRow * 255 / 199;
+ }
+
+ imgbuf->copyRow (imgRow, buf);
+ image->drawRow (imgRow);
+ imgRow++;
+ }
+ }
+
+ if(imgRow < 200)
+ ::fltk::repeat_timeout (0.5, imageDrawTimeout, NULL);
+}
+
+int main(int argc, char **argv)
+{
+ FltkPlatform *platform = new FltkPlatform ();
+ layout = new Layout (platform);
+
+ ::fltk::Window *window = new ::fltk::Window(410, 210, "Dw Simple Image");
+ window->begin();
+
+ FltkViewport *viewport = new FltkViewport (0, 0, 410, 210);
+ layout->attachView (viewport);
+
+ StyleAttrs styleAttrs;
+ styleAttrs.initValues ();
+ styleAttrs.margin.setVal (5);
+
+ FontAttrs fontAttrs;
+ fontAttrs.name = "Bitstream Charter";
+ fontAttrs.size = 14;
+ fontAttrs.weight = 400;
+ fontAttrs.style = FONT_STYLE_NORMAL;
+ styleAttrs.font = Font::create (layout, &fontAttrs);
+
+ styleAttrs.color = Color::createSimple (layout, 0x000000);
+ styleAttrs.backgroundColor = Color::createSimple (layout, 0xffffff);
+
+ Style *widgetStyle = Style::create (layout, &styleAttrs);
+
+ Textblock *textblock = new Textblock (false);
+ textblock->setStyle (widgetStyle);
+ layout->setWidget (textblock);
+
+ widgetStyle->unref();
+
+ styleAttrs.margin.setVal (0);
+ styleAttrs.backgroundColor = NULL;
+
+ Style *imageStyle = Style::create (layout, &styleAttrs);
+
+ image = new dw::Image ("");
+ textblock->addWidget (image, imageStyle);
+ textblock->addSpace (imageStyle);
+
+ imageStyle->unref();
+
+ textblock->flush ();
+
+ window->resizable(viewport);
+ window->show();
+
+ ::fltk::add_timeout (2.0, imageInitTimeout, NULL);
+ ::fltk::add_timeout (0.1, imageDrawTimeout, NULL);
+
+ int errorCode = ::fltk::run();
+
+ delete layout;
+
+ return errorCode;
+}
diff --git a/test/dw_imgbuf_mem_test.cc b/test/dw_imgbuf_mem_test.cc
new file mode 100644
index 00000000..897e47f8
--- /dev/null
+++ b/test/dw_imgbuf_mem_test.cc
@@ -0,0 +1,114 @@
+/*
+ * Dillo Widget
+ *
+ * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+
+
+#include <fltk/Window.h>
+#include <fltk/run.h>
+
+#include "../dw/core.hh"
+#include "../dw/fltkcore.hh"
+
+using namespace lout::signal;
+using namespace dw::core;
+using namespace dw::fltk;
+
+void solution1 ()
+{
+ FltkPlatform *platform = new FltkPlatform ();
+ Layout *layout = new Layout (platform);
+
+ Imgbuf *rootbuf = layout->createImgbuf (Imgbuf::RGB, 100, 100);
+ rootbuf->ref (); // Extra reference by the dicache.
+ printf ("=== Can be deleted? %s.\n",
+ rootbuf->lastReference () ? "Yes" : "No");
+ Imgbuf *scaledbuf = rootbuf->getScaledBuf (50, 50);
+ printf ("=== Can be deleted? %s.\n",
+ rootbuf->lastReference () ? "Yes" : "No");
+ rootbuf->unref ();
+ printf ("=== Can be deleted? %s.\n",
+ rootbuf->lastReference () ? "Yes" : "No");
+ scaledbuf->unref ();
+ printf ("=== Can be deleted? %s.\n",
+ rootbuf->lastReference () ? "Yes" : "No");
+ rootbuf->unref (); // Extra reference by the dicache.
+
+ delete layout;
+}
+
+void solution2 ()
+{
+ FltkPlatform *platform = new FltkPlatform ();
+ Layout *layout = new Layout (platform);
+
+ Imgbuf *rootbuf = layout->createImgbuf (Imgbuf::RGB, 100, 100);
+ rootbuf->setDeleteOnUnref (false);
+ printf ("=== Can be deleted? %s.\n",
+ !rootbuf->isReferred () ? "Yes" : "No");
+ Imgbuf *scaledbuf = rootbuf->getScaledBuf (50, 50);
+ printf ("=== Can be deleted? %s.\n",
+ !rootbuf->isReferred () ? "Yes" : "No");
+ rootbuf->unref ();
+ printf ("=== Can be deleted? %s.\n",
+ !rootbuf->isReferred () ? "Yes" : "No");
+ scaledbuf->unref ();
+ printf ("=== Can be deleted? %s.\n",
+ !rootbuf->isReferred () ? "Yes" : "No");
+ delete rootbuf;
+
+ delete layout;
+}
+
+class RootbufDeletionReceiver: public ObservedObject::DeletionReceiver
+{
+ void deleted (ObservedObject *object);
+};
+
+void RootbufDeletionReceiver::deleted (ObservedObject *object)
+{
+ printf ("=== Is deleted now.\n");
+ delete this;
+}
+
+void solution3 ()
+{
+ FltkPlatform *platform = new FltkPlatform ();
+ Layout *layout = new Layout (platform);
+
+ Imgbuf *rootbuf = layout->createImgbuf (Imgbuf::RGB, 100, 100);
+ rootbuf->connectDeletion (new RootbufDeletionReceiver ());
+ Imgbuf *scaledbuf = rootbuf->getScaledBuf (50, 50);
+ rootbuf->unref ();
+ scaledbuf->unref ();
+
+ delete layout;
+}
+
+int main (int argc, char **argv)
+{
+ printf ("========== SOLUTION 1 ==========\n");
+ solution1 ();
+ printf ("========== SOLUTION 2 ==========\n");
+ solution2 ();
+ printf ("========== SOLUTION 3 ==========\n");
+ solution3 ();
+
+ return 0;
+}
diff --git a/test/dw_links.cc b/test/dw_links.cc
new file mode 100644
index 00000000..4d15d520
--- /dev/null
+++ b/test/dw_links.cc
@@ -0,0 +1,158 @@
+/*
+ * Dillo Widget
+ *
+ * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+
+
+#include <fltk/Window.h>
+#include <fltk/run.h>
+
+#include "../dw/core.hh"
+#include "../dw/fltkcore.hh"
+#include "../dw/fltkviewport.hh"
+#include "../dw/textblock.hh"
+
+using namespace dw;
+using namespace dw::core;
+using namespace dw::core::style;
+using namespace dw::fltk;
+
+class LinkTestReceiver: public Widget::LinkReceiver
+{
+ bool enter (Widget *widget, int link, int x, int y);
+ bool press (Widget *widget, int link, int x, int y, EventButton *event);
+ bool release (Widget *widget, int link, int x, int y, EventButton *event);
+ bool click (Widget *widget, int link, int x, int y, EventButton *event);
+};
+
+bool LinkTestReceiver::enter (Widget *widget, int link, int x, int y)
+{
+ printf ("enter: %d\n", link);
+ return true;
+}
+
+bool LinkTestReceiver::press (Widget *widget, int link, int x, int y,
+ EventButton *event)
+{
+ printf ("press: %d\n", link);
+ return true;
+}
+
+bool LinkTestReceiver::release (Widget *widget, int link, int x, int y,
+ EventButton *event)
+{
+ printf ("release: %d\n", link);
+ return true;
+}
+
+bool LinkTestReceiver::click (Widget *widget, int link, int x, int y,
+ EventButton *event)
+{
+ printf ("click: %d\n", link);
+ return true;
+}
+
+int main(int argc, char **argv)
+{
+ LinkTestReceiver linkTestReceiver;
+ FltkPlatform *platform = new FltkPlatform ();
+ Layout *layout = new Layout (platform);
+
+ ::fltk::Window *window = new ::fltk::Window(200, 300, "Dw Links");
+ window->begin();
+
+ FltkViewport *viewport = new FltkViewport (0, 0, 200, 300);
+ layout->attachView (viewport);
+
+ StyleAttrs styleAttrs;
+ styleAttrs.initValues ();
+ styleAttrs.margin.setVal (5);
+
+ FontAttrs fontAttrs;
+ fontAttrs.name = "Bitstream Charter";
+ fontAttrs.size = 14;
+ fontAttrs.weight = 400;
+ fontAttrs.style = FONT_STYLE_NORMAL;
+ styleAttrs.font = Font::create (layout, &fontAttrs);
+
+ styleAttrs.color = Color::createSimple (layout, 0x000000);
+ styleAttrs.backgroundColor = Color::createSimple (layout, 0xffffff);
+
+ Style *widgetStyle = Style::create (layout, &styleAttrs);
+
+ Textblock *textblock = new Textblock (false);
+ textblock->setStyle (widgetStyle);
+ layout->setWidget (textblock);
+
+ textblock->connectLink (&linkTestReceiver);
+
+ widgetStyle->unref();
+
+ styleAttrs.margin.setVal (0);
+ styleAttrs.backgroundColor = NULL;
+ styleAttrs.cursor = CURSOR_TEXT;
+
+ Style *wordStyle = Style::create (layout, &styleAttrs);
+
+ styleAttrs.color = Color::createSimple (layout, 0x0000ff);
+ styleAttrs.textDecoration = TEXT_DECORATION_UNDERLINE;
+ styleAttrs.cursor = CURSOR_POINTER;
+
+ for(int i = 1; i <= 10; i++) {
+ char buf[4];
+ sprintf(buf, "%d.", i);
+
+ const char *words1[] = {
+ "This", "is", "the", buf, "paragraph.",
+ "Here", "comes", "some", "more", "text",
+ "to", "demonstrate", "word", "wrapping.",
+ NULL };
+ const char *words2[] = {
+ "Click", "here", "for", "more..", NULL };
+
+ for(int j = 0; words1[j]; j++) {
+ textblock->addText(words1[j], wordStyle);
+ textblock->addSpace(wordStyle);
+ }
+
+ styleAttrs.x_link = i;
+ Style *linkStyle = Style::create (layout, &styleAttrs);
+
+ for(int j = 0; words2[j]; j++) {
+ textblock->addText(words2[j], linkStyle);
+ textblock->addSpace(wordStyle);
+ }
+
+ linkStyle->unref ();
+
+ textblock->addParbreak(10, wordStyle);
+ }
+
+ wordStyle->unref();
+
+ textblock->flush ();
+
+ window->resizable(viewport);
+ window->show();
+ int errorCode = ::fltk::run();
+
+ delete layout;
+
+ return errorCode;
+}
diff --git a/test/dw_links2.cc b/test/dw_links2.cc
new file mode 100644
index 00000000..e1db9011
--- /dev/null
+++ b/test/dw_links2.cc
@@ -0,0 +1,186 @@
+/*
+ * Dillo Widget
+ *
+ * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+
+
+#include <fltk/Window.h>
+#include <fltk/run.h>
+
+#include "../dw/core.hh"
+#include "../dw/fltkcore.hh"
+#include "../dw/fltkviewport.hh"
+#include "../dw/textblock.hh"
+
+using namespace dw;
+using namespace dw::core;
+using namespace dw::core::style;
+using namespace dw::fltk;
+
+class LinkTestReceiver: public Widget::LinkReceiver
+{
+ bool enter (Widget *widget, int link, int x, int y);
+ bool press (Widget *widget, int link, int x, int y, EventButton *event);
+ bool release (Widget *widget, int link, int x, int y, EventButton *event);
+ bool click (Widget *widget, int link, int x, int y, EventButton *event);
+};
+
+bool LinkTestReceiver::enter (Widget *widget, int link, int x, int y)
+{
+ printf ("enter: %d\n", link);
+ return true;
+}
+
+bool LinkTestReceiver::press (Widget *widget, int link, int x, int y,
+ EventButton *event)
+{
+ printf ("press: %d\n", link);
+ return true;
+}
+
+bool LinkTestReceiver::release (Widget *widget, int link, int x, int y,
+ EventButton *event)
+{
+ printf ("release: %d\n", link);
+ return true;
+}
+
+bool LinkTestReceiver::click (Widget *widget, int link, int x, int y,
+ EventButton *event)
+{
+ printf ("click: %d\n", link);
+ return true;
+}
+
+int main(int argc, char **argv)
+{
+ int MainIdx;
+ int ww = 200, wh = 300, lh = 24;
+
+ FltkPlatform *platform = new FltkPlatform ();
+ Layout *layout = new Layout (platform);
+
+ ::fltk::Window *window = new ::fltk::Window(200, 300, "Dw Links");
+ window->begin();
+ ::fltk::Widget *Panel = new ::fltk::Widget(0, 0, ww, lh, "CONTROL PANEL");
+
+ Panel->color(::fltk::GRAY15);
+ Panel->labelcolor(::fltk::WHITE);
+ ::fltk::Widget *Main =
+ new ::fltk::Widget(0, lh, ww, wh - 2*lh, "MAIN RENDERING AREA");
+ Main->color(::fltk::GRAY20);
+ Main->labelcolor(::fltk::WHITE);
+ MainIdx = window->find(Main);
+ /* status bar */
+ ::fltk::Widget *Bar =
+ new ::fltk::Widget(0, wh - lh, 200, lh, "STATUS BAR...");
+ Bar->color(::fltk::GRAY15);
+ Bar->labelcolor(::fltk::WHITE);
+
+ window->resizable(Main);
+ window->end();
+
+ //
+ // Create the main Dw and add some text there.
+ //
+ window->remove(MainIdx);
+ window->begin();
+
+ FltkViewport *viewport = new FltkViewport (0, lh, ww, wh - 2*lh);
+ layout->attachView (viewport);
+
+ window->end();
+
+
+ StyleAttrs styleAttrs;
+ styleAttrs.initValues ();
+ styleAttrs.margin.setVal (5);
+
+ FontAttrs fontAttrs;
+ fontAttrs.name = "Bitstream Charter";
+ fontAttrs.size = 14;
+ fontAttrs.weight = 400;
+ fontAttrs.style = FONT_STYLE_NORMAL;
+ styleAttrs.font = Font::create (layout, &fontAttrs);
+
+ styleAttrs.color = Color::createSimple (layout, 0x000000);
+ styleAttrs.backgroundColor = Color::createSimple (layout, 0xffffff);
+
+ Style *widgetStyle = Style::create (layout, &styleAttrs);
+
+ Textblock *textblock = new Textblock (false);
+ textblock->setStyle (widgetStyle);
+ layout->setWidget (textblock);
+
+ textblock->connectLink (new LinkTestReceiver ());
+
+ widgetStyle->unref();
+
+ styleAttrs.margin.setVal (0);
+ styleAttrs.backgroundColor = NULL;
+ styleAttrs.cursor = CURSOR_TEXT;
+
+ Style *wordStyle = Style::create (layout, &styleAttrs);
+
+ styleAttrs.color = Color::createSimple (layout, 0x0000ff);
+ styleAttrs.textDecoration = TEXT_DECORATION_UNDERLINE;
+ styleAttrs.cursor = CURSOR_POINTER;
+
+ for(int i = 1; i <= 30; i++) {
+ char buf[4];
+ sprintf(buf, "%d.", i);
+
+ const char *words1[] = {
+ "This", "is", "the", buf, "paragraph.",
+ "Here", "comes", "some", "more", "text",
+ "to", "demonstrate", "word", "wrapping.",
+ NULL };
+ const char *words2[] = {
+ "Click", "here", "for", "more..", NULL };
+
+ for(int j = 0; words1[j]; j++) {
+ textblock->addText (words1[j], wordStyle);
+ textblock->addSpace(wordStyle);
+ }
+
+ styleAttrs.x_link = i;
+ Style *linkStyle = Style::create (layout, &styleAttrs);
+
+ for(int j = 0; words2[j]; j++) {
+ textblock->addText (words2[j], linkStyle);
+ textblock->addSpace(wordStyle);
+ }
+
+ linkStyle->unref ();
+
+ textblock->addParbreak(10, wordStyle);
+ }
+
+ wordStyle->unref();
+
+ textblock->flush ();
+
+ window->resizable(viewport);
+ window->show();
+ int errorCode = ::fltk::run();
+
+ delete layout;
+
+ return errorCode;
+}
diff --git a/test/dw_lists.cc b/test/dw_lists.cc
new file mode 100644
index 00000000..99e78149
--- /dev/null
+++ b/test/dw_lists.cc
@@ -0,0 +1,134 @@
+/*
+ * Dillo Widget
+ *
+ * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+
+
+#include <fltk/Window.h>
+#include <fltk/run.h>
+
+#include "../dw/core.hh"
+#include "../dw/fltkcore.hh"
+#include "../dw/fltkviewport.hh"
+#include "../dw/textblock.hh"
+#include "../dw/listitem.hh"
+
+using namespace dw;
+using namespace dw::core;
+using namespace dw::core::style;
+using namespace dw::fltk;
+
+int main(int argc, char **argv)
+{
+ FltkPlatform *platform = new FltkPlatform ();
+ Layout *layout = new Layout (platform);
+
+ ::fltk::Window *window = new ::fltk::Window(200, 300, "Dw Lists");
+ window->begin();
+
+ FltkViewport *viewport = new FltkViewport (0, 0, 200, 300);
+ layout->attachView (viewport);
+
+ StyleAttrs styleAttrs;
+ styleAttrs.initValues ();
+ styleAttrs.margin.setVal (5);
+
+ FontAttrs fontAttrs;
+ fontAttrs.name = "Bitstream Charter";
+ fontAttrs.size = 14;
+ fontAttrs.weight = 400;
+ fontAttrs.style = FONT_STYLE_NORMAL;
+ styleAttrs.font = Font::create (layout, &fontAttrs);
+
+ styleAttrs.color = Color::createSimple (layout, 0x000000);
+ styleAttrs.backgroundColor = Color::createSimple (layout, 0xffffff);
+
+ Style *widgetStyle = Style::create (layout, &styleAttrs);
+
+ Textblock *textblock = new Textblock (false);
+ textblock->setStyle (widgetStyle);
+ layout->setWidget (textblock);
+
+ widgetStyle->unref();
+
+ styleAttrs.margin.setVal (0);
+ styleAttrs.backgroundColor = NULL;
+ styleAttrs.cursor = CURSOR_TEXT;
+
+ Style *wordStyle = Style::create (layout, &styleAttrs);
+
+ styleAttrs.margin.setVal (5);
+ styleAttrs.padding.setVal (5);
+ styleAttrs.backgroundColor = Color::createSimple (layout, 0xffff40);
+ styleAttrs.setBorderColor (Color::createSimple (layout, 0x000000));
+ styleAttrs.setBorderStyle (BORDER_SOLID);
+ styleAttrs.borderWidth.setVal (1);
+
+ Style *itemStyle = Style::create (layout, &styleAttrs);
+
+ const char *wordsPar[] = {
+ "This", "is", "a", "normal", "paragraph.", "And",
+ "some", "list", "items", "follow:", NULL };
+ const char *wordsItem[] = {
+ "This", "is", "a", "list", "item.", "Here",
+ "comes", "some", "more", "text", "to",
+ "demonstrate", "word", "wrapping.", NULL };
+
+
+ for(int i = 0; wordsPar[i]; i++) {
+ if(i != 0)
+ textblock->addSpace (wordStyle);
+ textblock->addText (wordsPar[i], wordStyle);
+ }
+ textblock->addParbreak (5, wordStyle);
+
+ ListItem *refItem = NULL;
+
+ for(int i = 1; i <= 100; i++) {
+ ListItem *listItem = new ListItem (refItem, false);
+ refItem = listItem;
+
+ textblock->addWidget (listItem, itemStyle);
+ textblock->addParbreak (2, wordStyle);
+
+ char buf[16];
+ sprintf (buf, "%d.", i);
+ listItem->initWithText (strdup (buf), wordStyle);
+
+ for(int j = 0; wordsItem[j]; j++) {
+ if(j != 0)
+ listItem->addSpace (wordStyle);
+ listItem->addText (wordsItem[j], wordStyle);
+ }
+
+ listItem->flush ();
+ }
+
+ wordStyle->unref();
+
+ textblock->flush ();
+
+ window->resizable(viewport);
+ window->show();
+ int errorCode = ::fltk::run();
+
+ delete layout;
+
+ return errorCode;
+}
diff --git a/test/dw_resource_test.cc b/test/dw_resource_test.cc
new file mode 100644
index 00000000..fc825836
--- /dev/null
+++ b/test/dw_resource_test.cc
@@ -0,0 +1,98 @@
+/*
+ * Dillo Widget
+ *
+ * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+
+
+#include <fltk/Window.h>
+#include <fltk/run.h>
+
+#include "../dw/core.hh"
+#include "../dw/fltkcore.hh"
+#include "../dw/fltkviewport.hh"
+#include "../dw/textblock.hh"
+#include "../dw/ui.hh"
+
+using namespace dw;
+using namespace dw::core;
+using namespace dw::core::style;
+using namespace dw::core::ui;
+using namespace dw::fltk;
+
+int main(int argc, char **argv)
+{
+ FltkPlatform *platform = new FltkPlatform ();
+ Layout *layout = new Layout (platform);
+
+ ::fltk::Window *window = new ::fltk::Window(410, 210, "Dw Simple Image");
+ window->begin();
+
+ FltkViewport *viewport = new FltkViewport (0, 0, 410, 210);
+ layout->attachView (viewport);
+
+ StyleAttrs styleAttrs;
+ styleAttrs.initValues ();
+ styleAttrs.margin.setVal (5);
+
+ FontAttrs fontAttrs;
+ fontAttrs.name = "Bitstream Charter";
+ fontAttrs.size = 14;
+ fontAttrs.weight = 400;
+ fontAttrs.style = FONT_STYLE_NORMAL;
+ styleAttrs.font = Font::create (layout, &fontAttrs);
+
+ styleAttrs.color = Color::createSimple (layout, 0x000000);
+ styleAttrs.backgroundColor = Color::createSimple (layout, 0xffffff);
+
+ Style *widgetStyle = Style::create (layout, &styleAttrs);
+
+ Textblock *textblock = new Textblock (false);
+ textblock->setStyle (widgetStyle);
+ layout->setWidget (textblock);
+
+ widgetStyle->unref();
+
+ styleAttrs.margin.setVal (0);
+ styleAttrs.backgroundColor = NULL;
+
+ SelectionResource *res = layout->getResourceFactory()->createListResource
+ (ListResource::SELECTION_AT_MOST_ONE);
+ //SelectionResource *res =
+ // layout->getResourceFactory()->createOptionMenuResource ();
+
+ Embed *embed = new Embed (res);
+ textblock->addWidget (embed, widgetStyle);
+ textblock->addSpace (widgetStyle);
+
+ widgetStyle->unref();
+
+ for(int i = 0; i < 50; i++)
+ res->addItem ("Hello, world!", true, false);
+
+ textblock->flush ();
+
+ window->resizable(viewport);
+ window->show();
+
+ int errorCode = ::fltk::run();
+
+ delete layout;
+
+ return errorCode;
+}
diff --git a/test/dw_table.cc b/test/dw_table.cc
new file mode 100644
index 00000000..d269d551
--- /dev/null
+++ b/test/dw_table.cc
@@ -0,0 +1,115 @@
+/*
+ * Dillo Widget
+ *
+ * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+
+
+#include <fltk/Window.h>
+#include <fltk/run.h>
+
+#include "../dw/core.hh"
+#include "../dw/fltkcore.hh"
+#include "../dw/fltkviewport.hh"
+#include "../dw/table.hh"
+#include "../dw/tablecell.hh"
+
+using namespace dw;
+using namespace dw::core;
+using namespace dw::core::style;
+using namespace dw::fltk;
+
+int main(int argc, char **argv)
+{
+ FltkPlatform *platform = new FltkPlatform ();
+ Layout *layout = new Layout (platform);
+
+ ::fltk::Window *window = new ::fltk::Window(300, 300, "Dw Table");
+ window->begin();
+
+ FltkViewport *viewport = new FltkViewport (0, 0, 300, 300);
+ layout->attachView (viewport);
+
+ StyleAttrs styleAttrs;
+ styleAttrs.initValues ();
+ styleAttrs.margin.setVal (5);
+ styleAttrs.padding.setVal (0);
+ styleAttrs.borderWidth.setVal (1);
+ styleAttrs.setBorderStyle (BORDER_OUTSET);
+ styleAttrs.setBorderColor (Color::createShaded (layout, 0xffffff));
+ styleAttrs.color = Color::createSimple (layout, 0x000000);
+ styleAttrs.backgroundColor = Color::createSimple (layout, 0xffffff);
+ styleAttrs.hBorderSpacing = 5;
+ styleAttrs.vBorderSpacing = 5;
+
+ FontAttrs fontAttrs;
+ fontAttrs.name = "Bitstream Charter";
+ fontAttrs.size = 14;
+ fontAttrs.weight = 400;
+ fontAttrs.style = FONT_STYLE_NORMAL;
+ styleAttrs.font = Font::create (layout, &fontAttrs);
+
+ Style *tableStyle = Style::create (layout, &styleAttrs);
+
+ Table *table = new Table (false);
+ table->setStyle (tableStyle);
+ layout->setWidget (table);
+
+ tableStyle->unref();
+
+ styleAttrs.setBorderStyle (BORDER_INSET);
+ styleAttrs.backgroundColor = NULL;
+ styleAttrs.margin.setVal (0);
+ styleAttrs.padding.setVal (5);
+
+ Style *cellStyle = Style::create (layout, &styleAttrs);
+
+ styleAttrs.borderWidth.setVal (0);
+ styleAttrs.margin.setVal (0);
+ styleAttrs.cursor = CURSOR_TEXT;
+ styleAttrs.textAlignChar = '.';
+
+ Style *wordStyle = Style::create (layout, &styleAttrs);
+
+ for (int i = 0; i < 4; i++) {
+ table->addRow (wordStyle);
+
+ for (int j = 0; j < 4; j++) {
+ Textblock *cell = new Textblock (false);
+ cell->setStyle (cellStyle);
+ table->addCell (cell, 1, 1);
+
+ char buf[10];
+ sprintf (buf, "cell %c", 'A' + 4 * i + j);
+
+ cell->addText (buf, wordStyle);
+ cell->flush ();
+ }
+ }
+
+ wordStyle->unref();
+ cellStyle->unref();
+
+ window->resizable(viewport);
+ window->show();
+ int errorCode = ::fltk::run();
+
+ delete layout;
+
+ return errorCode;
+}
diff --git a/test/dw_table_aligned.cc b/test/dw_table_aligned.cc
new file mode 100644
index 00000000..beb8525d
--- /dev/null
+++ b/test/dw_table_aligned.cc
@@ -0,0 +1,119 @@
+/*
+ * Dillo Widget
+ *
+ * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+
+
+#include <fltk/Window.h>
+#include <fltk/run.h>
+
+#include "../dw/core.hh"
+#include "../dw/fltkcore.hh"
+#include "../dw/fltkviewport.hh"
+#include "../dw/table.hh"
+#include "../dw/tablecell.hh"
+
+using namespace dw;
+using namespace dw::core;
+using namespace dw::core::style;
+using namespace dw::fltk;
+
+int main(int argc, char **argv)
+{
+ FltkPlatform *platform = new FltkPlatform ();
+ Layout *layout = new Layout (platform);
+
+ ::fltk::Window *window = new ::fltk::Window(200, 300, "Dw Table Aligned");
+ window->begin();
+
+ FltkViewport *viewport = new FltkViewport (0, 0, 200, 300);
+ layout->attachView (viewport);
+
+ StyleAttrs styleAttrs;
+ styleAttrs.initValues ();
+ styleAttrs.margin.setVal (5);
+ styleAttrs.borderWidth.setVal (1);
+ styleAttrs.setBorderStyle (BORDER_OUTSET);
+ styleAttrs.setBorderColor (Color::createShaded (layout, 0x808080));
+
+ FontAttrs fontAttrs;
+ fontAttrs.name = "Bitstream Charter";
+ fontAttrs.size = 14;
+ fontAttrs.weight = 400;
+ fontAttrs.style = FONT_STYLE_NORMAL;
+ styleAttrs.font = Font::create (layout, &fontAttrs);
+
+ styleAttrs.color = Color::createSimple (layout, 0x000000);
+ styleAttrs.backgroundColor = Color::createSimple (layout, 0xa0a0a0);
+ styleAttrs.hBorderSpacing = 5;
+ styleAttrs.vBorderSpacing = 5;
+
+ Style *tableStyle = Style::create (layout, &styleAttrs);
+
+ Table *table = new Table (false);
+ table->setStyle (tableStyle);
+ layout->setWidget (table);
+
+ tableStyle->unref();
+
+ styleAttrs.borderWidth.setVal (1);
+ styleAttrs.setBorderStyle (BORDER_INSET);
+
+ Style *cellStyle = Style::create (layout, &styleAttrs);
+
+ styleAttrs.borderWidth.setVal (0);
+ styleAttrs.margin.setVal (0);
+ styleAttrs.backgroundColor = NULL;
+ styleAttrs.cursor = CURSOR_TEXT;
+ styleAttrs.textAlignChar = '.';
+
+ Style *wordStyle = Style::create (layout, &styleAttrs);
+
+ TableCell *ref = NULL;
+ for(int i = 0; i < 10; i++) {
+ //for(int i = 0; i < 1; i++) {
+ TableCell *cell = new TableCell (ref, false);
+ cell->setStyle (cellStyle);
+ ref = cell;
+ table->addRow (wordStyle);
+ table->addCell (cell, 1, 1);
+
+ char buf[16];
+ for(int j = 0; j < i; j++)
+ buf[j] = '0' + j;
+ buf[i] = '.';
+ for(int j = i + 1; j < 11; j++)
+ buf[j] = '0' + (j - 1);
+ buf[11] = 0;
+
+ cell->addText (buf, wordStyle);
+ cell->flush ();
+ }
+
+ wordStyle->unref();
+ cellStyle->unref();
+
+ window->resizable(viewport);
+ window->show();
+ int errorCode = ::fltk::run();
+
+ delete layout;
+
+ return errorCode;
+}
diff --git a/test/dw_ui_test.cc b/test/dw_ui_test.cc
new file mode 100644
index 00000000..5ce949ef
--- /dev/null
+++ b/test/dw_ui_test.cc
@@ -0,0 +1,241 @@
+/*
+ * Dillo Widget
+ *
+ * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+
+
+#include <fltk/Window.h>
+#include <fltk/run.h>
+
+#include "../dw/core.hh"
+#include "../dw/fltkcore.hh"
+#include "../dw/fltkviewport.hh"
+#include "../dw/table.hh"
+#include "../dw/textblock.hh"
+#include "../dw/ui.hh"
+#include "form.hh"
+
+using namespace lout::object;
+using namespace lout::container::typed;
+using namespace dw;
+using namespace dw::core;
+using namespace dw::core::style;
+using namespace dw::core::ui;
+using namespace dw::fltk;
+
+int main(int argc, char **argv)
+{
+ FltkPlatform *platform = new FltkPlatform ();
+ Layout *layout = new Layout (platform);
+
+ ::fltk::Window *window = new ::fltk::Window(400, 400, "Dw UI Test");
+ window->begin();
+
+ FltkViewport *viewport = new FltkViewport (0, 0, 400, 400);
+ layout->attachView (viewport);
+
+ StyleAttrs styleAttrs;
+ styleAttrs.initValues ();
+ styleAttrs.margin.setVal (5);
+ styleAttrs.color = Color::createSimple (layout, 0x000000);
+ styleAttrs.backgroundColor = Color::createSimple (layout, 0xffffff);
+
+ FontAttrs fontAttrs;
+ fontAttrs.name = "Helvetica";
+ fontAttrs.size = 14;
+ fontAttrs.weight = 400;
+ fontAttrs.style = FONT_STYLE_NORMAL;
+ styleAttrs.font = Font::create (layout, &fontAttrs);
+
+ Style *tableStyle = Style::create (layout, &styleAttrs);
+
+ Table *table = new Table (false);
+ table->setStyle (tableStyle);
+ layout->setWidget (table);
+
+ tableStyle->unref();
+
+ styleAttrs.backgroundColor = NULL;
+ styleAttrs.margin.setVal (0);
+
+ Style *cellStyle = Style::create (layout, &styleAttrs);
+
+ // First of all, the resources. Later, they are embedded into the
+ // widget tree.
+ EntryResource *entryres1 =
+ layout->getResourceFactory()->createEntryResource (10, false);
+ entryres1->setText ("Hi!");
+ EntryResource *entryres2 =
+ layout->getResourceFactory()->createEntryResource (10, true);
+ MultiLineTextResource *textres =
+ layout->getResourceFactory()->createMultiLineTextResource (15,3);
+ RadioButtonResource *radiores1 =
+ layout->getResourceFactory()->createRadioButtonResource (NULL, false);
+ RadioButtonResource *radiores2 =
+ layout->getResourceFactory()->createRadioButtonResource (radiores1,
+ false);
+ CheckButtonResource *checkres =
+ layout->getResourceFactory()->createCheckButtonResource (true);
+ SelectionResource *selres[2];
+ selres[0] = layout->getResourceFactory()->createOptionMenuResource ();
+ selres[1] = layout->getResourceFactory()->createListResource
+ (ListResource::SELECTION_AT_MOST_ONE);
+ LabelButtonResource *buttonres =
+ layout->getResourceFactory()->createLabelButtonResource ("Run!");
+
+ // Note on complex buttons: before any operations on the widget, which
+ // need a layout, the complex button resource should be created, since
+ // then, a layout and a platform are instanciated.
+ Textblock *cbuttontext = new Textblock(false);
+ ComplexButtonResource *cbuttonres =
+ layout->getResourceFactory()->createComplexButtonResource (cbuttontext,
+ true);
+ cbuttontext->setStyle (cellStyle);
+ cbuttontext->addText ("Run (complex)!", cellStyle);
+ cbuttontext->flush ();
+
+ // The entry resources are put into a special handler, which is
+ // also a receiver for the button resources.
+ form::Form *form = new form::Form();
+ form->addTextResource ("val1", entryres1);
+ form->addTextResource ("val2", entryres2);
+ form->addTextResource ("text", textres);
+ const char *radiovalues[] = { "radio1", "radio2", NULL };
+ form->addRadioButtonResource ("val3", radiores1, radiovalues);
+ form->addCheckButtonResource ("check", checkres);
+ const char *selvalues[] = { "i1", "i11", "i12", "i13", "i2",
+ "i21", "i22", "i23", "i3", NULL };
+ form->addSelectionResource ("val4", selres[0], selvalues);
+ form->addSelectionResource ("val5", selres[1], selvalues);
+ form->addButtonResource ("button", buttonres, "Run!");
+ form->addButtonResource ("cbutton", cbuttonres, "cbuttonval");
+
+ // Create the widgets.
+ table->addRow (cellStyle);
+
+ Textblock *label1 = new Textblock(false);
+ label1->setStyle (cellStyle);
+ table->addCell (label1, 1, 1);
+ label1->addText ("val1 = ", cellStyle);
+ label1->flush ();
+
+ Embed *input1 = new Embed (entryres1);
+ input1->setStyle (cellStyle);
+ table->addCell (input1, 1, 1);
+
+ table->addRow (cellStyle);
+
+ Textblock *label2 = new Textblock(false);
+ label2->setStyle (cellStyle);
+ table->addCell (label2, 1, 1);
+ label2->addText ("val2 = ", cellStyle);
+ label2->flush ();
+
+ Embed *input2 = new Embed (entryres2);
+ input2->setStyle (cellStyle);
+ table->addCell (input2, 1, 1);
+
+ table->addRow (cellStyle);
+
+ Textblock *label = new Textblock(false);
+ label->setStyle (cellStyle);
+ table->addCell (label, 1, 1);
+ label->addText ("text = ", cellStyle);
+ label->flush ();
+
+ Embed *text = new Embed (textres);
+ textres->setText("Hi textarea");
+ text->setStyle (cellStyle);
+ table->addCell (text, 1, 1);
+
+ table->addRow (cellStyle);
+
+ Textblock *radiolabel1 = new Textblock(false);
+ radiolabel1->setStyle (cellStyle);
+ table->addCell (radiolabel1, 2, 1);
+ Embed *radio1 = new Embed (radiores1);
+ radiolabel1->addWidget (radio1, cellStyle);
+ radiolabel1->addText (" radio1", cellStyle);
+ radiolabel1->flush ();
+
+ table->addRow (cellStyle);
+ Textblock *radiolabel2 = new Textblock(false);
+ radiolabel2->setStyle (cellStyle);
+ table->addCell (radiolabel2, 2, 1);
+ Embed *radio2 = new Embed (radiores2);
+ radiolabel2->addWidget (radio2, cellStyle);
+ radiolabel2->addText (" radio2", cellStyle);
+ radiolabel2->flush ();
+
+ table->addRow (cellStyle);
+ Textblock *checklabel = new Textblock(false);
+ checklabel->setStyle (cellStyle);
+ table->addCell (checklabel, 2, 1);
+ Embed *check = new Embed (checkres);
+ checklabel->addWidget (check, cellStyle);
+ checklabel->addText (" check", cellStyle);
+ checklabel->flush ();
+
+ for(int i = 0; i < 2; i++) {
+ table->addRow (cellStyle);
+
+ Embed *sel = new Embed (selres[i]);
+ sel->setStyle (cellStyle);
+ table->addCell (sel, 2, 1);
+
+ selres[i]->addItem("item 1", true, false);
+
+ selres[i]->pushGroup("group 1", true);
+ selres[i]->addItem("item 1/1", true, false);
+ selres[i]->addItem("item 1/2", true, true);
+ selres[i]->addItem("item 1/3", false, false);
+ selres[i]->popGroup();
+
+ selres[i]->addItem("item 2", true, i == 1);
+
+ selres[i]->pushGroup("group 2", false);
+ selres[i]->addItem("item 2/1", true, false);
+ selres[i]->addItem("item 2/2", true, false);
+ selres[i]->addItem("item 2/3", false, false);
+ selres[i]->popGroup();
+
+ selres[i]->addItem("item 3", false, false);
+ }
+
+ table->addRow (cellStyle);
+ Embed *button = new Embed (buttonres);
+ button->setStyle (cellStyle);
+ table->addCell (button, 2, 1);
+
+ table->addRow (cellStyle);
+ Embed *cbutton = new Embed (cbuttonres);
+ cbutton->setStyle (cellStyle);
+ table->addCell (cbutton, 2, 1);
+
+ cellStyle->unref();
+
+ window->resizable(viewport);
+ window->show();
+ int errorCode = ::fltk::run();
+
+ delete form;
+ delete layout;
+
+ return errorCode;
+}
diff --git a/test/fltk_browser.cc b/test/fltk_browser.cc
new file mode 100644
index 00000000..4186156e
--- /dev/null
+++ b/test/fltk_browser.cc
@@ -0,0 +1,47 @@
+/*
+ * Dillo Widget
+ *
+ * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+
+
+#include <fltk/Window.h>
+#include <fltk/Browser.h>
+#include <fltk/MultiBrowser.h>
+#include <fltk/Item.h>
+#include <fltk/run.h>
+
+using namespace fltk;
+
+int main (int argc, char *argv[])
+{
+ Window *window = new Window (300, 300, "FLTK Browser");
+ window->begin ();
+ Browser *browser = new MultiBrowser (0, 0, 300, 300);
+ browser->begin ();
+
+ for (int i = 0; i < 10; i++) {
+ new Item ("first");
+ new Item ("second");
+ new Item ("third");
+ }
+
+ window->resizable(browser);
+ window->show();
+ return run();
+}
diff --git a/test/form.cc b/test/form.cc
new file mode 100644
index 00000000..5e8e0471
--- /dev/null
+++ b/test/form.cc
@@ -0,0 +1,264 @@
+/*
+ * Dillo Widget
+ *
+ * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+
+
+#include "form.hh"
+
+namespace form {
+
+using namespace dw::core::ui;
+
+Form::ResourceDecorator::ResourceDecorator (const char *name)
+{
+ this->name = strdup (name);
+}
+
+Form::ResourceDecorator::~ResourceDecorator ()
+{
+ delete name;
+}
+
+Form::TextResourceDecorator::TextResourceDecorator (const char *name,
+ TextResource *resource):
+ Form::ResourceDecorator (name)
+{
+ this->resource = resource;
+}
+
+const char *Form::TextResourceDecorator::getValue ()
+{
+ return resource->getText ();
+}
+
+Form::RadioButtonResourceDecorator::RadioButtonResourceDecorator
+ (const char *name, RadioButtonResource *resource, const char **values):
+ Form::ResourceDecorator (name)
+{
+ this->resource = resource;
+
+ int n = 0;
+ while (values[n])
+ n++;
+ this->values = new const char*[n + 1];
+ for(int i = 0; i < n; i++)
+ this->values[i] = strdup (values[i]);
+ values[n] = 0;
+}
+
+Form::RadioButtonResourceDecorator::~RadioButtonResourceDecorator ()
+{
+ for(int i = 0; values[i]; i++)
+ delete values[i];
+ delete[] values;
+}
+
+const char *Form::RadioButtonResourceDecorator::getValue ()
+{
+ RadioButtonResource::GroupIterator *it;
+ int i;
+ for (it = resource->groupIterator (), i = 0; it->hasNext (); i++) {
+ RadioButtonResource *resource = it->getNext ();
+ if(resource->isActivated ()) {
+ it->unref ();
+ return values[i];
+ }
+ }
+
+ it->unref ();
+ return NULL;
+}
+
+Form::CheckButtonResourceDecorator::CheckButtonResourceDecorator
+ (const char *name, CheckButtonResource *resource):
+ Form::ResourceDecorator (name)
+{
+ this->resource = resource;
+}
+
+const char *Form::CheckButtonResourceDecorator::getValue ()
+{
+ return resource->isActivated () ? "true" : NULL;
+}
+
+Form::SelectionResourceDecorator::SelectionResourceDecorator
+ (const char *name, SelectionResource *resource, const char **values):
+ Form::ResourceDecorator (name)
+{
+ this->resource = resource;
+
+ int n = 0;
+ while (values[n])
+ n++;
+ this->values = new const char*[n + 1];
+ for(int i = 0; i < n; i++)
+ this->values[i] = strdup (values[i]);
+ this->values[n] = 0;
+}
+
+Form::SelectionResourceDecorator::~SelectionResourceDecorator ()
+{
+ for(int i = 0; values[i]; i++)
+ delete values[i];
+ delete[] values;
+}
+
+const char *Form::SelectionResourceDecorator::getValue ()
+{
+ valueBuf.clear();
+ int n = resource->getNumberOfItems ();
+ bool first = true;
+ for (int i = 0; i < n; i++) {
+ if (resource->isSelected (i)) {
+ if (!first)
+ valueBuf.append (", ");
+ valueBuf.append (values[i]);
+ first = false;
+ }
+ }
+
+ return valueBuf.getChars ();
+}
+
+void Form::FormActivateReceiver::activate (Resource *resource)
+{
+ form->send (NULL, NULL, -1, -1);
+}
+
+void Form::FormActivateReceiver::enter (Resource *resource)
+{
+}
+
+void Form::FormActivateReceiver::leave (Resource *resource)
+{
+}
+
+Form::FormClickedReceiver::FormClickedReceiver (Form *form, const char *name,
+ const char *value)
+{
+ this->form = form;
+ this->name = strdup (name);
+ this->value = strdup (value);
+}
+
+Form::FormClickedReceiver::~FormClickedReceiver ()
+{
+ delete name;
+ delete[] value;
+}
+
+void Form::FormClickedReceiver::clicked (ButtonResource *resource,
+ int buttonNo, int x, int y)
+{
+ form->send (name, value, x, y);
+}
+
+Form::Form ()
+{
+ resources = new lout::container::typed::List <ResourceDecorator> (true);
+ activateReceiver = new FormActivateReceiver (this);
+ clickedReceivers =
+ new lout::container::typed::List <FormClickedReceiver> (true);
+}
+
+Form::~Form ()
+{
+ delete resources;
+ delete activateReceiver;
+ delete clickedReceivers;
+}
+
+/**
+ * \brief Adds an instance of dw::core::ui::TextResource.
+ */
+void Form::addTextResource (const char *name,
+ dw::core::ui::TextResource *resource)
+{
+ resources->append (new TextResourceDecorator (name, resource));
+ resource->connectActivate (activateReceiver);
+}
+
+/**
+ * \brief Adds an instance of dw::core::ui::RadioButtonResource.
+ *
+ * This method has to be called only once for a group of radio buttons.
+ */
+void Form::addRadioButtonResource (const char *name,
+ dw::core::ui::RadioButtonResource *resource,
+ const char **values)
+{
+ resources->append (new RadioButtonResourceDecorator (name, resource,
+ values));
+ resource->connectActivate (activateReceiver);
+}
+
+/**
+ * \brief Adds an instance of dw::core::ui::CheckButtonResource.
+ */
+void Form::addCheckButtonResource (const char *name,
+ dw::core::ui::CheckButtonResource *resource)
+{
+ resources->append (new CheckButtonResourceDecorator (name, resource));
+ resource->connectActivate (activateReceiver);
+}
+
+/**
+ * \brief Adds an instance of dw::core::ui::SelectionResource.
+ */
+void Form::addSelectionResource (const char *name,
+ dw::core::ui::SelectionResource *resource,
+ const char **values)
+{
+ resources->append (new SelectionResourceDecorator (name, resource, values));
+ resource->connectActivate (activateReceiver);
+}
+
+/**
+ * \todo Comment this;
+ */
+void Form::addButtonResource (const char *name,
+ dw::core::ui::ButtonResource *resource,
+ const char *value)
+{
+ FormClickedReceiver *receiver =
+ new FormClickedReceiver (this, name, value);
+ resource->connectClicked (receiver);
+ clickedReceivers->append (receiver);
+}
+
+/**
+ * \todo Comment this;
+ */
+void Form::send (const char *buttonName, const char *buttonValue, int x, int y)
+{
+ for (lout::container::typed::Iterator <ResourceDecorator> it =
+ resources->iterator ();
+ it.hasNext (); ) {
+ ResourceDecorator *resource = it.getNext ();
+ const char *value = resource->getValue ();
+ if (value)
+ printf ("%s = %s; x=%d y=%d\n", resource->getName (), value, x, y);
+ }
+
+ if(buttonName && buttonValue)
+ printf ("%s = %s\n", buttonName, buttonValue);
+}
+
+} // namespace form
diff --git a/test/form.hh b/test/form.hh
new file mode 100644
index 00000000..a04460f4
--- /dev/null
+++ b/test/form.hh
@@ -0,0 +1,164 @@
+#ifndef __TEST_FORM_HH__
+#define __TEST_FORM_HH__
+
+#include "../dw/core.hh"
+#include "../dw/ui.hh"
+
+namespace form {
+
+/**
+ * \brief Handles HTML form data.
+ *
+ * Add resources by calling the respective add...Resource method. Furtermore,
+ * this class impelements dw::core::ui::ButtonResource::ClickedReceiver, the
+ * form data is printed to stdout, when the "clicked" signal is received.
+ *
+ * \todo wrong comment
+ */
+class Form
+{
+private:
+ /**
+ * \brief Decorates instances of dw::core::ui::Resource.
+ *
+ * This is the abstract base class, sub classes have to be defined to
+ * decorate specific sub interfaces of dw::core::ui::Resource.
+ */
+ class ResourceDecorator: public lout::object::Object
+ {
+ private:
+ const char *name;
+
+ protected:
+ ResourceDecorator (const char *name);
+ ~ResourceDecorator ();
+
+ public:
+ inline const char *getName () { return name; }
+ virtual const char *getValue () = 0;
+ };
+
+ /**
+ * \brief Decorates instances of dw::core::ui::TextResource.
+ */
+ class TextResourceDecorator: public ResourceDecorator
+ {
+ private:
+ dw::core::ui::TextResource *resource;
+
+ public:
+ TextResourceDecorator (const char *name,
+ dw::core::ui::TextResource *resource);
+ const char *getValue ();
+ };
+
+ /**
+ * \brief Decorates instances of dw::core::ui::RadioButtonResource.
+ *
+ * This class has to be instanciated only once for a group of radio
+ * buttons.
+ */
+ class RadioButtonResourceDecorator: public ResourceDecorator
+ {
+ private:
+ dw::core::ui::RadioButtonResource *resource;
+ const char **values;
+
+ public:
+ RadioButtonResourceDecorator (const char *name,
+ dw::core::ui::RadioButtonResource
+ *resource,
+ const char **values);
+ ~RadioButtonResourceDecorator ();
+ const char *getValue ();
+ };
+
+ /**
+ * \brief Decorates instances of dw::core::ui::CheckButtonResource.
+ */
+ class CheckButtonResourceDecorator: public ResourceDecorator
+ {
+ private:
+ dw::core::ui::CheckButtonResource *resource;
+
+ public:
+ CheckButtonResourceDecorator (const char *name,
+ dw::core::ui::CheckButtonResource
+ *resource);
+ const char *getValue ();
+ };
+
+ /**
+ * \brief Decorates instances of dw::core::ui::SelectionResource.
+ */
+ class SelectionResourceDecorator: public ResourceDecorator
+ {
+ private:
+ dw::core::ui::SelectionResource *resource;
+ const char **values;
+ lout::misc::StringBuffer valueBuf;
+
+ public:
+ SelectionResourceDecorator (const char *name,
+ dw::core::ui::SelectionResource *resource,
+ const char **values);
+ ~SelectionResourceDecorator ();
+ const char *getValue ();
+ };
+
+ class FormActivateReceiver: public dw::core::ui::Resource::ActivateReceiver
+ {
+ private:
+ Form *form;
+
+ public:
+ inline FormActivateReceiver (Form *form) { this->form = form; }
+
+ void activate (dw::core::ui::Resource *resource);
+ void enter (dw::core::ui::Resource *resource);
+ void leave (dw::core::ui::Resource *resource);
+ };
+
+ class FormClickedReceiver:
+ public dw::core::ui::ButtonResource::ClickedReceiver
+ {
+ private:
+ Form *form;
+ const char *name, *value;
+
+ public:
+ FormClickedReceiver (Form *form, const char *name, const char *value);
+ ~FormClickedReceiver ();
+
+ void clicked (dw::core::ui::ButtonResource *resource, int buttonNo,
+ int x, int y);
+ };
+
+ lout::container::typed::List <ResourceDecorator> *resources;
+ FormActivateReceiver *activateReceiver;
+ lout::container::typed::List <FormClickedReceiver> *clickedReceivers;
+
+public:
+ Form ();
+ ~Form ();
+
+ void addTextResource (const char *name,
+ dw::core::ui::TextResource *resource);
+ void addRadioButtonResource (const char *name,
+ dw::core::ui::RadioButtonResource *resource,
+ const char **values);
+ void addCheckButtonResource (const char *name,
+ dw::core::ui::CheckButtonResource *resource);
+ void addSelectionResource (const char *name,
+ dw::core::ui::SelectionResource *resource,
+ const char **values);
+ void addButtonResource (const char *name,
+ dw::core::ui::ButtonResource *resource,
+ const char *value);
+
+ void send (const char *buttonName, const char *buttonValue, int x, int y);
+};
+
+} // namespace form
+
+#endif // __TEST_FORM_HH__
diff --git a/test/shapes.cc b/test/shapes.cc
new file mode 100644
index 00000000..8d33152b
--- /dev/null
+++ b/test/shapes.cc
@@ -0,0 +1,41 @@
+/*
+ * Dillo Widget
+ *
+ * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+
+
+#include "../dw/core.hh"
+
+using namespace dw::core;
+using namespace misc;
+
+int main()
+{
+ Polygon poly;
+ poly.addPoint (50, 10);
+ poly.addPoint (90, 90);
+ poly.addPoint (10, 90);
+
+ printf("first test\n");
+ assert (poly.isPointWithin (50, 50));
+ printf("second test\n");
+ assert (!poly.isPointWithin (10, 10));
+ printf("third test\n");
+ assert (!poly.isPointWithin (90, 50));
+}