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