// Copyright (C) 2009 The Android Open Source Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package com.google.gerrit.client.ui; import com.google.gerrit.client.Gerrit; import com.google.gwt.dom.client.Document; import com.google.gwt.event.dom.client.KeyPressEvent; import com.google.gwt.event.shared.HandlerRegistration; import com.google.gwt.user.client.DOM; import com.google.gwt.user.client.Element; import com.google.gwt.user.client.ui.Image; import com.google.gwt.user.client.ui.ScrollPanel; import com.google.gwt.user.client.ui.UIObject; import com.google.gwt.user.client.ui.Widget; import com.google.gwt.user.client.ui.HTMLTable.CellFormatter; import com.google.gwtexpui.globalkey.client.GlobalKey; import com.google.gwtexpui.globalkey.client.KeyCommand; import com.google.gwtexpui.globalkey.client.KeyCommandSet; import com.google.gwtexpui.safehtml.client.SafeHtml; import java.util.LinkedHashMap; import java.util.Map.Entry; public abstract class NavigationTable<RowItem> extends FancyFlexTable<RowItem> { @SuppressWarnings("serial") private static final LinkedHashMap<String, Object> savedPositions = new LinkedHashMap<String, Object>(10, 0.75f, true) { @Override protected boolean removeEldestEntry(Entry<String, Object> eldest) { return size() >= 20; } }; private final Image pointer; protected final KeyCommandSet keysNavigation; protected final KeyCommandSet keysAction; private HandlerRegistration regNavigation; private HandlerRegistration regAction; private int currentRow = -1; private String saveId; private boolean computedScrollType; private ScrollPanel parentScrollPanel; protected NavigationTable() { pointer = new Image(Gerrit.RESOURCES.arrowRight()); keysNavigation = new KeyCommandSet(Gerrit.C.sectionNavigation()); keysAction = new KeyCommandSet(Gerrit.C.sectionActions()); } protected abstract void onOpenRow(int row); protected abstract Object getRowItemKey(RowItem item); private void onUp() { for (int row = currentRow - 1; row >= 0; row--) { if (getRowItem(row) != null) { movePointerTo(row); break; } } } private void onDown() { final int max = table.getRowCount(); for (int row = currentRow + 1; row < max; row++) { if (getRowItem(row) != null) { movePointerTo(row); break; } } } private void onOpen() { if (0 <= currentRow && currentRow < table.getRowCount()) { if (getRowItem(currentRow) != null) { onOpenRow(currentRow); } } } protected int getCurrentRow() { return currentRow; } protected void ensurePointerVisible() { final int max = table.getRowCount(); int row = currentRow; final int init = row; if (row < 0) { row = 0; } else if (max <= row) { row = max - 1; } final CellFormatter fmt = table.getCellFormatter(); final int sTop = Document.get().getScrollTop(); final int sEnd = sTop + Document.get().getClientHeight(); while (0 <= row && row < max) { final Element cur = DOM.getParent(fmt.getElement(row, C_ARROW)); final int cTop = cur.getAbsoluteTop(); final int cEnd = cTop + cur.getOffsetHeight(); if (cEnd < sTop) { row++; } else if (sEnd < cTop) { row--; } else { break; } } if (init != row) { movePointerTo(row, false); } } protected void movePointerTo(final int newRow) { movePointerTo(newRow, true); } protected void movePointerTo(final int newRow, final boolean scroll) { final CellFormatter fmt = table.getCellFormatter(); final boolean clear = 0 <= currentRow && currentRow < table.getRowCount(); if (clear) { final Element tr = DOM.getParent(fmt.getElement(currentRow, C_ARROW)); UIObject.setStyleName(tr, Gerrit.RESOURCES.css().activeRow(), false); } if (newRow >= 0) { table.setWidget(newRow, C_ARROW, pointer); final Element tr = DOM.getParent(fmt.getElement(newRow, C_ARROW)); UIObject.setStyleName(tr, Gerrit.RESOURCES.css().activeRow(), true); if (scroll) { scrollIntoView(tr); } } else if (clear) { table.setWidget(currentRow, C_ARROW, null); pointer.removeFromParent(); } currentRow = newRow; } protected void scrollIntoView(final Element tr) { if (!computedScrollType) { parentScrollPanel = null; Widget w = getParent(); while (w != null) { if (w instanceof ScrollPanel) { parentScrollPanel = (ScrollPanel) w; break; } w = w.getParent(); } computedScrollType = true; } if (parentScrollPanel != null) { parentScrollPanel.ensureVisible(new UIObject() { { setElement(tr); } }); } else { tr.scrollIntoView(); } } protected void movePointerTo(final Object oldId) { final int row = findRow(oldId); if (0 <= row) { movePointerTo(row); } } protected int findRow(final Object oldId) { if (oldId != null) { final int max = table.getRowCount(); for (int row = 0; row < max; row++) { final RowItem c = getRowItem(row); if (c != null && oldId.equals(getRowItemKey(c))) { return row; } } } return -1; } @Override protected void resetHtml(SafeHtml body) { currentRow = -1; super.resetHtml(body); } public void finishDisplay() { if (saveId != null) { movePointerTo(savedPositions.get(saveId)); } if (currentRow < 0) { onDown(); } } public void setSavePointerId(final String id) { saveId = id; } public void setRegisterKeys(final boolean on) { if (on && isAttached()) { if (regNavigation == null) { regNavigation = GlobalKey.add(this, keysNavigation); } if (regAction == null) { regAction = GlobalKey.add(this, keysAction); } } else { if (regNavigation != null) { regNavigation.removeHandler(); regNavigation = null; } if (regAction != null) { regAction.removeHandler(); regAction = null; } } } @Override protected void onLoad() { computedScrollType = false; parentScrollPanel = null; } @Override protected void onUnload() { setRegisterKeys(false); if (saveId != null && currentRow >= 0) { final RowItem c = getRowItem(currentRow); if (c != null) { savedPositions.put(saveId, getRowItemKey(c)); } } computedScrollType = false; parentScrollPanel = null; super.onUnload(); } public class PrevKeyCommand extends KeyCommand { public PrevKeyCommand(int mask, char key, String help) { super(mask, key, help); } @Override public void onKeyPress(final KeyPressEvent event) { ensurePointerVisible(); onUp(); } } public class NextKeyCommand extends KeyCommand { public NextKeyCommand(int mask, char key, String help) { super(mask, key, help); } @Override public void onKeyPress(final KeyPressEvent event) { ensurePointerVisible(); onDown(); } } public class OpenKeyCommand extends KeyCommand { public OpenKeyCommand(int mask, int key, String help) { super(mask, key, help); } @Override public void onKeyPress(final KeyPressEvent event) { ensurePointerVisible(); onOpen(); } } }