1 | /*
|
---|
2 | * Interface to the ncurses panel library
|
---|
3 | *
|
---|
4 | * Original version by Thomas Gellekum
|
---|
5 | */
|
---|
6 |
|
---|
7 | /* Release Number */
|
---|
8 |
|
---|
9 | static char *PyCursesVersion = "2.1";
|
---|
10 |
|
---|
11 | /* Includes */
|
---|
12 |
|
---|
13 | #include "Python.h"
|
---|
14 |
|
---|
15 | #include "py_curses.h"
|
---|
16 |
|
---|
17 | #include <panel.h>
|
---|
18 |
|
---|
19 | static PyObject *PyCursesError;
|
---|
20 |
|
---|
21 |
|
---|
22 | /* Utility Functions */
|
---|
23 |
|
---|
24 | /*
|
---|
25 | * Check the return code from a curses function and return None
|
---|
26 | * or raise an exception as appropriate.
|
---|
27 | */
|
---|
28 |
|
---|
29 | static PyObject *
|
---|
30 | PyCursesCheckERR(int code, char *fname)
|
---|
31 | {
|
---|
32 | if (code != ERR) {
|
---|
33 | Py_INCREF(Py_None);
|
---|
34 | return Py_None;
|
---|
35 | } else {
|
---|
36 | if (fname == NULL) {
|
---|
37 | PyErr_SetString(PyCursesError, catchall_ERR);
|
---|
38 | } else {
|
---|
39 | PyErr_Format(PyCursesError, "%s() returned ERR", fname);
|
---|
40 | }
|
---|
41 | return NULL;
|
---|
42 | }
|
---|
43 | }
|
---|
44 |
|
---|
45 | /*****************************************************************************
|
---|
46 | The Panel Object
|
---|
47 | ******************************************************************************/
|
---|
48 |
|
---|
49 | /* Definition of the panel object and panel type */
|
---|
50 |
|
---|
51 | typedef struct {
|
---|
52 | PyObject_HEAD
|
---|
53 | PANEL *pan;
|
---|
54 | PyCursesWindowObject *wo; /* for reference counts */
|
---|
55 | } PyCursesPanelObject;
|
---|
56 |
|
---|
57 | PyTypeObject PyCursesPanel_Type;
|
---|
58 |
|
---|
59 | #define PyCursesPanel_Check(v) (Py_TYPE(v) == &PyCursesPanel_Type)
|
---|
60 |
|
---|
61 | /* Some helper functions. The problem is that there's always a window
|
---|
62 | associated with a panel. To ensure that Python's GC doesn't pull
|
---|
63 | this window from under our feet we need to keep track of references
|
---|
64 | to the corresponding window object within Python. We can't use
|
---|
65 | dupwin(oldwin) to keep a copy of the curses WINDOW because the
|
---|
66 | contents of oldwin is copied only once; code like
|
---|
67 |
|
---|
68 | win = newwin(...)
|
---|
69 | pan = win.panel()
|
---|
70 | win.addstr(some_string)
|
---|
71 | pan.window().addstr(other_string)
|
---|
72 |
|
---|
73 | will fail. */
|
---|
74 |
|
---|
75 | /* We keep a linked list of PyCursesPanelObjects, lop. A list should
|
---|
76 | suffice, I don't expect more than a handful or at most a few
|
---|
77 | dozens of panel objects within a typical program. */
|
---|
78 | typedef struct _list_of_panels {
|
---|
79 | PyCursesPanelObject *po;
|
---|
80 | struct _list_of_panels *next;
|
---|
81 | } list_of_panels;
|
---|
82 |
|
---|
83 | /* list anchor */
|
---|
84 | static list_of_panels *lop;
|
---|
85 |
|
---|
86 | /* Insert a new panel object into lop */
|
---|
87 | static int
|
---|
88 | insert_lop(PyCursesPanelObject *po)
|
---|
89 | {
|
---|
90 | list_of_panels *new;
|
---|
91 |
|
---|
92 | if ((new = (list_of_panels *)malloc(sizeof(list_of_panels))) == NULL) {
|
---|
93 | PyErr_NoMemory();
|
---|
94 | return -1;
|
---|
95 | }
|
---|
96 | new->po = po;
|
---|
97 | new->next = lop;
|
---|
98 | lop = new;
|
---|
99 | return 0;
|
---|
100 | }
|
---|
101 |
|
---|
102 | /* Remove the panel object from lop */
|
---|
103 | static void
|
---|
104 | remove_lop(PyCursesPanelObject *po)
|
---|
105 | {
|
---|
106 | list_of_panels *temp, *n;
|
---|
107 |
|
---|
108 | temp = lop;
|
---|
109 | if (temp->po == po) {
|
---|
110 | lop = temp->next;
|
---|
111 | free(temp);
|
---|
112 | return;
|
---|
113 | }
|
---|
114 | while (temp->next == NULL || temp->next->po != po) {
|
---|
115 | if (temp->next == NULL) {
|
---|
116 | PyErr_SetString(PyExc_RuntimeError,
|
---|
117 | "remove_lop: can't find Panel Object");
|
---|
118 | return;
|
---|
119 | }
|
---|
120 | temp = temp->next;
|
---|
121 | }
|
---|
122 | n = temp->next->next;
|
---|
123 | free(temp->next);
|
---|
124 | temp->next = n;
|
---|
125 | return;
|
---|
126 | }
|
---|
127 |
|
---|
128 | /* Return the panel object that corresponds to pan */
|
---|
129 | static PyCursesPanelObject *
|
---|
130 | find_po(PANEL *pan)
|
---|
131 | {
|
---|
132 | list_of_panels *temp;
|
---|
133 | for (temp = lop; temp->po->pan != pan; temp = temp->next)
|
---|
134 | if (temp->next == NULL) return NULL; /* not found!? */
|
---|
135 | return temp->po;
|
---|
136 | }
|
---|
137 |
|
---|
138 | /* Function Prototype Macros - They are ugly but very, very useful. ;-)
|
---|
139 |
|
---|
140 | X - function name
|
---|
141 | TYPE - parameter Type
|
---|
142 | ERGSTR - format string for construction of the return value
|
---|
143 | PARSESTR - format string for argument parsing */
|
---|
144 |
|
---|
145 | #define Panel_NoArgNoReturnFunction(X) \
|
---|
146 | static PyObject *PyCursesPanel_##X(PyCursesPanelObject *self) \
|
---|
147 | { return PyCursesCheckERR(X(self->pan), # X); }
|
---|
148 |
|
---|
149 | #define Panel_NoArgTrueFalseFunction(X) \
|
---|
150 | static PyObject *PyCursesPanel_##X(PyCursesPanelObject *self) \
|
---|
151 | { \
|
---|
152 | if (X (self->pan) == FALSE) { Py_INCREF(Py_False); return Py_False; } \
|
---|
153 | else { Py_INCREF(Py_True); return Py_True; } }
|
---|
154 |
|
---|
155 | #define Panel_TwoArgNoReturnFunction(X, TYPE, PARSESTR) \
|
---|
156 | static PyObject *PyCursesPanel_##X(PyCursesPanelObject *self, PyObject *args) \
|
---|
157 | { \
|
---|
158 | TYPE arg1, arg2; \
|
---|
159 | if (!PyArg_ParseTuple(args, PARSESTR, &arg1, &arg2)) return NULL; \
|
---|
160 | return PyCursesCheckERR(X(self->pan, arg1, arg2), # X); }
|
---|
161 |
|
---|
162 | /* ------------- PANEL routines --------------- */
|
---|
163 |
|
---|
164 | Panel_NoArgNoReturnFunction(bottom_panel)
|
---|
165 | Panel_NoArgNoReturnFunction(hide_panel)
|
---|
166 | Panel_NoArgNoReturnFunction(show_panel)
|
---|
167 | Panel_NoArgNoReturnFunction(top_panel)
|
---|
168 | Panel_NoArgTrueFalseFunction(panel_hidden)
|
---|
169 | Panel_TwoArgNoReturnFunction(move_panel, int, "ii;y,x")
|
---|
170 |
|
---|
171 | /* Allocation and deallocation of Panel Objects */
|
---|
172 |
|
---|
173 | static PyObject *
|
---|
174 | PyCursesPanel_New(PANEL *pan, PyCursesWindowObject *wo)
|
---|
175 | {
|
---|
176 | PyCursesPanelObject *po;
|
---|
177 |
|
---|
178 | po = PyObject_NEW(PyCursesPanelObject, &PyCursesPanel_Type);
|
---|
179 | if (po == NULL) return NULL;
|
---|
180 | po->pan = pan;
|
---|
181 | if (insert_lop(po) < 0) {
|
---|
182 | po->wo = NULL;
|
---|
183 | Py_DECREF(po);
|
---|
184 | return NULL;
|
---|
185 | }
|
---|
186 | po->wo = wo;
|
---|
187 | Py_INCREF(wo);
|
---|
188 | return (PyObject *)po;
|
---|
189 | }
|
---|
190 |
|
---|
191 | static void
|
---|
192 | PyCursesPanel_Dealloc(PyCursesPanelObject *po)
|
---|
193 | {
|
---|
194 | (void)del_panel(po->pan);
|
---|
195 | if (po->wo != NULL) {
|
---|
196 | Py_DECREF(po->wo);
|
---|
197 | remove_lop(po);
|
---|
198 | }
|
---|
199 | PyObject_DEL(po);
|
---|
200 | }
|
---|
201 |
|
---|
202 | /* panel_above(NULL) returns the bottom panel in the stack. To get
|
---|
203 | this behaviour we use curses.panel.bottom_panel(). */
|
---|
204 | static PyObject *
|
---|
205 | PyCursesPanel_above(PyCursesPanelObject *self)
|
---|
206 | {
|
---|
207 | PANEL *pan;
|
---|
208 | PyCursesPanelObject *po;
|
---|
209 |
|
---|
210 | pan = panel_above(self->pan);
|
---|
211 |
|
---|
212 | if (pan == NULL) { /* valid output, it means the calling panel
|
---|
213 | is on top of the stack */
|
---|
214 | Py_INCREF(Py_None);
|
---|
215 | return Py_None;
|
---|
216 | }
|
---|
217 | po = find_po(pan);
|
---|
218 | if (po == NULL) {
|
---|
219 | PyErr_SetString(PyExc_RuntimeError,
|
---|
220 | "panel_above: can't find Panel Object");
|
---|
221 | return NULL;
|
---|
222 | }
|
---|
223 | Py_INCREF(po);
|
---|
224 | return (PyObject *)po;
|
---|
225 | }
|
---|
226 |
|
---|
227 | /* panel_below(NULL) returns the top panel in the stack. To get
|
---|
228 | this behaviour we use curses.panel.top_panel(). */
|
---|
229 | static PyObject *
|
---|
230 | PyCursesPanel_below(PyCursesPanelObject *self)
|
---|
231 | {
|
---|
232 | PANEL *pan;
|
---|
233 | PyCursesPanelObject *po;
|
---|
234 |
|
---|
235 | pan = panel_below(self->pan);
|
---|
236 |
|
---|
237 | if (pan == NULL) { /* valid output, it means the calling panel
|
---|
238 | is on the bottom of the stack */
|
---|
239 | Py_INCREF(Py_None);
|
---|
240 | return Py_None;
|
---|
241 | }
|
---|
242 | po = find_po(pan);
|
---|
243 | if (po == NULL) {
|
---|
244 | PyErr_SetString(PyExc_RuntimeError,
|
---|
245 | "panel_below: can't find Panel Object");
|
---|
246 | return NULL;
|
---|
247 | }
|
---|
248 | Py_INCREF(po);
|
---|
249 | return (PyObject *)po;
|
---|
250 | }
|
---|
251 |
|
---|
252 | static PyObject *
|
---|
253 | PyCursesPanel_window(PyCursesPanelObject *self)
|
---|
254 | {
|
---|
255 | Py_INCREF(self->wo);
|
---|
256 | return (PyObject *)self->wo;
|
---|
257 | }
|
---|
258 |
|
---|
259 | static PyObject *
|
---|
260 | PyCursesPanel_replace_panel(PyCursesPanelObject *self, PyObject *args)
|
---|
261 | {
|
---|
262 | PyCursesPanelObject *po;
|
---|
263 | PyCursesWindowObject *temp;
|
---|
264 | int rtn;
|
---|
265 |
|
---|
266 | if (PyTuple_Size(args) != 1) {
|
---|
267 | PyErr_SetString(PyExc_TypeError, "replace requires one argument");
|
---|
268 | return NULL;
|
---|
269 | }
|
---|
270 | if (!PyArg_ParseTuple(args, "O!;window object",
|
---|
271 | &PyCursesWindow_Type, &temp))
|
---|
272 | return NULL;
|
---|
273 |
|
---|
274 | po = find_po(self->pan);
|
---|
275 | if (po == NULL) {
|
---|
276 | PyErr_SetString(PyExc_RuntimeError,
|
---|
277 | "replace_panel: can't find Panel Object");
|
---|
278 | return NULL;
|
---|
279 | }
|
---|
280 |
|
---|
281 | rtn = replace_panel(self->pan, temp->win);
|
---|
282 | if (rtn == ERR) {
|
---|
283 | PyErr_SetString(PyCursesError, "replace_panel() returned ERR");
|
---|
284 | return NULL;
|
---|
285 | }
|
---|
286 | Py_DECREF(po->wo);
|
---|
287 | po->wo = temp;
|
---|
288 | Py_INCREF(po->wo);
|
---|
289 | Py_INCREF(Py_None);
|
---|
290 | return Py_None;
|
---|
291 | }
|
---|
292 |
|
---|
293 | static PyObject *
|
---|
294 | PyCursesPanel_set_panel_userptr(PyCursesPanelObject *self, PyObject *obj)
|
---|
295 | {
|
---|
296 | PyObject *oldobj;
|
---|
297 | int rc;
|
---|
298 | PyCursesInitialised;
|
---|
299 | Py_INCREF(obj);
|
---|
300 | oldobj = (PyObject *) panel_userptr(self->pan);
|
---|
301 | rc = set_panel_userptr(self->pan, (void*)obj);
|
---|
302 | if (rc == ERR) {
|
---|
303 | /* In case of an ncurses error, decref the new object again */
|
---|
304 | Py_DECREF(obj);
|
---|
305 | }
|
---|
306 | Py_XDECREF(oldobj);
|
---|
307 | return PyCursesCheckERR(rc, "set_panel_userptr");
|
---|
308 | }
|
---|
309 |
|
---|
310 | static PyObject *
|
---|
311 | PyCursesPanel_userptr(PyCursesPanelObject *self)
|
---|
312 | {
|
---|
313 | PyObject *obj;
|
---|
314 | PyCursesInitialised;
|
---|
315 | obj = (PyObject *) panel_userptr(self->pan);
|
---|
316 | if (obj == NULL) {
|
---|
317 | PyErr_SetString(PyCursesError, "no userptr set");
|
---|
318 | return NULL;
|
---|
319 | }
|
---|
320 |
|
---|
321 | Py_INCREF(obj);
|
---|
322 | return obj;
|
---|
323 | }
|
---|
324 |
|
---|
325 |
|
---|
326 | /* Module interface */
|
---|
327 |
|
---|
328 | static PyMethodDef PyCursesPanel_Methods[] = {
|
---|
329 | {"above", (PyCFunction)PyCursesPanel_above, METH_NOARGS},
|
---|
330 | {"below", (PyCFunction)PyCursesPanel_below, METH_NOARGS},
|
---|
331 | {"bottom", (PyCFunction)PyCursesPanel_bottom_panel, METH_NOARGS},
|
---|
332 | {"hidden", (PyCFunction)PyCursesPanel_panel_hidden, METH_NOARGS},
|
---|
333 | {"hide", (PyCFunction)PyCursesPanel_hide_panel, METH_NOARGS},
|
---|
334 | {"move", (PyCFunction)PyCursesPanel_move_panel, METH_VARARGS},
|
---|
335 | {"replace", (PyCFunction)PyCursesPanel_replace_panel, METH_VARARGS},
|
---|
336 | {"set_userptr", (PyCFunction)PyCursesPanel_set_panel_userptr, METH_O},
|
---|
337 | {"show", (PyCFunction)PyCursesPanel_show_panel, METH_NOARGS},
|
---|
338 | {"top", (PyCFunction)PyCursesPanel_top_panel, METH_NOARGS},
|
---|
339 | {"userptr", (PyCFunction)PyCursesPanel_userptr, METH_NOARGS},
|
---|
340 | {"window", (PyCFunction)PyCursesPanel_window, METH_NOARGS},
|
---|
341 | {NULL, NULL} /* sentinel */
|
---|
342 | };
|
---|
343 |
|
---|
344 | static PyObject *
|
---|
345 | PyCursesPanel_GetAttr(PyCursesPanelObject *self, char *name)
|
---|
346 | {
|
---|
347 | return Py_FindMethod(PyCursesPanel_Methods, (PyObject *)self, name);
|
---|
348 | }
|
---|
349 |
|
---|
350 | /* -------------------------------------------------------*/
|
---|
351 |
|
---|
352 | PyTypeObject PyCursesPanel_Type = {
|
---|
353 | PyVarObject_HEAD_INIT(NULL, 0)
|
---|
354 | "_curses_panel.curses panel", /*tp_name*/
|
---|
355 | sizeof(PyCursesPanelObject), /*tp_basicsize*/
|
---|
356 | 0, /*tp_itemsize*/
|
---|
357 | /* methods */
|
---|
358 | (destructor)PyCursesPanel_Dealloc, /*tp_dealloc*/
|
---|
359 | 0, /*tp_print*/
|
---|
360 | (getattrfunc)PyCursesPanel_GetAttr, /*tp_getattr*/
|
---|
361 | (setattrfunc)0, /*tp_setattr*/
|
---|
362 | 0, /*tp_compare*/
|
---|
363 | 0, /*tp_repr*/
|
---|
364 | 0, /*tp_as_number*/
|
---|
365 | 0, /*tp_as_sequence*/
|
---|
366 | 0, /*tp_as_mapping*/
|
---|
367 | 0, /*tp_hash*/
|
---|
368 | };
|
---|
369 |
|
---|
370 | /* Wrapper for panel_above(NULL). This function returns the bottom
|
---|
371 | panel of the stack, so it's renamed to bottom_panel().
|
---|
372 | panel.above() *requires* a panel object in the first place which
|
---|
373 | may be undesirable. */
|
---|
374 | static PyObject *
|
---|
375 | PyCurses_bottom_panel(PyObject *self)
|
---|
376 | {
|
---|
377 | PANEL *pan;
|
---|
378 | PyCursesPanelObject *po;
|
---|
379 |
|
---|
380 | PyCursesInitialised;
|
---|
381 |
|
---|
382 | pan = panel_above(NULL);
|
---|
383 |
|
---|
384 | if (pan == NULL) { /* valid output, it means
|
---|
385 | there's no panel at all */
|
---|
386 | Py_INCREF(Py_None);
|
---|
387 | return Py_None;
|
---|
388 | }
|
---|
389 | po = find_po(pan);
|
---|
390 | if (po == NULL) {
|
---|
391 | PyErr_SetString(PyExc_RuntimeError,
|
---|
392 | "panel_above: can't find Panel Object");
|
---|
393 | return NULL;
|
---|
394 | }
|
---|
395 | Py_INCREF(po);
|
---|
396 | return (PyObject *)po;
|
---|
397 | }
|
---|
398 |
|
---|
399 | static PyObject *
|
---|
400 | PyCurses_new_panel(PyObject *self, PyObject *args)
|
---|
401 | {
|
---|
402 | PyCursesWindowObject *win;
|
---|
403 | PANEL *pan;
|
---|
404 |
|
---|
405 | if (!PyArg_ParseTuple(args, "O!", &PyCursesWindow_Type, &win))
|
---|
406 | return NULL;
|
---|
407 | pan = new_panel(win->win);
|
---|
408 | if (pan == NULL) {
|
---|
409 | PyErr_SetString(PyCursesError, catchall_NULL);
|
---|
410 | return NULL;
|
---|
411 | }
|
---|
412 | return (PyObject *)PyCursesPanel_New(pan, win);
|
---|
413 | }
|
---|
414 |
|
---|
415 |
|
---|
416 | /* Wrapper for panel_below(NULL). This function returns the top panel
|
---|
417 | of the stack, so it's renamed to top_panel(). panel.below()
|
---|
418 | *requires* a panel object in the first place which may be
|
---|
419 | undesirable. */
|
---|
420 | static PyObject *
|
---|
421 | PyCurses_top_panel(PyObject *self)
|
---|
422 | {
|
---|
423 | PANEL *pan;
|
---|
424 | PyCursesPanelObject *po;
|
---|
425 |
|
---|
426 | PyCursesInitialised;
|
---|
427 |
|
---|
428 | pan = panel_below(NULL);
|
---|
429 |
|
---|
430 | if (pan == NULL) { /* valid output, it means
|
---|
431 | there's no panel at all */
|
---|
432 | Py_INCREF(Py_None);
|
---|
433 | return Py_None;
|
---|
434 | }
|
---|
435 | po = find_po(pan);
|
---|
436 | if (po == NULL) {
|
---|
437 | PyErr_SetString(PyExc_RuntimeError,
|
---|
438 | "panel_below: can't find Panel Object");
|
---|
439 | return NULL;
|
---|
440 | }
|
---|
441 | Py_INCREF(po);
|
---|
442 | return (PyObject *)po;
|
---|
443 | }
|
---|
444 |
|
---|
445 | static PyObject *PyCurses_update_panels(PyObject *self)
|
---|
446 | {
|
---|
447 | PyCursesInitialised;
|
---|
448 | update_panels();
|
---|
449 | Py_INCREF(Py_None);
|
---|
450 | return Py_None;
|
---|
451 | }
|
---|
452 |
|
---|
453 |
|
---|
454 | /* List of functions defined in the module */
|
---|
455 |
|
---|
456 | static PyMethodDef PyCurses_methods[] = {
|
---|
457 | {"bottom_panel", (PyCFunction)PyCurses_bottom_panel, METH_NOARGS},
|
---|
458 | {"new_panel", (PyCFunction)PyCurses_new_panel, METH_VARARGS},
|
---|
459 | {"top_panel", (PyCFunction)PyCurses_top_panel, METH_NOARGS},
|
---|
460 | {"update_panels", (PyCFunction)PyCurses_update_panels, METH_NOARGS},
|
---|
461 | {NULL, NULL} /* sentinel */
|
---|
462 | };
|
---|
463 |
|
---|
464 | /* Initialization function for the module */
|
---|
465 |
|
---|
466 | PyMODINIT_FUNC
|
---|
467 | init_curses_panel(void)
|
---|
468 | {
|
---|
469 | PyObject *m, *d, *v;
|
---|
470 |
|
---|
471 | /* Initialize object type */
|
---|
472 | Py_TYPE(&PyCursesPanel_Type) = &PyType_Type;
|
---|
473 |
|
---|
474 | import_curses();
|
---|
475 |
|
---|
476 | /* Create the module and add the functions */
|
---|
477 | m = Py_InitModule("_curses_panel", PyCurses_methods);
|
---|
478 | if (m == NULL)
|
---|
479 | return;
|
---|
480 | d = PyModule_GetDict(m);
|
---|
481 |
|
---|
482 | /* For exception _curses_panel.error */
|
---|
483 | PyCursesError = PyErr_NewException("_curses_panel.error", NULL, NULL);
|
---|
484 | PyDict_SetItemString(d, "error", PyCursesError);
|
---|
485 |
|
---|
486 | /* Make the version available */
|
---|
487 | v = PyString_FromString(PyCursesVersion);
|
---|
488 | PyDict_SetItemString(d, "version", v);
|
---|
489 | PyDict_SetItemString(d, "__version__", v);
|
---|
490 | Py_DECREF(v);
|
---|
491 | }
|
---|