/* horizon, a sketch, note and scribbling application. Copyright (C) 2005, 2006 Øyvind Kolås Eero Tanskanen This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 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 General Public License for more details. You should have received a copy of the GNU 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 #include #include #include #include #include #include #include #include "malloc.h" #include "config.h" #include "horizon.h" #include "stylus.h" #include "canvas/canvas.h" #include "toolkit/box.h" #include "toolkit/label.h" #include "toolkit/easel.h" #include "tools/tool_library.h" #include "link.h" #include "actions.h" #include "timer.h" #include "./png.h" static Horizon * horizon_new (gint argc, gchar **argv); static void horizon_main (Horizon *horizon); static gboolean horizon_idle (Horizon *horizon); static void horizon_destroy (Horizon *horizon); static Horizon *horizon; Horizon * get_global_horizon (void) { return horizon; } gint main (gint argc, gchar *argv[]) { #ifdef __GLIBC__ /* force allocations for the tile buffers to be allocated using mmap, guaranteeing * that they can be returned to the system */ mallopt (M_MMAP_THRESHOLD, TILE_SIZE * TILE_SIZE * 4 - 1); #endif g_type_init(); if( SDL_Init(SDL_INIT_VIDEO) < 0 ) { fprintf(stderr, "Could not initialize SDL: %s\n", SDL_GetError()); exit(-1); } atexit(SDL_Quit); horizon = horizon_new (argc, argv); action (horizon, "restore_position"); #ifdef DO_TELEPORT action (horizon, "initial_teleport"); #endif action (horizon, "select_tool_nav"); horizon_main (horizon); action (horizon, "save_position"); horizon_destroy (horizon); SDL_Quit (); return 0; } static gboolean status_visible = FALSE; static Timer *status_timer = NULL; void horizon_status (Horizon *horizon, const gchar *message) { Label *status = (Label*) box_id (horizon->root, "status"); Box *status_parent = box_id (horizon->root, "status_parent"); Easel *easel = (Easel*) box_id (horizon->root, "easel"); if (message == NULL) { gint ratios[]={0,10}; if (status_visible == TRUE) { gint offset = box_get_height ((Box*) status) * 65536 / view_get_scale ((View*)easel); view_set_y ((View*)easel, view_get_y ((View*)easel) - offset); } box_set_ratios (status_parent, ratios); status_visible = FALSE; if (status_timer) timer_free (status_timer); status_timer = NULL; } else { gint ratios[]={1,9}; box_set_ratios (status_parent, ratios); label_set_label (status, message); if (!status_timer) status_timer = timer_new (); else timer_start (status_timer); if (status_visible == FALSE) { gint offset; offset = box_get_height ((Box*) status) * 65536 / view_get_scale ((View*)easel); view_set_y ((View*)easel, view_get_y ((View*)easel) + offset); } status_visible = TRUE; } action (horizon, "refresh"); } void status_idle (Horizon *horizon) { if (!status_timer) return; if (timer_get_elapsed_ms (status_timer) > 4000) horizon_status (horizon, NULL); } void create_ui (Horizon *horizon) { Canvas *canvas = horizon->canvas; Box *tool_pane; Box *tool_options; Easel *easel; Box *root = horizon->root = hsplit_new (); box_set_ratio (root, 65536); easel = easel_new (horizon->canvas); horizon->easel = easel; box_set_id ((Box*)easel, "easel"); { Box *vbox = vbox_new (2); Label *status = label_new (canvas); gint ratios[]={0,10}; box_set_ratios (vbox, ratios); box_set_id ((Box*)status, "status"); box_set_id (vbox, "status_parent"); box_set_child (vbox, 0, (Box*)status); box_set_child (vbox, 1, (Box*)easel); box_set_child (root, 0, vbox); } view_lookat ((View*)easel, horizon->canvas, 65536,65536,65536); box_recalc_sizes (root, horizon->screen_width, horizon->screen_height); if (horizon->first_run) { horizon_status (horizon, "Starting horizon for the first time, user guide is being rendered into canvas, please wait..."); action (horizon, "welcome"); } else { horizon_status (horizon, "Loading horizon..."); } tool_pane = vbox_new (2); box_set_id (tool_pane, "tool_pane"); box_set_ratio (tool_pane, 0.2 * 65536); tool_options = hbox_new (1); box_set_id (tool_options, "tool_options"); box_set_child (root, 1, tool_pane); box_set_child (tool_pane, 0, tool_library_get_tool_box (horizon->canvas)); box_set_child (tool_pane, 1, tool_options); tool_library_create_tool_uis (horizon); view_set_x ((View*)easel, USER_X); view_set_y ((View*)easel, USER_Y); view_set_scale ((View*)easel, USER_ZOOM); } static Horizon * horizon_new (gint argc, gchar **argv) { SDL_Surface *screen; Horizon *horizon = g_malloc0 (sizeof (Horizon)); horizon->screen_width = SCREEN_WIDTH; horizon->screen_height = SCREEN_HEIGHT; horizon->prefetch = TRUE; tool_library_set_horizon (horizon); /* Assumption is that when run without arguments horizon is running * on a Nokia 770 */ if (argc > 1) { horizon->fullscreen = FALSE; horizon->storage_path=argv[1]; } else { horizon->fullscreen = TRUE; horizon->storage_path=CANVAS_PATH; } /* create data directory */ if (!g_file_test (horizon->storage_path, G_FILE_TEST_IS_DIR)) { if (g_mkdir (horizon->storage_path, 0777)) { g_error ("failed to create horizon directory %s\n", horizon->storage_path); exit (-1); } else { horizon->first_run = TRUE; } } else { horizon->first_run = FALSE; } horizon->canvas = canvas_new (); horizon->undo_stack = undo_stack_new (horizon->canvas); canvas_set_undo_stack (horizon->canvas, horizon->undo_stack); { gchar canvas_path[512]; sprintf (canvas_path, "%s/canvas", horizon->storage_path); canvas_set_path (horizon->canvas, canvas_path); } { char links_path[512]; sprintf (links_path, "%s/links", horizon->storage_path); links_load (links_path); } SDL_WM_SetCaption ("horizon", "horizon"); screen = SDL_SetVideoMode (horizon->screen_width, horizon->screen_height, 16, SDL_HWSURFACE | (horizon->fullscreen?SDL_FULLSCREEN:SDL_RESIZABLE)); if (horizon->fullscreen) SDL_ShowCursor (0); { SDL_SysWMinfo wminfo; SDL_VERSION(&wminfo.version); SDL_GetWMInfo (&wminfo); horizon->dpy = wminfo.info.x11.display; XSelectInput (wminfo.info.x11.display, wminfo.info.x11.window, KeyPressMask | PointerMotionMask | ButtonPressMask | ButtonReleaseMask | ExposureMask | StructureNotifyMask); } { char calibration[128] =""; if (horizon->fullscreen) { FILE *fp = fopen ("/etc/pointercal", "r"); if (fp) { fgets (calibration, 128, fp); fclose (fp); } } horizon->stylus = stylus_init (horizon->dpy); stylus_set_calibration (horizon->stylus, calibration); stylus_set_screen_size (horizon->stylus, horizon->screen_width, horizon->screen_height); } create_tools (); create_ui (horizon); action (horizon, "toggle_fullscreen"); horizon_status (horizon, NULL); return horizon; } static void horizon_destroy (Horizon *horizon) { /* do it early, hoping if we fail we fail with motion events intact */ stylus_destroy (horizon->stylus); tools_destroy (); /*view_destroy (view); destroyed by destroying root box */ box_destroy (horizon->root); { char links_path[512]; sprintf (links_path, "%s/links", horizon->storage_path); links_save (links_path); } undo_stack_destroy (horizon->undo_stack); g_object_unref (G_OBJECT (horizon->canvas)); g_free (horizon); } /* returns TRUE if something was done, upong FALSE the process will not get * another idle iteration until after the next user event is processed */ static gboolean horizon_idle (Horizon *horizon) { gboolean did_something = FALSE; View *view = (View*)horizon->easel; Canvas *canvas = horizon->canvas; if (get_current_tool () == lookup_tool("Nav") && horizon->prefetch) did_something |= canvas_prefetch (canvas, view_get_x (view), view_get_y (view), box_get_width ((Box*)view) * 65536 / view_get_scale (view), box_get_height ((Box*)view) * 65536 / view_get_scale (view), view_get_scale (view), TRUE); did_something |= canvas_idle (canvas); status_idle (horizon); return did_something; } static gboolean handle_configure (Horizon *horizon, XConfigureEvent *cev) { if (horizon->screen_width == cev->width && horizon->screen_height == cev->height) return FALSE; horizon->screen_width = cev->width; horizon->screen_height = cev->height; SDL_SetVideoMode (horizon->screen_width, horizon->screen_height, 16, SDL_HWSURFACE | (horizon->fullscreen?SDL_FULLSCREEN:SDL_RESIZABLE)); box_recalc_sizes (horizon->root, horizon->screen_width, horizon->screen_height); stylus_set_screen_size (horizon->stylus, horizon->screen_width, horizon->screen_height); return TRUE; } static void horizon_main (Horizon *horizon) { XEvent event; Tool *saved_tool = NULL; /* set to the previously active tool when middle mouse button drag is activated */ action (horizon, "refresh"); views_flush (); while (!horizon->quit) { gboolean redraw_ui = FALSE; gint queued_events; if ((queued_events = XEventsQueued(horizon->dpy, QueuedAfterReading)) > 0 || !stylus_has_next(horizon->stylus)) { if (queued_events == 0 && !stylus_has_next (horizon->stylus)) { if (horizon_idle (horizon)) continue; } XNextEvent(horizon->dpy, &event); switch (event.type) { case KeyPress: if (saved_tool != NULL) /* disable keyevents during middle mouse button drag, pippin@gimp.org has a mousewheel as middle mouse button that emits cursor key events when nudged left/right */ break; { gint i; for (i=0; actions[i].keysym != NULL; i++) if (event.xkey.keycode == XKeysymToKeycode (horizon->dpy, XStringToKeysym (actions[i].keysym))) /* FIXME: cache these lookups */ { redraw_ui = actions[i].action (horizon); } } break; case ConfigureNotify: redraw_ui = handle_configure (horizon, &event.xconfigure); break; case Expose: SDL_UpdateRect (SDL_GetVideoSurface (), 0,0,0,0); break; default: /* the code here implements additional filtering * for events that should be passed to stylus depending * on exact data */ switch (event.type) { /* used for temporarily saving the current tool when middle mouse * draggin the canvas */ case ButtonPress: switch (((XButtonEvent*)&event)->button) { case 4: redraw_ui = action (horizon, "zoom_in"); break; case 5: redraw_ui = action (horizon, "zoom_out"); break; case 2: saved_tool = get_current_tool (); set_current_tool (lookup_tool("Nav")); default: stylus_process_event (horizon->stylus, &event); break; } break; case ButtonRelease: switch (((XButtonEvent*)&event)->button) { case 2: set_current_tool (saved_tool); saved_tool = NULL; default: stylus_process_event (horizon->stylus, &event); break; } break; default: stylus_process_event (horizon->stylus, &event); break; } break; } } else if (stylus_has_next(horizon->stylus)) { gint x,y,pressure; gulong time; while (stylus_get_next (horizon->stylus, &x, &y, &pressure, &time)) { redraw_ui = box_event (horizon->root, x, y, pressure, time); } } if (redraw_ui) { action (horizon, "refresh"); } } } static Box *create_setting (Canvas *canvas, const gchar *text, void (*value_changed_function) (Slider *slider, gpointer data), gpointer data) { Box *hbox = hbox_new (2); Box *vbox = vbox_new (2); gchar val_id[64]; sprintf (val_id, "val-%s", text); Slider *slider = slider_new (canvas); Label *label = label_new (canvas); Label *label_val = label_new (canvas); label_set_label (label, text); label_set_label (label_val, "0"); box_set_id ((Box*)label_val, val_id); box_set_id ((Box*)slider , text); box_set_child (hbox, 0, (Box*)label); box_set_child (hbox, 1, (Box*)label_val); box_set_child (vbox, 0, hbox); box_set_child (vbox, 1, (Box*)slider); box_set_ratio (hbox, 0.66*65536); slider_set_value_changed_function (slider, value_changed_function, data); return vbox; } #include "canvas/canvas.h" #include "canvas/tile_cache.h" static void update_prefs (Slider *slider, gpointer data) { Horizon *horizon = data; Label *label; gint value; gchar str_value[64]; if (!horizon->prefs) return; label = (Label*) box_id (horizon->root, "val-wash"); slider = (Slider*) box_id (horizon->root, "wash"); value = slider_get_value (slider); tile_cache_set_wash_percentage (horizon->canvas->cache, 100*value/65536); sprintf (str_value, "%f", value/65536.0); label_set_label (label, str_value); label = (Label*) box_id (horizon->root, "val-prefetch"); slider = (Slider*) box_id (horizon->root, "prefetch"); value = slider_get_value (slider); if (value > 65536*0.5) value = 65536; else value = 0; slider_set_value (slider, value); horizon->prefetch = value?TRUE:FALSE; sprintf (str_value, "%s", value?"yep":"nope"); label_set_label (label, str_value); label = (Label*) box_id (horizon->root, "val-cache"); slider = (Slider*) box_id (horizon->root, "cache"); value = slider_get_value (slider); tile_cache_set_size (horizon->canvas->cache, 400*value/65536); sprintf (str_value, "%f", 400*value/65536.0/16); label_set_label (label, str_value); } static void set_prefs (Horizon *horizon) { slider_set_value ((Slider*)box_id (horizon->root, "prefetch"), horizon->prefetch?65535:0); slider_set_value ((Slider*)box_id (horizon->root, "wash"), 65536 * tile_cache_get_wash_percentage (horizon->canvas->cache) / 100); slider_set_value ((Slider*)box_id (horizon->root, "cache"), 65536 * tile_cache_get_size (horizon->canvas->cache) / 400); update_prefs (NULL, horizon); } void horizon_show_prefs (Horizon *horizon) { Box *tool_options = box_id (horizon->root, "tool_options"); if (horizon->prefs == NULL) { gint pos=0; Canvas *canvas = horizon->canvas; Box *vbox = vbox_new (3); box_set_child (vbox, pos++, create_setting (canvas, "wash", update_prefs, horizon)); box_set_child (vbox, pos++, create_setting (canvas, "prefetch", update_prefs, horizon)); box_set_child (vbox, pos++, create_setting (canvas, "cache", update_prefs, horizon)); horizon->prefs = vbox; } if (box_get_child (tool_options, 0) != horizon->prefs) box_set_child (tool_options, 0, horizon->prefs); set_prefs (horizon); }