Upstream version 7.35.144.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / ui / gtk / bookmarks / bookmark_editor_gtk.cc
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "chrome/browser/ui/gtk/bookmarks/bookmark_editor_gtk.h"
6
7 #include <gtk/gtk.h>
8
9 #include <set>
10
11 #include "base/basictypes.h"
12 #include "base/logging.h"
13 #include "base/prefs/pref_service.h"
14 #include "base/strings/string_util.h"
15 #include "base/strings/utf_string_conversions.h"
16 #include "chrome/browser/bookmarks/bookmark_expanded_state_tracker.h"
17 #include "chrome/browser/bookmarks/bookmark_model.h"
18 #include "chrome/browser/bookmarks/bookmark_model_factory.h"
19 #include "chrome/browser/bookmarks/bookmark_utils.h"
20 #include "chrome/browser/history/history_service.h"
21 #include "chrome/browser/profiles/profile.h"
22 #include "chrome/browser/ui/bookmarks/bookmark_utils.h"
23 #include "chrome/browser/ui/gtk/bookmarks/bookmark_tree_model.h"
24 #include "chrome/browser/ui/gtk/bookmarks/bookmark_utils_gtk.h"
25 #include "chrome/browser/ui/gtk/gtk_theme_service.h"
26 #include "chrome/browser/ui/gtk/gtk_util.h"
27 #include "chrome/browser/ui/gtk/menu_gtk.h"
28 #include "chrome/common/net/url_fixer_upper.h"
29 #include "components/user_prefs/user_prefs.h"
30 #include "grit/chromium_strings.h"
31 #include "grit/generated_resources.h"
32 #include "grit/locale_settings.h"
33 #include "ui/base/gtk/gtk_hig_constants.h"
34 #include "ui/base/l10n/l10n_util.h"
35 #include "ui/base/models/simple_menu_model.h"
36 #include "ui/gfx/gtk_util.h"
37 #include "ui/gfx/image/image.h"
38 #include "ui/gfx/point.h"
39 #include "url/gurl.h"
40
41 namespace {
42
43 // Background color of text field when URL is invalid.
44 const GdkColor kErrorColor = GDK_COLOR_RGB(0xFF, 0xBC, 0xBC);
45
46 // Preferred initial dimensions, in pixels, of the folder tree.
47 const int kTreeWidth = 300;
48 const int kTreeHeight = 150;
49
50 typedef std::set<int64> ExpandedNodeIDs;
51
52 // Used by ExpandNodes.
53 struct ExpandNodesData {
54   const ExpandedNodeIDs* ids;
55   GtkWidget* tree_view;
56 };
57
58 // Expands all the nodes in |pointer_data| (which is a ExpandNodesData). This is
59 // intended for use by gtk_tree_model_foreach to expand a particular set of
60 // nodes.
61 gboolean ExpandNodes(GtkTreeModel* model,
62                      GtkTreePath* path,
63                      GtkTreeIter* iter,
64                      gpointer pointer_data) {
65   ExpandNodesData* data = reinterpret_cast<ExpandNodesData*>(pointer_data);
66   int64 node_id = GetIdFromTreeIter(model, iter);
67   if (data->ids->find(node_id) != data->ids->end())
68     gtk_tree_view_expand_to_path(GTK_TREE_VIEW(data->tree_view), path);
69   return FALSE;  // Indicates we want to continue iterating.
70 }
71
72 // Used by SaveExpandedNodes.
73 struct SaveExpandedNodesData {
74   // Filled in by SaveExpandedNodes.
75   BookmarkExpandedStateTracker::Nodes nodes;
76   BookmarkModel* bookmark_model;
77 };
78
79 // Adds the node at |path| to |pointer_data| (which is a SaveExpandedNodesData).
80 // This is intended for use with gtk_tree_view_map_expanded_rows to save all
81 // the expanded paths.
82 void SaveExpandedNodes(GtkTreeView* tree_view,
83                        GtkTreePath* path,
84                        gpointer pointer_data) {
85   SaveExpandedNodesData* data =
86       reinterpret_cast<SaveExpandedNodesData*>(pointer_data);
87   GtkTreeIter iter;
88   gtk_tree_model_get_iter(gtk_tree_view_get_model(tree_view), &iter, path);
89   const BookmarkNode* node = data->bookmark_model->GetNodeByID(
90       GetIdFromTreeIter(gtk_tree_view_get_model(tree_view), &iter));
91   if (node)
92     data->nodes.insert(node);
93 }
94
95 }  // namespace
96
97 class BookmarkEditorGtk::ContextMenuController
98     : public ui::SimpleMenuModel::Delegate {
99  public:
100   explicit ContextMenuController(BookmarkEditorGtk* editor)
101       : editor_(editor),
102         running_menu_for_root_(false) {
103     menu_model_.reset(new ui::SimpleMenuModel(this));
104     menu_model_->AddItemWithStringId(COMMAND_EDIT, IDS_EDIT);
105     menu_model_->AddItemWithStringId(COMMAND_DELETE, IDS_DELETE);
106     menu_model_->AddItemWithStringId(
107         COMMAND_NEW_FOLDER,
108         IDS_BOOKMARK_EDITOR_NEW_FOLDER_MENU_ITEM);
109     menu_.reset(new MenuGtk(NULL, menu_model_.get()));
110   }
111   virtual ~ContextMenuController() {}
112
113   void RunMenu(const gfx::Point& point, guint32 event_time) {
114     const BookmarkNode* selected_node = GetSelectedNode();
115     if (selected_node)
116       running_menu_for_root_ = selected_node->parent()->is_root();
117     menu_->PopupAsContext(point, event_time);
118   }
119
120   void Cancel() {
121     editor_ = NULL;
122     menu_->Cancel();
123   }
124
125  private:
126   enum ContextMenuCommand {
127     COMMAND_DELETE,
128     COMMAND_EDIT,
129     COMMAND_NEW_FOLDER
130   };
131
132   // Overridden from ui::SimpleMenuModel::Delegate:
133   virtual bool IsCommandIdEnabled(int command_id) const OVERRIDE {
134     if (editor_ == NULL)
135       return false;
136
137     switch (command_id) {
138       case COMMAND_DELETE:
139       case COMMAND_EDIT:
140         return !running_menu_for_root_;
141       case COMMAND_NEW_FOLDER:
142         return true;
143     }
144     return false;
145   }
146
147   virtual bool IsCommandIdChecked(int command_id) const OVERRIDE {
148     return false;
149   }
150
151   virtual bool GetAcceleratorForCommandId(
152       int command_id,
153       ui::Accelerator* accelerator) OVERRIDE {
154     return false;
155   }
156
157   virtual void ExecuteCommand(int command_id, int event_flags) OVERRIDE {
158     if (!editor_)
159       return;
160
161     switch (command_id) {
162       case COMMAND_DELETE: {
163         GtkTreeIter iter;
164         GtkTreeModel* model = NULL;
165         if (!gtk_tree_selection_get_selected(editor_->tree_selection_,
166                                              &model,
167                                              &iter)) {
168           break;
169         }
170         const BookmarkNode* selected_node = GetNodeAt(model, &iter);
171         if (selected_node) {
172           DCHECK(selected_node->is_folder());
173           // Deleting an existing bookmark folder. Confirm if it has other
174           // bookmarks.
175           if (!selected_node->empty()) {
176             if (!chrome::ConfirmDeleteBookmarkNode(selected_node,
177                   GTK_WINDOW(editor_->dialog_)))
178               break;
179           }
180           editor_->deletes_.push_back(selected_node->id());
181         }
182         gtk_tree_store_remove(editor_->tree_store_, &iter);
183         break;
184       }
185       case COMMAND_EDIT: {
186         GtkTreeIter iter;
187         if (!gtk_tree_selection_get_selected(editor_->tree_selection_,
188                                              NULL,
189                                              &iter)) {
190           return;
191         }
192
193         GtkTreePath* path = gtk_tree_model_get_path(
194             GTK_TREE_MODEL(editor_->tree_store_), &iter);
195         gtk_tree_view_expand_to_path(GTK_TREE_VIEW(editor_->tree_view_), path);
196
197         // Make the folder name editable.
198         gtk_tree_view_set_cursor(GTK_TREE_VIEW(editor_->tree_view_), path,
199             gtk_tree_view_get_column(GTK_TREE_VIEW(editor_->tree_view_), 0),
200             TRUE);
201
202         gtk_tree_path_free(path);
203         break;
204       }
205       case COMMAND_NEW_FOLDER:
206         editor_->NewFolder();
207         break;
208       default:
209         NOTREACHED();
210         break;
211     }
212   }
213
214   const BookmarkNode* GetNodeAt(GtkTreeModel* model, GtkTreeIter* iter) const {
215     int64 id = GetIdFromTreeIter(model, iter);
216     return (id > 0) ? editor_->bb_model_->GetNodeByID(id) : NULL;
217   }
218
219   const BookmarkNode* GetSelectedNode() const {
220     GtkTreeModel* model;
221     GtkTreeIter iter;
222     if (!gtk_tree_selection_get_selected(editor_->tree_selection_,
223                                          &model,
224                                          &iter)) {
225       return NULL;
226     }
227
228     return GetNodeAt(model, &iter);
229   }
230
231   // The model and view for the right click context menu.
232   scoped_ptr<ui::SimpleMenuModel> menu_model_;
233   scoped_ptr<MenuGtk> menu_;
234
235   // The context menu was brought up for. Set to NULL when the menu is canceled.
236   BookmarkEditorGtk* editor_;
237
238   // If true, we're running the menu for the bookmark bar or other bookmarks
239   // nodes.
240   bool running_menu_for_root_;
241
242   DISALLOW_COPY_AND_ASSIGN(ContextMenuController);
243 };
244
245 // static
246 void BookmarkEditor::Show(gfx::NativeWindow parent_hwnd,
247                           Profile* profile,
248                           const EditDetails& details,
249                           Configuration configuration) {
250   DCHECK(profile);
251   BookmarkEditorGtk* editor =
252       new BookmarkEditorGtk(parent_hwnd,
253                             profile,
254                             details.parent_node,
255                             details,
256                             configuration);
257   editor->Show();
258 }
259
260 BookmarkEditorGtk::BookmarkEditorGtk(
261     GtkWindow* window,
262     Profile* profile,
263     const BookmarkNode* parent,
264     const EditDetails& details,
265     BookmarkEditor::Configuration configuration)
266     : profile_(profile),
267       dialog_(NULL),
268       parent_(parent),
269       details_(details),
270       running_menu_for_root_(false),
271       show_tree_(configuration == SHOW_TREE) {
272   DCHECK(profile);
273   Init(window);
274 }
275
276 BookmarkEditorGtk::~BookmarkEditorGtk() {
277   // The tree model is deleted before the view. Reset the model otherwise the
278   // tree will reference a deleted model.
279
280   bb_model_->RemoveObserver(this);
281 }
282
283 void BookmarkEditorGtk::Init(GtkWindow* parent_window) {
284   bb_model_ = BookmarkModelFactory::GetForProfile(profile_);
285   DCHECK(bb_model_);
286   bb_model_->AddObserver(this);
287
288   dialog_ = gtk_dialog_new_with_buttons(
289       l10n_util::GetStringUTF8(details_.GetWindowTitleId()).c_str(),
290       parent_window,
291       GTK_DIALOG_MODAL,
292       GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
293       GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
294       NULL);
295 #if !GTK_CHECK_VERSION(2, 22, 0)
296   gtk_dialog_set_has_separator(GTK_DIALOG(dialog_), FALSE);
297 #endif
298
299   if (show_tree_) {
300     GtkWidget* action_area = gtk_dialog_get_action_area(GTK_DIALOG(dialog_));
301     new_folder_button_ = gtk_button_new_with_label(
302         l10n_util::GetStringUTF8(
303             IDS_BOOKMARK_EDITOR_NEW_FOLDER_BUTTON).c_str());
304     g_signal_connect(new_folder_button_, "clicked",
305                      G_CALLBACK(OnNewFolderClickedThunk), this);
306     gtk_container_add(GTK_CONTAINER(action_area), new_folder_button_);
307     gtk_button_box_set_child_secondary(GTK_BUTTON_BOX(action_area),
308                                        new_folder_button_, TRUE);
309   }
310
311   gtk_dialog_set_default_response(GTK_DIALOG(dialog_), GTK_RESPONSE_ACCEPT);
312
313   // The GTK dialog content area layout (overview)
314   //
315   // +- GtkVBox |vbox| ----------------------------------------------+
316   // |+- GtkTable |table| ------------------------------------------+|
317   // ||+- GtkLabel ------+ +- GtkEntry |name_entry_| --------------+||
318   // |||                 | |                                       |||
319   // ||+-----------------+ +---------------------------------------+||
320   // ||+- GtkLabel ------+ +- GtkEntry |url_entry_| ---------------+|| *
321   // |||                 | |                                       |||
322   // ||+-----------------+ +---------------------------------------+||
323   // |+-------------------------------------------------------------+|
324   // |+- GtkScrollWindow |scroll_window| ---------------------------+|
325   // ||+- GtkTreeView |tree_view_| --------------------------------+||
326   // |||+- GtkTreeViewColumn |name_column| -----------------------+|||
327   // ||||                                                         ||||
328   // ||||                                                         ||||
329   // ||||                                                         ||||
330   // ||||                                                         ||||
331   // |||+---------------------------------------------------------+|||
332   // ||+-----------------------------------------------------------+||
333   // |+-------------------------------------------------------------+|
334   // +---------------------------------------------------------------+
335   //
336   // * The url and corresponding label are not shown if creating a new folder.
337   GtkWidget* content_area = gtk_dialog_get_content_area(GTK_DIALOG(dialog_));
338   gtk_box_set_spacing(GTK_BOX(content_area), ui::kContentAreaSpacing);
339
340   GtkWidget* vbox = gtk_vbox_new(FALSE, 12);
341
342   name_entry_ = gtk_entry_new();
343   std::string title;
344   GURL url;
345   if (details_.type == EditDetails::EXISTING_NODE) {
346     title = base::UTF16ToUTF8(details_.existing_node->GetTitle());
347     url = details_.existing_node->url();
348   } else if (details_.type == EditDetails::NEW_FOLDER) {
349     title = l10n_util::GetStringUTF8(IDS_BOOKMARK_EDITOR_NEW_FOLDER_NAME);
350   } else if (details_.type == EditDetails::NEW_URL) {
351     url = details_.url;
352     title = base::UTF16ToUTF8(details_.title);
353   }
354   gtk_entry_set_text(GTK_ENTRY(name_entry_), title.c_str());
355   g_signal_connect(name_entry_, "changed",
356                    G_CALLBACK(OnEntryChangedThunk), this);
357   gtk_entry_set_activates_default(GTK_ENTRY(name_entry_), TRUE);
358
359   GtkWidget* table;
360   if (details_.GetNodeType() != BookmarkNode::FOLDER) {
361     url_entry_ = gtk_entry_new();
362     PrefService* prefs =
363       profile_ ? user_prefs::UserPrefs::Get(profile_) :  NULL;
364     gtk_entry_set_text(
365         GTK_ENTRY(url_entry_),
366         base::UTF16ToUTF8(
367             chrome::FormatBookmarkURLForDisplay(url, prefs)).c_str());
368     g_signal_connect(url_entry_, "changed",
369                      G_CALLBACK(OnEntryChangedThunk), this);
370     gtk_entry_set_activates_default(GTK_ENTRY(url_entry_), TRUE);
371     table = gtk_util::CreateLabeledControlsGroup(NULL,
372         l10n_util::GetStringUTF8(IDS_BOOKMARK_EDITOR_NAME_LABEL).c_str(),
373         name_entry_,
374         l10n_util::GetStringUTF8(IDS_BOOKMARK_EDITOR_URL_LABEL).c_str(),
375         url_entry_,
376         NULL);
377
378   } else {
379     url_entry_ = NULL;
380     table = gtk_util::CreateLabeledControlsGroup(NULL,
381         l10n_util::GetStringUTF8(IDS_BOOKMARK_EDITOR_NAME_LABEL).c_str(),
382         name_entry_,
383         NULL);
384   }
385
386   gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
387
388   if (show_tree_) {
389     GtkTreeIter selected_iter;
390     int64 selected_id = 0;
391     if (details_.type == EditDetails::EXISTING_NODE)
392       selected_id = details_.existing_node->parent()->id();
393     else if (parent_)
394       selected_id = parent_->id();
395     tree_store_ = MakeFolderTreeStore();
396     AddToTreeStore(bb_model_, selected_id, tree_store_, &selected_iter);
397     tree_view_ = MakeTreeViewForStore(tree_store_);
398     gtk_widget_set_size_request(tree_view_, kTreeWidth, kTreeHeight);
399     tree_selection_ = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree_view_));
400     g_signal_connect(tree_view_, "button-press-event",
401                      G_CALLBACK(OnTreeViewButtonPressEventThunk), this);
402
403     BookmarkExpandedStateTracker::Nodes expanded_nodes =
404         bb_model_->expanded_state_tracker()->GetExpandedNodes();
405     if (!expanded_nodes.empty()) {
406       ExpandedNodeIDs ids;
407       for (BookmarkExpandedStateTracker::Nodes::iterator i =
408            expanded_nodes.begin(); i != expanded_nodes.end(); ++i) {
409         ids.insert((*i)->id());
410       }
411       ExpandNodesData data = { &ids, tree_view_ };
412       gtk_tree_model_foreach(GTK_TREE_MODEL(tree_store_), &ExpandNodes,
413                              reinterpret_cast<gpointer>(&data));
414     }
415
416     GtkTreePath* path = NULL;
417     if (selected_id) {
418       path = gtk_tree_model_get_path(GTK_TREE_MODEL(tree_store_),
419                                      &selected_iter);
420     } else {
421       // We don't have a selected parent (Probably because we're making a new
422       // bookmark). Select the first item in the list.
423       path = gtk_tree_path_new_from_string("0");
424     }
425
426     gtk_tree_view_expand_to_path(GTK_TREE_VIEW(tree_view_), path);
427     gtk_tree_selection_select_path(tree_selection_, path);
428     gtk_tree_path_free(path);
429
430     GtkWidget* scroll_window = gtk_scrolled_window_new(NULL, NULL);
431     gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll_window),
432                                     GTK_POLICY_NEVER,
433                                    GTK_POLICY_AUTOMATIC);
434     gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scroll_window),
435                                         GTK_SHADOW_ETCHED_IN);
436     gtk_container_add(GTK_CONTAINER(scroll_window), tree_view_);
437
438     gtk_box_pack_start(GTK_BOX(vbox), scroll_window, TRUE, TRUE, 0);
439
440     g_signal_connect(gtk_tree_view_get_selection(GTK_TREE_VIEW(tree_view_)),
441                      "changed", G_CALLBACK(OnSelectionChangedThunk), this);
442   }
443
444   gtk_box_pack_start(GTK_BOX(content_area), vbox, TRUE, TRUE, 0);
445
446   g_signal_connect(dialog_, "response",
447                    G_CALLBACK(OnResponseThunk), this);
448   g_signal_connect(dialog_, "delete-event",
449                    G_CALLBACK(OnWindowDeleteEventThunk), this);
450   g_signal_connect(dialog_, "destroy",
451                    G_CALLBACK(OnWindowDestroyThunk), this);
452 }
453
454 void BookmarkEditorGtk::Show() {
455   // Manually call our OnEntryChanged handler to set the initial state.
456   OnEntryChanged(NULL);
457
458   gtk_util::ShowDialog(dialog_);
459 }
460
461 void BookmarkEditorGtk::Close() {
462   // Under the model that we've inherited from Windows, dialogs can receive
463   // more than one Close() call inside the current message loop event.
464   if (dialog_) {
465     gtk_widget_destroy(dialog_);
466     dialog_ = NULL;
467   }
468 }
469
470 void BookmarkEditorGtk::BookmarkNodeMoved(BookmarkModel* model,
471                                           const BookmarkNode* old_parent,
472                                           int old_index,
473                                           const BookmarkNode* new_parent,
474                                           int new_index) {
475   Reset();
476 }
477
478 void BookmarkEditorGtk::BookmarkNodeAdded(BookmarkModel* model,
479                                           const BookmarkNode* parent,
480                                           int index) {
481   Reset();
482 }
483
484 void BookmarkEditorGtk::BookmarkNodeRemoved(BookmarkModel* model,
485                                             const BookmarkNode* parent,
486                                             int index,
487                                             const BookmarkNode* node) {
488   if ((details_.type == EditDetails::EXISTING_NODE &&
489        details_.existing_node->HasAncestor(node)) ||
490       (parent_ && parent_->HasAncestor(node))) {
491     // The node, or its parent was removed. Close the dialog.
492     Close();
493   } else {
494     Reset();
495   }
496 }
497
498 void BookmarkEditorGtk::BookmarkAllNodesRemoved(BookmarkModel* model) {
499   Reset();
500 }
501
502 void BookmarkEditorGtk::BookmarkNodeChildrenReordered(
503     BookmarkModel* model, const BookmarkNode* node) {
504   Reset();
505 }
506
507 void BookmarkEditorGtk::Reset() {
508   // TODO(erg): The windows implementation tries to be smart. For now, just
509   // close the window.
510   Close();
511 }
512
513 GURL BookmarkEditorGtk::GetInputURL() const {
514   if (!url_entry_)
515     return GURL();  // Happens when we're editing a folder.
516   return URLFixerUpper::FixupURL(gtk_entry_get_text(GTK_ENTRY(url_entry_)),
517                                  std::string());
518 }
519
520 base::string16 BookmarkEditorGtk::GetInputTitle() const {
521   return base::UTF8ToUTF16(gtk_entry_get_text(GTK_ENTRY(name_entry_)));
522 }
523
524 void BookmarkEditorGtk::ApplyEdits() {
525   DCHECK(bb_model_->loaded());
526
527   GtkTreeIter currently_selected_iter;
528   if (show_tree_) {
529     if (!gtk_tree_selection_get_selected(tree_selection_, NULL,
530                                          &currently_selected_iter)) {
531       ApplyEdits(NULL);
532       return;
533     }
534   }
535
536   ApplyEdits(&currently_selected_iter);
537 }
538
539 void BookmarkEditorGtk::ApplyEdits(GtkTreeIter* selected_parent) {
540   // We're going to apply edits to the bookmark bar model, which will call us
541   // back. Normally when a structural edit occurs we reset the tree model.
542   // We don't want to do that here, so we remove ourselves as an observer.
543   bb_model_->RemoveObserver(this);
544
545   GURL new_url(GetInputURL());
546   base::string16 new_title(GetInputTitle());
547
548   if (!show_tree_ || !selected_parent) {
549     // TODO: this is wrong. Just because there is no selection doesn't mean new
550     // folders weren't added.
551     BookmarkEditor::ApplyEditsWithNoFolderChange(
552         bb_model_, parent_, details_, new_title, new_url);
553     return;
554   }
555
556   // Create the new folders and update the titles.
557   const BookmarkNode* new_parent = CommitTreeStoreDifferencesBetween(
558       bb_model_, tree_store_, selected_parent);
559
560   SaveExpandedNodesData data;
561   data.bookmark_model = bb_model_;
562   gtk_tree_view_map_expanded_rows(GTK_TREE_VIEW(tree_view_),
563                                   &SaveExpandedNodes,
564                                   reinterpret_cast<gpointer>(&data));
565   bb_model_->expanded_state_tracker()->SetExpandedNodes(data.nodes);
566
567   if (!new_parent) {
568     // Bookmarks must be parented.
569     NOTREACHED();
570     return;
571   }
572
573   BookmarkEditor::ApplyEditsWithPossibleFolderChange(
574       bb_model_, new_parent, details_, new_title, new_url);
575
576   // Remove the folders that were removed. This has to be done after all the
577   // other changes have been committed.
578   bookmark_utils::DeleteBookmarkFolders(bb_model_, deletes_);
579 }
580
581 void BookmarkEditorGtk::AddNewFolder(GtkTreeIter* parent, GtkTreeIter* child) {
582   gtk_tree_store_append(tree_store_, child, parent);
583   gtk_tree_store_set(
584       tree_store_,
585       child,
586       FOLDER_ICON, GtkThemeService::GetFolderIcon(true).ToGdkPixbuf(),
587       FOLDER_NAME,
588           l10n_util::GetStringUTF8(IDS_BOOKMARK_EDITOR_NEW_FOLDER_NAME).c_str(),
589       ITEM_ID, static_cast<int64>(0),
590       IS_EDITABLE, TRUE,
591       -1);
592 }
593
594 void BookmarkEditorGtk::OnSelectionChanged(GtkWidget* selection) {
595   if (!gtk_tree_selection_get_selected(tree_selection_, NULL, NULL))
596     gtk_widget_set_sensitive(new_folder_button_, FALSE);
597   else
598     gtk_widget_set_sensitive(new_folder_button_, TRUE);
599 }
600
601 void BookmarkEditorGtk::OnResponse(GtkWidget* dialog, int response_id) {
602   if (response_id == GTK_RESPONSE_ACCEPT)
603     ApplyEdits();
604
605   Close();
606 }
607
608 gboolean BookmarkEditorGtk::OnWindowDeleteEvent(GtkWidget* widget,
609                                                 GdkEvent* event) {
610   Close();
611
612   // Return true to prevent the gtk dialog from being destroyed. Close will
613   // destroy it for us and the default gtk_dialog_delete_event_handler() will
614   // force the destruction without us being able to stop it.
615   return TRUE;
616 }
617
618 void BookmarkEditorGtk::OnWindowDestroy(GtkWidget* widget) {
619   base::MessageLoop::current()->DeleteSoon(FROM_HERE, this);
620 }
621
622 void BookmarkEditorGtk::OnEntryChanged(GtkWidget* entry) {
623   gboolean can_close = TRUE;
624   if (details_.GetNodeType() != BookmarkNode::FOLDER) {
625     if (GetInputURL().is_valid()) {
626       gtk_widget_modify_base(url_entry_, GTK_STATE_NORMAL, NULL);
627     } else {
628       gtk_widget_modify_base(url_entry_, GTK_STATE_NORMAL, &kErrorColor);
629       can_close = FALSE;
630     }
631   }
632   gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog_), GTK_RESPONSE_ACCEPT,
633                                     can_close);
634 }
635
636 void BookmarkEditorGtk::OnNewFolderClicked(GtkWidget* button) {
637   NewFolder();
638 }
639
640 gboolean BookmarkEditorGtk::OnTreeViewButtonPressEvent(GtkWidget* widget,
641                                                        GdkEventButton* event) {
642   if (event->button == 3) {
643     if (!menu_controller_.get())
644       menu_controller_.reset(new ContextMenuController(this));
645     menu_controller_->RunMenu(gfx::Point(event->x_root, event->y_root),
646                               event->time);
647   }
648
649   return FALSE;
650 }
651
652 void BookmarkEditorGtk::NewFolder() {
653   GtkTreeIter iter;
654   if (!gtk_tree_selection_get_selected(tree_selection_,
655                                        NULL,
656                                        &iter)) {
657     NOTREACHED() << "Something should always be selected if New Folder " <<
658                     "is clicked";
659     return;
660   }
661
662   GtkTreeIter new_item_iter;
663   AddNewFolder(&iter, &new_item_iter);
664
665   GtkTreePath* path = gtk_tree_model_get_path(
666       GTK_TREE_MODEL(tree_store_), &new_item_iter);
667   gtk_tree_view_expand_to_path(GTK_TREE_VIEW(tree_view_), path);
668
669   // Make the folder name editable.
670   gtk_tree_view_set_cursor(GTK_TREE_VIEW(tree_view_), path,
671       gtk_tree_view_get_column(GTK_TREE_VIEW(tree_view_), 0),
672       TRUE);
673
674   gtk_tree_path_free(path);
675 }