// This file is part of AceWiki. // Copyright 2008-2013, AceWiki developers. // // AceWiki is free software: you can redistribute it and/or modify it under the terms of the GNU // Lesser General Public License as published by the Free Software Foundation, either version 3 of // the License, or (at your option) any later version. // // AceWiki is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without // even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public License along with AceWiki. If // not, see http://www.gnu.org/licenses/. package ch.uzh.ifi.attempto.preditor; import java.util.ArrayList; import java.util.List; import nextapp.echo.app.Component; import nextapp.echo.app.Extent; import nextapp.echo.app.Row; /** * This is a helper class that takes care of the management and layout of the menu blocks for the * predictive editor. * * @author Tobias Kuhn */ class MenuBlockManager { private List<MenuBlockContent> mbContents = new ArrayList<MenuBlockContent>(); private List<MenuBlockColumn> mbColumns = new ArrayList<MenuBlockColumn>(); private PreditorWindow preditor; private String filter; /** * Creates a new menu block manager. * * @param preditor The predictive editor object. */ public MenuBlockManager(PreditorWindow preditor) { this.preditor = preditor; } /** * Returns the number of menu blocks. * * @return The number of menu blocks. */ public int getMenuBlockCount() { return mbContents.size(); } /** * Adds a menu block with the given content. * * @param m The content of the menu block to be added. */ public void addMenuBlockContent(MenuBlockContent m) { if (!m.isEmpty()) { mbContents.add(m); } } /** * Removes all menu blocks. */ public void clear() { mbContents.clear(); } /** * Returns the number of menu entries. * * @return The number of menu entries. */ public int getMenuEntryCount() { int c = 0; for (MenuBlockContent mc : mbContents) { c += mc.getEntryCount(); } return c; } /** * Sets the filter for the menu entries. * * @param filter The filter to be set. */ public void setFilter(String filter) { if (filter == null) filter = ""; filter = filter.replaceFirst("^\\s*", "").replaceFirst("\\s*$", ""); for (MenuBlockContent c : mbContents) { c.setFilter(filter); } this.filter = filter; } /** * Returns the current filter for the menu entries. * * @return The filter. */ public String getFilter() { return filter; } /** * Returns the longest possible string with which all menu entries start (after filtering). An * empty string is returned if at least two entries start with a different character. * * @return The start string. */ public String getStartString() { String startString = ""; List<String> blockStartStrings = new ArrayList<String>(); for (MenuBlockContent mc : mbContents) { String s = mc.getStartString(); if (s != null) { blockStartStrings.add(s); } } if (blockStartStrings.isEmpty()) return null; String first = blockStartStrings.get(0); blockStartStrings.remove(0); if (blockStartStrings.isEmpty()) return first; for (int i = 0; i < first.length(); i++) { char c = first.charAt(i); boolean stop = false; for (String s : blockStartStrings) { if (s.length() <= i || s.charAt(i) != c) stop = true; } if (stop) break; startString += c; } return startString; } /** * Creates an returns the GUI component showing all menu blocks. * * @return The GUI component. */ public synchronized Component createGUI() { mbColumns.clear(); for (MenuBlockContent c : mbContents) { mbColumns.add(new MenuBlockColumn(c, preditor)); } condenseColumns(); Row menuBlockRow = new Row(); menuBlockRow.setCellSpacing(new Extent(9)); int colCount = mbColumns.size(); int width = ( 720 / ( colCount > 3 ? colCount : 3 ) ) - 12; for (MenuBlockColumn c : mbColumns) { menuBlockRow.add(c.createGUI(width)); } return menuBlockRow; } /** * This method condenses the columns by putting several menu blocks into the same column. */ private void condenseColumns() { int i = mbColumns.size()-1; while (i > 0 && mbColumns.size() > 3) { MergeStep step = new MergeStep(i-1); if (step.getHeightUnits() <= 18) { step.apply(); } i--; } while (hasTooManyColumns()) { CondenseStep step = new MergeStep(0); for (int j = 1; j < mbColumns.size()-1; j++) { CondenseStep s = new MergeStep(j); if (s.getWeightDiff() < step.getWeightDiff()) { step = s; } s = new SplitStep(j); if (s.getWeightDiff() < step.getWeightDiff()) { step = s; } } step.apply(); } } /** * This method returns whether the number of columns has to be condensed further or not. * * @return Whether there are still too many columns. */ private boolean hasTooManyColumns() { return mbContents.size() < mbColumns.size()*(mbColumns.size()-2); } /** * This internal interface represents a step of condensing the columns. */ private interface CondenseStep { public int getWeightDiff(); public void apply(); } /** * This internal class represents a condensing step of merging two columns. */ private class MergeStep implements CondenseStep { private MenuBlockColumn c1, c2; private int diff; private int heightUnits; MergeStep(int i) { c1 = mbColumns.get(i); c2 = mbColumns.get(i+1); diff = MenuBlockColumn.getCombinedWeight(c1, c2) - c1.getWeight() - c2.getWeight(); heightUnits = c1.getHeightUnits() + c2.getHeightUnits(); } public int getWeightDiff() { return diff; } public int getHeightUnits() { return heightUnits; } public void apply() { c1.includeAtBottom(c2); mbColumns.remove(c2); } } /** * This internal class represents a condensing step of splitting one column by moving its menu * blocks to the adjacent columns. */ private class SplitStep implements CondenseStep { private MenuBlockColumn c1, s, c2; private MenuBlockColumn[] splitted; private int diff; SplitStep(int i) { c1 = mbColumns.get(i-1); s = mbColumns.get(i); c2 = mbColumns.get(i+1); splitted = s.getSplitted(c2.getWeight() - c1.getWeight()); int w1 = MenuBlockColumn.getCombinedWeight(c1, splitted[0]); int w2 = MenuBlockColumn.getCombinedWeight(c2, splitted[1]); diff = w1 + w2 - c1.getWeight() - s.getWeight() - c2.getWeight(); } public int getWeightDiff() { return diff; } public void apply() { c1.includeAtBottom(splitted[0]); c2.includeAtTop(splitted[1]); mbColumns.remove(s); } } }