]> Git Repo - binutils.git/blob - gdb/python/py-tui.c
gdb: move go_language class declaration into header file
[binutils.git] / gdb / python / py-tui.c
1 /* TUI windows implemented in Python
2
3    Copyright (C) 2020 Free Software Foundation, Inc.
4
5    This file is part of GDB.
6
7    This program is free software; you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation; either version 3 of the License, or
10    (at your option) any later version.
11
12    This program is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16
17    You should have received a copy of the GNU General Public License
18    along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
19
20
21 #include "defs.h"
22 #include "arch-utils.h"
23 #include "python-internal.h"
24
25 #ifdef TUI
26
27 /* Note that Python's public headers may define HAVE_NCURSES_H, so if
28    we unconditionally include this (outside the #ifdef above), then we
29    can get a compile error when ncurses is not in fact installed.  See
30    PR tui/25597; or the upstream Python bug
31    https://bugs.python.org/issue20768.  */
32 #include "gdb_curses.h"
33
34 #include "tui/tui-data.h"
35 #include "tui/tui-io.h"
36 #include "tui/tui-layout.h"
37 #include "tui/tui-wingeneral.h"
38 #include "tui/tui-winsource.h"
39
40 class tui_py_window;
41
42 /* A PyObject representing a TUI window.  */
43
44 struct gdbpy_tui_window
45 {
46   PyObject_HEAD
47
48   /* The TUI window, or nullptr if the window has been deleted.  */
49   tui_py_window *window;
50 };
51
52 extern PyTypeObject gdbpy_tui_window_object_type
53     CPYCHECKER_TYPE_OBJECT_FOR_TYPEDEF ("gdbpy_tui_window");
54
55 /* A TUI window written in Python.  */
56
57 class tui_py_window : public tui_win_info
58 {
59 public:
60
61   tui_py_window (const char *name, gdbpy_ref<gdbpy_tui_window> wrapper)
62     : m_name (name),
63       m_wrapper (std::move (wrapper))
64   {
65     m_wrapper->window = this;
66   }
67
68   ~tui_py_window ();
69
70   DISABLE_COPY_AND_ASSIGN (tui_py_window);
71
72   /* Set the "user window" to the indicated reference.  The user
73      window is the object returned the by user-defined window
74      constructor.  */
75   void set_user_window (gdbpy_ref<> &&user_window)
76   {
77     m_window = std::move (user_window);
78   }
79
80   const char *name () const override
81   {
82     return m_name.c_str ();
83   }
84
85   void rerender () override;
86   void do_scroll_vertical (int num_to_scroll) override;
87   void do_scroll_horizontal (int num_to_scroll) override;
88
89   void refresh_window () override
90   {
91     tui_win_info::refresh_window ();
92     if (m_inner_window != nullptr)
93       {
94         touchwin (m_inner_window.get ());
95         tui_wrefresh (m_inner_window.get ());
96       }
97   }
98
99   /* Erase and re-box the window.  */
100   void erase ()
101   {
102     if (is_visible () && m_inner_window != nullptr)
103       {
104         werase (m_inner_window.get ());
105         check_and_display_highlight_if_needed ();
106       }
107   }
108
109   /* Write STR to the window.  */
110   void output (const char *str);
111
112   /* A helper function to compute the viewport width.  */
113   int viewport_width () const
114   {
115     return std::max (0, width - 2);
116   }
117
118   /* A helper function to compute the viewport height.  */
119   int viewport_height () const
120   {
121     return std::max (0, height - 2);
122   }
123
124 private:
125
126   /* The name of this window.  */
127   std::string m_name;
128
129   /* We make our own inner window, so that it is easy to print without
130      overwriting the border.  */
131   std::unique_ptr<WINDOW, curses_deleter> m_inner_window;
132
133   /* The underlying Python window object.  */
134   gdbpy_ref<> m_window;
135
136   /* The Python wrapper for this object.  */
137   gdbpy_ref<gdbpy_tui_window> m_wrapper;
138 };
139
140 tui_py_window::~tui_py_window ()
141 {
142   gdbpy_enter enter_py (get_current_arch (), current_language);
143
144   /* This can be null if the user-provided Python construction
145      function failed.  */
146   if (m_window != nullptr
147       && PyObject_HasAttrString (m_window.get (), "close"))
148     {
149       gdbpy_ref<> result (PyObject_CallMethod (m_window.get (), "close",
150                                                nullptr));
151       if (result == nullptr)
152         gdbpy_print_stack ();
153     }
154
155   /* Unlink.  */
156   m_wrapper->window = nullptr;
157   /* Explicitly free the Python references.  We have to do this
158      manually because we need to hold the GIL while doing so.  */
159   m_wrapper.reset (nullptr);
160   m_window.reset (nullptr);
161 }
162
163 void
164 tui_py_window::rerender ()
165 {
166   tui_win_info::rerender ();
167
168   gdbpy_enter enter_py (get_current_arch (), current_language);
169
170   int h = viewport_height ();
171   int w = viewport_width ();
172   if (h == 0 || w == 0)
173     {
174       /* The window would be too small, so just remove the
175          contents.  */
176       m_inner_window.reset (nullptr);
177       return;
178     }
179   m_inner_window.reset (newwin (h, w, y + 1, x + 1));
180
181   if (PyObject_HasAttrString (m_window.get (), "render"))
182     {
183       gdbpy_ref<> result (PyObject_CallMethod (m_window.get (), "render",
184                                                nullptr));
185       if (result == nullptr)
186         gdbpy_print_stack ();
187     }
188 }
189
190 void
191 tui_py_window::do_scroll_horizontal (int num_to_scroll)
192 {
193   gdbpy_enter enter_py (get_current_arch (), current_language);
194
195   if (PyObject_HasAttrString (m_window.get (), "hscroll"))
196     {
197       gdbpy_ref<> result (PyObject_CallMethod (m_window.get(), "hscroll",
198                                                "i", num_to_scroll, nullptr));
199       if (result == nullptr)
200         gdbpy_print_stack ();
201     }
202 }
203
204 void
205 tui_py_window::do_scroll_vertical (int num_to_scroll)
206 {
207   gdbpy_enter enter_py (get_current_arch (), current_language);
208
209   if (PyObject_HasAttrString (m_window.get (), "vscroll"))
210     {
211       gdbpy_ref<> result (PyObject_CallMethod (m_window.get (), "vscroll",
212                                                "i", num_to_scroll, nullptr));
213       if (result == nullptr)
214         gdbpy_print_stack ();
215     }
216 }
217
218 void
219 tui_py_window::output (const char *text)
220 {
221   if (m_inner_window != nullptr)
222     {
223       tui_puts (text, m_inner_window.get ());
224       tui_wrefresh (m_inner_window.get ());
225     }
226 }
227
228 \f
229
230 /* A callable that is used to create a TUI window.  It wraps the
231    user-supplied window constructor.  */
232
233 class gdbpy_tui_window_maker
234 {
235 public:
236
237   explicit gdbpy_tui_window_maker (gdbpy_ref<> &&constr)
238     : m_constr (std::move (constr))
239   {
240   }
241
242   ~gdbpy_tui_window_maker ();
243
244   gdbpy_tui_window_maker (gdbpy_tui_window_maker &&other) noexcept
245     : m_constr (std::move (other.m_constr))
246   {
247   }
248
249   gdbpy_tui_window_maker (const gdbpy_tui_window_maker &other)
250   {
251     gdbpy_enter enter_py (get_current_arch (), current_language);
252     m_constr = other.m_constr;
253   }
254
255   gdbpy_tui_window_maker &operator= (gdbpy_tui_window_maker &&other)
256   {
257     m_constr = std::move (other.m_constr);
258     return *this;
259   }
260
261   gdbpy_tui_window_maker &operator= (const gdbpy_tui_window_maker &other)
262   {
263     gdbpy_enter enter_py (get_current_arch (), current_language);
264     m_constr = other.m_constr;
265     return *this;
266   }
267
268   tui_win_info *operator() (const char *name);
269
270 private:
271
272   /* A constructor that is called to make a TUI window.  */
273   gdbpy_ref<> m_constr;
274 };
275
276 gdbpy_tui_window_maker::~gdbpy_tui_window_maker ()
277 {
278   gdbpy_enter enter_py (get_current_arch (), current_language);
279   m_constr.reset (nullptr);
280 }
281
282 tui_win_info *
283 gdbpy_tui_window_maker::operator() (const char *win_name)
284 {
285   gdbpy_enter enter_py (get_current_arch (), current_language);
286
287   gdbpy_ref<gdbpy_tui_window> wrapper
288     (PyObject_New (gdbpy_tui_window, &gdbpy_tui_window_object_type));
289   if (wrapper == nullptr)
290     {
291       gdbpy_print_stack ();
292       return nullptr;
293     }
294
295   std::unique_ptr<tui_py_window> window
296     (new tui_py_window (win_name, wrapper));
297
298   gdbpy_ref<> user_window
299     (PyObject_CallFunctionObjArgs (m_constr.get (),
300                                    (PyObject *) wrapper.get (),
301                                    nullptr));
302   if (user_window == nullptr)
303     {
304       gdbpy_print_stack ();
305       return nullptr;
306     }
307
308   window->set_user_window (std::move (user_window));
309   /* Window is now owned by the TUI.  */
310   return window.release ();
311 }
312
313 /* Implement "gdb.register_window_type".  */
314
315 PyObject *
316 gdbpy_register_tui_window (PyObject *self, PyObject *args, PyObject *kw)
317 {
318   static const char *keywords[] = { "name", "constructor", nullptr };
319
320   const char *name;
321   PyObject *cons_obj;
322
323   if (!gdb_PyArg_ParseTupleAndKeywords (args, kw, "sO", keywords,
324                                         &name, &cons_obj))
325     return nullptr;
326
327   try
328     {
329       gdbpy_tui_window_maker constr (gdbpy_ref<>::new_reference (cons_obj));
330       tui_register_window (name, constr);
331     }
332   catch (const gdb_exception &except)
333     {
334       gdbpy_convert_exception (except);
335       return nullptr;
336     }
337
338   Py_RETURN_NONE;
339 }
340
341 \f
342
343 /* Require that "Window" be a valid window.  */
344
345 #define REQUIRE_WINDOW(Window)                                  \
346     do {                                                        \
347       if ((Window)->window == nullptr)                          \
348         return PyErr_Format (PyExc_RuntimeError,                \
349                              _("TUI window is invalid."));      \
350     } while (0)
351
352 /* Python function which checks the validity of a TUI window
353    object.  */
354 static PyObject *
355 gdbpy_tui_is_valid (PyObject *self, PyObject *args)
356 {
357   gdbpy_tui_window *win = (gdbpy_tui_window *) self;
358
359   if (win->window != nullptr)
360     Py_RETURN_TRUE;
361   Py_RETURN_FALSE;
362 }
363
364 /* Python function that erases the TUI window.  */
365 static PyObject *
366 gdbpy_tui_erase (PyObject *self, PyObject *args)
367 {
368   gdbpy_tui_window *win = (gdbpy_tui_window *) self;
369
370   REQUIRE_WINDOW (win);
371
372   win->window->erase ();
373
374   Py_RETURN_NONE;
375 }
376
377 /* Python function that writes some text to a TUI window.  */
378 static PyObject *
379 gdbpy_tui_write (PyObject *self, PyObject *args)
380 {
381   gdbpy_tui_window *win = (gdbpy_tui_window *) self;
382   const char *text;
383
384   if (!PyArg_ParseTuple (args, "s", &text))
385     return nullptr;
386
387   REQUIRE_WINDOW (win);
388
389   win->window->output (text);
390
391   Py_RETURN_NONE;
392 }
393
394 /* Return the width of the TUI window.  */
395 static PyObject *
396 gdbpy_tui_width (PyObject *self, void *closure)
397 {
398   gdbpy_tui_window *win = (gdbpy_tui_window *) self;
399   REQUIRE_WINDOW (win);
400   gdbpy_ref<> result
401     = gdb_py_object_from_longest (win->window->viewport_width ());
402   return result.release ();
403 }
404
405 /* Return the height of the TUI window.  */
406 static PyObject *
407 gdbpy_tui_height (PyObject *self, void *closure)
408 {
409   gdbpy_tui_window *win = (gdbpy_tui_window *) self;
410   REQUIRE_WINDOW (win);
411   gdbpy_ref<> result
412     = gdb_py_object_from_longest (win->window->viewport_height ());
413   return result.release ();
414 }
415
416 /* Return the title of the TUI window.  */
417 static PyObject *
418 gdbpy_tui_title (PyObject *self, void *closure)
419 {
420   gdbpy_tui_window *win = (gdbpy_tui_window *) self;
421   REQUIRE_WINDOW (win);
422   return host_string_to_python_string (win->window->title.c_str ()).release ();
423 }
424
425 /* Set the title of the TUI window.  */
426 static int
427 gdbpy_tui_set_title (PyObject *self, PyObject *newvalue, void *closure)
428 {
429   gdbpy_tui_window *win = (gdbpy_tui_window *) self;
430
431   if (win->window == nullptr)
432     {
433       PyErr_Format (PyExc_RuntimeError, _("TUI window is invalid."));
434       return -1;
435     }
436
437   if (win->window == nullptr)
438     {
439       PyErr_Format (PyExc_TypeError, _("Cannot delete \"title\" attribute."));
440       return -1;
441     }
442
443   gdb::unique_xmalloc_ptr<char> value
444     = python_string_to_host_string (newvalue);
445   if (value == nullptr)
446     return -1;
447
448   win->window->title = value.get ();
449   return 0;
450 }
451
452 static gdb_PyGetSetDef tui_object_getset[] =
453 {
454   { "width", gdbpy_tui_width, NULL, "Width of the window.", NULL },
455   { "height", gdbpy_tui_height, NULL, "Height of the window.", NULL },
456   { "title", gdbpy_tui_title, gdbpy_tui_set_title, "Title of the window.",
457     NULL },
458   { NULL }  /* Sentinel */
459 };
460
461 static PyMethodDef tui_object_methods[] =
462 {
463   { "is_valid", gdbpy_tui_is_valid, METH_NOARGS,
464     "is_valid () -> Boolean\n\
465 Return true if this TUI window is valid, false if not." },
466   { "erase", gdbpy_tui_erase, METH_NOARGS,
467     "Erase the TUI window." },
468   { "write", (PyCFunction) gdbpy_tui_write, METH_VARARGS,
469     "Append a string to the TUI window." },
470   { NULL } /* Sentinel.  */
471 };
472
473 PyTypeObject gdbpy_tui_window_object_type =
474 {
475   PyVarObject_HEAD_INIT (NULL, 0)
476   "gdb.TuiWindow",                /*tp_name*/
477   sizeof (gdbpy_tui_window),      /*tp_basicsize*/
478   0,                              /*tp_itemsize*/
479   0,                              /*tp_dealloc*/
480   0,                              /*tp_print*/
481   0,                              /*tp_getattr*/
482   0,                              /*tp_setattr*/
483   0,                              /*tp_compare*/
484   0,                              /*tp_repr*/
485   0,                              /*tp_as_number*/
486   0,                              /*tp_as_sequence*/
487   0,                              /*tp_as_mapping*/
488   0,                              /*tp_hash */
489   0,                              /*tp_call*/
490   0,                              /*tp_str*/
491   0,                              /*tp_getattro*/
492   0,                              /*tp_setattro */
493   0,                              /*tp_as_buffer*/
494   Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,  /*tp_flags*/
495   "GDB TUI window object",        /* tp_doc */
496   0,                              /* tp_traverse */
497   0,                              /* tp_clear */
498   0,                              /* tp_richcompare */
499   0,                              /* tp_weaklistoffset */
500   0,                              /* tp_iter */
501   0,                              /* tp_iternext */
502   tui_object_methods,             /* tp_methods */
503   0,                              /* tp_members */
504   tui_object_getset,              /* tp_getset */
505   0,                              /* tp_base */
506   0,                              /* tp_dict */
507   0,                              /* tp_descr_get */
508   0,                              /* tp_descr_set */
509   0,                              /* tp_dictoffset */
510   0,                              /* tp_init */
511   0,                              /* tp_alloc */
512 };
513
514 #endif /* TUI */
515
516 /* Initialize this module.  */
517
518 int
519 gdbpy_initialize_tui ()
520 {
521 #ifdef TUI
522   gdbpy_tui_window_object_type.tp_new = PyType_GenericNew;
523   if (PyType_Ready (&gdbpy_tui_window_object_type) < 0)
524     return -1;
525 #endif  /* TUI */
526
527   return 0;
528 }
This page took 0.054669 seconds and 4 git commands to generate.