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. |
---|