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