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