/* 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.ircui; import java.awt.*; import java.awt.event.*; import java.util.*; import javax.swing.*; import util.*; import com.leafdigital.irc.api.*; import com.leafdigital.ui.api.*; import com.leafdigital.ui.api.Button; import com.leafdigital.ui.api.Dialog; import com.leafdigital.ui.api.Label; import com.leafdigital.ui.api.Window; import leafchat.core.api.*; /** * Implements the mode display/change area. */ @UIHandler("modeparam") public class ModeDisplay extends JComponent implements SizeInfo { private String chan; private Server s; private PluginContext context; private Window parent; private JButton add; private Font smallFont; private final static int HGAP=4,VGAP=4; private Map<String, JButton> letterModes = new HashMap<String, JButton>(); void changeServer(Server s) { this.s=s; } /** * * @param pc Context * @param server Server * @param parent Parent window * @param chan Channel */ public ModeDisplay(PluginContext pc,Server server,Window parent,String chan) { this.context=pc; this.s=server; this.chan=chan; this.parent=parent; setLayout(null); JLabel label=new JLabel("Mode"); label.setBorder(BorderFactory.createEmptyBorder(7,0,0,2)); add(label); add=new FancyButton("+"); add.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent ae) { JPopupMenu pm=new JPopupMenu("Add mode"); String modeString=s.getChanModes(); String[] modes=new String[modeString.length()]; for(int i=0;i<modes.length;i++) { modes[i]=modeString.substring(i,i+1); } Arrays.sort(modes,new Comparator<String>() { @Override public int compare(String s1, String s2) { int compare=s1.compareToIgnoreCase(s2); if(compare==0) return Character.isUpperCase(s1.charAt(0)) ? 1 : -1; else return compare; } }); for(int i=0;i<modes.length;i++) { final char mode=modes[i].charAt(0); if(letterModes.containsKey(mode+"")) continue; switch(s.getChanModeType(mode)) { case Server.CHANMODE_ALWAYSPARAM: case Server.CHANMODE_SETPARAM: pm.add(new AbstractAction() { @Override public Object getValue(String key) { if(key==NAME) return "+"+mode+getMenuDescription(mode)+"..."; else if(key==MNEMONIC_KEY && Character.isLowerCase(mode)) return new Integer(mode); return super.getValue(key); } @Override public void actionPerformed(ActionEvent e) { showSetDialog(mode,null); } }); break; case Server.CHANMODE_NOPARAM: pm.add(new AbstractAction() { @Override public Object getValue(String key) { if(key==NAME) return "+"+mode+getMenuDescription(mode); else if(key==MNEMONIC_KEY && Character.isLowerCase(mode)) return new Integer(mode); return super.getValue(key); } @Override public void actionPerformed(ActionEvent e) { s.sendLine(IRCMsg.constructBytes("MODE "+ModeDisplay.this.chan+" +"+mode)); } }); break; } } pm.show(ModeDisplay.this,add.getX(),add.getY()+add.getHeight()); } }); setBorder(BorderFactory.createEmptyBorder(0,0,2,0)); smallFont=label.getFont(); smallFont=smallFont.deriveFont(smallFont.getSize()*0.85f); label.setFont(smallFont); add.setFont(smallFont); relayout(); } /** * @param mode Mode letter * @return True if the mode with that letter exists */ boolean hasMode(char mode) { return letterModes.containsKey(mode+""); } /** * @param mode Mode letter * @return Value of mode or null if not set */ String getModeValue(char mode) { LetterButton button = (LetterButton)letterModes.get(mode + ""); if(button == null) { return null; } return button.getParam(); } private static String getDescription(char mode) { switch(mode) { case 'm' : return "moderated"; case 'p' : return "private"; case 's' : return "secret"; case 'i' : return "invite-only"; case 't' : return "ops control topic"; case 'n' : return "no messages from outside"; case 'l' : return "user limit"; case 'k' : return "channel key"; default: return null; } } private static String getMenuDescription(char mode) { String description=getDescription(mode); if(description==null) return ""; else return " ("+description+")"; } private void relayout() { Insets insets=getInsets(); int x=0,y=0,rowY=0,width=getWidth()-insets.left-insets.right; for(int i=0;i<getComponentCount();i++) { if(i>0) x+=HGAP; Dimension size=getComponent(i).getPreferredSize(); if(x+size.width>width) // Next row { if(y!=0) y+=VGAP; y+=rowY; x=0; getComponent(i).setBounds(x+insets.left,y+insets.top,size.width,size.height); x=size.width; rowY=size.height; } else { getComponent(i).setBounds(x+insets.left,y+insets.top,size.width,size.height); x+=size.width; rowY=Math.max(rowY,size.height); } } } @Override public void setBounds(int x,int y,int width,int height) { super.setBounds(x,y,width,height); relayout(); } /** * Adds a mode. * @param letter Mode letter * @param param Parameter */ public void addMode(char letter,String param) { if(letterModes.containsKey(letter+"")) removeMode(letter); JButton letterButton=new LetterButton(letter,param); letterModes.put(letter+"",letterButton); int insertBefore=1; for(;insertBefore<getComponentCount()-1;insertBefore++) { LetterButton lb=(LetterButton)getComponent(insertBefore); if(lb.getLetter()>letter) break; } add(letterButton,insertBefore); relayout(); revalidate(); } /** * Removes a mode. * @param letter Mode letter */ public void removeMode(char letter) { JButton letterButton=letterModes.get(letter+""); if(letterButton!=null) { letterModes.remove(letter+""); remove(letterButton); relayout(); revalidate(); } } /** Clears all modes. */ public void clearModes() { while(getComponentCount()>2) { remove(1); } letterModes.clear(); relayout(); revalidate(); } private boolean op=false; /** * Updates op status (whether you can change modes). * @param op True if currently op */ public void updateOpStatus(boolean op) { if(this.op==op) return; this.op=op; if(op) add(add); else remove(add); relayout(); revalidate(); } private class FancyButton extends JButton { FancyButton(String label) { super(label); setBorder(BorderFactory.createCompoundBorder( BorderFactory.createEmptyBorder(5,0,0,0), BorderFactory.createCompoundBorder( BorderFactory.createLineBorder(new Color(151,151,151),1), BorderFactory.createEmptyBorder(1,3,2,3) ))); setContentAreaFilled(false); setFocusPainted(false); setFont(smallFont); } @Override protected void paintComponent(Graphics g) { Insets i=getInsets(); int x=i.left-3,y=i.top-1,w=getWidth()-(i.right-3)-x,h=getHeight()-(i.bottom-2)-y; int half=(h+1)/2; g.setColor(new Color(253,253,253)); g.fillRect(x,y,w,half-1); g.setColor(new Color(245,245,245)); g.fillRect(x,y+half,w,1); g.setColor(new Color(243,243,243)); g.fillRect(x,y+half+1,w,h-(half+1)); super.paintComponent(g); if(hasFocus()) { GraphicsUtils.drawFocus((Graphics2D)g,x,y,w,h); } } } private class LetterButton extends FancyButton implements ActionListener { LetterButton(char letter,String param) { super(letter+(param==null ? "" : "="+param)); addActionListener(this); String tooltip=getDescription(letter); if(tooltip!=null) setToolTipText(StringUtils.capitalise(tooltip)); } char getLetter() { return getText().charAt(0); } String getParam() { if(getText().length()<2) return null; return getText().substring(2); } @Override public void actionPerformed(ActionEvent e) { if(!op) return; JPopupMenu pm=new JPopupMenu("Mode "+getText()); final int type=s.getChanModeType(getLetter()); pm.add(new AbstractAction() { @Override public Object getValue(String key) { if(key==NAME) return "Remove"; else if(key==MNEMONIC_KEY) return KeyEvent.VK_R; return super.getValue(key); } @Override public void actionPerformed(ActionEvent e) { s.sendLine(IRCMsg.constructBytes("MODE "+chan+" -"+getLetter()+ (type==Server.CHANMODE_ALWAYSPARAM ? " "+getParam() : ""))); } }); if(type==Server.CHANMODE_ALWAYSPARAM || type==Server.CHANMODE_SETPARAM) { pm.add(new AbstractAction() { @Override public Object getValue(String key) { if(key==NAME) return "Change"; else if(key==MNEMONIC_KEY) return KeyEvent.VK_C; return super.getValue(key); } @Override public void actionPerformed(ActionEvent e) { showSetDialog(getLetter(),getParam()); } }); } pm.show(this,0,getHeight()); } } private Dialog d=null; String dialogPrevious; char dialogLetter; private void showSetDialog(char letter,String previous) { dialogPrevious=previous; dialogLetter=letter; UI ui=context.getSingle(UI.class); d = ui.createDialog("modeparam", this); Label l=(Label)d.getWidget("modeletter"); l.setText(letter+"="); EditBox eb=(EditBox)d.getWidget("value"); if(previous!=null) { eb.setValue(previous); eb.setFlag(EditBox.FLAG_DIM); } d.show(parent); } /** * Callback: Cancel button. * @throws GeneralException */ @UIAction public void actionCancel() throws GeneralException { d.close(); } /** * Callback: Set button. * @throws GeneralException */ @UIAction public void actionSet() throws GeneralException { Button b=(Button)d.getWidget("set"); if(!b.isEnabled()) return; EditBox eb=(EditBox)d.getWidget("value"); String value=eb.getValue(); s.sendLine(IRCMsg.constructBytes("MODE "+chan+" +"+dialogLetter+ " "+value)); d.close(); } /** * Callback: Mode value changed. * @throws GeneralException */ @UIAction public void changeValue() throws GeneralException { EditBox eb=(EditBox)d.getWidget("value"); boolean error=!eb.getValue().matches("^[\\x21-\\x7f]+$"); boolean same=eb.getValue().equals(dialogPrevious); eb.setFlag(error ? EditBox.FLAG_ERROR : same ? EditBox.FLAG_DIM : EditBox.FLAG_NORMAL); Button b=(Button)d.getWidget("set"); b.setEnabled(!error && !same); } @Override public int getPreferredHeight(int width) { // Pretend to layout the components to get sizes Insets insets=getInsets(); int x=0,y=0,rowY=0; for(int i=0;i<getComponentCount();i++) { if(i>0) x+=HGAP; Dimension size=getComponent(i).getPreferredSize(); x+=size.width; if(x>width) // Next row { if(y!=0) y+=VGAP; y+=rowY; x=size.width; rowY=size.height; } else { rowY=Math.max(rowY,size.height); } } return y+rowY+insets.top+insets.bottom; } @Override public int getPreferredWidth() { // Add up all the components Insets insets=getInsets(); int width=0; for(int i=0;i<getComponentCount();i++) { if(i>0) width+=HGAP; width+=getComponent(i).getPreferredSize().width; } return width+insets.left+insets.right; } }