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