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 *
PyCursesCheckERR(int code,char * fname)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
insert_lop(PyCursesPanelObject * po)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
remove_lop(PyCursesPanelObject * po)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 *
find_po(PANEL * pan)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)
Panel_NoArgNoReturnFunction(hide_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
PyCursesPanel_Dealloc(PyCursesPanelObject * po)192 PyCursesPanel_Dealloc(PyCursesPanelObject *po)
193 {
194     PyObject *obj = (PyObject *) panel_userptr(po->pan);
195     if (obj) {
196         (void)set_panel_userptr(po->pan, NULL);
197         Py_DECREF(obj);
198     }
199     (void)del_panel(po->pan);
200     if (po->wo != NULL) {
201         Py_DECREF(po->wo);
202         remove_lop(po);
203     }
204     PyObject_DEL(po);
205 }
206 
207 /* panel_above(NULL) returns the bottom panel in the stack. To get
208    this behaviour we use curses.panel.bottom_panel(). */
209 static PyObject *
PyCursesPanel_above(PyCursesPanelObject * self)210 PyCursesPanel_above(PyCursesPanelObject *self)
211 {
212     PANEL *pan;
213     PyCursesPanelObject *po;
214 
215     pan = panel_above(self->pan);
216 
217     if (pan == NULL) {          /* valid output, it means the calling panel
218                                    is on top of the stack */
219         Py_INCREF(Py_None);
220         return Py_None;
221     }
222     po = find_po(pan);
223     if (po == NULL) {
224         PyErr_SetString(PyExc_RuntimeError,
225                         "panel_above: can't find Panel Object");
226         return NULL;
227     }
228     Py_INCREF(po);
229     return (PyObject *)po;
230 }
231 
232 /* panel_below(NULL) returns the top panel in the stack. To get
233    this behaviour we use curses.panel.top_panel(). */
234 static PyObject *
PyCursesPanel_below(PyCursesPanelObject * self)235 PyCursesPanel_below(PyCursesPanelObject *self)
236 {
237     PANEL *pan;
238     PyCursesPanelObject *po;
239 
240     pan = panel_below(self->pan);
241 
242     if (pan == NULL) {          /* valid output, it means the calling panel
243                                    is on the bottom of the stack */
244         Py_INCREF(Py_None);
245         return Py_None;
246     }
247     po = find_po(pan);
248     if (po == NULL) {
249         PyErr_SetString(PyExc_RuntimeError,
250                         "panel_below: can't find Panel Object");
251         return NULL;
252     }
253     Py_INCREF(po);
254     return (PyObject *)po;
255 }
256 
257 static PyObject *
PyCursesPanel_window(PyCursesPanelObject * self)258 PyCursesPanel_window(PyCursesPanelObject *self)
259 {
260     Py_INCREF(self->wo);
261     return (PyObject *)self->wo;
262 }
263 
264 static PyObject *
PyCursesPanel_replace_panel(PyCursesPanelObject * self,PyObject * args)265 PyCursesPanel_replace_panel(PyCursesPanelObject *self, PyObject *args)
266 {
267     PyCursesPanelObject *po;
268     PyCursesWindowObject *temp;
269     int rtn;
270 
271     if (PyTuple_Size(args) != 1) {
272         PyErr_SetString(PyExc_TypeError, "replace requires one argument");
273         return NULL;
274     }
275     if (!PyArg_ParseTuple(args, "O!;window object",
276                           &PyCursesWindow_Type, &temp))
277         return NULL;
278 
279     po = find_po(self->pan);
280     if (po == NULL) {
281         PyErr_SetString(PyExc_RuntimeError,
282                         "replace_panel: can't find Panel Object");
283         return NULL;
284     }
285 
286     rtn = replace_panel(self->pan, temp->win);
287     if (rtn == ERR) {
288         PyErr_SetString(PyCursesError, "replace_panel() returned ERR");
289         return NULL;
290     }
291     Py_INCREF(temp);
292     Py_SETREF(po->wo, temp);
293     Py_INCREF(Py_None);
294     return Py_None;
295 }
296 
297 static PyObject *
PyCursesPanel_set_panel_userptr(PyCursesPanelObject * self,PyObject * obj)298 PyCursesPanel_set_panel_userptr(PyCursesPanelObject *self, PyObject *obj)
299 {
300     PyObject *oldobj;
301     int rc;
302     PyCursesInitialised;
303     Py_INCREF(obj);
304     oldobj = (PyObject *) panel_userptr(self->pan);
305     rc = set_panel_userptr(self->pan, (void*)obj);
306     if (rc == ERR) {
307         /* In case of an ncurses error, decref the new object again */
308         Py_DECREF(obj);
309     }
310     Py_XDECREF(oldobj);
311     return PyCursesCheckERR(rc, "set_panel_userptr");
312 }
313 
314 static PyObject *
PyCursesPanel_userptr(PyCursesPanelObject * self)315 PyCursesPanel_userptr(PyCursesPanelObject *self)
316 {
317     PyObject *obj;
318     PyCursesInitialised;
319     obj = (PyObject *) panel_userptr(self->pan);
320     if (obj == NULL) {
321         PyErr_SetString(PyCursesError, "no userptr set");
322         return NULL;
323     }
324 
325     Py_INCREF(obj);
326     return obj;
327 }
328 
329 
330 /* Module interface */
331 
332 static PyMethodDef PyCursesPanel_Methods[] = {
333     {"above",           (PyCFunction)PyCursesPanel_above, METH_NOARGS},
334     {"below",           (PyCFunction)PyCursesPanel_below, METH_NOARGS},
335     {"bottom",          (PyCFunction)PyCursesPanel_bottom_panel, METH_NOARGS},
336     {"hidden",          (PyCFunction)PyCursesPanel_panel_hidden, METH_NOARGS},
337     {"hide",            (PyCFunction)PyCursesPanel_hide_panel, METH_NOARGS},
338     {"move",            (PyCFunction)PyCursesPanel_move_panel, METH_VARARGS},
339     {"replace",         (PyCFunction)PyCursesPanel_replace_panel, METH_VARARGS},
340     {"set_userptr",     (PyCFunction)PyCursesPanel_set_panel_userptr, METH_O},
341     {"show",            (PyCFunction)PyCursesPanel_show_panel, METH_NOARGS},
342     {"top",             (PyCFunction)PyCursesPanel_top_panel, METH_NOARGS},
343     {"userptr",         (PyCFunction)PyCursesPanel_userptr, METH_NOARGS},
344     {"window",          (PyCFunction)PyCursesPanel_window, METH_NOARGS},
345     {NULL,              NULL}   /* sentinel */
346 };
347 
348 static PyObject *
PyCursesPanel_GetAttr(PyCursesPanelObject * self,char * name)349 PyCursesPanel_GetAttr(PyCursesPanelObject *self, char *name)
350 {
351     return Py_FindMethod(PyCursesPanel_Methods, (PyObject *)self, name);
352 }
353 
354 /* -------------------------------------------------------*/
355 
356 PyTypeObject PyCursesPanel_Type = {
357     PyVarObject_HEAD_INIT(NULL, 0)
358     "_curses_panel.curses panel",       /*tp_name*/
359     sizeof(PyCursesPanelObject),        /*tp_basicsize*/
360     0,                  /*tp_itemsize*/
361     /* methods */
362     (destructor)PyCursesPanel_Dealloc, /*tp_dealloc*/
363     0,                  /*tp_print*/
364     (getattrfunc)PyCursesPanel_GetAttr, /*tp_getattr*/
365     (setattrfunc)0, /*tp_setattr*/
366     0,                  /*tp_compare*/
367     0,                  /*tp_repr*/
368     0,                  /*tp_as_number*/
369     0,                  /*tp_as_sequence*/
370     0,                  /*tp_as_mapping*/
371     0,                  /*tp_hash*/
372 };
373 
374 /* Wrapper for panel_above(NULL). This function returns the bottom
375    panel of the stack, so it's renamed to bottom_panel().
376    panel.above() *requires* a panel object in the first place which
377    may be undesirable. */
378 static PyObject *
PyCurses_bottom_panel(PyObject * self)379 PyCurses_bottom_panel(PyObject *self)
380 {
381     PANEL *pan;
382     PyCursesPanelObject *po;
383 
384     PyCursesInitialised;
385 
386     pan = panel_above(NULL);
387 
388     if (pan == NULL) {          /* valid output, it means
389                                    there's no panel at all */
390         Py_INCREF(Py_None);
391         return Py_None;
392     }
393     po = find_po(pan);
394     if (po == NULL) {
395         PyErr_SetString(PyExc_RuntimeError,
396                         "panel_above: can't find Panel Object");
397         return NULL;
398     }
399     Py_INCREF(po);
400     return (PyObject *)po;
401 }
402 
403 static PyObject *
PyCurses_new_panel(PyObject * self,PyObject * args)404 PyCurses_new_panel(PyObject *self, PyObject *args)
405 {
406     PyCursesWindowObject *win;
407     PANEL *pan;
408 
409     if (!PyArg_ParseTuple(args, "O!", &PyCursesWindow_Type, &win))
410         return NULL;
411     pan = new_panel(win->win);
412     if (pan == NULL) {
413         PyErr_SetString(PyCursesError, catchall_NULL);
414         return NULL;
415     }
416     return (PyObject *)PyCursesPanel_New(pan, win);
417 }
418 
419 
420 /* Wrapper for panel_below(NULL). This function returns the top panel
421    of the stack, so it's renamed to top_panel(). panel.below()
422    *requires* a panel object in the first place which may be
423    undesirable. */
424 static PyObject *
PyCurses_top_panel(PyObject * self)425 PyCurses_top_panel(PyObject *self)
426 {
427     PANEL *pan;
428     PyCursesPanelObject *po;
429 
430     PyCursesInitialised;
431 
432     pan = panel_below(NULL);
433 
434     if (pan == NULL) {          /* valid output, it means
435                                    there's no panel at all */
436         Py_INCREF(Py_None);
437         return Py_None;
438     }
439     po = find_po(pan);
440     if (po == NULL) {
441         PyErr_SetString(PyExc_RuntimeError,
442                         "panel_below: can't find Panel Object");
443         return NULL;
444     }
445     Py_INCREF(po);
446     return (PyObject *)po;
447 }
448 
PyCurses_update_panels(PyObject * self)449 static PyObject *PyCurses_update_panels(PyObject *self)
450 {
451     PyCursesInitialised;
452     update_panels();
453     Py_INCREF(Py_None);
454     return Py_None;
455 }
456 
457 
458 /* List of functions defined in the module */
459 
460 static PyMethodDef PyCurses_methods[] = {
461     {"bottom_panel",        (PyCFunction)PyCurses_bottom_panel,  METH_NOARGS},
462     {"new_panel",           (PyCFunction)PyCurses_new_panel,     METH_VARARGS},
463     {"top_panel",           (PyCFunction)PyCurses_top_panel,     METH_NOARGS},
464     {"update_panels",       (PyCFunction)PyCurses_update_panels, METH_NOARGS},
465     {NULL,              NULL}           /* sentinel */
466 };
467 
468 /* Initialization function for the module */
469 
470 PyMODINIT_FUNC
init_curses_panel(void)471 init_curses_panel(void)
472 {
473     PyObject *m, *d, *v;
474 
475     /* Initialize object type */
476     Py_TYPE(&PyCursesPanel_Type) = &PyType_Type;
477 
478     import_curses();
479 
480     /* Create the module and add the functions */
481     m = Py_InitModule("_curses_panel", PyCurses_methods);
482     if (m == NULL)
483         return;
484     d = PyModule_GetDict(m);
485 
486     /* For exception _curses_panel.error */
487     PyCursesError = PyErr_NewException("_curses_panel.error", NULL, NULL);
488     PyDict_SetItemString(d, "error", PyCursesError);
489 
490     /* Make the version available */
491     v = PyString_FromString(PyCursesVersion);
492     PyDict_SetItemString(d, "version", v);
493     PyDict_SetItemString(d, "__version__", v);
494     Py_DECREF(v);
495 }
496