- add sources.
[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 = 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 = 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         UTF16ToUTF8(chrome::FormatBookmarkURLForDisplay(url, prefs)).c_str());
367     g_signal_connect(url_entry_, "changed",
368                      G_CALLBACK(OnEntryChangedThunk), this);
369     gtk_entry_set_activates_default(GTK_ENTRY(url_entry_), TRUE);
370     table = gtk_util::CreateLabeledControlsGroup(NULL,
371         l10n_util::GetStringUTF8(IDS_BOOKMARK_EDITOR_NAME_LABEL).c_str(),
372         name_entry_,
373         l10n_util::GetStringUTF8(IDS_BOOKMARK_EDITOR_URL_LABEL).c_str(),
374         url_entry_,
375         NULL);
376
377   } else {
378     url_entry_ = NULL;
379     table = gtk_util::CreateLabeledControlsGroup(NULL,
380         l10n_util::GetStringUTF8(IDS_BOOKMARK_EDITOR_NAME_LABEL).c_str(),
381         name_entry_,
382         NULL);
383   }
384
385   gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
386
387   if (show_tree_) {
388     GtkTreeIter selected_iter;
389     int64 selected_id = 0;
390     if (details_.type == EditDetails::EXISTING_NODE)
391       selected_id = details_.existing_node->parent()->id();
392     else if (parent_)
393       selected_id = parent_->id();
394     tree_store_ = MakeFolderTreeStore();
395     AddToTreeStore(bb_model_, selected_id, tree_store_, &selected_iter);
396     tree_view_ = MakeTreeViewForStore(tree_store_);
397     gtk_widget_set_size_request(tree_view_, kTreeWidth, kTreeHeight);
398     tree_selection_ = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree_view_));
399     g_signal_connect(tree_view_, "button-press-event",
400                      G_CALLBACK(OnTreeViewButtonPressEventThunk), this);
401
402     BookmarkExpandedStateTracker::Nodes expanded_nodes =
403         bb_model_->expanded_state_tracker()->GetExpandedNodes();
404     if (!expanded_nodes.empty()) {
405       ExpandedNodeIDs ids;
406       for (BookmarkExpandedStateTracker::Nodes::iterator i =
407            expanded_nodes.begin(); i != expanded_nodes.end(); ++i) {
408         ids.insert((*i)->id());
409       }
410       ExpandNodesData data = { &ids, tree_view_ };
411       gtk_tree_model_foreach(GTK_TREE_MODEL(tree_store_), &ExpandNodes,
412                              reinterpret_cast<gpointer>(&data));
413     }
414
415     GtkTreePath* path = NULL;
416     if (selected_id) {
417       path = gtk_tree_model_get_path(GTK_TREE_MODEL(tree_store_),
418                                      &selected_iter);
419     } else {
420       // We don't have a selected parent (Probably because we're making a new
421       // bookmark). Select the first item in the list.
422       path = gtk_tree_path_new_from_string("0");
423     }
424
425     gtk_tree_view_expand_to_path(GTK_TREE_VIEW(tree_view_), path);
426     gtk_tree_selection_select_path(tree_selection_, path);
427     gtk_tree_path_free(path);
428
429     GtkWidget* scroll_window = gtk_scrolled_window_new(NULL, NULL);
430     gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll_window),
431                                     GTK_POLICY_NEVER,
432                                    GTK_POLICY_AUTOMATIC);
433     gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scroll_window),
434                                         GTK_SHADOW_ETCHED_IN);
435     gtk_container_add(GTK_CONTAINER(scroll_window), tree_view_);
436
437     gtk_box_pack_start(GTK_BOX(vbox), scroll_window, TRUE, TRUE, 0);
438
439     g_signal_connect(gtk_tree_view_get_selection(GTK_TREE_VIEW(tree_view_)),
440                      "changed", G_CALLBACK(OnSelectionChangedThunk), this);
441   }
442
443   gtk_box_pack_start(GTK_BOX(content_area), vbox, TRUE, TRUE, 0);
444
445   g_signal_connect(dialog_, "response",
446                    G_CALLBACK(OnResponseThunk), this);
447   g_signal_connect(dialog_, "delete-event",
448                    G_CALLBACK(OnWindowDeleteEventThunk), this);
449   g_signal_connect(dialog_, "destroy",
450                    G_CALLBACK(OnWindowDestroyThunk), this);
451 }
452
453 void BookmarkEditorGtk::Show() {
454   // Manually call our OnEntryChanged handler to set the initial state.
455   OnEntryChanged(NULL);
456
457   gtk_util::ShowDialog(dialog_);
458 }
459
460 void BookmarkEditorGtk::Close() {
461   // Under the model that we've inherited from Windows, dialogs can receive
462   // more than one Close() call inside the current message loop event.
463   if (dialog_) {
464     gtk_widget_destroy(dialog_);
465     dialog_ = NULL;
466   }
467 }
468
469 void BookmarkEditorGtk::BookmarkNodeMoved(BookmarkModel* model,
470                                           const BookmarkNode* old_parent,
471                                           int old_index,
472                                           const BookmarkNode* new_parent,
473                                           int new_index) {
474   Reset();
475 }
476
477 void BookmarkEditorGtk::BookmarkNodeAdded(BookmarkModel* model,
478                                           const BookmarkNode* parent,
479                                           int index) {
480   Reset();
481 }
482
483 void BookmarkEditorGtk::BookmarkNodeRemoved(BookmarkModel* model,
484                                             const BookmarkNode* parent,
485                                             int index,
486                                             const BookmarkNode* node) {
487   if ((details_.type == EditDetails::EXISTING_NODE &&
488        details_.existing_node->HasAncestor(node)) ||
489       (parent_ && parent_->HasAncestor(node))) {
490     // The node, or its parent was removed. Close the dialog.
491     Close();
492   } else {
493     Reset();
494   }
495 }
496
497 void BookmarkEditorGtk::BookmarkAllNodesRemoved(BookmarkModel* model) {
498   Reset();
499 }
500
501 void BookmarkEditorGtk::BookmarkNodeChildrenReordered(
502     BookmarkModel* model, const BookmarkNode* node) {
503   Reset();
504 }
505
506 void BookmarkEditorGtk::Reset() {
507   // TODO(erg): The windows implementation tries to be smart. For now, just
508   // close the window.
509   Close();
510 }
511
512 GURL BookmarkEditorGtk::GetInputURL() const {
513   if (!url_entry_)
514     return GURL();  // Happens when we're editing a folder.
515   return URLFixerUpper::FixupURL(gtk_entry_get_text(GTK_ENTRY(url_entry_)),
516                                  std::string());
517 }
518
519 string16 BookmarkEditorGtk::GetInputTitle() const {
520   return UTF8ToUTF16(gtk_entry_get_text(GTK_ENTRY(name_entry_)));
521 }
522
523 void BookmarkEditorGtk::ApplyEdits() {
524   DCHECK(bb_model_->loaded());
525
526   GtkTreeIter currently_selected_iter;
527   if (show_tree_) {
528     if (!gtk_tree_selection_get_selected(tree_selection_, NULL,
529                                          &currently_selected_iter)) {
530       ApplyEdits(NULL);
531       return;
532     }
533   }
534
535   ApplyEdits(&currently_selected_iter);
536 }
537
538 void BookmarkEditorGtk::ApplyEdits(GtkTreeIter* selected_parent) {
539   // We're going to apply edits to the bookmark bar model, which will call us
540   // back. Normally when a structural edit occurs we reset the tree model.
541   // We don't want to do that here, so we remove ourselves as an observer.
542   bb_model_->RemoveObserver(this);
543
544   GURL new_url(GetInputURL());
545   string16 new_title(GetInputTitle());
546
547   if (!show_tree_ || !selected_parent) {
548     // TODO: this is wrong. Just because there is no selection doesn't mean new
549     // folders weren't added.
550     BookmarkEditor::ApplyEditsWithNoFolderChange(
551         bb_model_, parent_, details_, new_title, new_url);
552     return;
553   }
554
555   // Create the new folders and update the titles.
556   const BookmarkNode* new_parent = CommitTreeStoreDifferencesBetween(
557       bb_model_, tree_store_, selected_parent);
558
559   SaveExpandedNodesData data;
560   data.bookmark_model = bb_model_;
561   gtk_tree_view_map_expanded_rows(GTK_TREE_VIEW(tree_view_),
562                                   &SaveExpandedNodes,
563                                   reinterpret_cast<gpointer>(&data));
564   bb_model_->expanded_state_tracker()->SetExpandedNodes(data.nodes);
565
566   if (!new_parent) {
567     // Bookmarks must be parented.
568     NOTREACHED();
569     return;
570   }
571
572   BookmarkEditor::ApplyEditsWithPossibleFolderChange(
573       bb_model_, new_parent, details_, new_title, new_url);
574
575   // Remove the folders that were removed. This has to be done after all the
576   // other changes have been committed.
577   bookmark_utils::DeleteBookmarkFolders(bb_model_, deletes_);
578 }
579
580 void BookmarkEditorGtk::AddNewFolder(GtkTreeIter* parent, GtkTreeIter* child) {
581   gtk_tree_store_append(tree_store_, child, parent);
582   gtk_tree_store_set(
583       tree_store_,
584       child,
585       FOLDER_ICON, GtkThemeService::GetFolderIcon(true).ToGdkPixbuf(),
586       FOLDER_NAME,
587           l10n_util::GetStringUTF8(IDS_BOOKMARK_EDITOR_NEW_FOLDER_NAME).c_str(),
588       ITEM_ID, static_cast<int64>(0),
589       IS_EDITABLE, TRUE,
590       -1);
591 }
592
593 void BookmarkEditorGtk::OnSelectionChanged(GtkWidget* selection) {
594   if (!gtk_tree_selection_get_selected(tree_selection_, NULL, NULL))
595     gtk_widget_set_sensitive(new_folder_button_, FALSE);
596   else
597     gtk_widget_set_sensitive(new_folder_button_, TRUE);
598 }
599
600 void BookmarkEditorGtk::OnResponse(GtkWidget* dialog, int response_id) {
601   if (response_id == GTK_RESPONSE_ACCEPT)
602     ApplyEdits();
603
604   Close();
605 }
606
607 gboolean BookmarkEditorGtk::OnWindowDeleteEvent(GtkWidget* widget,
608                                                 GdkEvent* event) {
609   Close();
610
611   // Return true to prevent the gtk dialog from being destroyed. Close will
612   // destroy it for us and the default gtk_dialog_delete_event_handler() will
613   // force the destruction without us being able to stop it.
614   return TRUE;
615 }
616
617 void BookmarkEditorGtk::OnWindowDestroy(GtkWidget* widget) {
618   base::MessageLoop::current()->DeleteSoon(FROM_HERE, this);
619 }
620
621 void BookmarkEditorGtk::OnEntryChanged(GtkWidget* entry) {
622   gboolean can_close = TRUE;
623   if (details_.GetNodeType() != BookmarkNode::FOLDER) {
624     if (GetInputURL().is_valid()) {
625       gtk_widget_modify_base(url_entry_, GTK_STATE_NORMAL, NULL);
626     } else {
627       gtk_widget_modify_base(url_entry_, GTK_STATE_NORMAL, &kErrorColor);
628       can_close = FALSE;
629     }
630   }
631   gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog_), GTK_RESPONSE_ACCEPT,
632                                     can_close);
633 }
634
635 void BookmarkEditorGtk::OnNewFolderClicked(GtkWidget* button) {
636   NewFolder();
637 }
638
639 gboolean BookmarkEditorGtk::OnTreeViewButtonPressEvent(GtkWidget* widget,
640                                                        GdkEventButton* event) {
641   if (event->button == 3) {
642     if (!menu_controller_.get())
643       menu_controller_.reset(new ContextMenuController(this));
644     menu_controller_->RunMenu(gfx::Point(event->x_root, event->y_root),
645                               event->time);
646   }
647
648   return FALSE;
649 }
650
651 void BookmarkEditorGtk::NewFolder() {
652   GtkTreeIter iter;
653   if (!gtk_tree_selection_get_selected(tree_selection_,
654                                        NULL,
655                                        &iter)) {
656     NOTREACHED() << "Something should always be selected if New Folder " <<
657                     "is clicked";
658     return;
659   }
660
661   GtkTreeIter new_item_iter;
662   AddNewFolder(&iter, &new_item_iter);
663
664   GtkTreePath* path = gtk_tree_model_get_path(
665       GTK_TREE_MODEL(tree_store_), &new_item_iter);
666   gtk_tree_view_expand_to_path(GTK_TREE_VIEW(tree_view_), path);
667
668   // Make the folder name editable.
669   gtk_tree_view_set_cursor(GTK_TREE_VIEW(tree_view_), path,
670       gtk_tree_view_get_column(GTK_TREE_VIEW(tree_view_), 0),
671       TRUE);
672
673   gtk_tree_path_free(path);
674 }