libyui-gtk  2.49.0
YGDialog.cc
1 /********************************************************************
2  * YaST2-GTK - http://en.opensuse.org/YaST2-GTK *
3  ********************************************************************/
4 
5 #define YUILogComponent "gtk"
6 #include <yui/Libyui_config.h>
7 #include "YGUI.h"
8 #include "YGDialog.h"
9 #include "YGUtils.h"
10 #include <YDialogSpy.h>
11 #include <YPushButton.h>
12 #include <gdk/gdkkeysyms.h>
13 #include <math.h> // easter
14 #include <string.h>
15 #include "ygtkwindow.h"
16 #include "YGMacros.h"
17 
18 
19 /* In the main dialog case, it doesn't necessarly have a window of its own. If
20  there is already a main window, it should replace its content -- and when closed,
21  the previous dialog restored.
22 
23  Therefore, we have a YGDialog (the YDialog implementation), and a YGWindow
24  that does the windowing work and has a YWidget has its children, which can
25  be a YGDialog and is swap-able.
26 */
27 
28 //#define DEFAULT_WIDTH 750
29 //#define DEFAULT_HEIGHT 650
30 #define DEFAULT_CHAR_WIDTH 60
31 #define DEFAULT_CHAR_HEIGHT 28
32 #define DEFAULT_PIXEL_WIDTH 330
33 #define DEFAULT_PIXEL_HEIGHT 200
34 
35 class YGWindow;
36 static YGWindow *main_window = 0;
37 
38 class YGWindow
39 {
40  GtkWidget *m_widget;
41  int m_refcount;
42  // we keep a pointer of the child just for debugging
43  // (ie. dump yast tree)
44  YWidget *m_child;
45  GdkCursor *m_busyCursor;
46  bool m_isBusy;
47 
48 public:
49  YGWindowCloseFn m_canClose;
50  void *m_canCloseData;
51 
52  YGWindow (bool _main_window, YGDialog *ydialog)
53  {
54  m_widget = ygtk_window_new();
55 
56 # if GTK_CHECK_VERSION (3, 12, 0)
57 # else
58  gtk_container_set_resize_mode (GTK_CONTAINER (m_widget), GTK_RESIZE_PARENT);
59 # endif
60 
61  g_object_ref_sink (G_OBJECT (m_widget));
62 
63 # if GTK_CHECK_VERSION (3, 14, 0)
64 # else
65  gtk_window_set_has_resize_grip (GTK_WINDOW (m_widget), TRUE);
66 # endif
67 
68  m_refcount = 0;
69  m_child = NULL;
70  m_canClose = NULL;
71  m_busyCursor = NULL;
72  m_isBusy = false;
73 
74  {
75  std::stack<YDialog *> &stack = YDialog::_dialogStack;
76  YDialog *ylast = stack.size() ? stack.top() : 0;
77  if (ylast == ydialog) {
78  if (stack.size() > 1) {
79  YDialog *t = ylast;
80  stack.pop();
81  ylast = stack.top();
82  stack.push (t);
83  }
84  else
85  ylast = NULL;
86  }
87 
88  GtkWindow *parent = NULL;
89  if (ylast) {
90  YGDialog *yglast = static_cast <YGDialog *> (ylast);
91  parent = GTK_WINDOW (yglast->m_window->getWidget());
92  }
93  GtkWindow *window = GTK_WINDOW (m_widget);
94  // to be back compatible
95  std::string dialogTitle = "YaSt";
96 
97 #ifdef LIBYUI_VERSION_NUM
98  #if LIBYUI_VERSION_AT_LEAST(2,42,3)
99  dialogTitle = YUI::app()->applicationTitle();
100  #endif
101 #endif
102  if (parent) {
103  // if there is a parent, this would be a dialog
104  gtk_window_set_title (window, dialogTitle.c_str());
105  gtk_window_set_modal (window, TRUE);
106  gtk_window_set_transient_for (window, parent);
107  gtk_window_set_type_hint (window, GDK_WINDOW_TYPE_HINT_DIALOG);
108  AtkObject *peer = gtk_widget_get_accessible (GTK_WIDGET (window));
109  if (peer != NULL)
110  atk_object_set_role (peer, ATK_ROLE_DIALOG);
111  }
112  else {
113  gtk_window_set_title (window, dialogTitle.c_str());
114 #ifdef LIBYUI_VERSION_NUM
115  #if LIBYUI_VERSION_AT_LEAST(2,42,3)
116  GdkPixbuf *pixbuf = YGUtils::loadPixbuf (YUI::app()->applicationIcon());
117  if (pixbuf) { // default window icon
118  gtk_window_set_default_icon (pixbuf);
119  g_object_unref (G_OBJECT (pixbuf));
120  }
121  #endif
122 #endif
123  if (YGUI::ui()->unsetBorder())
124  gtk_window_set_decorated (window, FALSE);
125  }
126 
127  if (_main_window) {
128  int width = YUI::app()->defaultWidth();
129  int height = YUI::app()->defaultHeight();
130  gtk_window_set_default_size ( window, width, height );
131  gtk_window_resize( window, width, height );
132  if (YGUI::ui()->setFullscreen())
133  gtk_window_fullscreen (window);
134  }
135  gtk_window_set_role (window, "yast2");
136  }
137 
138  if (_main_window)
139  main_window = this;
140 
141  g_signal_connect (G_OBJECT (m_widget), "delete-event",
142  G_CALLBACK (close_window_cb), this);
143  g_signal_connect_after (G_OBJECT (m_widget), "key-press-event",
144  G_CALLBACK (key_pressed_cb), this);
145  g_signal_connect (G_OBJECT (m_widget), "focus-in-event",
146  G_CALLBACK (focus_in_event_cb), this);
147  // set busy cursor at start
148  g_signal_connect_after (G_OBJECT (m_widget), "realize",
149  G_CALLBACK (realize_cb), this);
150  }
151 
152  ~YGWindow()
153  {
154  setChild (NULL);
155  if (m_busyCursor)
156  g_object_unref (G_OBJECT (m_busyCursor));
157  gtk_widget_destroy (m_widget);
158  g_object_unref (G_OBJECT (m_widget));
159  }
160 
161  void show()
162  { gtk_widget_show (m_widget); }
163 
164  void normalCursor()
165  {
166  if (m_isBusy)
167  gdk_window_set_cursor (gtk_widget_get_window(m_widget), NULL);
168  m_isBusy = false;
169  }
170 
171  void busyCursor()
172  {
173  if (!m_busyCursor) {
174  GdkDisplay *display = gtk_widget_get_display (m_widget);
175  m_busyCursor = gdk_cursor_new_for_display (display, GDK_WATCH);
176  g_object_ref (G_OBJECT (m_busyCursor));
177  }
178  if (!m_isBusy) {
179  GdkDisplay *display = gtk_widget_get_display (m_widget);
180  gdk_window_set_cursor (gtk_widget_get_window(m_widget), m_busyCursor);
181  gdk_display_sync(display);
182  }
183  m_isBusy = true;
184  }
185 
186  void setChild (YWidget *new_child)
187  {
188  GtkWidget *child = gtk_bin_get_child (GTK_BIN (m_widget));
189  if (child)
190  gtk_container_remove (GTK_CONTAINER (m_widget), child);
191  if (new_child) {
192  child = YGWidget::get (new_child)->getLayout();
193  gtk_container_add (GTK_CONTAINER (m_widget), child);
194  }
195  m_child = new_child;
196  }
197 
198  static void ref (YGWindow *window)
199  {
200  window->m_refcount++;
201  }
202 
203  static void unref (YGWindow *window)
204  {
205  if (--window->m_refcount == 0) {
206  bool is_main_window = (window == main_window);
207  delete window;
208  if (is_main_window)
209  main_window = NULL;
210  }
211  }
212 
213  // Y(G)Widget-like methods
214  GtkWidget *getWidget() { return m_widget; }
215  YWidget *getChild() { return m_child; }
216 
217 private:
218  void close()
219  {
220  if (!m_canClose || m_canClose (m_canCloseData))
221  YGUI::ui()->sendEvent (new YCancelEvent());
222  }
223 
224  static gboolean close_window_cb (GtkWidget *widget, GdkEvent *event,
225  YGWindow *pThis)
226  {
227  // never let GTK+ destroy the window! just inform YCP, and let it
228  // do its thing.
229  pThis->close();
230  return TRUE;
231  }
232 
233  static gboolean key_pressed_cb (GtkWidget *widget, GdkEventKey *event,
234  YGWindow *pThis)
235  {
236  // if not main dialog, close it on escape
237  if (event->keyval == GDK_KEY_Escape &&
238  /* not main window */ main_window != pThis) {
239  pThis->close();
240  return TRUE;
241 
242  }
243 
244  if (event->state & GDK_SHIFT_MASK) {
245  switch (event->keyval) {
246  case GDK_KEY_F8:
247  YGUI::ui()->askSaveLogs();
248  return TRUE;
249  default:
250  break;
251  }
252  }
253  if ((event->state & GDK_CONTROL_MASK) && (event->state & GDK_SHIFT_MASK)
254  && (event->state & GDK_MOD1_MASK)) {
255  yuiMilestone() << "Caught YaST2 magic key combination\n";
256  int ret = -1;
257  switch (event->keyval) {
258  case GDK_KEY_S:
259  YGUI::ui()->makeScreenShot();
260  return TRUE;
261  case GDK_KEY_M:
262  YGUI::ui()->toggleRecordMacro();
263  return TRUE;
264  case GDK_KEY_P:
265  YGUI::ui()->askPlayMacro();
266  return TRUE;
267  case GDK_KEY_D:
268  YGUI::ui()->sendEvent (new YDebugEvent());
269  return TRUE;
270  case GDK_KEY_X:
271  yuiMilestone() << "Starting xterm\n";
272  ret = system ("/usr/bin/xterm &");
273  if (ret != 0)
274  yuiError() << "Can't launch xterm (error code" << ret << ")" << std::endl;
275  return TRUE;
276  case GDK_KEY_Y:
277  yuiMilestone() << "Opening dialog spy" << std::endl;
278  YDialogSpy::showDialogSpy();
279  YGUI::ui()->normalCursor();
280  break;
281  default:
282  break;
283  }
284  }
285  return FALSE;
286  }
287 
288  static gboolean focus_in_event_cb (GtkWidget *widget, GdkEventFocus *event)
289  { gtk_window_set_urgency_hint (GTK_WINDOW (widget), FALSE); return FALSE; }
290 
291  static void realize_cb (GtkWidget *widget, YGWindow *pThis)
292  { pThis->busyCursor(); }
293 };
294 
295 YGDialog::YGDialog (YDialogType dialogType, YDialogColorMode colorMode)
296  : YDialog (dialogType, colorMode),
297  YGWidget (this, NULL, YGTK_HBOX_NEW(0), NULL)
298 {
299  setBorder (0);
300  m_stickyTitle = false;
301  m_containee = gtk_event_box_new();
302  if (dialogType == YMainDialog && main_window)
303  m_window = main_window;
304  else
305  m_window = new YGWindow (dialogType == YMainDialog, this);
306  YGWindow::ref (m_window);
307 
308  if (colorMode != YDialogNormalColor) {
309  // emulate a warning / info dialog
310  GtkWidget *icon = gtk_image_new_from_icon_name
311  (colorMode == YDialogWarnColor ? "dialog-warning" : "dialog-information",
312  GTK_ICON_SIZE_DIALOG);
313 
314 # if GTK_CHECK_VERSION (3, 14, 0)
315  gtk_widget_set_halign (icon, GTK_ALIGN_CENTER);
316  gtk_widget_set_valign (icon, GTK_ALIGN_START);
317  gtk_widget_set_margin_start (icon, 0);
318  gtk_widget_set_margin_end (icon, 0);
319  gtk_widget_set_margin_top (icon, 12);
320  gtk_widget_set_margin_bottom (icon, 12);
321 # else
322  gtk_misc_set_alignment (GTK_MISC (icon), 0.5, 0);
323  gtk_misc_set_padding (GTK_MISC (icon), 0, 12);
324 # endif
325 
326  gtk_box_pack_start (GTK_BOX (getWidget()), icon, FALSE, FALSE, 12);
327  gtk_box_pack_start (GTK_BOX (getWidget()), m_containee, TRUE, TRUE, 0);
328  }
329  else
330  gtk_box_pack_start (GTK_BOX (getWidget()), m_containee, TRUE, TRUE, 0);
331  gtk_widget_show_all (getWidget());
332 
333  // NOTE: we need to add this containter to the window right here, else
334  // weird stuff happens (like if we set a pango font description to a
335  // GtkLabel, size request would output the size without that description
336  // set...)
337  m_window->setChild (this);
338 }
339 
340 YGDialog::~YGDialog()
341 {
342  YGWindow::unref (m_window);
343 }
344 
345 void YGDialog::setDefaultButton(YPushButton* newDefaultButton)
346 {
347  YDialog::setDefaultButton( 0 ); // prevent complaints about multiple default buttons
348  if ( newDefaultButton )
349  {
350  newDefaultButton->setKeyboardFocus();
351  YDialog::setDefaultButton(newDefaultButton);
352  }
353 }
354 
355 void YGDialog::openInternal()
356 {
357  m_window->show();
358 }
359 
360 void YGDialog::activate()
361 {
362  m_window->setChild (this);
363 }
364 
365 void YGDialog::present()
366 {
367  GtkWindow *window = GTK_WINDOW (m_window->getWidget());
368  if (!gtk_window_is_active (window))
369  gtk_window_set_urgency_hint (window, TRUE);
370 }
371 
372 YGDialog *YGDialog::currentDialog()
373 {
374  YDialog *ydialog = YDialog::currentDialog (false);
375  if (ydialog)
376  return static_cast <YGDialog *> (ydialog);
377  return NULL;
378 }
379 
380 GtkWindow *YGDialog::currentWindow()
381 {
382  YGDialog *ydialog = YGDialog::currentDialog();
383  if (ydialog)
384  return GTK_WINDOW (ydialog->m_window->getWidget());
385  return NULL;
386 }
387 
388 void YGDialog::setCloseCallback (YGWindowCloseFn canClose, void *canCloseData)
389 {
390  m_window->m_canClose = canClose;
391  m_window->m_canCloseData = canCloseData;
392 }
393 
394 void YGDialog::unsetCloseCallback()
395 {
396  m_window->m_canClose = NULL;
397 }
398 
399 void YGDialog::normalCursor()
400 {
401  m_window->normalCursor();
402 }
403 
404 void YGDialog::busyCursor()
405 {
406  m_window->busyCursor();
407 }
408 
409 // YWidget
410 
411 void YGDialog::doSetSize (int width, int height)
412 {
413  //yuiDebug() << "layout pass " << layoutPass() << " (" << width << "x" << height << ")" << endl;
414  // libyui calls YDialog::setSize() to force a geometry recalculation as a
415  // result of changed layout properties
416  bool resize = false;
417  GtkWidget *window = m_window->getWidget();
418 
419  gint w,h;
420  gtk_window_get_size(GTK_WINDOW (window), &w, &h);
421 
422  if (w < width || h < height) {
423  resize = true;
424  width = MAX (width, w),
425  height = MAX (height, h);
426  }
427 
428  if (gtk_widget_get_realized (window)) {
429  gtk_widget_queue_resize (window);
430  width = MIN (width, YUI::app()->displayWidth());
431  height = MIN (height, YUI::app()->displayHeight());
432  if (isMainDialog()) {
433  GtkAllocation allocation;
434  gtk_widget_get_allocation(window, &allocation);
435  if (allocation.width < width || allocation.height < height) {
436  resize = true;
437  width = MAX (width, allocation.width),
438  height = MAX (height, allocation.height);
439  }
440  }
441  else
442  resize = true;
443  }
444  int lpass = layoutPass();
445  if ( lpass == 0 )
446  {
447  if (resize)
448  gtk_window_resize (GTK_WINDOW (window), width, height);
449  else
450  gtk_window_set_default_size (GTK_WINDOW (window), width, height);
451  }
452 }
453 
454 void YGDialog::highlight (YWidget *ywidget)
455 {
456  struct inner {
457  static gboolean draw_highlight_cb (GtkWidget *widget, cairo_t *cr)
458  {
459  int w = gtk_widget_get_allocated_width(widget);
460  int h = gtk_widget_get_allocated_height(widget);
461 
462  cairo_rectangle (cr, 0, 0, w, h);
463  cairo_set_source_rgb (cr, 0xff/255.0, 0x88/255.0, 0);
464  cairo_fill (cr);
465  return FALSE;
466  }
467 
468  static bool hasWindow (GtkWidget *widget)
469  {
470  if (gtk_widget_get_has_window(widget))
471  return true;
472  // widgets like GtkButton add their windows to parent's
473  for (GList *children = gdk_window_peek_children (gtk_widget_get_window(widget));
474  children; children = children->next) {
475  GdkWindow *child = (GdkWindow *) children->data;
476  gpointer data;
477  gdk_window_get_user_data (child, &data);
478  if ((GtkWidget *) data == widget)
479  return true;
480  }
481  return false;
482  }
483 
484  };
485  static YWidget *previousWidget = NULL;
486  if (previousWidget && previousWidget->isValid()) {
487  YGWidget *prev = YGWidget::get (previousWidget);
488  if (prev) {
489  GtkWidget *widget = prev->getWidget();
490  if (inner::hasWindow (widget)) {
491  gtk_widget_override_background_color (widget, GTK_STATE_FLAG_NORMAL, NULL);
492  gtk_widget_override_color (widget, GTK_STATE_FLAG_NORMAL, NULL);
493  }
494  else {
495  g_signal_handlers_disconnect_by_func (widget,
496  (gpointer) inner::draw_highlight_cb, NULL);
497  gtk_widget_queue_draw (widget);
498  }
499  }
500  }
501  if (ywidget) {
502  YGWidget *ygwidget = YGWidget::get (ywidget);
503  if (ygwidget) {
504  GtkWidget *widget = ygwidget->getWidget();
505  if (inner::hasWindow (widget)) {
506  GdkRGBA bg_color = { 0, 0xffff, 0xaaaa, 0 };
507  GdkRGBA base_color = { 0, 0xffff, 0xeeee, 0 };
508  gtk_widget_override_background_color (widget, GTK_STATE_FLAG_NORMAL, &bg_color);
509  gtk_widget_override_color (widget, GTK_STATE_FLAG_NORMAL, &base_color);
510  }
511  else {
512  g_signal_connect (G_OBJECT (widget), "draw",
513  G_CALLBACK (inner::draw_highlight_cb), NULL);
514  gtk_widget_queue_draw (widget);
515  }
516  }
517  }
518  previousWidget = ywidget;
519 }
520 
521 void YGDialog::setTitle (const std::string &title, bool sticky)
522 {
523  if (title.empty())
524  return;
525  if (!m_stickyTitle || sticky) {
526  GtkWindow *window = GTK_WINDOW (m_window->getWidget());
527  gchar *str = g_strdup_printf ("%s - YaST", title.c_str());
528  gtk_window_set_title (window, str);
529  g_free (str);
530  m_stickyTitle = sticky;
531  }
532  present();
533 }
534 
535 extern "C" {
536  void ygdialog_setTitle (const gchar *title, gboolean sticky);
537 };
538 
539 void ygdialog_setTitle (const gchar *title, gboolean sticky)
540 {
541  YGDialog::currentDialog()->setTitle (title, sticky);
542 }
543 
544 void YGDialog::setIcon (const std::string &icon)
545 {
546  GtkWindow *window = GTK_WINDOW (m_window->getWidget());
547  GdkPixbuf *pixbuf = YGUtils::loadPixbuf (icon);
548  if (pixbuf) {
549  gtk_window_set_icon (window, pixbuf);
550  g_object_unref (G_OBJECT (pixbuf));
551  }
552 }
553 
554 typedef bool (*FindWidgetsCb) (YWidget *widget, void *data) ;
555 
556 static void findWidgets (
557  std::list <YWidget *> *widgets, YWidget *widget, FindWidgetsCb find_cb, void *cb_data)
558 {
559  if (find_cb (widget, cb_data))
560  widgets->push_back (widget);
561  for (YWidgetListConstIterator it = widget->childrenBegin();
562  it != widget->childrenEnd(); it++)
563  findWidgets (widgets, *it, find_cb, cb_data);
564 }
565 
566 static bool IsFunctionWidget (YWidget *widget, void *data)
567 { return widget->functionKey() == GPOINTER_TO_INT (data); }
568 
569 YWidget *YGDialog::getFunctionWidget (int key)
570 {
571  std::list <YWidget *> widgets;
572  findWidgets (&widgets, this, IsFunctionWidget, GINT_TO_POINTER (key));
573  return widgets.empty() ? NULL : widgets.front();
574 }
575 
576 static bool IsClassWidget (YWidget *widget, void *data)
577 { return !strcmp (widget->widgetClass(), (char *) data); }
578 
579 std::list <YWidget *> YGDialog::getClassWidgets (const char *className)
580 {
581  std::list <YWidget *> widgets;
582  findWidgets (&widgets, this, IsClassWidget, (void *) className);
583  return widgets;
584 }
585 
586 YDialog *YGWidgetFactory::createDialog (YDialogType dialogType, YDialogColorMode colorMode)
587 { return new YGDialog (dialogType, colorMode); }
588 
589 YEvent *YGDialog::waitForEventInternal (int timeout_millisec)
590 { return YGUI::ui()->waitInput (timeout_millisec, true); }
591 
592 YEvent *YGDialog::pollEventInternal()
593 { return YGUI::ui()->waitInput (0, false); }
594 
void setDefaultButton(YPushButton *newDefaultButton)
Set the dialog's default button.
Definition: YGDialog.cc:345