diff options
author | groby@chromium.org <groby@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-06-14 01:00:48 +0000 |
---|---|---|
committer | groby@chromium.org <groby@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-06-14 01:00:48 +0000 |
commit | 21c52bd950c03e810d3e25b65e15c06d92eb547e (patch) | |
tree | 20581d659e93857462ddce1acc794cf0e1f5d507 | |
parent | f1933791ead4e0c44066287396f0475e95a2e1e3 (diff) | |
download | chromium_src-21c52bd950c03e810d3e25b65e15c06d92eb547e.zip chromium_src-21c52bd950c03e810d3e25b65e15c06d92eb547e.tar.gz chromium_src-21c52bd950c03e810d3e25b65e15c06d92eb547e.tar.bz2 |
Support drag&drop for startup pages
BUG=69686
TEST=Drag & drop individual or multiple startup pages, confirm D&D works
Review URL: http://codereview.chromium.org/7044136
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@88936 0039d316-1c4b-4281-b951-d872f2087c98
7 files changed, 262 insertions, 0 deletions
diff --git a/chrome/browser/custom_home_pages_table_model.cc b/chrome/browser/custom_home_pages_table_model.cc index 5f2cf61..8380230 100644 --- a/chrome/browser/custom_home_pages_table_model.cc +++ b/chrome/browser/custom_home_pages_table_model.cc @@ -66,6 +66,64 @@ void CustomHomePagesTableModel::SetURLs(const std::vector<GURL>& urls) { observer_->OnModelChanged(); } +/** + * Move a number of existing entries to a new position, reordering the table. + * + * We determine the range of elements affected by the move, save the moved + * elements, compact the remaining ones, and re-insert moved elements. + * Expects |index_list| to be ordered ascending. + */ +void CustomHomePagesTableModel::MoveURLs(int insert_before, + const std::vector<int>& index_list) +{ + DCHECK(insert_before >= 0 && insert_before <= RowCount()); + + // The range of elements that needs to be reshuffled is [ |first|, |last| ). + int first = std::min(insert_before, index_list.front()); + int last = std::max(insert_before, index_list.back() + 1); + + // Save the dragged elements. Also, adjust insertion point if it is before a + // dragged element. + std::vector<Entry> moved_entries; + for (size_t i = 0; i < index_list.size(); ++i) { + moved_entries.push_back(entries_[index_list[i]]); + if (index_list[i] == insert_before) + insert_before++; + } + + // Compact the range between beginning and insertion point, moving downwards. + size_t skip_count = 0; + for (int i = first; i < insert_before; ++i) { + if (skip_count < index_list.size() && index_list[skip_count] == i) + skip_count++; + else + entries_[i - skip_count]=entries_[i]; + } + + // Moving items down created a gap. We start compacting up after it. + first = insert_before; + insert_before -= skip_count; + + // Now compact up for elements after the insertion point. + skip_count = 0; + for (int i = last - 1; i >= first; --i) { + if (skip_count < index_list.size() && + index_list[index_list.size() - skip_count - 1] == i) { + skip_count++; + } else { + entries_[i + skip_count] = entries_[i]; + } + } + + // Insert moved elements. + std::copy(moved_entries.begin(), moved_entries.end(), + entries_.begin() + insert_before); + + // Possibly large change, so tell the view to just rebuild itself. + if (observer_) + observer_->OnModelChanged(); +} + void CustomHomePagesTableModel::Add(int index, const GURL& url) { DCHECK(index >= 0 && index <= RowCount()); entries_.insert(entries_.begin() + static_cast<size_t>(index), Entry()); diff --git a/chrome/browser/custom_home_pages_table_model.h b/chrome/browser/custom_home_pages_table_model.h index 5145564..0403d8d 100644 --- a/chrome/browser/custom_home_pages_table_model.h +++ b/chrome/browser/custom_home_pages_table_model.h @@ -33,6 +33,11 @@ class CustomHomePagesTableModel : public ui::TableModel { // Sets the set of urls that this model contains. void SetURLs(const std::vector<GURL>& urls); + // Collect all entries indexed by |index_list|, and moves them to be right + // before the element addressed by |insert_before|. Used by Drag&Drop. + // Expects |index_list| to be ordered ascending. + void MoveURLs(int insert_before, const std::vector<int>& index_list); + // Adds an entry at the specified index. void Add(int index, const GURL& url); diff --git a/chrome/browser/resources/options/browser_options.html b/chrome/browser/resources/options/browser_options.html index 740e98d..9f97abb 100644 --- a/chrome/browser/resources/options/browser_options.html +++ b/chrome/browser/resources/options/browser_options.html @@ -35,6 +35,7 @@ i18n-content="startupUseCurrent"></button> </div> </div> + <div id="startupPagesListDropmarker"></div> </div> </div> </section> diff --git a/chrome/browser/resources/options/browser_options_page.css b/chrome/browser/resources/options/browser_options_page.css index 1d0568d..4e3a5fa 100644 --- a/chrome/browser/resources/options/browser_options_page.css +++ b/chrome/browser/resources/options/browser_options_page.css @@ -16,6 +16,22 @@ color: #666; } +#startupPagesListDropmarker { + background-clip: padding-box; + background-color: hsl(214, 91%, 65%); + border-bottom-color: transparent; + border-radius: 0; + border-top-color: transparent; + border: 2px solid hsl(214, 91%, 65%); + box-sizing: border-box; + display: none; + height: 6px; + overflow: hidden; + pointer-events: none; + position: fixed; + z-index: 10; +} + #customHomePageGroup { display: -webkit-box; -webkit-box-orient: horizontal; diff --git a/chrome/browser/resources/options/browser_options_startup_page_list.js b/chrome/browser/resources/options/browser_options_startup_page_list.js index 9ad47668..84f5516 100644 --- a/chrome/browser/resources/options/browser_options_startup_page_list.js +++ b/chrome/browser/resources/options/browser_options_startup_page_list.js @@ -80,6 +80,7 @@ cr.define('options.browser_options', function() { urlField.addEventListener('blur', function(event) { self.parentNode.autocompleteList.detach(); }); + this.draggable = true; }, /** @inheritDoc */ @@ -117,6 +118,23 @@ cr.define('options.browser_options', function() { */ autocompleteList: null, + /** + * The drop position information: "below" or "above". + */ + dropPos: null, + + /** @inheritDoc */ + decorate: function() { + InlineEditableItemList.prototype.decorate.call(this); + + // Listen to drag and drop events. + this.addEventListener('dragstart', this.handleDragStart_.bind(this)); + this.addEventListener('dragenter', this.handleDragEnter_.bind(this)); + this.addEventListener('dragover', this.handleDragOver_.bind(this)); + this.addEventListener('drop', this.handleDrop_.bind(this)); + this.addEventListener('dragleave', this.handleDragLeave_.bind(this)); + }, + /** @inheritDoc */ createItem: function(pageInfo) { var item = new StartupPageListItem(pageInfo); @@ -128,6 +146,140 @@ cr.define('options.browser_options', function() { deleteItemAtIndex: function(index) { chrome.send('removeStartupPages', [String(index)]); }, + + /* + * Computes the target item of drop event. + * @param {Event} e The drop or dragover event. + * @private + */ + getTargetFromDropEvent_ : function(e) { + var target = e.target; + // e.target may be an inner element of the list item + while (target != null && !(target instanceof StartupPageListItem)) { + target = target.parentNode; + } + return target; + }, + + /* + * Handles the dragstart event. + * @param {Event} e The dragstart event. + * @private + */ + handleDragStart_: function(e) { + var target = e.target; + // StartupPageListItem should be the only draggable element type in the + // page but let's make sure. + if (target instanceof StartupPageListItem) { + this.draggedItem = target; + this.draggedItem.editable = false; + e.dataTransfer.effectAllowed = 'move'; + // We need to put some kind of data in the drag or it will be + // ignored. Use the URL in case the user drags to a text field or the + // desktop. + e.dataTransfer.setData('text/plain', target.urlField_.value); + } + }, + + /* + * Handles the dragenter event. + * @param {Event} e The dragenter event. + * @private + */ + handleDragEnter_: function(e) { + e.preventDefault(); + }, + + /* + * Handles the dragover event. + * @param {Event} e The dragover event. + * @private + */ + handleDragOver_: function(e) { + var dropTarget = this.getTargetFromDropEvent_(e); + // Determines whether the drop target is to accept the drop. + // The drop is only successful on another StartupPageListItem. + if (!(dropTarget instanceof StartupPageListItem) || + dropTarget == this.draggedItem || dropTarget.isPlaceholder) { + this.hideDropMarker_(); + return; + } + // Compute the drop postion. Should we move the dragged item to + // below or above the drop target? + var rect = dropTarget.getBoundingClientRect(); + var dy = e.clientY - rect.top; + var yRatio = dy / rect.height; + var dropPos = yRatio <= .5 ? 'above' : 'below'; + this.dropPos = dropPos; + this.showDropMarker_(dropTarget, dropPos); + e.preventDefault(); + }, + + /* + * Handles the drop event. + * @param {Event} e The drop event. + * @private + */ + handleDrop_: function(e) { + var dropTarget = this.getTargetFromDropEvent_(e); + this.hideDropMarker_(); + + // Insert the selection at the new position. + var newIndex = this.dataModel.indexOf(dropTarget.pageInfo_); + if (this.dropPos == 'below') + newIndex += 1; + + var selected = this.selectionModel.selectedIndexes; + var stringized_selected = []; + for (var j = 0; j < selected.length; j++) + stringized_selected.push(String(selected[j])); + + chrome.send('dragDropStartupPage', + [String(newIndex), stringized_selected] ); + }, + + /* + * Handles the dragleave event. + * @param {Event} e The dragleave event + * @private + */ + handleDragLeave_ : function(e) { + this.hideDropMarker_(); + }, + + /* + * Shows and positions the marker to indicate the drop target. + * @param {HTMLElement} target The current target list item of drop + * @param {string} pos 'below' or 'above' + * @private + */ + showDropMarker_ : function(target, pos) { + window.clearTimeout(this.hideDropMarkerTimer_); + var marker = $('startupPagesListDropmarker'); + var rect = target.getBoundingClientRect(); + var markerHeight = 6; + if (pos == 'above') { + marker.style.top = (rect.top - markerHeight/2) + 'px'; + } else { + marker.style.top = (rect.bottom - markerHeight/2) + 'px'; + } + marker.style.width = rect.width + 'px'; + marker.style.left = rect.left + 'px'; + marker.style.display = 'block'; + }, + + /* + * Hides the drop marker. + * @private + */ + hideDropMarker_ : function() { + // Hide the marker in a timeout to reduce flickering as we move between + // valid drop targets. + window.clearTimeout(this.hideDropMarkerTimer_); + this.hideDropMarkerTimer_ = window.setTimeout(function() { + $('startupPagesListDropmarker').style.display = ''; + }, 100); + }, }; return { diff --git a/chrome/browser/ui/webui/options/browser_options_handler.cc b/chrome/browser/ui/webui/options/browser_options_handler.cc index 6a31815..3eadb9a 100644 --- a/chrome/browser/ui/webui/options/browser_options_handler.cc +++ b/chrome/browser/ui/webui/options/browser_options_handler.cc @@ -114,6 +114,9 @@ void BrowserOptionsHandler::RegisterMessages() { "setStartupPagesToCurrentPages", NewCallback(this, &BrowserOptionsHandler::SetStartupPagesToCurrentPages)); web_ui_->RegisterMessageCallback( + "dragDropStartupPage", + NewCallback(this, &BrowserOptionsHandler::DragDropStartupPage)); + web_ui_->RegisterMessageCallback( "requestAutocompleteSuggestions", NewCallback(this, &BrowserOptionsHandler::RequestAutocompleteSuggestions)); @@ -405,6 +408,30 @@ void BrowserOptionsHandler::EditStartupPage(const ListValue* args) { startup_custom_pages_table_model_->SetURLs(urls); } +void BrowserOptionsHandler::DragDropStartupPage(const ListValue* args) { + CHECK_EQ(args->GetSize(), 2U); + + std::string value; + int to_index; + + CHECK(args->GetString(0, &value)); + base::StringToInt(value, &to_index); + + ListValue* selected; + CHECK(args->GetList(1, &selected)); + + std::vector<int> index_list; + for (size_t i = 0; i < selected->GetSize(); ++i) { + int index; + CHECK(selected->GetString(i, &value)); + base::StringToInt(value, &index); + index_list.push_back(index); + } + + startup_custom_pages_table_model_->MoveURLs(to_index, index_list); + SaveStartupPagesPref(); +} + void BrowserOptionsHandler::SaveStartupPagesPref() { PrefService* prefs = web_ui_->GetProfile()->GetPrefs(); diff --git a/chrome/browser/ui/webui/options/browser_options_handler.h b/chrome/browser/ui/webui/options/browser_options_handler.h index 7264581..021a747 100644 --- a/chrome/browser/ui/webui/options/browser_options_handler.h +++ b/chrome/browser/ui/webui/options/browser_options_handler.h @@ -79,6 +79,9 @@ class BrowserOptionsHandler : public OptionsPageUIHandler, // Sets the startup page set to the current pages. Called from WebUI. void SetStartupPagesToCurrentPages(const ListValue* args); + // Writes the current set of startup pages to prefs. Called from WebUI. + void DragDropStartupPage(const ListValue* args); + // Gets autocomplete suggestions asychronously for the given string. // Called from WebUI. void RequestAutocompleteSuggestions(const ListValue* args); |