========== owl_window ========== owl_window is intended to be a wrapper over ncurses WINDOW and PANEL objects to abstract away much of the nastiness that is ncurses. It provides redraw scheduling, and sane window moving and resizing. ------------------- Hierarchy and types ------------------- owl_window creates a hierarchy of window objects. The hierarchy currently uses ncurses' built-in window hierarchy system with subwins. This was easier to implement but has some caveats, detailed later. At the top level, we have the screen window which represents the actual screen. The screen window is not usually interacted with directly, but rather it is a handle to interface with the terminal as if it were a normal window. Underneath the screen, we have top-level windows, or panels. Panels are normal WINDOWs (newwin) connected to a PANEL (from libpanel). Panels may overlap freely and maintain a stacking order. (For now, this stacking order is not exposed except for a mechanism to move a panel to the top. This is not difficult to otherwise correct otherwise.) Under each panel is a tree of subwins. These are backed by ncurses subwins. Because ncurses subwins simply share their parent windows' buffer, we cannot provide as many nice guarantees about ordering. Sibling subwins may not overlap, and parents must (sometimes) take children position into account. More on this later. This model is sufficient for BarnOwl's current purposes. Should we need to, this may later be reworked. (Specifically, we'd want to back everything by pads and build a compositing window manager.) Each of these three types is temporarily stuffed into one type. We can later do subtyping of sorts, but that will require more heavy use of GObject. As an example, here is the graph used by BarnOwl's current interface: +==========+ || screen || +==========+ / \ +-----------+ +--------+ | mainpanel | | popwin | +-----------+ +--------+ / / | \ \ recwin sepwin msgwin typwin viewwin A window may be unlinked from its parent with a call to owl_window_unlink. From then on, the object is still accessible, but it will not do anything. If desired, we may add the ability to relink a window in future. This behavior allows us to safely hold references to windows, even after they have been "destroyed". ---------- Visibility ---------- Each window maintains state for whether or not the user has requested it be visible. A user calls owl_window_show to flip the 'shown' bit on a window and owl_window_show_all to do so recursively. Likewise, owl_window_hide disables this bit. If a window and all its parents are shown, then the user has requested this window be visible. We say a window is realized if it has a corresponding on-screen window. A window will only be realized if it is requested to be shown. Furthermore, the window's parent must also be realized (unless it is the screen). If a window is failed to be created for any reason (most notably if its dimensions are zero), owl_window will cope and consider it unrealized. This realized/unrealized state fixes two nuisances with the old code: First, owl_window can safely manage all NULL windows. Interacting code needn't check to avoid segfaults; the owl_window is never NULL, and code requesting the WINDOW will never be called when NULL. Second, we have a consistent handle to a logical window, despite changes in the physical window. This is important for resizing windows. It is difficult to safely resize windows in ncurses. It is usually far easier to destroy everything and recreate it. Note that this means owl_window will intentionally never expose the underlying WINDOW except during appropriate events. -------- Resizing -------- Windows may be moved and resized with owl_window_move, owl_window_resize, and owl_window_set_position. Internally, resizing is very simple. We unrealize the window, set new position, and then realize it at the new location. When a window changes size, it emits a "resized" signal which windows may react to. It may actually possible to optimize moves, but this has a slight nuisance with incorrect begy/begx values which cursors currently rely on. It is intended that top-level windows connect to this signal to change themselves, while windows containing subwins connect to their own signals to relayout their subwins. This is because top-level windows may be sized independently, while sibling subwins should not overlap. The signals are implemented currently with GObject signals. --------- Redrawing --------- Currently, users of widgets in BarnOwl must remember to call owl_foo_redisplay when a widget needs to be redrawn. This is quite annoying and an implementation detail that functions like owl_editwin_insert_string should take care of. To allow widget implementations to redraw themselves without calling an expensive redisplay many times, owl_window_dirty flags a window as "dirty". The framework will promise to redraw that window before the next doupdate, while repeated owl_window_dirty calls remain cheap. Windows currently redraw with a "redraw" GObject signal. This and the resize signal, is somewhat awkward for the editwin which attaches to and releases windows, as we must remember signal ids. However, if we make widget objects (owl_editwin, owl_mainwin, etc.) also GObjects, these signals will automatically detach. We may also consider a different mechanism than continuously attaching/detaching things. That's kinda weird anyway. (We may want to replace it with a normal callback at some point. Mostly using the GObject signals to play with them, and because we'd likely get some form of automatic binding generation.) ---------------------- Known issues and notes ---------------------- - owl_window does not export the underlying WINDOW*. This is intentional. To safely resize windows, owl_window reserves the right to destroy/recreate the window whenever it feels like. We can add "window-realized"/"window-unrealized" signals if people really want though. - This is currently using GObject. This buys us a lot of niceness, but it is adding a new library with full-blown (and somewhat overengineered) object system. It's very useful for prototyping, but if people want, it can be replaced. I think it's actually worthwhile, as most of the overengineering is explicitly designed for language bindings, something we could be better at. - There is this really sketchy "three types in one" thing going on with the screen, panels, and subwins. I'd like to refactor that into some sort of subtyping, but that would ideally involve even more use of GObject. - owl_mainwin is not very well ported and the windows do not have a very consistent implementation. This is a known problem that will be addressed in the next iteration. The current ports were done partly as experiments for conventions and mostly to get something working as soon as possible. Among things that should change is for the widgets to all use new/delete/pointers instead of init/cleanup/embed-in-struct. - The editwin and a few others are strange and keep track of signal ids. This is somewhat a side effect of us not using GObject everywhere; the signals can automatically disconnect in the right contexts if we do. - The sepbar depends on a value computed while the mainwin is drawn, so we currently ensure the windows are created in the right order for the repaints to occur correctly. This is rather poor and should be refactored later. - The code fairly routinely does casts to add extra throw-away parameters to functions. Most notably, casting functions of type void do_something(void *obj) to something of type void do_something(void *obj, void *user_data) (the latter is a GFunc) with the expectation that the user_data argument is discarded. While calling with this cast is undefined by the standard and depends on calling convention, glib uses it internally /everywhere/ and much of glib and gobject API heavily depends on it. As BarnOwl already depends on glib, we implicitly assume this cast works.