blob: 7ad279310aaa4f2ecdf2c17bb727d54c3efe41d7 [file] [log] [blame]
// Copyright 2010-2015, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package org.mozc.android.inputmethod.japanese.ui;
import org.mozc.android.inputmethod.japanese.protobuf.ProtoCandidates.CandidateList;
import org.mozc.android.inputmethod.japanese.protobuf.ProtoCandidates.CandidateWord;
import org.mozc.android.inputmethod.japanese.ui.CandidateLayout.Row;
import org.mozc.android.inputmethod.japanese.ui.CandidateLayout.Span;
import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
/**
* Layouts the symbol candidate words.
*
* This is a table-like layout, which can be scrollable vertically.
* The client can set the minimum column width and the row height.
* The number of columns is calculated based on the given minimum column width
* and the view's width. Each row should horizontally fit the view width.
*
*/
public class SymbolCandidateLayouter implements CandidateLayouter {
private Optional<SpanFactory> spanFactory = Optional.absent();
/** The minimum width for each column. */
private float minColumnWidth;
/** The number of rows in a page. */
private int rowHeight;
/** The current view's width. */
private int viewWidth;
public void setMinColumnWidth(float minColumnWidth) {
this.minColumnWidth = minColumnWidth;
}
public void setRowHeight(int rowHeight) {
this.rowHeight = rowHeight;
}
@Override
public boolean setViewSize(int width, int height) {
// Ignore the height.
if (viewWidth == width) {
return false;
}
viewWidth = width;
return true;
}
@Override
public int getPageWidth() {
return viewWidth;
}
@Override
public int getPageHeight() {
return rowHeight;
}
@Override
public Optional<CandidateLayout> layout(CandidateList candidateList) {
Preconditions.checkNotNull(candidateList);
if (viewWidth <= 0 || rowHeight <= 0 || minColumnWidth <= 0 ||
candidateList == null || candidateList.getCandidatesCount() == 0 ||
!spanFactory.isPresent()) {
return Optional.absent();
}
int numColumns = getNumColumns(viewWidth, minColumnWidth);
List<Row> rowList = buildRowList(candidateList, spanFactory.get(), numColumns);
for (Row row : rowList) {
layoutSpanList(row.getSpanList(), viewWidth, numColumns);
}
layoutRowList(rowList, rowHeight);
return Optional.of(new CandidateLayout(rowList, viewWidth, rowHeight * rowList.size()));
}
private static int getNumColumns(int viewWidth, float minColumnWidth) {
// Ensure at least one column.
return Math.max((int) (viewWidth / minColumnWidth), 1);
}
/** Builds a list of {@code Row}s from candidate word list. */
static List<Row> buildRowList(
CandidateList candidateList, SpanFactory spanFactory, int numColumns) {
Preconditions.checkNotNull(candidateList);
Preconditions.checkNotNull(spanFactory);
List<Row> rowList = new ArrayList<Row>(
(candidateList.getCandidatesCount() + numColumns - 1) / numColumns);
int columnIndex = 0;
Row row = null;
for (CandidateWord candidateWord : candidateList.getCandidatesList()) {
if (columnIndex == 0) {
row = new Row();
rowList.add(row);
}
row.addSpan(spanFactory.newInstance(candidateWord));
columnIndex = (columnIndex + 1) % numColumns;
}
return rowList;
}
/** Sets the left and right position to all spans. */
static void layoutSpanList(List<Span> spanList, int viewWidth, int numColumns) {
Preconditions.checkNotNull(spanList);
float left = 0;
for (ListIterator<Span> iter = spanList.listIterator(); iter.hasNext(); ) {
Span span = iter.next();
// To avoid fp error at the end of the span list, we use the following expression.
float right = viewWidth * iter.nextIndex() / (float) numColumns;
span.setLeft(left);
span.setRight(right);
left = right;
}
}
/** Sets the top, height and width to all rows. */
static void layoutRowList(List<Row> rowList, int rowHeight) {
Preconditions.checkNotNull(rowList);
// The pageHeight will be divided evenly to the each row.
for (ListIterator<Row> iter = rowList.listIterator(); iter.hasNext(); ) {
int index = iter.nextIndex();
Row row = iter.next();
row.setTop(rowHeight * index);
row.setHeight(rowHeight);
List<Span> spanList = row.getSpanList();
row.setWidth(spanList.isEmpty() ? 0 : spanList.get(spanList.size() - 1).getRight());
}
}
/**
* @param spanFactory the spanFactory to set
*/
public void setSpanFactory(SpanFactory spanFactory) {
this.spanFactory = Optional.of(Preconditions.checkNotNull(spanFactory));
}
}