/*
Eteria IRC Client, an RFC 1459 compliant client program written in Java.
Copyright (C) 2000-2001 Javier Kohen <jkohen at tough.com>
This program 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 2 of the License, or
(at your option) any later version.
This program 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 this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
import java.awt.*;
import java.awt.event.*;
import java.util.Enumeration;
import java.util.Vector;
import ar.com.jkohen.irc.Channel;
import ar.com.jkohen.awt.NewGraphics2D;
import ar.com.jkohen.awt.event.ChatPanelListener;
import ar.com.jkohen.irc.RFC1459;
import ar.com.jkohen.util.Resources;
import ar.com.jkohen.util.CollatedHashtable;
public class ChatTabs extends Canvas implements MouseListener, MouseMotionListener
{
public static final int NORMAL = 0;
public static final int CURRENT = 1;
public static final int ALARM = 2;
public static final int STATES = 3;
public static final int LEFT = 0;
public static final int CENTER = 1;
public static final int COMPLETE = 2;
public static final int POSITIONS = 3;
public static final int PRIV = 0;
public static final int CHAN = 1;
public static final int SERV = 2;
public static final int SOURCES = 3;
private ActionListener actionListener;
private Image image;
private Graphics img_gfx;
private boolean dirty_tabs;
private boolean dirty_images;
private CollatedHashtable name_to_tab;
private Vector public_tabs;
private Vector private_tabs;
private Vector service_tabs;
// Index of the tab displayed on the left margin.
private int first_displayed;
private Tab current_visible;
private Image images[][][];
private EIRC eirc;
public ChatTabs(EIRC eirc)
{
this.public_tabs = new Vector();
this.private_tabs = new Vector();
this.service_tabs = new Vector();
this.name_to_tab = new CollatedHashtable(RFC1459.getCollator());
this.images = new Image[ChatTabs.SOURCES][ChatTabs.STATES][ChatTabs.POSITIONS];
this.eirc = eirc;
addMouseListener(this);
addMouseMotionListener(this);
}
public void setImage(int source, int state, int pos, Image img)
{
this.images[source][state][pos] = img;
boolean changed_size = false;
int height = img.getHeight(this);
// while ((height = img.getHeight(this)) == -1);
// Use maximum height as a reference.
if (height > Tab.HEIGHT)
{
Tab.HEIGHT = height;
changed_size = true;
}
Image [] state_images = images[source][state];
if (state != CENTER && state_images[LEFT] != null)
{
int width_left = state_images[LEFT].getWidth(this);
// while ((width_left = state_images[LEFT].getWidth(this)) == -1);
int width_sides = width_left;
// Use maximum width as a reference.
if (Tab.WIDTH - width_sides > Tab.CENTER_WIDTH)
{
Tab.CENTER_WIDTH = Tab.WIDTH - width_sides;
changed_size = true;
}
}
if (changed_size)
this.dirty_tabs = true;
this.dirty_images = true;
repaint();
}
public void add(String name)
{
Tab tab = new Tab(name);
if (Channel.isChannel(name))
{
public_tabs.addElement(tab);
}
else
{
private_tabs.addElement(tab);
}
if (eirc.isService(name))
{
tab.setService(true);
}
name_to_tab.put(name, tab);
this.dirty_tabs = true;
repaint();
}
public void remove(String name)
{
Tab tab = (Tab) name_to_tab.remove(name);
if (tab == null) return;
if (tab.equals(current_visible))
{
this.current_visible = null;
}
if (Channel.isChannel(name))
{
removeTab(public_tabs, tab);
}
else
{
removeTab(private_tabs, tab);
}
this.dirty_tabs = true;
repaint();
}
private void removeTab(Vector v, Tab t)
{
int index = v.indexOf(t);
v.removeElementAt(index);
if (t.getImageIndex() == CURRENT && 0 != v.size())
{
if (index > 0)
index--;
Tab reverted = (Tab) v.elementAt(index);
reverted.setImageIndex(CURRENT);
}
}
public void rename(String old_name, String name)
{
// FIXME: Must use an atomic operator.
Tab tab = (Tab) name_to_tab.get(old_name);
tab.setName(name);
name_to_tab.put(name, tab);
name_to_tab.remove(old_name);
this.dirty_tabs = true;
repaint();
}
public void setAlarm(String name, boolean alarm)
{
Tab tab = (Tab) name_to_tab.get(name);
int current_index = tab.getImageIndex();
int new_index = tab.equals(current_visible) ? ChatTabs.CURRENT : alarm ? ChatTabs.ALARM : ChatTabs.NORMAL;
if (current_index != new_index)
{
tab.setImageIndex(new_index);
repaint();
}
}
public String getCurrent()
{
for (Enumeration e = name_to_tab.elements(); e.hasMoreElements(); )
{
Tab tab = (Tab) e.nextElement();
if (tab.getImageIndex() == CURRENT)
return tab.getName();
}
return null;
}
public void shiftLeft()
{
if (first_displayed > 0)
{
this.first_displayed--;
repaint();
}
}
public void shiftRight()
{
int visible_tabs = getSize().width / Tab.WIDTH;
int tabs_in_longest_row = Math.max(public_tabs.size(), private_tabs.size());
if (first_displayed < tabs_in_longest_row - visible_tabs)
{
this.first_displayed++;
repaint();
}
}
public void makeVisible(String name)
{
Tab t = (Tab) name_to_tab.get(name);
if (t != null)
{
makeVisible(t);
}
}
protected void makeVisible(Tab new_visible)
{
if (new_visible == null)
return;
int visible_tabs = getSize().width / Tab.WIDTH;
Vector v = Channel.isChannel(new_visible.getName()) ? public_tabs : private_tabs;
int tabs_in_row = v.size();
int index = v.indexOf(new_visible);
if (tabs_in_row <= visible_tabs)
{
this.first_displayed = 0;
}
else
{
if (tabs_in_row - index >= visible_tabs)
this.first_displayed = index;
else
this.first_displayed = tabs_in_row - visible_tabs;
}
if (current_visible != null)
current_visible.setImageIndex(ChatTabs.NORMAL);
new_visible.setImageIndex(ChatTabs.CURRENT);
current_visible = new_visible;
repaint();
}
public void notVisible()
{
if (current_visible != null)
current_visible.setImageIndex(ChatTabs.NORMAL);
current_visible = null;
repaint();
}
public String getVisible()
{
if (current_visible != null)
return (current_visible.getName());
else
return (null);
}
public String getTabAt(Point p)
{
p.translate(first_displayed * Tab.WIDTH, 0);
Vector v = p.y < Tab.HEIGHT ? private_tabs : public_tabs;
int index = p.x / Tab.WIDTH;
if (index < v.size())
return ((Tab) v.elementAt(index)).getName();
return null;
}
protected void adjustTabsTitle(Graphics g)
{
FontMetrics fm = g.getFontMetrics();
for (Enumeration e = name_to_tab.elements(); e.hasMoreElements(); )
{
Tab tab = (Tab) e.nextElement();
String name = tab.getName();
String title = name;
int title_length = title.length();
// Trim tab's title if the name's too long.
while (fm.stringWidth(title) > Tab.CENTER_WIDTH)
title = name.substring(0, --title_length).concat("...");
tab.setTitle(title);
}
}
private void cacheImages()
{
for (int j = 0; j < SOURCES; j++)
{
for (int i = 0; i < STATES; i++)
{
Image state_images[] = images[j][i];
int width_left = state_images[LEFT].getWidth(this);
// Build and cache the complete image.
Image image = createImage(Tab.WIDTH, Tab.HEIGHT);
Graphics g = image.getGraphics();
g.drawImage(state_images[LEFT], 0, 0, this);
g.drawImage(state_images[CENTER], width_left, 0, Tab.CENTER_WIDTH, Tab.HEIGHT, this);
g.dispose();
state_images[COMPLETE] = image;
}
}
}
private void createImageBuffer(Dimension size)
{
if (size.height <= 0 || size.width <= 0)
return;
image = createImage(size.width, size.height);
if (null != img_gfx)
img_gfx.dispose();
img_gfx = image.getGraphics();
if (NewGraphics2D.hasRenderingHints)
NewGraphics2D.setRendering(img_gfx);
}
public void setFont(Font f)
{
super.setFont(f);
repaint();
}
public void setBackground(Color c)
{
super.setBackground(c);
this.dirty_images = true;
repaint();
}
public void update(Graphics g)
{
paint(g);
}
public void paint(Graphics g)
{
Dimension size = getSize();
if (img_gfx == null || size.height != image.getHeight(this) || size.width != image.getWidth(this))
createImageBuffer(size);
if (dirty_tabs)
{
this.dirty_tabs = false;
adjustTabsTitle(img_gfx);
}
if (dirty_images)
{
this.dirty_images = false;
cacheImages();
}
img_gfx.setColor(getBackground());
img_gfx.fillRect(0, 0, size.width, size.height);
img_gfx.setColor(getForeground());
img_gfx.setFont(getFont());
FontMetrics fm = img_gfx.getFontMetrics();
int baseline = fm.getMaxDescent();
int text_height = fm.getMaxAscent() + fm.getLeading() + baseline;
Vector [] v = { private_tabs, public_tabs };
for (int i = 0; i < v.length; i++)
{
int x = -first_displayed * Tab.WIDTH;
int y = i * Tab.HEIGHT;
int source = i;
for (Enumeration e = v[i].elements(); e.hasMoreElements(); )
{
Tab tab = (Tab) e.nextElement();
int state = tab.getImageIndex();
// Render the tab's background.
img_gfx.drawImage(images[(tab.isService() ? SERV : source)][state][ChatTabs.COMPLETE], x, y, this);
// Render the title centered on the CENTER image.
int width_left = images[(tab.isService() ? SERV : source)][state][ChatTabs.LEFT].getWidth(this);
String title = tab.getTitle();
int text_width = fm.stringWidth(title);
img_gfx.drawString(title, x - (state != CURRENT ? 1 : 0) + width_left + (Tab.CENTER_WIDTH - text_width) / 2, y - (state != CURRENT ? 1 : 0) + Tab.HEIGHT - baseline - (Tab.HEIGHT - text_height) / 2);
x += Tab.WIDTH;
}
}
g.drawImage(image, 0, 0, this);
}
public Dimension getMinimumSize()
{
return getPreferredSize();
}
public Dimension getPreferredSize()
{
int tabs_in_longest_row = Math.max(public_tabs.size(), private_tabs.size());
return new Dimension(tabs_in_longest_row * Tab.WIDTH, 2 * Tab.HEIGHT);
}
public void setBounds(int x, int y, int w, int h)
{
super.setBounds(x, y, w, h);
if (null != image && (h != image.getHeight(this) || w != image.getWidth(this)))
createImageBuffer(new Dimension(w, h));
makeVisible(current_visible);
}
public void addActionListener(ActionListener l)
{
if (null != l)
actionListener = AWTEventMulticaster.add(actionListener, l);
}
public void removeActionListener(ActionListener l)
{
if (null != l)
actionListener = AWTEventMulticaster.remove(actionListener, l);
}
protected void processActionEvent(ActionEvent e)
{
if (actionListener != null)
actionListener.actionPerformed(e);
}
public void mousePressed(MouseEvent e)
{
}
public void mouseReleased(MouseEvent e)
{
if ((e.getModifiers() & MouseEvent.BUTTON1_MASK) != 0 || e.getModifiers() == 0)
{
// WORKAROUND: #32 Netscape Navigator doesn't set it right for BUTTON1.
Point p = e.getPoint();
String tab = getTabAt(p);
if (tab != null)
processActionEvent(new ActionEvent(this, ActionEvent.ACTION_PERFORMED, tab));
// Hack to avoid getting mouse focus, and therefore take keyboard focus from other components.
e.consume();
}
}
public void mouseClicked(MouseEvent e)
{
}
public void mouseDragged(MouseEvent e)
{
}
public void mouseMoved(MouseEvent e)
{
Point p = e.getPoint();
String tab = getTabAt(p);
if (tab != null)
setCursor(new Cursor(Cursor.HAND_CURSOR));
else
setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
}
public void mouseEntered(MouseEvent e)
{
Point p = e.getPoint();
String tab = getTabAt(p);
if (tab != null)
setCursor(new Cursor(Cursor.HAND_CURSOR));
else
setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
}
public void mouseExited(MouseEvent e)
{
setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
}
}
class Tab
{
static int HEIGHT = 0;
final static int WIDTH = 96;
static int CENTER_WIDTH = 0;
private String name;
private String title;
private int image_index;
private boolean service = false;
public Tab(String name)
{
this.name = this.title = name;
this.image_index = ChatTabs.NORMAL;
}
public String toString()
{
return getClass().getName().concat("[name=").concat(name).concat(",title=").concat(title).concat(",state=").concat(String.valueOf(image_index)).concat("]");
}
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
public String getTitle()
{
return title;
}
public void setTitle(String title)
{
this.title = title;
}
public int getImageIndex()
{
return image_index;
}
public void setImageIndex(int image_index)
{
this.image_index = image_index;
}
public void setService(boolean service)
{
this.service = service;
}
public boolean isService()
{
return(service);
}
}