Home | History | Annotate | Download | only in Modules
      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
     27 _curses_panel_clear(PyObject *m)
     28 {
     29     Py_CLEAR(_curses_panelstate(m)->PyCursesError);
     30     return 0;
     31 }
     32 
     33 static int
     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
     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 *
     59 PyCursesCheckERR(int code, const char *fname)
     60 {
     61     if (code != ERR) {
     62         Py_INCREF(Py_None);
     63         return Py_None;
     64     } else {
     65         if (fname == NULL) {
     66             PyErr_SetString(_curses_panelstate_global->PyCursesError, catchall_ERR);
     67         } else {
     68             PyErr_Format(_curses_panelstate_global->PyCursesError, "%s() returned ERR", fname);
     69         }
     70         return NULL;
     71     }
     72 }
     73 
     74 /*****************************************************************************
     75  The Panel Object
     76 ******************************************************************************/
     77 
     78 /* Definition of the panel object and panel type */
     79 
     80 typedef struct {
     81     PyObject_HEAD
     82     PANEL *pan;
     83     PyCursesWindowObject *wo;   /* for reference counts */
     84 } PyCursesPanelObject;
     85 
     86 #define PyCursesPanel_Check(v)  \
     87  (Py_TYPE(v) == _curses_panelstate_global->PyCursesPanel_Type)
     88 
     89 /* Some helper functions. The problem is that there's always a window
     90    associated with a panel. To ensure that Python's GC doesn't pull
     91    this window from under our feet we need to keep track of references
     92    to the corresponding window object within Python. We can't use
     93    dupwin(oldwin) to keep a copy of the curses WINDOW because the
     94    contents of oldwin is copied only once; code like
     95 
     96    win = newwin(...)
     97    pan = win.panel()
     98    win.addstr(some_string)
     99    pan.window().addstr(other_string)
    100 
    101    will fail. */
    102 
    103 /* We keep a linked list of PyCursesPanelObjects, lop. A list should
    104    suffice, I don't expect more than a handful or at most a few
    105    dozens of panel objects within a typical program. */
    106 typedef struct _list_of_panels {
    107     PyCursesPanelObject *po;
    108     struct _list_of_panels *next;
    109 } list_of_panels;
    110 
    111 /* list anchor */
    112 static list_of_panels *lop;
    113 
    114 /* Insert a new panel object into lop */
    115 static int
    116 insert_lop(PyCursesPanelObject *po)
    117 {
    118     list_of_panels *new;
    119 
    120     if ((new = (list_of_panels *)PyMem_Malloc(sizeof(list_of_panels))) == NULL) {
    121         PyErr_NoMemory();
    122         return -1;
    123     }
    124     new->po = po;
    125     new->next = lop;
    126     lop = new;
    127     return 0;
    128 }
    129 
    130 /* Remove the panel object from lop */
    131 static void
    132 remove_lop(PyCursesPanelObject *po)
    133 {
    134     list_of_panels *temp, *n;
    135 
    136     temp = lop;
    137     if (temp->po == po) {
    138         lop = temp->next;
    139         PyMem_Free(temp);
    140         return;
    141     }
    142     while (temp->next == NULL || temp->next->po != po) {
    143         if (temp->next == NULL) {
    144             PyErr_SetString(PyExc_RuntimeError,
    145                             "remove_lop: can't find Panel Object");
    146             return;
    147         }
    148         temp = temp->next;
    149     }
    150     n = temp->next->next;
    151     PyMem_Free(temp->next);
    152     temp->next = n;
    153     return;
    154 }
    155 
    156 /* Return the panel object that corresponds to pan */
    157 static PyCursesPanelObject *
    158 find_po(PANEL *pan)
    159 {
    160     list_of_panels *temp;
    161     for (temp = lop; temp->po->pan != pan; temp = temp->next)
    162         if (temp->next == NULL) return NULL;    /* not found!? */
    163     return temp->po;
    164 }
    165 
    166 /* Function Prototype Macros - They are ugly but very, very useful. ;-)
    167 
    168    X - function name
    169    TYPE - parameter Type
    170    ERGSTR - format string for construction of the return value
    171    PARSESTR - format string for argument parsing */
    172 
    173 #define Panel_NoArgNoReturnFunction(X) \
    174 static PyObject *PyCursesPanel_##X(PyCursesPanelObject *self) \
    175 { return PyCursesCheckERR(X(self->pan), # X); }
    176 
    177 #define Panel_NoArgTrueFalseFunction(X) \
    178 static PyObject *PyCursesPanel_##X(PyCursesPanelObject *self) \
    179 { \
    180   if (X (self->pan) == FALSE) { Py_INCREF(Py_False); return Py_False; } \
    181   else { Py_INCREF(Py_True); return Py_True; } }
    182 
    183 #define Panel_TwoArgNoReturnFunction(X, TYPE, PARSESTR) \
    184 static PyObject *PyCursesPanel_##X(PyCursesPanelObject *self, PyObject *args) \
    185 { \
    186   TYPE arg1, arg2; \
    187   if (!PyArg_ParseTuple(args, PARSESTR, &arg1, &arg2)) return NULL; \
    188   return PyCursesCheckERR(X(self->pan, arg1, arg2), # X); }
    189 
    190 /* ------------- PANEL routines --------------- */
    191 
    192 Panel_NoArgNoReturnFunction(bottom_panel)
    193 Panel_NoArgNoReturnFunction(hide_panel)
    194 Panel_NoArgNoReturnFunction(show_panel)
    195 Panel_NoArgNoReturnFunction(top_panel)
    196 Panel_NoArgTrueFalseFunction(panel_hidden)
    197 Panel_TwoArgNoReturnFunction(move_panel, int, "ii;y,x")
    198 
    199 /* Allocation and deallocation of Panel Objects */
    200 
    201 static PyObject *
    202 PyCursesPanel_New(PANEL *pan, PyCursesWindowObject *wo)
    203 {
    204     PyCursesPanelObject *po;
    205 
    206     po = PyObject_NEW(PyCursesPanelObject,
    207                       (PyTypeObject *)(_curses_panelstate_global)->PyCursesPanel_Type);
    208     if (po == NULL) return NULL;
    209     po->pan = pan;
    210     if (insert_lop(po) < 0) {
    211         po->wo = NULL;
    212         Py_DECREF(po);
    213         return NULL;
    214     }
    215     po->wo = wo;
    216     Py_INCREF(wo);
    217     return (PyObject *)po;
    218 }
    219 
    220 static void
    221 PyCursesPanel_Dealloc(PyCursesPanelObject *po)
    222 {
    223     PyObject *obj = (PyObject *) panel_userptr(po->pan);
    224     if (obj) {
    225         (void)set_panel_userptr(po->pan, NULL);
    226         Py_DECREF(obj);
    227     }
    228     (void)del_panel(po->pan);
    229     if (po->wo != NULL) {
    230         Py_DECREF(po->wo);
    231         remove_lop(po);
    232     }
    233     PyObject_DEL(po);
    234 }
    235 
    236 /* panel_above(NULL) returns the bottom panel in the stack. To get
    237    this behaviour we use curses.panel.bottom_panel(). */
    238 static PyObject *
    239 PyCursesPanel_above(PyCursesPanelObject *self)
    240 {
    241     PANEL *pan;
    242     PyCursesPanelObject *po;
    243 
    244     pan = panel_above(self->pan);
    245 
    246     if (pan == NULL) {          /* valid output, it means the calling panel
    247                                    is on top of the stack */
    248         Py_INCREF(Py_None);
    249         return Py_None;
    250     }
    251     po = find_po(pan);
    252     if (po == NULL) {
    253         PyErr_SetString(PyExc_RuntimeError,
    254                         "panel_above: can't find Panel Object");
    255         return NULL;
    256     }
    257     Py_INCREF(po);
    258     return (PyObject *)po;
    259 }
    260 
    261 /* panel_below(NULL) returns the top panel in the stack. To get
    262    this behaviour we use curses.panel.top_panel(). */
    263 static PyObject *
    264 PyCursesPanel_below(PyCursesPanelObject *self)
    265 {
    266     PANEL *pan;
    267     PyCursesPanelObject *po;
    268 
    269     pan = panel_below(self->pan);
    270 
    271     if (pan == NULL) {          /* valid output, it means the calling panel
    272                                    is on the bottom of the stack */
    273         Py_INCREF(Py_None);
    274         return Py_None;
    275     }
    276     po = find_po(pan);
    277     if (po == NULL) {
    278         PyErr_SetString(PyExc_RuntimeError,
    279                         "panel_below: can't find Panel Object");
    280         return NULL;
    281     }
    282     Py_INCREF(po);
    283     return (PyObject *)po;
    284 }
    285 
    286 static PyObject *
    287 PyCursesPanel_window(PyCursesPanelObject *self)
    288 {
    289     Py_INCREF(self->wo);
    290     return (PyObject *)self->wo;
    291 }
    292 
    293 static PyObject *
    294 PyCursesPanel_replace_panel(PyCursesPanelObject *self, PyObject *args)
    295 {
    296     PyCursesPanelObject *po;
    297     PyCursesWindowObject *temp;
    298     int rtn;
    299 
    300     if (PyTuple_Size(args) != 1) {
    301         PyErr_SetString(PyExc_TypeError, "replace requires one argument");
    302         return NULL;
    303     }
    304     if (!PyArg_ParseTuple(args, "O!;window object",
    305                           &PyCursesWindow_Type, &temp))
    306         return NULL;
    307 
    308     po = find_po(self->pan);
    309     if (po == NULL) {
    310         PyErr_SetString(PyExc_RuntimeError,
    311                         "replace_panel: can't find Panel Object");
    312         return NULL;
    313     }
    314 
    315     rtn = replace_panel(self->pan, temp->win);
    316     if (rtn == ERR) {
    317         PyErr_SetString(_curses_panelstate_global->PyCursesError, "replace_panel() returned ERR");
    318         return NULL;
    319     }
    320     Py_INCREF(temp);
    321     Py_SETREF(po->wo, temp);
    322     Py_INCREF(Py_None);
    323     return Py_None;
    324 }
    325 
    326 static PyObject *
    327 PyCursesPanel_set_panel_userptr(PyCursesPanelObject *self, PyObject *obj)
    328 {
    329     PyObject *oldobj;
    330     int rc;
    331     PyCursesInitialised;
    332     Py_INCREF(obj);
    333     oldobj = (PyObject *) panel_userptr(self->pan);
    334     rc = set_panel_userptr(self->pan, (void*)obj);
    335     if (rc == ERR) {
    336         /* In case of an ncurses error, decref the new object again */
    337         Py_DECREF(obj);
    338     }
    339     Py_XDECREF(oldobj);
    340     return PyCursesCheckERR(rc, "set_panel_userptr");
    341 }
    342 
    343 static PyObject *
    344 PyCursesPanel_userptr(PyCursesPanelObject *self)
    345 {
    346     PyObject *obj;
    347     PyCursesInitialised;
    348     obj = (PyObject *) panel_userptr(self->pan);
    349     if (obj == NULL) {
    350         PyErr_SetString(_curses_panelstate_global->PyCursesError, "no userptr set");
    351         return NULL;
    352     }
    353 
    354     Py_INCREF(obj);
    355     return obj;
    356 }
    357 
    358 
    359 /* Module interface */
    360 
    361 static PyMethodDef PyCursesPanel_Methods[] = {
    362     {"above",           (PyCFunction)PyCursesPanel_above, METH_NOARGS},
    363     {"below",           (PyCFunction)PyCursesPanel_below, METH_NOARGS},
    364     {"bottom",          (PyCFunction)PyCursesPanel_bottom_panel, METH_NOARGS},
    365     {"hidden",          (PyCFunction)PyCursesPanel_panel_hidden, METH_NOARGS},
    366     {"hide",            (PyCFunction)PyCursesPanel_hide_panel, METH_NOARGS},
    367     {"move",            (PyCFunction)PyCursesPanel_move_panel, METH_VARARGS},
    368     {"replace",         (PyCFunction)PyCursesPanel_replace_panel, METH_VARARGS},
    369     {"set_userptr",     (PyCFunction)PyCursesPanel_set_panel_userptr, METH_O},
    370     {"show",            (PyCFunction)PyCursesPanel_show_panel, METH_NOARGS},
    371     {"top",             (PyCFunction)PyCursesPanel_top_panel, METH_NOARGS},
    372     {"userptr",         (PyCFunction)PyCursesPanel_userptr, METH_NOARGS},
    373     {"window",          (PyCFunction)PyCursesPanel_window, METH_NOARGS},
    374     {NULL,              NULL}   /* sentinel */
    375 };
    376 
    377 /* -------------------------------------------------------*/
    378 
    379 static PyType_Slot PyCursesPanel_Type_slots[] = {
    380     {Py_tp_dealloc, PyCursesPanel_Dealloc},
    381     {Py_tp_methods, PyCursesPanel_Methods},
    382     {0, 0},
    383 };
    384 
    385 static PyType_Spec PyCursesPanel_Type_spec = {
    386     "_curses_panel.curses panel",
    387     sizeof(PyCursesPanelObject),
    388     0,
    389     Py_TPFLAGS_DEFAULT,
    390     PyCursesPanel_Type_slots
    391 };
    392 
    393 /* Wrapper for panel_above(NULL). This function returns the bottom
    394    panel of the stack, so it's renamed to bottom_panel().
    395    panel.above() *requires* a panel object in the first place which
    396    may be undesirable. */
    397 static PyObject *
    398 PyCurses_bottom_panel(PyObject *self)
    399 {
    400     PANEL *pan;
    401     PyCursesPanelObject *po;
    402 
    403     PyCursesInitialised;
    404 
    405     pan = panel_above(NULL);
    406 
    407     if (pan == NULL) {          /* valid output, it means
    408                                    there's no panel at all */
    409         Py_INCREF(Py_None);
    410         return Py_None;
    411     }
    412     po = find_po(pan);
    413     if (po == NULL) {
    414         PyErr_SetString(PyExc_RuntimeError,
    415                         "panel_above: can't find Panel Object");
    416         return NULL;
    417     }
    418     Py_INCREF(po);
    419     return (PyObject *)po;
    420 }
    421 
    422 static PyObject *
    423 PyCurses_new_panel(PyObject *self, PyObject *args)
    424 {
    425     PyCursesWindowObject *win;
    426     PANEL *pan;
    427 
    428     if (!PyArg_ParseTuple(args, "O!", &PyCursesWindow_Type, &win))
    429         return NULL;
    430     pan = new_panel(win->win);
    431     if (pan == NULL) {
    432         PyErr_SetString(_curses_panelstate_global->PyCursesError, catchall_NULL);
    433         return NULL;
    434     }
    435     return (PyObject *)PyCursesPanel_New(pan, win);
    436 }
    437 
    438 
    439 /* Wrapper for panel_below(NULL). This function returns the top panel
    440    of the stack, so it's renamed to top_panel(). panel.below()
    441    *requires* a panel object in the first place which may be
    442    undesirable. */
    443 static PyObject *
    444 PyCurses_top_panel(PyObject *self)
    445 {
    446     PANEL *pan;
    447     PyCursesPanelObject *po;
    448 
    449     PyCursesInitialised;
    450 
    451     pan = panel_below(NULL);
    452 
    453     if (pan == NULL) {          /* valid output, it means
    454                                    there's no panel at all */
    455         Py_INCREF(Py_None);
    456         return Py_None;
    457     }
    458     po = find_po(pan);
    459     if (po == NULL) {
    460         PyErr_SetString(PyExc_RuntimeError,
    461                         "panel_below: can't find Panel Object");
    462         return NULL;
    463     }
    464     Py_INCREF(po);
    465     return (PyObject *)po;
    466 }
    467 
    468 static PyObject *PyCurses_update_panels(PyObject *self)
    469 {
    470     PyCursesInitialised;
    471     update_panels();
    472     Py_INCREF(Py_None);
    473     return Py_None;
    474 }
    475 
    476 
    477 /* List of functions defined in the module */
    478 
    479 static PyMethodDef PyCurses_methods[] = {
    480     {"bottom_panel",        (PyCFunction)PyCurses_bottom_panel,  METH_NOARGS},
    481     {"new_panel",           (PyCFunction)PyCurses_new_panel,     METH_VARARGS},
    482     {"top_panel",           (PyCFunction)PyCurses_top_panel,     METH_NOARGS},
    483     {"update_panels",       (PyCFunction)PyCurses_update_panels, METH_NOARGS},
    484     {NULL,              NULL}           /* sentinel */
    485 };
    486 
    487 /* Initialization function for the module */
    488 
    489 
    490 static struct PyModuleDef _curses_panelmodule = {
    491         PyModuleDef_HEAD_INIT,
    492         "_curses_panel",
    493         NULL,
    494         sizeof(_curses_panelstate),
    495         PyCurses_methods,
    496         NULL,
    497         _curses_panel_traverse,
    498         _curses_panel_clear,
    499         _curses_panel_free
    500 };
    501 
    502 PyMODINIT_FUNC
    503 PyInit__curses_panel(void)
    504 {
    505     PyObject *m, *d, *v;
    506 
    507     /* Create the module and add the functions */
    508     m = PyModule_Create(&_curses_panelmodule);
    509     if (m == NULL)
    510         goto fail;
    511     d = PyModule_GetDict(m);
    512 
    513     /* Initialize object type */
    514     v = PyType_FromSpec(&PyCursesPanel_Type_spec);
    515     if (v == NULL)
    516         goto fail;
    517     ((PyTypeObject *)v)->tp_new = NULL;
    518     _curses_panelstate(m)->PyCursesPanel_Type = v;
    519 
    520     import_curses();
    521     if (PyErr_Occurred())
    522         goto fail;
    523 
    524     /* For exception _curses_panel.error */
    525     _curses_panelstate(m)->PyCursesError = PyErr_NewException("_curses_panel.error", NULL, NULL);
    526     PyDict_SetItemString(d, "error", _curses_panelstate(m)->PyCursesError);
    527 
    528     /* Make the version available */
    529     v = PyUnicode_FromString(PyCursesVersion);
    530     PyDict_SetItemString(d, "version", v);
    531     PyDict_SetItemString(d, "__version__", v);
    532     Py_DECREF(v);
    533     return m;
    534   fail:
    535     Py_XDECREF(m);
    536     return NULL;
    537 }
    538