Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/jangernert/FeedReader.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJan Lukas Gernert <jangernert@gmail.com>2018-02-28 15:52:21 +0300
committerBrendan Long <self@brendanlong.com>2018-04-25 16:24:55 +0300
commit6f91c37f470b923efa8f8cc5435a33e545b4523f (patch)
treeafe50978cf2ed8af4bfae219a6ce5447e602ef8b /libraries
parent78d7ced862aa491d64d9b5f135b2ba2d893b79d1 (diff)
switch to meson build system
Diffstat (limited to 'libraries')
-rw-r--r--libraries/WebExtension/meson.build18
-rw-r--r--libraries/WebExtension/webextension.vala188
-rw-r--r--libraries/libIvy/Extractor.vala422
-rw-r--r--libraries/libIvy/Frame.vala102
-rw-r--r--libraries/libIvy/Printer.vala242
-rw-r--r--libraries/libIvy/Stacktrace.vala401
-rw-r--r--libraries/libIvy/meson.build16
-rw-r--r--libraries/libVilistextum/charset.c127
-rw-r--r--libraries/libVilistextum/charset.h12
-rw-r--r--libraries/libVilistextum/fileio.c214
-rw-r--r--libraries/libVilistextum/fileio.h15
-rw-r--r--libraries/libVilistextum/html.c513
-rw-r--r--libraries/libVilistextum/html.h30
-rw-r--r--libraries/libVilistextum/html_tag.c306
-rw-r--r--libraries/libVilistextum/html_tag.h6
-rw-r--r--libraries/libVilistextum/latin1.c425
-rw-r--r--libraries/libVilistextum/latin1.h8
-rw-r--r--libraries/libVilistextum/lists.c166
-rw-r--r--libraries/libVilistextum/lists.h17
-rw-r--r--libraries/libVilistextum/meson.build18
-rw-r--r--libraries/libVilistextum/microsoft.c223
-rw-r--r--libraries/libVilistextum/microsoft.h9
-rw-r--r--libraries/libVilistextum/multibyte.h26
-rw-r--r--libraries/libVilistextum/text.c331
-rw-r--r--libraries/libVilistextum/text.h44
-rw-r--r--libraries/libVilistextum/unicode_entities.c76
-rw-r--r--libraries/libVilistextum/unicode_entities.h9
-rw-r--r--libraries/libVilistextum/util.c160
-rw-r--r--libraries/libVilistextum/util.h21
-rw-r--r--libraries/libVilistextum/vilistextum.c55
-rw-r--r--libraries/libVilistextum/vilistextum.h26
-rw-r--r--libraries/libgd/gd-notification.c875
-rw-r--r--libraries/libgd/gd-notification.h67
-rw-r--r--libraries/libgd/gd-types-catalog.c34
-rw-r--r--libraries/libgd/gd-types-catalog.h31
-rw-r--r--libraries/libgd/gd.h32
-rw-r--r--libraries/libgd/meson.build12
-rw-r--r--libraries/libgtkimageview/gtkimageview.c2530
-rw-r--r--libraries/libgtkimageview/gtkimageview.h126
-rw-r--r--libraries/libgtkimageview/meson.build10
40 files changed, 7943 insertions, 0 deletions
diff --git a/libraries/WebExtension/meson.build b/libraries/WebExtension/meson.build
new file mode 100644
index 00000000..6e059734
--- /dev/null
+++ b/libraries/WebExtension/meson.build
@@ -0,0 +1,18 @@
+webkit2gtk_vapi = vala_compiler.find_library('webkit2gtk-web-extension-4.0',
+ dirs: VAPI_DIR)
+
+webextension_lib = shared_library(
+ 'webextension',
+ [
+ 'webextension.vala'
+ ],
+ dependencies: [
+ gtk,
+ libsoup,
+ webkitextension
+ ]
+)
+
+lib_webextension = declare_dependency(
+ link_with: webextension_lib,
+)
diff --git a/libraries/WebExtension/webextension.vala b/libraries/WebExtension/webextension.vala
new file mode 100644
index 00000000..5b64914c
--- /dev/null
+++ b/libraries/WebExtension/webextension.vala
@@ -0,0 +1,188 @@
+[DBus (name = "org.gnome.FeedReader.ArticleView")]
+public class FeedReaderWebExtension : Object {
+
+ private WebKit.DOM.Document m_doc;
+ public signal void onClick(string path, int width, int height, string url);
+ public signal void message(string message);
+
+ [DBus (visible = false)]
+ public void on_bus_aquired(DBusConnection connection)
+ {
+ try
+ {
+ connection.register_object("/org/gnome/FeedReader/ArticleView", this);
+ }
+ catch(GLib.IOError e)
+ {
+ warning("Could not register object");
+ }
+ }
+
+ [DBus (visible = false)]
+ public void on_page_created(WebKit.WebExtension extension, WebKit.WebPage page)
+ {
+ page.document_loaded.connect(() => {
+ onDocLoaded(page);
+ });
+ message("on_page_created");
+ }
+
+ private void onDocLoaded(WebKit.WebPage page)
+ {
+ m_doc = page.get_dom_document();
+ message("onDocLoaded");
+ }
+
+ public void recalculate()
+ {
+ message("recalculate");
+ var images = m_doc.get_images();
+ ulong count = images.get_length();
+
+ for(ulong i = 0; i < count; i++)
+ {
+ var image = (WebKit.DOM.HTMLImageElement)images.item(i);
+
+ // don't offer imageviewer if image isn't local
+ if(image.src.has_prefix("http"))
+ continue;
+
+ // if image was so huge it had to be replaced with a downscaled version
+ if(image.has_attribute("FR_huge"))
+ {
+ addListener(image, image.get_attribute("FR_huge"));
+ continue;
+ }
+ else if(image.has_attribute("FR_parent"))
+ {
+ addListener(image, image.get_attribute("FR_parent"));
+ continue;
+ }
+
+ long nHeight = image.get_natural_height();
+ long nWidth = image.get_natural_width();
+ long height = image.get_height();
+ long width = image.get_width();
+
+ if(nHeight > 250 || nWidth > 250)
+ {
+ double hRatio = (double)height / (double)nHeight;
+ double wRatio = (double)width / (double)nWidth;
+ double threshold = 0.8;
+
+ if(hRatio <= threshold
+ || wRatio <= threshold)
+ addListener(image, image.src);
+ else
+ removeListener(image);
+ }
+ }
+ }
+
+ [DBus (visible = false)]
+ private void addListener(WebKit.DOM.HTMLImageElement image, string url)
+ {
+ // check if url exists
+ if(GLib.FileUtils.test(url, GLib.FileTest.EXISTS))
+ {
+ ((WebKit.DOM.EventTarget) image).add_event_listener_with_closure("mouseover", on_enter, false);
+ ((WebKit.DOM.EventTarget) image).add_event_listener_with_closure("mousemove", on_enter, false);
+ ((WebKit.DOM.EventTarget) image).add_event_listener_with_closure("mouseout", on_leave, false);
+ ((WebKit.DOM.EventTarget) image).add_event_listener_with_closure("click", on_click, false);
+ }
+ }
+
+ [DBus (visible = false)]
+ private void removeListener(WebKit.DOM.HTMLImageElement image)
+ {
+ ((WebKit.DOM.EventTarget) image).remove_event_listener_with_closure("mouseover", on_enter, false);
+ ((WebKit.DOM.EventTarget) image).remove_event_listener_with_closure("mousemove", on_enter, false);
+ ((WebKit.DOM.EventTarget) image).remove_event_listener_with_closure("mouseout", on_leave, false);
+ ((WebKit.DOM.EventTarget) image).remove_event_listener_with_closure("click", on_click, false);
+
+ try
+ {
+ image.set_attribute("class", "");
+ }
+ catch(GLib.Error e)
+ {
+ stderr.printf("WebExtension.recalculate: %s", e.message);
+ }
+ }
+
+ [DBus (visible = false)]
+ private void on_enter(WebKit.DOM.EventTarget target, WebKit.DOM.Event event)
+ {
+ try
+ {
+ var image = (WebKit.DOM.HTMLImageElement)target;
+ image.set_attribute("class", "clickable-img-hover");
+ }
+ catch(GLib.Error e)
+ {
+
+ }
+ }
+
+ [DBus (visible = false)]
+ private void on_leave(WebKit.DOM.EventTarget target, WebKit.DOM.Event event)
+ {
+ try
+ {
+ var image = (WebKit.DOM.HTMLImageElement)target;
+ image.set_attribute("class", "");
+ }
+ catch(GLib.Error e)
+ {
+
+ }
+ }
+
+ [DBus (visible = false)]
+ public void on_click(WebKit.DOM.EventTarget target, WebKit.DOM.Event event)
+ {
+ event.prevent_default();
+ var image = (WebKit.DOM.HTMLImageElement)target;
+
+ string url = "";
+ var parent = image.get_parent_element();
+ if(parent.tag_name == "A")
+ url = parent.get_attribute("href");
+
+
+ int height = (int)image.natural_height;
+ int width = (int)image.natural_width;
+ string src = image.src;
+ string pref = "file://";
+ if(src.has_prefix(pref))
+ src = src.substring(pref.length);
+
+ if(image.has_attribute("FR_huge"))
+ {
+ src = image.get_attribute("FR_huge");
+ Gdk.Pixbuf.get_file_info(src, out width, out height);
+ }
+ else if(image.has_attribute("FR_parent"))
+ {
+ src = image.get_attribute("FR_parent");
+ Gdk.Pixbuf.get_file_info(src, out width, out height);
+ }
+
+ onClick(src, width, height, url);
+ }
+}
+
+[DBus (name = "org.gnome.FeedReader.ArticleView")]
+public errordomain FeedReaderWebExtensionError
+{
+ ERROR
+}
+
+[CCode (cname = "G_MODULE_EXPORT webkit_web_extension_initialize", instance_pos = -1)]
+public void webkit_web_extension_initialize(WebKit.WebExtension extension)
+{
+ var server = new FeedReaderWebExtension();
+ extension.page_created.connect(server.on_page_created);
+ Bus.own_name(BusType.SESSION, "org.gnome.FeedReader.ArticleView", BusNameOwnerFlags.NONE,
+ server.on_bus_aquired, null, () => { warning("Could not aquire name"); });
+}
diff --git a/libraries/libIvy/Extractor.vala b/libraries/libIvy/Extractor.vala
new file mode 100644
index 00000000..cb939567
--- /dev/null
+++ b/libraries/libIvy/Extractor.vala
@@ -0,0 +1,422 @@
+/*
+ * Copyright (C) 2014 PerfectCarl - https://github.com/PerfectCarl/vala-stacktrace
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace Ivy {
+
+ /**
+ * Extracts frames and builds a {@link Stacktrace}
+ *
+ */
+ public class Extractor {
+
+ private bool show_debug_frames = false;
+
+ private string func = "";
+ private string file_path = "";
+ private string short_file_path = "";
+ private string l = "";
+ private string file_line = "";
+ private string func_line = "";
+ private string lib_address ="";
+
+ private static Gee.List<string> libraries_with_no_info = new Gee.ArrayList<string>();
+
+ private string get_module_name () {
+ var path = new char[1024];
+ Posix.readlink ("/proc/self/exe", path);
+ string result = (string) path;
+ return result;
+ }
+
+ // TODO CARL convert this piece of code to vala conventions
+ private static string get_relative_path (string p_fullDestinationPath, string p_startPath) {
+
+ string[] l_startPathParts = p_startPath.split ("/");
+ string[] l_destinationPathParts = p_fullDestinationPath.split ("/");
+
+ int l_sameCounter = 0;
+ while ((l_sameCounter < l_startPathParts.length) &&
+ (l_sameCounter < l_destinationPathParts.length) &&
+ l_startPathParts[l_sameCounter] == l_destinationPathParts[l_sameCounter]) {
+ l_sameCounter++;
+ }
+
+ if (l_sameCounter == 0) {
+ return p_fullDestinationPath; // There is no relative link.
+ }
+
+ StringBuilder l_builder = new StringBuilder ();
+ for (int i = l_sameCounter ; i < l_startPathParts.length ; i++) {
+ l_builder.append ("../");
+ }
+
+ for (int i = l_sameCounter ; i < l_destinationPathParts.length ; i++) {
+ l_builder.append (l_destinationPathParts[i] + "/");
+ }
+
+ // CARL l_builder.Length--;
+ // Remove the last /
+ var result = l_builder.str;
+ result = result.substring (0, result.length - 1);
+ return result;
+ }
+
+ private string extract_short_file_path (string file_path) {
+ var path = Environment.get_current_dir ();
+ /*var i = file_path.index_of ( path );
+ if( i>=0 )
+ return file_path.substring ( path.length, file_path.length - path.length );
+ return file_path; */
+ var result = get_relative_path (file_path, path);
+ return result;
+ }
+
+ // input : '/home/cran/Documents/Projects/elementary/noise/instant-beta/build/core/libnoise-core.so.0(noise_job_repository_create_job+0x309) [0x7ff60a021e69]'
+ // ouput: 0x309
+ private int extract_base_address (string line) {
+ int result = 0;
+ var start = line.last_index_of ("+");
+ if (start >= 0) {
+ var end = line.last_index_of (")");
+ if( end > start ) {
+ var text = line.substring (start+3,end-start-3);
+ text.scanf("%x", &result);
+ }
+ }
+ return result;
+ }
+
+ private void process_info_for_file (string full_line, string str ) {
+ func = "";
+ file_path = "";
+ short_file_path = "";
+ l = "";
+ file_line = "";
+ func_line = "";
+ if (full_line == "")
+ return;
+
+ var lines = full_line.split ("\n");
+
+ if (lines.length > 0)
+ func_line = lines[0];
+
+ if (lines.length > 1)
+ file_line = lines[1];
+ if (file_line == "??:0" || file_line == "??:?")
+ file_line = "";
+ func = extract_function_name (str);
+
+ file_path = "";
+ short_file_path = "";
+ l = "";
+ if (file_line != "") {
+ if (func == "")
+ func = extract_function_name_from_line (func_line);
+ file_path = extract_file_path (file_line);
+ short_file_path = extract_short_file_path (file_path);
+ l = extract_line (file_line);
+ }
+ }
+
+ private void process_info_from_lib (string file_path, string str) {
+ //stdout.printf( "process_info_from_lib('%s', '%s') func: '%s'\n", file_path, str, func);
+ var has_info = true;
+ var addr1_s = "";
+ var lib_addr = "";
+ var cmd2 = "";
+ lib_address ="";
+ lock( libraries_with_no_info)
+ {
+ if( libraries_with_no_info.index_of (file_path) == -1 ){
+ // The library is not on the black list
+ cmd2 = "nm %s".printf(file_path);
+
+ addr1_s = execute_command_sync_get_output (cmd2);
+ if( addr1_s == null || addr1_s == "" )
+ {
+ // stdout.printf( "ADDED TO NO INFO: '%s'\n", file_path);
+ libraries_with_no_info.add (file_path);
+ has_info = false;
+
+ }
+ }
+ else
+ has_info = false;
+ }
+ if( has_info && func != "" )
+ {
+ MatchInfo info;
+ var expression = "\\n[^ ]* T "+func;
+ try {
+
+ Regex regex = new Regex (expression);
+ int count = 0;
+ string matches = "";
+ if( regex.match (addr1_s, 0, out info) )
+ {
+ while( info.matches() ){
+ var lll = info.fetch(0);
+ // stdout.printf ( "lll '%s'\n", lll );
+ lib_addr = lll.substring(0, lll.index_of(" "));
+ matches += lib_addr + "\n";
+ info.next();
+ count++;
+ }
+ if( count >1 )
+ {
+ stdout.printf (" XX %d matches for '%s'. Command: '%s'. Matches: '%s'\n", count, func, cmd2, matches);
+ }
+ // stdout.printf (" YY %d matches for '%s'. Command: '%s'. Matches: '%s'\n", count, func, cmd2, matches);
+ }
+
+ } catch (RegexError e)
+ {
+ critical( "Error while processing regex '%s. Err: '%s", expression, e.message );
+ }
+ //stdout.printf ("addr1_s %s\n", addr1_s);
+ int addr1 = 0;
+ lib_addr.scanf("%x", &addr1);
+ if( addr1 != 0 ) {
+ int addr2 = extract_base_address (str);
+ string addr3 = "%#08x".printf (addr1+addr2);
+ lib_address = addr3;
+ // stdout.printf ("lib_address : %s\n", lib_address);
+ var new_full_line = process_line (file_path, addr3);
+ //stdout.printf ("STR : %s\n", str);
+ // stdout.printf ("AD1 : %s\n", addr1_s);
+ //stdout.printf ("AD2 : %#08x\n", addr2);
+ //stdout.printf ("AD3 : %s\n", addr3);
+ //stdout.printf ("LIB : %s\n", file_path);
+ //stdout.printf ("RES : %s\n", new_full_line);
+
+ process_info_for_file (new_full_line, str );
+ }
+ else
+ stdout.printf ("NULL\n");
+ }
+
+ }
+
+ private string extract_function_name (string line) {
+ if (line == "")
+ return "";
+ var start = line.index_of ("(");
+ if (start >= 0) {
+ var end = line.index_of ("+", start);
+ if (end >= 0) {
+ var result = line.substring (start + 1, end - start - 1);
+ return result.strip ();
+ }
+ }
+ return "";
+ }
+
+ private string extract_function_name_from_line (string line) {
+ return line.strip ();
+ }
+
+ private string extract_file_path_from (string str) {
+ if (str == "")
+ return "";
+ /*if( str.index_of("??") >= 0)
+ //result = result.substring (4, line.length - 4 );
+ stdout.printf ("ERR2?? : %s\n", str ) ; */
+ var start = str.index_of ("(");
+ if (start >= 0) {
+ return str.substring (0, start).strip ();
+ }
+ return str.strip ();
+ }
+
+ private string extract_file_path (string line) {
+ var result = line;
+ if (result == "")
+ return "";
+ if (result == "??:0??:0")
+ return "";
+ // For some reason, the file name can starts with ??:0
+ if (result.has_prefix ("??:0"))
+ result = result.substring (4, line.length - 4);
+ // stdout.printf ("ERR1?? : %s\n", line );
+ var start = result.index_of (":");
+ if (start >= 0) {
+ result = result.substring (0, start);
+ return result.strip ();
+ }
+ return "";
+ }
+
+ private static string extract_line (string line) {
+ var result = line;
+ if (result == "")
+ return "";
+ if (result.has_prefix ("??:0"))
+ result = result.substring (4, line.length - 4);
+ var start = result.index_of (":");
+ if (start >= 0) {
+ result = result.substring (start + 1, line.length - start - 1);
+ var end = result.index_of ("(");
+ if (end >= 0) {
+ result = result.substring (0, end);
+ }
+ return result.strip ();
+ }
+ return "";
+ }
+
+ private string extract_address (string line) {
+ if (line == "")
+ return "";
+ var start = line.index_of ("[");
+ if (start >= 0) {
+ var end = line.index_of ("]", start);
+ if (end >= 0) {
+ var result = line.substring (start + 1, end - start - 1);
+ return result.strip ();
+ }
+ }
+ return "";
+ }
+
+ private string execute_command_sync_get_output (string cmd) {
+ try {
+ int exitCode;
+ string std_out;
+ string std_err;
+ Process.spawn_command_line_sync (cmd, out std_out, out std_err, out exitCode);
+ if( exitCode == 0)
+ return std_out;
+ else
+ print ("Error while executing '%s'. Exit code '%d'\n".printf(cmd, exitCode));
+
+ }
+ catch (Error e) {
+ print ("Error while executing '%s': %s\n".printf(cmd,e.message));
+ }
+ return "";
+ }
+
+ // Poor's man demangler. libunwind is another dep
+ // TODO : Optimize this
+ // module : app
+ // address : 0x007f80
+ // output : /home/cran/Projects/noise/noise-perf-instant-search/tests/errors.vala:87
+ private string process_line (string module, string address) {
+ var cmd = "addr2line -f -e %s %s".printf (module, address);
+ var result = execute_command_sync_get_output (cmd);
+ //stdout.printf( "CMD %s\n", cmd);
+ return result;
+ }
+
+ /**
+ * Populates the stacktrace with frames
+ *
+ * The frames are extracted from ``Linux.Backtrace`` and enriched
+ * via calls to unix tools ``nm`` and ``addr2line``.
+ *
+ * ''Warning:'' because this methods calls synchronously other applications (nm and addr2line), it
+ * can have a significant impact on performance.
+ *
+ * @param trace the stacktrace
+ */
+ public void create_stacktrace (Stacktrace trace) {
+ int frame_count = 100;
+ int skipped_frames_count = 5;
+ // Stacktrace not due to a crash
+ if (trace.is_custom)
+ skipped_frames_count = 3;
+
+ void *[] array = new void *[frame_count];
+
+ trace.frames.clear ();
+ trace.first_vala = null;
+ trace.max_file_name_length = 0;
+ trace.is_all_function_name_blank = true;
+ trace.is_all_file_name_blank = true;
+
+ // TODO fix that > 0.26
+ #if VALA_0_26 || VALA_0_28
+ var size = Linux.Backtrace.@get (array);
+ var strings = Linux.Backtrace.symbols (array);
+ #else
+ int size = Linux.backtrace (array, frame_count);
+ unowned string[] strings = Linux.backtrace_symbols (array, size);
+ // Needed because of some weird bug
+ strings.length = size;
+ #endif
+
+ int[] addresses = (int[])array;
+ string module = get_module_name ();
+ // First ones are the handler
+ for (int i = skipped_frames_count ; i < size ; i++) {
+ int address = addresses[i];
+ string str = strings[i];
+ var addr = extract_address (str);
+ lib_address ="";
+ //stdout.printf ("9 '%s'. Addr: '%s' \n", func, addr);
+ var full_line = process_line (module, addr);
+ //stdout.printf ("10 '%s'\n", func);
+ if( full_line == "" ) {
+ // Happens when the process memory is going up and up
+ // Likely a memory leak
+ // Like in the test suite for echo
+ // ** (/home/cran/Documents/Projects/i-hate-farms/ide/echo/build/test:2859):
+ // CRITICAL **: vala_data_type_copy: assertion 'self != NULL' failed
+ // Error while executing 'addr2line -f -e /home/cran/Documents/Projects/i-
+ // hate-farms/ide/echo/build/test 0x2afe3b5beb32': Failed to fork (Cannot allocate memory)
+
+ print ("Something went very wrong. Your stacktrace cannot be displayed\n");
+ break;
+ }
+ process_info_for_file( full_line, str);
+ //stdout.printf ("11 '%s'\n", func);
+ if (file_line == "") {
+ file_path = extract_file_path_from (str);
+
+ }
+ //stdout.printf ("12 '%s'\n", func);
+ // The file name may ends with .so or .so.0 ...
+ if( ".so" in file_path ) {
+ process_info_from_lib (file_path, str);
+ }
+ //stdout.printf ("14 '%s'\n", func);
+ if( show_debug_frames )
+ {
+ stdout.printf ("\nFrame %d \n--------\n . addr: [%s]\n . full_line: '%s'\n . file_line: '%s'\n . func_line: '%s'\n . str : '%s'\n . func: '%s'\n . file: '%s'\n . line: '%s'\n . address: '%#08x'\n . lib_address: '%s'\n",
+ i, addr, full_line, file_line, func_line, str, func, file_path, l, address, lib_address);
+ }
+ if (func != "" && file_path.has_suffix (".vala") && trace.is_all_function_name_blank)
+ trace.is_all_function_name_blank = false;
+
+ if (short_file_path != "" && trace.is_all_file_name_blank)
+ trace.is_all_file_name_blank = false;
+
+ var line_number = extract_line (file_line);
+ var frame = new Frame (addr, file_line, func, file_path, short_file_path, line_number);
+
+ if (trace.first_vala == null && file_path.has_suffix (".vala"))
+ trace.first_vala = frame;
+
+ if (short_file_path.length > trace.max_file_name_length)
+ trace.max_file_name_length = short_file_path.length;
+ if (l.length > trace.max_line_number_length)
+ trace.max_line_number_length = l.length;
+ trace.frames.add (frame);
+ }
+ }
+ }
+}
diff --git a/libraries/libIvy/Frame.vala b/libraries/libIvy/Frame.vala
new file mode 100644
index 00000000..383576b1
--- /dev/null
+++ b/libraries/libIvy/Frame.vala
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2014 PerfectCarl - https://github.com/PerfectCarl/vala-stacktrace
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace Ivy {
+
+ /**
+ * A part of a stacktrace
+ *
+ * This class represent on instance of a frame, ie a particular location
+ * in a binary (application or library) on the system called by the application
+ *
+ * ''Note:'' frames from system libraries without code information available are
+ * not displayed by default. See {@link Stacktrace.hide_installed_libraries} for how to
+ * display them.
+ **/
+ public class Frame {
+
+ /**
+ * Address of the stack in hexadecimal
+ *
+ * Ex: ``0x309``
+ **/
+ public string address { get;private set;default = "";}
+
+ /**
+ * Line of code of the frame
+ *
+ * Can point to C code, Vala code or be blank if
+ * no symbol is available (or if -rdynamic has not been set during the
+ * compilation of the binary)
+ *
+ * Ex:
+ **/
+ public string line { get;private set;default = "";}
+
+ /**
+ * Line number in the code file.
+ *
+ * May be blank if no code information is available
+ *
+ * Ex: ``25``
+ **/
+ public string line_number { get;private set;default = "";}
+
+ /**
+ * Full path to the code file as it was stored on the building machine
+ *
+ * Returns the path to the installed binary if no code information is available
+ *
+ * Ex: ``/home/cran/Documents/Projects/i-hate-farms/stacktrace/samples/error_sigsegv.vala``
+ *
+ * Ex: ``/lib/x86_64-linux-gnu/libc.so.6`` if no code information is available
+ **/
+ public string file_path { get;private set;default = "";}
+
+ /**
+ * Path the code file relative to the current path
+ *
+ * Returns the path to the installed binary if no code information is available
+ *
+ * Ex: ``/stuff/to/stuff/``
+ **/
+ public string file_short_path { get;private set;default = "";}
+
+ /**
+ * C function name
+ *
+ * Because only the C function name is avaialable,
+ * the name mixes the vala class and vala method name (by default separated by ``_``).
+ *
+ * For more information about getting full vala names see [[https://bugzilla.gnome.org/show_bug.cgi?id=738784|vala bug #738784]]
+ *
+ * Ex: ``namespace_someclass_method``
+ **/
+ public string function { get;private set;default = "";}
+
+ public Frame (string address, string line, string function, string file_path, string file_short_path, string line_number) {
+ this._address = address;
+ this._line = line;
+
+ this._file_path = file_path;
+ this._file_short_path = file_short_path;
+ this._function = function;
+ this.line_number = line_number;
+ }
+
+ }
+
+}
diff --git a/libraries/libIvy/Printer.vala b/libraries/libIvy/Printer.vala
new file mode 100644
index 00000000..84d7812b
--- /dev/null
+++ b/libraries/libIvy/Printer.vala
@@ -0,0 +1,242 @@
+/*
+ * Copyright (C) 2014 PerfectCarl - https://github.com/PerfectCarl/vala-stacktrace
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace Ivy {
+
+ /**
+ * Prints the stacktrace to ``stdout`` in colors
+ *
+ */
+ public class Printer {
+
+ private Color background_color = Color.BLACK;
+ private int title_length = 0;
+
+ private Stacktrace stacktrace;
+
+ private string get_reset_code () {
+ // return get_color_code (Style.RESET, Colors.WHITE, Colors.BLACK);
+ return "\x1b[0m";
+ }
+
+ private string get_reset_style () {
+ return get_color_code (Style.DIM, stacktrace.highlight_color, background_color);
+ }
+
+ private string get_color_code (Style attr, Color fg, Color bg = background_color) {
+ /* Command is the control command to the terminal */
+ if (bg == Color.BLACK)
+ return "%c[%d;%dm".printf (0x1B, (int) attr, (int) fg + 30);
+ else
+ return "%c[%d;%d;%dm".printf (0x1B, (int) attr, (int) fg + 30, (int) bg + 40);
+ }
+
+ private string get_signal_name () {
+ return stacktrace.sig.to_string ();
+ }
+
+ private string get_highlight_code () {
+ return get_color_code (Style.BRIGHT, stacktrace.highlight_color);
+ }
+
+ private string get_printable_function (Frame frame, int padding = 0) {
+ var result = "";
+ var is_unknown = false;
+ if (frame.function == "") {
+ result = "<unknown> " + frame.address;
+ is_unknown = true;
+ } else {
+ var s = "";
+ int count = padding - get_signal_name ().length;
+ if (padding != 0 && count > 0)
+ s = string.nfill (count, ' ');
+ result = "'" + frame.function + "'" + s;
+ }
+ if (is_unknown)
+ return result + get_reset_code ();
+ else
+ return get_highlight_code () + result + get_reset_code ();
+ }
+
+ private string get_printable_line_number (Frame frame, bool pad = true) {
+ var path = frame.line_number;
+ var max_line_number_length = stacktrace.max_line_number_length;
+ var result = "";
+ var color = get_highlight_code ();
+ if (path.length >= max_line_number_length || !pad)
+ result = color + path + get_reset_style ();
+ else {
+ result = color + path + get_reset_style ();
+ result = string.nfill (max_line_number_length - path.length, ' ') + result;
+ }
+ return result;
+ }
+
+ private string get_printable_file_short_path (Frame frame, bool pad = true) {
+ var path = frame.file_short_path;
+ var max_file_name_length = stacktrace.max_file_name_length;
+ var result = "";
+ var color = get_highlight_code ();
+ if (path.length >= max_file_name_length || !pad)
+ result = color + path + get_reset_style ();
+ else {
+ result = color + path + get_reset_style ();
+ result = result + string.nfill (max_file_name_length - path.length, ' ');
+ }
+ return result;
+ }
+
+ private string get_printable_title () {
+ var c = get_color_code (Style.DIM, stacktrace.highlight_color, background_color);
+ var color = get_highlight_code ();
+
+ var result = "";
+
+ if( stacktrace.is_custom)
+ result = "%sA function was called in %s".printf (
+ c,
+ get_reset_style ());
+ else
+ result = "%sAn error occured %s(%s)%s".printf (
+ c,
+ color,
+ get_signal_name (),
+ get_reset_style ());
+
+ title_length = get_signal_name ().length;
+ return result;
+ }
+
+ private string get_reason () {
+ // var c = get_reset_code();
+ var sig = stacktrace.sig;
+
+ var color = get_highlight_code ();
+ if (sig == ProcessSignal.TRAP) {
+ return "The reason is likely %san uncaught error%s".printf (
+ color, get_reset_code ());
+ }
+ if (sig == ProcessSignal.ABRT) {
+ return "The reason is likely %sa failed assertion (assert...)%s".printf (
+ color, get_reset_code ());
+ }
+ if (sig == ProcessSignal.SEGV) {
+ return "The reason is likely %sa null reference being used%s".printf (
+ color, get_reset_code ());
+ }
+ return "Unknown reason";
+ }
+
+ /**
+ * Print the stacktrace to ``stdout``
+ *
+ * @param trace the stacktrace
+ *
+ */
+ public virtual void print (Stacktrace trace) {
+ this.stacktrace = trace;
+ background_color = stacktrace.error_background;
+ var header = "%s%s\n".printf (get_printable_title (),
+ get_reset_code ());
+ var first_vala = trace.first_vala;
+
+ if (trace.first_vala != null) {
+ header = "%s in %s, line %s in %s\n".printf (
+ get_printable_title (),
+ get_printable_file_short_path (first_vala, false),
+ get_printable_line_number (first_vala, false),
+ get_printable_function (first_vala) + get_reset_code ());
+ title_length += first_vala.line_number.length +
+ first_vala.function.length +
+ first_vala.file_short_path.length;
+ }
+ stdout.printf (header);
+ background_color = Color.BLACK;
+ if( !stacktrace.is_custom) {
+ var reason = get_reason ();
+ stdout.printf (" %s.\n", reason);
+ }
+ var is_all_file_name_blank = stacktrace.is_all_file_name_blank;
+
+ // Has the user forgot to compile with -g -X -rdynamic flag ?
+ if (is_all_file_name_blank) {
+ var advice = " %sNote%s: no file path and line numbers can be retrieved. Are you sure %syou added -g -X -rdynamic%s to valac command line?\n";
+ var color = get_highlight_code ();
+ stdout.printf (advice, color, get_reset_code (), color, get_reset_code ());
+ }
+
+ // Has the user forgot to compile with rdynamic flag ?
+ if (stacktrace.is_all_function_name_blank && !is_all_file_name_blank) {
+ var advice = " %sNote%s: no vala function name can be retrieved. Are you sure %syou added -X -rdynamic%s to valac command line?\n";
+ var color = get_highlight_code ();
+ stdout.printf (advice, color, get_reset_code (), color, get_reset_code ());
+ }
+
+ stdout.printf ("\n");
+ int i = 1;
+ bool has_displayed_first_vala = false;
+ foreach (var frame in trace.frames) {
+ var show_frame = frame.function != "" || frame.file_path.has_suffix (".vala") || frame.file_path.has_suffix (".c");
+ if (Stacktrace.hide_installed_libraries && has_displayed_first_vala)
+ show_frame = show_frame && frame.file_short_path != "";
+
+ // Ignore glib tracing code if displayed before the first vala frame
+ if ((frame.function == "g_logv" || frame.function == "g_log") && !has_displayed_first_vala)
+ show_frame = false;
+ if (show_frame) {
+ // #2 ./OtherModule.c line 80 in 'other_module_do_it'
+ // at /home/cran/Projects/noise/noise-perf-instant-search/tests/errors/module/OtherModule.vala:10
+ var str = " %s #%d %s line %s in %s\n";
+ background_color = Color.BLACK;
+ var lead = " ";
+ var function_padding = 0;
+ if (frame == first_vala) {
+ has_displayed_first_vala = true;
+ lead = "*";
+ background_color = stacktrace.error_background;
+ function_padding = 22;
+ }
+ var l_number = "";
+ if (frame.line_number == "") {
+ str = " %s #%d <unknown> %s in %s\n";
+ var func_name = get_printable_function (frame);
+ var fill_len = int.max (stacktrace.max_file_name_length + stacktrace.max_line_number_length - 1, 0);
+ str = str.printf (
+ lead,
+ i,
+ string.nfill (fill_len, ' '),
+ func_name);
+ } else {
+ str = str.printf (
+ lead,
+ i,
+ get_printable_file_short_path (frame),
+ get_printable_line_number (frame),
+ get_printable_function (frame, function_padding));
+ l_number = ":" + frame.line_number;
+ }
+ stdout.printf (str);
+ str = " at %s%s\n".printf (
+ frame.file_path, l_number);
+ stdout.printf (str);
+
+ i++;
+ }
+ }
+ }
+
+ }
+}
diff --git a/libraries/libIvy/Stacktrace.vala b/libraries/libIvy/Stacktrace.vala
new file mode 100644
index 00000000..1bd78ff1
--- /dev/null
+++ b/libraries/libIvy/Stacktrace.vala
@@ -0,0 +1,401 @@
+/*
+ * Copyright (C) 2014 PerfectCarl - https://github.com/PerfectCarl/vala-stacktrace
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+ /**
+ * Provides services to display vala stacktraces
+ */
+namespace Ivy {
+
+ internal enum Style {
+ RESET = 0,
+ BRIGHT = 1,
+ DIM = 2,
+ UNDERLINE = 3,
+ BLINK = 4,
+ REVERSE = 7,
+ HIDDEN = 8
+ }
+
+ /**
+ * Defines how Unix signals are processed
+ */
+ public enum CriticalHandler {
+ /**
+ * Unix signals are ignored
+ */
+ IGNORE,
+ /**
+ * When a signal is intercepted, a stacktrace is displayed
+ * to ``stdout`` and the execution of the application is
+ * resumed
+ */
+ PRINT_STACKTRACE,
+ /**
+ * When a signal is intercepted, a stacktrace is displayed
+ * to ``stdout`` and the application is stopped
+ */
+ CRASH
+ }
+
+ /**
+ * Colors used for displaying stacktraces
+ */
+ public enum Color {
+ BLACK = 0,
+ RED = 1,
+ GREEN = 2,
+ YELLOW = 3,
+ BLUE = 4,
+ MAGENTA = 5,
+ CYAN = 6,
+ WHITE = 7
+ }
+
+ /**
+ * A complete execution stacktrace
+ *
+ * Holds a collection of {@link Frame} and the basic methods to intercept Unix signals
+ * and prints the complete stacktrace to ``stdout`` in colors.
+ *
+ * For more information, refer to the [[https://github.com/I-hate-farms/stacktrace|official website]].
+ *
+ * Here's a sample of a printed stacktrace:
+ * {{{
+ * An error occured (SIGSEGV) in samples/vala_file.vala, line 21 in 'this_will_crash_harder'
+ * The reason is likely a null reference being used.
+ *
+ * #1 <unknown> in 'strlen'
+ * at /lib/x86_64-linux-gnu/libc.so.6
+ * * #2 samples/vala_file.vala line 21 in 'this_will_crash_harder'
+ * at /home/cran/Documents/Projects/i-hate-farms/stacktrace/samples/vala_file.vala:21
+ * #3 samples/module/OtherModule.vala line 11 in 'other_module_do_it'
+ * at /home/cran/Documents/Projects/i-hate-farms/stacktrace/samples/module/OtherModule.vala:11
+ * #4 samples/error_sigsegv.vala line 19 in 'namespace_someclass_exec'
+ * at /home/cran/Documents/Projects/i-hate-farms/stacktrace/samples/error_sigsegv.vala:19
+ * #5 samples/error_sigsegv.vala line 29 in 'this_will_crash'
+ * at /home/cran/Documents/Projects/i-hate-farms/stacktrace/samples/error_sigsegv.vala:29
+ * #6 samples/error_sigsegv.vala line 39 in '_vala_main'
+ * at /home/cran/Documents/Projects/i-hate-farms/stacktrace/samples/error_sigsegv.vala:39
+ * #7 error_sigsegv.vala.c line 421 in 'main'
+ * at /home/cran/Documents/Projects/i-hate-farms/stacktrace/error_sigsegv.vala.c:421
+ * #8 <unknown> in '__libc_start_main'
+ * at /lib/x86_64-linux-gnu/libc.so.6
+ * }}}
+ */
+ public class Stacktrace {
+
+ internal Frame first_vala = null;
+
+ internal int max_file_name_length = 0;
+
+ internal int max_line_number_length = 0;
+
+ internal bool is_all_function_name_blank = true;
+
+ internal bool is_all_file_name_blank = true;
+
+ private Gee.List<Frame> _frames = new Gee.ArrayList<Frame>();
+
+ /**
+ * Unix signal being intercepted
+ *
+ */
+ public ProcessSignal sig;
+
+ /**
+ * Enables the Unix signals interception
+ *
+ * Setting it to false and preventing signals interception and the collection
+ * of the complete stacktrace (that might a significant effect on performance)
+ * is useful when the application uses a library that emits a ``SIGTRAP``
+ * signal for unknown reasons.
+ *
+ * Such a case would cripple the application performance and clutter the
+ * ``stdout`` ouput for no benefit.
+ *
+ * Default is ``true``
+ */
+ public static bool enabled { get;set;default = true;}
+
+ /**
+ * Hides frames located in external system libraries (like ``libgc``) without
+ * code information
+ *
+ * * Default is ``true``
+ *
+ * Before
+ * {{{
+ * An error occured (SIGTRAP) in ../src/Database/Core/QueryResult.vala, line 26 in 'app_center_core_query_result_finalize'
+ * The reason is likely an uncaught error.
+ *
+ * #1 <unknown> in 'g_signal_handlers_disconnect_matched'
+ * at /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0
+ * * #2 ../src/Database/Core/QueryResult.vala line 26 in 'app_center_core_query_result_finalize'
+ * at /home/cran/Documents/Projects/i-hate-farms/app/exocron/src/Database/Core/QueryResult.vala:26
+ * #3 <unknown> in 'g_object_unref'
+ * at /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0
+ * #4 ../src/Database/Core/Dao.vala line 88 in 'app_center_core_dao_insert'
+ * at /home/cran/Documents/Projects/i-hate-farms/app/exocron/src/Database/Core/Dao.vala:88
+ * #5 ../src/Database/PackageKitSource.vala line 18 in 'app_center_core_package_kit_source_fetch'
+ * at /home/cran/Documents/Projects/i-hate-farms/app/exocron/src/Database/PackageKitSource.vala:18
+ * #6 ../src/MainPanel.vala line 68 in '__lambda19_'
+ * at /home/cran/Documents/Projects/i-hate-farms/app/exocron/src/MainPanel.vala:68
+ * #7 src/MainPanel.c line 297 in '___lambda19__app_center_views_browse_view_show_app_info'
+ * at /home/cran/Documents/Projects/i-hate-farms/app/exocron/build/src/MainPanel.c:297
+ * #8 <unknown> in 'g_cclosure_marshal_VOID__STRINGv'
+ * at /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0
+ * #9 <unknown> in 'g_signal_emit_valist'
+ * at /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0
+ * #10 <unknown> in 'g_signal_emit_by_name'
+ * at /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0
+ * #11 src/BrowseView.c line 871 in '__lambda16_'
+ * at /home/cran/Documents/Projects/i-hate-farms/app/exocron/build/src/BrowseView.c:871
+ * #12 src/BrowseView.c line 878 in '___lambda16__gtk_button_clicked'
+ * at /home/cran/Documents/Projects/i-hate-farms/app/exocron/build/src/BrowseView.c:878
+ * #13 <unknown> in 'g_signal_emit_valist'
+ * at /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0
+ * #14 <unknown> in 'g_signal_emit'
+ * at /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0
+ * #15 <unknown> in 'g_closure_invoke'
+ * at /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0
+ * #16 <unknown> in 'g_signal_emit_valist'
+ * at /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0
+ * #17 <unknown> in 'g_signal_emit'
+ * at /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0
+ * #18 <unknown> in 'ffi_call_unix64'
+ * at /usr/lib/x86_64-linux-gnu/libffi.so.6
+ * #19 <unknown> in 'ffi_call'
+ * at /usr/lib/x86_64-linux-gnu/libffi.so.6
+ * #20 <unknown> in 'g_cclosure_marshal_generic_va'
+ * at /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0
+ * #21 <unknown> in 'g_signal_emit_valist'
+ * at /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0
+ * #22 <unknown> in 'g_signal_emit'
+ * at /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0
+ * #23 <unknown> in 'g_cclosure_marshal_VOID__BOXEDv'
+ * at /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0
+ * #24 <unknown> in 'g_signal_emit_valist'
+ * at /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0
+ * #25 <unknown> in 'g_signal_emit'
+ * at /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0
+ * #26 <unknown> in 'gtk_event_controller_handle_event'
+ * at /usr/lib/x86_64-linux-gnu/libgtk-3.so.0
+ * #27 <unknown> in 'g_signal_emit_valist'
+ * at /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0
+ * #28 <unknown> in 'g_signal_emit'
+ * at /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0
+ * #29 <unknown> in 'gtk_main_do_event'
+ * at /usr/lib/x86_64-linux-gnu/libgtk-3.so.0
+ * #30 <unknown> in 'g_main_context_dispatch'
+ * at /lib/x86_64-linux-gnu/libglib-2.0.so.0
+ * #31 <unknown> in 'g_main_context_iteration'
+ * at /lib/x86_64-linux-gnu/libglib-2.0.so.0
+ * #32 <unknown> in 'g_application_run'
+ * at /usr/lib/x86_64-linux-gnu/libgio-2.0.so.0
+ * #33 <unknown> in 'granite_application_run'
+ * at /usr/lib/x86_64-linux-gnu/libgranite.so.2
+ * #34 ../src/Application.vala line 54 in 'app_center_main'
+ * at /home/cran/Documents/Projects/i-hate-farms/app/exocron/src/Application.vala:54
+ * #35 src/Application.c line 296 in 'main'
+ * at /home/cran/Documents/Projects/i-hate-farms/app/exocron/build/src/Application.c:296
+ * #36 <unknown> in '__libc_start_main'
+ * at /lib/x86_64-linux-gnu/libc.so.6
+ *
+ * }}}
+ *
+ * After :
+ * {{{
+ * An error occured (SIGTRAP) in ../src/Database/Core/QueryResult.vala, line 26 in 'app_center_core_query_result_finalize'
+ * The reason is likely an uncaught error.
+ *
+ * #1 <unknown> in 'g_signal_handlers_disconnect_matched'
+ * at /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0
+ * * #2 ../src/Database/Core/QueryResult.vala line 26 in 'app_center_core_query_result_finalize'
+ * at /home/cran/Documents/Projects/i-hate-farms/app/exocron/src/Database/Core/QueryResult.vala:26
+ * #3 ../src/Database/Core/Dao.vala line 88 in 'app_center_core_dao_insert'
+ * at /home/cran/Documents/Projects/i-hate-farms/app/exocron/src/Database/Core/Dao.vala:88
+ * #4 ../src/Database/PackageKitSource.vala line 18 in 'app_center_core_package_kit_source_fetch'
+ * at /home/cran/Documents/Projects/i-hate-farms/app/exocron/src/Database/PackageKitSource.vala:18
+ * #5 ../src/MainPanel.vala line 68 in '__lambda19_'
+ * at /home/cran/Documents/Projects/i-hate-farms/app/exocron/src/MainPanel.vala:68
+ * #6 src/MainPanel.c line 297 in '___lambda19__app_center_views_browse_view_show_app_info'
+ * at /home/cran/Documents/Projects/i-hate-farms/app/exocron/build/src/MainPanel.c:297
+ * #7 src/BrowseView.c line 871 in '__lambda16_'
+ * at /home/cran/Documents/Projects/i-hate-farms/app/exocron/build/src/BrowseView.c:871
+ * #8 src/BrowseView.c line 878 in '___lambda16__gtk_button_clicked'
+ * at /home/cran/Documents/Projects/i-hate-farms/app/exocron/build/src/BrowseView.c:878
+ * #9 ../src/Application.vala line 55 in 'app_center_main'
+ * at /home/cran/Documents/Projects/i-hate-farms/app/exocron/src/Application.vala:55
+ * #10 src/Application.c line 304 in 'main'
+ * at /home/cran/Documents/Projects/i-hate-farms/app/exocron/build/src/Application.c:304
+ * }}}
+ */
+ public static bool hide_installed_libraries { get;set;default = true;}
+
+ /**
+ * Sets the default higlighted text color for stacktrace that are created
+ * via Unix signals interception
+ *
+ * Default is ``Color.WHITE``
+ */
+ public static Color default_highlight_color { get;set;default = Color.WHITE;}
+
+ /**
+ * Sets the default background color for stacktrace that are created
+ * via Unix signals interception
+ *
+ * Default is ``Color.RED``
+ */
+ public static Color default_error_background { get;set;default = Color.RED;}
+
+ /**
+ * Sets the stacktrace higlighted text color when printed on ``stdout``
+ *
+ * Default is ``Color.WHITE``
+ */
+ public Color highlight_color { get;set;default = Color.WHITE;}
+
+ /**
+ * Sets the stacktrace background color when printed on ``stdout``
+ *
+ * Default is ``Color.RED``
+ */
+ public Color error_background { get;set;default = Color.RED;}
+
+ /**
+ * Collection of frames
+ *
+ */
+ public Gee.List<Frame> frames {
+ get {
+ return _frames;
+ }
+ }
+
+ private Printer printer = new Printer ();
+ private Extractor extractor = new Extractor ();
+
+ public Stacktrace (GLib.ProcessSignal sig = GLib.ProcessSignal.TTOU) {
+ this.sig = sig;
+ // The stacktrace is used likely to understand the inner
+ // working of the app so we displays everything.
+ if (is_custom) {
+ hide_installed_libraries = false;
+ error_background = Color.BLUE;
+ } else {
+ error_background = default_error_background;
+ highlight_color = default_highlight_color;
+ }
+ extractor.create_stacktrace (this);
+ }
+
+ /**
+ * Returns true if the stacktrace is "custom"
+ *
+ * A custom stacktrace has been created via code as
+ * opposed to created via Unix signal interception.
+ *
+ * Custom stacktrace are displayed with a different color scheme (default ``Color.GREEN``) that
+ * can be set via {@link highlight_color} and {@link error_background}
+ *
+ * Here's how to create a custom stacktrace:
+ * {{{
+ *
+ * int my_function (string arg) {
+ * var custom_stracktrace = new Stacktrace ();
+ * custom_stracktrace.print ();
+ * return 0;
+ * }
+ * }}}
+ */
+ public bool is_custom {
+ get {
+ return sig == ProcessSignal.TTOU;
+ }
+ }
+
+ /**
+ * Prints the stacktrace to ``stdout`` with colors
+ *
+ */
+ public void print () {
+ printer.print (this);
+ }
+
+ /**
+ * Registers handlers to intercept Unix signals
+ *
+ * Calling ``register_handlers`` is required for the
+ * library to display a stacktrace when the application encounters an error (ie raises a ``SIGABRT``,
+ * a ``SIGSEV`` or a ``SIGTRAP`` signal).
+ *
+ * ''Note:'' calling ``register_handlers`` is not needed to be able to display custom stacktraces. <<BR>>
+ * (See {@link is_custom} for more information about custom stacktraces).
+ *
+ * How to initialize the library so it can intercept Unix signals:
+ * {{{
+ *
+ * static int main (string[] arg) {
+ * Stacktrace.register_handlers ();
+ * // Start your application
+ * ...
+ * return 0;
+ * }
+ * }}}
+ *
+ */
+ public static void register_handlers () {
+ //Log.set_always_fatal (LogLevelFlags.LEVEL_CRITICAL);
+ Log.set_always_fatal (LogLevelFlags.LEVEL_ERROR);
+
+ Process.@signal (ProcessSignal.SEGV, handler);
+ Process.@signal (ProcessSignal.TRAP, handler);
+ if (critical_handling != CriticalHandler.IGNORE)
+ Process.@signal (ProcessSignal.ABRT, handler);
+ }
+
+ /**
+ * Defines how Unix signals are processed
+ *
+ * Default is ``CriticalHandler.PRINT_STACKTRACE``.
+ */
+ public static CriticalHandler critical_handling { get;set;default = CriticalHandler.PRINT_STACKTRACE;}
+
+ /*{
+ set {
+ _critical_handling = value;
+ if( value == CriticalHandler.CRASH )
+ //var variables = Environ.get ();
+ //Environ.set_variable (variables, "G_DEBUG", "fatal-criticals" );
+ Log.set_always_fatal (LogLevelFlags.LEVEL_CRITICAL);
+ }
+ get {
+ }
+
+ }*/
+
+ private static void handler (int sig) {
+ if( !enabled)
+ return;
+ Stacktrace stack = new Stacktrace ((ProcessSignal) sig);
+ stack.print ();
+ if (sig != ProcessSignal.TRAP ||
+ (sig == ProcessSignal.TRAP && critical_handling == CriticalHandler.CRASH))
+ Process.exit (1);
+ }
+
+ }
+}
diff --git a/libraries/libIvy/meson.build b/libraries/libIvy/meson.build
new file mode 100644
index 00000000..c5f3593e
--- /dev/null
+++ b/libraries/libIvy/meson.build
@@ -0,0 +1,16 @@
+ivy_inc = include_directories('.')
+ivy_lib = static_library(
+ 'ivy',
+ [
+ 'Extractor.vala',
+ 'Frame.vala',
+ 'Printer.vala',
+ 'Stacktrace.vala'
+ ],
+ dependencies: [
+ gee,
+ linux,
+ posix,
+ ],
+ vala_vapi: 'ivy.vapi'
+)
diff --git a/libraries/libVilistextum/charset.c b/libraries/libVilistextum/charset.c
new file mode 100644
index 00000000..25c7ba78
--- /dev/null
+++ b/libraries/libVilistextum/charset.c
@@ -0,0 +1,127 @@
+/*
+ * Copyright (c) 1998-2006 Patric Müller
+ * bhaak@gmx.net
+ * http://bhaak.dyndns.org/vilistextum/
+ *
+ * Released under the GNU GPL Version 2 - http://www.gnu.org/copyleft/gpl.html
+ *
+ * history
+ * 14.10.2001: creation of this file
+ *
+ */
+
+#define _POSIX_C_SOURCE 2 /* for popen, pclose */
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <locale.h>
+#include <stdlib.h>
+#include <iconv.h>
+
+#include "vilistextum.h"
+#include "text.h"
+#include "multibyte.h"
+
+char *default_charset = "iso-8859-1";
+char iconv_charset[DEF_STR_LEN];
+int usr=0;
+iconv_t conv;
+char internal_locale[256];
+
+/* ------------------------------------------------ */
+
+static int suffix(const char * str, const char * suffix)
+{
+ if ( strlen(str) < strlen(suffix) ) return 0;
+ if ( ! strcmp(suffix, str + ( strlen(str) - strlen(suffix) ) ) ) return 1;
+ return 0;
+}
+
+static int utf_8_locale(const char * locale)
+{
+ if (!locale) return 0;
+ return suffix(locale,".utf8") || suffix(locale, ".UTF-8");
+}
+
+int init_multibyte()
+{
+ char *locale_found;
+ locale_found = setlocale(LC_CTYPE, "");
+
+ if (locale_found == NULL)
+ {
+ FILE *fp = popen("locale -a", "r");
+ if (fp)
+ {
+ while (!feof(fp) && !locale_found)
+ {
+ char buf[256];
+ if (fgets(buf, sizeof(buf), fp) != NULL)
+ {
+ /* remove newline */
+ buf[strlen(buf)-1] = '\0';
+ /* check for a working UTF-8 locale */
+ if (utf_8_locale(buf) &&
+ (locale_found = setlocale(LC_CTYPE, buf)))
+ {
+ strcpy(internal_locale, buf);
+ }
+ }
+ }
+ }
+ }
+
+
+ if (locale_found == NULL)
+ {
+ fprintf(stderr, "setlocale failed with: \"\"\n\n");
+ error = 1;
+ return 0;
+ }
+ return 1;
+}
+
+/* ------------------------------------------------ */
+
+int convert_character(int num, CHAR *outstring)
+{
+ outstring[0] = num;
+ outstring[1] = L'\0';
+ return 1;
+}
+
+/* ------------------------------------------------ */
+
+char* get_iconv_charset()
+{
+ return(iconv_charset);
+}
+
+/* ------------------------------------------------ */
+
+void set_iconv_charset(char *charset)
+{
+ /* set charset for iconv conversion */
+ strcpy(iconv_charset, charset);
+ if (transliteration) { strcat(iconv_charset, "//TRANSLIT");}
+}
+
+/* ------------------------------------------------ */
+
+void use_default_charset() { set_iconv_charset(default_charset); }
+
+/* ------------------------------------------------ */
+
+void strip_wchar(CHAR *locale, char *stripped_locale)
+{
+ CHAR *in = locale;
+ char *out = stripped_locale;
+ int len;
+ int i;
+
+ len = STRLEN(locale);
+ /* copy stripped string to out */
+ for (i=0; i<len; i++) { out[i] = wctob(in[i]); }
+ out[i] = 0x00;
+}
+/* ------------------------------------------------ */
diff --git a/libraries/libVilistextum/charset.h b/libraries/libVilistextum/charset.h
new file mode 100644
index 00000000..37e21d07
--- /dev/null
+++ b/libraries/libVilistextum/charset.h
@@ -0,0 +1,12 @@
+
+void use_default_charset();
+
+void strip_wchar(CHAR *locale, char *stripped_locale);
+
+int init_multibyte();
+
+char* get_iconv_charset();
+void set_iconv_charset(char*);
+
+int convert_character(int number, CHAR *out);
+
diff --git a/libraries/libVilistextum/fileio.c b/libraries/libVilistextum/fileio.c
new file mode 100644
index 00000000..322c272d
--- /dev/null
+++ b/libraries/libVilistextum/fileio.c
@@ -0,0 +1,214 @@
+/*
+ * Copyright (c) 1998-2006 Patric Müller
+ * bhaak@gmx.net
+ * http://bhaak.dyndns.org/vilistextum/
+ *
+ * Released under the GNU GPL Version 2 - http://www.gnu.org/copyleft/gpl.html
+ *
+ * history
+ * 18.04.2001 : incorporated stdin/stdout patch from Luke Ravitch
+ */
+
+#include "multibyte.h"
+#include "charset.h"
+
+#include <stdio.h>
+
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+
+#include "text.h"
+#include "html.h"
+#include "charset.h"
+#include "multibyte.h"
+#include "vilistextum.h"
+
+#include <iconv.h>
+#include <locale.h>
+
+FILE *in;
+
+CHAR* OUTPUT = NULL;
+CHAR LINEBREAK[1] = L"\n";
+size_t currentsize = 0;
+size_t outputsize = 0;
+long int count = 0;
+
+/* ------------------------------------------------ */
+
+void open_files(char *input)
+{
+ in = fmemopen(input, strlen(input), "r");
+ if(in == NULL)
+ {
+ fprintf(stderr, "Couldn't open input file %s!\n",input);
+ error = 1;
+ }
+
+ outputsize = strlen(input);
+ OUTPUT = malloc(sizeof(CHAR)*(outputsize+1));
+ OUTPUT[0]='\0';
+}
+
+/* ------------------------------------------------ */
+
+void output_string(CHAR *str)
+{
+ currentsize += wcslen(str) + wcslen(LINEBREAK);
+
+ if(currentsize > outputsize)
+ {
+ if(2*outputsize > currentsize)
+ outputsize *= 2;
+ else
+ outputsize += currentsize;
+
+ OUTPUT = realloc(OUTPUT, sizeof(CHAR)*(outputsize+1));
+ }
+
+ wcscat(OUTPUT, str);
+ wcscat(OUTPUT, LINEBREAK);
+}
+
+/* ------------------------------------------------ */
+
+void cleanup()
+{
+ currentsize = 0;
+ free(OUTPUT);
+ OUTPUT = NULL;
+ fclose(in);
+}
+
+/* ------------------------------------------------ */
+
+char* getOutput(size_t input_length)
+{
+ if(!error)
+ {
+ size_t buffersize = 2*sizeof(char)*input_length;
+ char* buffer = malloc(buffersize);
+ int ret = wcstombs ( buffer, OUTPUT, buffersize );
+ if (ret==buffersize) buffer[buffersize-1]='\0';
+ cleanup();
+ if (ret)
+ return buffer;
+
+ return NULL;
+ }
+
+
+ cleanup();
+ return NULL;
+}
+
+/* ------------------------------------------------ */
+
+void quit()
+{
+ if (!is_zeile_empty())
+ {
+ wort_ende();
+ print_zeile();
+ }
+}
+
+/* ------------------------------------------------ */
+
+int read_char()
+{
+ count = count + 1;
+ int c = ' ';
+ int fehlernr=0; /* tmp variable for errno */
+ static int i=0;
+ int j=0,k;
+ wchar_t outstring[33];
+ iconv_t conv;
+ char input[33], output[33];
+ CHAR tmpstr[33];
+ char *inp, *outp;
+ size_t insize = 1, outsize = 32;
+
+ inp = input;
+ outp = output;
+
+ /* make source the strings are cleared */
+ for (j=0; j<33; j++) {
+ input[j] = '\0';
+ output[j] = '\0';
+ }
+
+ /* check if the conversion from the character set from the HTML document
+ to utf-8 is possible */
+ if ((conv = iconv_open("utf-8", get_iconv_charset()))==(iconv_t)(-1)) {
+ printf("read_char: iconv_open failed, wrong character set?\n");
+ printf("iconv_open(\"utf-8\", \"%s\");\n", get_iconv_charset());
+ perror(get_iconv_charset());
+ printf("count: %li\n", count);
+ error = 1;
+ return EOF;
+ }
+
+ j=0;
+ do {
+ c=fgetc(in);
+ input[j] = c;
+
+ errno=0;
+ insize = j+1;
+ iconv(conv, &inp, &insize, &outp, &outsize);
+ fehlernr = errno;
+
+ if (fehlernr==E2BIG) { fprintf(stderr, "read_char: errno==E2BIG\n"); }
+ /* character is invalid */
+ else if (fehlernr==EILSEQ) {
+ if(c != EOF)
+ fprintf(stderr, "read_char: errno==EILSEQ; invalid byte sequence for %s: %c\n", get_iconv_charset(), c);
+ for (k=0; k<j;k++) {
+ fprintf(stderr, "%d ",(unsigned char)input[k]);
+ }
+ fehlernr=0; j=0;
+ c = '?';
+ }
+ /* incomplete but still valid byte sequence */
+ else if (fehlernr==EINVAL) { /* printf("errno==EINVAL\n"); */ }
+ /* valid character found */
+ else if (fehlernr==0) {
+ mbstowcs(outstring, output, strlen(output));
+ if (convert_character(outstring[0], tmpstr))
+ {
+ c = outstring[0];
+ }
+ else
+ {
+ error = 1;
+ }
+ }
+
+ j++;
+ } while ((fehlernr!=0) && (c!=EOF));
+ iconv_close(conv);
+
+ i++;
+
+ errno = 0;
+
+ if (feof(in)) {
+ return EOF;
+ } else {
+ return c;
+ }
+}
+
+/* ------------------------------------------------ */
+
+/* put c back onto stream */
+void putback_char(CHAR c)
+{
+ char buffer[1];
+ wcstombs(buffer, &c, 1);
+ ungetc(buffer[0], in);
+}
+
+/* ------------------------------------------------ */
diff --git a/libraries/libVilistextum/fileio.h b/libraries/libVilistextum/fileio.h
new file mode 100644
index 00000000..c744f36d
--- /dev/null
+++ b/libraries/libVilistextum/fileio.h
@@ -0,0 +1,15 @@
+#ifndef fileio_h
+#define fileio_h 1
+
+#include "multibyte.h"
+
+void open_files(char *input);
+void output_string(CHAR *str);
+
+int get_current_char();
+int read_char();
+void putback_char(CHAR c);
+void quit();
+CHAR* getOutput();
+
+#endif
diff --git a/libraries/libVilistextum/html.c b/libraries/libVilistextum/html.c
new file mode 100644
index 00000000..bab62cc2
--- /dev/null
+++ b/libraries/libVilistextum/html.c
@@ -0,0 +1,513 @@
+/*
+ * Copyright (c) 1998-2006 Patric Müller
+ * bhaak@gmx.net
+ * http://bhaak.dyndns.org/vilistextum/
+ *
+ * Released under the GNU GPL Version 2 - http://www.gnu.org/copyleft/gpl.html
+ *
+ *
+ * history
+ * 19.04.2001: get_attr parses text of a alt attribute
+ * 20.04.2001: added references ala lynx
+ * 24.08.2001: Frisskommentar could'nt cope with <!--comment-->
+ * 03.09.2001: check_for_center worked only correct if align was last attribute
+ * 22.03.2002: process_meta crashed if Contenttype was provided, but no charset
+ * 10.04.2002: corrected check_for_center to prevent align_errors.
+ * 28.01.2003: TAB and CR treated as white space.
+ * 17.12.2004: fixed buffer overflow when attribute content longer than DEF_STR_LEN
+ *
+ */
+
+#include <ctype.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "vilistextum.h"
+#include "html_tag.h"
+#include "text.h"
+#include "microsoft.h"
+#include "latin1.h"
+#include "fileio.h"
+#include "charset.h"
+#include "util.h"
+
+int pre=0; /* for PRE-Tag */
+int processed_meta=0; /* only parse meta tags once */
+
+CHAR attr_name[DEF_STR_LEN], /* Attribut name of a HTML-Tag */
+attr_ctnt[DEF_STR_LEN], /* Attribut content of a HTML-Tag */
+link_inline[DEF_STR_LEN]; /* Link of a HTML-Tag */
+
+/* ------------------------------------------------ */
+#if !defined(__GNU_LIBRARY__)
+#include <wchar.h>
+static int wcscasecmp(const wchar_t *s1, const wchar_t *s2)
+{
+ size_t i;
+ wint_t c1, c2;
+
+ for (i = 0; s1[i] != L'\0' && s2[i] != L'\0'; i ++)
+ {
+ c1 = towlower(s1[i]);
+ c2 = towlower(s2[i]);
+
+ if (c1 != c2)
+ return c1 - c2;
+ }
+
+ if (s1[i] != L'\0' && s2[i] == L'\0')
+ return s1[i];
+
+ if (s1[i] == L'\0' && s2[i] != L'\0')
+ return -s2[i];
+
+ return 0;
+}
+#endif
+
+/* ------------------------------------------------ */
+
+
+/* get the next attribute and writes it to attr_name and attr_ctnt. */
+/* attr_name is converted to uppercase. */
+int get_attr()
+{
+ int i;
+ CHAR temp[DEF_STR_LEN];
+ attr_name[0] = '\0';
+ attr_ctnt[0] = '\0';
+
+ /* skip whitespace */
+ while ((isspace(ch)) && (ch!='>')) { ch=read_char(); }
+ if (ch=='>') { return '>'; };
+
+ /* read attribute's name */
+ i=1;
+ attr_name[0] = ch;
+
+ while ((ch!='=') && (ch!='>') && (ch!=EOF)) {
+ ch=read_char();
+ if (i<DEF_STR_LEN) { attr_name[i++] = ch; }
+ } /* post cond: i<=DEF_STR_LEN */
+ attr_name[i-1] = '\0';
+
+ if (ch=='>') { attr_ctnt[0]='\0'; return '>'; }
+
+ /* content of attribute */
+ ch=read_char();
+ /* skip white_space */
+ while ((isspace(ch)) && (ch!='>')) { ch=read_char(); }
+ temp[0] = '\0';
+
+ /* if quoted */
+ if ((ch=='"') || (ch=='\''))
+ {
+ /* attribute looks like alt="bla" or alt='bla'. */
+ /* we'll have to remember what the quote was. */
+ int quote=ch;
+ i=0;
+ ch=read_char();
+ while(quote!=ch) {
+ if(ch == EOF) { temp[i++] = quote; ch=quote; break; }
+ if (i<DEF_STR_LEN-1) { temp[i++] = ch; }
+ ch=read_char();
+ } /* post cond: i<=DEF_STR_LEN-1 */
+ temp[i] = '\0';
+ ch=read_char();
+ }
+ else
+ {
+ /* attribute looks like alt=bla */
+ i=1;
+ temp[0] = ch;
+ while ((ch!='>') && (!isspace(ch)) && (ch!=EOF)){
+ ch=read_char();
+ if (i<DEF_STR_LEN) { temp[i++] = ch; }
+ } /* post cond: i<=DEF_STR_LEN */
+ temp[i-1] = '\0';
+ }
+
+ uppercase_str(attr_name);
+ if CMP("ALT", attr_name) { parse_entities(temp); }
+ CPYSS(attr_ctnt, temp);
+
+ return ch;
+}
+
+/* ------------------------------------------------ */
+
+void html(int extractText)
+{
+ int i;
+ CHAR str[DEF_STR_LEN];
+
+ for (i=0; i<DEF_STR_LEN; i++) { str[i]=0x00; }
+
+ if(extractText)
+ {
+ for (;;)
+ {
+ ch = read_char();
+ //printf("'%ls'\n", &ch);
+ if(ch == EOF)
+ {
+ wort_ende();
+ return;
+ }
+ switch (ch)
+ {
+ case '<':
+ html_tag();
+ break;
+
+ /* Entities */
+ case '&':
+ i=1;
+ str[0] = ch;
+ do {
+ ch = read_char();
+ str[i++] = ch;
+ }
+ while ((isalnum(ch)) || (ch=='#'));
+
+ /* if last char is no ';', then the string is no valid entity. */
+ /* maybe it is something like &nbsp or even '& ' */
+ if (ch!=';') {
+ /* save last char */
+ putback_char(ch);
+ /* no ';' at end */
+ str[i-1] = '\0'; }
+ else {
+ /* valid entity */
+ str[i] = '\0';
+ /* strcpy(tmpstr, str); */
+ }
+ parse_entity(&str[0]);
+ /* str contains the converted entity or the original string */
+ wort_plus_string(str);
+ break;
+
+ case 173: /* soft hyphen, just swallow it */
+ break;
+
+ case 9: /* TAB */
+ if (pre) {
+ wort_plus_ch(0x09);
+ } else {
+ wort_ende();
+ }
+ break;
+
+ case 13: /* CR */
+ case '\n':
+ wort_ende();
+ if (pre) { line_break(); }
+ break;
+
+ /* Microsoft ... */
+ case 0x80: case 0x81: case 0x82: case 0x83: case 0x84: case 0x85: case 0x86: case 0x87:
+ case 0x88: case 0x89: case 0x8a: case 0x8b: case 0x8c: case 0x8d: case 0x8e: case 0x8f:
+ case 0x90: case 0x91: case 0x92: case 0x93: case 0x94: case 0x95: case 0x96: case 0x97:
+ case 0x98: case 0x99: case 0x9a: case 0x9b: case 0x9c: case 0x9d: case 0x9e: case 0x9f:
+ if (convert_characters) { microsoft_character(ch); }
+ else wort_plus_ch(ch);
+ break;
+
+ default:
+ if (pre==0) {
+ if (ch==' ') { wort_ende(); }
+ else { wort_plus_ch(ch); }
+ }
+ else { wort_plus_ch(ch); }
+ break;
+ }
+ }
+ }
+ else
+ {
+ for (;;)
+ {
+ ch = read_char();
+ if(ch == EOF)
+ {
+ wort_ende();
+ return;
+ }
+ switch (ch)
+ {
+ /* Entities */
+ case '&':
+ i=1;
+ str[0] = ch;
+ do {
+ ch = read_char();
+ str[i++] = ch;
+ }
+ while ((isalnum(ch)) || (ch=='#'));
+
+ /* if last char is no ';', then the string is no valid entity. */
+ /* maybe it is something like &nbsp or even '& ' */
+ if (ch!=';') {
+ /* save last char */
+ putback_char(ch);
+ /* no ';' at end */
+ str[i-1] = '\0'; }
+ else {
+ /* valid entity */
+ str[i] = '\0';
+ /* strcpy(tmpstr, str); */
+ }
+ parse_entity(&str[0]);
+ /* str contains the converted entity or the original string */
+ wort_plus_string(str);
+ break;
+
+ case 173: /* soft hyphen, just swallow it */
+ break;
+
+ case 9: /* TAB */
+ if (pre) {
+ wort_plus_ch(0x09);
+ } else {
+ wort_ende();
+ }
+ break;
+
+ case 13: /* CR */
+ case '\n':
+ wort_ende();
+ if (pre) { line_break(); }
+ break;
+
+ /* Microsoft ... */
+ case 0x80: case 0x81: case 0x82: case 0x83: case 0x84: case 0x85: case 0x86: case 0x87:
+ case 0x88: case 0x89: case 0x8a: case 0x8b: case 0x8c: case 0x8d: case 0x8e: case 0x8f:
+ case 0x90: case 0x91: case 0x92: case 0x93: case 0x94: case 0x95: case 0x96: case 0x97:
+ case 0x98: case 0x99: case 0x9a: case 0x9b: case 0x9c: case 0x9d: case 0x9e: case 0x9f:
+ if (convert_characters) { microsoft_character(ch); }
+ else wort_plus_ch(ch);
+ break;
+
+ default:
+ if (pre==0) {
+ if (ch==' ') { wort_ende(); }
+ else { wort_plus_ch(ch); }
+ }
+ else { wort_plus_ch(ch); }
+ break;
+ }
+ }
+ }
+}
+
+/* ------------------------------------------------ */
+
+/* used when there's only the align-attribut to be checked */
+void check_for_center()
+{
+ int found=0;
+ while (ch!='>' && ch!=EOF)
+ {
+ ch=get_attr();
+ if CMP("ALIGN", attr_name)
+ {
+ found=1;
+ uppercase_str(attr_ctnt);
+ if CMP("LEFT", attr_ctnt) { push_align(LEFT); }
+ else if CMP("CENTER", attr_ctnt) { push_align(CENTER); }
+ else if CMP("RIGHT", attr_ctnt) { push_align(RIGHT); }
+ else if CMP("JUSTIFY", attr_ctnt) { push_align(LEFT); }
+ else { if (errorlevel>=2) { fprintf(stderr, "No LEFT|CENTER|RIGHT found!\n"); push_align(LEFT); } }
+ }
+ }
+ /* found no ALIGN */
+ if (found==0) { push_align(LEFT); }
+}
+
+/* ------------------------------------------------ */
+
+void start_p()
+{
+ push_align(LEFT);
+ neuer_paragraph();
+ check_for_center();
+}
+
+/* ------------------------------------------------ */
+
+void start_div(int a)
+{
+ line_break();
+ if (a!=0) { div_test=a; push_align(div_test); }
+ else { check_for_center(); }
+}
+
+/* ------------------------------------------------ */
+
+void end_div()
+{
+ wort_ende();
+
+ if (paragraph!=0) { paragraphen_ende(); }
+ else { print_zeile(); }
+ pop_align(); /* einer für start_div */
+ div_test = 0;
+}
+
+/* ------------------------------------------------ */
+
+void print_footnote_number(CHAR *temp, int number)
+{
+ swprintf(temp, 1000, L"[%d]", number);
+}
+void construct_footnote(CHAR *temp, int number, CHAR *link)
+{
+ swprintf(temp, 1000, L" %3d. %ls\n", number, link);
+}
+
+/* ------------------------------------------------ */
+
+char *schemes[] = {"ftp://","file://" ,"http://" ,"gopher://" ,"mailto:" ,"news:" ,"nntp://" ,"telnet://" ,"wais://" ,"prospero://" };
+
+/* ------------------------------------------------ */
+
+/* find alt attribute in current tag */
+void image(CHAR *alt_text, int show_alt)
+{
+ int found_alt=0;
+ while (ch!='>' && ch!=EOF)
+ {
+ ch=get_attr();
+ if CMP("ALT", attr_name)
+ {
+ /*printf("+1+\n"); */
+ if (!(remove_empty_alt && CMP("", attr_ctnt))) {
+ /*printf("+2+\n"); */
+ if (!option_no_alt)
+ { wort_plus_ch('['); wort_plus_string(attr_ctnt); wort_plus_ch(']');}
+ }
+ found_alt=1;
+ }
+ }
+
+ if ((found_alt==0) && (show_alt)) {
+ if (!option_no_image)
+ {
+ wort_plus_ch('['); wort_plus_string(alt_text); wort_plus_ch(']');
+ }
+ }
+}
+
+/* ------------------------------------------------ */
+
+/* extract encoding information from META or ?xml tags */
+void find_encoding()
+{
+ int found_ctnt=0;
+ int found_chst=0;
+ int found_ecdg=0;
+ CHAR *locale=NULL;
+ char stripped_locale[DEF_STR_LEN];
+ CHAR temp_locale[DEF_STR_LEN];
+
+ if (!processed_meta) {
+ while (ch!='>' && ch!=EOF) {
+ ch=get_attr();
+ if ((CMP("HTTP-EQUIV", attr_name)) || (CMP("NAME", attr_name))) {
+ if STRCASECMP("Content-Type", attr_ctnt) { found_ctnt=1; }
+ else if STRCASECMP("charset", attr_ctnt) { found_chst=1; }
+ } else if CMP("CONTENT", attr_name) {
+ CPYSS(temp_locale, attr_ctnt);
+ } else if CMP("ENCODING", attr_name) {
+ CPYSS(temp_locale, attr_ctnt);
+ found_ecdg=1;
+ }
+ }
+ if (found_ctnt||found_chst||found_ecdg) {
+ if (found_ctnt) {
+ locale = wcsstr(temp_locale, L"charset=");
+ if (locale!=NULL) { locale += 8; }
+ } else if (found_chst||found_ecdg) {
+ locale = temp_locale;
+ }
+
+ found_ctnt=0; found_chst=0; found_ecdg=0;
+ /* search and set character set */
+ if (locale!=NULL) {
+ strip_wchar(locale, stripped_locale);
+ /* Yahoo Search does strange things to cached pages */
+ if (strcmp(stripped_locale, "Array")!=0) {
+ if (strcmp(stripped_locale, "x-user-defined")==0) {
+ use_default_charset();
+ } else {
+ set_iconv_charset(stripped_locale);
+ }
+ processed_meta=1;
+ }
+ }
+ }
+ }
+}
+
+/* ------------------------------------------------ */
+
+/* extract encoding information ?xml tags */
+void find_xml_encoding()
+{
+ if (!processed_meta) {
+ /* xml default charset is utf-8 */
+ set_iconv_charset("utf-8");
+ find_encoding();
+ }
+}
+
+/* ------------------------------------------------ */
+
+/* simple finite state machine to eat up complete comment '!--' */
+CHAR friss_kommentar()
+{
+ int c, dontquit=1;
+ while (dontquit)
+ {
+ c=read_char();
+ if (c=='-')
+ {
+ c=read_char();
+ while (c=='-')
+ {
+ c=read_char();
+ if (c=='>') { dontquit=0; }
+ }
+ }
+ }
+
+ return c;
+}
+
+/* ------------------------------------------------ */
+
+void start_nooutput()
+{
+ wort_ende();
+ print_zeile();
+ nooutput = 1;
+
+ while (ch!='>' && ch!=EOF)
+ {
+ ch=get_attr();
+ if CMP("/", attr_name)
+ {
+ printf("Empty tag\n");
+ nooutput = 0;
+ }
+ }
+}
+
+void end_nooutput()
+{
+ wort_ende();
+ print_zeile();
+ nooutput = 0;
+}
+
+/* ------------------------------------------------ */
diff --git a/libraries/libVilistextum/html.h b/libraries/libVilistextum/html.h
new file mode 100644
index 00000000..68b1b935
--- /dev/null
+++ b/libraries/libVilistextum/html.h
@@ -0,0 +1,30 @@
+#ifndef html_h
+#define html_h 1
+
+#include "text.h"
+#include "multibyte.h"
+
+int pre;
+
+int get_attr();
+int get_new_attr(CHAR *name, CHAR *content);
+
+CHAR attr_name[DEF_STR_LEN];
+CHAR attr_ctnt[DEF_STR_LEN];
+
+void html();
+void check_for_center();
+void start_p();
+void start_div(int a);
+void end_div();
+void image(CHAR *, int);
+CHAR friss_kommentar();
+
+void find_encoding();
+void find_xml_encoding();
+
+void href_link_inline_output();
+
+void start_nooutput();
+void end_nooutput();
+#endif
diff --git a/libraries/libVilistextum/html_tag.c b/libraries/libVilistextum/html_tag.c
new file mode 100644
index 00000000..c1fce910
--- /dev/null
+++ b/libraries/libVilistextum/html_tag.c
@@ -0,0 +1,306 @@
+/*
+ * Copyright (c) 1998-2006 Patric Müller
+ * bhaak@gmx.net
+ * http://bhaak.dyndns.org/vilistextum/
+ *
+ * Released under the GNU GPL Version 2 - http://www.gnu.org/copyleft/gpl.html
+ *
+ * 23.04.01 : Ignoring SPAN, /SPAN and /LI
+ * IMG, APPLET, AREA and INPUT are searched for ALT attribute
+ * 13.08.01 : Ignoring DFN and /DFN
+ * 24.08.01 : Fixed Frisskommentar
+ * 02.09.01 : Ignoring BLINK, /BLINK, CITE and /CITE
+ * 10.04.02 : Ignoring NOBR, /NOBR, SELECT, /SELECT, OPTION
+ * 17.12.04 : html tags longer than DEF_STR_LEN are truncated
+ *
+ */
+
+#include "multibyte.h"
+
+#include <ctype.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "html.h"
+#include "text.h"
+#include "vilistextum.h"
+#include "lists.h"
+#include "fileio.h"
+#include "charset.h"
+#include "util.h"
+
+void html_tag()
+{
+ CHAR str[DEF_STR_LEN];
+ int i=0;
+
+ ch = uppercase(read_char());
+
+ /* letter -> normal tag */
+ /* '!' -> CDATA section or comment */
+ /* '/' -> end tag */
+ /* '?' -> XML processing instruction */
+ if ((!isalpha(ch)) && (ch!='/') && (ch!='!') && (ch!='?'))
+ {
+ wort_plus_ch('<');
+ putback_char(ch);
+ /* fprintf(stderr, "no html tag: %c\n",ch); */
+ return;
+ }
+
+ /* read html tag */
+ while ((ch!='>') && (ch!=' ') && (ch!=13) && (ch!=10))
+ {
+ if (i<DEF_STR_LEN-1) { str[i++] = ch; }
+ ch = uppercase(read_char());
+ }
+ str[i] = '\0';
+
+ /* first all tags, that affect if there is any output at all */
+ if CMP("SCRIPT", str) { start_nooutput(); }
+ else if CMP("/SCRIPT", str) { end_nooutput(); }
+ else if CMP("STYLE", str) { start_nooutput(); }
+ else if CMP("/STYLE", str) { end_nooutput(); }
+ else if CMP("TITLE", str) {
+ if (option_title) { push_align(LEFT); neuer_paragraph(); }
+ else { wort_ende(); print_zeile(); nooutput = 1; }
+ } else if CMP("/TITLE", str) {
+ if (option_title) { paragraphen_ende(); print_zeile(); }
+ else { wort_ende(); clear_line(); print_zeile(); nooutput = 0; }
+ }
+
+ if (nooutput==0) {
+ if CMP("/HTML", str) {/* fprintf(stderr, "File ended!\n"); */ }
+ else if CMP("!DOCTYPE", str) { while ((ch=read_char())!='>'); }
+ else if CMP("META", str) { find_encoding(); }
+ else if CMP("?XML", str) { find_xml_encoding(); }
+
+ /* Linebreak */
+ else if CMP("BR", str) { line_break(); }
+ else if CMP("BR/", str) { line_break(); } /* xhtml */
+
+ else if CMP("P", str) { start_p(); }
+ else if CMP("/P", str) { paragraphen_ende(); }
+ else if CMP("BLOCKQUOTE", str) { start_p(); }
+ else if CMP("/BLOCKQUOTE", str) { paragraphen_ende(); }
+ else if CMP("Q", str) { wort_plus_ch('"'); }
+ else if CMP("/Q", str) { wort_plus_ch('"'); }
+
+ /* Convert these Tags */
+ else if CMP("B", str) { if (convert_tags) { wort_plus_ch('*'); } }
+ else if CMP("/B", str) { if (convert_tags) { wort_plus_ch('*'); } }
+ else if CMP("I", str) { if (convert_tags) { wort_plus_ch('/'); } }
+ else if CMP("/I", str) { if (convert_tags) { wort_plus_ch('/'); } }
+ else if CMP("U", str) { if (convert_tags) { wort_plus_ch('_'); } } /* deprecated */
+ else if CMP("/U", str) { if (convert_tags) { wort_plus_ch('_'); } } /* deprecated */
+ else if CMP("STRONG", str) { if (convert_tags) { wort_plus_ch('*'); } }
+ else if CMP("/STRONG", str) { if (convert_tags) { wort_plus_ch('*'); } }
+ else if CMP("EM", str) { if (convert_tags) { wort_plus_ch('/'); } }
+ else if CMP("/EM", str) { if (convert_tags) { wort_plus_ch('/'); } }
+ else if CMP("EMPH", str) { if (convert_tags) { wort_plus_ch('/'); } } /* sometimes used, but doesn't really exist */
+ else if CMP("/EMPH", str) { if (convert_tags) { wort_plus_ch('/'); } } /* sometimes used, but doesn't really exist */
+
+
+ /* headings */
+ else if CMP("H1", str) { start_p(); }
+ else if CMP("/H1", str) { paragraphen_ende(); }
+ else if CMP("H2", str) { start_p(); }
+ else if CMP("/H2", str) { paragraphen_ende(); }
+ else if CMP("H3", str) { start_p(); }
+ else if CMP("/H3", str) { paragraphen_ende(); }
+ else if CMP("H4", str) { start_p(); }
+ else if CMP("/H4", str) { paragraphen_ende(); }
+ else if CMP("H5", str) { start_p(); }
+ else if CMP("/H5", str) { paragraphen_ende(); }
+ else if CMP("H6", str) { start_p(); }
+ else if CMP("/H6", str) { paragraphen_ende(); }
+
+ else if CMP("HR", str) { hr(); }
+ else if CMP("HR/", str) { hr(); } /* xhtml */
+
+ else if CMP("LI", str) { start_lis(); }
+ else if CMP("/LI", str) { end_lis(); }
+ else if CMP("UL", str) { start_uls(); }
+ else if CMP("/UL", str) { end_uls(); return; }
+ else if CMP("DIR", str) { start_uls(); } /* deprecated */
+ else if CMP("/DIR", str) { end_uls(); return; } /* deprecated */
+ else if CMP("MENU", str) { start_uls(); } /* deprecated */
+ else if CMP("/MENU", str) { end_uls(); return; } /* deprecated */
+ else if CMP("OL", str) { start_ols(); }
+ else if CMP("/OL", str) { end_ols(); }
+
+ else if CMP("DIV", str) { start_div(0); }
+ else if CMP("/DIV", str) { end_div(); }
+ else if CMP("CENTER", str) { start_div(CENTER); } /* deprecated */
+ else if CMP("/CENTER", str) { end_div(); } /* deprecated */
+ else if CMP("RIGHT", str) { start_div(RIGHT); }
+ else if CMP("/RIGHT", str) { end_div(); }
+
+ /* tags with alt attribute */
+ else if CMP("IMG", str) { image(default_image, 1); }
+ else if CMP("APPLET", str) { image(STRING("Applet"), 1); } /* deprecated */
+ else if CMP("AREA", str) { image(STRING("Area"), 0); }
+ else if CMP("INPUT", str) { image(STRING("Input"), 0); }
+
+ /* table */
+ else if CMP("TABLE", str) { /*start_p();*/ push_align(LEFT); neuer_paragraph(); }
+ else if CMP("/TABLE", str) { paragraphen_ende(); }
+ else if CMP("TD", str) { wort_plus_ch(' '); }
+ else if CMP("/TD", str) {}
+ else if CMP("TH", str) { wort_plus_ch(' '); }
+ else if CMP("/TH", str) {}
+ else if CMP("TR", str) { line_break(); } /* start_p(); */
+ else if CMP("/TR", str) { /*paragraphen_ende();*/ }
+ else if CMP("CAPTION", str) {}
+ else if CMP("/CAPTION", str) {}
+
+ else if CMP("PRE", str) { start_p(); pre=1; }
+ else if CMP("/PRE", str) { paragraphen_ende(); pre=0; }
+
+ else if CMP("DL", str) { start_dl();} /* Definition List */
+ else if CMP("/DL", str) { end_dl(); }
+ else if CMP("DT", str) { start_dt(); } /* Definition Title */
+ else if CMP("/DT", str) { end_dt(); }
+ else if CMP("DD", str) { start_dd(); } /* Definition Description */
+ else if CMP("/DD", str) { end_dd(); }
+
+ /* tags for forms */
+ else if CMP("FORM", str) {}
+ else if CMP("/FORM", str) {}
+ else if CMP("BUTTON", str) {} /* TODO: extract name? */
+ else if CMP("/BUTTON", str) {}
+ else if CMP("FIELDSET", str) {}
+ else if CMP("/FIELDSET", str) {}
+ else if CMP("TEXTAREA", str) {}
+ else if CMP("/TEXTAREA", str) {}
+ else if CMP("LEGEND", str) {}
+ else if CMP("/LEGEND", str) {}
+ else if CMP("LABEL", str) {}
+ else if CMP("/LABEL", str) {}
+
+ /* tags that have no visible effect */
+ else if CMP("SAMP", str) {}
+ else if CMP("/SAMP", str) {}
+ else if CMP("CODE", str) {}
+ else if CMP("/CODE", str) {}
+ else if CMP("ABBR", str) {}
+ else if CMP("/ABBR", str) {}
+ else if CMP("ACRONYM", str) {}
+ else if CMP("/ACRONYM", str) {}
+ else if CMP("BIG", str) {}
+ else if CMP("/BIG", str) {}
+ else if CMP("VAR", str) {}
+ else if CMP("/VAR", str) {}
+ else if CMP("KBD", str) {}
+ else if CMP("/KBD", str) {}
+
+ /* tags that should have some visible effect */
+ else if CMP("BDO", str) {}
+ else if CMP("/BDO", str) {}
+ else if CMP("INS", str) {}
+ else if CMP("/INS", str) {}
+ else if CMP("DEL", str) {}
+ else if CMP("/DEL", str) {}
+ else if CMP("S", str) {} /* deprecated */
+ else if CMP("/S", str) {} /* deprecated */
+ else if CMP("STRIKE", str) {} /* deprecated */
+ else if CMP("/STRIKE", str) {} /* deprecated */
+
+ /* those tags are ignored */
+ else if CMP("HTML", str) {}
+ else if CMP("BASE", str) {}
+ else if CMP("LINK", str) {}
+ else if CMP("BASEFONT", str) {} /* deprecated */
+
+ else if CMP("HEAD", str) {}
+ else if CMP("/HEAD", str) {}
+ else if CMP("BODY", str) {}
+ else if CMP("/BODY", str) {}
+ else if CMP("FONT", str) {} /* deprecated */
+ else if CMP("/FONT", str) {} /* deprecated */
+ else if CMP("MAP", str) {}
+ else if CMP("/MAP", str) {}
+ else if CMP("SUP", str) {}
+ else if CMP("/SUP", str) {}
+ else if CMP("ADDRESS", str) {}
+ else if CMP("/ADDRESS", str) {}
+ else if CMP("TT", str) {}
+ else if CMP("/TT", str) {}
+ else if CMP("SUB", str) {}
+ else if CMP("/SUB", str) {}
+ else if CMP("NOSCRIPT", str) {}
+ else if CMP("/NOSCRIPT", str) {}
+ else if CMP("SMALL", str) {}
+ else if CMP("/SMALL", str) {}
+ else if CMP("SPAN", str) {}
+ else if CMP("/SPAN", str) {}
+ else if CMP("DFN", str) {}
+ else if CMP("/DFN", str) {}
+ else if CMP("BLINK", str) {}
+ else if CMP("/BLINK", str) {}
+ else if CMP("CITE", str) {}
+ else if CMP("/CITE", str) {}
+
+ else if CMP("NOBR", str) {}
+ else if CMP("/NOBR", str) {}
+ else if CMP("SELECT", str) {}
+ else if CMP("/SELECT", str) {}
+ else if CMP("OPTION", str) {}
+
+ else if CMP("FRAME", str) {}
+ else if CMP("/FRAME", str) {}
+ else if CMP("FRAMESET", str) {}
+ else if CMP("/FRAMESET", str) {}
+ else if CMP("NOFRAMES", str) {}
+ else if CMP("/NOFRAMES", str) {}
+ else if CMP("IFRAME", str) {}
+ else if CMP("/IFRAME", str) {}
+ else if CMP("LAYER", str) {}
+ else if CMP("/LAYER", str) {}
+ else if CMP("ILAYER", str) {}
+ else if CMP("/ILAYER", str) {}
+ else if CMP("NOLAYER", str) {}
+ else if CMP("/NOLAYER", str) {}
+
+ else if CMP("COL", str) {}
+ else if CMP("COLGROUP", str) {}
+ else if CMP("/COLGROUP", str) {}
+ else if CMP("ISINDEX", str) {} /* deprecated */
+ else if CMP("THEAD", str) {}
+ else if CMP("/THEAD", str) {}
+ else if CMP("TFOOT", str) {}
+ else if CMP("/TFOOT", str) {}
+ else if CMP("TBODY", str) {}
+ else if CMP("/TBODY", str) {}
+ else if CMP("PARAM", str) {}
+ else if CMP("/PARAM", str) {}
+ else if CMP("OBJECT", str) {}
+ else if CMP("/OBJECT", str) {}
+ else if CMP("OPTGROUP", str) {}
+ else if CMP("/OPTGROUP", str) {}
+
+ else if CMP("/AREA", str) {}
+
+ else if (STRNCMP("!--", str, 3)==0) {
+ putback_char(ch);
+ putback_char(str[STRLEN(str)-1]);
+ putback_char(str[STRLEN(str)-2]);
+ ch = friss_kommentar();
+ }
+
+ /* these have to be ignored, to avoid the following error to show up */
+ else if CMP("SCRIPT", str) {}
+ else if CMP("/SCRIPT", str) {}
+ else if CMP("STYLE", str) {}
+ else if CMP("/STYLE", str) {}
+ else if CMP("TITLE", str) {}
+ else if CMP("/TITLE", str) {}
+ else { if (errorlevel>=2) { print_error("tag ignored: ", str);} }
+ }
+
+ /* Skip attributes */
+ while (ch!='>' && ch!=EOF)
+ {
+ ch = get_attr();
+ }
+}
diff --git a/libraries/libVilistextum/html_tag.h b/libraries/libVilistextum/html_tag.h
new file mode 100644
index 00000000..42456144
--- /dev/null
+++ b/libraries/libVilistextum/html_tag.h
@@ -0,0 +1,6 @@
+#ifndef html_tag_h
+#define html_tag_h
+
+void html_tag();
+
+#endif
diff --git a/libraries/libVilistextum/latin1.c b/libraries/libVilistextum/latin1.c
new file mode 100644
index 00000000..90b394f2
--- /dev/null
+++ b/libraries/libVilistextum/latin1.c
@@ -0,0 +1,425 @@
+/*
+ * Copyright (c) 1998-2006 Patric Müller
+ * bhaak@gmx.net
+ * http://bhaak.dyndns.org/vilistextum/
+ *
+ * Released under the GNU GPL Version 2 - http://www.gnu.org/copyleft/gpl.html
+ *
+ */
+
+/* History:
+ * 18.04.01: now ignores entities for ascii control characters (0-31)
+ * 19.04.01: added parse_entities(char *)
+ * 03.09.01: added hexadecimal entities
+ * 18.02.02: made tmpstr global. Amiga gcc 2.95 has problem with accessing it as local variable.
+ * 16.10.02: entity number > 255 was not handled for multibyte
+ *
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <string.h>
+
+#include "latin1.h"
+#include "text.h"
+#include "util.h"
+#include "vilistextum.h"
+#include "microsoft.h"
+#include "unicode_entities.h"
+#include "charset.h"
+
+#include "multibyte.h"
+
+int entity_number(CHAR *s);
+int html_entity(CHAR*);
+int latin1(CHAR*);
+
+CHAR tmpstr[DEF_STR_LEN];
+
+/* ------------------------------------------------ */
+
+int set_char_wrapper(CHAR *str, int num)
+{
+ return(convert_character(num, str));
+}
+
+/* ------------------------------------------------ */
+
+/* parse entity in string */
+void parse_entity(CHAR *str)
+{
+ int len = STRLEN(str);
+ CPYSS(tmpstr, str);
+
+ if (tmpstr[len-1]!=';') {
+ tmpstr[len] = ';';
+ tmpstr[len+1] = '\0';
+ }
+
+ if ( entity_number(tmpstr) ||
+ html_entity(tmpstr) ||
+ latin1(tmpstr) ||
+ microsoft_entities(tmpstr) ||
+ unicode_entity(tmpstr) ||
+ ligature_entity(tmpstr))
+ {
+ /* if true entity was known */
+ CPYSS(str, tmpstr);
+ }
+ else {
+ if (errorlevel>=1) { print_error("entity unknown: ", tmpstr); }
+ }
+}
+
+/* ------------------------------------------------ */
+
+/* parses entities in string */
+void parse_entities(CHAR *s)
+{
+ int i=0,j=0,k=0;
+ CHAR tmp[DEF_STR_LEN];
+ CHAR entity[DEF_STR_LEN];
+ int len=STRLEN(s);
+ CHAR result[DEF_STR_LEN];
+
+ if (len>=DEF_STR_LEN) { len = DEF_STR_LEN-1; }
+ result[0] = '\0';
+
+ while(i<=len) {
+ j=0;
+ while((s[i]!='\0') && (s[i]!='&') && (i<DEF_STR_LEN)) {
+ tmp[j++] = s[i++];
+ }
+ tmp[j] = '\0';
+ STRCAT(result, tmp);
+
+ if (s[i]=='&') {
+ k=0;
+ while((s[i]!='\0') && (s[i]!=';') && (!isspace(s[i])) && (i<DEF_STR_LEN)) {
+ entity[k++] = s[i++];
+ }
+ entity[k] = '\0';
+ parse_entity(entity);
+
+ STRCAT(result, entity);
+ }
+ i++;
+ }
+
+ CPYSS(s, result);
+}
+
+/* ------------------------------------------------ */
+
+int entity_number(CHAR *s)
+{
+ int number;
+
+ number = extract_entity_number(s);
+ /* printf("entity_number: %d\n", number); */
+
+ /* no numeric entity */
+ if (number==-1) {
+ return(0);
+ } else {
+ /* ascii printable character 32-127 */
+ if ((number>=32) && (number<=127)) {
+ return(convert_character(number, s));
+ }
+ /* ansi printable character 160-255 */
+ else if ((number>=160) && (number<=255)) {
+ /* latin1 soft hyphen, just swallow it and return empty string */
+ if (number==173) {
+ s[0] = '\0';
+ return(1);
+ }
+ return(convert_character(number, s));
+ }
+ /* ascii control character -> return empty string */
+ else if ((number >= 0) && (number < 32)) {
+ s[0] = '\0';
+ return(1);
+ }
+ else if (number > 255) {
+ return(convert_character(number, s));
+ }
+ }
+ return(0);
+}
+
+/* ------------------------------------------------ */
+
+int html_entity(CHAR *str)
+{
+ if CMP("&quot;", str) { return(set_char_wrapper(str, '"')); }
+ else if CMP("&;", str) { return(set_char_wrapper(str, '&')); } /* for those brain damaged ones */
+ else if CMP("&amp;", str) { return(set_char_wrapper(str, '&')); }
+ else if CMP("&gt;", str) { return(set_char_wrapper(str, '>')); }
+ else if CMP("&lt;", str) { return(set_char_wrapper(str, '<')); }
+ else if CMP("&apos;", str) { return(set_char_wrapper(str, '\'')); }
+ else { return(0); } /* found no html entity */
+}
+
+/* ------------------------------------------------ */
+
+/* returns true if str is a known entity and changes str to the printout */
+int latin1(CHAR *str)
+{
+ if CMP("&nbsp;", str) { return(set_char_wrapper(str, 160)); } /* no-break space */
+ else if CMP("&iexcl;", str) { return(set_char_wrapper(str, 161)); } /* inverted exclamation mark */
+ else if CMP("&cent;", str) { return(set_char_wrapper(str, 162)); } /* cent sign */
+ else if CMP("&pound;", str) { return(set_char_wrapper(str, 163)); } /* pound sterling sign */
+ else if CMP("&curren;", str) { return(set_char_wrapper(str, 164)); } /* general currency sign */
+ else if CMP("&yen;", str) { return(set_char_wrapper(str, 165)); } /* yen sign */
+ else if CMP("&brvbar;", str) { return(set_char_wrapper(str, 166)); } /* broken (vertical) bar */
+ else if CMP("&sect;", str) { return(set_char_wrapper(str, 167)); } /* section sign */
+ else if CMP("&uml;", str) { return(set_char_wrapper(str, 168)); } /* umlaut (dieresis) */
+ else if CMP("&copy;", str) { return(set_char_wrapper(str, 169)); } /* copyright sign */
+ else if CMP("&ordf;", str) { return(set_char_wrapper(str, 170)); } /* ordinal indicator, feminine */
+ else if CMP("&laquo;", str) { return(set_char_wrapper(str, 171)); } /* angle quotation mark, left */
+ else if CMP("&not;", str) { return(set_char_wrapper(str, 172)); } /* not sign */
+ else if CMP("&shy;", str) { return(set_char_wrapper(str, '\0')); } /* soft hyphen, just swallow it */
+ else if CMP("&reg;", str) { return(set_char_wrapper(str, 174)); } /* registered sign */
+ else if CMP("&macr;", str) { return(set_char_wrapper(str, 175)); } /* macron */
+ else if CMP("&deg;", str) { return(set_char_wrapper(str, 176)); } /* degree sign */
+ else if CMP("&plusmn;", str) { return(set_char_wrapper(str, 177)); } /* plus-or-minus sign */
+ else if CMP("&sup2;", str) { return(set_char_wrapper(str, 178)); } /* superscript two */
+ else if CMP("&sup3;", str) { return(set_char_wrapper(str, 179)); } /* superscript three */
+ else if CMP("&acute;", str) { return(set_char_wrapper(str, 180)); } /* acute accent */
+ else if CMP("&micro;", str) { return(set_char_wrapper(str, 181)); } /* micro sign */
+ else if CMP("&para;", str) { return(set_char_wrapper(str, 182)); } /* pilcrow (paragraph sign) */
+ else if CMP("&middot;", str) { return(set_char_wrapper(str, 183)); } /* middle dot */
+ else if CMP("&cedil;", str) { return(set_char_wrapper(str, 184)); } /* cedilla */
+ else if CMP("&sup1;", str) { return(set_char_wrapper(str, 185)); } /* superscript one */
+ else if CMP("&ordm;", str) { return(set_char_wrapper(str, 186)); } /* ordinal indicator, masculine */
+ else if CMP("&raquo;", str) { return(set_char_wrapper(str, 187)); } /* angle quotation mark, right */
+ else if CMP("&frac14;", str) { return(set_char_wrapper(str, 188)); } /* fraction one-quarter */
+ else if CMP("&frac12;", str) { return(set_char_wrapper(str, 189)); } /* fraction one-half */
+ else if CMP("&frac34;", str) { return(set_char_wrapper(str, 190)); } /* fraction three-quarters */
+ else if CMP("&iquest;", str) { return(set_char_wrapper(str, 191)); } /* inverted question mark */
+ else if CMP("&Agrave;", str) { return(set_char_wrapper(str, 192)); } /* capital A, grave accent */
+ else if CMP("&Aacute;", str) { return(set_char_wrapper(str, 193)); } /* capital A, acute accent */
+ else if CMP("&Acirc;", str) { return(set_char_wrapper(str, 194)); } /* capital A, circumflex accent */
+ else if CMP("&Atilde;", str) { return(set_char_wrapper(str, 195)); } /* capital A, tilde */
+ else if CMP("&Auml;", str) { return(set_char_wrapper(str, 196)); } /* capital A, dieresis or umlaut mark */
+ else if CMP("&Aring;", str) { return(set_char_wrapper(str, 197)); } /* capital A, ring */
+ else if CMP("&AElig;", str) { return(set_char_wrapper(str, 198)); } /* capital AE diphthong (ligature) */
+ else if CMP("&Ccedil;", str) { return(set_char_wrapper(str, 199)); } /* capital C, cedilla */
+ else if CMP("&Egrave;", str) { return(set_char_wrapper(str, 200)); } /* capital E, grave accent */
+ else if CMP("&Eacute;", str) { return(set_char_wrapper(str, 201)); } /* capital E, acute accent */
+ else if CMP("&Ecirc;", str) { return(set_char_wrapper(str, 202)); } /* capital E, circumflex accent */
+ else if CMP("&Euml;", str) { return(set_char_wrapper(str, 203)); } /* capital E, dieresis or umlaut mark */
+ else if CMP("&Igrave;", str) { return(set_char_wrapper(str, 204)); } /* capital I, grave accent */
+ else if CMP("&Iacute;", str) { return(set_char_wrapper(str, 205)); } /* capital I, acute accent */
+ else if CMP("&Icirc;", str) { return(set_char_wrapper(str, 206)); } /* capital I, circumflex accent */
+ else if CMP("&Iuml;", str) { return(set_char_wrapper(str, 207)); } /* capital I, dieresis or umlaut mark */
+ else if CMP("&ETH;", str) { return(set_char_wrapper(str, 208)); } /* capital Eth, Icelandic */
+ else if CMP("&Ntilde;", str) { return(set_char_wrapper(str, 209)); } /* capital N, tilde */
+ else if CMP("&Ograve;", str) { return(set_char_wrapper(str, 210)); } /* capital O, grave accent */
+ else if CMP("&Oacute;", str) { return(set_char_wrapper(str, 211)); } /* capital O, acute accent */
+ else if CMP("&Ocirc;", str) { return(set_char_wrapper(str, 212)); } /* capital O, circumflex accent */
+ else if CMP("&Otilde;", str) { return(set_char_wrapper(str, 213)); } /* capital O, tilde */
+ else if CMP("&Ouml;", str) { return(set_char_wrapper(str, 214)); } /* capital O, dieresis or umlaut mark */
+ else if CMP("&times;", str) { return(set_char_wrapper(str, 215)); } /* multiply sign */
+ else if CMP("&Oslash;", str) { return(set_char_wrapper(str, 216)); } /* capital O, slash */
+ else if CMP("&Ugrave;", str) { return(set_char_wrapper(str, 217)); } /* capital U, grave accent */
+ else if CMP("&Uacute;", str) { return(set_char_wrapper(str, 218)); } /* capital U, acute accent */
+ else if CMP("&Ucirc;", str) { return(set_char_wrapper(str, 219)); } /* capital U, circumflex accent */
+ else if CMP("&Uuml;", str) { return(set_char_wrapper(str, 220)); } /* capital U, dieresis or umlaut mark */
+ else if CMP("&Yacute;", str) { return(set_char_wrapper(str, 221)); } /* capital Y, acute accent */
+ else if CMP("&THORN;", str) { return(set_char_wrapper(str, 222)); } /* capital THORN, Icelandic */
+ else if CMP("&szlig;", str) { return(set_char_wrapper(str, 223)); } /* small sharp s, German (sz ligature) */
+ else if CMP("&agrave;", str) { return(set_char_wrapper(str, 224)); } /* small a, grave accent */
+ else if CMP("&aacute;", str) { return(set_char_wrapper(str, 225)); } /* small a, acute accent */
+ else if CMP("&acirc;", str) { return(set_char_wrapper(str, 226)); } /* small a, circumflex accent */
+ else if CMP("&atilde;", str) { return(set_char_wrapper(str, 227)); } /* small a, tilde */
+ else if CMP("&auml;", str) { return(set_char_wrapper(str, 228)); } /* small a, dieresis or umlaut mark */
+ else if CMP("&aring;", str) { return(set_char_wrapper(str, 229)); } /* small a, ring */
+ else if CMP("&aelig;", str) { return(set_char_wrapper(str, 230)); } /* small ae diphthong (ligature) */
+ else if CMP("&ccedil;", str) { return(set_char_wrapper(str, 231)); } /* small c, cedilla */
+ else if CMP("&egrave;", str) { return(set_char_wrapper(str, 232)); } /* small e, grave accent */
+ else if CMP("&eacute;", str) { return(set_char_wrapper(str, 233)); } /* small e, acute accent */
+ else if CMP("&ecirc;", str) { return(set_char_wrapper(str, 234)); } /* small e, circumflex accent */
+ else if CMP("&euml;", str) { return(set_char_wrapper(str, 235)); } /* small e, dieresis or umlaut mark */
+ else if CMP("&igrave;", str) { return(set_char_wrapper(str, 236)); } /* small i, grave accent */
+ else if CMP("&iacute;", str) { return(set_char_wrapper(str, 237)); } /* small i, acute accent */
+ else if CMP("&icirc;", str) { return(set_char_wrapper(str, 238)); } /* small i, circumflex accent */
+ else if CMP("&iuml;", str) { return(set_char_wrapper(str, 239)); } /* small i, dieresis or umlaut mark */
+ else if CMP("&eth;", str) { return(set_char_wrapper(str, 240)); } /* small eth, Icelandic */
+ else if CMP("&ntilde;", str) { return(set_char_wrapper(str, 241)); } /* small n, tilde */
+ else if CMP("&ograve;", str) { return(set_char_wrapper(str, 242)); } /* small o, grave accent */
+ else if CMP("&oacute;", str) { return(set_char_wrapper(str, 243)); } /* small o, acute accent */
+ else if CMP("&ocirc;", str) { return(set_char_wrapper(str, 244)); } /* small o, circumflex accent */
+ else if CMP("&otilde;", str) { return(set_char_wrapper(str, 245)); } /* small o, tilde */
+ else if CMP("&ouml;", str) { return(set_char_wrapper(str, 246)); } /* small o, dieresis or umlaut mark */
+ else if CMP("&divide;", str) { return(set_char_wrapper(str, 247)); } /* divide sign */
+ else if CMP("&oslash;", str) { return(set_char_wrapper(str, 248)); } /* small o, slash */
+ else if CMP("&ugrave;", str) { return(set_char_wrapper(str, 249)); } /* small u, grave accent */
+ else if CMP("&uacute;", str) { return(set_char_wrapper(str, 250)); } /* small u, acute accent */
+ else if CMP("&ucirc;", str) { return(set_char_wrapper(str, 251)); } /* small u, circumflex accent */
+ else if CMP("&uuml;", str) { return(set_char_wrapper(str, 252)); } /* small u, dieresis or umlaut mark */
+ else if CMP("&yacute;", str) { return(set_char_wrapper(str, 253)); } /* small y, acute accent */
+ else if CMP("&thorn;", str) { return(set_char_wrapper(str, 254)); } /* small thorn, Icelandic */
+ else if CMP("&yuml;", str) { return(set_char_wrapper(str, 255)); } /* small y, dieresis or umlaut mark */
+ else if CMP("&fnof;", str) { return(set_char_wrapper(str, 402)); }
+ else if CMP("&Alpha;", str) { return(set_char_wrapper(str, 913)); }
+ else if CMP("&Beta;", str) { return(set_char_wrapper(str, 914)); }
+ else if CMP("&Gamma;", str) { return(set_char_wrapper(str, 915)); }
+ else if CMP("&Delta;", str) { return(set_char_wrapper(str, 916)); }
+ else if CMP("&Epsilon;", str) { return(set_char_wrapper(str, 917)); }
+ else if CMP("&Zeta;", str) { return(set_char_wrapper(str, 918)); }
+ else if CMP("&Eta;", str) { return(set_char_wrapper(str, 919)); }
+ else if CMP("&Theta;", str) { return(set_char_wrapper(str, 920)); }
+ else if CMP("&Iota;", str) { return(set_char_wrapper(str, 921)); }
+ else if CMP("&Kappa;", str) { return(set_char_wrapper(str, 922)); }
+ else if CMP("&Lambda;", str) { return(set_char_wrapper(str, 923)); }
+ else if CMP("&Mu;", str) { return(set_char_wrapper(str, 924)); }
+ else if CMP("&Nu;", str) { return(set_char_wrapper(str, 925)); }
+ else if CMP("&Xi;", str) { return(set_char_wrapper(str, 926)); }
+ else if CMP("&Omicron;", str) { return(set_char_wrapper(str, 927)); }
+ else if CMP("&Pi;", str) { return(set_char_wrapper(str, 928)); }
+ else if CMP("&Rho;", str) { return(set_char_wrapper(str, 929)); }
+ else if CMP("&Sigma;", str) { return(set_char_wrapper(str, 931)); }
+ else if CMP("&Tau;", str) { return(set_char_wrapper(str, 932)); }
+ else if CMP("&Upsilon;", str) { return(set_char_wrapper(str, 933)); }
+ else if CMP("&Phi;", str) { return(set_char_wrapper(str, 934)); }
+ else if CMP("&Chi;", str) { return(set_char_wrapper(str, 935)); }
+ else if CMP("&Psi;", str) { return(set_char_wrapper(str, 936)); }
+ else if CMP("&Omega;", str) { return(set_char_wrapper(str, 937)); }
+ else if CMP("&alpha;", str) { return(set_char_wrapper(str, 945)); }
+ else if CMP("&beta;", str) { return(set_char_wrapper(str, 946)); }
+ else if CMP("&gamma;", str) { return(set_char_wrapper(str, 947)); }
+ else if CMP("&delta;", str) { return(set_char_wrapper(str, 948)); }
+ else if CMP("&epsilon;", str) { return(set_char_wrapper(str, 949)); }
+ else if CMP("&zeta;", str) { return(set_char_wrapper(str, 950)); }
+ else if CMP("&eta;", str) { return(set_char_wrapper(str, 951)); }
+ else if CMP("&theta;", str) { return(set_char_wrapper(str, 952)); }
+ else if CMP("&iota;", str) { return(set_char_wrapper(str, 953)); }
+ else if CMP("&kappa;", str) { return(set_char_wrapper(str, 954)); }
+ else if CMP("&lambda;", str) { return(set_char_wrapper(str, 955)); }
+ else if CMP("&mu;", str) { return(set_char_wrapper(str, 956)); }
+ else if CMP("&nu;", str) { return(set_char_wrapper(str, 957)); }
+ else if CMP("&xi;", str) { return(set_char_wrapper(str, 958)); }
+ else if CMP("&omicron;", str) { return(set_char_wrapper(str, 959)); }
+ else if CMP("&pi;", str) { return(set_char_wrapper(str, 960)); }
+ else if CMP("&rho;", str) { return(set_char_wrapper(str, 961)); }
+ else if CMP("&sigmaf;", str) { return(set_char_wrapper(str, 962)); }
+ else if CMP("&sigma;", str) { return(set_char_wrapper(str, 963)); }
+ else if CMP("&tau;", str) { return(set_char_wrapper(str, 964)); }
+ else if CMP("&upsilon;", str) { return(set_char_wrapper(str, 965)); }
+ else if CMP("&phi;", str) { return(set_char_wrapper(str, 966)); }
+ else if CMP("&chi;", str) { return(set_char_wrapper(str, 967)); }
+ else if CMP("&psi;", str) { return(set_char_wrapper(str, 968)); }
+ else if CMP("&omega;", str) { return(set_char_wrapper(str, 969)); }
+ else if CMP("&thetasym;", str) { return(set_char_wrapper(str, 977)); }
+ else if CMP("&upsih;", str) { return(set_char_wrapper(str, 978)); }
+ else if CMP("&piv;", str) { return(set_char_wrapper(str, 982)); }
+ else if CMP("&bull;", str) { return(set_char_wrapper(str, 8226)); }
+ else if CMP("&hellip;", str) { return(set_char_wrapper(str, 8230)); }
+ else if CMP("&prime;", str) { return(set_char_wrapper(str, 8242)); }
+ else if CMP("&Prime;", str) { return(set_char_wrapper(str, 8243)); }
+ else if CMP("&oline;", str) { return(set_char_wrapper(str, 8254)); }
+ else if CMP("&frasl;", str) { return(set_char_wrapper(str, 8260)); }
+ else if CMP("&weierp;", str) { return(set_char_wrapper(str, 8472)); }
+ else if CMP("&image;", str) { return(set_char_wrapper(str, 8465)); }
+ else if CMP("&real;", str) { return(set_char_wrapper(str, 8476)); }
+ else if CMP("&trade;", str) { return(set_char_wrapper(str, 8482)); }
+ else if CMP("&alefsym;", str) { return(set_char_wrapper(str, 8501)); }
+ else if CMP("&larr;", str) { return(set_char_wrapper(str, 8592)); }
+ else if CMP("&uarr;", str) { return(set_char_wrapper(str, 8593)); }
+ else if CMP("&rarr;", str) { return(set_char_wrapper(str, 8594)); }
+ else if CMP("&darr;", str) { return(set_char_wrapper(str, 8595)); }
+ else if CMP("&harr;", str) { return(set_char_wrapper(str, 8596)); }
+ else if CMP("&crarr;", str) { return(set_char_wrapper(str, 8629)); }
+ else if CMP("&lArr;", str) { return(set_char_wrapper(str, 8656)); }
+ else if CMP("&uArr;", str) { return(set_char_wrapper(str, 8657)); }
+ else if CMP("&rArr;", str) { return(set_char_wrapper(str, 8658)); }
+ else if CMP("&dArr;", str) { return(set_char_wrapper(str, 8659)); }
+ else if CMP("&hArr;", str) { return(set_char_wrapper(str, 8660)); }
+ else if CMP("&forall;", str) { return(set_char_wrapper(str, 8704)); }
+ else if CMP("&part;", str) { return(set_char_wrapper(str, 8706)); }
+ else if CMP("&exist;", str) { return(set_char_wrapper(str, 8707)); }
+ else if CMP("&empty;", str) { return(set_char_wrapper(str, 8709)); }
+ else if CMP("&nabla;", str) { return(set_char_wrapper(str, 8711)); }
+ else if CMP("&isin;", str) { return(set_char_wrapper(str, 8712)); }
+ else if CMP("&notin;", str) { return(set_char_wrapper(str, 8713)); }
+ else if CMP("&ni;", str) { return(set_char_wrapper(str, 8715)); }
+ else if CMP("&prod;", str) { return(set_char_wrapper(str, 8719)); }
+ else if CMP("&sum;", str) { return(set_char_wrapper(str, 8721)); }
+ else if CMP("&minus;", str) { return(set_char_wrapper(str, 8722)); }
+ else if CMP("&lowast;", str) { return(set_char_wrapper(str, 8727)); }
+ else if CMP("&radic;", str) { return(set_char_wrapper(str, 8730)); }
+ else if CMP("&prop;", str) { return(set_char_wrapper(str, 8733)); }
+ else if CMP("&infin;", str) { return(set_char_wrapper(str, 8734)); }
+ else if CMP("&ang;", str) { return(set_char_wrapper(str, 8736)); }
+ else if CMP("&and;", str) { return(set_char_wrapper(str, 8743)); }
+ else if CMP("&or;", str) { return(set_char_wrapper(str, 8744)); }
+ else if CMP("&cap;", str) { return(set_char_wrapper(str, 8745)); }
+ else if CMP("&cup;", str) { return(set_char_wrapper(str, 8746)); }
+ else if CMP("&int;", str) { return(set_char_wrapper(str, 8747)); }
+ else if CMP("&there4;", str) { return(set_char_wrapper(str, 8756)); }
+ else if CMP("&sim;", str) { return(set_char_wrapper(str, 8764)); }
+ else if CMP("&cong;", str) { return(set_char_wrapper(str, 8773)); }
+ else if CMP("&asymp;", str) { return(set_char_wrapper(str, 8776)); }
+ else if CMP("&ne;", str) { return(set_char_wrapper(str, 8800)); }
+ else if CMP("&equiv;", str) { return(set_char_wrapper(str, 8801)); }
+ else if CMP("&le;", str) { return(set_char_wrapper(str, 8804)); }
+ else if CMP("&ge;", str) { return(set_char_wrapper(str, 8805)); }
+ else if CMP("&sub;", str) { return(set_char_wrapper(str, 8834)); }
+ else if CMP("&sup;", str) { return(set_char_wrapper(str, 8835)); }
+ else if CMP("&nsub;", str) { return(set_char_wrapper(str, 8836)); }
+ else if CMP("&sube;", str) { return(set_char_wrapper(str, 8838)); }
+ else if CMP("&supe;", str) { return(set_char_wrapper(str, 8839)); }
+ else if CMP("&oplus;", str) { return(set_char_wrapper(str, 8853)); }
+ else if CMP("&otimes;", str) { return(set_char_wrapper(str, 8855)); }
+ else if CMP("&perp;", str) { return(set_char_wrapper(str, 8869)); }
+ else if CMP("&sdot;", str) { return(set_char_wrapper(str, 8901)); }
+ else if CMP("&lceil;", str) { return(set_char_wrapper(str, 8968)); }
+ else if CMP("&rceil;", str) { return(set_char_wrapper(str, 8969)); }
+ else if CMP("&lfloor;", str) { return(set_char_wrapper(str, 8970)); }
+ else if CMP("&rfloor;", str) { return(set_char_wrapper(str, 8971)); }
+ else if CMP("&lang;", str) { return(set_char_wrapper(str, 9001)); }
+ else if CMP("&rang;", str) { return(set_char_wrapper(str, 9002)); }
+ else if CMP("&loz;", str) { return(set_char_wrapper(str, 9674)); }
+ else if CMP("&spades;", str) { return(set_char_wrapper(str, 9824)); }
+ else if CMP("&clubs;", str) { return(set_char_wrapper(str, 9827)); }
+ else if CMP("&hearts;", str) { return(set_char_wrapper(str, 9829)); }
+ else if CMP("&diams;", str) { return(set_char_wrapper(str, 9830)); }
+ else if CMP("&quot;", str) { return(set_char_wrapper(str, 34)); }
+ else if CMP("&amp;", str) { return(set_char_wrapper(str, 38)); }
+ else if CMP("&apos;", str) { return(set_char_wrapper(str, 39)); }
+ else if CMP("&lt;", str) { return(set_char_wrapper(str, 60)); }
+ else if CMP("&gt;", str) { return(set_char_wrapper(str, 62)); }
+ else if CMP("&OElig;", str) { return(set_char_wrapper(str, 338)); }
+ else if CMP("&oelig;", str) { return(set_char_wrapper(str, 339)); }
+ else if CMP("&Scaron;", str) { return(set_char_wrapper(str, 352)); }
+ else if CMP("&scaron;", str) { return(set_char_wrapper(str, 353)); }
+ else if CMP("&Yuml;", str) { return(set_char_wrapper(str, 376)); }
+ else if CMP("&circ;", str) { return(set_char_wrapper(str, 710)); }
+ else if CMP("&tilde;", str) { return(set_char_wrapper(str, 732)); }
+ else if CMP("&ensp;", str) { return(set_char_wrapper(str, 8194)); }
+ else if CMP("&emsp;", str) { return(set_char_wrapper(str, 8195)); }
+ else if CMP("&thinsp;", str) { return(set_char_wrapper(str, 8201)); }
+ else if CMP("&zwnj;", str) { return(set_char_wrapper(str, 8204)); }
+ else if CMP("&zwj;", str) { return(set_char_wrapper(str, 8205)); }
+ else if CMP("&lrm;", str) { return(set_char_wrapper(str, 8206)); }
+ else if CMP("&rlm;", str) { return(set_char_wrapper(str, 8207)); }
+ else if CMP("&ndash;", str) { return(set_char_wrapper(str, 8211)); }
+ else if CMP("&mdash;", str) { return(set_char_wrapper(str, 8212)); }
+ else if CMP("&lsquo;", str) { return(set_char_wrapper(str, 8216)); }
+ else if CMP("&rsquo;", str) { return(set_char_wrapper(str, 8217)); }
+ else if CMP("&sbquo;", str) { return(set_char_wrapper(str, 8218)); }
+ else if CMP("&ldquo;", str) { return(set_char_wrapper(str, 8220)); }
+ else if CMP("&rdquo;", str) { return(set_char_wrapper(str, 8221)); }
+ else if CMP("&bdquo;", str) { return(set_char_wrapper(str, 8222)); }
+ else if CMP("&dagger;", str) { return(set_char_wrapper(str, 8224)); }
+ else if CMP("&Dagger;", str) { return(set_char_wrapper(str, 8225)); }
+ else if CMP("&permil;", str) { return(set_char_wrapper(str, 8240)); }
+ else if CMP("&lsaquo;", str) { return(set_char_wrapper(str, 8249)); }
+ else if CMP("&rsaquo;", str) { return(set_char_wrapper(str, 8250)); }
+ else if CMP("&euro;", str) { return(set_char_wrapper(str, 8364)); }
+ else { return(0); } /* found no latin1 entity */
+
+ return(1); /* found latin1 entity */
+}
diff --git a/libraries/libVilistextum/latin1.h b/libraries/libVilistextum/latin1.h
new file mode 100644
index 00000000..2b754fae
--- /dev/null
+++ b/libraries/libVilistextum/latin1.h
@@ -0,0 +1,8 @@
+#ifndef latin1_h
+#define latin1_h 1
+
+#include "multibyte.h"
+
+void parse_entity(CHAR *string);
+void parse_entities(CHAR *string);
+#endif
diff --git a/libraries/libVilistextum/lists.c b/libraries/libVilistextum/lists.c
new file mode 100644
index 00000000..6b75d2f8
--- /dev/null
+++ b/libraries/libVilistextum/lists.c
@@ -0,0 +1,166 @@
+/*
+ * Copyright (c) 1998-2006 Patric Müller
+ * bhaak@gmx.net
+ * http://bhaak.dyndns.org/vilistextum/
+ *
+ * Released under the GNU GPL Version 2 - http://www.gnu.org/copyleft/gpl.html
+ *
+ * 04.09.01: added some more bullet_styles.
+ * 15.03.04: lists generate less newlines
+ *
+ */
+
+#include <stdio.h>
+#include <string.h>
+
+#include "html.h"
+#include "text.h"
+
+CHAR bullet_style=' ';
+
+/* ------------------------------------------------ */
+
+int check_style()
+{
+ while (ch!='>')
+ {
+ ch=get_attr();
+ if CMP("TYPE", attr_name)
+ {
+ if CMP("disc", attr_ctnt) { return '*'; }
+ if CMP("square", attr_ctnt) { return '+'; }
+ if CMP("circle", attr_ctnt) { return 'o'; }
+ }
+ }
+ return 0;
+}
+
+/* ------------------------------------------------ */
+
+void start_uls()
+{
+ line_break();
+
+ push_align(LEFT);
+
+ /* * o + # @ - = ~ $ % */
+ if (bullet_style==' ') { bullet_style='*'; }
+ else if (bullet_style=='*') { bullet_style='o'; }
+ else if (bullet_style=='o') { bullet_style='+'; }
+ else if (bullet_style=='+') { bullet_style='#'; }
+ else if (bullet_style=='#') { bullet_style='@'; }
+ else if (bullet_style=='@') { bullet_style='-'; }
+ else if (bullet_style=='-') { bullet_style='='; }
+ else if (bullet_style=='=') { bullet_style='~'; }
+ else if (bullet_style=='~') { bullet_style='$'; }
+ else if (bullet_style=='$') { bullet_style='%'; }
+
+ spaces += tab;
+}
+
+void end_uls()
+{
+ spaces -= tab;
+ line_break();
+
+ if (bullet_style=='%') { bullet_style='$'; }
+ else if (bullet_style=='$') { bullet_style='~'; }
+ else if (bullet_style=='~') { bullet_style='='; }
+ else if (bullet_style=='=') { bullet_style='-'; }
+ else if (bullet_style=='-') { bullet_style='@'; }
+
+ else if (bullet_style=='@') { bullet_style='#'; }
+ else if (bullet_style=='#') { bullet_style='+'; }
+ else if (bullet_style=='+') { bullet_style='o'; }
+ else if (bullet_style=='o') { bullet_style='*'; }
+ else if (bullet_style=='*') { bullet_style=' '; }
+
+ pop_align();
+}
+
+/* ------------------------------------------------ */
+
+void start_ols()
+{
+ start_uls();
+}
+
+/* ------------------------------------------------ */
+
+void end_ols()
+{
+ end_uls();
+}
+
+/* ------------------------------------------------ */
+
+void start_lis()
+{
+ spaces-=2;
+
+ /* don't output line break, if this list item is immediately
+ after a start or end list tag. start_uls and end_uls have
+ already take care of the line break */
+ if (!is_zeile_empty()) { line_break(); }
+
+ wort_plus_ch(bullet_style);
+
+ wort_ende();
+ spaces+=2;
+}
+
+/* ------------------------------------------------ */
+
+void end_lis() { }
+
+/* ------------------------------------------------ */
+
+int definition_list=0;
+void end_dd();
+
+/* Definition List */
+void start_dl()
+{
+ end_dd();
+ start_p();
+}
+
+void end_dl()
+{
+ paragraphen_ende();
+
+ end_dd();
+}
+
+/* Definition Title */
+void start_dt()
+{
+ end_dd();
+
+ line_break();
+}
+
+void end_dt()
+{
+}
+
+/* Definition Description */
+void start_dd()
+{
+ end_dd();
+
+ line_break();
+ spaces+=tab;
+
+ definition_list=1;
+}
+
+void end_dd()
+{
+ if (definition_list==1)
+ {
+ spaces-=tab;
+ definition_list=0;
+ }
+}
+
diff --git a/libraries/libVilistextum/lists.h b/libraries/libVilistextum/lists.h
new file mode 100644
index 00000000..2c6041c4
--- /dev/null
+++ b/libraries/libVilistextum/lists.h
@@ -0,0 +1,17 @@
+#ifndef lists_h
+#define lists_h 1
+
+void start_uls();
+void end_uls();
+void start_ols();
+void end_ols();
+void start_lis();
+void end_lis();
+void start_dl();
+void end_dl();
+void start_dt();
+void end_dt();
+void start_dd();
+void end_dd();
+
+#endif
diff --git a/libraries/libVilistextum/meson.build b/libraries/libVilistextum/meson.build
new file mode 100644
index 00000000..0f507026
--- /dev/null
+++ b/libraries/libVilistextum/meson.build
@@ -0,0 +1,18 @@
+vilistextum_inc = include_directories('.')
+vilistextum_lib = static_library(
+ 'vilistextum',
+ [
+ 'charset.c',
+ 'fileio.c',
+ 'html.c',
+ 'html_tag.c',
+ 'latin1.c',
+ 'lists.c',
+ 'microsoft.c',
+ 'text.h',
+ 'text.c',
+ 'unicode_entities.c',
+ 'util.c',
+ 'vilistextum.c'
+ ]
+)
diff --git a/libraries/libVilistextum/microsoft.c b/libraries/libVilistextum/microsoft.c
new file mode 100644
index 00000000..bd493fb7
--- /dev/null
+++ b/libraries/libVilistextum/microsoft.c
@@ -0,0 +1,223 @@
+/*
+ * Copyright (c) 1998-2006 Patric Müller
+ * bhaak@gmx.net
+ * http://bhaak.dyndns.org/vilistextum/
+ *
+ * Released under the GNU GPL Version 2 - http://www.gnu.org/copyleft/gpl.html
+ *
+ */
+
+#include <string.h>
+#include <stdio.h>
+
+#include "text.h"
+#include "vilistextum.h"
+#include "util.h"
+#include "multibyte.h"
+
+/* ------------------------------------------------ */
+
+int microsoft_entities(CHAR *s)
+{
+ int number = extract_entity_number(s);
+
+ if (!convert_characters) { return(0); }
+ /* Euro */
+ else if (number==128) { CPYSL(s, "EUR"); }
+ else if CMP("&euro;", s) { CPYSL(s, "EUR"); }
+ else if (number==8364) { CPYSL(s, "EUR"); }
+
+ /* Single Low-9 Quotation Mark */
+ else if (number==130) { set_char(s, ','); }
+ else if CMP("&sbquo;", s) { set_char(s, ','); }
+ else if (number==8218) { set_char(s, ','); }
+
+ else if (number==131) { set_char(s, 'f'); } /* Latin Small Letter F With Hook */
+ else if CMP("&fnof;", s) { set_char(s, 'f'); } /* Latin Small Letter F With Hook */
+ else if (number==402) { set_char(s, 'f'); } /* Latin Small Letter F With Hook */
+
+ /* Double Low-9 Quotation Mark */
+ else if (number==132) { CPYSL(s, "\""); }
+ else if CMP("&bdquo;", s) { CPYSL(s, "\""); }
+ else if (number==8222) { CPYSL(s, "\""); }
+
+ else if (number==133) { CPYSL(s, "..."); } /* Horizontal Ellipsis */
+ else if CMP("&hellip;", s) { CPYSL(s, "..."); } /* Horizontal Ellipsis */
+ else if (number==8230) { CPYSL(s, "..."); } /* Horizontal Ellipsis */
+
+ /* Dagger */
+ else if (number==134) { CPYSL(s, "/-"); }
+ else if CMP("&dagger;", s) { CPYSL(s, "/-"); }
+ else if (number==8224) { CPYSL(s, "/-"); }
+
+ /* Double Dagger */
+ else if (number==135) { CPYSL(s, "/="); }
+ else if CMP("&Dagger;", s) { CPYSL(s, "/="); }
+ else if (number==8225) { CPYSL(s, "/="); }
+
+ /* Modifier Letter Circumflex Accent */
+ else if (number==136) { set_char(s, '^'); }
+ else if CMP("&circ;", s) { set_char(s, '^'); }
+ else if (number==710) { set_char(s, '^'); }
+
+ /* Per Mille Sign */
+ else if (number==137) { CPYSL(s, "0/00"); }
+ else if CMP("&permil;", s) { CPYSL(s, "0/00"); }
+ else if (number==8240) { CPYSL(s, "0/00"); }
+
+ /* Latin Capital Letter S With Caron */
+ else if (number==138) { set_char(s, 'S'); }
+ else if CMP("&Scaron;", s) { set_char(s, 'S'); }
+ else if (number==352) { set_char(s, 'S'); }
+
+ /* Single Left-Pointing Angle Quotation Mark */
+ else if (number==139) { set_char(s, '<'); }
+ else if CMP("&lsaquo;", s) { set_char(s, '<'); }
+ else if (number==8249) { set_char(s, '<'); }
+
+ /* Latin Capital Ligature OE */
+ else if (number==140) { CPYSL(s, "OE"); }
+ else if CMP("&OElig;", s) { CPYSL(s, "OE"); }
+ else if (number==338) { CPYSL(s, "OE"); }
+
+ /* Z\/ */
+ else if (number==142) { set_char(s, 'Z'); }
+ else if (number==381) { set_char(s, 'Z'); }
+
+ /* Left Single Quotation Mark */
+ else if (number==145) { set_char(s, '`'); }
+ else if CMP("&lsquo;", s) { set_char(s, '`'); }
+ else if (number==8216) { set_char(s, '`'); }
+
+ /* Right Single Quotation Mark */
+ else if (number==146) { set_char(s, '\''); }
+ else if CMP("&rsquo;", s) { set_char(s, '\''); }
+ else if (number==8217) { set_char(s, '\''); }
+
+ /* Left Double Quotation Mark */
+ else if (number==147) { set_char(s, '"'); }
+ else if CMP("&ldquo;", s) { set_char(s, '"'); }
+ else if (number==8220) { set_char(s, '"'); }
+
+ /* Right Double Quotation Mark */
+ else if (number==148) { set_char(s, '"'); }
+ else if CMP("&rdquo;", s) { set_char(s, '"'); }
+ else if (number==8221) { set_char(s, '"'); }
+
+ /* Bullet */
+ else if (number==149) { set_char(s, '*'); }
+ else if CMP("&bull;", s) { set_char(s, '*'); }
+ else if (number==8226) { set_char(s, '*'); }
+
+ /* En Dash */
+ else if (number==150) { set_char(s, '-'); }
+ else if CMP("&ndash;", s) { set_char(s, '-'); }
+ else if (number==8211) { set_char(s, '-'); }
+
+ /* Em Dash */
+ else if (number==151) { CPYSL(s, "--"); }
+ else if CMP("&mdash;", s) { CPYSL(s, "--"); }
+ else if (number==8212) { CPYSL(s, "--"); }
+
+ /* Small Tilde */
+ else if (number==152) { set_char(s, '~'); }
+ else if CMP("&tilde;", s) { set_char(s, '~'); }
+ else if (number==732) { set_char(s, '~'); }
+
+ /* Trade Mark Sign */
+ else if (number==153) { CPYSL(s, "[tm]"); }
+ else if CMP("&trade;", s) { CPYSL(s, "[tm]"); }
+ else if (number==8482) { CPYSL(s, "[tm]"); }
+
+ /* Latin Small Letter S With Caron */
+ else if (number==154) { set_char(s, 's'); }
+ else if CMP("&scaron;", s) { set_char(s, 's'); }
+ else if (number==353) { set_char(s, 's'); }
+
+ /* Single Right-Pointing Angle Quotation Mark */
+ else if (number==155) { set_char(s, '>'); }
+ else if CMP("&rsaquo;", s) { set_char(s, '>'); }
+ else if (number==8250) { set_char(s, '>'); }
+
+ /* Latin Small Ligature OE */
+ else if (number==156) { CPYSL(s, "oe"); }
+ else if CMP("&oelig;", s) { CPYSL(s, "oe"); }
+ else if (number==339) { CPYSL(s, "oe"); }
+
+ /* z\/ */
+ else if (number==158) { set_char(s, 'z'); }
+ else if (number==382) { set_char(s, 'z'); }
+
+ /* Latin Capital Letter Y With Diaeresis */
+ else if (number==159) { set_char(s, 'Y'); }
+ else if CMP("&Yuml;", s) { set_char(s, 'Y'); }
+ else if (number==376) { set_char(s, 'Y'); }
+
+ else { return(0); }
+
+ return(1); /* Microsoft entity found */
+}
+
+/* ------------------------------------------------ */
+
+void microsoft_character(int c)
+{
+ switch (c)
+ {
+ /* Microsoft... */
+ case 0x80: /* MICROSOFT EURO */
+ WORT_PLUS_STRING("EUR"); break;
+ case 0x82: /* SINGLE LOW-9 QUOTATION MARK */
+ wort_plus_ch(','); break;
+ case 0x83: /* Latin Small Letter F With Hook */
+ wort_plus_ch('f'); break;
+ case 0x84: /* Double Low-9 Quotation Mark */
+ WORT_PLUS_STRING("\""); break;
+ case 0x85: /* HORIZONTAL ELLIPSIS */
+ WORT_PLUS_STRING("..."); break;
+ case 0x86: /* Dagger */
+ WORT_PLUS_STRING("/-"); break;
+ case 0x87: /* Double Dagger */
+ WORT_PLUS_STRING("/="); break;
+ case 0x88: /* Modifier Letter Circumflex Accent */
+ wort_plus_ch('^'); break;
+ case 0x89: /* Per Mille Sign */
+ WORT_PLUS_STRING("0/00"); break;
+ case 0x8a: /* Latin Capital Letter S With Caron */
+ wort_plus_ch('S'); break;
+ case 0x8b: /* Single Left-Pointing Angle Quotation Mark */
+ wort_plus_ch('<'); break;
+ case 0x8c: /* Latin Capital Ligature OE */
+ WORT_PLUS_STRING("OE"); break;
+ case 0x8e: /* Z\/ */
+ wort_plus_ch('Z'); break;
+ case 0x91: /* LEFT SINGLE QUOTATION MARK */
+ wort_plus_ch('`'); break;
+ case 0x92: /* RIGHT SINGLE QUOTATION MARK */
+ wort_plus_ch('\''); break;
+ case 0x93: /* LEFT DOUBLE QUOTATION MARK */
+ wort_plus_ch('\"'); break;
+ case 0x94: /* RIGHT DOUBLE QUOTATION MARK */
+ wort_plus_ch('\"'); break;
+ case 0x95: /* BULLET */
+ wort_plus_ch('*'); break;
+ case 0x96: /* EN DASH */
+ wort_plus_ch('-'); break;
+ case 0x97: /* EM DASH */
+ WORT_PLUS_STRING("--"); break;
+ case 0x98: /* SMALL TILDE */
+ wort_plus_ch('~'); break;
+ case 0x99: /* TRADE MARK SIGN */
+ WORT_PLUS_STRING("[tm]"); break;
+ case 0x9a: /* LATIN SMALL LETTER S WITH CARON */
+ wort_plus_ch('s'); break;
+ case 0x9b: /* SINGLE RIGHT-POINTING ANGLE QUOTATION MARK */
+ wort_plus_ch('>'); break;
+ case 0x9c: /* LATIN SMALL LIGATURE OE */
+ WORT_PLUS_STRING("oe"); break;
+ case 0x9e: /* z\/ */
+ wort_plus_ch('z'); break;
+ case 0x9f: /* LATIN CAPITAL LETTER Y WITH DIAERESIS */
+ wort_plus_ch('Y'); break;
+ }
+}
diff --git a/libraries/libVilistextum/microsoft.h b/libraries/libVilistextum/microsoft.h
new file mode 100644
index 00000000..2efe8f51
--- /dev/null
+++ b/libraries/libVilistextum/microsoft.h
@@ -0,0 +1,9 @@
+#ifndef microsoft_h
+#define microsoft_h 1
+
+#include "multibyte.h"
+
+int microsoft_entities(CHAR *s);
+void microsoft_character(int c);
+
+#endif
diff --git a/libraries/libVilistextum/multibyte.h b/libraries/libVilistextum/multibyte.h
new file mode 100644
index 00000000..f84588e3
--- /dev/null
+++ b/libraries/libVilistextum/multibyte.h
@@ -0,0 +1,26 @@
+#include <wchar.h>
+#define CHAR wchar_t
+#define STRLEN(s) wcslen(s)
+#define CPYSS(dest, src) wcscpy(dest, src) /* copy str to str */
+#define CPYSL(dest, src) wcscpy(dest, L##src) /* copy str to L"str" */
+
+#define STRCMP(s1, s2) wcscmp( L##s1, s2 )
+#define STRCASECMP(s1, s2) (wcscasecmp(L##s1, s2)==0)
+#define CMP(s1, s2) (wcscmp(L##s1, s2)==0)
+#define STRCAT(dest, src) wcscat(dest, (wchar_t*) src)
+
+#define wcstoi(tmp) wcstol(tmp, (wchar_t **)NULL, 10)
+#define ATOI(n) wcstoi(n)
+
+#define ONESPACE L" "
+#define WORT_PLUS_STRING(str) wort_plus_string(L##str)
+
+#define STRSTR(haystack, needle) wcsstr(haystack, L##needle)
+
+#define STRNCMP(str1, str2, nr) wcsncmp(L##str1, str2, nr)
+
+#define STRING(string) L##string
+
+#define GETC(stream) fgetwc(stream)
+#define CEOF WEOF
+
diff --git a/libraries/libVilistextum/text.c b/libraries/libVilistextum/text.c
new file mode 100644
index 00000000..dd9c5324
--- /dev/null
+++ b/libraries/libVilistextum/text.c
@@ -0,0 +1,331 @@
+/*
+ * Copyright (c) 1998-2006 Patric Müller
+ * bhaak@gmx.net
+ * http://bhaak.dyndns.org/vilistextum/
+ *
+ * Released under the GNU GPL Version 2 - http://www.gnu.org/copyleft/gpl.html
+ *
+ */
+
+#include "multibyte.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+
+#include "text.h"
+#include "vilistextum.h"
+#include "html.h"
+#include "fileio.h"
+#include "util.h"
+
+int LEFT = 1,
+CENTER = 2,
+RIGHT = 3;
+
+int breite=76,
+hr_breite=76,
+paragraph=0,
+
+tab=4, /* tabulator */
+spaces=0, /* spaces at beginning of line */
+nooutput=0, /* for SCRIPT, etc: no output */
+orderedlist=0, /* OL */
+
+div_test=0;
+
+CHAR wort[DEF_STR_LEN];
+
+CHAR zeile[DEF_STR_LEN];
+int zeilen_len=0, /* apparent length of the line */
+zeilen_len_old=0,
+zeilen_pos=0, /* true length of line */
+wort_len=0, /* apparent length of the word */
+wort_pos=0, /* true length of word */
+anz_leere_zeilen=0, /* how many line were blank */
+noleadingblanks=0; /* remove blanks lines at the start of the output */
+
+/* ------------------------------------------------ */
+
+void center_zeile()
+{
+ int i,j;
+
+ if (!palm)
+ {
+ /* ensure that the string is not the empty string */
+ if (zeilen_len!=0)
+ {
+ /* ensure that centering is possible */
+ if (zeilen_pos<breite)
+ {
+ j=(breite-zeilen_len)/2;
+
+ for (i=zeilen_pos+j; i>=0; i--)
+ {
+ zeile[i+j]=zeile[i];
+ }
+ for (i=0; i<j; i++) { zeile[i]=' '; }
+ }
+ }
+ }
+}
+
+/* ------------------------------------------------ */
+
+void right_zeile()
+{
+ int i,j;
+
+ if (!palm)
+ {
+ if (zeilen_len!=0)
+ {
+ j=breite-zeilen_len;
+ for (i=zeilen_pos+j+2; i>=0; i--)
+ {
+ zeile[i+j]=zeile[i];
+ }
+ for (i=0; i<j; i++) { zeile[i]=' '; }
+ }
+ }
+}
+
+/* ------------------------------------------------ */
+
+/* return true, if z is all spaces or nonbreakable space */
+int only_spaces(CHAR *z)
+{
+ int len=STRLEN(z);
+ int i, ret=1;
+ CHAR j;
+
+ for (i=0; i<len; i++) { j=z[i]; ret = (ret && ((j==' ')||(j==160))); }
+ return(ret);
+}
+
+/* ------------------------------------------------ */
+
+void clear_line() {
+ zeile[0]='\0';
+ zeilen_len=0; zeilen_pos=0;
+}
+
+/* ------------------------------------------------ */
+
+/* print line */
+void print_zeile()
+{
+ int printzeile;
+
+ if ((shrink_lines) && only_spaces(zeile))
+ {
+ clear_line();
+ anz_leere_zeilen++;
+ } else {
+ anz_leere_zeilen=0;
+ }
+
+ /* Don't allow leading blank lines.
+ That means the first line of the output is never an empty line */
+ if (noleadingblanks==0) { noleadingblanks = !only_spaces(zeile); }
+
+ if (shrink_lines==0)
+ {
+ printzeile = (!((zeilen_len==0)&&(zeilen_len_old==0)));
+ } else {
+ printzeile = (!((anz_leere_zeilen>shrink_lines)||(noleadingblanks==0)));
+ }
+
+ if (printzeile)
+ {
+ if (get_align()==LEFT) {}
+ if (get_align()==CENTER) { center_zeile(); }
+ if (get_align()==RIGHT) { right_zeile(); }
+
+ if (!nooutput)
+ {
+ output_string(zeile);
+ }
+
+ zeilen_len_old=zeilen_len;
+ clear_line();
+ }
+}
+
+/* ------------------------------------------------ */
+
+int is_zeile_empty()
+{
+ return(zeile[0]=='\0');
+}
+
+/* ------------------------------------------------ */
+
+void status()
+{
+ printf(" paragraph: %d; div_test: %d; align[align_nr]: %d; z_o: %d\n",paragraph, div_test, get_align(), zeilen_len_old);
+}
+
+/* ------------------------------------------------ */
+
+void zeile_plus_wort(CHAR *s, int wl, int wp)
+{
+ int i=zeilen_pos,
+ j=0;
+
+ if (zeilen_pos+wp<DEF_STR_LEN-1) {
+ while (i<zeilen_pos+wp) { zeile[i] = s[j]; j++; i++; }
+ zeile[i] = '\0';
+ zeilen_len += wl; zeilen_pos += wp;
+ }
+}
+
+/* ------------------------------------------------ */
+
+void wort_plus_string_nocount(CHAR *s)
+{
+ int len=STRLEN(s),
+ i=wort_pos,
+ j=0;
+
+ if (!palm)
+ {
+ if (wort_pos+len<DEF_STR_LEN-1) {
+ while (i<wort_pos+len) { wort[i] = s[j]; j++; i++; }
+ wort[i] = '\0';
+ wort_pos += len;
+ }
+ }
+}
+
+/* ------------------------------------------------ */
+
+void wort_plus_string(CHAR *s)
+{
+ int len=STRLEN(s),
+ i=wort_pos,
+ j=0;
+
+ if (wort_pos+len<DEF_STR_LEN-1) {
+ while (i<wort_pos+len) { wort[i] = s[j]; j++; i++; }
+ wort[i] = '\0';
+ wort_pos += len; wort_len += len;
+ }
+}
+
+/* ------------------------------------------------ */
+
+void wort_plus_ch(int c)
+{
+ if (wort_pos<DEF_STR_LEN-1) {
+ wort[wort_pos++] = c;
+ wort_len++;
+ }
+}
+
+/* ------------------------------------------------ */
+
+void wort_ende()
+{
+ int i=0;
+
+ if (wort_len > 0)
+ {
+ wort[wort_pos] = '\0';
+
+ if (zeilen_len+wort_len+1 > breite)
+ {
+ print_zeile();
+ i=0;
+ while (i<spaces) { zeile_plus_wort(ONESPACE,1,1); i++; }
+ if (orderedlist>0) { zeile_plus_wort(ONESPACE,1,1); }
+ zeile_plus_wort(wort, wort_len, wort_pos);
+ }
+ else if (zeilen_len != 0)
+ {
+ /* add space + word */
+ zeile_plus_wort(ONESPACE,1,1);
+ zeile_plus_wort(wort,wort_len, wort_pos);
+ }
+ else /* zeilen_len==0 => new beginning of a paragraph */
+ {
+ i=0;
+ while (i<spaces) { zeile_plus_wort(ONESPACE,1,1); i++; }
+ if (orderedlist>0) { zeile_plus_wort(ONESPACE,1,1); }
+ zeile_plus_wort(wort,wort_len, wort_pos);
+ }
+ wort_pos = 0;
+ wort_len = 0;
+ }
+}
+
+/* ------------------------------------------------ */
+
+void line_break()
+{
+ wort_ende();
+ print_zeile();
+}
+
+/* ------------------------------------------------ */
+
+void paragraphen_ende()
+{
+ if (paragraph!=0)
+ {
+ line_break();
+ print_zeile();
+ paragraph--;
+ pop_align();
+ }
+}
+
+/* ------------------------------------------------ */
+
+void neuer_paragraph()
+{
+ if (paragraph!=0) { paragraphen_ende(); }
+ line_break();
+ print_zeile();
+ paragraph++;
+}
+
+/* ------------------------------------------------ */
+
+void hr()
+{
+ int i, hr_width=hr_breite-4, hr_align=CENTER;
+ while (ch!='>')
+ {
+ ch=get_attr();
+ if CMP("ALIGN", attr_name)
+ {
+ uppercase_str(attr_ctnt);
+ if CMP("LEFT", attr_ctnt) { hr_align=LEFT; }
+ else if CMP("CENTER", attr_ctnt) { hr_align=CENTER; }
+ else if CMP("RIGHT", attr_ctnt) { hr_align=RIGHT; }
+ else if CMP("JUSTIFY", attr_ctnt) { hr_align=LEFT; }
+ else { if (errorlevel>=2) { fprintf(stderr, "No LEFT|CENTER|RIGHT found!\n");} }
+ }
+ else if CMP("WIDTH", attr_name)
+ {
+ i=STRLEN(attr_ctnt);
+ if (attr_ctnt[i-1]=='%') {
+ attr_ctnt[i-1] = '\0';
+ hr_width = ATOI(attr_ctnt);
+ if (hr_width==100) { hr_width = hr_breite-4; }
+ else { hr_width = hr_breite*hr_width/100; }
+ } else {
+ hr_width = ATOI(attr_ctnt)/8;
+ if (hr_width>hr_breite-4) { hr_width = hr_breite-4; }
+ }
+ }
+ }
+
+ neuer_paragraph();
+ push_align(hr_align);
+ for (i=0; i<hr_width; i++) { wort_plus_ch('-'); }
+ paragraphen_ende();
+}
diff --git a/libraries/libVilistextum/text.h b/libraries/libVilistextum/text.h
new file mode 100644
index 00000000..d772bbf3
--- /dev/null
+++ b/libraries/libVilistextum/text.h
@@ -0,0 +1,44 @@
+#ifndef text_h
+#define text_h
+
+#define DEF_STR_LEN 32768
+
+#include "multibyte.h"
+
+int LEFT;
+int CENTER;
+int RIGHT;
+
+CHAR ch;
+
+int paragraph;
+int div_test;
+int nooutput;
+
+int breite;
+int hr_breite;
+
+void status();
+
+int tab;
+int spaces;
+
+void print_zeile();
+int is_zeile_empty();
+void clear_line();
+
+void push_align(int a);
+void pop_align();
+
+void wort_plus_string(CHAR *s);
+void wort_plus_ch(int c);
+void wort_ende();
+
+void line_break();
+
+void paragraphen_ende();
+void neuer_paragraph();
+
+void hr();
+
+#endif
diff --git a/libraries/libVilistextum/unicode_entities.c b/libraries/libVilistextum/unicode_entities.c
new file mode 100644
index 00000000..31b8441e
--- /dev/null
+++ b/libraries/libVilistextum/unicode_entities.c
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 1998-2006 Patric Müller
+ * bhaak@gmx.net
+ * http://bhaak.dyndns.org/vilistextum/
+ *
+ * Released under the GNU GPL Version 2 - http://www.gnu.org/copyleft/gpl.html
+ *
+ */
+
+#include <string.h>
+#include <stdio.h>
+
+#include "vilistextum.h"
+#include "util.h"
+#include "multibyte.h"
+
+/* ------------------------------------------------ */
+
+int unicode_entity(CHAR *s)
+{
+ int number;
+ if (!convert_characters) { return(0); }
+
+ number = extract_entity_number(s);
+
+ switch (number) {
+ case 8208: /* 0x2010 HYPHEN */
+ case 8209: /* 0x2011 NON-BREAKING HYPHEN */
+ set_char(s, '-'); break;
+ default:
+ return(0);
+ }
+
+ return(1); /* found a transcription for entity */
+} /* end unicode_entity */
+
+/* ------------------------------------------------ */
+
+int ligature_entity(CHAR *s)
+{
+ int number;
+ if (!convert_characters) { return(0); }
+
+ number = extract_entity_number(s);
+
+ switch (number) {
+ case 64256: /* FB00 LATIN SMALL LIGATURE FF */
+ CPYSL(s, "ff"); break;
+ case 64257: /* FB01 LATIN SMALL LIGATURE FI */
+ CPYSL(s, "fi"); break;
+ case 64258: /* FB02 LATIN SMALL LIGATURE FL */
+ CPYSL(s, "fl"); break;
+ case 64259: /* FB03 LATIN SMALL LIGATURE FFI */
+ CPYSL(s, "ffi"); break;
+ case 64260: /* FB04 LATIN SMALL LIGATURE FFL */
+ CPYSL(s, "ffl"); break;
+ case 64261: /* FB05 LATIN SMALL LIGATURE LONG S T */
+ CPYSL(s, "ft"); break;
+ case 64262: /* FB06 LATIN SMALL LIGATURE ST */
+ CPYSL(s, "st"); break;
+ case 306: /* 0132 LATIN CAPITAL LIGATURE IJ */
+ CPYSL(s, "IJ"); break;
+ case 307: /* 0133 LATIN SMALL LIGATURE IJ */
+ CPYSL(s, "ij"); break;
+ case 338: /* 0152 LATIN CAPITAL LIGATURE OE */
+ CPYSL(s, "OE"); break;
+ case 339: /* 0153 LATIN SMALL LIGATURE OE */
+ CPYSL(s, "oe"); break;
+ default:
+ return(0);
+ }
+
+ return(1); /* found a transcription for entity */
+} /* end ligature_entity */
+
+/* ------------------------------------------------ */
diff --git a/libraries/libVilistextum/unicode_entities.h b/libraries/libVilistextum/unicode_entities.h
new file mode 100644
index 00000000..f6873a57
--- /dev/null
+++ b/libraries/libVilistextum/unicode_entities.h
@@ -0,0 +1,9 @@
+#ifndef unicode_entities_h
+#define unicode_entities_h 1
+
+#include "multibyte.h"
+
+int unicode_entity(CHAR *s);
+int ligature_entity(CHAR *s);
+
+#endif
diff --git a/libraries/libVilistextum/util.c b/libraries/libVilistextum/util.c
new file mode 100644
index 00000000..218fecd9
--- /dev/null
+++ b/libraries/libVilistextum/util.c
@@ -0,0 +1,160 @@
+/*
+ * Copyright (c) 1998-2006 Patric Müller
+ * bhaak@gmx.net
+ * http://bhaak.dyndns.org/vilistextum/
+ *
+ * Released under the GNU GPL Version 2 - http://www.gnu.org/copyleft/gpl.html
+ *
+ * 08.03.02: align[0] hasn't been set by push_align
+ * 18.02.02: some multibyte code not enclosed by define's
+ * uppercase now available in onebyte and multibyte version
+ * include ctype.h for toupper
+ * 10.04.02: changed the align stack code to let it work on the amiga
+ *
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <string.h>
+#include <wctype.h>
+
+#include "vilistextum.h"
+#include "multibyte.h"
+
+/* Dynamic align added by autophile@starband.net 29 Mar 2002 */
+int *align = NULL;
+int align_nr=0,
+align_size=0;
+
+/* ------------------------------------------------ */
+
+int get_align()
+{
+ /* Dynamic align added by autophile@starband.net 29 Mar 2002 */
+ if (align==NULL)
+ {
+ align = (int *)malloc(256*sizeof(int));
+ align[0] = 1; /* default LEFT alignment. */
+ }
+ return(align[align_nr]);
+}
+
+/* ------------------------------------------------ */
+
+void push_align(int a)
+{
+ int *tmp_align = NULL;
+ align_nr++;
+
+ /* Dynamic align added by autophile@starband.net 29 Mar 2002 */
+ if (align_nr >= align_size)
+ {
+ align_size += 256;
+ if (align == NULL) {
+ align = (int *)malloc(align_size*sizeof(int));
+ align[0] = 1; /* default LEFT alignment. */
+ } else {
+ /*align = (int *)realloc(align, align_size*sizeof(int));*/
+ tmp_align = (int *)malloc(align_size*sizeof(int));
+ if (align_size!=0) { memcpy(tmp_align, align, (align_size-256)*sizeof(int)); }
+ free(align);
+ align = tmp_align;
+ }
+ }
+
+ /* if (div_test!=0) { align[align_nr]=div_test; } else { */
+ align[align_nr]=a; /*} */
+}
+
+void pop_align()
+{
+ if (align_nr==0) { if (errorlevel>=5) { fprintf(stdout, "Error: align_nr=0\n");} }
+ else { align_nr--; }
+}
+
+/* ------------------------------------------------ */
+
+wint_t uppercase(wint_t c)
+{
+ if ((c>='a') && (c<='z')) { c=towupper(c); }
+ return c;
+}
+
+/* ------------------------------------------------ */
+
+void uppercase_str(CHAR *s)
+{
+ int i=0;
+ while(s[i]!='\0') { s[i]=uppercase(s[i]); i++; }
+}
+
+/* ------------------------------------------------ */
+
+/* copy the character to the string */
+void set_char(CHAR *s, CHAR c)
+{
+ s[0] = c;
+ s[1] = '\0';
+}
+
+/* ------------------------------------------------ */
+
+int x2dec(CHAR *str, int base)
+{
+ int i=0,
+ current_nr=0,
+ nr=0;
+ int len=STRLEN(str);
+
+ for (i=0;i<len;i++)
+ {
+ current_nr=str[i];
+ nr*=base;
+ if ((current_nr>='0') && (current_nr<='9')) { nr += current_nr-'0'; }
+ else
+ {
+ current_nr = towupper(current_nr)-'A'+10;
+ if ((current_nr>=10) && (current_nr<base)) { nr += current_nr; }
+ else { return(nr/base); }
+ }
+ }
+ return nr;
+}
+
+/* ------------------------------------------------ */
+
+void print_error(char *error, CHAR *text)
+{
+ fprintf(stderr, "%s%ls\n", error, text);
+}
+
+/* ------------------------------------------------ */
+
+/* return the value of an numeric character entity
+ e.g: 169 for "&#169;" or "&#xA9" */
+int extract_entity_number(CHAR *s)
+{
+ int number;
+ CHAR *tmp = s;
+
+ /* Numeric entity */
+ if ((s[0]=='&') && (s[1]=='#')) {
+ /* Hex entity */
+ if (uppercase(s[2])=='X')
+ {
+ tmp += 3;
+ number = x2dec(tmp, 16);
+ }
+ /* Decimal entity */
+ else {
+ tmp += 2;
+ number = ATOI(tmp);
+ }
+ return(number);
+ } else {
+ return(-1);
+ }
+}
+
+/* ------------------------------------------------ */
diff --git a/libraries/libVilistextum/util.h b/libraries/libVilistextum/util.h
new file mode 100644
index 00000000..f87b6dcf
--- /dev/null
+++ b/libraries/libVilistextum/util.h
@@ -0,0 +1,21 @@
+#ifndef util_h
+#define util_h 1
+
+#include "multibyte.h"
+
+int get_align();
+void push_align(int a);
+void pop_align();
+
+int uppercase(int c);
+void uppercase_str(CHAR *s);
+
+void set_char(CHAR *s, CHAR c);
+
+int x2dec(CHAR *s, int base);
+
+void print_error(char *error, CHAR *text);
+
+int extract_entity_number(CHAR *s);
+
+#endif
diff --git a/libraries/libVilistextum/vilistextum.c b/libraries/libVilistextum/vilistextum.c
new file mode 100644
index 00000000..898d9893
--- /dev/null
+++ b/libraries/libVilistextum/vilistextum.c
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 1998-2006 Patric Müller
+ * bhaak@gmx.net
+ * http://bhaak.dyndns.org/vilistextum/
+ *
+ * Released under the GNU GPL Version 2 - http://www.gnu.org/copyleft/gpl.html
+ */
+
+#include <unistd.h>
+#include <string.h>
+#include <getopt.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "vilistextum.h"
+#include "html.h"
+#include "fileio.h"
+#include "charset.h"
+
+/* ------------------------------------------------ */
+
+void set_options()
+{
+ convert_characters = 1;
+ shrink_lines = 1;
+ remove_empty_alt = 1;
+ option_no_image = 1;
+ option_no_alt = 1;
+ convert_tags = 0;
+ option_links = 0;
+ option_links_inline = 0;
+ option_title = 0;
+ set_iconv_charset("utf-8");
+ errorlevel = 0;
+}
+
+char* vilistextum(char* text, int extractText)
+{
+ if(text == NULL)
+ return NULL;
+
+ error = 0;
+ set_options();
+
+ if(init_multibyte())
+ {
+ open_files(text);
+ html(extractText);
+ quit();
+ }
+
+ char* output = getOutput(strlen(text));
+ return output;
+}
diff --git a/libraries/libVilistextum/vilistextum.h b/libraries/libVilistextum/vilistextum.h
new file mode 100644
index 00000000..26ab0836
--- /dev/null
+++ b/libraries/libVilistextum/vilistextum.h
@@ -0,0 +1,26 @@
+#ifndef vilistextum_h
+#define vilistextum_h
+
+#include "multibyte.h"
+
+int error;
+int palm;
+int convert_tags;
+int errorlevel;
+int convert_characters;
+int shrink_lines;
+int remove_empty_alt;
+int option_links;
+int option_links_inline;
+int option_title;
+int sevenbit;
+int transliteration;
+
+int option_no_image;
+int option_no_alt;
+
+CHAR* default_image;
+
+char* vilistextum(char* text, int extractText);
+
+#endif
diff --git a/libraries/libgd/gd-notification.c b/libraries/libgd/gd-notification.c
new file mode 100644
index 00000000..eee71c47
--- /dev/null
+++ b/libraries/libgd/gd-notification.c
@@ -0,0 +1,875 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ * gd-notification
+ * Based on gtk-notification from gnome-contacts:
+ * http://git.gnome.org/browse/gnome-contacts/tree/src/gtk-notification.c?id=3.3.91
+ *
+ * Copyright (C) Erick Pérez Castellanos 2011 <erick.red@gmail.com>
+ * Copyright (C) 2012 Red Hat, Inc.
+ *
+ * This program 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 program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.";
+ */
+
+#include "gd-notification.h"
+
+/**
+ * SECTION:gdnotification
+ * @short_description: Report notification messages to the user
+ * @include: gtk/gtk.h
+ * @see_also: #GtkStatusbar, #GtkMessageDialog, #GtkInfoBar
+ *
+ * #GdNotification is a widget made for showing notifications to
+ * the user, allowing them to close the notification or wait for it
+ * to time out.
+ *
+ * #GdNotification provides one signal (#GdNotification::dismissed), for when the notification
+ * times out or is closed.
+ *
+ */
+
+#define GTK_PARAM_READWRITE G_PARAM_READWRITE|G_PARAM_STATIC_NAME|G_PARAM_STATIC_NICK|G_PARAM_STATIC_BLURB
+#define SHADOW_OFFSET_X 2
+#define SHADOW_OFFSET_Y 3
+#define ANIMATION_TIME 200 /* msec */
+#define ANIMATION_STEP 40 /* msec */
+
+enum {
+ PROP_0,
+ PROP_TIMEOUT,
+ PROP_SHOW_CLOSE_BUTTON
+};
+
+struct _GdNotificationPrivate {
+ GtkWidget *close_button;
+ gboolean show_close_button;
+
+ GdkWindow *bin_window;
+
+ int animate_y; /* from 0 to allocation.height */
+ gboolean waiting_for_viewable;
+ gboolean revealed;
+ gboolean dismissed;
+ gboolean sent_dismissed;
+ guint animate_timeout;
+
+ gint timeout;
+ guint timeout_source_id;
+};
+
+enum {
+ DISMISSED,
+ LAST_SIGNAL
+};
+
+static guint notification_signals[LAST_SIGNAL] = { 0 };
+
+static gboolean gd_notification_draw (GtkWidget *widget,
+ cairo_t *cr);
+static void gd_notification_get_preferred_width (GtkWidget *widget,
+ gint *minimum_size,
+ gint *natural_size);
+static void gd_notification_get_preferred_height_for_width (GtkWidget *widget,
+ gint width,
+ gint *minimum_height,
+ gint *natural_height);
+static void gd_notification_get_preferred_height (GtkWidget *widget,
+ gint *minimum_size,
+ gint *natural_size);
+static void gd_notification_get_preferred_width_for_height (GtkWidget *widget,
+ gint height,
+ gint *minimum_width,
+ gint *natural_width);
+static void gd_notification_size_allocate (GtkWidget *widget,
+ GtkAllocation *allocation);
+static gboolean gd_notification_timeout_cb (gpointer user_data);
+static void gd_notification_show (GtkWidget *widget);
+static void gd_notification_add (GtkContainer *container,
+ GtkWidget *child);
+
+/* signals handlers */
+static void gd_notification_close_button_clicked_cb (GtkWidget *widget,
+ gpointer user_data);
+
+G_DEFINE_TYPE(GdNotification, gd_notification, GTK_TYPE_BIN);
+
+static void
+gd_notification_init (GdNotification *notification)
+{
+ GtkWidget *close_button_image;
+ GtkStyleContext *context;
+ GdNotificationPrivate *priv;
+
+ context = gtk_widget_get_style_context (GTK_WIDGET (notification));
+ gtk_style_context_add_class (context, GTK_STYLE_CLASS_FRAME);
+ gtk_style_context_add_class (context, "app-notification");
+
+ gtk_widget_set_halign (GTK_WIDGET (notification), GTK_ALIGN_CENTER);
+ gtk_widget_set_valign (GTK_WIDGET (notification), GTK_ALIGN_START);
+
+ gtk_widget_set_has_window (GTK_WIDGET (notification), TRUE);
+
+ priv = notification->priv =
+ G_TYPE_INSTANCE_GET_PRIVATE (notification,
+ GD_TYPE_NOTIFICATION,
+ GdNotificationPrivate);
+
+ priv->animate_y = 0;
+ priv->close_button = gtk_button_new ();
+ gtk_widget_set_parent (priv->close_button, GTK_WIDGET (notification));
+ gtk_widget_show (priv->close_button);
+ g_object_set (priv->close_button,
+ "relief", GTK_RELIEF_NONE,
+ "focus-on-click", FALSE,
+ NULL);
+ g_signal_connect (priv->close_button,
+ "clicked",
+ G_CALLBACK (gd_notification_close_button_clicked_cb),
+ notification);
+ close_button_image = gtk_image_new_from_icon_name ("window-close-symbolic", GTK_ICON_SIZE_BUTTON);
+ gtk_button_set_image (GTK_BUTTON (notification->priv->close_button), close_button_image);
+
+ priv->timeout_source_id = 0;
+}
+
+static void
+gd_notification_finalize (GObject *object)
+{
+ GdNotification *notification;
+ GdNotificationPrivate *priv;
+
+ g_return_if_fail (GTK_IS_NOTIFICATION (object));
+
+ notification = GD_NOTIFICATION (object);
+ priv = notification->priv;
+
+ if (priv->animate_timeout != 0)
+ g_source_remove (priv->animate_timeout);
+
+ if (priv->timeout_source_id != 0)
+ g_source_remove (priv->timeout_source_id);
+
+ G_OBJECT_CLASS (gd_notification_parent_class)->finalize (object);
+}
+
+static void
+gd_notification_destroy (GtkWidget *widget)
+{
+ GdNotification *notification = GD_NOTIFICATION (widget);
+ GdNotificationPrivate *priv = notification->priv;
+
+ if (!priv->sent_dismissed)
+ {
+ g_signal_emit (notification, notification_signals[DISMISSED], 0);
+ priv->sent_dismissed = TRUE;
+ }
+
+ if (priv->close_button)
+ {
+ gtk_widget_unparent (priv->close_button);
+ priv->close_button = NULL;
+ }
+
+ GTK_WIDGET_CLASS (gd_notification_parent_class)->destroy (widget);
+}
+
+static void
+gd_notification_realize (GtkWidget *widget)
+{
+ GdNotification *notification = GD_NOTIFICATION (widget);
+ GdNotificationPrivate *priv = notification->priv;
+ GtkBin *bin = GTK_BIN (widget);
+ GtkAllocation allocation;
+ GtkWidget *child;
+ GdkWindow *window;
+ GdkWindowAttr attributes;
+ gint attributes_mask;
+
+ gtk_widget_set_realized (widget, TRUE);
+
+ gtk_widget_get_allocation (widget, &allocation);
+
+ attributes.x = allocation.x;
+ attributes.y = allocation.y;
+ attributes.width = allocation.width;
+ attributes.height = allocation.height;
+ attributes.window_type = GDK_WINDOW_CHILD;
+ attributes.wclass = GDK_INPUT_OUTPUT;
+ attributes.visual = gtk_widget_get_visual (widget);
+
+ attributes.event_mask = GDK_VISIBILITY_NOTIFY_MASK | GDK_EXPOSURE_MASK;
+
+ attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL;
+
+ window = gdk_window_new (gtk_widget_get_parent_window (widget),
+ &attributes, attributes_mask);
+ gtk_widget_set_window (widget, window);
+ gtk_widget_register_window (widget, window);
+
+ attributes.x = 0;
+ attributes.y = attributes.height + priv->animate_y;
+ attributes.event_mask = gtk_widget_get_events (widget) |
+ GDK_EXPOSURE_MASK |
+ GDK_VISIBILITY_NOTIFY_MASK |
+ GDK_ENTER_NOTIFY_MASK |
+ GDK_LEAVE_NOTIFY_MASK;
+
+ priv->bin_window = gdk_window_new (window, &attributes, attributes_mask);
+ gtk_widget_register_window (widget, priv->bin_window);
+
+ child = gtk_bin_get_child (bin);
+ if (child)
+ gtk_widget_set_parent_window (child, priv->bin_window);
+ gtk_widget_set_parent_window (priv->close_button, priv->bin_window);
+
+ gdk_window_show (priv->bin_window);
+}
+
+static void
+gd_notification_unrealize (GtkWidget *widget)
+{
+ GdNotification *notification = GD_NOTIFICATION (widget);
+ GdNotificationPrivate *priv = notification->priv;
+
+ gtk_widget_unregister_window (widget, priv->bin_window);
+ gdk_window_destroy (priv->bin_window);
+ priv->bin_window = NULL;
+
+ GTK_WIDGET_CLASS (gd_notification_parent_class)->unrealize (widget);
+}
+
+static int
+animation_target (GdNotification *notification)
+{
+ GdNotificationPrivate *priv = notification->priv;
+ GtkAllocation allocation;
+
+ if (priv->revealed) {
+ gtk_widget_get_allocation (GTK_WIDGET (notification), &allocation);
+ return allocation.height;
+ } else {
+ return 0;
+ }
+}
+
+static gboolean
+animation_timeout_cb (gpointer user_data)
+{
+ GdNotification *notification = GD_NOTIFICATION (user_data);
+ GdNotificationPrivate *priv = notification->priv;
+ GtkAllocation allocation;
+ int target, delta;
+
+ target = animation_target (notification);
+
+ if (priv->animate_y != target) {
+ gtk_widget_get_allocation (GTK_WIDGET (notification), &allocation);
+
+ delta = allocation.height * ANIMATION_STEP / ANIMATION_TIME;
+
+ if (priv->revealed)
+ priv->animate_y += delta;
+ else
+ priv->animate_y -= delta;
+
+ priv->animate_y = CLAMP (priv->animate_y, 0, allocation.height);
+
+ if (priv->bin_window != NULL)
+ gdk_window_move (priv->bin_window,
+ 0,
+ -allocation.height + priv->animate_y);
+ return G_SOURCE_CONTINUE;
+ }
+
+ if (priv->dismissed && priv->animate_y == 0)
+ gtk_widget_destroy (GTK_WIDGET (notification));
+
+ priv->animate_timeout = 0;
+ return G_SOURCE_REMOVE;
+}
+
+static void
+start_animation (GdNotification *notification)
+{
+ GdNotificationPrivate *priv = notification->priv;
+ int target;
+
+ if (priv->animate_timeout != 0)
+ return; /* Already running */
+
+ target = animation_target (notification);
+ if (priv->animate_y != target)
+ notification->priv->animate_timeout =
+ gdk_threads_add_timeout (ANIMATION_STEP,
+ animation_timeout_cb,
+ notification);
+}
+
+static void
+gd_notification_show (GtkWidget *widget)
+{
+ GdNotification *notification = GD_NOTIFICATION (widget);
+ GdNotificationPrivate *priv = notification->priv;
+
+ GTK_WIDGET_CLASS (gd_notification_parent_class)->show (widget);
+ priv->revealed = TRUE;
+ priv->waiting_for_viewable = TRUE;
+}
+
+static void
+gd_notification_hide (GtkWidget *widget)
+{
+ GdNotification *notification = GD_NOTIFICATION (widget);
+ GdNotificationPrivate *priv = notification->priv;
+
+ GTK_WIDGET_CLASS (gd_notification_parent_class)->hide (widget);
+ priv->revealed = FALSE;
+ priv->waiting_for_viewable = FALSE;
+}
+
+static void
+gd_notification_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
+{
+ GdNotification *notification = GD_NOTIFICATION (object);
+
+ g_return_if_fail (GTK_IS_NOTIFICATION (object));
+
+ switch (prop_id) {
+ case PROP_TIMEOUT:
+ gd_notification_set_timeout (notification,
+ g_value_get_int (value));
+ break;
+ case PROP_SHOW_CLOSE_BUTTON:
+ gd_notification_set_show_close_button (notification,
+ g_value_get_boolean (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gd_notification_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
+{
+ g_return_if_fail (GTK_IS_NOTIFICATION (object));
+ GdNotification *notification = GD_NOTIFICATION (object);
+
+ switch (prop_id) {
+ case PROP_TIMEOUT:
+ g_value_set_int (value, notification->priv->timeout);
+ break;
+ case PROP_SHOW_CLOSE_BUTTON:
+ g_value_set_boolean (value,
+ notification->priv->show_close_button);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gd_notification_forall (GtkContainer *container,
+ gboolean include_internals,
+ GtkCallback callback,
+ gpointer callback_data)
+{
+ GtkBin *bin = GTK_BIN (container);
+ GdNotification *notification = GD_NOTIFICATION (container);
+ GdNotificationPrivate *priv = notification->priv;
+ GtkWidget *child;
+
+ child = gtk_bin_get_child (bin);
+ if (child)
+ (* callback) (child, callback_data);
+
+ if (include_internals)
+ (* callback) (priv->close_button, callback_data);
+}
+
+static void
+unqueue_autohide (GdNotification *notification)
+{
+ GdNotificationPrivate *priv = notification->priv;
+
+ if (priv->timeout_source_id)
+ {
+ g_source_remove (priv->timeout_source_id);
+ priv->timeout_source_id = 0;
+ }
+}
+
+static void
+queue_autohide (GdNotification *notification)
+{
+ GdNotificationPrivate *priv = notification->priv;
+
+ if (priv->timeout_source_id == 0 &&
+ priv->timeout != -1)
+ priv->timeout_source_id =
+ gdk_threads_add_timeout (priv->timeout * 1000,
+ gd_notification_timeout_cb,
+ notification);
+}
+
+static gboolean
+gd_notification_visibility_notify_event (GtkWidget *widget,
+ GdkEventVisibility *event)
+{
+ GdNotification *notification = GD_NOTIFICATION (widget);
+ GdNotificationPrivate *priv = notification->priv;
+
+ if (!gtk_widget_get_visible (widget))
+ return FALSE;
+
+ if (priv->waiting_for_viewable)
+ {
+ start_animation (notification);
+ priv->waiting_for_viewable = FALSE;
+ }
+
+ queue_autohide (notification);
+
+ return FALSE;
+}
+
+static gboolean
+gd_notification_enter_notify (GtkWidget *widget,
+ GdkEventCrossing *event)
+{
+ GdNotification *notification = GD_NOTIFICATION (widget);
+ GdNotificationPrivate *priv = notification->priv;
+
+ if ((event->window == priv->bin_window) &&
+ (event->detail != GDK_NOTIFY_INFERIOR))
+ {
+ unqueue_autohide (notification);
+ }
+
+ return FALSE;
+}
+
+static gboolean
+gd_notification_leave_notify (GtkWidget *widget,
+ GdkEventCrossing *event)
+{
+ GdNotification *notification = GD_NOTIFICATION (widget);
+ GdNotificationPrivate *priv = notification->priv;
+
+ if ((event->window == priv->bin_window) &&
+ (event->detail != GDK_NOTIFY_INFERIOR))
+ {
+ queue_autohide (notification);
+ }
+
+ return FALSE;
+}
+
+static void
+gd_notification_class_init (GdNotificationClass *klass)
+{
+ GObjectClass* object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass* widget_class = GTK_WIDGET_CLASS(klass);
+ GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);
+
+ object_class->finalize = gd_notification_finalize;
+ object_class->set_property = gd_notification_set_property;
+ object_class->get_property = gd_notification_get_property;
+
+ widget_class->show = gd_notification_show;
+ widget_class->hide = gd_notification_hide;
+ widget_class->destroy = gd_notification_destroy;
+ widget_class->get_preferred_width = gd_notification_get_preferred_width;
+ widget_class->get_preferred_height_for_width = gd_notification_get_preferred_height_for_width;
+ widget_class->get_preferred_height = gd_notification_get_preferred_height;
+ widget_class->get_preferred_width_for_height = gd_notification_get_preferred_width_for_height;
+ widget_class->size_allocate = gd_notification_size_allocate;
+ widget_class->draw = gd_notification_draw;
+ widget_class->realize = gd_notification_realize;
+ widget_class->unrealize = gd_notification_unrealize;
+ widget_class->visibility_notify_event = gd_notification_visibility_notify_event;
+ widget_class->enter_notify_event = gd_notification_enter_notify;
+ widget_class->leave_notify_event = gd_notification_leave_notify;
+
+ container_class->add = gd_notification_add;
+ container_class->forall = gd_notification_forall;
+ gtk_container_class_handle_border_width (container_class);
+
+
+ /**
+ * GdNotification:timeout:
+ *
+ * The time it takes to hide the widget, in seconds.
+ *
+ * Since: 0.1
+ */
+ g_object_class_install_property (object_class,
+ PROP_TIMEOUT,
+ g_param_spec_int("timeout", "timeout",
+ "The time it takes to hide the widget, in seconds",
+ -1, G_MAXINT, -1,
+ GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+ g_object_class_install_property (object_class,
+ PROP_SHOW_CLOSE_BUTTON,
+ g_param_spec_boolean("show-close-button", "show-close-button",
+ "Whether to show a stock close button that dismisses the notification",
+ TRUE,
+ GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+
+ notification_signals[DISMISSED] = g_signal_new ("dismissed",
+ G_OBJECT_CLASS_TYPE (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GdNotificationClass, dismissed),
+ NULL,
+ NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE,
+ 0);
+
+ g_type_class_add_private (object_class, sizeof (GdNotificationPrivate));
+}
+
+static void
+get_padding_and_border (GdNotification *notification,
+ GtkBorder *border)
+{
+ GtkStyleContext *context;
+ GtkStateFlags state;
+ GtkBorder tmp;
+
+ context = gtk_widget_get_style_context (GTK_WIDGET (notification));
+ state = gtk_widget_get_state_flags (GTK_WIDGET (notification));
+
+ gtk_style_context_get_padding (context, state, border);
+
+ gtk_style_context_get_border (context, state, &tmp);
+ border->top += tmp.top;
+ border->right += tmp.right;
+ border->bottom += tmp.bottom;
+ border->left += tmp.left;
+}
+
+static gboolean
+gd_notification_draw (GtkWidget *widget, cairo_t *cr)
+{
+ GdNotification *notification = GD_NOTIFICATION (widget);
+ GdNotificationPrivate *priv = notification->priv;
+ GtkStyleContext *context;
+
+ if (gtk_cairo_should_draw_window (cr, priv->bin_window))
+ {
+ context = gtk_widget_get_style_context (widget);
+
+ gtk_render_background (context, cr,
+ 0, 0,
+ gtk_widget_get_allocated_width (widget),
+ gtk_widget_get_allocated_height (widget));
+ gtk_render_frame (context,cr,
+ 0, 0,
+ gtk_widget_get_allocated_width (widget),
+ gtk_widget_get_allocated_height (widget));
+
+
+ if (GTK_WIDGET_CLASS (gd_notification_parent_class)->draw)
+ GTK_WIDGET_CLASS (gd_notification_parent_class)->draw(widget, cr);
+ }
+
+ return FALSE;
+}
+
+static void
+gd_notification_add (GtkContainer *container,
+ GtkWidget *child)
+{
+ GtkBin *bin = GTK_BIN (container);
+ GdNotification *notification = GD_NOTIFICATION (bin);
+ GdNotificationPrivate *priv = notification->priv;
+
+ g_return_if_fail (gtk_bin_get_child (bin) == NULL);
+
+ gtk_widget_set_parent_window (child, priv->bin_window);
+
+ GTK_CONTAINER_CLASS (gd_notification_parent_class)->add (container, child);
+}
+
+
+static void
+gd_notification_get_preferred_width (GtkWidget *widget, gint *minimum_size, gint *natural_size)
+{
+ GdNotification *notification = GD_NOTIFICATION (widget);
+ GdNotificationPrivate *priv = notification->priv;
+ GtkBin *bin = GTK_BIN (widget);
+ gint child_min, child_nat;
+ GtkWidget *child;
+ GtkBorder padding;
+ gint minimum, natural;
+
+ get_padding_and_border (notification, &padding);
+
+ minimum = 0;
+ natural = 0;
+
+ child = gtk_bin_get_child (bin);
+ if (child && gtk_widget_get_visible (child))
+ {
+ gtk_widget_get_preferred_width (child,
+ &child_min, &child_nat);
+ minimum += child_min;
+ natural += child_nat;
+ }
+
+ if (priv->show_close_button)
+ {
+ gtk_widget_get_preferred_width (priv->close_button,
+ &child_min, &child_nat);
+ minimum += child_min;
+ natural += child_nat;
+ }
+
+ minimum += padding.left + padding.right + 2 * SHADOW_OFFSET_X;
+ natural += padding.left + padding.right + 2 * SHADOW_OFFSET_X;
+
+ if (minimum_size)
+ *minimum_size = minimum;
+
+ if (natural_size)
+ *natural_size = natural;
+}
+
+static void
+gd_notification_get_preferred_width_for_height (GtkWidget *widget,
+ gint height,
+ gint *minimum_width,
+ gint *natural_width)
+{
+ GdNotification *notification = GD_NOTIFICATION (widget);
+ GdNotificationPrivate *priv = notification->priv;
+ GtkBin *bin = GTK_BIN (widget);
+ gint child_min, child_nat, child_height;
+ GtkWidget *child;
+ GtkBorder padding;
+ gint minimum, natural;
+
+ get_padding_and_border (notification, &padding);
+
+ minimum = 0;
+ natural = 0;
+
+ child_height = height - SHADOW_OFFSET_Y - padding.top - padding.bottom;
+
+ child = gtk_bin_get_child (bin);
+ if (child && gtk_widget_get_visible (child))
+ {
+ gtk_widget_get_preferred_width_for_height (child, child_height,
+ &child_min, &child_nat);
+ minimum += child_min;
+ natural += child_nat;
+ }
+
+ if (priv->show_close_button)
+ {
+ gtk_widget_get_preferred_width_for_height (priv->close_button, child_height,
+ &child_min, &child_nat);
+ minimum += child_min;
+ natural += child_nat;
+ }
+
+ minimum += padding.left + padding.right + 2 * SHADOW_OFFSET_X;
+ natural += padding.left + padding.right + 2 * SHADOW_OFFSET_X;
+
+ if (minimum_width)
+ *minimum_width = minimum;
+
+ if (natural_width)
+ *natural_width = natural;
+}
+
+static void
+gd_notification_get_preferred_height_for_width (GtkWidget *widget,
+ gint width,
+ gint *minimum_height,
+ gint *natural_height)
+{
+ GdNotification *notification = GD_NOTIFICATION (widget);
+ GdNotificationPrivate *priv = notification->priv;
+ GtkBin *bin = GTK_BIN (widget);
+ gint child_min, child_nat, child_width, button_width = 0;
+ GtkWidget *child;
+ GtkBorder padding;
+ gint minimum = 0, natural = 0;
+
+ get_padding_and_border (notification, &padding);
+
+ if (priv->show_close_button)
+ {
+ gtk_widget_get_preferred_height (priv->close_button,
+ &minimum, &natural);
+ gtk_widget_get_preferred_width (priv->close_button,
+ NULL, &button_width);
+ }
+
+ child = gtk_bin_get_child (bin);
+ if (child && gtk_widget_get_visible (child))
+ {
+ child_width = width - button_width -
+ 2 * SHADOW_OFFSET_X - padding.left - padding.right;
+
+ gtk_widget_get_preferred_height_for_width (child, child_width,
+ &child_min, &child_nat);
+ minimum = MAX (minimum, child_min);
+ natural = MAX (natural, child_nat);
+ }
+
+ minimum += padding.top + padding.bottom + SHADOW_OFFSET_Y;
+ natural += padding.top + padding.bottom + SHADOW_OFFSET_Y;
+
+ if (minimum_height)
+ *minimum_height = minimum;
+
+ if (natural_height)
+ *natural_height = natural;
+}
+
+static void
+gd_notification_get_preferred_height (GtkWidget *widget,
+ gint *minimum_height,
+ gint *natural_height)
+{
+ gint width;
+
+ gd_notification_get_preferred_width (widget, &width, NULL);
+ gd_notification_get_preferred_height_for_width (widget, width,
+ minimum_height, natural_height);
+}
+
+static void
+gd_notification_size_allocate (GtkWidget *widget,
+ GtkAllocation *allocation)
+{
+ GdNotification *notification = GD_NOTIFICATION (widget);
+ GdNotificationPrivate *priv = notification->priv;
+ GtkBin *bin = GTK_BIN (widget);
+ GtkAllocation child_allocation;
+ GtkBorder padding;
+ GtkRequisition button_req;
+ GtkWidget *child;
+
+ gtk_widget_set_allocation (widget, allocation);
+
+ /* If somehow the notification changes while not hidden
+ and we're not animating, immediately follow the resize */
+ if (priv->animate_y > 0 &&
+ !priv->animate_timeout)
+ priv->animate_y = allocation->height;
+
+ get_padding_and_border (notification, &padding);
+
+ if (gtk_widget_get_realized (widget))
+ {
+ gdk_window_move_resize (gtk_widget_get_window (widget),
+ allocation->x,
+ allocation->y,
+ allocation->width,
+ allocation->height);
+ gdk_window_move_resize (priv->bin_window,
+ 0,
+ -allocation->height + priv->animate_y,
+ allocation->width,
+ allocation->height);
+ }
+
+ child_allocation.x = SHADOW_OFFSET_X + padding.left;
+ child_allocation.y = padding.top;
+
+ if (priv->show_close_button)
+ gtk_widget_get_preferred_size (priv->close_button, &button_req, NULL);
+ else
+ button_req.width = button_req.height = 0;
+
+ child_allocation.height = MAX (1, allocation->height - SHADOW_OFFSET_Y - padding.top - padding.bottom);
+ child_allocation.width = MAX (1, (allocation->width - button_req.width -
+ 2 * SHADOW_OFFSET_X - padding.left - padding.right));
+
+ child = gtk_bin_get_child (bin);
+ if (child && gtk_widget_get_visible (child))
+ gtk_widget_size_allocate (child, &child_allocation);
+
+ if (priv->show_close_button)
+ {
+ child_allocation.x += child_allocation.width;
+ child_allocation.width = button_req.width;
+ child_allocation.y += (child_allocation.height - button_req.height) / 2;
+ child_allocation.height = button_req.height;
+
+ gtk_widget_size_allocate (priv->close_button, &child_allocation);
+ }
+}
+
+static gboolean
+gd_notification_timeout_cb (gpointer user_data)
+{
+ GdNotification *notification = GD_NOTIFICATION (user_data);
+
+ gd_notification_dismiss (notification);
+
+ return G_SOURCE_REMOVE;
+}
+
+void
+gd_notification_set_timeout (GdNotification *notification,
+ gint timeout_sec)
+{
+ GdNotificationPrivate *priv = notification->priv;
+
+ priv->timeout = timeout_sec;
+ g_object_notify (G_OBJECT (notification), "timeout");
+}
+
+void
+gd_notification_set_show_close_button (GdNotification *notification,
+ gboolean show_close_button)
+{
+ GdNotificationPrivate *priv = notification->priv;
+
+ priv->show_close_button = show_close_button;
+
+ gtk_widget_set_visible (priv->close_button, show_close_button);
+ gtk_widget_queue_resize (GTK_WIDGET (notification));
+}
+
+void
+gd_notification_dismiss (GdNotification *notification)
+{
+ GdNotificationPrivate *priv = notification->priv;
+
+ unqueue_autohide (notification);
+
+ priv->dismissed = TRUE;
+ priv->revealed = FALSE;
+ start_animation (notification);
+}
+
+static void
+gd_notification_close_button_clicked_cb (GtkWidget *widget, gpointer user_data)
+{
+ GdNotification *notification = GD_NOTIFICATION(user_data);
+
+ gd_notification_dismiss (notification);
+}
+
+GtkWidget *
+gd_notification_new (void)
+{
+ return g_object_new (GD_TYPE_NOTIFICATION, NULL);
+}
diff --git a/libraries/libgd/gd-notification.h b/libraries/libgd/gd-notification.h
new file mode 100644
index 00000000..cfd11e08
--- /dev/null
+++ b/libraries/libgd/gd-notification.h
@@ -0,0 +1,67 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ * gd-notification
+ * Based on gtk-notification from gnome-contacts:
+ * http://git.gnome.org/browse/gnome-contacts/tree/src/gtk-notification.c?id=3.3.91
+ *
+ * Copyright (C) Erick Pérez Castellanos 2011 <erick.red@gmail.com>
+ * Copyright (C) 2012 Red Hat, Inc.
+ *
+ * This program 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 program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.";
+ */
+
+#ifndef _GD_NOTIFICATION_H_
+#define _GD_NOTIFICATION_H_
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define GD_TYPE_NOTIFICATION (gd_notification_get_type ())
+#define GD_NOTIFICATION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GD_TYPE_NOTIFICATION, GdNotification))
+#define GD_NOTIFICATION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GD_TYPE_NOTIFICATION, GdNotificationClass))
+#define GTK_IS_NOTIFICATION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GD_TYPE_NOTIFICATION))
+#define GTK_IS_NOTIFICATION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GD_TYPE_NOTIFICATION))
+#define GD_NOTIFICATION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GD_TYPE_NOTIFICATION, GdNotificationClass))
+
+typedef struct _GdNotificationPrivate GdNotificationPrivate;
+typedef struct _GdNotificationClass GdNotificationClass;
+typedef struct _GdNotification GdNotification;
+
+struct _GdNotificationClass {
+ GtkBinClass parent_class;
+
+ /* Signals */
+ void (*dismissed) (GdNotification *self);
+};
+
+struct _GdNotification {
+ GtkBin parent_instance;
+
+ /*< private > */
+ GdNotificationPrivate *priv;
+};
+
+GType gd_notification_get_type (void) G_GNUC_CONST;
+
+GtkWidget *gd_notification_new (void);
+void gd_notification_set_timeout (GdNotification *notification,
+ gint timeout_sec);
+void gd_notification_dismiss (GdNotification *notification);
+void gd_notification_set_show_close_button (GdNotification *notification,
+ gboolean show_close_button);
+
+G_END_DECLS
+
+#endif /* _GD_NOTIFICATION_H_ */
diff --git a/libraries/libgd/gd-types-catalog.c b/libraries/libgd/gd-types-catalog.c
new file mode 100644
index 00000000..3a6c72e2
--- /dev/null
+++ b/libraries/libgd/gd-types-catalog.c
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2012 Red Hat, Inc.
+ *
+ * This program 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 program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+# include "gd-notification.h"
+
+
+/**
+ * gd_ensure_types:
+ *
+ * This functions must be called during initialization
+ * to make sure the widget types are available to GtkBuilder.
+ */
+void
+gd_ensure_types (void)
+{
+
+}
+
diff --git a/libraries/libgd/gd-types-catalog.h b/libraries/libgd/gd-types-catalog.h
new file mode 100644
index 00000000..fc99416b
--- /dev/null
+++ b/libraries/libgd/gd-types-catalog.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2012 Red Hat, Inc.
+ *
+ * This program 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 program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifndef __GD_TYPES_CATALOG_H__
+#define __GD_TYPES_CATALOG_H__
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+void gd_ensure_types (void);
+
+G_END_DECLS
+
+#endif /* __GD_TYPES_CATALOG_H__ */
diff --git a/libraries/libgd/gd.h b/libraries/libgd/gd.h
new file mode 100644
index 00000000..27da22fc
--- /dev/null
+++ b/libraries/libgd/gd.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2012 Red Hat, Inc.
+ *
+ * This program 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 program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifndef __GD_H__
+#define __GD_H__
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#include "gd-types-catalog.h"
+# include "gd-notification.h"
+
+G_END_DECLS
+
+#endif /* __GD_H__ */
diff --git a/libraries/libgd/meson.build b/libraries/libgd/meson.build
new file mode 100644
index 00000000..6832514b
--- /dev/null
+++ b/libraries/libgd/meson.build
@@ -0,0 +1,12 @@
+gd_inc = include_directories('..')
+gd_lib = static_library(
+ 'gd',
+ [
+ 'gd-notification.c',
+ 'gd-types-catalog.c'
+ ],
+ dependencies: [
+ glib,
+ gtk
+ ]
+)
diff --git a/libraries/libgtkimageview/gtkimageview.c b/libraries/libgtkimageview/gtkimageview.c
new file mode 100644
index 00000000..30ab7337
--- /dev/null
+++ b/libraries/libgtkimageview/gtkimageview.c
@@ -0,0 +1,2530 @@
+/* Copyright 2016 Timm Bäder
+ *
+ * GTK+ 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.
+ *
+ * GLib 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 GTK+; see the file COPYING. If not,
+ * see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * SECTION:gtkimageview
+ * @Short_description: A widget for displaying content images to users
+ * @Title: GtkImageView
+ *
+ * #GtkImageView is a widget intended to be used to display "content images"
+ * to users. What we refer to as "content images" in the documentation could
+ * be characterized as "images the user is deeply interested in". You should
+ * use #GtkImageView whenever you want to actually present an image instead
+ * of just using an icon.
+ *
+ * Contrary to #GtkImage, #GtkImageView does not just display an image with
+ * a fixed size, but provides ways of rotating and scaling it, as well as
+ * built-in gestures (via #GtkGestureRotate and #GtkGestureZoom) to rotate
+ * and zoom the image.
+ *
+ *
+ * # Scale factor handling
+ *
+ * All the functions intended to set the image of a #GtkImageView instance take a
+ * "scale_factor" parameter (except for gtk_image_view_set_surface(), in which case
+ * the scale factor of the surface is taken instead). This scale factor can be interpreted
+ * the same as the #GtkWidget:scale-factor property of #GtkWidget, but for the given image.
+ *
+ */
+
+#include "gtkimageview.h"
+#include <gtk/gtk.h>
+#include <math.h>
+
+#define DEG_TO_RAD(x) (((x) / 360.0) * (2 * M_PI))
+#define RAD_TO_DEG(x) (((x) / (2.0 * M_PI) * 360.0))
+
+#define TRANSITION_DURATION (150.0 * 1000.0)
+#define ANGLE_TRANSITION_MIN_DELTA (1.0)
+#define SCALE_TRANSITION_MIN_DELTA (0.01)
+
+
+#define _PARAM_READABLE G_PARAM_READABLE|G_PARAM_STATIC_NAME|G_PARAM_STATIC_NICK|G_PARAM_STATIC_BLURB
+#define _PARAM_READWRITE G_PARAM_READWRITE|G_PARAM_STATIC_NAME|G_PARAM_STATIC_NICK|G_PARAM_STATIC_BLURB
+
+
+typedef struct
+{
+ double hupper;
+ double vupper;
+ double hvalue;
+ double vvalue;
+ double angle;
+ double scale;
+} State;
+
+struct _GtkImageViewPrivate
+{
+ double scale;
+ double angle;
+ int scale_factor;
+
+ gboolean fit_allocation : 1;
+ gboolean scale_set : 1;
+ gboolean snap_angle : 1;
+ gboolean rotatable : 1;
+ gboolean zoomable : 1;
+ gboolean in_rotate : 1;
+ gboolean in_zoom : 1;
+ gboolean size_valid : 1;
+ gboolean transitions_enabled : 1;
+ gboolean in_angle_transition : 1;
+ gboolean in_scale_transition : 1;
+
+ GtkGesture *rotate_gesture;
+ double gesture_start_angle;
+ double visible_angle;
+
+ GtkGesture *zoom_gesture;
+ double gesture_start_scale;
+ double visible_scale;
+
+ /* Current anchor point, or -1/-1.
+ * In widget coordinates. */
+ double anchor_x;
+ double anchor_y;
+
+
+ GdkWindow *event_window;
+
+ /* GtkScrollable stuff */
+ GtkAdjustment *hadjustment;
+ GtkAdjustment *vadjustment;
+ GtkScrollablePolicy hscroll_policy : 1;
+ GtkScrollablePolicy vscroll_policy : 1;
+
+ gboolean is_animation;
+ GdkPixbufAnimation *source_animation;
+ GdkPixbufAnimationIter *source_animation_iter;
+ cairo_surface_t *image_surface;
+ int animation_timeout;
+
+ /* Transitions */
+ double transition_start_angle;
+ gint64 angle_transition_start;
+ guint angle_transition_id;
+
+ double transition_start_scale;
+ gint64 scale_transition_start;
+ guint scale_transition_id;
+
+ /* We cache the bounding box size so we don't have to
+ * recompute it at every draw() */
+ double cached_width;
+ double cached_height;
+ double cached_scale;
+};
+
+enum
+{
+ PROP_SCALE = 1,
+ PROP_SCALE_SET,
+ PROP_ANGLE,
+ PROP_ROTATABLE,
+ PROP_ZOOMABLE,
+ PROP_SNAP_ANGLE,
+ PROP_FIT_ALLOCATION,
+ PROP_TRANSITIONS_ENABLED,
+
+ LAST_WIDGET_PROPERTY,
+ PROP_HADJUSTMENT,
+ PROP_VADJUSTMENT,
+ PROP_HSCROLL_POLICY,
+ PROP_VSCROLL_POLICY,
+
+ LAST_PROPERTY
+};
+
+static GParamSpec *widget_props[LAST_WIDGET_PROPERTY] = { NULL, };
+
+
+G_DEFINE_TYPE_WITH_CODE (GtkImageView, gtk_image_view, GTK_TYPE_WIDGET,
+ G_ADD_PRIVATE (GtkImageView)
+ G_IMPLEMENT_INTERFACE (GTK_TYPE_SCROLLABLE, NULL))
+
+typedef struct _LoadTaskData LoadTaskData;
+
+struct _LoadTaskData
+{
+ int scale_factor;
+ gpointer source;
+};
+
+
+static void gtk_image_view_update_surface (GtkImageView *image_view,
+ const GdkPixbuf *frame,
+ int scale_factor);
+
+static void adjustment_value_changed_cb (GtkAdjustment *adjustment,
+ gpointer user_data);
+
+static void gtk_image_view_update_adjustments (GtkImageView *image_view);
+
+static void gtk_image_view_compute_bounding_box (GtkImageView *image_view,
+ double *width,
+ double *height,
+ double *scale_out);
+static void gtk_image_view_ensure_gestures (GtkImageView *image_view);
+
+static inline void gtk_image_view_restrict_adjustment (GtkAdjustment *adjustment);
+static void gtk_image_view_fix_anchor (GtkImageView *image_view,
+ double anchor_x,
+ double anchor_y,
+ State *old_state);
+
+
+static inline double
+gtk_image_view_get_real_scale (GtkImageView *image_view)
+{
+ GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
+
+ if (priv->in_zoom || priv->in_scale_transition)
+ return priv->visible_scale;
+ else
+ return priv->scale;
+}
+
+static inline double
+gtk_image_view_get_real_angle (GtkImageView *image_view)
+{
+ GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
+
+ if (priv->in_rotate || priv->in_angle_transition)
+ return priv->visible_angle;
+ else
+ return priv->angle;
+}
+
+static inline double
+gtk_image_view_clamp_angle (double angle)
+{
+ double new_angle = angle;
+
+ if (angle > 360.0)
+ new_angle -= (int)(angle / 360.0) * 360;
+ else if (angle < 0.0)
+ new_angle += 360 - ((int)(angle /360) * 360.0);
+
+ g_assert (new_angle >= 0.0);
+ g_assert (new_angle <= 360.0);
+
+ return new_angle;
+}
+
+static inline int
+gtk_image_view_get_snapped_angle (double angle)
+{
+ return (int) ((angle + 45.0) / 90.0) * 90;
+}
+
+static void
+gtk_image_view_get_current_state (GtkImageView *image_view,
+ State *state)
+{
+ GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
+
+ if (priv->hadjustment != NULL && priv->vadjustment != NULL)
+ {
+ state->hvalue = gtk_adjustment_get_value (priv->hadjustment);
+ state->vvalue = gtk_adjustment_get_value (priv->vadjustment);
+ state->hupper = gtk_adjustment_get_upper (priv->hadjustment);
+ state->vupper = gtk_adjustment_get_upper (priv->vadjustment);
+ }
+ state->angle = gtk_image_view_get_real_angle (image_view);
+ state->scale = gtk_image_view_get_real_scale (image_view);
+}
+
+static gboolean
+gtk_image_view_transitions_enabled (GtkImageView *image_view)
+{
+ GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
+ gboolean animations_enabled;
+
+ g_object_get (gtk_widget_get_settings (GTK_WIDGET (image_view)),
+ "gtk-enable-animations", &animations_enabled,
+ NULL);
+
+ return priv->transitions_enabled && animations_enabled && priv->image_surface;
+}
+
+static gboolean
+scale_frameclock_cb (GtkWidget *widget,
+ GdkFrameClock *frame_clock,
+ gpointer user_data)
+{
+ GtkImageView *image_view = GTK_IMAGE_VIEW (widget);
+ GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
+ State state;
+ gint64 now = gdk_frame_clock_get_frame_time (frame_clock);
+
+ double t = (now - priv->scale_transition_start) / TRANSITION_DURATION;
+ double new_scale = (priv->scale - priv->transition_start_scale) * t;
+
+ gtk_image_view_get_current_state (image_view, &state);
+
+ priv->visible_scale = priv->transition_start_scale + new_scale;
+ priv->size_valid = FALSE;
+
+ if (t >= 1.0)
+ priv->in_scale_transition = FALSE;
+
+ if (priv->hadjustment && priv->vadjustment)
+ {
+ GtkAllocation allocation;
+ gtk_widget_get_allocation (widget, &allocation);
+ gtk_image_view_update_adjustments (image_view);
+
+ gtk_image_view_fix_anchor (image_view,
+ allocation.width / 2,
+ allocation.height / 2,
+ &state);
+ }
+
+ if (priv->fit_allocation)
+ gtk_widget_queue_draw (widget);
+ else
+ gtk_widget_queue_resize (widget);
+
+ if (t >= 1.0)
+ {
+ priv->scale_transition_id = 0;
+ return G_SOURCE_REMOVE;
+ }
+
+ return G_SOURCE_CONTINUE;
+}
+
+static void
+gtk_image_view_animate_to_scale (GtkImageView *image_view)
+{
+ GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
+
+ if (priv->scale_transition_id != 0)
+ gtk_widget_remove_tick_callback (GTK_WIDGET (image_view), priv->scale_transition_id);
+
+ /* Target scale is priv->scale */
+ priv->in_scale_transition = TRUE;
+ priv->visible_scale = priv->scale;
+ priv->transition_start_scale = priv->scale;
+ priv->scale_transition_start = gdk_frame_clock_get_frame_time (gtk_widget_get_frame_clock (GTK_WIDGET (image_view)));
+
+ priv->scale_transition_id = gtk_widget_add_tick_callback (GTK_WIDGET (image_view),
+ scale_frameclock_cb,
+ NULL, NULL);
+}
+
+static gboolean
+angle_frameclock_cb (GtkWidget *widget,
+ GdkFrameClock *frame_clock,
+ gpointer user_data)
+{
+ GtkImageView *image_view = GTK_IMAGE_VIEW (widget);
+ GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
+ int direction = GPOINTER_TO_INT (user_data);
+ gint64 now = gdk_frame_clock_get_frame_time (frame_clock);
+ State state;
+ double target_angle = priv->angle;
+
+ if (direction == 1 && target_angle < priv->transition_start_angle)
+ target_angle += 360.0;
+ else if (direction == 0 && target_angle > priv->transition_start_angle)
+ target_angle -= 360.0;
+
+ double t = (now - priv->angle_transition_start) / TRANSITION_DURATION;
+ double new_angle = (target_angle - priv->transition_start_angle) * t;
+
+ gtk_image_view_get_current_state (image_view, &state);
+
+ priv->visible_angle = priv->transition_start_angle + new_angle;
+ priv->size_valid = FALSE;
+
+ if (t >= 1.0)
+ priv->in_angle_transition = FALSE;
+
+ if (priv->hadjustment && priv->vadjustment)
+ {
+ GtkAllocation allocation;
+ gtk_widget_get_allocation (widget, &allocation);
+ gtk_image_view_update_adjustments (image_view);
+
+ gtk_image_view_fix_anchor (image_view,
+ allocation.width / 2,
+ allocation.height / 2,
+ &state);
+ }
+
+ if (priv->fit_allocation)
+ gtk_widget_queue_draw (widget);
+ else
+ gtk_widget_queue_resize (widget);
+
+ if (t >= 1.0)
+ {
+ priv->angle_transition_id = 0;
+ return G_SOURCE_REMOVE;
+ }
+
+ return G_SOURCE_CONTINUE;
+}
+
+static void
+gtk_image_view_animate_to_angle (GtkImageView *image_view,
+ int direction)
+{
+ GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
+
+ if (priv->angle_transition_id != 0)
+ {
+ gtk_widget_remove_tick_callback (GTK_WIDGET (image_view), priv->angle_transition_id);
+ priv->angle_transition_id = 0;
+ }
+
+ /* Target angle is priv->angle */
+ priv->in_angle_transition = TRUE;
+ priv->visible_angle = priv->angle;
+ priv->transition_start_angle = priv->angle;
+ priv->angle_transition_start = gdk_frame_clock_get_frame_time (gtk_widget_get_frame_clock (GTK_WIDGET (image_view)));
+
+ priv->angle_transition_id = gtk_widget_add_tick_callback (GTK_WIDGET (image_view),
+ angle_frameclock_cb,
+ GINT_TO_POINTER (direction),
+ NULL);
+}
+
+static void
+gtk_image_view_do_snapping (GtkImageView *image_view)
+{
+ GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
+ double new_angle = gtk_image_view_get_snapped_angle (priv->angle);
+
+ g_assert (priv->snap_angle);
+
+ if (gtk_image_view_transitions_enabled (image_view))
+ gtk_image_view_animate_to_angle (image_view, new_angle > priv->angle);
+
+ priv->angle = new_angle;
+
+ /* Don't notify! */
+}
+
+static void
+free_load_task_data (LoadTaskData *data)
+{
+ g_clear_object (&data->source);
+ g_slice_free (LoadTaskData, data);
+}
+
+static void
+to_rotate_coords (GtkImageView *image_view,
+ State *state,
+ double in_x, double in_y,
+ double *out_x, double *out_y)
+{
+ double cx = state->hupper / 2.0 - state->hvalue;
+ double cy = state->vupper / 2.0 - state->vvalue;
+
+ *out_x = in_x - cx;
+ *out_y = in_y - cy;
+}
+
+static void
+gtk_image_view_fix_anchor (GtkImageView *image_view,
+ double anchor_x,
+ double anchor_y,
+ State *old_state)
+{
+ GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
+ double hupper_delta = gtk_adjustment_get_upper (priv->hadjustment)
+ - old_state->hupper;
+ double vupper_delta = gtk_adjustment_get_upper (priv->vadjustment)
+ - old_state->vupper;
+ double hupper_delta_scale, vupper_delta_scale;
+ double hupper_delta_angle, vupper_delta_angle;
+ double cur_scale = gtk_image_view_get_real_scale (image_view);
+
+ g_assert (old_state->hupper > 0);
+ g_assert (old_state->vupper > 0);
+ g_assert (priv->hadjustment);
+ g_assert (priv->vadjustment);
+ g_assert (priv->size_valid);
+ g_assert (anchor_x >= 0);
+ g_assert (anchor_y >= 0);
+ g_assert (anchor_x < gtk_widget_get_allocated_width (GTK_WIDGET (image_view)));
+ g_assert (anchor_y < gtk_widget_get_allocated_height (GTK_WIDGET (image_view)));
+
+ /* Amount of upper change caused by scale */
+ hupper_delta_scale = ((old_state->hupper / old_state->scale) * cur_scale)
+ - old_state->hupper;
+ vupper_delta_scale = ((old_state->vupper / old_state->scale) * cur_scale)
+ - old_state->vupper;
+
+ /* Amount of upper change caused by angle */
+ hupper_delta_angle = hupper_delta - hupper_delta_scale;
+ vupper_delta_angle = vupper_delta - vupper_delta_scale;
+
+ /* As a first step, fix the anchor point with regard to the
+ * updated scale
+ */
+ {
+ double hvalue = gtk_adjustment_get_value (priv->hadjustment);
+ double vvalue = gtk_adjustment_get_value (priv->vadjustment);
+
+ double px = anchor_x + hvalue;
+ double py = anchor_y + vvalue;
+
+ double px_after = (px / old_state->scale) * cur_scale;
+ double py_after = (py / old_state->scale) * cur_scale;
+
+ gtk_adjustment_set_value (priv->hadjustment,
+ hvalue + px_after - px);
+ gtk_adjustment_set_value (priv->vadjustment,
+ vvalue + py_after - py);
+ }
+
+ gtk_adjustment_set_value (priv->hadjustment,
+ gtk_adjustment_get_value (priv->hadjustment) + hupper_delta_angle / 2.0);
+
+ gtk_adjustment_set_value (priv->vadjustment,
+ gtk_adjustment_get_value (priv->vadjustment) + vupper_delta_angle / 2.0);
+
+ {
+ double rotate_anchor_x = 0;
+ double rotate_anchor_y = 0;
+ double anchor_angle;
+ double anchor_length;
+ double new_anchor_x, new_anchor_y;
+ double delta_x, delta_y;
+
+ /* Calculate the angle of the given anchor point relative to the
+ * bounding box center and the OLD state */
+ to_rotate_coords (image_view, old_state,
+ anchor_x, anchor_y,
+ &rotate_anchor_x, &rotate_anchor_y);
+ anchor_angle = atan2 (rotate_anchor_y, rotate_anchor_x);
+ anchor_length = sqrt ((rotate_anchor_x * rotate_anchor_x) +
+ (rotate_anchor_y * rotate_anchor_y));
+
+ /* The angle of the anchor point NOW is the old angle plus
+ * the difference between old surface angle and new surface angle */
+ anchor_angle += DEG_TO_RAD (gtk_image_view_get_real_angle (image_view)
+ - old_state->angle);
+
+ /* Calculate the position of the new anchor point, relative
+ * to the bounding box center */
+ new_anchor_x = cos (anchor_angle) * anchor_length;
+ new_anchor_y = sin (anchor_angle) * anchor_length;
+
+ /* The difference between old anchor and new anchor
+ * is what we care about... */
+ delta_x = rotate_anchor_x - new_anchor_x;
+ delta_y = rotate_anchor_y - new_anchor_y;
+
+ /* At last, make the old anchor match the new anchor */
+ gtk_adjustment_set_value (priv->hadjustment,
+ gtk_adjustment_get_value (priv->hadjustment) - delta_x);
+ gtk_adjustment_set_value (priv->vadjustment,
+ gtk_adjustment_get_value (priv->vadjustment) - delta_y);
+
+ }
+
+ gtk_widget_queue_draw (GTK_WIDGET (image_view));
+}
+
+static void
+gtk_image_view_compute_bounding_box (GtkImageView *image_view,
+ double *width,
+ double *height,
+ double *scale_out)
+{
+ GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
+ GtkAllocation alloc;
+ double image_width;
+ double image_height;
+ double bb_width = 0;
+ double bb_height = 0;
+ double upper_right_degrees;
+ double upper_left_degrees;
+ double r;
+ double upper_right_x, upper_right_y;
+ double upper_left_x, upper_left_y;
+ double scale;
+ double angle;
+
+ if (priv->size_valid)
+ {
+ *width = priv->cached_width;
+ *height = priv->cached_height;
+ if (scale_out)
+ *scale_out = priv->cached_scale;
+ return;
+ }
+
+ if (!priv->image_surface)
+ {
+ *width = 0;
+ *height = 0;
+ return;
+ }
+
+ gtk_widget_get_allocation (GTK_WIDGET (image_view), &alloc);
+ angle = gtk_image_view_get_real_angle (image_view);
+
+ image_width = cairo_image_surface_get_width (priv->image_surface) / priv->scale_factor;
+ image_height = cairo_image_surface_get_height (priv->image_surface) / priv->scale_factor;
+
+ upper_right_degrees = DEG_TO_RAD (angle) + atan (image_height / image_width);
+ upper_left_degrees = DEG_TO_RAD (angle) + atan (image_height / -image_width);
+ r = sqrt ((image_width / 2.0) * (image_width / 2.0) + (image_height / 2.0) * (image_height / 2.0));
+
+ upper_right_x = r * cos (upper_right_degrees);
+ upper_right_y = r * sin (upper_right_degrees);
+
+ upper_left_x = r * cos (upper_left_degrees);
+ upper_left_y = r * sin (upper_left_degrees);
+
+ bb_width = round (MAX (fabs (upper_right_x), fabs (upper_left_x)) * 2.0);
+ bb_height = round (MAX (fabs (upper_right_y), fabs (upper_left_y)) * 2.0);
+
+ if (priv->fit_allocation)
+ {
+ double scale_x = (double)alloc.width / (double)bb_width;
+ double scale_y = (double)alloc.height / (double)bb_height;
+
+ scale = MIN (MIN (scale_x, scale_y), 1.0);
+ }
+ else
+ {
+ scale = gtk_image_view_get_real_scale (image_view);
+ }
+
+ priv->cached_scale = scale;
+ if (scale_out)
+ *scale_out = scale;
+
+ if (priv->fit_allocation)
+ {
+ g_assert (!priv->scale_set);
+ priv->scale = scale;
+ g_object_notify_by_pspec (G_OBJECT (image_view),
+ widget_props[PROP_SCALE]);
+ }
+
+ *width = priv->cached_width = bb_width * scale;
+ *height = priv->cached_height = bb_height * scale;
+
+ priv->size_valid = TRUE;
+}
+
+static inline void
+gtk_image_view_restrict_adjustment (GtkAdjustment *adjustment)
+{
+ double value = gtk_adjustment_get_value (adjustment);
+ double upper = gtk_adjustment_get_upper (adjustment);
+ double page_size = gtk_adjustment_get_page_size (adjustment);
+
+ value = gtk_adjustment_get_value (adjustment);
+ upper = gtk_adjustment_get_upper (adjustment);
+
+ if (value > upper - page_size)
+ gtk_adjustment_set_value (adjustment, upper - page_size);
+ else if (value < 0)
+ gtk_adjustment_set_value (adjustment, 0);
+}
+
+static void
+gtk_image_view_update_adjustments (GtkImageView *image_view)
+{
+ GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
+ double width, height;
+ int widget_width = gtk_widget_get_allocated_width (GTK_WIDGET (image_view));
+ int widget_height = gtk_widget_get_allocated_height (GTK_WIDGET (image_view));
+
+ if (!priv->hadjustment && !priv->vadjustment)
+ return;
+
+ if (!priv->image_surface)
+ {
+ if (priv->hadjustment)
+ gtk_adjustment_configure (priv->hadjustment, 0, 0, 1, 0, 0, 1);
+
+ if (priv->vadjustment)
+ gtk_adjustment_configure (priv->vadjustment, 0, 0, 1, 0, 0, 1);
+
+ return;
+ }
+
+ gtk_image_view_compute_bounding_box (image_view,
+ &width,
+ &height,
+ NULL);
+
+ /* compute_bounding_box makes sure that the bounding box is never bigger than
+ * the widget allocation if fit-allocation is set */
+ if (priv->hadjustment)
+ {
+ gtk_adjustment_set_upper (priv->hadjustment, MAX (width, widget_width));
+ gtk_adjustment_set_page_size (priv->hadjustment, widget_width);
+ gtk_image_view_restrict_adjustment (priv->hadjustment);
+ }
+
+ if (priv->vadjustment)
+ {
+ gtk_adjustment_set_upper (priv->vadjustment, MAX (height, widget_height));
+ gtk_adjustment_set_page_size (priv->vadjustment, widget_height);
+ gtk_image_view_restrict_adjustment (priv->vadjustment);
+ }
+}
+
+static void
+gtk_image_view_set_scale_internal (GtkImageView *image_view,
+ double scale)
+{
+ GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
+ scale = MAX (0, scale);
+
+ priv->scale = scale;
+ priv->size_valid = FALSE;
+ g_object_notify_by_pspec (G_OBJECT (image_view),
+ widget_props[PROP_SCALE]);
+
+ if (priv->scale_set)
+ {
+ priv->scale_set = FALSE;
+ g_object_notify_by_pspec (G_OBJECT (image_view),
+ widget_props[PROP_SCALE_SET]);
+ }
+
+ if (priv->fit_allocation)
+ {
+ priv->fit_allocation = FALSE;
+ g_object_notify_by_pspec (G_OBJECT (image_view),
+ widget_props[PROP_FIT_ALLOCATION]);
+ }
+
+ gtk_image_view_update_adjustments (image_view);
+
+ gtk_widget_queue_resize (GTK_WIDGET (image_view));
+}
+
+static void
+gesture_zoom_begin_cb (GtkGesture *gesture,
+ GdkEventSequence *sequence,
+ gpointer user_data)
+{
+ GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (user_data);
+
+ if (!priv->zoomable ||
+ !priv->image_surface)
+ {
+ gtk_gesture_set_state (gesture, GTK_EVENT_SEQUENCE_DENIED);
+ return;
+ }
+
+ if (priv->anchor_x == -1 && priv->anchor_y == -1)
+ {
+ gtk_gesture_get_bounding_box_center (gesture,
+ &priv->anchor_x,
+ &priv->anchor_y);
+ }
+}
+
+static void
+gesture_zoom_end_cb (GtkGesture *gesture,
+ GdkEventSequence *sequence,
+ gpointer image_view)
+{
+ GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
+
+ gtk_image_view_set_scale_internal (image_view, priv->visible_scale);
+
+ priv->in_zoom = FALSE;
+ priv->anchor_x = -1;
+ priv->anchor_y = -1;
+}
+
+static void
+gesture_zoom_cancel_cb (GtkGesture *gesture,
+ GdkEventSequence *sequence,
+ gpointer user_data)
+{
+ GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (user_data);
+
+ if (priv->in_zoom)
+ gtk_image_view_set_scale (user_data, priv->gesture_start_scale);
+
+ priv->in_zoom = FALSE;
+ priv->anchor_x = -1;
+ priv->anchor_y = -1;
+}
+
+static void
+gesture_zoom_changed_cb (GtkGestureZoom *gesture,
+ double delta,
+ GtkWidget *widget)
+{
+ GtkImageView *image_view = GTK_IMAGE_VIEW (widget);
+ GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
+ State state;
+ double new_scale;
+
+ if (!priv->in_zoom)
+ {
+ priv->in_zoom = TRUE;
+ priv->gesture_start_scale = priv->scale;
+ }
+
+ if (priv->fit_allocation)
+ {
+ priv->fit_allocation = FALSE;
+ g_object_notify_by_pspec (G_OBJECT (widget),
+ widget_props[PROP_FIT_ALLOCATION]);
+ }
+
+ new_scale = priv->gesture_start_scale * delta;
+ gtk_image_view_get_current_state (image_view, &state);
+
+ priv->visible_scale = new_scale;
+ priv->size_valid = FALSE;
+
+ gtk_image_view_update_adjustments (image_view);
+
+ if (priv->hadjustment != NULL && priv->vadjustment != NULL)
+ {
+ gtk_image_view_fix_anchor (image_view,
+ priv->anchor_x,
+ priv->anchor_y,
+ &state);
+ }
+
+ gtk_widget_queue_resize (GTK_WIDGET (image_view));
+}
+
+static void
+gesture_rotate_begin_cb (GtkGesture *gesture,
+ GdkEventSequence *sequence,
+ gpointer user_data)
+{
+ GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (user_data);
+
+ if (!priv->rotatable ||
+ !priv->image_surface)
+ {
+ gtk_gesture_set_state (gesture, GTK_EVENT_SEQUENCE_DENIED);
+ return;
+ }
+
+ if (priv->anchor_x == -1 && priv->anchor_y == -1)
+ {
+ gtk_gesture_get_bounding_box_center (gesture,
+ &priv->anchor_x,
+ &priv->anchor_y);
+ }
+}
+
+static void
+gesture_rotate_end_cb (GtkGesture *gesture,
+ GdkEventSequence *sequence,
+ gpointer image_view)
+{
+ GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
+
+ priv->angle = gtk_image_view_clamp_angle (priv->visible_angle);
+
+ if (priv->snap_angle)
+ {
+ /* Will update priv->angle */
+ gtk_image_view_do_snapping (image_view);
+ }
+ g_object_notify_by_pspec (image_view,
+ widget_props[PROP_ANGLE]);
+
+ priv->in_rotate = FALSE;
+ priv->anchor_x = -1;
+ priv->anchor_y = -1;
+}
+
+static void
+gesture_rotate_cancel_cb (GtkGesture *gesture,
+ GdkEventSequence *sequence,
+ gpointer image_view)
+{
+ GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
+
+ priv->size_valid = FALSE;
+ gtk_image_view_update_adjustments (image_view);
+
+ priv->in_rotate = FALSE;
+ priv->anchor_x = -1;
+ priv->anchor_y = -1;
+}
+
+static void
+gesture_rotate_changed_cb (GtkGestureRotate *gesture,
+ double angle,
+ double delta,
+ GtkWidget *widget)
+{
+ GtkImageView *image_view = GTK_IMAGE_VIEW (widget);
+ GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
+ State old_state;
+ double new_angle;
+
+ if (!priv->in_rotate)
+ {
+ priv->in_rotate = TRUE;
+ priv->gesture_start_angle = priv->angle;
+ }
+
+ new_angle = priv->gesture_start_angle + RAD_TO_DEG (delta);
+ gtk_image_view_get_current_state (image_view, &old_state);
+
+ priv->visible_angle = new_angle;
+ priv->size_valid = FALSE;
+ gtk_image_view_update_adjustments (image_view);
+
+ if (priv->hadjustment && priv->vadjustment && !priv->fit_allocation)
+ gtk_image_view_fix_anchor (image_view,
+ priv->anchor_x,
+ priv->anchor_y,
+ &old_state);
+
+ if (priv->fit_allocation)
+ gtk_widget_queue_draw (widget);
+ else
+ gtk_widget_queue_resize (widget);
+}
+
+static void
+gtk_image_view_ensure_gestures (GtkImageView *image_view)
+{
+ GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
+
+ if (priv->zoomable && priv->zoom_gesture == NULL)
+ {
+ priv->zoom_gesture = gtk_gesture_zoom_new (GTK_WIDGET (image_view));
+ g_signal_connect (priv->zoom_gesture, "scale-changed",
+ (GCallback)gesture_zoom_changed_cb, image_view);
+ g_signal_connect (priv->zoom_gesture, "begin",
+ (GCallback)gesture_zoom_begin_cb, image_view);
+ g_signal_connect (priv->zoom_gesture, "end",
+ (GCallback)gesture_zoom_end_cb, image_view);
+ g_signal_connect (priv->zoom_gesture, "cancel",
+ (GCallback)gesture_zoom_cancel_cb, image_view);
+ }
+ else if (!priv->zoomable && priv->zoom_gesture != NULL)
+ {
+ g_clear_object (&priv->zoom_gesture);
+ }
+
+ if (priv->rotatable && priv->rotate_gesture == NULL)
+ {
+ priv->rotate_gesture = gtk_gesture_rotate_new (GTK_WIDGET (image_view));
+ g_signal_connect (priv->rotate_gesture, "angle-changed", (GCallback)gesture_rotate_changed_cb, image_view);
+ g_signal_connect (priv->rotate_gesture, "begin", (GCallback)gesture_rotate_begin_cb, image_view);
+ g_signal_connect (priv->rotate_gesture, "end", (GCallback)gesture_rotate_end_cb, image_view);
+ g_signal_connect (priv->rotate_gesture, "cancel", (GCallback)gesture_rotate_cancel_cb, image_view);
+
+ }
+ else if (!priv->rotatable && priv->rotate_gesture != NULL)
+ {
+ g_clear_object (&priv->rotate_gesture);
+ }
+
+ if (priv->zoom_gesture && priv->rotate_gesture)
+ gtk_gesture_group (priv->zoom_gesture,
+ priv->rotate_gesture);
+}
+
+static void
+gtk_image_view_init (GtkImageView *image_view)
+{
+ GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
+ GtkWidget *widget = GTK_WIDGET (image_view);
+
+ gtk_widget_set_can_focus (widget, TRUE);
+ gtk_widget_set_has_window (widget, FALSE);
+
+ priv->scale = 1.0;
+ priv->angle = 0.0;
+ priv->visible_scale = 1.0;
+ priv->visible_angle = 0.0;
+ priv->snap_angle = FALSE;
+ priv->fit_allocation = FALSE;
+ priv->scale_set = FALSE;
+ priv->size_valid = FALSE;
+ priv->anchor_x = -1;
+ priv->anchor_y = -1;
+ priv->rotatable = TRUE;
+ priv->zoomable = TRUE;
+ priv->transitions_enabled = TRUE;
+ priv->angle_transition_id = 0;
+ priv->scale_transition_id = 0;
+
+ gtk_image_view_ensure_gestures (image_view);
+}
+
+static GdkPixbuf *
+gtk_image_view_get_current_frame (GtkImageView *image_view)
+{
+ GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
+
+ g_assert (priv->source_animation);
+
+ if (priv->is_animation)
+ return gdk_pixbuf_animation_iter_get_pixbuf (priv->source_animation_iter);
+ else
+ return gdk_pixbuf_animation_get_static_image (priv->source_animation);
+}
+
+static gboolean
+gtk_image_view_update_animation (gpointer user_data)
+{
+ GtkImageView *image_view = user_data;
+ GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
+
+ if (priv->is_animation)
+ {
+ gdk_pixbuf_animation_iter_advance (priv->source_animation_iter, NULL);
+ gtk_image_view_update_surface (image_view,
+ gtk_image_view_get_current_frame (image_view),
+ priv->scale_factor);
+ }
+
+ return priv->is_animation;
+}
+
+static void
+gtk_image_view_start_animation (GtkImageView *image_view)
+{
+ GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
+ int delay_ms;
+
+ g_assert (priv->is_animation);
+
+ delay_ms = gdk_pixbuf_animation_iter_get_delay_time (priv->source_animation_iter);
+
+ priv->animation_timeout = g_timeout_add (delay_ms, gtk_image_view_update_animation, image_view);
+}
+
+static void
+gtk_image_view_stop_animation (GtkImageView *image_view)
+{
+ GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
+
+ if (priv->animation_timeout != 0)
+ {
+ g_assert (priv->is_animation);
+ g_source_remove (priv->animation_timeout);
+ priv->animation_timeout = 0;
+ }
+}
+
+static gboolean
+gtk_image_view_draw (GtkWidget *widget, cairo_t *ct)
+{
+ GtkImageView *image_view = GTK_IMAGE_VIEW (widget);
+ GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
+ GtkStyleContext *sc = gtk_widget_get_style_context (widget);
+ int widget_width = gtk_widget_get_allocated_width (widget);
+ int widget_height = gtk_widget_get_allocated_height (widget);
+ double draw_x = 0;
+ double draw_y = 0;
+ int image_width;
+ int image_height;
+ double draw_width;
+ double draw_height;
+ double scale = 0.0;
+
+ if (priv->vadjustment && priv->hadjustment)
+ {
+ int x = - gtk_adjustment_get_value (priv->hadjustment);
+ int y = - gtk_adjustment_get_value (priv->vadjustment);
+ int w = gtk_adjustment_get_upper (priv->hadjustment);
+ int h = gtk_adjustment_get_upper (priv->vadjustment);
+
+ gtk_render_background (sc, ct, x, y, w, h);
+ gtk_render_frame (sc, ct, x, y, w, h);
+ }
+ else
+ {
+ gtk_render_background (sc, ct, 0, 0, widget_width, widget_height);
+ gtk_render_frame (sc, ct, 0, 0, widget_width, widget_height);
+ }
+
+ if (!priv->image_surface)
+ return GDK_EVENT_PROPAGATE;
+
+ gtk_image_view_compute_bounding_box (image_view,
+ &draw_width, &draw_height,
+ &scale);
+
+ if (draw_width == 0 || draw_height == 0)
+ return GDK_EVENT_PROPAGATE;
+
+ image_width = cairo_image_surface_get_width (priv->image_surface) * scale / priv->scale_factor;
+ image_height = cairo_image_surface_get_height (priv->image_surface) * scale / priv->scale_factor;
+
+ if (priv->hadjustment && priv->vadjustment)
+ {
+ draw_x = (gtk_adjustment_get_page_size (priv->hadjustment) - image_width) / 2.0;
+ draw_y = (gtk_adjustment_get_page_size (priv->vadjustment) - image_height) / 2.0;
+ }
+ else
+ {
+ draw_x = (widget_width - image_width) / 2.0;
+ draw_y = (widget_height - image_height) / 2.0;
+ }
+
+ cairo_rectangle (ct, 0, 0, widget_width, widget_height);
+
+ if (priv->hadjustment && draw_width >= widget_width)
+ {
+ draw_x = (draw_width - image_width) / 2.0;
+ draw_x -= gtk_adjustment_get_value (priv->hadjustment);
+ }
+
+ if (priv->vadjustment && draw_height >= widget_height)
+ {
+ draw_y = (draw_height - image_height) / 2.0;
+ draw_y -= gtk_adjustment_get_value (priv->vadjustment);
+ }
+
+ /* Rotate around the center */
+ cairo_translate (ct,
+ draw_x + (image_width / 2.0),
+ draw_y + (image_height / 2.0));
+ cairo_rotate (ct, DEG_TO_RAD (gtk_image_view_get_real_angle (image_view)));
+ cairo_translate (ct,
+ - draw_x - (image_width / 2.0),
+ - draw_y - (image_height / 2.0));
+
+ cairo_scale (ct, scale , scale );
+ cairo_set_source_surface (ct,
+ priv->image_surface,
+ draw_x / scale ,
+ draw_y / scale);
+ cairo_pattern_set_filter (cairo_get_source (ct), CAIRO_FILTER_BILINEAR);
+ cairo_fill (ct);
+
+ return GDK_EVENT_PROPAGATE;
+}
+
+static void
+gtk_image_view_set_hadjustment (GtkImageView *image_view,
+ GtkAdjustment *hadjustment)
+{
+ GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
+
+ if (priv->hadjustment && priv->hadjustment == hadjustment)
+ return;
+
+ if (priv->hadjustment)
+ {
+ g_signal_handlers_disconnect_by_func (priv->hadjustment, adjustment_value_changed_cb, image_view);
+ g_object_unref (priv->hadjustment);
+ }
+
+ if (hadjustment)
+ {
+ g_signal_connect (G_OBJECT (hadjustment), "value-changed",
+ G_CALLBACK (adjustment_value_changed_cb), image_view);
+ priv->hadjustment = g_object_ref_sink (hadjustment);
+ }
+ else
+ {
+ priv->hadjustment = hadjustment;
+ }
+
+ g_object_notify (G_OBJECT (image_view), "hadjustment");
+
+ gtk_image_view_update_adjustments (image_view);
+
+ if (priv->fit_allocation)
+ gtk_widget_queue_draw (GTK_WIDGET (image_view));
+ else
+ gtk_widget_queue_resize (GTK_WIDGET (image_view));
+
+}
+
+static void
+gtk_image_view_set_vadjustment (GtkImageView *image_view,
+ GtkAdjustment *vadjustment)
+{
+ GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
+
+ if (priv->vadjustment == vadjustment)
+ return;
+
+ if (priv->vadjustment)
+ {
+ g_signal_handlers_disconnect_by_func (priv->vadjustment, adjustment_value_changed_cb, image_view);
+ g_object_unref (priv->vadjustment);
+ }
+
+ if (vadjustment)
+ {
+ g_signal_connect ((GObject *)vadjustment, "value-changed",
+ (GCallback) adjustment_value_changed_cb, image_view);
+ priv->vadjustment = g_object_ref_sink (vadjustment);
+ }
+ else
+ {
+ priv->vadjustment = vadjustment;
+ }
+
+ g_object_notify (G_OBJECT (image_view), "vadjustment");
+
+ gtk_image_view_update_adjustments (image_view);
+
+ if (priv->fit_allocation)
+ gtk_widget_queue_draw ((GtkWidget *)image_view);
+ else
+ gtk_widget_queue_resize ((GtkWidget *)image_view);
+}
+
+static void
+gtk_image_view_set_hscroll_policy (GtkImageView *image_view,
+ GtkScrollablePolicy hscroll_policy)
+{
+ GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
+
+ if (priv->hscroll_policy == hscroll_policy)
+ return;
+
+ priv->hscroll_policy = hscroll_policy;
+}
+
+static void
+gtk_image_view_set_vscroll_policy (GtkImageView *image_view,
+ GtkScrollablePolicy vscroll_policy)
+{
+ GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
+
+ if (priv->vscroll_policy == vscroll_policy)
+ return;
+
+ priv->vscroll_policy = vscroll_policy;
+}
+
+/**
+ * gtk_image_view_set_scale:
+ * @image_view: A #GtkImageView instance
+ * @scale: The new scale value
+ *
+ * Sets the value of the #scale property. This will cause the
+ * #scale-set property to be set to #FALSE as well
+ *
+ * If #GtkImageView:fit-allocation is %TRUE, it will be set to %FALSE, and @image_view
+ * will be resized to the image's current size, taking the new scale into
+ * account.
+ *
+ * If #GtkImageView:transitions-enabled is set to %TRUE, the internal scale value will be
+ * interpolated between the old and the new scale, gtk_image_view_get_scale()
+ * will report the one passed to gtk_image_view_set_scale() however.
+ *
+ * When calling this function, #GtkImageView will try to keep the currently centered
+ * point of the image where it is, so visually it will "zoom" into the current
+ * center of the widget. Note that #GtkImageView is a #GtkScrollable, so the center
+ * of the image is also the center of the scrolled window in case it is packed into
+ * a #GtkScrolledWindow.
+ *
+ */
+void
+gtk_image_view_set_scale (GtkImageView *image_view,
+ double scale)
+{
+ GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
+ State state;
+
+ g_return_if_fail (GTK_IS_IMAGE_VIEW (image_view));
+ g_return_if_fail (scale > 0.0);
+
+ if (scale == priv->scale)
+ return;
+
+ gtk_image_view_get_current_state (image_view, &state);
+
+ if (gtk_image_view_transitions_enabled (image_view))
+ gtk_image_view_animate_to_scale (image_view);
+
+ priv->scale = scale;
+ g_object_notify_by_pspec (G_OBJECT (image_view),
+ widget_props[PROP_SCALE]);
+
+ if (priv->scale_set)
+ {
+ priv->scale_set = FALSE;
+ g_object_notify_by_pspec (G_OBJECT (image_view),
+ widget_props[PROP_SCALE_SET]);
+ }
+
+ if (priv->fit_allocation)
+ {
+ priv->fit_allocation = FALSE;
+ g_object_notify_by_pspec (G_OBJECT (image_view),
+ widget_props[PROP_FIT_ALLOCATION]);
+ }
+
+ priv->size_valid = FALSE;
+ gtk_image_view_update_adjustments (image_view);
+
+ if (!priv->image_surface)
+ return;
+
+ if (priv->hadjustment != NULL && priv->vadjustment != NULL)
+ {
+ int pointer_x = gtk_widget_get_allocated_width (GTK_WIDGET (image_view)) / 2;
+ int pointer_y = gtk_widget_get_allocated_height (GTK_WIDGET (image_view)) / 2;
+ gtk_image_view_fix_anchor (image_view,
+ pointer_x,
+ pointer_y,
+ &state);
+ }
+
+ gtk_widget_queue_resize (GTK_WIDGET (image_view));
+}
+
+/**
+ * gtk_image_view_get_scale:
+ * @image_view: A #GtkImageView instance
+ *
+ * Returns: The current value of the #GtkImageView:scale property.
+ *
+ */
+double
+gtk_image_view_get_scale (GtkImageView *image_view)
+{
+ GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
+ g_return_val_if_fail (GTK_IS_IMAGE_VIEW (image_view), 0.0);
+
+ return priv->scale;
+}
+
+/**
+ * gtk_image_view_set_angle:
+ * @image_view: A #GtkImageView instance
+ * @angle: The angle to rotate the image about, in
+ * degrees. If this is < 0 or > 360, the value will
+ * be wrapped. So e.g. setting this to 362 will result in a
+ * angle of 2, setting it to -2 will result in 358.
+ * Both 0 and 360 are possible.
+ *
+ * Sets the value of the #GtkImageView:angle property. When calling this function,
+ * #GtkImageView will try to keep the currently centered point of the image where it is,
+ * so visually the image will not be rotated around its center, but around the current
+ * center of the widget. Note that #GtkImageView is a #GtkScrollable, so the center
+ * of the image is also the center of the scrolled window in case it is packed into
+ * a #GtkScrolledWindow.
+ *
+ */
+void
+gtk_image_view_set_angle (GtkImageView *image_view,
+ double angle)
+{
+ GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
+ State state;
+
+ g_return_if_fail (GTK_IS_IMAGE_VIEW (image_view));
+
+ if (angle == priv->angle)
+ return;
+
+ gtk_image_view_get_current_state (image_view, &state);
+
+ if (gtk_image_view_transitions_enabled (image_view) &&
+ ABS(gtk_image_view_clamp_angle (angle) - priv->angle) > ANGLE_TRANSITION_MIN_DELTA)
+ {
+ gtk_image_view_animate_to_angle (image_view, angle > priv->angle);
+ }
+
+ angle = gtk_image_view_clamp_angle (angle);
+
+ if (priv->snap_angle)
+ priv->angle = gtk_image_view_get_snapped_angle (angle);
+ else
+ priv->angle = angle;
+
+ priv->size_valid = FALSE;
+
+ gtk_image_view_update_adjustments (image_view);
+
+ g_object_notify_by_pspec (G_OBJECT (image_view),
+ widget_props[PROP_ANGLE]);
+
+ if (!priv->image_surface)
+ return;
+
+ if (priv->hadjustment && priv->vadjustment && !priv->fit_allocation)
+ {
+ int pointer_x = gtk_widget_get_allocated_width (GTK_WIDGET (image_view)) / 2;
+ int pointer_y = gtk_widget_get_allocated_height (GTK_WIDGET (image_view)) / 2;
+ gtk_image_view_fix_anchor (image_view,
+ pointer_x,
+ pointer_y,
+ &state);
+ }
+
+ if (priv->fit_allocation)
+ gtk_widget_queue_draw (GTK_WIDGET (image_view));
+ else
+ gtk_widget_queue_resize (GTK_WIDGET (image_view));
+}
+
+/**
+ * gtk_image_view_get_angle:
+ * @image_view: A #GtkImageView instance
+ *
+ * Returns: The current angle value.
+ *
+ */
+double
+gtk_image_view_get_angle (GtkImageView *image_view)
+{
+ GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
+ g_return_val_if_fail (GTK_IS_IMAGE_VIEW (image_view), 0.0);
+
+ return priv->angle;
+}
+
+/**
+ * gtk_image_view_set_snap_angle:
+ * @image_view: A #GtkImageView instance
+ * @snap_angle: The new value of the #GtkImageView:snap-angle property
+ *
+ * Setting #snap-angle to %TRUE will cause @image_view's angle to
+ * be snapped to 90° steps. Setting the #GtkImageView:angle property will cause it to
+ * be set to the closest 90° step, so e.g. using an angle of 40 will result
+ * in an angle of 0, using 240 will result in 270, etc.
+ *
+ */
+void
+gtk_image_view_set_snap_angle (GtkImageView *image_view,
+ gboolean snap_angle)
+{
+ GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
+
+ g_return_if_fail (GTK_IS_IMAGE_VIEW (image_view));
+
+ snap_angle = !!snap_angle;
+
+ if (snap_angle == priv->snap_angle)
+ return;
+
+ priv->snap_angle = snap_angle;
+ g_object_notify_by_pspec (G_OBJECT (image_view),
+ widget_props[PROP_SNAP_ANGLE]);
+
+ if (priv->snap_angle)
+ {
+ gtk_image_view_do_snapping (image_view);
+ g_object_notify_by_pspec (G_OBJECT (image_view),
+ widget_props[PROP_ANGLE]);
+ }
+}
+
+/**
+ * gtk_image_view_get_snap_angle:
+ * @image_view: A #GtkImageView instance
+ *
+ * Returns: The current value of the #GtkImageView:snap-angle property.
+ *
+ */
+gboolean
+gtk_image_view_get_snap_angle (GtkImageView *image_view)
+{
+ GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
+ g_return_val_if_fail (GTK_IS_IMAGE_VIEW (image_view), FALSE);
+
+ return priv->snap_angle;
+}
+
+/**
+ * gtk_image_view_set_fit_allocation:
+ * @image_view: A #GtkImageView instance
+ * @fit_allocation: The new value of the #GtkImageView:fit-allocation property.
+ *
+ * Setting #GtkImageView:fit-allocation to %TRUE will cause the image to be scaled
+ * to the widget's allocation, unless it would cause the image to be
+ * scaled up.
+ *
+ * Setting #GtkImageView:fit-allocation will have the side effect of setting
+ * #scale-set set to %FALSE, thus giving the #GtkImageView the control
+ * over the image's scale. Additionally, if the new #GtkImageView:fit-allocation
+ * value is %FALSE, the scale will be reset to 1.0 and the #GtkImageView
+ * will be resized to take at least the image's real size.
+ *
+ */
+void
+gtk_image_view_set_fit_allocation (GtkImageView *image_view,
+ gboolean fit_allocation)
+{
+ GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
+ g_return_if_fail (GTK_IS_IMAGE_VIEW (image_view));
+
+ fit_allocation = !!fit_allocation;
+
+ if (fit_allocation == priv->fit_allocation)
+ return;
+
+ priv->fit_allocation = fit_allocation;
+ g_object_notify_by_pspec (G_OBJECT (image_view),
+ widget_props[PROP_FIT_ALLOCATION]);
+
+ priv->scale_set = FALSE;
+ priv->size_valid = FALSE;
+ g_object_notify_by_pspec (G_OBJECT (image_view),
+ widget_props[PROP_SCALE_SET]);
+
+ if (!priv->fit_allocation)
+ {
+ priv->scale = 1.0;
+ g_object_notify_by_pspec (G_OBJECT (image_view),
+ widget_props[PROP_SCALE]);
+ }
+
+ gtk_image_view_update_adjustments (image_view);
+
+ gtk_widget_queue_resize (GTK_WIDGET (image_view));
+}
+
+/**
+ * gtk_image_view_get_fit_allocation:
+ * @image_view: A #GtkImageView instance
+ *
+ * Returns: The current value of the #GtkImageView:fit-allocation property.
+ *
+ */
+gboolean
+gtk_image_view_get_fit_allocation (GtkImageView *image_view)
+{
+ GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
+ g_return_val_if_fail (GTK_IS_IMAGE_VIEW (image_view), FALSE);
+
+ return priv->fit_allocation;
+}
+
+/**
+ * gtk_image_view_set_rotatable:
+ * @image_view: A #GtkImageView instance
+ * @rotatable: The new value of the #GtkImageView:rotatable property
+ *
+ * Sets the value of the #GtkImageView:rotatable property to @rotatable. This controls whether
+ * the user can change the angle of the displayed image using a two-finger gesture.
+ *
+ */
+void
+gtk_image_view_set_rotatable (GtkImageView *image_view,
+ gboolean rotatable)
+{
+ GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
+ g_return_if_fail (GTK_IS_IMAGE_VIEW (image_view));
+
+ rotatable = !!rotatable;
+
+ if (priv->rotatable != rotatable)
+ {
+ priv->rotatable = rotatable;
+ gtk_image_view_ensure_gestures (image_view);
+ g_object_notify_by_pspec (G_OBJECT (image_view),
+ widget_props[PROP_ROTATABLE]);
+ }
+}
+
+/**
+ * gtk_image_view_get_rotatable:
+ * @image_view: A #GtkImageView instance
+ *
+ * Returns: The current value of the #GtkImageView:rotatable property
+ *
+ */
+gboolean
+gtk_image_view_get_rotatable (GtkImageView *image_view)
+{
+ GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
+ g_return_val_if_fail (GTK_IS_IMAGE_VIEW (image_view), FALSE);
+
+ return priv->rotatable;
+}
+
+/**
+ * gtk_image_view_set_zoomable:
+ * @image_view: A #GtkImageView instance
+ * @zoomable: The new value of the #GtkImageView:zoomable property
+ *
+ * Sets the new value of the #GtkImageView:zoomable property. This controls whether the user can
+ * change the #GtkImageView:scale property using a two-finger gesture.
+ *
+ */
+void
+gtk_image_view_set_zoomable (GtkImageView *image_view,
+ gboolean zoomable)
+{
+ GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
+ g_return_if_fail (GTK_IS_IMAGE_VIEW (image_view));
+
+ zoomable = !!zoomable;
+
+ if (zoomable != priv->zoomable)
+ {
+ priv->zoomable = zoomable;
+ gtk_image_view_ensure_gestures (image_view);
+ g_object_notify_by_pspec (G_OBJECT (image_view),
+ widget_props[PROP_ZOOMABLE]);
+ }
+}
+
+/**
+ * gtk_image_view_get_zoomable:
+ * @image_view: A #GtkImageView instance
+ *
+ * Returns: The current value of the #GtkImageView:zoomable property.
+ *
+ */
+gboolean
+gtk_image_view_get_zoomable (GtkImageView *image_view)
+{
+ GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
+ g_return_val_if_fail (GTK_IS_IMAGE_VIEW (image_view), FALSE);
+
+ return priv->zoomable;
+}
+
+/**
+ * gtk_image_view_set_transitions_enabled:
+ * @image_view: A #GtkImageView instance
+ * @transitions_enabled: The new value of the #GtkImageView:transitions-enabled property
+ *
+ * Sets the new value of the #GtkImageView:transitions-enabled property.
+ * Note that even if #GtkImageView:transitions-enabled is %TRUE, transitions will
+ * not be used if #GtkSettings:gtk-enable-animations is %FALSE.
+ *
+ */
+void
+gtk_image_view_set_transitions_enabled (GtkImageView *image_view,
+ gboolean transitions_enabled)
+{
+ GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
+ g_return_if_fail (GTK_IS_IMAGE_VIEW (image_view));
+
+ transitions_enabled = !!transitions_enabled;
+
+ if (transitions_enabled != priv->transitions_enabled)
+ {
+ priv->transitions_enabled = transitions_enabled;
+ g_object_notify_by_pspec (G_OBJECT (image_view),
+ widget_props[PROP_TRANSITIONS_ENABLED]);
+ }
+}
+
+/**
+ * gtk_image_view_get_transitions_enabled:
+ * @image_view: A #GtkImageView instance
+ *
+ * Returns: the current value of the #GtkImageView:transitions-enabled property.
+ *
+ */
+gboolean
+gtk_image_view_get_transitions_enabled (GtkImageView *image_view)
+{
+ GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
+ g_return_val_if_fail (GTK_IS_IMAGE_VIEW (image_view), FALSE);
+
+ return priv->transitions_enabled;
+}
+
+/**
+ * gtk_image_view_get_scale_set:
+ * @image_view: A #GtkImageView instance
+ *
+ * Returns: the current value of the #GtkImageView:scale-set property.
+ *
+ */
+gboolean
+gtk_image_view_get_scale_set (GtkImageView *image_view)
+{
+ GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
+ g_return_val_if_fail (GTK_IS_IMAGE_VIEW (image_view), FALSE);
+
+ return priv->scale_set;
+}
+
+static void
+gtk_image_view_realize (GtkWidget *widget)
+{
+ GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (GTK_IMAGE_VIEW (widget));
+ GtkAllocation allocation;
+ GdkWindowAttr attributes = { 0, };
+ GdkWindow *window;
+
+ gtk_widget_get_allocation (widget, &allocation);
+ gtk_widget_set_realized (widget, TRUE);
+
+ attributes.x = allocation.x;
+ attributes.y = allocation.y;
+ attributes.width = allocation.width;
+ attributes.height = allocation.height;
+ attributes.window_type = GDK_WINDOW_CHILD;
+ attributes.event_mask = gtk_widget_get_events (widget) |
+ GDK_POINTER_MOTION_MASK |
+ GDK_BUTTON_PRESS_MASK |
+ GDK_BUTTON_RELEASE_MASK |
+ GDK_SMOOTH_SCROLL_MASK |
+ GDK_SCROLL_MASK |
+ GDK_TOUCH_MASK;
+ attributes.wclass = GDK_INPUT_ONLY;
+
+ window = gtk_widget_get_parent_window (widget);
+
+ gtk_widget_set_window (widget, window);
+ g_object_ref (G_OBJECT (window));
+
+ window = gdk_window_new (gtk_widget_get_parent_window (widget),
+ &attributes, GDK_WA_X | GDK_WA_Y);
+ priv->event_window = window;
+
+ gtk_widget_register_window (widget, priv->event_window);
+ gdk_window_set_user_data (window, widget);
+}
+
+static void
+gtk_image_view_unrealize (GtkWidget *widget)
+{
+ GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (GTK_IMAGE_VIEW (widget));
+
+ if (priv->event_window)
+ {
+ gtk_widget_unregister_window (widget, priv->event_window);
+ gdk_window_destroy (priv->event_window);
+ priv->event_window = NULL;
+ }
+
+ GTK_WIDGET_CLASS (gtk_image_view_parent_class)->unrealize (widget);
+}
+
+static void
+gtk_image_view_size_allocate (GtkWidget *widget,
+ GtkAllocation *allocation)
+{
+ GtkImageView *image_view = GTK_IMAGE_VIEW (widget);
+ GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
+
+ gtk_widget_set_allocation (widget, allocation);
+
+ if (gtk_widget_get_realized (widget))
+ {
+ gdk_window_move_resize (priv->event_window,
+ allocation->x, allocation->y,
+ allocation->width, allocation->height);
+ }
+
+ if (priv->fit_allocation)
+ priv->size_valid = FALSE;
+
+ gtk_image_view_update_adjustments (image_view);
+}
+
+static void
+gtk_image_view_map (GtkWidget *widget)
+{
+ GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (GTK_IMAGE_VIEW (widget));
+
+ if (priv->is_animation)
+ gtk_image_view_start_animation (GTK_IMAGE_VIEW (widget));
+
+ if (priv->event_window)
+ gdk_window_show (priv->event_window);
+
+ GTK_WIDGET_CLASS (gtk_image_view_parent_class)->map (widget);
+}
+
+static void
+gtk_image_view_unmap (GtkWidget *widget)
+{
+ GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (GTK_IMAGE_VIEW (widget));
+
+ if (priv->is_animation)
+ gtk_image_view_stop_animation (GTK_IMAGE_VIEW (widget));
+
+ GTK_WIDGET_CLASS (gtk_image_view_parent_class)->unmap (widget);
+}
+
+static void
+adjustment_value_changed_cb (GtkAdjustment *adjustment,
+ gpointer user_data)
+{
+ GtkImageView *image_view = user_data;
+
+ gtk_image_view_update_adjustments (image_view);
+
+ gtk_widget_queue_draw (GTK_WIDGET (image_view));
+}
+
+static void
+gtk_image_view_get_preferred_height (GtkWidget *widget,
+ int *minimal,
+ int *natural)
+{
+ GtkImageView *image_view = GTK_IMAGE_VIEW (widget);
+ GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
+
+ double width, height;
+ gtk_image_view_compute_bounding_box (image_view,
+ &width,
+ &height,
+ NULL);
+
+ if (priv->fit_allocation)
+ {
+ *minimal = 0;
+ *natural = height;
+ }
+ else
+ {
+ *minimal = height;
+ *natural = height;
+ }
+}
+
+static void
+gtk_image_view_get_preferred_width (GtkWidget *widget,
+ int *minimal,
+ int *natural)
+{
+ GtkImageView *image_view = GTK_IMAGE_VIEW (widget);
+ GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
+ double width, height;
+
+ gtk_image_view_compute_bounding_box (image_view,
+ &width,
+ &height,
+ NULL);
+ if (priv->fit_allocation)
+ {
+ *minimal = 0;
+ *natural = width;
+ }
+ else
+ {
+ *minimal = width;
+ *natural = width;
+ }
+}
+
+static gboolean
+gtk_image_view_scroll_event (GtkWidget *widget,
+ GdkEventScroll *event)
+{
+ GtkImageView *image_view = GTK_IMAGE_VIEW (widget);
+ GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
+ double new_scale = priv->scale - (0.1 * event->delta_y);
+ State state;
+
+ if (!priv->image_surface ||
+ !priv->zoomable)
+ return GDK_EVENT_PROPAGATE;
+
+ if (event->state & GDK_SHIFT_MASK ||
+ event->state & GDK_CONTROL_MASK)
+ return GDK_EVENT_PROPAGATE;
+
+ gtk_image_view_get_current_state (image_view, &state);
+
+ gtk_image_view_set_scale_internal (image_view, new_scale);
+
+ if (priv->hadjustment && priv->vadjustment)
+ {
+ gtk_image_view_fix_anchor (image_view,
+ event->x,
+ event->y,
+ &state);
+ }
+
+ return GDK_EVENT_STOP;
+}
+
+static void
+gtk_image_view_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+
+{
+ GtkImageView *image_view = (GtkImageView *) object;
+
+ switch (prop_id)
+ {
+ case PROP_SCALE:
+ gtk_image_view_set_scale (image_view, g_value_get_double (value));
+ break;
+ case PROP_ANGLE:
+ gtk_image_view_set_angle (image_view, g_value_get_double (value));
+ break;
+ case PROP_SNAP_ANGLE:
+ gtk_image_view_set_snap_angle (image_view, g_value_get_boolean (value));
+ break;
+ case PROP_FIT_ALLOCATION:
+ gtk_image_view_set_fit_allocation (image_view, g_value_get_boolean (value));
+ break;
+ case PROP_ROTATABLE:
+ gtk_image_view_set_rotatable (image_view, g_value_get_boolean (value));
+ break;
+ case PROP_ZOOMABLE:
+ gtk_image_view_set_zoomable (image_view, g_value_get_boolean (value));
+ break;
+ case PROP_TRANSITIONS_ENABLED:
+ gtk_image_view_set_transitions_enabled (image_view, g_value_get_boolean (value));
+ break;
+ case PROP_HADJUSTMENT:
+ gtk_image_view_set_hadjustment (image_view, g_value_get_object (value));
+ break;
+ case PROP_VADJUSTMENT:
+ gtk_image_view_set_vadjustment (image_view, g_value_get_object (value));
+ break;
+ case PROP_HSCROLL_POLICY:
+ gtk_image_view_set_hscroll_policy (image_view, g_value_get_enum (value));
+ break;
+ case PROP_VSCROLL_POLICY:
+ gtk_image_view_set_vscroll_policy (image_view, g_value_get_enum (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+gtk_image_view_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GtkImageView *image_view = (GtkImageView *)object;
+ GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
+ switch (prop_id)
+ {
+ case PROP_SCALE:
+ g_value_set_double (value, priv->scale);
+ break;
+ case PROP_SCALE_SET:
+ g_value_set_boolean (value, priv->scale_set);
+ break;
+ case PROP_ANGLE:
+ g_value_set_double (value, priv->angle);
+ break;
+ case PROP_SNAP_ANGLE:
+ g_value_set_boolean (value, priv->snap_angle);
+ break;
+ case PROP_FIT_ALLOCATION:
+ g_value_set_boolean (value, priv->fit_allocation);
+ break;
+ case PROP_ROTATABLE:
+ g_value_set_boolean (value, priv->rotatable);
+ break;
+ case PROP_ZOOMABLE:
+ g_value_set_boolean (value, priv->zoomable);
+ break;
+ case PROP_TRANSITIONS_ENABLED:
+ g_value_set_boolean (value, priv->transitions_enabled);
+ break;
+ case PROP_HADJUSTMENT:
+ g_value_set_object (value, priv->hadjustment);
+ break;
+ case PROP_VADJUSTMENT:
+ g_value_set_object (value, priv->vadjustment);
+ break;
+ case PROP_HSCROLL_POLICY:
+ g_value_set_enum (value, priv->hscroll_policy);
+ break;
+ case PROP_VSCROLL_POLICY:
+ g_value_set_enum (value, priv->vscroll_policy);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+gtk_image_view_finalize (GObject *object)
+{
+ GtkImageView *image_view = (GtkImageView *)object;
+ GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
+
+ gtk_image_view_stop_animation (image_view);
+
+ g_clear_object (&priv->source_animation);
+
+ g_clear_object (&priv->rotate_gesture);
+ g_clear_object (&priv->zoom_gesture);
+
+ g_clear_object (&priv->hadjustment);
+ g_clear_object (&priv->vadjustment);
+
+ if (priv->image_surface)
+ cairo_surface_destroy (priv->image_surface);
+
+ G_OBJECT_CLASS (gtk_image_view_parent_class)->finalize (object);
+}
+
+static void
+gtk_image_view_class_init (GtkImageViewClass *view_class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (view_class);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (view_class);
+
+ object_class->set_property = gtk_image_view_set_property;
+ object_class->get_property = gtk_image_view_get_property;
+ object_class->finalize = gtk_image_view_finalize;
+
+ widget_class->draw = gtk_image_view_draw;
+ widget_class->realize = gtk_image_view_realize;
+ widget_class->unrealize = gtk_image_view_unrealize;
+ widget_class->size_allocate = gtk_image_view_size_allocate;
+ widget_class->map = gtk_image_view_map;
+ widget_class->unmap = gtk_image_view_unmap;
+ widget_class->scroll_event = gtk_image_view_scroll_event;
+ widget_class->get_preferred_width = gtk_image_view_get_preferred_width;
+ widget_class->get_preferred_height = gtk_image_view_get_preferred_height;
+
+ /**
+ * GtkImageView:scale:
+ * The scale the internal surface gets drawn with.
+ *
+ */
+ widget_props[PROP_SCALE] = g_param_spec_double ("scale",
+ "Scale",
+ "The scale the internal surface gets drawn with",
+ 0.0,
+ G_MAXDOUBLE,
+ 1.0,
+ _PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
+ /**
+ * GtkImageView:scale-set:
+ * Whether or not the current value of the scale property was set by the user.
+ * This is to distringuish between scale values set by the #GtkImageView itself,
+ * e.g. when #GtkImageView:fit-allocation is true, which will change the scale
+ * depending on the widget allocation.
+ *
+ */
+ widget_props[PROP_SCALE_SET] = g_param_spec_boolean ("scale-set",
+ "Scale Set",
+ "Wheter the scale property has been set by the user or by GtkImageView itself",
+ FALSE,
+ _PARAM_READABLE|G_PARAM_EXPLICIT_NOTIFY);
+ /**
+ * GtkImageView:angle:
+ * The angle the surface gets rotated about.
+ * This is in degrees and we rotate clock-wise.
+ *
+ */
+ widget_props[PROP_ANGLE] = g_param_spec_double ("angle",
+ "Angle",
+ "The angle the internal surface gets rotated about",
+ 0.0,
+ 360.0,
+ 0.0,
+ _PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
+ /**
+ * GtkImageView:rotatable:
+ * Whether or not the image can be rotated using a two-finger rotate gesture.
+ *
+ */
+ widget_props[PROP_ROTATABLE] = g_param_spec_boolean ("rotatable",
+ "Rotatable",
+ "Controls user-rotatability",
+ TRUE,
+ _PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
+/**
+ * GtkImageView:zoomable:
+ * Whether or not the image can be scaled using a two-finger zoom gesture, as well as
+ * scrolling on the #GtkImageView.
+ *
+ */
+ widget_props[PROP_ZOOMABLE] = g_param_spec_boolean ("zoomable",
+ "Zoomable",
+ "Controls user-zoomability",
+ TRUE,
+ _PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
+/**
+ * GtkImageView:snap-angle:
+ * Whether or not the angle property snaps to 90° steps. If this is enabled
+ * and the angle property gets set to a non-90° step, the new value will be
+ * set to the closest 90° step. If #GtkImageView:transitions-enabled is %TRUE,
+ * the angle change from the current angle to the new angle will be interpolated.
+ *
+ */
+ widget_props[PROP_SNAP_ANGLE] = g_param_spec_boolean ("snap-angle",
+ "Snap Angle",
+ "Snap angle to 90° steps",
+ FALSE,
+ _PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * GtkImageView:fit-allocation:
+ * If this is %TRUE, the scale the image will be drawn in will depend on the current
+ * widget allocation. The image will be scaled down to fit into the widget allocation,
+ * but never scaled up. The aspect ratio of the image will be kept at all times.
+ *
+ */
+ widget_props[PROP_FIT_ALLOCATION] = g_param_spec_boolean ("fit-allocation",
+ "Fit Allocation",
+ "Scale the image down to fit into the widget allocation",
+ FALSE,
+ _PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * GtkImageView:transitions-enabled
+ *
+ * Whether or not certain property changes will be interpolated. This affects a variety
+ * of function calls on a #GtkImageView instance, e.g. setting the angle property, the
+ * scale property, but also the angle snapping in case #GtkImageView:snap-angle is set.
+ *
+ * Note that the transitions in #GtkImageView never apply to the actual property values
+ * set and instead interpolate between the visual angle/scale, so you cannot depend on
+ * getting 60 notify signal emissions per second.
+ *
+ */
+ widget_props[PROP_TRANSITIONS_ENABLED] = g_param_spec_boolean ("transitions-enabled",
+ "Transitions Enabled",
+ "Whether scale and angle changes get interpolated",
+ TRUE,
+ _PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
+
+ g_object_class_install_properties (object_class, LAST_WIDGET_PROPERTY, widget_props);
+
+ g_object_class_override_property (object_class, PROP_HADJUSTMENT, "hadjustment");
+ g_object_class_override_property (object_class, PROP_VADJUSTMENT, "vadjustment");
+ g_object_class_override_property (object_class, PROP_HSCROLL_POLICY, "hscroll-policy");
+ g_object_class_override_property (object_class, PROP_VSCROLL_POLICY, "vscroll-policy");
+
+}
+
+/**
+ * gtk_image_view_new:
+ *
+ * Returns: A newly created #GtkImageView instance.
+ *
+ */
+GtkWidget *
+gtk_image_view_new ()
+{
+ return g_object_new (GTK_TYPE_IMAGE_VIEW, NULL);
+}
+
+static void
+gtk_image_view_replace_surface (GtkImageView *image_view,
+ cairo_surface_t *surface,
+ int scale_factor)
+{
+ GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
+
+ if (priv->image_surface)
+ cairo_surface_destroy (priv->image_surface);
+
+ if (scale_factor == 0)
+ priv->scale_factor = gtk_widget_get_scale_factor (GTK_WIDGET (image_view));
+ else
+ priv->scale_factor = scale_factor;
+
+ priv->image_surface = surface;
+ priv->size_valid = FALSE;
+
+ if (surface)
+ cairo_surface_reference (priv->image_surface);
+}
+
+static void
+gtk_image_view_update_surface (GtkImageView *image_view,
+ const GdkPixbuf *frame,
+ int scale_factor)
+{
+ GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
+ GdkWindow *window = gtk_widget_get_window (GTK_WIDGET (image_view));
+ cairo_surface_t *new_surface;
+ gboolean size_changed = TRUE;
+
+ new_surface = gdk_cairo_surface_create_from_pixbuf (frame,
+ scale_factor,
+ window);
+
+ if (priv->image_surface)
+ {
+ size_changed = (cairo_image_surface_get_width (priv->image_surface) !=
+ cairo_image_surface_get_width (new_surface)) ||
+ (cairo_image_surface_get_height (priv->image_surface) !=
+ cairo_image_surface_get_height (new_surface)) ||
+ (scale_factor != priv->scale_factor);
+ }
+
+ gtk_image_view_replace_surface (image_view,
+ new_surface,
+ scale_factor);
+
+ if (priv->fit_allocation || !size_changed)
+ gtk_widget_queue_draw (GTK_WIDGET (image_view));
+ else
+ gtk_widget_queue_resize (GTK_WIDGET (image_view));
+
+ g_assert (priv->image_surface != NULL);
+}
+
+static void
+gtk_image_view_replace_animation (GtkImageView *image_view,
+ GdkPixbufAnimation *animation,
+ int scale_factor)
+{
+ GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
+
+ if (priv->source_animation)
+ {
+ g_assert (priv->image_surface);
+ if (priv->is_animation)
+ {
+ gtk_image_view_stop_animation (image_view);
+ g_clear_object (&priv->source_animation_iter);
+ }
+ }
+
+ priv->is_animation = !gdk_pixbuf_animation_is_static_image (animation);
+
+ if (priv->is_animation)
+ {
+ priv->source_animation = animation;
+ priv->source_animation_iter = gdk_pixbuf_animation_get_iter (priv->source_animation,
+ NULL);
+ gtk_image_view_update_surface (image_view,
+ gtk_image_view_get_current_frame (image_view),
+ scale_factor);
+
+ gtk_image_view_start_animation (image_view);
+ }
+ else
+ {
+ gtk_image_view_update_surface (image_view,
+ gdk_pixbuf_animation_get_static_image (animation),
+ scale_factor);
+ g_object_unref (animation);
+ }
+
+}
+
+static void
+gtk_image_view_load_image_from_stream (GtkImageView *image_view,
+ GInputStream *input_stream,
+ int scale_factor,
+ GCancellable *cancellable,
+ GError *error)
+{
+ GdkPixbufAnimation *result;
+
+ g_assert (error == NULL);
+ result = gdk_pixbuf_animation_new_from_stream (input_stream,
+ cancellable,
+ &error);
+
+ if (!error)
+ gtk_image_view_replace_animation (image_view, result, scale_factor);
+
+ g_object_unref (input_stream);
+}
+
+static void
+gtk_image_view_load_image_contents (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ GtkImageView *image_view = source_object;
+ LoadTaskData *data = task_data;
+ GFile *file = G_FILE (data->source);
+ GError *error = NULL;
+ GFileInputStream *in_stream;
+
+ in_stream = g_file_read (file, cancellable, &error);
+
+ if (error)
+ {
+ /* in_stream is NULL */
+ g_object_unref (file);
+ g_task_return_error (task, error);
+ return;
+ }
+
+ /* Closes and unrefs the input stream */
+ gtk_image_view_load_image_from_stream (image_view,
+ G_INPUT_STREAM (in_stream),
+ data->scale_factor,
+ cancellable,
+ error);
+
+ if (error)
+ g_task_return_error (task, error);
+ else
+ g_task_return_boolean (task, TRUE);
+}
+
+static void
+gtk_image_view_load_from_input_stream (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ GtkImageView *image_view = source_object;
+ LoadTaskData *data = task_data;
+ GInputStream *in_stream = G_INPUT_STREAM (data->source);
+ GError *error = NULL;
+
+ /* Closes and unrefs the input stream */
+ gtk_image_view_load_image_from_stream (image_view,
+ in_stream,
+ data->scale_factor,
+ cancellable,
+ error);
+
+ if (error)
+ g_task_return_error (task, error);
+ else
+ g_task_return_boolean (task, TRUE);
+}
+
+/**
+ * gtk_image_view_load_from_file_async:
+ * @image_view: A #GtkImageView instance
+ * @file: The file to read from
+ * @scale_factor: Scale factor of the image. Pass 0 to use the
+ * scale factor of @image_view
+ * @cancellable: (nullable): A #GCancellable that can be used to
+ * cancel the loading operation
+ * @callback: (scope async): Callback to call once the operation finished
+ * @user_data: (closure): Data to pass to @callback
+ *
+ * Asynchronously loads an image from the given file.
+ *
+ */
+void
+gtk_image_view_load_from_file_async (GtkImageView *image_view,
+ GFile *file,
+ int scale_factor,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ LoadTaskData *task_data;
+ g_return_if_fail (GTK_IS_IMAGE_VIEW (image_view));
+ g_return_if_fail (G_IS_FILE (file));
+ g_return_if_fail (scale_factor >= 0);
+
+ task_data = g_slice_new (LoadTaskData);
+ task_data->scale_factor = scale_factor;
+ task_data->source = file;
+ g_object_ref (file);
+
+ task = g_task_new (image_view, cancellable, callback, user_data);
+ g_task_set_task_data (task, task_data, (GDestroyNotify)free_load_task_data);
+ g_task_run_in_thread (task, gtk_image_view_load_image_contents);
+
+ g_object_unref (task);
+}
+
+/**
+ * gtk_image_view_load_from_file_finish:
+ * @image_view: A #GtkImageView instance
+ * @result: A #GAsyncResult
+ * @error: (nullable): Location to store error information in case the operation fails
+ *
+ * Finished an asynchronous operation started with gtk_image_view_load_from_file_async().
+ *
+ * Returns: %TRUE if the operation succeeded, %FALSE otherwise,
+ * in which case @error will be set.
+ *
+ */
+gboolean
+gtk_image_view_load_from_file_finish (GtkImageView *image_view,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (g_task_is_valid (result, image_view), FALSE);
+
+ return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+/**
+ * gtk_image_view_load_from_stream_async:
+ * @image_view: A #GtkImageView instance
+ * @input_stream: (transfer full): Input stream to read from
+ * @scale_factor: The scale factor of the image. Pass 0 to use the scale factor
+ * of @image_view.
+ * @cancellable: (nullable): The #GCancellable used to cancel the operation
+ * @callback: (scope async): A #GAsyncReadyCallback invoked when the operation finishes
+ * @user_data: (closure): The data to pass to @callback
+ *
+ * Asynchronously loads an image from the given input stream.
+ *
+ */
+void
+gtk_image_view_load_from_stream_async (GtkImageView *image_view,
+ GInputStream *input_stream,
+ int scale_factor,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ LoadTaskData *task_data;
+ g_return_if_fail (GTK_IS_IMAGE_VIEW (image_view));
+ g_return_if_fail (G_IS_INPUT_STREAM (input_stream));
+ g_return_if_fail (scale_factor >= 0);
+
+ task_data = g_slice_new (LoadTaskData);
+ task_data->scale_factor = scale_factor;
+ task_data->source = input_stream;
+
+ task = g_task_new (image_view, cancellable, callback, user_data);
+ g_task_set_task_data (task, task_data, (GDestroyNotify)free_load_task_data);
+ g_task_run_in_thread (task, gtk_image_view_load_from_input_stream);
+
+ g_object_unref (task);
+}
+
+/**
+ * gtk_image_view_load_from_stream_finish:
+ * @image_view: A #GtkImageView instance
+ * @result: A #GAsyncResult
+ * @error: (nullable): Location to store error information on failure
+ *
+ * Finishes an asynchronous operation started by gtk_image_view_load_from_stream_async().
+ *
+ * Returns: %TRUE if the operation finished successfully, %FALSE otherwise.
+ *
+ */
+gboolean
+gtk_image_view_load_from_stream_finish (GtkImageView *image_view,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (g_task_is_valid (result, image_view), FALSE);
+
+ return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+/**
+ * gtk_image_view_set_pixbuf:
+ * @image_view: A #GtkImageView instance
+ * @pixbuf: (transfer none): A #GdkPixbuf instance
+ * @scale_factor: The scale factor of the pixbuf. Pass 0 to use the scale factor
+ * of @image_view
+ *
+ * Sets the internal image to @pixbuf. @image_view will not take ownership of @pixbuf,
+ * so it will not unref or free it in any way. If you want to unset the internal
+ * image data, look at gtk_image_view_set_surface().
+ *
+ */
+void
+gtk_image_view_set_pixbuf (GtkImageView *image_view,
+ const GdkPixbuf *pixbuf,
+ int scale_factor)
+{
+ GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
+
+ g_return_if_fail (GTK_IS_IMAGE_VIEW (image_view));
+ g_return_if_fail (GDK_IS_PIXBUF (pixbuf));
+ g_return_if_fail (scale_factor >= 0);
+
+ if (priv->is_animation)
+ {
+ g_clear_object (&priv->source_animation);
+ gtk_image_view_stop_animation (image_view);
+ priv->is_animation = FALSE;
+ }
+
+ gtk_image_view_update_surface (image_view, pixbuf, scale_factor);
+
+ gtk_image_view_update_adjustments (image_view);
+
+ /* gtk_image_view_update_surface already calls queue_draw/queue_resize */
+}
+
+/**
+ * gtk_image_view_set_surface:
+ * @image_view: A #GtkImageView instance
+ * @surface: (nullable) (transfer full): A #cairo_surface_t of type %CAIRO_SURFACE_TYPE_IMAGE, or
+ * %NULL to unset any internal image data.
+ *
+ * Sets the internal surface to @surface. @image_view will assume ownership of this surface.
+ * You can use this function to unset any internal image data by passing %NULL as @surface.
+ *
+ */
+void
+gtk_image_view_set_surface (GtkImageView *image_view,
+ cairo_surface_t *surface)
+{
+ GtkImageViewPrivate *priv = gtk_image_view_get_instance_private (image_view);
+ double scale_x = 0.0;
+ double scale_y;
+
+ g_return_if_fail (GTK_IS_IMAGE_VIEW (image_view));
+
+ if (surface)
+ {
+ g_return_if_fail (cairo_surface_get_type (surface) == CAIRO_SURFACE_TYPE_IMAGE);
+
+ cairo_surface_get_device_scale (surface, &scale_x, &scale_y);
+
+ g_return_if_fail (scale_x == scale_y);
+ }
+
+ if (priv->is_animation)
+ {
+ g_clear_object (&priv->source_animation);
+ gtk_image_view_stop_animation (image_view);
+ priv->is_animation = FALSE;
+ }
+
+ gtk_image_view_replace_surface (image_view,
+ surface,
+ scale_x);
+
+ gtk_image_view_update_adjustments (image_view);
+
+ if (priv->fit_allocation)
+ gtk_widget_queue_draw (GTK_WIDGET (image_view));
+ else
+ gtk_widget_queue_resize (GTK_WIDGET (image_view));
+}
+
+/**
+ * gtk_image_view_set_animation:
+ * @image_view: A #GtkImageView instance
+ * @animation: (transfer full): The #GdkPixbufAnimation to use
+ * @scale_factor: The scale factor of the animation. Pass 0 to use
+ * the scale factor of @image_view
+ *
+ * Takes the given #GdkPixbufAnimation and sets the internal image to that
+ * animation. This will also automatically start the animation. If you want
+ * to unset the internal image data, look at gtk_image_view_set_surface().
+ *
+ */
+void
+gtk_image_view_set_animation (GtkImageView *image_view,
+ GdkPixbufAnimation *animation,
+ int scale_factor)
+{
+ g_return_if_fail (GTK_IS_IMAGE_VIEW (image_view));
+ g_return_if_fail (GDK_IS_PIXBUF_ANIMATION (animation));
+ g_return_if_fail (scale_factor >= 0);
+
+ gtk_image_view_replace_animation (image_view, animation, scale_factor);
+}
diff --git a/libraries/libgtkimageview/gtkimageview.h b/libraries/libgtkimageview/gtkimageview.h
new file mode 100644
index 00000000..edd26fb3
--- /dev/null
+++ b/libraries/libgtkimageview/gtkimageview.h
@@ -0,0 +1,126 @@
+/* Copyright 2016 Timm Bäder
+ *
+ * GTK+ 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.
+ *
+ * GLib 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 GTK+; see the file COPYING. If not,
+ * see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GTK_IMAGE_VIEW_H__
+#define __GTK_IMAGE_VIEW_H__
+
+#include <gtk/gtk.h>
+
+
+G_BEGIN_DECLS
+
+
+#define GTK_TYPE_IMAGE_VIEW (gtk_image_view_get_type ())
+#define GTK_IMAGE_VIEW(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_IMAGE_VIEW, GtkImageView))
+#define GTK_IMAGE_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_IMAGE_VIEW, GtkImageViewClass))
+#define GTK_IS_IMAGE_VIEW(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_IMAGE_VIEW))
+#define GTK_IS_IMAGE_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_IMAGE_VIEW))
+#define GTK_IMAGE_VIEW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_IMAGE_VIEW, GtkImageViewClass))
+
+
+typedef struct _GtkImageView GtkImageView;
+typedef struct _GtkImageViewPrivate GtkImageViewPrivate;
+typedef struct _GtkImageViewClass GtkImageViewClass;
+
+
+struct _GtkImageView
+{
+ GtkWidget parent_instance;
+};
+
+struct _GtkImageViewClass
+{
+ GtkWidgetClass parent_class;
+};
+
+
+GType gtk_image_view_get_type (void) G_GNUC_CONST;
+
+GtkWidget * gtk_image_view_new (void);
+
+void gtk_image_view_set_pixbuf (GtkImageView *image_view,
+ const GdkPixbuf *pixbuf,
+ int scale_factor);
+
+void gtk_image_view_set_surface (GtkImageView *image_view,
+ cairo_surface_t *surface);
+
+void gtk_image_view_set_animation (GtkImageView *image_view,
+ GdkPixbufAnimation *animation,
+ int scale_factor);
+
+
+void gtk_image_view_load_from_file_async (GtkImageView *image_view,
+ GFile *file,
+ int scale_factor,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean gtk_image_view_load_from_file_finish (GtkImageView *image_view,
+ GAsyncResult *result,
+ GError **error);
+void gtk_image_view_load_from_stream_async (GtkImageView *image_view,
+ GInputStream *input_stream,
+ int scale_factor,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+gboolean gtk_image_view_load_from_stream_finish (GtkImageView *image_view,
+ GAsyncResult *result,
+ GError **error);
+
+void gtk_image_view_set_scale (GtkImageView *image_view,
+ double scale);
+
+double gtk_image_view_get_scale (GtkImageView *image_view);
+
+void gtk_image_view_set_angle (GtkImageView *image_view,
+ double angle);
+
+double gtk_image_view_get_angle (GtkImageView *image_view);
+
+void gtk_image_view_set_snap_angle (GtkImageView *image_view,
+ gboolean snap_angle);
+
+gboolean gtk_image_view_get_snap_angle (GtkImageView *image_view);
+
+void gtk_image_view_set_fit_allocation (GtkImageView *image_view,
+ gboolean fit_allocation);
+
+gboolean gtk_image_view_get_fit_allocation (GtkImageView *image_view);
+
+void gtk_image_view_set_rotatable (GtkImageView *image_view,
+ gboolean rotatable);
+
+gboolean gtk_image_view_get_rotatable (GtkImageView *image_view);
+
+void gtk_image_view_set_zoomable (GtkImageView *image_view,
+ gboolean zoomable);
+
+gboolean gtk_image_view_get_zoomable (GtkImageView *image_view);
+
+gboolean gtk_image_view_get_scale_set (GtkImageView *image_view);
+
+void gtk_image_view_set_transitions_enabled (GtkImageView *image_view,
+ gboolean transitions_enabled);
+
+gboolean gtk_image_view_get_transitions_enabled (GtkImageView *image_view);
+
+G_END_DECLS
+
+#endif
diff --git a/libraries/libgtkimageview/meson.build b/libraries/libgtkimageview/meson.build
new file mode 100644
index 00000000..9accd45a
--- /dev/null
+++ b/libraries/libgtkimageview/meson.build
@@ -0,0 +1,10 @@
+gtkimageview_inc = include_directories('.')
+gtkimageview_lib = static_library(
+ 'gtkimageview',
+ [
+ 'gtkimageview.c'
+ ],
+ dependencies: [
+ gtk
+ ]
+)