/******************************************************************************* * Copyright (c) 2014 Mentor Graphics and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Mentor Graphics - initial API and implementation *******************************************************************************/ package com.codesourcery.installer.console; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import org.eclipse.osgi.util.NLS; import com.codesourcery.installer.IInstallConsoleProvider; import com.codesourcery.internal.installer.InstallMessages; /** * This class can be used to present a list of check box items * that can be selected in the console. * 1. [x] Item 1 * 2. [x] Item 2 * ... * * Call {@link #addItem(String, Object, boolean, boolean)} to add items * for the list. * * Example: * private ConsoeListPrompter<String> prompter; * * public MyClass() { * prompter = new ConsoleListPrompter<String>("Select an item:"); * prompter.addItem("Item 1", item1Data, true, true); * prompter.addItem("Item 2", item2Data, true, true); * } * * public String getConsoleResponse(String input) throws IllegalArgumentException { * String response = prompter.getResponse(input); * if (response == null) { * ArrayList<String> data = new ArrayList<String>(); * prompter.getSelectedDatas(data); * // Handle data... * } * * return response; * } * * The list can also be set to select a single option. * 1. Item 1 * 2. Item 2 * * In this case use {@link #addItem(String, Object)} and get the selected * item using ... */ public class ConsoleListPrompter<T> implements IInstallConsoleProvider { /** Checked item prefix */ private String checkedPrefix = "[*]"; /** Unchecked item prefix */ private String uncheckedPrefix = "[ ]"; /** Items */ private ArrayList<Item<T>> items = new ArrayList<Item<T>>(); /** Console message */ private String message; /** <code>true</code> if only a single option can be selected */ private boolean single = false; /** Default item */ private Item<T> defaultItem = null; /** * Constructor * * @param message Message to display */ public ConsoleListPrompter(String message) { this.message = message; } /** * Constructor * * @param message Message to display * @param single <code>true</code> if only a single option can be selected */ public ConsoleListPrompter(String message, boolean single) { this(message); this.single = single; } /** * Returns if only a single selection can be made. * * @return <code>true</code> if single selection */ public boolean isSingleSelection() { return single; } /** * Sets a default item. This is only used if a single * selection is allowed. * * @param index Index of the default item or <code>-1</code> */ public void setDefaultItem(int index) { if (index == -1) { this.defaultItem = null; } else { this.defaultItem = items.get(index); } } /** * Returns the message. * * @return Message */ public String getMessage() { return message; } /** * Sets the string used for checked and un-checked items. * The default is "[*]" and "[ ]". * * @param checked Checked string * @param unchecked Un-checked string */ public void setCheckedString(String checked, String unchecked) { checkedPrefix = checked; uncheckedPrefix = unchecked; } /** * Adds an item to the list. * * @param name Name to display for the item * @param data Data associated with the item * @param selected <code>true</code> if item should be checked initially * @param optional <code>true</code> if the item can be changed * @return Index of item */ public int addItem(String name, T data, boolean selected, boolean optional) { Item<T> item = new Item<T>(name, data, selected, optional); items.add(item); return items.size() - 1; } /** * Adds an item to the list. * * @param index Index of parent item * @param name Name to display for the item * @param data Data associated with the item * @param selected <code>true</code> if item should be checked initially * @param optional <code>true</code> if the item can be changed * @return Index of item */ public int addItem(int index, String name, T data, boolean selected, boolean optional) { Item<T> item = new Item<T>(items.get(index), name, data, selected, optional); items.add(item); return items.size() - 1; } /** * Returns the index of the first item with data. * * @param data Data * @return Index of item or <code>-1</code> */ public int getItemIndex(T data) { int index = -1; for (int i = 0; i < items.size(); i ++) { if (data.equals(items.get(i).getData())) { index = i; break; } } return index; } /** * Adds an item to the list. * * @param name Name to display for the item * @param data Data associated with the item * @return Index of item */ public int addItem(String name, T data) { return addItem(name, data, false, true); } /** * Removes an item from the list. * * @param index Index of item to remove */ public void removeItem(int index) { items.remove(index); } @Override public String getConsoleResponse(String input) throws IllegalArgumentException { String response = null; if (input == null) { response = toString(); } else { // Continue if (input.isEmpty()) { response = null; // If single selection if (isSingleSelection()) { // If there is a default then select it if (defaultItem != null) { defaultItem.setSelected(true); } // Else prompt user to select an item else { response = NLS.bind(InstallMessages.Error_InvalidSelection0, Integer.toString(items.size())); } } } // Toggle item else { try { int index = Integer.parseInt(input) - 1; // Invalid input if ((index < 0) || (index >= items.size())) { response = NLS.bind(InstallMessages.Error_InvalidSelection0, Integer.toString(items.size())); } else { Item<T> item = items.get(index); // Single selection if (isSingleSelection()) { item.setSelected(true); response = null; } // Multiple selection else { // Item can be changed if (item.isOptional()) { item.setSelected(!item.isSelected()); response = toString(); } else { response = InstallMessages.Error_ItemCantChange; } } } } catch (NumberFormatException e) { response = NLS.bind(InstallMessages.Error_InvalidSelection0, Integer.toString(items.size())); } } } return response; } /** * Returns the number of items in the list. * * @return Number of items */ public int getItemCount() { return items.size(); } /** * Returns an item name. * * @param index Index of the item * @return Item name */ public String getItemName(int index) { Item<T> item = items.get(index); return item.getName(); } /** * Returns the item data. * * @param index Index of the item * @return Item data */ public T getItemData(int index) { Item<T> item = items.get(index); return item.getData(); } /** * Returns the data for all selected items. * * @param list List to fill with item data */ public void getSelectedData(List<T> list) { list.clear(); for (Item<T> item : items) { if (item.isSelected()) { list.add(item.getData()); } } } /** * Returns if an item is selected. * * @param index Index of item * @return <code>true</code> if item is selected */ public boolean isItemSelected(int index) { Item<T> item = items.get(index); return item.isSelected(); } /** * Sets an item selected. * * @param index Index of item * @param selected <code>true</code> to select item. */ public void setItemSelected(int index, boolean selected) { Item<T> item = items.get(index); item.setSelected(selected); } @Override public String toString() { return toString(true, true, true); } /** * Returns the list as a string. * * @param message <code>true</code> to include message * @param numbers <code>true</code> to include item numbers * @param prompt <code>true</code> to include prompt * @return List as string */ public String toString(boolean message, boolean numbers, boolean prompt) { StringBuilder buffer = new StringBuilder(); // Message if (message && (getMessage() != null)) { buffer.append(getMessage()); buffer.append("\n\n"); } // Items int index = 0; int defaultIndex = -1; for (Item<T> item : items) { if (item == defaultItem) { defaultIndex = index; } if (numbers) { buffer.append(String.format("%4d. %s\n", ++index, item.toString())); } else { buffer.append(String.format("%s\n", item.toString())); } } // Prompt if (prompt) { buffer.append("\n"); if (isSingleSelection()) { // Select option if (defaultIndex == -1) { buffer.append(NLS.bind(InstallMessages.ConsoleItemsSelectPrompt1, "1", Integer.toString(items.size()))); } // Select option or use default else { buffer.append(NLS.bind(InstallMessages.ConsoleItemsSelectDefaultPrompt2, new Object[] { Integer.toString(defaultIndex + 1), "1", Integer.toString(items.size()) })); } } // Select option to toggle else { buffer.append(InstallMessages.ConsoleItemsTogglePrompt); } } return buffer.toString(); } /** * Internal class to store item attributes. */ @SuppressWarnings("hiding") private class Item<T> { /** Item name */ private String name; /** <code>true</code> if item is selected */ private boolean selected; /** <code>true</code> if item can change */ private boolean optional; /** Item data */ private T data; /** Parent item */ private Item<T> parent; /** Child items */ private ArrayList<Item<T>> children = new ArrayList<Item<T>>(); /** * Constructor * * @param name Item name * @param data Data for item * @param selected <code>true</code> to select item by default * @param optional <code>true</code> if item can change */ public Item(String name, T data, boolean selected, boolean optional) { this.name = name; this.data = data; this.selected = selected; this.optional = optional; } /** * Constructor * * @param parent Parent item * @param name Item name * @param data Data for item * @param selected <code>true</code> to select item by default * @param optional <code>true</code> if item can change */ public Item(Item<T> parent, String name, T data, boolean selected, boolean optional) { this.parent = parent; this.data = data; this.selected = selected; this.optional = optional; StringBuffer prefix = new StringBuffer(); Item<T> p = parent; while (p != null) { prefix.append(" "); p = p.parent; } prefix.append(name); this.name = prefix.toString(); this.parent.children.add(this); } /** * Returns the item name. * * @return Name */ public String getName() { return name; } /** * Returns the item data. * * @return Data */ public T getData() { return data; } /** * Returns if the item is selected. * * @return <code>true</code> if selected */ public boolean isSelected() { return selected; } /** * Sets the item selected. * * @param selected <code>true</code> selected */ public void setSelected(boolean selected) { this.selected = selected; // Set children state if (children.size() > 0) { for (Item<T> child : children) { child.setSelected(selected); } } // If de-selected, set parents de-selected if (!selected) { Item<T> parent = this.parent; while (parent != null) { parent.selected = false; parent = parent.parent; } } // If selected, see if parent can be selected else { Item<T> parent = this.parent; while (parent != null) { boolean childrenSelected = true; for (Item<T> child : parent.children) { if (!child.selected) { childrenSelected = false; break; } } parent.selected = childrenSelected; parent = parent.parent; } } } /** * Returns if the item can change. * * @return <code>true</code> if item can change */ public boolean isOptional() { return optional; } @Override public String toString() { // Single selection if (isSingleSelection()) { return getName(); } // Multiple selection else { if (isOptional()) { return (isSelected() ? checkedPrefix : uncheckedPrefix) + " " + getName(); } else { char[] padding = new char[checkedPrefix.length()]; Arrays.fill(padding, ' '); return new String(padding) + " " + getName(); } } } } }