/* * Copyright © 2008 Keith Packard * * This library is free software; you can redistribute it and/or * modify it either under the terms of the GNU Lesser General Public * License version 2.1 as published by the Free Software Foundation * (the "LGPL") or, at your option, under the terms of the Mozilla * Public License Version 1.1 (the "MPL"). If you do not alter this * notice, a recipient may use your version of this file under either * the MPL or the LGPL. * * You should have received a copy of the LGPL along with this library * in the file COPYING-LGPL-2.1; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * You should have received a copy of the MPL along with this library * in the file COPYING-MPL-1.1 * * The contents of this file are subject to the Mozilla Public License * Version 1.1 (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.mozilla.org/MPL/ * * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY * OF ANY KIND, either express or implied. See the LGPL or the MPL for * the specific language governing rights and limitations. * * The Original Code is the cairo graphics library. * * The Initial Developer of the Original Code is Keith Packard * * Contributor(s): * Keith Packard */ autoload Cairo; autoimport Mutex; namespace Nichrome { import Cairo; /* * Event types */ public typedef enum { press, release, double, triple } button_type_t; public typedef struct { int time; real x; real y; } pos_event_t; public typedef pos_event_t + struct { button_type_t type; int button; } button_event_t; public typedef enum { inside, outside, entering, leaving } motion_type_t; public typedef pos_event_t + struct { motion_type_t type; } motion_event_t; public typedef enum { press, release } key_type_t; public typedef struct { key_type_t type; int time; string key; string text; } key_event_t; public typedef nichrome_t; public typedef widget_t; public typedef container_t; public typedef contained_t; /* * Containers, as seen by the toolkit */ public typedef void(&container_t, &contained_t) resize_handler_t; public typedef void(&contained_t, rect_t) configure_handler_t; public typedef struct { resize_handler_t resize; } container_t; public typedef void(cairo_t cr, &widget_t widget) draw_t; public typedef void(&widget_t, &button_event_t) button_handler_t; public typedef void(&widget_t, &motion_event_t) motion_handler_t; public typedef void(&widget_t, &key_event_t) key_handler_t; public typedef void(&widget_t, bool focus) focus_handler_t; public typedef void(&contained_t) remove_handler_t; public typedef void(&poly object, int indent) print_handler_t; public typedef bool(&key_event_t) global_key_handler_t; public void do_indent (int indent) { while (indent-- > 0) printf(" "); } public typedef struct { &container_t container; configure_handler_t configure; print_handler_t print; remove_handler_t remove; bool active; } contained_t; /* * Widgets, as seen by the toolkit */ public typedef contained_t + struct { &nichrome_t nichrome; rect_t geometry; rect_t extents; draw_t outline; draw_t draw; draw_t natural; button_handler_t button; motion_handler_t motion; key_handler_t key; focus_handler_t focus; } widget_t; /* * An application surface */ public typedef void(&nichrome_t nichrome) callback_t; public typedef struct { surface_t surface; file event; bool draw; bool resize; bool shown; bool want_shown; point_t scale; (&widget_t)[...] widgets; &widget_t key_focus; &widget_t mouse_grab; int button_down; &widget_t mouse_in; callback_t destroy; callback_t configure; cairo_t outline_cr; &widget_t outline_widget; bool running; mutex drawing; thread self; int suspend_draw; global_key_handler_t global_key; } nichrome_t; public widget_t no_widget; public cairo_t cairo (&nichrome_t nichrome) { cairo_t cr = create (nichrome.surface); scale (cr, nichrome.scale.x, nichrome.scale.y); return cr; } public void add (&nichrome_t nichrome, &widget_t widget) { &nichrome.widgets[dim(nichrome.widgets)] = &widget; nichrome.draw = true; } public exception unknown_widget (&nichrome_t nichrome, &widget_t widget); int widget_pos (&nichrome_t nichrome, &widget_t widget) { for (int i = 0; i < dim(nichrome.widgets); i++) if (&nichrome.widgets[i] == &widget) return i; raise unknown_widget (&nichrome, &widget); } public void remove (&nichrome_t nichrome, &widget_t widget) { twixt (Mutex::acquire (nichrome.drawing); Mutex::release (nichrome.drawing)) { for (int i = widget_pos (&nichrome, &widget); i < dim(nichrome.widgets) - 1; i++) { &nichrome.widgets[i] = &nichrome.widgets[i + 1]; } setdim (nichrome.widgets, dim(nichrome.widgets) - 1); nichrome.draw = true; } } void draw (&nichrome_t nichrome); public void redraw (&nichrome_t nichrome) { if (is_uninit (&nichrome.self)) return; if (Thread::current() != nichrome.self && nichrome.suspend_draw == 0) draw (&nichrome); else nichrome.draw = true; } public void suspend_draw(&nichrome_t nichrome) { ++nichrome.suspend_draw; } public void release_draw(&nichrome_t nichrome) { assert (nichrome.suspend_draw > 0, "mismatched suspend_draw/release_draw calls"); --nichrome.suspend_draw; if (!nichrome.running) return; if (nichrome.suspend_draw == 0 && nichrome.draw && Thread::current() != nichrome.self) draw(&nichrome); } public void set_active(&nichrome_t nichrome, &contained_t contained, bool active) { if (active != contained.active) { contained.active = active; redraw (&nichrome); } } public void set_global_key(&nichrome_t nichrome, global_key_handler_t global_key) { nichrome.global_key = global_key; } public namespace Widget { protected void configure (&widget_t widget, rect_t geometry) { widget.geometry = geometry; } protected void natural (cairo_t cr, &widget_t widget) = ◊; protected void remove (&widget_t widget) { Nichrome::remove (&widget.nichrome, &widget); } protected void print (&widget_t widget, int indent) { do_indent(indent); printf ("widget %v\n", widget.geometry); } protected void init (&nichrome_t nichrome, &widget_t widget) { widget.geometry = (rect_t) { x = 0, y = 0, width = 0, height = 0 }; widget.extents = widget.geometry; &widget.nichrome = &nichrome; widget.configure = configure; widget.natural = natural; widget.outline = natural; widget.draw = natural; widget.remove = remove; widget.print = print; widget.active = true; Nichrome::add (&nichrome, &widget); } protected void redraw (&widget_t widget) { Nichrome::redraw (&widget.nichrome); } protected void resize (&widget_t widget) { if (!is_uninit (&widget.container)) { if (widget.nichrome.drawing->owner != (mutex_owner.owner) Thread::current()) { twixt (Mutex::acquire (widget.nichrome.drawing); Mutex::release (widget.nichrome.drawing)) widget.container.resize (&widget.container, &widget); } else widget.container.resize (&widget.container, &widget); } else redraw (&widget); } protected void reoutline (&widget_t widget) { if (&widget.nichrome.outline_widget == &widget) &widget.nichrome.outline_widget = &no_widget; } protected bool in_widget (&widget_t widget, real x, real y) { bool in; if (x < widget.extents.x || widget.extents.x + widget.extents.width <= x) return false; if (y < widget.extents.y || widget.extents.y + widget.extents.height <= y) return false; cairo_t cr = widget.nichrome.outline_cr; if (&widget.nichrome.outline_widget != &widget) { new_path (cr); save (cr); translate (cr, widget.geometry.x, widget.geometry.y); widget.outline (cr, &widget); restore (cr); &widget.nichrome.outline_widget = &widget; } in = in_fill (cr, x, y); return in; } protected string default_font = "sans"; protected rgba_color_t default_color = { red = 0.6, green = 0.6, blue = 0.6, alpha = 0.75 }; } public *widget_t xy_to_widget (&nichrome_t nichrome, real x, real y) { twixt (Mutex::acquire(nichrome.drawing); Mutex::release(nichrome.drawing)) { point_t a = device_to_user (nichrome.outline_cr, (point_t) { x = x, y = y }); x = a.x; y = a.y; for (int w = 0; w < dim (nichrome.widgets); w++) { &widget_t widget = &nichrome.widgets[w]; if (widget.active && Widget::in_widget (&widget, x, y)) return &widget; } } return &no_widget; } /* * As usual, mouse grabs make this more complicated than we'd like. * * nichrome.mouse_in tracks the widget containing the pointer. * nichrome.mouse_grab tracks the grabbing widget.* * nichrome.button_down tracks which buttons are down * * While grabbed, events are delivered only to the grab widget. * Otherwise, events are delivered to the containing widget. * Pressing any button grabs the mouse. * Releasing all buttons ungrabs the mouse. This can also * generate an entering motion event when the ungrab * occurs over another widget. */ void deliver_button (&widget_t widget, button_event_t event) { if (!is_uninit (&widget.button)) { event.x -= widget.geometry.x; event.y -= widget.geometry.y; widget.button (&widget, &event); } } void deliver_motion (&widget_t widget, motion_event_t event) { if (!is_uninit (&widget.motion)) { event.x -= widget.geometry.x; event.y -= widget.geometry.y; widget.motion (&widget, &event); } } public void dispatch_button (&nichrome_t nichrome, &button_event_t event) { &widget_t widget; if (&nichrome.mouse_grab != &no_widget) &widget = &nichrome.mouse_grab; else { &widget = &nichrome.mouse_in; if (&widget == &no_widget) return; } enum switch (event.type) { case press: &nichrome.mouse_grab = &widget; nichrome.button_down |= 1 << event.button; break; case release: if (&nichrome.mouse_grab == &no_widget) return; break; default: break; } deliver_button (&widget, event); if (event.type == button_type_t.release) { nichrome.button_down &= ~(1 << event.button); if (nichrome.button_down == 0) { &nichrome.mouse_grab = &no_widget; /* * On ungrab, deliver an entering motion * to the widget under the pointer */ if (&widget != &nichrome.mouse_in) deliver_motion (&nichrome.mouse_in, (motion_event_t) { type = motion_type_t.entering, time = event.time, x = event.x, y = event.y }); } } } public void dispatch_motion (&nichrome_t nichrome, &motion_event_t event) { &widget_t in = xy_to_widget (&nichrome, event.x, event.y); if (&nichrome.mouse_grab == &no_widget) { /* * Without a mouse grab, deliver events to the containing widget */ if (&in != &nichrome.mouse_in) { event.type = motion_type_t.leaving; deliver_motion (&nichrome.mouse_in, event); event.type = motion_type_t.entering; } else event.type = motion_type_t.inside; deliver_motion (&in, event); } else { /* * With mouse grab, deliver events to the grab widget */ if (&in != &nichrome.mouse_grab) { if (&nichrome.mouse_in == &nichrome.mouse_grab) event.type = motion_type_t.leaving; else event.type = motion_type_t.outside; } else { if (&nichrome.mouse_in == &nichrome.mouse_grab) event.type = motion_type_t.inside; else event.type = motion_type_t.entering; } deliver_motion (&nichrome.mouse_grab, event); } &nichrome.mouse_in = ∈ } public void dispatch_key (&nichrome_t nichrome, &key_event_t event) { if (!is_uninit(&nichrome.global_key)) if (nichrome.global_key(&event)) return; &widget_t widget = &nichrome.key_focus; if (&widget != &no_widget) if (!is_uninit (&widget.key)) widget.key (&widget, &event); } public void set_key_focus (&nichrome_t nichrome, &widget_t widget) { if (&widget != &nichrome.key_focus) { if (!is_uninit (&nichrome.key_focus)) if (!is_uninit(&nichrome.key_focus.focus)) nichrome.key_focus.focus(&nichrome.key_focus, false); &nichrome.key_focus = &widget; if (&widget != &no_widget) if (!is_uninit(&widget.focus)) widget.focus(&widget, true); } } string hex_utf8 (int[...] bytes) { string s = ""; for (int i = 0; i < dim (bytes); i++) { int first = bytes[i]; int c; int extra = 0; if ((first & 0x80) == 0) { c = first; } else if ((first & 0xc0) != 0xc0) { c = first; } else { int mask = 0x40; while ((first & mask) != 0) { extra++; mask >>= 1; } c = first & (mask - 1); while (i < dim (bytes) && extra-- > 0) c = c << 6 | (bytes[i++] & 0x3f); } s += String::new (c); } return s; } void draw (&nichrome_t nichrome) { if (Mutex::owner(nichrome.drawing) == (mutex_owner.owner) Thread::current()) { nichrome.draw = true; return; } twixt (Mutex::acquire (nichrome.drawing); Mutex::release (nichrome.drawing)) { if (nichrome.shown != nichrome.want_shown) { Cairo::Surface::set_window_shown(nichrome.surface, nichrome.want_shown); nichrome.shown = nichrome.want_shown; } if (!nichrome.shown) return; if (nichrome.resize) { nichrome.resize = false; Cairo::destroy (nichrome.outline_cr); nichrome.outline_cr = cairo (&nichrome); &nichrome.outline_widget = &no_widget; if (!is_uninit (&nichrome.configure)) nichrome.configure (&nichrome); } nichrome.draw = false; cairo_t cr = cairo (&nichrome); int i; disable (cr); set_source_rgba (cr, 1, 1, 1, 1); paint (cr); for (i = dim (nichrome.widgets) - 1; i >= 0; i--) { &widget_t widget = &nichrome.widgets[i]; if (!widget.active) continue; if (!is_uninit (&widget.draw)) { save (cr); translate (cr, widget.geometry.x, widget.geometry.y); widget.draw (cr, &widget); restore (cr); if (status (cr) != status_t.SUCCESS) printf ("draw status %g for %g\n", status (cr), &widget); } if (!is_uninit (&widget.outline)) { save (cr); translate (cr, widget.geometry.x, widget.geometry.y); widget.outline (cr, &widget); restore (cr); widget.extents = fill_extents (cr); new_path (cr); if (status (cr) != status_t.SUCCESS) printf ("draw status %g for %g\n", status (cr), &widget); } } enable (cr); destroy (cr); } } public void show (&nichrome_t nichrome) { nichrome.want_shown = true; if (!nichrome.shown) redraw(&nichrome); } public void hide (&nichrome_t nichrome) { if (nichrome.shown) Cairo::Surface::set_window_shown(nichrome.surface, false); nichrome.shown = nichrome.want_shown = false; } public void destroy (&nichrome_t nichrome) { Cairo::Surface::destroy (nichrome.surface); nichrome.running = false; } public *nichrome_t new (string name, real size ...) { nichrome_t nichrome; real width, height; switch (dim (size)) { case 0: width = height = 300; break; case 1: width = height = size[0]; break; default: width = size[0], height = size[1]; break; } nichrome.surface = Cairo::Surface::create_window_hidden (name, width, height); nichrome.event = Cairo::Surface::open_event (nichrome.surface); nichrome.draw = false; nichrome.resize = true; nichrome.shown = false; nichrome.want_shown = true; nichrome.running = false; nichrome.widgets = ((&widget_t)[...]) {}; nichrome.scale = (point_t) { x = 1, y = 1 }; nichrome.destroy = destroy; &nichrome.key_focus = &no_widget; &nichrome.mouse_grab = &no_widget; nichrome.button_down = 0; &nichrome.mouse_in = &no_widget; &nichrome.outline_widget = &no_widget; nichrome.outline_cr = cairo (&nichrome); nichrome.drawing = Mutex::new (); nichrome.suspend_draw = 0; return &nichrome; } public void resize (&nichrome_t nichrome, real width, real height) { Cairo::Surface::resize_window(nichrome.surface, max(1, floor(width)), max(1, floor(height))); } public void top (&nichrome_t nichrome, &widget_t widget) { for (int i = widget_pos (&nichrome, &widget); i > 0; i--) { &nichrome.widgets[i] = &nichrome.widgets[i-1]; } &nichrome.widgets[0] = &widget; nichrome.draw = true; } public void main_loop (&nichrome_t nichrome) { nichrome.running = true; nichrome.draw = true; nichrome.self = Thread::current (); string event_line; semaphore has_event = Semaphore::new(0); semaphore read_event = Semaphore::new(0); void event_reader () { for (;;) { event_line = File::fgets (nichrome.event); Semaphore::signal (has_event); Semaphore::wait (read_event); } } thread event_thread = fork event_reader (); while (nichrome.running) { if (nichrome.draw) if (Semaphore::count (has_event) == 0) draw (&nichrome); Semaphore::wait (has_event); string line = event_line; Semaphore::signal (read_event); file l = File::string_read (line); File::fscanf (l, "%d %s", &(int time), &(string name)); switch (name) { case "press": case "release": case "double-press": case "triple-press": File::fscanf (l, "%d %g %g", &(int button), &(real x), &(real y)); button_event_t bev = { type = ((button_type_t[string]) { "press" => button_type_t.press, "release" => button_type_t.release, "double-press" => button_type_t.double, "triple-press" => button_type_t.triple }) [name], time = time, button = button, x = x, y = y }; dispatch_button (&nichrome, &bev); break; case "motion": File::fscanf (l, "%g %g", &(real x), &(real y)); motion_event_t mev = { type = motion_type_t.inside, time = time, x = x, y = y }; dispatch_motion (&nichrome, &mev); break; case "key-down": case "key-up": File::fscanf (l, "%s", &(string keyname)); int[...] bytes = {}; while (File::fscanf (l, "%x", &(int byte)) == 1) bytes[dim(bytes)] = byte; string s = hex_utf8(bytes); key_event_t kev = { type = (key_type_t[string]) { "key-down" => key_type_t.press, "key-up" => key_type_t.release } [name], time = time, key = keyname, text = s }; dispatch_key (&nichrome, &kev); break; case "delete": nichrome.destroy (&nichrome); break; case "configure": nichrome.resize = true; nichrome.draw = true; break; } } Thread::kill (event_thread); } string preferences_file = ".nichromerc"; public void load_preferences () { string home = Environ::get ("HOME"); if (home[String::length(home) - 1] != '/') home += "/"; try { Command::lex_file (home + preferences_file); } catch File::open_error (string error_name, File::error_type error, string name) { if (error != File::error_type.NOENT) raise File::open_error (error_name, error, name); } } } Nichrome::load_preferences ();