diff --git a/atom.gyp b/atom.gyp index cce3b2de43..b2f65d5d32 100644 --- a/atom.gyp +++ b/atom.gyp @@ -127,6 +127,8 @@ 'browser/ui/gtk/gtk_custom_menu.h', 'browser/ui/gtk/gtk_custom_menu_item.cc', 'browser/ui/gtk/gtk_custom_menu_item.h', + 'browser/ui/gtk/gtk_util.cc', + 'browser/ui/gtk/gtk_util.h', 'browser/ui/gtk/gtk_window_util.cc', 'browser/ui/gtk/gtk_window_util.h', 'browser/ui/message_box.h', diff --git a/browser/ui/gtk/gtk_util.cc b/browser/ui/gtk/gtk_util.cc new file mode 100644 index 0000000000..efb1b34290 --- /dev/null +++ b/browser/ui/gtk/gtk_util.cc @@ -0,0 +1,89 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "browser/ui/gtk/gtk_util.h" + +#include + +#include "base/logging.h" + +namespace gtk_util { + +namespace { + +const char kBoldLabelMarkup[] = "%s"; + +// Returns the approximate number of characters that can horizontally fit in +// |pixel_width| pixels. +int GetCharacterWidthForPixels(GtkWidget* widget, int pixel_width) { + DCHECK(gtk_widget_get_realized(widget)) + << " widget must be realized to compute font metrics correctly"; + + PangoContext* context = gtk_widget_create_pango_context(widget); + GtkStyle* style = gtk_widget_get_style(widget); + PangoFontMetrics* metrics = pango_context_get_metrics(context, + style->font_desc, pango_context_get_language(context)); + + // This technique (max of char and digit widths) matches the code in + // gtklabel.c. + int char_width = pixel_width * PANGO_SCALE / + std::max(pango_font_metrics_get_approximate_char_width(metrics), + pango_font_metrics_get_approximate_digit_width(metrics)); + + pango_font_metrics_unref(metrics); + g_object_unref(context); + + return char_width; +} + +void OnLabelRealize(GtkWidget* label, gpointer pixel_width) { + gtk_label_set_width_chars( + GTK_LABEL(label), + GetCharacterWidthForPixels(label, GPOINTER_TO_INT(pixel_width))); +} + +} // namespace + +GtkWidget* LeftAlignMisc(GtkWidget* misc) { + gtk_misc_set_alignment(GTK_MISC(misc), 0, 0.5); + return misc; +} + +GtkWidget* CreateBoldLabel(const std::string& text) { + GtkWidget* label = gtk_label_new(NULL); + char* markup = g_markup_printf_escaped(kBoldLabelMarkup, text.c_str()); + gtk_label_set_markup(GTK_LABEL(label), markup); + g_free(markup); + + return LeftAlignMisc(label); +} + +bool IsWidgetAncestryVisible(GtkWidget* widget) { + GtkWidget* parent = widget; + while (parent && gtk_widget_get_visible(parent)) + parent = gtk_widget_get_parent(parent); + return !parent; +} + +void SetLabelWidth(GtkWidget* label, int pixel_width) { + gtk_label_set_line_wrap(GTK_LABEL(label), TRUE); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); + + // Do the simple thing in LTR because the bug only affects right-aligned + // text. Also, when using the workaround, the label tries to maintain + // uniform line-length, which we don't really want. + if (gtk_widget_get_direction(label) == GTK_TEXT_DIR_LTR) { + gtk_widget_set_size_request(label, pixel_width, -1); + } else { + // The label has to be realized before we can adjust its width. + if (gtk_widget_get_realized(label)) { + OnLabelRealize(label, GINT_TO_POINTER(pixel_width)); + } else { + g_signal_connect(label, "realize", G_CALLBACK(OnLabelRealize), + GINT_TO_POINTER(pixel_width)); + } + } +} + +} // namespace gtk_util diff --git a/browser/ui/gtk/gtk_util.h b/browser/ui/gtk/gtk_util.h new file mode 100644 index 0000000000..38961fc0b9 --- /dev/null +++ b/browser/ui/gtk/gtk_util.h @@ -0,0 +1,31 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef ATOM_BROWSER_UI_GTK_GTK_UTIL_H_ +#define ATOM_BROWSER_UI_GTK_GTK_UTIL_H_ + +#include +#include + +namespace gtk_util { + +// Left-align the given GtkMisc and return the same pointer. +GtkWidget* LeftAlignMisc(GtkWidget* misc); + +// Create a left-aligned label with the given text in bold. +GtkWidget* CreateBoldLabel(const std::string& text); + +// Checks whether a widget is actually visible, i.e. whether it and all its +// ancestors up to its toplevel are visible. +bool IsWidgetAncestryVisible(GtkWidget* widget); + +// Sets the given label's size request to |pixel_width|. This will cause the +// label to wrap if it needs to. The reason for this function is that some +// versions of GTK mis-align labels that have a size request and line wrapping, +// and this function hides the complexity of the workaround. +void SetLabelWidth(GtkWidget* label, int pixel_width); + +} // namespace gtk_util + +#endif // ATOM_BROWSER_UI_GTK_GTK_UTIL_H_ diff --git a/browser/ui/message_box_gtk.cc b/browser/ui/message_box_gtk.cc index f5fc72ee80..0fbb156f7d 100644 --- a/browser/ui/message_box_gtk.cc +++ b/browser/ui/message_box_gtk.cc @@ -5,16 +5,116 @@ #include "browser/ui/message_box.h" #include "base/callback.h" +#include "base/strings/string_util.h" +#include "browser/native_window.h" +#include "browser/ui/gtk/gtk_util.h" +#include "ui/base/gtk/gtk_signal.h" namespace atom { +namespace { + +class MessageBox { + public: + MessageBox(NativeWindow* parent_window, + MessageBoxType type, + const std::vector& buttons, + const std::string& title, + const std::string& message, + const std::string& detail) + : cancel_id_(0) { + GtkWindow* window = parent_window ? parent_window->GetNativeWindow() : NULL; + dialog_ = gtk_dialog_new_with_buttons( + title.c_str(), + window, + static_cast(GTK_DIALOG_MODAL | GTK_DIALOG_NO_SEPARATOR), + NULL); + + for (size_t i = 0; i < buttons.size(); ++i) + gtk_dialog_add_button(GTK_DIALOG(dialog_), + TranslateToStock(i, buttons[i]), + i); + + GtkWidget* content_area = gtk_dialog_get_content_area(GTK_DIALOG(dialog_)); + GtkWidget* message_label = gtk_util::CreateBoldLabel(message); + gtk_util::LeftAlignMisc(message_label); + gtk_box_pack_start(GTK_BOX(content_area), message_label, FALSE, FALSE, 0); + GtkWidget* detail_label = gtk_label_new(detail.c_str()); + gtk_util::LeftAlignMisc(detail_label); + gtk_box_pack_start(GTK_BOX(content_area), detail_label, FALSE, FALSE, 0); + + gtk_window_set_resizable(GTK_WINDOW(dialog_), FALSE); + } + + ~MessageBox() { + gtk_widget_destroy(dialog_); + } + + const char* TranslateToStock(int id, const std::string& text) { + if (LowerCaseEqualsASCII(text, "cancel")) { + cancel_id_ = id; + return GTK_STOCK_CANCEL; + } else if (LowerCaseEqualsASCII(text, "no")) { + cancel_id_ = id; + return GTK_STOCK_NO; + } else if (LowerCaseEqualsASCII(text, "ok")) { + return GTK_STOCK_OK; + } else if (LowerCaseEqualsASCII(text, "yes")) { + return GTK_STOCK_YES; + } else { + return text.c_str(); + } + } + + void RunAsynchronous(const MessageBoxCallback& callback) { + callback_ = callback; + g_signal_connect(dialog_, "delete-event", + G_CALLBACK(gtk_widget_hide_on_delete), NULL); + g_signal_connect(dialog_, "response", + G_CALLBACK(OnResponseDialogThunk), this); + gtk_widget_show_all(dialog_); + } + + CHROMEGTK_CALLBACK_1(MessageBox, void, OnResponseDialog, int); + + GtkWidget* dialog() const { return dialog_; } + int cancel_id() const { return cancel_id_; } + + private: + GtkWidget* dialog_; + MessageBoxCallback callback_; + + // The id to return when the dialog is closed without pressing buttons. + int cancel_id_; + + DISALLOW_COPY_AND_ASSIGN(MessageBox); +}; + +void MessageBox::OnResponseDialog(GtkWidget* widget, int response) { + gtk_widget_hide_all(dialog_); + + if (response < 0) + callback_.Run(cancel_id_); + else + callback_.Run(response); + delete this; +} + +} // namespace + int ShowMessageBox(NativeWindow* parent_window, MessageBoxType type, const std::vector& buttons, const std::string& title, const std::string& message, const std::string& detail) { - return 0; + MessageBox message_box(parent_window, type, buttons, title, message, detail); + gtk_widget_show_all(message_box.dialog()); + int response = gtk_dialog_run(GTK_DIALOG(message_box.dialog())); + if (response < 0) + return message_box.cancel_id(); + else + return response; } void ShowMessageBox(NativeWindow* parent_window, @@ -24,7 +124,9 @@ void ShowMessageBox(NativeWindow* parent_window, const std::string& message, const std::string& detail, const MessageBoxCallback& callback) { - callback.Run(0); + MessageBox* message_box = new MessageBox( + parent_window, type, buttons, title, message, detail); + message_box->RunAsynchronous(callback); } } // namespace atom diff --git a/script/create-dist.py b/script/create-dist.py index f96f34ce4b..de08d34e00 100755 --- a/script/create-dist.py +++ b/script/create-dist.py @@ -9,7 +9,7 @@ import tarfile from lib.config import LIBCHROMIUMCONTENT_COMMIT, BASE_URL, NODE_VERSION from lib.util import scoped_cwd, rm_rf, get_atom_shell_version, make_zip, \ - safe_mkdir, safe_unlink, execute + safe_mkdir, execute ATOM_SHELL_VRESION = get_atom_shell_version()