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