[fcce3f1] | 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.) |
---|
[358eeae] | 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. |
---|
[4eee948] | 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. |
---|
[95d54e6] | 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. |
---|