| // 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)); |
| } |
| } |