1/* 2 * Interface to the ncurses panel library 3 * 4 * Original version by Thomas Gellekum 5 */ 6 7/* Release Number */ 8 9static 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 19typedef struct { 20 PyObject *PyCursesError; 21 PyTypeObject *PyCursesPanel_Type; 22} _curses_panel_state; 23 24static inline _curses_panel_state * 25get_curses_panel_state(PyObject *module) 26{ 27 void *state = PyModule_GetState(module); 28 assert(state != NULL); 29 return (_curses_panel_state *)state; 30} 31 32static int 33_curses_panel_clear(PyObject *mod) 34{ 35 _curses_panel_state *state = get_curses_panel_state(mod); 36 Py_CLEAR(state->PyCursesError); 37 Py_CLEAR(state->PyCursesPanel_Type); 38 return 0; 39} 40 41static int 42_curses_panel_traverse(PyObject *mod, visitproc visit, void *arg) 43{ 44 Py_VISIT(Py_TYPE(mod)); 45 _curses_panel_state *state = get_curses_panel_state(mod); 46 Py_VISIT(state->PyCursesError); 47 Py_VISIT(state->PyCursesPanel_Type); 48 return 0; 49} 50 51static void 52_curses_panel_free(void *mod) 53{ 54 _curses_panel_clear((PyObject *) mod); 55} 56 57/* Utility Functions */ 58 59/* 60 * Check the return code from a curses function and return None 61 * or raise an exception as appropriate. 62 */ 63 64static PyObject * 65PyCursesCheckERR(_curses_panel_state *state, int code, const char *fname) 66{ 67 if (code != ERR) { 68 Py_RETURN_NONE; 69 } 70 else { 71 if (fname == NULL) { 72 PyErr_SetString(state->PyCursesError, catchall_ERR); 73 } 74 else { 75 PyErr_Format(state->PyCursesError, "%s() returned ERR", fname); 76 } 77 return NULL; 78 } 79} 80 81/***************************************************************************** 82 The Panel Object 83******************************************************************************/ 84 85/* Definition of the panel object and panel type */ 86 87typedef struct { 88 PyObject_HEAD 89 PANEL *pan; 90 PyCursesWindowObject *wo; /* for reference counts */ 91} PyCursesPanelObject; 92 93/* Some helper functions. The problem is that there's always a window 94 associated with a panel. To ensure that Python's GC doesn't pull 95 this window from under our feet we need to keep track of references 96 to the corresponding window object within Python. We can't use 97 dupwin(oldwin) to keep a copy of the curses WINDOW because the 98 contents of oldwin is copied only once; code like 99 100 win = newwin(...) 101 pan = win.panel() 102 win.addstr(some_string) 103 pan.window().addstr(other_string) 104 105 will fail. */ 106 107/* We keep a linked list of PyCursesPanelObjects, lop. A list should 108 suffice, I don't expect more than a handful or at most a few 109 dozens of panel objects within a typical program. */ 110typedef struct _list_of_panels { 111 PyCursesPanelObject *po; 112 struct _list_of_panels *next; 113} list_of_panels; 114 115/* list anchor */ 116static list_of_panels *lop; 117 118/* Insert a new panel object into lop */ 119static int 120insert_lop(PyCursesPanelObject *po) 121{ 122 list_of_panels *new; 123 124 if ((new = (list_of_panels *)PyMem_Malloc(sizeof(list_of_panels))) == NULL) { 125 PyErr_NoMemory(); 126 return -1; 127 } 128 new->po = po; 129 new->next = lop; 130 lop = new; 131 return 0; 132} 133 134/* Remove the panel object from lop */ 135static void 136remove_lop(PyCursesPanelObject *po) 137{ 138 list_of_panels *temp, *n; 139 140 temp = lop; 141 if (temp->po == po) { 142 lop = temp->next; 143 PyMem_Free(temp); 144 return; 145 } 146 while (temp->next == NULL || temp->next->po != po) { 147 if (temp->next == NULL) { 148 PyErr_SetString(PyExc_RuntimeError, 149 "remove_lop: can't find Panel Object"); 150 return; 151 } 152 temp = temp->next; 153 } 154 n = temp->next->next; 155 PyMem_Free(temp->next); 156 temp->next = n; 157 return; 158} 159 160/* Return the panel object that corresponds to pan */ 161static PyCursesPanelObject * 162find_po(PANEL *pan) 163{ 164 list_of_panels *temp; 165 for (temp = lop; temp->po->pan != pan; temp = temp->next) 166 if (temp->next == NULL) return NULL; /* not found!? */ 167 return temp->po; 168} 169 170/*[clinic input] 171module _curses_panel 172class _curses_panel.panel "PyCursesPanelObject *" "&PyCursesPanel_Type" 173[clinic start generated code]*/ 174/*[clinic end generated code: output=da39a3ee5e6b4b0d input=2f4ef263ca850a31]*/ 175 176#include "clinic/_curses_panel.c.h" 177 178/* ------------- PANEL routines --------------- */ 179 180/*[clinic input] 181_curses_panel.panel.bottom 182 183 cls: defining_class 184 185Push the panel to the bottom of the stack. 186[clinic start generated code]*/ 187 188static PyObject * 189_curses_panel_panel_bottom_impl(PyCursesPanelObject *self, PyTypeObject *cls) 190/*[clinic end generated code: output=8ec7fbbc08554021 input=6b7d2c0578b5a1c4]*/ 191{ 192 _curses_panel_state *state = PyType_GetModuleState(cls); 193 return PyCursesCheckERR(state, bottom_panel(self->pan), "bottom"); 194} 195 196/*[clinic input] 197_curses_panel.panel.hide 198 199 cls: defining_class 200 201Hide the panel. 202 203This does not delete the object, it just makes the window on screen invisible. 204[clinic start generated code]*/ 205 206static PyObject * 207_curses_panel_panel_hide_impl(PyCursesPanelObject *self, PyTypeObject *cls) 208/*[clinic end generated code: output=cc6ab7203cdc1450 input=1bfc741f473e6055]*/ 209{ 210 _curses_panel_state *state = PyType_GetModuleState(cls); 211 return PyCursesCheckERR(state, hide_panel(self->pan), "hide"); 212} 213 214/*[clinic input] 215_curses_panel.panel.show 216 217 cls: defining_class 218 219Display the panel (which might have been hidden). 220[clinic start generated code]*/ 221 222static PyObject * 223_curses_panel_panel_show_impl(PyCursesPanelObject *self, PyTypeObject *cls) 224/*[clinic end generated code: output=dc3421de375f0409 input=8122e80151cb4379]*/ 225{ 226 _curses_panel_state *state = PyType_GetModuleState(cls); 227 return PyCursesCheckERR(state, show_panel(self->pan), "show"); 228} 229 230/*[clinic input] 231_curses_panel.panel.top 232 233 cls: defining_class 234 235Push panel to the top of the stack. 236[clinic start generated code]*/ 237 238static PyObject * 239_curses_panel_panel_top_impl(PyCursesPanelObject *self, PyTypeObject *cls) 240/*[clinic end generated code: output=10a072e511e873f7 input=1f372d597dda3379]*/ 241{ 242 _curses_panel_state *state = PyType_GetModuleState(cls); 243 return PyCursesCheckERR(state, top_panel(self->pan), "top"); 244} 245 246/* Allocation and deallocation of Panel Objects */ 247 248static PyObject * 249PyCursesPanel_New(_curses_panel_state *state, PANEL *pan, 250 PyCursesWindowObject *wo) 251{ 252 PyCursesPanelObject *po = PyObject_New(PyCursesPanelObject, 253 state->PyCursesPanel_Type); 254 if (po == NULL) { 255 return NULL; 256 } 257 258 po->pan = pan; 259 if (insert_lop(po) < 0) { 260 po->wo = NULL; 261 Py_DECREF(po); 262 return NULL; 263 } 264 po->wo = wo; 265 Py_INCREF(wo); 266 return (PyObject *)po; 267} 268 269static void 270PyCursesPanel_Dealloc(PyCursesPanelObject *po) 271{ 272 PyObject *tp, *obj; 273 274 tp = (PyObject *) Py_TYPE(po); 275 obj = (PyObject *) panel_userptr(po->pan); 276 if (obj) { 277 (void)set_panel_userptr(po->pan, NULL); 278 Py_DECREF(obj); 279 } 280 (void)del_panel(po->pan); 281 if (po->wo != NULL) { 282 Py_DECREF(po->wo); 283 remove_lop(po); 284 } 285 PyObject_Free(po); 286 Py_DECREF(tp); 287} 288 289/* panel_above(NULL) returns the bottom panel in the stack. To get 290 this behaviour we use curses.panel.bottom_panel(). */ 291/*[clinic input] 292_curses_panel.panel.above 293 294Return the panel above the current panel. 295[clinic start generated code]*/ 296 297static PyObject * 298_curses_panel_panel_above_impl(PyCursesPanelObject *self) 299/*[clinic end generated code: output=70ac06d25fd3b4da input=c059994022976788]*/ 300{ 301 PANEL *pan; 302 PyCursesPanelObject *po; 303 304 pan = panel_above(self->pan); 305 306 if (pan == NULL) { /* valid output, it means the calling panel 307 is on top of the stack */ 308 Py_RETURN_NONE; 309 } 310 po = find_po(pan); 311 if (po == NULL) { 312 PyErr_SetString(PyExc_RuntimeError, 313 "panel_above: can't find Panel Object"); 314 return NULL; 315 } 316 Py_INCREF(po); 317 return (PyObject *)po; 318} 319 320/* panel_below(NULL) returns the top panel in the stack. To get 321 this behaviour we use curses.panel.top_panel(). */ 322/*[clinic input] 323_curses_panel.panel.below 324 325Return the panel below the current panel. 326[clinic start generated code]*/ 327 328static PyObject * 329_curses_panel_panel_below_impl(PyCursesPanelObject *self) 330/*[clinic end generated code: output=282861122e06e3de input=cc08f61936d297c6]*/ 331{ 332 PANEL *pan; 333 PyCursesPanelObject *po; 334 335 pan = panel_below(self->pan); 336 337 if (pan == NULL) { /* valid output, it means the calling panel 338 is on the bottom of the stack */ 339 Py_RETURN_NONE; 340 } 341 po = find_po(pan); 342 if (po == NULL) { 343 PyErr_SetString(PyExc_RuntimeError, 344 "panel_below: can't find Panel Object"); 345 return NULL; 346 } 347 Py_INCREF(po); 348 return (PyObject *)po; 349} 350 351/*[clinic input] 352_curses_panel.panel.hidden 353 354Return True if the panel is hidden (not visible), False otherwise. 355[clinic start generated code]*/ 356 357static PyObject * 358_curses_panel_panel_hidden_impl(PyCursesPanelObject *self) 359/*[clinic end generated code: output=66eebd1ab4501a71 input=453d4b4fce25e21a]*/ 360{ 361 if (panel_hidden(self->pan)) 362 Py_RETURN_TRUE; 363 else 364 Py_RETURN_FALSE; 365} 366 367/*[clinic input] 368_curses_panel.panel.move 369 370 cls: defining_class 371 y: int 372 x: int 373 / 374 375Move the panel to the screen coordinates (y, x). 376[clinic start generated code]*/ 377 378static PyObject * 379_curses_panel_panel_move_impl(PyCursesPanelObject *self, PyTypeObject *cls, 380 int y, int x) 381/*[clinic end generated code: output=ce546c93e56867da input=60a0e7912ff99849]*/ 382{ 383 _curses_panel_state *state = PyType_GetModuleState(cls); 384 return PyCursesCheckERR(state, move_panel(self->pan, y, x), "move_panel"); 385} 386 387/*[clinic input] 388_curses_panel.panel.window 389 390Return the window object associated with the panel. 391[clinic start generated code]*/ 392 393static PyObject * 394_curses_panel_panel_window_impl(PyCursesPanelObject *self) 395/*[clinic end generated code: output=5f05940d4106b4cb input=6067353d2c307901]*/ 396{ 397 Py_INCREF(self->wo); 398 return (PyObject *)self->wo; 399} 400 401/*[clinic input] 402_curses_panel.panel.replace 403 404 cls: defining_class 405 win: object(type="PyCursesWindowObject *", subclass_of="&PyCursesWindow_Type") 406 / 407 408Change the window associated with the panel to the window win. 409[clinic start generated code]*/ 410 411static PyObject * 412_curses_panel_panel_replace_impl(PyCursesPanelObject *self, 413 PyTypeObject *cls, 414 PyCursesWindowObject *win) 415/*[clinic end generated code: output=c71f95c212d58ae7 input=dbec7180ece41ff5]*/ 416{ 417 _curses_panel_state *state = PyType_GetModuleState(cls); 418 419 PyCursesPanelObject *po = find_po(self->pan); 420 if (po == NULL) { 421 PyErr_SetString(PyExc_RuntimeError, 422 "replace_panel: can't find Panel Object"); 423 return NULL; 424 } 425 426 int rtn = replace_panel(self->pan, win->win); 427 if (rtn == ERR) { 428 PyErr_SetString(state->PyCursesError, "replace_panel() returned ERR"); 429 return NULL; 430 } 431 Py_INCREF(win); 432 Py_SETREF(po->wo, win); 433 Py_RETURN_NONE; 434} 435 436/*[clinic input] 437_curses_panel.panel.set_userptr 438 439 cls: defining_class 440 obj: object 441 / 442 443Set the panel's user pointer to obj. 444[clinic start generated code]*/ 445 446static PyObject * 447_curses_panel_panel_set_userptr_impl(PyCursesPanelObject *self, 448 PyTypeObject *cls, PyObject *obj) 449/*[clinic end generated code: output=db74f3db07b28080 input=e3fee2ff7b1b8e48]*/ 450{ 451 PyCursesInitialised; 452 Py_INCREF(obj); 453 PyObject *oldobj = (PyObject *) panel_userptr(self->pan); 454 int rc = set_panel_userptr(self->pan, (void*)obj); 455 if (rc == ERR) { 456 /* In case of an ncurses error, decref the new object again */ 457 Py_DECREF(obj); 458 } 459 else { 460 Py_XDECREF(oldobj); 461 } 462 463 _curses_panel_state *state = PyType_GetModuleState(cls); 464 return PyCursesCheckERR(state, rc, "set_panel_userptr"); 465} 466 467/*[clinic input] 468_curses_panel.panel.userptr 469 470 cls: defining_class 471 472Return the user pointer for the panel. 473[clinic start generated code]*/ 474 475static PyObject * 476_curses_panel_panel_userptr_impl(PyCursesPanelObject *self, 477 PyTypeObject *cls) 478/*[clinic end generated code: output=eea6e6f39ffc0179 input=f22ca4f115e30a80]*/ 479{ 480 _curses_panel_state *state = PyType_GetModuleState(cls); 481 482 PyCursesInitialised; 483 PyObject *obj = (PyObject *) panel_userptr(self->pan); 484 if (obj == NULL) { 485 PyErr_SetString(state->PyCursesError, "no userptr set"); 486 return NULL; 487 } 488 489 Py_INCREF(obj); 490 return obj; 491} 492 493 494/* Module interface */ 495 496static PyMethodDef PyCursesPanel_Methods[] = { 497 _CURSES_PANEL_PANEL_ABOVE_METHODDEF 498 _CURSES_PANEL_PANEL_BELOW_METHODDEF 499 _CURSES_PANEL_PANEL_BOTTOM_METHODDEF 500 _CURSES_PANEL_PANEL_HIDDEN_METHODDEF 501 _CURSES_PANEL_PANEL_HIDE_METHODDEF 502 _CURSES_PANEL_PANEL_MOVE_METHODDEF 503 _CURSES_PANEL_PANEL_REPLACE_METHODDEF 504 _CURSES_PANEL_PANEL_SET_USERPTR_METHODDEF 505 _CURSES_PANEL_PANEL_SHOW_METHODDEF 506 _CURSES_PANEL_PANEL_TOP_METHODDEF 507 _CURSES_PANEL_PANEL_USERPTR_METHODDEF 508 _CURSES_PANEL_PANEL_WINDOW_METHODDEF 509 {NULL, NULL} /* sentinel */ 510}; 511 512/* -------------------------------------------------------*/ 513 514static PyType_Slot PyCursesPanel_Type_slots[] = { 515 {Py_tp_dealloc, PyCursesPanel_Dealloc}, 516 {Py_tp_methods, PyCursesPanel_Methods}, 517 {0, 0}, 518}; 519 520static PyType_Spec PyCursesPanel_Type_spec = { 521 .name = "_curses_panel.panel", 522 .basicsize = sizeof(PyCursesPanelObject), 523 .flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_DISALLOW_INSTANTIATION, 524 .slots = PyCursesPanel_Type_slots 525}; 526 527/* Wrapper for panel_above(NULL). This function returns the bottom 528 panel of the stack, so it's renamed to bottom_panel(). 529 panel.above() *requires* a panel object in the first place which 530 may be undesirable. */ 531/*[clinic input] 532_curses_panel.bottom_panel 533 534Return the bottom panel in the panel stack. 535[clinic start generated code]*/ 536 537static PyObject * 538_curses_panel_bottom_panel_impl(PyObject *module) 539/*[clinic end generated code: output=3aba9f985f4c2bd0 input=634c2a8078b3d7e4]*/ 540{ 541 PANEL *pan; 542 PyCursesPanelObject *po; 543 544 PyCursesInitialised; 545 546 pan = panel_above(NULL); 547 548 if (pan == NULL) { /* valid output, it means 549 there's no panel at all */ 550 Py_RETURN_NONE; 551 } 552 po = find_po(pan); 553 if (po == NULL) { 554 PyErr_SetString(PyExc_RuntimeError, 555 "panel_above: can't find Panel Object"); 556 return NULL; 557 } 558 Py_INCREF(po); 559 return (PyObject *)po; 560} 561 562/*[clinic input] 563_curses_panel.new_panel 564 565 win: object(type="PyCursesWindowObject *", subclass_of="&PyCursesWindow_Type") 566 / 567 568Return a panel object, associating it with the given window win. 569[clinic start generated code]*/ 570 571static PyObject * 572_curses_panel_new_panel_impl(PyObject *module, PyCursesWindowObject *win) 573/*[clinic end generated code: output=45e948e0176a9bd2 input=74d4754e0ebe4800]*/ 574{ 575 _curses_panel_state *state = get_curses_panel_state(module); 576 577 PANEL *pan = new_panel(win->win); 578 if (pan == NULL) { 579 PyErr_SetString(state->PyCursesError, catchall_NULL); 580 return NULL; 581 } 582 return (PyObject *)PyCursesPanel_New(state, pan, win); 583} 584 585 586/* Wrapper for panel_below(NULL). This function returns the top panel 587 of the stack, so it's renamed to top_panel(). panel.below() 588 *requires* a panel object in the first place which may be 589 undesirable. */ 590/*[clinic input] 591_curses_panel.top_panel 592 593Return the top panel in the panel stack. 594[clinic start generated code]*/ 595 596static PyObject * 597_curses_panel_top_panel_impl(PyObject *module) 598/*[clinic end generated code: output=86704988bea8508e input=e62d6278dba39e79]*/ 599{ 600 PANEL *pan; 601 PyCursesPanelObject *po; 602 603 PyCursesInitialised; 604 605 pan = panel_below(NULL); 606 607 if (pan == NULL) { /* valid output, it means 608 there's no panel at all */ 609 Py_RETURN_NONE; 610 } 611 po = find_po(pan); 612 if (po == NULL) { 613 PyErr_SetString(PyExc_RuntimeError, 614 "panel_below: can't find Panel Object"); 615 return NULL; 616 } 617 Py_INCREF(po); 618 return (PyObject *)po; 619} 620 621/*[clinic input] 622_curses_panel.update_panels 623 624Updates the virtual screen after changes in the panel stack. 625 626This does not call curses.doupdate(), so you'll have to do this yourself. 627[clinic start generated code]*/ 628 629static PyObject * 630_curses_panel_update_panels_impl(PyObject *module) 631/*[clinic end generated code: output=2f3b4c2e03d90ded input=5299624c9a708621]*/ 632{ 633 PyCursesInitialised; 634 update_panels(); 635 Py_RETURN_NONE; 636} 637 638/* List of functions defined in the module */ 639 640static PyMethodDef PyCurses_methods[] = { 641 _CURSES_PANEL_BOTTOM_PANEL_METHODDEF 642 _CURSES_PANEL_NEW_PANEL_METHODDEF 643 _CURSES_PANEL_TOP_PANEL_METHODDEF 644 _CURSES_PANEL_UPDATE_PANELS_METHODDEF 645 {NULL, NULL} /* sentinel */ 646}; 647 648/* Initialization function for the module */ 649static int 650_curses_panel_exec(PyObject *mod) 651{ 652 _curses_panel_state *state = get_curses_panel_state(mod); 653 /* Initialize object type */ 654 state->PyCursesPanel_Type = (PyTypeObject *)PyType_FromModuleAndSpec( 655 mod, &PyCursesPanel_Type_spec, NULL); 656 if (state->PyCursesPanel_Type == NULL) { 657 return -1; 658 } 659 660 if (PyModule_AddType(mod, state->PyCursesPanel_Type) < 0) { 661 return -1; 662 } 663 664 import_curses(); 665 if (PyErr_Occurred()) { 666 return -1; 667 } 668 669 /* For exception _curses_panel.error */ 670 state->PyCursesError = PyErr_NewException( 671 "_curses_panel.error", NULL, NULL); 672 673 Py_INCREF(state->PyCursesError); 674 if (PyModule_AddObject(mod, "error", state->PyCursesError) < 0) { 675 Py_DECREF(state->PyCursesError); 676 return -1; 677 } 678 679 /* Make the version available */ 680 PyObject *v = PyUnicode_FromString(PyCursesVersion); 681 if (v == NULL) { 682 return -1; 683 } 684 685 PyObject *d = PyModule_GetDict(mod); 686 if (PyDict_SetItemString(d, "version", v) < 0) { 687 Py_DECREF(v); 688 return -1; 689 } 690 if (PyDict_SetItemString(d, "__version__", v) < 0) { 691 Py_DECREF(v); 692 return -1; 693 } 694 695 Py_DECREF(v); 696 697 return 0; 698} 699 700static PyModuleDef_Slot _curses_slots[] = { 701 {Py_mod_exec, _curses_panel_exec}, 702 {0, NULL} 703}; 704 705static struct PyModuleDef _curses_panelmodule = { 706 PyModuleDef_HEAD_INIT, 707 .m_name = "_curses_panel", 708 .m_size = sizeof(_curses_panel_state), 709 .m_methods = PyCurses_methods, 710 .m_slots = _curses_slots, 711 .m_traverse = _curses_panel_traverse, 712 .m_clear = _curses_panel_clear, 713 .m_free = _curses_panel_free 714}; 715 716PyMODINIT_FUNC 717PyInit__curses_panel(void) 718{ 719 return PyModuleDef_Init(&_curses_panelmodule); 720} 721