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