diff options
author | sky@google.com <sky@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2008-10-29 23:38:06 +0000 |
---|---|---|
committer | sky@google.com <sky@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2008-10-29 23:38:06 +0000 |
commit | 7f856bee73ffdccdbbbbbab4cb79185290d38359 (patch) | |
tree | a837328e08a113abdc70b2fc0ae2f2a4a3f804eb /chrome/browser/views | |
parent | 281fe14063dd8fb81cea102f5abb7b82f407c3d1 (diff) | |
download | chromium_src-7f856bee73ffdccdbbbbbab4cb79185290d38359.zip chromium_src-7f856bee73ffdccdbbbbbab4cb79185290d38359.tar.gz chromium_src-7f856bee73ffdccdbbbbbab4cb79185290d38359.tar.bz2 |
First cut at the bookmark manager. There are still a fair number of
rough edges, but I'm at a good point where I want to land what I
have. Here's what is left:
. Flicker on show, likely the result of restoring window placement.
. tree flickers when dragging splitter.
. table/tree need to autoscroll when drop cursor held at bottom of view.
. prompts for deleting.
. When you move an item the table snaps to the top, this is because
I'm sending out model changed. need a better notification.
. Operations in menu to add need to change selection.
. Remember split location.
I would have preferred to split this up into a couple of reviews, but
everything is intertwined now. Sorry.
BUG=674
TEST=don't test the bookmark manager yet, but make sure bookmark bar
still works.
Review URL: http://codereview.chromium.org/8197
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@4191 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/views')
-rw-r--r-- | chrome/browser/views/bookmark_bar_view.cc | 87 | ||||
-rw-r--r-- | chrome/browser/views/bookmark_bar_view.h | 11 | ||||
-rw-r--r-- | chrome/browser/views/bookmark_bubble_view.cc | 3 | ||||
-rw-r--r-- | chrome/browser/views/bookmark_editor_view.cc | 149 | ||||
-rw-r--r-- | chrome/browser/views/bookmark_editor_view.h | 27 | ||||
-rw-r--r-- | chrome/browser/views/bookmark_editor_view_unittest.cc | 59 | ||||
-rw-r--r-- | chrome/browser/views/bookmark_folder_tree_view.cc | 321 | ||||
-rw-r--r-- | chrome/browser/views/bookmark_folder_tree_view.h | 117 | ||||
-rw-r--r-- | chrome/browser/views/bookmark_manager_view.cc | 383 | ||||
-rw-r--r-- | chrome/browser/views/bookmark_manager_view.h | 160 | ||||
-rw-r--r-- | chrome/browser/views/bookmark_table_view.cc | 394 | ||||
-rw-r--r-- | chrome/browser/views/bookmark_table_view.h | 121 | ||||
-rw-r--r-- | chrome/browser/views/browser_views.vcproj | 24 | ||||
-rw-r--r-- | chrome/browser/views/toolbar_view.cc | 2 |
14 files changed, 1753 insertions, 105 deletions
diff --git a/chrome/browser/views/bookmark_bar_view.cc b/chrome/browser/views/bookmark_bar_view.cc index 70be891..8c08453 100644 --- a/chrome/browser/views/bookmark_bar_view.cc +++ b/chrome/browser/views/bookmark_bar_view.cc @@ -9,8 +9,8 @@ #include "base/base_drag_source.h" #include "base/gfx/skia_utils.h" #include "chrome/app/theme/theme_resources.h" -#include "chrome/browser/bookmark_bar_context_menu_controller.h" -#include "chrome/browser/bookmarks/bookmark_drag_utils.h" +#include "chrome/browser/bookmarks/bookmark_context_menu.h" +#include "chrome/browser/bookmarks/bookmark_utils.h" #include "chrome/browser/browser.h" #include "chrome/browser/browser_list.h" #include "chrome/browser/browser_process.h" @@ -395,8 +395,6 @@ class MenuRunner : public views::MenuDelegate, } virtual void ModelChanged() { - if (context_menu_.get()) - context_menu_->ModelChanged(); menu_.Cancel(); } @@ -531,8 +529,16 @@ class MenuRunner : public views::MenuDelegate, int y, bool is_mouse_gesture) { DCHECK(menu_id_to_node_map_.find(id) != menu_id_to_node_map_.end()); + std::vector<BookmarkNode*> nodes; + nodes.push_back(menu_id_to_node_map_[id]); context_menu_.reset( - new BookmarkBarContextMenuController(view_, menu_id_to_node_map_[id])); + new BookmarkContextMenu(view_->GetContainer()->GetHWND(), + view_->GetProfile(), + view_->browser(), + view_->GetPageNavigator(), + nodes[0]->GetParent(), + nodes, + BookmarkContextMenu::BOOKMARK_BAR)); context_menu_->RunMenuAt(x, y); context_menu_.reset(NULL); return true; @@ -581,7 +587,7 @@ class MenuRunner : public views::MenuDelegate, // Data for the drop. BookmarkDragData drop_data_; - scoped_ptr<BookmarkBarContextMenuController> context_menu_; + scoped_ptr<BookmarkContextMenu> context_menu_; DISALLOW_COPY_AND_ASSIGN(MenuRunner); }; @@ -646,6 +652,22 @@ static const SkBitmap& GetGroupIcon() { } // static +void BookmarkBarView::ToggleWhenVisible(Profile* profile) { + PrefService* prefs = profile->GetPrefs(); + const bool always_show = !prefs->GetBoolean(prefs::kShowBookmarkBar); + + // The user changed when the bookmark bar is shown, update the preferences. + prefs->SetBoolean(prefs::kShowBookmarkBar, always_show); + prefs->ScheduleSavePersistentPrefs(g_browser_process->file_thread()); + + // And notify the notification service. + Source<Profile> source(profile); + NotificationService::current()->Notify( + NOTIFY_BOOKMARK_BAR_VISIBILITY_PREF_CHANGED, source, + NotificationService::NoDetails()); +} + +// static void BookmarkBarView::RegisterUserPrefs(PrefService* prefs) { prefs->RegisterBooleanPref(prefs::kShowBookmarkBar, false); } @@ -1079,24 +1101,6 @@ int BookmarkBarView::OnPerformDrop(const DropTargetEvent& event) { return PerformDropImpl(data, parent_node, index); } -void BookmarkBarView::ToggleWhenVisible() { - PrefService* prefs = profile_->GetPrefs(); - const bool always_show = !prefs->GetBoolean(prefs::kShowBookmarkBar); - - // The user changed when the bookmark bar is shown, update the preferences. - prefs->SetBoolean(prefs::kShowBookmarkBar, always_show); - prefs->ScheduleSavePersistentPrefs(g_browser_process->file_thread()); - - // And notify the notification service. - Source<Profile> source(profile_); - NotificationService::current()->Notify( - NOTIFY_BOOKMARK_BAR_VISIBILITY_PREF_CHANGED, source, - NotificationService::NoDetails()); - - // May need to redraw the bar with a new style. - SchedulePaint(); -} - bool BookmarkBarView::IsAlwaysShown() { return profile_->GetPrefs()->GetBoolean(prefs::kShowBookmarkBar); } @@ -1422,8 +1426,8 @@ void BookmarkBarView::ButtonPressed(views::BaseButton* sender) { event_utils::DispositionFromEventFlags(sender->mouse_event_flags()), PageTransition::AUTO_BOOKMARK); } else { - BookmarkBarContextMenuController::OpenAll( - GetContainer()->GetHWND(), GetPageNavigator(), node, + bookmark_utils::OpenAll( + GetContainer()->GetHWND(), profile_, GetPageNavigator(), node, event_utils::DispositionFromEventFlags(sender->mouse_event_flags())); } UserMetrics::RecordAction(L"ClickedBookmarkBarURLButton", profile_); @@ -1438,18 +1442,30 @@ void BookmarkBarView::ShowContextMenu(View* source, return; } - BookmarkNode* node = model_->GetBookmarkBarNode(); + BookmarkNode* parent = NULL; + std::vector<BookmarkNode*> nodes; if (source == other_bookmarked_button_) { - node = model_->other_node(); + parent = model_->other_node(); + // Do this so the user can open all bookmarks. BookmarkContextMenu makes + // sure the user can edit/delete the node in this case. + nodes.push_back(parent); } else if (source != this) { // User clicked on one of the bookmark buttons, find which one they // clicked on. int bookmark_button_index = GetChildIndex(source); DCHECK(bookmark_button_index != -1 && bookmark_button_index < GetBookmarkButtonCount()); - node = model_->GetBookmarkBarNode()->GetChild(bookmark_button_index); + BookmarkNode* node = + model_->GetBookmarkBarNode()->GetChild(bookmark_button_index); + nodes.push_back(node); + parent = node->GetParent(); + } else { + parent = model_->GetBookmarkBarNode(); } - BookmarkBarContextMenuController controller(this, node); + BookmarkContextMenu controller(GetContainer()->GetHWND(), + GetProfile(), browser(), GetPageNavigator(), + parent, nodes, + BookmarkContextMenu::BOOKMARK_BAR); controller.RunMenuAt(x, y); } @@ -1492,7 +1508,7 @@ bool BookmarkBarView::IsItemChecked(int id) const { } void BookmarkBarView::ExecuteCommand(int id) { - ToggleWhenVisible(); + ToggleWhenVisible(profile_); } void BookmarkBarView::Observe(NotificationType type, @@ -1652,7 +1668,7 @@ int BookmarkBarView::CalculateDropOperation(const DropTargetEvent& event, int ops = data.GetFirstNode(profile_) ? DragDropTypes::DRAG_MOVE : DragDropTypes::DRAG_COPY | DragDropTypes::DRAG_LINK; - return bookmark_drag_utils::PreferredDropOperation(event, ops); + return bookmark_utils::PreferredDropOperation(event, ops); } for (int i = 0; i < GetBookmarkButtonCount() && @@ -1733,7 +1749,7 @@ int BookmarkBarView::CalculateDropOperation(const DropTargetEvent& event, // Currently only accept one dragged node at a time. return DragDropTypes::DRAG_NONE; - if (!bookmark_drag_utils::IsValidDropLocation(profile_, data, parent, index)) + if (!bookmark_utils::IsValidDropLocation(profile_, data, parent, index)) return DragDropTypes::DRAG_NONE; if (data.GetFirstNode(profile_)) { @@ -1741,7 +1757,7 @@ int BookmarkBarView::CalculateDropOperation(const DropTargetEvent& event, return DragDropTypes::DRAG_MOVE; } else { // User is dragging from another app, copy. - return bookmark_drag_utils::PreferredDropOperation( + return bookmark_utils::PreferredDropOperation( event, DragDropTypes::DRAG_COPY | DragDropTypes::DRAG_LINK); } } @@ -1767,8 +1783,7 @@ int BookmarkBarView::PerformDropImpl(const BookmarkDragData& data, return DragDropTypes::DRAG_COPY | DragDropTypes::DRAG_LINK; } else { // Dropping a group from different profile. Always accept. - bookmark_drag_utils::CloneDragData(model_, data.elements, parent_node, - index); + bookmark_utils::CloneDragData(model_, data.elements, parent_node, index); return DragDropTypes::DRAG_COPY; } } diff --git a/chrome/browser/views/bookmark_bar_view.h b/chrome/browser/views/bookmark_bar_view.h index 242a7f2..754d6b6 100644 --- a/chrome/browser/views/bookmark_bar_view.h +++ b/chrome/browser/views/bookmark_bar_view.h @@ -72,6 +72,10 @@ class BookmarkBarView : public views::View, explicit BookmarkBarView(Profile* profile, Browser* browser); virtual ~BookmarkBarView(); + // Toggles whether the bookmark bar is shown only on the new tab page or on + // all tabs. + static void ToggleWhenVisible(Profile* profile); + static void RegisterUserPrefs(PrefService* prefs); // Resets the profile. This removes any buttons for the current profile and @@ -81,6 +85,9 @@ class BookmarkBarView : public views::View, // Returns the current profile. Profile* GetProfile() { return profile_; } + // Returns the current browser. + Browser* browser() const { return browser_; } + // Sets the PageNavigator that is used when the user selects an entry on // the bookmark bar. void SetPageNavigator(PageNavigator* navigator); @@ -122,10 +129,6 @@ class BookmarkBarView : public views::View, // Returns the model. BookmarkModel* GetModel() { return model_; } - // Toggles whether the bookmark bar is shown only on the new tab page or on - // all tabs. - void ToggleWhenVisible(); - // Returns true if the bookmarks bar preference is set to 'always show', we // use this as a shorthand way of knowing what style of bar to draw (if the // pref is set to false but we're painting, then we must be on the new tab diff --git a/chrome/browser/views/bookmark_bubble_view.cc b/chrome/browser/views/bookmark_bubble_view.cc index 103df07..9136014 100644 --- a/chrome/browser/views/bookmark_bubble_view.cc +++ b/chrome/browser/views/bookmark_bubble_view.cc @@ -358,7 +358,8 @@ void BookmarkBubbleView::ShowEditor() { Close(); if (node) - BookmarkEditorView::Show(parent, profile_, NULL, node); + BookmarkEditorView::Show(parent, profile_, NULL, node, + BookmarkEditorView::SHOW_TREE); } void BookmarkBubbleView::SetNodeTitleFromTextField() { diff --git a/chrome/browser/views/bookmark_editor_view.cc b/chrome/browser/views/bookmark_editor_view.cc index 8eb7979..1179037 100644 --- a/chrome/browser/views/bookmark_editor_view.cc +++ b/chrome/browser/views/bookmark_editor_view.cc @@ -44,27 +44,34 @@ static const int kNewGroupButtonID = 1002; void BookmarkEditorView::Show(HWND parent_hwnd, Profile* profile, BookmarkNode* parent, - BookmarkNode* node) { + BookmarkNode* node, + Configuration configuration) { DCHECK(profile); - BookmarkEditorView* editor = new BookmarkEditorView(profile, parent, node); + BookmarkEditorView* editor = + new BookmarkEditorView(profile, parent, node, configuration); editor->Show(parent_hwnd); } BookmarkEditorView::BookmarkEditorView(Profile* profile, BookmarkNode* parent, - BookmarkNode* node) + BookmarkNode* node, + Configuration configuration) : profile_(profile), -#pragma warning(suppress: 4355) // Okay to pass "this" here. - new_group_button_( - l10n_util::GetString(IDS_BOOMARK_EDITOR_NEW_FOLDER_BUTTON)), + tree_view_(NULL), + new_group_button_(NULL), parent_(parent), node_(node), - running_menu_for_root_(false) { + running_menu_for_root_(false), + show_tree_(configuration == SHOW_TREE) { DCHECK(profile); Init(); } BookmarkEditorView::~BookmarkEditorView() { + // The tree model is deleted before the view. Reset the model otherwise the + // tree will reference a deleted model. + if (tree_view_) + tree_view_->SetModel(NULL); bb_model_->RemoveObserver(this); } @@ -96,7 +103,7 @@ bool BookmarkEditorView::Accept() { } bool BookmarkEditorView::AreAcceleratorsEnabled(DialogButton button) { - return !tree_view_.GetEditingNode(); + return !show_tree_ || !tree_view_->GetEditingNode(); } views::View* BookmarkEditorView::GetContentsView() { @@ -107,16 +114,23 @@ void BookmarkEditorView::Layout() { // Let the grid layout manager lay out most of the dialog... GetLayoutManager()->Layout(this); + if (!show_tree_) + return; + // Manually lay out the New Folder button in the same row as the OK/Cancel // buttons... gfx::Rect parent_bounds = GetParent()->GetLocalBounds(false); - gfx::Size prefsize = new_group_button_.GetPreferredSize(); - int button_y = parent_bounds.bottom() - prefsize.height() - kButtonVEdgeMargin; - new_group_button_.SetBounds(kPanelHorizMargin, button_y, prefsize.width(), + gfx::Size prefsize = new_group_button_->GetPreferredSize(); + int button_y = + parent_bounds.bottom() - prefsize.height() - kButtonVEdgeMargin; + new_group_button_->SetBounds(kPanelHorizMargin, button_y, prefsize.width(), prefsize.height()); } gfx::Size BookmarkEditorView::GetPreferredSize() { + if (!show_tree_) + return views::View::GetPreferredSize(); + return gfx::Size(views::Window::GetLocalizedContentsSize( IDS_EDITBOOKMARK_DIALOG_WIDTH_CHARS, IDS_EDITBOOKMARK_DIALOG_HEIGHT_LINES)); @@ -125,12 +139,12 @@ gfx::Size BookmarkEditorView::GetPreferredSize() { void BookmarkEditorView::ViewHierarchyChanged(bool is_add, views::View* parent, views::View* child) { - if (child == this) { + if (show_tree_ && child == this) { // Add and remove the New Folder button from the ClientView's hierarchy. if (is_add) { - parent->AddChildView(&new_group_button_); + parent->AddChildView(new_group_button_.get()); } else { - parent->RemoveChildView(&new_group_button_); + parent->RemoveChildView(new_group_button_.get()); } } } @@ -164,9 +178,9 @@ void BookmarkEditorView::ButtonPressed(NativeButton* sender) { } void BookmarkEditorView::ExecuteCommand(int id) { - DCHECK(tree_view_.GetSelectedNode()); + DCHECK(tree_view_->GetSelectedNode()); if (id == IDS_EDIT) { - tree_view_.StartEditing(tree_view_.GetSelectedNode()); + tree_view_->StartEditing(tree_view_->GetSelectedNode()); } else { DCHECK(id == IDS_BOOMARK_EDITOR_NEW_FOLDER_MENU_ITEM); NewGroup(); @@ -180,7 +194,7 @@ bool BookmarkEditorView::IsCommandEnabled(int id) const { void BookmarkEditorView::Show(HWND parent_hwnd) { views::Window::CreateChromeWindow(parent_hwnd, gfx::Rect(), this); UserInputChanged(); - if (bb_model_->IsLoaded()) + if (show_tree_ && bb_model_->IsLoaded()) ExpandAndSelect(); window()->Show(); // Select all the text in the name textfield. @@ -198,11 +212,11 @@ void BookmarkEditorView::ShowContextMenu(View* source, int x, int y, bool is_mouse_gesture) { - DCHECK(source == &tree_view_); - if (!tree_view_.GetSelectedNode()) + DCHECK(source == tree_view_); + if (!tree_view_->GetSelectedNode()) return; running_menu_for_root_ = - (tree_model_->GetParent(tree_view_.GetSelectedNode()) == + (tree_model_->GetParent(tree_view_->GetSelectedNode()) == tree_model_->GetRoot()); context_menu_.reset(new Menu(this, Menu::TOPLEFT, GetContainer()->GetHWND())); @@ -215,28 +229,32 @@ void BookmarkEditorView::ShowContextMenu(View* source, } void BookmarkEditorView::Init() { - tree_view_.SetContextMenuController(this); bb_model_ = profile_->GetBookmarkModel(); DCHECK(bb_model_); bb_model_->AddObserver(this); - tree_view_.SetRootShown(false); - // Tell View not to delete all Views declared by value. - tree_view_.SetParentOwned(false); - new_group_button_.SetParentOwned(false); url_tf_.SetParentOwned(false); title_tf_.SetParentOwned(false); - new_group_button_.SetEnabled(false); - new_group_button_.SetListener(this); - new_group_button_.SetID(kNewGroupButtonID); - title_tf_.SetText(node_ ? node_->GetTitle() : std::wstring()); title_tf_.SetController(this); url_tf_.SetText(node_ ? UTF8ToWide(node_->GetURL().spec()) : std::wstring()); url_tf_.SetController(this); + if (show_tree_) { + tree_view_ = new views::TreeView(); + new_group_button_.reset(new views::NativeButton( + l10n_util::GetString(IDS_BOOMARK_EDITOR_NEW_FOLDER_BUTTON))); + new_group_button_->SetParentOwned(false); + tree_view_->SetContextMenuController(this); + + tree_view_->SetRootShown(false); + new_group_button_->SetEnabled(false); + new_group_button_->SetListener(this); + new_group_button_->SetID(kNewGroupButtonID); + } + // Yummy layout code. const int labels_column_set_id = 0; const int single_column_view_set_id = 1; @@ -277,14 +295,15 @@ void BookmarkEditorView::Init() { new Label(l10n_util::GetString(IDS_BOOMARK_EDITOR_URL_LABEL))); layout->AddView(&url_tf_); - layout->AddPaddingRow(0, kRelatedControlVerticalSpacing); - - layout->StartRow(1, single_column_view_set_id); - layout->AddView(&tree_view_); + if (show_tree_) { + layout->AddPaddingRow(0, kRelatedControlVerticalSpacing); + layout->StartRow(1, single_column_view_set_id); + layout->AddView(tree_view_); + } layout->AddPaddingRow(0, kRelatedControlVerticalSpacing); - if (bb_model_->IsLoaded()) + if (!show_tree_ || bb_model_->IsLoaded()) Reset(); } @@ -304,8 +323,10 @@ void BookmarkEditorView::BookmarkNodeAdded(BookmarkModel* model, void BookmarkEditorView::BookmarkNodeRemoved(BookmarkModel* model, BookmarkNode* parent, - int index) { - if ((node_ && !node_->GetParent()) || (parent_ && !parent_->GetParent())) { + int index, + BookmarkNode* node) { + if ((node_ && node_->HasAncestor(node)) || + (parent_ && parent_->HasAncestor(node))) { // The node, or its parent was removed. Close the dialog. window()->Close(); } else { @@ -314,25 +335,30 @@ void BookmarkEditorView::BookmarkNodeRemoved(BookmarkModel* model, } void BookmarkEditorView::Reset() { + if (!show_tree_) { + if (GetParent()) + UserInputChanged(); + return; + } + + new_group_button_->SetEnabled(true); + // Do this first, otherwise when we invoke SetModel with the real one // tree_view will try to invoke something on the model we just deleted. - tree_view_.SetModel(NULL); + tree_view_->SetModel(NULL); EditorNode* root_node = CreateRootNode(); tree_model_.reset(new EditorTreeModel(root_node)); - tree_view_.SetModel(tree_model_.get()); - tree_view_.SetController(this); - - new_group_button_.SetEnabled(true); + tree_view_->SetModel(tree_model_.get()); + tree_view_->SetController(this); context_menu_.reset(); if (GetParent()) { ExpandAndSelect(); - UserInputChanged(); } else if (GetParent()) { - tree_view_.ExpandAll(); + tree_view_->ExpandAll(); } } GURL BookmarkEditorView::GetInputURL() const { @@ -356,13 +382,13 @@ void BookmarkEditorView::UserInputChanged() { void BookmarkEditorView::NewGroup() { // Create a new entry parented to the selected item, or the bookmark // bar if nothing is selected. - EditorNode* parent = tree_model_->AsNode(tree_view_.GetSelectedNode()); + EditorNode* parent = tree_model_->AsNode(tree_view_->GetSelectedNode()); if (!parent) { NOTREACHED(); return; } - tree_view_.StartEditing(AddNewGroup(parent)); + tree_view_->StartEditing(AddNewGroup(parent)); } BookmarkEditorView::EditorNode* BookmarkEditorView::AddNewGroup( @@ -376,7 +402,7 @@ BookmarkEditorView::EditorNode* BookmarkEditorView::AddNewGroup( } void BookmarkEditorView::ExpandAndSelect() { - tree_view_.ExpandAll(); + tree_view_->ExpandAll(); BookmarkNode* to_select = node_ ? node_->GetParent() : parent_; int group_id_to_select = to_select->id(); @@ -386,7 +412,7 @@ void BookmarkEditorView::ExpandAndSelect() { if (!b_node) b_node = tree_model_->GetRoot()->GetChild(0); // Bookmark bar node. - tree_view_.SetSelectedNode(b_node); + tree_view_->SetSelectedNode(b_node); } BookmarkEditorView::EditorNode* BookmarkEditorView::CreateRootNode() { @@ -429,15 +455,17 @@ BookmarkEditorView::EditorNode* BookmarkEditorView::FindNodeWithID( void BookmarkEditorView::ApplyEdits() { DCHECK(bb_model_->IsLoaded()); - if (!tree_view_.GetSelectedNode()) { + EditorNode* parent = show_tree_ ? + tree_model_->AsNode(tree_view_->GetSelectedNode()) : NULL; + if (show_tree_ && !parent) { NOTREACHED(); return; } - ApplyEdits(tree_model_->AsNode(tree_view_.GetSelectedNode())); + ApplyEdits(parent); } void BookmarkEditorView::ApplyEdits(EditorNode* parent) { - DCHECK(parent); + DCHECK(!show_tree_ || parent); // We're going to apply edits to the bookmark bar model, which will call us // back. Normally when a structural edit occurs we reset the tree model. @@ -450,6 +478,26 @@ void BookmarkEditorView::ApplyEdits(EditorNode* parent) { BookmarkNode* old_parent = node_ ? node_->GetParent() : NULL; const int old_index = old_parent ? old_parent->IndexOfChild(node_) : -1; + if (!show_tree_ ) { + if (!node_) { + bb_model_->AddURL(parent_, parent_->GetChildCount(), new_title, new_url); + return; + } + // If we're not showing the tree we only need to modify the node. + if (old_index == -1) { + NOTREACHED(); + return; + } + if (new_url != node_->GetURL()) { + bb_model_->AddURLWithCreationTime(old_parent, old_index, new_title, + new_url, node_->date_added()); + bb_model_->Remove(old_parent, old_index + 1); + } else { + bb_model_->SetTitle(node_, new_title); + } + return; + } + // Create the new groups and update the titles. BookmarkNode* new_parent = NULL; ApplyNameChangesAndCreateNewGroups( @@ -523,4 +571,3 @@ void BookmarkEditorView::ApplyNameChangesAndCreateNewGroups( parent_b_node, parent_bb_node); } } - diff --git a/chrome/browser/views/bookmark_editor_view.h b/chrome/browser/views/bookmark_editor_view.h index 796f041..4e6e317 100644 --- a/chrome/browser/views/bookmark_editor_view.h +++ b/chrome/browser/views/bookmark_editor_view.h @@ -47,17 +47,28 @@ class BookmarkEditorView : public views::View, FRIEND_TEST(BookmarkEditorViewTest, ModelsMatch); FRIEND_TEST(BookmarkEditorViewTest, MoveToNewParent); FRIEND_TEST(BookmarkEditorViewTest, NewURL); + FRIEND_TEST(BookmarkEditorViewTest, ChangeURLNoTree); + FRIEND_TEST(BookmarkEditorViewTest, ChangeTitleNoTree); public: + // An enumeration of the possible configurations offered. + enum Configuration { + SHOW_TREE, + NO_TREE + }; + // Shows the BookmarkEditorView editing |node|. If |node| is NULL a new entry - // is created initially parented to |parent|. + // is created initially parented to |parent|. If |show_tree| is false the + // tree is not shown. static void Show(HWND parent_window, Profile* profile, BookmarkNode* parent, - BookmarkNode* node); + BookmarkNode* node, + Configuration configuration); BookmarkEditorView(Profile* profile, BookmarkNode* parent, - BookmarkNode* node); + BookmarkNode* node, + Configuration configuration); virtual ~BookmarkEditorView(); @@ -148,7 +159,8 @@ class BookmarkEditorView : public views::View, int index); virtual void BookmarkNodeRemoved(BookmarkModel* model, BookmarkNode* parent, - int index); + int index, + BookmarkNode* node); virtual void BookmarkNodeChanged(BookmarkModel* model, BookmarkNode* node) {} virtual void BookmarkNodeFavIconLoaded(BookmarkModel* model, @@ -222,10 +234,10 @@ class BookmarkEditorView : public views::View, scoped_ptr<EditorTreeModel> tree_model_; // Displays star groups. - views::TreeView tree_view_; + views::TreeView* tree_view_; // Used to create a new group. - views::NativeButton new_group_button_; + scoped_ptr<views::NativeButton> new_group_button_; // Used for editing the URL. views::TextField url_tf_; @@ -249,6 +261,9 @@ class BookmarkEditorView : public views::View, // nodes. bool running_menu_for_root_; + // Is the tree shown? + bool show_tree_; + DISALLOW_COPY_AND_ASSIGN(BookmarkEditorView); }; diff --git a/chrome/browser/views/bookmark_editor_view_unittest.cc b/chrome/browser/views/bookmark_editor_view_unittest.cc index 0a5ed68..24fb05e 100644 --- a/chrome/browser/views/bookmark_editor_view_unittest.cc +++ b/chrome/browser/views/bookmark_editor_view_unittest.cc @@ -79,7 +79,8 @@ class BookmarkEditorViewTest : public testing::Test { // Makes sure the tree model matches that of the bookmark bar model. TEST_F(BookmarkEditorViewTest, ModelsMatch) { - BookmarkEditorView editor(profile_.get(), NULL, NULL); + BookmarkEditorView editor(profile_.get(), NULL, NULL, + BookmarkEditorView::SHOW_TREE); BookmarkEditorView::EditorNode* editor_root = editor.tree_model_->GetRoot(); // The root should have two children, one for the bookmark bar node, // the other for the 'other bookmarks' folder. @@ -103,7 +104,8 @@ TEST_F(BookmarkEditorViewTest, ModelsMatch) { // Changes the title and makes sure parent/visual order doesn't change. TEST_F(BookmarkEditorViewTest, EditTitleKeepsPosition) { - BookmarkEditorView editor(profile_.get(), NULL, GetNode("a")); + BookmarkEditorView editor(profile_.get(), NULL, GetNode("a"), + BookmarkEditorView::SHOW_TREE); editor.title_tf_.SetText(L"new_a"); editor.ApplyEdits(editor.tree_model_->GetRoot()->GetChild(0)); @@ -118,7 +120,8 @@ TEST_F(BookmarkEditorViewTest, EditTitleKeepsPosition) { TEST_F(BookmarkEditorViewTest, EditURLKeepsPosition) { Time node_time = Time::Now() + TimeDelta::FromDays(2); GetNode("a")->date_added_ = node_time; - BookmarkEditorView editor(profile_.get(), NULL, GetNode("a")); + BookmarkEditorView editor(profile_.get(), NULL, GetNode("a"), + BookmarkEditorView::SHOW_TREE); editor.url_tf_.SetText(UTF8ToWide(GURL(base_path() + "new_a").spec())); @@ -133,7 +136,8 @@ TEST_F(BookmarkEditorViewTest, EditURLKeepsPosition) { // Moves 'a' to be a child of the other node. TEST_F(BookmarkEditorViewTest, ChangeParent) { - BookmarkEditorView editor(profile_.get(), NULL, GetNode("a")); + BookmarkEditorView editor(profile_.get(), NULL, GetNode("a"), + BookmarkEditorView::SHOW_TREE); editor.ApplyEdits(editor.tree_model_->GetRoot()->GetChild(1)); @@ -146,7 +150,8 @@ TEST_F(BookmarkEditorViewTest, ChangeParent) { TEST_F(BookmarkEditorViewTest, ChangeParentAndURL) { Time node_time = Time::Now() + TimeDelta::FromDays(2); GetNode("a")->date_added_ = node_time; - BookmarkEditorView editor(profile_.get(), NULL, GetNode("a")); + BookmarkEditorView editor(profile_.get(), NULL, GetNode("a"), + BookmarkEditorView::SHOW_TREE); editor.url_tf_.SetText(UTF8ToWide(GURL(base_path() + "new_a").spec())); @@ -160,7 +165,8 @@ TEST_F(BookmarkEditorViewTest, ChangeParentAndURL) { // Creates a new folder and moves a node to it. TEST_F(BookmarkEditorViewTest, MoveToNewParent) { - BookmarkEditorView editor(profile_.get(), NULL, GetNode("a")); + BookmarkEditorView editor(profile_.get(), NULL, GetNode("a"), + BookmarkEditorView::SHOW_TREE); // Create two nodes: "F21" as a child of "F2" and "F211" as a child of "F21". BookmarkEditorView::EditorNode* f2 = @@ -191,7 +197,8 @@ TEST_F(BookmarkEditorViewTest, MoveToNewParent) { // Brings up the editor, creating a new URL on the bookmark bar. TEST_F(BookmarkEditorViewTest, NewURL) { - BookmarkEditorView editor(profile_.get(), NULL, NULL); + BookmarkEditorView editor(profile_.get(), NULL, NULL, + BookmarkEditorView::SHOW_TREE); editor.url_tf_.SetText(UTF8ToWide(GURL(base_path() + "a").spec())); editor.title_tf_.SetText(L"new_a"); @@ -206,3 +213,41 @@ TEST_F(BookmarkEditorViewTest, NewURL) { EXPECT_EQ(L"new_a", new_node->GetTitle()); EXPECT_TRUE(GURL(base_path() + "a") == new_node->GetURL()); } + +// Brings up the editor with no tree and modifies the url. +TEST_F(BookmarkEditorViewTest, ChangeURLNoTree) { + BookmarkEditorView editor(profile_.get(), NULL, + model_->other_node()->GetChild(0), + BookmarkEditorView::NO_TREE); + + editor.url_tf_.SetText(UTF8ToWide(GURL(base_path() + "a").spec())); + editor.title_tf_.SetText(L"new_a"); + + editor.ApplyEdits(NULL); + + BookmarkNode* other_node = profile_->GetBookmarkModel()->other_node(); + ASSERT_EQ(2, other_node->GetChildCount()); + + BookmarkNode* new_node = other_node->GetChild(0); + + EXPECT_EQ(L"new_a", new_node->GetTitle()); + EXPECT_TRUE(GURL(base_path() + "a") == new_node->GetURL()); +} + +// Brings up the editor with no tree and modifies only the title. +TEST_F(BookmarkEditorViewTest, ChangeTitleNoTree) { + BookmarkEditorView editor(profile_.get(), NULL, + model_->other_node()->GetChild(0), + BookmarkEditorView::NO_TREE); + + editor.title_tf_.SetText(L"new_a"); + + editor.ApplyEdits(NULL); + + BookmarkNode* other_node = profile_->GetBookmarkModel()->other_node(); + ASSERT_EQ(2, other_node->GetChildCount()); + + BookmarkNode* new_node = other_node->GetChild(0); + + EXPECT_EQ(L"new_a", new_node->GetTitle()); +} diff --git a/chrome/browser/views/bookmark_folder_tree_view.cc b/chrome/browser/views/bookmark_folder_tree_view.cc new file mode 100644 index 0000000..c068103 --- /dev/null +++ b/chrome/browser/views/bookmark_folder_tree_view.cc @@ -0,0 +1,321 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/views/bookmark_folder_tree_view.h" + +#include "base/base_drag_source.h" +#include "chrome/browser/bookmarks/bookmark_utils.h" +#include "chrome/browser/bookmarks/bookmark_model.h" +#include "chrome/browser/bookmarks/bookmark_folder_tree_model.h" +#include "chrome/browser/profile.h" +#include "chrome/common/drag_drop_types.h" +#include "chrome/common/os_exchange_data.h" +#include "chrome/views/chrome_menu.h" + +#include "generated_resources.h" + +BookmarkFolderTreeView::BookmarkFolderTreeView(Profile* profile, + BookmarkFolderTreeModel* model) + : views::TreeView(), + profile_(profile), + is_dragging_(false) { + SetModel(model); + SetEditable(false); + SetRootShown(false); + set_drag_enabled(true); +} + +bool BookmarkFolderTreeView::CanDrop(const OSExchangeData& data) { + if (!profile_->GetBookmarkModel()->IsLoaded()) + return false; + + drop_info_.reset(new DropInfo()); + if (!drop_info_->drag_data.Read(data)) + return false; + + // See if there are any urls being dropped. + for (size_t i = 0; i < drop_info_->drag_data.size(); ++i) { + if (drop_info_->drag_data.elements[0].is_url) { + drop_info_->only_folders = false; + break; + } + } + + return true; +} + +void BookmarkFolderTreeView::OnDragEntered( + const views::DropTargetEvent& event) { +} + +int BookmarkFolderTreeView::OnDragUpdated(const views::DropTargetEvent& event) { + int drop_index; + bool drop_on; + FolderNode* drop_parent = + CalculateDropParent(event.y(), drop_info_->only_folders, &drop_index, + &drop_on); + drop_info_->drop_operation = + CalculateDropOperation(event, drop_parent, drop_index, drop_on); + + if (drop_info_->drop_operation == DragDropTypes::DRAG_NONE) { + drop_parent = NULL; + drop_index = -1; + drop_on = false; + } + + SetDropParent(drop_parent, drop_index, drop_on); + + return drop_info_->drop_operation; +} + +void BookmarkFolderTreeView::OnDragExited() { + SetDropParent(NULL, -1, false); + + drop_info_.reset(); +} + +int BookmarkFolderTreeView::OnPerformDrop(const views::DropTargetEvent& event) { + OnPerformDropImpl(); + + int drop_operation = drop_info_->drop_operation; + SetDropParent(NULL, -1, false); + drop_info_.reset(); + return drop_operation; +} + +BookmarkNode* BookmarkFolderTreeView::GetSelectedBookmarkNode() { + views::TreeModelNode* selected_node = GetSelectedNode(); + if (!selected_node) + return NULL; + return TreeNodeAsBookmarkNode(folder_model()->AsNode(selected_node)); +} + +LRESULT BookmarkFolderTreeView::OnNotify(int w_param, LPNMHDR l_param) { + switch (l_param->code) { + case TVN_BEGINDRAG: { + HTREEITEM tree_item = + reinterpret_cast<LPNMTREEVIEW>(l_param)->itemNew.hItem; + FolderNode* folder_node = + folder_model()->AsNode(GetNodeForTreeItem(tree_item)); + BeginDrag(TreeNodeAsBookmarkNode(folder_node)); + return 0; // Return value ignored. + } + } + + return TreeView::OnNotify(w_param, l_param); +} + +void BookmarkFolderTreeView::BeginDrag(BookmarkNode* node) { + BookmarkModel* model = profile_->GetBookmarkModel(); + // Only allow the drag if the user has selected a node of type bookmark and it + // isn't the bookmark bar or other bookmarks folders. + if (!node || node == model->other_node() || + node == model->GetBookmarkBarNode()) { + return; + } + + std::vector<BookmarkNode*> nodes_to_drag; + nodes_to_drag.push_back(node); + + scoped_refptr<OSExchangeData> data = new OSExchangeData; + BookmarkDragData(nodes_to_drag).Write(profile_, data); + scoped_refptr<BaseDragSource> drag_source(new BaseDragSource); + DWORD effects; + is_dragging_ = true; + DoDragDrop(data, drag_source, + DROPEFFECT_LINK | DROPEFFECT_COPY | DROPEFFECT_MOVE, &effects); + is_dragging_ = false; +} + +FolderNode* BookmarkFolderTreeView::CalculateDropParent(int y, + bool only_folders, + int* drop_index, + bool* drop_on) { + *drop_on = false; + *drop_index = -1; + HWND hwnd = GetNativeControlHWND(); + HTREEITEM item = TreeView_GetFirstVisible(hwnd); + while (item) { + RECT bounds; + TreeView_GetItemRect(hwnd, item, &bounds, TRUE); + if (y < bounds.bottom) { + views::TreeModelNode* model_node = GetNodeForTreeItem(item); + if (folder_model()->GetNodeType(model_node) != + BookmarkFolderTreeModel::BOOKMARK) { + // Only allow drops on bookmark nodes. + return NULL; + } + + FolderNode* node = folder_model()->AsNode(model_node); + if (!only_folders || !node->GetParent() || + !node->GetParent()->GetParent()) { + // If some of the elements being dropped are urls, then we only allow + // dropping on a folder. Similarly you can't drop between the + // bookmark bar and other folder nodes. + *drop_on = true; + *drop_index = node->GetChildCount(); + return node; + } + + // Drop contains all folders, allow them to be dropped between + // folders. + if (y < bounds.top + views::MenuItemView::kDropBetweenPixels) { + *drop_index = node->GetParent()->IndexOfChild(node); + return node->GetParent(); + } + if (y >= bounds.bottom - views::MenuItemView::kDropBetweenPixels) { + if (IsExpanded(node) && folder_model()->GetChildCount(node) > 0) { + // The node is expanded and has children, treat the drop as occurring + // as the first child. This is done to avoid the selection highlight + // dancing around when dragging over expanded folders. Without this + // the highlight jumps past the last expanded child of node. + *drop_index = 0; + return node; + } + *drop_index = node->GetParent()->IndexOfChild(node) + 1; + return node->GetParent(); + } + *drop_on = true; + *drop_index = node->GetChildCount(); + return node; + } + item = TreeView_GetNextVisible(hwnd, item); + } + return NULL; +} + +int BookmarkFolderTreeView::CalculateDropOperation( + const views::DropTargetEvent& event, + FolderNode* drop_parent, + int drop_index, + bool drop_on) { + if (!drop_parent) + return DragDropTypes::DRAG_NONE; + + if (drop_info_->drag_data.IsFromProfile(profile_)) { + int bookmark_model_drop_index = + FolderIndexToBookmarkIndex(drop_parent, drop_index, drop_on); + if (!bookmark_utils::IsValidDropLocation( + profile_, drop_info_->drag_data, + TreeNodeAsBookmarkNode(drop_parent), + bookmark_model_drop_index)) { + return DragDropTypes::DRAG_NONE; + } + + // Data from the same profile. Prefer move, but do copy if the user wants + // that. + if (event.IsControlDown()) + return DragDropTypes::DRAG_COPY; + + return DragDropTypes::DRAG_MOVE; + } + // We're going to copy, but return an operation compatible with the source + // operations so that the user can drop. + return bookmark_utils::PreferredDropOperation( + event, DragDropTypes::DRAG_COPY | DragDropTypes::DRAG_LINK); +} + +void BookmarkFolderTreeView::OnPerformDropImpl() { + BookmarkNode* parent_node = TreeNodeAsBookmarkNode(drop_info_->drop_parent); + int drop_index = FolderIndexToBookmarkIndex( + drop_info_->drop_parent, drop_info_->drop_index, drop_info_->drop_on); + BookmarkModel* model = profile_->GetBookmarkModel(); + // If the data is not from this profile we return an operation compatible + // with the source. As such, we need to need to check the data here too. + if (!drop_info_->drag_data.IsFromProfile(profile_) || + drop_info_->drop_operation == DragDropTypes::DRAG_COPY) { + bookmark_utils::CloneDragData(model, drop_info_->drag_data.elements, + parent_node, drop_index); + return; + } + + // else, move. + std::vector<BookmarkNode*> nodes = drop_info_->drag_data.GetNodes(profile_); + if (nodes.empty()) + return; + + for (size_t i = 0; i < nodes.size(); ++i) { + model->Move(nodes[i], parent_node, drop_index); + // Reset the drop_index, just in case the index didn't really change. + drop_index = parent_node->IndexOfChild(nodes[i]) + 1; + if (nodes[i]->is_folder()) { + Expand(folder_model()->GetFolderNodeForBookmarkNode(nodes[i])); + } + } + + if (is_dragging_ && nodes[0]->is_folder()) { + // We're the drag source. Select the node that was moved. + SetSelectedNode(folder_model()->GetFolderNodeForBookmarkNode(nodes[0])); + } +} + +void BookmarkFolderTreeView::SetDropParent(FolderNode* node, + int drop_index, + bool drop_on) { + if (drop_info_->drop_parent == node && + drop_info_->drop_index == drop_index && + drop_info_->drop_on == drop_on) { + return; + } + // Remove the indicator over the previous location. + if (drop_info_->drop_on) { + HTREEITEM item = GetTreeItemForNode(drop_info_->drop_parent); + if (item) + TreeView_SetItemState(GetNativeControlHWND(), item, 0, TVIS_DROPHILITED); + } else if (drop_info_->drop_index != -1) { + TreeView_SetInsertMark(GetNativeControlHWND(), NULL, FALSE); + } + + drop_info_->drop_parent = node; + drop_info_->drop_index = drop_index; + drop_info_->drop_on = drop_on; + + // And show the new indicator. + if (drop_info_->drop_on) { + HTREEITEM item = GetTreeItemForNode(drop_info_->drop_parent); + if (item) { + TreeView_SetItemState(GetNativeControlHWND(), item, TVIS_DROPHILITED, + TVIS_DROPHILITED); + } + } else if (drop_info_->drop_index != -1) { + BOOL after = FALSE; + if (folder_model()->GetChildCount(drop_info_->drop_parent) == + drop_info_->drop_index) { + after = TRUE; + node = + folder_model()->GetChild(drop_info_->drop_parent, + drop_info_->drop_index - 1); + } else { + node = + folder_model()->GetChild(drop_info_->drop_parent, + drop_info_->drop_index); + } + HTREEITEM item = GetTreeItemForNode(node); + if (item) + TreeView_SetInsertMark(GetNativeControlHWND(), item, after); + } +} + +BookmarkFolderTreeModel* BookmarkFolderTreeView::folder_model() const { + return static_cast<BookmarkFolderTreeModel*>(model()); +} + +BookmarkNode* BookmarkFolderTreeView::TreeNodeAsBookmarkNode(FolderNode* node) { + return folder_model()->TreeNodeAsBookmarkNode(node); +} + +int BookmarkFolderTreeView::FolderIndexToBookmarkIndex(FolderNode* node, + int index, + bool drop_on) { + BookmarkNode* parent_node = TreeNodeAsBookmarkNode(node); + if (drop_on || index == node->GetChildCount()) + return parent_node->GetChildCount(); + + if (index != 0) { + return parent_node->IndexOfChild( + TreeNodeAsBookmarkNode(node->GetChild(index))); + } + + return 0; +} diff --git a/chrome/browser/views/bookmark_folder_tree_view.h b/chrome/browser/views/bookmark_folder_tree_view.h new file mode 100644 index 0000000..ae12b2c --- /dev/null +++ b/chrome/browser/views/bookmark_folder_tree_view.h @@ -0,0 +1,117 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_VIEWS_BOOKMARK_FOLDER_TREE_VIEW_H_ +#define CHROME_BROWSER_VIEWS_BOOKMARK_FOLDER_TREE_VIEW_H_ + +#include "chrome/browser/bookmarks/bookmark_drag_data.h" +#include "chrome/browser/bookmarks/bookmark_folder_tree_model.h" +#include "chrome/views/tree_view.h" + +class BookmarkModel; +class BookmarkNode; +class OSExchangeData; +class Profile; + +// BookmarkFolderTreeView is used to show the contents of a +// BookmarkFolderTreeModel and provides drag and drop support. +class BookmarkFolderTreeView : public views::TreeView { + public: + BookmarkFolderTreeView(Profile* profile, BookmarkFolderTreeModel* model); + + // Drag and drop methods. + virtual bool CanDrop(const OSExchangeData& data); + virtual void OnDragEntered(const views::DropTargetEvent& event); + virtual int OnDragUpdated(const views::DropTargetEvent& event); + virtual void OnDragExited(); + virtual int OnPerformDrop(const views::DropTargetEvent& event); + + // Returns the selected node as a BookmarkNode. This returns NULL if the + // selected node is not of type BookmarkFolderTreeModel::BOOKMARK or + // nothing is selected. + BookmarkNode* GetSelectedBookmarkNode(); + + protected: + // Overriden to start a drag. + virtual LRESULT OnNotify(int w_param, LPNMHDR l_param); + + private: + // Provides information used during a drop. + struct DropInfo { + DropInfo() + : drop_parent(NULL), + only_folders(true), + drop_index(-1), + drop_operation(0), + drop_on(false) {} + + // Parent the mouse is over. + FolderNode* drop_parent; + + // Drag data. + BookmarkDragData drag_data; + + // Does drag_data consists of folders only. + bool only_folders; + + // If drop_on is false, this is the index to add the child. + // WARNING: this index is in terms of the BookmarkFolderTreeModel, which is + // not the same as the BookmarkModel. + int drop_index; + + // Operation for the drop. + int drop_operation; + + // Is the user dropping on drop_parent? If false, the mouse is positioned + // such that the drop should insert the data at position drop_index in + // drop_parent. + bool drop_on; + }; + + // Starts a drag operation for the specified node. + void BeginDrag(BookmarkNode* node); + + // Calculates the drop parent. Returns NULL if not over a valid drop + // location. See DropInfos documentation for a description of |drop_index| + // and |drop_on|. + FolderNode* CalculateDropParent(int y, + bool only_folders, + int* drop_index, + bool* drop_on); + + // Determines the appropriate drop operation. This returns DRAG_NONE + // if the location is not valid. + int CalculateDropOperation(const views::DropTargetEvent& event, + FolderNode* drop_parent, + int drop_index, + bool drop_on); + + // Performs the drop operation. + void OnPerformDropImpl(); + + // Sets the parent of the drop operation. + void SetDropParent(FolderNode* node, int drop_index, bool drop_on); + + // Returns the model as a BookmarkFolderTreeModel. + BookmarkFolderTreeModel* folder_model() const; + + // Converts FolderNode into a BookmarkNode. + BookmarkNode* TreeNodeAsBookmarkNode(FolderNode* node); + + // Converts an index in terms of the BookmarkFolderTreeModel to an index + // in terms of the BookmarkModel. + int FolderIndexToBookmarkIndex(FolderNode* node, int index, bool drop_on); + + Profile* profile_; + + // Non-null during a drop. + scoped_ptr<DropInfo> drop_info_; + + // Did we originate the drag? + bool is_dragging_; + + DISALLOW_COPY_AND_ASSIGN(BookmarkFolderTreeView); +}; + +#endif // CHROME_BROWSER_VIEWS_BOOKMARK_FOLDER_TREE_VIEW_H_ diff --git a/chrome/browser/views/bookmark_manager_view.cc b/chrome/browser/views/bookmark_manager_view.cc new file mode 100644 index 0000000..050323a --- /dev/null +++ b/chrome/browser/views/bookmark_manager_view.cc @@ -0,0 +1,383 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/views/bookmark_manager_view.h" + +#include <algorithm> + +#include "base/gfx/skia_utils.h" +#include "chrome/app/locales/locale_settings.h" +#include "chrome/browser/bookmarks/bookmark_context_menu.h" +#include "chrome/browser/bookmarks/bookmark_folder_tree_model.h" +#include "chrome/browser/bookmarks/bookmark_model.h" +#include "chrome/browser/bookmarks/bookmark_table_model.h" +#include "chrome/browser/bookmarks/bookmark_utils.h" +#include "chrome/browser/browser_list.h" +#include "chrome/browser/browser_process.h" +#include "chrome/browser/profile.h" +#include "chrome/browser/views/bookmark_editor_view.h" +#include "chrome/browser/views/bookmark_folder_tree_view.h" +#include "chrome/browser/views/bookmark_table_view.h" +#include "chrome/browser/views/standard_layout.h" +#include "chrome/common/gfx/chrome_canvas.h" +#include "chrome/common/gfx/color_utils.h" +#include "chrome/common/pref_names.h" +#include "chrome/common/pref_service.h" +#include "chrome/views/container_win.h" +#include "chrome/views/grid_layout.h" +#include "chrome/views/single_split_view.h" +#include "chrome/views/window.h" + +#include "generated_resources.h" + +// If non-null, there is an open editor and this is the window it is contained +// in it. +static views::Window* open_window = NULL; +// And this is the manager contained in it. +static BookmarkManagerView* manager = NULL; + +// Delay, in ms, between when the user types and when we run the search. +static const int kSearchDelayMS = 200; + +BookmarkManagerView::BookmarkManagerView(Profile* profile) + : profile_(profile->GetOriginalProfile()), + table_view_(NULL), + tree_view_(NULL), + search_factory_(this) { + search_tf_ = new views::TextField(); + search_tf_->set_default_width_in_chars(40); + + table_view_ = new BookmarkTableView(profile_, NULL); + table_view_->SetObserver(this); + table_view_->SetContextMenuController(this); + + tree_view_ = new BookmarkFolderTreeView(profile_, NULL); + tree_view_->SetController(this); + tree_view_->SetContextMenuController(this); + + views::SingleSplitView* split_view = + new views::SingleSplitView(tree_view_, table_view_); + + views::GridLayout* layout = new views::GridLayout(this); + SetLayoutManager(layout); + const int search_cs_id = 1; + const int split_cs_id = 2; + layout->SetInsets(kPanelVertMargin, 0, 0, 0); + views::ColumnSet* column_set = layout->AddColumnSet(search_cs_id); + column_set->AddColumn(views::GridLayout::TRAILING, views::GridLayout::CENTER, + 1, views::GridLayout::USE_PREF, 0, 0); + column_set->AddPaddingColumn(0, kButtonHEdgeMargin); + + column_set = layout->AddColumnSet(split_cs_id); + column_set->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL, 1, + views::GridLayout::USE_PREF, 0, 0); + + layout->StartRow(0, search_cs_id); + layout->AddView(search_tf_); + + layout->AddPaddingRow(0, kRelatedControlVerticalSpacing); + + layout->StartRow(1, split_cs_id); + layout->AddView(split_view); + + BookmarkModel* bookmark_model = profile_->GetBookmarkModel(); + if (!bookmark_model->IsLoaded()) + bookmark_model->AddObserver(this); +} + +BookmarkManagerView::~BookmarkManagerView() { + if (!GetBookmarkModel()->IsLoaded()) { + GetBookmarkModel()->RemoveObserver(this); + } else { + // The models are deleted before the views. Make sure we set the models of + // the views to NULL so that they aren't left holding a reference to a + // deleted model. + table_view_->SetModel(NULL); + tree_view_->SetModel(NULL); + } + manager = NULL; + open_window = NULL; +} + +// static +void BookmarkManagerView::RegisterPrefs(PrefService* prefs) { + prefs->RegisterDictionaryPref(prefs::kBookmarkManagerPlacement); +} + +// static +void BookmarkManagerView::Show(Profile* profile) { + if (!profile->GetBookmarkModel()) + return; + + if (open_window != NULL) { + open_window->MoveToFront(true); + return; + } + + // Both of these will be deleted when the dialog closes. + manager = new BookmarkManagerView(profile); + + // Create the window. + open_window = views::Window::CreateChromeWindow(NULL, gfx::Rect(), manager); + // Let the manager know it's parented. + manager->PrepareForShow(); + // And show it. + open_window->Show(); +} + +// static +BookmarkManagerView* BookmarkManagerView::current() { + return manager; +} + +void BookmarkManagerView::SelectInTree(BookmarkNode* node) { + if (!node) + return; + + BookmarkNode* parent = node->is_url() ? node->GetParent() : node; + FolderNode* folder_node = tree_model_->GetFolderNodeForBookmarkNode(parent); + if (!folder_node) { + NOTREACHED(); + return; + } + + tree_view_->SetSelectedNode(folder_node); + + if (node->is_url()) { + int index = table_model_->IndexOfNode(node); + if (index != -1) + table_view_->Select(index); + } +} + + +std::vector<BookmarkNode*> BookmarkManagerView::GetSelectedTableNodes() { + std::vector<BookmarkNode*> nodes; + for (views::TableView::iterator i = table_view_->SelectionBegin(); + i != table_view_->SelectionEnd(); ++i) { + nodes.push_back(table_model_->GetNodeForRow(*i)); + } + // TableViews iterator iterates in reverse order. Reverse the nodes so they + // are opened in visual order. + std::reverse(nodes.begin(), nodes.end()); + return nodes; +} + +void BookmarkManagerView::PaintBackground(ChromeCanvas* canvas) { + canvas->drawColor(color_utils::GetSysSkColor(COLOR_3DFACE), + SkPorterDuff::kSrc_Mode); +} + +gfx::Size BookmarkManagerView::GetPreferredSize() { + return gfx::Size(views::Window::GetLocalizedContentsSize( + IDS_BOOKMARK_MANAGER_DIALOG_WIDTH_CHARS, + IDS_BOOKMARK_MANAGER_DIALOG_HEIGHT_LINES)); +} + +std::wstring BookmarkManagerView::GetWindowTitle() const { + return l10n_util::GetString(IDS_BOOKMARK_MANAGER_TITLE); +} + +void BookmarkManagerView::SaveWindowPosition(const CRect& bounds, + bool maximized, + bool always_on_top) { + window()->SaveWindowPositionToPrefService(g_browser_process->local_state(), + prefs::kBookmarkManagerPlacement, + bounds, maximized, always_on_top); +} + +bool BookmarkManagerView::RestoreWindowPosition(CRect* bounds, + bool* maximized, + bool* always_on_top) { + return window()->RestoreWindowPositionFromPrefService( + g_browser_process->local_state(), + prefs::kBookmarkManagerPlacement, + bounds, maximized, always_on_top); +} + +void BookmarkManagerView::OnDoubleClick() { + std::vector<BookmarkNode*> nodes = GetSelectedTableNodes(); + if (nodes.empty()) + return; + if (nodes.size() == 1 && nodes[0]->is_folder()) { + // Double click on a folder descends into the folder. + SelectInTree(nodes[0]); + return; + } + // TODO(sky): OnDoubleClick needs a handle to the current mouse event so that + // we can use + // event_utils::DispositionFromEventFlags(sender->mouse_event_flags()) . + bookmark_utils::OpenAll( + GetContainer()->GetHWND(), profile_, NULL, nodes, CURRENT_TAB); +} + +void BookmarkManagerView::OnTableViewDelete(views::TableView* table) { + std::vector<BookmarkNode*> nodes = GetSelectedTableNodes(); + if (nodes.empty()) + return; + for (size_t i = 0; i < nodes.size(); ++i) { + GetBookmarkModel()->Remove(nodes[i]->GetParent(), + nodes[i]->GetParent()->IndexOfChild(nodes[i])); + } +} + +void BookmarkManagerView::OnTreeViewSelectionChanged( + views::TreeView* tree_view) { + views::TreeModelNode* node = tree_view_->GetSelectedNode(); + + BookmarkTableModel* new_table_model = NULL; + BookmarkNode* table_parent_node = NULL; + + if (node) { + switch (tree_model_->GetNodeType(node)) { + case BookmarkFolderTreeModel::BOOKMARK: + table_parent_node = tree_model_->TreeNodeAsBookmarkNode(node); + new_table_model = + BookmarkTableModel::CreateBookmarkTableModelForFolder( + profile_->GetBookmarkModel(), + table_parent_node); + break; + + case BookmarkFolderTreeModel::RECENTLY_BOOKMARKED: + new_table_model = BookmarkTableModel::CreateRecentlyBookmarkedModel( + profile_->GetBookmarkModel()); + break; + + case BookmarkFolderTreeModel::SEARCH: + search_factory_.RevokeAll(); + new_table_model = CreateSearchTableModel(); + break; + + default: + NOTREACHED(); + break; + } + } + + SetTableModel(new_table_model, table_parent_node); +} + +void BookmarkManagerView::Loaded(BookmarkModel* model) { + model->RemoveObserver(this); + LoadedImpl(); +} + +void BookmarkManagerView::ContentsChanged(views::TextField* sender, + const std::wstring& new_contents) { + search_factory_.RevokeAll(); + MessageLoop::current()->PostDelayedTask(FROM_HERE, + search_factory_.NewRunnableMethod(&BookmarkManagerView::PerformSearch), + kSearchDelayMS); +} + +void BookmarkManagerView::HandleKeystroke(views::TextField* sender, + UINT message, TCHAR key, + UINT repeat_count, + UINT flags) { + if (key == VK_RETURN) { + PerformSearch(); + search_tf_->SelectAll(); + } +} + +void BookmarkManagerView::ShowContextMenu(views::View* source, + int x, + int y, + bool is_mouse_gesture) { + if (!GetBookmarkModel()->IsLoaded()) + return; + + if (source == table_view_) { + std::vector<BookmarkNode*> nodes = GetSelectedTableNodes(); + if (nodes.empty()) + return; + + BookmarkNode* parent = tree_view_->GetSelectedBookmarkNode(); + BookmarkContextMenu menu(GetContainer()->GetHWND(), profile_, NULL, NULL, + parent, nodes, + BookmarkContextMenu::BOOKMARK_MANAGER_TABLE); + menu.RunMenuAt(x, y); + } else if (source == tree_view_) { + BookmarkNode* node = tree_view_->GetSelectedBookmarkNode(); + if (!node) + return; + std::vector<BookmarkNode*> nodes; + nodes.push_back(node); + BookmarkContextMenu menu(GetContainer()->GetHWND(), profile_, NULL, NULL, + node, nodes, + BookmarkContextMenu::BOOKMARK_MANAGER_TREE); + menu.RunMenuAt(x, y); + } +} + +BookmarkTableModel* BookmarkManagerView::CreateSearchTableModel() { + std::wstring search_text = search_tf_->GetText(); + if (search_text.empty()) + return NULL; + return BookmarkTableModel::CreateSearchTableModel(GetBookmarkModel(), + search_text); +} + +void BookmarkManagerView::SetTableModel(BookmarkTableModel* new_table_model, + BookmarkNode* parent_node) { + // Be sure and reset the model on the view before updating table_model_. + // Otherwise the view will attempt to use the deleted model when we set the + // new one. + table_view_->SetModel(NULL); + table_view_->SetShowPathColumn(!parent_node); + table_view_->SetModel(new_table_model); + table_view_->set_parent_node(parent_node); + table_model_.reset(new_table_model); +} + +void BookmarkManagerView::PerformSearch() { + search_factory_.RevokeAll(); + // Reset the controller, otherwise when we change the selection we'll get + // notified and update the model twice. + tree_view_->SetController(NULL); + tree_view_->SetSelectedNode(tree_model_->search_node()); + tree_view_->SetController(this); + SetTableModel(CreateSearchTableModel(), NULL); +} + +void BookmarkManagerView::PrepareForShow() { + views::SingleSplitView* split_view = + static_cast<views::SingleSplitView*>(table_view_->GetParent()); + // Give a third of the space to the tree. + split_view->set_divider_x(split_view->width() / 3); + if (!GetBookmarkModel()->IsLoaded()) { + search_tf_->SetReadOnly(true); + return; + } + + LoadedImpl(); +} + +void BookmarkManagerView::LoadedImpl() { + BookmarkModel* bookmark_model = GetBookmarkModel(); + BookmarkNode* bookmark_bar_node = bookmark_model->GetBookmarkBarNode(); + table_model_.reset( + BookmarkTableModel::CreateBookmarkTableModelForFolder(bookmark_model, + bookmark_bar_node)); + table_view_->SetModel(table_model_.get()); + table_view_->set_parent_node(bookmark_bar_node); + + tree_model_.reset(new BookmarkFolderTreeModel(bookmark_model)); + tree_view_->SetModel(tree_model_.get()); + + tree_view_->ExpandAll(); + + tree_view_->SetSelectedNode( + tree_model_->GetFolderNodeForBookmarkNode(bookmark_bar_node)); + + search_tf_->SetReadOnly(false); + search_tf_->SetController(this); + + Layout(); + SchedulePaint(); +} + +BookmarkModel* BookmarkManagerView::GetBookmarkModel() const { + return profile_->GetBookmarkModel(); +} diff --git a/chrome/browser/views/bookmark_manager_view.h b/chrome/browser/views/bookmark_manager_view.h new file mode 100644 index 0000000..c397b33 --- /dev/null +++ b/chrome/browser/views/bookmark_manager_view.h @@ -0,0 +1,160 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_VIEWS_BOOKMARK_MANAGER_VIEW_H_ +#define CHROME_BROWSER_VIEWS_BOOKMARK_MANAGER_VIEW_H_ + +#include "base/task.h" +#include "chrome/browser/bookmarks/bookmark_model.h" +#include "chrome/views/table_view.h" +#include "chrome/views/text_field.h" +#include "chrome/views/tree_view.h" +#include "chrome/views/view.h" +#include "chrome/views/window_delegate.h" +#include "webkit/glue/window_open_disposition.h" + +class BookmarkFolderTreeModel; +class BookmarkFolderTreeView; +class BookmarkTableModel; +class BookmarkTableView; +class PrefService; +class Profile; + +// A view that lets the user manage their bookmarks. The bookmark manager +// shows a tree on the left with a table on the right. The tree shows the +// folder nodes and the table the contents of the selected tree node. The +// tree is a BookmarkFolderTreeView and the table a BookmarkTableView. A +// text field is also provided that allows the user to search the contents +// of the bookmarks. +class BookmarkManagerView : public views::View, + public views::WindowDelegate, + public views::TreeViewController, + public views::TableViewObserver, + public views::TextField::Controller, + public BookmarkModelObserver, + public views::ContextMenuController { + public: + explicit BookmarkManagerView(Profile* profile); + virtual ~BookmarkManagerView(); + + static void RegisterPrefs(PrefService* prefs); + + // Shows the bookmark manager. Only one bookmark manager exists. + static void Show(Profile* profile); + + // Returns the current manager, or NULL if the manager is not showing. + static BookmarkManagerView* current(); + + // Selects the specified node in the tree. If node is a URL it's parent is + // selected and node is selected in the table. + void SelectInTree(BookmarkNode* node); + + // Returns the selection of the table. + std::vector<BookmarkNode*> GetSelectedTableNodes(); + + virtual void PaintBackground(ChromeCanvas* canvas); + + virtual gfx::Size GetPreferredSize(); + + // WindowDelegate. + virtual bool CanResize() const { return true; } + virtual bool CanMaximize() const { return true; } + virtual std::wstring GetWindowTitle() const; + virtual void SaveWindowPosition(const CRect& bounds, + bool maximized, + bool always_on_top); + virtual bool RestoreWindowPosition(CRect* bounds, + bool* maximized, + bool* always_on_top); + virtual View* GetContentsView() { return this; } + // TODO(sky): implement these when we have an icon. + //virtual SkBitmap GetWindowIcon(); + //virtual bool ShouldShowWindowIcon() const { return true; } + + private: + // TableViewObserver methods. + virtual void OnSelectionChanged() {} + // Overriden to open the selected table nodes in the current browser. + virtual void OnDoubleClick(); + virtual void OnTableViewDelete(views::TableView* table); + + // TreeViewController method. + virtual void OnTreeViewSelectionChanged(views::TreeView* tree_view); + + // BookmarkModelObserver. We're only installed as an observer until the + // bookmarks are loaded. + virtual void Loaded(BookmarkModel* model); + virtual void BookmarkModelBeingDeleted(BookmarkModel* model) {} + virtual void BookmarkNodeMoved(BookmarkModel* model, + BookmarkNode* old_parent, + int old_index, + BookmarkNode* new_parent, + int new_index) {} + virtual void BookmarkNodeAdded(BookmarkModel* model, + BookmarkNode* parent, + int index) {} + virtual void BookmarkNodeRemoved(BookmarkModel* model, + BookmarkNode* parent, + int index) {} + virtual void BookmarkNodeRemoved(BookmarkModel* model, + BookmarkNode* parent, + int old_index, + BookmarkNode* node) {} + virtual void BookmarkNodeChanged(BookmarkModel* model, + BookmarkNode* node) {} + virtual void BookmarkNodeFavIconLoaded(BookmarkModel* model, + BookmarkNode* node) {} + + // TextField::Controller methods. + // Starts a timer to search for the search text. + virtual void ContentsChanged(views::TextField* sender, + const std::wstring& new_contents); + // If return has been pressed this performs an immediate search. + virtual void HandleKeystroke(views::TextField* sender, + UINT message, TCHAR key, UINT repeat_count, + UINT flags); + + // ContextMenuController. + virtual void ShowContextMenu(views::View* source, + int x, + int y, + bool is_mouse_gesture); + + // Creates the table model to use when searching. This returns NULL if there + // is no search text. + BookmarkTableModel* CreateSearchTableModel(); + + // Sets the model of the table and its parent node. + void SetTableModel(BookmarkTableModel* new_table_model, + BookmarkNode* parent_node); + + // Sets the table's model to the results of CreateSearchTableModel and selects + // the search node in the tree. + void PerformSearch(); + + // Invoked prior to showing. If the BookmarkModel is loaded this invokes + // LoadedImpl. + void PrepareForShow(); + + // Invoked when we're parented and the BookmarkModel is loaded. Sets the + // models of the tree/table appropriately and expands the necessary nodes. + void LoadedImpl(); + + // Returns the BookmarkModel. + BookmarkModel* GetBookmarkModel() const; + + Profile* profile_; + BookmarkTableView* table_view_; + BookmarkFolderTreeView* tree_view_; + scoped_ptr<BookmarkTableModel> table_model_; + scoped_ptr<BookmarkFolderTreeModel> tree_model_; + views::TextField* search_tf_; + + // Factory used for delaying search. + ScopedRunnableMethodFactory<BookmarkManagerView> search_factory_; + + DISALLOW_COPY_AND_ASSIGN(BookmarkManagerView); +}; + +#endif // CHROME_BROWSER_VIEWS_BOOKMARK_MANAGER_VIEW_H_ diff --git a/chrome/browser/views/bookmark_table_view.cc b/chrome/browser/views/bookmark_table_view.cc new file mode 100644 index 0000000..ffda218 --- /dev/null +++ b/chrome/browser/views/bookmark_table_view.cc @@ -0,0 +1,394 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/views/bookmark_table_view.h" + +#include "base/base_drag_source.h" +#include "chrome/browser/bookmarks/bookmark_utils.h" +#include "chrome/browser/bookmarks/bookmark_model.h" +#include "chrome/browser/bookmarks/bookmark_table_model.h" +#include "chrome/browser/profile.h" +#include "chrome/common/drag_drop_types.h" +#include "chrome/common/os_exchange_data.h" +#include "chrome/common/pref_names.h" +#include "chrome/common/pref_service.h" +#include "chrome/views/chrome_menu.h" + +#include "generated_resources.h" + +namespace { + +// Height of the drop indicator used when dropping between rows. +const int kDropHighlightHeight = 2; + +int GetWidthOfColumn(const std::vector<views::TableColumn>& columns, + const std::vector<int> widths, + int column_id) { + for (size_t i = 0; i < columns.size(); ++i) { + if (columns[i].id == column_id) + return widths[i]; + } + NOTREACHED(); + return -1; +} + +} // namespace + +BookmarkTableView::BookmarkTableView(Profile* profile, + BookmarkTableModel* model) + : views::TableView(model, std::vector<views::TableColumn>(), + views::ICON_AND_TEXT, false, true, true), + profile_(profile), + show_path_column_(false) { + UpdateColumns(); +} + +// static +void BookmarkTableView::RegisterUserPrefs(PrefService* prefs) { + prefs->RegisterIntegerPref(prefs::kBookmarkTableNameWidth1, -1); + prefs->RegisterIntegerPref(prefs::kBookmarkTableURLWidth1, -1); + prefs->RegisterIntegerPref(prefs::kBookmarkTableNameWidth2, -1); + prefs->RegisterIntegerPref(prefs::kBookmarkTableURLWidth2, -1); + prefs->RegisterIntegerPref(prefs::kBookmarkTablePathWidth, -1); +} + +bool BookmarkTableView::CanDrop(const OSExchangeData& data) { + if (!parent_node_ || !profile_->GetBookmarkModel()->IsLoaded()) + return false; + + drop_info_.reset(new DropInfo()); + if (!drop_info_->drag_data.Read(data)) + return false; + + // Don't allow the user to drop an ancestor of the parent node onto the + // parent node. This would create a cycle, which is definitely a no-no. + std::vector<BookmarkNode*> nodes = drop_info_->drag_data.GetNodes(profile_); + for (size_t i = 0; i < nodes.size(); ++i) { + if (parent_node_->HasAncestor(nodes[i])) + return false; + } + return true; +} + +void BookmarkTableView::OnDragEntered(const views::DropTargetEvent& event) { +} + +int BookmarkTableView::OnDragUpdated(const views::DropTargetEvent& event) { + if (!parent_node_) + return false; + + LVHITTESTINFO hit_info = {0}; + hit_info.pt.x = event.x(); + hit_info.pt.y = event.y(); + // TODO(sky): need to support auto-scroll and all that good stuff. + + int drop_index; + bool drop_on; + drop_index = CalculateDropIndex(event.y(), &drop_on); + + drop_info_->drop_operation = + CalculateDropOperation(event, drop_index, drop_on); + + if (drop_info_->drop_operation == DragDropTypes::DRAG_NONE) { + drop_index = -1; + drop_on = false; + } + + SetDropIndex(drop_index, drop_on); + + return drop_info_->drop_operation; +} + +void BookmarkTableView::OnDragExited() { + SetDropIndex(-1, false); + drop_info_.reset(); +} + +int BookmarkTableView::OnPerformDrop(const views::DropTargetEvent& event) { + OnPerformDropImpl(); + int drop_operation = drop_info_->drop_operation; + SetDropIndex(-1, false); + drop_info_.reset(); + return drop_operation; +} + +BookmarkTableModel* BookmarkTableView::bookmark_table_model() const { + return static_cast<BookmarkTableModel*>(model()); +} + +void BookmarkTableView::SaveColumnConfiguration() { + PrefService* prefs = profile_->GetPrefs(); + if (!prefs) + return; + + if (show_path_column_) { + prefs->SetInteger(prefs::kBookmarkTableNameWidth2, + GetColumnWidth(IDS_BOOKMARK_TABLE_TITLE)); + prefs->SetInteger(prefs::kBookmarkTableURLWidth2, + GetColumnWidth(IDS_BOOKMARK_TABLE_URL)); + prefs->SetInteger(prefs::kBookmarkTablePathWidth, + GetColumnWidth(IDS_BOOKMARK_TABLE_PATH)); + } else { + prefs->SetInteger(prefs::kBookmarkTableNameWidth1, + GetColumnWidth(IDS_BOOKMARK_TABLE_TITLE)); + prefs->SetInteger(prefs::kBookmarkTableURLWidth1, + GetColumnWidth(IDS_BOOKMARK_TABLE_URL)); + } +} + +void BookmarkTableView::SetShowPathColumn(bool show_path_column) { + if (show_path_column == show_path_column_) + return; + + SaveColumnConfiguration(); + + show_path_column_ = show_path_column; + UpdateColumns(); +} + +void BookmarkTableView::PostPaint() { + if (!drop_info_.get() || drop_info_->drop_index == -1 || + drop_info_->drop_on) { + return; + } + + RECT bounds = GetDropBetweenHighlightRect(drop_info_->drop_index); + HDC dc = GetDC(GetNativeControlHWND()); + HBRUSH brush = CreateSolidBrush(GetSysColor(COLOR_WINDOWTEXT)); + FillRect(dc, &bounds, brush); + DeleteObject(brush); + ReleaseDC(GetNativeControlHWND(), dc); +} + +LRESULT BookmarkTableView::OnNotify(int w_param, LPNMHDR l_param) { + switch(l_param->code) { + case LVN_BEGINDRAG: + BeginDrag(); + return 0; // Return value doesn't matter for this message. + } + + return TableView::OnNotify(w_param, l_param); +} + +void BookmarkTableView::BeginDrag() { + std::vector<BookmarkNode*> nodes_to_drag; + for (TableView::iterator i = SelectionBegin(); i != SelectionEnd(); ++i) + nodes_to_drag.push_back(bookmark_table_model()->GetNodeForRow(*i)); + if (nodes_to_drag.empty()) + return; // Nothing to drag. + + scoped_refptr<OSExchangeData> data = new OSExchangeData; + BookmarkDragData(nodes_to_drag).Write(profile_, data); + scoped_refptr<BaseDragSource> drag_source(new BaseDragSource); + DWORD effects; + DoDragDrop(data, drag_source, + DROPEFFECT_LINK | DROPEFFECT_COPY | DROPEFFECT_MOVE, &effects); +} + +int BookmarkTableView::CalculateDropOperation( + const views::DropTargetEvent& event, + int drop_index, + bool drop_on) { + if (drop_info_->drag_data.IsFromProfile(profile_)) { + // Data from the same profile. Prefer move, but do copy if the user wants + // that. + if (event.IsControlDown()) + return DragDropTypes::DRAG_COPY; + + int real_drop_index; + BookmarkNode* drop_parent = GetDropParentAndIndex(drop_index, drop_on, + &real_drop_index); + if (!bookmark_utils::IsValidDropLocation( + profile_, drop_info_->drag_data, drop_parent, real_drop_index)) { + return DragDropTypes::DRAG_NONE; + } + return DragDropTypes::DRAG_MOVE; + } + // We're going to copy, but return an operation compatible with the source + // operations so that the user can drop. + return bookmark_utils::PreferredDropOperation( + event, DragDropTypes::DRAG_COPY | DragDropTypes::DRAG_LINK); +} + +void BookmarkTableView::OnPerformDropImpl() { + int drop_index; + BookmarkNode* drop_parent = GetDropParentAndIndex( + drop_info_->drop_index, drop_info_->drop_on, &drop_index); + BookmarkModel* model = profile_->GetBookmarkModel(); + int min_selection; + int max_selection; + // If the data is not from this profile we return an operation compatible + // with the source. As such, we need to need to check the data here too. + if (!drop_info_->drag_data.IsFromProfile(profile_) || + drop_info_->drop_operation == DragDropTypes::DRAG_COPY) { + bookmark_utils::CloneDragData(model, drop_info_->drag_data.elements, + drop_parent, drop_index); + min_selection = drop_index; + max_selection = drop_index + + static_cast<int>(drop_info_->drag_data.elements.size()); + } else { + // else, move. + std::vector<BookmarkNode*> nodes = drop_info_->drag_data.GetNodes(profile_); + if (nodes.empty()) + return; + + for (size_t i = 0; i < nodes.size(); ++i) { + model->Move(nodes[i], drop_parent, drop_index); + // Reset the drop_index, just in case the index didn't really change. + drop_index = drop_parent->IndexOfChild(nodes[i]) + 1; + } + min_selection = drop_parent->IndexOfChild(nodes[0]); + max_selection = min_selection + static_cast<int>(nodes.size()); + } + if (min_selection < RowCount() && max_selection < RowCount()) { + // Select the moved/copied rows. + Select(min_selection); + if (min_selection + 1 < max_selection) { + // SetSelectedState doesn't send notification, so we manually do it. + for (int i = min_selection + 1; i < max_selection; ++i) + SetSelectedState(i, true); + if (observer()) + observer()->OnSelectionChanged(); + } + } +} + +void BookmarkTableView::SetDropIndex(int index, bool drop_on) { + if (drop_info_->drop_index == index && drop_info_->drop_on == drop_on) + return; + + UpdateDropIndex(drop_info_->drop_index, drop_info_->drop_on, false); + + drop_info_->drop_index = index; + drop_info_->drop_on = drop_on; + + UpdateDropIndex(drop_info_->drop_index, drop_info_->drop_on, true); +} + +void BookmarkTableView::UpdateDropIndex(int index, bool drop_on, bool turn_on) { + if (index == -1) + return; + + if (drop_on) { + ListView_SetItemState(GetNativeControlHWND(), index, + turn_on ? LVIS_DROPHILITED : 0, LVIS_DROPHILITED); + } else { + RECT bounds = GetDropBetweenHighlightRect(index); + InvalidateRect(GetNativeControlHWND(), &bounds, FALSE); + } +} + +int BookmarkTableView::CalculateDropIndex(int y, bool* drop_on) { + *drop_on = false; + HWND hwnd = GetNativeControlHWND(); + int row_count = RowCount(); + int top_index = ListView_GetTopIndex(hwnd); + if (row_count == 0 || top_index < 0) + return 0; + + for (int i = top_index; i < row_count; ++i) { + RECT bounds; + ListView_GetItemRect(hwnd, i, &bounds, LVIR_BOUNDS); + if (y < bounds.top) + return i; + if (y < bounds.bottom) { + if (bookmark_table_model()->GetNodeForRow(i)->is_folder()) { + if (y < bounds.top + views::MenuItemView::kDropBetweenPixels) + return i; + if (y >= bounds.bottom - views::MenuItemView::kDropBetweenPixels) + return i + 1; + *drop_on = true; + return i; + } + if (y < (bounds.bottom - bounds.top) / 2 + bounds.top) + return i; + return i + 1; + } + } + return row_count; +} + +BookmarkNode* BookmarkTableView::GetDropParentAndIndex(int visual_drop_index, + bool drop_on, + int* index) { + if (drop_on) { + BookmarkNode* parent = parent_node_->GetChild(visual_drop_index); + *index = parent->GetChildCount(); + return parent; + } + *index = visual_drop_index; + return parent_node_; +} + +RECT BookmarkTableView::GetDropBetweenHighlightRect(int index) { + RECT bounds = { 0 }; + if (RowCount() == 0) { + bounds.top = content_offset(); + bounds.left = 0; + bounds.right = width(); + } else if (index >= RowCount()) { + ListView_GetItemRect(GetNativeControlHWND(), index - 1, &bounds, + LVIR_BOUNDS); + bounds.top = bounds.bottom - kDropHighlightHeight / 2; + } else { + ListView_GetItemRect(GetNativeControlHWND(), index, &bounds, LVIR_BOUNDS); + bounds.top -= kDropHighlightHeight / 2; + } + bounds.bottom = bounds.top + kDropHighlightHeight; + return bounds; +} +void BookmarkTableView::UpdateColumns() { + PrefService* prefs = profile_->GetPrefs(); + views::TableColumn name_column = + views::TableColumn(IDS_BOOKMARK_TABLE_TITLE, views::TableColumn::LEFT, + -1); + views::TableColumn url_column = + views::TableColumn(IDS_BOOKMARK_TABLE_URL, views::TableColumn::LEFT, -1); + views::TableColumn path_column = + views::TableColumn(IDS_BOOKMARK_TABLE_PATH, views::TableColumn::LEFT, -1); + + std::vector<views::TableColumn> columns; + if (show_path_column_) { + int name_width = -1; + int url_width = -1; + int path_width = -1; + if (prefs) { + name_width = prefs->GetInteger(prefs::kBookmarkTableNameWidth2); + url_width = prefs->GetInteger(prefs::kBookmarkTableURLWidth2); + path_width = prefs->GetInteger(prefs::kBookmarkTablePathWidth); + } + if (name_width != -1 && url_width != -1 && path_width != -1) { + name_column.width = name_width; + url_column.width = url_width; + path_column.width = path_width; + } else { + name_column.percent = .5; + url_column.percent = .25; + path_column.percent= .25; + } + columns.push_back(name_column); + columns.push_back(url_column); + columns.push_back(path_column); + } else { + int name_width = -1; + int url_width = -1; + if (prefs) { + name_width = prefs->GetInteger(prefs::kBookmarkTableNameWidth1); + url_width = prefs->GetInteger(prefs::kBookmarkTableURLWidth1); + } + if (name_width != -1 && url_width != -1) { + name_column.width = name_width; + url_column.width = url_width; + } else { + name_column.percent = .5; + url_column.percent = .5; + } + columns.push_back(name_column); + columns.push_back(url_column); + } + SetColumns(columns); + for (size_t i = 0; i < columns.size(); ++i) + SetColumnVisibility(columns[i].id, true); + OnModelChanged(); +} diff --git a/chrome/browser/views/bookmark_table_view.h b/chrome/browser/views/bookmark_table_view.h new file mode 100644 index 0000000..a01f1af --- /dev/null +++ b/chrome/browser/views/bookmark_table_view.h @@ -0,0 +1,121 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_VIEWS_BOOKMARK_TABLE_VIEW_H_ +#define CHROME_BROWSER_VIEWS_BOOKMARK_TABLE_VIEW_H_ + +#include "chrome/browser/bookmarks/bookmark_drag_data.h" +#include "chrome/views/menu.h" +#include "chrome/views/table_view.h" + +class BookmarkModel; +class BookmarkNode; +class BookmarkTableModel; +class OSExchangeData; +class PrefService; +class Profile; + +// A TableView implementation that shows a BookmarkTableModel. +// BookmarkTableView provides drag and drop support as well as showing a +// separate set of columns when showing search results. +class BookmarkTableView : public views::TableView { + public: + BookmarkTableView(Profile* profile, BookmarkTableModel* model); + + static void RegisterUserPrefs(PrefService* prefs); + + // Drag and drop methods. + virtual bool CanDrop(const OSExchangeData& data); + virtual void OnDragEntered(const views::DropTargetEvent& event); + virtual int OnDragUpdated(const views::DropTargetEvent& event); + virtual void OnDragExited(); + virtual int OnPerformDrop(const views::DropTargetEvent& event); + + // Sets the parent of the nodes being displayed. For search and recently + // found results |parent| is NULL. + void set_parent_node(BookmarkNode* parent) { parent_node_ = parent; } + + // Sets whether the path column should be shown. The path column is shown + // for search results and recently bookmarked. + void SetShowPathColumn(bool show_path_column); + + // The model as a BookmarkTableModel. + BookmarkTableModel* bookmark_table_model() const; + + // Saves the widths of the table columns. + void SaveColumnConfiguration(); + + protected: + // Overriden to draw a drop indicator when dropping between rows. + virtual void PostPaint(); + + // Overriden to start a drag. + virtual LRESULT OnNotify(int w_param, LPNMHDR l_param); + + private: + // Information used when we're the drop target of a drag and drop operation. + struct DropInfo { + DropInfo() : drop_index(-1), drop_operation(0), drop_on(false) {} + + BookmarkDragData drag_data; + + // Index into the table model of where the drop should occur. + int drop_index; + + // The drop operation that should occur. + int drop_operation; + + // Whether the drop is on drop_index or before it. + bool drop_on; + }; + + // Starts a drop operation. + void BeginDrag(); + + // Returns the drop operation for the specified index. + int CalculateDropOperation(const views::DropTargetEvent& event, + int drop_index, + bool drop_on); + + // Performs the drop operation. + void OnPerformDropImpl(); + + // Sets the drop index. If index differs from the current drop index + // UpdateDropIndex is invoked for the old and new values. + void SetDropIndex(int index, bool drop_on); + + // Invoked from SetDropIndex to update the state for the specified index + // and schedule a paint. If |turn_on| is true the highlight is being turned + // on for the specified index, otherwise it is being turned off. + void UpdateDropIndex(int index, bool drop_on, bool turn_on); + + // Determines the drop index for the specified location. + int CalculateDropIndex(int y, bool* drop_on); + + // Returns the BookmarkNode the drop should occur on, or NULL if not over + // a valid location. + BookmarkNode* GetDropParentAndIndex(int visual_drop_index, + bool drop_on, + int* index); + + // Returns the bounds of drop indicator shown when the drop is to occur + // between rows (drop_on is false). + RECT GetDropBetweenHighlightRect(int index); + + // Resets the columns. BookmarkTableView shows different sets of columns. + // See ShowPathColumn for details. + void UpdateColumns(); + + Profile* profile_; + + BookmarkNode* parent_node_; + + scoped_ptr<DropInfo> drop_info_; + + bool show_path_column_; + + DISALLOW_COPY_AND_ASSIGN(BookmarkTableView); +}; + +#endif // CHROME_BROWSER_VIEWS_BOOKMARK_TABLE_VIEW_H_ diff --git a/chrome/browser/views/browser_views.vcproj b/chrome/browser/views/browser_views.vcproj index cfce335..4a47e9a 100644 --- a/chrome/browser/views/browser_views.vcproj +++ b/chrome/browser/views/browser_views.vcproj @@ -470,6 +470,30 @@ > </File> <File + RelativePath=".\bookmark_folder_tree_view.cc" + > + </File> + <File + RelativePath=".\bookmark_folder_tree_view.h" + > + </File> + <File + RelativePath=".\bookmark_manager_view.cc" + > + </File> + <File + RelativePath=".\bookmark_manager_view.h" + > + </File> + <File + RelativePath=".\bookmark_table_view.cc" + > + </File> + <File + RelativePath=".\bookmark_table_view.h" + > + </File> + <File RelativePath=".\bookmark_bubble_view.cc" > </File> diff --git a/chrome/browser/views/toolbar_view.cc b/chrome/browser/views/toolbar_view.cc index a7f26e3..d87e205 100644 --- a/chrome/browser/views/toolbar_view.cc +++ b/chrome/browser/views/toolbar_view.cc @@ -546,6 +546,8 @@ void BrowserToolbarView::RunAppMenu(const CPoint& pt, HWND hwnd) { menu.AppendSeparator(); menu.AppendMenuItemWithLabel(IDC_SHOW_HISTORY, l10n_util::GetString(IDS_SHOW_HISTORY)); + menu.AppendMenuItemWithLabel(IDC_SHOW_BOOKMARK_MANAGER, + l10n_util::GetString(IDS_BOOKMARK_MANAGER)); menu.AppendMenuItemWithLabel(IDC_SHOW_DOWNLOADS, l10n_util::GetString(IDS_SHOW_DOWNLOADS)); menu.AppendSeparator(); |