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