libyui-gtk  2.44.9
YGUI.cc
1 /********************************************************************
2  * YaST2-GTK - http://en.opensuse.org/YaST2-GTK *
3  ********************************************************************/
4 
5 /*
6  Textdomain "gtk"
7  */
8 
9 #include <string.h>
10 #include <stdio.h>
11 #include <sys/stat.h>
12 #include <sys/types.h>
13 #include <YEvent.h>
14 #include <YMacro.h>
15 #include <YCommandLine.h>
16 #include "YGUI.h"
17 #include "YGi18n.h"
18 #include "YGUtils.h"
19 #include "YGDialog.h"
20 #include <glib.h>
21 
22 static std::string askForFileOrDirectory (GtkFileChooserAction action,
23  const std::string &path, const std::string &filter, const std::string &title);
24 
25 static void errorMsg (const char *msg)
26 {
27  GtkWidget* dialog = gtk_message_dialog_new (NULL,
28  GtkDialogFlags (0), GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, "%s", _("Error"));
29  gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog), "%s", msg);
30  gtk_dialog_run (GTK_DIALOG (dialog));
31  gtk_widget_destroy (dialog);
32 }
33 
34 #define DEFAULT_MACRO_FILE_NAME "macro.ycp"
35 #define BUSY_CURSOR_TIMEOUT 250
36 
37 YUI *createUI( bool withThreads )
38 {
39  static YGUI *ui = 0;
40  if (!ui)
41  ui = new YGUI (withThreads);
42  return ui;
43 }
44 
45 YGUI::YGUI (bool with_threads)
46  : YUI (with_threads), m_done_init (false), busy_timeout (0)
47 {
48  yuiMilestone() << "This is libyui-gtk " << VERSION << std::endl;
49 
50  m_no_border = m_fullscreen = m_swsingle = false;
51 
52  YGUI::setTextdomain( TEXTDOMAIN );
53 
54  // If we're running without threads, initialize Gtk stuff
55  // This enables standalone libyui use Gtk interface
56  if (!with_threads)
57  checkInit();
58 
59  // without this none of the (default) threading action works ...
60  topmostConstructorHasFinished();
61 }
62 
63 void YGUI::setTextdomain( const char * domain )
64 {
65  bindtextdomain( domain, YSettings::localeDir().c_str() );
66  bind_textdomain_codeset( domain, "utf8" );
67  textdomain( domain );
68 
69  // Make change known.
70  {
71  extern int _nl_msg_cat_cntr;
72  ++_nl_msg_cat_cntr;
73  }
74 }
75 
76 static void print_log (const gchar *domain, GLogLevelFlags level, const gchar *message, void *pData)
77 {
78  YUILogLevel_t ylevel = YUI_LOG_MILESTONE;
79  switch (level) {
80  case G_LOG_LEVEL_ERROR:
81  case G_LOG_LEVEL_CRITICAL:
82  ylevel = YUI_LOG_ERROR;
83  break;
84  case G_LOG_LEVEL_WARNING:
85  ylevel = YUI_LOG_WARNING;
86  break;
87  case G_LOG_LEVEL_DEBUG:
88  ylevel = YUI_LOG_DEBUG;
89  break;
90  case G_LOG_LEVEL_MESSAGE:
91  case G_LOG_LEVEL_INFO:
92  default:
93  break;
94  }
95  // YUILog.cc assumes 'logComponent' (etc.) are static strings, that it
96  // can just keep lying around for ever, and use later, so we have to
97  // intern the domain - that can be allocated (or belong to a transient
98  // plugin's address space).
99  const char *component = domain ? g_intern_string (domain) : "libyui-gtk";
100  YUILog::instance()->log (ylevel, component, "libyui-gtk", 0, "") << message << std::endl;
101 #if 0 // uncomment to put a stop to gdb
102  static int bugStop = 0;
103  if (bugStop-- <= 0)
104  abort();
105 #endif
106 }
107 
108 void YGUI::checkInit()
109 {
110  if (m_done_init)
111  return;
112  m_done_init = true;
113 
114  // retrieve command line args from /proc/<pid>/cmdline
115  YCommandLine cmdLine;
116  int argc = cmdLine.argc();
117  char **argv = cmdLine.argv();
118  for (int i = 1; i < argc; i++) {
119  const char *argp = argv[i];
120  if (argp[0] != '-') {
121  if (!strcmp (argp, "sw_single") || !strcmp (argp, "online_update"))
122  m_swsingle = true;
123  continue;
124  }
125  argp++;
126  if (argp[0] == '-') argp++;
127 
128  if (!strcmp (argp, "fullscreen"))
129  m_fullscreen = true;
130  else if (!strcmp (argp, "noborder"))
131  m_no_border = true;
132  else if (!strcmp (argp, "help")) {
133  printf ("%s",
134  _("Command line options for the YaST2 UI (GTK plugin):\n\n"
135  "--noborder no window manager border for main dialogs\n"
136  "--fullscreen use full screen for main dialogs\n"
137  "--nothreads run without additional UI threads\n"
138  "--help prints this help text\n"
139  "\n"
140  ));
141  exit (0);
142  }
143  }
144 
145  gtk_init (&argc, &argv);
146 
147  g_log_set_default_handler (print_log, NULL); // send gtk logs to libyui system
148 #if 0 // to crash right away in order to get a stack trace
149  g_log_set_always_fatal (GLogLevelFlags (G_LOG_LEVEL_ERROR|G_LOG_LEVEL_CRITICAL|
150  G_LOG_LEVEL_WARNING| G_LOG_LEVEL_MESSAGE|G_LOG_LEVEL_INFO|G_LOG_LEVEL_DEBUG));
151 #endif
152  std::string themeSubDir = YSettings::themeDir();
153 
154  char* st = getenv("Y2STYLE");
155  std::string style = st ? st : "";
156 
157  if (style.size())
158  {
159  style = themeSubDir + style;
160  }
161  else
162  {
163  style = themeSubDir + "style.css";
164  }
165 
166  yuiMilestone() << "Style \"" << style << "\"\n";
167 
168  GtkCssProvider *provider = gtk_css_provider_new();
169  GFile * file = g_file_new_for_path(style.c_str());
170  if (g_file_query_exists(file, NULL))
171  {
172  GError *error = NULL;
173  if (!gtk_css_provider_load_from_file (provider, file, &error))
174  {
175  g_printerr ("%s\n", error->message);
176  }
177  else
178  {
179  GdkDisplay *display = gdk_display_get_default ();
180  GdkScreen *screen = gdk_display_get_default_screen (display);
181 
182  gtk_style_context_add_provider_for_screen (screen,
183  GTK_STYLE_PROVIDER (provider),
184  GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
185  }
186  }
187  else
188  yuiMilestone() << "Style \"" << style << "\" not found. Ignoring style\n";
189 
190  g_object_unref (provider);
191 
192  GdkPixbuf *pixbuf = YGUtils::loadPixbuf (THEMEDIR "/icons/32x32/apps/yast.png");
193  if (pixbuf) { // default window icon
194  gtk_window_set_default_icon (pixbuf);
195  g_object_unref (G_OBJECT (pixbuf));
196  }
197 }
198 
199 static gboolean ycp_wakeup_fn (GIOChannel *source, GIOCondition condition,
200  gpointer data)
201 {
202  *(int *)data = TRUE;
203  return TRUE;
204 }
205 
206 void YGUI::idleLoop (int fd_ycp)
207 {
208  // The rational for this is that we need somewhere to run
209  // the magic 'main' thread, that can process thread unsafe
210  // incoming CORBA messages for us
211  checkInit();
212 
213  GIOChannel *wakeup;
214  wakeup = g_io_channel_unix_new (fd_ycp);
215  g_io_channel_set_encoding (wakeup, NULL, NULL);
216  g_io_channel_set_buffered (wakeup, FALSE);
217 
218  int woken = FALSE;
219  guint watch_tag = g_io_add_watch (wakeup, (GIOCondition)(G_IO_IN | G_IO_PRI),
220  ycp_wakeup_fn, &woken);
221  while (!woken)
222  g_main_context_iteration (NULL, TRUE);
223 
224  g_source_remove (watch_tag);
225  g_io_channel_unref (wakeup);
226 }
227 
228 static gboolean user_input_timeout_cb (YGUI *pThis)
229 {
230  if (!pThis->pendingEvent())
231  pThis->sendEvent (new YTimeoutEvent());
232  return FALSE;
233 }
234 
235 // utility that implements both userInput() and pollInput()
236 YEvent *YGUI::waitInput (unsigned long timeout_ms, bool block)
237 {
238  checkInit();
239  if (!YDialog::currentDialog (false))
240  return NULL;
241 
242  if (block)
243  normalCursor(); // waiting for input, so no more busy
244 
245  guint timeout = 0;
246 
247  if (timeout_ms > 0)
248  timeout = g_timeout_add (timeout_ms,
249  (GSourceFunc) user_input_timeout_cb, this);
250 
251  if (block) {
252  while (!pendingEvent())
253  g_main_context_iteration (NULL, TRUE);
254  }
255  else
256  while (g_main_context_iteration (NULL, FALSE)) ;
257 
258  YEvent *event = NULL;
259  if (pendingEvent())
260  event = m_event_handler.consumePendingEvent();
261 
262  if (timeout)
263  g_source_remove (timeout);
264 
265  if (block) { // if YCP keeps working for more than X time, set busy cursor
266  if (busy_timeout)
267  g_source_remove (busy_timeout);
268  busy_timeout = g_timeout_add (BUSY_CURSOR_TIMEOUT, busy_timeout_cb, this);
269  }
270  return event;
271 }
272 
273 void YGUI::sendEvent (YEvent *event)
274 {
275  m_event_handler.sendEvent (event);
276  g_main_context_wakeup (NULL);
277 }
278 
279 gboolean YGUI::busy_timeout_cb (gpointer data)
280 {
281  YGUI *pThis = (YGUI *) data;
282  pThis->busyCursor();
283  pThis->busy_timeout = 0;
284  return FALSE;
285 }
286 
287 void YGUI::busyCursor()
288 {
289  YGDialog *dialog = YGDialog::currentDialog();
290  if (dialog)
291  dialog->busyCursor();
292 }
293 
294 void YGUI::normalCursor()
295 {
296  if (busy_timeout) {
297  g_source_remove (busy_timeout);
298  busy_timeout = 0;
299  }
300 
301  YGDialog *dialog = YGDialog::currentDialog();
302  if (dialog)
303  dialog->normalCursor();
304 }
305 
306 void YGUI::makeScreenShot()
307 { ((YGApplication *) app())->makeScreenShot (""); }
308 
309 YEvent *YGUI::runPkgSelection (YWidget *packageSelector)
310 {
311  yuiMilestone() << "Running package selection...\n";
312  YEvent *event = 0;
313 
314  try {
315  event = packageSelector->findDialog()->waitForEvent();
316  } catch (const std::exception &e) {
317  yuiError() << "UI::RunPkgSelection() error: " << e.what() << std::endl;
318  yuiError() << "This is a libzypp problem. Do not file a bug against the UI!\n";
319  } catch (...) {
320  yuiError() << "UI::RunPkgSelection() error (unspecified)\n";
321  yuiError() << "This is a libzypp problem. Do not file a bug against the UI!\n";
322  }
323  return event;
324 }
325 
326 void YGUI::askPlayMacro()
327 {
328  std::string filename = askForFileOrDirectory (GTK_FILE_CHOOSER_ACTION_OPEN,
329  DEFAULT_MACRO_FILE_NAME, "*.ycp", _("Open Macro file"));
330  if (!filename.empty()) {
331  busyCursor();
332  YMacro::play (filename);
333  sendEvent (new YEvent()); // flush
334  }
335 }
336 
337 void YGUI::toggleRecordMacro()
338 {
339  if (YMacro::recording()) {
340  YMacro::endRecording();
341  normalCursor();
342 
343  GtkWidget* dialog = gtk_message_dialog_new (NULL,
344  GtkDialogFlags (0), GTK_MESSAGE_INFO, GTK_BUTTONS_OK, "%s",
345  _("Macro recording done."));
346  gtk_dialog_run (GTK_DIALOG (dialog));
347  gtk_widget_destroy (dialog);
348  }
349  else {
350  std::string filename = askForFileOrDirectory (GTK_FILE_CHOOSER_ACTION_SAVE,
351  DEFAULT_MACRO_FILE_NAME, "*.ycp", _("Save Macro"));
352  if (!filename.empty())
353  YMacro::record (filename);
354  }
355 }
356 
357 void YGUI::askSaveLogs()
358 {
359  std::string filename = askForFileOrDirectory (GTK_FILE_CHOOSER_ACTION_SAVE,
360  "/tmp/y2logs.tgz", "*.tgz *.tar.gz", _("Save y2logs"));
361  if (!filename.empty()) {
362  std::string command = "/usr/sbin/save_y2logs";
363  command += " '" + filename + "'";
364  yuiMilestone() << "Saving y2logs: " << command << std::endl;
365  int ret = system (command.c_str());
366  if (ret == 0)
367  yuiMilestone() << "y2logs saved to " << filename << std::endl;
368  else {
369  char *error = g_strdup_printf (
370  _("Could not run: '%s' (exit value: %d)"),
371  command.c_str(), ret);
372  yuiError() << error << std::endl;
373  errorMsg (error);
374  g_free (error);
375  }
376  }
377 }
378 
379 //** YGApplication
380 
381 #define ICONDIR THEMEDIR "/icons/22x22/apps/"
382 
383 YGApplication::YGApplication()
384 {
385  setIconBasePath (ICONDIR);
386 }
387 
388 void YGApplication::makeScreenShot (const std::string &_filename)
389 {
390  std::string filename (_filename);
391  bool interactive = filename.empty();
392 
393  GtkWidget *widget = GTK_WIDGET (YGDialog::currentWindow());
394  if (!widget) {
395  if (interactive)
396  errorMsg (_("No dialog to take screenshot of."));
397  return;
398  }
399 
400  GtkAllocation alloc;
401  gtk_widget_get_allocation(widget, &alloc);
402 
403  GError *error = 0;
404  GdkPixbuf *shot =
405  gdk_pixbuf_get_from_window (gtk_widget_get_window(widget),
406  0, 0, alloc.width,
407  alloc.height);
408 
409  if (!shot) {
410  if (interactive)
411  errorMsg (_("Could not take screenshot."));
412  return;
413  }
414 
415  if (interactive) {
416  //** ask user for filename
417  // calculate a default directory...
418  if (screenShotNameTemplate.empty()) {
419  std::string dir;
420  const char *homedir = getenv("HOME");
421  const char *ssdir = getenv("Y2SCREENSHOTS");
422  if (!homedir || !strcmp (homedir, "/")) {
423  // no homedir defined (installer)
424  dir = "/tmp/" + (ssdir ? (std::string(ssdir)) : (std::string("")));
425  if (mkdir (dir.c_str(), 0700) == -1)
426  dir = "";
427  }
428  else {
429  dir = homedir + (ssdir ? ("/" + std::string(ssdir)) : (std::string("")));
430  mkdir (dir.c_str(), 0750); // create a dir for what to put the pics
431  }
432 
433  screenShotNameTemplate = dir + "/%s-%03d.png";
434  }
435 
436  // calculate a default filename...
437  const char *baseName = "yast2-";
438 
439  int nb;
440  std::map <std::string, int>::iterator it = screenShotNb.find (baseName);
441  if (it == screenShotNb.end())
442  nb = 0;
443 
444  {
445  char *tmp_name = g_strdup_printf (screenShotNameTemplate.c_str(), baseName, nb);
446  filename = tmp_name;
447  g_free (tmp_name);
448  }
449  yuiDebug() << "screenshot: " << filename << std::endl;
450 
451  filename = askForFileOrDirectory (
452  GTK_FILE_CHOOSER_ACTION_SAVE, "", "*.png", _("Save screenshot"));
453  if (filename.empty()) { // user dismissed the dialog
454  yuiDebug() << "Save screen shot canceled by user\n";
455  goto makeScreenShot_ret;
456  }
457 
458  screenShotNb.erase (baseName);
459  screenShotNb[baseName] = nb + 1;
460  }
461 
462  yuiDebug() << "Saving screen shot to " << filename << std::endl;
463  if (!gdk_pixbuf_save (shot, filename.c_str(), "png", &error, NULL)) {
464  std::string msg = _("Could not save to:");
465  msg += " "; msg += filename;
466  if (error) {
467  msg += "\n"; msg += "\n";
468  msg += error->message;
469  }
470  yuiError() << msg << std::endl;
471  if (interactive)
472  errorMsg (msg.c_str());
473  goto makeScreenShot_ret;
474  }
475 
476  makeScreenShot_ret:
477  g_object_unref (G_OBJECT (shot));
478 }
479 
480 void YGApplication::beep()
481 {
482  GtkWindow *window = YGDialog::currentWindow();
483  if (window) {
484  gtk_window_present (window);
485  gtk_widget_error_bell (GTK_WIDGET (window));
486  }
487  else
488  gdk_beep();
489 }
490 
491 // File/directory dialogs
492 #include <sstream>
493 
494 std::string askForFileOrDirectory (GtkFileChooserAction action,
495  const std::string &path, const std::string &filter, const std::string &title)
496 {
497  GtkWindow *parent = YGDialog::currentWindow();
498  const char *button;
499  switch (action) {
500  case GTK_FILE_CHOOSER_ACTION_SAVE:
501  button = "document-save"; break;
502  case GTK_FILE_CHOOSER_ACTION_OPEN:
503  button = "folder-open"; break;
504  case GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER:
505  case GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER:
506  default:
507  button = _("Select"); break;
508  }
509  GtkWidget *dialog;
510  dialog = gtk_file_chooser_dialog_new (title.c_str(),
511  parent, action, "application-exit", GTK_RESPONSE_CANCEL,
512  button, GTK_RESPONSE_ACCEPT, NULL);
513  GtkFileChooser *fileChooser = GTK_FILE_CHOOSER (dialog);
514  gtk_file_chooser_set_local_only (fileChooser, TRUE);
515  gtk_file_chooser_set_do_overwrite_confirmation (fileChooser, TRUE);
516 
517  // filepath can be a dir or a file path, split that up
518  std::string dirname, filename;
519  if (!path.empty()) {
520  if (path[0] != '/')
521  yuiWarning() << "FileDialog: Relative paths are not supported: '" << path << "'\n";
522  else if (!g_file_test (path.c_str(), G_FILE_TEST_EXISTS))
523  yuiWarning() << "FileDialog: Path doesn't exist: '" << path << "'\n";
524  else if (g_file_test (path.c_str(), G_FILE_TEST_IS_DIR))
525  dirname = path;
526  else { // its a file
527  std::string::size_type i = path.find_last_of ("/");
528  if (i != std::string::npos) {
529  dirname = path.substr (0, i+1);
530  filename = path.substr (i+1);
531  }
532  }
533  }
534 
535  if (!dirname.empty())
536  gtk_file_chooser_set_current_folder (fileChooser, dirname.c_str());
537  if (!filename.empty())
538  gtk_file_chooser_set_current_name (fileChooser, filename.c_str());
539 
540  if (!filter.empty() && filter != "*") {
541  GtkFileFilter *gtk_filter = gtk_file_filter_new();
542  gtk_file_filter_set_name (gtk_filter, filter.c_str());
543  // cut filter_pattern into chuncks like GTK likes
544  std::istringstream stream (filter);
545  while (!stream.eof()) {
546  std::string str;
547  stream >> str;
548  if (!str.empty() && str [str.size()-1] == ',')
549  str.erase (str.size()-1);
550  gtk_file_filter_add_pattern (gtk_filter, str.c_str());
551  }
552  gtk_file_chooser_add_filter (fileChooser, gtk_filter);
553  }
554 
555  // bug 335492: because "/" gets hidden as a an arrow at the top, make sure
556  // there is a root shortcut at the side pane (will not add if already exists)
557  gtk_file_chooser_add_shortcut_folder (fileChooser, "/", NULL);
558 
559  std::string ret;
560  if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT) {
561  gchar *filename = gtk_file_chooser_get_filename (fileChooser);
562  if (filename) {
563  ret = filename;
564  g_free (filename);
565  }
566  }
567  gtk_widget_destroy (dialog);
568  return ret;
569 }
570 
571 std::string YGApplication::askForExistingDirectory (
572  const std::string &path, const std::string &title)
573 { return askForFileOrDirectory (GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER, path, "", title); }
574 
575 std::string YGApplication::askForExistingFile (
576  const std::string &path, const std::string &filter, const std::string &title)
577 { return askForFileOrDirectory (GTK_FILE_CHOOSER_ACTION_OPEN, path, filter, title); }
578 
579 std::string YGApplication::askForSaveFileName (
580  const std::string &path, const std::string &filter, const std::string &title)
581 { return askForFileOrDirectory (GTK_FILE_CHOOSER_ACTION_SAVE, path, filter, title); }
582 
583 std::string YGApplication::glyph (const std::string &sym)
584 {
585  bool reverse = gtk_widget_get_default_direction() == GTK_TEXT_DIR_RTL;
586  if (sym == YUIGlyph_ArrowLeft)
587  return reverse ? "\u25b6" : "\u25c0";
588  if (sym == YUIGlyph_ArrowRight)
589  return reverse ? "\u25c0" : "\u25b6";
590  if (sym == YUIGlyph_ArrowUp)
591  return "\u25b2";
592  if (sym == YUIGlyph_ArrowDown)
593  return "\u25bc";
594  if (sym == YUIGlyph_CheckMark)
595  return "\u2714";
596  if (sym == YUIGlyph_BulletArrowRight)
597  return reverse ? "\u21e6" : "\u279c";
598  if (sym == YUIGlyph_BulletCircle)
599  return "\u26ab";
600  if (sym == YUIGlyph_BulletSquare)
601  return "\u25fe";
602  return "";
603 }
604 
605 // YWidget layout units -> pixels conversion. Same as yast-qt's.
606 int YGApplication::deviceUnits (YUIDimension dim, float size)
607 {
608  if (dim == YD_HORIZ)
609  size *= 640.0 / 80;
610  else
611  size *= 480.0 / 25;
612  return size + 0.5;
613 }
614 
615 float YGApplication::layoutUnits (YUIDimension dim, int units)
616 {
617  float size = (float) units;
618  if (dim == YD_HORIZ) return size * (80/640.0);
619  else return size * (25/480.0);
620 }
621 
622 static inline GdkScreen *getScreen ()
623 { return gdk_display_get_default_screen (gdk_display_get_default()); }
624 // GTK doesn't seem to have some desktopWidth/Height like Qt, so we to report
625 // a reduced display size to compensate for the panel, or the window frame
626 int YGApplication::displayWidth()
627 { return gdk_screen_get_width (getScreen()) - 80; }
628 int YGApplication::displayHeight()
629 { return gdk_screen_get_height (getScreen()) - 80; }
630 
631 int YGApplication::displayDepth()
632 { return gdk_visual_get_best_depth(); }
633 
634 long YGApplication::displayColors()
635 { return 1L << displayDepth(); /*from yast-qt*/ }
636 
637 // YCP uses defaultWidth/Height() to use their smaller YWizard impl; we
638 // want to use a smaller default size than qt though, so assume a bigger size
639 int YGApplication::defaultWidth() { return MIN (displayWidth(), 1024); }
640 int YGApplication::defaultHeight() { return MIN (displayHeight(), 768); }
641 
642 YWidgetFactory *YGUI::createWidgetFactory()
643 { return new YGWidgetFactory; }
644 YOptionalWidgetFactory *YGUI::createOptionalWidgetFactory()
645 { return new YGOptionalWidgetFactory; }
646 YApplication *YGUI::createApplication()
647 { return new YGApplication(); }
648 
YGOptionalWidgetFactory
Definition: YGUI.h:122
YGUI
Definition: YGUI.h:20
YGWidgetFactory
Definition: YGUI.h:78
YGDialog
Definition: YGDialog.h:14
YGApplication
Definition: YGUI.h:175