• 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 #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