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 Py_XDECREF(oldobj);
444 return PyCursesCheckERR(rc, "set_panel_userptr");
445 }
446
447 /*[clinic input]
448 _curses_panel.panel.userptr
449
450 Return the user pointer for the panel.
451 [clinic start generated code]*/
452
453 static PyObject *
_curses_panel_panel_userptr_impl(PyCursesPanelObject * self)454 _curses_panel_panel_userptr_impl(PyCursesPanelObject *self)
455 /*[clinic end generated code: output=e849c307b5dc9237 input=f78b7a47aef0fd50]*/
456 {
457 PyObject *obj;
458 PyCursesInitialised;
459 obj = (PyObject *) panel_userptr(self->pan);
460 if (obj == NULL) {
461 PyErr_SetString(_curses_panelstate_global->PyCursesError, "no userptr set");
462 return NULL;
463 }
464
465 Py_INCREF(obj);
466 return obj;
467 }
468
469
470 /* Module interface */
471
472 static PyMethodDef PyCursesPanel_Methods[] = {
473 _CURSES_PANEL_PANEL_ABOVE_METHODDEF
474 _CURSES_PANEL_PANEL_BELOW_METHODDEF
475 _CURSES_PANEL_PANEL_BOTTOM_METHODDEF
476 _CURSES_PANEL_PANEL_HIDDEN_METHODDEF
477 _CURSES_PANEL_PANEL_HIDE_METHODDEF
478 _CURSES_PANEL_PANEL_MOVE_METHODDEF
479 _CURSES_PANEL_PANEL_REPLACE_METHODDEF
480 _CURSES_PANEL_PANEL_SET_USERPTR_METHODDEF
481 _CURSES_PANEL_PANEL_SHOW_METHODDEF
482 _CURSES_PANEL_PANEL_TOP_METHODDEF
483 _CURSES_PANEL_PANEL_USERPTR_METHODDEF
484 _CURSES_PANEL_PANEL_WINDOW_METHODDEF
485 {NULL, NULL} /* sentinel */
486 };
487
488 /* -------------------------------------------------------*/
489
490 static PyType_Slot PyCursesPanel_Type_slots[] = {
491 {Py_tp_dealloc, PyCursesPanel_Dealloc},
492 {Py_tp_methods, PyCursesPanel_Methods},
493 {0, 0},
494 };
495
496 static PyType_Spec PyCursesPanel_Type_spec = {
497 "_curses_panel.panel",
498 sizeof(PyCursesPanelObject),
499 0,
500 Py_TPFLAGS_DEFAULT,
501 PyCursesPanel_Type_slots
502 };
503
504 /* Wrapper for panel_above(NULL). This function returns the bottom
505 panel of the stack, so it's renamed to bottom_panel().
506 panel.above() *requires* a panel object in the first place which
507 may be undesirable. */
508 /*[clinic input]
509 _curses_panel.bottom_panel
510
511 Return the bottom panel in the panel stack.
512 [clinic start generated code]*/
513
514 static PyObject *
_curses_panel_bottom_panel_impl(PyObject * module)515 _curses_panel_bottom_panel_impl(PyObject *module)
516 /*[clinic end generated code: output=3aba9f985f4c2bd0 input=634c2a8078b3d7e4]*/
517 {
518 PANEL *pan;
519 PyCursesPanelObject *po;
520
521 PyCursesInitialised;
522
523 pan = panel_above(NULL);
524
525 if (pan == NULL) { /* valid output, it means
526 there's no panel at all */
527 Py_RETURN_NONE;
528 }
529 po = find_po(pan);
530 if (po == NULL) {
531 PyErr_SetString(PyExc_RuntimeError,
532 "panel_above: can't find Panel Object");
533 return NULL;
534 }
535 Py_INCREF(po);
536 return (PyObject *)po;
537 }
538
539 /*[clinic input]
540 _curses_panel.new_panel
541
542 win: object(type="PyCursesWindowObject *", subclass_of="&PyCursesWindow_Type")
543 /
544
545 Return a panel object, associating it with the given window win.
546 [clinic start generated code]*/
547
548 static PyObject *
_curses_panel_new_panel_impl(PyObject * module,PyCursesWindowObject * win)549 _curses_panel_new_panel_impl(PyObject *module, PyCursesWindowObject *win)
550 /*[clinic end generated code: output=45e948e0176a9bd2 input=74d4754e0ebe4800]*/
551 {
552 PANEL *pan = new_panel(win->win);
553 if (pan == NULL) {
554 PyErr_SetString(_curses_panelstate_global->PyCursesError, catchall_NULL);
555 return NULL;
556 }
557 return (PyObject *)PyCursesPanel_New(pan, win);
558 }
559
560
561 /* Wrapper for panel_below(NULL). This function returns the top panel
562 of the stack, so it's renamed to top_panel(). panel.below()
563 *requires* a panel object in the first place which may be
564 undesirable. */
565 /*[clinic input]
566 _curses_panel.top_panel
567
568 Return the top panel in the panel stack.
569 [clinic start generated code]*/
570
571 static PyObject *
_curses_panel_top_panel_impl(PyObject * module)572 _curses_panel_top_panel_impl(PyObject *module)
573 /*[clinic end generated code: output=86704988bea8508e input=e62d6278dba39e79]*/
574 {
575 PANEL *pan;
576 PyCursesPanelObject *po;
577
578 PyCursesInitialised;
579
580 pan = panel_below(NULL);
581
582 if (pan == NULL) { /* valid output, it means
583 there's no panel at all */
584 Py_RETURN_NONE;
585 }
586 po = find_po(pan);
587 if (po == NULL) {
588 PyErr_SetString(PyExc_RuntimeError,
589 "panel_below: can't find Panel Object");
590 return NULL;
591 }
592 Py_INCREF(po);
593 return (PyObject *)po;
594 }
595
596 /*[clinic input]
597 _curses_panel.update_panels
598
599 Updates the virtual screen after changes in the panel stack.
600
601 This does not call curses.doupdate(), so you'll have to do this yourself.
602 [clinic start generated code]*/
603
604 static PyObject *
_curses_panel_update_panels_impl(PyObject * module)605 _curses_panel_update_panels_impl(PyObject *module)
606 /*[clinic end generated code: output=2f3b4c2e03d90ded input=5299624c9a708621]*/
607 {
608 PyCursesInitialised;
609 update_panels();
610 Py_RETURN_NONE;
611 }
612
613
614 /* List of functions defined in the module */
615
616 static PyMethodDef PyCurses_methods[] = {
617 _CURSES_PANEL_BOTTOM_PANEL_METHODDEF
618 _CURSES_PANEL_NEW_PANEL_METHODDEF
619 _CURSES_PANEL_TOP_PANEL_METHODDEF
620 _CURSES_PANEL_UPDATE_PANELS_METHODDEF
621 {NULL, NULL} /* sentinel */
622 };
623
624 /* Initialization function for the module */
625
626
627 static struct PyModuleDef _curses_panelmodule = {
628 PyModuleDef_HEAD_INIT,
629 "_curses_panel",
630 NULL,
631 sizeof(_curses_panelstate),
632 PyCurses_methods,
633 NULL,
634 _curses_panel_traverse,
635 _curses_panel_clear,
636 _curses_panel_free
637 };
638
639 PyMODINIT_FUNC
PyInit__curses_panel(void)640 PyInit__curses_panel(void)
641 {
642 PyObject *m, *d, *v;
643
644 /* Create the module and add the functions */
645 m = PyModule_Create(&_curses_panelmodule);
646 if (m == NULL)
647 goto fail;
648 d = PyModule_GetDict(m);
649
650 /* Initialize object type */
651 v = PyType_FromSpec(&PyCursesPanel_Type_spec);
652 if (v == NULL)
653 goto fail;
654 ((PyTypeObject *)v)->tp_new = NULL;
655 get_curses_panelstate(m)->PyCursesPanel_Type = v;
656
657 import_curses();
658 if (PyErr_Occurred())
659 goto fail;
660
661 /* For exception _curses_panel.error */
662 get_curses_panelstate(m)->PyCursesError = PyErr_NewException("_curses_panel.error", NULL, NULL);
663 PyDict_SetItemString(d, "error", get_curses_panelstate(m)->PyCursesError);
664
665 /* Make the version available */
666 v = PyUnicode_FromString(PyCursesVersion);
667 PyDict_SetItemString(d, "version", v);
668 PyDict_SetItemString(d, "__version__", v);
669 Py_DECREF(v);
670
671 Py_INCREF(get_curses_panelstate(m)->PyCursesPanel_Type);
672 PyModule_AddObject(m, "panel",
673 (PyObject *)get_curses_panelstate(m)->PyCursesPanel_Type);
674 return m;
675 fail:
676 Py_XDECREF(m);
677 return NULL;
678 }
679