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