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  // window default width is calculated as a proportion of a default
129  // char and pixel width to compensate for the fact that each widget's
130  // required size comes from a proportion of both parameters
131  int width = YGUtils::getCharsWidth (m_widget, DEFAULT_CHAR_WIDTH);
132  width += DEFAULT_PIXEL_WIDTH;
133  int height = YGUtils::getCharsHeight (m_widget, DEFAULT_CHAR_HEIGHT);
134  height += DEFAULT_PIXEL_HEIGHT;
135 
136  if (YGUI::ui()->isSwsingle())
137  height += YGUtils::getCharsHeight (m_widget, 10);
138 
139  width = MIN (width, YUI::app()->displayWidth());
140  height = MIN (height, YUI::app()->displayHeight());
141 
142  gtk_window_set_default_size (window, width, height);
143  gtk_window_resize(window, width, height);
144 
145  if (YGUI::ui()->setFullscreen())
146  gtk_window_fullscreen (window);
147  else if (YUI::app()->displayWidth() <= 800 || YUI::app()->displayHeight() <= 600)
148  // maximize window for small displays
149  gtk_window_maximize (window);
150  }
151 
152  gtk_window_set_role (window, "yast2");
153  }
154 
155  if (_main_window)
156  main_window = this;
157 
158  g_signal_connect (G_OBJECT (m_widget), "delete-event",
159  G_CALLBACK (close_window_cb), this);
160  g_signal_connect_after (G_OBJECT (m_widget), "key-press-event",
161  G_CALLBACK (key_pressed_cb), this);
162  g_signal_connect (G_OBJECT (m_widget), "focus-in-event",
163  G_CALLBACK (focus_in_event_cb), this);
164  // set busy cursor at start
165  g_signal_connect_after (G_OBJECT (m_widget), "realize",
166  G_CALLBACK (realize_cb), this);
167  }
168 
169  ~YGWindow()
170  {
171  setChild (NULL);
172  if (m_busyCursor)
173  g_object_unref (G_OBJECT (m_busyCursor));
174  gtk_widget_destroy (m_widget);
175  g_object_unref (G_OBJECT (m_widget));
176  }
177 
178  void show()
179  { gtk_widget_show (m_widget); }
180 
181  void normalCursor()
182  {
183  if (m_isBusy)
184  gdk_window_set_cursor (gtk_widget_get_window(m_widget), NULL);
185  m_isBusy = false;
186  }
187 
188  void busyCursor()
189  {
190  if (!m_busyCursor) {
191  GdkDisplay *display = gtk_widget_get_display (m_widget);
192  m_busyCursor = gdk_cursor_new_for_display (display, GDK_WATCH);
193  g_object_ref (G_OBJECT (m_busyCursor));
194  }
195  if (!m_isBusy) {
196  GdkDisplay *display = gtk_widget_get_display (m_widget);
197  gdk_window_set_cursor (gtk_widget_get_window(m_widget), m_busyCursor);
198  gdk_display_sync(display);
199  }
200  m_isBusy = true;
201  }
202 
203  void setChild (YWidget *new_child)
204  {
205  GtkWidget *child = gtk_bin_get_child (GTK_BIN (m_widget));
206  if (child)
207  gtk_container_remove (GTK_CONTAINER (m_widget), child);
208  if (new_child) {
209  child = YGWidget::get (new_child)->getLayout();
210  gtk_container_add (GTK_CONTAINER (m_widget), child);
211  }
212  m_child = new_child;
213  }
214 
215  static void ref (YGWindow *window)
216  {
217  window->m_refcount++;
218  }
219 
220  static void unref (YGWindow *window)
221  {
222  if (--window->m_refcount == 0) {
223  bool is_main_window = (window == main_window);
224  delete window;
225  if (is_main_window)
226  main_window = NULL;
227  }
228  }
229 
230  // Y(G)Widget-like methods
231  GtkWidget *getWidget() { return m_widget; }
232  YWidget *getChild() { return m_child; }
233 
234 private:
235  void close()
236  {
237  if (!m_canClose || m_canClose (m_canCloseData))
238  YGUI::ui()->sendEvent (new YCancelEvent());
239  }
240 
241  static gboolean close_window_cb (GtkWidget *widget, GdkEvent *event,
242  YGWindow *pThis)
243  {
244  // never let GTK+ destroy the window! just inform YCP, and let it
245  // do its thing.
246  pThis->close();
247  return TRUE;
248  }
249 
250  static gboolean key_pressed_cb (GtkWidget *widget, GdkEventKey *event,
251  YGWindow *pThis)
252  {
253  // if not main dialog, close it on escape
254  if (event->keyval == GDK_KEY_Escape &&
255  /* not main window */ main_window != pThis) {
256  pThis->close();
257  return TRUE;
258 
259  }
260 
261  if (event->state & GDK_SHIFT_MASK) {
262  switch (event->keyval) {
263  case GDK_KEY_F8:
264  YGUI::ui()->askSaveLogs();
265  return TRUE;
266  default:
267  break;
268  }
269  }
270  if ((event->state & GDK_CONTROL_MASK) && (event->state & GDK_SHIFT_MASK)
271  && (event->state & GDK_MOD1_MASK)) {
272  yuiMilestone() << "Caught YaST2 magic key combination\n";
273  int ret = -1;
274  switch (event->keyval) {
275  case GDK_KEY_S:
276  YGUI::ui()->makeScreenShot();
277  return TRUE;
278  case GDK_KEY_M:
279  YGUI::ui()->toggleRecordMacro();
280  return TRUE;
281  case GDK_KEY_P:
282  YGUI::ui()->askPlayMacro();
283  return TRUE;
284  case GDK_KEY_D:
285  YGUI::ui()->sendEvent (new YDebugEvent());
286  return TRUE;
287  case GDK_KEY_X:
288  yuiMilestone() << "Starting xterm\n";
289  ret = system ("/usr/bin/xterm &");
290  if (ret != 0)
291  yuiError() << "Can't launch xterm (error code" << ret << ")" << std::endl;
292  return TRUE;
293  case GDK_KEY_Y:
294  yuiMilestone() << "Opening dialog spy" << std::endl;
295  YDialogSpy::showDialogSpy();
296  YGUI::ui()->normalCursor();
297  break;
298  default:
299  break;
300  }
301  }
302  return FALSE;
303  }
304 
305  static gboolean focus_in_event_cb (GtkWidget *widget, GdkEventFocus *event)
306  { gtk_window_set_urgency_hint (GTK_WINDOW (widget), FALSE); return FALSE; }
307 
308  static void realize_cb (GtkWidget *widget, YGWindow *pThis)
309  { pThis->busyCursor(); }
310 };
311 
312 YGDialog::YGDialog (YDialogType dialogType, YDialogColorMode colorMode)
313  : YDialog (dialogType, colorMode),
314  YGWidget (this, NULL, YGTK_HBOX_NEW(0), NULL)
315 {
316  setBorder (0);
317  m_stickyTitle = false;
318  m_containee = gtk_event_box_new();
319  if (dialogType == YMainDialog && main_window)
320  m_window = main_window;
321  else
322  m_window = new YGWindow (dialogType == YMainDialog, this);
323  YGWindow::ref (m_window);
324 
325  if (colorMode != YDialogNormalColor) {
326  // emulate a warning / info dialog
327  GtkWidget *icon = gtk_image_new_from_icon_name
328  (colorMode == YDialogWarnColor ? "dialog-warning" : "dialog-information",
329  GTK_ICON_SIZE_DIALOG);
330 
331 # if GTK_CHECK_VERSION (3, 14, 0)
332  gtk_widget_set_halign (icon, GTK_ALIGN_CENTER);
333  gtk_widget_set_valign (icon, GTK_ALIGN_START);
334  gtk_widget_set_margin_start (icon, 0);
335  gtk_widget_set_margin_end (icon, 0);
336  gtk_widget_set_margin_top (icon, 12);
337  gtk_widget_set_margin_bottom (icon, 12);
338 # else
339  gtk_misc_set_alignment (GTK_MISC (icon), 0.5, 0);
340  gtk_misc_set_padding (GTK_MISC (icon), 0, 12);
341 # endif
342 
343  gtk_box_pack_start (GTK_BOX (getWidget()), icon, FALSE, FALSE, 12);
344  gtk_box_pack_start (GTK_BOX (getWidget()), m_containee, TRUE, TRUE, 0);
345  }
346  else
347  gtk_box_pack_start (GTK_BOX (getWidget()), m_containee, TRUE, TRUE, 0);
348  gtk_widget_show_all (getWidget());
349 
350  // NOTE: we need to add this containter to the window right here, else
351  // weird stuff happens (like if we set a pango font description to a
352  // GtkLabel, size request would output the size without that description
353  // set...)
354  m_window->setChild (this);
355 }
356 
357 YGDialog::~YGDialog()
358 {
359  YGWindow::unref (m_window);
360 }
361 
362 void YGDialog::setDefaultButton(YPushButton* newDefaultButton)
363 {
364  YDialog::setDefaultButton( 0 ); // prevent complaints about multiple default buttons
365  if ( newDefaultButton )
366  {
367  newDefaultButton->setKeyboardFocus();
368  YDialog::setDefaultButton(newDefaultButton);
369  }
370 }
371 
372 void YGDialog::openInternal()
373 {
374  m_window->show();
375 }
376 
377 void YGDialog::activate()
378 {
379  m_window->setChild (this);
380 }
381 
382 void YGDialog::present()
383 {
384  GtkWindow *window = GTK_WINDOW (m_window->getWidget());
385  if (!gtk_window_is_active (window))
386  gtk_window_set_urgency_hint (window, TRUE);
387 }
388 
389 YGDialog *YGDialog::currentDialog()
390 {
391  YDialog *ydialog = YDialog::currentDialog (false);
392  if (ydialog)
393  return static_cast <YGDialog *> (ydialog);
394  return NULL;
395 }
396 
397 GtkWindow *YGDialog::currentWindow()
398 {
399  YGDialog *ydialog = YGDialog::currentDialog();
400  if (ydialog)
401  return GTK_WINDOW (ydialog->m_window->getWidget());
402  return NULL;
403 }
404 
405 void YGDialog::setCloseCallback (YGWindowCloseFn canClose, void *canCloseData)
406 {
407  m_window->m_canClose = canClose;
408  m_window->m_canCloseData = canCloseData;
409 }
410 
411 void YGDialog::unsetCloseCallback()
412 {
413  m_window->m_canClose = NULL;
414 }
415 
416 void YGDialog::normalCursor()
417 {
418  m_window->normalCursor();
419 }
420 
421 void YGDialog::busyCursor()
422 {
423  m_window->busyCursor();
424 }
425 
426 // YWidget
427 
428 void YGDialog::doSetSize (int width, int height)
429 {
430  //yuiDebug() << "layout pass " << layoutPass() << " (" << width << "x" << height << ")" << endl;
431  // libyui calls YDialog::setSize() to force a geometry recalculation as a
432  // result of changed layout properties
433  bool resize = false;
434  GtkWidget *window = m_window->getWidget();
435 
436  gint w,h;
437  gtk_window_get_size(GTK_WINDOW (window), &w, &h);
438 
439  if (w < width || h < height) {
440  resize = true;
441  width = MAX (width, w),
442  height = MAX (height, h);
443  }
444 
445  if (gtk_widget_get_realized (window)) {
446  gtk_widget_queue_resize (window);
447  width = MIN (width, YUI::app()->displayWidth());
448  height = MIN (height, YUI::app()->displayHeight());
449  if (isMainDialog()) {
450  GtkAllocation allocation;
451  gtk_widget_get_allocation(window, &allocation);
452  if (allocation.width < width || allocation.height < height) {
453  resize = true;
454  width = MAX (width, allocation.width),
455  height = MAX (height, allocation.height);
456  }
457  }
458  else
459  resize = true;
460  }
461  int lpass = layoutPass();
462  if ( lpass == 0 )
463  {
464  if (resize)
465  gtk_window_resize (GTK_WINDOW (window), width, height);
466  else
467  gtk_window_set_default_size (GTK_WINDOW (window), width, height);
468  }
469 }
470 
471 void YGDialog::highlight (YWidget *ywidget)
472 {
473  struct inner {
474  static gboolean draw_highlight_cb (GtkWidget *widget, cairo_t *cr)
475  {
476  int w = gtk_widget_get_allocated_width(widget);
477  int h = gtk_widget_get_allocated_height(widget);
478 
479  cairo_rectangle (cr, 0, 0, w, h);
480  cairo_set_source_rgb (cr, 0xff/255.0, 0x88/255.0, 0);
481  cairo_fill (cr);
482  return FALSE;
483  }
484 
485  static bool hasWindow (GtkWidget *widget)
486  {
487  if (gtk_widget_get_has_window(widget))
488  return true;
489  // widgets like GtkButton add their windows to parent's
490  for (GList *children = gdk_window_peek_children (gtk_widget_get_window(widget));
491  children; children = children->next) {
492  GdkWindow *child = (GdkWindow *) children->data;
493  gpointer data;
494  gdk_window_get_user_data (child, &data);
495  if ((GtkWidget *) data == widget)
496  return true;
497  }
498  return false;
499  }
500 
501  };
502  static YWidget *previousWidget = NULL;
503  if (previousWidget && previousWidget->isValid()) {
504  YGWidget *prev = YGWidget::get (previousWidget);
505  if (prev) {
506  GtkWidget *widget = prev->getWidget();
507  if (inner::hasWindow (widget)) {
508  gtk_widget_override_background_color (widget, GTK_STATE_FLAG_NORMAL, NULL);
509  gtk_widget_override_color (widget, GTK_STATE_FLAG_NORMAL, NULL);
510  }
511  else {
512  g_signal_handlers_disconnect_by_func (widget,
513  (gpointer) inner::draw_highlight_cb, NULL);
514  gtk_widget_queue_draw (widget);
515  }
516  }
517  }
518  if (ywidget) {
519  YGWidget *ygwidget = YGWidget::get (ywidget);
520  if (ygwidget) {
521  GtkWidget *widget = ygwidget->getWidget();
522  if (inner::hasWindow (widget)) {
523  GdkRGBA bg_color = { 0, 0xffff, 0xaaaa, 0 };
524  GdkRGBA base_color = { 0, 0xffff, 0xeeee, 0 };
525  gtk_widget_override_background_color (widget, GTK_STATE_FLAG_NORMAL, &bg_color);
526  gtk_widget_override_color (widget, GTK_STATE_FLAG_NORMAL, &base_color);
527  }
528  else {
529  g_signal_connect (G_OBJECT (widget), "draw",
530  G_CALLBACK (inner::draw_highlight_cb), NULL);
531  gtk_widget_queue_draw (widget);
532  }
533  }
534  }
535  previousWidget = ywidget;
536 }
537 
538 void YGDialog::setTitle (const std::string &title, bool sticky)
539 {
540  if (title.empty())
541  return;
542  if (!m_stickyTitle || sticky) {
543  GtkWindow *window = GTK_WINDOW (m_window->getWidget());
544  gchar *str = g_strdup_printf ("%s - YaST", title.c_str());
545  gtk_window_set_title (window, str);
546  g_free (str);
547  m_stickyTitle = sticky;
548  }
549  present();
550 }
551 
552 extern "C" {
553  void ygdialog_setTitle (const gchar *title, gboolean sticky);
554 };
555 
556 void ygdialog_setTitle (const gchar *title, gboolean sticky)
557 {
558  YGDialog::currentDialog()->setTitle (title, sticky);
559 }
560 
561 void YGDialog::setIcon (const std::string &icon)
562 {
563  GtkWindow *window = GTK_WINDOW (m_window->getWidget());
564  GdkPixbuf *pixbuf = YGUtils::loadPixbuf (icon);
565  if (pixbuf) {
566  gtk_window_set_icon (window, pixbuf);
567  g_object_unref (G_OBJECT (pixbuf));
568  }
569 }
570 
571 typedef bool (*FindWidgetsCb) (YWidget *widget, void *data) ;
572 
573 static void findWidgets (
574  std::list <YWidget *> *widgets, YWidget *widget, FindWidgetsCb find_cb, void *cb_data)
575 {
576  if (find_cb (widget, cb_data))
577  widgets->push_back (widget);
578  for (YWidgetListConstIterator it = widget->childrenBegin();
579  it != widget->childrenEnd(); it++)
580  findWidgets (widgets, *it, find_cb, cb_data);
581 }
582 
583 static bool IsFunctionWidget (YWidget *widget, void *data)
584 { return widget->functionKey() == GPOINTER_TO_INT (data); }
585 
586 YWidget *YGDialog::getFunctionWidget (int key)
587 {
588  std::list <YWidget *> widgets;
589  findWidgets (&widgets, this, IsFunctionWidget, GINT_TO_POINTER (key));
590  return widgets.empty() ? NULL : widgets.front();
591 }
592 
593 static bool IsClassWidget (YWidget *widget, void *data)
594 { return !strcmp (widget->widgetClass(), (char *) data); }
595 
596 std::list <YWidget *> YGDialog::getClassWidgets (const char *className)
597 {
598  std::list <YWidget *> widgets;
599  findWidgets (&widgets, this, IsClassWidget, (void *) className);
600  return widgets;
601 }
602 
603 YDialog *YGWidgetFactory::createDialog (YDialogType dialogType, YDialogColorMode colorMode)
604 { return new YGDialog (dialogType, colorMode); }
605 
606 YEvent *YGDialog::waitForEventInternal (int timeout_millisec)
607 { return YGUI::ui()->waitInput (timeout_millisec, true); }
608 
609 YEvent *YGDialog::pollEventInternal()
610 { return YGUI::ui()->waitInput (0, false); }
611 
YGWindow
Definition: YGDialog.cc:39
YGDialog
Definition: YGDialog.h:15
YGWidget
Definition: YGWidget.h:14
YGDialog::setDefaultButton
void setDefaultButton(YPushButton *newDefaultButton)
Set the dialog's default button.
Definition: YGDialog.cc:362