1/*
2 *   Interface to the ncurses panel library
3 *
4 * Original version by Thomas Gellekum
5 */
6
7/* Release Number */
8
9static char *PyCursesVersion = "2.1";
10
11/* Includes */
12
13#include "Python.h"
14
15#include "py_curses.h"
16
17#include <panel.h>
18
19static 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
29static PyObject *
30PyCursesCheckERR(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
51typedef struct {
52    PyObject_HEAD
53    PANEL *pan;
54    PyCursesWindowObject *wo;   /* for reference counts */
55} PyCursesPanelObject;
56
57PyTypeObject 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. */
78typedef struct _list_of_panels {
79    PyCursesPanelObject *po;
80    struct _list_of_panels *next;
81} list_of_panels;
82
83/* list anchor */
84static list_of_panels *lop;
85
86/* Insert a new panel object into lop */
87static int
88insert_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 */
103static void
104remove_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 */
129static PyCursesPanelObject *
130find_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) \
146static PyObject *PyCursesPanel_##X(PyCursesPanelObject *self) \
147{ return PyCursesCheckERR(X(self->pan), # X); }
148
149#define Panel_NoArgTrueFalseFunction(X) \
150static 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) \
156static 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
164Panel_NoArgNoReturnFunction(bottom_panel)
165Panel_NoArgNoReturnFunction(hide_panel)
166Panel_NoArgNoReturnFunction(show_panel)
167Panel_NoArgNoReturnFunction(top_panel)
168Panel_NoArgTrueFalseFunction(panel_hidden)
169Panel_TwoArgNoReturnFunction(move_panel, int, "ii;y,x")
170
171/* Allocation and deallocation of Panel Objects */
172
173static PyObject *
174PyCursesPanel_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
191static void
192PyCursesPanel_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(). */
209static PyObject *
210PyCursesPanel_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(). */
234static PyObject *
235PyCursesPanel_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
257static PyObject *
258PyCursesPanel_window(PyCursesPanelObject *self)
259{
260    Py_INCREF(self->wo);
261    return (PyObject *)self->wo;
262}
263
264static PyObject *
265PyCursesPanel_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
297static PyObject *
298PyCursesPanel_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
314static PyObject *
315PyCursesPanel_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
332static 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
348static PyObject *
349PyCursesPanel_GetAttr(PyCursesPanelObject *self, char *name)
350{
351    return Py_FindMethod(PyCursesPanel_Methods, (PyObject *)self, name);
352}
353
354/* -------------------------------------------------------*/
355
356PyTypeObject 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. */
378static PyObject *
379PyCurses_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
403static PyObject *
404PyCurses_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. */
424static PyObject *
425PyCurses_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
449static 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
460static 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
470PyMODINIT_FUNC
471init_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