/*
* CompletionList.java
*
* Copyright (C) 2009-12 by RStudio, Inc.
*
* Unless you have received this program directly from RStudio pursuant
* to the terms of a commercial license agreement with RStudio, then
* this program is licensed to you under the terms of version 3 of the
* GNU Affero General Public License. This program is distributed WITHOUT
* ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
* AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
*
*/
package org.rstudio.studio.client.workbench.views.console.shell.assist;
import com.google.gwt.event.dom.client.*;
import com.google.gwt.dom.client.Element;
import com.google.gwt.event.logical.shared.HasSelectionHandlers;
import com.google.gwt.event.logical.shared.SelectionEvent;
import com.google.gwt.event.logical.shared.SelectionHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.HTMLTable.Cell;
import com.google.gwt.user.client.ui.HTMLTable.CellFormatter;
import com.google.gwt.user.client.ui.ScrollPanel;
import org.rstudio.core.client.Point;
import org.rstudio.core.client.Rectangle;
import org.rstudio.core.client.dom.DomUtils;
import org.rstudio.core.client.events.HasSelectionCommitHandlers;
import org.rstudio.core.client.events.SelectionCommitEvent;
import org.rstudio.core.client.events.SelectionCommitHandler;
import org.rstudio.core.client.widget.FontSizer;
import org.rstudio.studio.client.workbench.views.console.ConsoleResources;
class CompletionList<TItem> extends Composite
implements HasSelectionCommitHandlers<TItem>,
HasSelectionHandlers<TItem>
{
public class GridMouseHandler implements ClickHandler, MouseMoveHandler
{
public void onClick(ClickEvent event)
{
Cell cell = grid_.getCellForEvent(event) ;
if (cell != null)
{
int rowClicked = cell.getRowIndex() ;
SelectionCommitEvent.fire(CompletionList.this, items_[rowClicked]) ;
}
}
public void onMouseMove(MouseMoveEvent event)
{
if (lastMouseMoveCoordinates_ != null)
{
if (event.getScreenX() == lastMouseMoveCoordinates_.getX()
&& event.getScreenY() == lastMouseMoveCoordinates_.getY())
{
return;
}
}
lastMouseMoveCoordinates_ = new Point(event.getScreenX(),
event.getScreenY());
if (firstEvent_)
{
// Want to avoid the bug where the cursor happens to be positioned
// where the popup shows up and thus fires a mouse move event;
// so even though the user isn't touching the mouse, the selection
// changes.
firstEvent_ = false;
return;
}
int mousedOverRow = grid_.getRowForEvent(event) ;
if (mousedOverRow >= 0)
{
setSelectedIndex(mousedOverRow) ;
}
}
private boolean firstEvent_ = true;
private Point lastMouseMoveCoordinates_;
}
public CompletionList(TItem[] items,
int visibleItems,
boolean asHtml,
boolean allowVerticalShrink)
{
allowVerticalShrink_ = allowVerticalShrink;
styles_ = ConsoleResources.INSTANCE.consoleStyles();
GridEx grid = new GridEx(items.length, 1) ;
for (int i = 0; i < items.length; i++)
{
if (asHtml)
grid.setHTML(i, 0, items[i].toString()) ;
else
grid.setText(i, 0, items[i].toString()) ;
}
grid.addClickHandler(new GridMouseHandler()) ;
grid.addMouseMoveHandler(new GridMouseHandler()) ;
grid.setStylePrimaryName(styles_.completionGrid()) ;
FontSizer.applyNormalFontSize(grid);
scrollPanel_ = new ScrollPanel() ;
scrollPanel_.getElement().getStyle().setProperty("overflowX", "hidden");
scrollPanel_.add(grid) ;
scrollPanel_.setHeight((visibleItems * 20) + "px") ;
initWidget(scrollPanel_) ;
grid_ = grid ;
items_ = items ;
}
@Override
protected void onLoad()
{
super.onLoad() ;
int width = grid_.getOffsetWidth() + 20;
if (maxWidthInPixels_ != null
&& maxWidthInPixels_ > 0
&& maxWidthInPixels_ < width)
width = maxWidthInPixels_;
scrollPanel_.setWidth(width + "px") ;
if (allowVerticalShrink_ &&
grid_.getOffsetHeight() < scrollPanel_.getOffsetHeight())
{
scrollPanel_.setHeight("") ;
}
grid_.setWidth("100%") ;
selectNext() ;
}
public int getItemCount()
{
if (grid_ != null)
return grid_.getRowCount() ;
else
return 0;
}
public TItem getSelectedItem()
{
int index = getSelectedIndex() ;
if (index < 0)
return null ;
return items_[index] ;
}
public boolean selectNext()
{
return moveSelection(1, true) ;
}
public boolean selectPrev()
{
return moveSelection(-1, true) ;
}
public boolean selectNextPage()
{
return moveSelection(4, false) ;
}
public boolean selectPrevPage()
{
return moveSelection(-4, false) ;
}
public boolean selectFirst()
{
return moveSelection(-getItemCount(), false) ;
}
public boolean selectLast()
{
return moveSelection(getItemCount(), false) ;
}
private boolean moveSelection(int offset, boolean allowWrap)
{
if (getItemCount() == 0)
return false ;
int index = getSelectedIndex() + offset ;
if (allowWrap)
index = (index + getItemCount()) % getItemCount() ;
else
index = Math.min(getItemCount() - 1, Math.max(0, index)) ;
setSelectedIndex(index) ;
return true ;
}
public HandlerRegistration addSelectionHandler(
SelectionHandler<TItem> handler)
{
return addHandler(handler, SelectionEvent.getType()) ;
}
public HandlerRegistration addSelectionCommitHandler(
SelectionCommitHandler<TItem> handler)
{
return addHandler(handler, SelectionCommitEvent.getType()) ;
}
public HTML getDetailedInfoPane()
{
return null ;
}
public int getSelectedIndex()
{
return selectedIndex_ ;
}
public void setSelectedIndex(int index)
{
if (selectedIndex_ != index)
{
CellFormatter cf = grid_.getCellFormatter() ;
if (selectedIndex_ >= 0)
cf.removeStyleName(selectedIndex_, 0, styles_.selected()) ;
selectedIndex_ = index ;
if (index >= 0)
{
cf.addStyleName(selectedIndex_, 0, styles_.selected()) ;
com.google.gwt.dom.client.Element el =
DomUtils.getTableCell(grid_.getElement(), index, 0) ;
DomUtils.ensureVisibleVert(scrollPanel_.getElement(), el, 2) ;
SelectionEvent.fire(this, getSelectedItem()) ;
}
}
}
/**
* Gets the rectangle of the selected row in absolute (document-relative)
* coordinates, or null if nothing is selected.
*/
public Rectangle getSelectionRect()
{
int index = getSelectedIndex() ;
if (index < 0)
return null;
com.google.gwt.dom.client.Element el =
DomUtils.getTableCell(grid_.getElement(), index, 0) ;
return new Rectangle(el.getAbsoluteLeft(),
el.getAbsoluteTop(),
el.getOffsetWidth(),
el.getOffsetHeight()) ;
}
private class GridEx extends Grid implements HasMouseMoveHandlers
{
public GridEx(int rows, int cols)
{
super(rows, cols) ;
sinkEvents(Event.ONMOUSEMOVE) ;
}
public HandlerRegistration addMouseMoveHandler(MouseMoveHandler handler)
{
return addHandler(handler, MouseMoveEvent.getType()) ;
}
public int getRowForEvent(MouseMoveEvent event)
{
Element td = getEventTargetCell(Event.as(event.getNativeEvent()));
if (td == null) {
return -1;
}
Element tr = DOM.getParent(td);
Element body = DOM.getParent(tr);
int row = DOM.getChildIndex(body, tr);
return row ;
}
}
public void setMaxWidth(int maxWidthInPixels)
{
maxWidthInPixels_ = maxWidthInPixels;
}
private int selectedIndex_ = -1 ;
private final GridEx grid_ ;
private final TItem[] items_ ;
private final ScrollPanel scrollPanel_ ;
private final ConsoleResources.ConsoleStyles styles_;
private final boolean allowVerticalShrink_;
private Integer maxWidthInPixels_;
}