Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
allendowney
GitHub Repository: allendowney/cpython
Path: blob/main/Modules/_curses_panel.c
12 views
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
PyTypeObject *PyCursesPanel_Type;
22
} _curses_panel_state;
23
24
static inline _curses_panel_state *
25
get_curses_panel_state(PyObject *module)
26
{
27
void *state = PyModule_GetState(module);
28
assert(state != NULL);
29
return (_curses_panel_state *)state;
30
}
31
32
static 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
41
static 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
51
static 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
64
static PyObject *
65
PyCursesCheckERR(_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
87
typedef 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. */
110
typedef struct _list_of_panels {
111
PyCursesPanelObject *po;
112
struct _list_of_panels *next;
113
} list_of_panels;
114
115
/* list anchor */
116
static list_of_panels *lop;
117
118
/* Insert a new panel object into lop */
119
static int
120
insert_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 */
135
static void
136
remove_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 */
161
static PyCursesPanelObject *
162
find_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]
171
module _curses_panel
172
class _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
185
Push the panel to the bottom of the stack.
186
[clinic start generated code]*/
187
188
static 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
201
Hide the panel.
202
203
This does not delete the object, it just makes the window on screen invisible.
204
[clinic start generated code]*/
205
206
static 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
219
Display the panel (which might have been hidden).
220
[clinic start generated code]*/
221
222
static 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
235
Push panel to the top of the stack.
236
[clinic start generated code]*/
237
238
static 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
248
static PyObject *
249
PyCursesPanel_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 = (PyCursesWindowObject*)Py_NewRef(wo);
265
return (PyObject *)po;
266
}
267
268
static void
269
PyCursesPanel_Dealloc(PyCursesPanelObject *po)
270
{
271
PyObject *tp, *obj;
272
273
tp = (PyObject *) Py_TYPE(po);
274
obj = (PyObject *) panel_userptr(po->pan);
275
if (obj) {
276
(void)set_panel_userptr(po->pan, NULL);
277
Py_DECREF(obj);
278
}
279
(void)del_panel(po->pan);
280
if (po->wo != NULL) {
281
Py_DECREF(po->wo);
282
remove_lop(po);
283
}
284
PyObject_Free(po);
285
Py_DECREF(tp);
286
}
287
288
/* panel_above(NULL) returns the bottom panel in the stack. To get
289
this behaviour we use curses.panel.bottom_panel(). */
290
/*[clinic input]
291
_curses_panel.panel.above
292
293
Return the panel above the current panel.
294
[clinic start generated code]*/
295
296
static PyObject *
297
_curses_panel_panel_above_impl(PyCursesPanelObject *self)
298
/*[clinic end generated code: output=70ac06d25fd3b4da input=c059994022976788]*/
299
{
300
PANEL *pan;
301
PyCursesPanelObject *po;
302
303
pan = panel_above(self->pan);
304
305
if (pan == NULL) { /* valid output, it means the calling panel
306
is on top of the stack */
307
Py_RETURN_NONE;
308
}
309
po = find_po(pan);
310
if (po == NULL) {
311
PyErr_SetString(PyExc_RuntimeError,
312
"panel_above: can't find Panel Object");
313
return NULL;
314
}
315
return Py_NewRef(po);
316
}
317
318
/* panel_below(NULL) returns the top panel in the stack. To get
319
this behaviour we use curses.panel.top_panel(). */
320
/*[clinic input]
321
_curses_panel.panel.below
322
323
Return the panel below the current panel.
324
[clinic start generated code]*/
325
326
static PyObject *
327
_curses_panel_panel_below_impl(PyCursesPanelObject *self)
328
/*[clinic end generated code: output=282861122e06e3de input=cc08f61936d297c6]*/
329
{
330
PANEL *pan;
331
PyCursesPanelObject *po;
332
333
pan = panel_below(self->pan);
334
335
if (pan == NULL) { /* valid output, it means the calling panel
336
is on the bottom of the stack */
337
Py_RETURN_NONE;
338
}
339
po = find_po(pan);
340
if (po == NULL) {
341
PyErr_SetString(PyExc_RuntimeError,
342
"panel_below: can't find Panel Object");
343
return NULL;
344
}
345
return Py_NewRef(po);
346
}
347
348
/*[clinic input]
349
_curses_panel.panel.hidden
350
351
Return True if the panel is hidden (not visible), False otherwise.
352
[clinic start generated code]*/
353
354
static PyObject *
355
_curses_panel_panel_hidden_impl(PyCursesPanelObject *self)
356
/*[clinic end generated code: output=66eebd1ab4501a71 input=453d4b4fce25e21a]*/
357
{
358
if (panel_hidden(self->pan))
359
Py_RETURN_TRUE;
360
else
361
Py_RETURN_FALSE;
362
}
363
364
/*[clinic input]
365
_curses_panel.panel.move
366
367
cls: defining_class
368
y: int
369
x: int
370
/
371
372
Move the panel to the screen coordinates (y, x).
373
[clinic start generated code]*/
374
375
static PyObject *
376
_curses_panel_panel_move_impl(PyCursesPanelObject *self, PyTypeObject *cls,
377
int y, int x)
378
/*[clinic end generated code: output=ce546c93e56867da input=60a0e7912ff99849]*/
379
{
380
_curses_panel_state *state = PyType_GetModuleState(cls);
381
return PyCursesCheckERR(state, move_panel(self->pan, y, x), "move_panel");
382
}
383
384
/*[clinic input]
385
_curses_panel.panel.window
386
387
Return the window object associated with the panel.
388
[clinic start generated code]*/
389
390
static PyObject *
391
_curses_panel_panel_window_impl(PyCursesPanelObject *self)
392
/*[clinic end generated code: output=5f05940d4106b4cb input=6067353d2c307901]*/
393
{
394
return Py_NewRef(self->wo);
395
}
396
397
/*[clinic input]
398
_curses_panel.panel.replace
399
400
cls: defining_class
401
win: object(type="PyCursesWindowObject *", subclass_of="&PyCursesWindow_Type")
402
/
403
404
Change the window associated with the panel to the window win.
405
[clinic start generated code]*/
406
407
static PyObject *
408
_curses_panel_panel_replace_impl(PyCursesPanelObject *self,
409
PyTypeObject *cls,
410
PyCursesWindowObject *win)
411
/*[clinic end generated code: output=c71f95c212d58ae7 input=dbec7180ece41ff5]*/
412
{
413
_curses_panel_state *state = PyType_GetModuleState(cls);
414
415
PyCursesPanelObject *po = find_po(self->pan);
416
if (po == NULL) {
417
PyErr_SetString(PyExc_RuntimeError,
418
"replace_panel: can't find Panel Object");
419
return NULL;
420
}
421
422
int rtn = replace_panel(self->pan, win->win);
423
if (rtn == ERR) {
424
PyErr_SetString(state->PyCursesError, "replace_panel() returned ERR");
425
return NULL;
426
}
427
Py_SETREF(po->wo, (PyCursesWindowObject*)Py_NewRef(win));
428
Py_RETURN_NONE;
429
}
430
431
/*[clinic input]
432
_curses_panel.panel.set_userptr
433
434
cls: defining_class
435
obj: object
436
/
437
438
Set the panel's user pointer to obj.
439
[clinic start generated code]*/
440
441
static PyObject *
442
_curses_panel_panel_set_userptr_impl(PyCursesPanelObject *self,
443
PyTypeObject *cls, PyObject *obj)
444
/*[clinic end generated code: output=db74f3db07b28080 input=e3fee2ff7b1b8e48]*/
445
{
446
PyCursesInitialised;
447
Py_INCREF(obj);
448
PyObject *oldobj = (PyObject *) panel_userptr(self->pan);
449
int rc = set_panel_userptr(self->pan, (void*)obj);
450
if (rc == ERR) {
451
/* In case of an ncurses error, decref the new object again */
452
Py_DECREF(obj);
453
}
454
else {
455
Py_XDECREF(oldobj);
456
}
457
458
_curses_panel_state *state = PyType_GetModuleState(cls);
459
return PyCursesCheckERR(state, rc, "set_panel_userptr");
460
}
461
462
/*[clinic input]
463
_curses_panel.panel.userptr
464
465
cls: defining_class
466
467
Return the user pointer for the panel.
468
[clinic start generated code]*/
469
470
static PyObject *
471
_curses_panel_panel_userptr_impl(PyCursesPanelObject *self,
472
PyTypeObject *cls)
473
/*[clinic end generated code: output=eea6e6f39ffc0179 input=f22ca4f115e30a80]*/
474
{
475
_curses_panel_state *state = PyType_GetModuleState(cls);
476
477
PyCursesInitialised;
478
PyObject *obj = (PyObject *) panel_userptr(self->pan);
479
if (obj == NULL) {
480
PyErr_SetString(state->PyCursesError, "no userptr set");
481
return NULL;
482
}
483
484
return Py_NewRef(obj);
485
}
486
487
488
/* Module interface */
489
490
static PyMethodDef PyCursesPanel_Methods[] = {
491
_CURSES_PANEL_PANEL_ABOVE_METHODDEF
492
_CURSES_PANEL_PANEL_BELOW_METHODDEF
493
_CURSES_PANEL_PANEL_BOTTOM_METHODDEF
494
_CURSES_PANEL_PANEL_HIDDEN_METHODDEF
495
_CURSES_PANEL_PANEL_HIDE_METHODDEF
496
_CURSES_PANEL_PANEL_MOVE_METHODDEF
497
_CURSES_PANEL_PANEL_REPLACE_METHODDEF
498
_CURSES_PANEL_PANEL_SET_USERPTR_METHODDEF
499
_CURSES_PANEL_PANEL_SHOW_METHODDEF
500
_CURSES_PANEL_PANEL_TOP_METHODDEF
501
_CURSES_PANEL_PANEL_USERPTR_METHODDEF
502
_CURSES_PANEL_PANEL_WINDOW_METHODDEF
503
{NULL, NULL} /* sentinel */
504
};
505
506
/* -------------------------------------------------------*/
507
508
static PyType_Slot PyCursesPanel_Type_slots[] = {
509
{Py_tp_dealloc, PyCursesPanel_Dealloc},
510
{Py_tp_methods, PyCursesPanel_Methods},
511
{0, 0},
512
};
513
514
static PyType_Spec PyCursesPanel_Type_spec = {
515
.name = "_curses_panel.panel",
516
.basicsize = sizeof(PyCursesPanelObject),
517
.flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_DISALLOW_INSTANTIATION,
518
.slots = PyCursesPanel_Type_slots
519
};
520
521
/* Wrapper for panel_above(NULL). This function returns the bottom
522
panel of the stack, so it's renamed to bottom_panel().
523
panel.above() *requires* a panel object in the first place which
524
may be undesirable. */
525
/*[clinic input]
526
_curses_panel.bottom_panel
527
528
Return the bottom panel in the panel stack.
529
[clinic start generated code]*/
530
531
static PyObject *
532
_curses_panel_bottom_panel_impl(PyObject *module)
533
/*[clinic end generated code: output=3aba9f985f4c2bd0 input=634c2a8078b3d7e4]*/
534
{
535
PANEL *pan;
536
PyCursesPanelObject *po;
537
538
PyCursesInitialised;
539
540
pan = panel_above(NULL);
541
542
if (pan == NULL) { /* valid output, it means
543
there's no panel at all */
544
Py_RETURN_NONE;
545
}
546
po = find_po(pan);
547
if (po == NULL) {
548
PyErr_SetString(PyExc_RuntimeError,
549
"panel_above: can't find Panel Object");
550
return NULL;
551
}
552
return Py_NewRef(po);
553
}
554
555
/*[clinic input]
556
_curses_panel.new_panel
557
558
win: object(type="PyCursesWindowObject *", subclass_of="&PyCursesWindow_Type")
559
/
560
561
Return a panel object, associating it with the given window win.
562
[clinic start generated code]*/
563
564
static PyObject *
565
_curses_panel_new_panel_impl(PyObject *module, PyCursesWindowObject *win)
566
/*[clinic end generated code: output=45e948e0176a9bd2 input=74d4754e0ebe4800]*/
567
{
568
_curses_panel_state *state = get_curses_panel_state(module);
569
570
PANEL *pan = new_panel(win->win);
571
if (pan == NULL) {
572
PyErr_SetString(state->PyCursesError, catchall_NULL);
573
return NULL;
574
}
575
return (PyObject *)PyCursesPanel_New(state, pan, win);
576
}
577
578
579
/* Wrapper for panel_below(NULL). This function returns the top panel
580
of the stack, so it's renamed to top_panel(). panel.below()
581
*requires* a panel object in the first place which may be
582
undesirable. */
583
/*[clinic input]
584
_curses_panel.top_panel
585
586
Return the top panel in the panel stack.
587
[clinic start generated code]*/
588
589
static PyObject *
590
_curses_panel_top_panel_impl(PyObject *module)
591
/*[clinic end generated code: output=86704988bea8508e input=e62d6278dba39e79]*/
592
{
593
PANEL *pan;
594
PyCursesPanelObject *po;
595
596
PyCursesInitialised;
597
598
pan = panel_below(NULL);
599
600
if (pan == NULL) { /* valid output, it means
601
there's no panel at all */
602
Py_RETURN_NONE;
603
}
604
po = find_po(pan);
605
if (po == NULL) {
606
PyErr_SetString(PyExc_RuntimeError,
607
"panel_below: can't find Panel Object");
608
return NULL;
609
}
610
return Py_NewRef(po);
611
}
612
613
/*[clinic input]
614
_curses_panel.update_panels
615
616
Updates the virtual screen after changes in the panel stack.
617
618
This does not call curses.doupdate(), so you'll have to do this yourself.
619
[clinic start generated code]*/
620
621
static PyObject *
622
_curses_panel_update_panels_impl(PyObject *module)
623
/*[clinic end generated code: output=2f3b4c2e03d90ded input=5299624c9a708621]*/
624
{
625
PyCursesInitialised;
626
update_panels();
627
Py_RETURN_NONE;
628
}
629
630
/* List of functions defined in the module */
631
632
static PyMethodDef PyCurses_methods[] = {
633
_CURSES_PANEL_BOTTOM_PANEL_METHODDEF
634
_CURSES_PANEL_NEW_PANEL_METHODDEF
635
_CURSES_PANEL_TOP_PANEL_METHODDEF
636
_CURSES_PANEL_UPDATE_PANELS_METHODDEF
637
{NULL, NULL} /* sentinel */
638
};
639
640
/* Initialization function for the module */
641
static int
642
_curses_panel_exec(PyObject *mod)
643
{
644
_curses_panel_state *state = get_curses_panel_state(mod);
645
/* Initialize object type */
646
state->PyCursesPanel_Type = (PyTypeObject *)PyType_FromModuleAndSpec(
647
mod, &PyCursesPanel_Type_spec, NULL);
648
if (state->PyCursesPanel_Type == NULL) {
649
return -1;
650
}
651
652
if (PyModule_AddType(mod, state->PyCursesPanel_Type) < 0) {
653
return -1;
654
}
655
656
import_curses();
657
if (PyErr_Occurred()) {
658
return -1;
659
}
660
661
/* For exception _curses_panel.error */
662
state->PyCursesError = PyErr_NewException(
663
"_curses_panel.error", NULL, NULL);
664
665
if (PyModule_AddObject(mod, "error", Py_NewRef(state->PyCursesError)) < 0) {
666
Py_DECREF(state->PyCursesError);
667
return -1;
668
}
669
670
/* Make the version available */
671
PyObject *v = PyUnicode_FromString(PyCursesVersion);
672
if (v == NULL) {
673
return -1;
674
}
675
676
PyObject *d = PyModule_GetDict(mod);
677
if (PyDict_SetItemString(d, "version", v) < 0) {
678
Py_DECREF(v);
679
return -1;
680
}
681
if (PyDict_SetItemString(d, "__version__", v) < 0) {
682
Py_DECREF(v);
683
return -1;
684
}
685
686
Py_DECREF(v);
687
688
return 0;
689
}
690
691
static PyModuleDef_Slot _curses_slots[] = {
692
{Py_mod_exec, _curses_panel_exec},
693
// XXX gh-103092: fix isolation.
694
{Py_mod_multiple_interpreters, Py_MOD_MULTIPLE_INTERPRETERS_NOT_SUPPORTED},
695
//{Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
696
{0, NULL}
697
};
698
699
static struct PyModuleDef _curses_panelmodule = {
700
PyModuleDef_HEAD_INIT,
701
.m_name = "_curses_panel",
702
.m_size = sizeof(_curses_panel_state),
703
.m_methods = PyCurses_methods,
704
.m_slots = _curses_slots,
705
.m_traverse = _curses_panel_traverse,
706
.m_clear = _curses_panel_clear,
707
.m_free = _curses_panel_free
708
};
709
710
PyMODINIT_FUNC
711
PyInit__curses_panel(void)
712
{
713
return PyModuleDef_Init(&_curses_panelmodule);
714
}
715
716