/* This file is part of leafdigital leafChat. leafChat is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. leafChat 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 General Public License for more details. You should have received a copy of the GNU General Public License along with leafChat. If not, see <http://www.gnu.org/licenses/>. Copyright 2011 Samuel Marshall. */ package com.leafdigital.ui; import java.awt.*; import java.awt.event.*; import java.lang.reflect.Array; import javax.swing.*; import javax.swing.event.*; import com.leafdigital.ui.api.*; import leafchat.core.api.BugException; /** Combo box */ public class ListBoxImp extends JPanel implements ThemeListener { private JList l; private boolean sort; private int macIndent; private DefaultListModel dlm; private JScrollPane scrollPane; private String onAction,onSelectionChange,onMenu; private boolean selecting=false; ListBoxImp(UISingleton owner) { super(null); setOpaque(false); owner.informThemeListener(this); dlm=new DefaultListModel(); l=new JList(dlm); scrollPane = new JScrollPane(l, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); add(scrollPane); outsideInterface.setMultiSelect(false); l.addMouseListener(new MouseAdapter() { @Override public void mouseClicked(MouseEvent me) { if(me.getClickCount()==2 && onAction!=null) { getInterface().getOwner().getCallbackHandler().callHandleErrors(onAction); } } @Override public void mousePressed(MouseEvent e) { if(e.isPopupTrigger()) { doMenu(e); } } @Override public void mouseReleased(MouseEvent e) { if(e.isPopupTrigger()) { doMenu(e); } } }); l.addListSelectionListener(new ListSelectionListener() { @Override public void valueChanged(ListSelectionEvent lse) { if(onSelectionChange!=null && !lse.getValueIsAdjusting() && !selecting) { getInterface().getOwner().getCallbackHandler().callHandleErrors(onSelectionChange); } } }); resetFont(); l.setCellRenderer(new Renderer()); } @Override public void setBounds(int x, int y, int width, int height) { super.setBounds(x, y, width, height); Insets i = getInsets(); scrollPane.setBounds(i.left + macIndent, i.top, width - macIndent - i.left - i.right, height - i.top - i.bottom); } @Override public Dimension getPreferredSize() { Dimension size = new Dimension(scrollPane.getPreferredSize()); size.width += macIndent; return size; } private class Renderer extends JLabel implements ListCellRenderer { @Override public Component getListCellRendererComponent(JList list,Object value, int index,boolean isSelected,boolean cellHasFocus) { ListItem li=(ListItem)value; if(li.bold) { if(getFont()!=currentBoldFont) setFont(currentBoldFont); } else { if(getFont()!=currentFont) setFont(currentFont); } setText(li.s); if(isSelected && l.isEnabled()) { setForeground(l.getSelectionForeground()); setBackground(l.getSelectionBackground()); setOpaque(true); } else if(li.faint || !l.isEnabled()) { Color original=l.getForeground(); setForeground(new Color(original.getRed(),original.getGreen(),original.getBlue(),128)); setBackground(l.getBackground()); } else { setForeground(l.getForeground()); setBackground(l.getBackground()); } if(cellHasFocus) { setBorder(BorderFactory.createMatteBorder(1,1,1,1,Color.gray)); } else { setBorder(BorderFactory.createEmptyBorder(1,1,1,1)); } return this; } } /** @return List interface */ public ListBox getInterface() { return outsideInterface; } /** Combo interface */ private ListBox outsideInterface=new ListBoxInterface(); private static class ListItem { String s; Object data; boolean bold,faint; public ListItem(String s,Object data) { this.s=s; this.data=data; } @Override public String toString() { return s; } } /** Class implementing combo interface */ class ListBoxInterface extends BasicWidget implements ListBox, InternalWidget { int width=-1; @Override public int getContentType() { return CONTENT_NONE; } @Override public void addXMLChild(String slotName, Widget child) { throw new BugException("Combos cannot contain children"); } @Override public JComponent getJComponent() { return ListBoxImp.this; } @Override public int getPreferredWidth() { if(width==-1) return getPreferredSize().width; else return width; } @Override public int getPreferredHeight(int iWidth) { return getPreferredSize().height; } @Override public String getSelected() { ListItem li=(ListItem)l.getSelectedValue(); return li==null ? null : li.s; } @Override public Object getSelectedData() { ListItem li=(ListItem)l.getSelectedValue(); return li==null ? null : li.data; } @Override public void setSort(boolean b) { sort=b; } @Override public void addItem(String s) { addItem(s,s); } @Override public void addItem(String s,Object data) { if(!sort) { try { selecting=true; dlm.addElement(new ListItem(s,data)); } finally { selecting=false; } } else { int afterIndex; // We want to insert before this for(afterIndex=0;afterIndex<dlm.getSize();afterIndex++) { if(((ListItem)dlm.get(afterIndex)).s.compareToIgnoreCase(s) > 0) break; } try { selecting=true; dlm.insertElementAt(new ListItem(s,data),afterIndex); } finally { selecting=false; } } } @Override public void removeItem(String s) { try { selecting=true; int index=findValue(s); if(index!=-1) dlm.remove(index); } finally { selecting=false; } } @Override public void removeData(Object data) { try { selecting=true; int index=findData(data); if(index!=-1) dlm.remove(index); } finally { selecting=false; } } @Override public void clear() { try { selecting=true; dlm.removeAllElements(); } finally { selecting=false; } } @Override public void clearSelection() { try { selecting=true; l.clearSelection(); } finally { selecting=false; } } @Override public<C> C[] getData(Class<C> c) { @SuppressWarnings("unchecked") C[] data = (C[])Array.newInstance(c,dlm.size()); for(int i=0;i<dlm.size();i++) { data[i] = c.cast(((ListItem)dlm.get(i)).data); } return data; } @Override public String[] getItems() { String[] items=new String[dlm.size()]; for(int i=0;i<dlm.size();i++) { items[i]=((ListItem)dlm.get(i)).s; } return items; } @Override public String[] getMultiSelected() { int[] selected=l.getSelectedIndices(); String[] values=new String[selected.length]; for(int i=0;i<values.length;i++) { values[i]=((ListItem)dlm.get(selected[i])).s; } return values; } @Override public Object[] getMultiSelectedData() { int[] selected=l.getSelectedIndices(); Object[] data=new Object[selected.length]; for(int i=0;i<selected.length;i++) { data[i]=((ListItem)dlm.get(selected[i])).data; } return data; } @Override public void setMultiSelect(boolean b) { try { selecting=true; l.setSelectionMode(b ? ListSelectionModel.MULTIPLE_INTERVAL_SELECTION : ListSelectionModel.SINGLE_SELECTION); } finally { selecting=false; } } @Override public void setOnAction(String callback) { getInterface().getOwner().getCallbackHandler().check(callback); ListBoxImp.this.onAction=callback; } @Override public void setOnChange(String callback) { getInterface().getOwner().getCallbackHandler().check(callback); ListBoxImp.this.onSelectionChange=callback; } @Override public void setWidth(int iWidth) { this.width=iWidth; } private int findValue(String s) { for(int i=0;i<dlm.size();i++) { ListItem li=(ListItem)dlm.get(i); if(li.s.equals(s)) { return i; } } return -1; } private ListItem getItemByName(String s) { for(int i=0;i<dlm.size();i++) { ListItem li=(ListItem)dlm.get(i); if(li.s.equals(s)) { return li; } } return null; } private int findData(Object data) { for(int i=0;i<dlm.size();i++) { ListItem li=(ListItem)dlm.get(i); if(li.data.equals(data)) { return i; } } return -1; } @Override public void setSelectedData(Object data,boolean select) { setSelected(null,data,select); } @Override public void setSelected(String s,boolean select) { setSelected(s,null,select); } private void setSelected(String s,Object data,boolean select) { int[] selected=l.getSelectedIndices(); if(select) { // Check it's not already selected for(int i=0;i<selected.length;i++) { ListItem li=(ListItem)dlm.get(selected[i]); if( (s!=null && li.s.equals(s)) || (data!=null && li.data.equals(data)) ) return; } // OK, add it to list int newIndex=s!=null ? findValue(s) : findData(data); if(newIndex==-1) return; int[] newSelected=new int[selected.length+1]; System.arraycopy(selected,0,newSelected,0,selected.length); newSelected[selected.length]=newIndex; try { selecting=true; l.setSelectedIndices(newSelected); } finally { selecting=false; } } else { // Check it's selected for(int i=0;i<selected.length;i++) { ListItem li=(ListItem)dlm.get(selected[i]); if((s!=null && li.s.equals(s)) || (data!=null && li.data.equals(data))) { int[] newSelected=new int[selected.length-1]; System.arraycopy(selected,0,newSelected,0,i); System.arraycopy(selected,i+1,newSelected,i,selected.length-i-1); try { selecting=true; l.setSelectedIndices(newSelected); } finally { selecting=false; } } } } } @Override public void setEnabled(boolean enabled) { if(isEnabled()!=enabled) { ListBoxImp.this.setEnabled(enabled); l.setEnabled(enabled); } } @Override public boolean isEnabled() { return ListBoxImp.this.isEnabled(); } @Override public void setOnMenu(String callback) { if(callback!=null) { getOwner().getCallbackHandler().check(callback,new Class[] { com.leafdigital.ui.api.PopupMenu.class }); } onMenu=callback; } @Override public void setUseFontSettings(boolean useFontSettings) { ListBoxImp.this.useFontSettings=useFontSettings; resetFont(); } @Override public void setBold(String s,boolean bold) { ListItem li=getItemByName(s); if(li.bold==bold) return; li.bold=bold; l.repaint(); } @Override public void setFaint(String s,boolean faint) { ListItem li=getItemByName(s); if(li.faint==faint) return; li.faint=faint; l.repaint(); } @Override public void setMacIndent(boolean macIndent) { setMacIndent(macIndent ? SupportsMacIndent.TYPE_EDIT_LEGACY : SupportsMacIndent.TYPE_NONE); } @Override public void setMacIndent(String macIndent) { int newValue = UISingleton.getMacIndent(macIndent); if(newValue != ListBoxImp.this.macIndent) { ListBoxImp.this.macIndent = newValue; Rectangle bounds = getBounds(); setBounds(bounds.x, bounds.y, bounds.width, bounds.height); } } } private void doMenu(MouseEvent e) { if(onMenu==null) return; // Get selection int[] selected=l.getSelectedIndices(); if(selected.length>0) { // Check they actually clicked on one of the selected thing boolean found=false; for(int i=0;i<selected.length;i++) { Rectangle r=l.getCellBounds(selected[i],selected[i]); if(r.contains(e.getPoint())) { found=true; break; } } if(!found) selected=new int[0]; // Clear selection; next bit // picks it up } if(selected.length==0) { // Nothing selected? OK let's select the thing under here int index=l.locationToIndex(e.getPoint()); if(index==-1) return; // Nothing in list Rectangle r=l.getCellBounds(index,index); if(!r.contains(e.getPoint())) return; // Didn't actually hit anything l.setSelectedIndex(index); // This will send 'user selected' messages selected=new int[] {index}; } // Build menu PopupMenuImp pm=new PopupMenuImp(); getInterface().getOwner().getCallbackHandler().call( onMenu,new Object[] {pm.getInterface()}); // If nothing was added, exit if(pm.getComponentCount()==0) return; // Show menu pm.show(l,e.getX(),e.getY()); } private final static Font defaultListFont=(new JList()).getFont(); private boolean useFontSettings; private Font currentFont,currentBoldFont; /** Update font based on current settings */ private void resetFont() { currentFont=null; if(useFontSettings) { currentFont=((ListBoxInterface)outsideInterface).getUI().getFont(); } if(currentFont==null) currentFont=defaultListFont; currentBoldFont=currentFont.deriveFont(Font.BOLD); if(!l.getFont().equals(currentFont)) { l.setFont(currentFont); ((ListBoxInterface)outsideInterface).redoLayout(); } } @Override public void updateTheme(Theme t) { resetFont(); } }