#include "owl.h" struct _owl_window { /*noproto*/ GObject object; /* hierarchy information */ owl_window *parent; owl_window *child; owl_window *next, *prev; /* flags */ int dirty : 1; int dirty_subtree : 1; int shown : 1; int is_screen : 1; /* window information */ WINDOW *win; PANEL *pan; int nlines, ncols; int begin_y, begin_x; }; enum { REDRAW, RESIZED, LAST_SIGNAL }; static guint window_signals[LAST_SIGNAL] = { 0 }; static void owl_window_dispose(GObject *gobject); static void owl_window_finalize(GObject *gobject); static owl_window *_owl_window_new(owl_window *parent, int nlines, int ncols, int begin_y, int begin_x); static void _owl_window_link(owl_window *w, owl_window *parent); static void _owl_window_create_curses(owl_window *w); static void _owl_window_destroy_curses(owl_window *w); static void _owl_window_realize(owl_window *w); static void _owl_window_unrealize(owl_window *w); static owl_window *cursor_owner; static owl_window *default_cursor; /* clang gets upset about the glib argument chopping hack because it manages to * inline owl_window_children_foreach. user_data should be a pointer to a * FuncOneArg. */ typedef void (*FuncOneArg)(void *); static void first_arg_only(gpointer data, gpointer user_data) { FuncOneArg *func = user_data; (*func)(data); } G_DEFINE_TYPE (OwlWindow, owl_window, G_TYPE_OBJECT) static void owl_window_class_init (OwlWindowClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); /* Set up the vtabl */ gobject_class->dispose = owl_window_dispose; gobject_class->finalize = owl_window_finalize; klass->redraw = NULL; klass->resized = NULL; /* Create the signals, remember IDs */ window_signals[REDRAW] = g_signal_new("redraw", G_TYPE_FROM_CLASS(gobject_class), G_SIGNAL_RUN_CLEANUP, G_STRUCT_OFFSET(OwlWindowClass, redraw), NULL, NULL, g_cclosure_marshal_VOID__POINTER, G_TYPE_NONE, 1, G_TYPE_POINTER, NULL); /* TODO: maybe type should be VOID__INT_INT_INT_INT; will need to generate a * marshaller */ window_signals[RESIZED] = g_signal_new("resized", G_TYPE_FROM_CLASS(gobject_class), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET(OwlWindowClass, resized), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0, NULL); } static void owl_window_dispose (GObject *object) { owl_window *w = OWL_WINDOW (object); /* Unmap the window */ owl_window_hide (w); /* Unlink and unref all children */ while (w->child) { owl_window *child = w->child; owl_window_unlink (child); } /* Remove from hierarchy */ owl_window_unlink (w); G_OBJECT_CLASS (owl_window_parent_class)->dispose (object); } static void owl_window_finalize (GObject *object) { owl_window *w = OWL_WINDOW(object); if (w->pan) { del_panel(w->pan); w->pan = NULL; } G_OBJECT_CLASS (owl_window_parent_class)->finalize (object); } static void owl_window_init (owl_window *w) { } /** singletons **/ static WINDOW *_dummy_window(void) { static WINDOW *dummy = NULL; if (!dummy) { dummy = newwin(1, 1, 0, 0); } return dummy; } owl_window *owl_window_get_screen(void) { static owl_window *screen = NULL; if (!screen) { /* The screen is magical. It's 'shown', but the only mean of it going * invisible is if we're tore down curses (i.e. screen resize) */ screen = _owl_window_new(NULL, g.lines, g.cols, 0, 0); screen->is_screen = 1; owl_window_show(screen); } return screen; } /** Creation and Destruction **/ owl_window *owl_window_new(owl_window *parent) { if (!parent) parent = owl_window_get_screen(); return _owl_window_new(parent, 0, 0, 0, 0); } static owl_window *_owl_window_new(owl_window *parent, int nlines, int ncols, int begin_y, int begin_x) { owl_window *w; w = g_object_new (OWL_TYPE_WINDOW, NULL); if (w == NULL) g_error("Failed to create owl_window instance"); w->nlines = nlines; w->ncols = ncols; w->begin_y = begin_y; w->begin_x = begin_x; _owl_window_link(w, parent); if (parent && parent->is_screen) { w->pan = new_panel(_dummy_window()); set_panel_userptr(w->pan, w); } return w; } /** Hierarchy **/ void owl_window_unlink(owl_window *w) { /* make sure the window is unmapped first */ _owl_window_unrealize(w); /* unlink parent/child information */ if (w->parent) { if (w->prev) w->prev->next = w->next; if (w->next) w->next->prev = w->prev; if (w->parent->child == w) w->parent->child = w->next; w->parent = NULL; g_object_unref (w); } } static void _owl_window_link(owl_window *w, owl_window *parent) { if (w->parent == parent) return; owl_window_unlink(w); if (parent) { w->parent = parent; w->next = parent->child; if (w->next) w->next->prev = w; parent->child = w; g_object_ref (w); } } /* mimic g_list_foreach for consistency */ void owl_window_children_foreach(owl_window *parent, GFunc func, gpointer user_data) { owl_window *w; for (w = parent->child; w != NULL; w = w->next) { func(w, user_data); } } owl_window *owl_window_parent(owl_window *w) { return w->parent; } owl_window *owl_window_first_child(owl_window *w) { return w->child; } owl_window *owl_window_next_sibling(owl_window *w) { return w->next; } owl_window *owl_window_previous_sibling(owl_window *w) { return w->prev; } /** Internal window management **/ static void _owl_window_create_curses(owl_window *w) { if (w->is_screen) { resizeterm(w->nlines, w->ncols); w->win = stdscr; } else { /* Explicitly disallow realizing an unlinked non-root */ if (w->parent == NULL || w->parent->win == NULL) return; if (w->pan) { w->win = newwin(w->nlines, w->ncols, w->begin_y, w->begin_x); replace_panel(w->pan, w->win); } else { w->win = derwin(w->parent->win, w->nlines, w->ncols, w->begin_y, w->begin_x); } } } static void _owl_window_destroy_curses(owl_window *w) { if (w->is_screen) { /* don't deallocate the dummy */ w->win = NULL; } else { if (w->pan) { /* panels assume their windows always exist, so we put in a fake one */ replace_panel(w->pan, _dummy_window()); } if (w->win) { /* and destroy our own window */ delwin(w->win); w->win = NULL; } } } void owl_window_show(owl_window *w) { w->shown = 1; _owl_window_realize(w); if (w->pan) show_panel(w->pan); } void owl_window_show_all(owl_window *w) { FuncOneArg ptr = (FuncOneArg)owl_window_show; owl_window_show(w); owl_window_children_foreach(w, first_arg_only, &ptr); } void owl_window_hide(owl_window *w) { /* you can't unmap the screen */ if (w->is_screen) return; w->shown = 0; if (w->pan) hide_panel(w->pan); _owl_window_unrealize(w); } bool owl_window_is_shown(owl_window *w) { return w->shown; } bool owl_window_is_realized(owl_window *w) { return w->win != NULL; } bool owl_window_is_toplevel(owl_window *w) { return w->pan != NULL; } bool owl_window_is_subwin(owl_window *w) { return w->pan == NULL && !w->is_screen; } static bool _owl_window_should_realize(owl_window *w) { return owl_window_is_shown(w) && (!w->parent || owl_window_is_realized(w->parent)); } static void _owl_window_realize_later(owl_window *w) { if (owl_window_is_realized(w) || !_owl_window_should_realize(w)) return; owl_window_dirty(w); } static void _owl_window_realize(owl_window *w) { FuncOneArg ptr = (FuncOneArg)_owl_window_realize_later; /* check if we can create a window */ if (owl_window_is_realized(w) || !_owl_window_should_realize(w)) return; if (w->nlines <= 0 || w->ncols <= 0) return; _owl_window_create_curses(w); if (w->win == NULL) return; /* schedule a repaint */ owl_window_dirty(w); /* map the children */ owl_window_children_foreach(w, first_arg_only, &ptr); } static void _owl_window_unrealize(owl_window *w) { FuncOneArg ptr = (FuncOneArg)_owl_window_unrealize; if (w->win == NULL) return; /* unmap all the children */ owl_window_children_foreach(w, first_arg_only, &ptr); _owl_window_destroy_curses(w); w->dirty = w->dirty_subtree = 0; /* subwins leave a mess in the parent; dirty it */ if (w->parent) owl_window_dirty(w->parent); } /** Painting and book-keeping **/ void owl_window_set_cursor(owl_window *w) { if (cursor_owner) g_object_remove_weak_pointer(G_OBJECT(cursor_owner), (gpointer*) &cursor_owner); cursor_owner = w; if (w) g_object_add_weak_pointer(G_OBJECT(w), (gpointer*) &cursor_owner); owl_window_dirty(owl_window_get_screen()); } void owl_window_set_default_cursor(owl_window *w) { if (default_cursor) g_object_remove_weak_pointer(G_OBJECT(default_cursor), (gpointer*) &default_cursor); default_cursor = w; if (w) g_object_add_weak_pointer(G_OBJECT(w), (gpointer*) &default_cursor); owl_window_dirty(owl_window_get_screen()); } static owl_window *_get_cursor(bool *is_default) { *is_default = false; if (cursor_owner && owl_window_is_realized(cursor_owner)) return cursor_owner; *is_default = true; if (default_cursor && owl_window_is_realized(default_cursor)) return default_cursor; return owl_window_get_screen(); } void owl_window_dirty(owl_window *w) { if (!_owl_window_should_realize(w)) return; if (!w->dirty) { w->dirty = 1; while (w && !w->dirty_subtree) { w->dirty_subtree = 1; w = w->parent; } } } void owl_window_dirty_children(owl_window *w) { FuncOneArg ptr = (FuncOneArg)owl_window_dirty; owl_window_children_foreach(w, first_arg_only, &ptr); } static void _owl_window_redraw(owl_window *w) { if (!w->dirty) return; _owl_window_realize(w); if (w->win && !w->is_screen) { if (owl_window_is_subwin(w)) { /* If a subwin, we might have gotten random touched lines from wsyncup or * past drawing. That information is useless, so we discard it all */ untouchwin(w->win); } g_signal_emit(w, window_signals[REDRAW], 0, w->win); wsyncup(w->win); } w->dirty = 0; } static bool _owl_window_is_subtree_dirty(owl_window *w) { owl_window *child; if (w->dirty) return true; for (child = w->child; child != NULL; child = child->next) { if (child->dirty_subtree) return true; } return false; } static void _owl_window_redraw_subtree(owl_window *w) { FuncOneArg ptr = (FuncOneArg)_owl_window_redraw_subtree; if (!w->dirty_subtree) return; _owl_window_redraw(w); owl_window_children_foreach(w, first_arg_only, &ptr); /* Clear the dirty_subtree bit, unless a child doesn't have it * cleared because we dirtied a window in redraw. Dirtying a * non-descendant window during a redraw handler is * discouraged. Redraw will not break, but it is undefined whether * the dirty is delayed to the next event loop iteration. */ if (_owl_window_is_subtree_dirty(w)) { owl_function_debugmsg("subtree still dirty after one iteration!"); } else { w->dirty_subtree = 0; } } /* Redraw all the windows with scheduled redraws. NOTE: This function shouldn't be called outside the event loop */ void owl_window_redraw_scheduled(void) { owl_window *cursor; owl_window *screen = owl_window_get_screen(); bool default_cursor; if (!screen->dirty_subtree) return; _owl_window_redraw_subtree(screen); update_panels(); cursor = _get_cursor(&default_cursor); if (cursor && cursor->win) { /* If supported, hide the default cursor. */ curs_set(default_cursor ? 0 : 1); /* untouch it to avoid drawing; window should be clean now, but we must * clean up in case there was junk left over on a subwin (cleaning up after * subwin drawing isn't sufficient because a wsyncup messes up subwin * ancestors */ untouchwin(cursor->win); wnoutrefresh(cursor->win); } doupdate(); } /** Window position **/ void owl_window_get_position(owl_window *w, int *nlines, int *ncols, int *begin_y, int *begin_x) { if (nlines) *nlines = w->nlines; if (ncols) *ncols = w->ncols; if (begin_y) *begin_y = w->begin_y; if (begin_x) *begin_x = w->begin_x; } void owl_window_move(owl_window *w, int begin_y, int begin_x) { /* It is possible to move a window efficiently with move_panel and mvderwin, * but begy and begx are then wrong. Currently, this only effects the * wnoutrefresh to move cursor. TODO: fix that and reinstate that * optimization if it's worth the trouble */ owl_window_set_position(w, w->nlines, w->ncols, begin_y, begin_x); } void owl_window_set_position(owl_window *w, int nlines, int ncols, int begin_y, int begin_x) { int resized; if (w->nlines == nlines && w->ncols == ncols && w->begin_y == begin_y && w->begin_x == begin_x) { return; } resized = w->nlines != nlines || w->ncols != ncols; _owl_window_unrealize(w); w->begin_y = begin_y; w->begin_x = begin_x; w->nlines = nlines; w->ncols = ncols; if (resized) g_signal_emit(w, window_signals[RESIZED], 0); if (w->shown) { /* ncurses is screwy: give up and recreate windows at the right place */ _owl_window_realize_later(w); } } void owl_window_resize(owl_window *w, int nlines, int ncols) { owl_window_set_position(w, nlines, ncols, w->begin_y, w->begin_x); } /** Redrawing main loop hooks **/ static bool _owl_window_should_redraw(void) { return g.resizepending || owl_window_get_screen()->dirty_subtree; } static gboolean _owl_window_redraw_prepare(GSource *source, int *timeout) { *timeout = -1; return _owl_window_should_redraw(); } static gboolean _owl_window_redraw_check(GSource *source) { return _owl_window_should_redraw(); } static gboolean _owl_window_redraw_dispatch(GSource *source, GSourceFunc callback, gpointer user_data) { owl_colorpair_mgr *cpmgr; /* if a resize has been scheduled, deal with it */ owl_global_check_resize(&g); /* update the terminal if we need to */ owl_window_redraw_scheduled(); /* On colorpair shortage, reset and redraw /everything/. NOTE: if we * still overflow, this be useless work. With 8-colors, we get 64 * pairs. With 256-colors, we get 32768 pairs with ext-colors * support and 256 otherwise. */ cpmgr = owl_global_get_colorpair_mgr(&g); if (cpmgr->overflow) { owl_function_debugmsg("colorpairs: used all %d pairs; reset pairs and redraw.", owl_util_get_colorpairs()); owl_fmtext_reset_colorpairs(cpmgr); owl_function_full_redisplay(); owl_window_redraw_scheduled(); } return TRUE; } static GSourceFuncs redraw_funcs = { _owl_window_redraw_prepare, _owl_window_redraw_check, _owl_window_redraw_dispatch, NULL }; CALLER_OWN GSource *owl_window_redraw_source_new(void) { GSource *source; source = g_source_new(&redraw_funcs, sizeof(GSource)); /* TODO: priority?? */ return source; }