| 1 | ========== |
|---|
| 2 | owl_window |
|---|
| 3 | ========== |
|---|
| 4 | |
|---|
| 5 | owl_window is intended to be a wrapper over ncurses WINDOW and PANEL |
|---|
| 6 | objects to abstract away much of the nastiness that is ncurses. It |
|---|
| 7 | provides redraw scheduling, and sane window moving and resizing. |
|---|
| 8 | |
|---|
| 9 | ------------------- |
|---|
| 10 | Hierarchy and types |
|---|
| 11 | ------------------- |
|---|
| 12 | |
|---|
| 13 | owl_window creates a hierarchy of window objects. The hierarchy |
|---|
| 14 | currently uses ncurses' built-in window hierarchy system with |
|---|
| 15 | subwins. This was easier to implement but has some caveats, detailed |
|---|
| 16 | later. |
|---|
| 17 | |
|---|
| 18 | At the top level, we have the screen window which represents the |
|---|
| 19 | actual screen. The screen window is not usually interacted with |
|---|
| 20 | directly, but rather it is a handle to interface with the terminal as |
|---|
| 21 | if it were a normal window. |
|---|
| 22 | |
|---|
| 23 | Underneath the screen, we have top-level windows, or panels. Panels |
|---|
| 24 | are normal WINDOWs (newwin) connected to a PANEL (from |
|---|
| 25 | libpanel). Panels may overlap freely and maintain a stacking |
|---|
| 26 | order. (For now, this stacking order is not exposed except for a |
|---|
| 27 | mechanism to move a panel to the top. This is not difficult to |
|---|
| 28 | otherwise correct otherwise.) |
|---|
| 29 | |
|---|
| 30 | Under each panel is a tree of subwins. These are backed by ncurses |
|---|
| 31 | subwins. Because ncurses subwins simply share their parent windows' |
|---|
| 32 | buffer, we cannot provide as many nice guarantees about |
|---|
| 33 | ordering. Sibling subwins may not overlap, and parents must |
|---|
| 34 | (sometimes) take children position into account. More on this |
|---|
| 35 | later. This model is sufficient for BarnOwl's current purposes. Should |
|---|
| 36 | we need to, this may later be reworked. (Specifically, we'd want to |
|---|
| 37 | back everything by pads and build a compositing window manager.) |
|---|
| 38 | |
|---|
| 39 | Each of these three types is temporarily stuffed into one type. We can |
|---|
| 40 | later do subtyping of sorts, but that will require more heavy use of |
|---|
| 41 | GObject. |
|---|
| 42 | |
|---|
| 43 | As an example, here is the graph used by BarnOwl's current interface: |
|---|
| 44 | |
|---|
| 45 | +==========+ |
|---|
| 46 | || screen || |
|---|
| 47 | +==========+ |
|---|
| 48 | / \ |
|---|
| 49 | +-----------+ +--------+ |
|---|
| 50 | | mainpanel | | popwin | |
|---|
| 51 | +-----------+ +--------+ |
|---|
| 52 | / / | \ \ |
|---|
| 53 | recwin sepwin msgwin typwin viewwin |
|---|
| 54 | |
|---|
| 55 | |
|---|
| 56 | A window may be unlinked from its parent with a call to |
|---|
| 57 | owl_window_unlink. From then on, the object is still accessible, but |
|---|
| 58 | it will not do anything. If desired, we may add the ability to relink |
|---|
| 59 | a window in future. This behavior allows us to safely hold references |
|---|
| 60 | to windows, even after they have been "destroyed". |
|---|
| 61 | |
|---|
| 62 | ---------- |
|---|
| 63 | Visibility |
|---|
| 64 | ---------- |
|---|
| 65 | |
|---|
| 66 | Each window maintains state for whether or not the user has requested |
|---|
| 67 | it be visible. A user calls owl_window_show to flip the 'shown' bit on |
|---|
| 68 | a window and owl_window_show_all to do so recursively. Likewise, |
|---|
| 69 | owl_window_hide disables this bit. If a window and all its parents are |
|---|
| 70 | shown, then the user has requested this window be visible. |
|---|
| 71 | |
|---|
| 72 | We say a window is realized if it has a corresponding on-screen |
|---|
| 73 | window. A window will only be realized if it is requested to be |
|---|
| 74 | shown. Furthermore, the window's parent must also be realized (unless |
|---|
| 75 | it is the screen). If a window is failed to be created for any reason |
|---|
| 76 | (most notably if its dimensions are zero), owl_window will cope and |
|---|
| 77 | consider it unrealized. |
|---|
| 78 | |
|---|
| 79 | This realized/unrealized state fixes two nuisances with the old code: |
|---|
| 80 | |
|---|
| 81 | First, owl_window can safely manage all NULL windows. Interacting |
|---|
| 82 | code needn't check to avoid segfaults; the owl_window is never |
|---|
| 83 | NULL, and code requesting the WINDOW will never be called when |
|---|
| 84 | NULL. |
|---|
| 85 | |
|---|
| 86 | Second, we have a consistent handle to a logical window, despite |
|---|
| 87 | changes in the physical window. This is important for resizing |
|---|
| 88 | windows. It is difficult to safely resize windows in ncurses. It |
|---|
| 89 | is usually far easier to destroy everything and recreate it. |
|---|
| 90 | |
|---|
| 91 | Note that this means owl_window will intentionally never expose the |
|---|
| 92 | underlying WINDOW except during appropriate events. |
|---|
| 93 | |
|---|
| 94 | -------- |
|---|
| 95 | Resizing |
|---|
| 96 | -------- |
|---|
| 97 | |
|---|
| 98 | Windows may be moved and resized with owl_window_move, |
|---|
| 99 | owl_window_resize, and owl_window_set_position. Internally, resizing |
|---|
| 100 | is very simple. We unrealize the window, set new position, and then |
|---|
| 101 | realize it at the new location. When a window changes size, it emits a |
|---|
| 102 | "resized" signal which windows may react to. It may actually possible |
|---|
| 103 | to optimize moves, but this has a slight nuisance with incorrect |
|---|
| 104 | begy/begx values which cursors currently rely on. |
|---|
| 105 | |
|---|
| 106 | It is intended that top-level windows connect to this signal to change |
|---|
| 107 | themselves, while windows containing subwins connect to their own |
|---|
| 108 | signals to relayout their subwins. This is because top-level windows |
|---|
| 109 | may be sized independently, while sibling subwins should not |
|---|
| 110 | overlap. The signals are implemented currently with GObject signals. |
|---|
| 111 | |
|---|
| 112 | --------- |
|---|
| 113 | Redrawing |
|---|
| 114 | --------- |
|---|
| 115 | |
|---|
| 116 | Currently, users of widgets in BarnOwl must remember to call |
|---|
| 117 | owl_foo_redisplay when a widget needs to be redrawn. This is quite |
|---|
| 118 | annoying and an implementation detail that functions like |
|---|
| 119 | owl_editwin_insert_string should take care of. To allow widget |
|---|
| 120 | implementations to redraw themselves without calling an expensive |
|---|
| 121 | redisplay many times, owl_window_dirty flags a window as "dirty". The |
|---|
| 122 | framework will promise to redraw that window before the next doupdate, |
|---|
| 123 | while repeated owl_window_dirty calls remain cheap. |
|---|
| 124 | |
|---|
| 125 | Windows currently redraw with a "redraw" GObject signal. This and the |
|---|
| 126 | resize signal, is somewhat awkward for the editwin which attaches to |
|---|
| 127 | and releases windows, as we must remember signal ids. However, if we |
|---|
| 128 | make widget objects (owl_editwin, owl_mainwin, etc.) also GObjects, |
|---|
| 129 | these signals will automatically detach. We may also consider a |
|---|
| 130 | different mechanism than continuously attaching/detaching |
|---|
| 131 | things. That's kinda weird anyway. |
|---|
| 132 | |
|---|
| 133 | (We may want to replace it with a normal callback at some |
|---|
| 134 | point. Mostly using the GObject signals to play with them, and because |
|---|
| 135 | we'd likely get some form of automatic binding generation.) |
|---|
| 136 | |
|---|
| 137 | ---------------------- |
|---|
| 138 | Known issues and notes |
|---|
| 139 | ---------------------- |
|---|
| 140 | |
|---|
| 141 | - owl_window does not export the underlying WINDOW*. This is |
|---|
| 142 | intentional. To safely resize windows, owl_window reserves the right |
|---|
| 143 | to destroy/recreate the window whenever it feels like. We can add |
|---|
| 144 | "window-realized"/"window-unrealized" signals if people really want |
|---|
| 145 | though. |
|---|
| 146 | |
|---|
| 147 | - This is currently using GObject. This buys us a lot of niceness, but |
|---|
| 148 | it is adding a new library with full-blown (and somewhat |
|---|
| 149 | overengineered) object system. It's very useful for prototyping, but |
|---|
| 150 | if people want, it can be replaced. I think it's actually |
|---|
| 151 | worthwhile, as most of the overengineering is explicitly designed |
|---|
| 152 | for language bindings, something we could be better at. |
|---|
| 153 | |
|---|
| 154 | - There is this really sketchy "three types in one" thing going on |
|---|
| 155 | with the screen, panels, and subwins. I'd like to refactor that into |
|---|
| 156 | some sort of subtyping, but that would ideally involve even more use |
|---|
| 157 | of GObject. |
|---|
| 158 | |
|---|
| 159 | - owl_mainwin is not very well ported and the windows do not have a |
|---|
| 160 | very consistent implementation. This is a known problem that will be |
|---|
| 161 | addressed in the next iteration. The current ports were done partly |
|---|
| 162 | as experiments for conventions and mostly to get something working |
|---|
| 163 | as soon as possible. Among things that should change is for the |
|---|
| 164 | widgets to all use new/delete/pointers instead of |
|---|
| 165 | init/cleanup/embed-in-struct. |
|---|
| 166 | |
|---|
| 167 | - The editwin and a few others are strange and keep track of signal |
|---|
| 168 | ids. This is somewhat a side effect of us not using GObject |
|---|
| 169 | everywhere; the signals can automatically disconnect in the right |
|---|
| 170 | contexts if we do. |
|---|
| 171 | |
|---|
| 172 | - The sepbar depends on a value computed while the mainwin is drawn, |
|---|
| 173 | so we currently ensure the windows are created in the right order |
|---|
| 174 | for the repaints to occur correctly. This is rather poor and should |
|---|
| 175 | be refactored later. |
|---|
| 176 | |
|---|
| 177 | - The code fairly routinely does casts to add extra throw-away |
|---|
| 178 | parameters to functions. Most notably, casting functions of type |
|---|
| 179 | |
|---|
| 180 | void do_something(void *obj) |
|---|
| 181 | |
|---|
| 182 | to something of type |
|---|
| 183 | |
|---|
| 184 | void do_something(void *obj, void *user_data) |
|---|
| 185 | |
|---|
| 186 | (the latter is a GFunc) with the expectation that the user_data |
|---|
| 187 | argument is discarded. While calling with this cast is undefined by |
|---|
| 188 | the standard and depends on calling convention, glib uses it |
|---|
| 189 | internally /everywhere/ and much of glib and gobject API heavily |
|---|
| 190 | depends on it. As BarnOwl already depends on glib, we implicitly |
|---|
| 191 | assume this cast works. |
|---|