summaryrefslogtreecommitdiffstats
path: root/chrome/browser/ui/gtk/bookmarks/bookmark_editor_gtk.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chrome/browser/ui/gtk/bookmarks/bookmark_editor_gtk.cc')
-rw-r--r--chrome/browser/ui/gtk/bookmarks/bookmark_editor_gtk.cc584
1 files changed, 584 insertions, 0 deletions
diff --git a/chrome/browser/ui/gtk/bookmarks/bookmark_editor_gtk.cc b/chrome/browser/ui/gtk/bookmarks/bookmark_editor_gtk.cc
new file mode 100644
index 0000000..466a078
--- /dev/null
+++ b/chrome/browser/ui/gtk/bookmarks/bookmark_editor_gtk.cc
@@ -0,0 +1,584 @@
+// Copyright (c) 2011 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 "chrome/browser/ui/gtk/bookmarks/bookmark_editor_gtk.h"
+
+#include <gtk/gtk.h>
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "base/string_util.h"
+#include "base/utf_string_conversions.h"
+#include "chrome/browser/bookmarks/bookmark_model.h"
+#include "chrome/browser/bookmarks/bookmark_utils.h"
+#include "chrome/browser/history/history.h"
+#include "chrome/browser/net/url_fixer_upper.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/gtk/bookmarks/bookmark_tree_model.h"
+#include "chrome/browser/ui/gtk/bookmarks/bookmark_utils_gtk.h"
+#include "chrome/browser/ui/gtk/gtk_theme_provider.h"
+#include "chrome/browser/ui/gtk/gtk_util.h"
+#include "googleurl/src/gurl.h"
+#include "grit/chromium_strings.h"
+#include "grit/generated_resources.h"
+#include "grit/locale_settings.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/models/simple_menu_model.h"
+#include "ui/gfx/gtk_util.h"
+#include "ui/gfx/point.h"
+
+#if defined(TOOLKIT_VIEWS)
+#include "views/controls/menu/menu_2.h"
+#else
+#include "chrome/browser/ui/gtk/menu_gtk.h"
+#endif
+
+namespace {
+
+// Background color of text field when URL is invalid.
+const GdkColor kErrorColor = GDK_COLOR_RGB(0xFF, 0xBC, 0xBC);
+
+// Preferred initial dimensions, in pixels, of the folder tree.
+static const int kTreeWidth = 300;
+static const int kTreeHeight = 150;
+
+} // namespace
+
+class BookmarkEditorGtk::ContextMenuController
+ : public ui::SimpleMenuModel::Delegate {
+ public:
+ explicit ContextMenuController(BookmarkEditorGtk* editor)
+ : editor_(editor),
+ running_menu_for_root_(false) {
+ menu_model_.reset(new ui::SimpleMenuModel(this));
+ menu_model_->AddItemWithStringId(COMMAND_EDIT, IDS_EDIT);
+ menu_model_->AddItemWithStringId(
+ COMMAND_NEW_FOLDER,
+ IDS_BOOMARK_EDITOR_NEW_FOLDER_MENU_ITEM);
+#if defined(TOOLKIT_VIEWS)
+ menu_.reset(new views::Menu2(menu_model_.get()));
+#else
+ menu_.reset(new MenuGtk(NULL, menu_model_.get()));
+#endif
+ }
+ virtual ~ContextMenuController() {}
+
+ void RunMenu(const gfx::Point& point, guint32 event_time) {
+ const BookmarkNode* selected_node = GetSelectedNode();
+ if (selected_node)
+ running_menu_for_root_ = selected_node->GetParent()->IsRoot();
+#if defined(TOOLKIT_VIEWS)
+ menu_->RunContextMenuAt(point);
+#else
+ menu_->PopupAsContext(point, event_time);
+#endif
+ }
+
+ void Cancel() {
+ editor_ = NULL;
+#if defined(TOOLKIT_VIEWS)
+ menu_->CancelMenu();
+#else
+ menu_->Cancel();
+#endif
+ }
+
+ private:
+ enum ContextMenuCommand {
+ COMMAND_EDIT,
+ COMMAND_NEW_FOLDER
+ };
+
+ // Overridden from ui::SimpleMenuModel::Delegate:
+ virtual bool IsCommandIdEnabled(int command_id) const {
+ return !(command_id == COMMAND_EDIT && running_menu_for_root_) &&
+ (editor_ != NULL);
+ }
+
+ virtual bool IsCommandIdChecked(int command_id) const {
+ return false;
+ }
+
+ virtual bool GetAcceleratorForCommandId(int command_id,
+ ui::Accelerator* accelerator) {
+ return false;
+ }
+
+ virtual void ExecuteCommand(int command_id) {
+ if (!editor_)
+ return;
+
+ switch (command_id) {
+ case COMMAND_EDIT: {
+ GtkTreeIter iter;
+ if (!gtk_tree_selection_get_selected(editor_->tree_selection_,
+ NULL,
+ &iter)) {
+ return;
+ }
+
+ GtkTreePath* path = gtk_tree_model_get_path(
+ GTK_TREE_MODEL(editor_->tree_store_), &iter);
+ gtk_tree_view_expand_to_path(GTK_TREE_VIEW(editor_->tree_view_), path);
+
+ // Make the folder name editable.
+ gtk_tree_view_set_cursor(GTK_TREE_VIEW(editor_->tree_view_), path,
+ gtk_tree_view_get_column(GTK_TREE_VIEW(editor_->tree_view_), 0),
+ TRUE);
+
+ gtk_tree_path_free(path);
+ break;
+ }
+ case COMMAND_NEW_FOLDER:
+ editor_->NewFolder();
+ break;
+ default:
+ NOTREACHED();
+ break;
+ }
+ }
+
+ int64 GetRowIdAt(GtkTreeModel* model, GtkTreeIter* iter) {
+ GValue value = { 0, };
+ gtk_tree_model_get_value(model, iter, bookmark_utils::ITEM_ID, &value);
+ int64 id = g_value_get_int64(&value);
+ g_value_unset(&value);
+ return id;
+ }
+
+ const BookmarkNode* GetNodeAt(GtkTreeModel* model, GtkTreeIter* iter) {
+ int64 id = GetRowIdAt(model, iter);
+ return (id > 0) ? editor_->bb_model_->GetNodeByID(id) : NULL;
+ }
+
+ const BookmarkNode* GetSelectedNode() {
+ GtkTreeModel* model;
+ GtkTreeIter iter;
+ if (!gtk_tree_selection_get_selected(editor_->tree_selection_,
+ &model,
+ &iter)) {
+ return NULL;
+ }
+
+ return GetNodeAt(model, &iter);
+ }
+
+ // The model and view for the right click context menu.
+ scoped_ptr<ui::SimpleMenuModel> menu_model_;
+#if defined(TOOLKIT_VIEWS)
+ scoped_ptr<views::Menu2> menu_;
+#else
+ scoped_ptr<MenuGtk> menu_;
+#endif
+
+ // The context menu was brought up for. Set to NULL when the menu is canceled.
+ BookmarkEditorGtk* editor_;
+
+ // If true, we're running the menu for the bookmark bar or other bookmarks
+ // nodes.
+ bool running_menu_for_root_;
+
+ DISALLOW_COPY_AND_ASSIGN(ContextMenuController);
+};
+
+// static
+void BookmarkEditor::Show(gfx::NativeWindow parent_hwnd,
+ Profile* profile,
+ const BookmarkNode* parent,
+ const EditDetails& details,
+ Configuration configuration) {
+ DCHECK(profile);
+ BookmarkEditorGtk* editor =
+ new BookmarkEditorGtk(parent_hwnd, profile, parent, details,
+ configuration);
+ editor->Show();
+}
+
+BookmarkEditorGtk::BookmarkEditorGtk(
+ GtkWindow* window,
+ Profile* profile,
+ const BookmarkNode* parent,
+ const EditDetails& details,
+ BookmarkEditor::Configuration configuration)
+ : profile_(profile),
+ dialog_(NULL),
+ parent_(parent),
+ details_(details),
+ running_menu_for_root_(false),
+ show_tree_(configuration == SHOW_TREE) {
+ DCHECK(profile);
+ Init(window);
+}
+
+BookmarkEditorGtk::~BookmarkEditorGtk() {
+ // The tree model is deleted before the view. Reset the model otherwise the
+ // tree will reference a deleted model.
+
+ bb_model_->RemoveObserver(this);
+}
+
+void BookmarkEditorGtk::Init(GtkWindow* parent_window) {
+ bb_model_ = profile_->GetBookmarkModel();
+ DCHECK(bb_model_);
+ bb_model_->AddObserver(this);
+
+ dialog_ = gtk_dialog_new_with_buttons(
+ l10n_util::GetStringUTF8(IDS_BOOMARK_EDITOR_TITLE).c_str(),
+ parent_window,
+ GTK_DIALOG_MODAL,
+ GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
+ GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
+ NULL);
+ gtk_dialog_set_has_separator(GTK_DIALOG(dialog_), FALSE);
+
+ if (show_tree_) {
+ GtkWidget* action_area = GTK_DIALOG(dialog_)->action_area;
+ new_folder_button_ = gtk_button_new_with_label(
+ l10n_util::GetStringUTF8(IDS_BOOMARK_EDITOR_NEW_FOLDER_BUTTON).c_str());
+ g_signal_connect(new_folder_button_, "clicked",
+ G_CALLBACK(OnNewFolderClickedThunk), this);
+ gtk_container_add(GTK_CONTAINER(action_area), new_folder_button_);
+ gtk_button_box_set_child_secondary(GTK_BUTTON_BOX(action_area),
+ new_folder_button_, TRUE);
+ }
+
+ gtk_dialog_set_default_response(GTK_DIALOG(dialog_), GTK_RESPONSE_ACCEPT);
+
+ // The GTK dialog content area layout (overview)
+ //
+ // +- GtkVBox |vbox| ----------------------------------------------+
+ // |+- GtkTable |table| ------------------------------------------+|
+ // ||+- GtkLabel ------+ +- GtkEntry |name_entry_| --------------+||
+ // ||| | | |||
+ // ||+-----------------+ +---------------------------------------+||
+ // ||+- GtkLabel ------+ +- GtkEntry |url_entry_| ---------------+|| *
+ // ||| | | |||
+ // ||+-----------------+ +---------------------------------------+||
+ // |+-------------------------------------------------------------+|
+ // |+- GtkScrollWindow |scroll_window| ---------------------------+|
+ // ||+- GtkTreeView |tree_view_| --------------------------------+||
+ // |||+- GtkTreeViewColumn |name_column| -----------------------+|||
+ // |||| ||||
+ // |||| ||||
+ // |||| ||||
+ // |||| ||||
+ // |||+---------------------------------------------------------+|||
+ // ||+-----------------------------------------------------------+||
+ // |+-------------------------------------------------------------+|
+ // +---------------------------------------------------------------+
+ //
+ // * The url and corresponding label are not shown if creating a new folder.
+ GtkWidget* content_area = GTK_DIALOG(dialog_)->vbox;
+ gtk_box_set_spacing(GTK_BOX(content_area), gtk_util::kContentAreaSpacing);
+
+ GtkWidget* vbox = gtk_vbox_new(FALSE, 12);
+
+ name_entry_ = gtk_entry_new();
+ std::string title;
+ if (details_.type == EditDetails::EXISTING_NODE) {
+ title = UTF16ToUTF8(details_.existing_node->GetTitle());
+ } else if (details_.type == EditDetails::NEW_FOLDER) {
+ title = l10n_util::GetStringUTF8(IDS_BOOMARK_EDITOR_NEW_FOLDER_NAME);
+ }
+ gtk_entry_set_text(GTK_ENTRY(name_entry_), title.c_str());
+ g_signal_connect(name_entry_, "changed",
+ G_CALLBACK(OnEntryChangedThunk), this);
+ gtk_entry_set_activates_default(GTK_ENTRY(name_entry_), TRUE);
+
+ GtkWidget* table;
+ if (details_.type != EditDetails::NEW_FOLDER) {
+ url_entry_ = gtk_entry_new();
+ std::string url_spec;
+ if (details_.type == EditDetails::EXISTING_NODE)
+ url_spec = details_.existing_node->GetURL().spec();
+ gtk_entry_set_text(GTK_ENTRY(url_entry_), url_spec.c_str());
+ g_signal_connect(url_entry_, "changed",
+ G_CALLBACK(OnEntryChangedThunk), this);
+ gtk_entry_set_activates_default(GTK_ENTRY(url_entry_), TRUE);
+ table = gtk_util::CreateLabeledControlsGroup(NULL,
+ l10n_util::GetStringUTF8(IDS_BOOMARK_EDITOR_NAME_LABEL).c_str(),
+ name_entry_,
+ l10n_util::GetStringUTF8(IDS_BOOMARK_EDITOR_URL_LABEL).c_str(),
+ url_entry_,
+ NULL);
+
+ } else {
+ url_entry_ = NULL;
+ table = gtk_util::CreateLabeledControlsGroup(NULL,
+ l10n_util::GetStringUTF8(IDS_BOOMARK_EDITOR_NAME_LABEL).c_str(),
+ name_entry_,
+ NULL);
+ }
+
+ gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
+
+ if (show_tree_) {
+ GtkTreeIter selected_iter;
+ int64 selected_id = 0;
+ if (details_.type == EditDetails::EXISTING_NODE)
+ selected_id = details_.existing_node->GetParent()->id();
+ else if (parent_)
+ selected_id = parent_->id();
+ tree_store_ = bookmark_utils::MakeFolderTreeStore();
+ bookmark_utils::AddToTreeStore(bb_model_, selected_id, tree_store_,
+ &selected_iter);
+ tree_view_ = bookmark_utils::MakeTreeViewForStore(tree_store_);
+ gtk_widget_set_size_request(tree_view_, kTreeWidth, kTreeHeight);
+ tree_selection_ = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree_view_));
+ g_signal_connect(tree_view_, "button-press-event",
+ G_CALLBACK(OnTreeViewButtonPressEventThunk), this);
+
+ GtkTreePath* path = NULL;
+ if (selected_id) {
+ path = gtk_tree_model_get_path(GTK_TREE_MODEL(tree_store_),
+ &selected_iter);
+ } else {
+ // We don't have a selected parent (Probably because we're making a new
+ // bookmark). Select the first item in the list.
+ path = gtk_tree_path_new_from_string("0");
+ }
+
+ gtk_tree_view_expand_to_path(GTK_TREE_VIEW(tree_view_), path);
+ gtk_tree_selection_select_path(tree_selection_, path);
+ gtk_tree_path_free(path);
+
+ GtkWidget* scroll_window = gtk_scrolled_window_new(NULL, NULL);
+ gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll_window),
+ GTK_POLICY_NEVER,
+ GTK_POLICY_AUTOMATIC);
+ gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scroll_window),
+ GTK_SHADOW_ETCHED_IN);
+ gtk_container_add(GTK_CONTAINER(scroll_window), tree_view_);
+
+ gtk_box_pack_start(GTK_BOX(vbox), scroll_window, TRUE, TRUE, 0);
+
+ g_signal_connect(gtk_tree_view_get_selection(GTK_TREE_VIEW(tree_view_)),
+ "changed", G_CALLBACK(OnSelectionChangedThunk), this);
+ }
+
+ gtk_box_pack_start(GTK_BOX(content_area), vbox, TRUE, TRUE, 0);
+
+ g_signal_connect(dialog_, "response",
+ G_CALLBACK(OnResponseThunk), this);
+ g_signal_connect(dialog_, "delete-event",
+ G_CALLBACK(OnWindowDeleteEventThunk), this);
+ g_signal_connect(dialog_, "destroy",
+ G_CALLBACK(OnWindowDestroyThunk), this);
+}
+
+void BookmarkEditorGtk::Show() {
+ // Manually call our OnEntryChanged handler to set the initial state.
+ OnEntryChanged(NULL);
+
+ gtk_util::ShowDialog(dialog_);
+}
+
+void BookmarkEditorGtk::Close() {
+ // Under the model that we've inherited from Windows, dialogs can receive
+ // more than one Close() call inside the current message loop event.
+ if (dialog_) {
+ gtk_widget_destroy(dialog_);
+ dialog_ = NULL;
+ }
+}
+
+void BookmarkEditorGtk::BookmarkNodeMoved(BookmarkModel* model,
+ const BookmarkNode* old_parent,
+ int old_index,
+ const BookmarkNode* new_parent,
+ int new_index) {
+ Reset();
+}
+
+void BookmarkEditorGtk::BookmarkNodeAdded(BookmarkModel* model,
+ const BookmarkNode* parent,
+ int index) {
+ Reset();
+}
+
+void BookmarkEditorGtk::BookmarkNodeRemoved(BookmarkModel* model,
+ const BookmarkNode* parent,
+ int index,
+ const BookmarkNode* node) {
+ if ((details_.type == EditDetails::EXISTING_NODE &&
+ details_.existing_node->HasAncestor(node)) ||
+ (parent_ && parent_->HasAncestor(node))) {
+ // The node, or its parent was removed. Close the dialog.
+ Close();
+ } else {
+ Reset();
+ }
+}
+
+void BookmarkEditorGtk::BookmarkNodeChildrenReordered(
+ BookmarkModel* model, const BookmarkNode* node) {
+ Reset();
+}
+
+void BookmarkEditorGtk::Reset() {
+ // TODO(erg): The windows implementation tries to be smart. For now, just
+ // close the window.
+ Close();
+}
+
+GURL BookmarkEditorGtk::GetInputURL() const {
+ if (!url_entry_)
+ return GURL(); // Happens when we're editing a folder.
+ return URLFixerUpper::FixupURL(gtk_entry_get_text(GTK_ENTRY(url_entry_)),
+ std::string());
+}
+
+string16 BookmarkEditorGtk::GetInputTitle() const {
+ return UTF8ToUTF16(gtk_entry_get_text(GTK_ENTRY(name_entry_)));
+}
+
+void BookmarkEditorGtk::ApplyEdits() {
+ DCHECK(bb_model_->IsLoaded());
+
+ GtkTreeIter currently_selected_iter;
+ if (show_tree_) {
+ if (!gtk_tree_selection_get_selected(tree_selection_, NULL,
+ &currently_selected_iter)) {
+ ApplyEdits(NULL);
+ return;
+ }
+ }
+
+ ApplyEdits(&currently_selected_iter);
+}
+
+void BookmarkEditorGtk::ApplyEdits(GtkTreeIter* selected_parent) {
+ // We're going to apply edits to the bookmark bar model, which will call us
+ // back. Normally when a structural edit occurs we reset the tree model.
+ // We don't want to do that here, so we remove ourselves as an observer.
+ bb_model_->RemoveObserver(this);
+
+ GURL new_url(GetInputURL());
+ string16 new_title(GetInputTitle());
+
+ if (!show_tree_ || !selected_parent) {
+ bookmark_utils::ApplyEditsWithNoGroupChange(
+ bb_model_, parent_, details_, new_title, new_url);
+ return;
+ }
+
+ // Create the new groups and update the titles.
+ const BookmarkNode* new_parent =
+ bookmark_utils::CommitTreeStoreDifferencesBetween(
+ bb_model_, tree_store_, selected_parent);
+
+ if (!new_parent) {
+ // Bookmarks must be parented.
+ NOTREACHED();
+ return;
+ }
+
+ bookmark_utils::ApplyEditsWithPossibleGroupChange(
+ bb_model_, new_parent, details_, new_title, new_url);
+}
+
+void BookmarkEditorGtk::AddNewGroup(GtkTreeIter* parent, GtkTreeIter* child) {
+ gtk_tree_store_append(tree_store_, child, parent);
+ gtk_tree_store_set(
+ tree_store_, child,
+ bookmark_utils::FOLDER_ICON, GtkThemeProvider::GetFolderIcon(true),
+ bookmark_utils::FOLDER_NAME,
+ l10n_util::GetStringUTF8(IDS_BOOMARK_EDITOR_NEW_FOLDER_NAME).c_str(),
+ bookmark_utils::ITEM_ID, static_cast<int64>(0),
+ bookmark_utils::IS_EDITABLE, TRUE,
+ -1);
+}
+
+void BookmarkEditorGtk::OnSelectionChanged(GtkWidget* selection) {
+ if (!gtk_tree_selection_get_selected(tree_selection_, NULL, NULL))
+ gtk_widget_set_sensitive(new_folder_button_, FALSE);
+ else
+ gtk_widget_set_sensitive(new_folder_button_, TRUE);
+}
+
+void BookmarkEditorGtk::OnResponse(GtkWidget* dialog, int response_id) {
+ if (response_id == GTK_RESPONSE_ACCEPT)
+ ApplyEdits();
+
+ Close();
+}
+
+gboolean BookmarkEditorGtk::OnWindowDeleteEvent(GtkWidget* widget,
+ GdkEvent* event) {
+ Close();
+
+ // Return true to prevent the gtk dialog from being destroyed. Close will
+ // destroy it for us and the default gtk_dialog_delete_event_handler() will
+ // force the destruction without us being able to stop it.
+ return TRUE;
+}
+
+void BookmarkEditorGtk::OnWindowDestroy(GtkWidget* widget) {
+ MessageLoop::current()->DeleteSoon(FROM_HERE, this);
+}
+
+void BookmarkEditorGtk::OnEntryChanged(GtkWidget* entry) {
+ gboolean can_close = TRUE;
+ if (details_.type == EditDetails::NEW_FOLDER) {
+ if (GetInputTitle().empty()) {
+ gtk_widget_modify_base(name_entry_, GTK_STATE_NORMAL,
+ &kErrorColor);
+ can_close = FALSE;
+ } else {
+ gtk_widget_modify_base(name_entry_, GTK_STATE_NORMAL, NULL);
+ }
+ } else {
+ GURL url(GetInputURL());
+ if (!url.is_valid()) {
+ gtk_widget_modify_base(url_entry_, GTK_STATE_NORMAL,
+ &kErrorColor);
+ can_close = FALSE;
+ } else {
+ gtk_widget_modify_base(url_entry_, GTK_STATE_NORMAL, NULL);
+ }
+ }
+ gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog_),
+ GTK_RESPONSE_ACCEPT, can_close);
+}
+
+void BookmarkEditorGtk::OnNewFolderClicked(GtkWidget* button) {
+ NewFolder();
+}
+
+gboolean BookmarkEditorGtk::OnTreeViewButtonPressEvent(GtkWidget* widget,
+ GdkEventButton* event) {
+ if (event->button == 3) {
+ if (!menu_controller_.get())
+ menu_controller_.reset(new ContextMenuController(this));
+ menu_controller_->RunMenu(gfx::Point(event->x_root, event->y_root),
+ event->time);
+ }
+
+ return FALSE;
+}
+
+void BookmarkEditorGtk::NewFolder() {
+ GtkTreeIter iter;
+ if (!gtk_tree_selection_get_selected(tree_selection_,
+ NULL,
+ &iter)) {
+ NOTREACHED() << "Something should always be selected if New Folder " <<
+ "is clicked";
+ return;
+ }
+
+ GtkTreeIter new_item_iter;
+ AddNewGroup(&iter, &new_item_iter);
+
+ GtkTreePath* path = gtk_tree_model_get_path(
+ GTK_TREE_MODEL(tree_store_), &new_item_iter);
+ gtk_tree_view_expand_to_path(GTK_TREE_VIEW(tree_view_), path);
+
+ // Make the folder name editable.
+ gtk_tree_view_set_cursor(GTK_TREE_VIEW(tree_view_), path,
+ gtk_tree_view_get_column(GTK_TREE_VIEW(tree_view_), 0),
+ TRUE);
+
+ gtk_tree_path_free(path);
+}