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