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 ¤tly_selected_iter)) { 532 ApplyEdits(NULL); 533 return; 534 } 535 } 536 537 ApplyEdits(¤tly_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