summaryrefslogtreecommitdiff
authorTim Janik <timj@gtk.org>2005-04-15 15:26:20 (GMT)
committer Tim Janik <timj@gtk.org>2005-04-15 15:26:20 (GMT)
commite63312fa6a02a945408a91245f20cfbb02d1af90 (patch) (side-by-side diff)
treeeedba2245f33773eab2d1914da8db0973e1c53b6
parentad9782465ab1c916e2a6bac5868fae6108ba72ed (diff)
Fri Apr 15 17:13:02 2005 Tim Janik <timj@gtk.org>
* table.hh, table.cc, tableimpl.hh: define Table interface, implement TableImpl, a very versatile container type, based on GtkTable of GTK+. * container.hh, container.cc, containerimpl.hh: define the most basic Container API. define Root widget API. implement SingleContainerImpl and MultiContainerImpl, implement RootImpl. * item.hh, item.cc, itemimpl.hh: define API of the most basic graphically visible instance, an Item. provide sample implementation ItemImpl. * primitives.hh, primitives.cc: implemented Affine transformations as class type. * factory.hh, factory.cc: implement factory logic to register item types and create item instances. added gadget parser (XML based item tree descriptions) so item definitions can be read in and created from XML definitions. * birnetmarkup.hh, birnetmarkup.cc: added simple XML markup parser, based on GMarklup from GLib. * private.hh: define MakeProperty() internally.
-rw-r--r--rapicorn/Makefile.am16
-rw-r--r--rapicorn/birnetmarkup.cc2061
-rw-r--r--rapicorn/birnetmarkup.hh83
-rw-r--r--rapicorn/container.cc759
-rw-r--r--rapicorn/container.hh124
-rw-r--r--rapicorn/containerimpl.hh60
-rw-r--r--rapicorn/factory.cc571
-rw-r--r--rapicorn/factory.hh79
-rw-r--r--rapicorn/item.cc467
-rw-r--r--rapicorn/item.hh191
-rw-r--r--rapicorn/itemimpl.hh53
-rw-r--r--rapicorn/primitives.cc55
-rw-r--r--rapicorn/primitives.hh280
-rw-r--r--rapicorn/private.hh2
-rw-r--r--rapicorn/rapicorn.hh3
-rw-r--r--rapicorn/root.cc759
-rw-r--r--rapicorn/root.hh124
-rw-r--r--rapicorn/table.cc838
-rw-r--r--rapicorn/table.hh45
-rw-r--r--rapicorn/tableimpl.hh133
20 files changed, 6702 insertions, 1 deletions
diff --git a/rapicorn/Makefile.am b/rapicorn/Makefile.am
index 1efb255..4fbbe9a 100644
--- a/rapicorn/Makefile.am
+++ b/rapicorn/Makefile.am
@@ -9,24 +9,40 @@ DEFS = -DRAPICORN_PRIVATE -DPARANOID
rapicorn_public_headers = $(strip \
rapicorn.hh keycodes.hh \
+ $(rapicorn_impl_headers) \
+ table.hh \
birnetutils.hh \
birnetsignal.hh \
birnetsignalvariants.hh \
+ birnetmarkup.hh \
utilities.hh \
enumdefs.hh \
primitives.hh \
appearance.hh \
events.hh \
properties.hh \
+ item.hh \
+ factory.hh \
+ container.hh \
+)
+rapicorn_impl_headers = $(strip \
+ itemimpl.hh \
+ containerimpl.hh \
+ tableimpl.hh \
)
rapicorn_cc_sources = $(strip \
+ table.cc \
birnetutils.cc \
+ birnetmarkup.cc \
utilities.cc \
enumdefs.cc \
primitives.cc \
appearance.cc \
events.cc \
properties.cc \
+ item.cc \
+ factory.cc \
+ container.cc \
)
rapicorn_private_headers = $(strip \
private.hh \
diff --git a/rapicorn/birnetmarkup.cc b/rapicorn/birnetmarkup.cc
new file mode 100644
index 0000000..ec4e154
--- a/dev/null
+++ b/rapicorn/birnetmarkup.cc
@@ -0,0 +1,2061 @@
+/* BirnetMarkup - simple XML parser
+ * Copyright (C) 2005 Tim Janik
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * based on glib/gmarkup.c, Copyright 2000, 2003 Red Hat, Inc.
+ */
+#include "birnetmarkup.hh"
+#include "private.hh"
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <errno.h>
+#include <vector>
+#include <stack>
+
+namespace Birnet {
+using namespace std;
+
+/* --- unicode hacks --- */
+#define _(x) x // FIXME
+inline bool
+unichar_isalpha (unichar uc) // FIXME
+{
+ return (uc >= 'A' && uc <= 'Z') || (uc >= 'a' && uc <= 'z');
+}
+inline int
+unichar_to_utf8 (unichar c, // FIXME
+ char *outbuf)
+{
+ *outbuf = c;
+ return 1;
+}
+inline unichar
+utf8_get_char (const char *p) // FIXME
+{
+ return *p;
+}
+inline char*
+utf8_next_char (char *p)
+{
+ static const char utf8_skip_data[256] = {
+ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+ 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+ 3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,5,5,5,5,6,6,1,1
+ };
+ return p + utf8_skip_data[*(uint8*) p];
+}
+inline const char*
+utf8_next_char (const char *c)
+{
+ char *result = utf8_next_char (const_cast<char*> (c));
+ return const_cast<const char*> (result);
+}
+inline char*
+utf8_find_prev_char (char *str,
+ char *p)
+{
+ for (--p; p >= str; --p)
+ {
+ if ((*p & 0xc0) != 0x80)
+ return p;
+ }
+ return NULL;
+}
+inline const char*
+utf8_find_prev_char (const char *str,
+ const char *p)
+{
+ char *result = utf8_find_prev_char (const_cast<char*> (str), const_cast<char*> (p));
+ return const_cast<const char*> (result);
+}
+inline char*
+utf8_find_next_char (char *p,
+ char *end)
+{
+ if (*p)
+ {
+ ++p;
+ if (end)
+ while (p < end && (*p & 0xc0) == 0x80)
+ ++p;
+ else
+ while ((*p & 0xc0) == 0x80)
+ ++p;
+ }
+ return (p == end) ? NULL : p;
+}
+inline const char*
+utf8_find_next_char (const char *p,
+ const char *end)
+{
+ char *result = utf8_find_next_char (const_cast<char*> (p), const_cast<char*> (end));
+ return const_cast<const char*> (result);
+}
+inline bool
+utf8_validate (const char *str, // FIXME
+ ssize_t max_len,
+ const char **end)
+{
+ const char *p;
+ for (p = str; (max_len < 0 || (p - str) < max_len) && *p; p++)
+ {
+ if (*(uint8*) p < 128)
+ /* done */;
+ else
+ break;
+ }
+ if (end)
+ *end = p;
+ if ((max_len >= 0 && p != str + max_len) ||
+ (max_len < 0 && *p != '\0'))
+ return false;
+ else
+ return true;
+}
+
+static inline bool
+str_has_prefix (const String &str,
+ const String &prefix)
+{
+ uint sl = str.size();
+ uint pl = prefix.size();
+ if (pl <= sl && strncmp (str.c_str(), prefix.c_str(), pl) == 0)
+ return true;
+ return false;
+}
+
+static inline bool
+str_has_suffix (const String &str,
+ const String &suffix)
+{
+ uint sl = str.size();
+ uint xl = suffix.size();
+ if (xl <= sl && strcmp (str.c_str() + sl - xl, suffix.c_str()) == 0)
+ return true;
+ return false;
+}
+
+
+/* --- usefull type aliases --- */
+typedef MarkupParser::Error MarkupError;
+typedef MarkupParser::ErrorType MarkupErrorType;
+typedef MarkupParser::Context MarkupParserContext;
+
+typedef enum
+{
+ STATE_START,
+ STATE_AFTER_OPEN_ANGLE,
+ STATE_AFTER_CLOSE_ANGLE,
+ STATE_AFTER_ELISION_SLASH, /* the slash that obviates need for end element */
+ STATE_INSIDE_OPEN_TAG_NAME,
+ STATE_INSIDE_ATTRIBUTE_NAME,
+ STATE_AFTER_ATTRIBUTE_NAME,
+ STATE_BETWEEN_ATTRIBUTES,
+ STATE_AFTER_ATTRIBUTE_EQUALS_SIGN,
+ STATE_INSIDE_ATTRIBUTE_VALUE_SQ,
+ STATE_INSIDE_ATTRIBUTE_VALUE_DQ,
+ STATE_INSIDE_TEXT,
+ STATE_AFTER_CLOSE_TAG_SLASH,
+ STATE_INSIDE_CLOSE_TAG_NAME,
+ STATE_AFTER_CLOSE_TAG_NAME,
+ STATE_INSIDE_PASSTHROUGH,
+ STATE_ERROR
+} GMarkupParseState;
+
+struct MarkupParser::Context {
+ MarkupParser *parser;
+
+ int line_number;
+ int char_number;
+
+ /* A piece of character data or an element that
+ * hasn't "ended" yet so we haven't yet called
+ * the callback for it.
+ */
+ String partial_chunk;
+
+ GMarkupParseState state;
+ stack<String> tag_stack;
+ vector<string> attr_names;
+ vector<string> attr_values;
+
+ const char *current_text;
+ ssize_t current_text_len;
+ const char *current_text_end;
+
+ String leftover_char_portion;
+
+ /* used to save the start of the last interesting thingy */
+ const char *start;
+
+ const char *iter;
+
+ uint document_empty : 1;
+ uint parsing : 1;
+ int balance;
+};
+
+MarkupParser::MarkupParser () :
+ context (NULL)
+{
+ context = new MarkupParserContext;
+
+ context->parser = this;
+ context->line_number = 1;
+ context->char_number = 1;
+ context->state = STATE_START;
+ context->current_text = NULL;
+ context->current_text_len = -1;
+ context->current_text_end = NULL;
+ context->start = NULL;
+ context->iter = NULL;
+ context->document_empty = true;
+ context->parsing = false;
+ context->balance = 0;
+}
+
+MarkupParser*
+MarkupParser::create_parser()
+{
+ MarkupParser *parser = new MarkupParser;
+ return parser;
+}
+
+MarkupParser::~MarkupParser ()
+{
+ return_if_fail (context != NULL);
+ delete context;
+ context = NULL;
+}
+
+static void
+mark_error (MarkupParserContext *context,
+ const MarkupError &error)
+{
+ context->state = STATE_ERROR;
+ context->parser->error (error);
+}
+
+static void BIRNET_PRINTF (4, 5)
+ set_error (MarkupParserContext *context,
+ MarkupError &error,
+ MarkupErrorType code,
+ const char *format,
+ ...)
+{
+ va_list args;
+ va_start (args, format);
+ String msg;
+ try {
+ msg = string_vprintf (format, args);
+ } catch (...) {
+ msg = "out of memory";
+ }
+ va_end (args);
+ error.message = msg;
+ error.code = code;
+ error.line_number = context->line_number;
+ error.char_number = context->char_number;
+ mark_error (context, error);
+}
+
+
+/* To make these faster, we first use the ascii-only tests, then check
+ * for the usual non-alnum name-end chars, and only then call the
+ * expensive unicode stuff. Nobody uses non-ascii in XML tag/attribute
+ * names, so this is a reasonable hack that virtually always avoids
+ * the guniprop call.
+ */
+#define IS_COMMON_NAME_END_CHAR(c) \
+ ((c) == '=' || (c) == '/' || (c) == '>' || (c) == ' ')
+
+static bool
+is_name_start_char (const char *p)
+{
+ if ((*p >= 'A' && *p <= 'Z') ||
+ (*p >= 'a' && *p <= 'z') ||
+ (!IS_COMMON_NAME_END_CHAR (*p) &&
+ (*p == '_' ||
+ *p == ':' ||
+ unichar_isalpha (utf8_get_char (p)))))
+ return true;
+ else
+ return false;
+}
+
+static bool
+is_name_char (const char *p)
+{
+ if ((*p >= 'A' && *p <= 'Z') ||
+ (*p >= 'a' && *p <= 'z') ||
+ (*p >= '0' && *p <= '9') ||
+ (!IS_COMMON_NAME_END_CHAR (*p) &&
+ (*p == '.' ||
+ *p == '-' ||
+ *p == '_' ||
+ *p == ':' ||
+ unichar_isalpha (utf8_get_char (p)))))
+ return true;
+ else
+ return false;
+}
+
+
+static char *
+char_str (unichar c,
+ char *buf)
+{
+ memset (buf, 0, 8);
+ unichar_to_utf8 (c, buf);
+ return buf;
+}
+
+static char *
+utf8_str (const char *utf8,
+ char *buf)
+{
+ char_str (utf8_get_char (utf8), buf);
+ return buf;
+}
+
+static void
+set_unescape_error (MarkupParserContext *context,
+ MarkupError &error,
+ const char *remaining_text,
+ const char *remaining_text_end,
+ MarkupErrorType code,
+ const char *format,
+ ...)
+{
+ int remaining_newlines = 0;
+ const char *p = remaining_text;
+ while (p != remaining_text_end)
+ {
+ if (*p == '\n')
+ ++remaining_newlines;
+ ++p;
+ }
+
+ va_list args;
+ va_start (args, format);
+ String msg;
+ try {
+ msg = string_vprintf (format, args);
+ } catch (...) {
+ msg = "out of memory";
+ }
+ va_end (args);
+
+ error.message = msg;
+ error.code = code;
+ error.line_number = context->line_number - remaining_newlines;
+ error.char_number = 0; // context->char_number;
+ mark_error (context, error);
+}
+
+typedef enum {
+ USTATE_INSIDE_TEXT,
+ USTATE_AFTER_AMPERSAND,
+ USTATE_INSIDE_ENTITY_NAME,
+ USTATE_AFTER_CHARREF_HASH
+} UnescapeState;
+
+struct UnescapeContext {
+ MarkupParserContext *context;
+ String str;
+ UnescapeState state;
+ const char *text;
+ const char *text_end;
+ const char *entity_start;
+};
+
+static const char*
+unescape_text_state_inside_text (UnescapeContext *ucontext,
+ const char *p,
+ MarkupError &error)
+{
+ const char *start;
+ bool normalize_attribute;
+
+ if (ucontext->context->state == STATE_INSIDE_ATTRIBUTE_VALUE_SQ ||
+ ucontext->context->state == STATE_INSIDE_ATTRIBUTE_VALUE_DQ)
+ normalize_attribute = true;
+ else
+ normalize_attribute = false;
+
+ start = p;
+
+ while (p != ucontext->text_end)
+ {
+ if (*p == '&')
+ {
+ break;
+ }
+ else if (normalize_attribute && (*p == '\t' || *p == '\n'))
+ {
+ ucontext->str.append (start, p - start);
+ ucontext->str.append (" ");
+ p = utf8_next_char (p);
+ start = p;
+ }
+ else if (*p == '\r')
+ {
+ ucontext->str.append (start, p - start);
+ ucontext->str.append (normalize_attribute ? " " : "\n");
+ p = utf8_next_char (p);
+ if (p != ucontext->text_end && *p == '\n')
+ p = utf8_next_char (p);
+ start = p;
+ }
+ else
+ p = utf8_next_char (p);
+ }
+
+ if (p != start)
+ ucontext->str.append (start, p - start);
+
+ if (p != ucontext->text_end && *p == '&')
+ {
+ p = utf8_next_char (p);
+ ucontext->state = USTATE_AFTER_AMPERSAND;
+ }
+
+ return p;
+}
+
+static const char *
+unescape_text_state_after_ampersand (UnescapeContext *ucontext,
+ const char *p,
+ MarkupError &error)
+{
+ ucontext->entity_start = NULL;
+
+ if (*p == '#')
+ {
+ p = utf8_next_char (p);
+
+ ucontext->entity_start = p;
+ ucontext->state = USTATE_AFTER_CHARREF_HASH;
+ }
+ else if (!is_name_start_char (p))
+ {
+ if (*p == ';')
+ {
+ set_unescape_error (ucontext->context, error,
+ p, ucontext->text_end,
+ MarkupParser::PARSE_ERROR,
+ _("Empty entity '&;' seen; valid "
+ "entities are: &amp; &quot; &lt; &gt; &apos;"));
+ }
+ else
+ {
+ char buf[8];
+
+ set_unescape_error (ucontext->context, error,
+ p, ucontext->text_end,
+ MarkupParser::PARSE_ERROR,
+ _("Character '%s' is not valid at "
+ "the start of an entity name; "
+ "the & character begins an entity; "
+ "if this ampersand isn't supposed "
+ "to be an entity, escape it as "
+ "&amp;"),
+ utf8_str (p, buf));
+ }
+ }
+ else
+ {
+ ucontext->entity_start = p;
+ ucontext->state = USTATE_INSIDE_ENTITY_NAME;
+ }
+
+ return p;
+}
+
+static const char *
+unescape_text_state_inside_entity_name (UnescapeContext *ucontext,
+ const char *p,
+ MarkupError &error)
+{
+ while (p != ucontext->text_end)
+ {
+ if (*p == ';')
+ break;
+ else if (!is_name_char (p))
+ {
+ char ubuf[8];
+
+ set_unescape_error (ucontext->context, error,
+ p, ucontext->text_end,
+ MarkupParser::PARSE_ERROR,
+ _("Character '%s' is not valid "
+ "inside an entity name"),
+ utf8_str (p, ubuf));
+ break;
+ }
+
+ p = utf8_next_char (p);
+ }
+
+ if (ucontext->context->state != STATE_ERROR)
+ {
+ if (p != ucontext->text_end)
+ {
+ int len = p - ucontext->entity_start;
+
+ /* move to after semicolon */
+ p = utf8_next_char (p);
+ ucontext->state = USTATE_INSIDE_TEXT;
+
+ if (strncmp (ucontext->entity_start, "lt", len) == 0)
+ ucontext->str.append ("<");
+ else if (strncmp (ucontext->entity_start, "gt", len) == 0)
+ ucontext->str.append (">");
+ else if (strncmp (ucontext->entity_start, "amp", len) == 0)
+ ucontext->str.append ("&");
+ else if (strncmp (ucontext->entity_start, "quot", len) == 0)
+ ucontext->str.append ("\"");
+ else if (strncmp (ucontext->entity_start, "apos", len) == 0)
+ ucontext->str.append ("'");
+ else
+ {
+ String name;
+ name.append (ucontext->entity_start, len);
+ set_unescape_error (ucontext->context, error,
+ p, ucontext->text_end,
+ MarkupParser::PARSE_ERROR,
+ _("Entity name '%s' is not known"),
+ name.c_str());
+ }
+ }
+ else
+ {
+ set_unescape_error (ucontext->context, error,
+ /* give line number of the & */
+ ucontext->entity_start, ucontext->text_end,
+ MarkupParser::PARSE_ERROR,
+ _("Entity did not end with a semicolon; "
+ "most likely you used an ampersand "
+ "character without intending to start "
+ "an entity - escape ampersand as &amp;"));
+ }
+ }
+#undef MAX_ENT_LEN
+
+ return p;
+}
+
+static const char *
+unescape_text_state_after_charref_hash (UnescapeContext *ucontext,
+ const char *p,
+ MarkupError &error)
+{
+ bool is_hex = false;
+ const char *start;
+
+ start = ucontext->entity_start;
+
+ if (*p == 'x')
+ {
+ is_hex = true;
+ p = utf8_next_char (p);
+ start = p;
+ }
+
+ while (p != ucontext->text_end && *p != ';')
+ p = utf8_next_char (p);
+
+ if (p != ucontext->text_end)
+ {
+ assert (*p == ';');
+
+ /* digit is between start and p */
+
+ if (start != p)
+ {
+ char *end = NULL;
+ uint l;
+
+ errno = 0;
+ if (is_hex)
+ l = strtoul (start, &end, 16);
+ else
+ l = strtoul (start, &end, 10);
+
+ if (end != p || errno != 0)
+ {
+ set_unescape_error (ucontext->context, error,
+ start, ucontext->text_end,
+ MarkupParser::PARSE_ERROR,
+ _("Failed to parse '%-.*s', which "
+ "should have been a digit "
+ "inside a character reference "
+ "(&#234; for example) - perhaps "
+ "the digit is too large"),
+ p - start, start);
+ }
+ else
+ {
+ /* characters XML permits */
+ if (l == 0x9 ||
+ l == 0xA ||
+ l == 0xD ||
+ (l >= 0x20 && l <= 0xD7FF) ||
+ (l >= 0xE000 && l <= 0xFFFD) ||
+ (l >= 0x10000 && l <= 0x10FFFF))
+ {
+ char buf[8];
+ ucontext->str.append (char_str (l, buf));
+ }
+ else
+ {
+ set_unescape_error (ucontext->context, error,
+ start, ucontext->text_end,
+ MarkupParser::PARSE_ERROR,
+ _("Character reference '%-.*s' does not "
+ "encode a permitted character"),
+ p - start, start);
+ }
+ }
+
+ /* Move to next state */
+ p = utf8_next_char (p); /* past semicolon */
+ ucontext->state = USTATE_INSIDE_TEXT;
+ }
+ else
+ {
+ set_unescape_error (ucontext->context, error,
+ start, ucontext->text_end,
+ MarkupParser::PARSE_ERROR,
+ _("Empty character reference; "
+ "should include a digit such as "
+ "&#454;"));
+ }
+ }
+ else
+ {
+ set_unescape_error (ucontext->context, error,
+ start, ucontext->text_end,
+ MarkupParser::PARSE_ERROR,
+ _("Character reference did not end with a "
+ "semicolon; "
+ "most likely you used an ampersand "
+ "character without intending to start "
+ "an entity - escape ampersand as &amp;"));
+ }
+
+ return p;
+}
+
+static bool
+unescape_text (MarkupParserContext *context,
+ const char *text,
+ const char *text_end,
+ String *unescaped,
+ MarkupError &error)
+{
+ UnescapeContext ucontext;
+ const char *p;
+
+ ucontext.context = context;
+ ucontext.text = text;
+ ucontext.text_end = text_end;
+ ucontext.entity_start = NULL;
+
+ ucontext.state = USTATE_INSIDE_TEXT;
+ p = text;
+
+ while (p != text_end && context->state != STATE_ERROR)
+ {
+ assert (p < text_end);
+
+ switch (ucontext.state)
+ {
+ case USTATE_INSIDE_TEXT:
+ {
+ p = unescape_text_state_inside_text (&ucontext,
+ p,
+ error);
+ }
+ break;
+
+ case USTATE_AFTER_AMPERSAND:
+ {
+ p = unescape_text_state_after_ampersand (&ucontext,
+ p,
+ error);
+ }
+ break;
+
+
+ case USTATE_INSIDE_ENTITY_NAME:
+ {
+ p = unescape_text_state_inside_entity_name (&ucontext,
+ p,
+ error);
+ }
+ break;
+
+ case USTATE_AFTER_CHARREF_HASH:
+ {
+ p = unescape_text_state_after_charref_hash (&ucontext,
+ p,
+ error);
+ }
+ break;
+
+ default:
+ assert_not_reached ();
+ break;
+ }
+ }
+
+ if (context->state != STATE_ERROR)
+ {
+ switch (ucontext.state)
+ {
+ case USTATE_INSIDE_TEXT:
+ break;
+ case USTATE_AFTER_AMPERSAND:
+ case USTATE_INSIDE_ENTITY_NAME:
+ set_unescape_error (context, error,
+ NULL, NULL,
+ MarkupParser::PARSE_ERROR,
+ _("Unfinished entity reference"));
+ break;
+ case USTATE_AFTER_CHARREF_HASH:
+ set_unescape_error (context, error,
+ NULL, NULL,
+ MarkupParser::PARSE_ERROR,
+ _("Unfinished character reference"));
+ break;
+ }
+ }
+
+ if (context->state == STATE_ERROR)
+ {
+ *unescaped = "";
+ return false;
+ }
+ else
+ {
+ *unescaped = ucontext.str;
+ return true;
+ }
+}
+
+static inline bool
+advance_char (MarkupParserContext *context)
+{
+ context->iter = utf8_next_char (context->iter);
+ context->char_number += 1;
+
+ if (context->iter == context->current_text_end)
+ {
+ return false;
+ }
+ else if (*context->iter == '\n')
+ {
+ context->line_number += 1;
+ context->char_number = 1;
+ }
+
+ return true;
+}
+
+static inline bool
+xml_isspace (char c)
+{
+ return c == ' ' || c == '\t' || c == '\n' || c == '\r';
+}
+
+static void
+skip_spaces (MarkupParserContext *context)
+{
+ do
+ {
+ if (!xml_isspace (*context->iter))
+ return;
+ }
+ while (advance_char (context));
+}
+
+static void
+advance_to_name_end (MarkupParserContext *context)
+{
+ do
+ {
+ if (!is_name_char (context->iter))
+ return;
+ }
+ while (advance_char (context));
+}
+
+static void
+add_to_partial (MarkupParserContext *context,
+ const char *text_start,
+ const char *text_end)
+{
+ if (text_start != text_end)
+ context->partial_chunk.append (text_start, text_end - text_start);
+}
+
+static void
+truncate_partial (MarkupParserContext *context)
+{
+ context->partial_chunk = "";
+}
+
+static const char *
+current_element (MarkupParserContext *context)
+{
+ assert (!context->tag_stack.empty());
+ return context->tag_stack.top().c_str();
+}
+
+static String&
+last_attribute (MarkupParserContext *context)
+{
+ assert (context->attr_names.size() > 0);
+ return context->attr_names[context->attr_names.size() - 1];
+}
+
+static String&
+last_value (MarkupParserContext *context)
+{
+ assert (context->attr_values.size() > 0);
+ return context->attr_values[context->attr_values.size() - 1];
+}
+
+static void
+find_current_text_end (MarkupParserContext *context)
+{
+ /* This function must be safe (non-segfaulting) on invalid UTF8.
+ * It assumes the string starts with a character start
+ */
+ const char *end = context->current_text + context->current_text_len;
+ const char *p;
+ const char *next;
+
+ assert (context->current_text_len > 0);
+
+ p = utf8_find_prev_char (context->current_text, end);
+
+ assert (p != NULL); /* since current_text was a char start */
+
+ /* p is now the start of the last character or character portion. */
+ assert (p != end);
+ next = utf8_next_char (p); /* this only touches *p, nothing beyond */
+
+ if (next == end)
+ {
+ /* whole character */
+ context->current_text_end = end;
+ }
+ else
+ {
+ /* portion */
+ context->leftover_char_portion = "";
+ context->leftover_char_portion.append (p, end - p);
+ context->current_text_len -= (end - p);
+ context->current_text_end = p;
+ }
+}
+
+
+static void
+add_attribute (MarkupParserContext *context, const char *name)
+{
+ context->attr_names.push_back (name);
+ context->attr_values.push_back ("");
+}
+bool
+MarkupParser::parse (const char *text,
+ ssize_t text_len,
+ MarkupError *errorp)
+{
+ Error dummy, &error = errorp ? *errorp : dummy;
+ const char *first_invalid;
+
+ return_val_if_fail (context != NULL, false);
+ return_val_if_fail (text != NULL, false);
+ return_val_if_fail (context->state != STATE_ERROR, false);
+ return_val_if_fail (!context->parsing, false);
+
+ if (text_len < 0)
+ text_len = strlen (text);
+
+ if (text_len == 0)
+ return true;
+
+ context->parsing = true;
+
+ if (context->leftover_char_portion.size())
+ {
+ const char *first_char;
+
+ if ((*text & 0xc0) != 0x80)
+ first_char = text;
+ else
+ first_char = utf8_find_next_char (text, text + text_len);
+
+ if (first_char)
+ {
+ /* leftover_char_portion was completed. Parse it. */
+ String portion = context->leftover_char_portion;
+ portion.append (text, first_char - text);
+
+ /* hacks to allow recursion */
+ context->parsing = false;
+ context->leftover_char_portion = "";
+ if (!parse (portion.c_str(), portion.size(), &error))
+ assert (context->state == STATE_ERROR);
+ context->parsing = true;
+
+ /* Skip the fraction of char that was in this text */
+ text_len -= (first_char - text);
+ text = first_char;
+ }
+ else
+ {
+ /* another little chunk of the leftover char; geez
+ * someone is inefficient.
+ */
+ context->leftover_char_portion.append (text, text_len);
+ if (context->leftover_char_portion.size() > 7)
+ {
+ /* The leftover char portion is too big to be
+ * a UTF-8 character
+ */
+ set_error (context, error, MarkupParser::BAD_UTF8, _("Invalid UTF-8 encoded text"));
+ }
+ goto finished;
+ }
+ }
+
+ context->current_text = text;
+ context->current_text_len = text_len;
+ context->iter = context->current_text;
+ context->start = context->iter;
+
+ /* Nothing left after finishing the leftover char, or nothing
+ * passed in to begin with.
+ */
+ if (context->current_text_len == 0)
+ goto finished;
+
+ /* find_current_text_end () assumes the string starts at
+ * a character start, so we need to validate at least
+ * that much. It doesn't assume any following bytes
+ * are valid.
+ */
+ if ((*context->current_text & 0xc0) == 0x80) /* not a char start */
+ {
+ set_error (context, error, MarkupParser::BAD_UTF8, _("Invalid UTF-8 encoded text"));
+ goto finished;
+ }
+
+ /* Initialize context->current_text_end, possibly adjusting
+ * current_text_len, and add any leftover char portion
+ */
+ find_current_text_end (context);
+
+ /* Validate UTF8 (must be done after we find the end, since
+ * we could have a trailing incomplete char)
+ */
+ if (!utf8_validate (context->current_text,
+ context->current_text_len,
+ &first_invalid))
+ {
+ int newlines = 0;
+ const char *p;
+ p = context->current_text;
+ while (p != context->current_text_end)
+ {
+ if (*p == '\n')
+ ++newlines;
+ ++p;
+ }
+
+ context->line_number += newlines;
+
+ set_error (context, error, MarkupParser::BAD_UTF8, _("Invalid UTF-8 encoded text"));
+ goto finished;
+ }
+
+ while (context->iter != context->current_text_end)
+ {
+ switch (context->state)
+ {
+ case STATE_START:
+ /* Possible next state: AFTER_OPEN_ANGLE */
+
+ assert (context->tag_stack.empty());
+
+ /* whitespace is ignored outside of any elements */
+ skip_spaces (context);
+
+ if (context->iter != context->current_text_end)
+ {
+ if (*context->iter == '<')
+ {
+ /* Move after the open angle */
+ advance_char (context);
+
+ context->state = STATE_AFTER_OPEN_ANGLE;
+
+ /* this could start a passthrough */
+ context->start = context->iter;
+
+ /* document is now non-empty */
+ context->document_empty = false;
+ }
+ else
+ {
+ set_error (context, error, MarkupParser::PARSE_ERROR, _("Document must begin with an element (e.g. <book>)"));
+ }
+ }
+ break;
+
+ case STATE_AFTER_OPEN_ANGLE:
+ /* Possible next states: INSIDE_OPEN_TAG_NAME,
+ * AFTER_CLOSE_TAG_SLASH, INSIDE_PASSTHROUGH
+ */
+ if (*context->iter == '?' ||
+ *context->iter == '!')
+ {
+ /* include < in the passthrough */
+ const char *openangle = "<";
+ add_to_partial (context, openangle, openangle + 1);
+ context->start = context->iter;
+ context->balance = 1;
+ context->state = STATE_INSIDE_PASSTHROUGH;
+ }
+ else if (*context->iter == '/')
+ {
+ /* move after it */
+ advance_char (context);
+
+ context->state = STATE_AFTER_CLOSE_TAG_SLASH;
+ }
+ else if (is_name_start_char (context->iter))
+ {
+ context->state = STATE_INSIDE_OPEN_TAG_NAME;
+
+ /* start of tag name */
+ context->start = context->iter;
+ }
+ else
+ {
+ char buf[8];
+
+ set_error (context, error, MarkupParser::PARSE_ERROR, _("'%s' is not a valid character following "
+ "a '<' character; it may not begin an "
+ "element name"),
+ utf8_str (context->iter, buf));
+ }
+ break;
+
+ /* The AFTER_CLOSE_ANGLE state is actually sort of
+ * broken, because it doesn't correspond to a range
+ * of characters in the input stream as the others do,
+ * and thus makes things harder to conceptualize
+ */
+ case STATE_AFTER_CLOSE_ANGLE:
+ /* Possible next states: INSIDE_TEXT, STATE_START */
+ if (context->tag_stack.empty())
+ {
+ context->start = NULL;
+ context->state = STATE_START;
+ }
+ else
+ {
+ context->start = context->iter;
+ context->state = STATE_INSIDE_TEXT;
+ }
+ break;
+
+ case STATE_AFTER_ELISION_SLASH:
+ /* Possible next state: AFTER_CLOSE_ANGLE */
+
+ {
+ /* We need to pop the tag stack and call the end_element
+ * function, since this is the close tag
+ */
+ assert (!context->tag_stack.empty());
+ error.line_number = context->line_number;
+ error.char_number = context->char_number;
+ context->parser->end_element (context->tag_stack.top(), error);
+ if (error.code)
+ mark_error (context, error);
+ else
+ {
+ if (*context->iter == '>')
+ {
+ /* move after the close angle */
+ advance_char (context);
+ context->state = STATE_AFTER_CLOSE_ANGLE;
+ }
+ else
+ {
+ char buf[8];
+ set_error (context, error, MarkupParser::PARSE_ERROR,
+ _("Odd character '%s', expected a '>' character "
+ "to end the start tag of element '%s'"),
+ utf8_str (context->iter, buf),
+ current_element (context));
+ }
+ }
+
+ context->tag_stack.pop();
+ }
+ break;
+
+ case STATE_INSIDE_OPEN_TAG_NAME:
+ /* Possible next states: BETWEEN_ATTRIBUTES */
+
+ /* if there's a partial chunk then it's the first part of the
+ * tag name. If there's a context->start then it's the start
+ * of the tag name in current_text, the partial chunk goes
+ * before that start though.
+ */
+ advance_to_name_end (context);
+
+ if (context->iter == context->current_text_end)
+ {
+ /* The name hasn't necessarily ended. Merge with
+ * partial chunk, leave state unchanged.
+ */
+ add_to_partial (context, context->start, context->iter);
+ }
+ else
+ {
+ /* The name has ended. Combine it with the partial chunk
+ * if any; push it on the stack; enter next state.
+ */
+ add_to_partial (context, context->start, context->iter);
+ context->tag_stack.push (context->partial_chunk);
+ context->partial_chunk = "";
+
+ context->state = STATE_BETWEEN_ATTRIBUTES;
+ context->start = NULL;
+ }
+ break;
+
+ case STATE_INSIDE_ATTRIBUTE_NAME:
+ /* Possible next states: AFTER_ATTRIBUTE_NAME */
+
+ advance_to_name_end (context);
+ add_to_partial (context, context->start, context->iter);
+
+ /* read the full name, if we enter the equals sign state
+ * then add the attribute to the list (without the value),
+ * otherwise store a partial chunk to be prepended later.
+ */
+ if (context->iter != context->current_text_end)
+ context->state = STATE_AFTER_ATTRIBUTE_NAME;
+ break;
+
+ case STATE_AFTER_ATTRIBUTE_NAME:
+ /* Possible next states: AFTER_ATTRIBUTE_EQUALS_SIGN */
+
+ skip_spaces (context);
+
+ if (context->iter != context->current_text_end)
+ {
+ /* The name has ended. Combine it with the partial chunk
+ * if any; push it on the stack; enter next state.
+ */
+ add_attribute (context, context->partial_chunk.c_str());
+ context->partial_chunk = "";
+ context->start = NULL;
+
+ if (*context->iter == '=')
+ {
+ advance_char (context);
+ context->state = STATE_AFTER_ATTRIBUTE_EQUALS_SIGN;
+ }
+ else
+ {
+ char buf[8];
+ set_error (context, error, MarkupParser::PARSE_ERROR,
+ _("Odd character '%s', expected a '=' after "
+ "attribute name '%s' of element '%s'"),
+ utf8_str (context->iter, buf),
+ last_attribute (context).c_str(),
+ current_element (context));
+
+ }
+ }
+ break;
+
+ case STATE_BETWEEN_ATTRIBUTES:
+ /* Possible next states: AFTER_CLOSE_ANGLE,
+ * AFTER_ELISION_SLASH, INSIDE_ATTRIBUTE_NAME
+ */
+ skip_spaces (context);
+
+ if (context->iter != context->current_text_end)
+ {
+ if (*context->iter == '/')
+ {
+ advance_char (context);
+ context->state = STATE_AFTER_ELISION_SLASH;
+ }
+ else if (*context->iter == '>')
+ {
+
+ advance_char (context);
+ context->state = STATE_AFTER_CLOSE_ANGLE;
+ }
+ else if (is_name_start_char (context->iter))
+ {
+ context->state = STATE_INSIDE_ATTRIBUTE_NAME;
+ /* start of attribute name */
+ context->start = context->iter;
+ }
+ else
+ {
+ char buf[8];
+ set_error (context, error, MarkupParser::PARSE_ERROR,
+ _("Odd character '%s', expected a '>' or '/' "
+ "character to end the start tag of "
+ "element '%s', or optionally an attribute; "
+ "perhaps you used an invalid character in "
+ "an attribute name"),
+ utf8_str (context->iter, buf),
+ current_element (context));
+ }
+
+ /* If we're done with attributes, invoke
+ * the start_element callback
+ */
+ if (context->state == STATE_AFTER_ELISION_SLASH ||
+ context->state == STATE_AFTER_CLOSE_ANGLE)
+ {
+ /* Call user callback for element start */
+ error.line_number = context->line_number;
+ error.char_number = context->char_number;
+ context->parser->start_element (current_element (context),
+ context->attr_names,
+ context->attr_values,
+ error);
+ /* Go ahead and free the attributes. */
+ context->attr_names.clear();
+ context->attr_values.clear();
+ if (error.code)
+ {
+ mark_error (context, error);
+ }
+ }
+ }
+ break;
+
+ case STATE_AFTER_ATTRIBUTE_EQUALS_SIGN:
+ /* Possible next state: INSIDE_ATTRIBUTE_VALUE_[SQ/DQ] */
+
+ skip_spaces (context);
+
+ if (context->iter != context->current_text_end)
+ {
+ if (*context->iter == '"')
+ {
+ advance_char (context);
+ context->state = STATE_INSIDE_ATTRIBUTE_VALUE_DQ;
+ context->start = context->iter;
+ }
+ else if (*context->iter == '\'')
+ {
+ advance_char (context);
+ context->state = STATE_INSIDE_ATTRIBUTE_VALUE_SQ;
+ context->start = context->iter;
+ }
+ else
+ {
+ char buf[8];
+ set_error (context, error, MarkupParser::PARSE_ERROR,
+ _("Odd character '%s', expected an open quote mark "
+ "after the equals sign when giving value for "
+ "attribute '%s' of element '%s'"),
+ utf8_str (context->iter, buf),
+ last_attribute (context).c_str(),
+ current_element (context));
+ }
+ }
+ break;
+
+ case STATE_INSIDE_ATTRIBUTE_VALUE_SQ:
+ case STATE_INSIDE_ATTRIBUTE_VALUE_DQ:
+ /* Possible next states: BETWEEN_ATTRIBUTES */
+ {
+ char delim;
+
+ if (context->state == STATE_INSIDE_ATTRIBUTE_VALUE_SQ)
+ {
+ delim = '\'';
+ }
+ else
+ {
+ delim = '"';
+ }
+
+ do
+ {
+ if (*context->iter == delim)
+ break;
+ }
+ while (advance_char (context));
+ }
+ if (context->iter == context->current_text_end)
+ {
+ /* The value hasn't necessarily ended. Merge with
+ * partial chunk, leave state unchanged.
+ */
+ add_to_partial (context, context->start, context->iter);
+ }
+ else
+ {
+ /* The value has ended at the quote mark. Combine it
+ * with the partial chunk if any; set it for the current
+ * attribute.
+ */
+ add_to_partial (context, context->start, context->iter);
+ String unescaped;
+ if (unescape_text (context,
+ context->partial_chunk.c_str(),
+ context->partial_chunk.c_str() + context->partial_chunk.size(),
+ &unescaped,
+ error))
+ {
+ /* success, advance past quote and set state. */
+ last_value (context) = unescaped;
+ advance_char (context);
+ context->state = STATE_BETWEEN_ATTRIBUTES;
+ context->start = NULL;
+ }
+
+ truncate_partial (context);
+ }
+ break;
+
+ case STATE_INSIDE_TEXT:
+ /* Possible next states: AFTER_OPEN_ANGLE */
+ do
+ {
+ if (*context->iter == '<')
+ break;
+ }
+ while (advance_char (context));
+
+ /* The text hasn't necessarily ended. Merge with
+ * partial chunk, leave state unchanged.
+ */
+
+ add_to_partial (context, context->start, context->iter);
+
+ if (context->iter != context->current_text_end)
+ {
+ /* The text has ended at the open angle. Call the text
+ * callback.
+ */
+ String unescaped;
+ if (unescape_text (context,
+ context->partial_chunk.c_str(),
+ context->partial_chunk.c_str() + context->partial_chunk.size(),
+ &unescaped,
+ error))
+ {
+ error.line_number = context->line_number;
+ error.char_number = context->char_number;
+ context->parser->text (unescaped, error);
+ if (!error.code)
+ {
+ /* advance past open angle and set state. */
+ advance_char (context);
+ context->state = STATE_AFTER_OPEN_ANGLE;
+ /* could begin a passthrough */
+ context->start = context->iter;
+ }
+ else
+ {
+ mark_error (context, error);
+ }
+ }
+
+ truncate_partial (context);
+ }
+ break;
+
+ case STATE_AFTER_CLOSE_TAG_SLASH:
+ /* Possible next state: INSIDE_CLOSE_TAG_NAME */
+ if (is_name_start_char (context->iter))
+ {
+ context->state = STATE_INSIDE_CLOSE_TAG_NAME;
+
+ /* start of tag name */
+ context->start = context->iter;
+ }
+ else
+ {
+ char buf[8];
+ set_error (context, error, MarkupParser::PARSE_ERROR,
+ _("'%s' is not a valid character following "
+ "the characters '</'; '%s' may not begin an "
+ "element name"),
+ utf8_str (context->iter, buf),
+ utf8_str (context->iter, buf));
+ }
+ break;
+
+ case STATE_INSIDE_CLOSE_TAG_NAME:
+ /* Possible next state: AFTER_CLOSE_TAG_NAME */
+ advance_to_name_end (context);
+ add_to_partial (context, context->start, context->iter);
+
+ if (context->iter != context->current_text_end)
+ context->state = STATE_AFTER_CLOSE_TAG_NAME;
+ break;
+
+ case STATE_AFTER_CLOSE_TAG_NAME:
+ /* Possible next state: AFTER_CLOSE_TAG_SLASH */
+
+ skip_spaces (context);
+
+ if (context->iter != context->current_text_end)
+ {
+ String close_name;
+
+ /* The name has ended. Combine it with the partial chunk
+ * if any; check that it matches stack top and pop
+ * stack; invoke proper callback; enter next state.
+ */
+ close_name = context->partial_chunk;
+ context->partial_chunk = "";
+
+ if (*context->iter != '>')
+ {
+ char buf[8];
+ set_error (context, error, MarkupParser::PARSE_ERROR,
+ _("'%s' is not a valid character following "
+ "the close element name '%s'; the allowed "
+ "character is '>'"),
+ utf8_str (context->iter, buf),
+ close_name.c_str());
+ }
+ else if (context->tag_stack.empty())
+ {
+ set_error (context, error, MarkupParser::PARSE_ERROR,
+ _("Element '%s' was closed, no element "
+ "is currently open"),
+ close_name.c_str());
+ }
+ else if (close_name != current_element (context))
+ {
+ set_error (context, error, MarkupParser::PARSE_ERROR,
+ _("Element '%s' was closed, but the currently "
+ "open element is '%s'"),
+ close_name.c_str(),
+ current_element (context));
+ }
+ else
+ {
+ advance_char (context);
+ context->state = STATE_AFTER_CLOSE_ANGLE;
+ context->start = NULL;
+
+ /* call the end_element callback */
+ error.line_number = context->line_number;
+ error.char_number = context->char_number;
+ context->parser->end_element (close_name, error);
+
+
+ /* Pop the tag stack */
+ context->tag_stack.pop();
+ if (error.code)
+ {
+ mark_error (context, error);
+ }
+ }
+ }
+ break;
+
+ case STATE_INSIDE_PASSTHROUGH:
+ /* Possible next state: AFTER_CLOSE_ANGLE */
+ do
+ {
+ if (*context->iter == '<')
+ context->balance++;
+ if (*context->iter == '>')
+ {
+ context->balance--;
+ add_to_partial (context, context->start, context->iter);
+ context->start = context->iter;
+ if ((str_has_prefix (context->partial_chunk, "<?")
+ && str_has_suffix (context->partial_chunk, "?")) ||
+ (str_has_prefix (context->partial_chunk, "<!--")
+ && str_has_suffix (context->partial_chunk, "--")) ||
+ (str_has_prefix (context->partial_chunk, "<![CDATA[")
+ && str_has_suffix (context->partial_chunk, "]]")) ||
+ (str_has_prefix (context->partial_chunk, "<!DOCTYPE")
+ && context->balance == 0))
+ break;
+ }
+ }
+ while (advance_char (context));
+
+ if (context->iter == context->current_text_end)
+ {
+ /* The passthrough hasn't necessarily ended. Merge with
+ * partial chunk, leave state unchanged.
+ */
+ add_to_partial (context, context->start, context->iter);
+ }
+ else
+ {
+ /* The passthrough has ended at the close angle. Combine
+ * it with the partial chunk if any. Call the passthrough
+ * callback. Note that the open/close angles are
+ * included in the text of the passthrough.
+ */
+ advance_char (context); /* advance past close angle */
+ add_to_partial (context, context->start, context->iter);
+
+ error.line_number = context->line_number;
+ error.char_number = context->char_number;
+ context->parser->pass_through (context->partial_chunk, error);
+
+ truncate_partial (context);
+
+ if (!error.code)
+ {
+ context->state = STATE_AFTER_CLOSE_ANGLE;
+ context->start = context->iter; /* could begin text */
+ }
+ else
+ {
+ mark_error (context, error);
+ }
+ }
+ break;
+
+ case STATE_ERROR:
+ goto finished;
+ break;
+
+ default:
+ assert_not_reached ();
+ break;
+ }
+ }
+
+ finished:
+ context->parsing = false;
+
+ return context->state != STATE_ERROR;
+}
+
+bool
+MarkupParser::end_parse (Error *errorp)
+{
+ Error dummy, &error = errorp ? *errorp : dummy;
+ return_val_if_fail (context != NULL, false);
+ return_val_if_fail (!context->parsing, false);
+ return_val_if_fail (context->state != STATE_ERROR, false);
+
+ context->partial_chunk = "";
+
+ if (context->document_empty)
+ {
+ set_error (context, error, MarkupParser::DOCUMENT_EMPTY, _("Document was empty or contained only whitespace"));
+ return false;
+ }
+
+ context->parsing = true;
+
+ switch (context->state)
+ {
+ case STATE_START:
+ /* Nothing to do */
+ break;
+
+ case STATE_AFTER_OPEN_ANGLE:
+ set_error (context, error, MarkupParser::PARSE_ERROR, _("Document ended unexpectedly just after an open angle bracket '<'"));
+ break;
+
+ case STATE_AFTER_CLOSE_ANGLE:
+ if (!context->tag_stack.empty())
+ {
+ /* Error message the same as for INSIDE_TEXT */
+ set_error (context, error, MarkupParser::PARSE_ERROR,
+ _("Document ended unexpectedly with elements still open - "
+ "'%s' was the last element opened"),
+ current_element (context));
+ }
+ break;
+
+ case STATE_AFTER_ELISION_SLASH:
+ set_error (context, error, MarkupParser::PARSE_ERROR,
+ _("Document ended unexpectedly, expected to see a close angle "
+ "bracket ending the tag <%s/>"), current_element (context));
+ break;
+
+ case STATE_INSIDE_OPEN_TAG_NAME:
+ set_error (context, error, MarkupParser::PARSE_ERROR,
+ _("Document ended unexpectedly inside an element name"));
+ break;
+
+ case STATE_INSIDE_ATTRIBUTE_NAME:
+ set_error (context, error, MarkupParser::PARSE_ERROR,
+ _("Document ended unexpectedly inside an attribute name"));
+ break;
+
+ case STATE_BETWEEN_ATTRIBUTES:
+ set_error (context, error, MarkupParser::PARSE_ERROR,
+ _("Document ended unexpectedly inside an element-opening "
+ "tag."));
+ break;
+
+ case STATE_AFTER_ATTRIBUTE_EQUALS_SIGN:
+ set_error (context, error, MarkupParser::PARSE_ERROR,
+ _("Document ended unexpectedly after the equals sign "
+ "following an attribute name; no attribute value"));
+ break;
+
+ case STATE_INSIDE_ATTRIBUTE_VALUE_SQ:
+ case STATE_INSIDE_ATTRIBUTE_VALUE_DQ:
+ set_error (context, error, MarkupParser::PARSE_ERROR,
+ _("Document ended unexpectedly while inside an attribute "
+ "value"));
+ break;
+
+ case STATE_INSIDE_TEXT:
+ assert (!context->tag_stack.empty());
+ set_error (context, error, MarkupParser::PARSE_ERROR,
+ _("Document ended unexpectedly with elements still open - "
+ "'%s' was the last element opened"),
+ current_element (context));
+ break;
+
+ case STATE_AFTER_CLOSE_TAG_SLASH:
+ case STATE_INSIDE_CLOSE_TAG_NAME:
+ set_error (context, error, MarkupParser::PARSE_ERROR,
+ _("Document ended unexpectedly inside the close tag for element '%s'"), current_element (context));
+ break;
+
+ case STATE_INSIDE_PASSTHROUGH:
+ set_error (context, error, MarkupParser::PARSE_ERROR,
+ _("Document ended unexpectedly inside a comment or "
+ "processing instruction"));
+ break;
+
+ case STATE_ERROR:
+ default:
+ assert_not_reached ();
+ break;
+ }
+
+ context->parsing = false;
+
+ return context->state != STATE_ERROR;
+}
+
+String
+MarkupParser::get_element()
+{
+ return_val_if_fail (context != NULL, NULL);
+ if (context->tag_stack.empty())
+ return NULL;
+ else
+ return current_element (context);
+}
+
+void
+MarkupParser::get_position (int *line_number,
+ int *char_number)
+{
+ /* For user-constructed error messages, has no precise semantics */
+ return_if_fail (context != NULL);
+ if (line_number)
+ *line_number = context->line_number;
+ if (char_number)
+ *char_number = context->char_number;
+}
+
+void
+MarkupParser::start_element (const String &element_name,
+ ConstStrings &attribute_names,
+ ConstStrings &attribute_values,
+ Error &error)
+{
+ /* Called for open tags <foo bar="baz"> */
+}
+
+void
+MarkupParser::end_element (const String &element_name,
+ Error &error)
+{
+ /* Called for close tags </foo> */
+}
+
+void
+MarkupParser::text (const String &text,
+ Error &error)
+{
+ /* Called for character data */
+}
+
+void
+MarkupParser::pass_through (const String &pass_through_text,
+ Error &error)
+{
+ /* Called for strings that should be re-saved verbatim in this same
+ * position, but are not otherwise interpretable. At the moment
+ * this includes comments and processing instructions.
+ */
+}
+
+void
+MarkupParser::error (const Error &error)
+{
+ /* Called on error, including one set by other
+ * methods in the vtable. The MarkupParserBase::Error should not be freed.
+ */
+}
+
+static void
+append_escaped_text (String &str,
+ const char *text,
+ ssize_t length)
+{
+ const char *p = text;
+ const char *end = text + length;
+ while (p != end)
+ {
+ const char *next = utf8_next_char (p);
+ switch (*p)
+ {
+ case '&':
+ str += "&amp;";
+ break;
+ case '<':
+ str += "&lt;";
+ break;
+ case '>':
+ str += "&gt;";
+ break;
+ case '\'':
+ str += "&apos;";
+ break;
+ case '"':
+ str += "&quot;";
+ break;
+ default:
+ str.append (p, next - p);
+ break;
+ }
+ p = next;
+ }
+}
+
+/**
+ * g_markup_escape_text:
+ * @text: some valid UTF-8 text
+ * @length: length of @text in bytes
+ *
+ * Escapes text so that the markup parser will parse it verbatim.
+ * Less than, greater than, ampersand, etc. are replaced with the
+ * corresponding entities. This function would typically be used
+ * when writing out a file to be parsed with the markup parser.
+ *
+ * Note that this function doesn't protect whitespace and line endings
+ * from being processed according to the XML rules for normalization
+ * of line endings and attribute values.
+ *
+ * Return value: escaped text
+ **/
+String
+MarkupParser::escape_text (const char *text,
+ ssize_t length)
+{
+ return_val_if_fail (text != NULL, NULL);
+
+ if (length < 0)
+ length = strlen (text);
+
+ /* prealloc at least as long as original text */
+ String str;
+ append_escaped_text (str, text, length);
+
+ return str;
+}
+
+/**
+ * find_conversion:
+ * @format: a printf-style format string
+ * @after: location to store a pointer to the character after
+ * the returned conversion. On a %NULL return, returns the
+ * pointer to the trailing NUL in the string
+ *
+ * Find the next conversion in a printf-style format string.
+ * Partially based on code from printf-parser.c,
+ * Copyright (C) 1999-2000, 2002-2003 Free Software Foundation, Inc.
+ *
+ * Return value: pointer to the next conversion in @format,
+ * or %NULL, if none.
+ **/
+static const char *
+find_conversion (const char *format,
+ const char **after)
+{
+ const char *start = format;
+ const char *cp;
+
+ while (*start != '\0' && *start != '%')
+ start++;
+
+ if (*start == '\0')
+ {
+ *after = start;
+ return NULL;
+ }
+
+ cp = start + 1;
+
+ if (*cp == '\0')
+ {
+ *after = cp;
+ return NULL;
+ }
+
+ /* Test for positional argument. */
+ if (*cp >= '0' && *cp <= '9')
+ {
+ const char *np;
+
+ for (np = cp; *np >= '0' && *np <= '9'; np++)
+ ;
+ if (*np == '$')
+ cp = np + 1;
+ }
+
+ /* Skip the flags. */
+ for (;;)
+ {
+ if (*cp == '\'' ||
+ *cp == '-' ||
+ *cp == '+' ||
+ *cp == ' ' ||
+ *cp == '#' ||
+ *cp == '0')
+ cp++;
+ else
+ break;
+ }
+
+ /* Skip the field width. */
+ if (*cp == '*')
+ {
+ cp++;
+
+ /* Test for positional argument. */
+ if (*cp >= '0' && *cp <= '9')
+ {
+ const char *np;
+
+ for (np = cp; *np >= '0' && *np <= '9'; np++)
+ ;
+ if (*np == '$')
+ cp = np + 1;
+ }
+ }
+ else
+ {
+ for (; *cp >= '0' && *cp <= '9'; cp++)
+ ;
+ }
+
+ /* Skip the precision. */
+ if (*cp == '.')
+ {
+ cp++;
+ if (*cp == '*')
+ {
+ /* Test for positional argument. */
+ if (*cp >= '0' && *cp <= '9')
+ {
+ const char *np;
+
+ for (np = cp; *np >= '0' && *np <= '9'; np++)
+ ;
+ if (*np == '$')
+ cp = np + 1;
+ }
+ }
+ else
+ {
+ for (; *cp >= '0' && *cp <= '9'; cp++)
+ ;
+ }
+ }
+
+ /* Skip argument type/size specifiers. */
+ while (*cp == 'h' ||
+ *cp == 'L' ||
+ *cp == 'l' ||
+ *cp == 'j' ||
+ *cp == 'z' ||
+ *cp == 'Z' ||
+ *cp == 't')
+ cp++;
+
+ /* Skip the conversion character. */
+ cp++;
+
+ *after = cp;
+ return start;
+}
+
+String
+MarkupParser::vprintf_escaped (const char *format,
+ va_list args)
+{
+ /* The technique here, is that we make two format strings that
+ * have the identical conversions in the identical order to the
+ * original strings, but differ in the text in-between. We
+ * then use the normal g_strdup_vprintf() to format the arguments
+ * with the two new format strings. By comparing the results,
+ * we can figure out what segments of the output come from
+ * the the original format string, and what from the arguments,
+ * and thus know what portions of the string to escape.
+ *
+ * For instance, for:
+ *
+ * g_markup_printf_escaped ("%s ate %d apples", "Susan & Fred", 5);
+ *
+ * We form the two format strings "%sX%dX" and %sY%sY". The results
+ * of formatting with those two strings are
+ *
+ * "%sX%dX" => "Susan & FredX5X"
+ * "%sY%dY" => "Susan & FredY5Y"
+ *
+ * To find the span of the first argument, we find the first position
+ * where the two arguments differ, which tells us that the first
+ * argument formatted to "Susan & Fred". We then escape that
+ * to "Susan &amp; Fred" and join up with the intermediate portions
+ * of the format string and the second argument to get
+ * "Susan &amp; Fred ate 5 apples".
+ */
+
+ /* Create the two modified format strings
+ */
+ String format1, format2;
+ const char *p = format;
+ while (true)
+ {
+ const char *after;
+ const char *conv = find_conversion (p, &after);
+ if (!conv)
+ break;
+
+ format1.append (conv, after - conv);
+ format1.append ("X");
+ format2.append (conv, after - conv);
+ format2.append ("Y");
+ p = after;
+ }
+
+ /* Use them to format the arguments
+ */
+ va_list args2;
+ va_copy (args2, args);
+
+ String output1, output2, result;
+ try {
+ output1 = string_vprintf (format1.c_str(), args);
+ output2 = string_vprintf (format2.c_str(), args2);
+ } catch (...) {
+ /* cleanup */
+ va_end (args);
+ va_end (args2);
+ throw; /* rethrow */
+ }
+ va_end (args);
+ va_end (args2);
+
+ /* Iterate through the original format string again,
+ * copying the non-conversion portions and the escaped
+ * converted arguments to the output string.
+ */
+ const char *op1 = output1.c_str();
+ const char *op2 = output2.c_str();
+ p = format;
+ while (true)
+ {
+ const char *after;
+ const char *output_start;
+ const char *conv = find_conversion (p, &after);
+
+ if (!conv) /* The end, after points to the trailing \0 */
+ {
+ result.append (p, after - p);
+ break;
+ }
+
+ result.append (p, conv - p);
+ output_start = op1;
+ while (*op1 == *op2)
+ {
+ op1++;
+ op2++;
+ }
+
+ String escaped = escape_text (output_start, op1 - output_start);
+ result.append (escaped);
+
+ p = after;
+ op1++;
+ op2++;
+ }
+
+ return result;
+}
+
+String
+MarkupParser::printf_escaped (const char *format,
+ ...)
+{
+ String result;
+ va_list args;
+ va_start (args, format);
+ try {
+ result = string_vprintf (format, args);
+ } catch (...) {
+ /* cleanup */
+ va_end (args);
+ throw; /* rethrow */
+ }
+ va_end (args);
+ return result;
+}
+
+} // Birnet
diff --git a/rapicorn/birnetmarkup.hh b/rapicorn/birnetmarkup.hh
new file mode 100644
index 0000000..c6dcdda
--- a/dev/null
+++ b/rapicorn/birnetmarkup.hh
@@ -0,0 +1,83 @@
+/* BirnetMarkup - simple XML parser
+ * Copyright (C) 2005 Tim Janik
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+#ifndef __BIRNET_MARKUP_HH__
+#define __BIRNET_MARKUP_HH__
+
+#include <rapicorn/birnetutils.hh>
+
+namespace Birnet {
+
+class MarkupParser {
+public:
+ typedef enum {
+ NONE = 0,
+ READ_FAILED,
+ BAD_UTF8,
+ DOCUMENT_EMPTY,
+ PARSE_ERROR,
+ /* client errors */
+ INVALID_CONTENT
+ } ErrorType;
+ struct Error {
+ ErrorType code;
+ String message;
+ uint line_number;
+ uint char_number;
+ explicit Error() : code (NONE), line_number (0), char_number (0) {}
+ void set (ErrorType c, String msg) { code = c; message = msg; }
+ bool set () { return code != NONE; }
+ };
+ typedef const vector<String> ConstStrings;
+ static MarkupParser* create_parser ();
+ virtual ~MarkupParser ();
+ bool parse (const char *text,
+ ssize_t text_len,
+ Error *error);
+ bool end_parse (Error *error);
+ String get_element ();
+ void get_position (int *line_number,
+ int *char_number);
+ virtual void start_element (const String &element_name,
+ ConstStrings &attribute_names,
+ ConstStrings &attribute_values,
+ Error &error);
+ virtual void end_element (const String &element_name,
+ Error &error);
+ virtual void text (const String &text,
+ Error &error);
+ virtual void pass_through (const String &pass_through_text,
+ Error &error);
+ virtual void error (const Error &error);
+ /* useful when saving */
+ static String escape_text (const char *text,
+ ssize_t length);
+ static String printf_escaped (const char *format,
+ ...) BIRNET_PRINTF (1, 2);
+ static String vprintf_escaped (const char *format,
+ va_list args);
+ struct Context;
+protected:
+ explicit MarkupParser ();
+private:
+ Context *context;
+};
+
+} // Birnet
+
+#endif /* __BIRNET_MARKUP_HH__ */
diff --git a/rapicorn/container.cc b/rapicorn/container.cc
new file mode 100644
index 0000000..ce152d2
--- a/dev/null
+++ b/rapicorn/container.cc
@@ -0,0 +1,759 @@
+/* Rapicorn
+ * Copyright (C) 2005 Tim Janik
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+#include "container.hh"
+#include "containerimpl.hh"
+using namespace std;
+
+namespace Rapicorn {
+
+const PropertyList&
+Container::list_properties()
+{
+ static Property *properties[] = {
+ };
+ static const PropertyList property_list (properties, Item::list_properties());
+ return property_list;
+}
+
+static DataKey<Container*> child_container_key;
+
+void
+Container::child_container (Container *child_container)
+{
+ if (child_container && !child_container->has_ancestor (*this))
+ throw Exception ("child container is not descendant of container \"", name(), "\": ", child_container->name());
+ set_data (&child_container_key, child_container);
+}
+
+Container&
+Container::child_container ()
+{
+ Container *container = get_data (&child_container_key);
+ if (!container)
+ container = this;
+ return *container;
+}
+
+void
+Container::add (Item &item, const PackPropertyList &pack_plist)
+{
+ if (item.parent())
+ throw Exception ("not adding item with parent: ", item.name());
+ item.ref();
+ Container &container = child_container();
+ if (this != &container)
+ {
+ container.add (item, pack_plist);
+ return;
+ }
+ bool added = container.add_child (item, pack_plist);
+ item.unref();
+ if (added)
+ item.invalidate();
+ else
+ throw Exception ("invalid attempt to add child: ", item.name());
+}
+
+void
+Container::add (Item *item, const PackPropertyList &pack_plist)
+{
+ if (!item)
+ throw NullPointer();
+ add (*item, pack_plist);
+}
+
+void
+Container::dispose_item (Item &item)
+{
+ if (&item == get_data (&child_container_key))
+ child_container (NULL);
+}
+
+void
+Container::remove (Item &item)
+{
+ Container *container = item.parent_container();
+ if (!container)
+ throw NullPointer();
+ item.ref();
+ item.invalidate();
+ Container *dcontainer = container;
+ while (dcontainer)
+ {
+ dcontainer->dispose_item (item);
+ dcontainer = dcontainer->parent_container();
+ }
+ container->remove_child (item);
+ item.unref();
+}
+
+void
+Container::point_children (double x,
+ double y,
+ Affine affine,
+ std::vector<Item*> &stack)
+{
+ Affine a = affine;
+ for (ChildWalker cw = local_children(); cw.has_next(); cw++)
+ if (cw->point (x, y, a))
+ {
+ cw->ref();
+ stack.push_back (&*cw);
+ Container *c = dynamic_cast<Container*> (&*cw);
+ if (c)
+ c->point_children (x, y, affine, stack);
+ }
+}
+
+bool
+Container::match_interface (InterfaceMatch &imatch,
+ const String &ident)
+{
+ if (imatch.done() ||
+ sig_find_interface.emit (imatch, ident) ||
+ ((!ident[0] || ident == name()) && imatch.match (this)))
+ return true;
+ for (ChildWalker cw = local_children(); cw.has_next(); cw++)
+ if (cw->match_interface (imatch, ident))
+ break;
+ return imatch.done();
+}
+
+void
+Container::render (Plane &plane,
+ Affine affine)
+{
+ for (ChildWalker cw = local_children(); cw.has_next(); cw++)
+ {
+ if (!cw->drawable())
+ continue;
+ const Allocation area = cw->allocation();
+ Plane scratch = Plane::create_from_intersection (plane, Point (area.x, area.y), area.width, area.height);
+ if (cw->test_flags (INVALID_REQUISITION))
+ warning ("rendering item with invalid %s: %s (%p)", "requisition", cw->name().c_str(), &*cw);
+ if (cw->test_flags (INVALID_ALLOCATION))
+ warning ("rendering item with invalid %s: %s (%p)", "allocation", cw->name().c_str(), &*cw);
+ cw->render (scratch, affine);
+ // plane.combine (scratch, COMBINE_VALUE);
+ plane.combine (scratch, COMBINE_NORMAL);
+ }
+}
+
+void
+Container::debug_tree (String indent)
+{
+ printf ("%s%s(%p) (%dx%d%+d%+d)\n", indent.c_str(), this->name().c_str(), this,
+ allocation().width, allocation().height, allocation().x, allocation().y);
+ for (ChildWalker cw = local_children(); cw.has_next(); cw++)
+ {
+ Item &child = *cw;
+ Container *c = dynamic_cast<Container*> (&child);
+ if (c)
+ c->debug_tree (indent + " ");
+ else
+ printf (" %s%s(%p) (%dx%d%+d%+d)\n", indent.c_str(), child.name().c_str(), &child,
+ child.allocation().width, child.allocation().height, child.allocation().x, child.allocation().y);
+ }
+}
+
+Container::ChildPacker::ChildPacker ()
+{}
+
+Container::Packer::Packer (ChildPacker *cp) :
+ m_child_packer (ref_sink (cp))
+{}
+
+Container::Packer::Packer (const Packer &src) :
+ m_child_packer (ref_sink (src.m_child_packer))
+{}
+
+Property*
+Container::Packer::lookup_property (const String &property_name)
+{
+ typedef std::map<const String, Property*> PropertyMap;
+ static std::map<const PropertyList*,PropertyMap*> plist_map;
+ /* find/construct property map */
+ const PropertyList &plist = list_properties();
+ PropertyMap *pmap = plist_map[&plist];
+ if (!pmap)
+ {
+ pmap = new PropertyMap;
+ for (uint i = 0; i < plist.n_properties; i++)
+ (*pmap)[plist.properties[i]->ident] = plist.properties[i];
+ plist_map[&plist] = pmap;
+ }
+ PropertyMap::iterator it = pmap->find (property_name);
+ if (it != pmap->end())
+ return it->second;
+ else
+ return NULL;
+}
+
+void
+Container::Packer::set_property (const String &property_name,
+ const String &value,
+ const nothrow_t &nt)
+{
+ Property *prop = lookup_property (property_name);
+ if (prop)
+ {
+ m_child_packer->update();
+ prop->set_value (m_child_packer, value);
+ m_child_packer->commit();
+ }
+ else if (&nt == &dothrow)
+ throw Exception ("no such property: ", property_name);
+}
+
+String
+Container::Packer::get_property (const String &property_name)
+{
+ Property *prop = lookup_property (property_name);
+ if (!prop)
+ throw Exception ("no such property: ", property_name);
+ m_child_packer->update();
+ return prop->get_value (m_child_packer);
+}
+
+void
+Container::Packer::apply_properties (const PackPropertyList &pack_plist)
+{
+ m_child_packer->update();
+ for (PackPropertyList::const_iterator it = pack_plist.begin(); it != pack_plist.end(); it++)
+ set_property (it->first, it->second, nothrow);
+ m_child_packer->commit();
+}
+
+const PropertyList&
+Container::Packer::list_properties()
+{
+ return m_child_packer->list_properties();
+}
+
+Container::Packer::~Packer ()
+{
+ unref (m_child_packer);
+}
+
+Container::Packer
+Container::child_packer (Item &item)
+{
+ Container *container = item.parent_container();
+ if (!container)
+ throw NullPointer();
+ return container->create_packer (item);
+}
+
+Container::ChildPacker*
+Container::void_packer ()
+{
+ class PackerSingleton : public ChildPacker {
+ PackerSingleton() { ref_sink(); }
+ public:
+ static PackerSingleton*
+ dummy_packer()
+ {
+ static PackerSingleton *singleton = NULL;
+ if (!singleton)
+ singleton = new PackerSingleton;
+ return singleton;
+ }
+ ~PackerSingleton() { assert_not_reached(); }
+ virtual const PropertyList&
+ list_properties ()
+ {
+ static Property *properties[] = { };
+ static const PropertyList property_list (properties);
+ return property_list;
+ }
+ virtual void update () {}
+ virtual void commit () {}
+ };
+ return PackerSingleton::dummy_packer();
+}
+
+SingleContainerImpl::SingleContainerImpl () :
+ child_item (NULL)
+{}
+
+Container::ChildWalker
+SingleContainerImpl::local_children ()
+{
+ Item **iter = &child_item, **iend = iter;
+ if (child_item)
+ iend++;
+ return value_walker (PointerIterator<Item*> (iter), PointerIterator<Item*> (iend));
+}
+
+bool
+SingleContainerImpl::add_child (Item &item, const PackPropertyList &pack_plist)
+{
+ if (!child_item)
+ {
+ item.ref_sink();
+ item.set_parent (this);
+ child_item = &item;
+ return true;
+ }
+ else
+ return false;
+}
+
+void
+SingleContainerImpl::remove_child (Item &item)
+{
+ assert (child_item == &item); /* ensured by remove() */
+ child_item = NULL;
+ item.set_parent (NULL);
+ item.unref();
+}
+
+void
+SingleContainerImpl::size_request (Requisition &requisition)
+{
+ bool chspread = false, cvspread = false;
+ if (has_visible_child())
+ {
+ Item &child = get_child();
+ Requisition cr = child.size_request ();
+ requisition.width += cr.width;
+ requisition.height += cr.height;
+ chspread = child.hspread();
+ cvspread = child.vspread();
+ }
+ set_flag (HSPREAD_CONTAINER, chspread);
+ set_flag (VSPREAD_CONTAINER, cvspread);
+}
+
+void
+SingleContainerImpl::size_allocate (Allocation area)
+{
+ allocation (area);
+ if (has_children())
+ {
+ Item &child = get_child();
+ if (child.visible())
+ child.set_allocation (area);
+ }
+}
+
+Container::Packer
+SingleContainerImpl::create_packer (Item &item)
+{
+ return void_packer(); /* no child properties */
+}
+
+SingleContainerImpl::~SingleContainerImpl()
+{
+ while (child_item)
+ remove (child_item);
+}
+
+MultiContainerImpl::MultiContainerImpl ()
+{}
+
+bool
+MultiContainerImpl::add_child (Item &item, const PackPropertyList &pack_plist)
+{
+ item.ref_sink();
+ item.set_parent (this);
+ items.push_back (&item);
+ return true;
+}
+
+void
+MultiContainerImpl::remove_child (Item &item)
+{
+ vector<Item*>::iterator it;
+ for (it = items.begin(); it != items.end(); it++)
+ if (*it == &item)
+ {
+ items.erase (it);
+ item.set_parent (NULL);
+ item.unref();
+ return;
+ }
+ assert_not_reached();
+}
+
+MultiContainerImpl::~MultiContainerImpl()
+{
+ while (items.size())
+ remove (*items[items.size() - 1]);
+}
+
+Root::Root() :
+ sig_expose (*this)
+{}
+
+class RootImpl : public Root, public SingleContainerImpl {
+public:
+ RootImpl()
+ {
+ Appearance *appearance = Appearance::create_default();
+ style (appearance->create_style ("normal"));
+ unref (appearance);
+ set_flag (PARENT_SENSITIVE, true);
+ }
+ virtual void
+ size_request (Requisition &requisition)
+ {
+ if (has_visible_child())
+ {
+ Item &child = get_child();
+ requisition = child.size_request();
+ }
+ }
+ virtual void
+ size_allocate (Allocation area)
+ {
+ allocation (area);
+ if (!has_visible_child())
+ return;
+ Item &child = get_child();
+ Requisition rq = child.size_request();
+ child.set_allocation (area);
+ }
+ virtual void
+ expose (const Allocation &area)
+ {
+ sig_expose.emit (area);
+ }
+protected:
+ vector<Item*>
+ item_difference (const vector<Item*> &clist, /* preserve order of clist */
+ const vector<Item*> &cminus)
+ {
+ map<Item*,bool> mminus;
+ for (uint i = 0; i < cminus.size(); i++)
+ mminus[cminus[i]] = true;
+ vector<Item*> result;
+ for (uint i = 0; i < clist.size(); i++)
+ if (!mminus[clist[i]])
+ result.push_back (clist[i]);
+ return result;
+ }
+ EventContext last_event_context;
+ vector<Item*> last_entered_children;
+ bool
+ dispatch_mouse_movement (const EventContext &econtext)
+ {
+ last_event_context = econtext;
+ EventMouse &mevent = *create_event_mouse (MOUSE_MOVE, econtext);
+ vector<Item*> pierced;
+ /* figure all entered children */
+ Item *grab_item = get_grab();
+ if (grab_item)
+ {
+ if (grab_item->point (mevent.x, mevent.y, Affine()))
+ pierced.push_back (ref (grab_item));
+ }
+ else if (drawable())
+ {
+ pierced.push_back (ref (this)); /* root receives all mouse events */
+ point_children (mevent.x, mevent.y, Affine(), pierced);
+ }
+ /* send leave events */
+ vector<Item*> left_children = item_difference (last_entered_children, pierced);
+ mevent.type = MOUSE_LEAVE;
+ for (vector<Item*>::reverse_iterator it = left_children.rbegin(); it != left_children.rend(); it++)
+ (*it)->handle_event (mevent);
+ /* send enter events */
+ vector<Item*> entered_children = item_difference (pierced, last_entered_children);
+ mevent.type = MOUSE_ENTER;
+ for (vector<Item*>::reverse_iterator it = entered_children.rbegin(); it != entered_children.rend(); it++)
+ (*it)->handle_event (mevent);
+ /* send actual move event */
+ bool handled = false;
+ mevent.type = MOUSE_MOVE;
+ for (vector<Item*>::reverse_iterator it = pierced.rbegin(); it != pierced.rend(); it++)
+ if (!handled && (*it)->sensitive())
+ handled = (*it)->handle_event (mevent);
+ /* cleanup */
+ delete &mevent;
+ for (vector<Item*>::reverse_iterator it = last_entered_children.rbegin(); it != last_entered_children.rend(); it++)
+ (*it)->unref();
+ last_entered_children = pierced;
+ return handled;
+ }
+ bool
+ dispatch_event_to_pierced_or_grab (Event &event)
+ {
+ vector<Item*> pierced;
+ /* figure all entered children */
+ Item *grab_item = get_grab();
+ if (grab_item)
+ pierced.push_back (ref (grab_item));
+ else if (drawable())
+ {
+ pierced.push_back (ref (this)); /* root receives all events */
+ point_children (event.x, event.y, Affine(), pierced);
+ }
+ /* send actual event */
+ bool handled = false;
+ for (vector<Item*>::reverse_iterator it = pierced.rbegin(); it != pierced.rend(); it++)
+ {
+ if (!handled && (*it)->sensitive())
+ handled = (*it)->handle_event (event);
+ (*it)->unref();
+ }
+ return handled;
+ }
+private:
+ struct ButtonState {
+ Item *item;
+ uint button;
+ ButtonState (Item *i, uint b) : item (i), button (b) {}
+ ButtonState () : item (NULL), button (0) {}
+ bool operator< (const ButtonState &bs2) const
+ {
+ const ButtonState &bs1 = *this;
+ return bs1.item < bs2.item || (bs1.item == bs2.item &&
+ bs1.button < bs2.button);
+ }
+ };
+ map<ButtonState,uint> button_state_map;
+ bool
+ dispatch_button_press (const EventContext &econtext,
+ uint button,
+ uint press_count)
+ {
+ assert (press_count >= 1 && press_count <= 3);
+ EventButton &bevent = *create_event_button (press_count == 3 ? BUTTON_3PRESS : press_count == 2 ? BUTTON_2PRESS : BUTTON_PRESS, econtext, button);
+ /* figure all entered children */
+ const vector<Item*> &pierced = last_entered_children;
+ /* send actual event */
+ bool handled = false;
+ for (vector<Item*>::const_reverse_iterator it = pierced.rbegin(); it != pierced.rend(); it++)
+ if (!handled && (*it)->sensitive())
+ {
+ ButtonState bs (*it, bevent.button);
+ if (button_state_map[bs] == 0) /* no press delivered for <button> on <item> yet */
+ {
+ button_state_map[bs] = press_count; /* record single press */
+ handled = (*it)->handle_event (bevent);
+ }
+ }
+ delete &bevent;
+ return handled;
+ }
+ bool
+ dispatch_button_release (const EventContext &econtext,
+ uint button)
+ {
+ EventButton &bevent = *create_event_button (BUTTON_RELEASE, econtext, button);
+ bool handled = false;
+ for (map<ButtonState,uint>::iterator it = button_state_map.begin(); it != button_state_map.end();)
+ {
+ const ButtonState &bs = it->first;
+ uint press_count = it->second;
+ map<ButtonState,uint>::iterator current = it++;
+ if (bs.button == button)
+ {
+ if (press_count == 3)
+ bevent.type = BUTTON_3RELEASE;
+ else if (press_count == 2)
+ bevent.type = BUTTON_2RELEASE;
+ handled |= bs.item->handle_event (bevent);
+ button_state_map.erase (current);
+ }
+ }
+ bevent.type = BUTTON_RELEASE;
+ delete &bevent;
+ return handled;
+ }
+ void
+ cancel_item_events (Item *item)
+ {
+ /* cancel enter events */
+ for (int i = last_entered_children.size(); i > 0;)
+ {
+ Item *current = last_entered_children[--i]; /* walk backwards */
+ if (item == current || !item)
+ {
+ EventMouse *mevent = create_event_mouse (MOUSE_LEAVE, last_event_context);
+ current->handle_event (*mevent);
+ delete mevent;
+ current->unref();
+ last_entered_children.erase (last_entered_children.begin() + i);
+ }
+ }
+ /* cancel button press events */
+ for (map<ButtonState,uint>::iterator it = button_state_map.begin(); it != button_state_map.end();)
+ {
+ const ButtonState &bs = it->first;
+ map<ButtonState,uint>::iterator current = it++;
+ if (bs.item == item || !item)
+ {
+ EventButton *bevent = create_event_button (BUTTON_CANCELED, last_event_context, bs.button);
+ bs.item->handle_event (*bevent);
+ delete bevent;
+ button_state_map.erase (current);
+ }
+ }
+ }
+ virtual void cancel_item_events (Item &item) { cancel_item_events (&item); }
+public:
+ virtual void
+ dispatch_cancel_events ()
+ {
+ cancel_item_events (NULL);
+ }
+ virtual bool
+ dispatch_button_event (const EventContext &econtext,
+ bool is_press,
+ uint button)
+ {
+ bool handled = false;
+ dispatch_mouse_movement (econtext);
+ if (is_press)
+ handled = dispatch_button_press (econtext, button, 1);
+ else
+ handled = dispatch_button_release (econtext, button);
+ return handled;
+ }
+ virtual bool
+ dispatch_leave_event (const EventContext &econtext)
+ {
+ dispatch_mouse_movement (econtext);
+ EventMouse &mevent = *create_event_mouse (MOUSE_LEAVE, econtext);
+ /* send leave events */
+ while (last_entered_children.size())
+ {
+ Item *item = last_entered_children.back();
+ last_entered_children.pop_back();
+ item->handle_event (mevent);
+ item->unref();
+ }
+ delete &mevent;
+ return false;
+ }
+ virtual bool
+ dispatch_move_event (const EventContext &econtext)
+ {
+ bool handled = dispatch_mouse_movement (econtext);
+ return handled;
+ }
+
+ virtual bool
+ dispatch_focus_event (const EventContext &econtext,
+ bool is_in)
+ {
+ dispatch_mouse_movement (econtext);
+ EventFocus *fevent = create_event_focus (is_in ? FOCUS_IN : FOCUS_OUT, econtext);
+ bool handled = false; // dispatch_event_to_pierced_or_grab (*fevent);
+ delete fevent;
+ return handled;
+ }
+ virtual bool
+ dispatch_key_event (const EventContext &econtext,
+ bool is_press,
+ KeyValue key,
+ const char *key_name)
+ {
+ dispatch_mouse_movement (econtext);
+ EventKey *kevent = create_event_key (is_press ? KEY_PRESS : KEY_RELEASE, econtext, key, key_name);
+ Item *grab_item = get_grab();
+ grab_item = grab_item ? grab_item : this;
+ bool handled = grab_item->handle_event (*kevent);
+ delete kevent;
+ return handled;
+ }
+ virtual bool
+ dispatch_scroll_event (const EventContext &econtext,
+ EventType scroll_type)
+ {
+ bool handled = false;
+ if (scroll_type == SCROLL_UP || scroll_type == SCROLL_RIGHT || scroll_type == SCROLL_DOWN || scroll_type == SCROLL_LEFT)
+ {
+ dispatch_mouse_movement (econtext);
+ EventScroll *sevent = create_event_scroll (scroll_type, econtext);
+ handled = dispatch_event_to_pierced_or_grab (*sevent);
+ delete sevent;
+ }
+ return handled;
+ }
+private:
+ vector<Item*> grab_stack;
+ virtual void
+ remove_grab_item (Item &child)
+ {
+ bool stack_changed = false;
+ for (int i = grab_stack.size() - 1; i >= 0; i--)
+ if (grab_stack[i] == &child)
+ {
+ grab_stack.erase (grab_stack.begin() + i);
+ stack_changed = true;
+ }
+ if (stack_changed)
+ grab_stack_changed();
+ }
+ void
+ grab_stack_changed()
+ {
+ dispatch_move_event (last_event_context);
+ }
+public:
+ virtual void
+ add_grab (Item &child)
+ {
+ if (!child.has_ancestor (*this))
+ throw Exception ("child is not descendant of container \"", name(), "\": ", child.name());
+ grab_stack.push_back (&child);
+ }
+ virtual void
+ remove_grab (Item &child)
+ {
+ for (int i = grab_stack.size() - 1; i >= 0; i--)
+ if (grab_stack[i] == &child)
+ {
+ grab_stack.erase (grab_stack.begin() + i);
+ grab_stack_changed();
+ return;
+ }
+ throw Exception ("no such child in grab stack: ", child.name());
+ }
+ virtual Item*
+ get_grab ()
+ {
+ for (int i = grab_stack.size() - 1; i >= 0; i--)
+ if (grab_stack[i]->visible())
+ return grab_stack[i];
+ return NULL;
+ }
+ virtual void
+ dispose_item (Item &item)
+ {
+ remove_grab_item (item);
+ cancel_item_events (item);
+ SingleContainerImpl::dispose_item (item);
+ }
+ using Item::render;
+ virtual void
+ render (Plane &plane)
+ {
+ plane.fill (background());
+ render (plane, Affine());
+ // render_coordinates (plane, Affine());
+ }
+};
+
+static const ItemFactory<RootImpl> root_factory ("Rapicorn::Root");
+
+} // Rapicorn
diff --git a/rapicorn/container.hh b/rapicorn/container.hh
new file mode 100644
index 0000000..aa515b2
--- a/dev/null
+++ b/rapicorn/container.hh
@@ -0,0 +1,124 @@
+/* Rapicorn
+ * Copyright (C) 2005 Tim Janik
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+#ifndef __RAPICORN_CONTAINER_HH__
+#define __RAPICORN_CONTAINER_HH__
+
+#include <rapicorn/item.hh>
+
+namespace Rapicorn {
+
+/* --- Container --- */
+struct Container : public virtual Item {
+ typedef std::map<String,String> PackPropertyList;
+protected:
+ virtual bool match_interface (InterfaceMatch &imatch,
+ const String &ident);
+ virtual bool add_child (Item &item,
+ const PackPropertyList &pack_plist = PackPropertyList()) = 0;
+ virtual void remove_child (Item &item) = 0;
+ void hide_child (Item &child) { child.set_flag (HIDDEN_CHILD, false); }
+ void show_child (Item &child) { child.set_flag (HIDDEN_CHILD, true); }
+ virtual void dispose_item (Item &item);
+public:
+ typedef Walker<Item> ChildWalker;
+ void child_container (Container *child_container);
+ Container& child_container ();
+ virtual ChildWalker local_children () = 0;
+ virtual bool has_children () = 0;
+ void add (Item &item, const PackPropertyList &pack_plist = PackPropertyList());
+ void add (Item *item, const PackPropertyList &pack_plist = PackPropertyList());
+ void remove (Item &item);
+ void remove (Item *item) { if (item) remove (*item); else throw NullPointer(); }
+ virtual
+ const PropertyList& list_properties (); /* essentially item properties */
+ virtual void point_children (double x,
+ double y,
+ Affine affine,
+ std::vector<Item*> &stack);
+ virtual void render (Plane &plane,
+ Affine affine);
+ void debug_tree (String indent = String());
+ /* child properties */
+ struct ChildPacker : public virtual ReferenceCountImpl {
+ virtual const PropertyList& list_properties () = 0;
+ virtual void update () = 0; /* fetch real pack properties */
+ virtual void commit () = 0; /* assign pack properties */
+ explicit ChildPacker ();
+ private:
+ /*Copy*/ ChildPacker (const ChildPacker&);
+ ChildPacker& operator= (const ChildPacker&);
+ };
+ struct Packer {
+ /*Con*/ Packer (ChildPacker *cp);
+ /*Copy*/ Packer (const Packer &src);
+ void set_property (const String &property_name,
+ const String &value,
+ const nothrow_t &nt = dothrow);
+ String get_property (const String &property_name);
+ Property* lookup_property (const String &property_name);
+ void apply_properties (const PackPropertyList &pplist);
+ const PropertyList& list_properties ();
+ /*Des*/ ~Packer ();
+ private:
+ ChildPacker *m_child_packer;
+ ChildPacker& operator= (const Packer &src);
+ friend class Container;
+ };
+ Packer child_packer (Item &item);
+ Packer child_packer (Item *item) { if (item) return child_packer (*item); else throw NullPointer(); }
+protected:
+ virtual Packer create_packer (Item &item) = 0;
+ static ChildPacker* void_packer ();
+ template<class PackerType>
+ PackerType extract_child_packer (Packer &packer) { return dynamic_cast<PackerType> (packer.m_child_packer); }
+};
+
+/* --- Root --- */
+class Root : public virtual Container {
+protected:
+ explicit Root ();
+ virtual void cancel_item_events (Item &item) = 0;
+public:
+ Signal<Container, void (const Allocation&)> sig_expose;
+ virtual void render (Plane &plane) = 0;
+ /* events */
+ virtual bool dispatch_move_event (const EventContext &econtext) = 0;
+ virtual bool dispatch_leave_event (const EventContext &econtext) = 0;
+ virtual bool dispatch_button_event (const EventContext &econtext,
+ bool is_press,
+ uint button) = 0;
+ virtual bool dispatch_focus_event (const EventContext &econtext,
+ bool is_in) = 0;
+ virtual bool dispatch_key_event (const EventContext &econtext,
+ bool is_press,
+ KeyValue key,
+ const char *key_name) = 0;
+ virtual bool dispatch_scroll_event (const EventContext &econtext,
+ EventType scroll_type) = 0;
+ virtual void dispatch_cancel_events () = 0;
+ virtual void add_grab (Item &child) = 0;
+ void add_grab (Item *child) { throw_if_null (child); return add_grab (*child); }
+ virtual void remove_grab (Item &child) = 0;
+ void remove_grab (Item *child) { throw_if_null (child); return remove_grab (*child); }
+ virtual Item* get_grab () = 0;
+};
+
+} // Rapicorn
+
+#endif /* __RAPICORN_CONTAINER_HH__ */
diff --git a/rapicorn/containerimpl.hh b/rapicorn/containerimpl.hh
new file mode 100644
index 0000000..8857602
--- a/dev/null
+++ b/rapicorn/containerimpl.hh
@@ -0,0 +1,60 @@
+/* Rapicorn
+ * Copyright (C) 2005 Tim Janik
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+#ifndef __RAPICORN_CONTAINER_IMPL_HH__
+#define __RAPICORN_CONTAINER_IMPL_HH__
+
+#include <rapicorn/container.hh>
+#include <rapicorn/itemimpl.hh>
+
+namespace Rapicorn {
+
+/* --- Single Child Container Impl --- */
+class SingleContainerImpl : public virtual ItemImpl, public virtual Container {
+ Item *child_item;
+protected:
+ virtual void size_request (Requisition &requisition);
+ virtual void size_allocate (Allocation area);
+ Item& get_child () { if (!child_item) throw NullPointer(); return *child_item; }
+ virtual ~SingleContainerImpl ();
+ virtual ChildWalker local_children ();
+ virtual bool has_children () { return child_item != NULL; }
+ bool has_visible_child () { return child_item && child_item->visible(); }
+ bool has_drawable_child () { return child_item && child_item->drawable(); }
+ virtual bool add_child (Item &item, const PackPropertyList &pack_plist = PackPropertyList());
+ virtual void remove_child (Item &item);
+ explicit SingleContainerImpl ();
+public:
+ virtual Packer create_packer (Item &item);
+};
+
+/* --- Multi Child Container Impl --- */
+class MultiContainerImpl : public virtual ItemImpl, public virtual Container {
+ std::vector<Item*> items;
+protected:
+ virtual ~MultiContainerImpl();
+ virtual ChildWalker local_children () { return value_walker (items); }
+ virtual bool has_children () { return items.size() > 0; }
+ virtual bool add_child (Item &item, const PackPropertyList &pack_plist = PackPropertyList());
+ virtual void remove_child (Item &item);
+ explicit MultiContainerImpl ();
+};
+
+} // Rapicorn
+
+#endif /* __RAPICORN_CONTAINER_IMPL_HH__ */
diff --git a/rapicorn/factory.cc b/rapicorn/factory.cc
new file mode 100644
index 0000000..d09aae5
--- a/dev/null
+++ b/rapicorn/factory.cc
@@ -0,0 +1,571 @@
+/* Rapicorn
+ * Copyright (C) 2002-2005 Tim Janik
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+#include "factory.hh"
+#include "birnetmarkup.hh"
+#include "container.hh"
+#include <stack>
+#include <errno.h>
+using namespace std;
+
+namespace Rapicorn {
+
+/* --- Factory --- */
+Factory::Factory()
+{}
+
+Factory::~Factory()
+{}
+
+/* --- FactorySingleton --- */
+class FactorySingleton : public Factory {
+ /* Gadget */
+ struct Gadget {
+ const String ident, ancestor;
+ const Gadget *child_container;
+ VariableMap ancestor_arguments;
+ vector<Gadget*> children;
+ void add_child (Gadget *child_gadget) { children.push_back (child_gadget); }
+ explicit Gadget (const String &cident,
+ const String &cancestor,
+ const VariableMap &cancestor_arguments = VariableMap()) :
+ ident (cident),
+ ancestor (cancestor),
+ child_container (NULL),
+ ancestor_arguments (cancestor_arguments)
+ {}
+ };
+ /* FactoryDomain */
+ struct FactoryDomain {
+ const String domain_name, i18n_domain;
+ std::map<String,Gadget*> definitions;
+ explicit FactoryDomain (const String &cdomain_name, const String &ci18n_domain) :
+ domain_name (cdomain_name), i18n_domain (ci18n_domain)
+ {}
+ explicit FactoryDomain()
+ {
+ std::map<String,Gadget*>::iterator it;
+ for (it = definitions.begin(); it != definitions.end(); it++)
+ {
+ delete it->second;
+ it->second = NULL;
+ }
+ }
+ };
+ std::list<FactoryDomain*> factory_domains;
+ FactoryDomain*
+ lookup_domain (const String &domain_name)
+ {
+ ValueIterator<std::list<FactoryDomain*>::iterator> it;
+ for (it = value_iterator (factory_domains.begin()); it != factory_domains.end(); it++)
+ if (domain_name == it->domain_name)
+ return &*it;
+ return NULL;
+ }
+ FactoryDomain*
+ add_domain (const String &domain_name,
+ const String &i18n_domain)
+ {
+ FactoryDomain *fdomain = new FactoryDomain (domain_name, i18n_domain);
+ factory_domains.push_back (fdomain);
+ return fdomain;
+ }
+ /* typedefs */
+ typedef std::pair<String,String> ArgumentPair;
+ typedef vector<ArgumentPair> ArgumentVector;
+ typedef VariableMap::iterator VariableIter;
+ typedef VariableMap::const_iterator ConstVariableIter;
+ /* attribute canonification */
+ static inline String
+ canonify (String s)
+ {
+ for (uint i = 0; i < s.size(); i++)
+ if (!((s[i] >= 'A' && s[i] <= 'Z') ||
+ (s[i] >= 'a' && s[i] <= 'z') ||
+ (s[i] >= '0' && s[i] <= '9') ||
+ s[i] == '-'))
+ s[i] = '-';
+ return s;
+ }
+ static inline String
+ rewrite_canonify_attribute (String s)
+ {
+ if (s == "id")
+ return "name";
+ else
+ return canonify (s);
+ }
+ /* GadgetParser */
+ struct GadgetParser : public MarkupParser {
+ FactoryDomain &fdomain;
+ std::stack<Gadget*> gadget_stack;
+ const Gadget **child_container_loc;
+ String child_container_name;
+ public:
+ GadgetParser (FactoryDomain &cfdomain) :
+ fdomain (cfdomain), child_container_loc (NULL)
+ {}
+ virtual void
+ start_element (const String &element_name,
+ ConstStrings &attribute_names,
+ ConstStrings &attribute_values,
+ Error &error)
+ {
+ // Gadget *top_gadget = !gadget_stack.empty() ? gadget_stack.top() : NULL;
+ if (element_name == "rapicorn-gadgets")
+ ; // outer element
+ else if (element_name.compare (0, 4, "def:") == 0 && gadget_stack.empty())
+ {
+ String ident = element_name.substr (4);
+ Gadget *gadget = fdomain.definitions[ident];
+ if (!gadget)
+ {
+ VariableMap vmap;
+ String inherit;
+ child_container_name = "";
+ for (uint i = 0; i < attribute_names.size(); i++)
+ {
+ String canonified_attribute = rewrite_canonify_attribute (attribute_names[i]);
+ if (canonified_attribute == "name")
+ error.set (INVALID_CONTENT,
+ String() + "invalid argument for inherited gadget: " +
+ attribute_names[i] + "=\"" + attribute_values[i] + "\"");
+ else if (canonified_attribute == "child-container")
+ child_container_name = attribute_values[i];
+ else if (canonified_attribute == "inherit")
+ inherit = attribute_values[i];
+ else
+ vmap[canonified_attribute] = attribute_values[i];
+ }
+ if (error.set())
+ ;
+ else if (inherit[0] == 0)
+ error.set (INVALID_CONTENT, String() + "missing ancestor for gadget \"" + ident + "\"");
+ else
+ {
+ gadget = new Gadget (ident, inherit, vmap);
+ if (child_container_name[0])
+ child_container_loc = &gadget->child_container;
+ fdomain.definitions[ident] = gadget;
+ gadget_stack.push (gadget);
+ }
+ }
+ else
+ error.set (INVALID_CONTENT, String() + "gadget \"" + ident + "\" already defined");
+ }
+ else if (!gadget_stack.empty() && canonify (element_name) == element_name)
+ {
+ Gadget *gparent = gadget_stack.top();
+ String gadget_name = element_name;
+ VariableMap vmap;
+ for (uint i = 0; i < attribute_names.size(); i++)
+ {
+ String canonified_attribute = rewrite_canonify_attribute (attribute_names[i]);
+ if (canonified_attribute == "name")
+ gadget_name = attribute_values[i];
+ else if (canonified_attribute == "child-container" ||
+ canonified_attribute == "inherit")
+ error.set (INVALID_CONTENT,
+ String() + "invalid argument for gadget construction: " +
+ attribute_names[i] + "=\"" + attribute_values[i] + "\"");
+ else
+ vmap[canonified_attribute] = attribute_values[i];
+ }
+ Gadget *gadget = new Gadget (gadget_name, element_name, vmap);
+ gparent->add_child (gadget);
+ gadget_stack.push (gadget);
+ if (child_container_loc && child_container_name == gadget_name)
+ *child_container_loc = gadget;
+ }
+ else
+ warning ("ignoring unknown element: " + element_name);
+ }
+ virtual void
+ end_element (const String &element_name,
+ Error &error)
+ {
+ Gadget *gadget = !gadget_stack.empty() ? gadget_stack.top() : NULL;
+ if (element_name.compare (0, 4, "def:") == 0 &&
+ gadget && gadget->ident.compare (&element_name[4]) == 0)
+ {
+ if (child_container_loc && !*child_container_loc)
+ error.set (INVALID_CONTENT, element_name + ": missing child container: " + child_container_name);
+ child_container_loc = NULL;
+ child_container_name = "";
+ gadget_stack.pop();
+ }
+ else if (gadget && gadget->ancestor.compare (&element_name[0]) == 0)
+ gadget_stack.pop();
+ }
+ virtual void
+ text (const String &text,
+ Error &error)
+ {
+ String t (text);
+ while (t[0] == ' ' || t[0] == '\t' || t[0] == '\r' || t[0] == '\n')
+ t.erase (0, 1);
+ if (t.size())
+ {
+ error.code = INVALID_CONTENT;
+ error.message = "unsolicited text: \"" + text + "\"";
+ }
+ }
+ virtual void
+ pass_through (const String &text,
+ Error &error)
+ {
+ if (text.compare (0, 4, "<!--") == 0 && text.compare (text.size() - 3, 3, "-->") == 0)
+ {
+ String comment = text.substr (4, text.size() - 4 - 3).c_str();
+ if (0)
+ printf ("COMMENT: %s\n", comment.c_str());
+ }
+ }
+ }; /* GadgetParser */
+ virtual void
+ parse_resource (const String &file_name,
+ const String &i18n_domain,
+ const String &domain,
+ const std::nothrow_t &nt)
+ {
+ FactoryDomain *fdomain = lookup_domain (domain);
+ if (!fdomain)
+ fdomain = add_domain (domain, i18n_domain);
+ GadgetParser gp (*fdomain);
+ MarkupParser::Error error;
+ FILE *f = fopen (file_name.c_str(), "r");
+ if (f)
+ {
+ char buffer[1024];
+ int n = fread (buffer, 1, 1024, f);
+ while (n > 0)
+ {
+ if (!gp.parse (buffer, n, &error))
+ break;
+ n = fread (buffer, 1, 1024, f);
+ }
+ if (!error.code)
+ gp.end_parse (&error);
+ }
+ else
+ {
+ error.code = MarkupParser::READ_FAILED;
+ error.message = strerror (errno);
+ }
+ if (error.code)
+ {
+ String ers = string_printf ("%s:%d:%d:error(%d): %s", file_name.c_str(), error.line_number, error.char_number, error.code, error.message.c_str());
+ if (&nt == &dothrow)
+ throw Exception (ers);
+ else
+ Birnet::error (ers);
+ }
+ }
+ /* Environment */
+ struct Environment {
+ static String
+ expand_value (const String &value,
+ const VariableMap &vmap = VariableMap())
+ {
+ return value;
+ }
+ };
+ /* gadget handling */
+ Gadget*
+ lookup_gadget (const String &gadget_identifier)
+ {
+ const char *tident = gadget_identifier.c_str();
+ const char *sep = strrchr (tident, ':');
+ String domain, ident;
+ if (sep && sep > tident && sep[-1] == ':') /* detected "::" */
+ {
+ ident = sep + 1;
+ domain.assign (tident, sep - tident - 1);
+ if (domain[0] == ':' && domain[1] == ':')
+ domain.assign (domain.substr (2));
+ }
+ else
+ ident = tident;
+ ValueIterator<std::list<FactoryDomain*>::iterator> it;
+ for (it = value_iterator (factory_domains.begin()); it != factory_domains.end(); it++)
+ if (!domain[0] || domain == it->domain_name)
+ {
+ Gadget *gadget = it->definitions[ident];
+ if (gadget)
+ return gadget;
+ }
+#if 0
+ diag ("gadgetlookup: %s :: %s (\"%s\")", domain.c_str(), ident.c_str(), gadget_identifier.c_str());
+ for (it = value_iterator (factory_domains.begin()); it != factory_domains.end(); it++)
+ if (!domain[0] || domain == it->domain_name)
+ diag ("unmatched: %s::%s", it->domain_name.c_str(), ident.c_str());
+ else
+ diag ("unused: %s::", it->domain_name.c_str());
+#endif
+ return NULL;
+ }
+ Item&
+ create_gadget (const String &gadget_identifier,
+ const ArgumentList &arguments)
+ {
+ Gadget *gadget = lookup_gadget (gadget_identifier);
+ if (!gadget)
+ throw Exception ("no such gadget: ", gadget_identifier);
+ VariableMap vmap;
+ for (ArgumentList::const_iterator it = arguments.begin(); it != arguments.end(); it++)
+ {
+ const char *key_value = it->c_str();
+ const char *equal = strchr (key_value, '=');
+ if (!equal || equal <= key_value)
+ throw Exception ("Invalid argument=value pair: ", *it);
+ String key = it->substr (0, equal - key_value);
+ vmap[rewrite_canonify_attribute (key)] = equal + 1;
+ }
+ Environment empty_env;
+ return call_gadget (gadget, vmap, VariableMap(), empty_env, NULL, NULL);
+ }
+ static String
+ variable_map_filter (VariableMap &vmap,
+ const String &key,
+ const String &value)
+ {
+ String result;
+ VariableIter it = vmap.find (key);
+ if (it != vmap.end())
+ {
+ result = it->second;
+ vmap.erase (it);
+ }
+ else
+ result = value;
+ return result;
+ }
+ static void
+ variable_map_filter (VariableMap &vmap,
+ const String &key)
+ {
+ variable_map_filter (vmap, key, "");
+ }
+ Item&
+ inherit_gadget (const String &ancestor_name,
+ const VariableMap &expanded_arguments,
+ Environment &environment)
+ {
+ if (ancestor_name[0] == '\177') /* item factory type */
+ {
+ Item &item = create_from_item_type (&ancestor_name[1]);
+ /* apply arguments */
+ try {
+ for (ConstVariableIter it = expanded_arguments.begin(); it != expanded_arguments.end(); it++)
+ item.set_property (it->first, it->second, nothrow);
+ } catch (...) {
+ sink (&item);
+ throw;
+ }
+ return item;
+ }
+ else
+ {
+ Gadget *gadget = lookup_gadget (ancestor_name);
+ if (!gadget)
+ throw Exception ("no such gadget: ", ancestor_name);
+ return call_gadget (gadget, expanded_arguments, VariableMap(), environment, NULL, NULL);
+ }
+ }
+ struct ChildContainerSlot {
+ const Gadget *gadget;
+ Item *item;
+ explicit ChildContainerSlot (const Gadget *cgadget) : gadget (cgadget), item (NULL) {}
+ };
+ Item&
+ call_gadget (const Gadget *gadget,
+ const VariableMap &canonified_arguments,
+ const VariableMap &fallback_variables,
+ Environment &environment,
+ ChildContainerSlot *ccslot,
+ Container *parent)
+ {
+ /* extend environament */
+ // FIXME: add gadget->env_variables (not overriding existing vars)
+ /* expand call arguments */
+ VariableMap expanded_arguments;
+ for (ConstVariableIter it = canonified_arguments.begin(); it != canonified_arguments.end(); it++)
+ expanded_arguments[it->first] = environment.expand_value (it->second, fallback_variables);
+ /* filter special arguments */
+ String name = gadget->ident;
+ name = variable_map_filter (expanded_arguments, "name", name);
+ /* ignore nonsense arguments */
+ variable_map_filter (expanded_arguments, "child-container");
+ variable_map_filter (expanded_arguments, "inherit");
+ /* construct argument list for ancestor */
+ VariableMap ancestor_arguments;
+ for (ConstVariableIter it = gadget->ancestor_arguments.begin(); it != gadget->ancestor_arguments.end(); it++)
+ {
+ VariableIter xt = expanded_arguments.find (it->first);
+ if (xt != expanded_arguments.end())
+ continue; /* skip overridden ancestor arguments */
+ ancestor_arguments[it->first] = environment.expand_value (it->second, expanded_arguments);
+ }
+ /* override with call arguments */
+ for (ConstVariableIter it = expanded_arguments.begin(); it != expanded_arguments.end(); it++)
+ ancestor_arguments[it->first] = it->second;
+ /* construct gadget from ancestor */
+ Item &item = inherit_gadget (gadget->ancestor, ancestor_arguments, environment);
+ /* construct gadget children */
+ try {
+ ChildContainerSlot outer_ccslot (gadget->child_container);
+ call_gadget_children (gadget, item, expanded_arguments, environment, ccslot ? ccslot : &outer_ccslot);
+ /* assign specials */
+ item.name (name);
+ /* setup child container */
+ if (!ccslot) /* outer call */
+ {
+ Container *container = dynamic_cast<Container*> (outer_ccslot.item);
+ Container *item_container = dynamic_cast<Container*> (&item);
+ if (item_container && !gadget->child_container)
+ item_container->child_container (item_container);
+ else if (item_container && container)
+ item_container->child_container (container);
+ else if (gadget->child_container)
+ throw Exception ("no such child container: ", gadget->child_container->ident);
+ }
+ /* add to parent */
+ if (parent)
+ parent->add (item, ancestor_arguments);
+ } catch (...) {
+ sink (item);
+ throw;
+ }
+ return item;
+ }
+ void
+ call_gadget_children (const Gadget *gadget,
+ Item &item,
+ const VariableMap &parent_arguments,
+ Environment &environment,
+ ChildContainerSlot *ccslot)
+ {
+ /* protect leafs */
+ if (!gadget->children.size())
+ return;
+ /* retrieve container */
+ Container *container = item.interface<Container*>();
+#if 0
+ diag ("children of %s:", gadget->ident.c_str());
+ uint nth = 0;
+ for (Walker<Gadget*const> cw = walker (gadget->children); cw.has_next(); cw++)
+ diag ("%d) %s", nth++, (*cw)->ident.c_str());
+#endif
+ if (!container)
+ throw Exception ("parent gadgets must implement Container interface: ", gadget->ident);
+ /* create children */
+ for (Walker<Gadget*const> cw = walker (gadget->children); cw.has_next(); cw++)
+ {
+ /* create child gadget */
+ Gadget *child_gadget = *cw;
+ /* the real call arguments are stored as ancestor arguments of the child */
+ VariableMap expanded_arguments;
+ Item &child = call_gadget (child_gadget, expanded_arguments, parent_arguments, environment, ccslot, container);
+ /* find child container */
+ if (ccslot->gadget == child_gadget)
+ ccslot->item = &child;
+ }
+ }
+private:
+ /* type registration */
+ std::list<const ItemTypeFactory*> types;
+ virtual void
+ register_item_factory (const ItemTypeFactory *itfactory)
+ {
+ const char *ident = itfactory->qualified_type.c_str();
+ const char *base = strrchr (ident, ':');
+ if (!base || base <= ident || base[-1] != ':')
+ throw Exception ("invalid/missing domain name in item type: ", ident);
+ String domain_name;
+ domain_name.assign (ident, base - ident - 1);
+ FactoryDomain *fdomain = lookup_domain (domain_name);
+ if (!fdomain)
+ fdomain = add_domain (domain_name, domain_name);
+ Gadget *gadget = new Gadget (base + 1, String ("\177") + itfactory->qualified_type);
+ fdomain->definitions[gadget->ident] = gadget;
+ types.push_back (itfactory);
+ }
+ const ItemTypeFactory*
+ lookup_item_factory (const String &namespaced_ident)
+ {
+ std::list<const ItemTypeFactory*>::iterator it;
+ for (it = types.begin(); it != types.end(); it++)
+ if ((*it)->qualified_type == namespaced_ident)
+ return *it;
+ return NULL;
+ }
+ Item&
+ create_from_item_type (const String &ident)
+ {
+ const ItemTypeFactory *itfactory = lookup_item_factory (ident);
+ if (itfactory)
+ return *itfactory->create_item (ident);
+ else
+ throw Exception ("no such item type: ", ident);
+ }
+
+public:
+ static bool queue_type_registrations;
+ FactorySingleton()
+ {
+ if (this != &Rapicorn::Factory)
+ throw Exception (STRFUNC, ": non-singleton initialization");
+ /* open up registration facility */
+ queue_type_registrations = false;
+ /* register backlog */
+ Factory::announce_item_factory (NULL);
+ }
+};
+bool FactorySingleton::queue_type_registrations = true;
+
+static class FactorySingleton static_factory_singleton;
+class Factory &Factory = static_factory_singleton;
+
+void
+Factory::announce_item_factory (const ItemTypeFactory *itfactory)
+{
+ struct QueuedEntry { const ItemTypeFactory *itfactory; QueuedEntry *next; };
+ static QueuedEntry *queued_entries = NULL;
+ if (FactorySingleton::queue_type_registrations)
+ {
+ assert (itfactory);
+ QueuedEntry *e = new QueuedEntry;
+ e->itfactory = itfactory;
+ e->next = queued_entries;
+ queued_entries = e;
+ }
+ else if (itfactory)
+ Rapicorn::Factory.register_item_factory (itfactory);
+ else /* !queue_type_registrations && !itfactory */
+ {
+ while (queued_entries)
+ {
+ QueuedEntry *e = queued_entries;
+ queued_entries = e->next;
+ Rapicorn::Factory.register_item_factory (e->itfactory);
+ delete e;
+ }
+ }
+}
+
+} // Rapicorn
diff --git a/rapicorn/factory.hh b/rapicorn/factory.hh
new file mode 100644
index 0000000..3eda39d
--- a/dev/null
+++ b/rapicorn/factory.hh
@@ -0,0 +1,79 @@
+/* Rapicorn
+ * Copyright (C) 2002-2005 Tim Janik
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+#ifndef __RAPICORN_FACTORY_HH__
+#define __RAPICORN_FACTORY_HH__
+
+#include <rapicorn/item.hh>
+#include <list>
+
+namespace Rapicorn {
+
+/* --- Factory --- */
+struct Factory {
+ typedef map<String,String> VariableMap;
+ typedef std::list<String> ArgumentList; /* elements: key=utf8string */
+ virtual Item& create_gadget (const String &gadget_identifier,
+ const ArgumentList &arguments = ArgumentList()) = 0;
+ virtual void parse_resource (const String &file_name,
+ const String &i18n_domain,
+ const String &domain,
+ const std::nothrow_t &nt = dothrow) = 0;
+ void parse_resource (const String &file_name,
+ const String &i18n_domain,
+ const std::nothrow_t &nt = dothrow) { return parse_resource (file_name, i18n_domain, i18n_domain, nt); }
+ struct ItemTypeFactory;
+ static void announce_item_factory (const ItemTypeFactory *itfactory);
+private:
+ virtual void register_item_factory (const ItemTypeFactory *itfactory) = 0;
+ BIRNET_PRIVATE_CLASS_COPY (Factory);
+protected:
+ Factory();
+ virtual ~Factory();
+};
+
+/* --- Factory Singleton --- */
+extern class Factory &Factory;
+
+/* --- item type registration --- */
+struct Factory::ItemTypeFactory : Deletable {
+ const String qualified_type;
+ ItemTypeFactory (const char *namespaced_ident) :
+ qualified_type (namespaced_ident)
+ {}
+ virtual Item* create_item (const String &name) const = 0;
+};
+template<class Type>
+struct ItemFactory : Factory::ItemTypeFactory {
+ ItemFactory (const char *namespaced_ident) :
+ ItemTypeFactory (namespaced_ident)
+ {
+ Factory::announce_item_factory (this);
+ }
+ virtual Item*
+ create_item (const String &name) const
+ {
+ Item *item = new Type();
+ item->name (name);
+ return item;
+ }
+};
+
+} // Rapicorn
+
+#endif /* __RAPICORN_FACTORY_HH__ */
diff --git a/rapicorn/item.cc b/rapicorn/item.cc
new file mode 100644
index 0000000..bfbee47
--- a/dev/null
+++ b/rapicorn/item.cc
@@ -0,0 +1,467 @@
+/* Rapicorn
+ * Copyright (C) 2005 Tim Janik
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+#include "item.hh"
+#include "itemimpl.hh"
+#include "container.hh"
+
+namespace Rapicorn {
+
+Item::Item () :
+ m_parent (NULL),
+ m_flags (VISIBLE | SENSITIVE),
+ m_style (NULL),
+ sig_event (*this, &Item::do_event),
+ sig_finalize (*this),
+ sig_changed (*this, &Item::do_changed),
+ sig_invalidate (*this, &Item::do_invalidate)
+{}
+
+bool
+Item::change_flags_silently (uint32 mask,
+ bool on)
+{
+ uint32 old_flags = m_flags;
+ if (on)
+ m_flags |= mask;
+ else
+ m_flags &= ~mask;
+ /* omit change notification */
+ return old_flags != m_flags;
+}
+
+void
+Item::propagate_flags()
+{
+ change_flags_silently (PARENT_SENSITIVE, !parent() || parent()->sensitive());
+ expose();
+ Container *container = dynamic_cast<Container*> (this);
+ if (container)
+ for (Container::ChildWalker it = container->local_children(); it.has_next(); it++)
+ it->propagate_flags();
+ if (!finalizing())
+ sig_changed.emit(); /* notify changed() without invalidate() */
+}
+
+void
+Item::set_flag (uint32 flag,
+ bool on)
+{
+ assert ((flag & (flag - 1)) == 0); /* single bit check */
+ const uint propagate_flag_mask = SENSITIVE | PARENT_SENSITIVE | PRELIGHT | IMPRESSED | HAS_FOCUS | HAS_DEFAULT;
+ const uint invalidate_flag_mask = HEXPAND | VEXPAND | HSPREAD | VSPREAD | HSPREAD_CONTAINER | VSPREAD_CONTAINER | VISIBLE;
+ bool fchanged = change_flags_silently (flag, on);
+ if (fchanged)
+ {
+ if (flag & propagate_flag_mask)
+ propagate_flags ();
+ if (flag & invalidate_flag_mask)
+ invalidate();
+ changed();
+ }
+}
+
+bool
+Item::grab_focus () const
+{
+ return false;
+}
+
+bool
+Item::grab_default () const
+{
+ return false;
+}
+
+void
+Item::sensitive (bool b)
+{
+ set_flag (SENSITIVE, b);
+}
+
+void
+Item::prelight (bool b)
+{
+ set_flag (PRELIGHT, b);
+}
+
+bool
+Item::branch_prelight () const
+{
+ const Item *item = this;
+ while (item)
+ {
+ if (item->prelight())
+ return true;
+ item = item->parent();
+ }
+ return false;
+}
+
+void
+Item::impressed (bool b)
+{
+ set_flag (IMPRESSED, b);
+}
+
+bool
+Item::branch_impressed () const
+{
+ const Item *item = this;
+ while (item)
+ {
+ if (item->impressed())
+ return true;
+ item = item->parent();
+ }
+ return false;
+}
+
+StateType
+Item::state () const
+{
+ StateType st = StateType (0);
+ st |= insensitive() ? STATE_INSENSITIVE : StateType (0);
+ st |= prelight() ? STATE_PRELIGHT : StateType (0);
+ st |= impressed() ? STATE_IMPRESSED : StateType (0);
+ st |= has_focus() ? STATE_FOCUS : StateType (0);
+ st |= has_default() ? STATE_DEFAULT : StateType (0);
+ return st;
+}
+
+bool
+Item::match_interface (InterfaceMatch &imatch,
+ const String &ident)
+{
+ return imatch.done() || sig_find_interface.emit (imatch, ident) || ((!ident[0] || ident == name()) && imatch.match (this));
+}
+
+void
+Item::finalize()
+{
+ sig_finalize.emit();
+}
+
+Item::~Item()
+{
+ if (parent_container())
+ parent_container()->remove (this);
+ if (m_style)
+ {
+ m_style->unref();
+ m_style = NULL;
+ }
+}
+
+Property*
+Item::lookup_property (const String &property_name)
+{
+ typedef std::map<const String, Property*> PropertyMap;
+ static std::map<const PropertyList*,PropertyMap*> plist_map;
+ /* find/construct property map */
+ const PropertyList &plist = list_properties();
+ PropertyMap *pmap = plist_map[&plist];
+ if (!pmap)
+ {
+ pmap = new PropertyMap;
+ for (uint i = 0; i < plist.n_properties; i++)
+ (*pmap)[plist.properties[i]->ident] = plist.properties[i];
+ plist_map[&plist] = pmap;
+ }
+ PropertyMap::iterator it = pmap->find (property_name);
+ if (it != pmap->end())
+ return it->second;
+ else
+ return NULL;
+}
+
+void
+Item::set_property (const String &property_name,
+ const String &value,
+ const nothrow_t &nt)
+{
+ Property *prop = lookup_property (property_name);
+ if (prop)
+ prop->set_value (this, value);
+ else if (&nt == &dothrow)
+ throw Exception ("no such property: ", property_name);
+}
+
+String
+Item::get_property (const String &property_name)
+{
+ Property *prop = lookup_property (property_name);
+ if (!prop)
+ throw Exception ("no such property: ", property_name);
+ return prop->get_value (this);
+}
+
+const PropertyList&
+Item::list_properties ()
+{
+ static Property *properties[] = {
+ MakeProperty (Item, name, _("Name"), _("Identification name of the item"), "", "rw"),
+ MakeProperty (Item, visible, _("Visible"), _("Whether this item is visible"), true, "rw"),
+ MakeProperty (Item, sensitive, _("Sensitive"), _("Whether this item is sensitive (receives events)"), true, "rw"),
+ MakeProperty (Item, hexpand, _("Horizontal Expand"), _("Whether to expand this item horizontally"), false, "rw"),
+ MakeProperty (Item, vexpand, _("Vertical Expand"), _("Whether to expand this item vertically"), false, "rw"),
+ MakeProperty (Item, hspread, _("Horizontal Spread"), _("Whether to expand this item and all its parents horizontally"), false, "rw"),
+ MakeProperty (Item, vspread, _("Vertical Spread"), _("Whether to expand this item and all its parents vertically"), false, "rw"),
+ };
+ static const PropertyList property_list (properties);
+ return property_list;
+}
+
+void
+Item::propagate_style ()
+{
+ Container *container = dynamic_cast<Container*> (this);
+ if (container)
+ for (Container::ChildWalker it = container->local_children(); it.has_next(); it++)
+ it->style (m_style);
+}
+
+void
+Item::style (Style *st)
+{
+ if (m_style)
+ {
+ m_style->unref();
+ m_style = NULL;
+ }
+ if (st)
+ {
+ m_style = st->create_style (st->name());
+ ref_sink (m_style);
+ invalidate();
+ }
+ propagate_style ();
+}
+
+void
+Item::set_parent (Item *pitem)
+{
+ if (parent())
+ {
+ invalidate();
+ style (NULL);
+ m_parent = NULL;
+ }
+ if (pitem)
+ {
+ /* ensure parent items are always containers (see parent_container()) */
+ if (!dynamic_cast<Container*> (pitem))
+ throw Exception ("not setting non-Container item as parent: ", pitem->name());
+ m_parent = pitem;
+ style (m_parent->style());
+ propagate_flags();
+ invalidate();
+ }
+}
+
+Container*
+Item::parent_container() const
+{
+ return dynamic_cast<Container*> (m_parent); /* see set_parent() */
+}
+
+bool
+Item::has_ancestor (const Item &ancestor)
+{
+ Item *item = this;
+ while (item)
+ {
+ if (item == &ancestor)
+ return true;
+ item = item->parent();
+ }
+ return false;
+}
+
+Root*
+Item::root ()
+{
+ Item *parent = this;
+ while (parent->parent())
+ parent = parent->parent();
+ return dynamic_cast<Root*> (parent); // NULL if parent is not of type Root*
+}
+
+void
+Item::changed()
+{
+ if (test_flags (INVALIDATE_ON_CHANGE))
+ invalidate();
+ if (test_flags (EXPOSE_ON_CHANGE))
+ expose();
+ if (!finalizing())
+ sig_changed.emit();
+}
+
+void
+Item::invalidate()
+{
+ if (!test_flags (INVALID_REQUISITION | INVALID_ALLOCATION))
+ {
+ change_flags_silently (INVALID_REQUISITION | INVALID_ALLOCATION, true); /* skip notification */
+ if (!finalizing())
+ sig_invalidate.emit();
+ if (parent())
+ parent()->invalidate();
+ }
+}
+
+bool
+Item::handle_event (Event &event)
+{
+ return sig_event.emit (event);
+}
+
+void
+ItemImpl::allocation (const Allocation &area)
+{
+ m_allocation = area;
+}
+
+void
+ItemImpl::do_changed()
+{
+}
+
+void
+ItemImpl::do_invalidate()
+{
+}
+
+bool
+ItemImpl::do_event (const Event &event)
+{
+ return false;
+}
+
+void
+ItemImpl::expose (const Allocation &area)
+{
+ if (parent() && !test_flags (INVALID_REQUISITION | INVALID_ALLOCATION))
+ parent()->expose (area);
+}
+
+String
+ItemImpl::name () const
+{
+ return m_name;
+}
+
+void
+ItemImpl::name (const String &str)
+{
+ m_name = str;
+}
+
+bool
+ItemImpl::point (double x,
+ double y,
+ Affine affine)
+{
+ Allocation a = allocation();
+ Point p = affine.ipoint (x, y);
+ if (drawable() &&
+ p.x >= a.x && p.x < a.x + a.width &&
+ p.y >= a.y && p.y < a.y + a.height)
+ return true;
+ return false;
+}
+
+const Requisition&
+ItemImpl::size_request ()
+{
+ while (test_flags (INVALID_REQUISITION))
+ {
+ change_flags_silently (INVALID_REQUISITION, false); /* skip notification */
+ Requisition req;
+ size_request (req);
+ req.width = MAX (req.width, 0);
+ req.height = MAX (req.height, 0);
+ m_requisition = req;
+ }
+ return m_requisition;
+}
+
+const Allocation&
+ItemImpl::allocation()
+{
+ return m_allocation;
+}
+
+void
+ItemImpl::set_allocation (const Allocation &area)
+{
+ Allocation sarea = area;
+ sarea.width = MAX (area.width, 0);
+ sarea.height = MAX (area.height, 0);
+ /* always reallocate to re-layout children */
+ expose();
+ change_flags_silently (INVALID_ALLOCATION, false); /* skip notification */
+ size_allocate (sarea);
+ Allocation a = allocation();
+ set_flag (POSITIVE_ALLOCATION, a.width > 0 && a.height > 0);
+ expose();
+}
+
+} // Rapicorn
+
+#if 0 // keyed data test
+namespace {
+using namespace Birnet;
+using namespace Birnet::Canvas;
+struct Test {
+ Test()
+ {
+ class MyKey : public DataKey<int> {
+ void destroy (int i)
+ {
+ printf ("delete %d;\n", i);
+ }
+ int fallback()
+ {
+ return -1;
+ }
+ } intk;
+ class Key2 : public DataKey<String> {
+ void destroy (String s)
+ {
+ printf ("delete \"%s\";\n", s.c_str());
+ }
+ } strkey;
+ Root &r = *new Root();
+ printf ("Keyed Data Test:\n");
+ printf ("set_data(\"%s\")\n", "otto"); r.set_data (&strkey, String ("otto")); printf ("data=\"%s\"\n", r.get_data (&strkey).c_str());
+ printf ("(fallback=-1)\n"); printf ("data=%d\n", r.get_data (&intk));
+ printf ("swap_data(%d)=%d\n", 4, r.swap_data (&intk, 4)); printf ("data=%d\n", r.get_data (&intk));
+ printf ("set_data(%d)\n", 5); r.set_data (&intk, 5); printf ("data=%d\n", r.get_data (&intk));
+ printf ("swap_data(%d)=%d\n", 6, r.swap_data (&intk, 6)); printf ("data=%d\n", r.get_data (&intk));
+ printf ("swap_data(%d)=%d\n", 6, r.swap_data (&intk, 6)); printf ("data=%d\n", r.get_data (&intk));
+ printf ("swap_data()=%d\n", r.swap_data (&intk)); printf ("data=%d\n", r.get_data (&intk));
+ printf ("set_data(%d)\n", 8); r.set_data (&intk, 8); printf ("data=%d\n", r.get_data (&intk));
+ printf ("delete_data()\n"); r.delete_data (&intk); printf ("data=%d\n", r.get_data (&intk));
+ printf ("set_data(%d)\n", 9); r.set_data (&intk, 9); printf ("data=%d\n", r.get_data (&intk));
+ r.unref();
+ }
+} test;
+} // anon
+#endif
diff --git a/rapicorn/item.hh b/rapicorn/item.hh
new file mode 100644
index 0000000..a9cf5d2
--- a/dev/null
+++ b/rapicorn/item.hh
@@ -0,0 +1,191 @@
+/* Rapicorn
+ * Copyright (C) 2005 Tim Janik
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+#ifndef __RAPICORN_ITEM_HH_
+#define __RAPICORN_ITEM_HH_
+
+#include <rapicorn/events.hh>
+#include <rapicorn/primitives.hh>
+#include <rapicorn/properties.hh>
+#include <rapicorn/appearance.hh>
+
+namespace Rapicorn {
+
+/* --- Item structures and forward decls --- */
+struct Requisition {
+ double width, height;
+ Requisition () : width (0), height (0) {}
+};
+struct Allocation {
+ int x, y, width, height;
+ Allocation() : x (0), y (0), width (0), height (0) {}
+ Allocation (int cx, int cy, int cwidth, int cheight) : x (cx), y (cy), width (cwidth), height (cheight) {}
+ bool operator== (const Allocation &other) { return other.x == x && other.y == y && other.width == width && other.height == height; }
+ bool operator!= (const Allocation &other) { return !operator== (other); }
+};
+class Root;
+class Container;
+
+/* --- Item --- */
+class Item : public virtual Convertible, public virtual DataListContainer, public virtual ReferenceCountImpl {
+ /*Copy*/ Item (const Item&);
+ Item& operator= (const Item&);
+ Item *m_parent; /* interface-inlined for fast read-out */
+ uint32 m_flags; /* interface-inlined for fast read-out */
+ Style *m_style;
+ friend class Container;
+ void propagate_flags ();
+ void propagate_style ();
+protected:
+ /* flag handling */
+ bool change_flags_silently (uint32 mask, bool on);
+ enum {
+ VISIBLE = 1 << 0,
+ HIDDEN_CHILD = 1 << 1,
+ SENSITIVE = 1 << 2,
+ PARENT_SENSITIVE = 1 << 3,
+ PRELIGHT = 1 << 4,
+ IMPRESSED = 1 << 5,
+ HAS_FOCUS = 1 << 6,
+ HAS_DEFAULT = 1 << 7,
+ /* REQUEST_DEFAULT = 1 << 8, */
+ INVALID_REQUISITION = 1 << 10,
+ INVALID_ALLOCATION = 1 << 11,
+ EXPOSE_ON_CHANGE = 1 << 12,
+ INVALIDATE_ON_CHANGE = 1 << 13,
+ HEXPAND = 1 << 14,
+ VEXPAND = 1 << 15,
+ HSPREAD = 1 << 16,
+ VSPREAD = 1 << 17,
+ HSPREAD_CONTAINER = 1 << 18,
+ VSPREAD_CONTAINER = 1 << 19,
+ POSITIVE_ALLOCATION = 1 << 20,
+ DEBUG = 1 << 21,
+ LAST_FLAG = 1 << 22
+ };
+ virtual void set_flag (uint32 flag, bool on = true);
+ void unset_flag (uint32 flag) { set_flag (flag, false); }
+ bool test_flags (uint32 mask) const { return (m_flags & mask) == mask; }
+ bool test_any_flag (uint32 mask) const { return (m_flags & mask) != 0; }
+ /* size requisition and allocation */
+ virtual void size_request (Requisition &requisition) = 0;
+ virtual void size_allocate (Allocation area) = 0;
+ /* signal methods */
+ virtual bool match_interface (InterfaceMatch &imatch, const String &ident);
+ virtual void do_invalidate () = 0;
+ virtual void do_changed () = 0;
+ virtual bool do_event (const Event &event) = 0;
+ /* misc */
+ virtual void style (Style *st);
+ virtual void finalize ();
+ virtual ~Item ();
+public:
+ explicit Item ();
+ bool visible () const { return test_flags (VISIBLE) && !test_flags (HIDDEN_CHILD); }
+ void visible (bool b) { set_flag (VISIBLE, b); }
+ bool sensitive () const { return test_flags (SENSITIVE | PARENT_SENSITIVE); }
+ virtual void sensitive (bool b);
+ bool insensitive () const { return !sensitive(); }
+ void insensitive (bool b) { sensitive (!b); }
+ bool prelight () const { return test_flags (PRELIGHT); }
+ virtual void prelight (bool b);
+ bool branch_prelight () const;
+ bool impressed () const { return test_flags (IMPRESSED); }
+ virtual void impressed (bool b);
+ bool branch_impressed() const;
+ bool has_focus () const { return test_flags (HAS_FOCUS); }
+ bool grab_focus () const;
+ bool has_default () const { return test_flags (HAS_DEFAULT); }
+ bool grab_default () const;
+ bool hexpand () const { return test_any_flag (HEXPAND | HSPREAD | HSPREAD_CONTAINER); }
+ void hexpand (bool b) { set_flag (HEXPAND, b); }
+ bool vexpand () const { return test_any_flag (VEXPAND | VSPREAD | VSPREAD_CONTAINER); }
+ void vexpand (bool b) { set_flag (VEXPAND, b); }
+ bool hspread () const { return test_any_flag (HSPREAD | HSPREAD_CONTAINER); }
+ void hspread (bool b) { set_flag (HSPREAD, b); }
+ bool vspread () const { return test_any_flag (VSPREAD | VSPREAD_CONTAINER); }
+ void vspread (bool b) { set_flag (VSPREAD, b); }
+ bool drawable () const { return visible() && test_flags (POSITIVE_ALLOCATION); }
+ bool debug () const { return test_flags (DEBUG); }
+ void debug (bool f) { set_flag (DEBUG, f); }
+ virtual String name () const = 0;
+ virtual void name (const String &str) = 0;
+ /* properties */
+ void set_property (const String &property_name,
+ const String &value,
+ const nothrow_t &nt = dothrow);
+ String get_property (const String &property_name);
+ Property* lookup_property (const String &property_name);
+ virtual const PropertyList& list_properties ();
+ /* parents */
+ virtual void set_parent (Item *parent);
+ Item* parent () const { return m_parent; }
+ Container* parent_container() const;
+ bool has_ancestor (const Item &ancestor);
+ Root* root ();
+ /* invalidation / changes */
+ void invalidate ();
+ void changed ();
+ virtual void expose (const Allocation &area) = 0;
+ void expose () { expose (allocation()); }
+ /* public signals */
+ Signal<Item,
+ bool (const Event&),
+ CollectorWhile0<bool> > sig_event;
+ Signal<Item, void ()> sig_finalize;
+ Signal<Item, void ()> sig_changed;
+ Signal<Item, void ()> sig_invalidate;
+ /* event handling */
+ bool handle_event (Event&);
+ virtual bool point (double x, /* global coordinate system */
+ double y,
+ Affine affine) = 0;
+ /* public size accessors */
+ virtual const Requisition& size_request () = 0; /* re-request size */
+ const Requisition& requisition () { return size_request(); } /* cached requisition */
+ virtual void set_allocation (const Allocation &area) = 0; /* assign new allocation */
+ virtual const Allocation& allocation () = 0; /* current allocation */
+ /* display */
+ virtual void render (Plane &plane,
+ Affine affine) = 0;
+ /* styles / appearance */
+ StateType state () const;
+ Style* style () { return m_style; }
+ Color foreground () { return style()->color (state(), COLOR_FOREGROUND); }
+ Color background () { return style()->color (state(), COLOR_BACKGROUND); }
+ Color selected_foreground () { return style()->color (state(), COLOR_SELECTED_FOREGROUND); }
+ Color selected_background () { return style()->color (state(), COLOR_SELECTED_BACKGROUND); }
+ Color focus_color () { return style()->color (state(), COLOR_FOCUS); }
+ Color default_color () { return style()->color (state(), COLOR_DEFAULT); }
+ Color light_glint () { return style()->color (state(), COLOR_LIGHT_GLINT); }
+ Color light_shadow () { return style()->color (state(), COLOR_LIGHT_SHADOW); }
+ Color dark_glint () { return style()->color (state(), COLOR_DARK_GLINT); }
+ Color dark_shadow () { return style()->color (state(), COLOR_DARK_SHADOW); }
+ Color white () { return style()->color (state(), COLOR_WHITE); }
+ Color black () { return style()->color (state(), COLOR_BLACK); }
+ Color red () { return style()->color (state(), COLOR_RED); }
+ Color yellow () { return style()->color (state(), COLOR_YELLOW); }
+ Color green () { return style()->color (state(), COLOR_GREEN); }
+ Color cyan () { return style()->color (state(), COLOR_CYAN); }
+ Color blue () { return style()->color (state(), COLOR_BLUE); }
+ Color magenta () { return style()->color (state(), COLOR_MAGENTA); }
+};
+
+} // Rapicorn
+
+#endif /* __RAPICORN_ITEM_HH_ */
diff --git a/rapicorn/itemimpl.hh b/rapicorn/itemimpl.hh
new file mode 100644
index 0000000..644d029
--- a/dev/null
+++ b/rapicorn/itemimpl.hh
@@ -0,0 +1,53 @@
+/* Rapicorn
+ * Copyright (C) 2005 Tim Janik
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+#ifndef __RAPICORN_ITEM_IMPL_HH_
+#define __RAPICORN_ITEM_IMPL_HH_
+
+#include <rapicorn/item.hh>
+#include <rapicorn/factory.hh>
+
+namespace Rapicorn {
+
+class ItemImpl : public virtual Item {
+ Requisition m_requisition;
+ Allocation m_allocation;
+ String m_name;
+protected:
+ virtual void allocation (const Allocation &area);
+ /* signal methods */
+ virtual void do_invalidate ();
+ virtual void do_changed ();
+ virtual bool do_event (const Event &event);
+ using Item::expose;
+ virtual void expose (const Allocation &area);
+ using Item::size_request;
+public:
+ virtual String name () const;
+ virtual void name (const String &str);
+ virtual bool point (double x, /* global coordinate system */
+ double y,
+ Affine affine);
+ virtual const Requisition& size_request ();
+ virtual const Allocation& allocation ();
+ virtual void set_allocation (const Allocation &area);
+};
+
+} // Rapicorn
+
+#endif /* __RAPICORN_ITEM_IMPL_HH_ */
diff --git a/rapicorn/primitives.cc b/rapicorn/primitives.cc
index 0a75167..75e3e4f 100644
--- a/rapicorn/primitives.cc
+++ b/rapicorn/primitives.cc
@@ -293,4 +293,59 @@ Plane::combine (const Plane &src, CombineType ct)
}
}
+Affine
+Affine::from_triangles (Point src_a, Point src_b, Point src_c,
+ Point dst_a, Point dst_b, Point dst_c)
+{
+ const double Ax = src_a.x, Ay = src_a.y;
+ const double Bx = src_b.x, By = src_b.y;
+ const double Cx = src_c.x, Cy = src_c.y;
+ const double ax = dst_a.x, ay = dst_a.y;
+ const double bx = dst_b.x, by = dst_b.y;
+ const double cx = dst_c.x, cy = dst_c.y;
+
+ /* solve the linear equation system:
+ * ax = Ax * matrix.xx + Ay * matrix.xy + matrix.xz;
+ * ay = Ax * matrix.yx + Ay * matrix.yy + matrix.yz;
+ * bx = Bx * matrix.xx + By * matrix.xy + matrix.xz;
+ * by = Bx * matrix.yx + By * matrix.yy + matrix.yz;
+ * cx = Cx * matrix.xx + Cy * matrix.xy + matrix.xz;
+ * cy = Cx * matrix.yx + Cy * matrix.yy + matrix.yz;
+ * for matrix.*
+ */
+ double AxBy = Ax * By, AyBx = Ay * Bx;
+ double AxCy = Ax * Cy, AyCx = Ay * Cx;
+ double BxCy = Bx * Cy, ByCx = By * Cx;
+ double divisor = AxBy - AyBx - AxCy + AyCx + BxCy - ByCx;
+
+ if (fabs (divisor) < 1e-9)
+ return false;
+
+ Affine matrix;
+ matrix.xx = By * ax - Ay * bx + Ay * cx - Cy * ax - By * cx + Cy * bx;
+ matrix.yx = By * ay - Ay * by + Ay * cy - Cy * ay - By * cy + Cy * by;
+ matrix.xy = Ax * bx - Bx * ax - Ax * cx + Cx * ax + Bx * cx - Cx * bx;
+ matrix.yy = Ax * by - Bx * ay - Ax * cy + Cx * ay + Bx * cy - Cx * by;
+ matrix.xz = AxBy * cx - AxCy * bx - AyBx * cx + AyCx * bx + BxCy * ax - ByCx * ax;
+ matrix.yz = AxBy * cy - AxCy * by - AyBx * cy + AyCx * by + BxCy * ay - ByCx * ay;
+ double rec_divisor = 1.0 / divisor;
+ matrix.xx *= rec_divisor;
+ matrix.xy *= rec_divisor;
+ matrix.xz *= rec_divisor;
+ matrix.yx *= rec_divisor;
+ matrix.yy *= rec_divisor;
+ matrix.yz *= rec_divisor;
+
+ return true;
+}
+
+String
+Affine::string() const
+{
+ char buffer[6 * 64 + 128];
+ sprintf (buffer, "{ { %.17g, %.17g, %.17g }, { %.17g, %.17g, %.17g } }",
+ xx, xy, xz, yx, yy, yz);
+ return String (buffer);
+}
+
} // Rapicorn
diff --git a/rapicorn/primitives.hh b/rapicorn/primitives.hh
index 6523797..df711db 100644
--- a/rapicorn/primitives.hh
+++ b/rapicorn/primitives.hh
@@ -531,6 +531,286 @@ public:
}
};
+/* --- Affine --- */
+class Affine {
+protected:
+ /* ( xx yx )
+ * ( xy yy )
+ * ( xz yz )
+ */
+ double xx, xy, xz, yx, yy, yz;
+public:
+ Affine (double cxx = 1,
+ double cxy = 0,
+ double cxz = 0,
+ double cyx = 0,
+ double cyy = 1,
+ double cyz = 0) :
+ xx (cxx), xy (cxy), xz (cxz),
+ yx (cyx), yy (cyy), yz (cyz)
+ {}
+ Affine&
+ translate (double tx, double ty)
+ {
+ multiply (Affine (1, 0, tx, 0, 1, ty));
+ return *this;
+ }
+ Affine&
+ translate (Point p)
+ {
+ multiply (Affine (1, 0, p.x, 0, 1, p.y));
+ return *this;
+ }
+ Affine&
+ set_translation (double tx, double ty)
+ {
+ xz = tx;
+ yz = ty;
+ return *this;
+ }
+ Affine&
+ hflip()
+ {
+ xx = -xx;
+ yx = -yx;
+ xz = -xz;
+ return *this;
+ }
+ Affine&
+ vflip()
+ {
+ xy = -xy;
+ yy = -yy;
+ yz = -yz;
+ return *this;
+ }
+ Affine&
+ rotate (double theta)
+ {
+ double s = sin (theta);
+ double c = cos (theta);
+ return multiply (Affine (c, -s, 0, s, c, 0));
+ }
+ Affine&
+ rotate (double theta, Point anchor)
+ {
+ translate (anchor.x, anchor.y);
+ rotate (theta);
+ return translate (-anchor.x, -anchor.y);
+ }
+ Affine&
+ scale (double sx, double sy)
+ {
+ xx *= sx;
+ xy *= sy;
+ yx *= sx;
+ yy *= sy;
+ return *this;
+ }
+ Affine&
+ shear (double shearx, double sheary)
+ {
+ return multiply (Affine (1, shearx, 0, sheary, 1, 0));
+ }
+ Affine&
+ shear (double theta)
+ {
+ return multiply (Affine (1, tan (theta), 0, 0, 1, 0));
+ }
+ Affine&
+ multiply (const Affine &a2)
+ {
+ Affine dst; // dst * point = this * (a2 * point)
+ dst.xx = a2.xx * xx + a2.yx * xy;
+ dst.xy = a2.xy * xx + a2.yy * xy;
+ dst.xz = a2.xz * xx + a2.yz * xy + xz;
+ dst.yx = a2.xx * yx + a2.yx * yy;
+ dst.yy = a2.xy * yx + a2.yy * yy;
+ dst.yz = a2.xz * yx + a2.yz * yy + yz;
+ return *this = dst;
+ }
+ Affine&
+ multiply_swapped (const Affine &a2)
+ {
+ Affine dst; // dst * point = a2 * (this * point)
+ dst.xx = xx * a2.xx + yx * a2.xy;
+ dst.xy = xy * a2.xx + yy * a2.xy;
+ dst.xz = xz * a2.xx + yz * a2.xy + a2.xz;
+ dst.yx = xx * a2.yx + yx * a2.yy;
+ dst.yy = xy * a2.yx + yy * a2.yy;
+ dst.yz = xz * a2.yx + yz * a2.yy + a2.yz;
+ return *this = dst;
+ }
+ Point
+ point (const Point &s) const
+ {
+ Point d;
+ d.x = xx * s.x + xy * s.y + xz;
+ d.y = yx * s.x + yy * s.y + yz;
+ return d;
+ }
+ Point point (double x, double y) const { return point (Point (x, y)); }
+ double
+ determinant() const
+ {
+ /* if this is != 0, the affine is invertible */
+ return xx * yy - xy * yx;
+ }
+ double
+ expansion() const
+ {
+ return sqrt (fabs (determinant()));
+ }
+ Affine&
+ invert()
+ {
+ double rec_det = 1.0 / determinant();
+ Affine dst (yy, -xy, 0, -yx, xx, 0);
+ dst.xx *= rec_det;
+ dst.xy *= rec_det;
+ dst.yx *= rec_det;
+ dst.yy *= rec_det;
+ dst.xz = -(dst.xx * xz + dst.xy * yz);
+ dst.yz = -(dst.yy * yz + dst.yx * xz);
+ return *this = dst;
+ }
+ Point
+ ipoint (const Point &s) const
+ {
+ double rec_det = 1.0 / determinant();
+ Point d;
+ d.x = yy * s.x - xy * s.y;
+ d.x *= rec_det;
+ d.x -= xz;
+ d.y = xx * s.y - yx * s.x;
+ d.y *= rec_det;
+ d.y -= yz;
+ return d;
+ }
+ Point ipoint (double x, double y) const { return ipoint (Point (x, y)); }
+ Point
+ operator* (const Point &p) const
+ {
+ return point (p);
+ }
+ Affine
+ operator* (const Affine &a2) const
+ {
+ return Affine (*this).multiply (a2);
+ }
+ Affine&
+ operator= (const Affine &a2)
+ {
+ xx = a2.xx;
+ xy = a2.xy;
+ xz = a2.xz;
+ yx = a2.yx;
+ yy = a2.yy;
+ yz = a2.yz;
+ return *this;
+ }
+ bool
+ is_identity () const
+ {
+ return xx == 1 && xy == 0 && xz == 0 && yx == 0 && yy == 1 && yz == 0;
+ }
+ Affine
+ create_inverse () const
+ {
+ Affine inv (*this);
+ return inv.invert();
+ }
+ String string() const;
+ static Affine from_triangles (Point src_a, Point src_b, Point src_c,
+ Point dst_a, Point dst_b, Point dst_c);
+ struct VectorReturn { double x, y, z; };
+ VectorReturn x() const { VectorReturn v = { xx, xy, xz }; return v; }
+ VectorReturn y() const { VectorReturn v = { yx, yy, yz }; return v; }
+};
+struct AffineIdentity : Affine {
+ AffineIdentity() :
+ Affine (1, 0, 0, 0, 1, 0)
+ {}
+};
+struct AffineHFlip : Affine {
+ AffineHFlip() :
+ Affine (-1, 0, 0, 0, 1, 0)
+ {}
+};
+struct AffineVFlip : Affine {
+ AffineVFlip() :
+ Affine (1, 0, 0, 0, -1, 0)
+ {}
+};
+struct AffineTranslate : Affine {
+ AffineTranslate (double tx, double ty)
+ {
+ xx = 1;
+ xy = 0;
+ xz = tx;
+ yx = 0;
+ yy = 1;
+ yz = ty;
+ }
+ AffineTranslate (Point p)
+ {
+ xx = 1;
+ xy = 0;
+ xz = p.x;
+ yx = 0;
+ yy = 1;
+ yz = p.y;
+ }
+};
+struct AffineScale : Affine {
+ AffineScale (double sx, double sy)
+ {
+ xx = sx;
+ xy = 0;
+ xz = 0;
+ yx = 0;
+ yy = sy;
+ yz = 0;
+ }
+};
+struct AffineRotate : Affine {
+ AffineRotate (double theta)
+ {
+ double s = sin (theta);
+ double c = cos (theta);
+ xx = c;
+ xy = -s;
+ xz = 0;
+ yx = s;
+ yy = c;
+ yz = 0;
+ }
+ AffineRotate (double theta, Point anchor)
+ {
+ rotate (theta, anchor);
+ }
+};
+struct AffineShear : Affine {
+ AffineShear (double shearx, double sheary)
+ {
+ xx = 1;
+ xy = shearx;
+ xz = 0;
+ yx = sheary;
+ yy = 1;
+ yz = 0;
+ }
+ AffineShear (double theta)
+ {
+ xx = 1;
+ xy = tan (theta);
+ xz = 0;
+ yx = 0;
+ yy = 1;
+ yz = 0;
+ }
+};
+
} // Rapicorn
#endif /* __RAPICORN_PRIMITIVES_HH__ */
diff --git a/rapicorn/private.hh b/rapicorn/private.hh
index b5f8a46..f6f9a9e 100644
--- a/rapicorn/private.hh
+++ b/rapicorn/private.hh
@@ -57,7 +57,7 @@ namespace Rapicorn {
#define assert_not_reached() BIRNET_ASSERT_NOT_REACHED()
#undef assert
#define assert(e) BIRNET_ASSERT(e)
-#define MakeProperty BIRNET_MakeProperty
+#define MakeProperty RAPICORN_MakeProperty
#define PRIVATE_CLASS_COPY(C) BIRNET_PRIVATE_CLASS_COPY(C)
} // Rapicorn
diff --git a/rapicorn/rapicorn.hh b/rapicorn/rapicorn.hh
index 9382e28..8821220 100644
--- a/rapicorn/rapicorn.hh
+++ b/rapicorn/rapicorn.hh
@@ -26,5 +26,8 @@
#include <rapicorn/events.hh>
#include <rapicorn/appearance.hh>
#include <rapicorn/properties.hh>
+#include <rapicorn/item.hh>
+#include <rapicorn/container.hh>
+#include <rapicorn/table.hh>
#endif /* __RAPICORN_HH__ */
diff --git a/rapicorn/root.cc b/rapicorn/root.cc
new file mode 100644
index 0000000..ce152d2
--- a/dev/null
+++ b/rapicorn/root.cc
@@ -0,0 +1,759 @@
+/* Rapicorn
+ * Copyright (C) 2005 Tim Janik
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+#include "container.hh"
+#include "containerimpl.hh"
+using namespace std;
+
+namespace Rapicorn {
+
+const PropertyList&
+Container::list_properties()
+{
+ static Property *properties[] = {
+ };
+ static const PropertyList property_list (properties, Item::list_properties());
+ return property_list;
+}
+
+static DataKey<Container*> child_container_key;
+
+void
+Container::child_container (Container *child_container)
+{
+ if (child_container && !child_container->has_ancestor (*this))
+ throw Exception ("child container is not descendant of container \"", name(), "\": ", child_container->name());
+ set_data (&child_container_key, child_container);
+}
+
+Container&
+Container::child_container ()
+{
+ Container *container = get_data (&child_container_key);
+ if (!container)
+ container = this;
+ return *container;
+}
+
+void
+Container::add (Item &item, const PackPropertyList &pack_plist)
+{
+ if (item.parent())
+ throw Exception ("not adding item with parent: ", item.name());
+ item.ref();
+ Container &container = child_container();
+ if (this != &container)
+ {
+ container.add (item, pack_plist);
+ return;
+ }
+ bool added = container.add_child (item, pack_plist);
+ item.unref();
+ if (added)
+ item.invalidate();
+ else
+ throw Exception ("invalid attempt to add child: ", item.name());
+}
+
+void
+Container::add (Item *item, const PackPropertyList &pack_plist)
+{
+ if (!item)
+ throw NullPointer();
+ add (*item, pack_plist);
+}
+
+void
+Container::dispose_item (Item &item)
+{
+ if (&item == get_data (&child_container_key))
+ child_container (NULL);
+}
+
+void
+Container::remove (Item &item)
+{
+ Container *container = item.parent_container();
+ if (!container)
+ throw NullPointer();
+ item.ref();
+ item.invalidate();
+ Container *dcontainer = container;
+ while (dcontainer)
+ {
+ dcontainer->dispose_item (item);
+ dcontainer = dcontainer->parent_container();
+ }
+ container->remove_child (item);
+ item.unref();
+}
+
+void
+Container::point_children (double x,
+ double y,
+ Affine affine,
+ std::vector<Item*> &stack)
+{
+ Affine a = affine;
+ for (ChildWalker cw = local_children(); cw.has_next(); cw++)
+ if (cw->point (x, y, a))
+ {
+ cw->ref();
+ stack.push_back (&*cw);
+ Container *c = dynamic_cast<Container*> (&*cw);
+ if (c)
+ c->point_children (x, y, affine, stack);
+ }
+}
+
+bool
+Container::match_interface (InterfaceMatch &imatch,
+ const String &ident)
+{
+ if (imatch.done() ||
+ sig_find_interface.emit (imatch, ident) ||
+ ((!ident[0] || ident == name()) && imatch.match (this)))
+ return true;
+ for (ChildWalker cw = local_children(); cw.has_next(); cw++)
+ if (cw->match_interface (imatch, ident))
+ break;
+ return imatch.done();
+}
+
+void
+Container::render (Plane &plane,
+ Affine affine)
+{
+ for (ChildWalker cw = local_children(); cw.has_next(); cw++)
+ {
+ if (!cw->drawable())
+ continue;
+ const Allocation area = cw->allocation();
+ Plane scratch = Plane::create_from_intersection (plane, Point (area.x, area.y), area.width, area.height);
+ if (cw->test_flags (INVALID_REQUISITION))
+ warning ("rendering item with invalid %s: %s (%p)", "requisition", cw->name().c_str(), &*cw);
+ if (cw->test_flags (INVALID_ALLOCATION))
+ warning ("rendering item with invalid %s: %s (%p)", "allocation", cw->name().c_str(), &*cw);
+ cw->render (scratch, affine);
+ // plane.combine (scratch, COMBINE_VALUE);
+ plane.combine (scratch, COMBINE_NORMAL);
+ }
+}
+
+void
+Container::debug_tree (String indent)
+{
+ printf ("%s%s(%p) (%dx%d%+d%+d)\n", indent.c_str(), this->name().c_str(), this,
+ allocation().width, allocation().height, allocation().x, allocation().y);
+ for (ChildWalker cw = local_children(); cw.has_next(); cw++)
+ {
+ Item &child = *cw;
+ Container *c = dynamic_cast<Container*> (&child);
+ if (c)
+ c->debug_tree (indent + " ");
+ else
+ printf (" %s%s(%p) (%dx%d%+d%+d)\n", indent.c_str(), child.name().c_str(), &child,
+ child.allocation().width, child.allocation().height, child.allocation().x, child.allocation().y);
+ }
+}
+
+Container::ChildPacker::ChildPacker ()
+{}
+
+Container::Packer::Packer (ChildPacker *cp) :
+ m_child_packer (ref_sink (cp))
+{}
+
+Container::Packer::Packer (const Packer &src) :
+ m_child_packer (ref_sink (src.m_child_packer))
+{}
+
+Property*
+Container::Packer::lookup_property (const String &property_name)
+{
+ typedef std::map<const String, Property*> PropertyMap;
+ static std::map<const PropertyList*,PropertyMap*> plist_map;
+ /* find/construct property map */
+ const PropertyList &plist = list_properties();
+ PropertyMap *pmap = plist_map[&plist];
+ if (!pmap)
+ {
+ pmap = new PropertyMap;
+ for (uint i = 0; i < plist.n_properties; i++)
+ (*pmap)[plist.properties[i]->ident] = plist.properties[i];
+ plist_map[&plist] = pmap;
+ }
+ PropertyMap::iterator it = pmap->find (property_name);
+ if (it != pmap->end())
+ return it->second;
+ else
+ return NULL;
+}
+
+void
+Container::Packer::set_property (const String &property_name,
+ const String &value,
+ const nothrow_t &nt)
+{
+ Property *prop = lookup_property (property_name);
+ if (prop)
+ {
+ m_child_packer->update();
+ prop->set_value (m_child_packer, value);
+ m_child_packer->commit();
+ }
+ else if (&nt == &dothrow)
+ throw Exception ("no such property: ", property_name);
+}
+
+String
+Container::Packer::get_property (const String &property_name)
+{
+ Property *prop = lookup_property (property_name);
+ if (!prop)
+ throw Exception ("no such property: ", property_name);
+ m_child_packer->update();
+ return prop->get_value (m_child_packer);
+}
+
+void
+Container::Packer::apply_properties (const PackPropertyList &pack_plist)
+{
+ m_child_packer->update();
+ for (PackPropertyList::const_iterator it = pack_plist.begin(); it != pack_plist.end(); it++)
+ set_property (it->first, it->second, nothrow);
+ m_child_packer->commit();
+}
+
+const PropertyList&
+Container::Packer::list_properties()
+{
+ return m_child_packer->list_properties();
+}
+
+Container::Packer::~Packer ()
+{
+ unref (m_child_packer);
+}
+
+Container::Packer
+Container::child_packer (Item &item)
+{
+ Container *container = item.parent_container();
+ if (!container)
+ throw NullPointer();
+ return container->create_packer (item);
+}
+
+Container::ChildPacker*
+Container::void_packer ()
+{
+ class PackerSingleton : public ChildPacker {
+ PackerSingleton() { ref_sink(); }
+ public:
+ static PackerSingleton*
+ dummy_packer()
+ {
+ static PackerSingleton *singleton = NULL;
+ if (!singleton)
+ singleton = new PackerSingleton;
+ return singleton;
+ }
+ ~PackerSingleton() { assert_not_reached(); }
+ virtual const PropertyList&
+ list_properties ()
+ {
+ static Property *properties[] = { };
+ static const PropertyList property_list (properties);
+ return property_list;
+ }
+ virtual void update () {}
+ virtual void commit () {}
+ };
+ return PackerSingleton::dummy_packer();
+}
+
+SingleContainerImpl::SingleContainerImpl () :
+ child_item (NULL)
+{}
+
+Container::ChildWalker
+SingleContainerImpl::local_children ()
+{
+ Item **iter = &child_item, **iend = iter;
+ if (child_item)
+ iend++;
+ return value_walker (PointerIterator<Item*> (iter), PointerIterator<Item*> (iend));
+}
+
+bool
+SingleContainerImpl::add_child (Item &item, const PackPropertyList &pack_plist)
+{
+ if (!child_item)
+ {
+ item.ref_sink();
+ item.set_parent (this);
+ child_item = &item;
+ return true;
+ }
+ else
+ return false;
+}
+
+void
+SingleContainerImpl::remove_child (Item &item)
+{
+ assert (child_item == &item); /* ensured by remove() */
+ child_item = NULL;
+ item.set_parent (NULL);
+ item.unref();
+}
+
+void
+SingleContainerImpl::size_request (Requisition &requisition)
+{
+ bool chspread = false, cvspread = false;
+ if (has_visible_child())
+ {
+ Item &child = get_child();
+ Requisition cr = child.size_request ();
+ requisition.width += cr.width;
+ requisition.height += cr.height;
+ chspread = child.hspread();
+ cvspread = child.vspread();
+ }
+ set_flag (HSPREAD_CONTAINER, chspread);
+ set_flag (VSPREAD_CONTAINER, cvspread);
+}
+
+void
+SingleContainerImpl::size_allocate (Allocation area)
+{
+ allocation (area);
+ if (has_children())
+ {
+ Item &child = get_child();
+ if (child.visible())
+ child.set_allocation (area);
+ }
+}
+
+Container::Packer
+SingleContainerImpl::create_packer (Item &item)
+{
+ return void_packer(); /* no child properties */
+}
+
+SingleContainerImpl::~SingleContainerImpl()
+{
+ while (child_item)
+ remove (child_item);
+}
+
+MultiContainerImpl::MultiContainerImpl ()
+{}
+
+bool
+MultiContainerImpl::add_child (Item &item, const PackPropertyList &pack_plist)
+{
+ item.ref_sink();
+ item.set_parent (this);
+ items.push_back (&item);
+ return true;
+}
+
+void
+MultiContainerImpl::remove_child (Item &item)
+{
+ vector<Item*>::iterator it;
+ for (it = items.begin(); it != items.end(); it++)
+ if (*it == &item)
+ {
+ items.erase (it);
+ item.set_parent (NULL);
+ item.unref();
+ return;
+ }
+ assert_not_reached();
+}
+
+MultiContainerImpl::~MultiContainerImpl()
+{
+ while (items.size())
+ remove (*items[items.size() - 1]);
+}
+
+Root::Root() :
+ sig_expose (*this)
+{}
+
+class RootImpl : public Root, public SingleContainerImpl {
+public:
+ RootImpl()
+ {
+ Appearance *appearance = Appearance::create_default();
+ style (appearance->create_style ("normal"));
+ unref (appearance);
+ set_flag (PARENT_SENSITIVE, true);
+ }
+ virtual void
+ size_request (Requisition &requisition)
+ {
+ if (has_visible_child())
+ {
+ Item &child = get_child();
+ requisition = child.size_request();
+ }
+ }
+ virtual void
+ size_allocate (Allocation area)
+ {
+ allocation (area);
+ if (!has_visible_child())
+ return;
+ Item &child = get_child();
+ Requisition rq = child.size_request();
+ child.set_allocation (area);
+ }
+ virtual void
+ expose (const Allocation &area)
+ {
+ sig_expose.emit (area);
+ }
+protected:
+ vector<Item*>
+ item_difference (const vector<Item*> &clist, /* preserve order of clist */
+ const vector<Item*> &cminus)
+ {
+ map<Item*,bool> mminus;
+ for (uint i = 0; i < cminus.size(); i++)
+ mminus[cminus[i]] = true;
+ vector<Item*> result;
+ for (uint i = 0; i < clist.size(); i++)
+ if (!mminus[clist[i]])
+ result.push_back (clist[i]);
+ return result;
+ }
+ EventContext last_event_context;
+ vector<Item*> last_entered_children;
+ bool
+ dispatch_mouse_movement (const EventContext &econtext)
+ {
+ last_event_context = econtext;
+ EventMouse &mevent = *create_event_mouse (MOUSE_MOVE, econtext);
+ vector<Item*> pierced;
+ /* figure all entered children */
+ Item *grab_item = get_grab();
+ if (grab_item)
+ {
+ if (grab_item->point (mevent.x, mevent.y, Affine()))
+ pierced.push_back (ref (grab_item));
+ }
+ else if (drawable())
+ {
+ pierced.push_back (ref (this)); /* root receives all mouse events */
+ point_children (mevent.x, mevent.y, Affine(), pierced);
+ }
+ /* send leave events */
+ vector<Item*> left_children = item_difference (last_entered_children, pierced);
+ mevent.type = MOUSE_LEAVE;
+ for (vector<Item*>::reverse_iterator it = left_children.rbegin(); it != left_children.rend(); it++)
+ (*it)->handle_event (mevent);
+ /* send enter events */
+ vector<Item*> entered_children = item_difference (pierced, last_entered_children);
+ mevent.type = MOUSE_ENTER;
+ for (vector<Item*>::reverse_iterator it = entered_children.rbegin(); it != entered_children.rend(); it++)
+ (*it)->handle_event (mevent);
+ /* send actual move event */
+ bool handled = false;
+ mevent.type = MOUSE_MOVE;
+ for (vector<Item*>::reverse_iterator it = pierced.rbegin(); it != pierced.rend(); it++)
+ if (!handled && (*it)->sensitive())
+ handled = (*it)->handle_event (mevent);
+ /* cleanup */
+ delete &mevent;
+ for (vector<Item*>::reverse_iterator it = last_entered_children.rbegin(); it != last_entered_children.rend(); it++)
+ (*it)->unref();
+ last_entered_children = pierced;
+ return handled;
+ }
+ bool
+ dispatch_event_to_pierced_or_grab (Event &event)
+ {
+ vector<Item*> pierced;
+ /* figure all entered children */
+ Item *grab_item = get_grab();
+ if (grab_item)
+ pierced.push_back (ref (grab_item));
+ else if (drawable())
+ {
+ pierced.push_back (ref (this)); /* root receives all events */
+ point_children (event.x, event.y, Affine(), pierced);
+ }
+ /* send actual event */
+ bool handled = false;
+ for (vector<Item*>::reverse_iterator it = pierced.rbegin(); it != pierced.rend(); it++)
+ {
+ if (!handled && (*it)->sensitive())
+ handled = (*it)->handle_event (event);
+ (*it)->unref();
+ }
+ return handled;
+ }
+private:
+ struct ButtonState {
+ Item *item;
+ uint button;
+ ButtonState (Item *i, uint b) : item (i), button (b) {}
+ ButtonState () : item (NULL), button (0) {}
+ bool operator< (const ButtonState &bs2) const
+ {
+ const ButtonState &bs1 = *this;
+ return bs1.item < bs2.item || (bs1.item == bs2.item &&
+ bs1.button < bs2.button);
+ }
+ };
+ map<ButtonState,uint> button_state_map;
+ bool
+ dispatch_button_press (const EventContext &econtext,
+ uint button,
+ uint press_count)
+ {
+ assert (press_count >= 1 && press_count <= 3);
+ EventButton &bevent = *create_event_button (press_count == 3 ? BUTTON_3PRESS : press_count == 2 ? BUTTON_2PRESS : BUTTON_PRESS, econtext, button);
+ /* figure all entered children */
+ const vector<Item*> &pierced = last_entered_children;
+ /* send actual event */
+ bool handled = false;
+ for (vector<Item*>::const_reverse_iterator it = pierced.rbegin(); it != pierced.rend(); it++)
+ if (!handled && (*it)->sensitive())
+ {
+ ButtonState bs (*it, bevent.button);
+ if (button_state_map[bs] == 0) /* no press delivered for <button> on <item> yet */
+ {
+ button_state_map[bs] = press_count; /* record single press */
+ handled = (*it)->handle_event (bevent);
+ }
+ }
+ delete &bevent;
+ return handled;
+ }
+ bool
+ dispatch_button_release (const EventContext &econtext,
+ uint button)
+ {
+ EventButton &bevent = *create_event_button (BUTTON_RELEASE, econtext, button);
+ bool handled = false;
+ for (map<ButtonState,uint>::iterator it = button_state_map.begin(); it != button_state_map.end();)
+ {
+ const ButtonState &bs = it->first;
+ uint press_count = it->second;
+ map<ButtonState,uint>::iterator current = it++;
+ if (bs.button == button)
+ {
+ if (press_count == 3)
+ bevent.type = BUTTON_3RELEASE;
+ else if (press_count == 2)
+ bevent.type = BUTTON_2RELEASE;
+ handled |= bs.item->handle_event (bevent);
+ button_state_map.erase (current);
+ }
+ }
+ bevent.type = BUTTON_RELEASE;
+ delete &bevent;
+ return handled;
+ }
+ void
+ cancel_item_events (Item *item)
+ {
+ /* cancel enter events */
+ for (int i = last_entered_children.size(); i > 0;)
+ {
+ Item *current = last_entered_children[--i]; /* walk backwards */
+ if (item == current || !item)
+ {
+ EventMouse *mevent = create_event_mouse (MOUSE_LEAVE, last_event_context);
+ current->handle_event (*mevent);
+ delete mevent;
+ current->unref();
+ last_entered_children.erase (last_entered_children.begin() + i);
+ }
+ }
+ /* cancel button press events */
+ for (map<ButtonState,uint>::iterator it = button_state_map.begin(); it != button_state_map.end();)
+ {
+ const ButtonState &bs = it->first;
+ map<ButtonState,uint>::iterator current = it++;
+ if (bs.item == item || !item)
+ {
+ EventButton *bevent = create_event_button (BUTTON_CANCELED, last_event_context, bs.button);
+ bs.item->handle_event (*bevent);
+ delete bevent;
+ button_state_map.erase (current);
+ }
+ }
+ }
+ virtual void cancel_item_events (Item &item) { cancel_item_events (&item); }
+public:
+ virtual void
+ dispatch_cancel_events ()
+ {
+ cancel_item_events (NULL);
+ }
+ virtual bool
+ dispatch_button_event (const EventContext &econtext,
+ bool is_press,
+ uint button)
+ {
+ bool handled = false;
+ dispatch_mouse_movement (econtext);
+ if (is_press)
+ handled = dispatch_button_press (econtext, button, 1);
+ else
+ handled = dispatch_button_release (econtext, button);
+ return handled;
+ }
+ virtual bool
+ dispatch_leave_event (const EventContext &econtext)
+ {
+ dispatch_mouse_movement (econtext);
+ EventMouse &mevent = *create_event_mouse (MOUSE_LEAVE, econtext);
+ /* send leave events */
+ while (last_entered_children.size())
+ {
+ Item *item = last_entered_children.back();
+ last_entered_children.pop_back();
+ item->handle_event (mevent);
+ item->unref();
+ }
+ delete &mevent;
+ return false;
+ }
+ virtual bool
+ dispatch_move_event (const EventContext &econtext)
+ {
+ bool handled = dispatch_mouse_movement (econtext);
+ return handled;
+ }
+
+ virtual bool
+ dispatch_focus_event (const EventContext &econtext,
+ bool is_in)
+ {
+ dispatch_mouse_movement (econtext);
+ EventFocus *fevent = create_event_focus (is_in ? FOCUS_IN : FOCUS_OUT, econtext);
+ bool handled = false; // dispatch_event_to_pierced_or_grab (*fevent);
+ delete fevent;
+ return handled;
+ }
+ virtual bool
+ dispatch_key_event (const EventContext &econtext,
+ bool is_press,
+ KeyValue key,
+ const char *key_name)
+ {
+ dispatch_mouse_movement (econtext);
+ EventKey *kevent = create_event_key (is_press ? KEY_PRESS : KEY_RELEASE, econtext, key, key_name);
+ Item *grab_item = get_grab();
+ grab_item = grab_item ? grab_item : this;
+ bool handled = grab_item->handle_event (*kevent);
+ delete kevent;
+ return handled;
+ }
+ virtual bool
+ dispatch_scroll_event (const EventContext &econtext,
+ EventType scroll_type)
+ {
+ bool handled = false;
+ if (scroll_type == SCROLL_UP || scroll_type == SCROLL_RIGHT || scroll_type == SCROLL_DOWN || scroll_type == SCROLL_LEFT)
+ {
+ dispatch_mouse_movement (econtext);
+ EventScroll *sevent = create_event_scroll (scroll_type, econtext);
+ handled = dispatch_event_to_pierced_or_grab (*sevent);
+ delete sevent;
+ }
+ return handled;
+ }
+private:
+ vector<Item*> grab_stack;
+ virtual void
+ remove_grab_item (Item &child)
+ {
+ bool stack_changed = false;
+ for (int i = grab_stack.size() - 1; i >= 0; i--)
+ if (grab_stack[i] == &child)
+ {
+ grab_stack.erase (grab_stack.begin() + i);
+ stack_changed = true;
+ }
+ if (stack_changed)
+ grab_stack_changed();
+ }
+ void
+ grab_stack_changed()
+ {
+ dispatch_move_event (last_event_context);
+ }
+public:
+ virtual void
+ add_grab (Item &child)
+ {
+ if (!child.has_ancestor (*this))
+ throw Exception ("child is not descendant of container \"", name(), "\": ", child.name());
+ grab_stack.push_back (&child);
+ }
+ virtual void
+ remove_grab (Item &child)
+ {
+ for (int i = grab_stack.size() - 1; i >= 0; i--)
+ if (grab_stack[i] == &child)
+ {
+ grab_stack.erase (grab_stack.begin() + i);
+ grab_stack_changed();
+ return;
+ }
+ throw Exception ("no such child in grab stack: ", child.name());
+ }
+ virtual Item*
+ get_grab ()
+ {
+ for (int i = grab_stack.size() - 1; i >= 0; i--)
+ if (grab_stack[i]->visible())
+ return grab_stack[i];
+ return NULL;
+ }
+ virtual void
+ dispose_item (Item &item)
+ {
+ remove_grab_item (item);
+ cancel_item_events (item);
+ SingleContainerImpl::dispose_item (item);
+ }
+ using Item::render;
+ virtual void
+ render (Plane &plane)
+ {
+ plane.fill (background());
+ render (plane, Affine());
+ // render_coordinates (plane, Affine());
+ }
+};
+
+static const ItemFactory<RootImpl> root_factory ("Rapicorn::Root");
+
+} // Rapicorn
diff --git a/rapicorn/root.hh b/rapicorn/root.hh
new file mode 100644
index 0000000..aa515b2
--- a/dev/null
+++ b/rapicorn/root.hh
@@ -0,0 +1,124 @@
+/* Rapicorn
+ * Copyright (C) 2005 Tim Janik
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+#ifndef __RAPICORN_CONTAINER_HH__
+#define __RAPICORN_CONTAINER_HH__
+
+#include <rapicorn/item.hh>
+
+namespace Rapicorn {
+
+/* --- Container --- */
+struct Container : public virtual Item {
+ typedef std::map<String,String> PackPropertyList;
+protected:
+ virtual bool match_interface (InterfaceMatch &imatch,
+ const String &ident);
+ virtual bool add_child (Item &item,
+ const PackPropertyList &pack_plist = PackPropertyList()) = 0;
+ virtual void remove_child (Item &item) = 0;
+ void hide_child (Item &child) { child.set_flag (HIDDEN_CHILD, false); }
+ void show_child (Item &child) { child.set_flag (HIDDEN_CHILD, true); }
+ virtual void dispose_item (Item &item);
+public:
+ typedef Walker<Item> ChildWalker;
+ void child_container (Container *child_container);
+ Container& child_container ();
+ virtual ChildWalker local_children () = 0;
+ virtual bool has_children () = 0;
+ void add (Item &item, const PackPropertyList &pack_plist = PackPropertyList());
+ void add (Item *item, const PackPropertyList &pack_plist = PackPropertyList());
+ void remove (Item &item);
+ void remove (Item *item) { if (item) remove (*item); else throw NullPointer(); }
+ virtual
+ const PropertyList& list_properties (); /* essentially item properties */
+ virtual void point_children (double x,
+ double y,
+ Affine affine,
+ std::vector<Item*> &stack);
+ virtual void render (Plane &plane,
+ Affine affine);
+ void debug_tree (String indent = String());
+ /* child properties */
+ struct ChildPacker : public virtual ReferenceCountImpl {
+ virtual const PropertyList& list_properties () = 0;
+ virtual void update () = 0; /* fetch real pack properties */
+ virtual void commit () = 0; /* assign pack properties */
+ explicit ChildPacker ();
+ private:
+ /*Copy*/ ChildPacker (const ChildPacker&);
+ ChildPacker& operator= (const ChildPacker&);
+ };
+ struct Packer {
+ /*Con*/ Packer (ChildPacker *cp);
+ /*Copy*/ Packer (const Packer &src);
+ void set_property (const String &property_name,
+ const String &value,
+ const nothrow_t &nt = dothrow);
+ String get_property (const String &property_name);
+ Property* lookup_property (const String &property_name);
+ void apply_properties (const PackPropertyList &pplist);
+ const PropertyList& list_properties ();
+ /*Des*/ ~Packer ();
+ private:
+ ChildPacker *m_child_packer;
+ ChildPacker& operator= (const Packer &src);
+ friend class Container;
+ };
+ Packer child_packer (Item &item);
+ Packer child_packer (Item *item) { if (item) return child_packer (*item); else throw NullPointer(); }
+protected:
+ virtual Packer create_packer (Item &item) = 0;
+ static ChildPacker* void_packer ();
+ template<class PackerType>
+ PackerType extract_child_packer (Packer &packer) { return dynamic_cast<PackerType> (packer.m_child_packer); }
+};
+
+/* --- Root --- */
+class Root : public virtual Container {
+protected:
+ explicit Root ();
+ virtual void cancel_item_events (Item &item) = 0;
+public:
+ Signal<Container, void (const Allocation&)> sig_expose;
+ virtual void render (Plane &plane) = 0;
+ /* events */
+ virtual bool dispatch_move_event (const EventContext &econtext) = 0;
+ virtual bool dispatch_leave_event (const EventContext &econtext) = 0;
+ virtual bool dispatch_button_event (const EventContext &econtext,
+ bool is_press,
+ uint button) = 0;
+ virtual bool dispatch_focus_event (const EventContext &econtext,
+ bool is_in) = 0;
+ virtual bool dispatch_key_event (const EventContext &econtext,
+ bool is_press,
+ KeyValue key,
+ const char *key_name) = 0;
+ virtual bool dispatch_scroll_event (const EventContext &econtext,
+ EventType scroll_type) = 0;
+ virtual void dispatch_cancel_events () = 0;
+ virtual void add_grab (Item &child) = 0;
+ void add_grab (Item *child) { throw_if_null (child); return add_grab (*child); }
+ virtual void remove_grab (Item &child) = 0;
+ void remove_grab (Item *child) { throw_if_null (child); return remove_grab (*child); }
+ virtual Item* get_grab () = 0;
+};
+
+} // Rapicorn
+
+#endif /* __RAPICORN_CONTAINER_HH__ */
diff --git a/rapicorn/table.cc b/rapicorn/table.cc
new file mode 100644
index 0000000..df83e2d
--- a/dev/null
+++ b/rapicorn/table.cc
@@ -0,0 +1,838 @@
+/* Rapicorn
+ * Copyright (C) 2005 Tim Janik
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * - This file is derived from GtkTable code which is
+ * Copyright (C) 1996-2002 by the GTK+ project.
+ */
+#include "tableimpl.hh"
+#include "factory.hh"
+
+namespace Rapicorn {
+
+DataKey<TableImpl::Location> TableImpl::child_location_key;
+
+TableImpl::Location
+TableImpl::child_location (Item &child)
+{
+ Location loc = child.get_data (&child_location_key);
+ loc.xexpand |= child.hexpand();
+ loc.yexpand |= child.vexpand();
+ return loc;
+}
+
+void
+TableImpl::child_location (Item &child,
+ Location loc)
+{
+ loc.right_attach = MAX (loc.right_attach, loc.left_attach + 1);
+ loc.top_attach = MAX (loc.top_attach, loc.bottom_attach + 1);
+ child.set_data (&child_location_key, loc);
+}
+
+TableImpl::TablePacker::~TablePacker()
+{}
+
+TableImpl::TablePacker::TablePacker (Item &citem) :
+ item (citem)
+{}
+
+void
+TableImpl::TablePacker::update () /* fetch real pack properties */
+{
+ if (dynamic_cast<Table*> (item.parent()))
+ loc = child_location (item);
+}
+
+void
+TableImpl::TablePacker::commit ()/* assign pack properties */
+{
+ TableImpl *table = dynamic_cast<TableImpl*> (item.parent());
+ if (table)
+ {
+ child_location (item, loc);
+ table->resize_grow (loc.right_attach, loc.top_attach);
+ }
+}
+
+const PropertyList&
+TableImpl::TablePacker::list_properties()
+{
+ static Property *properties[] = {
+ MakeProperty (TablePacker, left_attach, _("Left Attach"), _("Column index to attach the child's left side to"), 0u, 0u, 99999u, 5u, "rw"),
+ MakeProperty (TablePacker, right_attach, _("Right Attach"), _("Column index to attach the child's right side to"), 1u, 1u, 100000u, 5u, "rw"),
+ MakeProperty (TablePacker, bottom_attach, _("Bottom Attach"), _("Column index to attach the child's bottom side to"), 0u, 0u, 99999u, 5u, "rw"),
+ MakeProperty (TablePacker, top_attach, _("Top Attach"), _("Column index to attach the child's top side to"), 1u, 1u, 100000u, 5u, "rw"),
+ MakeProperty (TablePacker, left_padding, _("Left Padding"), _("Amount of padding to add at the child's left side"), 0u, 0u, 65535u, 3u, "rw"),
+ MakeProperty (TablePacker, right_padding, _("Right Padding"), _("Amount of padding to add at the child's right side"), 0u, 0u, 65535u, 3u, "rw"),
+ MakeProperty (TablePacker, bottom_padding, _("Bottom Padding"), _("Amount of padding to add at the child's bottom side"), 0u, 0u, 65535u, 3u, "rw"),
+ MakeProperty (TablePacker, top_padding, _("Top Padding"), _("Amount of padding to add at the child's top side"), 0u, 0u, 65535u, 3u, "rw"),
+ MakeProperty (TablePacker, hshrink, _("Horizontal Shrink"), _("Whether the child may be shrunken horizontally"), false, "rw"),
+ MakeProperty (TablePacker, vshrink, _("Vertical Shrink"), _("Whether the child may be shrunken vertically"), false, "rw"),
+ MakeProperty (TablePacker, hfill, _("Horizontal Fill"), _("Whether the child may fill all extra horizontal space"), true, "rw"),
+ MakeProperty (TablePacker, vfill, _("Vertical Fill"), _("Whether the child may fill all extra vertical space"), true, "rw"),
+ };
+ static const PropertyList property_list (properties);
+ return property_list;
+}
+
+Container::Packer
+TableImpl::create_packer (Item &item)
+{
+ if (item.parent() == this)
+ return Packer (new TablePacker (item));
+ else
+ throw Exception ("foreign child: ", item.name());
+}
+
+bool
+TableImpl::add_child (Item &item, const PackPropertyList &pack_plist)
+{
+ if (MultiContainerImpl::add_child (item, pack_plist)) /* ref, sink, set_parent, insert */
+ {
+ Packer packer = create_packer (item);
+ packer.apply_properties (pack_plist);
+ return true;
+ }
+ return false;
+}
+
+TableImpl::TableImpl() :
+ default_row_spacing (0),
+ default_column_spacing (0),
+ homogeneous_items (false)
+{
+ resize (1, 1);
+}
+
+bool
+TableImpl::is_row_used (uint row)
+{
+ for (ChildWalker cw = local_children(); cw.has_next(); cw++)
+ {
+ Location loc = child_location (*cw);
+ if (row >= loc.bottom_attach && row < loc.top_attach)
+ return true;
+ }
+ return false;
+}
+
+bool
+TableImpl::is_col_used (uint col)
+{
+ for (ChildWalker cw = local_children(); cw.has_next(); cw++)
+ {
+ Location loc = child_location (*cw);
+ if (col >= loc.left_attach && col < loc.right_attach)
+ return true;
+ }
+ return false;
+}
+
+void
+TableImpl::resize_grow (uint n_cols, uint n_rows)
+{
+ resize (MAX (n_cols, cols.size()), MAX (n_rows, rows.size()));
+}
+
+void
+TableImpl::resize (uint n_cols, uint n_rows)
+{
+ n_rows = MAX (n_rows, 1);
+ n_cols = MAX (n_cols, 1);
+ if (n_rows == rows.size() && n_cols == cols.size())
+ return;
+ /* grow as children require */
+ for (ChildWalker cw = local_children(); cw.has_next(); cw++)
+ {
+ Location loc = child_location (*cw);
+ n_rows = MAX (n_rows, loc.top_attach);
+ n_cols = MAX (n_cols, loc.right_attach);
+ }
+ if (n_rows == rows.size() && n_cols == cols.size())
+ return;
+ /* resize rows and cols */
+ if (n_rows != rows.size())
+ {
+ uint i = rows.size();
+ rows.resize (n_rows);
+ for (; i < rows.size(); i++)
+ rows[i].spacing = default_row_spacing;
+ }
+ if (n_cols != cols.size())
+ {
+ uint i = cols.size();
+ cols.resize (n_cols);
+ for (; i < cols.size(); i++)
+ cols[i].spacing = default_column_spacing;
+ }
+ invalidate();
+}
+
+void
+TableImpl::insert_rows (uint first_row, uint n_rows)
+{
+ if (!n_rows)
+ return;
+ resize (cols.size(), rows.size() + n_rows);
+ for (ChildWalker cw = local_children(); cw.has_next(); cw++)
+ {
+ Location loc = child_location (*cw);
+ if (loc.bottom_attach >= first_row)
+ {
+ loc.bottom_attach += n_rows;
+ loc.top_attach += n_rows;
+ child_location (*cw, loc);
+ }
+ else if (first_row < loc.top_attach)
+ {
+ loc.top_attach += n_rows;
+ child_location (*cw, loc);
+ }
+ }
+}
+
+void
+TableImpl::insert_cols (uint first_col, uint n_cols)
+{
+ if (!n_cols)
+ return;
+ resize (cols.size() + n_cols, rows.size());
+ for (ChildWalker cw = local_children(); cw.has_next(); cw++)
+ {
+ Location loc = child_location (*cw);
+ if (loc.left_attach >= first_col)
+ {
+ loc.left_attach += n_cols;
+ loc.right_attach += n_cols;
+ child_location (*cw, loc);
+ }
+ else if (first_col < loc.right_attach)
+ {
+ loc.right_attach += n_cols;
+ child_location (*cw, loc);
+ }
+ }
+}
+
+void
+TableImpl::column_spacing (uint cspacing)
+{
+ default_column_spacing = cspacing;
+ for (uint col = 0; col < cols.size(); col++)
+ cols[col].spacing = default_column_spacing;
+ invalidate();
+}
+
+void
+TableImpl::row_spacing (uint rspacing)
+{
+ default_row_spacing = rspacing;
+ for (uint row = 0; row < rows.size(); row++)
+ rows[row].spacing = default_row_spacing;
+ invalidate();
+}
+
+TableImpl::~TableImpl()
+{}
+
+void
+TableImpl::size_request (Requisition &requisition)
+{
+ size_request_init ();
+ size_request_pass1 ();
+ size_request_pass2 ();
+ size_request_pass3 ();
+ size_request_pass2 ();
+
+ for (uint col = 0; col < cols.size(); col++)
+ requisition.width += cols[col].requisition;
+ for (uint col = 0; col + 1 < cols.size(); col++)
+ requisition.width += cols[col].spacing;
+ for (uint row = 0; row < rows.size(); row++)
+ requisition.height += rows[row].requisition;
+ for (uint row = 0; row + 1 < rows.size(); row++)
+ requisition.height += rows[row].spacing;
+ uint c = 0;
+ for (ChildWalker cw = local_children(); cw.has_next(); cw++)
+ if (cw->visible())
+ c++;
+}
+
+void
+TableImpl::size_allocate (Allocation area)
+{
+ allocation (area);
+ size_allocate_init ();
+ size_allocate_pass1 ();
+ size_allocate_pass2 ();
+}
+
+const PropertyList&
+TableImpl::list_properties()
+{
+ static Property *properties[] = {
+ MakeProperty (Table, homogeneous, _("Homogeneous"), _("Whether all children get the same size"), false, "rw"),
+ MakeProperty (Table, column_spacing, _("Column Spacing"), _("The amount of space between two consecutive columns"), 0, 0, 65535, 10, "rw"),
+ MakeProperty (Table, row_spacing, _("Row Spacing"), _("The amount of space between two consecutive rows"), 0, 0, 65535, 10, "rw"),
+ };
+ static const PropertyList property_list (properties, Container::list_properties());
+ return property_list;
+}
+
+void
+TableImpl::size_request_init()
+{
+ for (uint row = 0; row < rows.size(); row++)
+ {
+ rows[row].requisition = 0;
+ rows[row].expand = false;
+ }
+ for (uint col = 0; col < cols.size(); col++)
+ {
+ cols[col].requisition = 0;
+ cols[col].expand = false;
+ }
+
+ bool chspread = false, cvspread = false;
+ for (ChildWalker cw = local_children(); cw.has_next(); cw++)
+ {
+ /* size request all children */
+ Requisition rq = cw->size_request();
+ if (!cw->visible())
+ continue;
+ chspread |= cw->hspread();
+ cvspread |= cw->vspread();
+ Location loc = child_location (*cw);
+ /* expand cols with single-column expand children */
+ if (loc.left_attach + 1 == loc.right_attach && loc.xexpand)
+ cols[loc.left_attach].expand = true;
+ /* expand rows with single-column expand children */
+ if (loc.bottom_attach + 1 == loc.top_attach && loc.yexpand)
+ rows[loc.bottom_attach].expand = true;
+ }
+ set_flag (HSPREAD_CONTAINER, chspread);
+ set_flag (VSPREAD_CONTAINER, cvspread);
+}
+
+void
+TableImpl::size_request_pass1()
+{
+ for (ChildWalker cw = local_children(); cw.has_next(); cw++)
+ {
+ Requisition crq = cw->size_request();
+ if (!cw->visible())
+ continue;
+ Location loc = child_location (*cw);
+ /* fetch requisition from single-column children */
+ if (loc.left_attach + 1 == loc.right_attach)
+ {
+ uint width = iround (crq.width + loc.left_padding + loc.right_padding);
+ cols[loc.left_attach].requisition = MAX (cols[loc.left_attach].requisition, width);
+ }
+ /* fetch requisition from single-row children */
+ if (loc.bottom_attach + 1 == loc.top_attach)
+ {
+ uint height = iround (crq.height + loc.bottom_padding + loc.top_padding);
+ rows[loc.bottom_attach].requisition = MAX (rows[loc.bottom_attach].requisition, height);
+ }
+ }
+}
+
+void
+TableImpl::size_request_pass2()
+{
+ if (homogeneous())
+ {
+ uint max_width = 0;
+ uint max_height = 0;
+ /* maximise requisition */
+ for (uint col = 0; col < cols.size(); col++)
+ max_width = MAX (max_width, cols[col].requisition);
+ for (uint row = 0; row < rows.size(); row++)
+ max_height = MAX (max_height, rows[row].requisition);
+ /* assign equal requisitions */
+ for (uint col = 0; col < cols.size(); col++)
+ cols[col].requisition = max_width;
+ for (uint row = 0; row < rows.size(); row++)
+ rows[row].requisition = max_height;
+ }
+}
+
+void
+TableImpl::size_request_pass3()
+{
+ for (ChildWalker cw = local_children(); cw.has_next(); cw++)
+ {
+ Location loc = child_location (*cw);
+ if (!cw->visible())
+ continue;
+ /* request remaining space for multi-column children */
+ if (loc.left_attach + 1 != loc.right_attach)
+ {
+ Requisition crq = cw->size_request();
+ /* Check and see if there is already enough space for the child. */
+ uint width = 0;
+ for (uint col = loc.left_attach; col < loc.right_attach; col++)
+ {
+ width += cols[col].requisition;
+ if (col + 1 < loc.right_attach)
+ width += cols[col].spacing;
+ }
+ /* If we need to request more space for this child to fill
+ * its requisition, then divide up the needed space amongst the
+ * columns it spans, favoring expandable columns if any.
+ */
+ if (width < crq.width + loc.left_padding + loc.right_padding)
+ {
+ bool force_expand = false;
+ uint n_expand = 0;
+ width = iround (crq.width + loc.left_padding + loc.right_padding - width);
+ for (uint col = loc.left_attach; col < loc.right_attach; col++)
+ if (cols[col].expand)
+ n_expand++;
+ if (n_expand == 0)
+ {
+ n_expand = loc.right_attach - loc.left_attach;
+ force_expand = true;
+ }
+ for (uint col = loc.left_attach; col < loc.right_attach; col++)
+ if (force_expand || cols[col].expand)
+ {
+ uint extra = width / n_expand;
+ cols[col].requisition += extra;
+ width -= extra;
+ n_expand--;
+ }
+ }
+ }
+ /* request remaining space for multi-row children */
+ if (loc.bottom_attach + 1 != loc.top_attach)
+ {
+ Requisition crq = cw->size_request();
+ /* Check and see if there is already enough space for the child. */
+ uint height = 0;
+ for (uint row = loc.bottom_attach; row < loc.top_attach; row++)
+ {
+ height += rows[row].requisition;
+ if (row + 1 < loc.top_attach)
+ height += rows[row].spacing;
+ }
+ /* If we need to request more space for this child to fill
+ * its requisition, then divide up the needed space amongst the
+ * rows it spans, favoring expandable rows if any.
+ */
+ if (height < crq.height + loc.bottom_padding + loc.top_padding)
+ {
+ bool force_expand = false;
+ uint n_expand = 0;
+ height = iround (crq.height + loc.bottom_padding + loc.top_padding - height);
+ for (uint row = loc.bottom_attach; row < loc.top_attach; row++)
+ if (rows[row].expand)
+ n_expand++;
+ if (n_expand == 0)
+ {
+ n_expand = loc.top_attach - loc.bottom_attach;
+ force_expand = true;
+ }
+ for (uint row = loc.bottom_attach; row < loc.top_attach; row++)
+ if (force_expand || rows[row].expand)
+ {
+ uint extra = height / n_expand;
+ rows[row].requisition += extra;
+ height -= extra;
+ n_expand--;
+ }
+ }
+ }
+ }
+}
+
+void
+TableImpl::size_allocate_init()
+{
+ /* Initialize the rows and cols.
+ * By default, rows and cols do not expand and do shrink.
+ * Those values are modified by the children that occupy
+ * the rows and cols.
+ */
+ for (uint col = 0; col < cols.size(); col++)
+ {
+ cols[col].allocation = cols[col].requisition;
+ cols[col].need_expand = false;
+ cols[col].need_shrink = true;
+ cols[col].expand = false;
+ cols[col].shrink = true;
+ cols[col].empty = true;
+ }
+ for (uint row = 0; row < rows.size(); row++)
+ {
+ rows[row].allocation = rows[row].requisition;
+ rows[row].need_expand = false;
+ rows[row].need_shrink = true;
+ rows[row].expand = false;
+ rows[row].shrink = true;
+ rows[row].empty = true;
+ }
+ /* adjust the row and col flags from expand/shrink flags of single row/col children */
+ for (ChildWalker cw = local_children(); cw.has_next(); cw++)
+ {
+ if (!cw->visible())
+ continue;
+ Location loc = child_location (*cw);
+ if (loc.left_attach + 1 == loc.right_attach)
+ {
+ cols[loc.left_attach].expand |= loc.xexpand;
+ cols[loc.left_attach].shrink &= loc.xshrink;
+ cols[loc.left_attach].empty = false;
+ }
+ if (loc.bottom_attach + 1 == loc.top_attach)
+ {
+ rows[loc.bottom_attach].expand |= loc.yexpand;
+ rows[loc.bottom_attach].shrink &= loc.yshrink;
+ rows[loc.bottom_attach].empty = false;
+ }
+ }
+ /* adjust the row and col flags from expand/shrink flags of multi row/col children */
+ for (ChildWalker cw = local_children(); cw.has_next(); cw++)
+ {
+ if (!cw->visible())
+ continue;
+ Location loc = child_location (*cw);
+ if (loc.left_attach + 1 != loc.right_attach)
+ {
+ uint col;
+ for (col = loc.left_attach; col < loc.right_attach; col++)
+ cols[col].empty = false;
+ if (loc.xexpand)
+ {
+ for (col = loc.left_attach; col < loc.right_attach; col++)
+ if (cols[col].expand)
+ break;
+ if (col >= loc.right_attach) /* no expand col found */
+ for (col = loc.left_attach; col < loc.right_attach; col++)
+ cols[col].need_expand = true;
+ }
+ if (!loc.xshrink)
+ {
+ for (col = loc.left_attach; col < loc.right_attach; col++)
+ if (!cols[col].shrink)
+ break;
+ if (col >= loc.right_attach) /* no shrink col found */
+ for (col = loc.left_attach; col < loc.right_attach; col++)
+ cols[col].need_shrink = false;
+ }
+ }
+ if (loc.bottom_attach + 1 != loc.top_attach)
+ {
+ uint row;
+ for (row = loc.bottom_attach; row < loc.top_attach; row++)
+ rows[row].empty = false;
+ if (loc.yexpand)
+ {
+ for (row = loc.bottom_attach; row < loc.top_attach; row++)
+ if (rows[row].expand)
+ break;
+ if (row >= loc.top_attach) /* no expand row found */
+ for (row = loc.bottom_attach; row < loc.top_attach; row++)
+ rows[row].need_expand = true;
+ }
+ if (!loc.yshrink)
+ {
+ for (row = loc.bottom_attach; row < loc.top_attach; row++)
+ if (!rows[row].shrink)
+ break;
+ if (row >= loc.top_attach) /* no shrink row found */
+ for (row = loc.bottom_attach; row < loc.top_attach; row++)
+ rows[row].need_shrink = false;
+ }
+ }
+ }
+ /* Loop over the columns and set the expand and shrink values
+ * if the column can be expanded or shrunk.
+ */
+ for (uint col = 0; col < cols.size(); col++)
+ if (cols[col].empty)
+ {
+ cols[col].expand = false;
+ cols[col].shrink = false;
+ }
+ else
+ {
+ cols[col].expand |= cols[col].need_expand;
+ cols[col].shrink &= cols[col].need_shrink;
+ }
+ /* Loop over the rows and set the expand and shrink values
+ * if the row can be expanded or shrunk.
+ */
+ for (uint row = 0; row < rows.size(); row++)
+ if (rows[row].empty)
+ {
+ rows[row].expand = false;
+ rows[row].shrink = false;
+ }
+ else
+ {
+ rows[row].expand |= rows[row].need_expand;
+ rows[row].shrink &= rows[row].need_shrink;
+ }
+}
+
+void
+TableImpl::size_allocate_pass1 ()
+{
+ /* If we were allocated more space than we requested
+ * then we have to expand any expandable rows and columns
+ * to fill in the extra space.
+ */
+ Allocation area = allocation();
+ const int real_width = area.width;
+ const int real_height = area.height;
+ if (homogeneous())
+ {
+ int nexpand, extra;
+ if (!has_children())
+ nexpand = 1;
+ else
+ {
+ nexpand = 0;
+ for (uint col = 0; col < cols.size(); col++)
+ if (cols[col].expand)
+ {
+ nexpand += 1;
+ break;
+ }
+ }
+ if (nexpand)
+ {
+ int width = real_width;
+ for (uint col = 0; col + 1 < cols.size(); col++)
+ width -= cols[col].spacing;
+ for (uint col = 0; col < cols.size(); col++)
+ {
+ extra = width / (cols.size() - col);
+ cols[col].allocation = MAX (1, extra);
+ width -= extra;
+ }
+ }
+ }
+ else
+ {
+ int width = 0, nexpand = 0, nshrink = 0, extra;
+ for (uint col = 0; col < cols.size(); col++)
+ {
+ width += cols[col].requisition;
+ nexpand += cols[col].expand;
+ nshrink += cols[col].shrink;
+ }
+ for (uint col = 0; col + 1 < cols.size(); col++)
+ width += cols[col].spacing;
+ /* Check to see if we were allocated more width than we requested. */
+ if (width < real_width && nexpand >= 1)
+ {
+ width = real_width - width;
+ for (uint col = 0; col < cols.size(); col++)
+ if (cols[col].expand)
+ {
+ extra = width / nexpand;
+ cols[col].allocation += extra;
+ width -= extra;
+ nexpand -= 1;
+ }
+ }
+ /* Check to see if we were allocated less width than we requested,
+ * then shrink until we fit the size given.
+ */
+ if (width > real_width)
+ {
+ uint total_nshrink = nshrink;
+ extra = width - real_width;
+ while (total_nshrink > 0 && extra > 0)
+ {
+ nshrink = total_nshrink;
+ for (uint col = 0; col < cols.size(); col++)
+ if (cols[col].shrink)
+ {
+ int allocation = cols[col].allocation;
+ cols[col].allocation = MAX (1, allocation - extra / nshrink);
+ extra -= allocation - cols[col].allocation;
+ nshrink -= 1;
+ if (cols[col].allocation < 2)
+ {
+ total_nshrink -= 1;
+ cols[col].shrink = false;
+ }
+ }
+ }
+ }
+ }
+ if (homogeneous())
+ {
+ int nexpand, extra;
+ if (!has_children())
+ nexpand = 1;
+ else
+ {
+ nexpand = 0;
+ for (uint row = 0; row < rows.size(); row++)
+ if (rows[row].expand)
+ {
+ nexpand += 1;
+ break;
+ }
+ }
+ if (nexpand)
+ {
+ int height = real_height;
+ for (uint row = 0; row + 1 < rows.size(); row++)
+ height -= rows[row].spacing;
+ for (uint row = 0; row < rows.size(); row++)
+ {
+ extra = height / (rows.size() - row);
+ rows[row].allocation = MAX (1, extra);
+ height -= extra;
+ }
+ }
+ }
+ else
+ {
+ int height = 0, nexpand = 0, nshrink = 0, extra;
+ for (uint row = 0; row < rows.size(); row++)
+ {
+ height += rows[row].requisition;
+ nexpand += rows[row].expand;
+ nshrink += rows[row].shrink;
+ }
+ for (uint row = 0; row + 1 < rows.size(); row++)
+ height += rows[row].spacing;
+ /* Check to see if we were allocated more height than we requested. */
+ if (height < real_height && nexpand >= 1)
+ {
+ height = real_height - height;
+ for (uint row = 0; row < rows.size(); row++)
+ if (rows[row].expand)
+ {
+ extra = height / nexpand;
+ rows[row].allocation += extra;
+ height -= extra;
+ nexpand -= 1;
+ }
+ }
+ /* Check to see if we were allocated less height than we requested.
+ * then shrink until we fit the size given.
+ */
+ if (height > real_height)
+ {
+ uint total_nshrink = nshrink;
+ extra = height - real_height;
+ while (total_nshrink > 0 && extra > 0)
+ {
+ nshrink = total_nshrink;
+ for (uint row = 0; row < rows.size(); row++)
+ if (rows[row].shrink)
+ {
+ int allocation = rows[row].allocation;
+ rows[row].allocation = MAX (1, allocation - extra / nshrink);
+ extra -= allocation - rows[row].allocation;
+ nshrink -= 1;
+ if (rows[row].allocation < 2)
+ {
+ total_nshrink -= 1;
+ rows[row].shrink = false;
+ }
+ }
+ }
+ }
+ }
+}
+
+void
+TableImpl::size_allocate_pass2 ()
+{
+ Allocation area = allocation(), child_area;
+ for (ChildWalker cw = local_children(); cw.has_next(); cw++)
+ {
+ if (!cw->visible())
+ continue;
+ Location loc = child_location (*cw);
+ Requisition crq = cw->size_request();
+ int x = area.x;
+ for (uint col = 0; col < loc.left_attach; col++)
+ x += cols[col].allocation + cols[col].spacing;
+ int max_width = 0;
+ for (uint col = loc.left_attach; col < loc.right_attach; col++)
+ {
+ max_width += cols[col].allocation;
+ if (col + 1 < loc.right_attach)
+ max_width += cols[col].spacing;
+ }
+ int y = area.y;
+ for (uint row = 0; row < loc.bottom_attach; row++)
+ y += rows[row].allocation + rows[row].spacing;
+ int max_height = 0;
+ for (uint row = loc.bottom_attach; row < loc.top_attach; row++)
+ {
+ max_height += rows[row].allocation;
+ if (row + 1 < loc.top_attach)
+ max_height += rows[row].spacing;
+ }
+ if (loc.xfill)
+ {
+ child_area.width = max_width; // MAX (0, max_width - int (loc.left_padding + loc.right_padding));
+ child_area.x = x; // + loc.left_padding; // (max_width - child_area.width) / 2;
+ }
+ else
+ {
+ child_area.width = MIN (iround (crq.width), max_width);
+ child_area.x = x + (max_width - child_area.width) / 2;
+ }
+ if (loc.yfill)
+ {
+ child_area.height = max_height; // MAX (0, max_height - int (loc.bottom_padding + loc.top_padding));
+ child_area.y = y; // + loc.bottom_padding; // (max_height - child_area.height) / 2;
+ }
+ else
+ {
+ child_area.height = MIN (iround (crq.height), max_height);
+ child_area.y = y + (max_height - child_area.height) / 2;
+ }
+ if (false) /* flip layout horizontally */
+ child_area.x = area.x + area.width - (child_area.x - area.x) - child_area.width;
+ /* constrain child allocation to table */
+ if (child_area.x + child_area.width > area.x + area.width)
+ child_area.width -= child_area.x + child_area.width - area.x - area.width;
+ if (child_area.y + child_area.height > area.y + area.height)
+ child_area.height -= child_area.y + child_area.height - area.y - area.height;
+ /* account for padding */
+ if (loc.xfill)
+ {
+ child_area.width -= int (loc.left_padding + loc.right_padding);
+ child_area.x += loc.left_padding;
+ }
+ if (loc.yfill)
+ {
+ child_area.height -= int (loc.bottom_padding + loc.top_padding);
+ child_area.y += loc.bottom_padding;
+ }
+ /* allocate child */
+ cw->set_allocation (child_area);
+ }
+}
+
+static const ItemFactory<TableImpl> table_factory ("Rapicorn::Table");
+
+} // Rapicorn
diff --git a/rapicorn/table.hh b/rapicorn/table.hh
new file mode 100644
index 0000000..35e0ec2
--- a/dev/null
+++ b/rapicorn/table.hh
@@ -0,0 +1,45 @@
+/* Rapicorn
+ * Copyright (C) 2005 Tim Janik
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+#ifndef __RAPICORN_TABLE_HH__
+#define __RAPICORN_TABLE_HH__
+
+#include <rapicorn/item.hh>
+
+namespace Rapicorn {
+
+class Table : public virtual Convertible {
+public:
+ virtual bool homogeneous () const = 0;
+ virtual void homogeneous (bool chomogeneous_items) = 0;
+ virtual uint column_spacing () = 0;
+ virtual void column_spacing (uint cspacing) = 0;
+ virtual uint row_spacing () = 0;
+ virtual void row_spacing (uint rspacing) = 0;
+ virtual void resize (uint n_cols, uint n_rows) = 0;
+ virtual uint get_n_rows () = 0;
+ virtual uint get_n_cols () = 0;
+ virtual bool is_row_used (uint row) = 0;
+ virtual bool is_col_used (uint col) = 0;
+ virtual void insert_rows (uint first_row, uint n_rows) = 0;
+ virtual void insert_cols (uint first_col, uint n_cols) = 0;
+};
+
+} // Rapicorn
+
+#endif /* __RAPICORN_TABLE_HH__ */
diff --git a/rapicorn/tableimpl.hh b/rapicorn/tableimpl.hh
new file mode 100644
index 0000000..f390f8f
--- a/dev/null
+++ b/rapicorn/tableimpl.hh
@@ -0,0 +1,133 @@
+/* Rapicorn
+ * Copyright (C) 2005 Tim Janik
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+#ifndef __RAPICORN_TABLE_IMPL_HH__
+#define __RAPICORN_TABLE_IMPL_HH__
+
+#include <rapicorn/table.hh>
+#include <rapicorn/containerimpl.hh>
+
+namespace Rapicorn {
+
+class TableImpl : public virtual Table, public virtual MultiContainerImpl {
+ struct RowCol {
+ uint requisition;
+ uint allocation;
+ uint16 spacing;
+ uint need_expand : 1;
+ uint need_shrink : 1;
+ uint expand : 1;
+ uint shrink : 1;
+ uint empty : 1;
+ RowCol() { memset (this, 0, sizeof (*this)); }
+ };
+ vector<RowCol> rows, cols;
+ uint16 default_row_spacing;
+ uint16 default_column_spacing;
+ uint homogeneous_items : 1;
+ void size_request_init ();
+ void size_request_pass1 ();
+ void size_request_pass2 ();
+ void size_request_pass3 ();
+ void size_allocate_init ();
+ void size_allocate_pass1 ();
+ void size_allocate_pass2 ();
+protected:
+ virtual bool add_child (Item &item, const PackPropertyList &pack_plist);
+ virtual void size_request (Requisition &requisition);
+ virtual void size_allocate (Allocation area);
+public:
+ explicit TableImpl ();
+ virtual uint get_n_rows () { return rows.size(); }
+ virtual uint get_n_cols () { return cols.size(); }
+ void resize_grow (uint n_cols, uint n_rows);
+ virtual void resize (uint n_cols, uint n_rows);
+ virtual bool is_row_used (uint row);
+ virtual bool is_col_used (uint col);
+ virtual void insert_rows (uint first_row, uint n_rows);
+ virtual void insert_cols (uint first_col, uint n_cols);
+ virtual ~TableImpl ();
+public:
+ virtual
+ const PropertyList& list_properties ();
+ virtual bool homogeneous () const { return homogeneous_items; }
+ virtual void homogeneous (bool chomogeneous_items) { homogeneous_items = chomogeneous_items; invalidate(); }
+ virtual uint column_spacing () { return default_column_spacing; }
+ virtual void column_spacing (uint cspacing);
+ virtual uint row_spacing () { return default_row_spacing; }
+ virtual void row_spacing (uint rspacing);
+protected:
+ /* child location */
+ struct Location {
+ uint left_attach, right_attach;
+ uint bottom_attach, top_attach;
+ uint16 left_padding, right_padding;
+ uint16 bottom_padding, top_padding;
+ uint xexpand : 1;
+ uint yexpand : 1;
+ uint xshrink : 1;
+ uint yshrink : 1;
+ uint xfill : 1;
+ uint yfill : 1;
+ Location() { memset (this, 0, sizeof (*this)); xfill = yfill = true; }
+ };
+ static Location child_location (Item &child);
+ static void child_location (Item &child, Location loc);
+ static DataKey<Location> child_location_key;
+ /* pack properties */
+ class TablePacker : public virtual ChildPacker {
+ Item &item;
+ Location loc;
+ public:
+ explicit TablePacker (Item &citem);
+ virtual ~TablePacker ();
+ virtual
+ const PropertyList& list_properties ();
+ virtual void update (); /* fetch real pack properties */
+ virtual void commit (); /* assign pack properties */
+ uint left_attach () const { return loc.left_attach; }
+ void left_attach (uint c) { loc.left_attach = c; loc.right_attach = MAX (loc.left_attach + 1, loc.right_attach); }
+ uint right_attach () const { return loc.right_attach; }
+ void right_attach (uint c) { loc.right_attach = c; loc.left_attach = MIN (loc.left_attach, loc.right_attach - 1); }
+ uint bottom_attach () const { return loc.bottom_attach; }
+ void bottom_attach (uint c) { loc.bottom_attach = c; loc.top_attach = MAX (loc.top_attach, loc.bottom_attach + 1); }
+ uint top_attach () const { return loc.top_attach; }
+ void top_attach (uint c) { loc.top_attach = c; loc.bottom_attach = MIN (loc.bottom_attach, loc.top_attach - 1); }
+ uint left_padding () const { return loc.left_padding; }
+ void left_padding (uint c) { loc.left_padding = c; }
+ uint right_padding () const { return loc.right_padding; }
+ void right_padding (uint c) { loc.right_padding = c; }
+ uint bottom_padding () const { return loc.bottom_padding; }
+ void bottom_padding (uint c) { loc.bottom_padding = c; }
+ uint top_padding () const { return loc.top_padding; }
+ void top_padding (uint c) { loc.top_padding = c; }
+ bool hshrink () const { return loc.xshrink; }
+ void hshrink (bool b) { loc.xshrink = b; }
+ bool vshrink () const { return loc.yshrink; }
+ void vshrink (bool b) { loc.yshrink = b; }
+ bool hfill () const { return loc.xfill; }
+ void hfill (bool b) { loc.xfill = b; }
+ bool vfill () const { return loc.yfill; }
+ void vfill (bool b) { loc.yfill = b; }
+ };
+ virtual Packer create_packer (Item &item);
+};
+
+} // Rapicorn
+
+#endif /* __RAPICORN_TABLE_IMPL_HH__ */