Chat.CHAT_BUFFER_SIZE)
+ {
+ int msgElementCount = 0;
+
+ Element firstMsgElement = null;
+
+ int firstMsgIndex = 0;
+
+ Element rootElement = this.document.getDefaultRootElement();
+ // Count how many messages we have in the document.
+ for (int i = 0; i < rootElement.getElementCount(); i++)
+ {
+ String idAttr = (String) rootElement.getElement(i)
+ .getAttributes().getAttribute("identifier");
+
+ if(idAttr != null
+ && (idAttr.equals("message")
+ || idAttr.equals("statusMessage")
+ || idAttr.equals("systemMessage")))
+ {
+ if(firstMsgElement == null)
+ {
+ firstMsgElement = rootElement.getElement(i);
+ firstMsgIndex = i;
+ }
+
+ msgElementCount++;
+ }
+ }
+
+ // If we doesn't have any known elements in the document or if we
+ // have only one long message we don't want to remove it.
+ if(firstMsgElement == null || msgElementCount < 2)
+ return;
+
+ try
+ {
+ // Remove the header of the message if such exists.
+ if(firstMsgIndex > 0)
+ {
+ Element headerElement = rootElement.getElement(firstMsgIndex - 1);
+
+ String idAttr = (String) headerElement
+ .getAttributes().getAttribute("identifier");
+
+ if(idAttr != null && idAttr.equals("messageHeader"))
+ {
+ this.document.remove(headerElement.getStartOffset(),
+ headerElement.getEndOffset()
+ - headerElement.getStartOffset());
+ }
+ }
+
+ // Remove the message itself.
+ this.document.remove(firstMsgElement.getStartOffset(),
+ firstMsgElement.getEndOffset()
+ - firstMsgElement.getStartOffset());
+ }
+ catch (BadLocationException e)
+ {
+ logger.error("Error removing messages from chat: ", e);
+ }
+ }
+ }
+
+ /**
+ * Highlights keywords searched in the history.
+ *
+ * @param message the source message
+ * @param contentType the content type
+ * @param keyword the searched keyword
+ * @return the formatted message
+ */
+ private String processKeyword( String message,
+ String contentType,
+ String keyword)
+ {
+ String startPlainTextTag;
+ String endPlainTextTag;
+
+ if (HTML_CONTENT_TYPE.equals(contentType))
+ {
+ startPlainTextTag = "";
+ endPlainTextTag = "";
+ }
+ else
+ {
+ startPlainTextTag = START_PLAINTEXT_TAG;
+ endPlainTextTag = END_PLAINTEXT_TAG;
+ }
+
+ Matcher m
+ = Pattern.compile(Pattern.quote(keyword), Pattern.CASE_INSENSITIVE)
+ .matcher(message);
+ StringBuffer msgBuffer = new StringBuffer();
+ int prevEnd = 0;
+
+ while (m.find())
+ {
+ msgBuffer.append(message.substring(prevEnd, m.start()));
+ prevEnd = m.end();
+
+ String keywordMatch = m.group().trim();
+
+ msgBuffer.append(endPlainTextTag);
+ msgBuffer.append("
");
+ msgBuffer.append(keywordMatch);
+ msgBuffer.append("");
+ msgBuffer.append(startPlainTextTag);
+ }
+
+ /*
+ * If the keyword didn't match, let the outside world be able to
+ * discover it.
+ */
+ if (prevEnd == 0)
+ return message;
+
+ msgBuffer.append(message.substring(prevEnd));
+ return msgBuffer.toString();
+ }
+
+ /**
+ * Formats the given message. Processes all smiley chars, new lines and
+ * links.
+ *
+ * @param message the message to be formatted
+ * @param contentType the content type of the message to be formatted
+ * @param keyword the word to be highlighted
+ * @return the formatted message
+ */
+ private String formatMessage(String message,
+ String contentType,
+ String keyword)
+ {
+ // If the message content type is HTML we won't process links and
+ // new lines, but only the smileys.
+ if (!HTML_CONTENT_TYPE.equals(contentType))
+ {
+
+ /*
+ * We disallow HTML in plain-text messages. But processKeyword
+ * introduces HTML. So we'll allow HTML if processKeyword has
+ * introduced it in order to not break highlighting.
+ */
+ boolean processHTMLChars;
+
+ if ((keyword != null) && (keyword.length() != 0))
+ {
+ String messageWithProcessedKeyword
+ = processKeyword(message, contentType, keyword);
+
+ /*
+ * The same String instance will be returned if there was no
+ * keyword match. Calling #equals() is expensive so == is
+ * intentional.
+ */
+ processHTMLChars = (messageWithProcessedKeyword == message);
+ message = messageWithProcessedKeyword;
+ }
+ else
+ processHTMLChars = true;
+
+ message
+ = processNewLines(
+ processLinksAndHTMLChars(message, processHTMLChars));
+ }
+ // If the message content is HTML, we process br and img tags.
+ else
+ {
+ if ((keyword != null) && (keyword.length() != 0))
+ message = processKeyword(message, contentType, keyword);
+ message = processImgTags(processBrTags(message));
+ }
+
+ return message;
+ }
+
+ /**
+ * Formats all links in a given message and optionally escapes special HTML
+ * characters such as <, >, & and " in order to prevent HTML
+ * injection in plain-text messages such as writing
+ *
</PLAINTEXT>, HTML which is going to be rendered as
+ * such and
<PLAINTEXT>. The two procedures are carried
+ * out in one call in order to not break URLs which contain special HTML
+ * characters such as &.
+ *
+ * @param message The source message string.
+ * @param processHTMLChars
true to escape the special HTML chars;
+ * otherwise,
false
+ * @return The message string with properly formatted links.
+ */
+ private String processLinksAndHTMLChars(String message,
+ boolean processHTMLChars)
+ {
+ Matcher m = URL_PATTERN.matcher(message);
+ StringBuffer msgBuffer = new StringBuffer();
+ int prevEnd = 0;
+
+ while (m.find())
+ {
+ String fromPrevEndToStart = message.substring(prevEnd, m.start());
+
+ if (processHTMLChars)
+ fromPrevEndToStart = processHTMLChars(fromPrevEndToStart);
+ msgBuffer.append(fromPrevEndToStart);
+ prevEnd = m.end();
+
+ String url = m.group().trim();
+
+ msgBuffer.append(END_PLAINTEXT_TAG);
+ msgBuffer.append("
");
+ msgBuffer.append(url);
+ msgBuffer.append("");
+ msgBuffer.append(START_PLAINTEXT_TAG);
+ }
+
+ String fromPrevEndToEnd = message.substring(prevEnd);
+
+ if (processHTMLChars)
+ fromPrevEndToEnd = processHTMLChars(fromPrevEndToEnd);
+ msgBuffer.append(fromPrevEndToEnd);
+
+ return msgBuffer.toString();
+ }
+
+ /**
+ * Escapes special HTML characters such as <, >, & and " in
+ * the specified message.
+ *
+ * @param message the message to be processed
+ * @return the processed message with escaped special HTML characters
+ */
+ private String processHTMLChars(String message)
+ {
+ return
+ message
+ .replace("&", "&")
+ .replace("<", "<")
+ .replace(">", ">")
+ .replace("\"", """);
+ }
+
+ /**
+ * Formats message new lines.
+ *
+ * @param message The source message string.
+ * @return The message string with properly formatted new lines.
+ */
+ private String processNewLines(String message)
+ {
+
+ /*
+ *
tags are needed to visualize a new line in the html format, but
+ * when copied to the clipboard they are exported to the plain text
+ * format as ' ' and not as '\n'.
+ *
+ * See bug N4988885:
+ * http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4988885
+ *
+ * To fix this we need "
" - the HTML-Code for ASCII-Character No.10
+ * (Line feed).
+ */
+ return
+ message
+ .replaceAll(
+ "\n",
+ END_PLAINTEXT_TAG + "
" + START_PLAINTEXT_TAG);
+ }
+
+ /**
+ * Opens a link in the default browser when clicked and shows link url in a
+ * popup on mouseover.
+ *
+ * @param e The HyperlinkEvent.
+ */
+ public void hyperlinkUpdate(HyperlinkEvent e)
+ {
+ if (e.getEventType() == HyperlinkEvent.EventType.ENTERED)
+ {
+ String href = e.getDescription();
+
+ this.currentHref = href;
+ }
+ else if (e.getEventType() == HyperlinkEvent.EventType.EXITED)
+ {
+ this.currentHref = "";
+ }
+ }
+
+ /**
+ * Returns the text pane of this conversation panel.
+ *
+ * @return The text pane of this conversation panel.
+ */
+ public JTextPane getChatTextPane()
+ {
+ return chatTextPane;
+ }
+
+ /**
+ * Returns the time of the last received message.
+ *
+ * @return The time of the last received message.
+ */
+ public long getLastIncomingMsgTimestamp()
+ {
+ return lastIncomingMsgTimestamp;
+ }
+
+ /**
+ * When a right button click is performed in the editor pane, a popup menu
+ * is opened.
+ * In case of the Scheme being internal, it won't open the Browser but
+ * instead it will trigger the forwarded action.
+ *
+ * @param e The MouseEvent.
+ */
+ public void mouseClicked(MouseEvent e)
+ {
+ Point p = e.getPoint();
+ SwingUtilities.convertPointToScreen(p, e.getComponent());
+
+ if ((e.getModifiers() & InputEvent.BUTTON3_MASK) != 0
+ || (e.isControlDown() && !e.isMetaDown()))
+ {
+ openContextMenu(p);
+ }
+ else if ((e.getModifiers() & InputEvent.BUTTON1_MASK) != 0
+ && currentHref != null && currentHref.length() != 0)
+ {
+ URI uri;
+ try
+ {
+ uri = new URI(currentHref);
+ }
+ catch (URISyntaxException e1)
+ {
+ logger.error("Invalid URL", e1);
+ return;
+ }
+ if(uri.getScheme().equals("jitsi"))
+ {
+ for(ChatLinkClickedListener l:chatLinkClickedListeners)
+ {
+ l.chatLinkClicked(uri);
+ }
+ }
+ else
+ GuiActivator.getBrowserLauncher().openURL(currentHref);
+
+ // after opening the link remove the currentHref to avoid
+ // clicking on the window to gain focus to open the link again
+ this.currentHref = "";
+ }
+ }
+
+ /**
+ * Opens this panel context menu at the given point.
+ *
+ * @param p the point where to position the left-top cornet of the context
+ * menu
+ */
+ private void openContextMenu(Point p)
+ {
+ if (currentHref != null && currentHref.length() != 0
+ && !currentHref.startsWith("jitsi://"))
+ {
+ rightButtonMenu.insert(openLinkItem, 0);
+ rightButtonMenu.insert(copyLinkItem, 1);
+ rightButtonMenu.insert(copyLinkSeparator, 2);
+ }
+ else
+ {
+ rightButtonMenu.remove(openLinkItem);
+ rightButtonMenu.remove(copyLinkItem);
+ rightButtonMenu.remove(copyLinkSeparator);
+ }
+
+ if (chatTextPane.getSelectedText() != null)
+ {
+ rightButtonMenu.enableCopy();
+ }
+ else
+ {
+ rightButtonMenu.disableCopy();
+ }
+ rightButtonMenu.setInvoker(chatTextPane);
+ rightButtonMenu.setLocation(p.x, p.y);
+ rightButtonMenu.setVisible(true);
+ }
+
+ public void mousePressed(MouseEvent e) {}
+
+ public void mouseReleased(MouseEvent e) {}
+
+ public void mouseEntered(MouseEvent e) {}
+
+ public void mouseExited(MouseEvent e) {}
+
+ public void lostOwnership(Clipboard clipboard, Transferable contents) {}
+
+ /**
+ * Returns the chat container.
+ *
+ * @return the chat container
+ */
+ public ChatConversationContainer getChatContainer()
+ {
+ return chatContainer;
+ }
+
+ /**
+ * Copies the selected conversation panel content to the clipboard.
+ */
+ public void copyConversation()
+ {
+ this.chatTextPane.copy();
+ }
+
+ /**
+ * Creates new document and all the messages that will be processed in the
+ * future will be appended in it.
+ */
+ public void clear()
+ {
+ this.document = (HTMLDocument) editorKit.createDefaultDocument();
+ Constants.loadSimpleStyle(
+ document.getStyleSheet(), chatTextPane.getFont());
+ }
+
+ /**
+ * Sets the given document to the editor pane in this panel.
+ *
+ * @param document the document to set
+ */
+ public void setContent(HTMLDocument document)
+ {
+ synchronized (scrollToBottomRunnable)
+ {
+ scrollToBottomIsPending = true;
+
+ this.document = document;
+ chatTextPane.setDocument(this.document);
+ }
+ }
+
+ /**
+ * Sets the default document contained in this panel, created on init or
+ * when clear is invoked.
+ */
+ public void setDefaultContent()
+ {
+ setContent(document);
+ }
+
+ /**
+ * Returns the document contained in this panel.
+ *
+ * @return the document contained in this panel
+ */
+ public HTMLDocument getContent()
+ {
+ return (HTMLDocument) this.chatTextPane.getDocument();
+ }
+
+ /**
+ * Returns the right button popup menu.
+ *
+ * @return the right button popup menu
+ */
+ public ChatRightButtonMenu getRightButtonMenu()
+ {
+ return rightButtonMenu;
+ }
+
+ /**
+ * Returns the date of the first message in the current page.
+ *
+ * @return the date of the first message in the current page
+ */
+ public Date getPageFirstMsgTimestamp()
+ {
+ Element rootElement = this.document.getDefaultRootElement();
+
+ Element firstMessageElement = null;
+
+ for(int i = 0; i < rootElement.getElementCount(); i ++)
+ {
+ String idAttr = (String) rootElement.getElement(i)
+ .getAttributes().getAttribute("identifier");
+
+ if (idAttr != null && idAttr.equals("messageHeader"))
+ {
+ firstMessageElement = rootElement.getElement(i);
+ break;
+ }
+ }
+
+ if(firstMessageElement == null)
+ return new Date(Long.MAX_VALUE);
+
+ String dateObject = firstMessageElement
+ .getAttributes().getAttribute("date").toString();
+
+ return new Date(Long.parseLong(dateObject));
+ }
+
+ /**
+ * Returns the date of the last message in the current page.
+ *
+ * @return the date of the last message in the current page
+ */
+ public Date getPageLastMsgTimestamp()
+ {
+ Element rootElement = this.document.getDefaultRootElement();
+
+ Element lastMessageElement = null;
+
+ for(int i = rootElement.getElementCount() - 1; i >= 0; i --)
+ {
+ String idAttr = (String) rootElement.getElement(i)
+ .getAttributes().getAttribute("identifier");
+
+ if (idAttr != null && idAttr.equals("messageHeader"))
+ {
+ lastMessageElement = rootElement.getElement(i);
+ break;
+ }
+ }
+
+ if(lastMessageElement == null)
+ return new Date(0);
+
+ String dateObject = lastMessageElement
+ .getAttributes().getAttribute("date").toString();
+
+ return new Date(Long.parseLong(dateObject));
+ }
+
+ /**
+ * Formats HTML tags <br/> to <br> or <BR/> to <BR>.
+ * The reason of this function is that the ChatPanel does not support
+ * <br /> closing tags (XHTML syntax), thus we have to remove every
+ * slash from each <br /> tags.
+ * @param message The source message string.
+ * @return The message string with properly formatted <br> tags.
+ */
+ private String processBrTags(String message)
+ {
+ // The resulting message after being processed by this function.
+ StringBuffer processedMessage = new StringBuffer();
+
+ // Compile the regex to match something like
or
.
+ // This regex is case sensitive and keeps the style or other
+ // attributes of the
tag.
+ Matcher m
+ = Pattern.compile("<\\s*[bB][rR](.*?)(/\\s*>)").matcher(message);
+ int start = 0;
+
+ // while we find some
closing tags with a slash inside.
+ while(m.find())
+ {
+ // First, we have to copy all the message preceding the
tag.
+ processedMessage.append(message.substring(start, m.start()));
+ // Then, we find the position of the slash inside the tag.
+ int slash_index = m.group().lastIndexOf("/");
+ // We copy the
tag till the slash exclude.
+ processedMessage.append(m.group().substring(0, slash_index));
+ // We copy all the end of the tag following the slash exclude.
+ processedMessage.append(m.group().substring(slash_index+1));
+ start = m.end();
+ }
+ // Finally, we have to add the end of the message following the last
+ //
tag, or the whole message if there is no
tag.
+ processedMessage.append(message.substring(start));
+
+ return processedMessage.toString();
+ }
+
+ /**
+ * Formats HTML tags <img ... /> to < img ... ></img> or
+ * <IMG ... /> to <IMG></IMG>.
+ * The reason of this function is that the ChatPanel does not support
+ * <img /> tags (XHTML syntax).
+ * Thus, we remove every slash from each <img /> and close it with a
+ * separate closing tag.
+ * @param message The source message string.
+ * @return The message string with properly formatted <img> tags.
+ */
+ private String processImgTags(String message)
+ {
+ // The resulting message after being processed by this function.
+ StringBuffer processedMessage = new StringBuffer();
+
+ // Compile the regex to match something like
![]()
or
+ //
![]()
. This regex is case sensitive and keeps the style,
+ // src or other attributes of the
![]()
tag.
+ Pattern p = Pattern.compile("<\\s*[iI][mM][gG](.*?)(/\\s*>)");
+ Matcher m = p.matcher(message);
+ int slash_index;
+ int start = 0;
+
+ // while we find some
![]()
self-closing tags with a slash inside.
+ while(m.find()){
+ // First, we have to copy all the message preceding the
![]()
tag.
+ processedMessage.append(message.substring(start, m.start()));
+ // Then, we find the position of the slash inside the tag.
+ slash_index = m.group().lastIndexOf("/");
+ // We copy the
![]()
tag till the slash exclude.
+ processedMessage.append(m.group().substring(0, slash_index));
+ // We copy all the end of the tag following the slash exclude.
+ processedMessage.append(m.group().substring(slash_index+1));
+ // We close the tag with a separate closing tag.
+ processedMessage.append("");
+ start = m.end();
+ }
+ // Finally, we have to add the end of the message following the last
+ //
![]()
tag, or the whole message if there is no
![]()
tag.
+ processedMessage.append(message.substring(start));
+
+ return processedMessage.toString();
+ }
+
+ /**
+ * Extend Editor pane to add URL tooltips.
+ */
+ private class MyTextPane
+ extends JTextPane
+ {
+ /**
+ * Returns the string to be used as the tooltip for
event.
+ *
+ * @param event the
MouseEvent
+ * @return the string to be used as the tooltip for
event.
+ */
+ @Override
+ public String getToolTipText(MouseEvent event)
+ {
+ return
+ ((currentHref != null) && (currentHref.length() != 0))
+ ? currentHref
+ : null;
+ }
+ }
+
+ /**
+ * Adds a custom component at the end of the conversation.
+ *
+ * @param component the component to add at the end of the conversation.
+ */
+ public void addComponent(ChatConversationComponent component)
+ {
+ synchronized (scrollToBottomRunnable)
+ {
+ StyleSheet styleSheet = document.getStyleSheet();
+ Style style
+ = styleSheet
+ .addStyle(
+ StyleConstants.ComponentElementName,
+ styleSheet.getStyle("body"));
+
+ // The image must first be wrapped in a style
+ style
+ .addAttribute(
+ AbstractDocument.ElementNameAttribute,
+ StyleConstants.ComponentElementName);
+
+ TransparentPanel wrapPanel
+ = new TransparentPanel(new BorderLayout());
+
+ wrapPanel.add(component, BorderLayout.NORTH);
+
+ style
+ .addAttribute(StyleConstants.ComponentAttribute, wrapPanel);
+ style.addAttribute("identifier", "messageHeader");
+ style.addAttribute("date", component.getDate().getTime());
+
+ scrollToBottomIsPending = true;
+
+ // Insert the component style at the end of the text
+ try
+ {
+ document
+ .insertString(document.getLength(), "ignored text", style);
+ }
+ catch (BadLocationException e)
+ {
+ logger.error("Insert in the HTMLDocument failed.", e);
+ }
+ }
+ }
+
+ /**
+ * Registers a new link click listener.
+ *
+ * @param listener the object that should be notified when an internal
+ * link was clicked.
+ */
+ public void addChatLinkClickedListener(ChatLinkClickedListener listener)
+ {
+ if(!chatLinkClickedListeners.contains(listener))
+ chatLinkClickedListeners.add(listener);
+ }
+
+ /**
+ * Remove a registered link click listener.
+ *
+ * @param listener a registered click listener to remove
+ */
+ public void removeChatLinkClickedListener(ChatLinkClickedListener listener)
+ {
+ chatLinkClickedListeners.remove(listener);
+ }
+
+ /**
+ * Returns the date string to show for the given date.
+ *
+ * @param date the date to format
+ * @return the date string to show for the given date
+ */
+ public static String getDateString(long date)
+ {
+ if (GuiUtils.compareDatesOnly(date, System.currentTimeMillis()) < 0)
+ {
+ StringBuffer dateStrBuf = new StringBuffer();
+
+ GuiUtils.formatDate(date, dateStrBuf);
+ dateStrBuf.append(" ");
+ return dateStrBuf.toString();
+ }
+
+ return "";
+ }
+
+ /**
+ * Reloads images.
+ */
+ public void loadSkin()
+ {
+ openLinkItem.setIcon(
+ new ImageIcon(ImageLoader.getImage(ImageLoader.BROWSER_ICON)));
+ copyLinkItem.setIcon(
+ new ImageIcon(ImageLoader.getImage(ImageLoader.COPY_ICON)));
+
+ getRightButtonMenu().loadSkin();
+ }
+
+ /**
+ * Highlights the string in multi user chat.
+ *
+ * @param message the message to process
+ * @param contentType the content type of the message
+ * @param keyWord the keyword to highlight
+ * @return the message string with the keyword highlighted
+ */
+ public String processChatRoomHighlight(String message, String contentType,
+ String keyWord)
+ {
+ return processKeyword(message, contentType, keyWord);
+ }
+
+ public String processMeCommand(ChatMessage chatMessage)
+ {
+ String contentType = chatMessage.getContentType();
+ String message = chatMessage.getMessage();
+
+ String msgID = "message";
+ String chatString = "";
+ String endHeaderTag = "";
+
+ String startDivTag = "
";
+ String endDivTag = "
";
+
+ String startPlainTextTag;
+ String endPlainTextTag;
+
+ if (HTML_CONTENT_TYPE.equals(contentType))
+ {
+ startPlainTextTag = "";
+ endPlainTextTag = "";
+ }
+ else
+ {
+ startPlainTextTag = START_PLAINTEXT_TAG;
+ endPlainTextTag = END_PLAINTEXT_TAG;
+ }
+
+ if (message.length() > 4 && message.substring(0, 4).equals("/me "))
+ {
+ chatString = startDivTag + "
";
+
+ endHeaderTag = "" + endDivTag;
+
+ chatString +=
+
+ processHTMLChars("*** " + chatMessage.getContactName() + " "
+ + message.substring(4))
+ + endHeaderTag;
+
+ Map
listSources =
+ GuiActivator.getReplacementSources();
+
+ Iterator> entrySetIter =
+ listSources.entrySet().iterator();
+ StringBuffer msgStore = new StringBuffer(chatString);
+
+ for (int i = 0; i < listSources.size(); i++)
+ {
+ Map.Entry entry =
+ entrySetIter.next();
+
+ ReplacementService source = entry.getValue();
+
+ boolean isSmiley = source instanceof SmiliesReplacementService;
+ if (isSmiley)
+ {
+ String sourcePattern = source.getPattern();
+ Pattern p =
+ Pattern.compile(sourcePattern, Pattern.CASE_INSENSITIVE
+ | Pattern.DOTALL);
+ Matcher m = p.matcher(msgStore);
+
+ StringBuffer msgTemp = new StringBuffer(chatString);
+
+ while (m.find())
+ {
+ msgTemp.insert(m.start(), startPlainTextTag);
+ msgTemp.insert(m.end() + startPlainTextTag.length(),
+ endPlainTextTag);
+
+ }
+ if (msgTemp.length() != msgStore.length())
+ msgStore = msgTemp;
+ }
+ }
+
+ return msgStore.toString();
+ }
+ else
+ return "";
+ }
+
+ private static String createIncomingMessageTag(
+ String messageID,
+ String incomingMessageHeader,
+ String incomingMessageParagraph)
+ {
+ StringBuffer messageBuff = new StringBuffer();
+
+//
+
+ messageBuff.append("");
+ messageBuff.append("
");
+ messageBuff.append("
");
+ messageBuff.append("
");
+ messageBuff.append(incomingMessageHeader);
+ messageBuff.append(incomingMessageParagraph);
+ messageBuff.append("
");
+ messageBuff.append("
");
+ messageBuff.append("
");
+ messageBuff.append("
");
+ messageBuff.append("
");
+ messageBuff.append("
");
+ messageBuff.append("
");
+ messageBuff.append("
");
+
+ return messageBuff.toString();
+ }
+
+ private static String createOutgoingMessageStyle()
+ {
+ StringBuffer styleBuff = new StringBuffer();
+
+ styleBuff.append("background-image:");
+ styleBuff.append("url('bundle://30.0:1/resources/images/impl/gui/lookandfeel/selectedTabMiddle.png');");
+ styleBuff.append("background-repeat:");
+ styleBuff.append("repeat-x;");
+
+ return styleBuff.toString();
+ }
+
+ private static String createSmsMessageStyle()
+ {
+ StringBuffer styleBuff = new StringBuffer();
+
+ styleBuff.append("background-image:");
+ styleBuff.append("url('bundle://30.0:1/resources/images/impl/gui/lookandfeel/tabRight.png');");
+ styleBuff.append("background-repeat:");
+ styleBuff.append("repeat-x;");
+
+ return styleBuff.toString();
+ }
+
+// .box {
+// width: 100%;
+// margin: 0px auto;
+// }
+ private static String createBoxStyle()
+ {
+ return "style=\"width: 100%;"
+ + " margin-top: 0px;"
+ + " margin-bottom: 0px;"
+ + " margin-left: auto;"
+ + " margin-right: auto;\"";
+ }
+
+// .box div.topleft {
+// display: block;
+// background: url("i/box-bg.png") top left no-repeat white;
+// padding: 0em 0em 0em 1.0em;
+// }
+ private static String createTopLeftStyle()
+ {
+ return "style=\"display: block;"
+ + " background-image: url('"+INCOMING_MESSAGE_IMAGE_PATH+"');"
+ + " background-repeat: no-repeat;"
+ + " background-position: top left;"
+ + " background-color: #FFFFFF;"
+ + " padding-top: 0em;"
+ + " padding-right: 0em;"
+ + " padding-bottom: 0em;"
+ + " padding-left: 0em;"
+ + "\"";
+ }
+
+// .box div.topright {
+// display: block;
+// background: url("i/box-bg.png") top right no-repeat white;
+// padding: 1.0em;
+// margin: -1.0em 0 0 1.0em;
+// }
+ private static String createTopRightStyle()
+ {
+ return "style=\"display: block;"
+ + " background-image: url('"+INCOMING_MESSAGE_IMAGE_PATH+"');"
+ + " background-repeat: no-repeat;"
+ + " background-position: top right;"
+ + " background-color: #FFFFFF;"
+ + " padding-top: 1em;"
+ + " padding-right: 1em;"
+ + " padding-bottom: 1em;"
+ + " padding-left: 1em;"
+ + " margin-top: -1.0em;"
+ + " margin-right: 0em;"
+ + " margin-bottom: 0em;"
+ + " margin-left: 1.0em;"
+ + "\"";
+ }
+
+// .box div.bottomleft {
+// display: block;
+// height: 55px;
+// margin-top: -1.0em;
+// background: url("i/box-bg.png") bottom left no-repeat white;
+// }
+ private static String createBottomLeftStyle()
+ {
+ return "style=\"display: block;"
+ + " height: 25px;"
+ + " margin-top: -1.0em;"
+ + " background-image: url('"+INCOMING_MESSAGE_IMAGE_PATH+"');"
+ + " background-repeat: no-repeat;"
+ + " background-position: bottom left;"
+ + " background-color: #FFFFFF;"
+ + "\"";
+ }
+
+// .box div.bottomright {
+// display: block;
+// background: url("i/box-bg.png") bottom right no-repeat white;
+// height: 55px;
+// margin-left: 3.0em;
+// }
+ private static String createBottomRightStyle()
+ {
+ return "style=\"display: block;"
+ + " height: 25px;"
+ + " margin-left: 3.0em;"
+ + " background-image: url('"+INCOMING_MESSAGE_IMAGE_PATH+"');"
+ + " background-repeat: no-repeat;"
+ + " background-position: bottom right;"
+ + " background-color: #FFFFFF;"
+ + "\"";
+ }
+
+// .box div.topright div {
+// margin-right: 1.5em;
+// }
+ private static String createMessageDivStyle()
+ {
+ return "style=\"margin-right: 1.5em;\"";
+ }
+
+// .box h4 {
+// margin-bottom: 0.4em;
+// background-image: none;
+// background-repeat: no-repeat;
+// margin:0;
+// padding:0;
+// text-align:center;
+// padding-bottom:15px;
+// }
+}
\ No newline at end of file
diff --git a/src/net/java/sip/communicator/impl/contactlist/MetaContactImpl.java b/src/net/java/sip/communicator/impl/contactlist/MetaContactImpl.java
index 8b1573d13..81528e486 100644
--- a/src/net/java/sip/communicator/impl/contactlist/MetaContactImpl.java
+++ b/src/net/java/sip/communicator/impl/contactlist/MetaContactImpl.java
@@ -6,7 +6,6 @@
*/
package net.java.sip.communicator.impl.contactlist;
-import java.io.*;
import java.util.*;
import net.java.sip.communicator.service.contactlist.*;
@@ -14,8 +13,6 @@
import net.java.sip.communicator.service.protocol.*;
import net.java.sip.communicator.util.*;
-import org.jitsi.service.fileaccess.*;
-
/**
* A default implementation of the MetaContact interface.
*
@@ -93,34 +90,11 @@ public class MetaContactImpl
*/
private Map> details;
- /**
- * The name (i.e. not the whole path) of the directory in which the avatar
- * files are to be cached for later reuse.
- */
- private final static String AVATAR_DIR = "avatarcache";
-
/**
* Whether user has renamed this meta contact.
*/
private boolean isDisplayNameUserDefined = false;
- /**
- * Characters and their replacement in created folder names
- */
- private final static String[][] ESCAPE_SEQUENCES = new String[][]
- {
- {"&", "&_amp"},
- {"/", "&_sl"},
- {"\\\\", "&_bs"}, // the char \
- {":", "&_co"},
- {"\\*", "&_as"}, // the char *
- {"\\?", "&_qm"}, // the char ?
- {"\"", "&_pa"}, // the char "
- {"<", "&_lt"},
- {">", "&_gt"},
- {"\\|", "&_pp"} // the char |
- };
-
/**
* Creates new meta contact with a newly generated meta contact UID.
*/
@@ -636,16 +610,8 @@ public byte[] getAvatar(boolean isLazy)
while (iter.hasNext())
{
Contact protoContact = iter.next();
- String avatarPath = AVATAR_DIR
- + File.separator
- + escapeSpecialCharacters(
- protoContact
- .getProtocolProvider()
- .getAccountID().getAccountUniqueID())
- + File.separator
- + escapeSpecialCharacters(protoContact.getAddress());
-
- cachedAvatar = getLocallyStoredAvatar(avatarPath);
+
+ cachedAvatar = AvatarCacheUtils.getCachedAvatar(protoContact);
/*
* Caching a zero-length avatar happens but such an avatar isn't
* very useful.
@@ -1103,136 +1069,7 @@ public void cacheAvatar( Contact protoContact,
this.cachedAvatar = avatarBytes;
this.avatarFileCacheAlreadyQueried = true;
- String avatarDirPath
- = AVATAR_DIR
- + File.separator
- + escapeSpecialCharacters(
- protoContact
- .getProtocolProvider()
- .getAccountID().getAccountUniqueID());
- String avatarFileName
- = escapeSpecialCharacters(protoContact.getAddress());
-
- File avatarDir = null;
- File avatarFile = null;
- try
- {
- FileAccessService fileAccessService
- = ContactlistActivator.getFileAccessService();
-
- avatarDir
- = fileAccessService.getPrivatePersistentDirectory(
- avatarDirPath);
- avatarFile
- = fileAccessService.getPrivatePersistentFile(
- avatarDirPath + File.separator + avatarFileName);
-
- if(!avatarFile.exists())
- {
- if (!avatarDir.exists() && !avatarDir.mkdirs())
- {
- throw
- new IOException(
- "Failed to create directory: "
- + avatarDir.getAbsolutePath());
- }
-
- if (!avatarFile.createNewFile())
- {
- throw
- new IOException(
- "Failed to create file"
- + avatarFile.getAbsolutePath());
- }
- }
-
- FileOutputStream fileOutStream = new FileOutputStream(avatarFile);
-
- try
- {
- fileOutStream.write(avatarBytes);
- fileOutStream.flush();
- }
- finally
- {
- fileOutStream.close();
- }
- }
- catch (Exception ex)
- {
- logger.error(
- "Failed to store avatar. dir =" + avatarDir
- + " file=" + avatarFile,
- ex);
- }
- }
-
- /**
- * Returns the avatar image corresponding to the given avatar path.
- *
- * @param avatarPath The path to the lovally stored avatar.
- * @return the avatar image corresponding to the given avatar path.
- */
- private byte[] getLocallyStoredAvatar(String avatarPath)
- {
- try
- {
- File avatarFile
- = ContactlistActivator
- .getFileAccessService()
- .getPrivatePersistentFile(avatarPath);
-
- if(avatarFile.exists())
- {
- FileInputStream avatarInputStream
- = new FileInputStream(avatarFile);
- byte[] bs = null;
-
- try
- {
- int available = avatarInputStream.available();
-
- if (available > 0)
- {
- bs = new byte[available];
- avatarInputStream.read(bs);
- }
- }
- finally
- {
- avatarInputStream.close();
- }
- if (bs != null)
- return bs;
- }
- }
- catch (Exception ex)
- {
- logger.error(
- "Could not read avatar image from file " + avatarPath,
- ex);
- }
- return null;
- }
-
- /**
- * Replaces the characters that we must escape used for the created
- * filename.
- *
- * @param id the String which is to have its characters escaped
- * @return a String derived from the specified id by
- * escaping characters
- */
- private String escapeSpecialCharacters(String id)
- {
- String resultId = id;
-
- for (int j = 0; j < ESCAPE_SEQUENCES.length; j++)
- {
- resultId = resultId.
- replaceAll(ESCAPE_SEQUENCES[j][0], ESCAPE_SEQUENCES[j][1]);
- }
- return resultId;
+ AvatarCacheUtils.cacheAvatar(protoContact, avatarBytes);
}
/**
diff --git a/src/net/java/sip/communicator/impl/gui/main/MainFrame.java b/src/net/java/sip/communicator/impl/gui/main/MainFrame.java
index a0f2eb9db..7bc1ea72a 100644
--- a/src/net/java/sip/communicator/impl/gui/main/MainFrame.java
+++ b/src/net/java/sip/communicator/impl/gui/main/MainFrame.java
@@ -276,7 +276,7 @@ private void init()
this.setJMenuBar(menu);
TransparentPanel searchPanel
- = new TransparentPanel(new BorderLayout(2, 0));
+ = new TransparentPanel(new BorderLayout(5, 0));
searchPanel.add(searchField);
searchPanel.add(new DialPadButton(), BorderLayout.WEST);
diff --git a/src/net/java/sip/communicator/impl/gui/main/call/AbstractCallToggleButton.java b/src/net/java/sip/communicator/impl/gui/main/call/AbstractCallToggleButton.java
index 7958d68db..0c67e0b72 100644
--- a/src/net/java/sip/communicator/impl/gui/main/call/AbstractCallToggleButton.java
+++ b/src/net/java/sip/communicator/impl/gui/main/call/AbstractCallToggleButton.java
@@ -37,17 +37,17 @@ public abstract class AbstractCallToggleButton
/**
* The background image.
*/
- protected ImageID bgImage;
+ protected ImageID bgImageID;
/**
* The rollover image
*/
- protected ImageID bgRolloverImage;
+ protected ImageID bgRolloverImageID;
/**
* The pressed image.
*/
- protected ImageID pressedImage;
+ protected ImageID pressedImageID;
/**
* The icon image.
@@ -136,27 +136,17 @@ public AbstractCallToggleButton(
this.fullScreen = fullScreen;
this.settingsPanel = settingsPanel;
- if (fullScreen)
+ if(settingsPanel)
{
- bgImage = ImageLoader.FULL_SCREEN_BUTTON_BG;
- bgRolloverImage = ImageLoader.FULL_SCREEN_BUTTON_BG;
- pressedImage = ImageLoader.FULL_SCREEN_BUTTON_BG_PRESSED;
+ bgRolloverImageID = ImageLoader.CALL_SETTING_BUTTON_BG;
+ pressedImageID = ImageLoader.CALL_SETTING_BUTTON_PRESSED_BG;
}
else
{
- if(settingsPanel)
- {
- bgImage = ImageLoader.CALL_SETTING_BUTTON_BG;
- bgRolloverImage = ImageLoader.CALL_SETTING_BUTTON_BG;
- pressedImage = ImageLoader.CALL_SETTING_BUTTON_PRESSED_BG;
- }
- else
- {
- bgImage = ImageLoader.SOUND_SETTING_BUTTON_BG;
- bgRolloverImage = ImageLoader.SOUND_SETTING_BUTTON_BG;
- pressedImage = ImageLoader.SOUND_SETTING_BUTTON_PRESSED;
+ bgImageID = ImageLoader.SOUND_SETTING_BUTTON_BG;
+ bgRolloverImageID = ImageLoader.SOUND_SETTING_BUTTON_BG;
+ pressedImageID = ImageLoader.SOUND_SETTING_BUTTON_PRESSED;
- }
}
if (toolTipTextKey != null)
@@ -171,13 +161,6 @@ public AbstractCallToggleButton(
// All items are now instantiated and could safely load the skin.
loadSkin();
-
- int width = getBgImage().getWidth(null);
- int height = getBgImage().getHeight(null);
-
- this.setPreferredSize(new Dimension(width, height));
- this.setMaximumSize(new Dimension(width, height));
- this.setMinimumSize(new Dimension(width, height));
}
/**
@@ -256,15 +239,30 @@ private void doRun()
*/
public void loadSkin()
{
- setBgImage(ImageLoader.getImage(bgImage));
- setBgRolloverImage(ImageLoader.getImage(bgRolloverImage));
- setPressedImage(ImageLoader.getImage(pressedImage));
+ int width = CallToolBarButton.DEFAULT_WIDTH;
+ int height = CallToolBarButton.DEFAULT_HEIGHT;
+
+ if (bgImageID != null)
+ {
+ Image bgImage = ImageLoader.getImage(bgImageID);
+ setBgImage(bgImage);
+
+ width = bgImage.getWidth(this);
+ height = bgImage.getHeight(this);
+ }
+
+ setPreferredSize(new Dimension(width, height));
+ setMaximumSize(new Dimension(width, height));
+ setMinimumSize(new Dimension(width, height));
+
+ setBgRolloverImage(ImageLoader.getImage(bgRolloverImageID));
+ setPressedImage(ImageLoader.getImage(pressedImageID));
if (iconImageID != null)
{
if (!fullScreen && !settingsPanel)
setIconImage(ImageUtils.scaleImageWithinBounds(
- ImageLoader.getImage(iconImageID), 12, 12));
+ ImageLoader.getImage(iconImageID), 18, 18));
else
setIconImage(ImageLoader.getImage(iconImageID));
}
@@ -273,7 +271,7 @@ public void loadSkin()
{
if (!fullScreen && !settingsPanel)
setPressedIconImage(ImageUtils.scaleImageWithinBounds(
- ImageLoader.getImage(pressedIconImageID), 12, 12));
+ ImageLoader.getImage(pressedIconImageID), 18, 18));
else
setPressedIconImage(ImageLoader.getImage(pressedIconImageID));
}
@@ -290,7 +288,7 @@ public void setIconImageID(ImageID iconImageID)
if (!fullScreen && !settingsPanel)
setIconImage(ImageUtils.scaleImageWithinBounds(
- ImageLoader.getImage(iconImageID), 12, 12));
+ ImageLoader.getImage(iconImageID), 18, 18));
else
setIconImage(ImageLoader.getImage(iconImageID));
}
diff --git a/src/net/java/sip/communicator/impl/gui/main/call/CallHistoryButton.java b/src/net/java/sip/communicator/impl/gui/main/call/CallHistoryButton.java
index 476ad524f..d00d60d06 100644
--- a/src/net/java/sip/communicator/impl/gui/main/call/CallHistoryButton.java
+++ b/src/net/java/sip/communicator/impl/gui/main/call/CallHistoryButton.java
@@ -41,6 +41,11 @@ public class CallHistoryButton
*/
private Image pressedImage;
+ /**
+ * The notification image.
+ */
+ private Image notificationImage;
+
/**
* Indicates if the history is visible.
*/
@@ -142,7 +147,10 @@ public void notificationReceived(UINotification notification)
*/
private void setHistoryView()
{
- isNotificationsView = false;
+ if (isNotificationsView)
+ isNotificationsView = false;
+ else
+ setIcon(null);
if (isHistoryVisible)
{
@@ -167,7 +175,6 @@ private void setNotificationView(
{
int notificationCount = 0;
isNotificationsView = true;
- this.setBgImage(null);
Iterator groupsIter
= notificationGroups.iterator();
@@ -204,7 +211,29 @@ private void setNotificationView(
this.setToolTipText(tooltipText + "