From 429d5f88b94ff28416cbfc6420b6389fa284df97 Mon Sep 17 00:00:00 2001 From: Rodrigo Arias Mallo Date: Tue, 10 Dec 2024 22:30:12 +0100 Subject: Import RTFL 0.1.1 --- .gitignore | 12 + AUTHORS | 3 + COPYING | 674 ++ ChangeLog | 149 + INSTALL | 370 + Makefile.am | 21 + NEWS | 1 + README | 149 + common/Makefile.am | 30 + common/about.cc | 109 + common/about.hh | 30 + common/fltk_lines.cc | 140 + common/fltk_lines.hh | 52 + common/lines.cc | 362 + common/lines.hh | 121 + common/parser.cc | 250 + common/parser.hh | 47 + common/rtfl_findrepeat.cc | 534 ++ common/rtfl_tee.c | 249 + common/tools.cc | 264 + common/tools.hh | 80 + configure.ac | 299 + debug_rtfl.hh | 461 ++ debug_rtfl.hh.in | 310 + doc/Makefile.am | 4 + doc/object-box-01.png | Bin 0 -> 18626 bytes doc/rtfl.html | 997 +++ doc/tipsandtricks.html | 415 ++ dw/Makefile.am | 57 + dw/core.hh | 60 + dw/events.hh | 83 + dw/findtext.cc | 307 + dw/findtext.hh | 84 + dw/fltkcore.hh | 36 + dw/fltkimgbuf.cc | 584 ++ dw/fltkimgbuf.hh | 93 + dw/fltkmisc.cc | 58 + dw/fltkmisc.hh | 22 + dw/fltkplatform.cc | 739 ++ dw/fltkplatform.hh | 186 + dw/fltkpreview.cc | 316 + dw/fltkpreview.hh | 95 + dw/fltkui.cc | 1395 ++++ dw/fltkui.hh | 505 ++ dw/fltkviewbase.cc | 744 +++ dw/fltkviewbase.hh | 141 + dw/fltkviewport.cc | 558 ++ dw/fltkviewport.hh | 84 + dw/imgbuf.hh | 229 + dw/imgrenderer.cc | 77 + dw/imgrenderer.hh | 87 + dw/iterator.cc | 920 +++ dw/iterator.hh | 271 + dw/layout.cc | 1445 ++++ dw/layout.hh | 532 ++ dw/platform.hh | 171 + dw/preview.xbm | 5 + dw/selection.cc | 494 ++ dw/selection.hh | 241 + dw/style.cc | 1468 ++++ dw/style.hh | 907 +++ dw/types.cc | 367 + dw/types.hh | 238 + dw/ui.cc | 519 ++ dw/ui.hh | 592 ++ dw/view.hh | 211 + dw/widget.cc | 1785 +++++ dw/widget.hh | 514 ++ dwr/Makefile.am | 31 + dwr/box.cc | 258 + dwr/box.hh | 110 + dwr/graph.cc | 642 ++ dwr/graph.hh | 126 + dwr/graph2.cc | 505 ++ dwr/graph2.hh | 122 + dwr/graph2_iterator.cc | 139 + dwr/hbox.cc | 123 + dwr/hbox.hh | 31 + dwr/hideable.cc | 104 + dwr/hideable.hh | 39 + dwr/label.cc | 378 ++ dwr/label.hh | 87 + dwr/toggle.cc | 402 ++ dwr/toggle.hh | 77 + dwr/tools.cc | 243 + dwr/tools.hh | 24 + dwr/vbox.cc | 164 + dwr/vbox.hh | 35 + java/Hello.java | 21 + java/Makefile.am | 46 + java/README | 40 + java/TestRtflObjects1.java | 50 + java/class.c | 39 + java/class.h | 9 + java/field.c | 156 + java/field.h | 12 + java/main.c | 68 + java/method.c | 111 + java/method.h | 12 + java/misc.c | 191 + java/misc.h | 41 + lout/Makefile.am | 21 + lout/container.cc | 813 +++ lout/container.hh | 551 ++ lout/debug.hh | 381 ++ lout/identity.cc | 141 + lout/identity.hh | 149 + lout/misc.cc | 197 + lout/misc.hh | 652 ++ lout/msg.h | 39 + lout/object.cc | 395 ++ lout/object.hh | 238 + lout/signal.cc | 180 + lout/signal.hh | 310 + lout/unicode.cc | 190 + lout/unicode.hh | 30 + ltmain.sh | 11156 +++++++++++++++++++++++++++++++ m4/libtool.m4 | 8388 +++++++++++++++++++++++ m4/ltoptions.m4 | 437 ++ m4/ltsugar.m4 | 124 + m4/ltversion.m4 | 23 + m4/lt~obsolete.m4 | 99 + objects/Makefile.am | 67 + objects/objcount_controller.cc | 114 + objects/objcount_controller.hh | 50 + objects/objcount_window.cc | 445 ++ objects/objcount_window.hh | 118 + objects/objdelete_controller.cc | 204 + objects/objdelete_controller.hh | 78 + objects/objects_buffer.cc | 281 + objects/objects_buffer.hh | 88 + objects/objects_parser.cc | 401 ++ objects/objects_parser.hh | 115 + objects/objects_writer.cc | 145 + objects/objects_writer.hh | 44 + objects/objident_controller.cc | 339 + objects/objident_controller.hh | 192 + objects/objview_commands.cc | 773 +++ objects/objview_commands.hh | 349 + objects/objview_controller.cc | 171 + objects/objview_controller.hh | 51 + objects/objview_graph.cc | 984 +++ objects/objview_graph.hh | 305 + objects/objview_stacktrace.cc | 127 + objects/objview_stacktrace.hh | 60 + objects/objview_window.cc | 707 ++ objects/objview_window.hh | 189 + objects/rtfl_objbase.cc | 45 + objects/rtfl_objcount.cc | 40 + objects/rtfl_objview.cc | 218 + scripts/Makefile.am | 6 + scripts/rtfl-check-objects | 60 + scripts/rtfl-filter-out-classes | 63 + scripts/rtfl-objfilter | 110 + scripts/rtfl-objtail | 134 + scripts/rtfl-stacktraces | 117 + tests/Makefile.am | 137 + tests/rtfl_cat.c | 53 + tests/rtfl_trickle.c | 55 + tests/simple_sink.cc | 61 + tests/simple_sink.hh | 30 + tests/test_fltk_1.cc | 64 + tests/test_fltk_2.cc | 47 + tests/test_graphviz_1.c | 46 + tests/test_pipes_1.c | 76 + tests/test_rtfl_objects_1.cc | 118 + tests/test_rtfl_objects_1_with_rtfl.cc | 2 + tests/test_rtfl_objects_2.cc | 69 + tests/test_rtfl_objects_2_with_rtfl.cc | 2 + tests/test_rtfl_objects_3.cc | 85 + tests/test_rtfl_objects_3_with_rtfl.cc | 2 + tests/test_rtfl_stats_1.cc | 54 + tests/test_rtfl_stats_1_with_rtfl.cc | 2 + tests/test_select_1.c | 67 + tests/test_tools_1.cc | 97 + tests/test_tools_2.cc | 26 + tests/test_tools_3.cc | 16 + tests/test_tools_4.cc | 19 + tests/test_tools_5.cc | 37 + tests/test_tools_6.cc | 29 + tests/test_version_cmp.c | 49 + tests/test_widget_b_splines.cc | 123 + tests/test_widgets_1.cc | 142 + tests/test_widgets_2.cc | 97 + tests/test_widgets_3.cc | 97 + tests/testtools.cc | 49 + tests/testtools.hh | 14 + 187 files changed, 61258 insertions(+) create mode 100644 .gitignore create mode 100644 AUTHORS create mode 100644 COPYING create mode 100644 ChangeLog create mode 100644 INSTALL create mode 100644 Makefile.am create mode 100644 NEWS create mode 100644 README create mode 100644 common/Makefile.am create mode 100644 common/about.cc create mode 100644 common/about.hh create mode 100644 common/fltk_lines.cc create mode 100644 common/fltk_lines.hh create mode 100644 common/lines.cc create mode 100644 common/lines.hh create mode 100644 common/parser.cc create mode 100644 common/parser.hh create mode 100644 common/rtfl_findrepeat.cc create mode 100644 common/rtfl_tee.c create mode 100644 common/tools.cc create mode 100644 common/tools.hh create mode 100644 configure.ac create mode 100644 debug_rtfl.hh create mode 100644 debug_rtfl.hh.in create mode 100644 doc/Makefile.am create mode 100644 doc/object-box-01.png create mode 100644 doc/rtfl.html create mode 100644 doc/tipsandtricks.html create mode 100644 dw/Makefile.am create mode 100644 dw/core.hh create mode 100644 dw/events.hh create mode 100644 dw/findtext.cc create mode 100644 dw/findtext.hh create mode 100644 dw/fltkcore.hh create mode 100644 dw/fltkimgbuf.cc create mode 100644 dw/fltkimgbuf.hh create mode 100644 dw/fltkmisc.cc create mode 100644 dw/fltkmisc.hh create mode 100644 dw/fltkplatform.cc create mode 100644 dw/fltkplatform.hh create mode 100644 dw/fltkpreview.cc create mode 100644 dw/fltkpreview.hh create mode 100644 dw/fltkui.cc create mode 100644 dw/fltkui.hh create mode 100644 dw/fltkviewbase.cc create mode 100644 dw/fltkviewbase.hh create mode 100644 dw/fltkviewport.cc create mode 100644 dw/fltkviewport.hh create mode 100644 dw/imgbuf.hh create mode 100644 dw/imgrenderer.cc create mode 100644 dw/imgrenderer.hh create mode 100644 dw/iterator.cc create mode 100644 dw/iterator.hh create mode 100644 dw/layout.cc create mode 100644 dw/layout.hh create mode 100644 dw/platform.hh create mode 100644 dw/preview.xbm create mode 100644 dw/selection.cc create mode 100644 dw/selection.hh create mode 100644 dw/style.cc create mode 100644 dw/style.hh create mode 100644 dw/types.cc create mode 100644 dw/types.hh create mode 100644 dw/ui.cc create mode 100644 dw/ui.hh create mode 100644 dw/view.hh create mode 100644 dw/widget.cc create mode 100644 dw/widget.hh create mode 100644 dwr/Makefile.am create mode 100644 dwr/box.cc create mode 100644 dwr/box.hh create mode 100644 dwr/graph.cc create mode 100644 dwr/graph.hh create mode 100644 dwr/graph2.cc create mode 100644 dwr/graph2.hh create mode 100644 dwr/graph2_iterator.cc create mode 100644 dwr/hbox.cc create mode 100644 dwr/hbox.hh create mode 100644 dwr/hideable.cc create mode 100644 dwr/hideable.hh create mode 100644 dwr/label.cc create mode 100644 dwr/label.hh create mode 100644 dwr/toggle.cc create mode 100644 dwr/toggle.hh create mode 100644 dwr/tools.cc create mode 100644 dwr/tools.hh create mode 100644 dwr/vbox.cc create mode 100644 dwr/vbox.hh create mode 100644 java/Hello.java create mode 100644 java/Makefile.am create mode 100644 java/README create mode 100644 java/TestRtflObjects1.java create mode 100644 java/class.c create mode 100644 java/class.h create mode 100644 java/field.c create mode 100644 java/field.h create mode 100644 java/main.c create mode 100644 java/method.c create mode 100644 java/method.h create mode 100644 java/misc.c create mode 100644 java/misc.h create mode 100644 lout/Makefile.am create mode 100644 lout/container.cc create mode 100644 lout/container.hh create mode 100644 lout/debug.hh create mode 100644 lout/identity.cc create mode 100644 lout/identity.hh create mode 100644 lout/misc.cc create mode 100644 lout/misc.hh create mode 100644 lout/msg.h create mode 100644 lout/object.cc create mode 100644 lout/object.hh create mode 100644 lout/signal.cc create mode 100644 lout/signal.hh create mode 100644 lout/unicode.cc create mode 100644 lout/unicode.hh create mode 100644 ltmain.sh create mode 100644 m4/libtool.m4 create mode 100644 m4/ltoptions.m4 create mode 100644 m4/ltsugar.m4 create mode 100644 m4/ltversion.m4 create mode 100644 m4/lt~obsolete.m4 create mode 100644 objects/Makefile.am create mode 100644 objects/objcount_controller.cc create mode 100644 objects/objcount_controller.hh create mode 100644 objects/objcount_window.cc create mode 100644 objects/objcount_window.hh create mode 100644 objects/objdelete_controller.cc create mode 100644 objects/objdelete_controller.hh create mode 100644 objects/objects_buffer.cc create mode 100644 objects/objects_buffer.hh create mode 100644 objects/objects_parser.cc create mode 100644 objects/objects_parser.hh create mode 100644 objects/objects_writer.cc create mode 100644 objects/objects_writer.hh create mode 100644 objects/objident_controller.cc create mode 100644 objects/objident_controller.hh create mode 100644 objects/objview_commands.cc create mode 100644 objects/objview_commands.hh create mode 100644 objects/objview_controller.cc create mode 100644 objects/objview_controller.hh create mode 100644 objects/objview_graph.cc create mode 100644 objects/objview_graph.hh create mode 100644 objects/objview_stacktrace.cc create mode 100644 objects/objview_stacktrace.hh create mode 100644 objects/objview_window.cc create mode 100644 objects/objview_window.hh create mode 100644 objects/rtfl_objbase.cc create mode 100644 objects/rtfl_objcount.cc create mode 100644 objects/rtfl_objview.cc create mode 100644 scripts/Makefile.am create mode 100644 scripts/rtfl-check-objects create mode 100644 scripts/rtfl-filter-out-classes create mode 100755 scripts/rtfl-objfilter create mode 100644 scripts/rtfl-objtail create mode 100755 scripts/rtfl-stacktraces create mode 100644 tests/Makefile.am create mode 100644 tests/rtfl_cat.c create mode 100644 tests/rtfl_trickle.c create mode 100644 tests/simple_sink.cc create mode 100644 tests/simple_sink.hh create mode 100644 tests/test_fltk_1.cc create mode 100644 tests/test_fltk_2.cc create mode 100644 tests/test_graphviz_1.c create mode 100644 tests/test_pipes_1.c create mode 100644 tests/test_rtfl_objects_1.cc create mode 100644 tests/test_rtfl_objects_1_with_rtfl.cc create mode 100644 tests/test_rtfl_objects_2.cc create mode 100644 tests/test_rtfl_objects_2_with_rtfl.cc create mode 100644 tests/test_rtfl_objects_3.cc create mode 100644 tests/test_rtfl_objects_3_with_rtfl.cc create mode 100644 tests/test_rtfl_stats_1.cc create mode 100644 tests/test_rtfl_stats_1_with_rtfl.cc create mode 100644 tests/test_select_1.c create mode 100644 tests/test_tools_1.cc create mode 100644 tests/test_tools_2.cc create mode 100644 tests/test_tools_3.cc create mode 100644 tests/test_tools_4.cc create mode 100644 tests/test_tools_5.cc create mode 100644 tests/test_tools_6.cc create mode 100644 tests/test_version_cmp.c create mode 100644 tests/test_widget_b_splines.cc create mode 100644 tests/test_widgets_1.cc create mode 100644 tests/test_widgets_2.cc create mode 100644 tests/test_widgets_3.cc create mode 100644 tests/testtools.cc create mode 100644 tests/testtools.hh diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e01432e --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +config.* +autom4te.cache +Makefile +Makefile.in +configure +compile +build +aclocal.m4 +depcomp +install-sh +stamp-h1 +missing diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..a1c94c9 --- /dev/null +++ b/AUTHORS @@ -0,0 +1,3 @@ +Author of RTFL is Sebastian Geerken (sgeerken-at-dillo.org). Parts are +copied from dillo, namely "lout" and "dw", whose main author is also +Sebastian Geerken. diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..94a9ed0 --- /dev/null +++ b/COPYING @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..1f2d56c --- /dev/null +++ b/ChangeLog @@ -0,0 +1,149 @@ +Version 0.1.1 (Jul 31 2016) +--------------------------- +General changes: +- New "general" protocol module, with one command, "time". +- Programs always read a file ".rtfl" in the current directory. +- New program "rtfl-objbase". + +Rtfl-objview: +- Some work on Graph2 (updated to Graphviz version 2.38.0, fixed rendering bug, + draw edges as b-splines, better library detection by configure script). It is + now the default. +- Ids of deleted objects are now forgotten. +- "obj-create" is now navigable. +- New option "-b"/"-B". +- Fixed bug when displaying stack trace. + +Miscellaneous: +- Scripts now honour optional return values of "obj-leave". + +New: +- Started work on JVM agent, which automates many RTFL messages. + +Internal Changes: +- ObjectsController (and implementations), LinesSource, LinesSink. + + +Version 0.1 (Feb 05 2015) +------------------------- +General changes: +- Versioning of protocol modules. +- Quoting in the protocol, so that a literal ':' is possible. +- Macros for "obj-mark", DBG_OBJ_MARK and DBG_OBJ_MARKF. +- "obj-leave" now supports optional return values; new macro DBG_OBJ_LEAVE_VAL. + +Rtfl-objview: +- Class patterns in "obj-class-color" are now sorted by specifity. +- Fixed redrawing and scrolling problem. +- Fixed sorting of attributes. +- "Switch between related" now also supports "obj-msg-start" and "obj-msg-end". +- Attributes with no children (sub-attributes or "obj-set" commands) are now + hidden. +- Colorful stacktrace. + +Internal changes: +- Updated to newer version of dillo widget (revision 3948:0769a58d63f9 + from ). +- The ascent of VBox is now the ascent of the first *visible* child. + + +Version 0.0.9 (Oct 26 2014) +--------------------------- +- New scripts "rtfl-stacktraces" and "rtfl-objfilter". +- New filters "rtfl-findrepeat" and "rtfl-tee". +- Updated other scripts: "obj-enter" and "obj-leave" are now regarded. +- Options "-a" and "-A" for "rtfl-objtail". +- Fixed parser bug. +- New command "obj-mark". +- New command "obj-object-color"; renamed "obj-color" to "obj-class-color". +- Updated parser of rtfl-objcount (eventually by the common parser). +- DBG_OBJ_COLOR: order of arguments has changed from (color, class) to + (class, color). +- Rtfl-objview: Experimental graph widget based on Graphviz ("./configure + --enable-graph2"). +- Rtfl-objview: Attributes are sorted alphabetically on the first level. + +Internal changes: +- General method Container::size() in lout::container. +- Methods "equal" and "hashValue" work now partly for containers. +- Common parser for objects module. + + +Version 0.0.8 (Jul 20 2014) +--------------------------- +- Scripts are now part of the RTFL package. +- New commands "obj-enter" and "obj-leave" (and respective macros) +- "Show strack trace" in rtfl-objview. +- "Switch between related" commands (currently "obj-enter" and "obj-leave") + + +Version 0.0.7 (May 26 2014) +--------------------------- +- Renamed "debug.hh" to "debug_rtfl.hh" and moved it to root directory. +- New macros DBG_OBJ_ARRATTRSET_*. +- "obj-msg-start" and "obj-msg-end" add messages (hidden by default). +- All navigable commands can be hidden, selectable by type. +- Hide all/show all commands; view code of command. +- Command line arguments. +- Corrected macros (DBG_OBJ_MSG_START and DBG_OBJ_MSG_END). +- Tests now in two versions: with and without RTFL messages active. +- Fixed (or worked around) overflow error in graph widget (arrow heads). +- Fixed overflow error in hbox and vbox widget. +- Fixed some memory problems. + + +Version 0.0.6 (Dec 29 2013) +--------------------------- +- "\n" at the beginning of RTFL messages. +- Fixed a bug introduced with 0.0.5 (indented messages). + + +Version 0.0.5 (Dec 26 2013) +--------------------------- +Important note: "rtfl-objects" has been renamed to "rtfl-objview", so you +should delete the old "rtfl-objects" from the target directory (typically +/usr/local/bin). + +Furthermore: +- UI changes (menu bar). +- Selectable and navigable command. +- Filtering of "obj-msg". +- New protocol command "obj-delete". +- Fixed a bug related to "obj-ident". +- New viewer "rtfl-objcount". + +Internal changes: +- Fixed bug in dw related to inverse backgrounds. +- Fixed handling zero size children in rtfl::dw::HBox and rtfl::dw::VBox. +- Added link handling in rtfl::dw::Label. + + +Version 0.0.4 (Dec 12 2013) +--------------------------- +- Fixed a bug (parser) introduced by 0.0.3. + + +Version 0.0.3 (Dec 12 2013) +--------------------------- +- Numbers are preceeded to some commands. +- Fixed two I/O related bugs (buffered reading of commands, cpu hogging). + +Internal changes: +- Made parser more robust (should not crash anymore). + + +Version 0.0.2 (Dec 09 2013) +--------------------------- +- Fixed some flaws in tests/debug.hh. +- Implemented "obj-color". +- New command "obj-ident" and macro DBG_OBJ_BASECLASS. +- Documentation "rtfl.html". + +Internal changes: +- Some work on widgets (rtfl::dw): removeChild, registration of class names. +- ObjectsGraph: list of commands, which can be undone (partly incomplete). + + +Version 0.0.1 (Dec 01 2013) +--------------------------- +- Initial release. diff --git a/INSTALL b/INSTALL new file mode 100644 index 0000000..2099840 --- /dev/null +++ b/INSTALL @@ -0,0 +1,370 @@ +Installation Instructions +************************* + +Copyright (C) 1994-1996, 1999-2002, 2004-2013 Free Software Foundation, +Inc. + + Copying and distribution of this file, with or without modification, +are permitted in any medium without royalty provided the copyright +notice and this notice are preserved. This file is offered as-is, +without warranty of any kind. + +Basic Installation +================== + + Briefly, the shell command `./configure && make && make install' +should configure, build, and install this package. The following +more-detailed instructions are generic; see the `README' file for +instructions specific to this package. Some packages provide this +`INSTALL' file but do not implement all of the features documented +below. The lack of an optional feature in a given package is not +necessarily a bug. More recommendations for GNU packages can be found +in *note Makefile Conventions: (standards)Makefile Conventions. + + The `configure' shell script attempts to guess correct values for +various system-dependent variables used during compilation. It uses +those values to create a `Makefile' in each directory of the package. +It may also create one or more `.h' files containing system-dependent +definitions. Finally, it creates a shell script `config.status' that +you can run in the future to recreate the current configuration, and a +file `config.log' containing compiler output (useful mainly for +debugging `configure'). + + It can also use an optional file (typically called `config.cache' +and enabled with `--cache-file=config.cache' or simply `-C') that saves +the results of its tests to speed up reconfiguring. Caching is +disabled by default to prevent problems with accidental use of stale +cache files. + + If you need to do unusual things to compile the package, please try +to figure out how `configure' could check whether to do them, and mail +diffs or instructions to the address given in the `README' so they can +be considered for the next release. If you are using the cache, and at +some point `config.cache' contains results you don't want to keep, you +may remove or edit it. + + The file `configure.ac' (or `configure.in') is used to create +`configure' by a program called `autoconf'. You need `configure.ac' if +you want to change it or regenerate `configure' using a newer version +of `autoconf'. + + The simplest way to compile this package is: + + 1. `cd' to the directory containing the package's source code and type + `./configure' to configure the package for your system. + + Running `configure' might take a while. While running, it prints + some messages telling which features it is checking for. + + 2. Type `make' to compile the package. + + 3. Optionally, type `make check' to run any self-tests that come with + the package, generally using the just-built uninstalled binaries. + + 4. Type `make install' to install the programs and any data files and + documentation. When installing into a prefix owned by root, it is + recommended that the package be configured and built as a regular + user, and only the `make install' phase executed with root + privileges. + + 5. Optionally, type `make installcheck' to repeat any self-tests, but + this time using the binaries in their final installed location. + This target does not install anything. Running this target as a + regular user, particularly if the prior `make install' required + root privileges, verifies that the installation completed + correctly. + + 6. You can remove the program binaries and object files from the + source code directory by typing `make clean'. To also remove the + files that `configure' created (so you can compile the package for + a different kind of computer), type `make distclean'. There is + also a `make maintainer-clean' target, but that is intended mainly + for the package's developers. If you use it, you may have to get + all sorts of other programs in order to regenerate files that came + with the distribution. + + 7. Often, you can also type `make uninstall' to remove the installed + files again. In practice, not all packages have tested that + uninstallation works correctly, even though it is required by the + GNU Coding Standards. + + 8. Some packages, particularly those that use Automake, provide `make + distcheck', which can by used by developers to test that all other + targets like `make install' and `make uninstall' work correctly. + This target is generally not run by end users. + +Compilers and Options +===================== + + Some systems require unusual options for compilation or linking that +the `configure' script does not know about. Run `./configure --help' +for details on some of the pertinent environment variables. + + You can give `configure' initial values for configuration parameters +by setting variables in the command line or in the environment. Here +is an example: + + ./configure CC=c99 CFLAGS=-g LIBS=-lposix + + *Note Defining Variables::, for more details. + +Compiling For Multiple Architectures +==================================== + + You can compile the package for more than one kind of computer at the +same time, by placing the object files for each architecture in their +own directory. To do this, you can use GNU `make'. `cd' to the +directory where you want the object files and executables to go and run +the `configure' script. `configure' automatically checks for the +source code in the directory that `configure' is in and in `..'. This +is known as a "VPATH" build. + + With a non-GNU `make', it is safer to compile the package for one +architecture at a time in the source code directory. After you have +installed the package for one architecture, use `make distclean' before +reconfiguring for another architecture. + + On MacOS X 10.5 and later systems, you can create libraries and +executables that work on multiple system types--known as "fat" or +"universal" binaries--by specifying multiple `-arch' options to the +compiler but only a single `-arch' option to the preprocessor. Like +this: + + ./configure CC="gcc -arch i386 -arch x86_64 -arch ppc -arch ppc64" \ + CXX="g++ -arch i386 -arch x86_64 -arch ppc -arch ppc64" \ + CPP="gcc -E" CXXCPP="g++ -E" + + This is not guaranteed to produce working output in all cases, you +may have to build one architecture at a time and combine the results +using the `lipo' tool if you have problems. + +Installation Names +================== + + By default, `make install' installs the package's commands under +`/usr/local/bin', include files under `/usr/local/include', etc. You +can specify an installation prefix other than `/usr/local' by giving +`configure' the option `--prefix=PREFIX', where PREFIX must be an +absolute file name. + + You can specify separate installation prefixes for +architecture-specific files and architecture-independent files. If you +pass the option `--exec-prefix=PREFIX' to `configure', the package uses +PREFIX as the prefix for installing programs and libraries. +Documentation and other data files still use the regular prefix. + + In addition, if you use an unusual directory layout you can give +options like `--bindir=DIR' to specify different values for particular +kinds of files. Run `configure --help' for a list of the directories +you can set and what kinds of files go in them. In general, the +default for these options is expressed in terms of `${prefix}', so that +specifying just `--prefix' will affect all of the other directory +specifications that were not explicitly provided. + + The most portable way to affect installation locations is to pass the +correct locations to `configure'; however, many packages provide one or +both of the following shortcuts of passing variable assignments to the +`make install' command line to change installation locations without +having to reconfigure or recompile. + + The first method involves providing an override variable for each +affected directory. For example, `make install +prefix=/alternate/directory' will choose an alternate location for all +directory configuration variables that were expressed in terms of +`${prefix}'. Any directories that were specified during `configure', +but not in terms of `${prefix}', must each be overridden at install +time for the entire installation to be relocated. The approach of +makefile variable overrides for each directory variable is required by +the GNU Coding Standards, and ideally causes no recompilation. +However, some platforms have known limitations with the semantics of +shared libraries that end up requiring recompilation when using this +method, particularly noticeable in packages that use GNU Libtool. + + The second method involves providing the `DESTDIR' variable. For +example, `make install DESTDIR=/alternate/directory' will prepend +`/alternate/directory' before all installation names. The approach of +`DESTDIR' overrides is not required by the GNU Coding Standards, and +does not work on platforms that have drive letters. On the other hand, +it does better at avoiding recompilation issues, and works well even +when some directory options were not specified in terms of `${prefix}' +at `configure' time. + +Optional Features +================= + + If the package supports it, you can cause programs to be installed +with an extra prefix or suffix on their names by giving `configure' the +option `--program-prefix=PREFIX' or `--program-suffix=SUFFIX'. + + Some packages pay attention to `--enable-FEATURE' options to +`configure', where FEATURE indicates an optional part of the package. +They may also pay attention to `--with-PACKAGE' options, where PACKAGE +is something like `gnu-as' or `x' (for the X Window System). The +`README' should mention any `--enable-' and `--with-' options that the +package recognizes. + + For packages that use the X Window System, `configure' can usually +find the X include and library files automatically, but if it doesn't, +you can use the `configure' options `--x-includes=DIR' and +`--x-libraries=DIR' to specify their locations. + + Some packages offer the ability to configure how verbose the +execution of `make' will be. For these packages, running `./configure +--enable-silent-rules' sets the default to minimal output, which can be +overridden with `make V=1'; while running `./configure +--disable-silent-rules' sets the default to verbose, which can be +overridden with `make V=0'. + +Particular systems +================== + + On HP-UX, the default C compiler is not ANSI C compatible. If GNU +CC is not installed, it is recommended to use the following options in +order to use an ANSI C compiler: + + ./configure CC="cc -Ae -D_XOPEN_SOURCE=500" + +and if that doesn't work, install pre-built binaries of GCC for HP-UX. + + HP-UX `make' updates targets which have the same time stamps as +their prerequisites, which makes it generally unusable when shipped +generated files such as `configure' are involved. Use GNU `make' +instead. + + On OSF/1 a.k.a. Tru64, some versions of the default C compiler cannot +parse its `' header file. The option `-nodtk' can be used as +a workaround. If GNU CC is not installed, it is therefore recommended +to try + + ./configure CC="cc" + +and if that doesn't work, try + + ./configure CC="cc -nodtk" + + On Solaris, don't put `/usr/ucb' early in your `PATH'. This +directory contains several dysfunctional programs; working variants of +these programs are available in `/usr/bin'. So, if you need `/usr/ucb' +in your `PATH', put it _after_ `/usr/bin'. + + On Haiku, software installed for all users goes in `/boot/common', +not `/usr/local'. It is recommended to use the following options: + + ./configure --prefix=/boot/common + +Specifying the System Type +========================== + + There may be some features `configure' cannot figure out +automatically, but needs to determine by the type of machine the package +will run on. Usually, assuming the package is built to be run on the +_same_ architectures, `configure' can figure that out, but if it prints +a message saying it cannot guess the machine type, give it the +`--build=TYPE' option. TYPE can either be a short name for the system +type, such as `sun4', or a canonical name which has the form: + + CPU-COMPANY-SYSTEM + +where SYSTEM can have one of these forms: + + OS + KERNEL-OS + + See the file `config.sub' for the possible values of each field. If +`config.sub' isn't included in this package, then this package doesn't +need to know the machine type. + + If you are _building_ compiler tools for cross-compiling, you should +use the option `--target=TYPE' to select the type of system they will +produce code for. + + If you want to _use_ a cross compiler, that generates code for a +platform different from the build platform, you should specify the +"host" platform (i.e., that on which the generated programs will +eventually be run) with `--host=TYPE'. + +Sharing Defaults +================ + + If you want to set default values for `configure' scripts to share, +you can create a site shell script called `config.site' that gives +default values for variables like `CC', `cache_file', and `prefix'. +`configure' looks for `PREFIX/share/config.site' if it exists, then +`PREFIX/etc/config.site' if it exists. Or, you can set the +`CONFIG_SITE' environment variable to the location of the site script. +A warning: not all `configure' scripts look for a site script. + +Defining Variables +================== + + Variables not defined in a site shell script can be set in the +environment passed to `configure'. However, some packages may run +configure again during the build, and the customized values of these +variables may be lost. In order to avoid this problem, you should set +them in the `configure' command line, using `VAR=value'. For example: + + ./configure CC=/usr/local2/bin/gcc + +causes the specified `gcc' to be used as the C compiler (unless it is +overridden in the site shell script). + +Unfortunately, this technique does not work for `CONFIG_SHELL' due to +an Autoconf limitation. Until the limitation is lifted, you can use +this workaround: + + CONFIG_SHELL=/bin/bash ./configure CONFIG_SHELL=/bin/bash + +`configure' Invocation +====================== + + `configure' recognizes the following options to control how it +operates. + +`--help' +`-h' + Print a summary of all of the options to `configure', and exit. + +`--help=short' +`--help=recursive' + Print a summary of the options unique to this package's + `configure', and exit. The `short' variant lists options used + only in the top level, while the `recursive' variant lists options + also present in any nested packages. + +`--version' +`-V' + Print the version of Autoconf used to generate the `configure' + script, and exit. + +`--cache-file=FILE' + Enable the cache: use and save the results of the tests in FILE, + traditionally `config.cache'. FILE defaults to `/dev/null' to + disable caching. + +`--config-cache' +`-C' + Alias for `--cache-file=config.cache'. + +`--quiet' +`--silent' +`-q' + Do not print messages saying which checks are being made. To + suppress all normal output, redirect it to `/dev/null' (any error + messages will still be shown). + +`--srcdir=DIR' + Look for the package's source code in directory DIR. Usually + `configure' can determine that directory automatically. + +`--prefix=DIR' + Use DIR as the installation prefix. *note Installation Names:: + for more details, including other options available for fine-tuning + the installation locations. + +`--no-create' +`-n' + Run the configure checks, but stop before creating any output + files. + +`configure' also accepts some other, not widely useful, options. Run +`configure --help' for more details. diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..acee8ce --- /dev/null +++ b/Makefile.am @@ -0,0 +1,21 @@ +SUBDIRS = lout common dw dwr objects scripts tests doc + +if HAS_JAVA + SUBDIRS += java +endif + +ACLOCAL_AMFLAGS=-I m4 + +# "debug_rtfl.hh" is generated from "debug_rtfl.hh.in". When calling +# "make", the subdirectories are processed before "debug_rtfl.hh" is +# generated, so it may be that you have to generate it explicitly by +# calling "make debug_rtfl.hh". + +# For convenience, "debug_rtfl.hh" is included into the distribution. +# Furthermore (mainly because of the problem mentioned above), it is +# also added to the SVN repository. + +EXTRA_DIST = debug_rtfl.hh debug_rtfl.hh.in + +debug_rtfl.hh: debug_rtfl.hh.in create-debug_rtfl-hh + ./create-debug_rtfl-hh < debug_rtfl.hh.in > debug_rtfl.hh diff --git a/NEWS b/NEWS new file mode 100644 index 0000000..898a3da --- /dev/null +++ b/NEWS @@ -0,0 +1 @@ +See ChangeLog. diff --git a/README b/README new file mode 100644 index 0000000..2d09acb --- /dev/null +++ b/README @@ -0,0 +1,149 @@ +Overview +-------- +RTFL, which stands for "Read The Figurative Logfile", is a both a +protocol for structured debug messages, as well as a collection of +programs (currently two) displaying these debug messages in a +semi-graphical way, so that it becomes simpler to determine what the +debugged program does. + +Programs are prepared to print these special debug messages to +standard output, which are then passed to a viewer program like +"rtfl-objcount" or "rtfl-objview". + +See "doc/rtfl.html" for a comprehensive description. + + +Copyright +--------- +RTFL is free software, released under the GPL version 3 or later (see +COPYING for details), with the following exception: + +The copyright holders of RTFL give you permission to link the program +rtfl-objview statically or dynamically against all versions of the +graphviz library, which are published by AT&T Corp. under one of the +following licenses: + +- Common Public License version 1.0 as published by International + Business Machines Corporation (IBM), or +- Eclipse Public License version 1.0 as published by the Eclipse + Foundation. + +Both the protocol and the header file "debug_rtfl.hh", which provides +macros for the protocol, are not protected at all, but in the public +domain. This means that are no restrictions for the programs being +debugged using RTFL. + +Some (informal) notes about the exception: + +1. Graphviz, which is used by the Graph2 widget, is published under + the Eclipse Public License version 1.0, older versions are + published under the Common Public License version 1.0. Both + licenses are free software licenses, but weak copyleft licenses, + and so incompatible with any version the GNU General Public + License. See [1] and [2] for details. The license exception solves + this. + +2. Since the CPL and EPL are *weak* copyleft licenses, there is no + problem in the other direction. + +3. All parts of RTFL are still compatible with the GNU GPL. + +4. I want to restrict this license exception to free libraries; this + is the reason that the CPL and the EPL are explicitly mentioned. If + you have some "technical" problems with this limitation, feel free + to contact me (see below). + + +Building +-------- +As usual: "./configure && make && make install" (or "install-strip"); +see "INSTALL" for details. If you are using the version from SVN +instead of the release tarball, you have to run "libtoolize && aclocal +&& autoconf && automake" before. + +You need FLTK version 1.3 (try "fltk-config --version"). Get it from +, or install a package suitable for your +operating system (on Debian: "apt-get install libfltk1.3-dev"). + +There is a widget, Graph2, which enhances the graph layouting of +rtfl-objview, as compared to the older Graph widget, and it has +matured enough to become the default. It depends on the Graphviz +library (see or install a +suitable package, e. g. "libgraphviz-dev" on Debian), version 2.38.0 +or later. If you use an older version, try to modify "configure.ac"; +if you succeed, drop me a note. + +If Graphviz is not installed, the old Graph widget is used. If you +want to use the old Graph widget in any case, run "./configure" with +"--disable-graph2" to use the old widget. + +Furthermore, there has been some work on a Java VM agent, which +automates generation of RTFL messages by Java programs. The JDK is +searched according to following rules: + + (i) If "--disable-java" is passed to "./configure", the JVM agent is + not build at all. + (ii) The root of the JDK (under which "bin", "include" etc. are + found) may be passed explicitly by "--with-java-home=...". +(iii) Otherwise, "javac" is found in the path, its symbolic links are + followed, and so the root of the JDK is searched. + +Of course, if "javac" is not found, or some crucial files within the +JDK are missing, the agent is neither build. + +See java/README for more details. + + +Hacking +------- +RTFL uses parts of the dillo widget from the dillo web browser +(); see "lout" and "dw" directory. The current +version was taken on Oct 27 2014 from the repository +, revision 3948:0769a58d63f9. Few changes +are generally made: + +- the parts of "libDw-widgets.a" are removed, they are not needed in + RTFL; + +- dw::fltk::ui:ComplexButtonResource and related classes and files + (FltkFlatView and ComplexButton) are removed, since they are not + needed, and (this is actually the main reason) the copyright of + ComplexButton is a bit unclear [3]; + +- the copyright notices are modified by adding the license exception + (this is automated with the script "update_copyright" which is part + of the SVN repository, albeit not release tarball). + +Smaller changes to "lout" and "dw" can be made within RTFL; from time +to time, these changes should be back-ported, before a new version of +"lout" and "dw" is copied to RTFL. + +The directory "dwr" provides some general dillo widgets used in RTFL, +and "common" base code for all viewers and protocol modules. Specific +viewers and protocol modules have there own directory (currently only +"objects": may become subject to change.) + + +Future +------ +First of all, there are numerous bugs, flaws, and things to +improve. (No list yet.) + +For more, see the file . See + for news. + + +Footnotes +--------- +[1] http://www.gnu.org/philosophy/license-list.html#CommonPublicLicense10 + +[2] http://www.gnu.org/philosophy/license-list.html#EPL + +[3] Nothing serious; ComplexButton is a derivate of the Button widget + of FLTK, which is released under the GNU LGPL, so that linking + with the graphviz library should not cause a problem. diff --git a/common/Makefile.am b/common/Makefile.am new file mode 100644 index 0000000..b8e1313 --- /dev/null +++ b/common/Makefile.am @@ -0,0 +1,30 @@ +# Notes about libraries: "librtfl-tools.a" contains everything not +# depending on FLTK, which can so be used in command line tools; +# "librtfl-common.a" depens on FLTK, and also on the former. + +AM_CPPFLAGS = \ + -I$(top_srcdir) + +noinst_LIBRARIES = librtfl-common.a librtfl-tools.a + +bin_PROGRAMS = rtfl-findrepeat rtfl-tee + +librtfl_common_a_SOURCES = \ + about.hh \ + about.cc \ + fltk_lines.hh \ + fltk_lines.cc + +librtfl_tools_a_SOURCES = \ + lines.hh \ + lines.cc \ + parser.hh \ + parser.cc \ + tools.hh \ + tools.cc + +rtfl_findrepeat_SOURCES = rtfl_findrepeat.cc + +rtfl_findrepeat_LDADD = librtfl-tools.a ../lout/liblout.a + +rtfl_tee_SOURCES = rtfl_tee.c diff --git a/common/about.cc b/common/about.cc new file mode 100644 index 0000000..01ad3ac --- /dev/null +++ b/common/about.cc @@ -0,0 +1,109 @@ +/* + * RTFL + * + * Copyright 2013-2015 Sebastian Geerken + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version; with the following exception: + * + * The copyright holders of RTFL give you permission to link this file + * statically or dynamically against all versions of the graphviz + * library, which are published by AT&T Corp. under one of the following + * licenses: + * + * - Common Public License version 1.0 as published by International + * Business Machines Corporation (IBM), or + * - Eclipse Public License version 1.0 as published by the Eclipse + * Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "about.hh" + +#include "config.h" + +#include +#include + +#include +#include +#include +#include + +namespace rtfl { + +namespace common { + +void AboutWindow::close (Fl_Widget *widget, void *data) +{ + ((AboutWindow*)data)->hide (); +} + + +AboutWindow::AboutWindow (const char *prgName, const char *licenceException, + int height) : + Fl_Window (WIDTH, height, "") +{ + + const char *titleFmt = "RTFL: About %s"; + const char *textFmt = + "%s " VERSION "\n" + "\n" + "%s is part of RTFL (Read The Figurative Logfile).\n" + "\n" + "Copyright 2013-2015 Sebastian Geerken \n" + "\n" + "RTFL 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%s.\n" + "\n" + "With RTFL comes some documentation, see “doc/rtfl.html” in the " + "tarball. For more informations, updates etc. see " + "."; + + int titleLen = strlen (titleFmt) - 2 + strlen (prgName) + 1; + title = new char [titleLen]; + snprintf (title, titleLen, titleFmt, prgName); + label (title); + + char *capName = strdup (prgName); + capName[0] = toupper (capName[0]); + int textLen = + strlen (textFmt) - 2 + strlen (prgName) - 2 + strlen (capName) + 1 + - 2 + strlen (licenceException); + text = new char[textLen]; + snprintf (text, textLen, textFmt, prgName, capName, licenceException); + free (capName); + + Fl_Box *textWidget = + new Fl_Box(SPACE, SPACE, WIDTH - 2 * SPACE, + height - 3 * SPACE - BUTTON_HEIGHT, text); + textWidget->box(FL_NO_BOX); + textWidget->align(FL_ALIGN_WRAP); + + Fl_Return_Button *close = + new Fl_Return_Button(WIDTH - BUTTON_WIDTH - SPACE, + height - BUTTON_HEIGHT - SPACE, BUTTON_WIDTH, + BUTTON_HEIGHT, "Close"); + close->callback (AboutWindow::close, this); +} + +AboutWindow::~AboutWindow () +{ + delete[] title; + delete[] text; +} + +} // namespace common + +} // namespace rtfl diff --git a/common/about.hh b/common/about.hh new file mode 100644 index 0000000..2a46483 --- /dev/null +++ b/common/about.hh @@ -0,0 +1,30 @@ +#ifndef __COMMON_ABOUT_HH__ +#define __COMMON_ABOUT_HH__ + +#include + +namespace rtfl { + +namespace common { + +class AboutWindow: public Fl_Window +{ +private: + char *title, *text; + + static void close (Fl_Widget *widget, void *data); + + enum { WIDTH = 450, BUTTON_WIDTH = 80, BUTTON_HEIGHT = 25, SPACE = 10 }; + +public: + enum { HEIGHT_SIMPLE = 300, HEIGHT_EXCEPTION = 480 }; + + AboutWindow (const char *prgName, const char *licenceException, int height); + ~AboutWindow (); +}; + +} // namespace common + +} // namespace rtfl + +#endif // __COMMON_ABOUT_HH__ diff --git a/common/fltk_lines.cc b/common/fltk_lines.cc new file mode 100644 index 0000000..5bf4b00 --- /dev/null +++ b/common/fltk_lines.cc @@ -0,0 +1,140 @@ +/* + * RTFL + * + * Copyright 2013-2015 Sebastian Geerken + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version; with the following exception: + * + * The copyright holders of RTFL give you permission to link this file + * statically or dynamically against all versions of the graphviz + * library, which are published by AT&T Corp. under one of the following + * licenses: + * + * - Common Public License version 1.0 as published by International + * Business Machines Corporation (IBM), or + * - Eclipse Public License version 1.0 as published by the Eclipse + * Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "fltk_lines.hh" + +#include +#include +#include + +using namespace lout::container::typed; + +namespace rtfl { + +namespace common { + +// ------------------------- +// FltkLinesSource +// ------------------------- + +FltkLinesSource::TimeoutInfo::TimeoutInfo (FltkLinesSource *source, int type) +{ + this->source = source; + this->type = type; +} + +FltkLinesSource::FltkLinesSource () +{ + timeoutInfos = new List (true); +} + +FltkLinesSource::~FltkLinesSource () +{ + delete timeoutInfos; +} + +void FltkLinesSource::staticProcessInputCallback (int fd, void *data) +{ + ((FltkLinesSource*)data)->processInputCallback (fd); +} + +void FltkLinesSource::processInputCallback (int fd) +{ + int n = processInput (fd); + + if (n == 0) { + // We read non-blocking, so -1 is returned and (errno set to + // EAGAIN) when no data is currently available. When 0 is + // returned, this means that there is permanently no data + // (typically that the tested program has terminated). For some + // reasons, the cpu is hogged then; this is avoided by removing + // the read function again. + Fl::remove_fd(0, FL_READ); + getSink()->finish (); + } +} + +void FltkLinesSource::setup (tools::LinesSink *sink) +{ + setSink (sink); + + int flags = fcntl(0, F_GETFL, 0); + fcntl(0, F_SETFL, flags | O_NONBLOCK); + + Fl::add_fd(0, FL_READ, staticProcessInputCallback, (void*)this); +} + +void FltkLinesSource::addTimeout (double secs, int type) +{ + TimeoutInfo *timeoutInfo = new TimeoutInfo (this, type); + timeoutInfos->append (timeoutInfo); + Fl::add_timeout(secs, timeoutCallback, timeoutInfo); +} + +void FltkLinesSource::timeoutCallback (void *data) +{ + TimeoutInfo *timeoutInfo = (TimeoutInfo*) data; + timeoutInfo->getSource()->getSink()->timeout (timeoutInfo->getType ()); + timeoutInfo->getSource()->timeoutInfos->removeRef (timeoutInfo); +} + +void FltkLinesSource::removeTimeout (int type) +{ + // Iterators will not work when the set is modified; hence this nested loop. + bool found; + do { + found = false; + for (Iterator it = timeoutInfos->iterator (); + !found && it.hasNext (); ) { + TimeoutInfo *timeout = it.getNext(); + if (timeout->getType () == type) { + found = true; + Fl::remove_timeout(timeoutCallback, timeout); + timeoutInfos->removeRef (timeout); + } + } + } while (found); +} + +// --------------------------- +// FltkDefaultSource +// --------------------------- + +FltkDefaultSource::FltkDefaultSource (): LinesSourceSequence (true) +{ + int fd = open (".rtfl", O_RDONLY); + if (fd != -1) + add (new tools::BlockingLinesSource (fd)); + + add (new FltkLinesSource ()); +} + +} // namespace objects + +} // namespace rtfl diff --git a/common/fltk_lines.hh b/common/fltk_lines.hh new file mode 100644 index 0000000..424ab82 --- /dev/null +++ b/common/fltk_lines.hh @@ -0,0 +1,52 @@ +#ifndef __COMMON_FLTK_LINES_HH__ +#define __COMMON_FLTK_LINES_HH__ + +#include "lines.hh" + +namespace rtfl { + +namespace common { + +class FltkLinesSource: public tools::FileLinesSource +{ + class TimeoutInfo: public lout::object::Object + { + private: + FltkLinesSource *source; + int type; + + public: + TimeoutInfo (FltkLinesSource *source, int type); + + inline FltkLinesSource *getSource () { return source; } + inline int getType () { return type; } + }; + + lout::container::typed::List *timeoutInfos; + + static void staticProcessInputCallback (int fd, void *data); + static void timeoutCallback (void *data); + void processInputCallback (int fd); + +public: + FltkLinesSource (); + ~FltkLinesSource (); + + void setup (tools::LinesSink *sink); + void addTimeout (double secs, int type); + void removeTimeout (int type); +}; + + +class FltkDefaultSource: public tools::LinesSourceSequence +{ +public: + FltkDefaultSource (); +}; + + +} // namespace common + +} // namespace rtfl + +#endif // __COMMON_FLTK_LINES_HH__ diff --git a/common/lines.cc b/common/lines.cc new file mode 100644 index 0000000..e6fa8c2 --- /dev/null +++ b/common/lines.cc @@ -0,0 +1,362 @@ +/* + * RTFL + * + * Copyright 2015 Sebastian Geerken + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version; with the following exception: + * + * The copyright holders of RTFL give you permission to link this file + * statically or dynamically against all versions of the graphviz + * library, which are published by AT&T Corp. under one of the following + * licenses: + * + * - Common Public License version 1.0 as published by International + * Business Machines Corporation (IBM), or + * - Eclipse Public License version 1.0 as published by the Eclipse + * Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "lines.hh" +#include "tools.hh" + +#include +#include +#include +#include + +#if 0 +# define PRINT(fmt) printf ("---- [%p] " fmt "\n", this) +# define PRINTF(fmt, ...) printf ("---- [%p] " fmt "\n", this, __VA_ARGS__) +#else +# define PRINT(fmt) +# define PRINTF(fmt, ...) +#endif + +using namespace lout::container::typed; +using namespace lout::misc; + +namespace rtfl { + +namespace tools { + +// ----------------------------- +// LinesSourceSequence +// ----------------------------- + +LinesSourceSequence::VirtualSink::VirtualSink () +{ +} + +void LinesSourceSequence::VirtualSink::processLine (char *line) +{ + sequence->sink->processLine (line); +} + +void LinesSourceSequence::VirtualSink::setLinesSource (LinesSource *source) +{ +} + +void LinesSourceSequence::VirtualSink::finish () +{ + // If a child source calls sink->finish() within setup(), this is + // called recursively, but this does not cause problems. + + if (sequence->iterator.hasNext ()) { + LinesSource *source = sequence->iterator.getNext (); + source->setup (this); + } else { + sequence->sink->finish (); + } +} + +void LinesSourceSequence::VirtualSink::timeout (int type) +{ + sequence->sink->timeout (type); +} + +LinesSourceSequence::LinesSourceSequence (bool ownerOfSources) +{ + virtualSink.sequence = this; + sources = new List (ownerOfSources); + setupCalled = false; +} + +LinesSourceSequence::~LinesSourceSequence () +{ + delete sources; +} + +void LinesSourceSequence::add (LinesSource *source) +{ + assert (!setupCalled); + sources->append (source); +} + +void LinesSourceSequence::setup (LinesSink *sink) +{ + this->sink = sink; + sink->setLinesSource (this); + setupCalled = true; + iterator = sources->iterator (); + virtualSink.finish (); +} + +void LinesSourceSequence::addTimeout (double secs, int type) +{ + // TODO: After calling this, no source should be added. + // TODO: Processed timeouts must be removed from other sources as well? + + // Sent to all, even if only one child source will actually trigger the + // timeout; but we do not know which one. + + // In the real world, LinesSourceSequence is used for ".rtfl" and stdin, so + // we do not have to worry too much about correctly handling timeouts. + + for (Iterator it = sources->iterator (); it.hasNext (); ) { + it.getNext()->addTimeout (secs, type); + } +} + +void LinesSourceSequence::removeTimeout (int type) +{ + for (Iterator it = sources->iterator (); it.hasNext (); ) { + it.getNext()->removeTimeout (type); + } +} + +// ------------------------- +// FileLinesSource +// ------------------------- + +FileLinesSource::FileLinesSource () +{ + bufPos = 0; + completeLine = true; +} + +int FileLinesSource::processInput (int fd) +{ + int n; + if ((n = read (fd, buf + bufPos, MAX_LINE_SIZE - bufPos)) > 0) { + int bytesAvail = bufPos + n; + int startOfLine = 0; + bool lineProcessed; + + //printf ("--> %d bytes read, %d available\n", n, bytesAvail); + + do { + lineProcessed = false; + for (int i = startOfLine; !lineProcessed && i < bytesAvail; i++) { + if (buf[i] == '\n') { + buf[i] = 0; + + // If lines are too long (see below, where completeLine + // is set to false), they are not processed. + if (completeLine) + sink->processLine (buf + startOfLine); + + lineProcessed = true; + startOfLine = i + 1; + + completeLine = true; + } + } + } while (lineProcessed); + + memmove (buf, buf + startOfLine, bytesAvail - startOfLine); + bufPos = bytesAvail - startOfLine; + + PRINTF ("processInput: %d bytes left in buffer", bufPos); + + // Handle case when line is to large (> MAX_LINE_SIZE + // bytes). The whole line is discarded (completeLine), so we + // empty the buffer by setting bufPos to 0. + if (bufPos == MAX_LINE_SIZE) { + bufPos = 0; + completeLine = false; + } + + //printf (" --> %d processed, new pos: %d; will read %d\n", + // startOfLine, bufPos, MAX_LINE_SIZE - bufPos); + } + + //printf (" --> read(2) returns %d\n", n); + + return n; +} + +// ----------------------------- +// BlockingLinesSource +// ----------------------------- + +BlockingLinesSource::TimeoutInfo::TimeoutInfo (long time, int type) +{ + this->time = time; + this->type = type; +} + +bool BlockingLinesSource::TimeoutInfo::equals(Object *other) +{ + return time == ((TimeoutInfo*)other)->time && + type == ((TimeoutInfo*)other)->type; +} + +int BlockingLinesSource::TimeoutInfo::hashValue() +{ + // This should better be hidden in lout::objects. Cf. Pointer::hashValue(). +#if SIZEOF_LONG == 4 + return (int)time ^ type; +#else + return ((intptr_t)time >> 32) ^ ((intptr_t)time) ^ type; +#endif +} + +BlockingLinesSource::BlockingLinesSource (int fd) +{ + this->fd = fd; + timeoutInfos = new HashSet (true); +} + +BlockingLinesSource::~BlockingLinesSource () +{ + delete timeoutInfos; +} + +void BlockingLinesSource::setup (LinesSink *sink) +{ + setSink (sink); + + // We read non-blocking so that select(2) will work properly. + // (FileLinesSource::processInput would block otherwise.) + int flags = fcntl(0, F_GETFL, 0); + fcntl(0, F_SETFL, flags | O_NONBLOCK); + + bool eos = false; + while (!eos) { + fd_set readfds; + FD_ZERO (&readfds); + FD_SET (fd, &readfds); + + TimeoutInfo *nextTimeout = getNextTimeoutInfo (); + + struct timeval tv, *tvp; + if (nextTimeout == NULL) { + tvp = NULL; + PRINT ("no timeout"); + } else { + long tdelta = max (nextTimeout->getTime () - getCurrentTime (), 0L); + tv.tv_sec = tdelta / 1000; + tv.tv_usec = (tdelta % 1000) * 1000; + tvp = &tv; + PRINTF ("waiting %ld (%ld, %ld)", tdelta, tv.tv_sec, tv.tv_usec); + } + + PRINT (">> processTimeouts"); + processTimeouts (); + PRINT ("<< processTimeouts"); + + PRINT (">> select"); + if (select (fd + 1, &readfds, NULL, NULL, tvp) == -1) + syserr ("select failed"); + PRINT ("<< select"); + + processTimeouts (); + + if (FD_ISSET (fd, &readfds)) { + PRINT (">> processInput"); + int n = processInput (fd); + PRINT ("<< processInput"); + if (n == 0) { + eos = true; + } + } + } + + close (fd); + sink->finish (); +} + +void BlockingLinesSource::addTimeout (double secs, int type) +{ + PRINTF ("addTimeout (%g, %d)", secs, type); + timeoutInfos->put (new TimeoutInfo (getCurrentTime () + secs * 1000, type)); +} + +void BlockingLinesSource::removeTimeout (int type) +{ + PRINTF ("removeTimeout (%d)", type); + + // Iterators will not work when the set is modified; hence this nested loop. + bool found; + do { + found = false; + for (Iterator it = timeoutInfos->iterator (); + !found && it.hasNext (); ) { + TimeoutInfo *timeout = it.getNext(); + if (timeout->getType () == type) { + found = true; + timeoutInfos->remove (timeout); + } + } + } while (found); +} + +long BlockingLinesSource::getCurrentTime () +{ + struct timeb t; + if (ftime (&t) == -1) + syserr ("ftime() failed"); + return t.time * 1000L + t.millitm; +} + +BlockingLinesSource::TimeoutInfo *BlockingLinesSource::getNextTimeoutInfo () +{ + TimeoutInfo *nextTimeout = NULL; + + for (Iterator it = timeoutInfos->iterator (); + it.hasNext (); ) { + TimeoutInfo *timeout = it.getNext(); + if (nextTimeout == NULL || + timeout->getTime () < nextTimeout->getTime ()) + nextTimeout = timeout; + } + + return nextTimeout; +} + +void BlockingLinesSource::processTimeouts () +{ + long currentTime = getCurrentTime (); + + while (true) { + TimeoutInfo *nextTimeout = getNextTimeoutInfo (); + if (nextTimeout == NULL) + break; + + PRINTF ("processTimeouts: %ld > %ld? %s", + nextTimeout->getTime (), currentTime, + nextTimeout->getTime () > currentTime ? "yes" : "no"); + if (nextTimeout->getTime () > currentTime) + break; + + PRINT ("processTimeouts: call timeout"); + + getSink()->timeout (nextTimeout->getType ()); + timeoutInfos->remove (nextTimeout); + } +} + +} // namespace tools + +} // namespace rtfl diff --git a/common/lines.hh b/common/lines.hh new file mode 100644 index 0000000..d3bfc75 --- /dev/null +++ b/common/lines.hh @@ -0,0 +1,121 @@ +#ifndef __COMMON_LINES_HH__ +#define __COMMON_LINES_HH__ + +#include "lout/object.hh" +#include "lout/container.hh" + +namespace rtfl { + +namespace tools { + +class LinesSource; + +class LinesSink: public lout::object::Object +{ +public: + virtual void setLinesSource (LinesSource *source) = 0; + virtual void processLine (char *line) = 0; + virtual void timeout (int type) = 0; + virtual void finish () = 0; +}; + + +class LinesSource: public lout::object::Object +{ +public: + virtual void setup (LinesSink *sink) = 0; + virtual void addTimeout (double secs, int type) = 0; + virtual void removeTimeout (int type) = 0; +}; + + +class LinesSourceSequence: public LinesSource +{ +private: + class VirtualSink: public LinesSink + { + public: + LinesSourceSequence *sequence; + + VirtualSink (); + void setLinesSource (LinesSource *source); + void processLine (char *line); + void timeout (int type); + void finish (); + }; + + VirtualSink virtualSink; + LinesSink *sink; + lout::container::typed::List *sources; + bool setupCalled; + lout::container::typed::Iterator iterator; + +public: + LinesSourceSequence (bool ownerOfSources); + ~LinesSourceSequence (); + void add (LinesSource *source); + void setup (LinesSink *sink); + void addTimeout (double secs, int type); + void removeTimeout (int type); +}; + + +class FileLinesSource: public LinesSource +{ +private: + enum { MAX_LINE_SIZE = 1000 }; + + tools::LinesSink *sink; + char buf[MAX_LINE_SIZE + 1]; + int bufPos; + bool completeLine; + +protected: + FileLinesSource (); + + int processInput (int fd); + inline void setSink (LinesSink *sink) { + this->sink = sink; sink->setLinesSource (this); } + inline LinesSink *getSink () { return sink; } +}; + + +class BlockingLinesSource: public FileLinesSource +{ +private: + class TimeoutInfo: public lout::object::Object + { + private: + long time; + int type; + + public: + TimeoutInfo (long time, int type); + bool equals(Object *other); + int hashValue(); + + inline long getTime () { return time; } + inline int getType () { return type; } + }; + + int fd; + lout::container::typed::HashSet *timeoutInfos; + + long getCurrentTime (); + TimeoutInfo *getNextTimeoutInfo (); + void processTimeouts (); + +public: + BlockingLinesSource (int fd); + ~BlockingLinesSource (); + void setup (LinesSink *sink); + void addTimeout (double secs, int type); + void removeTimeout (int type); +}; + + +} // namespace tools + +} // namespace rtfl + +#endif // __COMMON_LINES_HH__ diff --git a/common/parser.cc b/common/parser.cc new file mode 100644 index 0000000..3311e45 --- /dev/null +++ b/common/parser.cc @@ -0,0 +1,250 @@ +/* + * RTFL + * + * Copyright 2013-2015 Sebastian Geerken + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version; with the following exception: + * + * The copyright holders of RTFL give you permission to link this file + * statically or dynamically against all versions of the graphviz + * library, which are published by AT&T Corp. under one of the following + * licenses: + * + * - Common Public License version 1.0 as published by International + * Business Machines Corporation (IBM), or + * - Eclipse Public License version 1.0 as published by the Eclipse + * Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "parser.hh" + +#include +#include + +namespace rtfl { + +namespace tools { + +void Parser::setLinesSource (LinesSource *source) +{ +} + +void Parser::processLine (char *line) +{ + char *lineCopy = strdup (line); + + if (strncmp (lineCopy, "[rtfl]", 6) == 0) { + // Pre-version: starts with "[rtfl]". + + char **parts = split (lineCopy + 6, 5); + + if (parts[1] && parts[2] && parts[3]) { + // Notice that parts[4] (arguments) is allowed to be NULL here. + CommonLineInfo info = + { parts[0], atoi(parts[1]), atoi(parts[2]), line }; + processCommand (&info, parts[3], parts[4]); + } else + fprintf (stderr, "Incomplete line:\n%s\n", line); + + freeSplit (parts); + } else if (strncmp (lineCopy, "[rtfl-", 6) == 0) { + // Versioned: starts with "[rtfl--.]". + + int i = 6; + while (isalpha (lineCopy[i])) + i++; + + if (lineCopy[i] != '-') + fprintf (stderr, "Expected '-' after module:\n%s\n", line); + else { + char *module = new char[i - 6 + 1]; + memcpy (module, lineCopy + 6, (i - 6) * sizeof (char)); + module[i - 6] = 0; + + i++; + if (!isdigit (lineCopy[i])) + fprintf (stderr, "Missing major version:\n%s\n", line); + else { + int majorVersion = 0, minorVersion = 0; + + while (isdigit (lineCopy[i])) { + majorVersion = 10 * majorVersion + (lineCopy[i] - '0'); + i++; + } + + if (majorVersion == 0) + fprintf (stderr, "Major version must be positive:\n%s\n", line); + else if (lineCopy[i] != '.') + fprintf (stderr, "Expected '.' after major version:\n%s\n", + line); + else if (!isdigit (lineCopy[i + 1])) + fprintf (stderr, "Missing minor version:\n%s\n", line); + else { + i++; + while (isdigit (lineCopy[i])) { + minorVersion = 10 * minorVersion + (lineCopy[i] - '0'); + i++; + } + + if (lineCopy[i] != ']') + fprintf (stderr, "Expected ']' after minor version:\n%s\n", + line); + else { + char **parts = splitEscaped (lineCopy + i + 1); + + if (parts[1] && parts[2] && parts[3]) { + // Notice that parts[4] (first argument) is allowed to be + // NULL here. + CommonLineInfo info = { parts[0], atoi(parts[1]), + atoi(parts[2]), line }; + processVCommand (&info, module, majorVersion, minorVersion, + parts[3], parts + 4); + } else + fprintf (stderr, "Incomplete line:\n%s\n", line); + + freeSplitEscaped (parts); + } + } + } + + delete[] module; + } + } + + free (lineCopy); +} + +void Parser::finish () +{ +} + +void Parser::timeout (int type) +{ +} + +char **Parser::splitEscaped (char *txt) +{ + int numParts; + char **parts; + + scanSplit (txt, &numParts, NULL); + parts = new char*[numParts + 1]; + scanSplit (txt, NULL, parts); + parts[numParts] = NULL; + + for (int i = 0; i < numParts; i++) + unquote (parts[i]); + + return parts; +} + +void Parser::scanSplit (char *txt, int *numParts, char **parts) +{ + int iChar, iPart; + bool quoted; + + if (numParts) + *numParts = 1; + + if (parts) + parts[0] = txt; + + for (iChar = 0, iPart = 1; txt[iChar]; iChar++) { + if (txt[iChar] == '\\' && txt[iChar + 1]) { + iChar++; + quoted = true; + } else + quoted = false; + + if (!quoted && txt[iChar] == ':') { + if (parts) { + txt[iChar] = 0; + parts[iPart] = txt + iChar + 1; + } + + iPart++; + + if (numParts) + (*numParts)++; + } + } +} + +void Parser::unquote (char *txt) +{ + int i, j; + for (i = 0, j = 0; txt[i]; i++, j++) { + if (txt[i] == '\\' && txt[i + 1]) + i++; + txt[j] = txt[i]; + } + txt[j] = 0; +} + +// Free result of splitEscaped(). +void Parser::freeSplitEscaped (char **parts) +{ + delete[] parts; +} + +// Split without escaping. +char **Parser::split (char *txt, int maxNum) +{ + // Only maxNum splits. If less parts are found, less parts are + // returned, so the caller should check the result (first part is + // always defined). Notice that the original text buffer (txt) is + // destroyed, for speed. + + //printf ("===> split ('%s', %d)\n", txt, maxNum); + + char **parts = new char*[maxNum + 1]; + + char *start = txt; + int i = 0; + while (i < maxNum) { + char *end = start; + while (*end != 0 && *end != ':') end++; + int endOfTxt = *end == 0; + + //printf (" start '%s'\n", start); + //printf (" end '%s' (%d character(s))\n", + // end, (int)(end - start)); + + parts[i] = start; + + if (i < maxNum -1) + *end = 0; + + //printf ("---> %d: '%s'\n", i, start); + + i++; + if (endOfTxt) + break; + + start = endOfTxt ? end : end + 1; + } + + parts[i] = NULL; + return parts; +} + +// Free result of split(). +void Parser::freeSplit (char **parts) +{ + delete[] parts; +} + +} // namespace tools + +} // namespace rtfl diff --git a/common/parser.hh b/common/parser.hh new file mode 100644 index 0000000..30cf02d --- /dev/null +++ b/common/parser.hh @@ -0,0 +1,47 @@ +#ifndef __COMMON_PARSER_HH__ +#define __COMMON_PARSER_HH__ + +#include "lines.hh" + +namespace rtfl { + +namespace tools { + +struct CommonLineInfo +{ + char *fileName; + int lineNo; + int processId; + char *completeLine; +}; + +class Parser: public LinesSink +{ +private: + char **splitEscaped (char *txt); + void scanSplit (char *txt, int *numParts, char **parts); + static void unquote (char *txt); + void freeSplitEscaped (char **parts); + +protected: + char **split (char *txt, int maxNum); + void freeSplit (char **parts); + + virtual void processCommand (CommonLineInfo *info, char *cmd, char *args) + = 0; + virtual void processVCommand (CommonLineInfo *info, const char *module, + int majorVersion, int minorVersion, + const char *cmd, char **args) = 0; + +public: + void setLinesSource (LinesSource *source); + void processLine (char *line); + void finish (); + void timeout (int type); +}; + +} // namespace common + +} // namespace rtfl + +#endif // __COMMON_PARSER_HH__ diff --git a/common/rtfl_findrepeat.cc b/common/rtfl_findrepeat.cc new file mode 100644 index 0000000..d464db8 --- /dev/null +++ b/common/rtfl_findrepeat.cc @@ -0,0 +1,534 @@ +/* + * RTFL + * + * Copyright 2014, 2015 Sebastian Geerken + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/* + * This program searches for identical sequences in a stream of RTFL + * messages (actually, any stream) and marks the beginnings and ends + * with an RTFL mark (obj-mark). You should first filter out other + * lines, so run + * + * $ ... | grep '^\[rtfl[^\]]*\]' | rtfl-findrepeat + * + * or use the script rtfl-objfilter. + * + * For options, see printHelp(). + * + * Warning: This program is highly experimental, and especially rather + * inefficient. Some ideas: + * + * - When finding a suitable lenght ("-l find"), a smaller number of + * lines is in many cases sufficient, so use "head". + * + * - Unless the length is searched for ("-l find"), hashing multiple + * lines (as many as are searched as minimum), as done in the + * searching algorithm by Rabin and Karp, may increase the speed. + */ + +#include +#include +#include +#include "tools.hh" +#include "../lout/object.hh" +#include "../lout/container.hh" + +using namespace lout::misc; +using namespace lout::object; +using namespace lout::container::typed; + +enum { MAX_LINE_SIZE = 1000 }; + +class Region: public Comparable +{ + int first, num; + +public: + inline Region (int first, int num) { this->first = first; this->num = num; } + + bool equals (Object *other); + int hashValue (); + void intoStringBuffer (StringBuffer *sb); + int compareTo(Comparable *other); + + inline int getFirst () { return first; } + inline int getNum () { return num; } + inline Region *cloneRegion () { return new Region (first, num); } + + inline bool subSetOf (Region *other) + { return first >= other->first && first + num <= other->first + other->num; } +}; + +class Mark: public Object +{ +public: + enum Type { START, END }; + +private: + Type type; + int majorNo, minorNo, length; + +public: + inline Mark (Type type, int majorNo, int minorNo, int length) + { this->type = type; this->majorNo = majorNo; this->minorNo = minorNo; + this->length = length; } + + void intoStringBuffer (StringBuffer *sb); + + inline Type getType () { return type; } + inline int getMajorNo () { return majorNo; } + inline int getMinorNo () { return minorNo; } + inline int getLength () { return length; } +}; + +bool Region::equals (Object *other) +{ + Region *otherRegion = (Region*)other; + return first == otherRegion->first && num == otherRegion->num; +} + +int Region::hashValue () +{ + return first ^ num; +} + +void Region::intoStringBuffer (StringBuffer *sb) +{ + char buf[32]; + + sb->append ("("); + snprintf (buf, 32, "%d", first); + sb->append (buf); + sb->append ("..."); + snprintf (buf, 32, "%d", first + num - 1); + sb->append (buf); + sb->append (")"); +} + +void Mark::intoStringBuffer (StringBuffer *sb) +{ + char buf[32]; + + + sb->append ("("); + sb->append (type == START ? "START" : "END"); + sb->append (" / "); + snprintf (buf, 32, "%d", majorNo); + sb->append (buf); + sb->append (" / "); + snprintf (buf, 32, "%d", minorNo); + sb->append (buf); + sb->append (")"); +} + +int Region::compareTo(Comparable *other) +{ + Region *otherRegion = (Region*)other; + return first - otherRegion->first; +} + +// ---------------------------------------------------------------------- + +static bool debug = false; + +static void printHelp (const char *argv0) +{ + fprintf + (stderr, "Usage: %s \n" + "\n" + "Options:\n" + " -l Search for sequence of at least lines.\n" + " -c Search for sequence repeated at least times.\n" + "\n" + "If an arguments is 'f' or 'find', the maximal value for this is\n" + "determined (possibly with the other argument set to a concrete\n" + "number).\n" + "\n" + "See RTFL documentation for more details.\n", + argv0); +} + +// ---------------------------------------------------------------------- + +static void readFile (FILE *file, Vector *lines, + HashTable > *lineNosByLines) +{ + char buf[MAX_LINE_SIZE + 1]; + + for (int lineNo = 0; fgets (buf, MAX_LINE_SIZE + 1, file); lineNo++) { + size_t l = strlen (buf); + if (buf[l - 1] == '\n') buf[l - 1] = 0; + + String *line = new String (buf); + + Vector *lineNos = lineNosByLines->get (line); + if (lineNos == NULL) { + lineNos = new Vector (1, true); + // Note: key is dublicated. + lineNosByLines->put (new String (buf), lineNos); + } + lineNos->put (new Integer (lineNo)); + + lines->put (line); + } +} + +static int findRegions (Vector *lines, + HashTable > *lineNosByLines, + List > *allSetsOfRegions, + int minLength, int minCount) +{ + int effMinLength = minLength == -1 ? 2 :minLength; + int effMinCount = minCount == -1 ? 2 : minCount; + int maxLength = 0, maxCount = 0; + + HashTable > *setsOfRegionsByRegion = + new HashTable > (false, false); + + List > *tmpAllSetsOfRegions = + new List > (false); + + for (int lineNo1 = 0; lineNo1 < lines->size (); lineNo1++) { + String *line = lines->get (lineNo1); + Vector *lineNos = lineNosByLines->get (line); + + // Examine only lines after this. + Integer lineNo1Key (lineNo1); + + for (int linesNoIndex = lineNos->bsearch (&lineNo1Key, true) + 1; + linesNoIndex < lineNos->size (); linesNoIndex++) { + int lineNo2 = lineNos->get(linesNoIndex)->getValue (); + int numMatching = 1; + while (lineNo2 + numMatching < lines->size () && + lines->get(lineNo1 + numMatching)->equals + (lines->get(lineNo2 + numMatching))) { + numMatching++; + + if (numMatching >= effMinLength) { + //printf ("equal: (%d...%d) and (%d...%d)\n", + // lineNo1, lineNo1 + numMatching - 1, + // lineNo2, lineNo2 + numMatching - 1); + + Region r1 (lineNo1, numMatching), r2 (lineNo2, numMatching); + HashSet *setOfRegions; + + if ((setOfRegions = setsOfRegionsByRegion->get (&r1))) { + if (!setsOfRegionsByRegion->contains (&r2)) { + assert (!setOfRegions->contains (&r2)); + Region *rr2 = r2.cloneRegion (); + setOfRegions->put (rr2); + setsOfRegionsByRegion->put (rr2, setOfRegions); + } + } else if ((setOfRegions = setsOfRegionsByRegion->get (&r2))) { + if (!setsOfRegionsByRegion->contains (&r1)) { + assert (!setOfRegions->contains (&r1)); + Region *rr1 = r1.cloneRegion (); + setOfRegions->put (rr1); + setsOfRegionsByRegion->put (rr1, setOfRegions); + } + } else { + Region *rr1 = r1.cloneRegion (), *rr2 = r2.cloneRegion (); + setOfRegions = new HashSet (false); + setOfRegions->put (rr1); + setOfRegions->put (rr2); + setsOfRegionsByRegion->put (rr1, setOfRegions); + setsOfRegionsByRegion->put (rr2, setOfRegions); + tmpAllSetsOfRegions->append (setOfRegions); + } + + if (debug) { + StringBuffer sb; + setsOfRegionsByRegion->intoStringBuffer (&sb); + printf ("findRegions: setsOfRegionsByRegion = %s\n", + sb.getChars ()); + } + } + } + } + } + + delete setsOfRegionsByRegion; + + for (Iterator > it1 = tmpAllSetsOfRegions->iterator (); + it1.hasNext (); ) { + HashSet *set = it1.getNext (); + if (set->size () >= effMinCount) { + allSetsOfRegions->append (set); + maxCount = max (maxCount, set->size ()); + + if (minLength == -1) { + for (Iterator it2 = set->iterator (); it2.hasNext (); ) { + Region *r = it2.getNext (); + maxLength = max (maxLength, r->getNum ()); + } + } + } else + delete set; + } + + delete tmpAllSetsOfRegions; + + if (minLength == -1) + return maxLength; + else if (minCount == -1) + return maxCount; + else + return -1; +} + +static void sortListsOfRegions (List > *allSetsOfRegions, + List > *allListsOfRegions) +{ + for (Iterator > it1 = allSetsOfRegions->iterator (); + it1.hasNext (); ) { + HashSet *set = it1.getNext (); + Vector *list = new Vector (1, true); + + for (Iterator it2 = set->iterator (); it2.hasNext (); ) { + Region *r = it2.getNext (); + list->put (r); + } + + if (debug) { + StringBuffer sb; + list->intoStringBuffer (&sb); + printf ("sortListsOfRegions: list = %s\n", sb.getChars ()); + } + + list->sort (); + allListsOfRegions->append (list); + } +} + +static void cleanupRegions (List > *allListsOfRegions) +{ + HashTable, Vector > > *allListsOfLists = + new HashTable, Vector > > (true, true); + + for (Iterator > it = allListsOfRegions->iterator (); + it.hasNext (); ) { + Vector *list = it.getNext (); + + List *key = new List (true); + for (int i = 1; i < list->size (); i++) + key->append (new Integer (list->get(i)->getFirst () - + list->get(i - 1)->getFirst ())); + + Vector > *listOfLists = allListsOfLists->get (key); + if (listOfLists) + delete key; + else { + listOfLists = new Vector > (1, false); + allListsOfLists->put (key, listOfLists); + } + + listOfLists->put (list); + } + + allListsOfRegions->clear (); + + if (debug) { + StringBuffer sb; + allListsOfLists->intoStringBuffer (&sb); + printf ("cleanupRegions: allListsOfLists = %s\n", sb.getChars ()); + } + + for (Iterator > it = allListsOfLists->iterator (); + it.hasNext (); ) { + List *key = it.getNext (); + Vector > *listOfLists = allListsOfLists->get (key); + + if (debug) { + StringBuffer sb; + listOfLists->intoStringBuffer (&sb); + printf ("cleanupRegions: listOfLists = %s\n", sb.getChars ()); + } + + for (int i = 0; i < listOfLists->size (); i++) { + Vector *list1 = listOfLists->get (i); + Region *r1 = list1->get (0); + bool redundant = false; + for (int j = 0; j < listOfLists->size () && !redundant; j++) { + if (i != j) { + Vector *list2 = listOfLists->get (j); + if (list2 != NULL) { + Region *r2 = list2->get (0); + if (r1->subSetOf (r2)) + redundant = true; + } + } + } + + if (redundant) { + listOfLists->put (NULL, i); + delete list1; + } else + allListsOfRegions->append (list1); + } + } + + delete allListsOfLists; +} + +// ---------------------------------------------------------------------- + +int main (int argc, char *argv[]) +{ + int minLength = 2, minCount = 2; + int opt; + + while ((opt = getopt(argc, argv, "c:dl:")) != -1) { + switch (opt) { + case 'c': + if (strcmp (optarg, "f") == 0 || strcmp (optarg, "find") == 0) + minCount = -1; + else + minCount = atoi (optarg); + break; + + case 'd': + debug = true; + break; + + case 'l': + if (strcmp (optarg, "f") == 0 || strcmp (optarg, "find") == 0) + minLength = -1; + else + minLength = atoi (optarg); + break; + + default: + printHelp (argv[0]); + return 1; + } + } + + Vector *lines = new Vector (8, true); + HashTable > *lineNosByLines = + new HashTable > (true, true); + + readFile (stdin, lines, lineNosByLines); + + List > *allSetsOfRegions = + new List > (true); + + int numFound = findRegions (lines, lineNosByLines, allSetsOfRegions, + minLength, minCount); + + if (debug) { + StringBuffer sb; + allSetsOfRegions->intoStringBuffer (&sb); + printf ("main: allSetsOfRegions = %s\n", sb.getChars ()); + } + + delete lineNosByLines; + + if (numFound != -1) { + delete allSetsOfRegions; + printf ("%d\n", numFound); + } else { + List > *allListsOfRegions = + new List > (false); // TODO Memory leak! + + sortListsOfRegions (allSetsOfRegions, allListsOfRegions); + + delete allSetsOfRegions; + + if (debug) { + StringBuffer sb; + allListsOfRegions->intoStringBuffer (&sb); + printf ("(a) main: allListsOfRegions = %s\n", sb.getChars ()); + } + + cleanupRegions (allListsOfRegions); + + if (debug) { + StringBuffer sb; + allListsOfRegions->intoStringBuffer (&sb); + printf ("(b) main: allListsOfRegions = %s\n", sb.getChars ()); + } + + HashTable > *marksByLineNo = + new HashTable > (true, true); + + int majorNo = 0; + for (Iterator > it1 = allListsOfRegions->iterator (); + it1.hasNext (); ) { + Vector *list = it1.getNext (); + int minorNo = 0; + + for (Iterator it2 = list->iterator (); it2.hasNext (); ) { + Region *r = it2.getNext (); + + for (int typeNo = 0; typeNo < 2; typeNo++) { + Mark::Type type = typeNo == 0 ? Mark::START : Mark::END; + int lineNo = + r->getFirst () + (type == Mark::START ? 0 : r->getNum ()); + Integer lineNoKey (lineNo); + + List *list = marksByLineNo->get (&lineNoKey); + if (list == NULL) { + list = new List (true); + marksByLineNo->put (new Integer (lineNo), list); + } + + list->append (new Mark (type, majorNo, minorNo, r->getNum ())); + } + + + minorNo++; + } + + majorNo++; + } + + delete allListsOfRegions; + + if (debug) { + StringBuffer sb; + marksByLineNo->intoStringBuffer (&sb); + printf ("main: marksByLineNo = %s\n", sb.getChars ()); + } + + for (int lineNo = 0; lineNo < lines->size (); lineNo++) { + Integer lineNoKey (lineNo); + List *list = marksByLineNo->get (&lineNoKey); + if (list) { + for (Iterator it = list->iterator (); it.hasNext (); ) { + Mark *m = it.getNext (); + char buf[200]; + rtfl::tools::numToRoman (m->getMajorNo () + 1, buf, + sizeof (buf)); + // Certainly no ':' or '\' in the message, so no quoting + // necessary. + printf ("[rtfl-obj-1.0]n:0:0:mark:findrepeat:findrepeat:0:" + "Sequence %s (length %d), %d%s occurence -- %s\n", + buf, m->getLength (), m->getMinorNo () + 1, + rtfl::tools::numSuffix (m->getMinorNo () + 1), + m->getType () == Mark::START ? "start" : "end"); + } + } + + String *line = lines->get (lineNo); + puts (line->chars ()); + } + + delete marksByLineNo; + } + + delete lines; +} diff --git a/common/rtfl_tee.c b/common/rtfl_tee.c new file mode 100644 index 0000000..4911b73 --- /dev/null +++ b/common/rtfl_tee.c @@ -0,0 +1,249 @@ +/* + * RTFL + * + * Copyright 2014 Sebastian Geerken + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/* + * Like tee(1), this program duplicates a stream; however, it does not + * write the copy to a file, but instead sends it via pipe to another + * program. Example: + * + * $ foo | rtfl-tee bar | qix + * + * Here, the standard output of "foo" is passed to the standard input + * of both "bar" and "qix". + * + * More informations in doc/rtfl.html. + * + * TODO: Something like "echo -n foo | rtfl-tee -b cat" does not work; + * since the line of the first "foo" is never finished, the copy of + * "foo" is never printed. + */ + +#include +#include +#include +#include +#include +#include +#include + +#define min(a, b) ((a) < (b) ? (a) : (b)) +#define max(a, b) ((a) > (b) ? (a) : (b)) + +static void usrerr (const char *fmt, ...) +{ + va_list args; + va_start (args, fmt); + vfprintf (stderr, fmt, args); + fprintf (stderr, "\n"); + exit (1); +} + +static void syserr (const char *fmt, ...) +{ + va_list args; + va_start (args, fmt); + vfprintf (stderr, fmt, args); + fprintf (stderr, ": %s\n", strerror (errno)); + exit (1); +} + +static ssize_t ewrite(int fd, const void *buf, size_t count) +{ + ssize_t w; + if ((w = write (fd, buf, count)) == -1) + syserr ("write(%d, ...) failed", fd); + return w; +} + +static void writestdout (int orig, char *buf, size_t count) +{ + // Basic idea: "orig" denotes to 0 (stdin of rtfl-tee) or 1 (stdout + // of the called program). "curorig" refers to the origin which is + // currently printed, so than the data from the other origin must + // be buffered. "startline" is set to 1 at the beginning, or iff + // the last printed character was '\n'. (In this case, switching is + // simply possible.) + + static int curorig = 0, startline = 1; + static char obuf[2048]; + static size_t ocount = 0; + + //printf ("\nwritestdout: %d, '%c...' (%d)\n", + // orig, count > 0 ? buf[0] : '.', (int)count); + //printf ("===> curorig = %d, ocount = %d, startline = %d\n", + // curorig, (int)ocount, startline); + + if (count > 0) { + if (orig != curorig) { + if (startline) { + // Simple switching case. + ewrite (1, obuf, ocount); + ocount = 0; + curorig = orig; + ewrite (1, buf, count); + startline = buf[count - 1] == '\n'; + } else { + // Buffer. + size_t odiff = min (count, ocount - sizeof (obuf)); + memcpy (obuf + ocount, buf, odiff); + ocount += odiff; + } + } else { + if (ocount == 0) { + // Nothing buffered: simply print all data. + ewrite (1, buf, count); + startline = buf[count - 1] == '\n'; + } else { + // Only print everything until the last newline character. + // (Note: printing everything until the *first* newline + // character whould make a larger buffer necessary, but, + // on the other hand, preserve better the original + // (temporal) order of the lines.) + ssize_t i, nl; + for (nl = -1, i = count - 1; nl == -1 && i >= 0; i--) + if (buf[i] == '\n') nl = i; + + if (nl == -1) { + // No newline: no switch. + ewrite (1, buf, count); + startline = 0; + } else { + // Newline: switch. + ewrite (1, buf, nl + 1); + ewrite (1, obuf, ocount); + startline = obuf[ocount - 1] == '\n'; + ocount = min (sizeof (obuf), count - (nl + 1)); + memcpy (obuf, buf + nl + 1, ocount); + curorig = 1 - curorig; + } + } + } + } +} + +int main (int argc, char *argv[]) +{ + int parent2child[2], child2parent[2], i, offsetcmd, bypass = 0, erroropt = 0; + char *argv2[argc - 1 + 1]; + int done1, done2; + + for (offsetcmd = 1; offsetcmd < argc && argv[offsetcmd][0] == '-'; + offsetcmd++) { + if (argv[offsetcmd][1] == 'b') + bypass = 1; + else if (argv[offsetcmd][1] == '-') { + offsetcmd++; + break; + } else + erroropt = 1; + } + + if (erroropt || offsetcmd >= argc) + usrerr ("Usage: %s [-b] [--] []", argv[0]); + + if (pipe (parent2child) == -1) syserr ("pipe failed"); + if (bypass && pipe (child2parent) == -1) syserr ("pipe failed"); + + switch (fork ()) { + case -1: + syserr ("fork failed"); + break; + + case 0: + if (close (parent2child[1]) == -1) + syserr ("close(%d) failed", parent2child[0]); + if (close (0) == -1) syserr ("close(0) failed"); + if (dup2 (parent2child[0], 0) == -1) + syserr ("dup2(%d, 0) failed", parent2child[0]); + if (close (parent2child[0]) == -1) + syserr ("close(%d) failed", parent2child[0]); + + if (bypass) { + if (close (child2parent[0]) == -1) + syserr ("close(%d) failed", child2parent[1]); + if (close (1) == -1) syserr ("close(1) failed"); + if (dup2 (child2parent[1], 1) == -1) + syserr ("dup2(%d, 1) failed", child2parent[1]); + if (close (child2parent[1]) == -1) + syserr ("close(%d) failed", child2parent[1]); + } + + for (i = 0; i < argc - offsetcmd; i++) argv2[i] = argv[i + offsetcmd]; + argv2[argc - offsetcmd] = NULL; + if (execvp (argv2[0], argv2) == -1) + syserr ("execvp(\"%s\", ...) failed", argv2[0]); + break; + + default: + if (close (parent2child[0]) == -1) + syserr ("close(%d) failed", parent2child[0]); + if (bypass && close (child2parent[1]) == -1) + syserr ("close(%d) failed", child2parent[1]); + + done1 = 0; + done2 = !bypass; + while (!done1 || !done2) { + //printf ("==> done1 = %d, done2 = %d\n", done1, done2); + + fd_set set; + FD_ZERO (&set); + + if (!done1) FD_SET (0, &set); + if (!done2) FD_SET (child2parent[0], &set); + + int s = select ((!done2 ? child2parent[0] : 0) + 1, + &set, NULL, NULL, NULL); + + //printf ("==> s = %d\n", s); + + if (s == -1) syserr ("select failed"); + else if (s > 0) { + char buf[2048]; + ssize_t n; + + if (!done1 && FD_ISSET(0, &set)) { + if ((n = read (0, buf, sizeof (buf))) == -1) + syserr ("read failed"); + else if (n == 0) { + if (close (parent2child[1]) == -1) + syserr ("close(%d) failed", parent2child[1]); + done1 = 1; + } else if (n > 0) { + ewrite (parent2child[1], buf, n); + writestdout (0, buf, n); + } + } + + if (!done2 && FD_ISSET (child2parent[0], &set)) { + if ((n = read (child2parent[0], buf, sizeof (buf))) == -1) + syserr ("read failed"); + else if (n == 0) { + if (close (child2parent[0]) == -1) + syserr ("close(%d) failed", child2parent[0]); + done2 = 1; + } else if (n > 0) + writestdout (1, buf, n); + } + } + } + break; + } + + return 0; +} diff --git a/common/tools.cc b/common/tools.cc new file mode 100644 index 0000000..7dfb412 --- /dev/null +++ b/common/tools.cc @@ -0,0 +1,264 @@ +/* + * RTFL + * + * Copyright 2013-2015 Sebastian Geerken + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version; with the following exception: + * + * The copyright holders of RTFL give you permission to link this file + * statically or dynamically against all versions of the graphviz + * library, which are published by AT&T Corp. under one of the following + * licenses: + * + * - Common Public License version 1.0 as published by International + * Business Machines Corporation (IBM), or + * - Eclipse Public License version 1.0 as published by the Eclipse + * Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "tools.hh" + +#include +#include + +using namespace lout::object; +using namespace lout::container::untyped; + +namespace rtfl { + +namespace tools { + +const char *numSuffix (int n) +{ + if (n % 10 == 1 && n != 11) + return "st"; + else if (n % 10 == 2 && n != 12) + return "nd"; + else if (n % 10 == 3 && n != 13) + return "rd"; + else + return "th"; +} + +static const char + *const roman_I0[] = { "","I","II","III","IV","V","VI","VII","VIII","IX" }, + *const roman_I1[] = { "","X","XX","XXX","XL","L","LX","LXX","LXXX","XC" }, + *const roman_I2[] = { "","C","CC","CCC","CD","D","DC","DCC","DCCC","CM" }, + *const roman_I3[] = { "","M","MM","MMM","MMMM" }; + +void numToRoman (int num, char *buf, int buflen) +{ + int i3, i2, i1, i0; + + if (buflen <= 0) + return; + + i0 = num; + i1 = i0/10; i2 = i1/10; i3 = i2/10; + i0 %= 10; i1 %= 10; i2 %= 10; + if (num < 0 || i3 > 4) /* more than 4999 elements ? */ + snprintf(buf, buflen, "****"); + else + snprintf(buf, buflen, "%s%s%s%s", roman_I3[i3], roman_I2[i2], + roman_I1[i1], roman_I0[i0]); +} + +void syserr (const char *fmt, ...) +{ + va_list args; + va_start (args, fmt); + vfprintf (stderr, fmt, args); + fprintf (stderr, ": %s\n", strerror (errno)); + exit (1); +} + +// ---------------------------------------------------------------------- + +EquivalenceRelation::RefTarget::RefTarget (Object *object, bool ownerOfObject) +{ + this->object = object; + this->ownerOfObject = ownerOfObject; + refCount = 1; + allKeys = new HashSet (false); +} + +EquivalenceRelation::RefTarget::~RefTarget () +{ + if (ownerOfObject) + delete object; + delete allKeys; +} + +// ---------------------------------------------------------------------- + +EquivalenceRelation::RefSource::RefSource (Object *key, RefTarget *target) +{ + this->target = target; + this->key = key; + refTarget (); +} + +EquivalenceRelation::RefSource::~RefSource () +{ + unrefTarget (); +} + +void EquivalenceRelation::RefSource::refTarget () +{ + if (target) { + target->ref (); + target->putKey (key); + } +} + +void EquivalenceRelation::RefSource::unrefTarget () +{ + if (target) { + target->removeKey (key); + target->unref (); + target = NULL; + } +} + +void EquivalenceRelation::RefSource::setTarget (RefTarget *target) +{ + if (target != this->target) { + unrefTarget (); + this->target = target; + refTarget (); + } +} + +// ---------------------------------------------------------------------- + +EquivalenceRelation::EquivalenceRelation (bool ownerOfKeys, bool ownerOfValues) +{ + this->ownerOfKeys = ownerOfKeys; + this->ownerOfValues = ownerOfValues; + sources = new HashTable (ownerOfKeys, true); +} + +EquivalenceRelation::~EquivalenceRelation () +{ + delete sources; +} + +void EquivalenceRelation::put (Object *key, Object *value) +{ + assert (!contains(key)); + + RefTarget *target = new RefTarget (value, ownerOfValues); + RefSource *source = new RefSource (key, target); + target->unref (); + sources->put (key, source); +} + +Object *EquivalenceRelation::get (Object *key) const +{ + RefSource *source = (RefSource*) sources->get(key); + if (source) { + Object *object = source->getTarget()->getObject (); + return object; + } else + return NULL; +} + +bool EquivalenceRelation::contains (Object *key) const +{ + return sources->contains (key); +} + +Iterator EquivalenceRelation::iterator () +{ + return sources->iterator (); +} + +Iterator EquivalenceRelation::relatedIterator (Object *key) +{ + assert (contains (key)); + + RefSource *source = (RefSource*) sources->get (key); + RefTarget *target = source->getTarget (); + return target->getAllKeys()->iterator (); +} + +void EquivalenceRelation::relate (Object *key1, Object *key2) +{ + assert (contains(key1) && contains(key2)); + + RefSource *source1 = (RefSource*) sources->get (key1); + RefSource *source2 = (RefSource*) sources->get (key2); + if (source1->getTarget () != source2->getTarget ()) { + // The first value is kept, the second destroyed. The caller has + // to care about the order. + + // Consider all keys already related to `key2`; this is possible by + // iterating over `RefTarget::allKeys`. To avoid accessing freed memory, + // copy all keys to a new temporary set. + + HashSet target2Keys (false); + for (Iterator it = source2->getTarget()->getAllKeys()->iterator (); + it.hasNext (); ) + target2Keys.put (it.getNext ()); + + for (Iterator it = target2Keys.iterator (); it.hasNext (); ) { + RefSource *otherSource = (RefSource*) sources->get (it.getNext ()); + otherSource->setTarget (source1->getTarget ()); + } + } +} + +void EquivalenceRelation::putRelated (Object *oldKey, Object *newKey) +{ + assert (contains(oldKey) && !contains(newKey)); + + RefSource *oldSource = (RefSource*) sources->get (oldKey); + RefSource *newSource = new RefSource (newKey, oldSource->getTarget ()); + sources->put (newKey, newSource); +} + +void EquivalenceRelation::removeSimple (lout::object::Object *key) +{ + // The order is important: a simple "sources->remove (key)" will + // cause an access to freed memory. + + RefSource *source = (RefSource*) sources->get (key); + source->setTarget (NULL); // Will unref() the target. + sources->remove (key); +} + +void EquivalenceRelation::remove (Object *key) +{ + assert (contains (key)); + + RefSource *source = (RefSource*) sources->get (key); + RefTarget *target = source->getTarget (); + target->ref (); + + for (Iterator it = target->getAllKeys()->iterator (); it.hasNext (); ) { + Object *otherKey = it.getNext (); + + // The order is important: see removeSimple(). + RefSource *otherSource = (RefSource*) sources->get (otherKey); + otherSource->setTarget (NULL); // Will unref() the target. + sources->remove (otherKey); + } + + target->unref (); +} + + +} // namespace tools + +} // namespace dw diff --git a/common/tools.hh b/common/tools.hh new file mode 100644 index 0000000..af8e5e9 --- /dev/null +++ b/common/tools.hh @@ -0,0 +1,80 @@ +#ifndef __COMMON_TOOLS_HH__ +#define __COMMON_TOOLS_HH__ + +#include "lout/object.hh" +#include "lout/container.hh" + +namespace rtfl { + +namespace tools { + +const char *numSuffix (int n); +void numToRoman (int num, char *buf, int buflen); +void syserr (const char *fmt, ...); + +class EquivalenceRelation: public lout::object::Object { +private: + class RefTarget: public lout::object::Object { + private: + bool ownerOfObject; + int refCount; + lout::object::Object *object; + lout::container::untyped::HashSet *allKeys; + + public: + RefTarget (lout::object::Object *object, bool ownerOfObject); + ~RefTarget (); + + inline lout::object::Object *getObject () { return object; } + inline void ref () { refCount++; } + inline void unref () { if (--refCount == 0) delete this; } + + inline lout::container::untyped::HashSet *getAllKeys () + { return allKeys; } + inline void putKey (Object *key) { allKeys->put (key); } + inline void removeKey (Object *key) { allKeys->remove (key); } + }; + + + class RefSource: public lout::object::Object { + RefTarget *target; + lout::object::Object *key; + + void refTarget (); + void unrefTarget (); + + public: + RefSource (lout::object::Object *key, RefTarget *target); + ~RefSource (); + + inline RefTarget *getTarget () { return target; } + void setTarget (RefTarget *target); + }; + + bool ownerOfKeys, ownerOfValues; + lout::container::untyped::HashTable *sources; + + lout::container::untyped::HashSet *initSet (lout::object::Object *o); + + public: + EquivalenceRelation (bool ownerOfKeys, bool ownerOfValues); + ~EquivalenceRelation (); + + void put (lout::object::Object *key, lout::object::Object *value); + lout::object::Object *get (lout::object::Object *key) const; + bool contains (lout::object::Object *key) const; + lout::container::untyped::Iterator iterator (); + lout::container::untyped::Iterator relatedIterator (Object *key); + + void relate (lout::object::Object *key1, lout::object::Object *key2); + void putRelated (lout::object::Object *oldKey, lout::object::Object *newKey); + + void removeSimple (lout::object::Object *key); + void remove (lout::object::Object *key); +}; + +} // namespace tools + +} // namespace rtfl + +#endif // __COMMON_TOOLS_HH__ diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..86b30b9 --- /dev/null +++ b/configure.ac @@ -0,0 +1,299 @@ +dnl Process this file with aclocal, autoconf and automake. + +AC_INIT([rtfl], [0.1.1]) + +dnl Detect the canonical target build environment +AC_CANONICAL_TARGET + +AM_INIT_AUTOMAKE +AC_CONFIG_SRCDIR([objects/rtfl_objview.cc]) +AC_CONFIG_HEADERS([config.h]) +AC_CONFIG_MACRO_DIR([m4]) + +dnl Options + +JAVA_HOME="" +WARN_GRAPH=no + +AC_ARG_ENABLE(efence, [ --enable-efence Try to compile and run with Electric Fence], + , enable_efence=no) +AC_ARG_ENABLE(gprof, [ --enable-gprof Try to compile and run with profiling enabled], + , enable_gprof=no) +AC_ARG_ENABLE(insure, [ --enable-insure Try to compile and run with Insure++], + , enable_insure=no) +AC_ARG_ENABLE(rtfl, [ --enable-rtfl Build with RTFL messages (for debugging rendering)]) +AC_ARG_ENABLE(graph2, [ --disable-graph2 Use simple Graph widget instead of Graph2]) +AC_ARG_ENABLE(java, [ --disable-java Build RTFL java agent]) +AC_ARG_WITH(java-home,[ --with-java-home=DIR Specify where to find the JDK], JAVA_HOME=$withval) + +AC_PROG_CC +AC_PROG_CXX +AC_PROG_CPP +AC_PROG_LIBTOOL + +dnl ---------------------------- +dnl Check our char and int types +dnl ---------------------------- +dnl +AC_CHECK_SIZEOF(char) +AC_CHECK_SIZEOF(short) +AC_CHECK_SIZEOF(long) +AC_CHECK_SIZEOF(int) +AC_CHECK_SIZEOF(void *) + +AC_TYPE_INT16_T +AC_TYPE_UINT16_T +AC_TYPE_INT32_T +AC_TYPE_UINT32_T + +dnl ----------------------------------------------------------------- +dnl Check for absolute path of working directory. +dnl This is needed for RTFL, to get full the full paths of the source +dnl file names +dnl ----------------------------------------------------------------- +dnl +BASE_CUR_WORKING_DIR=`pwd` + +dnl -------------------------------------- +dnl Check whether to add /usr/local or not +dnl (this is somewhat a religious problem) +dnl -------------------------------------- +dnl +if test "`$CPP -v < /dev/null 2>&1 | grep '/usr/local/include' 2>&1`" = ""; then + CPPFLAGS="$CPPFLAGS -I/usr/local/include" + LDFLAGS="$LDFLAGS -L/usr/local/lib" +fi + +dnl ---------------------- +dnl Test for FLTK 1.3 library +dnl ---------------------- +dnl +dnl For debugging and to be user friendly +AC_MSG_CHECKING([FLTK 1.3]) +fltk_version="`fltk-config --version 2>/dev/null`" +case $fltk_version in + 1.3.*) AC_MSG_RESULT(yes) + LIBFLTK_CXXFLAGS=`fltk-config --cxxflags` + LIBFLTK_LIBS=`fltk-config --ldflags`;; + ?*) AC_MSG_RESULT(no) + AC_MSG_ERROR(FLTK 1.3 required; version found: $fltk_version);; + *) AC_MSG_RESULT(no) + AC_MSG_ERROR(FLTK 1.3 required; fltk-config not found) +esac + +dnl ------------------------- +dnl Test for Graphviz library +dnl ------------------------- +AC_MSG_CHECKING([Graphviz library >= 2.38.0]) +AC_RUN_IFELSE([AC_LANG_PROGRAM([[ +#include +#include +#include + +static int version_cmp (const char *v1, const char *v2) +{ + const char *s1 = v1, *s2 = v2; + while (*s1 && *s2) { + if (isdigit (*s1) && isdigit (*s2)) { + char buf1[10], buf2[10]; + int n1 = 0, n2 = 0; + + while (isdigit (*s1)) { + if (n1 < 9) buf1[n1++] = *s1; + s1++; + } + + while (isdigit (*s2)) { + if (n2 < 9) buf2[n2++] = *s2; + s2++; + } + + buf1[n1] = buf2[n2] = 0; + int c = atoi (buf1) - atoi (buf2); + if (c != 0) + return c; + } else { + if (*s1 != *s2) + return *s1 - *s2; + s1++; + s2++; + } + } + + return *s1 - *s2; +} +]],[[ +if(version_cmp (PACKAGE_VERSION, "2.38.0") >= 0) + return 0; +else + return 1; +]])], +[AC_MSG_RESULT(yes) + HAS_GRAPHVIZ=yes + AM_CONDITIONAL([HAS_GRAPHVIZ], [true]) + GRAPHVIZ_LIBS="-lcgraph -lgvc"], +[AC_MSG_RESULT(no) + HAS_GRAPHVIZ=no + AM_CONDITIONAL([HAS_GRAPHVIZ], [false])] +[AC_MSG_RESULT(no) + HAS_GRAPHVIZ=no + AM_CONDITIONAL([HAS_GRAPHVIZ], [false]) + AC_MSG_WARN([Testing Graphviz library not possible when cross-compiling.])]) + +dnl ------------ +dnl Test for JDK +dnl ------------ +AC_MSG_CHECKING([JDK]) +if test "$enable_java" = no ; then + AC_MSG_RESULT(disabled) + AM_CONDITIONAL([HAS_JAVA], [false]) + JAVA_HOME="" +else + # Follow symbolic links of javac in $PATH. + if test -z "$JAVA_HOME"; then + if which javac >/dev/null; then + javac=$(which javac) + while test -n "$javac"; do + nextjavac=$(readlink $javac) + if test -z "$nextjavac"; then + JAVA_HOME=${javac%/bin/javac} + if test $JAVA_HOME = $javac; then + AC_MSG_RESULT(no) + AC_MSG_WARN([Javac found at $javac, which is strange.]) + AM_CONDITIONAL([HAS_JAVA], [false]) + JAVA_HOME="" + fi + fi + javac=$nextjavac + done + else + AC_MSG_RESULT(no) + AM_CONDITIONAL([HAS_JAVA], [false]) + JAVA_HOME="" + fi + fi +fi + +if test -n "$JAVA_HOME"; then + if test '!' -e $JAVA_HOME/include/jvmti.h; then + AC_MSG_RESULT(no) + AC_MSG_WARN([$JAVA_HOME/include/jvmti.h not found: JDK not usable.]) + AM_CONDITIONAL([HAS_JAVA], [false]) + JAVA_HOME="" + else + AC_MSG_RESULT(yes) + AM_CONDITIONAL([HAS_JAVA], [true]) + # Hopefully only simple directory names ... + JAVA_CFLAGS="-I$JAVA_HOME/include" + if test '!' -e $JAVA_HOME/include/jni_md.h; then + # For some JDKs, "jni_md.h" is only in a OS specific directory. + JAVA_CFLAGS=$JAVA_CFLAGS" -I"`find $JAVA_HOME/include/ -name jni_md.h \ + | head -1 | xargs dirname` + fi + fi +fi + +dnl -------------------- +dnl Command line options +dnl -------------------- +dnl +if test "x$enable_efence" = "xyes" ; then + LIBS="-lefence $LIBS" +fi +if test "x$enable_gprof" = "xyes" ; then + CXXFLAGS="$CXXFLAGS -pg" +fi +if test "x$enable_insure" = "xyes" ; then + CC="insure -Zoi \"compiler $CC\"" + LIBS="$LIBS -lstdc++-2-libc6.1-1-2.9.0" +fi +if test "x$enable_rtfl" = "xyes" ; then + CXXFLAGS="$CXXFLAGS -DDBG_RTFL" +fi +if test "$enable_graph2" = no ; then + AM_CONDITIONAL([USE_GRAPH2], [false]) +else + if test $HAS_GRAPHVIZ = yes ; then + AM_CONDITIONAL([USE_GRAPH2], [true]) + CXXFLAGS="$CXXFLAGS -DUSE_GRAPH2" + else + AM_CONDITIONAL([USE_GRAPH2], [false]) + AC_MSG_WARN([Graphviz library >= 2.38.0 not found. Graph2 is not enabled.]) + WARN_GRAPH=yes + fi +fi + +dnl ----------------------- +dnl Checks for header files +dnl ----------------------- +dnl +AC_CHECK_HEADERS(fcntl.h unistd.h sys/uio.h) + +dnl -------------------------- +dnl Check for compiler options +dnl -------------------------- +dnl +if eval "test x$GCC = xyes"; then + if test "`echo $CFLAGS | grep '\-D_REENTRANT' 2> /dev/null`" = ""; then + CFLAGS="$CFLAGS -D_REENTRANT" + fi + if test "`echo $CFLAGS | grep '\-D_THREAD_SAFE' 2> /dev/null`" = ""; then + CFLAGS="$CFLAGS -D_THREAD_SAFE" + fi + if test "`echo $CFLAGS | grep '\-Wall' 2> /dev/null`" = ""; then + CFLAGS="$CFLAGS -Wall" + fi + if test "`echo $CFLAGS | grep -e '-W ' -e '-W$' 2> /dev/null`" = ""; then + CFLAGS="$CFLAGS -W" + fi + if test "`echo $CFLAGS | grep '\-Wno-unused-parameter' 2> /dev/null`" = ""; then + CFLAGS="$CFLAGS -Wno-unused-parameter" + fi + if test "`echo $CFLAGS | grep '\-Waggregate-return' 2> /dev/null`" = ""; then + CFLAGS="$CFLAGS -Waggregate-return" + fi +fi + +dnl ----------- +dnl CXX options +dnl ----------- +dnl +if eval "test x$GCC = xyes"; then + CXXFLAGS="$CXXFLAGS -Wall -W -Wno-unused-parameter -fno-rtti -fno-exceptions" +fi + +AC_SUBST(BASE_CUR_WORKING_DIR) +AC_SUBST(LIBFLTK_CXXFLAGS) +AC_SUBST(GRAPHVIZ_LIBS) +AC_SUBST(LIBFLTK_LIBS) +AC_SUBST(JAVA_HOME) +AC_SUBST(JAVA_CFLAGS) +AC_SUBST(datadir) + +AC_CONFIG_FILES([ + Makefile + lout/Makefile + common/Makefile + dw/Makefile + dwr/Makefile + objects/Makefile + tests/Makefile + scripts/Makefile + doc/Makefile + java/Makefile +]) + +AC_OUTPUT + +dnl -------------------------- +dnl Some more notable messages +dnl -------------------------- + +if test "$WARN_GRAPH" = yes; then + echo '----------------------------------------------------------------------' + echo 'NOTE: No Graphviz library >= 2.38.0 was found, and you did not' + echo 'explicitly disable the Graph2 widget, which depends on Graphviz. It is' + echo 'recommend to install the Graphviz library and thus enable Graph2 which' + echo 'greatly improves rendering of rtfl-objview. See README for details.' + echo '----------------------------------------------------------------------' +fi diff --git a/debug_rtfl.hh b/debug_rtfl.hh new file mode 100644 index 0000000..a4b5bca --- /dev/null +++ b/debug_rtfl.hh @@ -0,0 +1,461 @@ +// WARNING: This file has been generated. Do not edit! + +/* + * This file is part of RTFL, see + * for details. + * + * This file (but not RTFL itself) is in the public domain, since it is only a + * simple implementation of a protocol, containing nothing more than trivial + * work. However, it would be nice to keep this notice, along with the URL + * above. + * + * ---------------------------------------------------------------------------- + * + * Defines macros for printing RTFL commands. See documentation for detail + * (online at ). These macros are only + * active, when the pre-processor variable DBG_RTFL is defined. If not, + * alternatives are defined, which have no effect. + * + * This variant assumes that __FILE__ is only the base of the source file name, + * so, to get the full path, CUR_WORKING_DIR has to be defined. See RTFL + * documentation for more details. + */ + +#ifndef __DEBUG_RTFL_HH__ +#define __DEBUG_RTFL_HH__ + +#ifdef DBG_RTFL + +// ======================================= +// Used by all modules +// ======================================= + +#include +#include +#include +#include + +#define DBG_IF_RTFL if(1) + +#define STMT_START do +#define STMT_END while (0) + +// Prints an RTFL message to stdout. "fmt" contains simple format +// characters how to deal with the additional arguments (no "%" +// preceeding, as in printf) or "q" (which additionally +// (double-)quotes quotation marks) or "c" (short for "#%06x" and used +// for colors), or other characters, which are simply printed. No +// quoting: this function cannot be used to print the characters "d", +// "p", "s" and "q" directly. + +inline void rtfl_print (const char *module, const char *version, + const char *file, int line, int processId, + const char *fmt, ...) +{ + // "\n" at the beginning just in case that the previous line is not + // finished yet. + printf ("\n[rtfl-%s-%s]%s:%d:%d:", module, version, file, line, processId); + + va_list args; + va_start (args, fmt); + + for (int i = 0; fmt[i]; i++) { + int n; + void *p; + char *s; + + switch (fmt[i]) { + case 'd': + n = va_arg(args, int); + printf ("%d", n); + break; + + case 'p': + p = va_arg(args, void*); + printf ("%p", p); + break; + + case 's': + s = va_arg (args, char*); + for (int j = 0; s[j]; j++) { + if (s[j] == ':' || s[j] == '\\') + putchar ('\\'); + putchar (s[j]); + } + break; + + case 'q': + s = va_arg (args, char*); + for (int j = 0; s[j]; j++) { + if (s[j] == ':' || s[j] == '\\') + putchar ('\\'); + else if (s[j] == '\"') + printf ("\\\\"); // a quoted quoting character + putchar (s[j]); + } + break; + + case 'c': + n = va_arg(args, int); + printf ("#%06x", n); + break; + + default: + putchar (fmt[i]); + break; + } + } + + va_end (args); + + putchar ('\n'); + fflush (stdout); +} + +#define RTFL_PRINT(module, version, cmd, fmt, ...) \ + rtfl_print (module, version, CUR_WORKING_DIR "/" __FILE__, __LINE__, \ + getpid (), "s:" fmt, cmd, __VA_ARGS__) + + +// ================================== +// General module +// ================================== + +#define RTFL_GEN_VERSION "1.0" + +#define RTFL_GEN_PRINT(cmd, fmt, ...) \ + RTFL_PRINT ("gen", RTFL_GEN_VERSION, cmd, fmt, __VA_ARGS__) + +#define DBG_GEN_TIME() \ + STMT_START { \ + struct timeval tv; \ + gettimeofday(&tv, NULL); \ + char buf[32]; \ + snprintf (buf, sizeof (buf), "%ld%06ld", tv.tv_sec, tv.tv_usec); \ + RTFL_GEN_PRINT ("time", "s", buf); \ + } STMT_END + + +// ================================== +// Objects module +// ================================== + +#define RTFL_OBJ_VERSION "1.0" + +#define RTFL_OBJ_PRINT(cmd, fmt, ...) \ + RTFL_PRINT ("obj", RTFL_OBJ_VERSION, cmd, fmt, __VA_ARGS__) + +#define DBG_OBJ_MSG(aspect, prio, msg) \ + DBG_OBJ_MSG_O (aspect, prio, this, msg) + +#define DBG_OBJ_MSG_O(aspect, prio, obj, msg) \ + RTFL_OBJ_PRINT ("msg", "p:s:d:s", obj, aspect, prio, msg) + +#define DBG_OBJ_MSGF(aspect, prio, fmt, ...) \ + STMT_START { \ + char msg[256]; \ + snprintf (msg, sizeof (msg), fmt, __VA_ARGS__); \ + DBG_OBJ_MSG (aspect, prio, msg); \ + } STMT_END + +#define DBG_OBJ_MSGF_O(aspect, prio, obj, fmt, ...) \ + STMT_START { \ + char msg[256]; \ + snprintf (msg, sizeof (msg), fmt, __VA_ARGS__); \ + DBG_OBJ_MSG_O (aspect, prio, obj, msg); \ + } STMT_END + +#define DBG_OBJ_MARK(aspect, prio, mark) \ + DBG_OBJ_MARK_O (aspect, prio, this, mark) + +#define DBG_OBJ_MARK_O(aspect, prio, obj, mark) \ + RTFL_OBJ_PRINT ("mark", "p:s:d:s", obj, aspect, prio, mark) + +#define DBG_OBJ_MARKF(aspect, prio, fmt, ...) \ + STMT_START { \ + char mark[256]; \ + snprintf (mark, sizeof (mark), fmt, __VA_ARGS__); \ + DBG_OBJ_MARK (aspect, prio, mark); \ + } STMT_END + +#define DBG_OBJ_MARKF_O(aspect, prio, obj, fmt, ...) \ + STMT_START { \ + char mark[256]; \ + snprintf (mark, sizeof (mark), fmt, __VA_ARGS__); \ + DBG_OBJ_MARK_O (aspect, prio, obj, mark); \ + } STMT_END + +#define DBG_OBJ_MSG_START() \ + DBG_OBJ_MSG_START_O (this) + +#define DBG_OBJ_MSG_START_O(obj) \ + RTFL_OBJ_PRINT ("msg-start", "p", obj) + +#define DBG_OBJ_MSG_END() \ + DBG_OBJ_MSG_END_O (this) + +#define DBG_OBJ_MSG_END_O(obj) \ + RTFL_OBJ_PRINT ("msg-end", "p", obj) + +#define DBG_OBJ_ENTER0(aspect, prio, funname) \ + DBG_OBJ_ENTER0_O (aspect, prio, this, funname) + +#define DBG_OBJ_ENTER0_O(aspect, prio, obj, funname) \ + RTFL_OBJ_PRINT ("enter", "p:s:d:s:", obj, aspect, prio, funname); + +#define DBG_OBJ_ENTER(aspect, prio, funname, fmt, ...) \ + STMT_START { \ + char args[256]; \ + snprintf (args, sizeof (args), fmt, __VA_ARGS__); \ + RTFL_OBJ_PRINT ("enter", "p:s:d:s:s", this, aspect, prio, funname, \ + args); \ + } STMT_END + +#define DBG_OBJ_ENTER_O(aspect, prio, obj, funname, fmt, ...) \ + STMT_START { \ + char args[256]; \ + snprintf (args, sizeof (args), fmt, __VA_ARGS__); \ + RTFL_OBJ_PRINT ("enter", "p:s:d:s:s", obj, aspect, prio, funname, \ + args); \ + } STMT_END + +#define DBG_OBJ_LEAVE() \ + DBG_OBJ_LEAVE_O (this) + +#define DBG_OBJ_LEAVE_O(obj) \ + RTFL_OBJ_PRINT ("leave", "p", obj); + +#define DBG_OBJ_LEAVE_VAL(fmt, ...) \ + STMT_START { \ + char vals[256]; \ + snprintf (vals, sizeof (vals), fmt, __VA_ARGS__); \ + RTFL_OBJ_PRINT ("leave", "p:s", this, vals); \ + } STMT_END + +#define DBG_OBJ_LEAVE_VAL_O(obj, fmt, ...) \ + STMT_START { \ + char vals[256]; \ + snprintf (vals, sizeof (vals), fmt, __VA_ARGS__); \ + RTFL_OBJ_PRINT ("leave", "p:s", obj, vals); \ + } STMT_END + +#define DBG_OBJ_LEAVE_VAL0(val) \ + DBG_OBJ_LEAVE_VAL0_O (this, val) + +#define DBG_OBJ_LEAVE_VAL0_O(obj, val) \ + RTFL_OBJ_PRINT ("leave", "p:s:", obj, val) + +#define DBG_OBJ_CREATE(klass) \ + DBG_OBJ_CREATE_O (this, klass) + +#define DBG_OBJ_CREATE_O(obj, klass) \ + RTFL_OBJ_PRINT ("create", "p:s", obj, klass); + +#define DBG_OBJ_DELETE() \ + DBG_OBJ_DELETE_O (this) + +#define DBG_OBJ_DELETE_O(obj) \ + RTFL_OBJ_PRINT ("delete", "p", obj); + +#define DBG_OBJ_BASECLASS(klass) \ + RTFL_OBJ_PRINT ("ident", "p:p", this, (klass*)this); + +#define DBG_OBJ_ASSOC(parent, child) \ + RTFL_OBJ_PRINT ("assoc", "p:p", parent, child); \ + +#define DBG_OBJ_ASSOC_PARENT(parent) \ + DBG_OBJ_ASSOC (parent, this); + +#define DBG_OBJ_ASSOC_CHILD(child) \ + DBG_OBJ_ASSOC (this, child); + +#define DBG_OBJ_SET_NUM(var, val) \ + DBG_OBJ_SET_NUM_O (this, var, val) + +#define DBG_OBJ_SET_NUM_O(obj, var, val) \ + RTFL_OBJ_PRINT ("set", "p:s:d", obj, var, val) + +#define DBG_OBJ_SET_SYM(var, val) \ + DBG_OBJ_SET_SYM_O (this, var, val) + +#define DBG_OBJ_SET_SYM_O(obj, var, val) \ + RTFL_OBJ_PRINT ("set", "p:s:s", obj, var, val) + +#define DBG_OBJ_SET_BOOL(var, val) \ + DBG_OBJ_SET_BOOL_O (this, var, val) + +#define DBG_OBJ_SET_BOOL_O(obj, var, val) \ + RTFL_OBJ_PRINT ("set", "p:s:s", obj, var, (val) ? "true" : "false") + +#define DBG_OBJ_SET_STR(var, val) \ + DBG_OBJ_SET_STR_O (this, var, val) + +#define DBG_OBJ_SET_STR_O(obj, var, val) \ + RTFL_OBJ_PRINT ("set", "p:s:\"q\"", obj, var, val) + +#define DBG_OBJ_SET_PTR(var, val) \ + DBG_OBJ_SET_PTR_O (this, var, val) + +#define DBG_OBJ_SET_PTR_O(obj, var, val) \ + RTFL_OBJ_PRINT ("set", "p:s:p", obj, var, val) + +#define DBG_OBJ_SET_COL(var, val) \ + DBG_OBJ_SET_COL_O (this, var, val) + +#define DBG_OBJ_SET_COL_O(obj, var, val) \ + RTFL_OBJ_PRINT ("set", "p:s:c", obj, var, val) + +#define DBG_OBJ_ARRSET_NUM(var, ind, val) \ + DBG_OBJ_ARRSET_NUM_O (this, var, ind, val) + +#define DBG_OBJ_ARRSET_NUM_O(obj, var, ind, val) \ + RTFL_OBJ_PRINT ("set", "p:s.d:d", obj, var, ind, val) + +#define DBG_OBJ_ARRSET_SYM(var, ind, val) \ + DBG_OBJ_ARRSET_SYM_O (this, var, ind, val) + +#define DBG_OBJ_ARRSET_SYM_O(obj, var, ind, val) \ + RTFL_OBJ_PRINT ("set", "p:s.d:s", obj, var, ind, val) + +#define DBG_OBJ_ARRSET_BOOL(var, ind, val) \ + DBG_OBJ_ARRSET_BOOL_O (this, var, ind, val) + +#define DBG_OBJ_ARRSET_BOOL_O(obj, var, ind, val) \ + RTFL_OBJ_PRINT ("set", "p:s.d:s", obj, var, ind, (val) ? "true" : "false") + +#define DBG_OBJ_ARRSET_STR(var, ind, val) \ + DBG_OBJ_ARRSET_STR_O (this, var, ind, val) + +#define DBG_OBJ_ARRSET_STR_O(obj, var, ind, val) \ + RTFL_OBJ_PRINT ("set", "p:s.d:\"q\"", obj, var, ind, val) + +#define DBG_OBJ_ARRSET_PTR(var, ind, val) \ + DBG_OBJ_ARRSET_PTR_O (this, var, ind, val) + +#define DBG_OBJ_ARRSET_PTR_O(obj, var, ind, val) \ + RTFL_OBJ_PRINT ("set", "p:s.d:p", obj, var, ind, val) + +#define DBG_OBJ_ARRSET_COL(var, ind, val) \ + DBG_OBJ_ARRSET_COL_O (this, var, ind, val) + +#define DBG_OBJ_ARRSET_COL_O(obj, var, ind, val) \ + RTFL_OBJ_PRINT ("set", "p:s.d:c", obj, var, ind, val) + +#define DBG_OBJ_ARRATTRSET_NUM(var, ind, attr, val) \ + DBG_OBJ_ARRATTRSET_NUM_O (this, var, ind, attr, val) + +#define DBG_OBJ_ARRATTRSET_NUM_O(obj, var, ind, attr, val) \ + RTFL_OBJ_PRINT ("set", "p:s.d.s:d", obj, var, ind, attr, val) + +#define DBG_OBJ_ARRATTRSET_SYM(var, ind, attr, val) \ + DBG_OBJ_ARRATTRSET_SYM_O (this, var, ind, attr, val) + +#define DBG_OBJ_ARRATTRSET_SYM_O(obj, var, ind, attr, val) \ + RTFL_OBJ_PRINT ("set", "p:s.d.s:s", obj, var, ind, attr, val) + +#define DBG_OBJ_ARRATTRSET_BOOL(var, ind, attr, val) \ + DBG_OBJ_ARRATTRSET_BOOL_O (this, var, ind, attr, val) + +#define DBG_OBJ_ARRATTRSET_BOOL_O(obj, var, ind, attr, val) \ + RTFL_OBJ_PRINT ("set", "p:s.d.s:s", obj, var, ind, attr, \ + (val) ? "true" : "false") + +#define DBG_OBJ_ARRATTRSET_STR(var, ind, attr, val) \ + DBG_OBJ_ARRATTRSET_STR_O (this, var, ind, attr, val) + +#define DBG_OBJ_ARRATTRSET_STR_O(obj, var, ind, attr, val) \ + RTFL_OBJ_PRINT ("set", "p:s.d.s:\"q\"", obj, var, ind, attr, val) + +#define DBG_OBJ_ARRATTRSET_PTR(var, ind, attr, val) \ + DBG_OBJ_ARRATTRSET_PTR_O (this, var, ind, attr, val) + +#define DBG_OBJ_ARRATTRSET_PTR_O(obj, var, ind, attr, val) \ + RTFL_OBJ_PRINT ("set", "p:s.d.s:p", obj, var, ind, attr, val) + +#define DBG_OBJ_ARRATTRSET_COL(var, ind, attr, val) \ + DBG_OBJ_ARRATTRSET_COL_O (this, var, ind, attr, val) + +#define DBG_OBJ_ARRATTRSET_COL_O(obj, var, ind, attr, val) \ + RTFL_OBJ_PRINT ("set", "p:s.d.s:c", obj, var, ind, attr, val) + +#define DBG_OBJ_CLASS_COLOR(klass, color) \ + RTFL_OBJ_PRINT ("class-color", "s:s", klass, color) + +#else /* DBG_RTFL */ + +#define STMT_NOP do { } while (0) + +#define DBG_IF_RTFL if(0) + +#define DBG_GEN_TIME() STMT_NOP +#define DBG_OBJ_MSG(aspect, prio, msg) STMT_NOP +#define DBG_OBJ_MSG_O(aspect, prio, obj, msg) STMT_NOP +#define DBG_OBJ_MSGF(aspect, prio, fmt, ...) STMT_NOP +#define DBG_OBJ_MSGF_O(aspect, prio, obj, fmt, ...) STMT_NOP +#define DBG_OBJ_MARK(aspect, prio, mark) STMT_NOP +#define DBG_OBJ_MARK_O(aspect, prio, obj, mark) STMT_NOP +#define DBG_OBJ_MARKF(aspect, prio, fmt, ...) STMT_NOP +#define DBG_OBJ_MARKF_O(aspect, prio, obj, fmt, ...) STMT_NOP +#define DBG_OBJ_MSG_START() STMT_NOP +#define DBG_OBJ_MSG_START_O(obj) STMT_NOP +#define DBG_OBJ_MSG_END() STMT_NOP +#define DBG_OBJ_MSG_END_O(obj) STMT_NOP +#define DBG_OBJ_ENTER0(aspect, prio, funname) STMT_NOP +#define DBG_OBJ_ENTER0_O(aspect, prio, obj, funname) STMT_NOP +#define DBG_OBJ_ENTER(aspect, prio, funname, fmt, ...) STMT_NOP +#define DBG_OBJ_ENTER_O(aspect, prio, obj, funname, fmt, ...) STMT_NOP +#define DBG_OBJ_LEAVE() STMT_NOP +#define DBG_OBJ_LEAVE_O(obj) STMT_NOP +#define DBG_OBJ_LEAVE_VAL(fmt, ...) STMT_NOP +#define DBG_OBJ_LEAVE_VAL_O(obj, fmt, ...) STMT_NOP +#define DBG_OBJ_LEAVE_VAL0(val) STMT_NOP +#define DBG_OBJ_LEAVE_VAL0_O(obj, val) STMT_NOP +#define DBG_OBJ_CREATE(klass) STMT_NOP +#define DBG_OBJ_CREATE_O(obj, klass) STMT_NOP +#define DBG_OBJ_DELETE() STMT_NOP +#define DBG_OBJ_DELETE_O(obj) STMT_NOP +#define DBG_OBJ_BASECLASS(klass) STMT_NOP +#define DBG_OBJ_ASSOC(parent, child) STMT_NOP +#define DBG_OBJ_ASSOC_PARENT(parent) STMT_NOP +#define DBG_OBJ_ASSOC_CHILD(child) STMT_NOP +#define DBG_OBJ_SET_NUM(var, val) STMT_NOP +#define DBG_OBJ_SET_NUM_O(obj, var, val) STMT_NOP +#define DBG_OBJ_SET_SYM(var, val) STMT_NOP +#define DBG_OBJ_SET_SYM_O(obj, var, val) STMT_NOP +#define DBG_OBJ_SET_BOOL(var, val) STMT_NOP +#define DBG_OBJ_SET_BOOL_O(obj, var, val) STMT_NOP +#define DBG_OBJ_SET_STR(var, val) STMT_NOP +#define DBG_OBJ_SET_STR_O(obj, var, val) STMT_NOP +#define DBG_OBJ_SET_PTR(var, val) STMT_NOP +#define DBG_OBJ_SET_PTR_O(obj, var, val) STMT_NOP +#define DBG_OBJ_SET_COL(var, val) STMT_NOP +#define DBG_OBJ_SET_COL_O(obj, var, val) STMT_NOP +#define DBG_OBJ_ARRSET_NUM(var, ind, val) STMT_NOP +#define DBG_OBJ_ARRSET_NUM_O(obj, var, ind, val) STMT_NOP +#define DBG_OBJ_ARRSET_SYM(var, ind, val) STMT_NOP +#define DBG_OBJ_ARRSET_SYM_O(obj, var, ind, val) STMT_NOP +#define DBG_OBJ_ARRSET_BOOL(var, ind, val) STMT_NOP +#define DBG_OBJ_ARRSET_BOOL_O(obj, var, ind, val) STMT_NOP +#define DBG_OBJ_ARRSET_STR(var, ind, val) STMT_NOP +#define DBG_OBJ_ARRSET_STR_O(obj, var, ind, val) STMT_NOP +#define DBG_OBJ_ARRSET_PTR(var, ind, val) STMT_NOP +#define DBG_OBJ_ARRSET_PTR_O(obj, var, ind, val) STMT_NOP +#define DBG_OBJ_ARRSET_COL(var, ind, val) STMT_NOP +#define DBG_OBJ_ARRSET_COL_O(obj, var, ind, val) STMT_NOP +#define DBG_OBJ_ARRATTRSET_NUM(var, ind, attr, val) STMT_NOP +#define DBG_OBJ_ARRATTRSET_NUM_O(obj, var, ind, attr, val) STMT_NOP +#define DBG_OBJ_ARRATTRSET_SYM(var, ind, attr, val) STMT_NOP +#define DBG_OBJ_ARRATTRSET_SYM_O(obj, var, ind, attr, val) STMT_NOP +#define DBG_OBJ_ARRATTRSET_BOOL(var, ind, attr, val) STMT_NOP +#define DBG_OBJ_ARRATTRSET_BOOL_O(obj, var, ind, attr, val) STMT_NOP +#define DBG_OBJ_ARRATTRSET_STR(var, ind, attr, val) STMT_NOP +#define DBG_OBJ_ARRATTRSET_STR_O(obj, var, ind, attr, val) STMT_NOP +#define DBG_OBJ_ARRATTRSET_PTR(var, ind, attr, val) STMT_NOP +#define DBG_OBJ_ARRATTRSET_PTR_O(obj, var, ind, attr, val) STMT_NOP +#define DBG_OBJ_ARRATTRSET_COL(var, ind, attr, val) STMT_NOP +#define DBG_OBJ_ARRATTRSET_COL_O(obj, var, ind, attr, val) STMT_NOP +#define DBG_OBJ_CLASS_COLOR(klass, color) STMT_NOP + +#endif /* DBG_RTFL */ + +#endif /* __DEBUG_RTFL_HH__ */ diff --git a/debug_rtfl.hh.in b/debug_rtfl.hh.in new file mode 100644 index 0000000..9d9fd55 --- /dev/null +++ b/debug_rtfl.hh.in @@ -0,0 +1,310 @@ +/* + * This file is part of RTFL, see + * for details. + * + * This file (but not RTFL itself) is in the public domain, since it is only a + * simple implementation of a protocol, containing nothing more than trivial + * work. However, it would be nice to keep this notice, along with the URL + * above. + * + * ---------------------------------------------------------------------------- + * + * Defines macros for printing RTFL commands. See documentation for detail + * (online at ). These macros are only + * active, when the pre-processor variable DBG_RTFL is defined. If not, + * alternatives are defined, which have no effect. + * + * This variant assumes that __FILE__ is only the base of the source file name, + * so, to get the full path, CUR_WORKING_DIR has to be defined. See RTFL + * documentation for more details. + */ + +#ifndef __DEBUG_RTFL_HH__ +#define __DEBUG_RTFL_HH__ + +#ifdef DBG_RTFL + +// ======================================= +// Used by all modules +// ======================================= + +#include +#include +#include +#include + +#define DBG_IF_RTFL if(1) + +#define STMT_START do +#define STMT_END while (0) + +// Prints an RTFL message to stdout. "fmt" contains simple format +// characters how to deal with the additional arguments (no "%" +// preceeding, as in printf) or "q" (which additionally +// (double-)quotes quotation marks) or "c" (short for "#%06x" and used +// for colors), or other characters, which are simply printed. No +// quoting: this function cannot be used to print the characters "d", +// "p", "s" and "q" directly. + +inline void rtfl_print (const char *module, const char *version, + const char *file, int line, const char *fmt, ...) +{ + // "\n" at the beginning just in case that the previous line is not + // finished yet. + printf ("\n[rtfl-%s-%s]%s:%d:%d:", module, version, file, line, getpid ()); + + va_list args; + va_start (args, fmt); + + for (int i = 0; fmt[i]; i++) { + int n; + void *p; + char *s; + + switch (fmt[i]) { + case 'd': + n = va_arg(args, int); + printf ("%d", n); + break; + + case 'p': + p = va_arg(args, void*); + printf ("%p", p); + break; + + case 's': + s = va_arg (args, char*); + for (int j = 0; s[j]; j++) { + if (s[j] == ':' || s[j] == '\\') + putchar ('\\'); + putchar (s[j]); + } + break; + + case 'q': + s = va_arg (args, char*); + for (int j = 0; s[j]; j++) { + if (s[j] == ':' || s[j] == '\\') + putchar ('\\'); + else if (s[j] == '\"') + printf ("\\\\"); // a quoted quoting character + putchar (s[j]); + } + break; + + case 'c': + n = va_arg(args, int); + printf ("#%06x", n); + break; + + default: + putchar (fmt[i]); + break; + } + } + + va_end (args); + + putchar ('\n'); + fflush (stdout); +} + +#define RTFL_PRINT(module, version, cmd, fmt, ...) \ + rtfl_print (module, version, CUR_WORKING_DIR "/" __FILE__, __LINE__, \ + "s:" fmt, cmd, __VA_ARGS__) + + +// ================================== +// General module +// ================================== + +#define RTFL_GEN_VERSION "1.0" + +#define RTFL_GEN_PRINT(cmd, fmt, ...) \ + RTFL_PRINT ("gen", RTFL_GEN_VERSION, cmd, fmt, __VA_ARGS__) + +#define DBG_GEN_TIME() \ + STMT_START { \ + struct timeval tv; \ + gettimeofday(&tv, NULL); \ + char buf[32]; \ + snprintf (buf, sizeof (buf), "%ld%06ld", tv.tv_sec, tv.tv_usec); \ + RTFL_GEN_PRINT ("time", "s", buf); \ + } STMT_END + + +// ================================== +// Objects module +// ================================== + +#define RTFL_OBJ_VERSION "1.0" + +#define RTFL_OBJ_PRINT(cmd, fmt, ...) \ + RTFL_PRINT ("obj", RTFL_OBJ_VERSION, cmd, fmt, __VA_ARGS__) + +#define DBG_OBJ_MSG_O(aspect, prio, obj, msg) \ + RTFL_OBJ_PRINT ("msg", "p:s:d:s", obj, aspect, prio, msg) + +#define DBG_OBJ_MSGF(aspect, prio, fmt, ...) \ + STMT_START { \ + char msg[256]; \ + snprintf (msg, sizeof (msg), fmt, __VA_ARGS__); \ + DBG_OBJ_MSG (aspect, prio, msg); \ + } STMT_END + +#define DBG_OBJ_MSGF_O(aspect, prio, obj, fmt, ...) \ + STMT_START { \ + char msg[256]; \ + snprintf (msg, sizeof (msg), fmt, __VA_ARGS__); \ + DBG_OBJ_MSG_O (aspect, prio, obj, msg); \ + } STMT_END + +#define DBG_OBJ_MARK_O(aspect, prio, obj, mark) \ + RTFL_OBJ_PRINT ("mark", "p:s:d:s", obj, aspect, prio, mark) + +#define DBG_OBJ_MARKF(aspect, prio, fmt, ...) \ + STMT_START { \ + char mark[256]; \ + snprintf (mark, sizeof (mark), fmt, __VA_ARGS__); \ + DBG_OBJ_MARK (aspect, prio, mark); \ + } STMT_END + +#define DBG_OBJ_MARKF_O(aspect, prio, obj, fmt, ...) \ + STMT_START { \ + char mark[256]; \ + snprintf (mark, sizeof (mark), fmt, __VA_ARGS__); \ + DBG_OBJ_MARK_O (aspect, prio, obj, mark); \ + } STMT_END + +#define DBG_OBJ_MSG_START_O(obj) \ + RTFL_OBJ_PRINT ("msg-start", "p", obj) + +#define DBG_OBJ_MSG_END_O(obj) \ + RTFL_OBJ_PRINT ("msg-end", "p", obj) + +#define DBG_OBJ_ENTER0_O(aspect, prio, obj, funname) \ + RTFL_OBJ_PRINT ("enter", "p:s:d:s:", obj, aspect, prio, funname); + +#define DBG_OBJ_ENTER(aspect, prio, funname, fmt, ...) \ + STMT_START { \ + char args[256]; \ + snprintf (args, sizeof (args), fmt, __VA_ARGS__); \ + RTFL_OBJ_PRINT ("enter", "p:s:d:s:s", this, aspect, prio, funname, \ + args); \ + } STMT_END + +#define DBG_OBJ_ENTER_O(aspect, prio, obj, funname, fmt, ...) \ + STMT_START { \ + char args[256]; \ + snprintf (args, sizeof (args), fmt, __VA_ARGS__); \ + RTFL_OBJ_PRINT ("enter", "p:s:d:s:s", obj, aspect, prio, funname, \ + args); \ + } STMT_END + +#define DBG_OBJ_LEAVE_O(obj) \ + RTFL_OBJ_PRINT ("leave", "p", obj); + +#define DBG_OBJ_LEAVE_VAL(fmt, ...) \ + STMT_START { \ + char vals[256]; \ + snprintf (vals, sizeof (vals), fmt, __VA_ARGS__); \ + RTFL_OBJ_PRINT ("leave", "p:s", this, vals); \ + } STMT_END + +#define DBG_OBJ_LEAVE_VAL_O(obj, fmt, ...) \ + STMT_START { \ + char vals[256]; \ + snprintf (vals, sizeof (vals), fmt, __VA_ARGS__); \ + RTFL_OBJ_PRINT ("leave", "p:s", obj, vals); \ + } STMT_END + +#define DBG_OBJ_LEAVE_VAL0_O(obj, val) \ + RTFL_OBJ_PRINT ("leave", "p:s:", obj, val) + +#define DBG_OBJ_CREATE_O(obj, klass) \ + RTFL_OBJ_PRINT ("create", "p:s", obj, klass); + +#define DBG_OBJ_DELETE_O(obj) \ + RTFL_OBJ_PRINT ("delete", "p", obj); + +#define DBG_OBJ_BASECLASS(klass) \ + RTFL_OBJ_PRINT ("ident", "p:p", this, (klass*)this); + +#define DBG_OBJ_ASSOC(parent, child) \ + RTFL_OBJ_PRINT ("assoc", "p:p", parent, child); \ + +#define DBG_OBJ_ASSOC_PARENT(parent) \ + DBG_OBJ_ASSOC (parent, this); + +#define DBG_OBJ_ASSOC_CHILD(child) \ + DBG_OBJ_ASSOC (this, child); + +#define DBG_OBJ_SET_NUM_O(obj, var, val) \ + RTFL_OBJ_PRINT ("set", "p:s:d", obj, var, val) + +#define DBG_OBJ_SET_SYM_O(obj, var, val) \ + RTFL_OBJ_PRINT ("set", "p:s:s", obj, var, val) + +#define DBG_OBJ_SET_BOOL_O(obj, var, val) \ + RTFL_OBJ_PRINT ("set", "p:s:s", obj, var, (val) ? "true" : "false") + +#define DBG_OBJ_SET_STR_O(obj, var, val) \ + RTFL_OBJ_PRINT ("set", "p:s:\"q\"", obj, var, val) + +#define DBG_OBJ_SET_PTR_O(obj, var, val) \ + RTFL_OBJ_PRINT ("set", "p:s:p", obj, var, val) + +#define DBG_OBJ_SET_COL_O(obj, var, val) \ + RTFL_OBJ_PRINT ("set", "p:s:c", obj, var, val) + +#define DBG_OBJ_ARRSET_NUM_O(obj, var, ind, val) \ + RTFL_OBJ_PRINT ("set", "p:s.d:d", obj, var, ind, val) + +#define DBG_OBJ_ARRSET_SYM_O(obj, var, ind, val) \ + RTFL_OBJ_PRINT ("set", "p:s.d:s", obj, var, ind, val) + +#define DBG_OBJ_ARRSET_BOOL_O(obj, var, ind, val) \ + RTFL_OBJ_PRINT ("set", "p:s.d:s", obj, var, ind, (val) ? "true" : "false") + +#define DBG_OBJ_ARRSET_STR_O(obj, var, ind, val) \ + RTFL_OBJ_PRINT ("set", "p:s.d:\"q\"", obj, var, ind, val) + +#define DBG_OBJ_ARRSET_PTR_O(obj, var, ind, val) \ + RTFL_OBJ_PRINT ("set", "p:s.d:p", obj, var, ind, val) + +#define DBG_OBJ_ARRSET_COL_O(obj, var, ind, val) \ + RTFL_OBJ_PRINT ("set", "p:s.d:c", obj, var, ind, val) + +#define DBG_OBJ_ARRATTRSET_NUM_O(obj, var, ind, attr, val) \ + RTFL_OBJ_PRINT ("set", "p:s.d.s:d", obj, var, ind, attr, val) + +#define DBG_OBJ_ARRATTRSET_SYM_O(obj, var, ind, attr, val) \ + RTFL_OBJ_PRINT ("set", "p:s.d.s:s", obj, var, ind, attr, val) + +#define DBG_OBJ_ARRATTRSET_BOOL_O(obj, var, ind, attr, val) \ + RTFL_OBJ_PRINT ("set", "p:s.d.s:s", obj, var, ind, attr, \ + (val) ? "true" : "false") + +#define DBG_OBJ_ARRATTRSET_STR_O(obj, var, ind, attr, val) \ + RTFL_OBJ_PRINT ("set", "p:s.d.s:\"q\"", obj, var, ind, attr, val) + +#define DBG_OBJ_ARRATTRSET_PTR_O(obj, var, ind, attr, val) \ + RTFL_OBJ_PRINT ("set", "p:s.d.s:p", obj, var, ind, attr, val) + +#define DBG_OBJ_ARRATTRSET_COL_O(obj, var, ind, attr, val) \ + RTFL_OBJ_PRINT ("set", "p:s.d.s:c", obj, var, ind, attr, val) + +#define DBG_OBJ_CLASS_COLOR(klass, color) \ + RTFL_OBJ_PRINT ("class-color", "s:s", klass, color) + +#else /* DBG_RTFL */ + +#define STMT_NOP do { } while (0) + +#define DBG_IF_RTFL if(0) + +//@inactive + +#endif /* DBG_RTFL */ + +#endif /* __DEBUG_RTFL_HH__ */ diff --git a/doc/Makefile.am b/doc/Makefile.am new file mode 100644 index 0000000..0dda147 --- /dev/null +++ b/doc/Makefile.am @@ -0,0 +1,4 @@ +EXTRA_DIST = \ + rtfl.html \ + tipsandtricks.html \ + object-box-01.png diff --git a/doc/object-box-01.png b/doc/object-box-01.png new file mode 100644 index 0000000..7fcc651 Binary files /dev/null and b/doc/object-box-01.png differ diff --git a/doc/rtfl.html b/doc/rtfl.html new file mode 100644 index 0000000..87bc50e --- /dev/null +++ b/doc/rtfl.html @@ -0,0 +1,997 @@ + + + + + RTFL documentation + + + + +

RTFL Documentation

+ + + +

Overview

+ +

RTFL, which stands for Read The Figurative Logfile, is a + both a protocol for structured debug messages, as well as a + collection of programs (currently two, rtfl-objcount + and rtfl-objview) displaying these debug messages in a + semi-graphical way.

+ +

Programs are prepared to print these special debug messages to + standard output (typically controllable via #ifdef); + for C++, there is already a header file which provides + convenient macros. By passing the messages to a viewer program + (rtfl-objcount, rtfl-objview, or, in the + future, similar programs), it becomes simpler to determine what + the debugged program does.

+ +

Synopsis

+ +

To use RTFL, prepare the program which is to be tested (see + Preparing the tested + program), and run

+ +

tested-program | rtfl-objcount
+ tested-program | rtfl-objview [options]

+ +

or, in the future, other programs, which will become part of + RTFL. The prepared, tested program will print special debug + messages to standard output, which will then be read + by rtfl-objcount or rtfl-objview via pipe. Of + course, using files instead of pipes is also possible.

+ +

Rtfl-objcount does not yet support options, the ones + of rtfl-objview are described in the section + Command line + options of rtfl-objview.

+ +

Details on using rtfl-objcount + and rtfl-objview can be found in the + sections Using + rtfl-objcount + and Using + rtfl-objview, respectively. Furthermore, + there is a useful filter, rtfl-tee, which is described + in Using rtfl-tee.

+ +

The filter rtfl-findrepeat has been moved + to the + attic. + +

Preparing the tested program

+ +

The program to be tested must print special commands to + standard output. The file debug_rtfl.hh, included in + RTFL, provides some convenient macros. Both commands and macros + are described in the section + Protocol and macros. + The macros are active only when the pre-processor variable + DBG_RTFL is defined. Furthermore, the full paths of the + source file names should be included into the commands; this + variant prefixes __FILE__ with CUR_WORKING_DIR, + which has to be defined.

+ +

It is best to copy debug_rtfl.hh in your project, so + that there is no direct dependencies to RTFL. This file itself + is public domain, so there are no restrictions on using + RTFL.

+ +

See the tests directory for some examples. (But notice + that explicitly passing -DDBG_RTFL, as in + tests/Makefile.am is not “comme il faut”; instead, + provide a way to let the user decide.)

+ +

Using RTFL with autoconf + and automake

+ +

The file configure.ac of RTFL itself shows how + to handle this best by an option --enable-rtfl. First, + define this option:

+ +
AC_ARG_ENABLE(rtfl, [--enable-rtfl Build with RTFL messages])
+ +

Later, a pre-processor variable has to be defined:

+ +
if test "x$enable_rtfl" = "xyes" ; then
+  CXXFLAGS="$CXXFLAGS -DDBG_RTFL"
+fi
+ +

Furthermore, we need CUR_WORKING_DIR; this is defined + later; here we define:

+ +
BASE_CUR_WORKING_DIR=`pwd`
+ +

and:

+ +
AC_SUBST(BASE_CUR_WORKING_DIR)
+ +

Finally, all instances of Makefile.am have to + be prepared. Any file foo/Makefile.am should + contain:

+ +
AM_CPPFLAGS = -DCUR_WORKING_DIR='"@BASE_CUR_WORKING_DIR@/foo"'
+ +

Protocol and macros

+ +

This section describes both the commands which are printed to + standard output, as well as the macros from debug_rtfl.hh.

+ +

The whole protocol is divided into modules (currently only + one). Each protocol module has a version number consisting of a + positive major number and a minor number, which may be 0. The + major number changes when the protocol module changes in an + incompatible way, while compatible changes affect the minor + number.[1][2]

+ + +

Generally, the colon (:) is used to divide different + parts of a command. To use the colon in a literal way, not for + division,it can be quoted using the backslash (\). + Likewise, a literally used backslash must be quoted + (\\).

+ +

In the detailed descriptions below, commands are shortened. All + commands begin with

+ +

[rtfl-module-major version.minor version]file name:line number:process identifier:

+ +

where the prefix [rtfl-module-major version.minor version] + is used to filter RTFL messages from other messages printed to + standard output. Major version and minor version + refer to the version of the protocol + module.[3] + Then follows the file name and the line number, where this + message is triggered (this may be used for extensions, like + simple navigation through the source code). The last part is a + process (or thread) identifier, e. g. as returned + by getpid(2). Then follows (not shown here) + the command identifier, which defines basically what to + do.

+ +

It is common to combine protocol module and command identifier, + separated by a hyphen. For example, obj-create refers + to the command create of the module obj. + +

The following descriptions only give the part following this common + prefix.[4]

+ +

General module

+ +

The general module is identified by gen, the current + version is 1.0.

+ +

The general module contains messages intended for use with other + modules.

+ +

Command: time:usecs
+ Macro: DBG_GEN_TIME()

+ +

Define the point in time of the previous command, in micro + seconds since a defined, but unspecified, origin. Can be used for simple + profiling.

+ +

Objects module

+ +

The object module is identified by obj, the current + version is 1.0.

+ +

All commands from this module are related to an object, which + is typically the first argument following the command + identifier. For C++, the value of this, as printed + by printf("%p", this) is most suitable. The first + occurrence of a specific object will "define" the object; a + specific command is not needed (but see + create).

+ +

For most macros dealing with this, there is an + additional macro with the suffix _O, which expects the + object explicitly. Use these in special cases + when this is not suitable. They are not mentioned here; + look into debug_rtfl.hh for details.

+ +

Command: + msg:object:aspect:priority:msg
+ Macros:
+ DBG_OBJ_MSG(aspect, priority, msg)
+ DBG_OBJ_MSGF(aspect, priority, fmt ...)

+ +

Display a message (msg) related to an object. The + macro DBG_OBJ_MSG expects a simple text, + while DBG_OBJ_MSGF can be used to use + printf(3) like formatting.

+ +

Aspects are arbitrary keywords, which can be used to + focus on different parts of a program (which may affect the same + objects). Priorities are positive numbers defining how + important a message should be regarded, with 0 denoting the most + important message. Both can be filtered by the viewer programs, + see Filtering + messages in rtfl-objview.

+ +

A very simple variant of HTML can be used in the messages, the + tags <i> and <b> display italics and bold text, + respectively.

+ +

Command: + mark:object:aspect:priority:mark
+ DBG_OBJ_MARK(aspect, priority, mark)
+ DBG_OBJ_MARKF(aspect, priority, fmt ...)

+ +

Very similar to + msg, but + marks are handled in a privileged way; + rtfl-objview displays + them in a seperate menu, so it is simple to select them in a + fast way.

+ +

Marks are used to mark special positions within the streams of + RTFL commands; although there are macros, they are typically + added by filters, which scan RTFL messages for notable + occurences. An example is + rtfl-stracktraces with the + option -m.

+ +

Commands: + msg-start:object and + msg-end:object
+ Macros: DBG_OBJ_MSG_START() and + DBG_OBJ_MSG_END()

+ +

Define the start and the end of a section, like a + function. Especially useful for recursions, where these commands + are nested.

+ +

Commands: + enter:object:aspect:priority:funname:args + and leave:object + or leave:object:vals
+ Macros:
+ DBG_OBJ_ENTER0(aspect, priority, funname) (for functions/methods with no arguments)
+ DBG_OBJ_ENTER(aspect, priority, funname, fmt ...)
+ DBG_OBJ_LEAVE()
+ DBG_OBJ_LEAVE_VAL(fmt ...)
+ DBG_OBJ_LEAVE_VAL0(val)

+ +

States that a function or a method has been entered or left, + respectively. Aspect and priority have the same + meaning as for msg. + The printf(3) like format fmt (macros + DBG_OBJ_ENTER and DBG_OBJ_LEAVE_VAL) must + match the following arguments. Any leave refers to + the last “open” enter. + +

Leave supports optional return values, the + macro DBG_OBJ_LEAVE_VAL should be used in this + case. Multiple values are allowed; either with languages which + support this, or indirectly by returning values by passing + references as arguments. For a literal text value, use + DBG_OBJ_LEAVE_VAL0.

+ +

Command: + create:object:class
+ Macro: DBG_OBJ_CREATE(class)

+ +

Assigns a class to an object. This is not really necessary, + but without this command, the class of an object is not known, + and for the user of RTFL, difficult to guess.

+ +

When multiple classes are assigned to one object, the last + class is valid. This way, calling DBG_OBJ_CREATE() in + both constructors, the one of the base class (which is called + first) and the one of the sub class (which is called second), + will work properly, by eventually assigning the class of the sub + class.

+ +

For C++, it is recommended to use the fully qualified class + name (path::to::namespace::ClassName).

+ +

Command: + delete:object
+ Macro: DBG_OBJ_DELETE()

+ +

Deletes an object. Can be called multiple times for one object, + when each destructor the class hierarchy prints this + command.

+ +

After delete has been called as often as + create before, the object + identifier must be regarded as unassigned, i. e., if it is used + again, it is regarded as identifying a new object.

+ +

Command: + ident:object1:object2
+ Macro: DBG_OBJ_BASECLASS(class)

+ +

This command defines two objects as identical, so that all + commands for object1 and object2 will be treated + in the same way, in a non-distinguishable way. + +

The reasoning behind this command is shown by the macro, and + lies in the way how C++ handles multiple inheritance. Assume a + class C which has two super-classes, A + and B. Typically, when regarding instances + of C as A (cast to A*), the value + of this will not change. However, when regarded + as B, the value will be different, + typically sizeof(A) bytes larger.

+ +

For this reason, DBG_OBJ_CREATE should, at least for + cases of multiple inheritance, be followed + by DBG_OBJ_BASECLASS: + +

DBG_OBJ_CREATE(C);
+ DBG_OBJ_BASECLASS(A);
+ DBG_OBJ_BASECLASS(B);

+ +

Command: noident

+ +

This command tells the processing program that + obj-ident is never used, which + makes it unnecessary to wait until the (non-)identity of object has been + clarified.

+ +

Command: + assoc:parent:child
+ Macros:
+ DBG_OBJ_ASSOC(parent, child)
+ DBG_OBJ_ASSOC_PARENT(parent)
+ DBG_OBJ_ASSOC_CHILD(child)

+ +

Creates an association between two objects. Objects have some + kind of hierarchy, not limited to a tree, not even a DAG, but + still not symmetric: DBG_OBJ_ASSOC(x, y) + is to be distinguished from DBG_OBJ_ASSOC(y, x). + +

Command: + set:object:var:val
+ Macros:
+ DBG_OBJ_SET_TYPE(var, val)
+ DBG_OBJ_ARRSET_TYPE(var, ind, val)
+ DBG_OBJ_ARRATTRSET_TYPE(var, ind, attr, val)

+ +

Set or change an attribute of an object. The name can be + divided by using the dot (.), both for sub structures + and for arrays. The different macros are used to format + different types:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TYPEC++ type of valOutput
NUMintval
SYMchar*val
BOOLbooltrue or false
STRchar*"val"
PTRvoid*val
COLint (0xRRGGBB, interpreted as RGB + color)#RRGGBB
+ +

Furthermore, variants use the dot notation for array + elements. The last variant is used for attributes of structures, + which are elements of an array. (For even more complex + notations, you can, of course, define new macros.)

+ +

Command: + class-color:class:color[5]
+ Macro: DBG_OBJ_CLASS_COLOR(class, + color)[6]

+ +

Defines a color for groups of class names. Classes takes + the form of patterns as defined by fnmatch(3), + e. .g. path::to::namespace::*; color is + given as #RGB or #RRGGBB.

+ +

When more than one pattern matches, the most specific is + applied.[7][8]

+ +

Command: + object-color:object:color

+ +

Defines a color for single objects. Like in + class-color, color + is given as #RGB or #RRGGBB.

+ +

Object colors are preferred over + class colors. + +

Using rtfl-objcount

+ +

Rtfl-objcount reads RTFL commands from the + objects module via + standard input, simply counts the number of instances + of each class, and shows them in a table, which is especially + useful to examine memory problems. Use New snapshot from + the Snapshot menu to preserve a current state: a column + is added, and new changes are only applied to the most right + column. + +

Like rtfl-objbase, + rtfl-objcount first reads commands from a file .rtfl in + the current directory, which typically contains commands for the program + to be debugged.

+ +

Using rtfl-objview

+ +

Rtfl-objview reads RTFL commands from the + objects module via + standard input, and displays them as a diagram, with all + commands related to one object as a box, and showing the + relations between the objects.

+ +

Like rtfl-objbase, + rtfl-objview first reads commands from a file .rtfl in + the current directory, which typically contains commands for the program + to be debugged. (See also option + -B.)

+ +

Basic usage

+ +
+ +

A typical object box

+
+ +

All messages related to an object will be displayed in a box + showing

+ +
    +
  • the object identifier, and when known, the class, at the top;
  • +
  • the attributes in the middle part;
  • +
  • all messages in the lower part.
  • +
+ +

At different levels, parts of a box can be toggled between + visible and invisible (or, instead, replaced by a much smaller + part, as e. g. used for attributes). This is indicated in + the usual way, by “+” (to show) and “−” (to hide) in the upper + left corner.

+ +

For attributes, the structure based on the dot notation + (see set) creates a + tree whose parts can be toggled between visible and + invisible. The complete history of values is displayed, but can + be hidden by instead only showing the recent value.

+ +

In the messages part, + msg-start and + msg-end change the indentation of the + messaged between, and also insert the messages start + and end.

+ +

Associations lead to a respective positioning of the object + boxes and connections by arrows, as well as a message in the + messages part.

+ +

(Many aspects can be configured, see + Filtering by + types.)

+ +

Command line options

+ +

Rtfl-objview supports these options:

+ +
+
-a aspect, -A aspect
+
Show (-a) or hide (-A) an aspect + (see Filtering + messages). This is equivalent to turning on/off an + aspect from the Aspects menu. The aspect “*” refers to + all, so this option is then equivalent to Show/Hide all + aspects. Both options can be used multiple times, and are + processed in the order in which they are given; so -A "*" + -a foo will only show messages with the aspect + “foo”.
+ +
-b, -B
+
Do (-b) or do not (-B) apply .rtfl and + filtering identities (what + rtfl-objbase does).
+ +
-m, -M
+
Show (-m) or hide (-M) the messages of all + object boxes. Hiding (-M) is useful when examining + attributes; the messages can be shown by clicking on + “+”. (Since showing is the default, the option -m + only exists for reasons of symmetry.)
+ +
-o, -O
+
Show (-o) or hide (-O) the contents of all + object boxes. Hiding (-O) can be useful to get an + overview over the relations, without initially caring about + the contents; the contents can be shown by clicking on + “+”. (Since showing is the default, the option -o + only exists for reasons of symmetry.)
+ +
-p priority
+
Set the priority of messages (see + Filtering + messages). This is equivalent to choosing the + respective value in the Priorities menu. The + priority “*” refers to No limit.
+ +
-t types, -T types
+
Show (-t) or hide (-T) certain command types + (see Filtering by + types). Types is a sequence of any of these characters: “c” + for creations, “i” for indentations, “m” for messages, “a” for marks, “f” + for functions, “s” for association, “t” for attributes, and “d” for + deletions. This is equivalent to turning on/off the respective types in + the Commands menu.
+ +
-v viewer
+
Set the program called for viewing code (see + Navigation). + Viewer is a command (passed to system(3)), + which may contain these variables: “%n” is replaced by the + line number, “%p” is replaced by the path of the file. Notice + that you should append “&” to avoid blocking. The default is + “xterm -e 'vi +%n %p' &”; another nice option is + “emacsclient -n %n %p”. (Also notice that there is + currently no quoting done for expanding “%p”, so it is best to + keep your file names as simple as possible.) +
+ +

Navigation

+ +

The commands (from the object module) + msg-start, + msg-end, + enter, + leave, + msg, + mark, + set, + assoc, + create, and + delete are + navigable. They are assigned a serial numbers (starting + with 0) which is preceding the actual message; furthermore, they + can be selected (by clicking on the respective message) and + navigated with the commands Previous and Next from + the Command menu, or the respective keyboard + accelerators. Furthermore, View Code calls an external + (and configurable) + viewer or editor to show the line in the code where the + selected message was printed.

+ +

Both preceding the serial number on one hand, and selection and + highlighting on the other, refers to the respective message in + the lower part of the object box (msg-start, + msg-end, msg, mark, + assoc, create, delete), or (set) + for the message in the attribute value history (click “+” to + show), respectively

+ +

Some navigable commands are related; by + activating Switch between related from the + Commands menu switches between them. Currently + enter and leave are regarded, as well as + msg-start and msg-end.

+ +

Hiding commands

+ +

Displaying of navigable commands can be limited to any + region. Select a command (mouse or navigating) and choose from + the Command menu:

+ +
    +
  • Hide before to hide all commands before selected one + (which so becomes the first visible command);
  • +
  • Hide after likewise to hide all commands after + selected one (which becomes the last visible command);
  • +
  • Hide all to hide all commands but show new commands + (equivalent to hiding before the next command to be + created);
  • +
  • Show before to undo Hide before;
  • +
  • Show after to undo Hide after;
  • +
  • Show all to show all commands again.
  • +
+ +

Notice that the serial numbers are recalculated, so that the + first visible command has the number 0. This is also + valid for commands filtered out + (by type, or + by aspect and + priority).

+ +

Stack traces

+ +

Based on enter and + leave, the command Show stack trace from + the Command menu opens a new window showing + all enter commands leading to the selected command. As + long as these are not hidden (which is represented by italics), + you can navigate to these by clicking on Jump to.

+ +

Marks

+ +

All marks are added to + the Marks menu, from where they can be directly + selected, as long as they are not hidden.

+ +

Filtering by type

+ +

All navigable commands + can be filtered by types, by using entries in the Command + menu. In all cases, this refers to the navigable part of the + command; other effects (indentation itself, current value of an + attribute, arrows for associations) are not affected.

+ + + +

By default, indentation messages are turned off, all others are + turned on.

+ +

Filtering messages

+ +

Message commands can be + filtered by aspects and priorities.

+ +

All aspects defined by the program are visible in + the Aspects menu, alphabetically sorted, and can be + toggled individually. Show all and Hide all not + only affect all aspects already shown in the Aspects + menu, but also define the default value for newly added aspects + (initially turned on, or initially turned off, + respectively).

+ +

Likewise, the Priorities menu contains all priorities, + numerically sorted. Choosing one defines the maximal + priority number (and so minimal importance); anything above this + number (and so less important) is not shown. No limit + will not filter any messages by priority.

+ +

Note: Both aspects and priorities should be sorted + (aspects alphabetically, priorities numerically), which is not + yet the case. Especially for priorities, this can become + confusing. Also, version 0.0.7 contains a bug that No + limit is not selected initially, when it should be. (This is + only a visualization problem, filtering works.)

+ +

Using rtfl-objbase

+ +

Rtfl-objbase is a filter which reads RTFL commands from the + objects module and writes + them again to standard output, after + +

    +
  • reading and applying commands from a file .rtfl in the + current directory (which typically contains commands for the program to + be debugged),
  • +
  • dealing with obj-ident: + identities declared as identical are replaced by actually + identical identites, so a program processing the output may simply + ignore obj-ident;
  • +
  • dealing with obj-delete + and replacing re-used identities of deleted objects by new + identities;
  • +
  • furthermore, the newest version is used for the output.
  • +
+ +

Rtfl-objbase is usefully for + scripts.

+ +

Using rtfl-tee

+ +

Rtfl-tee is a simple program which works similar + to tee(1): it copies standard input to standard output + as well as to a new pipe which is connected to standard input of + another process. The exact syntax is:

+ +

rtfl-tee [-b] [--] command [arguments ...]

+ +

All arguments refer to command.

+ +

The option -b enables the bypass mode, in which + the standard output of command is piped to + rtfl-tee, which prints both its own standard input and + the standard output of command line by line, so that the + lines from both streams are preserved. Use this + when command is a filter and you want to mix both the + original and the filtered stream in a simple way. Notice that no + order of the lines is guaranteed.

+ +

Everything after -- is regarded as command. Use + this for commands starting with “-”.

+ +

A typical (simple) example:

+ +

tested-program | rtfl-tee rtf-objview -O | rtfl-objcount

+ +

This will start tested-program in a way that + the output is passed both + to rtfl-objview + (started with option -O) + and rtfl-objcount. + +

An example for the bypass mode is given in the section + Transformation + of attributes in Tips and tricks.

+ +

Scripts

+ +

Part of the RTFL package are some scripts:

+ +
    +
  • rtfl-check-objects, which uses RTFL messages to + check for invalid object access,
  • +
  • rtfl-filter-out-classes, which helps filtering out + classes not currently interesting (a function which should + become part + of rtfl-objview),
  • +
  • rtfl-objfilter, which filters messages by types, + aspects and priorities (as + rtfl-objview + would do it),
  • +
  • rtfl-objtail, which limits a stream of RTFL + messages, and
  • +
  • rtfl-stacktraces, which prints stacktraces leading + to a specific method, or helps, as a filter, finding such + stack traces.
  • +
+ +

See the comments at the respective scripts for more information + on usage.

+ +

The scripts rtfl-filter-out-classes, + rtfl-objfilter, rtfl-objtail, and (with + options -e and -m) rtfl-stacktraces + are used as filters: if e. g. you want to + use rtfl-objview but ignore all classes of the + package some::package, run

+ +

tested-program | rtfl-filter-out-classes "some::package::*" | rtfl-objview [options]

+ +

N. b. that since versioning and escaping of + protocols, parsing is incorrect:

+ +
    +
  • The tag is checked as /^\[rtfl[^\]]*\]/, which + matches more as it should. (Fixed when using rtfl-objbase, + see below.)
  • +
  • More serious: the object module (prefix in the pre-version + protocol) is considered as optional; e. g. + (obj-)?create is used to cover both versions. This + may conflict with another module which also contains a + command create. (Fixed when using rtfl-objbase, + see below.)
  • +
  • Splitting up the line is done as before, ignoring quoting. + This works as long as there are no colons in parts where no + colons were allowed before (all parts except the last one). + For object identifiers etc., this will be still the case, but + for some parts (like method identifiers, which may in some + cases be fully qualified as Class::method), this may + cause problems in the future.
  • +
  • In one case quoting is supported: rtfl-objfilter + unquotes class names, replacing “\:” by “:” (but non “\\” by + “\”). This is done regardless of the protocol version, which + works as long as there are no backslashes in class names.
  • +
+ +

Furthermore, rtfl-objbase + should be used by all scripts to achieve a uniform handling of + obj-ident and + obj-delete, as well as + to fix some parsing problems. Currently, this has been done for + rtfl-filter-out-classes and rtfl-objtail.

+ +

See also

+ + + +
+ +

[1] + Some notes and definitions:

+ +

Backward compatibility means than a parser can focus + on one new version and ignore differences to older + versions. Backward compatibility is not generally + given, only within a major version; instead, a parser must be + aware of older versions. However, changes will be made in a way + making this as simple as possible; how this is done will be + decided from case to case.

+ +

Forward compatibility means than a parser implementing + an older version can deal with newer versions. Forward + compatibility is given within a major version, by defining the + following rules:

+ +
    +
  • All lines with an unsupported major version for a + given protocol module must be ignored, as well as lines from + unsupported protocol modules.
  • +
  • For unsupported minor versions, +
      +
    • new commands and
    • +
    • unexpected arguments at the end
    • +
    + must be ignored.
  • +
+ +

This implies that there are two ways how a protocol module can + be extended in a forward compatible way (leading to a + new minor version):

+ +
    +
  • adding new commands, and
  • +
  • adding new optional arguments (at the end) to existing + commands.
  • +
+ +

For simplicity, a parser may simply check the module + and major version, and, for supported modules and major + versions, ignore new commands and new arguments. New + forward-compatible changes can be implemented by and by.

+ +

[2] + Currently, there is no version for the general part of the + protocol, which is independent of the modules. This may change + when the general protocol changes.

+ +

[3] + Since a stream may consist of command lines of different + protocol modules, it can consist of command lines with different + versions. It is even allowed to mix different versions of the same + protocol module.

+ +

[4] + + An old version of the protocol, called “pre-version protocol”, + is still supported for the objects module, albeit deprecated. + Lines start with [rtfl], with neither module name nor + version; the module name is put before the command + (example: obj-create). No escaping mechanism is defined, so + that only the last part may contain colons. If not otherwise + stated, the specific commands are equivalent

+ +

[5] + In the “pre-version protocol”, obj-color is still a + (deprecated) synonym of obj-class-color.

+ +

[6] + In the “pre-version protocol”, the order of the arguments + is obj-class-color:color:class, + since class typically contains colons, as used for C++ + namespaces, which are only allowed in the last argument. As for + the macro, there are different versions, so you have to be + cautious. (As opposed to the protocols, old versions of macros + are not supported, since the file debug_rtfl.hh is + typically copied into the tested program, and so the maintainer + of the tested program is responsible.) +

+ +

[7] + The exact definition of the specifity of a pattern: number of + “?” − 100 ∙ number of “*” + 100 ∙ number of other + characters.

+ +

[8] + In the “pre-version protocol”, the rule was different (the first + pattern was choosen), but for simplicity, newer versions of RTFL + treat the “pre-version protocol” like the new version.

+ + diff --git a/doc/tipsandtricks.html b/doc/tipsandtricks.html new file mode 100644 index 0000000..92479f4 --- /dev/null +++ b/doc/tipsandtricks.html @@ -0,0 +1,415 @@ + + + + + RTFL: Tips and tricks + + + +

[Back to RTFL documentation.]

+ +

Tips and tricks

+ +

Here, I'll summarize some useful experiences during the work + on dillo, for which RTFL was + initially written.

+ +

Preparing the tested program

+ +
    +
  • Do not prepare your program for a specific purpose, but in a + way that the RTFL message tell what happens in the + program, in an objective way. Ideally, most + preparation work is done once and pays off for many different + debugging problems.
  • +
  • Use different aspects so that filtering (as + in rtfl-objview) + helps to focus on your specific problem. +
  • +
+ +

rtfl-objview

+ +

Work around bugs and flaws

+ +

Currently, RTFL is not at the top of my priority list, but just + a mean to an end (with dillo + being the end). This means that I am not too motivated to fix + bugs which can be worked around:

+ +
    +
  • There are several redrawing problems. Calling + xrefresh or switching between virtual screens should + should redraw everything correctly. (Redrawing + problems seem fixed now.)
  • +
  • Sometimes, the focused command is not visible. Pressing + Ctrl+N and then Ctrl+P should get it back into the viewport + again. (Scrolling problems seem fixed now.)
  • +
  • Use scripts to filter the RTFL messages. Hacking a script in + Perl is often much faster as integrating the functionality + into rtfl-objview. Some useful + scripts are already part of RTFL.
  • +
+ +

General approach

+ +

Run the tested program and pass the standard output via pipe + to rtfl-objview. The + command + line options -M and -O are useful to get + an overview, -a and -A will help you to filter + out irrelevant messages.

+ +

Then search for an entry point:

+ +
    +
  • First of all, get an overview what the objects displayed in + the diagram actually represent. In + dillo, many objects + represent elements of the rendered document, so if an element + is rendered wrong, it is important to find the respective + object.
  • +
  • Depending on the tested program, it may be useful to search + for strange patterns of function calls. Simple example: + endless recursions are easily recognized by a steadily + increasing indentation.
  • +
  • An often helpful approach is to examine the attributes: + search for the respective object and the respective attribute; + a value which looks strange is a good starting point.
  • +
+ +

If you have troubles to find the respective object, try to + color it via + obj-object-color, + using a filter like + this:[1]

+ +
sed 's/^\(\[rtfl-obj-1\.[0-9]*\].*\):set:\([^:]*\):words\.0\.text\/widget\/breakSpace:"Abc"$/\0\n\1:object-color:\2:#ff80ff/g'
+ +

which colors all objects to light purple which have the + attribute words.0.text/widget/breakSpace set to the + value "Abc". This example is again taken + from dillo, like the + following one, which colors objects in the color in which they + are rendered (represented by the attribute + style.background-color):[2]

+ +
sed 's/^\(\[rtfl-obj-1\.[0-9]*\].*\):set:\([^:]*\):style.background-color:\(#[0-9a-f]*\)$/\0\n\1:object-color:\2:\3/g'
+ +

As soon as you have selected a specific command, try to + determine the commands before:

+ +
    +
  • Navigate through the + previous + commands.
  • +
  • Or use the stack + traces function to get an overview.
  • +
  • One more tip: Use Switch between related for skipping + the messages within one method call if you think that the + problem is found outside of this method.
  • +
+ +

Advanced problems

+ +

CPU hogging

+ +

In this case, the number of messages (infinite) will be too + large for rtfl-objview. Simply use head(1) to + reduce them; for the number of lines, some trial and error is + necessary.

+ +

Too many messages

+ +

In some cases, it is useful to concentrate on what happens at + the end, especially if the program aborts. In this cases, the + script rtfl-objtail + helps to filter messages, but preserve relevant object + creations, associations etc. at the beginning. Again, some tests + are needed for the number of lines.

+ +

Transformation of attributes

+ +

While obj-set + is used to set actual attributes, it is sometimes useful to + accumulate values. Instead of adding code to the tested program, + filters can do this and add respective pseudo attributes. The + following snippet counts for each object (and each process) the + number of calls of the method + draw:[3]

+ +
rtfl-tee -b sh -c \
+   "grep '^\[rtfl\].*:obj-enter:[^:]*:[^:]*:[^:]*:draw:' | \
+    sed 's/^.*:\([^:]*\):obj-enter:\([^:]*\):.*$/\1:\2/g' | \
+    sort | uniq -c | \
+    sed 's/^ *\([^ ]*\)  *\([^:]*\):\(.*\)$/[rtfl]:0:\2:obj-set:\3:<b>called <i>draw<\/i><\/b>:\1/g'"
+ +

More tips

+ +
    +
  • Use the script + rtfl-filter-out-classes + to reduce again messages. (I plan to integrate this + into rtfl-objview.)
  • +
  • The serial numbers preceding commands are often useful to + determine which command follows the other. E. g., by + comparing the numbers of attributes, it can be determined + which attribute possibly depends on another.
  • +
+ +

Attic

+ +

Some ideas proved to be less practical that they looked at + first. Here, you will find several smaller projects which I have + abandoned, but you might find them useful as an inspiration.

+ +

Dillo: HTML + attribute rtfl-id

+ +

To make finding the exact relation between HTML elements of a + tested page and the dillo + widgets simpler, one could define a new HTML + element, rtfl-id, which is recognized by the HTML + parser of dillo and eventually passed to the respective + widget. (Printing a + obj-set + message is sufficient, storage in the widget is not necessary.) + This is demonstrated by the patch + dillo-rtfl-id.diff + below.

+ +

Furthermore, a simple Perl + script, add-rtfl-id.pl, + adds unique rtfl-id's to a given HTML page (standard + input to standard output).

+ +

(Warning: both are incomplete.)

+ +

rtfl-findrepeat

+ +

Rtfl-findrepeat is a program which searches in a + stream for identical sequences. See comment at the beginning + of common/rtfl_findrepeat.cc for more details on + usage. Suitable arguments should be achieved by trial and + error.

+ +

The idea behind this: if a program hogs the CPU, it is most + likely that it does similar things again and again, by + error. With a bit of luck, this will result in identical RTFL + messages, which rtfl-findrepeat helps to + find. With dillo, however, + this rendered not as really usable, so I have moved + rtfl-findrepeat to here.

+ +

Appendix

+ +

dillo-rtfl-id.diff

+ +
diff -r 45a8d0d4b0d6 dw/widget.hh
+--- a/dw/widget.hh	Mon Sep 08 23:20:10 2014 +0200
++++ b/dw/widget.hh	Tue Sep 09 13:09:08 2014 +0200
+@@ -502,6 +502,11 @@
+     */
+    virtual Iterator *iterator (Content::Type mask, bool atEnd) = 0;
+    virtual void removeChild (Widget *child);
++
++   void setRtflId (const char *rtflId) {
++      if (rtflId) DBG_OBJ_SET_STR ("rtfl-id", rtflId);
++      else DBG_OBJ_SET_SYM ("rtfl-id", "none");
++   }
+ };
+ 
+ void splitHeightPreserveAscent (int height, int *ascent, int *descent);
+diff -r 45a8d0d4b0d6 src/doctree.hh
+--- a/src/doctree.hh	Mon Sep 08 23:20:10 2014 +0200
++++ b/src/doctree.hh	Tue Sep 09 13:09:08 2014 +0200
+@@ -13,6 +13,7 @@
+       lout::misc::SimpleVector<char*> *klass;
+       const char *pseudo;
+       const char *id;
++      const char *rtflId;
+ 
+       DoctreeNode () {
+          parent = NULL;
+@@ -21,11 +22,13 @@
+          klass = NULL;
+          pseudo = NULL;
+          id = NULL;
++         rtflId = NULL;
+          element = 0;
+       };
+ 
+       ~DoctreeNode () {
+          dFree ((void*) id);
++         dFree ((void*) rtflId);
+          while (lastChild) {
+             DoctreeNode *n = lastChild;
+             lastChild = lastChild->sibling;
+diff -r 45a8d0d4b0d6 src/form.cc
+--- a/src/form.cc	Mon Sep 08 23:20:10 2014 +0200
++++ b/src/form.cc	Tue Sep 09 13:09:08 2014 +0200
+@@ -938,6 +938,7 @@
+        * but it caused 100% CPU usage.
+        */
+       page = new Textblock (false);
++      page->setRtflId (html->rtflId ());
+       page->setStyle (html->backgroundStyle ());
+ 
+       ResourceFactory *factory = HT2LT(html)->getResourceFactory();
+diff -r 45a8d0d4b0d6 src/html.cc
+--- a/src/html.cc	Mon Sep 08 23:20:10 2014 +0200
++++ b/src/html.cc	Tue Sep 09 13:09:08 2014 +0200
+@@ -362,6 +362,7 @@
+ static void Html_add_textblock(DilloHtml *html, bool addBreaks, int breakSpace)
+ {
+    Textblock *textblock = new Textblock (prefs.limit_text_width);
++   textblock->setRtflId (html->rtflId ());
+ 
+    if (addBreaks)
+       HT2TB(html)->addParbreak (breakSpace, html->wordStyle ());
+@@ -2186,6 +2187,7 @@
+    }
+ 
+    dw::Image *dw = new dw::Image(alt_ptr);
++   dw->setRtflId (html->rtflId ());
+    image =
+       a_Image_new(html->dw->getLayout(), (void*)(dw::core::ImgRenderer*)dw, 0);
+ 
+@@ -3038,6 +3040,7 @@
+    HT2TB(html)->addParbreak (5, html->wordStyle ());
+ 
+    hruler = new Ruler();
++   hruler->setRtflId (html->rtflId ());
+    hruler->setStyle (html->style ());
+    HT2TB(html)->addWidget (hruler, html->style ());
+    HT2TB(html)->addParbreak (5, html->wordStyle ());
+@@ -3783,6 +3786,10 @@
+    const char *attrbuf;
+    char lang[3];
+ 
++   if (tagsize >= 13 &&        /* length of "<t rtfl-id=i>" */
++       (attrbuf = a_Html_get_attr(html, tag, tagsize, "rtfl-id")))
++      html->styleEngine->setRtflId(attrbuf);
++
+    if (tagsize >= 8 &&        /* length of "<t id=i>" */
+        (attrbuf = a_Html_get_attr(html, tag, tagsize, "id"))) {
+       /* According to the SGML declaration of HTML 4, all NAME values
+@@ -3882,6 +3889,7 @@
+    HT2TB(html)->addParbreak (0, wordStyle);
+ 
+    list_item = new ListItem ((ListItem*)*ref_list_item,prefs.limit_text_width);
++   list_item->setRtflId (html->rtflId ());
+    HT2TB(html)->addWidget (list_item, style);
+    HT2TB(html)->addParbreak (0, wordStyle);
+    *ref_list_item = list_item;
+diff -r 45a8d0d4b0d6 src/html_common.hh
+--- a/src/html_common.hh	Mon Sep 08 23:20:10 2014 +0200
++++ b/src/html_common.hh	Tue Sep 09 13:09:08 2014 +0200
+@@ -234,6 +234,7 @@
+ 
+    inline void restyle () { styleEngine->restyle (bw); }
+ 
++   inline const char *rtflId () { return styleEngine->rtflId (); }
+ };
+ 
+ /*
+diff -r 45a8d0d4b0d6 src/styleengine.cc
+--- a/src/styleengine.cc	Mon Sep 08 23:20:10 2014 +0200
++++ b/src/styleengine.cc	Tue Sep 09 13:09:08 2014 +0200
+@@ -160,6 +160,12 @@
+    dn->id = dStrdup (id);
+ }
+ 
++void StyleEngine::setRtflId (const char *rtflId) {
++   DoctreeNode *dn =  doctree->top ();
++   assert (dn->rtflId == NULL);
++   dn->rtflId = dStrdup (rtflId);
++}
++
+ /**
+  * \brief split a string at sep chars and return a SimpleVector of strings
+  */
+diff -r 45a8d0d4b0d6 src/styleengine.hh
+--- a/src/styleengine.hh	Mon Sep 08 23:20:10 2014 +0200
++++ b/src/styleengine.hh	Tue Sep 09 13:09:08 2014 +0200
+@@ -77,6 +77,7 @@
+       void startElement (int tag, BrowserWindow *bw);
+       void startElement (const char *tagname, BrowserWindow *bw);
+       void setId (const char *id);
++      void setRtflId (const char *rtflId);
+       const char * getId () { return doctree->top ()->id; };
+       void setClass (const char *klass);
+       void setStyle (const char *style);
+@@ -122,6 +123,10 @@
+          else
+             return wordStyle0 (bw);
+       };
++
++      inline const char *rtflId () {
++         return stack->getRef(stack->size()-1)->doctreeNode->rtflId;
++      };
+ };
+ 
+ #endif
+diff -r 45a8d0d4b0d6 src/table.cc
+--- a/src/table.cc	Mon Sep 08 23:20:10 2014 +0200
++++ b/src/table.cc	Tue Sep 09 13:09:08 2014 +0200
+@@ -158,6 +158,7 @@
+ 
+    HT2TB(html)->addParbreak (0, html->wordStyle ());
+    table = new dw::Table(prefs.limit_text_width);
++   table->setRtflId (html->rtflId ());
+    HT2TB(html)->addWidget (table, html->style ());
+    HT2TB(html)->addParbreak (0, html->wordStyle ());
+ 
+@@ -451,6 +452,7 @@
+                      prefs.limit_text_width);
+       else
+          col_tb = new SimpleTableCell (prefs.limit_text_width);
++      col_tb->setRtflId (html->rtflId ());
+ 
+       if (html->style()->borderCollapse == BORDER_MODEL_COLLAPSE){
+          Html_set_collapsing_border_model(html, col_tb);
+ +

add-rtfl-id.pl

+ +
#!/usr/bin/perl
+
+$id = 0;
+
+while(<STDIN>) {
+   chomp;
+   $first = 1;
+   foreach $part (split /w</) {
+      if ($first) {
+         print $part;
+      } else {
+         if ($part =~ /^\//) {
+            print "<$part";
+         } else {
+            $part =~ s/([^ >]*)/\1 rtfl-id="$id"/;
+            $id++;
+            print "<$part";
+         }
+      }
+      $first = 0;
+   }
+   print "\n";
+}
+ +
+ +

[1] + Use this for the pre-version protocol:

+
sed 's/^\(\[rtfl\].*\):obj-set:\([^:]*\):words\.0\.text\/widget\/breakSpace:"Abc"$/\0\n\1:obj-object-color:\2:#ff80ff/g'
+ +

[2] + Again, for the pre-version protocol:

+
sed 's/^\(\[rtfl\].*\):obj-set:\([^:]*\):style.background-color:\(#[0-9a-f]*\)$/\0\n\1:obj-object-color:\2:\3/g'
+ +

[3] + This works only with the pre-version protocol. Should be + adjusted eventually.

+ + + diff --git a/dw/Makefile.am b/dw/Makefile.am new file mode 100644 index 0000000..471a309 --- /dev/null +++ b/dw/Makefile.am @@ -0,0 +1,57 @@ +AM_CPPFLAGS = \ + -I$(top_srcdir) \ + -DDILLO_LIBDIR='"$(pkglibdir)/"' \ + -DCUR_WORKING_DIR='"@BASE_CUR_WORKING_DIR@/dw"' + +noinst_LIBRARIES = \ + libDw-core.a \ + libDw-fltk.a + +libDw_core_a_SOURCES = \ + core.hh \ + events.hh \ + findtext.cc \ + findtext.hh \ + imgbuf.hh \ + imgrenderer.hh \ + imgrenderer.cc \ + iterator.cc \ + iterator.hh \ + layout.cc \ + layout.hh \ + platform.hh \ + selection.hh \ + selection.cc \ + style.cc \ + style.hh \ + types.cc \ + types.hh \ + ui.cc \ + ui.hh \ + view.hh \ + widget.cc \ + widget.hh + +# "fltkcomplexbutton.cc", "fltkcomplexbutton.hh", "fltkflatview.cc", +# and "fltkflatview.hh" have been removed from libDw-fltk.a. + +libDw_fltk_a_SOURCES = \ + fltkcore.hh \ + fltkimgbuf.cc \ + fltkimgbuf.hh \ + fltkmisc.cc \ + fltkmisc.hh \ + fltkplatform.cc \ + fltkplatform.hh \ + fltkpreview.hh \ + fltkpreview.cc \ + fltkui.cc \ + fltkui.hh \ + fltkviewbase.cc \ + fltkviewbase.hh \ + fltkviewport.cc \ + fltkviewport.hh + +libDw_fltk_a_CXXFLAGS = @LIBFLTK_CXXFLAGS@ + +EXTRA_DIST = preview.xbm diff --git a/dw/core.hh b/dw/core.hh new file mode 100644 index 0000000..022574b --- /dev/null +++ b/dw/core.hh @@ -0,0 +1,60 @@ +#ifndef __DW_CORE_HH__ +#define __DW_CORE_HH__ + +#define __INCLUDED_FROM_DW_CORE_HH__ + +/** + * \brief Dw is in this namespace, or sub namespaces of this one. + * + * The core can be found in dw::core, widgets are defined directly here. + * + * \sa \ref dw-overview + */ +namespace dw { + +/** + * \brief The core of Dw is defined in this namespace. + * + * \sa \ref dw-overview + */ +namespace core { + +typedef unsigned char byte; + +class Layout; +class View; +class Widget; +class Iterator; + +// Nothing yet to free. +inline void freeall () { } + +namespace ui { + +class ResourceFactory; + +} // namespace ui +} // namespace core +} // namespace dw + +#include "../lout/object.hh" +#include "../lout/container.hh" +#include "../lout/signal.hh" + +#include "types.hh" +#include "events.hh" +#include "imgbuf.hh" +#include "imgrenderer.hh" +#include "style.hh" +#include "view.hh" +#include "platform.hh" +#include "iterator.hh" +#include "findtext.hh" +#include "selection.hh" +#include "layout.hh" +#include "widget.hh" +#include "ui.hh" + +#undef __INCLUDED_FROM_DW_CORE_HH__ + +#endif // __DW_CORE_HH__ diff --git a/dw/events.hh b/dw/events.hh new file mode 100644 index 0000000..5309186 --- /dev/null +++ b/dw/events.hh @@ -0,0 +1,83 @@ +#ifndef __DW_EVENTS_HH__ +#define __DW_EVENTS_HH__ + +#ifndef __INCLUDED_FROM_DW_CORE_HH__ +# error Do not include this file directly, use "core.hh" instead. +#endif + +namespace dw { +namespace core { + +/** + * \brief Platform independent representation. + */ +enum ButtonState +{ + /* We won't use more than these ones. */ + SHIFT_MASK = 1 << 0, + CONTROL_MASK = 1 << 1, + META_MASK = 1 << 2, + BUTTON1_MASK = 1 << 3, + BUTTON2_MASK = 1 << 4, + BUTTON3_MASK = 1 << 5 +}; + +/** + * \brief Base class for all events. + * + * The dw::core::Event hierarchy describes events in a platform independent + * way. + */ +class Event: public lout::object::Object +{ +public: +}; + +/** + * \brief Base class for all mouse events. + */ +class MouseEvent: public Event +{ +public: + ButtonState state; +}; + +/** + * \brief Base class for all mouse events related to a specific position. + */ +class MousePositionEvent: public MouseEvent +{ +public: + int xCanvas, yCanvas, xWidget, yWidget; +}; + +/** + * \brief Represents a button press or release event. + */ +class EventButton: public MousePositionEvent +{ +public: + int numPressed; /* 1 for simple click, 2 for double click, etc. */ + int button; +}; + +/** + * \brief Represents a mouse motion event. + */ +class EventMotion: public MousePositionEvent +{ +}; + +/** + * \brief Represents a enter or leave notify event. + */ +class EventCrossing: public MouseEvent +{ +public: + Widget *lastWidget, *currentWidget; +}; + +} // namespace core +} // namespace dw + +#endif // __DW_EVENTS_HH__ diff --git a/dw/findtext.cc b/dw/findtext.cc new file mode 100644 index 0000000..57c83c5 --- /dev/null +++ b/dw/findtext.cc @@ -0,0 +1,307 @@ +/* + * RTFL (originally part of dillo) + * + * Copyright 2005-2007 Sebastian Geerken + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version; with the following exception: + * + * The copyright holders of RTFL give you permission to link this file + * statically or dynamically against all versions of the graphviz + * library, which are published by AT&T Corp. under one of the following + * licenses: + * + * - Common Public License version 1.0 as published by International + * Business Machines Corporation (IBM), or + * - Eclipse Public License version 1.0 as published by the Eclipse + * Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + + + +#include "core.hh" +#include "../lout/debug.hh" +#include "../lout/msg.h" + +namespace dw { +namespace core { + +FindtextState::FindtextState () +{ + DBG_OBJ_CREATE ("dw::core::FindtextState"); + + key = NULL; + nexttab = NULL; + widget = NULL; + iterator = NULL; + hlIterator = NULL; +} + +FindtextState::~FindtextState () +{ + if (key) + free(key); + if (nexttab) + delete[] nexttab; + if (iterator) + delete iterator; + if (hlIterator) + delete hlIterator; + + DBG_OBJ_DELETE (); +} + +void FindtextState::setWidget (Widget *widget) +{ + this->widget = widget; + + // A widget change will restart the search. + if (key) + free(key); + key = NULL; + if (nexttab) + delete[] nexttab; + nexttab = NULL; + + if (iterator) + delete iterator; + iterator = NULL; + if (hlIterator) + delete hlIterator; + hlIterator = NULL; +} + +FindtextState::Result FindtextState::search (const char *key, bool caseSens, + bool backwards) +{ + if (!widget || *key == 0) // empty keys are not found + return NOT_FOUND; + + bool wasHighlighted = unhighlight (); + bool newKey; + + // If the key (or the widget) changes (including case sensitivity), + // the search is started from the beginning. + if (this->key == NULL || this->caseSens != caseSens || + strcmp (this->key, key) != 0) { + newKey = true; + if (this->key) + free(this->key); + this->key = strdup (key); + this->caseSens = caseSens; + + if (nexttab) + delete[] nexttab; + nexttab = createNexttab (key, caseSens, backwards); + + if (iterator) + delete iterator; + iterator = new CharIterator (widget, true); + + if (backwards) { + /* Go to end */ + while (iterator->next () ) ; + iterator->prev (); //We don't want to be at CharIterator::END. + } else { + iterator->next (); + } + } else + newKey = false; + + bool firstTrial = !wasHighlighted || newKey; + + if (search0 (backwards, firstTrial)) { + // Highlighting is done with a clone. + hlIterator = iterator->cloneCharIterator (); + for (int i = 0; key[i]; i++) + hlIterator->next (); + CharIterator::highlight (iterator, hlIterator, HIGHLIGHT_FINDTEXT); + CharIterator::scrollTo (iterator, hlIterator, + HPOS_INTO_VIEW, VPOS_CENTER); + + // The search will continue from the word after the found position. + iterator->next (); + return SUCCESS; + } else { + if (firstTrial) { + return NOT_FOUND; + } else { + // Nothing found anymore, reset the state for the next trial. + delete iterator; + iterator = new CharIterator (widget, true); + if (backwards) { + /* Go to end */ + while (iterator->next ()) ; + iterator->prev (); //We don't want to be at CharIterator::END. + } else { + iterator->next (); + } + // We expect a success. + Result result2 = search (key, caseSens, backwards); + assert (result2 == SUCCESS); + return RESTART; + } + } +} + +/** + * \brief This method is called when the user closes the "find text" dialog. + */ +void FindtextState::resetSearch () +{ + unhighlight (); + + if (key) + free(key); + key = NULL; +} + +/* + * Return a new string: with the reverse of the original. + */ +const char* FindtextState::rev(const char *str) +{ + if (!str) + return NULL; + + int len = strlen(str); + char *nstr = new char[len+1]; + for (int i = 0; i < len; ++i) + nstr[i] = str[len-1 -i]; + nstr[len] = 0; + + return nstr; +} + +int *FindtextState::createNexttab (const char *needle, bool caseSens, + bool backwards) +{ + const char* key; + + key = (backwards) ? rev(needle) : needle; + int i = 0; + int j = -1; + int l = strlen (key); + int *nexttab = new int[l + 1]; // + 1 is necessary for l == 1 case + nexttab[0] = -1; + + do { + if (j == -1 || charsEqual (key[i], key[j], caseSens)) { + i++; + j++; + nexttab[i] = j; + //_MSG ("nexttab[%d] = %d\n", i, j); + } else + j = nexttab[j]; + } while (i < l - 1); + + if (backwards) + delete [] key; + + return nexttab; +} + +/** + * \brief Unhighlight, and return whether a region was highlighted. + */ +bool FindtextState::unhighlight () +{ + if (hlIterator) { + CharIterator *start = hlIterator->cloneCharIterator (); + for (int i = 0; key[i]; i++) + start->prev (); + + CharIterator::unhighlight (start, hlIterator, HIGHLIGHT_FINDTEXT); + delete start; + delete hlIterator; + hlIterator = NULL; + + return true; + } else + return false; +} + +bool FindtextState::search0 (bool backwards, bool firstTrial) +{ + if (iterator->getChar () == CharIterator::END) + return false; + + bool ret = false; + const char* searchKey = (backwards) ? rev(key) : key; + int j = 0; + bool nextit = true; + int l = strlen (key); + + if (backwards && !firstTrial) { + _MSG("Having to do."); + /* Position correctly */ + /* In order to achieve good results (i.e: find a word that ends within + * the previously searched word's limit) we have to position the + * iterator in the semilast character of the previously searched word. + * + * Since we know that if a word was found before it was exactly the + * same word as the one we are searching for now, we can apply the + * following expression: + * + * Where l=length of the key and n=num of positions to move: + * + * n = l - 3 + * + * If n is negative, we have to move backwards, but if it is + * positive, we have to move forward. So, when l>=4, we start moving + * the iterator forward. */ + + if (l==1) { + iterator->prev(); + iterator->prev(); + } else if (l==2) { + iterator->prev(); + } else if (l>=4) { + for (int i=0; inext(); + } + } + + } else if (backwards && l==1) { + /* Particular case where we can't find the last character */ + iterator->next(); + } + + do { + if (j == -1 || charsEqual (iterator->getChar(),searchKey[j],caseSens)) { + j++; + nextit = backwards ? iterator->prev () : iterator->next (); + } else + j = nexttab[j]; + } while (nextit && j < l); + + if (j >= l) { + if (backwards) { + //This is the location of the key + iterator->next(); + } else { + // Go back to where the key was found. + for (int i = 0; i < l; i++) + iterator->prev (); + } + ret = true; + } + + if (backwards) + delete [] searchKey; + + return ret; +} + +} // namespace core +} // namespace dw diff --git a/dw/findtext.hh b/dw/findtext.hh new file mode 100644 index 0000000..c680348 --- /dev/null +++ b/dw/findtext.hh @@ -0,0 +1,84 @@ +#ifndef __DW_FINDTEXT_STATE_H__ +#define __DW_FINDTEXT_STATE_H__ + +#ifndef __INCLUDED_FROM_DW_CORE_HH__ +# error Do not include this file directly, use "core.hh" instead. +#endif + +#include + +namespace dw { +namespace core { + +class FindtextState +{ +public: + typedef enum { + /** \brief The next occurrence of the pattern has been found. */ + SUCCESS, + + /** + * \brief There is no further occurrence of the pattern, instead, the + * first occurrence has been selected. + */ + RESTART, + + /** \brief The patten does not at all occur in the text. */ + NOT_FOUND + } Result; + +private: + /** + * \brief The key used for the last search. + * + * If dw::core::Findtext::search is called with the same key, the search + * is continued, otherwise it is restarted. + */ + char *key; + + /** \brief Whether the last search was case sensitive. */ + bool caseSens; + + /** \brief The table used for KMP search. */ + int *nexttab; + + /** \brief The top of the widget tree, in which the search is done. + * + * From this, the iterator will be constructed. Set by + * dw::core::Findtext::widget + */ + Widget *widget; + + /** \brief The position from where the next search will start. */ + CharIterator *iterator; + + /** + * \brief The position from where the characters are highlighted. + * + * NULL, when no text is highlighted. + */ + CharIterator *hlIterator; + + static const char* rev(const char* _str); /* reverse a C string */ + + static int *createNexttab (const char *needle,bool caseSens,bool backwards); + bool unhighlight (); + bool search0 (bool backwards, bool firstTrial); + + inline static bool charsEqual (char c1, char c2, bool caseSens) + { return caseSens ? c1 == c2 : tolower (c1) == tolower (c2) || + (isspace (c1) && isspace (c2)); } + +public: + FindtextState (); + ~FindtextState (); + + void setWidget (Widget *widget); + Result search (const char *key, bool caseSens, bool backwards); + void resetSearch (); +}; + +} // namespace core +} // namespace dw + +#endif // __DW_FINDTEXT_STATE_H__ diff --git a/dw/fltkcore.hh b/dw/fltkcore.hh new file mode 100644 index 0000000..5ac619b --- /dev/null +++ b/dw/fltkcore.hh @@ -0,0 +1,36 @@ +#ifndef __DW_FLTK_CORE_HH__ +#define __DW_FLTK_CORE_HH__ + +#define __INCLUDED_FROM_DW_FLTK_CORE_HH__ + +namespace dw { +namespace fltk { +namespace ui { + +class FltkResource; + +} // namespace ui +} // namespace fltk +} // namespace dw + +#include + +#include "core.hh" +#include "fltkimgbuf.hh" +#include "fltkplatform.hh" +#include "fltkui.hh" + +namespace dw { +namespace fltk { + +inline void freeall () +{ + FltkImgbuf::freeall (); +} + +} // namespace fltk +} // namespace dw + +#undef __INCLUDED_FROM_DW_FLTK_CORE_HH__ + +#endif // __DW_FLTK_CORE_HH__ diff --git a/dw/fltkimgbuf.cc b/dw/fltkimgbuf.cc new file mode 100644 index 0000000..6621dc5 --- /dev/null +++ b/dw/fltkimgbuf.cc @@ -0,0 +1,584 @@ +/* + * RTFL (originally part of dillo) + * + * Copyright 2005-2007, 2012-2013 Sebastian Geerken + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version; with the following exception: + * + * The copyright holders of RTFL give you permission to link this file + * statically or dynamically against all versions of the graphviz + * library, which are published by AT&T Corp. under one of the following + * licenses: + * + * - Common Public License version 1.0 as published by International + * Business Machines Corporation (IBM), or + * - Eclipse Public License version 1.0 as published by the Eclipse + * Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "fltkcore.hh" +#include "../lout/msg.h" +#include "../lout/misc.hh" + +#include +#include + +#define IMAGE_MAX_AREA (6000 * 6000) + +#define MAX_WIDTH 0x8000 +#define MAX_HEIGHT 0x8000 + +namespace dw { +namespace fltk { + +using namespace lout::container::typed; + +const enum ScaleMode { SIMPLE, BEAUTIFUL, BEAUTIFUL_GAMMA } + scaleMode = BEAUTIFUL_GAMMA; + +Vector *FltkImgbuf::gammaCorrectionTables + = new Vector (true, 2); + +uchar *FltkImgbuf::findGammaCorrectionTable (double gamma) +{ + // Since the number of possible keys is low, a linear search is + // sufficiently fast. + + for (int i = 0; i < gammaCorrectionTables->size(); i++) { + GammaCorrectionTable *gct = gammaCorrectionTables->get(i); + if (gct->gamma == gamma) + return gct->map; + } + + _MSG("Creating new table for gamma = %g\n", gamma); + + GammaCorrectionTable *gct = new GammaCorrectionTable(); + gct->gamma = gamma; + + for (int i = 0; i < 256; i++) + gct->map[i] = 255 * pow((double)i / 255, gamma); + + gammaCorrectionTables->put (gct); + return gct->map; +} + +bool FltkImgbuf::excessiveImageDimensions (int width, int height) +{ + return width <= 0 || height <= 0 || + width > IMAGE_MAX_AREA / height; +} + +void FltkImgbuf::freeall () +{ + _MSG("Deleting gammaCorrectionTables\n"); + delete gammaCorrectionTables; + gammaCorrectionTables = NULL; +} + +FltkImgbuf::FltkImgbuf (Type type, int width, int height, double gamma) +{ + DBG_OBJ_CREATE ("dw::fltk::FltkImgbuf"); + + _MSG ("FltkImgbuf::FltkImgbuf: new root %p\n", this); + init (type, width, height, gamma, NULL); +} + +FltkImgbuf::FltkImgbuf (Type type, int width, int height, double gamma, + FltkImgbuf *root) +{ + DBG_OBJ_CREATE ("dw::fltk::FltkImgbuf"); + + _MSG ("FltkImgbuf::FltkImgbuf: new scaled %p, root is %p\n", this, root); + init (type, width, height, gamma, root); +} + +void FltkImgbuf::init (Type type, int width, int height, double gamma, + FltkImgbuf *root) +{ + if (excessiveImageDimensions (width, height)) { + // Excessive image sizes which would cause crashes due to too + // big allocations for the image buffer (for root buffers, when + // the image was specially prepared). In this case we use a 1 x + // 1 size. + MSG("FltkImgbuf::init: suspicious image size request %d x %d\n", + width, height); + init (type, 1, 1, gamma, root); + } else if (width > MAX_WIDTH) { + // Too large dimensions cause dangerous overflow errors, so we + // limit dimensions to harmless values. + // + // Example: 65535 * 65536 / 65536 (see scaling below) results in + // the negative value -1. + + MSG("FltkImgbuf::init: cannot handle large width %d\n", width); + init (type, MAX_WIDTH, height, gamma, root); + } else if (height > MAX_HEIGHT) { + MSG("FltkImgbuf::init: cannot handle large height %d\n", height); + init (type, width, MAX_HEIGHT, gamma, root); + } else if (gamma <= 0) { + MSG("FltkImgbuf::init: non-positive gamma %g\n", gamma); + init (type, width, height, 1, root); + } else { + this->root = root; + this->type = type; + this->width = width; + this->height = height; + this->gamma = gamma; + + DBG_OBJ_SET_NUM ("width", width); + DBG_OBJ_SET_NUM ("height", height); + + // TODO: Maybe this is only for root buffers + switch (type) { + case RGBA: bpp = 4; break; + case RGB: bpp = 3; break; + default: bpp = 1; break; + } + _MSG("FltkImgbuf::init this=%p width=%d height=%d bpp=%d gamma=%g\n", + this, width, height, bpp, gamma); + rawdata = new uchar[bpp * width * height]; + // Set light-gray as interim background color. + memset(rawdata, 222, width*height*bpp); + + refCount = 1; + deleteOnUnref = true; + copiedRows = new lout::misc::BitSet (height); + + // The list is only used for root buffers. + if (isRoot()) + scaledBuffers = new lout::container::typed::List (true); + else + scaledBuffers = NULL; + + if (!isRoot()) { + // Scaling + for (int row = 0; row < root->height; row++) { + if (root->copiedRows->get (row)) + scaleRow (row, root->rawdata + row*root->width*root->bpp); + } + } + } +} + +FltkImgbuf::~FltkImgbuf () +{ + _MSG ("FltkImgbuf::~FltkImgbuf\n"); + + if (!isRoot()) + root->detachScaledBuf (this); + + delete[] rawdata; + delete copiedRows; + + if (scaledBuffers) + delete scaledBuffers; + + DBG_OBJ_DELETE (); +} + +/** + * \brief This method is called for the root buffer, when a scaled buffer + * removed. + */ +void FltkImgbuf::detachScaledBuf (FltkImgbuf *scaledBuf) +{ + scaledBuffers->detachRef (scaledBuf); + + _MSG("FltkImgbuf[root %p]: scaled buffer %p is detached, %d left\n", + this, scaledBuf, scaledBuffers->size ()); + + if (refCount == 0 && scaledBuffers->isEmpty () && deleteOnUnref) + // If the root buffer is not used anymore, but this is the last scaled + // buffer. + // See also: FltkImgbuf::unref(). + delete this; +} + +void FltkImgbuf::setCMap (int *colors, int num_colors) +{ +} + +inline void FltkImgbuf::scaleRow (int row, const core::byte *data) +{ + if (row < root->height) { + if (scaleMode == SIMPLE) + scaleRowSimple (row, data); + else + scaleRowBeautiful (row, data); + } +} + +inline void FltkImgbuf::scaleRowSimple (int row, const core::byte *data) +{ + int sr1 = scaledY (row); + int sr2 = scaledY (row + 1); + + for (int sr = sr1; sr < sr2; sr++) { + // Avoid multiple passes. + if (copiedRows->get(sr)) continue; + + copiedRows->set (sr, true); + if (sr == sr1) { + for (int px = 0; px < root->width; px++) { + int px1 = px * width / root->width; + int px2 = (px+1) * width / root->width; + for (int sp = px1; sp < px2; sp++) { + memcpy(rawdata + (sr*width + sp)*bpp, data + px*bpp, bpp); + } + } + } else { + memcpy(rawdata + sr*width*bpp, rawdata + sr1*width*bpp, width*bpp); + } + } +} + +inline void FltkImgbuf::scaleRowBeautiful (int row, const core::byte *data) +{ + int sr1 = scaledY (row); + int sr2 = scaledY (row + 1); + bool allRootRows = false; + + // Don't rescale rows! + if (copiedRows->get(sr1)) return; + + if (height > root->height) { + scaleBuffer (data, root->width, 1, + rawdata + sr1 * width * bpp, width, sr2 - sr1, + bpp, gamma); + // Mark scaled rows done + for (int sr = sr1; sr < sr2 || sr == sr1; sr++) + copiedRows->set (sr, true); + } else { + assert (sr1 == sr2 || sr1 + 1 == sr2); + int row1 = backscaledY(sr1), row2 = backscaledY(sr1 + 1); + + // Check all the necessary root lines already arrived, + // a larger area than a single row may be accessed here. + for (int r=row1; (allRootRows=root->copiedRows->get(r)) && ++r < row2; ); + if (allRootRows) { + scaleBuffer (root->rawdata + row1 * root->width * bpp, + root->width, row2 - row1, + rawdata + sr1 * width * bpp, width, 1, + bpp, gamma); + // Mark scaled row done + copiedRows->set (sr1, true); + } + } +} + +/** + * General method to scale an image buffer. Used to scale single lines + * in scaleRowBeautiful. + * + * The algorithm is rather simple. If the scaled buffer is smaller + * (both width and height) than the original buffer, each pixel in the + * scaled buffer is assigned a rectangle of pixels in the original + * buffer; the resulting pixel value (red, green, blue) is simply the + * average of all pixel values. This is pretty fast and leads to + * rather good results. + * + * Nothing special (like interpolation) is done when scaling up. + * + * If scaleMode is set to BEAUTIFUL_GAMMA, gamma correction is + * considered, see . + * + * TODO Could be optimized as in scaleRowSimple: when the destination + * image is larger, calculate only one row/column, and copy it to the + * other rows/columns. + */ +inline void FltkImgbuf::scaleBuffer (const core::byte *src, int srcWidth, + int srcHeight, core::byte *dest, + int destWidth, int destHeight, int bpp, + double gamma) +{ + uchar *gammaMap1, *gammaMap2; + + if (scaleMode == BEAUTIFUL_GAMMA) { + gammaMap1 = findGammaCorrectionTable (gamma); + gammaMap2 = findGammaCorrectionTable (1 / gamma); + } + + for(int x = 0; x < destWidth; x++) + for(int y = 0; y < destHeight; y++) { + int xo1 = x * srcWidth / destWidth; + int xo2 = lout::misc::max ((x + 1) * srcWidth / destWidth, xo1 + 1); + int yo1 = y * srcHeight / destHeight; + int yo2 = lout::misc::max ((y + 1) * srcHeight / destHeight, yo1 + 1); + int n = (xo2 - xo1) * (yo2 - yo1); + + int v[bpp]; + for(int i = 0; i < bpp; i++) + v[i] = 0; + + for(int xo = xo1; xo < xo2; xo++) + for(int yo = yo1; yo < yo2; yo++) { + const core::byte *ps = src + bpp * (yo * srcWidth + xo); + for(int i = 0; i < bpp; i++) + v[i] += + (scaleMode == BEAUTIFUL_GAMMA ? gammaMap2[ps[i]] : ps[i]); + } + + core::byte *pd = dest + bpp * (y * destWidth + x); + for(int i = 0; i < bpp; i++) + pd[i] = + scaleMode == BEAUTIFUL_GAMMA ? gammaMap1[v[i] / n] : v[i] / n; + } +} + +void FltkImgbuf::copyRow (int row, const core::byte *data) +{ + assert (isRoot()); + + if (row < height) { + // Flag the row done and copy its data. + copiedRows->set (row, true); + memcpy(rawdata + row * width * bpp, data, width * bpp); + + // Update all the scaled buffers of this root image. + for (Iterator it = scaledBuffers->iterator(); + it.hasNext(); ) { + FltkImgbuf *sb = it.getNext (); + sb->scaleRow (row, data); + } + } +} + +void FltkImgbuf::newScan () +{ + if (isRoot()) { + for (Iterator it = scaledBuffers->iterator(); it.hasNext();){ + FltkImgbuf *sb = it.getNext (); + sb->copiedRows->clear(); + } + } +} + +core::Imgbuf* FltkImgbuf::getScaledBuf (int width, int height) +{ + if (!isRoot()) + return root->getScaledBuf (width, height); + + if (width > MAX_WIDTH) { + // Similar to init. + MSG("FltkImgbuf::getScaledBuf: cannot handle large width %d\n", width); + return getScaledBuf (MAX_WIDTH, height); + } + if (height > MAX_HEIGHT) { + MSG("FltkImgbuf::getScaledBuf: cannot handle large height %d\n", height); + return getScaledBuf (width, MAX_HEIGHT); + } + + if (width == this->width && height == this->height) { + ref (); + return this; + } + + for (Iterator it = scaledBuffers->iterator(); it.hasNext(); ) { + FltkImgbuf *sb = it.getNext (); + if (sb->width == width && sb->height == height) { + sb->ref (); + return sb; + } + } + + // Check for excessive image sizes which would cause crashes due to + // too big allocations for the image buffer. In this case we return + // a pointer to the unscaled image buffer. + if (excessiveImageDimensions (width, height)) { + MSG("FltkImgbuf::getScaledBuf: suspicious image size request %d x %d\n", + width, height); + ref (); + return this; + } + + // This size is not yet used, so a new buffer has to be created. + FltkImgbuf *sb = new FltkImgbuf (type, width, height, gamma, this); + scaledBuffers->append (sb); + DBG_OBJ_ASSOC_CHILD (sb); + + return sb; +} + +void FltkImgbuf::getRowArea (int row, dw::core::Rectangle *area) +{ + // TODO: May have to be adjusted. + + if (isRoot()) { + /* root buffer */ + area->x = 0; + area->y = row; + area->width = width; + area->height = 1; + _MSG("::getRowArea: area x=%d y=%d width=%d height=%d\n", + area->x, area->y, area->width, area->height); + } else { + if (row > root->height) + area->x = area->y = area->width = area->height = 0; + else { + // scaled buffer + int sr1 = scaledY (row); + int sr2 = scaledY (row + 1); + + area->x = 0; + area->y = sr1; + area->width = width; + area->height = sr2 - sr1; + _MSG("::getRowArea: area x=%d y=%d width=%d height=%d\n", + area->x, area->y, area->width, area->height); + } + } +} + +int FltkImgbuf::getRootWidth () +{ + return root ? root->width : width; +} + +int FltkImgbuf::getRootHeight () +{ + return root ? root->height : height; +} + +core::Imgbuf *FltkImgbuf::createSimilarBuf (int width, int height) +{ + return new FltkImgbuf (type, width, height, gamma); +} + +void FltkImgbuf::copyTo (Imgbuf *dest, int xDestRoot, int yDestRoot, + int xSrc, int ySrc, int widthSrc, int heightSrc) +{ + FltkImgbuf *fDest = (FltkImgbuf*)dest; + assert (bpp == fDest->bpp); + + int xSrc2 = lout::misc::min (xSrc + widthSrc, fDest->width - xDestRoot); + int ySrc2 = lout::misc::min (ySrc + heightSrc, fDest->height - yDestRoot); + + //printf ("copying from (%d, %d), %d x %d to (%d, %d) (root) => " + // "xSrc2 = %d, ySrc2 = %d\n", + // xSrc, ySrc, widthSrc, heightSrc, xDestRoot, yDestRoot, + // xSrc2, ySrc2); + + for (int x = xSrc; x < xSrc2; x++) + for (int y = ySrc; y < ySrc2; y++) { + int iSrc = x + width * y; + int iDest = xDestRoot + x + fDest->width * (yDestRoot + y); + + //printf (" (%d, %d): %d -> %d\n", x, y, iSrc, iDest); + + for (int b = 0; b < bpp; b++) + fDest->rawdata[bpp * iDest + b] = rawdata[bpp * iSrc + b]; + } +} + +void FltkImgbuf::ref () +{ + refCount++; + + //if (root) + // MSG("FltkImgbuf[scaled %p, root is %p]: ref() => %d\n", + // this, root, refCount); + //else + // MSG("FltkImgbuf[root %p]: ref() => %d\n", this, refCount); +} + +void FltkImgbuf::unref () +{ + //if (root) + // MSG("FltkImgbuf[scaled %p, root is %p]: ref() => %d\n", + // this, root, refCount - 1); + //else + // MSG("FltkImgbuf[root %p]: ref() => %d\n", this, refCount - 1); + + if (--refCount == 0) { + if (isRoot ()) { + // Root buffer, it must be ensured that no scaled buffers are left. + // See also FltkImgbuf::detachScaledBuf(). + if (scaledBuffers->isEmpty () && deleteOnUnref) { + delete this; + } else { + _MSG("FltkImgbuf[root %p]: not deleted. numScaled=%d\n", + this, scaledBuffers->size ()); + } + } else + // Scaled buffer buffer, simply delete it. + delete this; + } +} + +bool FltkImgbuf::lastReference () +{ + return refCount == 1 && + (scaledBuffers == NULL || scaledBuffers->isEmpty ()); +} + +void FltkImgbuf::setDeleteOnUnref (bool deleteOnUnref) +{ + assert (isRoot ()); + this->deleteOnUnref = deleteOnUnref; +} + +bool FltkImgbuf::isReferred () +{ + return refCount != 0 || + (scaledBuffers != NULL && !scaledBuffers->isEmpty ()); +} + + +int FltkImgbuf::scaledY(int ySrc) +{ + // TODO: May have to be adjusted. + assert (root != NULL); + return ySrc * height / root->height; +} + +int FltkImgbuf::backscaledY(int yScaled) +{ + assert (root != NULL); + + // Notice that rounding errors because of integers do not play a + // role. This method cannot be the exact inverse of scaledY, since + // scaleY is not bijective, and so not invertible. Instead, both + // values always return the smallest value. + return yScaled * root->height / height; +} + +void FltkImgbuf::draw (Fl_Widget *target, int xRoot, int yRoot, + int x, int y, int width, int height) +{ + // TODO: Clarify the question, whether "target" is the current widget + // (and so has not to be passed at all). + + _MSG("::draw: xRoot=%d x=%d yRoot=%d y=%d width=%d height=%d\n" + " this->width=%d this->height=%d\n", + xRoot, x, yRoot, y, width, height, this->width, this->height); + + if (x > this->width || y > this->height) { + return; + } + + if (x + width > this->width) { + width = this->width - x; + } + + if (y + height > this->height) { + height = this->height - y; + } + + fl_draw_image(rawdata+bpp*(y*this->width + x), xRoot + x, yRoot + y, width, + height, bpp, this->width * bpp); + +} + +} // namespace fltk +} // namespace dw diff --git a/dw/fltkimgbuf.hh b/dw/fltkimgbuf.hh new file mode 100644 index 0000000..0b8b554 --- /dev/null +++ b/dw/fltkimgbuf.hh @@ -0,0 +1,93 @@ +#ifndef __DW_FLTKIMGBUF_HH__ +#define __DW_FLTKIMGBUF_HH__ + +#ifndef __INCLUDED_FROM_DW_FLTK_CORE_HH__ +# error Do not include this file directly, use "fltkcore.hh" instead. +#endif + +namespace dw { +namespace fltk { + +class FltkImgbuf: public core::Imgbuf +{ +private: + class GammaCorrectionTable: public lout::object::Object + { + public: + double gamma; + uchar map[256]; + }; + + FltkImgbuf *root; + int refCount; + bool deleteOnUnref; + lout::container::typed::List *scaledBuffers; + + int width, height; + Type type; + double gamma; + +//{ + int bpp; + uchar *rawdata; +//} + + // This is just for testing drawing, it has to be replaced by + // the image buffer. + lout::misc::BitSet *copiedRows; + + static lout::container::typed::Vector + *gammaCorrectionTables; + + static uchar *findGammaCorrectionTable (double gamma); + static bool excessiveImageDimensions (int width, int height); + + FltkImgbuf (Type type, int width, int height, double gamma, + FltkImgbuf *root); + void init (Type type, int width, int height, double gamma, FltkImgbuf *root); + int scaledY(int ySrc); + int backscaledY(int yScaled); + int isRoot() { return (root == NULL); } + void detachScaledBuf (FltkImgbuf *scaledBuf); + +protected: + ~FltkImgbuf (); + +public: + FltkImgbuf (Type type, int width, int height, double gamma); + + static void freeall (); + + void setCMap (int *colors, int num_colors); + inline void scaleRow (int row, const core::byte *data); + inline void scaleRowSimple (int row, const core::byte *data); + inline void scaleRowBeautiful (int row, const core::byte *data); + inline static void scaleBuffer (const core::byte *src, int srcWidth, + int srcHeight, core::byte *dest, + int destWidth, int destHeight, int bpp, + double gamma); + + void newScan (); + void copyRow (int row, const core::byte *data); + core::Imgbuf* getScaledBuf (int width, int height); + void getRowArea (int row, dw::core::Rectangle *area); + int getRootWidth (); + int getRootHeight (); + core::Imgbuf *createSimilarBuf (int width, int height); + void copyTo (Imgbuf *dest, int xDestRoot, int yDestRoot, + int xSrc, int ySrc, int widthSrc, int heightSrc); + void ref (); + void unref (); + + bool lastReference (); + void setDeleteOnUnref (bool deleteOnUnref); + bool isReferred (); + + void draw (Fl_Widget *target, int xRoot, int yRoot, + int x, int y, int width, int height); +}; + +} // namespace fltk +} // namespace dw + +#endif // __DW_FLTK_IMGBUF_HH__ diff --git a/dw/fltkmisc.cc b/dw/fltkmisc.cc new file mode 100644 index 0000000..45230ad --- /dev/null +++ b/dw/fltkmisc.cc @@ -0,0 +1,58 @@ +/* + * RTFL (originally part of dillo) + * + * Copyright 2005-2007 Sebastian Geerken + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version; with the following exception: + * + * The copyright holders of RTFL give you permission to link this file + * statically or dynamically against all versions of the graphviz + * library, which are published by AT&T Corp. under one of the following + * licenses: + * + * - Common Public License version 1.0 as published by International + * Business Machines Corporation (IBM), or + * - Eclipse Public License version 1.0 as published by the Eclipse + * Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + + +#include "../lout/msg.h" +#include "fltkmisc.hh" + +#include +#include + +namespace dw { +namespace fltk { +namespace misc { + +int screenWidth () +{ + return Fl::w (); +} + +int screenHeight () +{ + return Fl::h (); +} + +void warpPointer (int x, int y) +{ + MSG_ERR("no warpPointer mechanism available.\n"); +} + +} // namespace misc +} // namespace fltk +} // namespace dw diff --git a/dw/fltkmisc.hh b/dw/fltkmisc.hh new file mode 100644 index 0000000..fc00431 --- /dev/null +++ b/dw/fltkmisc.hh @@ -0,0 +1,22 @@ +#ifndef __FLTKMISC_HH__ +#define __FLTKMISC_HH__ + +namespace dw { +namespace fltk { + +/** + * \brief Miscellaneous FLTK stuff. + */ +namespace misc { + +int screenWidth (); +int screenHeight (); + +void warpPointer (int x, int y); + +} // namespace misc +} // namespace fltk +} // namespace dw + + +#endif // __FLTKMISC_HH__ diff --git a/dw/fltkplatform.cc b/dw/fltkplatform.cc new file mode 100644 index 0000000..a244765 --- /dev/null +++ b/dw/fltkplatform.cc @@ -0,0 +1,739 @@ +/* + * RTFL (originally part of dillo) + * + * Copyright 2005-2007 Sebastian Geerken + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version; with the following exception: + * + * The copyright holders of RTFL give you permission to link this file + * statically or dynamically against all versions of the graphviz + * library, which are published by AT&T Corp. under one of the following + * licenses: + * + * - Common Public License version 1.0 as published by International + * Business Machines Corporation (IBM), or + * - Eclipse Public License version 1.0 as published by the Eclipse + * Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include + +#include "../lout/msg.h" +#include "../lout/debug.hh" +#include "fltkcore.hh" + +#include +#include +#include +#include +#include + +/* + * Local data + */ + +/* Tooltips */ +static Fl_Menu_Window *tt_window = NULL; +static int in_tooltip = 0, req_tooltip = 0; + +namespace dw { +namespace fltk { + +using namespace lout; + +/** + * \todo Distinction between italics and oblique would be nice. + */ + +container::typed::HashTable *FltkFont::fontsTable = + new container::typed::HashTable (false, false); + +container::typed::HashTable *FltkFont::systemFonts = + NULL; + +FltkFont::FontFamily FltkFont::standardFontFamily (FL_HELVETICA, + FL_HELVETICA_BOLD, + FL_HELVETICA_ITALIC, + FL_HELVETICA_BOLD_ITALIC); + +FltkFont::FontFamily::FontFamily (Fl_Font fontNormal, Fl_Font fontBold, + Fl_Font fontItalic, Fl_Font fontBoldItalic) +{ + font[0] = fontNormal; + font[1] = fontBold; + font[2] = fontItalic; + font[3] = fontBoldItalic; +} + +void FltkFont::FontFamily::set (Fl_Font f, int attrs) +{ + int idx = 0; + if (attrs & FL_BOLD) + idx += 1; + if (attrs & FL_ITALIC) + idx += 2; + font[idx] = f; +} + +Fl_Font FltkFont::FontFamily::get (int attrs) +{ + int idx = 0; + if (attrs & FL_BOLD) + idx += 1; + if (attrs & FL_ITALIC) + idx += 2; + + // should the desired font style not exist, we + // return the normal font of the fontFamily + return font[idx] >= 0 ? font[idx] : font[0]; +} + + + +FltkFont::FltkFont (core::style::FontAttrs *attrs) +{ + if (!systemFonts) + initSystemFonts (); + + copyAttrs (attrs); + + int fa = 0; + if (weight >= 500) + fa |= FL_BOLD; + if (style != core::style::FONT_STYLE_NORMAL) + fa |= FL_ITALIC; + + object::ConstString nameString (name); + FontFamily *family = systemFonts->get (&nameString); + if (!family) + family = &standardFontFamily; + + font = family->get (fa); + + fl_font(font, size); + // WORKAROUND: A bug with fl_width(uint_t) on non-xft X was present in + // 1.3.0 (STR #2688). + spaceWidth = misc::max(0, (int)fl_width(" ") + letterSpacing); + int xx, xy, xw, xh; + fl_text_extents("x", xx, xy, xw, xh); + xHeight = xh; + descent = fl_descent(); + ascent = fl_height() - descent; +} + +FltkFont::~FltkFont () +{ + fontsTable->remove (this); +} + +static void strstrip(char *big, const char *little) +{ + if (strlen(big) >= strlen(little) && + misc::AsciiStrcasecmp(big + strlen(big) - strlen(little), little) == 0) + *(big + strlen(big) - strlen(little)) = '\0'; +} + +void FltkFont::initSystemFonts () +{ + systemFonts = new container::typed::HashTable + (true, true); + + int k = Fl::set_fonts ("-*-iso10646-1"); + for (int i = 0; i < k; i++) { + int t; + char *name = strdup (Fl::get_font_name ((Fl_Font) i, &t)); + + // normalize font family names (strip off "bold", "italic") + if (t & FL_ITALIC) + strstrip(name, " italic"); + if (t & FL_BOLD) + strstrip(name, " bold"); + + _MSG("Found font: %s%s%s\n", name, t & FL_BOLD ? " bold" : "", + t & FL_ITALIC ? " italic" : ""); + + object::String *familyName = new object::String(name); + free (name); + FontFamily *family = systemFonts->get (familyName); + + if (family) { + family->set ((Fl_Font) i, t); + delete familyName; + } else { + // set first font of family also as normal font in case there + // is no normal (non-bold, non-italic) font + family = new FontFamily ((Fl_Font) i, -1, -1, -1); + family->set ((Fl_Font) i, t); + systemFonts->put (familyName, family); + } + } +} + +bool +FltkFont::fontExists (const char *name) +{ + if (!systemFonts) + initSystemFonts (); + object::ConstString familyName (name); + return systemFonts->get (&familyName) != NULL; +} + +Fl_Font +FltkFont::get (const char *name, int attrs) +{ + if (!systemFonts) + initSystemFonts (); + object::ConstString familyName (name); + FontFamily *family = systemFonts->get (&familyName); + if (family) + return family->get (attrs); + else + return FL_HELVETICA; +} + +bool +FltkPlatform::fontExists (const char *name) +{ + return FltkFont::fontExists (name); +} + +FltkFont* +FltkFont::create (core::style::FontAttrs *attrs) +{ + FltkFont *font = fontsTable->get (attrs); + + if (font == NULL) { + font = new FltkFont (attrs); + fontsTable->put (font, font); + } + + return font; +} + +container::typed::HashTable + *FltkColor::colorsTable = + new container::typed::HashTable (false, false); + +FltkColor::FltkColor (int color): Color (color) +{ + this->color = color; + + if (!(colors[SHADING_NORMAL] = shadeColor (color, SHADING_NORMAL) << 8)) + colors[SHADING_NORMAL] = FL_BLACK; + if (!(colors[SHADING_INVERSE] = shadeColor (color, SHADING_INVERSE) << 8)) + colors[SHADING_INVERSE] = FL_BLACK; + if (!(colors[SHADING_DARK] = shadeColor (color, SHADING_DARK) << 8)) + colors[SHADING_DARK] = FL_BLACK; + if (!(colors[SHADING_LIGHT] = shadeColor (color, SHADING_LIGHT) << 8)) + colors[SHADING_LIGHT] = FL_BLACK; +} + +FltkColor::~FltkColor () +{ + colorsTable->remove (this); +} + +FltkColor * FltkColor::create (int col) +{ + ColorAttrs attrs(col); + FltkColor *color = colorsTable->get (&attrs); + + if (color == NULL) { + color = new FltkColor (col); + colorsTable->put (color, color); + } + + return color; +} + +FltkTooltip::FltkTooltip (const char *text) : Tooltip(text) +{ +} + +FltkTooltip::~FltkTooltip () +{ + if (in_tooltip || req_tooltip) + cancel(); /* cancel tooltip window */ +} + +FltkTooltip *FltkTooltip::create (const char *text) +{ + return new FltkTooltip(text); +} + +/* + * Tooltip callback: used to delay it a bit + * INVARIANT: Only one instance of this function is requested. + */ +static void tooltip_tcb(void *data) +{ + req_tooltip = 2; + ((FltkTooltip *)data)->onEnter(); + req_tooltip = 0; +} + +void FltkTooltip::onEnter() +{ + _MSG("FltkTooltip::onEnter\n"); + if (!str || !*str) + return; + if (req_tooltip == 0) { + Fl::remove_timeout(tooltip_tcb); + Fl::add_timeout(1.0, tooltip_tcb, this); + req_tooltip = 1; + return; + } + + if (!tt_window) { + tt_window = new Fl_Menu_Window(0,0,100,24); + tt_window->set_override(); + tt_window->box(FL_NO_BOX); + Fl_Box *b = new Fl_Box(0,0,100,24); + b->box(FL_BORDER_BOX); + b->color(fl_color_cube(FL_NUM_RED-1, FL_NUM_GREEN-1, FL_NUM_BLUE-2)); + b->labelcolor(FL_BLACK); + b->labelfont(FL_HELVETICA); + b->labelsize(14); + b->align(FL_ALIGN_WRAP|FL_ALIGN_LEFT|FL_ALIGN_INSIDE); + tt_window->resizable(b); + tt_window->end(); + } + + /* prepare tooltip window */ + int x, y; + Fl_Box *box = (Fl_Box*)tt_window->child(0); + box->label(str); + Fl::get_mouse(x,y); y += 6; + /* calculate window size */ + int ww, hh; + ww = 800; // max width; + box->measure_label(ww, hh); + ww += 6 + 2 * Fl::box_dx(box->box()); + hh += 6 + 2 * Fl::box_dy(box->box()); + tt_window->resize(x,y,ww,hh); + tt_window->show(); + in_tooltip = 1; +} + +/* + * Leaving the widget cancels the tooltip + */ +void FltkTooltip::onLeave() +{ + _MSG(" FltkTooltip::onLeave in_tooltip=%d\n", in_tooltip); + cancel(); +} + +void FltkPlatform::cancelTooltip() +{ + FltkTooltip::cancel(); +} + +/* + * Remove a shown tooltip or cancel a pending one + */ +void FltkTooltip::cancel() +{ + if (req_tooltip) { + Fl::remove_timeout(tooltip_tcb); + req_tooltip = 0; + } + if (!in_tooltip) return; + in_tooltip = 0; + tt_window->hide(); + + /* WORKAROUND: (Black magic here) + * Hiding a tooltip with the keyboard or mousewheel doesn't work. + * The code below "fixes" the problem */ + Fl_Widget *widget = Fl::belowmouse(); + if (widget && widget->window()) { + widget->window()->damage(FL_DAMAGE_EXPOSE,0,0,1,1); + } +} + +void FltkTooltip::onMotion() +{ +} + +void FltkView::addFltkWidget (Fl_Widget *widget, + core::Allocation *allocation) +{ +} + +void FltkView::removeFltkWidget (Fl_Widget *widget) +{ +} + +void FltkView::allocateFltkWidget (Fl_Widget *widget, + core::Allocation *allocation) +{ +} + +void FltkView::drawFltkWidget (Fl_Widget *widget, core::Rectangle *area) +{ +} + + +core::ui::LabelButtonResource * +FltkPlatform::FltkResourceFactory::createLabelButtonResource (const char + *label) +{ + return new ui::FltkLabelButtonResource (platform, label); +} + +core::ui::ComplexButtonResource * +FltkPlatform::FltkResourceFactory::createComplexButtonResource (core::Widget + *widget, + bool relief) +{ + // Not needed within RTFL. + lout::misc::assertNotReached (); + return NULL; +} + +core::ui::ListResource * +FltkPlatform::FltkResourceFactory::createListResource (core::ui + ::ListResource + ::SelectionMode + selectionMode, int rows) +{ + return new ui::FltkListResource (platform, selectionMode, rows); +} + +core::ui::OptionMenuResource * +FltkPlatform::FltkResourceFactory::createOptionMenuResource () +{ + return new ui::FltkOptionMenuResource (platform); +} + +core::ui::EntryResource * +FltkPlatform::FltkResourceFactory::createEntryResource (int size, + bool password, + const char *label) +{ + return new ui::FltkEntryResource (platform, size, password, label); +} + +core::ui::MultiLineTextResource * +FltkPlatform::FltkResourceFactory::createMultiLineTextResource (int cols, + int rows) +{ + return new ui::FltkMultiLineTextResource (platform, cols, rows); +} + +core::ui::CheckButtonResource * +FltkPlatform::FltkResourceFactory::createCheckButtonResource (bool activated) +{ + return new ui::FltkCheckButtonResource (platform, activated); +} + +core::ui::RadioButtonResource +*FltkPlatform::FltkResourceFactory::createRadioButtonResource +(core::ui::RadioButtonResource *groupedWith, bool activated) +{ + return + new ui::FltkRadioButtonResource (platform, + (ui::FltkRadioButtonResource*) + groupedWith, + activated); +} + +// ---------------------------------------------------------------------- + +FltkPlatform::FltkPlatform () +{ + DBG_OBJ_CREATE ("dw::fltk::FltkPlatform"); + + layout = NULL; + idleQueue = new container::typed::List (true); + idleFuncRunning = false; + idleFuncId = 0; + + view = NULL; + resources = new container::typed::List (false); + + resourceFactory.setPlatform (this); +} + +FltkPlatform::~FltkPlatform () +{ + if (idleFuncRunning) + Fl::remove_idle (generalStaticIdle, (void*)this); + delete idleQueue; + delete resources; + + DBG_OBJ_DELETE (); +} + +void FltkPlatform::setLayout (core::Layout *layout) +{ + this->layout = layout; + DBG_OBJ_ASSOC_CHILD (layout); +} + + +void FltkPlatform::attachView (core::View *view) +{ + if (this->view) + MSG_ERR("FltkPlatform::attachView: multiple views!\n"); + this->view = (FltkView*)view; + + for (container::typed::Iterator it = + resources->iterator (); it.hasNext (); ) { + ui::FltkResource *resource = it.getNext (); + resource->attachView (this->view); + } +} + + +void FltkPlatform::detachView (core::View *view) +{ + if (this->view != view) + MSG_ERR("FltkPlatform::detachView: this->view: %p view: %p\n", + this->view, view); + + for (container::typed::Iterator it = + resources->iterator (); it.hasNext (); ) { + ui::FltkResource *resource = it.getNext (); + resource->detachView ((FltkView*)view); + } + this->view = NULL; +} + + +int FltkPlatform::textWidth (core::style::Font *font, const char *text, + int len) +{ + char chbuf[4]; + int c, cu; + int width = 0; + FltkFont *ff = (FltkFont*) font; + int curr = 0, next = 0, nb; + + if (font->fontVariant == core::style::FONT_VARIANT_SMALL_CAPS) { + int sc_fontsize = lout::misc::roundInt(ff->size * 0.78); + for (curr = 0; next < len; curr = next) { + next = nextGlyph(text, curr); + c = fl_utf8decode(text + curr, text + next, &nb); + if ((cu = fl_toupper(c)) == c) { + /* already uppercase, just draw the character */ + fl_font(ff->font, ff->size); + if (fl_nonspacing(cu) == 0) { + width += font->letterSpacing; + width += (int)fl_width(text + curr, next - curr); + } + } else { + /* make utf8 string for converted char */ + nb = fl_utf8encode(cu, chbuf); + fl_font(ff->font, sc_fontsize); + if (fl_nonspacing(cu) == 0) { + width += font->letterSpacing; + width += (int)fl_width(chbuf, nb); + } + } + } + } else { + fl_font (ff->font, ff->size); + width = (int) fl_width (text, len); + + if (font->letterSpacing) { + int curr = 0, next = 0; + + while (next < len) { + next = nextGlyph(text, curr); + c = fl_utf8decode(text + curr, text + next, &nb); + if (fl_nonspacing(c) == 0) + width += font->letterSpacing; + curr = next; + } + } + } + + return width; +} + +char *FltkPlatform::textToUpper (const char *text, int len) +{ + char *newstr = NULL; + + if (len > 0) { + int newlen; + + newstr = (char*) malloc(3 * len + 1); + newlen = fl_utf_toupper((const unsigned char*)text, len, newstr); + assert(newlen <= 3 * len); + newstr[newlen] = '\0'; + } + return newstr; +} + +char *FltkPlatform::textToLower (const char *text, int len) +{ + char *newstr = NULL; + + if (len > 0) { + int newlen; + + newstr = (char*) malloc(3 * len + 1); + newlen = fl_utf_tolower((const unsigned char*)text, len, newstr); + assert(newlen <= 3 * len); + newstr[newlen] = '\0'; + } + return newstr; +} + +int FltkPlatform::nextGlyph (const char *text, int idx) +{ + return fl_utf8fwd (&text[idx + 1], text, &text[strlen (text)]) - text; +} + +int FltkPlatform::prevGlyph (const char *text, int idx) +{ + return fl_utf8back (&text[idx - 1], text, &text[strlen (text)]) - text; +} + +float FltkPlatform::dpiX () +{ + float horizontal, vertical; + + Fl::screen_dpi(horizontal, vertical); + return horizontal; +} + +float FltkPlatform::dpiY () +{ + float horizontal, vertical; + + Fl::screen_dpi(horizontal, vertical); + return vertical; +} + +void FltkPlatform::generalStaticIdle (void *data) +{ + ((FltkPlatform*)data)->generalIdle(); +} + +void FltkPlatform::generalIdle () +{ + IdleFunc *idleFunc; + + if (!idleQueue->isEmpty ()) { + /* Execute the first function in the list. */ + idleFunc = idleQueue->getFirst (); + (layout->*(idleFunc->func)) (); + + /* Remove this function. */ + idleQueue->removeRef(idleFunc); + } + + if (idleQueue->isEmpty()) { + idleFuncRunning = false; + Fl::remove_idle (generalStaticIdle, (void*)this); + } +} + +/** + * \todo Incomplete comments. + */ +int FltkPlatform::addIdle (void (core::Layout::*func) ()) +{ + /* + * Since ... (todo) we have to wrap around fltk_add_idle. There is only one + * idle function, the passed idle function is put into a queue. + */ + if (!idleFuncRunning) { + Fl::add_idle (generalStaticIdle, (void*)this); + idleFuncRunning = true; + } + + idleFuncId++; + + IdleFunc *idleFunc = new IdleFunc(); + idleFunc->id = idleFuncId; + idleFunc->func = func; + idleQueue->append (idleFunc); + + return idleFuncId; +} + +void FltkPlatform::removeIdle (int idleId) +{ + bool found; + container::typed::Iterator it; + IdleFunc *idleFunc; + + for (found = false, it = idleQueue->iterator(); !found && it.hasNext(); ) { + idleFunc = it.getNext(); + if (idleFunc->id == idleId) { + idleQueue->removeRef (idleFunc); + found = true; + } + } + + if (idleFuncRunning && idleQueue->isEmpty()) + Fl::remove_idle (generalStaticIdle, (void*)this); +} + +core::style::Font *FltkPlatform::createFont (core::style::FontAttrs + *attrs, + bool tryEverything) +{ + return FltkFont::create (attrs); +} + +core::style::Color *FltkPlatform::createColor (int color) +{ + return FltkColor::create (color); +} + +core::style::Tooltip *FltkPlatform::createTooltip (const char *text) +{ + return FltkTooltip::create (text); +} + +void FltkPlatform::copySelection(const char *text) +{ + Fl::copy(text, strlen(text), 0); +} + +core::Imgbuf *FltkPlatform::createImgbuf (core::Imgbuf::Type type, + int width, int height, double gamma) +{ + return new FltkImgbuf (type, width, height, gamma); +} + +core::ui::ResourceFactory *FltkPlatform::getResourceFactory () +{ + return &resourceFactory; +} + + +void FltkPlatform::attachResource (ui::FltkResource *resource) +{ + resources->append (resource); + resource->attachView (view); +} + +void FltkPlatform::detachResource (ui::FltkResource *resource) +{ + resources->removeRef (resource); +} + +} // namespace fltk +} // namespace dw diff --git a/dw/fltkplatform.hh b/dw/fltkplatform.hh new file mode 100644 index 0000000..2fb9563 --- /dev/null +++ b/dw/fltkplatform.hh @@ -0,0 +1,186 @@ +#ifndef __DW_FLTKPLATFORM_HH__ +#define __DW_FLTKPLATFORM_HH__ + +#ifndef __INCLUDED_FROM_DW_FLTK_CORE_HH__ +# error Do not include this file directly, use "fltkcore.hh" instead. +#endif + +namespace dw { + +/** + * \brief This namespace contains FLTK implementations of Dw interfaces. + */ +namespace fltk { + +class FltkFont: public core::style::Font +{ + class FontFamily: public lout::object::Object { + Fl_Font font[4]; + public: + FontFamily (Fl_Font fontNormal, Fl_Font fontBold, + Fl_Font fontItalic, Fl_Font fontBoldItalic); + void set (Fl_Font, int attrs); + Fl_Font get (int attrs); + }; + + static FontFamily standardFontFamily; + + static lout::container::typed::HashTable *systemFonts; + static lout::container::typed::HashTable *fontsTable; + + FltkFont (core::style::FontAttrs *attrs); + ~FltkFont (); + + static void initSystemFonts (); + +public: + Fl_Font font; + + static FltkFont *create (core::style::FontAttrs *attrs); + static bool fontExists (const char *name); + static Fl_Font get (const char *name, int attrs); +}; + + +class FltkColor: public core::style::Color +{ + static lout::container::typed::HashTable *colorsTable; + + FltkColor (int color); + ~FltkColor (); + +public: + int colors[SHADING_NUM]; + + static FltkColor *create(int color); +}; + +class FltkTooltip: public core::style::Tooltip +{ +private: + FltkTooltip (const char *text); + ~FltkTooltip (); +public: + static FltkTooltip *create(const char *text); + static void cancel(); + void onEnter(); + void onLeave(); + void onMotion(); +}; + + +/** + * \brief This interface adds some more methods for all flkt-based views. + */ +class FltkView: public core::View +{ +public: + virtual bool usesFltkWidgets () = 0; + + virtual void addFltkWidget (Fl_Widget *widget, + core::Allocation *allocation); + virtual void removeFltkWidget (Fl_Widget *widget); + virtual void allocateFltkWidget (Fl_Widget *widget, + core::Allocation *allocation); + virtual void drawFltkWidget (Fl_Widget *widget, core::Rectangle *area); +}; + + +class FltkPlatform: public core::Platform +{ +private: + class FltkResourceFactory: public core::ui::ResourceFactory + { + private: + FltkPlatform *platform; + + public: + inline void setPlatform (FltkPlatform *platform) { + this->platform = platform; } + + core::ui::LabelButtonResource *createLabelButtonResource (const char + *label); + core::ui::ComplexButtonResource * + createComplexButtonResource (core::Widget *widget, bool relief); + core::ui::ListResource * + createListResource (core::ui::ListResource::SelectionMode selectionMode, + int rows); + core::ui::OptionMenuResource *createOptionMenuResource (); + core::ui::EntryResource *createEntryResource (int size, bool password, + const char *label); + core::ui::MultiLineTextResource *createMultiLineTextResource (int cols, + int rows); + core::ui::CheckButtonResource *createCheckButtonResource (bool + activated); + core::ui::RadioButtonResource * + createRadioButtonResource (core::ui::RadioButtonResource + *groupedWith, bool activated); + }; + + FltkResourceFactory resourceFactory; + + class IdleFunc: public lout::object::Object + { + public: + int id; + void (core::Layout::*func) (); + }; + + core::Layout *layout; + + lout::container::typed::List *idleQueue; + bool idleFuncRunning; + int idleFuncId; + + static void generalStaticIdle(void *data); + void generalIdle(); + + FltkView *view; + lout::container::typed::List *resources; + +public: + FltkPlatform (); + ~FltkPlatform (); + + void setLayout (core::Layout *layout); + + void attachView (core::View *view); + + void detachView (core::View *view); + + int textWidth (core::style::Font *font, const char *text, int len); + char *textToUpper (const char *text, int len); + char *textToLower (const char *text, int len); + int nextGlyph (const char *text, int idx); + int prevGlyph (const char *text, int idx); + float dpiX (); + float dpiY (); + + int addIdle (void (core::Layout::*func) ()); + void removeIdle (int idleId); + + core::style::Font *createFont (core::style::FontAttrs *attrs, + bool tryEverything); + bool fontExists (const char *name); + core::style::Color *createColor (int color); + core::style::Tooltip *createTooltip (const char *text); + void cancelTooltip(); + + core::Imgbuf *createImgbuf (core::Imgbuf::Type type, int width, int height, + double gamma); + + void copySelection(const char *text); + + core::ui::ResourceFactory *getResourceFactory (); + + void attachResource (ui::FltkResource *resource); + void detachResource (ui::FltkResource *resource); +}; + +} // namespace fltk +} // namespace dw + +#endif // __DW_FLTKPLATFORM_HH__ diff --git a/dw/fltkpreview.cc b/dw/fltkpreview.cc new file mode 100644 index 0000000..b234e81 --- /dev/null +++ b/dw/fltkpreview.cc @@ -0,0 +1,316 @@ +/* + * RTFL (originally part of dillo) + * + * Copyright 2005-2007 Sebastian Geerken + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version; with the following exception: + * + * The copyright holders of RTFL give you permission to link this file + * statically or dynamically against all versions of the graphviz + * library, which are published by AT&T Corp. under one of the following + * licenses: + * + * - Common Public License version 1.0 as published by International + * Business Machines Corporation (IBM), or + * - Eclipse Public License version 1.0 as published by the Eclipse + * Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "../lout/msg.h" + +#include "fltkpreview.hh" +#include "fltkmisc.hh" + +#include +#include +#include +#include + +#include "preview.xbm" + +namespace dw { +namespace fltk { + +FltkPreview::FltkPreview (int x, int y, int w, int h, + dw::core::Layout *layout, const char *label): + FltkViewBase (x, y, w, h, label) +{ + layout->attachView (this); + + scrollX = 0; + scrollY = 0; + scrollWidth = 1; + scrollHeight = 1; +} + +FltkPreview::~FltkPreview () +{ +} + +int FltkPreview::handle (int event) +{ + return FltkViewBase::handle (event); +} + +int FltkPreview::translateViewXToCanvasX (int x) +{ + return x * canvasWidth / w (); +} + +int FltkPreview::translateViewYToCanvasY (int y) +{ + return y * canvasHeight / h (); +} + +int FltkPreview::translateCanvasXToViewX (int x) +{ + return x * w () / canvasWidth; +} + +int FltkPreview::translateCanvasYToViewY (int y) +{ + return y * h () / canvasHeight; +} + +void FltkPreview::setCanvasSize (int width, int ascent, int descent) +{ + FltkViewBase::setCanvasSize (width, ascent, descent); + if (parent() && parent()->visible ()) + ((FltkPreviewWindow*)parent())->reallocate (); +} + +bool FltkPreview::usesViewport () +{ + return true; +} + +int FltkPreview::getHScrollbarThickness () +{ + return 0; +} + +int FltkPreview::getVScrollbarThickness () +{ + return 0; +} + +void FltkPreview::scrollTo (int x, int y) +{ + scrollX = x; + scrollY = y; +} + +void FltkPreview::scroll (dw::core::ScrollCommand cmd) +{ + MSG_ERR("FltkPreview::scroll not implemented\n"); +} + +void FltkPreview::setViewportSize (int width, int height, + int hScrollbarThickness, + int vScrollbarThickness) +{ + scrollWidth = width - vScrollbarThickness; + scrollHeight = height - hScrollbarThickness; +} + +void FltkPreview::drawText (core::style::Font *font, + core::style::Color *color, + core::style::Color::Shading shading, + int x, int y, const char *text, int len) +{ + /* + * We must call setfont() before calling getwidth() (or anything + * else that measures text). + */ + FltkFont *ff = (FltkFont*)font; + Fl::set_font(ff->font, translateCanvasXToViewX (ff->size)); +#if 0 + /** + * \todo Normally, this should already be known, maybe it + * should be passed? + */ + int width = (int)getwidth (text, len); + int height = font->ascent; // No descent, this would look to "bold". + + int x1 = translateCanvasXToViewX (x); + int y1 = translateCanvasYToViewY (y); + int x2 = translateCanvasXToViewX (x + width); + int y2 = translateCanvasYToViewY (y + height); + Rectangle rect (x1, y1, x2 - x1, y2 - y1); + + setcolor(((FltkColor*)color)->colors[shading]); + fillrect (rect); +#endif + fl_color(((FltkColor*)color)->colors[shading]); + fl_draw(text, len, translateCanvasXToViewX (x), translateCanvasYToViewY(y)); +} + +void FltkPreview::drawSimpleWrappedText (core::style::Font *font, + core::style::Color *color, + core::style::Color::Shading shading, + int x, int y, int w, int h, + const char *text) +{ +} + +void FltkPreview::drawImage (core::Imgbuf *imgbuf, int xRoot, int yRoot, + int x, int y, int width, int height) +{ +} + +bool FltkPreview::usesFltkWidgets () +{ + return false; +} + +void FltkPreview::drawFltkWidget (Fl_Widget *widget, + core::Rectangle *area) +{ +} + +// ---------------------------------------------------------------------- + +FltkPreviewWindow::FltkPreviewWindow (dw::core::Layout *layout): + Fl_Menu_Window (1, 1) +{ + box (FL_EMBOSSED_BOX); + + begin (); + preview = new FltkPreview (BORDER_WIDTH, BORDER_WIDTH, 1, 1, layout); + end (); + + hide (); +} + +FltkPreviewWindow::~FltkPreviewWindow () +{ +} + +void FltkPreviewWindow::showWindow () +{ + reallocate (); + show (); +} + +void FltkPreviewWindow::reallocate () +{ + int maxWidth = misc::screenWidth () / 2; + int maxHeight = misc::screenHeight () * 4 / 5; + int mx, my, width, height; + bool warp = false; + + if (preview->canvasHeight * maxWidth > maxHeight * preview->canvasWidth) { + // Expand to maximal height (most likely case). + width = preview->canvasWidth * maxHeight / preview->canvasHeight; + height = maxHeight; + } else { + // Expand to maximal width. + width = maxWidth; + height = preview->canvasHeight * maxWidth / preview->canvasWidth; + } + + Fl::get_mouse(mx, my); + + posX = mx - preview->translateCanvasXToViewX (preview->scrollX + + preview->scrollWidth / 2); + posY = my - preview->translateCanvasYToViewY (preview->scrollY + + preview->scrollHeight / 2); + + if (posX < 0) { + mx -= posX; + posX = 0; + warp = true; + } else if (posX + width > misc::screenWidth ()) { + mx -= (posX - (misc::screenWidth () - width)); + posX = misc::screenWidth () - width; + warp = true; + } + + if (posY < 0) { + my -= posY; + posY = 0; + warp = true; + } else if (posY + height > misc::screenHeight ()) { + my -= (posY - (misc::screenHeight () - height)); + posY = misc::screenHeight () - height; + warp = true; + } + + if (warp) + misc::warpPointer (mx, my); + + resize (posX, posY, width, height); + + preview->size(w () - 2 * BORDER_WIDTH, h () - 2 * BORDER_WIDTH); +} + +void FltkPreviewWindow::hideWindow () +{ + Fl_Window::hide (); +} + +void FltkPreviewWindow::scrollTo (int mouseX, int mouseY) +{ + preview->scrollX = + preview->translateViewXToCanvasX (mouseX - posX - BORDER_WIDTH) + - preview->scrollWidth / 2; + preview->scrollY = + preview->translateViewYToCanvasY (mouseY - posY - BORDER_WIDTH) + - preview->scrollHeight / 2; + preview->theLayout->scrollPosChanged (preview, + preview->scrollX, preview->scrollY); +} + +// ---------------------------------------------------------------------- + +FltkPreviewButton::FltkPreviewButton (int x, int y, int w, int h, + dw::core::Layout *layout, + const char *label): + Fl_Button (x, y, w, h, label) +{ + image (new Fl_Bitmap (preview_bits, preview_width, preview_height)); + window = new FltkPreviewWindow (layout); +} + +FltkPreviewButton::~FltkPreviewButton () +{ +} + +int FltkPreviewButton::handle (int event) +{ + /** \bug Some parts are missing. */ + + switch (event) { + case FL_PUSH: + window->showWindow (); + return Fl_Button::handle (event); + + case FL_DRAG: + if (window->visible ()) { + window->scrollTo (Fl::event_x_root (), Fl::event_y_root ()); + return 1; + } + return Fl_Button::handle (event); + + case FL_RELEASE: + window->hideWindow (); + return Fl_Button::handle (event); + + default: + return Fl_Button::handle (event); + } +} + +} // namespace fltk +} // namespace dw diff --git a/dw/fltkpreview.hh b/dw/fltkpreview.hh new file mode 100644 index 0000000..2382b86 --- /dev/null +++ b/dw/fltkpreview.hh @@ -0,0 +1,95 @@ +#ifndef __FlTKPREVIEW_HH__ +#define __FlTKPREVIEW_HH__ + +#include +#include +#include "fltkviewbase.hh" + +namespace dw { +namespace fltk { + +class FltkPreview: public FltkViewBase +{ + friend class FltkPreviewWindow; + +private: + int scrollX, scrollY, scrollWidth, scrollHeight; + +protected: + int translateViewXToCanvasX (int x); + int translateViewYToCanvasY (int y); + int translateCanvasXToViewX (int x); + int translateCanvasYToViewY (int y); + +public: + FltkPreview (int x, int y, int w, int h, dw::core::Layout *layout, + const char *label = 0); + ~FltkPreview (); + + int handle (int event); + + void setCanvasSize (int width, int ascent, int descent); + + bool usesViewport (); + int getHScrollbarThickness (); + int getVScrollbarThickness (); + void scrollTo (int x, int y); + void scroll (dw::core::ScrollCommand cmd); + void setViewportSize (int width, int height, + int hScrollbarThickness, int vScrollbarThickness); + + void drawText (core::style::Font *font, + core::style::Color *color, + core::style::Color::Shading shading, + int x, int y, const char *text, int len); + void drawSimpleWrappedText (core::style::Font *font, + core::style::Color *color, + core::style::Color::Shading shading, + int x, int y, int w, int h, + const char *text); + void drawImage (core::Imgbuf *imgbuf, int xRoot, int yRoot, + int x, int y, int width, int height); + + bool usesFltkWidgets (); + void drawFltkWidget (Fl_Widget *widget, core::Rectangle *area); +}; + + +class FltkPreviewWindow: public Fl_Menu_Window +{ +private: + enum { BORDER_WIDTH = 2 }; + + FltkPreview *preview; + int posX, posY; + +public: + FltkPreviewWindow (dw::core::Layout *layout); + ~FltkPreviewWindow (); + + void reallocate (); + + void showWindow (); + void hideWindow (); + + void scrollTo (int mouseX, int mouseY); +}; + + +class FltkPreviewButton: public Fl_Button +{ +private: + FltkPreviewWindow *window; + +public: + FltkPreviewButton (int x, int y, int w, int h, + dw::core::Layout *layout, const char *label = 0); + ~FltkPreviewButton (); + + int handle (int event); +}; + +} // namespace fltk +} // namespace dw + +#endif // __FlTKPREVIEW_HH__ diff --git a/dw/fltkui.cc b/dw/fltkui.cc new file mode 100644 index 0000000..ce47dcd --- /dev/null +++ b/dw/fltkui.cc @@ -0,0 +1,1395 @@ +/* + * RTFL (originally part of dillo) + * + * Copyright 2005-2007 Sebastian Geerken + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version; with the following exception: + * + * The copyright holders of RTFL give you permission to link this file + * statically or dynamically against all versions of the graphviz + * library, which are published by AT&T Corp. under one of the following + * licenses: + * + * - Common Public License version 1.0 as published by International + * Business Machines Corporation (IBM), or + * - Eclipse Public License version 1.0 as published by the Eclipse + * Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + + + +#include "fltkcore.hh" +#include "../lout/msg.h" +#include "../lout/misc.hh" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +//---------------------------------------------------------------------------- +/* + * Local sub classes + */ + +/* + * Used to enable CTRL+{a,e,d,k} in form inputs (for start,end,del,cut) + */ +class CustInput2 : public Fl_Input { +public: + CustInput2 (int x, int y, int w, int h, const char* l=0) : + Fl_Input(x,y,w,h,l) {}; + int handle(int e); +}; + +int CustInput2::handle(int e) +{ + int k = Fl::event_key(); + + _MSG("CustInput2::handle event=%d\n", e); + + // We're only interested in some flags + unsigned modifier = Fl::event_state() & (FL_SHIFT | FL_CTRL | FL_ALT); + + if (e == FL_KEYBOARD) { + if (k == FL_Page_Down || k == FL_Page_Up || k == FL_Up || k == FL_Down) { + // Let them through for key commands and viewport motion. + return 0; + } + if (modifier == FL_CTRL) { + if (k == 'a' || k == 'e') { + position(k == 'a' ? 0 : size()); + return 1; + } else if (k == 'k') { + cut(position(), size()); + return 1; + } else if (k == 'd') { + cut(position(), position()+1); + return 1; + } else if (k == 'h' || k == 'i' || k == 'j' || k == 'l' || k == 'm') { + // Fl_Input wants to use ^H as backspace, and also "insert a few + // selected control characters literally", but this gets in the way + // of key commands. + return 0; + } + } + } + return Fl_Input::handle(e); +} + + +/* + * Used to handle some keystrokes as shortcuts to option menuitems + * (i.e. jump to the next menuitem whose label starts with the pressed key) + */ +class CustChoice : public Fl_Choice { +public: + CustChoice (int x, int y, int w, int h, const char* l=0) : + Fl_Choice(x,y,w,h,l) {}; + int handle(int e); +}; + +int CustChoice::handle(int e) +{ + int k = Fl::event_key(); + unsigned modifier = Fl::event_state() & (FL_SHIFT|FL_CTRL|FL_ALT|FL_META); + + _MSG("CustChoice::handle %p e=%d active=%d focus=%d\n", + this, e, active(), (Fl::focus() == this)); + if (Fl::focus() != this) { + ; // Not Focused, let FLTK handle it + } else if (e == FL_KEYDOWN && modifier == 0) { + if (k == FL_Enter || k == FL_Down) { + return Fl_Choice::handle(FL_PUSH); // activate menu + + } else if (isalnum(k)) { // try key as shortcut to menuitem + int t = value()+1 >= size() ? 0 : value()+1; + while (t != value()) { + const Fl_Menu_Item *mi = &(menu()[t]); + if (mi->submenu()) // submenu? + ; + else if (mi->label() && mi->active()) { // menu item? + if (k == tolower(mi->label()[0])) { + value(mi); + return 1; // Let FLTK know we used this key + } + } + if (++t == size()) + t = 0; + } + } + } + + return Fl_Choice::handle(e); +} + +//---------------------------------------------------------------------------- + +namespace dw { +namespace fltk { +namespace ui { + +enum { RELIEF_X_THICKNESS = 3, RELIEF_Y_THICKNESS = 3 }; + +using namespace lout::object; +using namespace lout::container::typed; + +FltkResource::FltkResource (FltkPlatform *platform) +{ + DBG_OBJ_CREATE ("dw::fltk::ui::FltkResource"); + + this->platform = platform; + + allocation.x = 0; + allocation.y = 0; + allocation.width = 1; + allocation.ascent = 1; + allocation.descent = 0; + + style = NULL; + + enabled = true; +} + +/** + * This is not a constructor, since it calls some virtual methods, which + * should not be done in a C++ base constructor. + */ +void FltkResource::init (FltkPlatform *platform) +{ + view = NULL; + widget = NULL; + platform->attachResource (this); +} + +FltkResource::~FltkResource () +{ + platform->detachResource (this); + if (widget) { + if (view) { + view->removeFltkWidget(widget); + } + delete widget; + } + if (style) + style->unref (); + + DBG_OBJ_DELETE (); +} + +void FltkResource::attachView (FltkView *view) +{ + if (this->view) + MSG_ERR("FltkResource::attachView: multiple views!\n"); + + if (view->usesFltkWidgets ()) { + this->view = view; + + widget = createNewWidget (&allocation); + view->addFltkWidget (widget, &allocation); + if (style) + setWidgetStyle (widget, style); + if (! enabled) + widget->deactivate (); + } +} + +void FltkResource::detachView (FltkView *view) +{ + if (this->view != view) + MSG_ERR("FltkResource::detachView: this->view: %p view: %p\n", + this->view, view); + this->view = NULL; +} + +void FltkResource::sizeAllocate (core::Allocation *allocation) +{ + DBG_OBJ_ENTER ("resize", 0, "sizeAllocate", "%d, %d; %d * (%d + %d)", + allocation->x, allocation->y, allocation->width, + allocation->ascent, allocation->descent); + + this->allocation = *allocation; + view->allocateFltkWidget (widget, allocation); + + DBG_OBJ_LEAVE (); +} + +void FltkResource::draw (core::View *view, core::Rectangle *area) +{ + FltkView *fltkView = (FltkView*)view; + if (fltkView->usesFltkWidgets () && this->view == fltkView) { + fltkView->drawFltkWidget (widget, area); + } +} + +void FltkResource::setStyle (core::style::Style *style) +{ + if (this->style) + this->style->unref (); + + this->style = style; + style->ref (); + + setWidgetStyle (widget, style); +} + +void FltkResource::setWidgetStyle (Fl_Widget *widget, + core::style::Style *style) +{ + FltkFont *font = (FltkFont*)style->font; + widget->labelsize (font->size); + widget->labelfont (font->font); + + FltkColor *bg = (FltkColor*)style->backgroundColor; + if (bg) { + int normal_bg = bg->colors[FltkColor::SHADING_NORMAL]; + + if (style->color) { + int style_fg = ((FltkColor*)style->color)->colors + [FltkColor::SHADING_NORMAL]; + Fl_Color fg = fl_contrast(style_fg, normal_bg); + + widget->labelcolor(fg); + widget->selection_color(fg); + } + + widget->color(normal_bg); + } +} + +void FltkResource::setDisplayed(bool displayed) +{ + if (displayed) + widget->show(); + else + widget->hide(); +} + +bool FltkResource::displayed() +{ + bool ret = false; + + if (widget) { + // visible() is not the same thing as being show()n exactly, but + // show()/hide() set it appropriately for our purposes. + ret = widget->visible(); + } + return ret; +} + +bool FltkResource::isEnabled () +{ + return enabled; +} + +void FltkResource::setEnabled (bool enabled) +{ + this->enabled = enabled; + + if (enabled) + widget->activate (); + else + widget->deactivate (); +} + +// ---------------------------------------------------------------------- + +template FltkSpecificResource::FltkSpecificResource (FltkPlatform + *platform) : + FltkResource (platform) +{ + DBG_OBJ_CREATE ("dw::fltk::ui::FltkSpecificResource<>"); + DBG_OBJ_BASECLASS (I); + DBG_OBJ_BASECLASS (FltkResource); +} + +template FltkSpecificResource::~FltkSpecificResource () +{ + DBG_OBJ_DELETE (); +} + +template void FltkSpecificResource::sizeAllocate (core::Allocation + *allocation) +{ + FltkResource::sizeAllocate (allocation); +} + +template void FltkSpecificResource::draw (core::View *view, + core::Rectangle *area) +{ + FltkResource::draw (view, area); +} + +template void FltkSpecificResource::setStyle (core::style::Style + *style) +{ + FltkResource::setStyle (style); +} + +template bool FltkSpecificResource::isEnabled () +{ + return FltkResource::isEnabled (); +} + +template void FltkSpecificResource::setEnabled (bool enabled) +{ + FltkResource::setEnabled (enabled); +} + +// ---------------------------------------------------------------------- + +class EnterButton : public Fl_Button { +public: + EnterButton (int x,int y,int w,int h, const char* label = 0) : + Fl_Button (x,y,w,h,label) {}; + int handle(int e); +}; + +int EnterButton::handle(int e) +{ + if (e == FL_KEYBOARD && Fl::focus() == this && Fl::event_key() == FL_Enter){ + set_changed(); + simulate_key_action(); + do_callback(); + return 1; + } + return Fl_Button::handle(e); +} + +FltkLabelButtonResource::FltkLabelButtonResource (FltkPlatform *platform, + const char *label): + FltkSpecificResource (platform) +{ + this->label = strdup (label); + init (platform); +} + +FltkLabelButtonResource::~FltkLabelButtonResource () +{ + free((char *)label); +} + +Fl_Widget *FltkLabelButtonResource::createNewWidget (core::Allocation + *allocation) +{ + Fl_Button *button = + new EnterButton (allocation->x, allocation->y, allocation->width, + allocation->ascent + allocation->descent, label); + button->callback (widgetCallback, this); + button->when (FL_WHEN_RELEASE); + return button; +} + +void FltkLabelButtonResource::sizeRequest (core::Requisition *requisition) +{ + DBG_OBJ_ENTER0 ("resize", 0, "sizeRequest"); + + if (style) { + FltkFont *font = (FltkFont*)style->font; + fl_font(font->font,font->size); + requisition->width = + (int)fl_width (label, strlen (label)) + + 2 * RELIEF_X_THICKNESS; + requisition->ascent = font->ascent + RELIEF_Y_THICKNESS; + requisition->descent = font->descent + RELIEF_Y_THICKNESS; + } else { + requisition->width = 1; + requisition->ascent = 1; + requisition->descent = 0; + } + + DBG_OBJ_MSGF ("resize", 1, "result: %d * (%d + %d)", + requisition->width, requisition->ascent, requisition->descent); + DBG_OBJ_LEAVE (); +} + +/* + * Get FLTK state and translate to dw + * + * TODO: find a good home for this and the fltkviewbase.cc original. + */ +static core::ButtonState getDwButtonState () +{ + int s1 = Fl::event_state (); + int s2 = (core::ButtonState)0; + + if (s1 & FL_SHIFT) s2 |= core::SHIFT_MASK; + if (s1 & FL_CTRL) s2 |= core::CONTROL_MASK; + if (s1 & FL_ALT) s2 |= core::META_MASK; + if (s1 & FL_BUTTON1) s2 |= core::BUTTON1_MASK; + if (s1 & FL_BUTTON2) s2 |= core::BUTTON2_MASK; + if (s1 & FL_BUTTON3) s2 |= core::BUTTON3_MASK; + + return (core::ButtonState)s2; +} + +static void setButtonEvent(dw::core::EventButton *event) +{ + event->xCanvas = Fl::event_x(); + event->yCanvas = Fl::event_y(); + event->state = getDwButtonState(); + event->button = Fl::event_button(); + event->numPressed = Fl::event_clicks() + 1; +} + +void FltkLabelButtonResource::widgetCallback (Fl_Widget *widget, + void *data) +{ + if (!Fl::event_button3()) { + FltkLabelButtonResource *lbr = (FltkLabelButtonResource*) data; + dw::core::EventButton event; + setButtonEvent(&event); + lbr->emitClicked(&event); + } +} + +const char *FltkLabelButtonResource::getLabel () +{ + return label; +} + + +void FltkLabelButtonResource::setLabel (const char *label) +{ + free((char *)this->label); + this->label = strdup (label); + + widget->label (this->label); + queueResize (true); +} + +// ---------------------------------------------------------------------- + +FltkEntryResource::FltkEntryResource (FltkPlatform *platform, int size, + bool password, const char *label): + FltkSpecificResource (platform) +{ + this->size = size; + this->password = password; + this->label = label ? strdup(label) : NULL; + this->label_w = 0; + + initText = NULL; + editable = false; + + init (platform); +} + +FltkEntryResource::~FltkEntryResource () +{ + if (initText) + free((char *)initText); + if (label) + free(label); +} + +Fl_Widget *FltkEntryResource::createNewWidget (core::Allocation + *allocation) +{ + Fl_Input *input = + new CustInput2(allocation->x, allocation->y, allocation->width, + allocation->ascent + allocation->descent); + if (password) + input->type(FL_SECRET_INPUT); + input->callback (widgetCallback, this); + input->when (FL_WHEN_ENTER_KEY_ALWAYS); + + if (label) { + input->label(label); + input->align(FL_ALIGN_LEFT); + } + if (initText) + input->value (initText); + + return input; +} + +void FltkEntryResource::setWidgetStyle (Fl_Widget *widget, + core::style::Style *style) +{ + Fl_Input *in = (Fl_Input *)widget; + + FltkResource::setWidgetStyle(widget, style); + + in->textcolor(widget->labelcolor()); + in->cursor_color(in->textcolor()); + in->textsize(in->labelsize()); + in->textfont(in->labelfont()); + + if (label) { + int h; + label_w = 0; + widget->measure_label(label_w, h); + label_w += RELIEF_X_THICKNESS; + } +} + +void FltkEntryResource::setDisplayed(bool displayed) +{ + FltkResource::setDisplayed(displayed); + queueResize(true); +} + +void FltkEntryResource::sizeRequest (core::Requisition *requisition) +{ + DBG_OBJ_ENTER0 ("resize", 0, "sizeRequest"); + + if (displayed() && style) { + FltkFont *font = (FltkFont*)style->font; + fl_font(font->font,font->size); + // WORKAROUND: A bug with fl_width(uint_t) on non-xft X was present in + // 1.3.0 (STR #2688). + requisition->width = + (int)fl_width ("n") + * (size == UNLIMITED_SIZE ? 10 : size) + + label_w + (2 * RELIEF_X_THICKNESS); + requisition->ascent = font->ascent + RELIEF_Y_THICKNESS; + requisition->descent = font->descent + RELIEF_Y_THICKNESS; + } else { + requisition->width = 0; + requisition->ascent = 0; + requisition->descent = 0; + } + + DBG_OBJ_MSGF ("resize", 1, "result: %d * (%d + %d)", + requisition->width, requisition->ascent, requisition->descent); + DBG_OBJ_LEAVE (); +} + +void FltkEntryResource::sizeAllocate (core::Allocation *allocation) +{ + if (!label) { + FltkResource::sizeAllocate(allocation); + } else { + DBG_OBJ_MSGF ("resize", 0, + "sizeAllocate (%d, %d; %d * (%d + %d))", + allocation->x, allocation->y, allocation->width, + allocation->ascent, allocation->descent); + + this->allocation = *allocation; + + /* push the Fl_Input over to the right of the label */ + core::Allocation a = this->allocation; + a.x += this->label_w; + a.width -= this->label_w; + view->allocateFltkWidget (widget, &a); + } +} + +void FltkEntryResource::widgetCallback (Fl_Widget *widget, void *data) +{ + ((FltkEntryResource*)data)->emitActivate (); +} + +const char *FltkEntryResource::getText () +{ + return ((Fl_Input*)widget)->value (); +} + +void FltkEntryResource::setText (const char *text) +{ + if (initText) + free((char *)initText); + initText = strdup (text); + + ((Fl_Input*)widget)->value (initText); +} + +bool FltkEntryResource::isEditable () +{ + return editable; +} + +void FltkEntryResource::setEditable (bool editable) +{ + this->editable = editable; +} + +void FltkEntryResource::setMaxLength (int maxlen) +{ + ((Fl_Input *)widget)->maximum_size(maxlen); +} + +// ---------------------------------------------------------------------- + +static int kf_backspace_word (int c, Fl_Text_Editor *e) +{ + int p1, p2 = e->insert_position(); + + e->previous_word(); + p1 = e->insert_position(); + e->buffer()->remove(p1, p2); + e->show_insert_position(); + e->set_changed(); + if (e->when() & FL_WHEN_CHANGED) + e->do_callback(); + return 0; +} + +FltkMultiLineTextResource::FltkMultiLineTextResource (FltkPlatform *platform, + int cols, int rows): + FltkSpecificResource (platform) +{ + buffer = new Fl_Text_Buffer; + text_copy = NULL; + editable = false; + + numCols = cols; + numRows = rows; + + // Check values. Upper bound check is left to the caller. + if (numCols < 1) { + MSG_WARN("numCols = %d is set to 1.\n", numCols); + numCols = 1; + } + if (numRows < 1) { + MSG_WARN("numRows = %d is set to 1.\n", numRows); + numRows = 1; + } + + init (platform); +} + +FltkMultiLineTextResource::~FltkMultiLineTextResource () +{ + /* Free memory avoiding a double-free of text buffers */ + ((Fl_Text_Editor *) widget)->buffer (0); + delete buffer; + if (text_copy) + free(text_copy); +} + +Fl_Widget *FltkMultiLineTextResource::createNewWidget (core::Allocation + *allocation) +{ + Fl_Text_Editor *text = + new Fl_Text_Editor (allocation->x, allocation->y, allocation->width, + allocation->ascent + allocation->descent); + text->wrap_mode(Fl_Text_Display::WRAP_AT_BOUNDS, 0); + text->buffer (buffer); + text->remove_key_binding(FL_BackSpace, FL_TEXT_EDITOR_ANY_STATE); + text->add_key_binding(FL_BackSpace, 0, Fl_Text_Editor::kf_backspace); + text->add_key_binding(FL_BackSpace, FL_CTRL, kf_backspace_word); + return text; +} + +void FltkMultiLineTextResource::setWidgetStyle (Fl_Widget *widget, + core::style::Style *style) +{ + Fl_Text_Editor *ed = (Fl_Text_Editor *)widget; + + FltkResource::setWidgetStyle(widget, style); + + ed->textcolor(widget->labelcolor()); + ed->cursor_color(ed->textcolor()); + ed->textsize(ed->labelsize()); + ed->textfont(ed->labelfont()); +} + +void FltkMultiLineTextResource::sizeRequest (core::Requisition *requisition) +{ + DBG_OBJ_ENTER0 ("resize", 0, "sizeRequest"); + + if (style) { + FltkFont *font = (FltkFont*)style->font; + fl_font(font->font,font->size); + // WORKAROUND: A bug with fl_width(uint_t) on non-xft X was present in + // 1.3.0 (STR #2688). + requisition->width = + (int)fl_width ("n") * numCols + 2 * RELIEF_X_THICKNESS; + requisition->ascent = + RELIEF_Y_THICKNESS + font->ascent + + (font->ascent + font->descent) * (numRows - 1); + requisition->descent = + font->descent + + RELIEF_Y_THICKNESS; + } else { + requisition->width = 1; + requisition->ascent = 1; + requisition->descent = 0; + } + + DBG_OBJ_MSGF ("resize", 1, "result: %d * (%d + %d)", + requisition->width, requisition->ascent, requisition->descent); + DBG_OBJ_LEAVE (); +} + +const char *FltkMultiLineTextResource::getText () +{ + /* FLTK-1.3 insists upon returning a new copy of the buffer text, so + * we have to keep track of it. + */ + if (text_copy) + free(text_copy); + text_copy = buffer->text(); + return text_copy; +} + +void FltkMultiLineTextResource::setText (const char *text) +{ + buffer->text (text); +} + +bool FltkMultiLineTextResource::isEditable () +{ + return editable; +} + +void FltkMultiLineTextResource::setEditable (bool editable) +{ + this->editable = editable; +} + +// ---------------------------------------------------------------------- + +template +FltkToggleButtonResource::FltkToggleButtonResource (FltkPlatform *platform, + bool activated): + FltkSpecificResource (platform) +{ + initActivated = activated; +} + + +template +FltkToggleButtonResource::~FltkToggleButtonResource () +{ +} + + +template +Fl_Widget *FltkToggleButtonResource::createNewWidget (core::Allocation + *allocation) +{ + Fl_Button *button = createNewButton (allocation); + button->value (initActivated); + return button; +} + +template +void FltkToggleButtonResource::setWidgetStyle (Fl_Widget *widget, + core::style::Style *style) +{ + FltkResource::setWidgetStyle(widget, style); + + widget->selection_color(FL_BLACK); +} + + +template +void FltkToggleButtonResource::sizeRequest (core::Requisition *requisition) +{ + DBG_OBJ_ENTER0 ("resize", 0, "sizeRequest"); + + FltkFont *font = (FltkFont *) + (this->FltkResource::style ? this->FltkResource::style->font : NULL); + + if (font) { + fl_font(font->font, font->size); + requisition->width = font->ascent + font->descent + 2*RELIEF_X_THICKNESS; + requisition->ascent = font->ascent + RELIEF_Y_THICKNESS; + requisition->descent = font->descent + RELIEF_Y_THICKNESS; + } else { + requisition->width = 1; + requisition->ascent = 1; + requisition->descent = 0; + } + + DBG_OBJ_MSGF ("resize", 1, "result: %d * (%d + %d)", + requisition->width, requisition->ascent, requisition->descent); + DBG_OBJ_LEAVE (); +} + + +template +bool FltkToggleButtonResource::isActivated () +{ + return ((Fl_Button*)this->widget)->value (); +} + + +template +void FltkToggleButtonResource::setActivated (bool activated) +{ + initActivated = activated; + ((Fl_Button*)this->widget)->value (initActivated); +} + +// ---------------------------------------------------------------------- + +FltkCheckButtonResource::FltkCheckButtonResource (FltkPlatform *platform, + bool activated): + FltkToggleButtonResource (platform, + activated) +{ + init (platform); +} + + +FltkCheckButtonResource::~FltkCheckButtonResource () +{ +} + + +Fl_Button *FltkCheckButtonResource::createNewButton (core::Allocation + *allocation) +{ + Fl_Check_Button *cb = + new Fl_Check_Button (allocation->x, allocation->y, allocation->width, + allocation->ascent + allocation->descent); + return cb; +} + +// ---------------------------------------------------------------------- + +bool FltkRadioButtonResource::Group::FltkGroupIterator::hasNext () +{ + return it.hasNext (); +} + +dw::core::ui::RadioButtonResource +*FltkRadioButtonResource::Group::FltkGroupIterator::getNext () +{ + return (dw::core::ui::RadioButtonResource*)it.getNext (); +} + +void FltkRadioButtonResource::Group::FltkGroupIterator::unref () +{ + delete this; +} + + +FltkRadioButtonResource::Group::Group (FltkRadioButtonResource + *radioButtonResource) +{ + list = new lout::container::typed::List (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 (platform, + activated) +{ + init (platform); + + if (groupedWith) { + group = groupedWith->group; + group->connect (this); + } else + group = new Group (this); +} + + +FltkRadioButtonResource::~FltkRadioButtonResource () +{ + group->unconnect (this); +} + +dw::core::ui::RadioButtonResource::GroupIterator +*FltkRadioButtonResource::groupIterator () +{ + return group->groupIterator (); +} + +void FltkRadioButtonResource::widgetCallback (Fl_Widget *widget, + void *data) +{ + if (widget->when () & FL_WHEN_CHANGED) + ((FltkRadioButtonResource*)data)->buttonClicked (); +} + +void FltkRadioButtonResource::buttonClicked () +{ + for (Iterator it = group->iterator (); + it.hasNext (); ) { + FltkRadioButtonResource *other = it.getNext (); + other->setActivated (other == this); + } +} + +Fl_Button *FltkRadioButtonResource::createNewButton (core::Allocation + *allocation) +{ + /* + * Groups of Fl_Radio_Button must be added to one Fl_Group, which is + * not possible in this context. For this, we do the grouping ourself, + * based on FltkRadioButtonResource::Group. + * + * What we actually need for this, is a widget, which behaves like a + * check button, but looks like a radio button. The first depends on the + * type, the second on the style. Since the type is simpler to change + * than the style, we create a radio button, and then change the type + * (instead of creating a check button, and changing the style). + */ + + Fl_Button *button = + new Fl_Round_Button (allocation->x, allocation->y, allocation->width, + allocation->ascent + allocation->descent); + button->when (FL_WHEN_CHANGED); + button->callback (widgetCallback, this); + button->type (FL_TOGGLE_BUTTON); + + return button; +} + +// ---------------------------------------------------------------------- + +template dw::core::Iterator * +FltkSelectionResource::iterator (dw::core::Content::Type mask, bool atEnd) +{ + /** \bug Implementation. */ + return new core::EmptyIterator (this->getEmbed (), mask, atEnd); +} + +// ---------------------------------------------------------------------- + +FltkOptionMenuResource::FltkOptionMenuResource (FltkPlatform *platform): + FltkSelectionResource (platform) +{ + /* Fl_Menu_ does not like multiple menu items with the same label, and + * insert() treats some characters specially unless escaped, so let's + * do our own menu handling. + */ + itemsAllocated = 0x10; + menu = new Fl_Menu_Item[itemsAllocated]; + memset(menu, 0, itemsAllocated * sizeof(Fl_Menu_Item)); + itemsUsed = 1; // menu[0].text == NULL, which is an end-of-menu marker. + + init (platform); +} + +FltkOptionMenuResource::~FltkOptionMenuResource () +{ + for (int i = 0; i < itemsUsed; i++) { + if (menu[i].text) + free((char *) menu[i].text); + } + delete[] menu; +} + +void FltkOptionMenuResource::setWidgetStyle (Fl_Widget *widget, + core::style::Style *style) +{ + Fl_Choice *ch = (Fl_Choice *)widget; + + FltkResource::setWidgetStyle(widget, style); + + ch->textcolor(widget->labelcolor()); + ch->textfont(ch->labelfont()); + ch->textsize(ch->labelsize()); +} + +Fl_Widget *FltkOptionMenuResource::createNewWidget (core::Allocation + *allocation) +{ + Fl_Choice *choice = + new CustChoice (allocation->x, allocation->y, + allocation->width, + allocation->ascent + allocation->descent); + choice->menu(menu); + return choice; +} + +void FltkOptionMenuResource::widgetCallback (Fl_Widget *widget, + void *data) +{ +} + +int FltkOptionMenuResource::getMaxItemWidth() +{ + int i, max = 0; + + for (i = 0; i < itemsUsed; i++) { + int width = 0; + const char *str = menu[i].text; + + if (str) { + width = fl_width(str); + if (width > max) + max = width; + } + } + return max; +} + +void FltkOptionMenuResource::sizeRequest (core::Requisition *requisition) +{ + DBG_OBJ_ENTER0 ("resize", 0, "sizeRequest"); + + if (style) { + FltkFont *font = (FltkFont*)style->font; + fl_font(font->font, font->size); + int maxItemWidth = getMaxItemWidth (); + requisition->ascent = font->ascent + RELIEF_Y_THICKNESS; + requisition->descent = font->descent + RELIEF_Y_THICKNESS; + requisition->width = maxItemWidth + + (requisition->ascent + requisition->descent) + + 2 * RELIEF_X_THICKNESS; + } else { + requisition->width = 1; + requisition->ascent = 1; + requisition->descent = 0; + } + + DBG_OBJ_MSGF ("resize", 1, "result: %d * (%d + %d)", + requisition->width, requisition->ascent, requisition->descent); + DBG_OBJ_LEAVE (); +} + +void FltkOptionMenuResource::enlargeMenu () +{ + Fl_Choice *ch = (Fl_Choice *)widget; + int selected = ch->value(); + Fl_Menu_Item *newMenu; + + itemsAllocated += 0x10; + newMenu = new Fl_Menu_Item[itemsAllocated]; + memcpy(newMenu, menu, itemsUsed * sizeof(Fl_Menu_Item)); + memset(newMenu + itemsUsed, 0, 0x10 * sizeof(Fl_Menu_Item)); + delete[] menu; + menu = newMenu; + ch->menu(menu); + ch->value(selected); +} + +Fl_Menu_Item *FltkOptionMenuResource::newItem() +{ + Fl_Menu_Item *item; + + if (itemsUsed == itemsAllocated) + enlargeMenu(); + + item = menu + itemsUsed - 1; + itemsUsed++; + + return item; +} + +void FltkOptionMenuResource::addItem (const char *str, + bool enabled, bool selected) +{ + Fl_Menu_Item *item = newItem(); + + item->text = strdup(str); + + if (enabled == false) + item->flags = FL_MENU_INACTIVE; + + if (selected) + ((Fl_Choice *)widget)->value(item); + + queueResize (true); +} + +void FltkOptionMenuResource::setItem (int index, bool selected) +{ + if (selected) + ((Fl_Choice *)widget)->value(menu+index); +} + +void FltkOptionMenuResource::pushGroup (const char *name, bool enabled) +{ + Fl_Menu_Item *item = newItem(); + + item->text = strdup(name); + + if (enabled == false) + item->flags = FL_MENU_INACTIVE; + + item->flags |= FL_SUBMENU; + + queueResize (true); +} + +void FltkOptionMenuResource::popGroup () +{ + /* Item with NULL text field closes the submenu */ + newItem(); + queueResize (true); +} + +bool FltkOptionMenuResource::isSelected (int index) +{ + return index == ((Fl_Choice *)widget)->value(); +} + +int FltkOptionMenuResource::getNumberOfItems() +{ + return ((Fl_Choice*)widget)->size(); +} + +// ---------------------------------------------------------------------- + +class CustBrowser : public Fl_Browser { +public: + CustBrowser(int x, int y, int w, int h) : Fl_Browser(x, y, w, h) {}; + int full_width() const; + int full_height() const {return Fl_Browser::full_height();} + int avg_height() {return size() ? Fl_Browser_::incr_height() : 0;} +}; + +/* + * Fl_Browser_ has a full_width(), but it has a tendency to contain 0, so... + */ +int CustBrowser::full_width() const +{ + int max = 0; + void *item = item_first(); + + while (item) { + int w = item_width(item); + + if (w > max) + max = w; + + item = item_next(item); + } + return max; +} + +FltkListResource::FltkListResource (FltkPlatform *platform, + core::ui::ListResource::SelectionMode + selectionMode, int rowCount): + FltkSelectionResource (platform), + currDepth(0) +{ + mode = selectionMode; + showRows = rowCount; + init (platform); +} + +FltkListResource::~FltkListResource () +{ +} + + +Fl_Widget *FltkListResource::createNewWidget (core::Allocation *allocation) +{ + CustBrowser *b = + new CustBrowser (allocation->x, allocation->y, allocation->width, + allocation->ascent + allocation->descent); + + b->type((mode == SELECTION_MULTIPLE) ? FL_MULTI_BROWSER : FL_HOLD_BROWSER); + b->callback(widgetCallback, this); + b->when(FL_WHEN_CHANGED); + b->column_widths(colWidths); + b->column_char('\a'); // I just chose a nonprinting character. + + return b; +} + +void FltkListResource::setWidgetStyle (Fl_Widget *widget, + core::style::Style *style) +{ + Fl_Browser *b = (Fl_Browser *)widget; + + FltkResource::setWidgetStyle(widget, style); + + b->textfont(widget->labelfont()); + b->textsize(widget->labelsize()); + b->textcolor(widget->labelcolor()); + + colWidths[0] = b->textsize(); + colWidths[1] = colWidths[0]; + colWidths[2] = colWidths[0]; + colWidths[3] = 0; +} + +void FltkListResource::widgetCallback (Fl_Widget *widget, void *data) +{ + Fl_Browser *b = (Fl_Browser *) widget; + + if (b->selected(b->value())) { + /* If it shouldn't be selectable, deselect it again. It would be nice to + * have a less unpleasant way to do this. + */ + const char *inactive_code; + if ((inactive_code = strstr(b->text(b->value()), "@N"))) { + const char *ignore_codes = strstr(b->text(b->value()), "@."); + + if (inactive_code < ignore_codes) + b->select(b->value(), 0); + } + } +} + +void *FltkListResource::newItem (const char *str, bool enabled, bool selected) +{ + Fl_Browser *b = (Fl_Browser *) widget; + int index = b->size() + 1; + char *label = (char *)malloc(strlen(str) + 1 + currDepth + 4), + *s = label; + + memset(s, '\a', currDepth); + s += currDepth; + if (!enabled) { + // FL_INACTIVE_COLOR + *s++ = '@'; + *s++ = 'N'; + } + // ignore further '@' chars + *s++ = '@'; + *s++ = '.'; + + strcpy(s, str); + + b->add(label); + free(label); + + if (selected) { + b->select(index, selected); + if (b->type() == FL_HOLD_BROWSER) { + /* Left to its own devices, it sometimes has some suboptimal ideas + * about how to scroll, and sometimes doesn't seem to show everything + * where it thinks it is. + */ + if (index > showRows) { + /* bottomline() and middleline() don't work because the widget is + * too tiny at this point for the bbox() call in + * Fl_Browser::lineposition() to do what one would want. + */ + b->topline(index - showRows + 1); + } else { + b->topline(1); + } + } + } + queueResize (true); + return NULL; +} + +void FltkListResource::addItem (const char *str, bool enabled, bool selected) +{ + // Fl_Browser_::incr_height() for item height won't do the right thing if + // the first item doesn't have anything to it. + if (!str || !*str) + str = " "; + newItem(str, enabled, selected); +} + +void FltkListResource::setItem (int index, bool selected) +{ + Fl_Browser *b = (Fl_Browser *) widget; + + b->select(index + 1, selected); +} + +void FltkListResource::pushGroup (const char *name, bool enabled) +{ + bool en = false; + bool selected = false; + + // Fl_Browser_::incr_height() for item height won't do the right thing if + // the first item doesn't have anything to it. + if (!name || !*name) + name = " "; + + // TODO: Proper disabling of item groups + newItem(name, en, selected); + + if (currDepth < 3) + currDepth++; +} + +void FltkListResource::popGroup () +{ + CustBrowser *b = (CustBrowser *) widget; + + newItem(" ", false, false); + b->hide(b->size()); + + if (currDepth) + currDepth--; +} + +int FltkListResource::getMaxItemWidth() +{ + return ((CustBrowser *) widget)->full_width(); +} + +void FltkListResource::sizeRequest (core::Requisition *requisition) +{ + DBG_OBJ_ENTER0 ("resize", 0, "sizeRequest"); + + if (style) { + CustBrowser *b = (CustBrowser *) widget; + int height = b->full_height(); + requisition->width = getMaxItemWidth() + 4; + + if (showRows * b->avg_height() < height) { + height = showRows * b->avg_height(); + b->has_scrollbar(Fl_Browser_::VERTICAL_ALWAYS); + requisition->width += Fl::scrollbar_size(); + } else { + b->has_scrollbar(0); + } + + requisition->descent = style->font->descent + 2; + requisition->ascent = height - style->font->descent + 2; + } else { + requisition->width = 1; + requisition->ascent = 1; + requisition->descent = 0; + } + + DBG_OBJ_MSGF ("resize", 1, "result: %d * (%d + %d)", + requisition->width, requisition->ascent, requisition->descent); + DBG_OBJ_LEAVE (); +} + +int FltkListResource::getNumberOfItems() +{ + return ((Fl_Browser*)widget)->size(); +} + +bool FltkListResource::isSelected (int index) +{ + Fl_Browser *b = (Fl_Browser *) widget; + + return b->selected(index + 1) ? true : false; +} + +} // namespace ui +} // namespace fltk +} // namespace dw + diff --git a/dw/fltkui.hh b/dw/fltkui.hh new file mode 100644 index 0000000..fa47992 --- /dev/null +++ b/dw/fltkui.hh @@ -0,0 +1,505 @@ +#ifndef __DW_FLTK_UI_HH__ +#define __DW_FLTK_UI_HH__ + +#ifndef __INCLUDED_FROM_DW_FLTK_CORE_HH__ +# error Do not include this file directly, use "fltkcore.hh" instead. +#endif + +#include +#include +#include + +namespace dw { +namespace fltk { + +/** + * \brief FLTK implementation of dw::core::ui. + * + *
Update: The complicated design + * results from my insufficient knowledge of C++ some years ago; since + * then, I've learned how to deal with "diamond inheritance", as the + * (ideal, not actually implemented) design in the first diagram + * shows. It should be possible to implement this ideal design in a + * straightforward way, and so get rid of templates. --SG
+ * + * The design should be like this: + * + * \dot + * digraph G { + * node [shape=record, fontname=Helvetica, fontsize=10]; + * edge [arrowhead="none", arrowtail="empty", dir="both", + * labelfontname=Helvetica, labelfontsize=10, color="#404040", + * labelfontcolor="#000080"]; + * fontname=Helvetica; fontsize=10; + * + * subgraph cluster_core { + * style="dashed"; color="#000080"; fontname=Helvetica; fontsize=10; + * label="dw::core::ui"; + * + * Resource [color="#a0a0a0", URL="\ref dw::core::ui::Resource"]; + * LabelButtonResource [color="#a0a0a0", + * URL="\ref dw::core::ui::LabelButtonResource"]; + * EntryResource [color="#a0a0a0", + * URL="\ref dw::core::ui::EntryResource"]; + * } + * + * subgraph cluster_fltk { + * style="dashed"; color="#000080"; fontname=Helvetica; fontsize=10; + * label="dw::fltk::ui"; + * + * FltkResource [color="#a0a0a0", URL="\ref dw::fltk::ui::FltkResource"]; + * FltkLabelButtonResource + * [URL="\ref dw::fltk::ui::FltkLabelButtonResource"]; + * FltkEntryResource [URL="\ref dw::fltk::ui::FltkEntryResource"]; + * } + * + * Resource -> LabelButtonResource; + * Resource -> EntryResource; + * FltkResource -> FltkLabelButtonResource; + * FltkResource -> FltkEntryResource; + * Resource -> FltkResource; + * LabelButtonResource -> FltkLabelButtonResource; + * EntryResource -> FltkEntryResource; + * } + * \enddot + * + *
[\ref uml-legend "legend"]
+ * + * where dw::fltk::ui::FltkResource provides some base funtionality for all + * conctrete FLTK implementations of sub-interfaces of dw::core::ui::Resource. + * However, this is not directly possible in C++, since the base class + * dw::core::ui::Resource is ambiguous for + * dw::fltk::ui::FltkLabelButtonResource. + * + * To solve this, we have to remove the dependency between + * dw::fltk::ui::FltkResource and dw::core::ui::Resource, instead, the part + * of dw::core::ui::Resource, which is implemented in + * dw::fltk::ui::FltkResource, must be explicitly delegated from + * dw::fltk::ui::FltkLabelButtonResourceto dw::fltk::ui::FltkResource: + * + * \dot + * digraph G { + * node [shape=record, fontname=Helvetica, fontsize=10]; + * edge [arrowhead="none", arrowtail="empty", dir="both", + * labelfontname=Helvetica, labelfontsize=10, color="#404040", + * labelfontcolor="#000080"]; + * fontname=Helvetica; fontsize=10; + * + * subgraph cluster_core { + * style="dashed"; color="#000080"; fontname=Helvetica; fontsize=10; + * label="dw::core::ui"; + * + * Resource [color="#a0a0a0", URL="\ref dw::core::ui::Resource"]; + * LabelButtonResource [color="#a0a0a0", + * URL="\ref dw::core::ui::LabelButtonResource"]; + * EntryResource [color="#a0a0a0", + * URL="\ref dw::core::ui::EntryResource"]; + * } + * + * subgraph cluster_fltk { + * style="dashed"; color="#000080"; fontname=Helvetica; fontsize=10; + * label="dw::fltk::ui"; + * + * FltkResource [color="#a0a0a0", URL="\ref dw::fltk::ui::FltkResource"]; + * FltkLabelButtonResource + * [URL="\ref dw::fltk::ui::FltkLabelButtonResource"]; + * FltkEntryResource [URL="\ref dw::fltk::ui::FltkEntryResource"]; + * } + * + * Resource -> LabelButtonResource; + * Resource -> EntryResource; + * FltkResource -> FltkLabelButtonResource; + * FltkResource -> FltkEntryResource; + * LabelButtonResource -> FltkLabelButtonResource; + * EntryResource -> FltkEntryResource; + * } + * \enddot + * + *
[\ref uml-legend "legend"]
+ * + * To make this a bit simpler, we use templates: + * + * \dot + * digraph G { + * node [shape=record, fontname=Helvetica, fontsize=10]; + * edge [arrowhead="none", arrowtail="empty", dir="both", + * labelfontname=Helvetica, labelfontsize=10, color="#404040", + * labelfontcolor="#000080"]; + * fontname=Helvetica; fontsize=10; + * + * subgraph cluster_core { + * style="dashed"; color="#000080"; fontname=Helvetica; fontsize=10; + * label="dw::core::ui"; + * + * Resource [color="#a0a0a0", URL="\ref dw::core::ui::Resource"]; + * LabelButtonResource [color="#a0a0a0", + * URL="\ref dw::core::ui::LabelButtonResource"]; + * EntryResource [color="#a0a0a0", + * URL="\ref dw::core::ui::EntryResource"]; + * } + * + * subgraph cluster_fltk { + * style="dashed"; color="#000080"; fontname=Helvetica; fontsize=10; + * label="dw::fltk::ui"; + * + * FltkResource [color="#a0a0a0", URL="\ref dw::fltk::ui::FltkResource"]; + * FltkSpecificResource [color="#a0a0a0", + * fillcolor="#ffffc0", style="filled" + * URL="\ref dw::fltk::ui::FltkSpecificResource"]; + * FltkSpecificResource_button [color="#a0a0a0", + * label="FltkSpecificResource \"]; + * FltkSpecificResource_entry [color="#a0a0a0", + * label="FltkSpecificResource \"]; + * FltkEntryResource [URL="\ref dw::fltk::ui::FltkEntryResource"]; + * FltkLabelButtonResource + * [URL="\ref dw::fltk::ui::FltkLabelButtonResource"]; + * } + * + * Resource -> LabelButtonResource; + * Resource -> EntryResource; + * FltkResource -> FltkSpecificResource; + * FltkSpecificResource -> FltkSpecificResource_button [arrowhead="open", + * arrowtail="none", + * dir="both", + * style="dashed", + * color="#808000"]; + * FltkSpecificResource -> FltkSpecificResource_entry [arrowhead="open", + * arrowtail="none", + * dir="both", + * style="dashed", + * color="#808000"]; + * LabelButtonResource -> FltkSpecificResource_button; + * EntryResource -> FltkSpecificResource_entry; + * FltkSpecificResource_button -> FltkLabelButtonResource; + * FltkSpecificResource_entry -> FltkEntryResource; + * } + * \enddot + * + *
[\ref uml-legend "legend"]
+ */ +namespace ui { + +/** + * ... + */ +class FltkResource: public lout::object::Object +{ +private: + bool enabled; + +protected: + FltkView *view; + Fl_Widget *widget; + core::Allocation allocation; + FltkPlatform *platform; + + core::style::Style *style; + + FltkResource (FltkPlatform *platform); + void init (FltkPlatform *platform); + virtual Fl_Widget *createNewWidget (core::Allocation *allocation) = 0; + + virtual void setWidgetStyle (Fl_Widget *widget, core::style::Style *style); + void setDisplayed (bool displayed); + bool displayed(); +public: + ~FltkResource (); + + virtual void attachView (FltkView *view); + virtual void detachView (FltkView *view); + + void sizeAllocate (core::Allocation *allocation); + void draw (core::View *view, core::Rectangle *area); + + void setStyle (core::style::Style *style); + + bool isEnabled (); + void setEnabled (bool enabled); +}; + + +template class FltkSpecificResource: public I, public FltkResource +{ +public: + FltkSpecificResource (FltkPlatform *platform); + ~FltkSpecificResource (); + + void sizeAllocate (core::Allocation *allocation); + void draw (core::View *view, core::Rectangle *area); + void setStyle (core::style::Style *style); + + bool isEnabled (); + void setEnabled (bool enabled); +}; + + +class FltkLabelButtonResource: + public FltkSpecificResource +{ +private: + const char *label; + + static void widgetCallback (Fl_Widget *widget, void *data); + +protected: + Fl_Widget *createNewWidget (core::Allocation *allocation); + +public: + FltkLabelButtonResource (FltkPlatform *platform, const char *label); + ~FltkLabelButtonResource (); + + void sizeRequest (core::Requisition *requisition); + + const char *getLabel (); + void setLabel (const char *label); +}; + +/** + * \bug Maximal length not supported yet. + * \todo Text values are not synchronized (not needed in dillo). + */ +class FltkEntryResource: + public FltkSpecificResource +{ +private: + int size; + bool password; + const char *initText; + char *label; + int label_w; + bool editable; + + static void widgetCallback (Fl_Widget *widget, void *data); + void setDisplayed (bool displayed); + +protected: + Fl_Widget *createNewWidget (core::Allocation *allocation); + void setWidgetStyle (Fl_Widget *widget, core::style::Style *style); + +public: + FltkEntryResource (FltkPlatform *platform, int size, bool password, + const char *label); + ~FltkEntryResource (); + + void sizeRequest (core::Requisition *requisition); + void sizeAllocate (core::Allocation *allocation); + + const char *getText (); + void setText (const char *text); + bool isEditable (); + void setEditable (bool editable); + void setMaxLength (int maxlen); +}; + + +class FltkMultiLineTextResource: + public FltkSpecificResource +{ +private: + Fl_Text_Buffer *buffer; + char *text_copy; + bool editable; + int numCols, numRows; + +protected: + Fl_Widget *createNewWidget (core::Allocation *allocation); + void setWidgetStyle (Fl_Widget *widget, core::style::Style *style); + +public: + FltkMultiLineTextResource (FltkPlatform *platform, int cols, int rows); + ~FltkMultiLineTextResource (); + + void sizeRequest (core::Requisition *requisition); + + const char *getText (); + void setText (const char *text); + bool isEditable (); + void setEditable (bool editable); +}; + + +template class FltkToggleButtonResource: + public FltkSpecificResource +{ +private: + bool initActivated; + +protected: + virtual Fl_Button *createNewButton (core::Allocation *allocation) = 0; + Fl_Widget *createNewWidget (core::Allocation *allocation); + void setWidgetStyle (Fl_Widget *widget, core::style::Style *style); + +public: + FltkToggleButtonResource (FltkPlatform *platform, + bool activated); + ~FltkToggleButtonResource (); + + void sizeRequest (core::Requisition *requisition); + + bool isActivated (); + void setActivated (bool activated); +}; + + +class FltkCheckButtonResource: + public FltkToggleButtonResource +{ +protected: + Fl_Button *createNewButton (core::Allocation *allocation); + +public: + FltkCheckButtonResource (FltkPlatform *platform, + bool activated); + ~FltkCheckButtonResource (); +}; + + +class FltkRadioButtonResource: + public FltkToggleButtonResource +{ +private: + class Group + { + private: + class FltkGroupIterator: + public dw::core::ui::RadioButtonResource::GroupIterator + { + private: + lout::container::typed::Iterator it; + + public: + inline FltkGroupIterator (lout::container::typed::List + + *list) + { it = list->iterator (); } + + bool hasNext (); + dw::core::ui::RadioButtonResource *getNext (); + void unref (); + }; + + lout::container::typed::List *list; + + protected: + ~Group (); + + public: + Group (FltkRadioButtonResource *radioButtonResource); + + inline lout::container::typed::Iterator + iterator () + { + return list->iterator (); + } + + inline dw::core::ui::RadioButtonResource::GroupIterator + *groupIterator () + { + return new FltkGroupIterator (list); + } + + void connect (FltkRadioButtonResource *radioButtonResource); + void unconnect (FltkRadioButtonResource *radioButtonResource); + }; + + Group *group; + + static void widgetCallback (Fl_Widget *widget, void *data); + void buttonClicked (); + +protected: + Fl_Button *createNewButton (core::Allocation *allocation); + +public: + FltkRadioButtonResource (FltkPlatform *platform, + FltkRadioButtonResource *groupedWith, + bool activated); + ~FltkRadioButtonResource (); + + GroupIterator *groupIterator (); +}; + + +template class FltkSelectionResource: + public FltkSpecificResource +{ +protected: + virtual bool setSelectedItems() { return false; } + virtual void addItem (const char *str, bool enabled, bool selected) = 0; + virtual void setItem (int index, bool selected) = 0; + virtual void pushGroup (const char *name, bool enabled) = 0; + virtual void popGroup () = 0; +public: + FltkSelectionResource (FltkPlatform *platform) : + FltkSpecificResource (platform) {}; + dw::core::Iterator *iterator (dw::core::Content::Type mask, bool atEnd); +}; + + +class FltkOptionMenuResource: + public FltkSelectionResource +{ +protected: + Fl_Widget *createNewWidget (core::Allocation *allocation); + virtual bool setSelectedItems() { return true; } + void setWidgetStyle (Fl_Widget *widget, core::style::Style *style); + int getNumberOfItems(); + int getMaxItemWidth (); +private: + static void widgetCallback (Fl_Widget *widget, void *data); + void enlargeMenu(); + Fl_Menu_Item *newItem(); + Fl_Menu_Item *menu; + int itemsAllocated, itemsUsed; +public: + FltkOptionMenuResource (FltkPlatform *platform); + ~FltkOptionMenuResource (); + + void addItem (const char *str, bool enabled, bool selected); + void setItem (int index, bool selected); + void pushGroup (const char *name, bool enabled); + void popGroup (); + + void sizeRequest (core::Requisition *requisition); + bool isSelected (int index); +}; + +class FltkListResource: + public FltkSelectionResource +{ +protected: + Fl_Widget *createNewWidget (core::Allocation *allocation); + void setWidgetStyle (Fl_Widget *widget, core::style::Style *style); + int getNumberOfItems(); + int getMaxItemWidth (); +private: + static void widgetCallback (Fl_Widget *widget, void *data); + void *newItem (const char *str, bool enabled, bool selected); + int currDepth; + int colWidths[4]; + int showRows; + ListResource::SelectionMode mode; +public: + FltkListResource (FltkPlatform *platform, + core::ui::ListResource::SelectionMode selectionMode, + int rows); + ~FltkListResource (); + + void addItem (const char *str, bool enabled, bool selected); + void setItem (int index, bool selected); + void pushGroup (const char *name, bool enabled); + void popGroup (); + + void sizeRequest (core::Requisition *requisition); + bool isSelected (int index); +}; + + +} // namespace ui +} // namespace fltk +} // namespace dw + + +#endif // __DW_FLTK_UI_HH__ diff --git a/dw/fltkviewbase.cc b/dw/fltkviewbase.cc new file mode 100644 index 0000000..3b1c0cc --- /dev/null +++ b/dw/fltkviewbase.cc @@ -0,0 +1,744 @@ +/* + * RTFL (originally part of dillo) + * + * Copyright 2005-2007 Sebastian Geerken + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version; with the following exception: + * + * The copyright holders of RTFL give you permission to link this file + * statically or dynamically against all versions of the graphviz + * library, which are published by AT&T Corp. under one of the following + * licenses: + * + * - Common Public License version 1.0 as published by International + * Business Machines Corporation (IBM), or + * - Eclipse Public License version 1.0 as published by the Eclipse + * Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + + + +#include "fltkviewport.hh" + +#include +#include + +#include +#include "../lout/msg.h" + +extern Fl_Widget* fl_oldfocus; + +using namespace lout::object; +using namespace lout::container::typed; + +namespace dw { +namespace fltk { + +FltkViewBase::BackBuffer::BackBuffer () +{ + w = 0; + h = 0; + created = false; +} + +FltkViewBase::BackBuffer::~BackBuffer () +{ + if (created) + fl_delete_offscreen (offscreen); +} + +void FltkViewBase::BackBuffer::setSize (int w, int h) +{ + if (!created || w > this->w || h > this->h) { + this->w = w; + this->h = h; + if (created) + fl_delete_offscreen (offscreen); + offscreen = fl_create_offscreen (w, h); + created = true; + } +} + +FltkViewBase::BackBuffer *FltkViewBase::backBuffer; +bool FltkViewBase::backBufferInUse; + +FltkViewBase::FltkViewBase (int x, int y, int w, int h, const char *label): + Fl_Group (x, y, w, h, label) +{ + Fl_Group::current(0); + canvasWidth = 1; + canvasHeight = 1; + bgColor = FL_WHITE; + mouse_x = mouse_y = 0; + focused_child = NULL; + exposeArea = NULL; + if (backBuffer == NULL) { + backBuffer = new BackBuffer (); + } + box(FL_NO_BOX); + resizable(NULL); +} + +FltkViewBase::~FltkViewBase () +{ + cancelQueueDraw (); +} + +void FltkViewBase::setBufferedDrawing (bool b) { + if (b && backBuffer == NULL) { + backBuffer = new BackBuffer (); + } else if (!b && backBuffer != NULL) { + delete backBuffer; + backBuffer = NULL; + } +} + +void FltkViewBase::draw () +{ + int d = damage (); + + if ((d & FL_DAMAGE_USER1) && !(d & FL_DAMAGE_EXPOSE)) { + lout::container::typed::Iterator it; + + for (it = drawRegion.rectangles (); it.hasNext (); ) { + draw (it.getNext (), DRAW_BUFFERED); + } + + drawRegion.clear (); + d &= ~FL_DAMAGE_USER1; + } + + if (d & FL_DAMAGE_CHILD) { + drawChildWidgets (); + d &= ~FL_DAMAGE_CHILD; + } + + if (d) { + dw::core::Rectangle rect ( + translateViewXToCanvasX (x ()), + translateViewYToCanvasY (y ()), + w (), + h ()); + + if (d == FL_DAMAGE_SCROLL) { + // a clipping rectangle has already been set by fltk::scrollrect () + draw (&rect, DRAW_PLAIN); + } else { + draw (&rect, DRAW_CLIPPED); + drawRegion.clear (); + } + } +} + +void FltkViewBase::draw (const core::Rectangle *rect, + DrawType type) +{ + int X = translateCanvasXToViewX (rect->x); + int Y = translateCanvasYToViewY (rect->y); + int W, H; + + // fl_clip_box() can't handle values greater than SHRT_MAX! + if (X > x () + w () || Y > y () + h ()) + return; + + W = X + rect->width > x () + w () ? x () + w () - X : rect->width; + H = Y + rect->height > y () + h () ? y () + h () - Y : rect->height; + + fl_clip_box(X, Y, W, H, X, Y, W, H); + + core::Rectangle r (translateViewXToCanvasX (X), + translateViewYToCanvasY (Y), W, H); + + if (r.isEmpty ()) + return; + + exposeArea = &r; + + if (type == DRAW_BUFFERED && backBuffer && !backBufferInUse) { + backBufferInUse = true; + backBuffer->setSize (X + W, Y + H); // would be nicer to use (W, H)... + fl_begin_offscreen (backBuffer->offscreen); + fl_push_matrix (); + fl_color (bgColor); + fl_rectf (X, Y, W, H); + theLayout->expose (this, &r); + fl_pop_matrix (); + fl_end_offscreen (); + fl_copy_offscreen (X, Y, W, H, backBuffer->offscreen, X, Y); + backBufferInUse = false; + } else if (type == DRAW_BUFFERED || type == DRAW_CLIPPED) { + // if type == DRAW_BUFFERED but we do not have backBuffer available + // we fall back to clipped drawing + fl_push_clip (X, Y, W, H); + fl_color (bgColor); + fl_rectf (X, Y, W, H); + theLayout->expose (this, &r); + fl_pop_clip (); + } else { + fl_color (bgColor); + fl_rectf (X, Y, W, H); + theLayout->expose (this, &r); + } + // DEBUG: + //fl_color(FL_RED); + //fl_rect(X, Y, W, H); + + exposeArea = NULL; +} + +void FltkViewBase::drawChildWidgets () { + for (int i = children () - 1; i >= 0; i--) { + Fl_Widget& w = *child(i); +#if 0 +PORT1.3 + if (w.damage() & DAMAGE_CHILD_LABEL) { + draw_outside_label(w); + w.set_damage(w.damage() & ~DAMAGE_CHILD_LABEL); + } +#endif + update_child(w); + } +} + +core::ButtonState getDwButtonState () +{ + int s1 = Fl::event_state (); + int s2 = (core::ButtonState)0; + + if (s1 & FL_SHIFT) s2 |= core::SHIFT_MASK; + if (s1 & FL_CTRL) s2 |= core::CONTROL_MASK; + if (s1 & FL_ALT) s2 |= core::META_MASK; + if (s1 & FL_BUTTON1) s2 |= core::BUTTON1_MASK; + if (s1 & FL_BUTTON2) s2 |= core::BUTTON2_MASK; + if (s1 & FL_BUTTON3) s2 |= core::BUTTON3_MASK; + + return (core::ButtonState)s2; +} + +/* + * We handle Tab to determine which FLTK widget should get focus. + * + * Presumably a proper solution that allows focusing links, etc., would live + * in Textblock and use iterators. + */ +int FltkViewBase::manageTabToFocus() +{ + int i, ret = 0; + Fl_Widget *old_child = NULL; + + if (this == Fl::focus()) { + // if we have focus, give it to a child. Go forward typically, + // or backward with Shift pressed. + if (!(Fl::event_state() & FL_SHIFT)) { + for (i = 0; i < children(); i++) { + if (child(i)->take_focus()) { + ret = 1; + break; + } + } + } else { + for (i = children() - 1; i >= 0; i--) { + if (child(i)->take_focus()) { + ret = 1; + break; + } + } + } + } else { + // tabbing between children + old_child = Fl::focus(); + + if (!(ret = Fl_Group::handle (FL_KEYBOARD))) { + // group didn't have any more children to focus. + Fl::focus(this); + return 1; + } else { + // which one did it focus? (Note i == children() if not found) + i = find(Fl::focus()); + } + } + if (ret) { + if (i >= 0 && i < children()) { + Fl_Widget *c = child(i); + int canvasX = translateViewXToCanvasX(c->x()), + canvasY = translateViewYToCanvasY(c->y()); + + theLayout->scrollTo(core::HPOS_INTO_VIEW, core::VPOS_INTO_VIEW, + canvasX, canvasY, c->w(), c->h()); + + // Draw the children who gained and lost focus. Otherwise a + // widget that had been only partly visible still shows its old + // appearance in the previously-visible portion. + core::Rectangle r(canvasX, canvasY, c->w(), c->h()); + + queueDraw(&r); + + if (old_child) { + r.x = translateViewXToCanvasX(old_child->x()); + r.y = translateViewYToCanvasY(old_child->y()); + r.width = old_child->w(); + r.height = old_child->h(); + queueDraw(&r); + } + } + } + return ret; +} + +int FltkViewBase::handle (int event) +{ + bool processed; + + /** + * \todo Consider, whether this from the FLTK documentation has any + * impacts: "To receive fltk::RELEASE events you must return non-zero + * when passed a fltk::PUSH event. " + */ + switch(event) { + case FL_PUSH: + /* Hide the tooltip */ + theLayout->cancelTooltip(); + + processed = + theLayout->buttonPress (this, Fl::event_clicks () + 1, + translateViewXToCanvasX (Fl::event_x ()), + translateViewYToCanvasY (Fl::event_y ()), + getDwButtonState (), Fl::event_button ()); + _MSG("PUSH => %s\n", processed ? "true" : "false"); + if (processed) { + /* pressed dw content; give focus to the view */ + if (Fl::event_button() != FL_RIGHT_MOUSE) + Fl::focus(this); + return true; + } + break; + case FL_RELEASE: + processed = + theLayout->buttonRelease (this, Fl::event_clicks () + 1, + translateViewXToCanvasX (Fl::event_x ()), + translateViewYToCanvasY (Fl::event_y ()), + getDwButtonState (), Fl::event_button ()); + _MSG("RELEASE => %s\n", processed ? "true" : "false"); + if (processed) + return true; + break; + case FL_MOVE: + mouse_x = Fl::event_x(); + mouse_y = Fl::event_y(); + processed = + theLayout->motionNotify (this, + translateViewXToCanvasX (mouse_x), + translateViewYToCanvasY (mouse_y), + getDwButtonState ()); + _MSG("MOVE => %s\n", processed ? "true" : "false"); + if (processed) + return true; + break; + case FL_DRAG: + processed = + theLayout->motionNotify (this, + translateViewXToCanvasX (Fl::event_x ()), + translateViewYToCanvasY (Fl::event_y ()), + getDwButtonState ()); + _MSG("DRAG => %s\n", processed ? "true" : "false"); + if (processed) + return true; + break; + case FL_ENTER: + theLayout->enterNotify (this, + translateViewXToCanvasX (Fl::event_x ()), + translateViewYToCanvasY (Fl::event_y ()), + getDwButtonState ()); + break; + case FL_HIDE: + /* WORKAROUND: strangely, the tooltip window is not automatically hidden + * with its parent. Here we fake a LEAVE to achieve it. */ + case FL_LEAVE: + theLayout->leaveNotify (this, getDwButtonState ()); + break; + case FL_FOCUS: + if (focused_child && find(focused_child) < children()) { + /* strangely, find() == children() if the child is not found */ + focused_child->take_focus(); + } + return 1; + case FL_UNFOCUS: + focused_child = fl_oldfocus; + return 0; + case FL_KEYBOARD: + if (Fl::event_key() == FL_Tab) + return manageTabToFocus(); + break; + default: + break; + } + return Fl_Group::handle (event); +} + +// ---------------------------------------------------------------------- + +void FltkViewBase::setLayout (core::Layout *layout) +{ + theLayout = layout; + if (usesViewport()) + theLayout->viewportSizeChanged(this, w(), h()); +} + +void FltkViewBase::setCanvasSize (int width, int ascent, int descent) +{ + canvasWidth = width; + canvasHeight = ascent + descent; +} + +void FltkViewBase::setCursor (core::style::Cursor cursor) +{ + static Fl_Cursor mapDwToFltk[] = { + FL_CURSOR_CROSS, + FL_CURSOR_DEFAULT, + FL_CURSOR_HAND, + FL_CURSOR_MOVE, + FL_CURSOR_WE, + FL_CURSOR_NESW, + FL_CURSOR_NWSE, + FL_CURSOR_NS, + FL_CURSOR_NWSE, + FL_CURSOR_NESW, + FL_CURSOR_NS, + FL_CURSOR_WE, + FL_CURSOR_INSERT, + FL_CURSOR_WAIT, + FL_CURSOR_HELP + }; + + fl_cursor (mapDwToFltk[cursor]); +} + +void FltkViewBase::setBgColor (core::style::Color *color) +{ + bgColor = color ? + ((FltkColor*)color)->colors[dw::core::style::Color::SHADING_NORMAL] : + FL_WHITE; +} + +void FltkViewBase::startDrawing (core::Rectangle *area) +{ +} + +void FltkViewBase::finishDrawing (core::Rectangle *area) +{ +} + +void FltkViewBase::queueDraw (core::Rectangle *area) +{ + drawRegion.addRectangle (area); + damage (FL_DAMAGE_USER1); +} + +void FltkViewBase::queueDrawTotal () +{ + damage (FL_DAMAGE_EXPOSE); +} + +void FltkViewBase::cancelQueueDraw () +{ +} + +void FltkViewBase::drawPoint (core::style::Color *color, + core::style::Color::Shading shading, + int x, int y) +{ +} + +void FltkViewBase::drawLine (core::style::Color *color, + core::style::Color::Shading shading, + int x1, int y1, int x2, int y2) +{ + fl_color(((FltkColor*)color)->colors[shading]); + // we clip with a large border (5000px), as clipping causes artefacts + // with non-solid line styles. + // However it's still better than no clipping at all. + clipPoint (&x1, &y1, 5000); + clipPoint (&x2, &y2, 5000); + fl_line (translateCanvasXToViewX (x1), + translateCanvasYToViewY (y1), + translateCanvasXToViewX (x2), + translateCanvasYToViewY (y2)); +} + +void FltkViewBase::drawTypedLine (core::style::Color *color, + core::style::Color::Shading shading, + core::style::LineType type, int width, + int x1, int y1, int x2, int y2) +{ + char dashes[3], w, ng, d, gap, len; + const int f = 2; + + w = (width == 1) ? 0 : width; + if (type == core::style::LINE_DOTTED) { + /* customized drawing for dotted lines */ + len = (x2 == x1) ? y2 - y1 + 1 : (y2 == y1) ? x2 - x1 + 1 : 0; + ng = len / f*width; + d = len % f*width; + gap = ng ? d/ng + (w > 3 ? 2 : 0) : 0; + dashes[0] = 1; dashes[1] = f*width-gap; dashes[2] = 0; + fl_line_style(FL_DASH + FL_CAP_ROUND, w, dashes); + + /* These formulas also work, but ain't pretty ;) + * fl_line_style(FL_DOT + FL_CAP_ROUND, w); + * dashes[0] = 1; dashes[1] = 3*width-2; dashes[2] = 0; + */ + } else if (type == core::style::LINE_DASHED) { + fl_line_style(FL_DASH + FL_CAP_ROUND, w); + } + + fl_color(((FltkColor*)color)->colors[shading]); + drawLine (color, shading, x1, y1, x2, y2); + + if (type != core::style::LINE_NORMAL) + fl_line_style(FL_SOLID); +} + +void FltkViewBase::drawRectangle (core::style::Color *color, + core::style::Color::Shading shading, + bool filled, + int X, int Y, int width, int height) +{ + fl_color(((FltkColor*)color)->colors[shading]); + if (width < 0) { + X += width; + width = -width; + } + if (height < 0) { + Y += height; + height = -height; + } + + int x1 = X; + int y1 = Y; + int x2 = X + width; + int y2 = Y + height; + + // We only support rectangles with line width 1px, so we clip with + // a rectangle 1px wider and higher than what we actually expose. + // This is only really necessary for non-filled rectangles. + clipPoint (&x1, &y1, 1); + clipPoint (&x2, &y2, 1); + + x1 = translateCanvasXToViewX (x1); + y1 = translateCanvasYToViewY (y1); + x2 = translateCanvasXToViewX (x2); + y2 = translateCanvasYToViewY (y2); + + if (filled) + fl_rectf (x1, y1, x2 - x1, y2 - y1); + else + fl_rect (x1, y1, x2 - x1, y2 - y1); +} + +void FltkViewBase::drawArc (core::style::Color *color, + core::style::Color::Shading shading, bool filled, + int centerX, int centerY, int width, int height, + int angle1, int angle2) +{ + fl_color(((FltkColor*)color)->colors[shading]); + int x = translateCanvasXToViewX (centerX) - width / 2; + int y = translateCanvasYToViewY (centerY) - height / 2; + + fl_arc(x, y, width, height, angle1, angle2); + if (filled) { + // WORKAROUND: We call both fl_arc and fl_pie due to a FLTK bug + // (STR #2703) that was present in 1.3.0. + fl_pie(x, y, width, height, angle1, angle2); + } +} + +void FltkViewBase::drawPolygon (core::style::Color *color, + core::style::Color::Shading shading, + bool filled, bool convex, core::Point *points, + int npoints) +{ + if (npoints > 0) { + fl_color(((FltkColor*)color)->colors[shading]); + + if (filled) { + if (convex) + fl_begin_polygon(); + else + fl_begin_complex_polygon(); + } else + fl_begin_loop(); + + for (int i = 0; i < npoints; i++) { + fl_vertex(translateCanvasXToViewX(points[i].x), + translateCanvasYToViewY(points[i].y)); + } + if (filled) { + if (convex) + fl_end_polygon(); + else + fl_end_complex_polygon(); + } else + fl_end_loop(); + } +} + +core::View *FltkViewBase::getClippingView (int x, int y, int width, int height) +{ + fl_push_clip (translateCanvasXToViewX (x), translateCanvasYToViewY (y), + width, height); + return this; +} + +void FltkViewBase::mergeClippingView (core::View *clippingView) +{ + fl_pop_clip (); +} + +// ---------------------------------------------------------------------- + +FltkWidgetView::FltkWidgetView (int x, int y, int w, int h, + const char *label): + FltkViewBase (x, y, w, h, label) +{ +} + +FltkWidgetView::~FltkWidgetView () +{ +} + +void FltkWidgetView::drawText (core::style::Font *font, + core::style::Color *color, + core::style::Color::Shading shading, + int X, int Y, const char *text, int len) +{ + //printf ("drawText (..., %d, %d, '", X, Y); + //for (int i = 0; i < len; i++) + // putchar (text[i]); + //printf ("'\n"); + + FltkFont *ff = (FltkFont*)font; + fl_font(ff->font, ff->size); + fl_color(((FltkColor*)color)->colors[shading]); + + if (!font->letterSpacing && !font->fontVariant) { + fl_draw(text, len, + translateCanvasXToViewX (X), translateCanvasYToViewY (Y)); + } else { + /* Nonzero letter spacing adjustment, draw each glyph individually */ + int viewX = translateCanvasXToViewX (X), + viewY = translateCanvasYToViewY (Y); + int curr = 0, next = 0, nb; + char chbuf[4]; + int c, cu, width; + + if (font->fontVariant == core::style::FONT_VARIANT_SMALL_CAPS) { + int sc_fontsize = lout::misc::roundInt(ff->size * 0.78); + for (curr = 0; next < len; curr = next) { + next = theLayout->nextGlyph(text, curr); + c = fl_utf8decode(text + curr, text + next, &nb); + if ((cu = fl_toupper(c)) == c) { + /* already uppercase, just draw the character */ + fl_font(ff->font, ff->size); + width = (int)fl_width(text + curr, next - curr); + if (curr && width) + viewX += font->letterSpacing; + fl_draw(text + curr, next - curr, viewX, viewY); + viewX += width; + } else { + /* make utf8 string for converted char */ + nb = fl_utf8encode(cu, chbuf); + fl_font(ff->font, sc_fontsize); + width = (int)fl_width(chbuf, nb); + if (curr && width) + viewX += font->letterSpacing; + fl_draw(chbuf, nb, viewX, viewY); + viewX += width; + } + } + } else { + while (next < len) { + next = theLayout->nextGlyph(text, curr); + width = (int)fl_width(text + curr, next - curr); + if (curr && width) + viewX += font->letterSpacing; + fl_draw(text + curr, next - curr, viewX, viewY); + viewX += width; + curr = next; + } + } + } +} + +/* + * "simple" in that it ignores letter-spacing, etc. This was added for image + * alt text where none of that matters. + */ +void FltkWidgetView::drawSimpleWrappedText (core::style::Font *font, + core::style::Color *color, + core::style::Color::Shading shading, + int X, int Y, int W, int H, + const char *text) +{ + FltkFont *ff = (FltkFont*)font; + fl_font(ff->font, ff->size); + fl_color(((FltkColor*)color)->colors[shading]); + fl_draw(text, + translateCanvasXToViewX (X), translateCanvasYToViewY (Y), + W, H, FL_ALIGN_TOP|FL_ALIGN_LEFT|FL_ALIGN_WRAP, NULL, 0); +} + +void FltkWidgetView::drawImage (core::Imgbuf *imgbuf, int xRoot, int yRoot, + int X, int Y, int width, int height) +{ + ((FltkImgbuf*)imgbuf)->draw (this, + translateCanvasXToViewX (xRoot), + translateCanvasYToViewY (yRoot), + X, Y, width, height); +} + +bool FltkWidgetView::usesFltkWidgets () +{ + return true; +} + +void FltkWidgetView::addFltkWidget (Fl_Widget *widget, + core::Allocation *allocation) +{ + allocateFltkWidget (widget, allocation); + add (widget); +} + +void FltkWidgetView::removeFltkWidget (Fl_Widget *widget) +{ + remove (widget); +} + +void FltkWidgetView::allocateFltkWidget (Fl_Widget *widget, + core::Allocation *allocation) +{ + widget->resize (translateCanvasXToViewX (allocation->x), + translateCanvasYToViewY (allocation->y), + allocation->width, + allocation->ascent + allocation->descent); +} + +void FltkWidgetView::drawFltkWidget (Fl_Widget *widget, + core::Rectangle *area) +{ + draw_child (*widget); + draw_outside_label(*widget); +} + +} // namespace fltk +} // namespace dw diff --git a/dw/fltkviewbase.hh b/dw/fltkviewbase.hh new file mode 100644 index 0000000..eb4ec32 --- /dev/null +++ b/dw/fltkviewbase.hh @@ -0,0 +1,141 @@ +#ifndef __DW_FLTKVIEWBASE_HH__ +#define __DW_FLTKVIEWBASE_HH__ + +#include // for time_t +#include // for time_t in FreeBSD + +#include +#include + +#include "fltkcore.hh" + +namespace dw { +namespace fltk { + +class FltkViewBase: public FltkView, public Fl_Group +{ +private: + class BackBuffer { + private: + int w; + int h; + bool created; + + public: + Fl_Offscreen offscreen; + + BackBuffer (); + ~BackBuffer (); + void setSize(int w, int h); + }; + + typedef enum { DRAW_PLAIN, DRAW_CLIPPED, DRAW_BUFFERED } DrawType; + + int bgColor; + core::Region drawRegion; + core::Rectangle *exposeArea; + static BackBuffer *backBuffer; + static bool backBufferInUse; + + void draw (const core::Rectangle *rect, DrawType type); + void drawChildWidgets (); + int manageTabToFocus(); + inline void clipPoint (int *x, int *y, int border) { + if (exposeArea) { + if (*x < exposeArea->x - border) + *x = exposeArea->x - border; + if (*x > exposeArea->x + exposeArea->width + border) + *x = exposeArea->x + exposeArea->width + border; + if (*y < exposeArea->y - border) + *y = exposeArea->y - border; + if (*y > exposeArea->y + exposeArea->height + border) + *y = exposeArea->y + exposeArea->height + border; + } + } +protected: + core::Layout *theLayout; + int canvasWidth, canvasHeight; + int mouse_x, mouse_y; + Fl_Widget *focused_child; + + virtual int translateViewXToCanvasX (int x) = 0; + virtual int translateViewYToCanvasY (int y) = 0; + virtual int translateCanvasXToViewX (int x) = 0; + virtual int translateCanvasYToViewY (int y) = 0; + +public: + FltkViewBase (int x, int y, int w, int h, const char *label = 0); + ~FltkViewBase (); + + void draw(); + int handle (int event); + + void setLayout (core::Layout *layout); + void setCanvasSize (int width, int ascent, int descent); + void setCursor (core::style::Cursor cursor); + void setBgColor (core::style::Color *color); + + void startDrawing (core::Rectangle *area); + void finishDrawing (core::Rectangle *area); + void queueDraw (core::Rectangle *area); + void queueDrawTotal (); + void cancelQueueDraw (); + void drawPoint (core::style::Color *color, + core::style::Color::Shading shading, + int x, int y); + void drawLine (core::style::Color *color, + core::style::Color::Shading shading, + int x1, int y1, int x2, int y2); + void drawTypedLine (core::style::Color *color, + core::style::Color::Shading shading, + core::style::LineType type, int width, + int x1, int y1, int x2, int y2); + void drawRectangle (core::style::Color *color, + core::style::Color::Shading shading, bool filled, + int x, int y, int width, int height); + void drawArc (core::style::Color *color, + core::style::Color::Shading shading, bool filled, + int centerX, int centerY, int width, int height, + int angle1, int angle2); + void drawPolygon (core::style::Color *color, + core::style::Color::Shading shading, + bool filled, bool convex, + core::Point *points, int npoints); + + core::View *getClippingView (int x, int y, int width, int height); + void mergeClippingView (core::View *clippingView); + void setBufferedDrawing (bool b); +}; + + +class FltkWidgetView: public FltkViewBase +{ +public: + FltkWidgetView (int x, int y, int w, int h, const char *label = 0); + ~FltkWidgetView (); + + void drawText (core::style::Font *font, + core::style::Color *color, + core::style::Color::Shading shading, + int x, int y, const char *text, int len); + void drawSimpleWrappedText (core::style::Font *font, + core::style::Color *color, + core::style::Color::Shading shading, + int x, int y, int w, int h, + const char *text); + void drawImage (core::Imgbuf *imgbuf, int xRoot, int yRoot, + int x, int y, int width, int height); + + bool usesFltkWidgets (); + void addFltkWidget (Fl_Widget *widget, core::Allocation *allocation); + void removeFltkWidget (Fl_Widget *widget); + void allocateFltkWidget (Fl_Widget *widget, + core::Allocation *allocation); + void drawFltkWidget (Fl_Widget *widget, core::Rectangle *area); +}; + +} // namespace fltk +} // namespace dw + +#endif // __DW_FLTKVIEWBASE_HH__ + diff --git a/dw/fltkviewport.cc b/dw/fltkviewport.cc new file mode 100644 index 0000000..804b62f --- /dev/null +++ b/dw/fltkviewport.cc @@ -0,0 +1,558 @@ +/* + * RTFL (originally part of dillo) + * + * Copyright 2005-2007 Sebastian Geerken + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version; with the following exception: + * + * The copyright holders of RTFL give you permission to link this file + * statically or dynamically against all versions of the graphviz + * library, which are published by AT&T Corp. under one of the following + * licenses: + * + * - Common Public License version 1.0 as published by International + * Business Machines Corporation (IBM), or + * - Eclipse Public License version 1.0 as published by the Eclipse + * Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + + + +#include "fltkviewport.hh" + +#include +#include +#include + +#include +#include "../lout/msg.h" +#include "../lout/debug.hh" + +using namespace lout; +using namespace lout::object; +using namespace lout::container::typed; + +namespace dw { +namespace fltk { + +/* + * Lets SHIFT+{Left,Right} go to the parent + */ +class CustScrollbar : public Fl_Scrollbar +{ +public: + CustScrollbar(int x, int y, int w, int h) : Fl_Scrollbar(x,y,w,h) {}; + int handle(int e) { + if (e == FL_SHORTCUT && Fl::event_state() == FL_SHIFT && + (Fl::event_key() == FL_Left || Fl::event_key() == FL_Right)) + return 0; + return Fl_Scrollbar::handle(e); + } +}; + +FltkViewport::FltkViewport (int X, int Y, int W, int H, const char *label): + FltkWidgetView (X, Y, W, H, label) +{ + DBG_OBJ_CREATE ("dw::fltk::FltkViewport"); + + hscrollbar = new CustScrollbar (x (), y (), 1, 1); + hscrollbar->type(FL_HORIZONTAL); + hscrollbar->callback (hscrollbarCallback, this); + hscrollbar->hide(); + add (hscrollbar); + + vscrollbar = new Fl_Scrollbar (x (), y(), 1, 1); + vscrollbar->type(FL_VERTICAL); + vscrollbar->callback (vscrollbarCallback, this); + vscrollbar->hide(); + add (vscrollbar); + + hasDragScroll = 1; + scrollX = scrollY = scrollDX = scrollDY = 0; + horScrolling = verScrolling = dragScrolling = 0; + + gadgetOrientation[0] = GADGET_HORIZONTAL; + gadgetOrientation[1] = GADGET_HORIZONTAL; + gadgetOrientation[2] = GADGET_VERTICAL; + gadgetOrientation[3] = GADGET_HORIZONTAL; + + gadgets = + new container::typed::List > + (true); +} + +FltkViewport::~FltkViewport () +{ + delete gadgets; + DBG_OBJ_DELETE (); +} + +void FltkViewport::adjustScrollbarsAndGadgetsAllocation () +{ + int hdiff = 0, vdiff = 0; + int visibility = 0; + + _MSG(" >>FltkViewport::adjustScrollbarsAndGadgetsAllocation\n"); + if (hscrollbar->visible ()) + visibility |= 1; + if (vscrollbar->visible ()) + visibility |= 2; + + if (gadgets->size () > 0) { + switch (gadgetOrientation [visibility]) { + case GADGET_VERTICAL: + hdiff = SCROLLBAR_THICKNESS; + vdiff = SCROLLBAR_THICKNESS * gadgets->size (); + break; + + case GADGET_HORIZONTAL: + hdiff = SCROLLBAR_THICKNESS * gadgets->size (); + vdiff = SCROLLBAR_THICKNESS; + break; + } + } else { + hdiff = vscrollbar->visible () ? SCROLLBAR_THICKNESS : 0; + vdiff = hscrollbar->visible () ? SCROLLBAR_THICKNESS : 0; + } + + hscrollbar->resize(x (), y () + h () - SCROLLBAR_THICKNESS, + w () - hdiff, SCROLLBAR_THICKNESS); + vscrollbar->resize(x () + w () - SCROLLBAR_THICKNESS, y (), + SCROLLBAR_THICKNESS, h () - vdiff); + + int X = x () + w () - SCROLLBAR_THICKNESS; + int Y = y () + h () - SCROLLBAR_THICKNESS; + for (Iterator > it = gadgets->iterator (); + it.hasNext (); ) { + Fl_Widget *widget = it.getNext()->getTypedValue (); + widget->resize(x (), y (), SCROLLBAR_THICKNESS, SCROLLBAR_THICKNESS); + + switch (gadgetOrientation [visibility]) { + case GADGET_VERTICAL: + Y -= SCROLLBAR_THICKNESS; + break; + + case GADGET_HORIZONTAL: + X -= SCROLLBAR_THICKNESS; + break; + } + } +} + +void FltkViewport::adjustScrollbarValues () +{ + hscrollbar->value (scrollX, hscrollbar->w (), 0, canvasWidth); + vscrollbar->value (scrollY, vscrollbar->h (), 0, canvasHeight); +} + +void FltkViewport::hscrollbarChanged () +{ + scroll (hscrollbar->value () - scrollX, 0); +} + +void FltkViewport::vscrollbarChanged () +{ + scroll (0, vscrollbar->value () - scrollY); +} + +void FltkViewport::vscrollbarCallback (Fl_Widget *vscrollbar,void *viewportPtr) +{ + ((FltkViewport*)viewportPtr)->vscrollbarChanged (); +} + +void FltkViewport::hscrollbarCallback (Fl_Widget *hscrollbar,void *viewportPtr) +{ + ((FltkViewport*)viewportPtr)->hscrollbarChanged (); +} + +// ---------------------------------------------------------------------- + +void FltkViewport::resize(int X, int Y, int W, int H) +{ + bool dimension_changed = W != w() || H != h(); + + Fl_Group::resize(X, Y, W, H); + if (dimension_changed) { + theLayout->viewportSizeChanged (this, W, H); + adjustScrollbarsAndGadgetsAllocation (); + } +} + +void FltkViewport::draw_area (void *data, int x, int y, int w, int h) +{ + FltkViewport *vp = (FltkViewport*) data; + fl_push_clip(x, y, w, h); + + vp->FltkWidgetView::draw (); + + for (Iterator > it = vp->gadgets->iterator(); + it.hasNext (); ) { + Fl_Widget *widget = it.getNext()->getTypedValue (); + vp->draw_child (*widget); + } + + fl_pop_clip(); + +} + +void FltkViewport::draw () +{ + int hdiff = vscrollbar->visible () ? SCROLLBAR_THICKNESS : 0; + int vdiff = hscrollbar->visible () ? SCROLLBAR_THICKNESS : 0; + int d = damage(); + + if (d & FL_DAMAGE_SCROLL) { + clear_damage (FL_DAMAGE_SCROLL); + fl_scroll(x(), y(), w() - hdiff, h() - vdiff, + -scrollDX, -scrollDY, draw_area, this); + clear_damage (d & ~FL_DAMAGE_SCROLL); + } + + if (d) { + draw_area(this, x(), y(), w () - hdiff, h () - vdiff); + + if (d == FL_DAMAGE_ALL || hscrollbar->damage ()) + draw_child (*hscrollbar); + if (d == FL_DAMAGE_ALL || vscrollbar->damage ()) + draw_child (*vscrollbar); + + if (d == FL_DAMAGE_ALL && hdiff && vdiff) { + fl_color(FL_BACKGROUND_COLOR); + fl_rectf(x()+w()-hdiff, y()+h()-vdiff, hdiff, vdiff); + } + } + + scrollDX = 0; + scrollDY = 0; +} + +int FltkViewport::handle (int event) +{ + _MSG("FltkViewport::handle %s\n", fl_eventnames[event]); + + switch(event) { + case FL_KEYBOARD: + /* When the viewport has focus (and not one of its children), FLTK + * sends the event here. Returning zero tells FLTK to resend the + * event as SHORTCUT, which we finally route to the parent. */ + + /* As we don't know the exact keybindings set by the user, we ask for + * all of them (except for the minimum needed to keep form navigation).*/ + if (Fl::event_key() != FL_Tab || Fl::event_ctrl()) + return 0; + break; + + case FL_SHORTCUT: + /* send it to the parent (UI) */ + return 0; + + case FL_FOCUS: + /** \bug Draw focus box. */ + break; + + case FL_UNFOCUS: + /** \bug Undraw focus box. */ + break; + + case FL_PUSH: + if (vscrollbar->visible() && Fl::event_inside(vscrollbar)) { + if (vscrollbar->handle(event)) + verScrolling = 1; + } else if (hscrollbar->visible() && Fl::event_inside(hscrollbar)) { + if (hscrollbar->handle(event)) + horScrolling = 1; + } else if (FltkWidgetView::handle(event) == 0 && + Fl::event_button() == FL_MIDDLE_MOUSE) { + if (!hasDragScroll) { + /* let the parent widget handle it... */ + return 0; + } else { + /* receive FL_DRAG and FL_RELEASE */ + dragScrolling = 1; + dragX = Fl::event_x(); + dragY = Fl::event_y(); + setCursor (core::style::CURSOR_MOVE); + } + } + return 1; + break; + + case FL_DRAG: + if (Fl::event_inside(this)) + Fl::remove_timeout(selectionScroll); + if (dragScrolling) { + scroll(dragX - Fl::event_x(), dragY - Fl::event_y()); + dragX = Fl::event_x(); + dragY = Fl::event_y(); + return 1; + } else if (verScrolling) { + vscrollbar->handle(event); + return 1; + } else if (horScrolling) { + hscrollbar->handle(event); + return 1; + } else if (!Fl::event_inside(this)) { + mouse_x = Fl::event_x(); + mouse_y = Fl::event_y(); + if (!Fl::has_timeout(selectionScroll, this)) + Fl::add_timeout(0.025, selectionScroll, this); + } + break; + + case FL_MOUSEWHEEL: + return (Fl::event_dx() ? hscrollbar : vscrollbar)->handle(event); + break; + + case FL_RELEASE: + Fl::remove_timeout(selectionScroll); + if (Fl::event_button() == FL_MIDDLE_MOUSE) { + setCursor (core::style::CURSOR_DEFAULT); + } else if (verScrolling) { + vscrollbar->handle(event); + } else if (horScrolling) { + hscrollbar->handle(event); + } + horScrolling = verScrolling = dragScrolling = 0; + break; + + case FL_ENTER: + /* could be the result of, e.g., closing another window. */ + mouse_x = Fl::event_x(); + mouse_y = Fl::event_y(); + positionChanged(); + break; + + case FL_LEAVE: + mouse_x = mouse_y = -1; + break; + } + + return FltkWidgetView::handle (event); +} + +// ---------------------------------------------------------------------- + +void FltkViewport::setCanvasSize (int width, int ascent, int descent) +{ + FltkWidgetView::setCanvasSize (width, ascent, descent); + adjustScrollbarValues (); +} + +/* + * This is used to simulate mouse motion (e.g., when scrolling). + */ +void FltkViewport::positionChanged () +{ + if (!dragScrolling && mouse_x >= x() && mouse_x < x()+w() && mouse_y >= y() + && mouse_y < y()+h()) + (void)theLayout->motionNotify (this, + translateViewXToCanvasX (mouse_x), + translateViewYToCanvasY (mouse_y), + (core::ButtonState)0); +} + +/* + * For scrollbars, this currently sets the same step to both vertical and + * horizontal. It may be differentiated if necessary. + */ +void FltkViewport::setScrollStep(int step) +{ + vscrollbar->linesize(step); + hscrollbar->linesize(step); +} + +bool FltkViewport::usesViewport () +{ + return true; +} + +int FltkViewport::getHScrollbarThickness () +{ + return SCROLLBAR_THICKNESS; +} + +int FltkViewport::getVScrollbarThickness () +{ + return SCROLLBAR_THICKNESS; +} + +void FltkViewport::scrollTo (int x, int y) +{ + int hdiff = vscrollbar->visible () ? SCROLLBAR_THICKNESS : 0; + int vdiff = hscrollbar->visible () ? SCROLLBAR_THICKNESS : 0; + + x = misc::min (x, canvasWidth - w() + hdiff); + x = misc::max (x, 0); + + y = misc::min (y, canvasHeight - h() + vdiff); + y = misc::max (y, 0); + + if (x == scrollX && y == scrollY) { + return; + } + + /* multiple calls to scroll can happen before a redraw occurs. + * scrollDX and scrollDY can therefore be non-zero here. + */ + updateCanvasWidgets (x - scrollX, y - scrollY); + scrollDX += x - scrollX; + scrollDY += y - scrollY; + + scrollX = x; + scrollY = y; + + adjustScrollbarValues (); + damage(FL_DAMAGE_SCROLL); + theLayout->scrollPosChanged (this, scrollX, scrollY); + positionChanged(); +} + +void FltkViewport::scroll (int dx, int dy) +{ + scrollTo (scrollX + dx, scrollY + dy); +} + +void FltkViewport::scroll (core::ScrollCommand cmd) +{ + if (cmd == core::SCREEN_UP_CMD) { + scroll (0, -h () + vscrollbar->linesize ()); + } else if (cmd == core::SCREEN_DOWN_CMD) { + scroll (0, h () - vscrollbar->linesize ()); + } else if (cmd == core::SCREEN_LEFT_CMD) { + scroll (-w() + hscrollbar->linesize (), 0); + } else if (cmd == core::SCREEN_RIGHT_CMD) { + scroll (w() - hscrollbar->linesize (), 0); + } else if (cmd == core::LINE_UP_CMD) { + scroll (0, (int) -vscrollbar->linesize ()); + } else if (cmd == core::LINE_DOWN_CMD) { + scroll (0, (int) vscrollbar->linesize ()); + } else if (cmd == core::LEFT_CMD) { + scroll ((int) -hscrollbar->linesize (), 0); + } else if (cmd == core::RIGHT_CMD) { + scroll ((int) hscrollbar->linesize (), 0); + } else if (cmd == core::TOP_CMD) { + scrollTo (scrollX, 0); + } else if (cmd == core::BOTTOM_CMD) { + scrollTo (scrollX, canvasHeight); /* gets adjusted in scrollTo () */ + } +} + +/* + * Scrolling in response to selection where the cursor is outside the view. + */ +void FltkViewport::selectionScroll () +{ + int distance; + int dx = 0, dy = 0; + + if ((distance = x() - mouse_x) > 0) + dx = -distance * hscrollbar->linesize () / 48 - 1; + else if ((distance = mouse_x - (x() + w())) > 0) + dx = distance * hscrollbar->linesize () / 48 + 1; + if ((distance = y() - mouse_y) > 0) + dy = -distance * vscrollbar->linesize () / 48 - 1; + else if ((distance = mouse_y - (y() + h())) > 0) + dy = distance * vscrollbar->linesize () / 48 + 1; + + scroll (dx, dy); +} + +void FltkViewport::selectionScroll (void *data) +{ + ((FltkViewport *)data)->selectionScroll (); + Fl::repeat_timeout(0.025, selectionScroll, data); +} + +void FltkViewport::setViewportSize (int width, int height, + int hScrollbarThickness, + int vScrollbarThickness) +{ + int adjustReq = + (hscrollbar->visible() ? !hScrollbarThickness : hScrollbarThickness) || + (vscrollbar->visible() ? !vScrollbarThickness : vScrollbarThickness); + + _MSG("FltkViewport::setViewportSize old_w,old_h=%dx%d -> w,h=%dx%d\n" + "\t hThick=%d hVis=%d, vThick=%d vVis=%d, adjustReq=%d\n", + w(),h(),width,height, + hScrollbarThickness,hscrollbar->visible(), + vScrollbarThickness,vscrollbar->visible(), adjustReq); + + (hScrollbarThickness > 0) ? hscrollbar->show () : hscrollbar->hide (); + (vScrollbarThickness > 0) ? vscrollbar->show () : vscrollbar->hide (); + + /* If no scrollbar, go to the beginning */ + scroll(hScrollbarThickness ? 0 : -scrollX, + vScrollbarThickness ? 0 : -scrollY); + + /* Adjust when scrollbar visibility changes */ + if (adjustReq) + adjustScrollbarsAndGadgetsAllocation (); +} + +void FltkViewport::updateCanvasWidgets (int dx, int dy) +{ + // scroll all child widgets except scroll bars + for (int i = children () - 1; i > 0; i--) { + Fl_Widget *widget = child (i); + + if (widget == hscrollbar || widget == vscrollbar) + continue; + + widget->position(widget->x () - dx, widget->y () - dy); + } +} + +int FltkViewport::translateViewXToCanvasX (int X) +{ + return X - x () + scrollX; +} + +int FltkViewport::translateViewYToCanvasY (int Y) +{ + return Y - y () + scrollY; +} + +int FltkViewport::translateCanvasXToViewX (int X) +{ + return X + x () - scrollX; +} + +int FltkViewport::translateCanvasYToViewY (int Y) +{ + return Y + y () - scrollY; +} + +// ---------------------------------------------------------------------- + +void FltkViewport::setGadgetOrientation (bool hscrollbarVisible, + bool vscrollbarVisible, + FltkViewport::GadgetOrientation + gadgetOrientation) +{ + this->gadgetOrientation[(hscrollbarVisible ? 0 : 1) | + (vscrollbarVisible ? 0 : 2)] = gadgetOrientation; + adjustScrollbarsAndGadgetsAllocation (); +} + +void FltkViewport::addGadget (Fl_Widget *gadget) +{ + /** \bug Reparent? */ + + gadgets->append (new TypedPointer < Fl_Widget> (gadget)); + adjustScrollbarsAndGadgetsAllocation (); +} + + +} // namespace fltk +} // namespace dw diff --git a/dw/fltkviewport.hh b/dw/fltkviewport.hh new file mode 100644 index 0000000..1569a7d --- /dev/null +++ b/dw/fltkviewport.hh @@ -0,0 +1,84 @@ +#ifndef __DW_FLTKVIEWPORT_HH__ +#define __DW_FLTKVIEWPORT_HH__ + +#include +#include + +#include "core.hh" +#include "fltkcore.hh" +#include "fltkviewbase.hh" + +namespace dw { +namespace fltk { + +class FltkViewport: public FltkWidgetView +{ +public: + enum GadgetOrientation { GADGET_VERTICAL, GADGET_HORIZONTAL }; + +private: + enum { SCROLLBAR_THICKNESS = 15 }; + + int scrollX, scrollY; + int scrollDX, scrollDY; + int hasDragScroll, dragScrolling, dragX, dragY; + int horScrolling, verScrolling; + + Fl_Scrollbar *vscrollbar, *hscrollbar; + + GadgetOrientation gadgetOrientation[4]; + lout::container::typed::List > + *gadgets; + + void adjustScrollbarsAndGadgetsAllocation (); + void adjustScrollbarValues (); + void hscrollbarChanged (); + void vscrollbarChanged (); + void positionChanged (); + + static void hscrollbarCallback (Fl_Widget *hscrollbar, void *viewportPtr); + static void vscrollbarCallback (Fl_Widget *vscrollbar, void *viewportPtr); + + void selectionScroll(); + static void selectionScroll(void *vport); + + void updateCanvasWidgets (int oldScrollX, int oldScrollY); + static void draw_area (void *data, int x, int y, int w, int h); + +protected: + int translateViewXToCanvasX (int x); + int translateViewYToCanvasY (int y); + int translateCanvasXToViewX (int x); + int translateCanvasYToViewY (int y); + +public: + FltkViewport (int x, int y, int w, int h, const char *label = 0); + ~FltkViewport (); + + void resize(int x, int y, int w, int h); + void draw (); + int handle (int event); + + void setCanvasSize (int width, int ascent, int descent); + + bool usesViewport (); + int getHScrollbarThickness (); + int getVScrollbarThickness (); + void scroll(int dx, int dy); + void scroll(dw::core::ScrollCommand cmd); + void scrollTo (int x, int y); + void setViewportSize (int width, int height, + int hScrollbarThickness, int vScrollbarThickness); + void setScrollStep(int step); + + void setGadgetOrientation (bool hscrollbarVisible, bool vscrollbarVisible, + GadgetOrientation gadgetOrientation); + void setDragScroll (bool enable) { hasDragScroll = enable ? 1 : 0; } + void addGadget (Fl_Widget *gadget); +}; + +} // namespace fltk +} // namespace dw + +#endif // __DW_FLTKVIEWPORT_HH__ + diff --git a/dw/imgbuf.hh b/dw/imgbuf.hh new file mode 100644 index 0000000..f9870bc --- /dev/null +++ b/dw/imgbuf.hh @@ -0,0 +1,229 @@ +#ifndef __DW_IMGBUF_HH__ +#define __DW_IMGBUF_HH__ + +#ifndef __INCLUDED_FROM_DW_CORE_HH__ +# error Do not include this file directly, use "core.hh" instead. +#endif + +#include "../lout/debug.hh" + +namespace dw { +namespace core { + +/** + * \brief The platform independent interface for image buffers. + * + * %Image buffers depend on the platform (see \ref dw-images-and-backgrounds), + * but have this general, platform independent interface. The purpose of + * an image buffer is + * + *
    + *
  1. storing the image data, + *
  2. handling scaled versions of this buffer, and + *
  3. drawing. + *
+ * + * The latter must be done independently from the window. + * + *

Creating

+ * + * %Image buffers are created by calling dw::core::Platform::createImgbuf. + * + *

Storing %Image Data

+ * + * 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. + * + * + *
Type (dw::core::Imgbuf::Type) Bytes per + * Pixel Representation + *
dw::core::Imgbuf::RGB 3 red, green, blue + *
dw::core::Imgbuf::RGBA 4 red, green, blue, alpha + *
dw::core::Imgbuf::GRAY 1 gray value + *
dw::core::Imgbuf::INDEXED 1 index to colormap + *
dw::core::Imgbuf::INDEXED_ALPHA 1 index to colormap + *
+ * + * 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. + * + * + *

Scaling

+ * + * 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): + * + *
    + *
  • If the method is called with an already scaled image buffer, this is + * delegated to the root buffer. + * + *
  • If the given size is the original size, the root buffer is + * returned, with an increased reference counter. + * + *
  • Otherwise, if this buffer has already been scaled to the given + * size, return this scaled buffer, with an increased reference + * counter. + * + *
  • Otherwise, return a new scaled buffer with reference counter 1. + *
+ * + * 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: + * + *
    + *
  • 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. + * + *
  • 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. + *
+ * + * 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. + * + *

Drawing

+ * + * 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: + * + *
    + *
  1. To react on expose events, the function dw::core::View::drawImage + * should be used, with the following parameters: + *
      + *
    • of course, the image buffer, + *
    • where the root of the image would be displayed (as \em xRoot + * and \em yRoot), and + *
    • the region within the image, which should be displayed (\em x, + * \em y, \em width, \em height). + *
    + * + *
  2. 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. + *
+ * + * \sa \ref dw-images-and-backgrounds + */ +class Imgbuf: public lout::object::Object, public lout::signal::ObservedObject +{ +public: + enum Type { RGB, RGBA, GRAY, INDEXED, INDEXED_ALPHA }; + + inline Imgbuf () { + DBG_OBJ_CREATE ("dw::core::Imgbuf"); + DBG_OBJ_BASECLASS (lout::object::Object); + DBG_OBJ_BASECLASS (lout::signal::ObservedObject); + } + + /* + * Methods called from the image decoding + */ + + virtual void setCMap (int *colors, int num_colors) = 0; + virtual void copyRow (int row, const byte *data) = 0; + virtual void newScan () = 0; + + /* + * Methods called from dw::Image + */ + + virtual Imgbuf* getScaledBuf (int width, int height) = 0; + virtual void getRowArea (int row, dw::core::Rectangle *area) = 0; + virtual int getRootWidth () = 0; + virtual int getRootHeight () = 0; + + + /** + * Creates an image buffer with same parameters (type, gamma etc.) + * except size. + */ + virtual Imgbuf *createSimilarBuf (int width, int height) = 0; + + /** + * Copies another image buffer into this image buffer. + */ + virtual void copyTo (Imgbuf *dest, int xDestRoot, int yDestRoot, + int xSrc, int ySrc, int widthSrc, int heightSrc) = 0; + + /* + * Reference counting. + */ + + virtual void ref () = 0; + virtual void unref () = 0; + + /** + * \todo Comment + */ + virtual bool lastReference () = 0; + + + /** + * \todo Comment + */ + virtual void setDeleteOnUnref (bool deleteOnUnref) = 0; + + /** + * \todo Comment + */ + virtual bool isReferred () = 0; +}; + +} // namespace core +} // namespace dw + +#endif // __DW_IMGBUF_HH__ diff --git a/dw/imgrenderer.cc b/dw/imgrenderer.cc new file mode 100644 index 0000000..1868122 --- /dev/null +++ b/dw/imgrenderer.cc @@ -0,0 +1,77 @@ +/* + * RTFL (originally part of dillo) + * + * Copyright 2013 Sebastian Geerken + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version; with the following exception: + * + * The copyright holders of RTFL give you permission to link this file + * statically or dynamically against all versions of the graphviz + * library, which are published by AT&T Corp. under one of the following + * licenses: + * + * - Common Public License version 1.0 as published by International + * Business Machines Corporation (IBM), or + * - Eclipse Public License version 1.0 as published by the Eclipse + * Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "core.hh" + +namespace dw { +namespace core { + +using namespace lout::container; +using namespace lout::object; + +void ImgRendererDist::setBuffer (core::Imgbuf *buffer, bool resize) +{ + for (typed::Iterator > it = + children->iterator (); it.hasNext (); ) { + TypedPointer *tp = it.getNext (); + tp->getTypedValue()->setBuffer (buffer, resize); + } +} + +void ImgRendererDist::drawRow (int row) +{ + for (typed::Iterator > it = + children->iterator (); it.hasNext (); ) { + TypedPointer *tp = it.getNext (); + tp->getTypedValue()->drawRow (row); + } +} + + +void ImgRendererDist::finish () +{ + for (typed::Iterator > it = + children->iterator (); it.hasNext (); ) { + TypedPointer *tp = it.getNext (); + tp->getTypedValue()->finish (); + } +} + +void ImgRendererDist::fatal () +{ + for (typed::Iterator > it = + children->iterator (); it.hasNext (); ) { + TypedPointer *tp = it.getNext (); + tp->getTypedValue()->fatal (); + } +} + + +} // namespace core +} // namespace dw diff --git a/dw/imgrenderer.hh b/dw/imgrenderer.hh new file mode 100644 index 0000000..e3b5e95 --- /dev/null +++ b/dw/imgrenderer.hh @@ -0,0 +1,87 @@ +#ifndef __DW_IMGRENDERER_HH__ +#define __DW_IMGRENDERER_HH__ + +#ifndef __INCLUDED_FROM_DW_CORE_HH__ +# error Do not include this file directly, use "core.hh" instead. +#endif + +namespace dw { +namespace core { + +/** + * \brief ... + * + * \sa \ref dw-images-and-backgrounds + */ +class ImgRenderer +{ +public: + virtual ~ImgRenderer () { } + + /** + * \brief Called, when an image buffer is attached. + * + * This is typically the case when all meta data (size, depth) has been read. + */ + virtual void setBuffer (core::Imgbuf *buffer, bool resize = false) = 0; + + /** + * \brief Called, when data from a row is available and has been copied into + * the image buffer. + * + * The implementation will typically queue the respective area for drawing. + */ + virtual void drawRow (int row) = 0; + + /** + * \brief Called, when all image data has been retrieved. + * + * The implementation may use this instead of "drawRow" for drawing, to + * limit the number of draws. + */ + virtual void finish () = 0; + + /** + * \brief Called, when there are problems with the retrieval of image data. + * + * The implementation may use this to indicate an error. + */ + virtual void fatal () = 0; +}; + +/** + * \brief Implementation of ImgRenderer, which distributes all calls + * to a set of other implementations of ImgRenderer. + * + * The order of the call children is not defined, especially not + * identical to the order in which they have been added. + */ +class ImgRendererDist: public ImgRenderer +{ + lout::container::typed::HashSet > + *children; + +public: + inline ImgRendererDist () + { children = new lout::container::typed::HashSet + > (true); } + ~ImgRendererDist () { delete children; } + + void setBuffer (core::Imgbuf *buffer, bool resize); + void drawRow (int row); + void finish (); + void fatal (); + + void put (ImgRenderer *child) + { children->put (new lout::object::TypedPointer (child)); } + void remove (ImgRenderer *child) + { lout::object::TypedPointer tp (child); + children->remove (&tp); } +}; + +} // namespace core +} // namespace dw + +#endif // __DW_IMGRENDERER_HH__ + + diff --git a/dw/iterator.cc b/dw/iterator.cc new file mode 100644 index 0000000..de934d0 --- /dev/null +++ b/dw/iterator.cc @@ -0,0 +1,920 @@ +/* + * RTFL (originally part of dillo) + * + * Copyright 2005-2007 Sebastian Geerken + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version; with the following exception: + * + * The copyright holders of RTFL give you permission to link this file + * statically or dynamically against all versions of the graphviz + * library, which are published by AT&T Corp. under one of the following + * licenses: + * + * - Common Public License version 1.0 as published by International + * Business Machines Corporation (IBM), or + * - Eclipse Public License version 1.0 as published by the Eclipse + * Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + + + +#include "core.hh" +#include + +using namespace lout; + +namespace dw { +namespace core { + +// -------------- +// Iterator +// -------------- + +Iterator::Iterator(Widget *widget, Content::Type mask, bool atEnd) +{ + this->widget = widget; + this->mask = mask; +} + +Iterator::Iterator(Iterator &it): object::Comparable () +{ + widget = it.widget; + content = it.content; +} + +Iterator::~Iterator() +{ +} + +bool Iterator::equals (Object *other) +{ + Iterator *otherIt = (Iterator*)other; + return + this == otherIt || + (getWidget() == otherIt->getWidget() && compareTo(otherIt) == 0); +} + +void Iterator::intoStringBuffer(misc::StringBuffer *sb) +{ + sb->append ("{ widget = "); + //widget->intoStringBuffer (sb); + sb->appendPointer (widget); + sb->append (" ("); + sb->append (widget->getClassName()); + sb->append (")>"); + + sb->append (", mask = "); + Content::maskIntoStringBuffer (mask, sb); + + sb->append (", content = "); + Content::intoStringBuffer (&content, sb); + + sb->append (" }"); +} + +/** + * \brief Delete the iterator. + * + * The destructor is hidden, implementations may use optimizations for + * the allocation. (Will soon be the case for dw::core::EmptyIteratorFactory.) + */ +void Iterator::unref () +{ + delete this; +} + +/** + * \brief Scrolls the viewport, so that the region between \em it1 and + * \em it2 is seen, according to \em hpos and \em vpos. + * + * The parameters \em start and \em end have the same meaning as in + * dw::core::Iterator::getAllocation, \em start refers + * to \em it1, while \em end rerers to \em it2. + * + * If \em it1 and \em it2 point to the same location (see code), only + * \em it1 is regarded, and both belowstart and belowend refer to it. + */ +void Iterator::scrollTo (Iterator *it1, Iterator *it2, int start, int end, + HPosition hpos, VPosition vpos) +{ + Allocation alloc1, alloc2, alloc; + int x1, x2, y1, y2; + DeepIterator *eit1, *eit2, *eit3; + int curStart, curEnd, cmp; + bool atStart; + + if (it1->equals(it2)) { + it1->getAllocation (start, end, &alloc); + it1->getWidget()->getLayout()->scrollTo (hpos, vpos, alloc.x, alloc.y, + alloc.width, + alloc.ascent + alloc.descent); + } else { + // First, determine the rectangle all iterators from it1 and it2 + // allocate, i.e. the smallest rectangle containing all allocations of + // these iterators. + eit1 = new DeepIterator (it1); + eit2 = new DeepIterator (it2); + + x1 = INT_MAX; + x2 = INT_MIN; + y1 = INT_MAX; + y2 = INT_MIN; + + for (eit3 = (DeepIterator*)eit1->clone (), atStart = true; + (cmp = eit3->compareTo (eit2)) <= 0; + eit3->next (), atStart = false) { + if (atStart) + curStart = start; + else + curStart = 0; + + if (cmp == 0) + curEnd = end; + else + curEnd = INT_MAX; + + eit3->getAllocation (curStart, curEnd, &alloc); + x1 = misc::min (x1, alloc.x); + x2 = misc::max (x2, alloc.x + alloc.width); + y1 = misc::min (y1, alloc.y); + y2 = misc::max (y2, alloc.y + alloc.ascent + alloc.descent); + } + + delete eit3; + delete eit2; + delete eit1; + + it1->getAllocation (start, INT_MAX, &alloc1); + it2->getAllocation (0, end, &alloc2); + + if (alloc1.x > alloc2.x) { + // + // This is due to a line break within the region. When the line is + // longer than the viewport, and the region is actually quite short, + // the user would not see anything of the region, as in this figure + // (with region marked as "#"): + // + // +----------+ ,-- alloc1 + // | | V + // | | ### ### + // ### ### | | + // ^ | | <-- viewport + // | +----------+ + // `-- alloc2 + // |----------------------------| + // width + // + // Therefore, we make the region smaller, so that the region will be + // displayed like this: + // + // ,-- alloc1 + // +----|-----+ + // | V | + // | ### ###| + // ### ### | | + // ^ | | <-- viewport + // `-- alloc2 +----------+ + // |----------| + // width + // + + /** \todo Changes in the viewport size, until the idle function is + * called, are not regarded. */ + + if (it1->getWidget()->getLayout()->getUsesViewport() && + x2 - x1 > it1->getWidget()->getLayout()->getWidthViewport()) { + x1 = x2 - it1->getWidget()->getLayout()->getWidthViewport(); + x2 = x1 + it1->getWidget()->getLayout()->getWidthViewport(); + } + } + + if (alloc1.y > alloc2.y) { + // This is similar to the case above, e.g. if the region ends in + // another table column. + if (it1->getWidget()->getLayout()->getUsesViewport() && + y2 - y1 > it1->getWidget()->getLayout()->getHeightViewport()) { + y1 = y2 - it1->getWidget()->getLayout()->getHeightViewport(); + y2 = y1 + it1->getWidget()->getLayout()->getHeightViewport(); + } + } + + it1->getWidget()->getLayout()->scrollTo (hpos, vpos, + x1, y1, x2 - x1, y2 - y1); + } +} + + +void Iterator::print () +{ + misc::StringBuffer sb; + intoStringBuffer (&sb); + printf ("%s", sb.getChars ()); +} + +// ------------------- +// EmptyIterator +// ------------------- + +EmptyIterator::EmptyIterator (Widget *widget, Content::Type mask, bool atEnd): + Iterator (widget, mask, atEnd) +{ + this->content.type = (atEnd ? Content::END : Content::START); +} + +EmptyIterator::EmptyIterator (EmptyIterator &it): Iterator (it) +{ +} + +object::Object *EmptyIterator::clone () +{ + return new EmptyIterator (*this); +} + +int EmptyIterator::compareTo (object::Comparable *other) +{ + EmptyIterator *otherIt = (EmptyIterator*)other; + + if (content.type == otherIt->content.type) + return 0; + else if (content.type == Content::START) + return -1; + else + return +1; +} + +bool EmptyIterator::next () +{ + content.type = Content::END; + return false; +} + +bool EmptyIterator::prev () +{ + content.type = Content::START; + return false; +} + +void EmptyIterator::highlight (int start, int end, HighlightLayer layer) +{ +} + +void EmptyIterator::unhighlight (int direction, HighlightLayer layer) +{ +} + +void EmptyIterator::getAllocation (int start, int end, Allocation *allocation) +{ +} + +// ------------------ +// TextIterator +// ------------------ + +TextIterator::TextIterator (Widget *widget, Content::Type mask, bool atEnd, + const char *text): Iterator (widget, mask, atEnd) +{ + this->content.type = (atEnd ? Content::END : Content::START); + this->text = (mask & Content::TEXT) ? text : NULL; +} + +TextIterator::TextIterator (TextIterator &it): Iterator (it) +{ + text = it.text; +} + +int TextIterator::compareTo (object::Comparable *other) +{ + TextIterator *otherIt = (TextIterator*)other; + + if (content.type == otherIt->content.type) + return 0; + + switch (content.type) { + case Content::START: + return -1; + + case Content::TEXT: + if (otherIt->content.type == Content::START) + return +1; + else + return -1; + + case Content::END: + return +1; + + default: + misc::assertNotReached(); + return 0; + } +} + +bool TextIterator::next () +{ + if (content.type == Content::START && text != NULL) { + content.type = Content::TEXT; + content.text = text; + return true; + } else { + content.type = Content::END; + return false; + } +} + +bool TextIterator::prev () +{ + if (content.type == Content::END && text != NULL) { + content.type = Content::TEXT; + content.text = text; + return true; + } else { + content.type = Content::START; + return false; + } +} + +void TextIterator::getAllocation (int start, int end, Allocation *allocation) +{ + // Return the allocation of the widget. + *allocation = *(getWidget()->getAllocation ()); +} + +// ------------------ +// DeepIterator +// ------------------ + +DeepIterator::Stack::~Stack () +{ + for (int i = 0; i < size (); i++) + get(i)->unref (); +} + +/* + * The following two methods are used by dw::core::DeepIterator::DeepIterator, + * when the passed dw::core::Iterator points to a widget. Since a + * dw::core::DeepIterator never returns a widget, the dw::core::Iterator has + * to be corrected, by searching for the next content downwards (within the + * widget pointed to), forwards, and backwards (in the traversed tree). + */ + +/* + * Search downwards. If fromEnd is true, start search at the end, + * otherwise at the beginning. + */ +Iterator *DeepIterator::searchDownward (Iterator *it, Content::Type mask, + bool fromEnd) +{ + Iterator *it2, *it3; + + //DEBUG_MSG (1, "%*smoving down (%swards) from %s\n", + // indent, "", from_end ? "back" : "for", a_Dw_iterator_text (it)); + + assert (it->getContent()->type & Content::ANY_WIDGET); + it2 = it->getContent()->widget->iterator (mask, fromEnd); + + if (it2 == NULL) { + // Moving downwards failed. + //DEBUG_MSG (1, "%*smoving down failed\n", indent, ""); + return NULL; + } + + while (fromEnd ? it2->prev () : it2->next ()) { + //DEBUG_MSG (1, "%*sexamining %s\n", + // indent, "", a_Dw_iterator_text (it2)); + + if (it2->getContent()->type & Content::ANY_WIDGET) { + // Another widget. Search in it downwards. + it3 = searchDownward (it2, mask, fromEnd); + if (it3 != NULL) { + it2->unref (); + return it3; + } + // Else continue in this widget. + } else { + // Success! + //DEBUG_MSG (1, "%*smoving down succeeded: %s\n", + // indent, "", a_Dw_iterator_text (it2)); + return it2; + } + } + + // Nothing found. + it2->unref (); + //DEBUG_MSG (1, "%*smoving down failed (nothing found)\n", indent, ""); + return NULL; +} + +/* + * Search sidewards. fromEnd specifies the direction, false means forwards, + * true means backwards. + */ +Iterator *DeepIterator::searchSideward (Iterator *it, Content::Type mask, + bool fromEnd) +{ + Iterator *it2, *it3; + + //DEBUG_MSG (1, "%*smoving %swards from %s\n", + // indent, "", from_end ? "back" : "for", a_Dw_iterator_text (it)); + + assert (it->getContent()->type & Content::ANY_WIDGET); + it2 = it->cloneIterator (); + + while (fromEnd ? it2->prev () : it2->next ()) { + if (it2->getContent()->type & Content::ANY_WIDGET) { + // Search downwards in this widget. + it3 = searchDownward (it2, mask, fromEnd); + if (it3 != NULL) { + it2->unref (); + //DEBUG_MSG (1, "%*smoving %swards succeeded: %s\n", + // indent, "", from_end ? "back" : "for", + // a_Dw_iterator_text (it3)); + return it3; + } + // Else continue in this widget. + } else { + // Success! + // DEBUG_MSG (1, "%*smoving %swards succeeded: %s\n", + // indent, "", from_end ? "back" : "for", + // a_Dw_iterator_text (it2)); + return it2; + } + } + + /* Nothing found, go upwards in the tree (if possible). */ + it2->unref (); + Widget *respParent = getRespectiveParent (it->getWidget(), it->getMask()); + if (respParent) { + it2 = respParent->iterator (mask, false); + while (true) { + if (!it2->next ()) + misc::assertNotReached (); + + if (it2->getContent()->type & Content::ANY_WIDGET && + it2->getContent()->widget == it->getWidget ()) { + it3 = searchSideward (it2, mask, fromEnd); + it2->unref (); + //DEBUG_MSG (1, "%*smoving %swards succeeded: %s\n", + // indent, "", from_end ? "back" : "for", + // a_Dw_iterator_text (it3)); + return it3; + } + } + } + + // Nothing found at all. + // DEBUG_MSG (1, "%*smoving %swards failed (nothing found)\n", + // indent, "", from_end ? "back" : "for"); + return NULL; +} + +Widget *DeepIterator::getRespectiveParent (Widget *widget, Content::Type mask) +{ + // Return, depending on which is requested indirectly (follow + // references or containments) the parent (container) or the + // generator. At this point, the type of the parent/generator is + // not known (since the parent/generator is not known), so we have + // to examine the mask. This is the reason why only one of + // WIDGET_OOF_CONT and WIDGET_OOF_REF is allowed. + + return (mask & Content::WIDGET_OOF_REF) ? + widget->getGenerator() : widget->getParent(); +} + +int DeepIterator::getRespectiveLevel (Widget *widget, Content::Type mask) +{ + // Similar to getRespectiveParent. + + return (mask & Content::WIDGET_OOF_REF) ? + widget->getGeneratorLevel() : widget->getLevel(); +} + +/** + * \brief Create a new deep iterator from an existing dw::core::Iterator. + * + * The content of the return value will be the content of \em it. If within + * the widget tree, there is no non-widget content, the resulting deep + * iterator is empty (denoted by dw::core::DeepIterator::stack == NULL). + * + * Notes: + * + *
    + *
  1. The mask of \em i" must include DW_CONTENT_WIDGET, but + * dw::core::DeepIterator::next will never return widgets. + *
+ */ +DeepIterator::DeepIterator (Iterator *it) +{ + //printf ("Starting creating DeepIterator %p ...\n", this); + //printf ("Initial iterator: "); + //it->print (); + //printf ("\n"); + + // Widgets out of flow are either followed widtin containers, or + // generators. Both (and also nothing at all) is not allowed. See + // also comment in getRespectiveParent. + int oofMask = + it->getMask() & (Content::WIDGET_OOF_CONT | Content::WIDGET_OOF_REF); + assert (oofMask == Content::WIDGET_OOF_CONT || + oofMask == Content::WIDGET_OOF_REF); + + //DEBUG_MSG (1, "a_Dw_ext_iterator_new: %s\n", a_Dw_iterator_text (it)); + + // Clone input iterator, so the iterator passed as parameter + // remains untouched. + it = it->cloneIterator (); + this->mask = it->getMask (); + + hasContents = true; + + // If it points to a widget, find a near non-widget content, + // since an DeepIterator should never return widgets. + if (it->getContent()->type & Content::ANY_WIDGET) { + Iterator *it2; + + // The second argument of searchDownward is actually a matter of + // taste :-) + if ((it2 = searchDownward (it, mask, false)) || + (it2 = searchSideward (it, mask, false)) || + (it2 = searchSideward (it, mask, true))) { + it->unref (); + it = it2; + } else { + // This may happen, when a page does not contain any non-widget + // content. + //DEBUG_MSG (1, "a_Dw_ext_iterator_new got totally helpless!\n"); + it->unref (); + hasContents = false; + } + } + + //DEBUG_MSG (1, " => %s\n", a_Dw_iterator_text (it)); + + if (hasContents) { + // If this widget has parents, we must construct appropriate iterators. + // + // \todo There may be a faster way instead of iterating through the + // parent widgets. + + //printf ("Starting with: "); + //it->print (); + //printf ("\n"); + + // Construct the iterators. + int thisLevel = getRespectiveLevel (it->getWidget()), level; + Widget *w; + for (w = it->getWidget (), level = thisLevel; + getRespectiveParent (w) != NULL; + w = getRespectiveParent (w), level--) { + Iterator *it = getRespectiveParent(w)->iterator (mask, false); + + //printf (" parent: %s %p\n", w->getClassName (), w); + + stack.put (it, level - 1); + while (true) { + //printf (" "); + //it->print (); + //printf ("\n"); + + bool hasNext = it->next(); + assert (hasNext); + + if (it->getContent()->type & Content::ANY_WIDGET && + it->getContent()->widget == w) + break; + } + + //printf (" %d: ", level - 1); + //it->print (); + //printf ("\n"); + } + + stack.put (it, thisLevel); + content = *(it->getContent()); + } + + //printf ("... done creating DeepIterator %p.\n", this); +} + + +DeepIterator::~DeepIterator () +{ + //printf ("Deleting DeepIterator %p ...\n", this); +} + +object::Object *DeepIterator::clone () +{ + DeepIterator *it = new DeepIterator (); + + for (int i = 0; i < stack.size (); i++) + it->stack.put (stack.get(i)->cloneIterator (), i); + + it->mask = mask; + it->content = content; + it->hasContents = hasContents; + + return it; +} + +int DeepIterator::compareTo (object::Comparable *other) +{ + DeepIterator *otherDeepIterator = (DeepIterator*)other; + + //printf ("Compare: %s\n", stack.toString ()); + //printf (" to: %s\n", otherDeepIterator->stack.toString ()); + + // Search the highest level, where the widgets are the same. + int level = 0; + + // The Comparable interface does not define "uncomparable". Deep + // iterators are only comparable if they belong to the same widget + // tree, so have the same widget at the bottom at the + // stack. If this is not the case, we abort. + + assert (stack.size() > 0); + assert (otherDeepIterator->stack.size() > 0); + + //printf ("Equal? The %s %p (of %p) and the %s %p (of %p)?\n", + // stack.get(0)->getWidget()->getClassName(), + // stack.get(0)->getWidget(), this, + // otherDeepIterator->stack.get(0)->getWidget()->getClassName(), + // otherDeepIterator->stack.get(0)->getWidget(), otherDeepIterator); + + assert (stack.get(0)->getWidget() + == otherDeepIterator->stack.get(level)->getWidget()); + + while (stack.get(level)->getWidget () + == otherDeepIterator->stack.get(level)->getWidget ()) { + if (level == stack.size() - 1 || + level == otherDeepIterator->stack.size() - 1) + break; + level++; + } + + //printf (" => level = %d (temorally)\n", level); + + while (stack.get(level)->getWidget () + != otherDeepIterator->stack.get(level)->getWidget ()) + level--; + + //printf (" => level = %d (finally)\n", level); + + return stack.get(level)->compareTo (otherDeepIterator->stack.get(level)); +} + +DeepIterator *DeepIterator::createVariant(Iterator *it) +{ + /** \todo Not yet implemented, and actually not yet needed very much. */ + return new DeepIterator (it); +} + +bool DeepIterator::isEmpty () { + return !hasContents; +} + +/** + * \brief Move iterator forward and store content it. + * + * Returns true on success. + */ +bool DeepIterator::next () +{ + Iterator *it = stack.getTop (); + + if (it->next ()) { + if (it->getContent()->type & Content::ANY_WIDGET) { + // Widget: new iterator on stack, to search in this widget. + stack.push (it->getContent()->widget->iterator (mask, false)); + return next (); + } else { + // Simply return the content of the iterartor. + content = *(it->getContent ()); + return true; + } + } else { + // No more data in the top-most widget. + if (stack.size () > 1) { + // Pop iterator from stack, and move to next item in the old one. + stack.pop (); + return next (); + } else { + // Stack is empty. + content.type = Content::END; + return false; + } + } +} + +/** + * \brief Move iterator backward and store content it. + * + * Returns true on success. + */ +bool DeepIterator::prev () +{ + Iterator *it = stack.getTop (); + + if (it->prev ()) { + if (it->getContent()->type & Content::ANY_WIDGET) { + // Widget: new iterator on stack, to search in this widget. + stack.push (it->getContent()->widget->iterator (mask, true)); + return prev (); + } else { + // Simply return the content of the iterartor. + content = *(it->getContent ()); + return true; + } + } else { + // No more data in the top-most widget. + if (stack.size () > 1) { + // Pop iterator from stack, and move to next item in the old one. + stack.pop (); + return prev (); + } else { + // Stack is empty. + content.type = Content::START; + return false; + } + } +} + +// ----------------- +// CharIterator +// ----------------- + +CharIterator::CharIterator () +{ + it = NULL; +} + +/** + * \brief ... + * + * If followReferences is true, only the reference are followed, when + * the container and generator for a widget is different. If false, + * only the container is followed. + */ +CharIterator::CharIterator (Widget *widget, bool followReferences) +{ + Iterator *i = + widget->iterator (Content::maskForSelection (followReferences), false); + it = new DeepIterator (i); + i->unref (); + ch = START; +} + +CharIterator::~CharIterator () +{ + if (it) + delete it; +} + +object::Object *CharIterator::clone() +{ + CharIterator *cloned = new CharIterator (); + cloned->it = it->cloneDeepIterator (); + cloned->ch = ch; + cloned->pos = pos; + return cloned; +} + +int CharIterator::compareTo(object::Comparable *other) +{ + CharIterator *otherIt = (CharIterator*)other; + int c = it->compareTo(otherIt->it); + if (c != 0) + return c; + else + return pos - otherIt->pos; +} + +bool CharIterator::next () +{ + if (ch == START || it->getContent()->type == Content::BREAK || + (it->getContent()->type == Content::TEXT && + it->getContent()->text[pos] == 0)) { + if (it->next()) { + if (it->getContent()->type == Content::BREAK) + ch = '\n'; + else { // if (it->getContent()->type == Content::TEXT) + pos = 0; + ch = it->getContent()->text[pos]; + if (ch == 0) + // should not happen, actually + return next (); + } + return true; + } + else { + ch = END; + return false; + } + } else if (ch == END) + return false; + else { + // at this point, it->getContent()->type == Content::TEXT + pos++; + ch = it->getContent()->text[pos]; + if (ch == 0) { + if (it->getContent()->space) { + ch = ' '; + } else { + return next (); + } + } + + return true; + } +} + +bool CharIterator::prev () +{ + if (ch == END || it->getContent()->type == Content::BREAK || + (it->getContent()->type == Content::TEXT && pos == 0)) { + if (it->prev()) { + if (it->getContent()->type == Content::BREAK) + ch = '\n'; + else { // if (it->getContent()->type == Content::TEXT) + if (it->getContent()->text[0] == 0) + return prev (); + else { + pos = strlen (it->getContent()->text); + if (it->getContent()->space) { + ch = ' '; + } else { + pos--; + ch = it->getContent()->text[pos]; + } + } + } + return true; + } + else { + ch = START; + return false; + } + } else if (ch == START) + return false; + else { + // at this point, it->getContent()->type == Content::TEXT + pos--; + ch = it->getContent()->text[pos]; + return true; + } +} + +void CharIterator::highlight (CharIterator *it1, CharIterator *it2, + HighlightLayer layer) +{ + if (it2->getChar () == CharIterator::END) + it2->prev (); + + if (it1->it->compareTo (it2->it) == 0) + // Only one content => highlight part of it. + it1->it->highlight (it1->pos, it2->pos, layer); + else { + DeepIterator *it = it1->it->cloneDeepIterator (); + int c; + bool start; + for (start = true; + (c = it->compareTo (it2->it)) <= 0; + it->next (), start = false) { + int endOfWord = + it->getContent()->type == Content::TEXT ? + strlen (it->getContent()->text) : 1; + if (start) // first iteration + it->highlight (it1->pos, endOfWord, layer); + else if (c == 0) // last iteration + it->highlight (0, it2->pos, layer); + else + it->highlight (0, endOfWord, layer); + } + delete it; + } +} + +void CharIterator::unhighlight (CharIterator *it1, CharIterator *it2, + HighlightLayer layer) +{ + if (it1->it->compareTo (it2->it) == 0) + // Only one content => unhighlight it (only for efficiency). + it1->it->unhighlight (0, layer); + else { + DeepIterator *it = it1->it->cloneDeepIterator (); + for (; it->compareTo (it2->it) <= 0; it->next ()) + it->unhighlight (-1, layer); + delete it; + } +} + +} // namespace core +} // namespace dw diff --git a/dw/iterator.hh b/dw/iterator.hh new file mode 100644 index 0000000..abf31d0 --- /dev/null +++ b/dw/iterator.hh @@ -0,0 +1,271 @@ +#ifndef __ITERATOR_HH__ +#define __ITERATOR_HH__ + +#ifndef __INCLUDED_FROM_DW_CORE_HH__ +# error Do not include this file directly, use "core.hh" instead. +#endif + +namespace dw { +namespace core { + +/** + * \brief Iterators are used to iterate through the contents of a widget. + * + * When using iterators, you should care about the results of + * dw::core::Widget::hasContents. + * + * \sa dw::core::Widget::iterator + */ +class Iterator: public lout::object::Comparable +{ +protected: + Iterator(Widget *widget, Content::Type mask, bool atEnd); + Iterator(Iterator &it); + ~Iterator(); + + Content content; + +private: + Widget *widget; + Content::Type mask; + +public: + bool equals (Object *other); + void intoStringBuffer(lout::misc::StringBuffer *sb); + + inline Widget *getWidget () { return widget; } + inline Content *getContent () { return &content; } + inline Content::Type getMask () { return mask; } + + virtual void unref (); + + /** + * \brief Move iterator forward and store content it. + * + * Returns true on success. + */ + virtual bool next () = 0; + + /** + * \brief Move iterator backward and store content it. + * + * Returns true on success. + */ + virtual bool prev () = 0; + + /** + * \brief Extend highlighted region to contain part of the current content. + * + * For text, start and end define the + * characters, otherwise, the shape is defined as [0, 1], i.e. for + * highlighting a whole dw::core::Content, pass 0 and >= 1. + * To unhighlight see also dw::core::Iterator::unhighlight. + */ + virtual void highlight (int start, int end, HighlightLayer layer) = 0; + + /** + * \brief Shrink highlighted region to no longer contain the + * current content. + * + * The direction parameter indicates whether the highlighted region should + * be reduced from the start (direction > 0) or from the end + * (direction < 0). If direction is 0 all content is unhighlighted. + */ + virtual void unhighlight (int direction, HighlightLayer layer) = 0; + + /** + * \brief Return the shape, which a part of the item, the iterator points + * on, allocates. + * + * The parameters start and end have the same meaning as in + * DwIterator::highlight(). + */ + virtual void getAllocation (int start, int end, Allocation *allocation) = 0; + + inline Iterator *cloneIterator () { return (Iterator*)clone(); } + + static void scrollTo (Iterator *it1, Iterator *it2, int start, int end, + HPosition hpos, VPosition vpos); + + virtual void print (); +}; + + +/** + * \brief This implementation of dw::core::Iterator can be used by widgets + * with no contents. + */ +class EmptyIterator: public Iterator +{ +private: + EmptyIterator (EmptyIterator &it); + +public: + EmptyIterator (Widget *widget, Content::Type mask, bool atEnd); + + lout::object::Object *clone(); + int compareTo(lout::object::Comparable *other); + bool next (); + bool prev (); + void highlight (int start, int end, HighlightLayer layer); + void unhighlight (int direction, HighlightLayer layer); + void getAllocation (int start, int end, Allocation *allocation); +}; + + +/** + * \brief This implementation of dw::core::Iterator can be used by widgets + * having one text word as contents + */ +class TextIterator: public Iterator +{ +private: + /** May be NULL, in this case, the next is skipped. */ + const char *text; + + TextIterator (TextIterator &it); + +public: + TextIterator (Widget *widget, Content::Type mask, bool atEnd, + const char *text); + + int compareTo(lout::object::Comparable *other); + + bool next (); + bool prev (); + void getAllocation (int start, int end, Allocation *allocation); +}; + + +/** + * \brief A stack of iterators, to iterate recursively through a widget tree. + * + * This class is similar to dw::core::Iterator, but not + * created by a widget, but explicitly from another iterator. Deep + * iterators do not have the limitation, that iteration is only done within + * a widget, instead, child widgets are iterated through recursively. + */ +class DeepIterator: public lout::object::Comparable +{ +private: + class Stack: public lout::container::typed::Vector + { + public: + inline Stack (): lout::container::typed::Vector (4, false) { } + ~Stack (); + inline Iterator *getTop () { return get (size () - 1); } + inline void push (Iterator *it) { put(it, -1); } + inline void pop() { getTop()->unref (); remove (size () - 1); } + }; + + Stack stack; + + static Iterator *searchDownward (Iterator *it, Content::Type mask, + bool fromEnd); + static Iterator *searchSideward (Iterator *it, Content::Type mask, + bool fromEnd); + + Content::Type mask; + Content content; + bool hasContents; + + inline DeepIterator () { } + + static Widget *getRespectiveParent (Widget *widget, Content::Type mask); + inline Widget *getRespectiveParent (Widget *widget) { + return getRespectiveParent (widget, mask); + } + + static int getRespectiveLevel (Widget *widget, Content::Type mask); + inline int getRespectiveLevel (Widget *widget) { + return getRespectiveLevel (widget, mask); + } + +public: + DeepIterator(Iterator *it); + ~DeepIterator(); + + lout::object::Object *clone (); + + DeepIterator *createVariant(Iterator *it); + inline Iterator *getTopIterator () { return stack.getTop(); } + inline Content *getContent () { return &content; } + + bool isEmpty (); + + bool next (); + bool prev (); + inline DeepIterator *cloneDeepIterator() { return (DeepIterator*)clone(); } + int compareTo(lout::object::Comparable *other); + + /** + * \brief Highlight a part of the current content. + * + * Unhighlight the current content by passing -1 as start (see also + * (dw::core::Iterator::unhighlight). For text, start and end define the + * characters, otherwise, the shape is defined as [0, 1], i.e. for + * highlighting a whole dw::core::Content, pass 0 and >= 1. + */ + inline void highlight (int start, int end, HighlightLayer layer) + { stack.getTop()->highlight (start, end, layer); } + + /** + * \brief Return the shape, which a part of the item, the iterator points + * on, allocates. + * + * The parameters start and end have the same meaning as in + * DwIterator::highlight(). + */ + inline void getAllocation (int start, int end, Allocation *allocation) + { stack.getTop()->getAllocation (start, end, allocation); } + + inline void unhighlight (int direction, HighlightLayer layer) + { stack.getTop()->unhighlight (direction, layer); } + + inline static void scrollTo (DeepIterator *it1, DeepIterator *it2, + int start, int end, + HPosition hpos, VPosition vpos) + { Iterator::scrollTo(it1->stack.getTop(), it2->stack.getTop(), + start, end, hpos, vpos); } +}; + +class CharIterator: public lout::object::Comparable +{ +public: + // START and END must not clash with any char value + // neither for signed nor unsigned char. + enum { START = 257, END = 258 }; + +private: + DeepIterator *it; + int pos, ch; + + CharIterator (); + +public: + CharIterator (Widget *widget, bool followReferences); + ~CharIterator (); + + lout::object::Object *clone(); + int compareTo(lout::object::Comparable *other); + + bool next (); + bool prev (); + inline int getChar() { return ch; } + inline CharIterator *cloneCharIterator() { return (CharIterator*)clone(); } + + static void highlight (CharIterator *it1, CharIterator *it2, + HighlightLayer layer); + static void unhighlight (CharIterator *it1, CharIterator *it2, + HighlightLayer layer); + + inline static void scrollTo (CharIterator *it1, CharIterator *it2, + HPosition hpos, VPosition vpos) + { DeepIterator::scrollTo(it1->it, it2->it, it1->pos, it2->pos, + hpos, vpos); } +}; + +} // namespace core +} // namespace dw + +#endif // __ITERATOR_HH__ diff --git a/dw/layout.cc b/dw/layout.cc new file mode 100644 index 0000000..96de6c2 --- /dev/null +++ b/dw/layout.cc @@ -0,0 +1,1445 @@ +/* + * RTFL (originally part of dillo) + * + * Copyright 2005-2007 Sebastian Geerken + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version; with the following exception: + * + * The copyright holders of RTFL give you permission to link this file + * statically or dynamically against all versions of the graphviz + * library, which are published by AT&T Corp. under one of the following + * licenses: + * + * - Common Public License version 1.0 as published by International + * Business Machines Corporation (IBM), or + * - Eclipse Public License version 1.0 as published by the Eclipse + * Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + + + +#include "core.hh" + +#include "../lout/msg.h" +#include "../lout/debug.hh" +#include "../lout/misc.hh" + +using namespace lout; +using namespace lout::container; +using namespace lout::object; + +namespace dw { +namespace core { + +bool Layout::LayoutImgRenderer::readyToDraw () +{ + return true; +} + +void Layout::LayoutImgRenderer::getBgArea (int *x, int *y, int *width, + int *height) +{ + // TODO Actually not padding area, but visible area? + getRefArea (x, y, width, height); +} + +void Layout::LayoutImgRenderer::getRefArea (int *xRef, int *yRef, int *widthRef, + int *heightRef) +{ + *xRef = 0; + *yRef = 0; + *widthRef = misc::max (layout->viewportWidth + - (layout->canvasHeightGreater ? + layout->vScrollbarThickness : 0), + layout->canvasWidth); + *heightRef = misc::max (layout->viewportHeight + - layout->hScrollbarThickness, + layout->canvasAscent + layout->canvasDescent); +} + +style::StyleImage *Layout::LayoutImgRenderer::getBackgroundImage () +{ + return layout->bgImage; +} + +style::BackgroundRepeat Layout::LayoutImgRenderer::getBackgroundRepeat () +{ + return layout->bgRepeat; +} + +style::BackgroundAttachment + Layout::LayoutImgRenderer::getBackgroundAttachment () +{ + return layout->bgAttachment; +} + +style::Length Layout::LayoutImgRenderer::getBackgroundPositionX () +{ + return layout->bgPositionX; +} + +style::Length Layout::LayoutImgRenderer::getBackgroundPositionY () +{ + return layout->bgPositionY; +} + +void Layout::LayoutImgRenderer::draw (int x, int y, int width, int height) +{ + layout->queueDraw (x, y, width, height); +} + +// ---------------------------------------------------------------------- + +void Layout::Receiver::resizeQueued (bool extremesChanged) +{ +} + +void Layout::Receiver::canvasSizeChanged (int width, int ascent, int descent) +{ +} + +// ---------------------------------------------------------------------- + +bool Layout::Emitter::emitToReceiver (lout::signal::Receiver *receiver, + int signalNo, int argc, + lout::object::Object **argv) +{ + Receiver *layoutReceiver = (Receiver*)receiver; + + switch (signalNo) { + case CANVAS_SIZE_CHANGED: + layoutReceiver->canvasSizeChanged (((Integer*)argv[0])->getValue (), + ((Integer*)argv[1])->getValue (), + ((Integer*)argv[2])->getValue ()); + break; + + case RESIZE_QUEUED: + layoutReceiver->resizeQueued (((Boolean*)argv[0])->getValue ()); + break; + + default: + misc::assertNotReached (); + } + + return false; +} + +void Layout::Emitter::emitResizeQueued (bool extremesChanged) +{ + Boolean ec (extremesChanged); + Object *argv[1] = { &ec }; + emitVoid (RESIZE_QUEUED, 1, argv); +} + +void Layout::Emitter::emitCanvasSizeChanged (int width, + int ascent, int descent) +{ + Integer w (width), a (ascent), d (descent); + Object *argv[3] = { &w, &a, &d }; + emitVoid (CANVAS_SIZE_CHANGED, 3, argv); +} + +// ---------------------------------------------------------------------- + +bool Layout::LinkReceiver::enter (Widget *widget, int link, int img, + int x, int y) +{ + return false; +} + +bool Layout::LinkReceiver::press (Widget *widget, int link, int img, + int x, int y, EventButton *event) +{ + return false; +} + +bool Layout::LinkReceiver::release (Widget *widget, int link, int img, + int x, int y, EventButton *event) +{ + return false; +} + +bool Layout::LinkReceiver::click (Widget *widget, int link, int img, + int x, int y, EventButton *event) +{ + return false; +} + +// ---------------------------------------------------------------------- + +bool Layout::LinkEmitter::emitToReceiver (lout::signal::Receiver *receiver, + int signalNo, int argc, + lout::object::Object **argv) +{ + LinkReceiver *linkReceiver = (LinkReceiver*)receiver; + + switch (signalNo) { + case ENTER: + return linkReceiver->enter ((Widget*)argv[0], + ((Integer*)argv[1])->getValue (), + ((Integer*)argv[2])->getValue (), + ((Integer*)argv[3])->getValue (), + ((Integer*)argv[4])->getValue ()); + + case PRESS: + return linkReceiver->press ((Widget*)argv[0], + ((Integer*)argv[1])->getValue (), + ((Integer*)argv[2])->getValue (), + ((Integer*)argv[3])->getValue (), + ((Integer*)argv[4])->getValue (), + (EventButton*)argv[5]); + + case RELEASE: + return linkReceiver->release ((Widget*)argv[0], + ((Integer*)argv[1])->getValue (), + ((Integer*)argv[2])->getValue (), + ((Integer*)argv[3])->getValue (), + ((Integer*)argv[4])->getValue (), + (EventButton*)argv[5]); + + case CLICK: + return linkReceiver->click ((Widget*)argv[0], + ((Integer*)argv[1])->getValue (), + ((Integer*)argv[2])->getValue (), + ((Integer*)argv[3])->getValue (), + ((Integer*)argv[4])->getValue (), + (EventButton*)argv[5]); + + default: + misc::assertNotReached (); + } + return false; +} + +bool Layout::LinkEmitter::emitEnter (Widget *widget, int link, int img, + int x, int y) +{ + Integer ilink (link), iimg (img), ix (x), iy (y); + Object *argv[5] = { widget, &ilink, &iimg, &ix, &iy }; + return emitBool (ENTER, 5, argv); +} + +bool Layout::LinkEmitter::emitPress (Widget *widget, int link, int img, + int x, int y, EventButton *event) +{ + Integer ilink (link), iimg (img), ix (x), iy (y); + Object *argv[6] = { widget, &ilink, &iimg, &ix, &iy, event }; + return emitBool (PRESS, 6, argv); +} + +bool Layout::LinkEmitter::emitRelease (Widget *widget, int link, int img, + int x, int y, EventButton *event) +{ + Integer ilink (link), iimg (img), ix (x), iy (y); + Object *argv[6] = { widget, &ilink, &iimg, &ix, &iy, event }; + return emitBool (RELEASE, 6, argv); +} + +bool Layout::LinkEmitter::emitClick (Widget *widget, int link, int img, + int x, int y, EventButton *event) +{ + Integer ilink (link), iimg (img), ix (x), iy (y); + Object *argv[6] = { widget, &ilink, &iimg, &ix, &iy, event }; + return emitBool (CLICK, 6, argv); +} + +// --------------------------------------------------------------------- + +Layout::Anchor::~Anchor () +{ + free(name); +} + +// --------------------------------------------------------------------- + +Layout::ScrollTarget::~ScrollTarget () +{ +} + +Layout::ScrollTargetBase::ScrollTargetBase (HPosition hPos, VPosition vPos) +{ + this->hPos = hPos; + this->vPos = vPos; +} + +HPosition Layout::ScrollTargetBase::getHPos () +{ + return hPos; +} + +VPosition Layout::ScrollTargetBase::getVPos () +{ + return vPos; +} + +Layout::ScrollTargetFixed::ScrollTargetFixed (HPosition hPos, VPosition vPos, + int x, int y, int width, int height) : + ScrollTargetBase (hPos, vPos) +{ + this->x = x; + this->y = y; + this->width = width; + this->height = height; +} + +int Layout::ScrollTargetFixed::getX () +{ + return x; +} + +int Layout::ScrollTargetFixed::getY () +{ + return y; +} + +int Layout::ScrollTargetFixed::getWidth () +{ + return width; +} + +int Layout::ScrollTargetFixed::getHeight () +{ + return height; +} + +Layout::ScrollTargetWidget::ScrollTargetWidget (HPosition hPos, VPosition vPos, + Widget *widget) : + ScrollTargetBase (hPos, vPos) +{ + this->widget = widget; +} + +int Layout::ScrollTargetWidget::getX () +{ + return widget->allocation.x; +} + +int Layout::ScrollTargetWidget::getY () +{ + return widget->allocation.y; +} + +int Layout::ScrollTargetWidget::getWidth () +{ + return widget->allocation.width; +} + +int Layout::ScrollTargetWidget::getHeight () +{ + return widget->allocation.ascent + widget->allocation.descent; +} + +// ---------------------------------------------------------------------- + +Layout::Layout (Platform *platform) +{ + this->platform = platform; + view = NULL; + topLevel = NULL; + widgetAtPoint = NULL; + + queueQueueResizeList = new typed::Stack (true); + queueResizeList = new typed::Vector (4, false); + + DBG_OBJ_CREATE ("dw::core::Layout"); + + bgColor = NULL; + bgImage = NULL; + cursor = style::CURSOR_DEFAULT; + + canvasWidth = canvasAscent = canvasDescent = 0; + + usesViewport = false; + drawAfterScrollReq = false; + scrollX = scrollY = 0; + viewportWidth = viewportHeight = 0; + hScrollbarThickness = vScrollbarThickness = 0; + + DBG_OBJ_SET_NUM ("viewportWidth", viewportWidth); + DBG_OBJ_SET_NUM ("viewportHeight", viewportHeight); + DBG_OBJ_SET_NUM ("hScrollbarThickness", hScrollbarThickness); + DBG_OBJ_SET_NUM ("vScrollbarThickness", vScrollbarThickness); + + requestedAnchor = NULL; + scrollTarget = NULL; + scrollIdleId = -1; + scrollIdleNotInterrupted = false; + + anchorsTable = + new container::typed::HashTable (true, true); + + resizeIdleId = -1; + + textZone = new misc::ZoneAllocator (16 * 1024); + + DBG_OBJ_ASSOC_CHILD (&findtextState); + DBG_OBJ_ASSOC_CHILD (&selectionState); + + platform->setLayout (this); + + selectionState.setLayout(this); + + queueResizeCounter = sizeAllocateCounter = sizeRequestCounter = + getExtremesCounter = 0; + + layoutImgRenderer = NULL; + + resizeIdleCounter = queueResizeCounter = sizeAllocateCounter + = sizeRequestCounter = getExtremesCounter = 0; +} + +Layout::~Layout () +{ + widgetAtPoint = NULL; + + if (layoutImgRenderer) { + if (bgImage) + bgImage->removeExternalImgRenderer (layoutImgRenderer); + delete layoutImgRenderer; + } + + if (scrollIdleId != -1) + platform->removeIdle (scrollIdleId); + if (resizeIdleId != -1) + platform->removeIdle (resizeIdleId); + if (bgColor) + bgColor->unref (); + if (bgImage) + bgImage->unref (); + if (topLevel) { + detachWidget (topLevel); + Widget *w = topLevel; + topLevel = NULL; + delete w; + } + + delete queueQueueResizeList; + delete queueResizeList; + delete platform; + delete view; + delete anchorsTable; + delete textZone; + + if (requestedAnchor) + free (requestedAnchor); + if (scrollTarget) + delete scrollTarget; + + DBG_OBJ_DELETE (); +} + +void Layout::detachWidget (Widget *widget) +{ + // Called form ~Layout. Sometimes, the widgets (not only the toplevel widget) + // do some stuff after the layout has been deleted, so *all* widgets have to + // be detached, and check "layout != NULL" at relevant points. + + // Could be replaced by a virtual method in Widget, like getWidgetAtPoint, + // if performace were really a problem. + + widget->layout = NULL; + Iterator *it = + widget->iterator ((Content::Type) + (Content::WIDGET_IN_FLOW | Content::WIDGET_OOF_CONT), + false); + while (it->next ()) + detachWidget (it->getContent()->widget); + + it->unref (); +} + +void Layout::addWidget (Widget *widget) +{ + if (topLevel) { + MSG_WARN("widget already set\n"); + return; + } + + topLevel = widget; + widget->layout = this; + widget->container = NULL; + DBG_OBJ_SET_PTR_O (widget, "container", widget->container); + + queueResizeList->clear (); + widget->notifySetAsTopLevel (); + + findtextState.setWidget (widget); + + canvasHeightGreater = false; + DBG_OBJ_SET_SYM ("canvasHeightGreater", + canvasHeightGreater ? "true" : "false"); + + // Do not directly call Layout::queueResize(), but + // Widget::queueResize(), so that all flags are set properly, + // queueResizeList is filled, etc. + topLevel->queueResize (-1, false); +} + +void Layout::removeWidget () +{ + /** + * \bug Some more attributes must be reset here. + */ + topLevel = NULL; + queueResizeList->clear (); + widgetAtPoint = NULL; + canvasWidth = canvasAscent = canvasDescent = 0; + scrollX = scrollY = 0; + + view->setCanvasSize (canvasWidth, canvasAscent, canvasDescent); + if (view->usesViewport ()) + view->setViewportSize (viewportWidth, viewportHeight, 0, 0); + view->queueDrawTotal (); + + setAnchor (NULL); + updateAnchor (); + + emitter.emitCanvasSizeChanged (canvasWidth, canvasAscent, canvasDescent); + + findtextState.setWidget (NULL); + selectionState.reset (); + + updateCursor (); +} + +void Layout::setWidget (Widget *widget) +{ + DBG_OBJ_ASSOC_CHILD (widget); + + widgetAtPoint = NULL; + if (topLevel) { + Widget *w = topLevel; + topLevel = NULL; + delete w; + } + textZone->zoneFree (); + addWidget (widget); + + updateCursor (); +} + +/** + * \brief Attach a view to the layout. + * + * It will become a child of the layout, + * and so it will be destroyed, when the layout will be destroyed. + */ +void Layout::attachView (View *view) +{ + if (this->view) + MSG_ERR("attachView: Multiple views for layout!\n"); + + DBG_OBJ_ASSOC_CHILD (view); + + this->view = view; + platform->attachView (view); + + /* + * The layout of the view is set later, first, we "project" the current + * state of the layout into the new view. A view must handle this without + * a layout. See also at the end of this function. + */ + if (bgColor) + view->setBgColor (bgColor); + view->setCursor (cursor); + view->setCanvasSize (canvasWidth, canvasAscent, canvasDescent); + + if (view->usesViewport ()) { + if (usesViewport) { + view->scrollTo (scrollX, scrollY); + view->setViewportSize (viewportWidth, viewportHeight, + hScrollbarThickness, vScrollbarThickness); + hScrollbarThickness = misc::max (hScrollbarThickness, + view->getHScrollbarThickness ()); + vScrollbarThickness = misc::max (vScrollbarThickness, + view->getVScrollbarThickness ()); + } + else { + usesViewport = true; + scrollX = scrollY = 0; + viewportWidth = viewportHeight = 100; // random values + hScrollbarThickness = view->getHScrollbarThickness (); + vScrollbarThickness = view->getVScrollbarThickness (); + } + + DBG_OBJ_SET_NUM ("viewportWidth", viewportWidth); + DBG_OBJ_SET_NUM ("viewportHeight", viewportHeight); + DBG_OBJ_SET_NUM ("hScrollbarThickness", hScrollbarThickness); + DBG_OBJ_SET_NUM ("vScrollbarThickness", vScrollbarThickness); + } + + /* + * This is the last call within this function, so that it is safe for + * the implementation of dw::core::View::setLayout, to call methods + * of dw::core::Layout. + */ + view->setLayout (this); +} + +void Layout::detachView (View *view) +{ + if (this->view != view) + MSG_ERR("detachView: this->view: %p view %p\n", this->view, view); + + view->setLayout (NULL); + platform->detachView (view); + this->view = NULL; + /** + * \todo Actually, viewportMarkerWidthDiff and + * viewportMarkerHeightDiff have to be recalculated here, since the + * effective (i.e. maximal) values may change, after the view has been + * detached. Same applies to the usage of viewports. + */ +} + +void Layout::scroll(ScrollCommand cmd) +{ + if (view->usesViewport ()) + view->scroll(cmd); +} + +/** + * \brief Scrolls all viewports, so that the region [x, y, width, height] + * is seen, according to hpos and vpos. + */ +void Layout::scrollTo (HPosition hpos, VPosition vpos, + int x, int y, int width, int height) +{ + scrollTo0 (new ScrollTargetFixed (hpos, vpos, x, y, width, height), true); +} + +void Layout::scrollToWidget (HPosition hpos, VPosition vpos, Widget *widget) +{ + scrollTo0 (new ScrollTargetWidget (hpos, vpos, widget), true); +} + +void Layout::scrollTo0 (ScrollTarget *scrollTarget, bool scrollingInterrupted) +{ + if (usesViewport) { + _MSG("scrollTo (%d, %d, %s)\n", + x, y, scrollingInterrupted ? "true" : "false"); + + if (this->scrollTarget) + delete this->scrollTarget; + + this->scrollTarget = scrollTarget; + + if (scrollIdleId == -1) { + scrollIdleId = platform->addIdle (&Layout::scrollIdle); + scrollIdleNotInterrupted = true; + } + + scrollIdleNotInterrupted = + scrollIdleNotInterrupted || !scrollingInterrupted; + } +} + +void Layout::scrollIdle () +{ + DBG_OBJ_ENTER0 ("scroll", 0, "scrollIdle"); + + bool xChanged = true; + switch (scrollTarget->getHPos ()) { + case HPOS_LEFT: + scrollX = scrollTarget->getX (); + break; + case HPOS_CENTER: + scrollX = + scrollTarget->getX () - (viewportWidth - currVScrollbarThickness() + - scrollTarget->getWidth ()) / 2; + break; + case HPOS_RIGHT: + scrollX = + scrollTarget->getX () - (viewportWidth - currVScrollbarThickness() + - scrollTarget->getWidth ()); + break; + case HPOS_INTO_VIEW: + xChanged = calcScrollInto (scrollTarget->getX (), + scrollTarget->getWidth (), &scrollX, + viewportWidth - currVScrollbarThickness()); + break; + case HPOS_NO_CHANGE: + xChanged = false; + break; + } + + bool yChanged = true; + switch (scrollTarget->getVPos ()) { + case VPOS_TOP: + scrollY = scrollTarget->getY (); + break; + case VPOS_CENTER: + scrollY = + scrollTarget->getY () - (viewportHeight - currHScrollbarThickness() + - scrollTarget->getHeight ()) / 2; + break; + case VPOS_BOTTOM: + scrollY = + scrollTarget->getY () - (viewportHeight - currHScrollbarThickness() + - scrollTarget->getHeight ()); + break; + case VPOS_INTO_VIEW: + yChanged = calcScrollInto (scrollTarget->getY (), + scrollTarget->getHeight (), &scrollY, + viewportHeight - currHScrollbarThickness()); + break; + case VPOS_NO_CHANGE: + yChanged = false; + break; + } + + DBG_OBJ_MSGF ("scroll", 1, "xChanged = %s, yChanged = %s", + xChanged ? "true" : "false", yChanged ? "true" : "false"); + + if (xChanged || yChanged) { + adjustScrollPos (); + view->scrollTo (scrollX, scrollY); + } + + // The following code was once inside the block above ("if + // (xChanged || yChanged)"). I'm not sure but it looks that this + // should be outside, or at least setting drawAfterScrollReq in + // Layout::draw should consider whether the scroll position will + // change (but this would be rather difficult). --SG + + if (drawAfterScrollReq) { + drawAfterScrollReq = false; + view->queueDrawTotal (); + } + + delete scrollTarget; + scrollTarget = NULL; + + scrollIdleId = -1; + + DBG_OBJ_LEAVE (); +} + +void Layout::adjustScrollPos () +{ + scrollX = misc::min (scrollX, + canvasWidth - (viewportWidth - vScrollbarThickness)); + scrollX = misc::max (scrollX, 0); + + scrollY = misc::min (scrollY, + canvasAscent + canvasDescent - (viewportHeight - hScrollbarThickness)); + scrollY = misc::max (scrollY, 0); + + _MSG("adjustScrollPos: scrollX=%d scrollY=%d\n", scrollX, scrollY); +} + +bool Layout::calcScrollInto (int requestedValue, int requestedSize, + int *value, int viewportSize) +{ + if (requestedSize > viewportSize) { + // The viewport size is smaller than the size of the region which will + // be shown. If the region is already visible, do not change the + // position. Otherwise, show the left/upper border, this is most likely + // what is needed. + if (*value >= requestedValue && + *value + viewportSize < requestedValue + requestedSize) + return false; + else + requestedSize = viewportSize; + } + + if (requestedValue < *value) { + *value = requestedValue; + return true; + } else if (requestedValue + requestedSize > *value + viewportSize) { + *value = requestedValue - viewportSize + requestedSize; + return true; + } else + return false; +} + +void Layout::draw (View *view, Rectangle *area) +{ + DBG_OBJ_ENTER ("draw", 0, "draw", "%d, %d, %d * %d", + area->x, area->y, area->width, area->height); + + Rectangle widgetArea, intersection, widgetDrawArea; + + // First of all, draw background image. (Unlike background *color*, + // this is not a feature of the views.) + if (bgImage != NULL && bgImage->getImgbufSrc() != NULL) + style::drawBackgroundImage (view, bgImage, bgRepeat, bgAttachment, + bgPositionX, bgPositionY, + area->x, area->y, area->width, + area->height, 0, 0, + // Reference area: maximum of canvas size and + // viewport size. + misc::max (viewportWidth + - (canvasHeightGreater ? + vScrollbarThickness : 0), + canvasWidth), + misc::max (viewportHeight + - hScrollbarThickness, + canvasAscent + canvasDescent)); + + if (scrollIdleId != -1) { + /* scroll is pending, defer draw until after scrollIdle() */ + drawAfterScrollReq = true; + DBG_OBJ_MSGF ("draw", 1, "scrollIdleId = %d => delayed", scrollIdleId); + } else if (topLevel) { + /* Draw the top level widget. */ + widgetArea.x = topLevel->allocation.x; + widgetArea.y = topLevel->allocation.y; + widgetArea.width = topLevel->allocation.width; + widgetArea.height = topLevel->getHeight (); + + if (area->intersectsWith (&widgetArea, &intersection)) { + DBG_OBJ_MSG ("draw", 1, "drawing toplevel widget"); + + view->startDrawing (&intersection); + + /* Intersection in widget coordinates. */ + widgetDrawArea.x = intersection.x - topLevel->allocation.x; + widgetDrawArea.y = intersection.y - topLevel->allocation.y; + widgetDrawArea.width = intersection.width; + widgetDrawArea.height = intersection.height; + + topLevel->draw (view, &widgetDrawArea); + + view->finishDrawing (&intersection); + } else + DBG_OBJ_MSG ("draw", 1, "no intersection"); + } else + DBG_OBJ_MSG ("draw", 1, "no toplevel widget"); + + DBG_OBJ_LEAVE (); +} + +int Layout::currHScrollbarThickness() +{ + return (canvasWidth > viewportWidth) ? hScrollbarThickness : 0; +} + +int Layout::currVScrollbarThickness() +{ + return (canvasAscent + canvasDescent > viewportHeight) ? + vScrollbarThickness : 0; +} + +/** + * Sets the anchor to scroll to. + */ +void Layout::setAnchor (const char *anchor) +{ + _MSG("setAnchor (%s)\n", anchor); + + if (requestedAnchor) + free (requestedAnchor); + requestedAnchor = anchor ? strdup (anchor) : NULL; + updateAnchor (); +} + +/** + * Used, when the widget is not allocated yet. + */ +char *Layout::addAnchor (Widget *widget, const char* name) +{ + return addAnchor (widget, name, -1); +} + +char *Layout::addAnchor (Widget *widget, const char* name, int y) +{ + String key (name); + if (anchorsTable->contains (&key)) + return NULL; + else { + Anchor *anchor = new Anchor (); + anchor->name = strdup (name); + anchor->widget = widget; + anchor->y = y; + + anchorsTable->put (new String (name), anchor); + updateAnchor (); + + return anchor->name; + } +} + +void Layout::changeAnchor (Widget *widget, char* name, int y) +{ + String key (name); + Anchor *anchor = anchorsTable->get (&key); + assert (anchor); + assert (anchor->widget == widget); + anchor->y = y; + updateAnchor (); +} + +void Layout::removeAnchor (Widget *widget, char* name) +{ + String key (name); + anchorsTable->remove (&key); +} + +void Layout::updateAnchor () +{ + Anchor *anchor; + if (requestedAnchor) { + String key (requestedAnchor); + anchor = anchorsTable->get (&key); + } else + anchor = NULL; + + if (anchor == NULL) { + /** \todo Copy comment from old docs. */ + if (scrollIdleId != -1 && !scrollIdleNotInterrupted) { + platform->removeIdle (scrollIdleId); + scrollIdleId = -1; + } + } else + if (anchor->y != -1) + scrollTo0 (new ScrollTargetFixed (HPOS_NO_CHANGE, VPOS_TOP, + 0, anchor->y, 0, 0), false); +} + +void Layout::setCursor (style::Cursor cursor) +{ + if (cursor != this->cursor) { + this->cursor = cursor; + view->setCursor (cursor); + } +} + +void Layout::updateCursor () +{ + if (widgetAtPoint && widgetAtPoint->style) + setCursor (widgetAtPoint->style->cursor); + else + setCursor (style::CURSOR_DEFAULT); +} + +void Layout::setBgColor (style::Color *color) +{ + color->ref (); + + if (bgColor) + bgColor->unref (); + + bgColor = color; + + if (view) + view->setBgColor (bgColor); +} + +void Layout::setBgImage (style::StyleImage *bgImage, + style::BackgroundRepeat bgRepeat, + style::BackgroundAttachment bgAttachment, + style::Length bgPositionX, style::Length bgPositionY) +{ + if (layoutImgRenderer && this->bgImage) + this->bgImage->removeExternalImgRenderer (layoutImgRenderer); + + if (bgImage) + bgImage->ref (); + + if (this->bgImage) + this->bgImage->unref (); + + this->bgImage = bgImage; + this->bgRepeat = bgRepeat; + this->bgAttachment = bgAttachment; + this->bgPositionX = bgPositionX; + this->bgPositionY = bgPositionY; + + if (bgImage) { + // Create instance of LayoutImgRenderer when needed. Until this + // layout is deleted, "layoutImgRenderer" will be kept, since it + // is not specific to the style, but only to this layout. + if (layoutImgRenderer == NULL) + layoutImgRenderer = new LayoutImgRenderer (this); + bgImage->putExternalImgRenderer (layoutImgRenderer); + } +} + + +void Layout::resizeIdle () +{ + DBG_OBJ_ENTER0 ("resize", 0, "resizeIdle"); + + enterResizeIdle (); + + //static int calls = 0; + //printf ("Layout::resizeIdle calls = %d\n", ++calls); + + assert (resizeIdleId != -1); + + for (typed::Iterator it = queueResizeList->iterator(); + it.hasNext (); ) { + Widget *widget = it.getNext (); + + //printf (" the %stop-level %s %p was queued (extremes changed: %s)\n", + // widget->parent ? "non-" : "", widget->getClassName(), widget, + // widget->extremesQueued () ? "yes" : "no"); + + if (widget->resizeQueued ()) { + widget->setFlags (Widget::NEEDS_RESIZE); + widget->unsetFlags (Widget::RESIZE_QUEUED); + } + + if (widget->allocateQueued ()) { + widget->setFlags (Widget::NEEDS_ALLOCATE); + widget->unsetFlags (Widget::ALLOCATE_QUEUED); + } + + if (widget->extremesQueued ()) { + widget->setFlags (Widget::EXTREMES_CHANGED); + widget->unsetFlags (Widget::EXTREMES_QUEUED); + } + } + queueResizeList->clear (); + + // Reset already here, since in this function, queueResize() may be + // called again. + resizeIdleId = -1; + + // If this method is triggered by a viewport change, we can save + // time when the toplevel widget is not affected (as for a toplevel + // image resource). + if (topLevel && (topLevel->needsResize () || topLevel->needsAllocate ())) { + Requisition requisition; + Allocation allocation; + + topLevel->sizeRequest (&requisition); + DBG_OBJ_MSGF ("resize", 1, "toplevel size: %d * (%d + %d)", + requisition.width, requisition.ascent, requisition.descent); + + // This method is triggered by Widget::queueResize, which will, + // in any case, set NEEDS_ALLOCATE (indirectly, as ALLOCATE_QUEUED). + // This assertion helps to find inconsistences. (Cases where + // this method is triggered by a viewport change, but the + // toplevel widget is not affected, are filtered out some lines + // above: "if (topLevel && topLevel->needsResize ())".) + assert (topLevel->needsAllocate ()); + + allocation.x = allocation.y = 0; + allocation.width = requisition.width; + allocation.ascent = requisition.ascent; + allocation.descent = requisition.descent; + topLevel->sizeAllocate (&allocation); + + canvasWidth = requisition.width; + canvasAscent = requisition.ascent; + canvasDescent = requisition.descent; + + emitter.emitCanvasSizeChanged (canvasWidth, canvasAscent, canvasDescent); + + // Tell the view about the new world size. + view->setCanvasSize (canvasWidth, canvasAscent, canvasDescent); + // view->queueDrawTotal (false); + + if (usesViewport) { + int currHThickness = currHScrollbarThickness(); + int currVThickness = currVScrollbarThickness(); + + if (!canvasHeightGreater && + canvasAscent + canvasDescent > viewportHeight - currHThickness) { + canvasHeightGreater = true; + DBG_OBJ_SET_SYM ("canvasHeightGreater", + canvasHeightGreater ? "true" : "false"); + containerSizeChanged (); + } + + // Set viewport sizes. + view->setViewportSize (viewportWidth, viewportHeight, + currHThickness, currVThickness); + } + + // views are redrawn via Widget::resizeDrawImpl () + } + + updateAnchor (); + + DBG_OBJ_MSGF ("resize", 1, + "after resizeIdle: resizeIdleId = %d", resizeIdleId); + DBG_OBJ_LEAVE (); + + leaveResizeIdle (); +} + +void Layout::queueDraw (int x, int y, int width, int height) +{ + DBG_OBJ_ENTER ("draw", 0, "queueDrawArea", "%d, %d, %d, %d", + x, y, width, height); + + Rectangle area; + area.x = x; + area.y = y; + area.width = width; + area.height = height; + + if (!area.isEmpty ()) + view->queueDraw (&area); + + DBG_OBJ_LEAVE (); +} + +void Layout::queueDrawExcept (int x, int y, int width, int height, + int ex, int ey, int ewidth, int eheight) { + + if (x == ex && y == ey && width == ewidth && height == eheight) + return; + + // queueDraw() the four rectangles within rectangle (x, y, width, height) + // around rectangle (ex, ey, ewidth, eheight). + // Some or all of these may be empty. + + // upper left corner of the intersection rectangle + int ix1 = misc::max (x, ex); + int iy1 = misc::max (y, ey); + // lower right corner of the intersection rectangle + int ix2 = misc::min (x + width, ex + ewidth); + int iy2 = misc::min (y + height, ey + eheight); + + queueDraw (x, y, width, iy1 - y); + queueDraw (x, iy2, width, y + height - iy2); + queueDraw (x, iy1, ix1 - x, iy2 - iy1); + queueDraw (ix2, iy1, x + width - ix2, iy2 - iy1); +} + +void Layout::queueResize (bool extremesChanged) +{ + DBG_OBJ_ENTER ("resize", 0, "queueResize", "%s", + extremesChanged ? "true" : "false"); + + if (resizeIdleId == -1) { + view->cancelQueueDraw (); + + resizeIdleId = platform->addIdle (&Layout::resizeIdle); + DBG_OBJ_MSGF ("resize", 1, "setting resizeIdleId = %d", resizeIdleId); + } + + emitter.emitResizeQueued (extremesChanged); + + DBG_OBJ_LEAVE (); +} + + +// Views + +bool Layout::buttonEvent (ButtonEventType type, View *view, int numPressed, + int x, int y, ButtonState state, int button) + +{ + EventButton event; + + moveToWidgetAtPoint (x, y, state); + + event.xCanvas = x; + event.yCanvas = y; + event.state = state; + event.button = button; + event.numPressed = numPressed; + + return processMouseEvent (&event, type); +} + +/** + * \brief This function is called by a view, to delegate a motion notify + * event. + * + * Arguments are similar to dw::core::Layout::buttonPress. + */ +bool Layout::motionNotify (View *view, int x, int y, ButtonState state) +{ + EventButton event; + + moveToWidgetAtPoint (x, y, state); + + event.xCanvas = x; + event.yCanvas = y; + event.state = state; + + return processMouseEvent (&event, MOTION_NOTIFY); +} + +/** + * \brief This function is called by a view, to delegate a enter notify event. + * + * Arguments are similar to dw::core::Layout::buttonPress. + */ +void Layout::enterNotify (View *view, int x, int y, ButtonState state) +{ + Widget *lastWidget; + EventCrossing event; + + lastWidget = widgetAtPoint; + moveToWidgetAtPoint (x, y, state); + + if (widgetAtPoint) { + event.state = state; + event.lastWidget = lastWidget; + event.currentWidget = widgetAtPoint; + widgetAtPoint->enterNotify (&event); + } +} + +/** + * \brief This function is called by a view, to delegate a leave notify event. + * + * Arguments are similar to dw::core::Layout::buttonPress. + */ +void Layout::leaveNotify (View *view, ButtonState state) +{ +#if 0 + Widget *lastWidget; + EventCrossing event; + + lastWidget = widgetAtPoint; + moveOutOfView (state); + + if (lastWidget) { + event.state = state; + event.lastWidget = lastWidget; + event.currentWidget = widgetAtPoint; + lastWidget->leaveNotify (&event); + } +#else + moveOutOfView (state); +#endif +} + +/* + * Return the widget at position (x, y). Return NULL, if there is no widget. + */ +Widget *Layout::getWidgetAtPoint (int x, int y) +{ + _MSG ("------------------------------------------------------------\n"); + _MSG ("widget at (%d, %d)\n", x, y); + if (topLevel && topLevel->wasAllocated ()) + return topLevel->getWidgetAtPoint (x, y, 0); + else + return NULL; +} + + +/* + * Emit the necessary crossing events, when the mouse pointer has moved to + * the given widget (by mouse or scrolling). + */ +void Layout::moveToWidget (Widget *newWidgetAtPoint, ButtonState state) +{ + Widget *ancestor, *w; + Widget **track; + int trackLen, i, i_a; + EventCrossing crossingEvent; + + _MSG("moveToWidget: wap=%p nwap=%p\n",widgetAtPoint,newWidgetAtPoint); + if (newWidgetAtPoint != widgetAtPoint) { + // The mouse pointer has been moved into another widget. + if (newWidgetAtPoint && widgetAtPoint) + ancestor = + newWidgetAtPoint->getNearestCommonAncestor (widgetAtPoint); + else if (newWidgetAtPoint) + ancestor = newWidgetAtPoint->getTopLevel (); + else + ancestor = widgetAtPoint->getTopLevel (); + + // Construct the track. + trackLen = 0; + if (widgetAtPoint) + // first part + for (w = widgetAtPoint; w != ancestor; w = w->getParent ()) + trackLen++; + trackLen++; // for the ancestor + if (newWidgetAtPoint) + // second part + for (w = newWidgetAtPoint; w != ancestor; w = w->getParent ()) + trackLen++; + + track = new Widget* [trackLen]; + i = 0; + if (widgetAtPoint) + /* first part */ + for (w = widgetAtPoint; w != ancestor; w = w->getParent ()) + track[i++] = w; + i_a = i; + track[i++] = ancestor; + if (newWidgetAtPoint) { + /* second part */ + i = trackLen - 1; + for (w = newWidgetAtPoint; w != ancestor; w = w->getParent ()) + track[i--] = w; + } +#if 0 + MSG("Track: %s[ ", widgetAtPoint ? "" : "nil "); + for (i = 0; i < trackLen; i++) + MSG("%s%p ", i == i_a ? ">" : "", track[i]); + MSG("] %s\n", newWidgetAtPoint ? "" : "nil"); +#endif + + /* Send events to the widgets on the track */ + for (i = 0; i < trackLen; i++) { + crossingEvent.state = state; + crossingEvent.currentWidget = widgetAtPoint; // ??? + crossingEvent.lastWidget = widgetAtPoint; // ??? + if (i < i_a) { + track[i]->leaveNotify (&crossingEvent); + } else if (i == i_a) { /* ancestor */ + /* Don't touch ancestor unless: + * - moving into/from NULL, + * - ancestor becomes the newWidgetAtPoint */ + if (i_a == trackLen-1 && !newWidgetAtPoint) + track[i]->leaveNotify (&crossingEvent); + else if ((i_a == 0 && !widgetAtPoint) || + (i_a == trackLen-1 && newWidgetAtPoint)) + track[i]->enterNotify (&crossingEvent); + } else { + track[i]->enterNotify (&crossingEvent); + } + } + + delete[] track; + + widgetAtPoint = newWidgetAtPoint; + updateCursor (); + } +} + +/** + * \brief Common processing of press, release and motion events. + * + * This function depends on that move_to_widget_at_point() + * has been called before. + */ +bool Layout::processMouseEvent (MousePositionEvent *event, + ButtonEventType type) +{ + Widget *widget; + + /* + * If the event is outside of the visible region of the canvas, treat it + * as occurring at the region's edge. Notably, this helps when selecting + * text. + */ + if (event->xCanvas < scrollX) + event->xCanvas = scrollX; + else { + int maxX = scrollX + viewportWidth - currVScrollbarThickness() - 1; + + if (event->xCanvas > maxX) + event->xCanvas = maxX; + } + if (event->yCanvas < scrollY) + event->yCanvas = scrollY; + else { + int maxY = misc::min(scrollY + viewportHeight -currHScrollbarThickness(), + canvasAscent + canvasDescent) - 1; + + if (event->yCanvas > maxY) + event->yCanvas = maxY; + } + + widget = getWidgetAtPoint(event->xCanvas, event->yCanvas); + + for (; widget; widget = widget->getParent ()) { + if (widget->isButtonSensitive ()) { + event->xWidget = event->xCanvas - widget->getAllocation()->x; + event->yWidget = event->yCanvas - widget->getAllocation()->y; + + switch (type) { + case BUTTON_PRESS: + return widget->buttonPress ((EventButton*)event); + + case BUTTON_RELEASE: + return widget->buttonRelease ((EventButton*)event); + + case MOTION_NOTIFY: + return widget->motionNotify ((EventMotion*)event); + + default: + misc::assertNotReached (); + } + } + } + if (type == BUTTON_PRESS) + return emitLinkPress (NULL, -1, -1, -1, -1, (EventButton*)event); + else if (type == BUTTON_RELEASE) + return emitLinkRelease(NULL, -1, -1, -1, -1, (EventButton*)event); + + return false; +} + +/* + * This function must be called by a view, when the user has manually changed + * the viewport position. It is *not* called, when the layout has requested the + * position change. + */ +void Layout::scrollPosChanged (View *view, int x, int y) +{ + if (x != scrollX || y != scrollY) { + scrollX = x; + scrollY = y; + + setAnchor (NULL); + updateAnchor (); + } +} + +/* + * This function must be called by a viewport view, when its viewport size has + * changed. It is *not* called, when the layout has requested the size change. + */ +void Layout::viewportSizeChanged (View *view, int width, int height) +{ + DBG_OBJ_ENTER ("resize", 0, "viewportSizeChanged", "%p, %d, %d", + view, width, height); + + /* If the width has become higher, we test again, whether the vertical + * scrollbar (so to speak) can be hidden again. */ + if (usesViewport && width > viewportWidth) { + canvasHeightGreater = false; + DBG_OBJ_SET_SYM ("canvasHeightGreater", + canvasHeightGreater ? "true" : "false"); + } + + /* if size changes, redraw this view. + * TODO: this is a resize call (redraw/resize code needs a review). */ + if (viewportWidth != width || viewportHeight != height) { + if (topLevel) + // similar to addWidget() + topLevel->queueResize (-1, false); + else + queueResize (false); + } + + viewportWidth = width; + viewportHeight = height; + + DBG_OBJ_SET_NUM ("viewportWidth", viewportWidth); + DBG_OBJ_SET_NUM ("viewportHeight", viewportHeight); + + containerSizeChanged (); + + DBG_OBJ_LEAVE (); +} + +void Layout::containerSizeChanged () +{ + DBG_OBJ_ENTER0 ("resize", 0, "containerSizeChanged"); + + if (topLevel) { + topLevel->containerSizeChanged (); + queueResize (true); + } + + DBG_OBJ_LEAVE (); +} + +} // namespace core +} // namespace dw diff --git a/dw/layout.hh b/dw/layout.hh new file mode 100644 index 0000000..dbcff99 --- /dev/null +++ b/dw/layout.hh @@ -0,0 +1,532 @@ +#ifndef __DW_LAYOUT_HH__ +#define __DW_LAYOUT_HH__ + +#ifndef __INCLUDED_FROM_DW_CORE_HH__ +# error Do not include this file directly, use "core.hh" instead. +#endif + +namespace dw { +namespace core { + +/** + * \brief The central class for managing and drawing a widget tree. + * + * \sa\ref dw-overview, \ref dw-layout-widgets, \ref dw-layout-views + */ +class Layout: public lout::object::Object +{ + friend class Widget; + +private: + class LayoutImgRenderer: public style::StyleImage::ExternalImgRenderer + { + Layout *layout; + + public: + LayoutImgRenderer (Layout *layout) { this->layout = layout; } + + bool readyToDraw (); + void getBgArea (int *x, int *y, int *width, int *height); + void getRefArea (int *xRef, int *yRef, int *widthRef, int *heightRef); + style::StyleImage *getBackgroundImage (); + style::BackgroundRepeat getBackgroundRepeat (); + style::BackgroundAttachment getBackgroundAttachment (); + style::Length getBackgroundPositionX (); + style::Length getBackgroundPositionY (); + void draw (int x, int y, int width, int height); + }; + + LayoutImgRenderer *layoutImgRenderer; + +public: + /** + * \brief Receiver interface different signals. + * + * May be extended. + */ + class Receiver: public lout::signal::Receiver + { + public: + virtual void resizeQueued (bool extremesChanged); + virtual void canvasSizeChanged (int width, int ascent, int descent); + }; + + class LinkReceiver: public lout::signal::Receiver + { + public: + /** + * \brief Called, when a link is entered, left, or the position has + * changed. + * + * When a link is entered, this method is called with the respective + * arguments. When a link is left, this method is called with all + * three arguments (\em link, \em x, \em y) set to -1. + * + * When coordinates are supported, a change of the coordinates also + * causes emitting this signal. + */ + virtual bool enter (Widget *widget, int link, int img, int x, int y); + + /** + * \brief Called, when the user has pressed the mouse button on a + * link (but not yet released). + * + * The causing event is passed as \em event. + */ + virtual bool press (Widget *widget, int link, int img, int x, int y, + EventButton *event); + + /** + * \brief Called, when the user has released the mouse button on a + * link. + * + * The causing event is passed as \em event. + */ + virtual bool release (Widget *widget, int link, int img, int x, int y, + EventButton *event); + + /** + * \brief Called, when the user has clicked on a link. + * + * For mouse interaction, this is equivalent to "press" and "release" + * on the same link. In this case, \em event contains the "release" + * event. + * + * + * When activating links via keyboard is supported, only a "clicked" + * signal will be emitted, and \em event will be NULL. + */ + virtual bool click (Widget *widget, int link, int img, int x, int y, + EventButton *event); + }; + + class LinkEmitter: public lout::signal::Emitter + { + private: + enum { ENTER, PRESS, RELEASE, CLICK }; + + protected: + bool emitToReceiver (lout::signal::Receiver *receiver, int signalNo, + int argc, lout::object::Object **argv); + + public: + inline void connectLink (LinkReceiver *receiver) { connect (receiver); } + + bool emitEnter (Widget *widget, int link, int img, int x, int y); + bool emitPress (Widget *widget, int link, int img, int x, int y, + EventButton *event); + bool emitRelease (Widget *widget, int link, int img, int x, int y, + EventButton *event); + bool emitClick (Widget *widget, int link, int img, int x, int y, + EventButton *event); + }; + + LinkEmitter linkEmitter; + +private: + class Emitter: public lout::signal::Emitter + { + private: + enum { RESIZE_QUEUED, CANVAS_SIZE_CHANGED }; + + protected: + bool emitToReceiver (lout::signal::Receiver *receiver, int signalNo, + int argc, lout::object::Object **argv); + + public: + inline void connectLayout (Receiver *receiver) { connect (receiver); } + + void emitResizeQueued (bool extremesChanged); + void emitCanvasSizeChanged (int width, int ascent, int descent); + }; + + Emitter emitter; + + class Anchor: public lout::object::Object + { + public: + char *name; + Widget *widget; + int y; + + ~Anchor (); + }; + + class QueueResizeItem: public lout::object::Object + { + public: + Widget *widget; + int ref; + bool extremesChanged, fast; + + inline QueueResizeItem (Widget *widget, int ref, bool extremesChanged, + bool fast) + { + this->widget = widget; + this->ref = ref; + this->extremesChanged = extremesChanged; + this->fast = fast; + } + }; + + /** + * \brief An abstract scrolling target. The values are first + * calculated when they are needed in scrollIdle(). + * + * (Note: perhaps a subclass should be uses for anchors.) + */ + class ScrollTarget + { + public: + virtual ~ScrollTarget (); + + virtual HPosition getHPos () = 0; + virtual VPosition getVPos () = 0; + virtual int getX () = 0; + virtual int getY () = 0; + virtual int getWidth () = 0; + virtual int getHeight () = 0; + }; + + class ScrollTargetBase: public ScrollTarget + { + HPosition hPos; + VPosition vPos; + + public: + ScrollTargetBase (HPosition hPos, VPosition vPos); + + HPosition getHPos (); + VPosition getVPos (); + }; + + /** + * \brief Scrolling target with concrete values. + */ + class ScrollTargetFixed: public ScrollTargetBase + { + int x, y, width, height; + + public: + ScrollTargetFixed (HPosition hPos, VPosition vPos, + int x, int y, int width, int height); + + int getX (); + int getY (); + int getWidth (); + int getHeight (); + }; + + /** + * \brief Scrolling target for a widget allocation. + * + * If the widget is allocated between scrollToWidget() and + * scrollIdle(), this is taken into account. + */ + class ScrollTargetWidget: public ScrollTargetBase + { + Widget *widget; + + public: + ScrollTargetWidget (HPosition hPos, VPosition vPos, Widget *widget); + + int getX (); + int getY (); + int getWidth (); + int getHeight (); + }; + + Platform *platform; + View *view; + Widget *topLevel, *widgetAtPoint; + lout::container::typed::Stack *queueQueueResizeList; + lout::container::typed::Vector *queueResizeList; + + /* The state, which must be projected into the view. */ + style::Color *bgColor; + style::StyleImage *bgImage; + style::BackgroundRepeat bgRepeat; + style::BackgroundAttachment bgAttachment; + style::Length bgPositionX, bgPositionY; + + style::Cursor cursor; + int canvasWidth, canvasAscent, canvasDescent; + + bool usesViewport, drawAfterScrollReq; + int scrollX, scrollY, viewportWidth, viewportHeight; + bool canvasHeightGreater; + int hScrollbarThickness, vScrollbarThickness; + + ScrollTarget *scrollTarget; + + char *requestedAnchor; + int scrollIdleId, resizeIdleId; + bool scrollIdleNotInterrupted; + + /* Anchors of the widget tree */ + lout::container::typed::HashTable + *anchorsTable; + + SelectionState selectionState; + FindtextState findtextState; + + enum ButtonEventType { BUTTON_PRESS, BUTTON_RELEASE, MOTION_NOTIFY }; + + void detachWidget (Widget *widget); + + Widget *getWidgetAtPoint (int x, int y); + void moveToWidget (Widget *newWidgetAtPoint, ButtonState state); + + /** + * \brief Emit the necessary crossing events, when the mouse pointer has + * moved to position (\em x, \em ); + */ + void moveToWidgetAtPoint (int x, int y, ButtonState state) + { moveToWidget (getWidgetAtPoint (x, y), state); } + + /** + * \brief Emit the necessary crossing events, when the mouse pointer + * has moved out of the view. + */ + void moveOutOfView (ButtonState state) { moveToWidget (NULL, state); } + + bool processMouseEvent (MousePositionEvent *event, ButtonEventType type); + bool buttonEvent (ButtonEventType type, View *view, + int numPressed, int x, int y, ButtonState state, + int button); + void resizeIdle (); + void setSizeHints (); + void draw (View *view, Rectangle *area); + + void scrollTo0(ScrollTarget *scrollTarget, bool scrollingInterrupted); + void scrollIdle (); + void adjustScrollPos (); + static bool calcScrollInto (int targetValue, int requestedSize, + int *value, int viewportSize); + int currHScrollbarThickness(); + int currVScrollbarThickness(); + + void updateAnchor (); + + /* Widget */ + + char *addAnchor (Widget *widget, const char* name); + char *addAnchor (Widget *widget, const char* name, int y); + void changeAnchor (Widget *widget, char* name, int y); + void removeAnchor (Widget *widget, char* name); + void setCursor (style::Cursor cursor); + void updateCursor (); + void queueDraw (int x, int y, int width, int height); + void queueDrawExcept (int x, int y, int width, int height, + int ex, int ey, int ewidth, int eheight); + void queueResize (bool extremesChanged); + void removeWidget (); + + /* For tests regarding the respective Layout and (mostly) Widget + methods. Accessed by respective methods (enter..., leave..., + ...Entered) defined here and in Widget. */ + + int resizeIdleCounter, queueResizeCounter, sizeAllocateCounter, + sizeRequestCounter, getExtremesCounter; + + void enterResizeIdle () { resizeIdleCounter++; } + void leaveResizeIdle () { resizeIdleCounter--; } + +public: + Layout (Platform *platform); + ~Layout (); + + inline void connectLink (LinkReceiver *receiver) + { linkEmitter.connectLink (receiver); } + + inline bool emitLinkEnter (Widget *w, int link, int img, int x, int y) + { return linkEmitter.emitEnter (w, link, img, x, y); } + + inline bool emitLinkPress (Widget *w, int link, int img, + int x, int y, EventButton *event) + { return linkEmitter.emitPress (w, link, img, x, y, event); } + + inline bool emitLinkRelease (Widget *w, int link, int img, + int x, int y, EventButton *event) + { return linkEmitter.emitRelease (w, link, img, x, y, event); } + + inline bool emitLinkClick (Widget *w, int link, int img, + int x, int y, EventButton *event) + { return linkEmitter.emitClick (w, link, img, x, y, event); } + + lout::misc::ZoneAllocator *textZone; + + void addWidget (Widget *widget); + void setWidget (Widget *widget); + + void attachView (View *view); + void detachView (View *view); + + inline bool getUsesViewport () { return usesViewport; } + inline int getWidthViewport () { return viewportWidth; } + inline int getHeightViewport () { return viewportHeight; } + inline int getScrollPosX () { return scrollX; } + inline int getScrollPosY () { return scrollY; } + + /* public */ + + void scrollTo (HPosition hpos, VPosition vpos, + int x, int y, int width, int height); + void scrollToWidget (HPosition hpos, VPosition vpos, Widget *widget); + void scroll (ScrollCommand cmd); + void setAnchor (const char *anchor); + + /* View */ + + inline void expose (View *view, Rectangle *area) { + DBG_OBJ_ENTER ("draw", 0, "expose", "%d, %d, %d * %d", + area->x, area->y, area->width, area->height); + draw (view, area); + DBG_OBJ_LEAVE (); + } + + /** + * \brief This function is called by a view, to delegate a button press + * event. + * + * \em numPressed is 1 for simple presses, 2 for double presses etc. (more + * that 2 is never needed), \em x and \em y the world coordinates, and + * \em button the number of the button pressed. + */ + inline bool buttonPress (View *view, int numPressed, int x, int y, + ButtonState state, int button) + { + return buttonEvent (BUTTON_PRESS, view, numPressed, x, y, state, button); + } + + void containerSizeChanged (); + + /** + * \brief This function is called by a view, to delegate a button press + * event. + * + * Arguments are similar to dw::core::Layout::buttonPress. + */ + inline bool buttonRelease (View *view, int numPressed, int x, int y, + ButtonState state, int button) + { + return buttonEvent (BUTTON_RELEASE, view, numPressed, x, y, state, + button); + } + + bool motionNotify (View *view, int x, int y, ButtonState state); + void enterNotify (View *view, int x, int y, ButtonState state); + void leaveNotify (View *view, ButtonState state); + + void scrollPosChanged (View *view, int x, int y); + void viewportSizeChanged (View *view, int width, int height); + + inline Platform *getPlatform () + { + return platform; + } + + /* delegated */ + + inline int textWidth (style::Font *font, const char *text, int len) + { + return platform->textWidth (font, text, len); + } + + inline char *textToUpper (const char *text, int len) + { + return platform->textToUpper (text, len); + } + + inline char *textToLower (const char *text, int len) + { + return platform->textToLower (text, len); + } + + inline int nextGlyph (const char *text, int idx) + { + return platform->nextGlyph (text, idx); + } + + inline int prevGlyph (const char *text, int idx) + { + return platform->prevGlyph (text, idx); + } + + inline float dpiX () + { + return platform->dpiX (); + } + + inline float dpiY () + { + return platform->dpiY (); + } + + inline style::Font *createFont (style::FontAttrs *attrs, bool tryEverything) + { + return platform->createFont (attrs, tryEverything); + } + + inline bool fontExists (const char *name) + { + return platform->fontExists (name); + } + + inline style::Color *createColor (int color) + { + return platform->createColor (color); + } + + inline style::Tooltip *createTooltip (const char *text) + { + return platform->createTooltip (text); + } + + inline void cancelTooltip () + { + return platform->cancelTooltip (); + } + + inline Imgbuf *createImgbuf (Imgbuf::Type type, int width, int height, + double gamma) + { + return platform->createImgbuf (type, width, height, gamma); + } + + inline void copySelection(const char *text) + { + platform->copySelection(text); + } + + inline ui::ResourceFactory *getResourceFactory () + { + return platform->getResourceFactory (); + } + + inline void connect (Receiver *receiver) { + emitter.connectLayout (receiver); } + + /** \brief See dw::core::FindtextState::search. */ + inline FindtextState::Result search (const char *str, bool caseSens, + int backwards) + { return findtextState.search (str, caseSens, backwards); } + + /** \brief See dw::core::FindtextState::resetSearch. */ + inline void resetSearch () { findtextState.resetSearch (); } + + void setBgColor (style::Color *color); + void setBgImage (style::StyleImage *bgImage, + style::BackgroundRepeat bgRepeat, + style::BackgroundAttachment bgAttachment, + style::Length bgPositionX, style::Length bgPositionY); + + inline style::Color* getBgColor () { return bgColor; } + inline style::StyleImage* getBgImage () { return bgImage; } +}; + +} // namespace core +} // namespace dw + +#endif // __DW_LAYOUT_HH__ + diff --git a/dw/platform.hh b/dw/platform.hh new file mode 100644 index 0000000..227cda3 --- /dev/null +++ b/dw/platform.hh @@ -0,0 +1,171 @@ +#ifndef __DW_PLATFORM_HH__ +#define __DW_PLATFORM_HH__ + +#ifndef __INCLUDED_FROM_DW_CORE_HH__ +# error Do not include this file directly, use "core.hh" instead. +#endif + +namespace dw { +namespace core { + +/** + * \brief An interface to encapsulate some platform dependencies. + * + * \sa\ref dw-overview + */ +class Platform: public lout::object::Object +{ +public: + /* + * ----------------------------------- + * General + * ----------------------------------- + */ + + /** + * \brief This methods notifies the platform, that it has been attached to + * a layout. + */ + virtual void setLayout (Layout *layout) = 0; + + /* + * ------------------------- + * Operations on views + * ------------------------- + */ + + /** + * \brief This methods notifies the platform, that a view has been attached + * to the related layout. + */ + virtual void attachView (View *view) = 0; + + /** + * \brief This methods notifies the platform, that a view has been detached + * from the related layout. + */ + virtual void detachView (View *view) = 0; + + /* + * ----------------------------------- + * Platform dependent properties + * ----------------------------------- + */ + + /** + * \brief Return the width of a text, with a given length and font. + */ + virtual int textWidth (style::Font *font, const char *text, int len) = 0; + + /** + * \brief Return the string resulting from transforming text to uppercase. + */ + virtual char *textToUpper (const char *text, int len) = 0; + + /** + * \brief Return the string resulting from transforming text to lowercase. + */ + virtual char *textToLower (const char *text, int len) = 0; + + /** + * \brief Return the index of the next glyph in string text. + */ + virtual int nextGlyph (const char *text, int idx) = 0; + + /** + * \brief Return the index of the previous glyph in string text. + */ + virtual int prevGlyph (const char *text, int idx) = 0; + + /** + * \brief Return screen resolution in x-direction. + */ + virtual float dpiX () = 0; + + /** + * \brief Return screen resolution in y-direction. + */ + virtual float dpiY () = 0; + + /* + * --------------------------------------------------------- + * These are to encapsulate some platform dependencies + * --------------------------------------------------------- + */ + + /** + * \brief Add an idle function. + * + * An idle function is called once, when no other + * tasks are to be done (e.g. there are no events to process), and then + * removed from the queue. The return value is a number, which can be + * used in removeIdle below. + */ + virtual int addIdle (void (Layout::*func) ()) = 0; + + /** + * \brief Remove an idle function, which has not been processed yet. + */ + virtual void removeIdle (int idleId) = 0; + + /* + * --------------------- + * Style Resources + * --------------------- + */ + + /** + * \brief Create a (platform dependent) font. + * + * Typically, within a platform, a sub class of dw::core::style::Font + * is defined, which holds more platform dependent data. + * + * Also, this method must fill the attributes "font" (when needed), + * "ascent", "descent", "spaceSidth" and "xHeight". If "tryEverything" + * is true, several methods should be used to use another font, when + * the requested font is not available. Passing false is typically done, + * if the caller wants to test different variations. + */ + virtual style::Font *createFont (style::FontAttrs *attrs, + bool tryEverything) = 0; + + virtual bool fontExists (const char *name) = 0; + + /** + * \brief Create a color resource for a given 0xrrggbb value. + */ + virtual style::Color *createColor (int color) = 0; + + /** + * \brief Create a tooltip + */ + virtual style::Tooltip *createTooltip (const char *text) = 0; + + /** + * \brief Cancel a tooltip (either shown or requested) + */ + virtual void cancelTooltip () = 0; + + /** + * \brief Create a (platform speficic) image buffer. + * + * "gamma" is the value by which the image data is gamma-encoded. + */ + virtual Imgbuf *createImgbuf (Imgbuf::Type type, int width, int height, + double gamma) = 0; + + /** + * \brief Copy selected text (0-terminated). + */ + virtual void copySelection(const char *text) = 0; + + /** + * ... + */ + virtual ui::ResourceFactory *getResourceFactory () = 0; +}; + +} // namespace core +} // namespace dw + +#endif // __DW_PLATFORM_HH__ diff --git a/dw/preview.xbm b/dw/preview.xbm new file mode 100644 index 0000000..85ea829 --- /dev/null +++ b/dw/preview.xbm @@ -0,0 +1,5 @@ +#define preview_width 11 +#define preview_height 11 +static unsigned char preview_bits[] = { + 0x20, 0x00, 0x70, 0x00, 0x20, 0x00, 0x20, 0x00, 0x22, 0x02, 0xff, 0x07, + 0x22, 0x02, 0x20, 0x00, 0x20, 0x00, 0x70, 0x00, 0x20, 0x00}; diff --git a/dw/selection.cc b/dw/selection.cc new file mode 100644 index 0000000..f5c7bda --- /dev/null +++ b/dw/selection.cc @@ -0,0 +1,494 @@ +/* + * RTFL (originally part of dillo) + * + * Copyright 2005-2007 Sebastian Geerken + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version; with the following exception: + * + * The copyright holders of RTFL give you permission to link this file + * statically or dynamically against all versions of the graphviz + * library, which are published by AT&T Corp. under one of the following + * licenses: + * + * - Common Public License version 1.0 as published by International + * Business Machines Corporation (IBM), or + * - Eclipse Public License version 1.0 as published by the Eclipse + * Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + + + +#include "core.hh" +#include "../lout/debug.hh" + +#include + +using namespace lout; + +/* + * strndup() is a GNU extension. + */ +extern "C" char *strndup(const char *s, size_t size) +{ + char *r = (char *) malloc (size + 1); + + if (r) { + strncpy (r, s, size); + r[size] = 0; + } + + return r; +} + +namespace dw { +namespace core { + +SelectionState::SelectionState () +{ + DBG_OBJ_CREATE ("dw::core::SelectionState"); + + layout = NULL; + + selectionState = NONE; + from = NULL; + to = NULL; + + linkState = LINK_NONE; + link = NULL; +} + +SelectionState::~SelectionState () +{ + reset (); + DBG_OBJ_DELETE (); +} + +void SelectionState::reset () +{ + resetSelection (); + resetLink (); +} + +void SelectionState::resetSelection () +{ + if (from) + delete from; + from = NULL; + if (to) + delete to; + to = NULL; + selectionState = NONE; +} + + +void SelectionState::resetLink () +{ + if (link) + delete link; + link = NULL; + linkState = LINK_NONE; +} + +bool SelectionState::buttonPress (Iterator *it, int charPos, int linkNo, + EventButton *event) +{ + Widget *itWidget = it->getWidget (); + bool ret = false; + + if (!event) return ret; + + if (event->button == 3) { + // menu popup + layout->emitLinkPress (itWidget, linkNo, -1, -1, -1, event); + ret = true; + } else if (linkNo != -1) { + // link handling + (void) layout->emitLinkPress (itWidget, linkNo, -1, -1, -1, event); + resetLink (); + linkState = LINK_PRESSED; + linkButton = event->button; + DeepIterator *newLink = new DeepIterator (it); + if (newLink->isEmpty ()) { + delete newLink; + resetLink (); + } else { + link = newLink; + // It may be that the user has pressed on something activatable + // (linkNo != -1), but there is no contents, e.g. with images + // without ALTernative text. + if (link) { + linkChar = correctCharPos (link, charPos); + linkNumber = linkNo; + } + } + // We do not return the value of the signal method, + // but we do actually process this event. + ret = true; + } else if (event->button == 1) { + // normal selection handling + highlight (false, 0); + resetSelection (); + + selectionState = SELECTING; + DeepIterator *newFrom = new DeepIterator (it); + if (newFrom->isEmpty ()) { + delete newFrom; + resetSelection (); + } else { + from = newFrom; + fromChar = correctCharPos (from, charPos); + to = from->cloneDeepIterator (); + toChar = correctCharPos (to, charPos); + } + ret = true; + } + + return ret; +} + +bool SelectionState::buttonRelease (Iterator *it, int charPos, int linkNo, + EventButton *event) +{ + Widget *itWidget = it->getWidget (); + bool ret = false; + + if (linkState == LINK_PRESSED && event && event->button == linkButton) { + // link handling + ret = true; + if (linkNo != -1) + (void) layout->emitLinkRelease (itWidget, linkNo, -1, -1, -1, event); + + // The link where the user clicked the mouse button? + if (linkNo == linkNumber) { + resetLink (); + (void) layout->emitLinkClick (itWidget, linkNo, -1, -1, -1, event); + } else { + if (event->button == 1) + // Reset links and switch to selection mode. The selection + // state will be set to SELECTING, which is handled some lines + // below. + switchLinkToSelection (it, charPos); + } + } + + if (selectionState == SELECTING && event && event->button == 1) { + // normal selection + ret = true; + adjustSelection (it, charPos); + + if (from->compareTo (to) == 0 && fromChar == toChar) + // nothing selected + resetSelection (); + else { + copy (); + selectionState = SELECTED; + } + } + + return ret; +} + +bool SelectionState::buttonMotion (Iterator *it, int charPos, int linkNo, + EventMotion *event) +{ + if (linkState == LINK_PRESSED) { + //link handling + if (linkNo != linkNumber) + // No longer the link where the user clicked the mouse button. + // Reset links and switch to selection mode. + switchLinkToSelection (it, charPos); + // Still in link: do nothing. + } else if (selectionState == SELECTING) { + // selection + adjustSelection (it, charPos); + } + + return true; +} + +/** + * \brief General form of dw::core::SelectionState::buttonPress, + * dw::core::SelectionState::buttonRelease and + * dw::core::SelectionState::buttonMotion. + */ +bool SelectionState::handleEvent (EventType eventType, Iterator *it, + int charPos, int linkNo, + MousePositionEvent *event) +{ + switch (eventType) { + case BUTTON_PRESS: + return buttonPress (it, charPos, linkNo, (EventButton*)event); + + case BUTTON_RELEASE: + return buttonRelease (it, charPos, linkNo, (EventButton*)event); + + case BUTTON_MOTION: + return buttonMotion (it, charPos, linkNo, (EventMotion*)event); + + + default: + misc::assertNotReached (); + } + + return false; +} + + +/** + * \brief This method is called when the user decides not to activate a link, + * but instead select text. + */ +void SelectionState::switchLinkToSelection (Iterator *it, int charPos) +{ + // It may be that selection->link is NULL, see a_Selection_button_press. + if (link) { + // Reset old selection. + highlight (false, 0); + resetSelection (); + + // Transfer link state into selection state. + from = link->cloneDeepIterator (); + fromChar = linkChar; + to = from->createVariant (it); + toChar = correctCharPos (to, charPos); + selectionState = SELECTING; + + // Reset link status. + resetLink (); + + highlight (true, 0); + + } else { + // A link was pressed on, but there is nothing to select. Reset + // everything. + resetSelection (); + resetLink (); + } +} + +/** + * \brief This method is used by core::dw::SelectionState::buttonMotion and + * core::dw::SelectionState::buttonRelease, and changes the second limit of + * the already existing selection region. + */ +void SelectionState::adjustSelection (Iterator *it, int charPos) +{ + DeepIterator *newTo; + int newToChar, cmpOld, cmpNew, cmpDiff, len; + bool bruteHighlighting = false; + + newTo = to->createVariant (it); + newToChar = correctCharPos (newTo, charPos); + + cmpOld = to->compareTo (from); + cmpNew = newTo->compareTo (from); + + if (cmpOld == 0 || cmpNew == 0) { + // Either before, or now, the limits differ only by the character + // position. + bruteHighlighting = true; + } else if (cmpOld * cmpNew < 0) { + // The selection order has changed, i.e. the user moved the selection + // end again beyond the position he started. + bruteHighlighting = true; + } else { + // Here, cmpOld and cmpNew are equivalent and != 0. + cmpDiff = newTo->compareTo (to); + + if (cmpOld * cmpDiff > 0) { + // The user has enlarged the selection. Highlight the difference. + if (cmpDiff < 0) { + len = correctCharPos (to, END_OF_WORD); + highlight0 (true, newTo, newToChar, to, len + 1, 1); + } else { + highlight0 (true, to, 0, newTo, newToChar, -1); + } + } else { + if (cmpOld * cmpDiff < 0) { + // The user has reduced the selection. Unhighlight the difference. + highlight0 (false, to, 0, newTo, 0, cmpDiff); + } + + // Otherwise, the user has changed the position only slightly. + // In both cases, re-highlight the new position. + if (cmpOld < 0) { + len = correctCharPos (newTo, END_OF_WORD); + newTo->highlight (newToChar, len + 1, HIGHLIGHT_SELECTION); + } else + newTo->highlight (0, newToChar, HIGHLIGHT_SELECTION); + } + } + + if (bruteHighlighting) + highlight (false, 0); + + delete to; + to = newTo; + toChar = newToChar; + + if (bruteHighlighting) + highlight (true, 0); +} + +/** + * \brief This method deals especially with the case that a widget passes + * dw::core::SelectionState::END_OF_WORD. + */ +int SelectionState::correctCharPos (DeepIterator *it, int charPos) +{ + Iterator *top = it->getTopIterator (); + int len; + + if (top->getContent()->type == Content::TEXT) + len = strlen(top->getContent()->text); + else + len = 1; + + return misc::min(charPos, len); +} + +void SelectionState::highlight0 (bool fl, DeepIterator *from, int fromChar, + DeepIterator *to, int toChar, int dir) +{ + DeepIterator *a, *b, *i; + int cmp, aChar, bChar; + bool start; + + if (from && to) { + cmp = from->compareTo (to); + if (cmp == 0) { + if (fl) { + if (fromChar < toChar) + from->highlight (fromChar, toChar, HIGHLIGHT_SELECTION); + else + from->highlight (toChar, fromChar, HIGHLIGHT_SELECTION); + } else + from->unhighlight (0, HIGHLIGHT_SELECTION); + return; + } + + if (cmp < 0) { + a = from; + aChar = fromChar; + b = to; + bChar = toChar; + } else { + a = to; + aChar = toChar; + b = from; + bChar = fromChar; + } + + for (i = a->cloneDeepIterator (), start = true; + (cmp = i->compareTo (b)) <= 0; + i->next (), start = false) { + if (i->getContent()->type == Content::TEXT) { + if (fl) { + if (start) { + i->highlight (aChar, strlen (i->getContent()->text) + 1, + HIGHLIGHT_SELECTION); + } else if (cmp == 0) { + // the end + i->highlight (0, bChar, HIGHLIGHT_SELECTION); + } else { + i->highlight (0, strlen (i->getContent()->text) + 1, + HIGHLIGHT_SELECTION); + } + } else { + i->unhighlight (dir, HIGHLIGHT_SELECTION); + } + } + } + delete i; + } +} + +void SelectionState::copy() +{ + if (from && to) { + Iterator *si; + DeepIterator *a, *b, *i; + int cmp, aChar, bChar; + bool start; + char *tmp; + misc::StringBuffer strbuf; + + cmp = from->compareTo (to); + if (cmp == 0) { + if (from->getContent()->type == Content::TEXT) { + si = from->getTopIterator (); + if (fromChar < toChar) + tmp = strndup (si->getContent()->text + fromChar, + toChar - fromChar); + else + tmp = strndup (si->getContent()->text + toChar, + fromChar - toChar); + strbuf.appendNoCopy (tmp); + } + } else { + if (cmp < 0) { + a = from; + aChar = fromChar; + b = to; + bChar = toChar; + } else { + a = to; + aChar = toChar; + b = from; + bChar = fromChar; + } + + for (i = a->cloneDeepIterator (), start = true; + (cmp = i->compareTo (b)) <= 0; + i->next (), start = false) { + si = i->getTopIterator (); + switch (si->getContent()->type) { + case Content::TEXT: + if (start) { + tmp = strndup (si->getContent()->text + aChar, + strlen (i->getContent()->text) - aChar); + strbuf.appendNoCopy (tmp); + } else if (cmp == 0) { + // the end + tmp = strndup (si->getContent()->text, bChar); + strbuf.appendNoCopy (tmp); + } else + strbuf.append (si->getContent()->text); + + if (si->getContent()->space && cmp != 0) + strbuf.append (" "); + + break; + + case Content::BREAK: + if (si->getContent()->breakSpace > 0) + strbuf.append ("\n\n"); + else + strbuf.append ("\n"); + break; + default: + // Make pedantic compilers happy. Especially + // DW_CONTENT_WIDGET is never returned by a DwDeepIterator. + break; + } + } + delete i; + } + + layout->copySelection(strbuf.getChars()); + } +} + +} // namespace core +} // namespace dw diff --git a/dw/selection.hh b/dw/selection.hh new file mode 100644 index 0000000..ef9df0e --- /dev/null +++ b/dw/selection.hh @@ -0,0 +1,241 @@ +#ifndef __DW_SELECTION_H__ +#define __DW_SELECTION_H__ + +#ifndef __INCLUDED_FROM_DW_CORE_HH__ +# error Do not include this file directly, use "core.hh" instead. +#endif + +namespace dw { +namespace core { + +/** + * \brief This class handles selections, as well as activation of links, + * which is closely related. + * + *

General Overview

+ * + * 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: + * + *
    + *
  • dw::core::SelectionState::buttonPress for button press events, + *
  • dw::core::SelectionState::buttonRelease for button release events, and + *
  • dw::core::SelectionState::buttonMotion for motion events (with pressed + * mouse button). + *
+ * + * 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: + * + * + *
dw::core::Iterator *it the iterator pointing on the item + * under the mouse pointer; this + * iterator \em must be created with + * dw::core::Content::SELECTION_CONTENT + * as mask + *
int charPos the exact (character) position + * within the iterator, + *
int linkNo if this item is associated with a + * link, its number (see + * dw::core::Layout::LinkReceiver), + * otherwise -1 + *
dw::core::EventButton *event the event itself; only the button + * is used + *
+ * + * 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 + * at the end of an image (DwImage), it constructs a simple iterator + * pointing on this image widget. In a simple iterator, that fact that + * the pointer is at the end, would be represented by \em charPos == 1. But + * when transferring this simple iterator into an deep iterator, this + * simple iterator is discarded and instead the stack has an iterator + * pointing to text at the top. As a result, only the first letter of the + * ALT text would be copied. + * + * To avoid this problem, widgets should in this case pass + * dw::core::SelectionState::END_OF_WORD as \em charPos, which is then + * automatically reduced to the actual length of the deep(!) iterator. + * + * The return value is the same as in DwWidget event handling methods. + * I.e., in most cases, they should simply return it. The events + * dw::core::Layout::LinkReceiver::press, + * dw::core::Layout::LinkReceiver::release and + * dw::core::Layout::LinkReceiver::click (but not + * dw::core::Layout::LinkReceiver::enter) are emitted by these methods, so + * that widgets which let dw::core::SelectionState handle links, should only + * emit dw::core::Layout::LinkReceiver::enter for themselves. + * + *

Selection State

+ * + * 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: + * + *
    + *
  1. 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.) + * + *
  2. The selection should stay as long as possible, i.e., the old + * selection is only cleared when a new selection is started. + *
+ * + * The latter leads to a model with two states: the selection state and + * the link handling state. + * + * The general selection works, for events not pointing on links, like + * this (numbers in parantheses after the event denote the button, "n" + * means arbitrary button): + * + * \dot + * digraph G { + * node [shape=ellipse, fontname=Helvetica, fontsize=10]; + * edge [arrowhead="open", labelfontname=Helvetica, labelfontsize=10, + * color="#404040", labelfontcolor="#000080", + * fontname=Helvetica, fontsize=10, fontcolor="#000080"]; + * fontname=Helvetica; fontsize=10; + * + * NONE; + * SELECTING; + * q [label="Anything selected?", shape=plaintext]; + * SELECTED; + * + * NONE -> SELECTING [label="press(1)\non non-link"]; + * SELECTING -> SELECTING [label="motion(1)"]; + * SELECTING -> q [label="release(1)"]; + * q -> SELECTED [label="yes"]; + * q -> NONE [label="no"]; + * SELECTED -> SELECTING [label="press(1)"]; + * + * } + * \enddot + * + * The selected region is represented by two instances of + * dw::core::DeepIterator. + * + * Links are handled by a different state machine: + * + * \dot + * digraph G { + * node [shape=ellipse, fontname=Helvetica, fontsize=10]; + * edge [arrowhead="open", labelfontname=Helvetica, labelfontsize=10, + * color="#404040", labelfontcolor="#000080", + * fontname=Helvetica, fontsize=10, fontcolor="#000080"]; + * fontname=Helvetica; fontsize=10; + * + * LINK_NONE; + * LINK_PRESSED; + * click [label="Emit \"click\" signal.", shape=record]; + * q11 [label="Still the same link?", shape=plaintext]; + * q21 [label="Still the same link?", shape=plaintext]; + * q22 [label="n == 1?", shape=plaintext]; + * SELECTED [label="Switch selection\nto SELECTED", shape=record]; + * q12 [label="n == 1?", shape=plaintext]; + * SELECTING [label="Switch selection\nto SELECTING", shape=record]; + * + * LINK_NONE -> LINK_PRESSED [label="press(n)\non link"]; + * LINK_PRESSED -> q11 [label="motion(n)"]; + * q11 -> LINK_PRESSED [label="yes"]; + * q11 -> q12 [label="no"]; + * q12 -> SELECTING [label="yes"]; + * SELECTING -> LINK_NONE; + * q12 -> LINK_NONE [label="no"]; + * LINK_PRESSED -> q21 [label="release(n)"]; + * q21 -> click [label="yes"]; + * click -> LINK_NONE; + * q21 -> q22 [label="no"]; + * q22 -> SELECTED [label="yes"]; + * SELECTED -> LINK_NONE; + * q22 -> LINK_NONE [label="no"]; + * } + * \enddot + * + * Switching selection simply means that the selection state will + * eventually be SELECTED/SELECTING, with the original and the current + * position making up the selection region. This happens for button 1, + * events with buttons other than 1 do not affect selection at all. + * + * + * \todo dw::core::SelectionState::buttonMotion currently always assumes + * that button 1 has been pressed (since otherwise it would not do + * anything). This should be made a bit cleaner. + * + * \todo The selection should be cleared, when the user selects something + * somewhere else (perhaps switched into "non-active" mode, as e.g. Gtk+ + * does). + * + */ +class SelectionState +{ +public: + enum { END_OF_WORD = 1 << 30 }; + +private: + Layout *layout; + + // selection + enum { + NONE, + SELECTING, + SELECTED + } selectionState; + + DeepIterator *from, *to; + int fromChar, toChar; + + // link handling + enum { + LINK_NONE, + LINK_PRESSED + } linkState; + + int linkButton; + DeepIterator *link; + int linkChar, linkNumber; + + void resetSelection (); + void resetLink (); + void switchLinkToSelection (Iterator *it, int charPos); + void adjustSelection (Iterator *it, int charPos); + static int correctCharPos (DeepIterator *it, int charPos); + + void highlight (bool fl, int dir) + { highlight0 (fl, from, fromChar, to, toChar, dir); } + + void highlight0 (bool fl, DeepIterator *from, int fromChar, + DeepIterator *to, int toChar, int dir); + void copy (); + +public: + enum EventType { BUTTON_PRESS, BUTTON_RELEASE, BUTTON_MOTION }; + + SelectionState (); + ~SelectionState (); + + inline void setLayout (Layout *layout) { this->layout = layout; } + void reset (); + bool buttonPress (Iterator *it, int charPos, int linkNo, + EventButton *event); + bool buttonRelease (Iterator *it, int charPos, int linkNo, + EventButton *event); + bool buttonMotion (Iterator *it, int charPos, int linkNo, + EventMotion *event); + + bool handleEvent (EventType eventType, Iterator *it, int charPos, + int linkNo, MousePositionEvent *event); +}; + +} // namespace core +} // namespace dw + +#endif // __DW_SELECTION_H__ diff --git a/dw/style.cc b/dw/style.cc new file mode 100644 index 0000000..aaeb959 --- /dev/null +++ b/dw/style.cc @@ -0,0 +1,1468 @@ +/* + * RTFL (originally part of dillo) + * + * Copyright 2005-2007 Sebastian Geerken + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version; with the following exception: + * + * The copyright holders of RTFL give you permission to link this file + * statically or dynamically against all versions of the graphviz + * library, which are published by AT&T Corp. under one of the following + * licenses: + * + * - Common Public License version 1.0 as published by International + * Business Machines Corporation (IBM), or + * - Eclipse Public License version 1.0 as published by the Eclipse + * Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include + +#include "core.hh" +#include "../lout/msg.h" + +using namespace lout; + +namespace dw { +namespace core { +namespace style { + +const bool drawBackgroundLineByLine = false; + +const int MIN_BG_IMG_W = 10; +const int MIN_BG_IMG_H = 10; +const int OPT_BG_IMG_W = 50; +const int OPT_BG_IMG_H = 50; + +static void calcBackgroundRelatedValues (StyleImage *backgroundImage, + BackgroundRepeat backgroundRepeat, + BackgroundAttachment + backgroundAttachment, + Length backgroundPositionX, + Length backgroundPositionY, + int xDraw, int yDraw, int widthDraw, + int heightDraw, int xRef, int yRef, + int widthRef, int heightRef, + bool *repeatX, bool *repeatY, + int *origX, int *origY, + int *tileX1, int *tileX2, int *tileY1, + int *tileY2, bool *doDraw); + +void StyleAttrs::initValues () +{ + x_link = -1; + x_lang[0] = x_lang[1] = 0; + x_img = -1; + x_tooltip = NULL; + textDecoration = TEXT_DECORATION_NONE; + textAlign = TEXT_ALIGN_LEFT; + textAlignChar = '.'; + textTransform = TEXT_TRANSFORM_NONE; + listStylePosition = LIST_STYLE_POSITION_OUTSIDE; + listStyleType = LIST_STYLE_TYPE_DISC; + valign = VALIGN_BASELINE; + backgroundColor = NULL; + backgroundImage = NULL; + backgroundRepeat = BACKGROUND_REPEAT; + backgroundAttachment = BACKGROUND_ATTACHMENT_SCROLL; + backgroundPositionX = createPerLength (0); + backgroundPositionY = createPerLength (0); + width = height = lineHeight = LENGTH_AUTO; + minWidth = maxWidth = minHeight = maxHeight = LENGTH_AUTO; + vloat = FLOAT_NONE; + clear = CLEAR_NONE; + overflow = OVERFLOW_VISIBLE; + position = POSITION_STATIC; + top = bottom = left = right = LENGTH_AUTO; + textIndent = 0; + margin.setVal (0); + borderWidth.setVal (0); + padding.setVal (0); + borderCollapse = BORDER_MODEL_SEPARATE; + setBorderColor (NULL); + setBorderStyle (BORDER_NONE); + hBorderSpacing = 0; + vBorderSpacing = 0; + wordSpacing = 0; + + display = DISPLAY_INLINE; + whiteSpace = WHITE_SPACE_NORMAL; + cursor = CURSOR_DEFAULT; +} + +/** + * \brief Reset those style attributes to their standard values, which are + * not inherited, according to CSS. + */ +void StyleAttrs::resetValues () +{ + x_img = -1; + + valign = VALIGN_BASELINE; + textAlignChar = '.'; + vloat = FLOAT_NONE; /** \todo Correct? Check specification. */ + clear = CLEAR_NONE; /** \todo Correct? Check specification. */ + overflow = OVERFLOW_VISIBLE; + position = POSITION_STATIC; /** \todo Correct? Check specification. */ + top = bottom = left = right = LENGTH_AUTO; /** \todo Correct? Check + specification. */ + backgroundColor = NULL; + backgroundImage = NULL; + backgroundRepeat = BACKGROUND_REPEAT; + backgroundAttachment = BACKGROUND_ATTACHMENT_SCROLL; + backgroundPositionX = createPerLength (0); + backgroundPositionY = createPerLength (0); + width = LENGTH_AUTO; + height = LENGTH_AUTO; + minWidth = maxWidth = minHeight = maxHeight = LENGTH_AUTO; + + margin.setVal (0); + borderWidth.setVal (0); + padding.setVal (0); + setBorderColor (NULL); + setBorderStyle (BORDER_NONE); + hBorderSpacing = 0; + vBorderSpacing = 0; + + display = DISPLAY_INLINE; +} + +/** + * \brief This method returns whether something may change its size, when + * its style changes from this style to \em otherStyle. + * + * It is mainly for optimizing style changes where only colors etc change + * (where false would be returned), in some cases it may return true, although + * a size change does not actually happen (e.g. when in a certain + * context a particular attribute is ignored). + * + * \todo Should for CSS implemented properly. Currently, size changes are + * not needed, so always false is returned. See also + * dw::core::Widget::setStyle. + */ +bool StyleAttrs::sizeDiffs (StyleAttrs *otherStyle) +{ + return false; +} + +bool StyleAttrs::equals (object::Object *other) { + StyleAttrs *otherAttrs = (StyleAttrs *) other; + + return this == otherAttrs || + (font == otherAttrs->font && + textDecoration == otherAttrs->textDecoration && + color == otherAttrs->color && + backgroundColor == otherAttrs->backgroundColor && + backgroundImage == otherAttrs->backgroundImage && + backgroundRepeat == otherAttrs->backgroundRepeat && + backgroundAttachment == otherAttrs->backgroundAttachment && + backgroundPositionX == otherAttrs->backgroundPositionX && + backgroundPositionY == otherAttrs->backgroundPositionY && + textAlign == otherAttrs->textAlign && + valign == otherAttrs->valign && + textAlignChar == otherAttrs->textAlignChar && + textTransform == otherAttrs->textTransform && + vloat == otherAttrs->vloat && + clear == otherAttrs->clear && + overflow == otherAttrs->overflow && + position == otherAttrs->position && + top == otherAttrs->top && + bottom == otherAttrs->bottom && + left == otherAttrs->left && + right == otherAttrs->right && + hBorderSpacing == otherAttrs->hBorderSpacing && + vBorderSpacing == otherAttrs->vBorderSpacing && + wordSpacing == otherAttrs->wordSpacing && + width == otherAttrs->width && + height == otherAttrs->height && + minWidth == otherAttrs->minWidth && + maxWidth == otherAttrs->maxWidth && + minHeight == otherAttrs->minHeight && + maxHeight == otherAttrs->maxHeight && + lineHeight == otherAttrs->lineHeight && + textIndent == otherAttrs->textIndent && + margin.equals (&otherAttrs->margin) && + borderWidth.equals (&otherAttrs->borderWidth) && + padding.equals (&otherAttrs->padding) && + borderCollapse == otherAttrs->borderCollapse && + borderColor.top == otherAttrs->borderColor.top && + borderColor.right == otherAttrs->borderColor.right && + borderColor.bottom == otherAttrs->borderColor.bottom && + borderColor.left == otherAttrs->borderColor.left && + borderStyle.top == otherAttrs->borderStyle.top && + borderStyle.right == otherAttrs->borderStyle.right && + borderStyle.bottom == otherAttrs->borderStyle.bottom && + borderStyle.left == otherAttrs->borderStyle.left && + display == otherAttrs->display && + whiteSpace == otherAttrs->whiteSpace && + listStylePosition == otherAttrs->listStylePosition && + listStyleType == otherAttrs->listStyleType && + cursor == otherAttrs->cursor && + x_link == otherAttrs->x_link && + x_lang[0] == otherAttrs->x_lang[0] && + x_lang[1] == otherAttrs->x_lang[1] && + x_img == otherAttrs->x_img && + x_tooltip == otherAttrs->x_tooltip); +} + +int StyleAttrs::hashValue () { + return (intptr_t) font + + textDecoration + + (intptr_t) color + + (intptr_t) backgroundColor + + (intptr_t) backgroundImage + + backgroundRepeat + + backgroundAttachment + + backgroundPositionX + + backgroundPositionY + + textAlign + + valign + + textAlignChar + + textTransform + + vloat + + clear + + overflow + + position + + top + + bottom + + left + + right + + hBorderSpacing + + vBorderSpacing + + wordSpacing + + width + + height + + minWidth + + maxWidth + + minHeight + + maxHeight + + lineHeight + + textIndent + + margin.hashValue () + + borderWidth.hashValue () + + padding.hashValue () + + borderCollapse + + (intptr_t) borderColor.top + + (intptr_t) borderColor.right + + (intptr_t) borderColor.bottom + + (intptr_t) borderColor.left + + borderStyle.top + + borderStyle.right + + borderStyle.bottom + + borderStyle.left + + display + + whiteSpace + + listStylePosition + + listStyleType + + cursor + + x_link + + x_lang[0] + x_lang[1] + + x_img + + (intptr_t) x_tooltip; +} + +int Style::totalRef = 0; +container::typed::HashTable * Style::styleTable = + new container::typed::HashTable (false, false, 1024); + +Style::Style (StyleAttrs *attrs) +{ + DBG_OBJ_CREATE ("dw::core::style::Style"); + + copyAttrs (attrs); + + DBG_OBJ_ASSOC_CHILD (font); + DBG_OBJ_ASSOC_CHILD (color); + DBG_OBJ_ASSOC_CHILD (backgroundColor); + DBG_OBJ_ASSOC_CHILD (backgroundImage); + DBG_OBJ_ASSOC_CHILD (borderColor.top); + DBG_OBJ_ASSOC_CHILD (borderColor.bottom); + DBG_OBJ_ASSOC_CHILD (borderColor.left); + DBG_OBJ_ASSOC_CHILD (borderColor.right); + //DBG_OBJ_ASSOC_CHILD (x_tooltip); + + refCount = 1; + + font->ref (); + if (color) + color->ref (); + if (backgroundColor) + backgroundColor->ref (); + if (backgroundImage) + backgroundImage->ref (); + if (borderColor.top) + borderColor.top->ref(); + if (borderColor.bottom) + borderColor.bottom->ref(); + if (borderColor.left) + borderColor.left->ref(); + if (borderColor.right) + borderColor.right->ref(); + if (x_tooltip) + x_tooltip->ref(); + + totalRef++; +} + +Style::~Style () +{ + font->unref (); + + if (color) + color->unref (); + if (backgroundColor) + backgroundColor->unref (); + if (backgroundImage) + backgroundImage->unref (); + if (borderColor.top) + borderColor.top->unref(); + if (borderColor.bottom) + borderColor.bottom->unref(); + if (borderColor.left) + borderColor.left->unref(); + if (borderColor.right) + borderColor.right->unref(); + if (x_tooltip) + x_tooltip->unref(); + + styleTable->remove (this); + totalRef--; + + DBG_OBJ_DELETE (); +} + +void Style::copyAttrs (StyleAttrs *attrs) +{ + font = attrs->font; + textDecoration = attrs->textDecoration; + color = attrs->color; + backgroundColor = attrs->backgroundColor; + backgroundImage = attrs->backgroundImage; + backgroundRepeat = attrs->backgroundRepeat; + backgroundAttachment = attrs->backgroundAttachment; + backgroundPositionX = attrs->backgroundPositionX; + backgroundPositionY = attrs->backgroundPositionY; + textAlign = attrs->textAlign; + valign = attrs->valign; + textAlignChar = attrs->textAlignChar; + textTransform = attrs->textTransform; + vloat = attrs->vloat; + clear = attrs->clear; + overflow = attrs->overflow; + position = attrs->position; + top = attrs->top; + bottom = attrs->bottom; + left = attrs->left; + right = attrs->right; + hBorderSpacing = attrs->hBorderSpacing; + vBorderSpacing = attrs->vBorderSpacing; + wordSpacing = attrs->wordSpacing; + width = attrs->width; + height = attrs->height; + lineHeight = attrs->lineHeight; + textIndent = attrs->textIndent; + minWidth = attrs->minWidth; + maxWidth = attrs->maxWidth; + minHeight = attrs->minHeight; + maxHeight = attrs->maxHeight; + margin = attrs->margin; + borderWidth = attrs->borderWidth; + padding = attrs->padding; + borderCollapse = attrs->borderCollapse; + borderColor = attrs->borderColor; + borderStyle = attrs->borderStyle; + display = attrs->display; + whiteSpace = attrs->whiteSpace; + listStylePosition = attrs->listStylePosition; + listStyleType = attrs->listStyleType; + cursor = attrs->cursor; + x_link = attrs->x_link; + x_lang[0] = attrs->x_lang[0]; + x_lang[1] = attrs->x_lang[1]; + x_img = attrs->x_img; + x_tooltip = attrs->x_tooltip; +} + +// ---------------------------------------------------------------------- + +bool FontAttrs::equals(object::Object *other) +{ + FontAttrs *otherAttrs = (FontAttrs*)other; + return + this == otherAttrs || + (size == otherAttrs->size && + weight == otherAttrs->weight && + style == otherAttrs->style && + letterSpacing == otherAttrs->letterSpacing && + fontVariant == otherAttrs->fontVariant && + strcmp (name, otherAttrs->name) == 0); +} + +int FontAttrs::hashValue() +{ + int h = object::String::hashValue (name); + h = (h << 5) - h + size; + h = (h << 5) - h + weight; + h = (h << 5) - h + style; + h = (h << 5) - h + letterSpacing; + h = (h << 5) - h + fontVariant; + return h; +} + +Font::~Font () +{ + free ((char*)name); + DBG_OBJ_DELETE (); +} + +void Font::copyAttrs (FontAttrs *attrs) +{ + name = strdup (attrs->name); + size = attrs->size; + weight = attrs->weight; + style = attrs->style; + letterSpacing = attrs->letterSpacing; + fontVariant = attrs->fontVariant; +} + +Font *Font::create0 (Layout *layout, FontAttrs *attrs, + bool tryEverything) +{ + return layout->createFont (attrs, tryEverything); +} + +Font *Font::create (Layout *layout, FontAttrs *attrs) +{ + return create0 (layout, attrs, false); +} + +bool Font::exists (Layout *layout, const char *name) +{ + return layout->fontExists (name); +} + +// ---------------------------------------------------------------------- + +bool ColorAttrs::equals(object::Object *other) +{ + ColorAttrs *oc = (ColorAttrs*)other; + return this == oc || (color == oc->color); +} + +int ColorAttrs::hashValue() +{ + return color; +} + +Color::~Color () +{ + DBG_OBJ_DELETE (); +} + +int Color::shadeColor (int color, int d) +{ + int red = (color >> 16) & 255; + int green = (color >> 8) & 255; + int blue = color & 255; + + double oldLightness = ((double) misc::max (red, green, blue)) / 255; + double newLightness; + + if (oldLightness > 0.8) { + if (d > 0) + newLightness = oldLightness - 0.2; + else + newLightness = oldLightness - 0.4; + } else if (oldLightness < 0.2) { + if (d > 0) + newLightness = oldLightness + 0.4; + else + newLightness = oldLightness + 0.2; + } else + newLightness = oldLightness + d * 0.2; + + if (oldLightness) { + double f = (newLightness / oldLightness); + red = (int)(red * f); + green = (int)(green * f); + blue = (int)(blue * f); + } else { + red = green = blue = (int)(newLightness * 255); + } + + return (red << 16) | (green << 8) | blue; +} + +int Color::shadeColor (int color, Shading shading) +{ + switch (shading) { + case SHADING_NORMAL: + return color; + + case SHADING_LIGHT: + return shadeColor(color, +1); + + case SHADING_INVERSE: + return color ^ 0xffffff; + + case SHADING_DARK: + return shadeColor(color, -1); + + default: + // compiler happiness + misc::assertNotReached (); + return -1; + } +} + + +Color *Color::create (Layout *layout, int col) +{ + ColorAttrs attrs(col); + + return layout->createColor (col); +} + +Tooltip *Tooltip::create (Layout *layout, const char *text) +{ + return layout->createTooltip (text); +} + +// ---------------------------------------------------------------------- + +void StyleImage::StyleImgRenderer::setBuffer (core::Imgbuf *buffer, bool resize) +{ + if (image->imgbufSrc) + image->imgbufSrc->unref (); + if (image->imgbufTiled) + image->imgbufTiled->unref (); + + image->imgbufTiled = NULL; + + image->imgbufSrc = buffer; + DBG_OBJ_ASSOC (image, image->imgbufSrc); + + if (image->imgbufSrc) { + image->imgbufSrc->ref (); + + // If the image is too small, drawing a background will cause + // many calls of View::drawImgbuf. For this reason, we create + // another image buffer, the "tiled" image buffer, which is + // larger (the "optimal" size is defined as OPT_BG_IMG_W * + // OPT_BG_IMG_H) and contains the "source" buffer several times. + // + // This "tiled" buffer is not used when 'background-repeat' has + // another value than 'repeat', for obvious reasons. Image + // buffers only "tiled" in one dimension (to optimize 'repeat-x' + // and 'repeat-y') are not supported. + + if (image->imgbufSrc->getRootWidth() * image->imgbufSrc->getRootHeight() + < MIN_BG_IMG_W * MIN_BG_IMG_H) { + image->tilesX = + misc::max (OPT_BG_IMG_W / image->imgbufSrc->getRootWidth(), 1); + image->tilesY = + misc::max (OPT_BG_IMG_H / image->imgbufSrc->getRootHeight(), 1); + image->imgbufTiled = + image->imgbufSrc->createSimilarBuf + (image->tilesX * image->imgbufSrc->getRootWidth(), + image->tilesY * image->imgbufSrc->getRootHeight()); + + DBG_OBJ_ASSOC (image, image->imgbufTiled); + } + } +} + +void StyleImage::StyleImgRenderer::drawRow (int row) +{ + if (image->imgbufTiled) { + // A row of data has been copied to the source buffer, here it + // is copied into the tiled buffer. + + // Unfortunately, this code may be called *after* some other + // implementations of ImgRenderer::drawRow, which actually + // *draw* the tiled buffer, which is so not up to date + // (ImgRendererDist does not define an order). OTOH, these + // drawing implementations calle Widget::queueResize, so the + // actual drawing (and so access to the tiled buffer) is done + // later. + + int w = image->imgbufSrc->getRootWidth (); + int h = image->imgbufSrc->getRootHeight (); + + for (int x = 0; x < image->tilesX; x++) + for (int y = 0; y < image->tilesX; y++) + image->imgbufSrc->copyTo (image->imgbufTiled, x * w, y * h, + 0, row, w, 1); + } +} + +void StyleImage::StyleImgRenderer::finish () +{ + // Nothing to do. +} + +void StyleImage::StyleImgRenderer::fatal () +{ + // Nothing to do. +} + +StyleImage::StyleImage () +{ + DBG_OBJ_CREATE ("dw::core::style::StyleImage"); + + refCount = 0; + imgbufSrc = NULL; + imgbufTiled = NULL; + + imgRendererDist = new ImgRendererDist (); + styleImgRenderer = new StyleImgRenderer (this); + imgRendererDist->put (styleImgRenderer); +} + +StyleImage::~StyleImage () +{ + if (imgbufSrc) + imgbufSrc->unref (); + if (imgbufTiled) + imgbufTiled->unref (); + + delete imgRendererDist; + delete styleImgRenderer; + + DBG_OBJ_DELETE (); +} + +void StyleImage::ExternalImgRenderer::setBuffer (core::Imgbuf *buffer, + bool resize) +{ + // Nothing to do? +} + +void StyleImage::ExternalImgRenderer::drawRow (int row) +{ + if (drawBackgroundLineByLine) { + StyleImage *backgroundImage; + if (readyToDraw () && (backgroundImage = getBackgroundImage ())) { + // All single rows are drawn. + + Imgbuf *imgbuf = backgroundImage->getImgbufSrc(); + int imgWidth = imgbuf->getRootWidth (); + int imgHeight = imgbuf->getRootHeight (); + + int x, y, width, height; + getBgArea (&x, &y, &width, &height); + + int xRef, yRef, widthRef, heightRef; + getRefArea (&xRef, &yRef, &widthRef, &heightRef); + + bool repeatX, repeatY, doDraw; + int origX, origY, tileX1, tileX2, tileY1, tileY2; + + calcBackgroundRelatedValues (backgroundImage, + getBackgroundRepeat (), + getBackgroundAttachment (), + getBackgroundPositionX (), + getBackgroundPositionY (), + x, y, width, height, xRef, yRef, widthRef, + heightRef, &repeatX, &repeatY, &origX, + &origY, &tileX1, &tileX2, &tileY1, + &tileY2, &doDraw); + + //printf ("tileX1 = %d, tileX2 = %d, tileY1 = %d, tileY2 = %d\n", + // tileX1, tileX2, tileY1, tileY2); + + if (doDraw) + // Only iterate over y, because the rows can be combined + // horizontally. + for (int tileY = tileY1; tileY <= tileY2; tileY++) { + int x1 = misc::max (origX + tileX1 * imgWidth, x); + int x2 = misc::min (origX + (tileX2 + 1) * imgWidth, x + width); + + int yt = origY + tileY * imgHeight + row; + if (yt >= y && yt < y + height) + draw (x1, yt, x2 - x1, 1); + } + } + } +} + +void StyleImage::ExternalImgRenderer::finish () +{ + if (!drawBackgroundLineByLine) { + if (readyToDraw ()) { + // Draw total area, as a whole. + int x, y, width, height; + getBgArea (&x, &y, &width, &height); + draw (x, y, width, height); + } + } +} + +void StyleImage::ExternalImgRenderer::fatal () +{ + // Nothing to do. +} + +// ---------------------------------------------------------------------- + +StyleImage *StyleImage::ExternalWidgetImgRenderer::getBackgroundImage () +{ + Style *style = getStyle (); + return style ? style->backgroundImage : NULL; +} + +BackgroundRepeat StyleImage::ExternalWidgetImgRenderer::getBackgroundRepeat () +{ + Style *style = getStyle (); + return style ? style->backgroundRepeat : BACKGROUND_REPEAT; +} + +BackgroundAttachment + StyleImage::ExternalWidgetImgRenderer::getBackgroundAttachment () +{ + Style *style = getStyle (); + return style ? style->backgroundAttachment : BACKGROUND_ATTACHMENT_SCROLL; +} + +Length StyleImage::ExternalWidgetImgRenderer::getBackgroundPositionX () +{ + Style *style = getStyle (); + return style ? style->backgroundPositionX : createPerLength (0); +} + +Length StyleImage::ExternalWidgetImgRenderer::getBackgroundPositionY () +{ + Style *style = getStyle (); + return style ? style->backgroundPositionY : createPerLength (0); +} + +// ---------------------------------------------------------------------- + +/* + * The drawBorder{Top,Bottom,Left,Right} functions are similar. They + * use a trapezium as draw polygon, or drawTypedLine() for dots and dashes. + * Although the concept is simple, achieving pixel accuracy is laborious [1]. + * + * [1] http://www.dillo.org/css_compat/tests/border-style.html + */ +static void drawBorderTop(View *view, Style *style, + int x1, int y1, int x2, int y2) + +{ + int d, w; + Point points[4]; + const bool filled = true, convex = true; + bool ridge = false, inset = false, dotted = false; + Color::Shading shading = Color::SHADING_NORMAL; + + if (!style->borderColor.top || style->borderWidth.top == 0) + return; + + switch (style->borderStyle.top) { + case BORDER_NONE: + case BORDER_HIDDEN: + break; + case BORDER_DOTTED: + dotted = true; + case BORDER_DASHED: + w = style->borderWidth.top; + view->drawTypedLine(style->borderColor.top, shading, + dotted ? LINE_DOTTED : LINE_DASHED, + w, x1+w/2, y1+w/2, x2-w/2, y2+w/2); + break; + case BORDER_SOLID: + case BORDER_INSET: + inset = true; + case BORDER_OUTSET: + if (style->borderStyle.top != BORDER_SOLID) + shading = (inset) ? Color::SHADING_DARK : Color::SHADING_LIGHT; + + if (style->borderWidth.top == 1) { + view->drawLine(style->borderColor.top, shading, x1, y1, x2, y2); + } else { + points[0].x = x1; + points[1].x = x2 + 1; + points[0].y = points[1].y = y1; + points[2].x = points[1].x - style->borderWidth.right; + points[3].x = x1 + style->borderWidth.left; + points[2].y = points[3].y = points[0].y + style->borderWidth.top; + view->drawPolygon (style->borderColor.top, shading, filled, convex, + points, 4); + } + break; + case BORDER_RIDGE: + ridge = true; + case BORDER_GROOVE: + d = style->borderWidth.top & 1; + points[0].x = x1; + points[1].x = x2 + 1; + points[0].y = points[1].y = y1; + points[2].x = x2 - style->borderWidth.right / 2; + points[3].x = x1 + style->borderWidth.left / 2; + points[2].y = points[3].y = y1 + style->borderWidth.top / 2 + d; + shading = (ridge) ? Color::SHADING_LIGHT : Color::SHADING_DARK; + view->drawPolygon (style->borderColor.top, shading, filled, convex, + points, 4); + points[0].x = x1 + style->borderWidth.left / 2 + d; + points[1].x = x2 - style->borderWidth.right / 2 + 1 - d; + points[0].y = points[1].y = y1 + style->borderWidth.top / 2 + d; + points[2].x = x2 - style->borderWidth.right + 1 - d; + points[3].x = x1 + style->borderWidth.left; + points[2].y = points[3].y = y1 + style->borderWidth.top; + shading = (ridge) ? Color::SHADING_DARK : Color::SHADING_LIGHT; + view->drawPolygon (style->borderColor.top, shading, filled, convex, + points, 4); + break; + case BORDER_DOUBLE: + w = (int) rint(style->borderWidth.top / 3.0); + d = w ? style->borderWidth.top - 2 * w : 0; + int w_l = (int) rint(style->borderWidth.left / 3.0); + int w_r = (int) rint(style->borderWidth.right / 3.0); + if (style->borderWidth.top == 1) { + view->drawLine(style->borderColor.top, shading, x1, y1, x2, y2); + break; + } + points[0].x = x1; + points[1].x = x2 + 1; + points[0].y = points[1].y = y1; + points[2].x = points[1].x - w_r; + points[3].x = points[0].x + w_l; + points[2].y = points[3].y = points[0].y + w; + view->drawPolygon (style->borderColor.top, shading, filled, convex, + points, 4); + points[0].x = x1 + style->borderWidth.left - w_l; + points[1].x = x2 + 1 - style->borderWidth.right + w_r; + points[0].y = points[1].y = y1 + w + d; + points[2].x = x2 + 1 - style->borderWidth.right; + points[3].x = x1 + style->borderWidth.left; + points[2].y = points[3].y = y1 + style->borderWidth.top; + view->drawPolygon (style->borderColor.top, shading, filled, convex, + points, 4); + break; + } +} + +static void drawBorderBottom(View *view, Style *style, + int x1, int y1, int x2, int y2) + +{ + int d, w; + Point points[4]; + const bool filled = true, convex = true; + bool ridge = false, inset = false, dotted = false; + Color::Shading shading = Color::SHADING_NORMAL; + + if (!style->borderColor.bottom || style->borderWidth.bottom == 0) + return; + + switch (style->borderStyle.bottom) { + case BORDER_NONE: + case BORDER_HIDDEN: + break; + case BORDER_DOTTED: + dotted = true; + case BORDER_DASHED: + w = style->borderWidth.bottom; + view->drawTypedLine(style->borderColor.bottom, shading, + dotted ? LINE_DOTTED : LINE_DASHED, + w, x1+w/2, y1-w/2, x2-w/2, y2-w/2); + break; + case BORDER_SOLID: + case BORDER_INSET: + inset = true; + case BORDER_OUTSET: + if (style->borderStyle.bottom != BORDER_SOLID) + shading = (inset) ? Color::SHADING_LIGHT : Color::SHADING_DARK; + + if (style->borderWidth.bottom == 1) { /* 1 pixel line */ + view->drawLine(style->borderColor.bottom, shading, x1, y1, x2, y2); + } else { + points[0].x = x1 - 1; + points[1].x = x2 + 2; + points[0].y = points[1].y = y1 + 1; + points[2].x = points[1].x - style->borderWidth.right; + points[3].x = points[0].x + style->borderWidth.left; + points[2].y = points[3].y = points[0].y-style->borderWidth.bottom; + view->drawPolygon (style->borderColor.bottom, shading, filled, convex, + points, 4); + } + break; + case BORDER_RIDGE: + ridge = true; + case BORDER_GROOVE: + w = style->borderWidth.bottom; + d = w & 1; + points[0].x = x1 - 1; + points[1].x = x2 + 2 - d; + points[0].y = points[1].y = y1 + 1; + points[2].x = points[1].x - style->borderWidth.right / 2; + points[3].x = points[0].x + style->borderWidth.left / 2 + d; + points[2].y = points[3].y = points[0].y - w/2 - d; + shading = (ridge) ? Color::SHADING_DARK : Color::SHADING_LIGHT; + view->drawPolygon (style->borderColor.bottom, shading, filled, convex, + points, 4); + // clockwise + points[0].x = x1 + style->borderWidth.left - 1; + points[1].x = x2 + 1 - style->borderWidth.right + 1; + points[0].y = points[1].y = y1 - w + 1; + points[2].x = points[1].x + style->borderWidth.right / 2; + points[3].x = points[0].x - style->borderWidth.left / 2; + points[2].y = points[3].y = points[0].y + w/2; + shading = (ridge) ? Color::SHADING_LIGHT : Color::SHADING_DARK; + view->drawPolygon (style->borderColor.bottom, shading, filled, convex, + points, 4); + break; + case BORDER_DOUBLE: + w = (int) rint(style->borderWidth.bottom / 3.0); + d = w ? style->borderWidth.bottom - 2 * w : 0; + int w_l = (int) rint(style->borderWidth.left / 3.0); + int w_r = (int) rint(style->borderWidth.right / 3.0); + if (style->borderWidth.bottom == 1) { + view->drawLine(style->borderColor.bottom, shading, x1, y1, x2, y2); + break; + } + points[0].x = x2 + 2; + points[1].x = x1 - 1; + points[0].y = points[1].y = y1 + 1; + points[2].x = points[1].x + w_l; + points[3].x = points[0].x - w_r; + points[2].y = points[3].y = points[0].y - w; + view->drawPolygon (style->borderColor.bottom, shading, filled, convex, + points, 4); + points[0].x = x2 + 2 - style->borderWidth.right + w_r; + points[1].x = x1 - 1 + style->borderWidth.left - w_l; + points[0].y = points[1].y = y1 + 1 - w - d; + points[2].x = x1 - 1 + style->borderWidth.left; + points[3].x = x2 + 2 - style->borderWidth.right; + points[2].y = points[3].y = y1 + 1 - style->borderWidth.bottom; + view->drawPolygon (style->borderColor.bottom, shading, filled, convex, + points, 4); + break; + } +} + +static void drawBorderLeft(View *view, Style *style, + int x1, int y1, int x2, int y2) + +{ + int d, w; + Point points[4]; + bool filled = true, convex = true; + bool ridge = false, inset = false, dotted = false; + Color::Shading shading = Color::SHADING_NORMAL; + + if (!style->borderColor.left || style->borderWidth.left == 0) + return; + + switch (style->borderStyle.left) { + case BORDER_NONE: + case BORDER_HIDDEN: + break; + case BORDER_DOTTED: + dotted = true; + case BORDER_DASHED: + w = style->borderWidth.left; + view->drawTypedLine(style->borderColor.left, shading, + dotted ? LINE_DOTTED : LINE_DASHED, + w, x1+w/2, y1+w/2, x1+w/2, y2-w/2); + break; + case BORDER_SOLID: + case BORDER_INSET: + inset = true; + case BORDER_OUTSET: + if (style->borderStyle.left != BORDER_SOLID) + shading = (inset) ? Color::SHADING_DARK : Color::SHADING_LIGHT; + if (style->borderWidth.left == 1) { /* 1 pixel line */ + view->drawLine(style->borderColor.left, shading, x1, y1, x2, y2); + } else { + points[0].x = points[1].x = x1; + points[0].y = y1 - 1; + points[1].y = y2 + 1; + points[2].x = points[3].x = points[0].x + style->borderWidth.left; + points[2].y = points[1].y - style->borderWidth.bottom; + points[3].y = points[0].y + style->borderWidth.top; + view->drawPolygon (style->borderColor.left, shading, filled, convex, + points, 4); + } + break; + case BORDER_RIDGE: + ridge = true; + case BORDER_GROOVE: + w = style->borderWidth.left; + d = w & 1; + points[0].x = points[1].x = x1; + points[0].y = y1; + points[1].y = y2; + points[2].x = points[3].x = x1 + w / 2 + d; + points[2].y = y2 - style->borderWidth.bottom / 2; + points[3].y = y1 + style->borderWidth.top / 2; + shading = (ridge) ? Color::SHADING_LIGHT : Color::SHADING_DARK; + view->drawPolygon (style->borderColor.left, shading, filled, convex, + points, 4); + points[0].x = points[1].x = x1 + w / 2 + d; + points[0].y = y1 + style->borderWidth.top / 2; + points[1].y = y2 - style->borderWidth.bottom / 2; + points[2].x = points[3].x = x1 + w; + points[2].y = y2 - style->borderWidth.bottom; + points[3].y = y1 + style->borderWidth.top; + shading = (ridge) ? Color::SHADING_DARK : Color::SHADING_LIGHT; + view->drawPolygon (style->borderColor.left, shading, filled, convex, + points, 4); + break; + case BORDER_DOUBLE: + w = (int) rint(style->borderWidth.left / 3.0); + d = w ? style->borderWidth.left - 2 * w : 0; + int w_b = (int) rint(style->borderWidth.bottom / 3.0); + int w_t = (int) rint(style->borderWidth.top / 3.0); + if (style->borderWidth.left == 1) { + view->drawLine(style->borderColor.left, shading, x1, y1, x2, y2-1); + break; + } + points[0].x = points[1].x = x1; + points[0].y = y1 - 1; + points[1].y = y2 + 1; + points[2].x = points[3].x = points[0].x + w; + points[2].y = points[1].y - w_b; + points[3].y = points[0].y + w_t; + view->drawPolygon (style->borderColor.left, shading, filled, convex, + points, 4); + points[0].x = points[1].x = x1 + w + d; + points[0].y = y1 - 1 + style->borderWidth.top - w_t; + points[1].y = y2 + 1 - style->borderWidth.bottom + w_b; + points[2].x = points[3].x = points[0].x + w; + points[2].y = y2 + 1 - style->borderWidth.bottom; + points[3].y = y1 - 1 + style->borderWidth.top; + view->drawPolygon (style->borderColor.left, shading, filled, convex, + points, 4); + break; + } +} + +static void drawBorderRight(View *view, Style *style, + int x1, int y1, int x2, int y2) + +{ + int d, w; + Point points[4]; + const bool filled = true, convex = true; + bool ridge = false, inset = false, dotted = false; + Color::Shading shading = Color::SHADING_NORMAL; + + if (!style->borderColor.right || style->borderWidth.right == 0) + return; + + switch (style->borderStyle.right) { + case BORDER_NONE: + case BORDER_HIDDEN: + break; + case BORDER_DOTTED: + dotted = true; + case BORDER_DASHED: + w = style->borderWidth.right; + view->drawTypedLine(style->borderColor.right, shading, + dotted ? LINE_DOTTED : LINE_DASHED, + w, x1 - w/2, y1 + w/2, x1 - w/2, y2 - w/2); + break; + case BORDER_SOLID: + case BORDER_INSET: + inset = true; + case BORDER_OUTSET: + if (style->borderStyle.right != BORDER_SOLID) + shading = (inset) ? Color::SHADING_LIGHT : Color::SHADING_DARK; + if (style->borderWidth.right == 1) { /* 1 pixel line */ + view->drawLine(style->borderColor.right, shading, x1, y1, x2, y2); + } else { + points[0].x = points[1].x = x1 + 1; + points[0].y = y1 - 1; + points[1].y = y2 + 1; + points[2].x = points[3].x = points[0].x-style->borderWidth.right; + points[2].y = points[1].y - style->borderWidth.bottom; + points[3].y = points[0].y + style->borderWidth.top; + view->drawPolygon (style->borderColor.right, shading, filled, convex, + points,4); + } + break; + case BORDER_RIDGE: + ridge = true; + case BORDER_GROOVE: + w = style->borderWidth.right; + d = w & 1; + points[0].x = points[1].x = x1 + 1; + points[0].y = y1; + points[1].y = y2; + points[2].x = points[3].x = points[0].x - w / 2 - d; + points[2].y = y2 - style->borderWidth.bottom / 2; + points[3].y = points[0].y + style->borderWidth.top / 2; + shading = (ridge) ? Color::SHADING_DARK : Color::SHADING_LIGHT; + view->drawPolygon (style->borderColor.right, shading, filled, convex, + points, 4); + points[0].x = points[1].x = x1 + 1 - w / 2 - d; + points[0].y = y1 + style->borderWidth.top / 2; + points[1].y = y2 - style->borderWidth.bottom / 2; + points[2].x = points[3].x = x1 + 1 - w; + points[2].y = y2 - style->borderWidth.bottom; + points[3].y = y1 + style->borderWidth.top; + shading = (ridge) ? Color::SHADING_LIGHT: Color::SHADING_DARK; + view->drawPolygon (style->borderColor.right, shading, filled, convex, + points, 4); + break; + case BORDER_DOUBLE: + w = (int) rint(style->borderWidth.right / 3.0); + d = w ? style->borderWidth.right - 2 * w : 0; + int w_b = (int) rint(style->borderWidth.bottom / 3.0); + int w_t = (int) rint(style->borderWidth.top / 3.0); + if (style->borderWidth.right == 1) { + view->drawLine(style->borderColor.right, shading, x1, y1, x2, y2); + break; + } + points[0].x = points[1].x = x1 + 1; + points[0].y = y1 - 1; + points[1].y = y2 + 1; + points[2].x = points[3].x = points[0].x - w; + points[2].y = points[1].y - w_b; + points[3].y = points[0].y + w_t; + view->drawPolygon (style->borderColor.right, shading, filled, convex, + points, 4); + points[0].x = points[1].x = x1 + 1 - w - d; + points[0].y = y1 - 1 + style->borderWidth.top - w_t; + points[1].y = y2 + 1 - style->borderWidth.bottom + w_b; + points[2].x = points[3].x = points[0].x - w; + points[2].y = y2 + 1 - style->borderWidth.bottom; + points[3].y = y1 - 1 + style->borderWidth.top; + view->drawPolygon (style->borderColor.right, shading, filled, convex, + points, 4); + break; + } +} + +/** + * \brief Draw the border of a region in window, according to style. + * + * Used by dw::core::Widget::drawBox and dw::core::Widget::drawWidgetBox. + * + * "area" is the area to be drawn, "x", "y", "width" and "height" + * define the box itself. All are given in canvas coordinates. + */ +void drawBorder (View *view, Layout *layout, Rectangle *area, + int x, int y, int width, int height, + Style *style, bool inverse) +{ + /** \todo a lot! */ + int xb1, yb1, xb2, yb2; + + // top left and bottom right point of outer border boundary + xb1 = x + style->margin.left; + yb1 = y + style->margin.top; + xb2 = x + (width > 0 ? width - 1 : 0) - style->margin.right; + yb2 = y + (height > 0 ? height - 1 : 0) - style->margin.bottom; + + /* + // top left and bottom right point of inner border boundary + xp1 = xb1 + style->borderWidth.left; + yp1 = yb1 + style->borderWidth.top; + xp2 = xb2 - style->borderWidth.right; + yp2 = yb2 - style->borderWidth.bottom; + + light = inverse ? Color::SHADING_DARK : Color::SHADING_LIGHT; + dark = inverse ? Color::SHADING_LIGHT : Color::SHADING_DARK; + normal = inverse ? Color::SHADING_INVERSE : Color::SHADING_NORMAL; + */ + + drawBorderRight(view, style, xb2, yb1, xb2, yb2); + drawBorderLeft(view, style, xb1, yb1, xb1, yb2); + drawBorderTop(view, style, xb1, yb1, xb2, yb1); + drawBorderBottom(view, style, xb1, yb2, xb2, yb2); +} + + +/** + * \brief Draw the background (content plus padding) of a region in window, + * according to style. + * + * Used by dw::core::Widget::drawBox and dw::core::Widget::drawWidgetBox. + * + * "area" is the area to be drawn, "x", "y", "width" and "height" + * define the box itself (padding box). "xRef", "yRef", "widthRef" and + * "heightRef" define the reference area, which is important for the + * tiling of background images (for position 0%/0%, a tile is set at + * xRef/yRef; for position 100%/100%, a tile is set at xRef + + * widthRef/yRef + widthRef). See calls for more informations; in most + * cases, these boxes are identical (padding box). All these + * coordinates are given in canvas coordinates. + * + * "atTop" should be true, only if the area is drawn directly on the + * canvas, not on top of other areas; this is only true for the + * toplevel widget itself (not parts of its contents). Toplevel widget + * background colors are already set as viewport background color, so + * that drawing again is is not neccessary, but some time can be + * saved. + * + * Otherwise, the caller should not try to increase the performance by + * doing some tests before; this is all done in this method. + * + * "bgColor" is passes implicitly. For non-inversed drawing, + * style->backgroundColor may simply used. However, when drawing is + * inversed, and style->backgroundColor is undefined (NULL), a + * background color defined higher in the hierarchy (which is not + * accessable here) must be used. + * + * (Background *images* are never drawn inverse.) + */ +void drawBackground (View *view, Layout *layout, Rectangle *area, + int x, int y, int width, int height, + int xRef, int yRef, int widthRef, int heightRef, + Style *style, Color *bgColor, bool inverse, bool atTop) +{ + bool hasBgColor = bgColor != NULL && + // The test for background colors is rather simple, since only the color + // has to be compared, ... + (!atTop || layout->getBgColor () != bgColor); + bool hasBgImage = (style->backgroundImage != NULL && + style->backgroundImage->getImgbufSrc() != NULL) && + // ... but for backgrounds, it would be rather complicated. To handle the + // two cases (normal HTML in a viewport, where the layout background + // image is set, and contents of