/* Copyright (c) 2008 Bluendo S.r.L. * See about.html for details about license. * * $Id: ChatScreen.java 1377 2009-04-21 14:17:38Z luca $ */ package it.yup.screens; import java.io.IOException; import java.util.Enumeration; import java.util.Hashtable; import java.util.Vector; import javax.microedition.io.ConnectionNotFoundException; import javax.microedition.lcdui.AlertType; import javax.microedition.lcdui.Canvas; import javax.microedition.lcdui.Command; import javax.microedition.lcdui.CommandListener; import javax.microedition.lcdui.Displayable; import javax.microedition.lcdui.Font; import javax.microedition.lcdui.Graphics; import javax.microedition.lcdui.Image; import lampiro.LampiroMidlet; import it.yup.util.ResourceIDs; import it.yup.util.ResourceManager; import it.yup.util.Utils; import it.yup.xml.Element; import it.yup.xmlstream.BasicXmlStream; import it.yup.xmlstream.EventQuery; import it.yup.xmlstream.EventQueryRegistration; import it.yup.xmlstream.PacketListener; import it.yup.xmpp.Contact; import it.yup.xmpp.XMPPClient; public class ChatScreen extends Canvas implements CommandListener, PacketListener { private static ResourceManager rm = ResourceManager.getManager("common", "en"); private Contact user; private Command cmd_exit; private Command cmd_write; private Command cmd_clear; private Hashtable cmd_urls = new Hashtable(); // XXX add a global handler for icons private static Image img_msg; private static byte SCROLL_UP = 0x01; private static byte SCROLL_DOWN = 0x02; private static byte SCROLL_LEFT = 0x04; private static byte SCROLL_RIGHT = 0x08; /** if true this entry can be scrolled inside (when the current entry exceed the screen)*/ private byte entry_scroll; /** true when paint maust scroll to bottom */ private boolean scroll_to_bottom = true; /** true when other chats receive new messages */ private boolean new_messages = false; private static Font cfont; // private Image buffer; /** height of the used font*/ private static int text_height; private static int header_height; /** first entry that is displayed */ private int start_entry; // /** number of currently displayed entries */ // private int displayed_entries; public boolean repaint_header = false; //private boolean _entries_scrolled = false; static private int header_bg = 0x5590AD; static private int header_fg = 0xDDE7EC; static private int bg_out_msg = 0xABBBC3; static private int fg_out_msg = 0x000000; static private int bg_in_msg = 0xCBDBE3; static private int fg_in_msg = 0x000000; static int scroll_color = 0x444444; private EventQueryRegistration reg = null; /** wrapped conversion cache */ private static Hashtable conversations = new Hashtable(); private Vector current_conversation = null; static { try { img_msg = Image.createImage("/icons/message.png"); cfont = Font.getFont(Font.FACE_PROPORTIONAL, Font.STYLE_PLAIN, Font.SIZE_SMALL); text_height = cfont.getHeight(); if (img_msg.getHeight() > text_height) { header_height = img_msg.getHeight() + 2; } else { header_height = text_height + 2; } } catch (IOException e) { img_msg = Image.createImage(16, 16); } } public ChatScreen(Contact u) { user = u; // buffer = null; start_entry = 0; cmd_exit = new Command(rm.getString(ResourceIDs.STR_CLOSE), Command.BACK, 1); cmd_write = new Command(rm.getString(ResourceIDs.STR_WRITE), Command.SCREEN, 1); cmd_clear = new Command(rm.getString(ResourceIDs.STR_CLEAR_HIST), Command.SCREEN, 2); addCommand(cmd_exit); addCommand(cmd_write); addCommand(cmd_clear); setCommandListener(this); // pending = rm.getString(ResourceIDs.STR_MESS_PENDING); setTitle(rm.getString(ResourceIDs.STR_CHAT_WITH) + user.getPrintableName()); current_conversation = (Vector) conversations.get(user.jid); if (current_conversation == null) { current_conversation = new Vector(); conversations.put(user.jid, current_conversation); } } /** * * @param screen_width * @return true if new messages have been added */ private boolean updateConversation(int screen_width) { Vector messages = user.getMessageHistory(null); if (messages == null) { return false; } for (int i = 0; i < messages.size(); i++) { String msg[] = (String[]) messages.elementAt(i); checkUrls(msg[1]); current_conversation.addElement(wrapMessage(msg, screen_width)); } user.resetMessageHistory(null); return true; } protected void showNotify() { // listen for all incoming messages with bodies EventQuery q = new EventQuery("message", null, null); q.child = new EventQuery("body", null, null); reg = BasicXmlStream.addEventListener(q, this); } protected void hideNotify() { if (reg != null) { reg.remove(); } } private void checkUrls(String text) { // parse the urls and add to the command menu Enumeration en = Utils.find_urls(text).elements(); while (en.hasMoreElements()) { String url = (String) en.nextElement(); if (!cmd_urls.containsKey(url)) { Command cmd = new Command(url.substring(7), url, Command.SCREEN, 10); cmd_urls.put(url, cmd); addCommand(cmd); } } } /** * Wrap a message so that it fits the windows * @param * @param screen_width * * @return */ private ConversationEntry wrapMessage(String text[], int screen_width) { // #ifdef TIMING long t1 = System.currentTimeMillis(); // #endif byte type = user.jid.equals(text[0]) ? ConversationEntry.ENTRY_TO : ConversationEntry.ENTRY_FROM; //String begin = (ConversationEntry.ENTRY_TO == type)? "":""; // split the message into tokens, keeping delimiters //Vector tokens = Utils.tokenize(begin + text[1], new String[]{"\n", "\r\n", " ", "\t"}, true); Vector tokens = Utils .tokenize(text[1], new String[] { "\n", "\r\n", " ", "\t" }, true); int line_width = 0; Vector lines = new Vector(); Enumeration en = tokens.elements(); StringBuffer line = new StringBuffer(); while (en.hasMoreElements()) { String t = (String) en.nextElement(); if ("\n".equals(t) || "\r\n".equals(t)) { line_width = 0; lines.addElement(line.toString()); line.setLength(0); } else { int width = cfont.stringWidth(t); if (line_width + width > screen_width) { if (width > screen_width) { // word too long, split it for (int i = 0; i < t.length(); i++) { int w = cfont.charWidth(t.charAt(i)); if (line_width + w > screen_width) { lines.addElement(line.toString()); line.setLength(0); line.append(t.charAt(i)); line_width = w; } else { line.append(t.charAt(i)); line_width += w; } } } else { line_width = width; lines.addElement(line.toString()); line.setLength(0); line.append(t); } } else { line.append(t); line_width += width; } } } if (line.length() > 0) { lines.addElement(line.toString()); } // #ifdef TIMING System.out.println("wrap conv: " + (System.currentTimeMillis() - t1)); // #endif return new ConversationEntry(lines, type); } public void commandAction(Command cmd, Displayable d) { if (cmd == cmd_exit) { RosterScreen roster = RosterScreen.getInstance(); roster.updateContact(user, Contact.CH_MESSAGE_READ); LampiroMidlet.disp.setCurrent(roster); } else if (cmd == cmd_write) { SimpleComposerScreen cs = new SimpleComposerScreen(this, user); LampiroMidlet.disp.setCurrent(cs); } else if (cmd == cmd_clear) { current_conversation.removeAllElements(); start_entry = 0; Enumeration en = cmd_urls.elements(); while (en.hasMoreElements()) { removeCommand((Command) en.nextElement()); } cmd_urls.clear(); repaint(); } else { String url = cmd.getLongLabel(); System.out.println(url); try { LampiroMidlet._lampiro.platformRequest(url); } catch (ConnectionNotFoundException e) { XMPPClient.getInstance().showAlert( AlertType.ERROR, "URL Error", "Can't open URL:" + e.getMessage(), this); } } } /** * Handle key events * @param kc the pressed key */ protected void keyPressed(int kc) { int ga = getGameAction(kc); switch (ga) { case Canvas.UP: { if ((entry_scroll & SCROLL_UP) == SCROLL_UP) { start_entry--; repaint(); } break; } case Canvas.DOWN: { if ((entry_scroll & SCROLL_DOWN) == SCROLL_DOWN) { start_entry++; repaint(); } break; } case Canvas.RIGHT: { if ((entry_scroll & SCROLL_RIGHT) == SCROLL_RIGHT) { ConversationEntry e = (ConversationEntry) current_conversation .elementAt(start_entry); if (e.entry_offset < e.lines.size() - 1) { e.entry_offset++; repaint(); } } break; } case Canvas.LEFT: { if ((entry_scroll & SCROLL_LEFT) == SCROLL_LEFT) { ConversationEntry e = (ConversationEntry) current_conversation .elementAt(start_entry); if (e.entry_offset > 0) { e.entry_offset--; repaint(); } } break; } case Canvas.FIRE: { SimpleComposerScreen cs = new SimpleComposerScreen(this, user); LampiroMidlet.disp.setCurrent(cs); break; } } } public void packetReceived(Element e) { if (user.getHistoryLength(null) > 0) { repaint(); } else { new_messages = true; repaint_header = true; repaint(); } } protected void paint(Graphics g) { // #ifdef TIMING long t1 = System.currentTimeMillis(); // #endif int w = g.getClipWidth(); if (repaint_header) { paintHeader(g); repaint_header = false; } else { paintHeader(g); scroll_to_bottom = updateConversation(w - 13) || scroll_to_bottom; paintEntries(g); scroll_to_bottom = false; } // #ifdef TIMING System.out.println("chat paint: " + (System.currentTimeMillis() - t1)); // #endif } protected void paintHeader(Graphics g) { int w = g.getClipWidth(); g.setColor(header_bg); g.fillRect(0, 0, w, header_height); g.setColor(0xbbbbbb); g.drawLine(0, header_height, w, header_height); XMPPClient client = XMPPClient.getInstance(); Image img = client.getPresenceIcon(user,null, user.getAvailability()); g.drawImage(img, 0, 0, Graphics.TOP | Graphics.LEFT); g.setColor(header_fg); g.setFont(cfont); g.drawString(user.getPrintableName(), 17, 1, Graphics.TOP | Graphics.LEFT); if (new_messages) { g.drawImage(img_msg, w, 0, Graphics.TOP | Graphics.RIGHT); } } protected void paintEntries(Graphics g) { int hpos = header_height + 1; int w = g.getClipWidth(); // // #ifdef MOTOROLA // int h = getHeight(); // // #endif //// #ifndef MOTOROLA // int h = g.getClipHeight(); // // #endif int h = false?g.getClipHeight():getHeight(); if (scroll_to_bottom && current_conversation.size() > 1) { start_entry = current_conversation.size() - 1; ConversationEntry entry = (ConversationEntry) current_conversation .elementAt(start_entry); int displayed_height = hpos + entry.lines.size() * text_height + 4; while (displayed_height <= h && start_entry > 0) { start_entry--; entry = (ConversationEntry) current_conversation .elementAt(start_entry); displayed_height += entry.lines.size() * text_height + 4; } if (displayed_height > h && start_entry < current_conversation.size() - 1) { start_entry++; } // do { // start_entry = i; // i--; // entry = (ConversationEntry) current_conversation.elementAt(i); // displayed_height += entry.lines.size() * text_height + 4; // } // while(displayed_height < h && i >=0); //i = start_entry; } int i = start_entry; g.setFont(cfont); while (i < current_conversation.size() && hpos < h) { ConversationEntry entry = (ConversationEntry) current_conversation .elementAt(i); int fg, bg; if (entry.type == ConversationEntry.ENTRY_TO) { fg = fg_out_msg; bg = bg_out_msg; } else { fg = fg_in_msg; bg = bg_in_msg; } g.setColor(bg); g.fillRect(0, hpos, w, text_height * entry.lines.size() + 4); g.setColor(fg); for (int j = entry.entry_offset; j < entry.lines.size() && hpos < h; j++) { if (entry.type == ConversationEntry.ENTRY_TO) { String s = (String) entry.lines.elementAt(j); g .drawString(s, w - 13, hpos, Graphics.TOP | Graphics.RIGHT); } else { g.drawString((String) entry.lines.elementAt(j), 1, hpos, Graphics.TOP | Graphics.LEFT); } hpos += text_height; } // if this is the start entry and there are too many lines, this entry can be scrolled if (i == start_entry) { entry_scroll = 0; if (entry.entry_offset > 0) { entry_scroll |= SCROLL_LEFT; g.setColor(scroll_color); g.fillTriangle(w - 2, header_height + 5 + 20, w - 10, header_height + 12 + 20, w - 2, header_height + 18 + 20); } if (hpos > h) { entry_scroll |= SCROLL_RIGHT; g.setColor(scroll_color); g.fillTriangle(w - 2, h - 9 - 20, w - 10, h - 2 - 20, w - 10, h - 15 - 20); } } g.setColor(0xbbbbbb); g.drawLine(0, hpos + 3, w, hpos + 3); hpos += 4; i++; } if (hpos < h) { g.setColor(header_bg); g.fillRect(0, hpos, w, h - hpos); } if (start_entry > 0) { entry_scroll |= SCROLL_UP; g.setColor(scroll_color); g.fillTriangle(w - 13, 12 + header_height, w - 2, 12 + header_height, w - 8, header_height + 2); } if (start_entry < current_conversation.size() - 1 && hpos > h) { entry_scroll |= SCROLL_DOWN; g.setColor(scroll_color); g.fillTriangle(w - 13, h - 14, w - 2, h - 14, w - 8, h - 4); } } private static class ConversationEntry { /** message from */ public static final byte ENTRY_FROM = 0; /** message to */ public static final byte ENTRY_TO = 1; /** previous message wrap XXX ? */ public static final byte ENTRY_ERROR = 2; /** message type in / on */ public byte type; /** the message itself */ public Vector lines; /** first line of the entry that is displayed */ public int entry_offset = 0; public ConversationEntry(Vector lines, byte type) { this.type = type; this.lines = lines; } } }