diff options
author | Jan Lukas Gernert <jangernert@gmail.com> | 2018-02-28 15:52:21 +0300 |
---|---|---|
committer | Brendan Long <self@brendanlong.com> | 2018-04-25 16:24:55 +0300 |
commit | 6f91c37f470b923efa8f8cc5435a33e545b4523f (patch) | |
tree | afe50978cf2ed8af4bfae219a6ce5447e602ef8b /libraries | |
parent | 78d7ced862aa491d64d9b5f135b2ba2d893b79d1 (diff) |
switch to meson build system
Diffstat (limited to 'libraries')
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   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   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(""", str) { return(set_char_wrapper(str, '"')); } + else if CMP("&;", str) { return(set_char_wrapper(str, '&')); } /* for those brain damaged ones */ + else if CMP("&", str) { return(set_char_wrapper(str, '&')); } + else if CMP(">", str) { return(set_char_wrapper(str, '>')); } + else if CMP("<", str) { return(set_char_wrapper(str, '<')); } + else if CMP("'", 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(" ", str) { return(set_char_wrapper(str, 160)); } /* no-break space */ + else if CMP("¡", str) { return(set_char_wrapper(str, 161)); } /* inverted exclamation mark */ + else if CMP("¢", str) { return(set_char_wrapper(str, 162)); } /* cent sign */ + else if CMP("£", str) { return(set_char_wrapper(str, 163)); } /* pound sterling sign */ + else if CMP("¤", str) { return(set_char_wrapper(str, 164)); } /* general currency sign */ + else if CMP("¥", str) { return(set_char_wrapper(str, 165)); } /* yen sign */ + else if CMP("¦", str) { return(set_char_wrapper(str, 166)); } /* broken (vertical) bar */ + else if CMP("§", str) { return(set_char_wrapper(str, 167)); } /* section sign */ + else if CMP("¨", str) { return(set_char_wrapper(str, 168)); } /* umlaut (dieresis) */ + else if CMP("©", str) { return(set_char_wrapper(str, 169)); } /* copyright sign */ + else if CMP("ª", str) { return(set_char_wrapper(str, 170)); } /* ordinal indicator, feminine */ + else if CMP("«", str) { return(set_char_wrapper(str, 171)); } /* angle quotation mark, left */ + else if CMP("¬", str) { return(set_char_wrapper(str, 172)); } /* not sign */ + else if CMP("­", str) { return(set_char_wrapper(str, '\0')); } /* soft hyphen, just swallow it */ + else if CMP("®", str) { return(set_char_wrapper(str, 174)); } /* registered sign */ + else if CMP("¯", str) { return(set_char_wrapper(str, 175)); } /* macron */ + else if CMP("°", str) { return(set_char_wrapper(str, 176)); } /* degree sign */ + else if CMP("±", str) { return(set_char_wrapper(str, 177)); } /* plus-or-minus sign */ + else if CMP("²", str) { return(set_char_wrapper(str, 178)); } /* superscript two */ + else if CMP("³", str) { return(set_char_wrapper(str, 179)); } /* superscript three */ + else if CMP("´", str) { return(set_char_wrapper(str, 180)); } /* acute accent */ + else if CMP("µ", str) { return(set_char_wrapper(str, 181)); } /* micro sign */ + else if CMP("¶", str) { return(set_char_wrapper(str, 182)); } /* pilcrow (paragraph sign) */ + else if CMP("·", str) { return(set_char_wrapper(str, 183)); } /* middle dot */ + else if CMP("¸", str) { return(set_char_wrapper(str, 184)); } /* cedilla */ + else if CMP("¹", str) { return(set_char_wrapper(str, 185)); } /* superscript one */ + else if CMP("º", str) { return(set_char_wrapper(str, 186)); } /* ordinal indicator, masculine */ + else if CMP("»", str) { return(set_char_wrapper(str, 187)); } /* angle quotation mark, right */ + else if CMP("¼", str) { return(set_char_wrapper(str, 188)); } /* fraction one-quarter */ + else if CMP("½", str) { return(set_char_wrapper(str, 189)); } /* fraction one-half */ + else if CMP("¾", str) { return(set_char_wrapper(str, 190)); } /* fraction three-quarters */ + else if CMP("¿", str) { return(set_char_wrapper(str, 191)); } /* inverted question mark */ + else if CMP("À", str) { return(set_char_wrapper(str, 192)); } /* capital A, grave accent */ + else if CMP("Á", str) { return(set_char_wrapper(str, 193)); } /* capital A, acute accent */ + else if CMP("Â", str) { return(set_char_wrapper(str, 194)); } /* capital A, circumflex accent */ + else if CMP("Ã", str) { return(set_char_wrapper(str, 195)); } /* capital A, tilde */ + else if CMP("Ä", str) { return(set_char_wrapper(str, 196)); } /* capital A, dieresis or umlaut mark */ + else if CMP("Å", str) { return(set_char_wrapper(str, 197)); } /* capital A, ring */ + else if CMP("Æ", str) { return(set_char_wrapper(str, 198)); } /* capital AE diphthong (ligature) */ + else if CMP("Ç", str) { return(set_char_wrapper(str, 199)); } /* capital C, cedilla */ + else if CMP("È", str) { return(set_char_wrapper(str, 200)); } /* capital E, grave accent */ + else if CMP("É", str) { return(set_char_wrapper(str, 201)); } /* capital E, acute accent */ + else if CMP("Ê", str) { return(set_char_wrapper(str, 202)); } /* capital E, circumflex accent */ + else if CMP("Ë", str) { return(set_char_wrapper(str, 203)); } /* capital E, dieresis or umlaut mark */ + else if CMP("Ì", str) { return(set_char_wrapper(str, 204)); } /* capital I, grave accent */ + else if CMP("Í", str) { return(set_char_wrapper(str, 205)); } /* capital I, acute accent */ + else if CMP("Î", str) { return(set_char_wrapper(str, 206)); } /* capital I, circumflex accent */ + else if CMP("Ï", str) { return(set_char_wrapper(str, 207)); } /* capital I, dieresis or umlaut mark */ + else if CMP("Ð", str) { return(set_char_wrapper(str, 208)); } /* capital Eth, Icelandic */ + else if CMP("Ñ", str) { return(set_char_wrapper(str, 209)); } /* capital N, tilde */ + else if CMP("Ò", str) { return(set_char_wrapper(str, 210)); } /* capital O, grave accent */ + else if CMP("Ó", str) { return(set_char_wrapper(str, 211)); } /* capital O, acute accent */ + else if CMP("Ô", str) { return(set_char_wrapper(str, 212)); } /* capital O, circumflex accent */ + else if CMP("Õ", str) { return(set_char_wrapper(str, 213)); } /* capital O, tilde */ + else if CMP("Ö", str) { return(set_char_wrapper(str, 214)); } /* capital O, dieresis or umlaut mark */ + else if CMP("×", str) { return(set_char_wrapper(str, 215)); } /* multiply sign */ + else if CMP("Ø", str) { return(set_char_wrapper(str, 216)); } /* capital O, slash */ + else if CMP("Ù", str) { return(set_char_wrapper(str, 217)); } /* capital U, grave accent */ + else if CMP("Ú", str) { return(set_char_wrapper(str, 218)); } /* capital U, acute accent */ + else if CMP("Û", str) { return(set_char_wrapper(str, 219)); } /* capital U, circumflex accent */ + else if CMP("Ü", str) { return(set_char_wrapper(str, 220)); } /* capital U, dieresis or umlaut mark */ + else if CMP("Ý", str) { return(set_char_wrapper(str, 221)); } /* capital Y, acute accent */ + else if CMP("Þ", str) { return(set_char_wrapper(str, 222)); } /* capital THORN, Icelandic */ + else if CMP("ß", str) { return(set_char_wrapper(str, 223)); } /* small sharp s, German (sz ligature) */ + else if CMP("à", str) { return(set_char_wrapper(str, 224)); } /* small a, grave accent */ + else if CMP("á", str) { return(set_char_wrapper(str, 225)); } /* small a, acute accent */ + else if CMP("â", str) { return(set_char_wrapper(str, 226)); } /* small a, circumflex accent */ + else if CMP("ã", str) { return(set_char_wrapper(str, 227)); } /* small a, tilde */ + else if CMP("ä", str) { return(set_char_wrapper(str, 228)); } /* small a, dieresis or umlaut mark */ + else if CMP("å", str) { return(set_char_wrapper(str, 229)); } /* small a, ring */ + else if CMP("æ", str) { return(set_char_wrapper(str, 230)); } /* small ae diphthong (ligature) */ + else if CMP("ç", str) { return(set_char_wrapper(str, 231)); } /* small c, cedilla */ + else if CMP("è", str) { return(set_char_wrapper(str, 232)); } /* small e, grave accent */ + else if CMP("é", str) { return(set_char_wrapper(str, 233)); } /* small e, acute accent */ + else if CMP("ê", str) { return(set_char_wrapper(str, 234)); } /* small e, circumflex accent */ + else if CMP("ë", str) { return(set_char_wrapper(str, 235)); } /* small e, dieresis or umlaut mark */ + else if CMP("ì", str) { return(set_char_wrapper(str, 236)); } /* small i, grave accent */ + else if CMP("í", str) { return(set_char_wrapper(str, 237)); } /* small i, acute accent */ + else if CMP("î", str) { return(set_char_wrapper(str, 238)); } /* small i, circumflex accent */ + else if CMP("ï", str) { return(set_char_wrapper(str, 239)); } /* small i, dieresis or umlaut mark */ + else if CMP("ð", str) { return(set_char_wrapper(str, 240)); } /* small eth, Icelandic */ + else if CMP("ñ", str) { return(set_char_wrapper(str, 241)); } /* small n, tilde */ + else if CMP("ò", str) { return(set_char_wrapper(str, 242)); } /* small o, grave accent */ + else if CMP("ó", str) { return(set_char_wrapper(str, 243)); } /* small o, acute accent */ + else if CMP("ô", str) { return(set_char_wrapper(str, 244)); } /* small o, circumflex accent */ + else if CMP("õ", str) { return(set_char_wrapper(str, 245)); } /* small o, tilde */ + else if CMP("ö", str) { return(set_char_wrapper(str, 246)); } /* small o, dieresis or umlaut mark */ + else if CMP("÷", str) { return(set_char_wrapper(str, 247)); } /* divide sign */ + else if CMP("ø", str) { return(set_char_wrapper(str, 248)); } /* small o, slash */ + else if CMP("ù", str) { return(set_char_wrapper(str, 249)); } /* small u, grave accent */ + else if CMP("ú", str) { return(set_char_wrapper(str, 250)); } /* small u, acute accent */ + else if CMP("û", str) { return(set_char_wrapper(str, 251)); } /* small u, circumflex accent */ + else if CMP("ü", str) { return(set_char_wrapper(str, 252)); } /* small u, dieresis or umlaut mark */ + else if CMP("ý", str) { return(set_char_wrapper(str, 253)); } /* small y, acute accent */ + else if CMP("þ", str) { return(set_char_wrapper(str, 254)); } /* small thorn, Icelandic */ + else if CMP("ÿ", str) { return(set_char_wrapper(str, 255)); } /* small y, dieresis or umlaut mark */ + else if CMP("ƒ", str) { return(set_char_wrapper(str, 402)); } + else if CMP("Α", str) { return(set_char_wrapper(str, 913)); } + else if CMP("Β", str) { return(set_char_wrapper(str, 914)); } + else if CMP("Γ", str) { return(set_char_wrapper(str, 915)); } + else if CMP("Δ", str) { return(set_char_wrapper(str, 916)); } + else if CMP("Ε", str) { return(set_char_wrapper(str, 917)); } + else if CMP("Ζ", str) { return(set_char_wrapper(str, 918)); } + else if CMP("Η", str) { return(set_char_wrapper(str, 919)); } + else if CMP("Θ", str) { return(set_char_wrapper(str, 920)); } + else if CMP("Ι", str) { return(set_char_wrapper(str, 921)); } + else if CMP("Κ", str) { return(set_char_wrapper(str, 922)); } + else if CMP("Λ", str) { return(set_char_wrapper(str, 923)); } + else if CMP("Μ", str) { return(set_char_wrapper(str, 924)); } + else if CMP("Ν", str) { return(set_char_wrapper(str, 925)); } + else if CMP("Ξ", str) { return(set_char_wrapper(str, 926)); } + else if CMP("Ο", str) { return(set_char_wrapper(str, 927)); } + else if CMP("Π", str) { return(set_char_wrapper(str, 928)); } + else if CMP("Ρ", str) { return(set_char_wrapper(str, 929)); } + else if CMP("Σ", str) { return(set_char_wrapper(str, 931)); } + else if CMP("Τ", str) { return(set_char_wrapper(str, 932)); } + else if CMP("Υ", str) { return(set_char_wrapper(str, 933)); } + else if CMP("Φ", str) { return(set_char_wrapper(str, 934)); } + else if CMP("Χ", str) { return(set_char_wrapper(str, 935)); } + else if CMP("Ψ", str) { return(set_char_wrapper(str, 936)); } + else if CMP("Ω", str) { return(set_char_wrapper(str, 937)); } + else if CMP("α", str) { return(set_char_wrapper(str, 945)); } + else if CMP("β", str) { return(set_char_wrapper(str, 946)); } + else if CMP("γ", str) { return(set_char_wrapper(str, 947)); } + else if CMP("δ", str) { return(set_char_wrapper(str, 948)); } + else if CMP("ε", str) { return(set_char_wrapper(str, 949)); } + else if CMP("ζ", str) { return(set_char_wrapper(str, 950)); } + else if CMP("η", str) { return(set_char_wrapper(str, 951)); } + else if CMP("θ", str) { return(set_char_wrapper(str, 952)); } + else if CMP("ι", str) { return(set_char_wrapper(str, 953)); } + else if CMP("κ", str) { return(set_char_wrapper(str, 954)); } + else if CMP("λ", str) { return(set_char_wrapper(str, 955)); } + else if CMP("μ", str) { return(set_char_wrapper(str, 956)); } + else if CMP("ν", str) { return(set_char_wrapper(str, 957)); } + else if CMP("ξ", str) { return(set_char_wrapper(str, 958)); } + else if CMP("ο", str) { return(set_char_wrapper(str, 959)); } + else if CMP("π", str) { return(set_char_wrapper(str, 960)); } + else if CMP("ρ", str) { return(set_char_wrapper(str, 961)); } + else if CMP("ς", str) { return(set_char_wrapper(str, 962)); } + else if CMP("σ", str) { return(set_char_wrapper(str, 963)); } + else if CMP("τ", str) { return(set_char_wrapper(str, 964)); } + else if CMP("υ", str) { return(set_char_wrapper(str, 965)); } + else if CMP("φ", str) { return(set_char_wrapper(str, 966)); } + else if CMP("χ", str) { return(set_char_wrapper(str, 967)); } + else if CMP("ψ", str) { return(set_char_wrapper(str, 968)); } + else if CMP("ω", str) { return(set_char_wrapper(str, 969)); } + else if CMP("ϑ", str) { return(set_char_wrapper(str, 977)); } + else if CMP("ϒ", str) { return(set_char_wrapper(str, 978)); } + else if CMP("ϖ", str) { return(set_char_wrapper(str, 982)); } + else if CMP("•", str) { return(set_char_wrapper(str, 8226)); } + else if CMP("…", str) { return(set_char_wrapper(str, 8230)); } + else if CMP("′", str) { return(set_char_wrapper(str, 8242)); } + else if CMP("″", str) { return(set_char_wrapper(str, 8243)); } + else if CMP("‾", str) { return(set_char_wrapper(str, 8254)); } + else if CMP("⁄", str) { return(set_char_wrapper(str, 8260)); } + else if CMP("℘", str) { return(set_char_wrapper(str, 8472)); } + else if CMP("ℑ", str) { return(set_char_wrapper(str, 8465)); } + else if CMP("ℜ", str) { return(set_char_wrapper(str, 8476)); } + else if CMP("™", str) { return(set_char_wrapper(str, 8482)); } + else if CMP("ℵ", str) { return(set_char_wrapper(str, 8501)); } + else if CMP("←", str) { return(set_char_wrapper(str, 8592)); } + else if CMP("↑", str) { return(set_char_wrapper(str, 8593)); } + else if CMP("→", str) { return(set_char_wrapper(str, 8594)); } + else if CMP("↓", str) { return(set_char_wrapper(str, 8595)); } + else if CMP("↔", str) { return(set_char_wrapper(str, 8596)); } + else if CMP("↵", str) { return(set_char_wrapper(str, 8629)); } + else if CMP("⇐", str) { return(set_char_wrapper(str, 8656)); } + else if CMP("⇑", str) { return(set_char_wrapper(str, 8657)); } + else if CMP("⇒", str) { return(set_char_wrapper(str, 8658)); } + else if CMP("⇓", str) { return(set_char_wrapper(str, 8659)); } + else if CMP("⇔", str) { return(set_char_wrapper(str, 8660)); } + else if CMP("∀", str) { return(set_char_wrapper(str, 8704)); } + else if CMP("∂", str) { return(set_char_wrapper(str, 8706)); } + else if CMP("∃", str) { return(set_char_wrapper(str, 8707)); } + else if CMP("∅", str) { return(set_char_wrapper(str, 8709)); } + else if CMP("∇", str) { return(set_char_wrapper(str, 8711)); } + else if CMP("∈", str) { return(set_char_wrapper(str, 8712)); } + else if CMP("∉", str) { return(set_char_wrapper(str, 8713)); } + else if CMP("∋", str) { return(set_char_wrapper(str, 8715)); } + else if CMP("∏", str) { return(set_char_wrapper(str, 8719)); } + else if CMP("∑", str) { return(set_char_wrapper(str, 8721)); } + else if CMP("−", str) { return(set_char_wrapper(str, 8722)); } + else if CMP("∗", str) { return(set_char_wrapper(str, 8727)); } + else if CMP("√", str) { return(set_char_wrapper(str, 8730)); } + else if CMP("∝", str) { return(set_char_wrapper(str, 8733)); } + else if CMP("∞", str) { return(set_char_wrapper(str, 8734)); } + else if CMP("∠", str) { return(set_char_wrapper(str, 8736)); } + else if CMP("∧", str) { return(set_char_wrapper(str, 8743)); } + else if CMP("∨", str) { return(set_char_wrapper(str, 8744)); } + else if CMP("∩", str) { return(set_char_wrapper(str, 8745)); } + else if CMP("∪", str) { return(set_char_wrapper(str, 8746)); } + else if CMP("∫", str) { return(set_char_wrapper(str, 8747)); } + else if CMP("∴", str) { return(set_char_wrapper(str, 8756)); } + else if CMP("∼", str) { return(set_char_wrapper(str, 8764)); } + else if CMP("≅", str) { return(set_char_wrapper(str, 8773)); } + else if CMP("≈", str) { return(set_char_wrapper(str, 8776)); } + else if CMP("≠", str) { return(set_char_wrapper(str, 8800)); } + else if CMP("≡", str) { return(set_char_wrapper(str, 8801)); } + else if CMP("≤", str) { return(set_char_wrapper(str, 8804)); } + else if CMP("≥", str) { return(set_char_wrapper(str, 8805)); } + else if CMP("⊂", str) { return(set_char_wrapper(str, 8834)); } + else if CMP("⊃", str) { return(set_char_wrapper(str, 8835)); } + else if CMP("⊄", str) { return(set_char_wrapper(str, 8836)); } + else if CMP("⊆", str) { return(set_char_wrapper(str, 8838)); } + else if CMP("⊇", str) { return(set_char_wrapper(str, 8839)); } + else if CMP("⊕", str) { return(set_char_wrapper(str, 8853)); } + else if CMP("⊗", str) { return(set_char_wrapper(str, 8855)); } + else if CMP("⊥", str) { return(set_char_wrapper(str, 8869)); } + else if CMP("⋅", str) { return(set_char_wrapper(str, 8901)); } + else if CMP("⌈", str) { return(set_char_wrapper(str, 8968)); } + else if CMP("⌉", str) { return(set_char_wrapper(str, 8969)); } + else if CMP("⌊", str) { return(set_char_wrapper(str, 8970)); } + else if CMP("⌋", str) { return(set_char_wrapper(str, 8971)); } + else if CMP("⟨", str) { return(set_char_wrapper(str, 9001)); } + else if CMP("⟩", str) { return(set_char_wrapper(str, 9002)); } + else if CMP("◊", str) { return(set_char_wrapper(str, 9674)); } + else if CMP("♠", str) { return(set_char_wrapper(str, 9824)); } + else if CMP("♣", str) { return(set_char_wrapper(str, 9827)); } + else if CMP("♥", str) { return(set_char_wrapper(str, 9829)); } + else if CMP("♦", str) { return(set_char_wrapper(str, 9830)); } + else if CMP(""", str) { return(set_char_wrapper(str, 34)); } + else if CMP("&", str) { return(set_char_wrapper(str, 38)); } + else if CMP("'", str) { return(set_char_wrapper(str, 39)); } + else if CMP("<", str) { return(set_char_wrapper(str, 60)); } + else if CMP(">", str) { return(set_char_wrapper(str, 62)); } + else if CMP("Œ", str) { return(set_char_wrapper(str, 338)); } + else if CMP("œ", str) { return(set_char_wrapper(str, 339)); } + else if CMP("Š", str) { return(set_char_wrapper(str, 352)); } + else if CMP("š", str) { return(set_char_wrapper(str, 353)); } + else if CMP("Ÿ", str) { return(set_char_wrapper(str, 376)); } + else if CMP("ˆ", str) { return(set_char_wrapper(str, 710)); } + else if CMP("˜", str) { return(set_char_wrapper(str, 732)); } + else if CMP(" ", str) { return(set_char_wrapper(str, 8194)); } + else if CMP(" ", str) { return(set_char_wrapper(str, 8195)); } + else if CMP(" ", str) { return(set_char_wrapper(str, 8201)); } + else if CMP("‌", str) { return(set_char_wrapper(str, 8204)); } + else if CMP("‍", str) { return(set_char_wrapper(str, 8205)); } + else if CMP("‎", str) { return(set_char_wrapper(str, 8206)); } + else if CMP("‏", str) { return(set_char_wrapper(str, 8207)); } + else if CMP("–", str) { return(set_char_wrapper(str, 8211)); } + else if CMP("—", str) { return(set_char_wrapper(str, 8212)); } + else if CMP("‘", str) { return(set_char_wrapper(str, 8216)); } + else if CMP("’", str) { return(set_char_wrapper(str, 8217)); } + else if CMP("‚", str) { return(set_char_wrapper(str, 8218)); } + else if CMP("“", str) { return(set_char_wrapper(str, 8220)); } + else if CMP("”", str) { return(set_char_wrapper(str, 8221)); } + else if CMP("„", str) { return(set_char_wrapper(str, 8222)); } + else if CMP("†", str) { return(set_char_wrapper(str, 8224)); } + else if CMP("‡", str) { return(set_char_wrapper(str, 8225)); } + else if CMP("‰", str) { return(set_char_wrapper(str, 8240)); } + else if CMP("‹", str) { return(set_char_wrapper(str, 8249)); } + else if CMP("›", str) { return(set_char_wrapper(str, 8250)); } + else if CMP("€", 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("€", 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("‚", 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("ƒ", 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("„", s) { CPYSL(s, "\""); } + else if (number==8222) { CPYSL(s, "\""); } + + else if (number==133) { CPYSL(s, "..."); } /* Horizontal Ellipsis */ + else if CMP("…", s) { CPYSL(s, "..."); } /* Horizontal Ellipsis */ + else if (number==8230) { CPYSL(s, "..."); } /* Horizontal Ellipsis */ + + /* Dagger */ + else if (number==134) { CPYSL(s, "/-"); } + else if CMP("†", s) { CPYSL(s, "/-"); } + else if (number==8224) { CPYSL(s, "/-"); } + + /* Double Dagger */ + else if (number==135) { CPYSL(s, "/="); } + else if CMP("‡", s) { CPYSL(s, "/="); } + else if (number==8225) { CPYSL(s, "/="); } + + /* Modifier Letter Circumflex Accent */ + else if (number==136) { set_char(s, '^'); } + else if CMP("ˆ", 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("‰", 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("Š", 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("‹", 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("Œ", 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("‘", 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("’", 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("“", 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("”", s) { set_char(s, '"'); } + else if (number==8221) { set_char(s, '"'); } + + /* Bullet */ + else if (number==149) { set_char(s, '*'); } + else if CMP("•", s) { set_char(s, '*'); } + else if (number==8226) { set_char(s, '*'); } + + /* En Dash */ + else if (number==150) { set_char(s, '-'); } + else if CMP("–", s) { set_char(s, '-'); } + else if (number==8211) { set_char(s, '-'); } + + /* Em Dash */ + else if (number==151) { CPYSL(s, "--"); } + else if CMP("—", s) { CPYSL(s, "--"); } + else if (number==8212) { CPYSL(s, "--"); } + + /* Small Tilde */ + else if (number==152) { set_char(s, '~'); } + else if CMP("˜", s) { set_char(s, '~'); } + else if (number==732) { set_char(s, '~'); } + + /* Trade Mark Sign */ + else if (number==153) { CPYSL(s, "[tm]"); } + else if CMP("™", 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("š", 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("›", 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("œ", 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("Ÿ", 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 "©" or "©" */ +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 + ] +) |