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