source: window.c @ bc6d81d

Last change on this file since bc6d81d was 8e40da74, checked in by David Benjamin <davidben@mit.edu>, 13 years ago
Fix dirtying windows inside a redraw handler This is a seriously sketchy thing to do, but at least maintain dirty and dirty_subtree bits correctly. Tested by triggering an editwin dirty from the sepbar code.
  • Property mode set to 100644
File size: 14.6 KB
Line 
1#include "owl.h"
2
3struct _owl_window { /*noproto*/
4  GObject object;
5  /* hierarchy information */
6  owl_window *parent;
7  owl_window *child;
8  owl_window *next, *prev;
9  /* flags */
10  int dirty : 1;
11  int dirty_subtree : 1;
12  int shown : 1;
13  int is_screen : 1;
14  /* window information */
15  WINDOW *win;
16  PANEL *pan;
17  int nlines, ncols;
18  int begin_y, begin_x;
19};
20
21enum {
22  REDRAW,
23  RESIZED,
24  LAST_SIGNAL
25};
26
27static guint window_signals[LAST_SIGNAL] = { 0 };
28
29static void owl_window_dispose(GObject *gobject);
30static void owl_window_finalize(GObject *gobject);
31
32static owl_window *_owl_window_new(owl_window *parent, int nlines, int ncols, int begin_y, int begin_x);
33
34static void _owl_window_link(owl_window *w, owl_window *parent);
35
36static void _owl_window_create_curses(owl_window *w);
37static void _owl_window_destroy_curses(owl_window *w);
38
39static void _owl_window_realize(owl_window *w);
40static void _owl_window_unrealize(owl_window *w);
41
42static owl_window *cursor_owner;
43static owl_window *default_cursor;
44
45/* clang gets upset about the glib argument chopping hack because it manages to
46 * inline owl_window_children_foreach. user_data should be a pointer to a
47 * FuncOneArg. */
48typedef void (*FuncOneArg)(void *);
49static void first_arg_only(gpointer data, gpointer user_data)
50{
51  FuncOneArg *func = user_data;
52  (*func)(data);
53}
54
55G_DEFINE_TYPE (OwlWindow, owl_window, G_TYPE_OBJECT)
56
57static void owl_window_class_init (OwlWindowClass *klass)
58{
59  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
60
61  /* Set up the vtabl */
62  gobject_class->dispose = owl_window_dispose;
63  gobject_class->finalize = owl_window_finalize;
64
65  klass->redraw = NULL;
66  klass->resized = NULL;
67
68  /* Create the signals, remember IDs */
69  window_signals[REDRAW] =
70    g_signal_new("redraw",
71                 G_TYPE_FROM_CLASS(gobject_class),
72                 G_SIGNAL_RUN_CLEANUP,
73                 G_STRUCT_OFFSET(OwlWindowClass, redraw),
74                 NULL, NULL,
75                 g_cclosure_marshal_VOID__POINTER,
76                 G_TYPE_NONE,
77                 1,
78                 G_TYPE_POINTER, NULL);
79
80  /* TODO: maybe type should be VOID__INT_INT_INT_INT; will need to generate a
81   * marshaller */
82  window_signals[RESIZED] =
83    g_signal_new("resized",
84                 G_TYPE_FROM_CLASS(gobject_class),
85                 G_SIGNAL_RUN_FIRST,
86                 G_STRUCT_OFFSET(OwlWindowClass, resized),
87                 NULL, NULL,
88                 g_cclosure_marshal_VOID__VOID,
89                 G_TYPE_NONE,
90                 0,
91                 NULL);
92}
93
94static void owl_window_dispose (GObject *object)
95{
96  owl_window *w = OWL_WINDOW (object);
97
98  /* Unmap the window */
99  owl_window_hide (w);
100
101  /* Unlink and unref all children */
102  while (w->child) {
103    owl_window *child = w->child;
104    owl_window_unlink (child);
105  }
106
107  /* Remove from hierarchy */
108  owl_window_unlink (w);
109
110  G_OBJECT_CLASS (owl_window_parent_class)->dispose (object);
111}
112
113static void owl_window_finalize (GObject *object)
114{
115  owl_window *w = OWL_WINDOW(object);
116
117  if (w->pan) {
118    del_panel(w->pan);
119    w->pan = NULL;
120  }
121
122  G_OBJECT_CLASS (owl_window_parent_class)->finalize (object);
123}
124
125static void owl_window_init (owl_window *w)
126{
127}
128
129/** singletons **/
130
131static WINDOW *_dummy_window(void)
132{
133  static WINDOW *dummy = NULL;
134  if (!dummy) {
135    dummy = newwin(1, 1, 0, 0);
136  }
137  return dummy;
138}
139
140owl_window *owl_window_get_screen(void)
141{
142  static owl_window *screen = NULL;
143  if (!screen) {
144    /* The screen is magical. It's 'shown', but the only mean of it going
145     * invisible is if we're tore down curses (i.e. screen resize) */
146    screen = _owl_window_new(NULL, g.lines, g.cols, 0, 0);
147    screen->is_screen = 1;
148    owl_window_show(screen);
149  }
150  return screen;
151}
152
153/** Creation and Destruction **/
154
155owl_window *owl_window_new(owl_window *parent)
156{
157  if (!parent)
158    parent = owl_window_get_screen();
159  return _owl_window_new(parent, 0, 0, 0, 0);
160}
161
162static owl_window *_owl_window_new(owl_window *parent, int nlines, int ncols, int begin_y, int begin_x)
163{
164  owl_window *w;
165
166  w = g_object_new (OWL_TYPE_WINDOW, NULL);
167  if (w == NULL) g_error("Failed to create owl_window instance");
168
169  w->nlines = nlines;
170  w->ncols = ncols;
171  w->begin_y = begin_y;
172  w->begin_x = begin_x;
173
174  _owl_window_link(w, parent);
175  if (parent && parent->is_screen) {
176    w->pan = new_panel(_dummy_window());
177    set_panel_userptr(w->pan, w);
178  }
179
180  return w;
181}
182
183/** Hierarchy **/
184
185void owl_window_unlink(owl_window *w)
186{
187  /* make sure the window is unmapped first */
188  _owl_window_unrealize(w);
189  /* unlink parent/child information */
190  if (w->parent) {
191    if (w->prev)
192      w->prev->next = w->next;
193    if (w->next)
194      w->next->prev = w->prev;
195    if (w->parent->child == w)
196      w->parent->child = w->next;
197    w->parent = NULL;
198    g_object_unref (w);
199  }
200}
201
202static void _owl_window_link(owl_window *w, owl_window *parent)
203{
204  if (w->parent == parent)
205    return;
206
207  owl_window_unlink(w);
208  if (parent) {
209    w->parent = parent;
210    w->next = parent->child;
211    if (w->next)
212      w->next->prev = w;
213    parent->child = w;
214    g_object_ref (w);
215  }
216}
217
218/* mimic g_list_foreach for consistency */
219void owl_window_children_foreach(owl_window *parent, GFunc func, gpointer user_data)
220{
221  owl_window *w;
222  for (w = parent->child;
223       w != NULL;
224       w = w->next) {
225    func(w, user_data);
226  }
227}
228
229owl_window *owl_window_parent(owl_window *w)
230{
231  return w->parent;
232}
233
234owl_window *owl_window_first_child(owl_window *w)
235{
236  return w->child;
237}
238
239owl_window *owl_window_next_sibling(owl_window *w)
240{
241  return w->next;
242}
243
244owl_window *owl_window_previous_sibling(owl_window *w)
245{
246  return w->prev;
247}
248
249/** Internal window management **/
250
251static void _owl_window_create_curses(owl_window *w)
252{
253  if (w->is_screen) {
254    resizeterm(w->nlines, w->ncols);
255    w->win = stdscr;
256  } else {
257    /* Explicitly disallow realizing an unlinked non-root */
258    if (w->parent == NULL || w->parent->win == NULL)
259      return;
260    if (w->pan) {
261      w->win = newwin(w->nlines, w->ncols, w->begin_y, w->begin_x);
262      replace_panel(w->pan, w->win);
263    } else {
264      w->win = derwin(w->parent->win, w->nlines, w->ncols, w->begin_y, w->begin_x);
265    }
266  }
267}
268
269static void _owl_window_destroy_curses(owl_window *w)
270{
271  if (w->is_screen) {
272    /* don't deallocate the dummy */
273    w->win = NULL;
274  } else {
275    if (w->pan) {
276      /* panels assume their windows always exist, so we put in a fake one */
277      replace_panel(w->pan, _dummy_window());
278    }
279    if (w->win) {
280      /* and destroy our own window */
281      delwin(w->win);
282      w->win = NULL;
283    }
284  }
285}
286
287void owl_window_show(owl_window *w)
288{
289  w->shown = 1;
290  _owl_window_realize(w);
291  if (w->pan)
292    show_panel(w->pan);
293}
294
295void owl_window_show_all(owl_window *w)
296{
297  FuncOneArg ptr = (FuncOneArg)owl_window_show;
298  owl_window_show(w);
299  owl_window_children_foreach(w, first_arg_only, &ptr);
300}
301
302void owl_window_hide(owl_window *w)
303{
304  /* you can't unmap the screen */
305  if (w->is_screen)
306    return;
307  w->shown = 0;
308  if (w->pan)
309    hide_panel(w->pan);
310  _owl_window_unrealize(w);
311}
312
313bool owl_window_is_shown(owl_window *w)
314{
315  return w->shown;
316}
317
318bool owl_window_is_realized(owl_window *w)
319{
320  return w->win != NULL;
321}
322
323bool owl_window_is_toplevel(owl_window *w)
324{
325  return w->pan != NULL;
326}
327
328bool owl_window_is_subwin(owl_window *w)
329{
330  return w->pan == NULL && !w->is_screen;
331}
332
333static bool _owl_window_should_realize(owl_window *w)
334{
335  return owl_window_is_shown(w) &&
336    (!w->parent || owl_window_is_realized(w->parent));
337}
338
339static void _owl_window_realize_later(owl_window *w)
340{
341  if (owl_window_is_realized(w) || !_owl_window_should_realize(w))
342    return;
343  owl_window_dirty(w);
344}
345
346static void _owl_window_realize(owl_window *w)
347{
348  FuncOneArg ptr = (FuncOneArg)_owl_window_realize_later;
349  /* check if we can create a window */
350  if (owl_window_is_realized(w) || !_owl_window_should_realize(w))
351    return;
352  if (w->nlines <= 0 || w->ncols <= 0)
353    return;
354  _owl_window_create_curses(w);
355  if (w->win == NULL)
356    return;
357  /* schedule a repaint */
358  owl_window_dirty(w);
359  /* map the children */
360  owl_window_children_foreach(w, first_arg_only, &ptr);
361}
362
363static void _owl_window_unrealize(owl_window *w)
364{
365  FuncOneArg ptr = (FuncOneArg)_owl_window_unrealize;
366  if (w->win == NULL)
367    return;
368  /* unmap all the children */
369  owl_window_children_foreach(w, first_arg_only, &ptr);
370  _owl_window_destroy_curses(w);
371  w->dirty = w->dirty_subtree = 0;
372  /* subwins leave a mess in the parent; dirty it */
373  if (w->parent)
374    owl_window_dirty(w->parent);
375}
376
377/** Painting and book-keeping **/
378
379void owl_window_set_cursor(owl_window *w)
380{
381  if (cursor_owner)
382    g_object_remove_weak_pointer(G_OBJECT(cursor_owner), (gpointer*) &cursor_owner);
383  cursor_owner = w;
384  if (w)
385    g_object_add_weak_pointer(G_OBJECT(w), (gpointer*) &cursor_owner);
386  owl_window_dirty(owl_window_get_screen());
387}
388
389void owl_window_set_default_cursor(owl_window *w)
390{
391  if (default_cursor)
392    g_object_remove_weak_pointer(G_OBJECT(default_cursor), (gpointer*) &default_cursor);
393  default_cursor = w;
394  if (w)
395    g_object_add_weak_pointer(G_OBJECT(w), (gpointer*) &default_cursor);
396  owl_window_dirty(owl_window_get_screen());
397}
398
399static owl_window *_get_cursor(void)
400{
401  if (cursor_owner && owl_window_is_realized(cursor_owner))
402    return cursor_owner;
403  if (default_cursor && owl_window_is_realized(default_cursor))
404    return default_cursor;
405  return owl_window_get_screen();
406}
407
408void owl_window_dirty(owl_window *w)
409{
410  if (!_owl_window_should_realize(w))
411    return;
412  if (!w->dirty) {
413    w->dirty = 1;
414    while (w && !w->dirty_subtree) {
415      w->dirty_subtree = 1;
416      w = w->parent;
417    }
418  }
419}
420
421void owl_window_dirty_children(owl_window *w)
422{
423  FuncOneArg ptr = (FuncOneArg)owl_window_dirty;
424  owl_window_children_foreach(w, first_arg_only, &ptr);
425}
426
427static void _owl_window_redraw(owl_window *w)
428{
429  if (!w->dirty) return;
430  _owl_window_realize(w);
431  if (w->win && !w->is_screen) {
432    if (owl_window_is_subwin(w)) {
433      /* If a subwin, we might have gotten random touched lines from wsyncup or
434       * past drawing. That information is useless, so we discard it all */
435      untouchwin(w->win);
436    }
437    g_signal_emit(w, window_signals[REDRAW], 0, w->win);
438    wsyncup(w->win);
439  }
440  w->dirty = 0;
441}
442
443static bool _owl_window_is_subtree_dirty(owl_window *w)
444{
445  owl_window *child;
446
447  if (w->dirty)
448    return true;
449  for (child = w->child;
450       child != NULL;
451       child = child->next) {
452    if (child->dirty_subtree)
453      return true;
454  }
455  return false;
456}
457
458static void _owl_window_redraw_subtree(owl_window *w)
459{
460  FuncOneArg ptr = (FuncOneArg)_owl_window_redraw_subtree;
461
462  if (!w->dirty_subtree)
463    return;
464
465  _owl_window_redraw(w);
466  owl_window_children_foreach(w, first_arg_only, &ptr);
467
468  /* Clear the dirty_subtree bit, unless a child doesn't have it
469   * cleared because we dirtied a window in redraw. Dirtying a
470   * non-descendant window during a redraw handler is
471   * discouraged. Redraw will not break, but it is undefined whether
472   * the dirty is delayed to the next event loop iteration. */
473  if (_owl_window_is_subtree_dirty(w)) {
474    owl_function_debugmsg("subtree still dirty after one iteration!");
475  } else {
476    w->dirty_subtree = 0;
477  }
478}
479
480/*
481Redraw all the windows with scheduled redraws.
482NOTE: This function shouldn't be called outside the event loop
483*/
484void owl_window_redraw_scheduled(void)
485{
486  owl_window *cursor;
487  owl_window *screen = owl_window_get_screen();
488
489  if (!screen->dirty_subtree)
490    return;
491  _owl_window_redraw_subtree(screen);
492  update_panels();
493  cursor = _get_cursor();
494  if (cursor && cursor->win) {
495    /* untouch it to avoid drawing; window should be clean now, but we must
496     * clean up in case there was junk left over on a subwin (cleaning up after
497     * subwin drawing isn't sufficient because a wsyncup messes up subwin
498     * ancestors */
499    untouchwin(cursor->win);
500    wnoutrefresh(cursor->win);
501  }
502  doupdate();
503}
504
505/** Window position **/
506
507void owl_window_get_position(owl_window *w, int *nlines, int *ncols, int *begin_y, int *begin_x)
508{
509  if (nlines)  *nlines  = w->nlines;
510  if (ncols)   *ncols   = w->ncols;
511  if (begin_y) *begin_y = w->begin_y;
512  if (begin_x) *begin_x = w->begin_x;
513}
514
515void owl_window_move(owl_window *w, int begin_y, int begin_x)
516{
517  /* It is possible to move a window efficiently with move_panel and mvderwin,
518   * but begy and begx are then wrong. Currently, this only effects the
519   * wnoutrefresh to move cursor. TODO: fix that and reinstate that
520   * optimization if it's worth the trouble */
521  owl_window_set_position(w, w->nlines, w->ncols, begin_y, begin_x);
522}
523
524void owl_window_set_position(owl_window *w, int nlines, int ncols, int begin_y, int begin_x)
525{
526  int resized;
527
528  if (w->nlines == nlines && w->ncols == ncols
529      && w->begin_y == begin_y && w->begin_x == begin_x) {
530    return;
531  }
532  resized = w->nlines != nlines || w->ncols != ncols;
533
534  _owl_window_unrealize(w);
535  w->begin_y = begin_y;
536  w->begin_x = begin_x;
537  w->nlines = nlines;
538  w->ncols = ncols;
539  if (resized)
540    g_signal_emit(w, window_signals[RESIZED], 0);
541  if (w->shown) {
542    /* ncurses is screwy: give up and recreate windows at the right place */
543    _owl_window_realize_later(w);
544  }
545}
546
547void owl_window_resize(owl_window *w, int nlines, int ncols)
548{
549  owl_window_set_position(w, nlines, ncols, w->begin_y, w->begin_x);
550}
551
552/** Redrawing main loop hooks **/
553
554static bool _owl_window_should_redraw(void) {
555  return g.resizepending || owl_window_get_screen()->dirty_subtree;
556}
557
558static gboolean _owl_window_redraw_prepare(GSource *source, int *timeout) {
559  *timeout = -1;
560  return _owl_window_should_redraw();
561}
562
563static gboolean _owl_window_redraw_check(GSource *source) {
564  return _owl_window_should_redraw();
565}
566
567static gboolean _owl_window_redraw_dispatch(GSource *source, GSourceFunc callback, gpointer user_data) {
568  owl_colorpair_mgr *cpmgr;
569
570  /* if a resize has been scheduled, deal with it */
571  owl_global_check_resize(&g);
572  /* update the terminal if we need to */
573  owl_window_redraw_scheduled();
574  /* On colorpair shortage, reset and redraw /everything/. NOTE: if we
575   * still overflow, this be useless work. With 8-colors, we get 64
576   * pairs. With 256-colors, we get 32768 pairs with ext-colors
577   * support and 256 otherwise. */
578  cpmgr = owl_global_get_colorpair_mgr(&g);
579  if (cpmgr->overflow) {
580    owl_function_debugmsg("colorpairs: used all %d pairs; reset pairs and redraw.",
581                          owl_util_get_colorpairs());
582    owl_fmtext_reset_colorpairs(cpmgr);
583    owl_function_full_redisplay();
584    owl_window_redraw_scheduled();
585  }
586  return TRUE;
587}
588
589static GSourceFuncs redraw_funcs = {
590  _owl_window_redraw_prepare,
591  _owl_window_redraw_check,
592  _owl_window_redraw_dispatch,
593  NULL
594};
595
596CALLER_OWN GSource *owl_window_redraw_source_new(void)
597{
598  GSource *source;
599  source = g_source_new(&redraw_funcs, sizeof(GSource));
600  /* TODO: priority?? */
601  return source;
602}
Note: See TracBrowser for help on using the repository browser.