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