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