/*******************************************************************************
* Copyright (c) 2001, 2010 Mathew A. Nelson and Robocode contributors
* 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://robocode.sourceforge.net/license/epl-v10.html
*
* Contributors:
* Matthew Reeder
* - Initial API and implementation
*******************************************************************************/
package net.sf.robocode.ui.editor;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.io.File;
/**
* Customized JMenuItem where each item is bound to a specific JInternalFrame,
* so we can have a dynamic menu of open windows.
*
* @author Matthew Reeder (original)
*/
@SuppressWarnings("serial")
public class WindowMenuItem extends JCheckBoxMenuItem implements ActionListener {
// Maximum number of windows that will be shown on the menu (to get the rest, you'll
// have to open the dialog). The number 9 is also the number of most recently used
// files that normally show up in other applications. The reason is so that you can
// give them dynamic hotkeys from 1 to 9. Otherwise, there's no reason (besides
// avoiding taking up way too much space) to limit the size of the menu.
public static final int WINDOW_MENU_MAX_SIZE = 9;
// Number of static menu items before the dynamic menu (including seperators)
public static final int PRECEDING_WINDOW_MENU_ITEMS = 3;
// Number of static menu items after the dynamic menu (including seperators
// and the More Windows... menu item)
public static final int SUBSEQUENT_WINDOW_MENU_ITEMS = 1;
// Normal max length of a window name
public static final int MAX_WINDOW_NAME_LENGTH = 30;
// I make one "special" menu item that isn't tied to a window. Since it has
// similar needs for enabling/visibility and labeling, I made it the same class.
public static final int REGULAR_WINDOW = 0, SPECIAL_MORE = 2;
private EditWindow window;
private JMenu parentMenu;
private final int type;
public WindowMenuItem(EditWindow window, JMenu parentMenu) {
super();
this.window = window;
this.parentMenu = parentMenu;
type = REGULAR_WINDOW;
parentMenu.add(this, parentMenu.getMenuComponentCount() - SUBSEQUENT_WINDOW_MENU_ITEMS);
addActionListener(this);
}
/**
* WindowMenuItem Constructor for "More Windows..." menu.
*/
public WindowMenuItem() {
type = SPECIAL_MORE;
}
/**
* Event handler for the menu item
* <p/>
* Brings the window to the front. This should be called for the "More
* Windows..." Item, because it doesn't make itself its own ActionListener.
* <p/>
* Note that e can be null, and this menu item might not be showing (if this
* is called from the "More Windows" dialog).
*/
public void actionPerformed(ActionEvent e) {
if (window.isIcon()) {
try {
window.setIcon(false);
} catch (Throwable ignored) {}
}
if (window.getDesktopPane() != null) {
window.getDesktopPane().setSelectedFrame(window);
}
window.toFront();
window.grabFocus();
try {
window.setSelected(true);
} catch (Throwable ignored) {}
}
/**
* Returns the label that should be used. If the menu item is supposed to be
* hidden, this may not be a real valid label.
*/
@Override
public String getText() {
if (type == SPECIAL_MORE) {
Container parent = getParent();
if (parent == null) {
return "";
}
int numWindows = parent.getComponentCount() - PRECEDING_WINDOW_MENU_ITEMS - SUBSEQUENT_WINDOW_MENU_ITEMS;
if (numWindows <= 0) {
return "No Windows Open";
}
return "More Windows...";
}
if (window == null || parentMenu == null) {
return "";
}
String text = (getIndex() + 1) + " " + getFileName();
if (window.modified) {
text += " *";
}
return text;
}
protected String getFileName() {
if (window.getFileName() == null) {
return "Untitled " + (getPrecedingNewFiles() + 1);
}
String name = window.getFileName();
if (name.length() < MAX_WINDOW_NAME_LENGTH) {
return name;
}
if (name.indexOf(File.separatorChar) < 0) {
return name;
} // If there are no separators, I can't really intelligently truncate.
int startLength = name.indexOf(File.separatorChar, 1) + 1;
int endLength = name.length() - name.lastIndexOf(File.separatorChar);
if (endLength + startLength + 3 > name.length()) {
return name;
} // return name anyways, since we're not getting it any shorter.
boolean change;
do {
change = false;
int newEndLength = name.length() - name.lastIndexOf(File.separatorChar, name.length() - endLength - 1);
if (newEndLength + startLength + 3 <= MAX_WINDOW_NAME_LENGTH) {
endLength = newEndLength;
change = true;
}
int newStartLength = name.indexOf(File.separatorChar, startLength + 1) + 1;
if (endLength + startLength + 3 <= MAX_WINDOW_NAME_LENGTH) {
startLength = newStartLength;
change = true;
}
} while (change);
return name.substring(0, startLength) + "..." + name.substring(name.length() - endLength);
}
/**
* @return how many nameless windows occur before this one in the parent.
*/
protected int getPrecedingNewFiles() {
int count = 0;
for (int i = 0; i < WINDOW_MENU_MAX_SIZE
&& i < parentMenu.getMenuComponentCount() - PRECEDING_WINDOW_MENU_ITEMS - SUBSEQUENT_WINDOW_MENU_ITEMS
&& parentMenu.getMenuComponent(i + PRECEDING_WINDOW_MENU_ITEMS) != this; i++) {
if (parentMenu.getMenuComponent(i + PRECEDING_WINDOW_MENU_ITEMS) instanceof WindowMenuItem
&& ((WindowMenuItem) parentMenu.getMenuComponent(i + PRECEDING_WINDOW_MENU_ITEMS)).window.getFileName()
== null) {
count++;
}
}
return count;
}
/**
* Figures out what index (from 0 to WINDOW_MENU_MAX_SIZE-1) this item is in
* the window menu.
* <p/>
* @return -1 if this item isn't showing.
*/
protected int getIndex() {
for (int i = 0; i < WINDOW_MENU_MAX_SIZE
&& i < parentMenu.getMenuComponentCount() - PRECEDING_WINDOW_MENU_ITEMS - SUBSEQUENT_WINDOW_MENU_ITEMS; i++) {
if (this == parentMenu.getMenuComponent(i + PRECEDING_WINDOW_MENU_ITEMS)) {
return i;
}
}
return -1;
}
/**
* Returns the index of the character in the label that should be underlined
*/
@Override
public int getDisplayedMnemonicIndex() {
return (type == SPECIAL_MORE) ? 11 : 0;
}
/**
* Returns the keyboard mnemonic for this item, which is the virtual key
* code for its 1-based index.
*/
@Override
public int getMnemonic() {
return (type == SPECIAL_MORE) ? KeyEvent.VK_S : KeyEvent.VK_1 + getIndex();
}
/**
* Returns true if this item should be showing.
* <p/>
* Returns false if there are more than WINDOW_MENU_MAX_SIZE items before it
* in the menu.
*/
@Override
public boolean isVisible() {
if (type == SPECIAL_MORE) {
Container parent = getParent();
if (parent == null) {
return true;
}
int numWindows = parent.getComponentCount() - PRECEDING_WINDOW_MENU_ITEMS - SUBSEQUENT_WINDOW_MENU_ITEMS;
updateSelection();
return (numWindows <= 0) || (numWindows > WINDOW_MENU_MAX_SIZE);
}
updateSelection();
return getIndex() >= 0;
}
/**
* Returns true if this item should be enabled (selectable).
* <p/>
* Returns false if it is a More Windows... item and there are no windows.
*/
@Override
public boolean isEnabled() {
if (type == SPECIAL_MORE) {
Container parent = getParent();
if (parent == null) {
return true;
}
int numWindows = parent.getComponentCount() - PRECEDING_WINDOW_MENU_ITEMS - SUBSEQUENT_WINDOW_MENU_ITEMS;
return (numWindows > 0);
}
return true;
}
/**
* Determines if this menu item should currently show as "selected".
* <p/>
* The item should be seleced if the window it's tied to has focus.
*/
@Override
public boolean isSelected() {
return (type != SPECIAL_MORE) && (window != null && window.getDesktopPane() != null)
&& window.getDesktopPane().getSelectedFrame() == window;
}
/**
* Makes sure the underlying menu item knows if we're selected.
*/
public void updateSelection() {
setSelected(isSelected()); // Sort of a silly thing to do...
setEnabled(isEnabled());
}
/**
* @return the EditWindow that this menu item is tied to.
*/
public EditWindow getEditWindow() {
return window;
}
/**
* Creates a string representation of this object.
* <p/>
* Handy for repurposing the menu items as list items :-)
*/
@Override
public String toString() {
return (type == SPECIAL_MORE) ? "" : getFileName();
}
}