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 static inline _curses_panelstate*
get_curses_panelstate(PyObject * module)25 get_curses_panelstate(PyObject *module)
26 {
27 void *state = PyModule_GetState(module);
28 assert(state != NULL);
29 return (_curses_panelstate *)state;
30 }
31
32 static int
_curses_panel_clear(PyObject * m)33 _curses_panel_clear(PyObject *m)
34 {
35 Py_CLEAR(get_curses_panelstate(m)->PyCursesError);
36 return 0;
37 }
38
39 static int
_curses_panel_traverse(PyObject * m,visitproc visit,void * arg)40 _curses_panel_traverse(PyObject *m, visitproc visit, void *arg)
41 {
42 Py_VISIT(Py_TYPE(m));
43 Py_VISIT(get_curses_panelstate(m)->PyCursesError);
44 return 0;
45 }
46
47 static void
_curses_panel_free(void * m)48 _curses_panel_free(void *m)
49 {
50 _curses_panel_clear((PyObject *) m);
51 }
52
53 static struct PyModuleDef _curses_panelmodule;
54
55 #define _curses_panelstate_global \
56 ((_curses_panelstate *) PyModule_GetState(PyState_FindModule(&_curses_panelmodule)))
57
58 /* Utility Functions */
59
60 /*
61 * Check the return code from a curses function and return None
62 * or raise an exception as appropriate.
63 */
64
65 static PyObject *
PyCursesCheckERR(int code,const char * fname)66 PyCursesCheckERR(int code, const char *fname)
67 {
68 if (code != ERR) {
69 Py_RETURN_NONE;
70 } else {
71 if (fname == NULL) {
72 PyErr_SetString(_curses_panelstate_global->PyCursesError, catchall_ERR);
73 } else {
74 PyErr_Format(_curses_panelstate_global->PyCursesError, "%s() returned ERR", fname);
75 }
76 return NULL;
77 }
78 }
79
80 /*****************************************************************************
81 The Panel Object
82 ******************************************************************************/
83
84 /* Definition of the panel object and panel type */
85
86 typedef struct {
87 PyObject_HEAD
88 PANEL *pan;
89 PyCursesWindowObject *wo; /* for reference counts */
90 } PyCursesPanelObject;
91
92 #define PyCursesPanel_Check(v) \
93 Py_IS_TYPE(v, _curses_panelstate_global->PyCursesPanel_Type)
94
95 /* Some helper functions. The problem is that there's always a window
96 associated with a panel. To ensure that Python's GC doesn't pull
97 this window from under our feet we need to keep track of references
98 to the corresponding window object within Python. We can't use
99 dupwin(oldwin) to keep a copy of the curses WINDOW because the
100 contents of oldwin is copied only once; code like
101
102 win = newwin(...)
103 pan = win.panel()
104 win.addstr(some_string)
105 pan.window().addstr(other_string)
106
107 will fail. */
108
109 /* We keep a linked list of PyCursesPanelObjects, lop. A list should
110 suffice, I don't expect more than a handful or at most a few
111 dozens of panel objects within a typical program. */
112 typedef struct _list_of_panels {
113 PyCursesPanelObject *po;
114 struct _list_of_panels *next;
115 } list_of_panels;
116
117 /* list anchor */
118 static list_of_panels *lop;
119
120 /* Insert a new panel object into lop */
121 static int
insert_lop(PyCursesPanelObject * po)122 insert_lop(PyCursesPanelObject *po)
123 {
124 list_of_panels *new;
125
126 if ((new = (list_of_panels *)PyMem_Malloc(sizeof(list_of_panels))) == NULL) {
127 PyErr_NoMemory();
128 return -1;
129 }
130 new->po = po;
131 new->next = lop;
132 lop = new;
133 return 0;
134 }
135
136 /* Remove the panel object from lop */
137 static void
remove_lop(PyCursesPanelObject * po)138 remove_lop(PyCursesPanelObject *po)
139 {
140 list_of_panels *temp, *n;
141
142 temp = lop;
143 if (temp->po == po) {
144 lop = temp->next;
145 PyMem_Free(temp);
146 return;
147 }
148 while (temp->next == NULL || temp->next->po != po) {
149 if (temp->next == NULL) {
150 PyErr_SetString(PyExc_RuntimeError,
151 "remove_lop: can't find Panel Object");
152 return;
153 }
154 temp = temp->next;
155 }
156 n = temp->next->next;
157 PyMem_Free(temp->next);
158 temp->next = n;
159 return;
160 }
161
162 /* Return the panel object that corresponds to pan */
163 static PyCursesPanelObject *
find_po(PANEL * pan)164 find_po(PANEL *pan)
165 {
166 list_of_panels *temp;
167 for (temp = lop; temp->po->pan != pan; temp = temp->next)
168 if (temp->next == NULL) return NULL; /* not found!? */
169 return temp->po;
170 }
171
172 /*[clinic input]
173 module _curses_panel
174 class _curses_panel.panel "PyCursesPanelObject *" "&PyCursesPanel_Type"
175 [clinic start generated code]*/
176 /*[clinic end generated code: output=da39a3ee5e6b4b0d input=2f4ef263ca850a31]*/
177
178 #include "clinic/_curses_panel.c.h"
179
180 /* ------------- PANEL routines --------------- */
181
182 /*[clinic input]
183 _curses_panel.panel.bottom
184
185 Push the panel to the bottom of the stack.
186 [clinic start generated code]*/
187
188 static PyObject *
_curses_panel_panel_bottom_impl(PyCursesPanelObject * self)189 _curses_panel_panel_bottom_impl(PyCursesPanelObject *self)
190 /*[clinic end generated code: output=7aa7d14d7e1d1ce6 input=b6c920c071b61e2e]*/
191 {
192 return PyCursesCheckERR(bottom_panel(self->pan), "bottom");
193 }
194
195 /*[clinic input]
196 _curses_panel.panel.hide
197
198 Hide the panel.
199
200 This does not delete the object, it just makes the window on screen invisible.
201 [clinic start generated code]*/
202
203 static PyObject *
_curses_panel_panel_hide_impl(PyCursesPanelObject * self)204 _curses_panel_panel_hide_impl(PyCursesPanelObject *self)
205 /*[clinic end generated code: output=a7bbbd523e1eab49 input=f6ab884e99386118]*/
206 {
207 return PyCursesCheckERR(hide_panel(self->pan), "hide");
208 }
209
210 /*[clinic input]
211 _curses_panel.panel.show
212
213 Display the panel (which might have been hidden).
214 [clinic start generated code]*/
215
216 static PyObject *
_curses_panel_panel_show_impl(PyCursesPanelObject * self)217 _curses_panel_panel_show_impl(PyCursesPanelObject *self)
218 /*[clinic end generated code: output=6b4553ab45c97769 input=57b167bbefaa3755]*/
219 {
220 return PyCursesCheckERR(show_panel(self->pan), "show");
221 }
222
223 /*[clinic input]
224 _curses_panel.panel.top
225
226 Push panel to the top of the stack.
227 [clinic start generated code]*/
228
229 static PyObject *
_curses_panel_panel_top_impl(PyCursesPanelObject * self)230 _curses_panel_panel_top_impl(PyCursesPanelObject *self)
231 /*[clinic end generated code: output=0f5f2f8cdd2d1777 input=be33975ec3ca0e9a]*/
232 {
233 return PyCursesCheckERR(top_panel(self->pan), "top");
234 }
235
236 /* Allocation and deallocation of Panel Objects */
237
238 static PyObject *
PyCursesPanel_New(PANEL * pan,PyCursesWindowObject * wo)239 PyCursesPanel_New(PANEL *pan, PyCursesWindowObject *wo)
240 {
241 PyCursesPanelObject *po;
242
243 po = PyObject_New(PyCursesPanelObject,
244 (PyTypeObject *)(_curses_panelstate_global)->PyCursesPanel_Type);
245 if (po == NULL) return NULL;
246 po->pan = pan;
247 if (insert_lop(po) < 0) {
248 po->wo = NULL;
249 Py_DECREF(po);
250 return NULL;
251 }
252 po->wo = wo;
253 Py_INCREF(wo);
254 return (PyObject *)po;
255 }
256
257 static void
PyCursesPanel_Dealloc(PyCursesPanelObject * po)258 PyCursesPanel_Dealloc(PyCursesPanelObject *po)
259 {
260 PyObject *tp, *obj;
261
262 tp = (PyObject *) Py_TYPE(po);
263 obj = (PyObject *) panel_userptr(po->pan);
264 if (obj) {
265 (void)set_panel_userptr(po->pan, NULL);
266 Py_DECREF(obj);
267 }
268 (void)del_panel(po->pan);
269 if (po->wo != NULL) {
270 Py_DECREF(po->wo);
271 remove_lop(po);
272 }
273 PyObject_DEL(po);
274 Py_DECREF(tp);
275 }
276
277 /* panel_above(NULL) returns the bottom panel in the stack. To get
278 this behaviour we use curses.panel.bottom_panel(). */
279 /*[clinic input]
280 _curses_panel.panel.above
281
282 Return the panel above the current panel.
283 [clinic start generated code]*/
284
285 static PyObject *
_curses_panel_panel_above_impl(PyCursesPanelObject * self)286 _curses_panel_panel_above_impl(PyCursesPanelObject *self)
287 /*[clinic end generated code: output=70ac06d25fd3b4da input=c059994022976788]*/
288 {
289 PANEL *pan;
290 PyCursesPanelObject *po;
291
292 pan = panel_above(self->pan);
293
294 if (pan == NULL) { /* valid output, it means the calling panel
295 is on top of the stack */
296 Py_RETURN_NONE;
297 }
298 po = find_po(pan);
299 if (po == NULL) {
300 PyErr_SetString(PyExc_RuntimeError,
301 "panel_above: can't find Panel Object");
302 return NULL;
303 }
304 Py_INCREF(po);
305 return (PyObject *)po;
306 }
307
308 /* panel_below(NULL) returns the top panel in the stack. To get
309 this behaviour we use curses.panel.top_panel(). */
310 /*[clinic input]
311 _curses_panel.panel.below
312
313 Return the panel below the current panel.
314 [clinic start generated code]*/
315
316 static PyObject *
_curses_panel_panel_below_impl(PyCursesPanelObject * self)317 _curses_panel_panel_below_impl(PyCursesPanelObject *self)
318 /*[clinic end generated code: output=282861122e06e3de input=cc08f61936d297c6]*/
319 {
320 PANEL *pan;
321 PyCursesPanelObject *po;
322
323 pan = panel_below(self->pan);
324
325 if (pan == NULL) { /* valid output, it means the calling panel
326 is on the bottom of the stack */
327 Py_RETURN_NONE;
328 }
329 po = find_po(pan);
330 if (po == NULL) {
331 PyErr_SetString(PyExc_RuntimeError,
332 "panel_below: can't find Panel Object");
333 return NULL;
334 }
335 Py_INCREF(po);
336 return (PyObject *)po;
337 }
338
339 /*[clinic input]
340 _curses_panel.panel.hidden
341
342 Return True if the panel is hidden (not visible), False otherwise.
343 [clinic start generated code]*/
344
345 static PyObject *
_curses_panel_panel_hidden_impl(PyCursesPanelObject * self)346 _curses_panel_panel_hidden_impl(PyCursesPanelObject *self)
347 /*[clinic end generated code: output=66eebd1ab4501a71 input=453d4b4fce25e21a]*/
348 {
349 if (panel_hidden(self->pan))
350 Py_RETURN_TRUE;
351 else
352 Py_RETURN_FALSE;
353 }
354
355 /*[clinic input]
356 _curses_panel.panel.move
357
358 y: int
359 x: int
360 /
361
362 Move the panel to the screen coordinates (y, x).
363 [clinic start generated code]*/
364
365 static PyObject *
_curses_panel_panel_move_impl(PyCursesPanelObject * self,int y,int x)366 _curses_panel_panel_move_impl(PyCursesPanelObject *self, int y, int x)
367 /*[clinic end generated code: output=d867535a89777415 input=e0b36b78acc03fba]*/
368 {
369 return PyCursesCheckERR(move_panel(self->pan, y, x), "move_panel");
370 }
371
372 /*[clinic input]
373 _curses_panel.panel.window
374
375 Return the window object associated with the panel.
376 [clinic start generated code]*/
377
378 static PyObject *
_curses_panel_panel_window_impl(PyCursesPanelObject * self)379 _curses_panel_panel_window_impl(PyCursesPanelObject *self)
380 /*[clinic end generated code: output=5f05940d4106b4cb input=6067353d2c307901]*/
381 {
382 Py_INCREF(self->wo);
383 return (PyObject *)self->wo;
384 }
385
386 /*[clinic input]
387 _curses_panel.panel.replace
388
389 win: object(type="PyCursesWindowObject *", subclass_of="&PyCursesWindow_Type")
390 /
391
392 Change the window associated with the panel to the window win.
393 [clinic start generated code]*/
394
395 static PyObject *
_curses_panel_panel_replace_impl(PyCursesPanelObject * self,PyCursesWindowObject * win)396 _curses_panel_panel_replace_impl(PyCursesPanelObject *self,
397 PyCursesWindowObject *win)
398 /*[clinic end generated code: output=2253a95f7b287255 input=4b1c4283987d9dfa]*/
399 {
400 PyCursesPanelObject *po;
401 int rtn;
402
403 po = find_po(self->pan);
404 if (po == NULL) {
405 PyErr_SetString(PyExc_RuntimeError,
406 "replace_panel: can't find Panel Object");
407 return NULL;
408 }
409
410 rtn = replace_panel(self->pan, win->win);
411 if (rtn == ERR) {
412 PyErr_SetString(_curses_panelstate_global->PyCursesError, "replace_panel() returned ERR");
413 return NULL;
414 }
415 Py_INCREF(win);
416 Py_SETREF(po->wo, win);
417 Py_RETURN_NONE;
418 }
419
420 /*[clinic input]
421 _curses_panel.panel.set_userptr
422
423 obj: object
424 /
425
426 Set the panel's user pointer to obj.
427 [clinic start generated code]*/
428
429 static PyObject *
_curses_panel_panel_set_userptr(PyCursesPanelObject * self,PyObject * obj)430 _curses_panel_panel_set_userptr(PyCursesPanelObject *self, PyObject *obj)
431 /*[clinic end generated code: output=6fb145b3af88cf4a input=d2c6a9dbefabbf39]*/
432 {
433 PyObject *oldobj;
434 int rc;
435 PyCursesInitialised;
436 Py_INCREF(obj);
437 oldobj = (PyObject *) panel_userptr(self->pan);
438 rc = set_panel_userptr(self->pan, (void*)obj);
439 if (rc == ERR) {
440 /* In case of an ncurses error, decref the new object again */
441 Py_DECREF(obj);
442 }
443 else {
444 Py_XDECREF(oldobj);
445 }
446 return PyCursesCheckERR(rc, "set_panel_userptr");
447 }
448
449 /*[clinic input]
450 _curses_panel.panel.userptr
451
452 Return the user pointer for the panel.
453 [clinic start generated code]*/
454
455 static PyObject *
_curses_panel_panel_userptr_impl(PyCursesPanelObject * self)456 _curses_panel_panel_userptr_impl(PyCursesPanelObject *self)
457 /*[clinic end generated code: output=e849c307b5dc9237 input=f78b7a47aef0fd50]*/
458 {
459 PyObject *obj;
460 PyCursesInitialised;
461 obj = (PyObject *) panel_userptr(self->pan);
462 if (obj == NULL) {
463 PyErr_SetString(_curses_panelstate_global->PyCursesError, "no userptr set");
464 return NULL;
465 }
466
467 Py_INCREF(obj);
468 return obj;
469 }
470
471
472 /* Module interface */
473
474 static PyMethodDef PyCursesPanel_Methods[] = {
475 _CURSES_PANEL_PANEL_ABOVE_METHODDEF
476 _CURSES_PANEL_PANEL_BELOW_METHODDEF
477 _CURSES_PANEL_PANEL_BOTTOM_METHODDEF
478 _CURSES_PANEL_PANEL_HIDDEN_METHODDEF
479 _CURSES_PANEL_PANEL_HIDE_METHODDEF
480 _CURSES_PANEL_PANEL_MOVE_METHODDEF
481 _CURSES_PANEL_PANEL_REPLACE_METHODDEF
482 _CURSES_PANEL_PANEL_SET_USERPTR_METHODDEF
483 _CURSES_PANEL_PANEL_SHOW_METHODDEF
484 _CURSES_PANEL_PANEL_TOP_METHODDEF
485 _CURSES_PANEL_PANEL_USERPTR_METHODDEF
486 _CURSES_PANEL_PANEL_WINDOW_METHODDEF
487 {NULL, NULL} /* sentinel */
488 };
489
490 /* -------------------------------------------------------*/
491
492 static PyType_Slot PyCursesPanel_Type_slots[] = {
493 {Py_tp_dealloc, PyCursesPanel_Dealloc},
494 {Py_tp_methods, PyCursesPanel_Methods},
495 {0, 0},
496 };
497
498 static PyType_Spec PyCursesPanel_Type_spec = {
499 "_curses_panel.panel",
500 sizeof(PyCursesPanelObject),
501 0,
502 Py_TPFLAGS_DEFAULT,
503 PyCursesPanel_Type_slots
504 };
505
506 /* Wrapper for panel_above(NULL). This function returns the bottom
507 panel of the stack, so it's renamed to bottom_panel().
508 panel.above() *requires* a panel object in the first place which
509 may be undesirable. */
510 /*[clinic input]
511 _curses_panel.bottom_panel
512
513 Return the bottom panel in the panel stack.
514 [clinic start generated code]*/
515
516 static PyObject *
_curses_panel_bottom_panel_impl(PyObject * module)517 _curses_panel_bottom_panel_impl(PyObject *module)
518 /*[clinic end generated code: output=3aba9f985f4c2bd0 input=634c2a8078b3d7e4]*/
519 {
520 PANEL *pan;
521 PyCursesPanelObject *po;
522
523 PyCursesInitialised;
524
525 pan = panel_above(NULL);
526
527 if (pan == NULL) { /* valid output, it means
528 there's no panel at all */
529 Py_RETURN_NONE;
530 }
531 po = find_po(pan);
532 if (po == NULL) {
533 PyErr_SetString(PyExc_RuntimeError,
534 "panel_above: can't find Panel Object");
535 return NULL;
536 }
537 Py_INCREF(po);
538 return (PyObject *)po;
539 }
540
541 /*[clinic input]
542 _curses_panel.new_panel
543
544 win: object(type="PyCursesWindowObject *", subclass_of="&PyCursesWindow_Type")
545 /
546
547 Return a panel object, associating it with the given window win.
548 [clinic start generated code]*/
549
550 static PyObject *
_curses_panel_new_panel_impl(PyObject * module,PyCursesWindowObject * win)551 _curses_panel_new_panel_impl(PyObject *module, PyCursesWindowObject *win)
552 /*[clinic end generated code: output=45e948e0176a9bd2 input=74d4754e0ebe4800]*/
553 {
554 PANEL *pan = new_panel(win->win);
555 if (pan == NULL) {
556 PyErr_SetString(_curses_panelstate_global->PyCursesError, catchall_NULL);
557 return NULL;
558 }
559 return (PyObject *)PyCursesPanel_New(pan, win);
560 }
561
562
563 /* Wrapper for panel_below(NULL). This function returns the top panel
564 of the stack, so it's renamed to top_panel(). panel.below()
565 *requires* a panel object in the first place which may be
566 undesirable. */
567 /*[clinic input]
568 _curses_panel.top_panel
569
570 Return the top panel in the panel stack.
571 [clinic start generated code]*/
572
573 static PyObject *
_curses_panel_top_panel_impl(PyObject * module)574 _curses_panel_top_panel_impl(PyObject *module)
575 /*[clinic end generated code: output=86704988bea8508e input=e62d6278dba39e79]*/
576 {
577 PANEL *pan;
578 PyCursesPanelObject *po;
579
580 PyCursesInitialised;
581
582 pan = panel_below(NULL);
583
584 if (pan == NULL) { /* valid output, it means
585 there's no panel at all */
586 Py_RETURN_NONE;
587 }
588 po = find_po(pan);
589 if (po == NULL) {
590 PyErr_SetString(PyExc_RuntimeError,
591 "panel_below: can't find Panel Object");
592 return NULL;
593 }
594 Py_INCREF(po);
595 return (PyObject *)po;
596 }
597
598 /*[clinic input]
599 _curses_panel.update_panels
600
601 Updates the virtual screen after changes in the panel stack.
602
603 This does not call curses.doupdate(), so you'll have to do this yourself.
604 [clinic start generated code]*/
605
606 static PyObject *
_curses_panel_update_panels_impl(PyObject * module)607 _curses_panel_update_panels_impl(PyObject *module)
608 /*[clinic end generated code: output=2f3b4c2e03d90ded input=5299624c9a708621]*/
609 {
610 PyCursesInitialised;
611 update_panels();
612 Py_RETURN_NONE;
613 }
614
615
616 /* List of functions defined in the module */
617
618 static PyMethodDef PyCurses_methods[] = {
619 _CURSES_PANEL_BOTTOM_PANEL_METHODDEF
620 _CURSES_PANEL_NEW_PANEL_METHODDEF
621 _CURSES_PANEL_TOP_PANEL_METHODDEF
622 _CURSES_PANEL_UPDATE_PANELS_METHODDEF
623 {NULL, NULL} /* sentinel */
624 };
625
626 /* Initialization function for the module */
627
628
629 static struct PyModuleDef _curses_panelmodule = {
630 PyModuleDef_HEAD_INIT,
631 "_curses_panel",
632 NULL,
633 sizeof(_curses_panelstate),
634 PyCurses_methods,
635 NULL,
636 _curses_panel_traverse,
637 _curses_panel_clear,
638 _curses_panel_free
639 };
640
641 PyMODINIT_FUNC
PyInit__curses_panel(void)642 PyInit__curses_panel(void)
643 {
644 PyObject *m, *d, *v;
645
646 /* Create the module and add the functions */
647 m = PyModule_Create(&_curses_panelmodule);
648 if (m == NULL)
649 goto fail;
650 d = PyModule_GetDict(m);
651
652 /* Initialize object type */
653 v = PyType_FromSpec(&PyCursesPanel_Type_spec);
654 if (v == NULL)
655 goto fail;
656 ((PyTypeObject *)v)->tp_new = NULL;
657 get_curses_panelstate(m)->PyCursesPanel_Type = v;
658
659 import_curses();
660 if (PyErr_Occurred())
661 goto fail;
662
663 /* For exception _curses_panel.error */
664 get_curses_panelstate(m)->PyCursesError = PyErr_NewException("_curses_panel.error", NULL, NULL);
665 PyDict_SetItemString(d, "error", get_curses_panelstate(m)->PyCursesError);
666
667 /* Make the version available */
668 v = PyUnicode_FromString(PyCursesVersion);
669 PyDict_SetItemString(d, "version", v);
670 PyDict_SetItemString(d, "__version__", v);
671 Py_DECREF(v);
672
673 Py_INCREF(get_curses_panelstate(m)->PyCursesPanel_Type);
674 PyModule_AddObject(m, "panel",
675 (PyObject *)get_curses_panelstate(m)->PyCursesPanel_Type);
676 return m;
677 fail:
678 Py_XDECREF(m);
679 return NULL;
680 }
681