• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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