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