Start line:  
End line:  

Snippet Preview

Snippet HTML Code

Stack Overflow Questions
  //
  // Nenya library - tools for developing networked games
  // Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
  // https://github.com/threerings/nenya
  //
  // This library is free software; you can redistribute it and/or modify it
  // under the terms of the GNU Lesser General Public License as published
  // by the Free Software Foundation; either version 2.1 of the License, or
  // (at your option) any later version.
 //
 // This library 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
 // Lesser General Public License for more details.
 //
 // You should have received a copy of the GNU Lesser General Public
 // License along with this library; if not, write to the Free Software
 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 
 package com.threerings.chat;
 
 import java.util.List;
 
 import java.awt.Point;
 import java.awt.Shape;
 
 
 
 
 
 import static com.threerings.NenyaLog.log;

Implements subtitle chat.
 
 public class SubtitleChatOverlay extends ChatOverlay
     implements ChangeListenerHistoryList.Observer
 {
    
Construct a subtitle chat overlay.

Parameters:
subtitleHeight the height of the subtitle area.
 
     public SubtitleChatOverlay (CrowdContext ctxChatLogic logicJScrollBar bar,
                                 int subtitleHeight)
     {
         this(ctxlogicbarsubtitleHeightfalse, 8, 8);
     }

    
Construct a comic chat overlay using all the available space for subtitling.
 
     public SubtitleChatOverlay (CrowdContext ctxChatLogic logicJScrollBar bar)
     {
         this(ctxlogicbarfalse);
     }
 
     public SubtitleChatOverlay (CrowdContext ctxChatLogic logicJScrollBar barboolean full)
     {
         this(ctxlogicbar, 0, truefull ? 8 : 3, full ? 8 : 4);
     }
 
     public SubtitleChatOverlay (CrowdContext ctxChatLogic logicJScrollBar barboolean full,
                                 boolean overrideHistory)
     {
         this(ctxlogicbarfull);
          = overrideHistory;
     }
 
     // from interface HistoryList.Observer
     public void historyUpdated (int adjustment)
     {
         if (adjustment != 0) {
             for (int ii = 0, nn = .size(); ii < nnii++) {
                 ChatGlyph cg = .get(ii);
                 cg.histIndex -= adjustment;
             }
             // some history entries were deleted, we need to re-figure the history scrollbar action
            resetHistoryOffset();
        }
        if (isLaidOut() && isHistoryMode()) {
            int val = .getValue();
            updateHistBar(val - adjustment);
            // only repaint if we need to
            if ((val != .getValue()) || (adjustment != 0) || !) {
                figureCurrentHistory();
            }
        }
    }
    // documentation inherited from interface ChangeListener
    public void stateChanged (ChangeEvent e)
    {
        // the scrollbar has changed.
        if (!) {
            figureCurrentHistory();
        }
    }
    // documentation inherited from superinterface ChatDisplay
    public void clear ()
    {
        clearGlyphs();
    }
    // documentation inherited from superinterface ChatDisplay
    public boolean displayMessage (ChatMessage messageboolean alreadyDisplayed)
    {
        // nothing doing if we've not been laid out
        if (!isLaidOut()) {
            return false;
        }
        // possibly display it now
        Graphics2D gfx = getTargetGraphics();
        if (gfx != null) {
            displayMessage(messagegfx); // display it
            gfx.dispose(); // clean up
            return true;
        }
        return false;
    }
    @Override
    public void viewDidScroll (int dxint dy)
    {
        super.viewDidScroll(dxdy);
        viewDidScroll(dxdy);
        viewDidScroll(dxdy);
    }
    @Override
    public void added (VirtualMediaPanel target)
    {
        super.added(target);
        .addObserver(this);
        if () {
            setHistoryEnabled(true);
            return;
        }
        // derived classes may want to override this method and set up chat history mode based on
        // whatever preference storage mechanism they use
    }
    @Override
    public void layout ()
    {
        // sanity check
        if ( == null) {
            .warning(this + " laid out without target?"new Exception());
            return;
        }
        Rectangle vbounds = .getViewBounds();
        if ((vbounds.height < 1) || (vbounds.width < 1)) {
            return// fuck that!
        }
        clearGlyphs(); // we'll re-populate from the history
        if () {
             = vbounds.height;
        }
        // make a guess as to the extent of the history (how many avg sized subtitles will fit in
        // the subtitle area)
        // show messages that were born recently enough to be shown.
        long now = System.currentTimeMillis();
        // find the first message to display
        int histSize = .size();
        int index = histSize - 1;
        for ( ; index >= 0; index--) {
            ChatMessage msg = .get(index);
             = 0L;
            if (now > getChatExpire(msg.timestampmsg.message)) {
                break;
            }
        }
        // now that we've found the message that's one too old, increment the index so that it
        // points to the first message we should display
        index++;
         = 0L;
        // now dispatch from that point
        Graphics2D gfx = getTargetGraphics();
        for ( ; index < histSizeindex++) {
            ChatMessage msg = .get(index);
            if (shouldShowFromHistory(msgindex)) {
                displayMessage(msggfx);
            }
        }
        // and clean up
        gfx.dispose();
        // make a note that we're laid out
         = true;
        // reset the history offset..
        resetHistoryOffset();
        // finally, if we're in history mode, we should figure that out too
        if (isHistoryMode()) {
            updateHistBar(histSize - 1);
            figureCurrentHistory();
        }
    }
    @Override
    public void setDimmed (boolean dimmed)
    {
        super.setDimmed(dimmed);
        updateDimmed();
    }
    @Override
    public void removed ()
    {
        // we need to do this before super so that our target is still around
        clearGlyphs();
        super.removed();
        .removeObserver(this);
        // clear out our history so that when we are once again added, we activate it and go
        // through the motions of refiguring everything
        setHistoryEnabled(false);
        // make a note that we'll need to lay ourselves out before we do anything fun next time
         = false;
    }

    
Shared chained constructor.
    protected SubtitleChatOverlay (CrowdContext ctxChatLogic logicJScrollBar barint height,
                                   boolean fillint xspaceint yspace)
    {
        super(ctxlogic);
         = bar;
         = height;
         = fill;
         = xspace;
         = yspace;
         = ctx.getChatDirector().getHistory();
    }

    
Update the chat glyphs in the specified list to be set to the current dimmed setting.
    protected void updateDimmed (List<? extends ChatGlyphglyphs)
    {
        for (ChatGlyph glyph : glyphs) {
            glyph.setDim();
        }
    }

    
Are we currently in history mode?
    protected boolean isHistoryMode ()
    {
        return ( != null);
    }

    
Return the current Graphics context of our target, or null if not applicable.
    protected Graphics2D getTargetGraphics ()
    {
        // this may return null even if target is not null.
        return ( == null) ? null : (Graphics2D).getGraphics();
    }

    
Configures us for display of chat history or not.
    protected void setHistoryEnabled (boolean historyEnabled)
    {
        if (historyEnabled &&  == null) {
             = .getModel();
            .addChangeListener(this);
            resetHistoryOffset();
            // out with the subtitles, we'll be displaying history
            clearGlyphs();
            // "scroll" down to the latest history entry
            updateHistBar(.size() - 1);
            // refigure our history
            figureCurrentHistory();
        } else if (!historyEnabled &&  != null) {
            .removeChangeListener(this);
             = null;
            // out with the history, we'll be displaying subtitles
            clearGlyphs();
        }
    }

    
Helper function for informing glyphs of the scrolled view.
    protected void viewDidScroll (List<? extends ChatGlyphglyphsint dxint dy)
    {
        for (ChatGlyph glyph : glyphs) {
            glyph.viewDidScroll(dxdy);
        }
    }

    
Update the history scrollbar with the specified value.
    protected void updateHistBar (int val)
    {
        // we may need to figure out the new history offset amount..
        if (! && .size() > ) {
            Graphics2D gfx = getTargetGraphics();
            if (gfx != null) {
                figureHistoryOffset(gfx);
                gfx.dispose();
            }
        }
        // then figure out the new value and range
        int oldval = Math.max(val);
        int newmaxval = Math.max(0, .size() - 1);
        int newval = (oldval >= newmaxval - 1) ? newmaxval : oldval;
        // and set it, which MAY generate a change event, but we want to ignore it so we use the
        // _settingBar flag
         = true;
                                         newmaxval + ,
                                         .getValueIsAdjusting());
         = false;
    }

    
Reset the history offset so that it will be recalculated next time it is needed.
    protected void resetHistoryOffset ()
    {
         = false;
         = 0;
    }

    
Figure out how many of the first history elements fit in our bounds such that we can set the bounds on the scrollbar correctly such that the scrolling to the smallest value just barely puts the first element onscreen.
    protected void figureHistoryOffset (Graphics2D gfx)
    {
        if (!isLaidOut()) {
            return;
        }
        int hei = ;
        int hsize = .size();
        for (int ii = 0; ii < hsizeii++) {
            ChatGlyph rec = getHistorySubtitle(iigfx);
            Rectangle r = rec.getBounds();
            hei += r.height;
            // oop, we passed it, it was the last one
            if (hei >= ) {
                 = Math.max(0, ii - 1);
                 = true;
                return;
            }
            hei += getHistorySubtitleSpacing(ii);
        }
        // basically, this means there isn't yet enough history to fill the first 'page' of the
        // history scrollback, so we set the offset to the max value, but we do not set
        // _histOffsetFinal to be true so that this will be recalculated
         = hsize - 1;
    }

    
Figure out which ChatMessages in the history should currently appear in the showing history.
    protected void figureCurrentHistory ()
    {
        int first = .getValue();
        int count = 0;
        Graphics2D gfx = null;
        if (isLaidOut() && !.isEmpty()) {
            gfx = getTargetGraphics();
            if (gfx == null) {
                .warning("Can't figure current history, no graphics.");
                return;
            }
            // start from the bottom..
            Rectangle vbounds = .getViewBounds();
            int ypos = vbounds.height - ;
            for (int ii = firstii >= 0; ii--, count++) {
                ChatGlyph rec = getHistorySubtitle(iigfx);
                // see if it will fit
                Rectangle r = rec.getBounds();
                ypos -= r.height;
                if ((count != 0) && ypos <= (vbounds.height - )) {
                    break// don't add that one..
                }
                // position it
                rec.setLocation(vbounds.x + vbounds.y + ypos);
                // add space for the next
                ypos -= getHistorySubtitleSpacing(ii);
            }
        }
        // finally, because we've been adding to the _showingHistory here (via getHistorySubtitle)
        // and in figureHistoryOffset (possibly called prior to this method) we now need to prune
        // out the ChatGlyphs that aren't actually needed and make sure the ones that are are
        // positioned on the screen correctly
        for (Iterator<ChatGlyphitr = .iterator(); itr.hasNext(); ) {
            ChatGlyph cg = itr.next();
            boolean managed = ( != null) && .isManaged(cg);
            if (cg.histIndex <= first && cg.histIndex > (first - count)) {
                // it should be showing
                if (!managed) {
                    .addAnimation(cg);
                }
            } else {
                // it shouldn't be showing
                if (managed) {
                    .abortAnimation(cg);
                }
                itr.remove();
            }
        }
        if (gfx != null) {
            gfx.dispose();
        }
    }

    
Get the glyph for the specified history index, creating if necessary.
    protected ChatGlyph getHistorySubtitle (int indexGraphics2D layoutGfx)
    {
        // do a brute search (over a small set) for an already-created subtitle
        for (int ii = 0, nn = .size(); ii < nnii++) {
            ChatGlyph cg = .get(ii);
            if (cg.histIndex == index) {
                return cg;
            }
        }
        // it looks like we have to create a new one: expensive!
        ChatGlyph cg = createHistorySubtitle(indexlayoutGfx);
        cg.histIndex = index;
        cg.setDim();
        .add(cg);
        return cg;
    }

    
Creates a subtitle for display in the history panel.

Parameters:
index the index of the message in the history list
    protected ChatGlyph createHistorySubtitle (int indexGraphics2D layoutGfx)
    {
        ChatMessage message = .get(index);
        int type = getType(messagetrue);
        return createSubtitle(messagetypelayoutGfxfalse);
    }

    
Determines the amount of spacing to put after a history subtitle.

Parameters:
index the index of the message in the history list
    protected int getHistorySubtitleSpacing (int index)
    {
        ChatMessage message = .get(index);
        return .getSubtitleSpacing(getType(messagetrue));
    }
    protected boolean isLaidOut ()
    {
        return isShowing() && ;
    }

    
We're looking through history to figure out what messages we should be showing, should we show the following?
    protected boolean shouldShowFromHistory (ChatMessage msgint index)
    {
        return true// yes by default.
    }

    
Clears out the supplied list of chat glyphs.
    protected void clearGlyphs (List<ChatGlyphglyphs)
    {
        if ( != null) {
            for (int ii = 0, nn = glyphs.size(); ii < nnii++) {
                ChatGlyph rec = glyphs.get(ii);
                .abortAnimation(rec);
            }
        } else if (!glyphs.isEmpty()) {
            .warning("No target to abort chat animations");
        }
        glyphs.clear();
    }

    
Display the specified message now, unless we are to ignore it.
    protected void displayMessage (ChatMessage messageGraphics2D gfx)
    {
        // get the non-history message type...
        int type = getType(messagefalse);
        if (type != .) {
            // display it now
            displayMessage(messagetypegfx);
        }
    }

    
Display the message after we've decided which type it is.
    protected void displayMessage (ChatMessage messageint typeGraphics2D layoutGfx)
    {
        // if we're in history mode, this will show up in the history and we'll rebuild our
        // subtitle list if and when history goes away
        if (isHistoryMode()) {
            return;
        }
        addSubtitle(createSubtitle(messagetypelayoutGfxtrue));
    }

    
Add a subtitle for display now.
    protected void addSubtitle (ChatGlyph rec)
    {
        // scroll up the old subtitles
        Rectangle r = rec.getBounds();
        scrollUpSubtitles(-r.height - .getSubtitleSpacing(rec.getType()));
        // put this one in place
        Rectangle vbounds = .getViewBounds();
        rec.setLocation(vbounds.x + ,
                        vbounds.y + vbounds.height -  - r.height);
        // add it to our list and to our media panel
        rec.setDim();
        .add(rec);
        .addAnimation(rec);
    }

    
Create a subtitle, but don't do anything funny with it.
    protected ChatGlyph createSubtitle (ChatMessage messageint typeGraphics2D layoutGfx,
                                        boolean expires)
    {
        // we might need to modify the textual part with translations, but we can't do that to the
        // message object, since other chatdisplays also get it.
        String text = message.message;
        Tuple<StringBooleanfinfo = .decodeFormat(typemessage.getFormat());
        String format = finfo.left;
        boolean quotes = finfo.right;
        // now format the text
        if (format != null) {
            if (quotes) {
                text = "\"" + text + "\"";
            }
            text = " " + text;
            text = xlate(MessageBundle.tcompose(
                             format, ((UserMessagemessage).getSpeakerDisplayName())) + text;
        }
        return createSubtitle(layoutGfxtypemessage.timestampnull, 0, textexpires);
    }

    
Create a subtitle- a line of text that goes on the bottom.
    protected ChatGlyph createSubtitle (Graphics2D gfxint typelong timestampIcon icon,
                                        int indentString textboolean expires)
    {
        Dimension is = new Dimension();
        if (icon != null) {
            is.setSize(icon.getIconWidth(), icon.getIconHeight());
        }
        Rectangle vbounds = .getViewBounds();
        Label label = .createLabel(text);
        label.setFont(.getFont(type));
        int paddedIconWidth = (icon == null) ? 0 : is.width + ;
        label.setTargetWidth(
            vbounds.width - indent - paddedIconWidth -
            2 * ( + Math.max(UIManager.getInt("ScrollBar.width"), )));
        label.layout(gfx);
        gfx.dispose();
        Dimension ls = label.getSize();
        Rectangle r = new Rectangle(0, 0, ls.width + indent + paddedIconWidth,
                                    Math.max(is.heightls.height));
        r.grow(0, 1);
        Point iconpos = r.getLocation();
        iconpos.translate(indentr.height - is.height - 1);
        Point labelpos = r.getLocation();
        labelpos.translate(indent + paddedIconWidth, 1);
        Rectangle lr = new Rectangle(r.x + indent + paddedIconWidthr.y,
                                     r.width - indent - paddedIconWidthls.height + 2);
        // last a really long time if we're not supposed to expire
        long lifetime = .;
        if (expires) {
            lifetime = getChatExpire(timestamplabel.getText()) - timestamp;
        }
        Shape shape = .getSubtitleShape(typelrr);
        return new ChatGlyph(thistypelifetimer.union(shape.getBounds()), shapeicon,
                             iconposlabellabelpos.getOutlineColor(type));
    }

    
Get the expire time for the specified chat.
    protected long getChatExpire (long timestampString text)
    {
        long[] durations = .getDisplayDurations(getDisplayDurationOffset());
        // start the computation from the maximum of the timestamp or our last expire time
        long start = Math.max(timestamp);
        // set the next expire to a time proportional to the text length
         = start + Math.min(text.length() * durations[0], durations[2]);
        // but don't let it be longer than the maximum display time
         = Math.min(timestamp + durations[2], );
        // and be sure to pop up the returned time so that it is above the min
        return Math.max(timestamp + durations[1], );
    }

    
A hack to allow subtitle chat to display longer and comic chat to display for a normal duration.
    protected int getDisplayDurationOffset ()
    {
        // the subtitle view adds one to bump up to the next longer display duration because we
        // want subtitles to stick around longer
        return 1;
    }

    
Called by a chat glyph when it has determined that it is expired.
    protected void glyphExpired (ChatGlyph glyph)
    {
        .remove(glyph);
    }

    
Convert the Message class/localtype/mode into our internal type code.
    protected int getType (ChatMessage messageboolean history)
    {
        String localtype = message.localtype;
        if (message instanceof TellFeedbackMessage) {
            if (((TellFeedbackMessage)message).isFailure()) {
                return .;
            }
            return (history || isApprovedLocalType(localtype)) ?
                . : .;
        } else if (message instanceof UserMessage) {
            int type = .decodeType(localtype);
            if (type != 0) {
                // factor in the mode
                return .adjustTypeByMode(((UserMessagemessage).type);
            }
            // if we're showing from history, include specialized chat messages
            if (history) {
                return .;
            }
            // otherwise fall through and IGNORECHAT
        } else if (message instanceof SystemMessage) {
            if (history || isApprovedLocalType(localtype)) {
                switch (((SystemMessagemessage).) {
                case .:
                    return .;
                case .:
                    return .;
                case .:
                    return .;
                default:
                    .warning("Unknown attention level for system message""msg"message);
                }
            }
            return .;
        }
        .warning("Skipping received message of unknown type""msg"message);
        return .;
    }

    
Check to see if we want to display the specified localtype.
    protected boolean isApprovedLocalType (String localtype)
    {
        return true// we show everything, the ComicChat is a little more picky
    }

    
Scroll all the subtitles up by the specified amount.
    protected void scrollUpSubtitles (int dy)
    {
        // dirty and move all the old glyphs
        Rectangle vbounds = .getViewBounds();
        int miny = vbounds.y + vbounds.height - ;
        for (Iterator<ChatGlyphiter = .iterator(); iter.hasNext();) {
            ChatGlyph sub = iter.next();
            sub.translate(0, dy);
            if (sub.getBounds(). <= miny) {
                iter.remove();
                .abortAnimation(sub);
            }
        }
    }

    
List of existing messages from our chat director.
    protected HistoryList _history;

    
If set, show history no matter what the client prefs say.
    protected boolean _overrideHistory;

    
The currently displayed subtitles O' history.
    protected List<ChatGlyph_showingHistory = Lists.newArrayList();

    
Our history scrollbar.
    protected JScrollBar _scrollbar;

    
Tracks whether or not we've been laid out.
    protected boolean _laidout;

    
If we're in history mode, this will be non-null and will notify us of our historical positioning.
    protected BoundedRangeModel _historyModel = null;

    
The currently displayed subtitle areas.
    protected List<ChatGlyph_subtitles = Lists.newArrayList();

    
The amount of vertical space to use for subtitles.
    protected int _subtitleHeight;

    
If true, subtitles should fill all available height.
    protected boolean _subtitlesFill;

    
The amount of space we want around the subtitles.
    protected int _subtitleXSpacing;
    protected int _subtitleYSpacing;

    
The unbounded expire time of the last chat glyph displayed.
    protected long _lastExpire;

    
If the history offset we've figured is all figured out or needs to be refigured.
    protected boolean _histOffsetFinal = false;

    
If true, we're the ones updating the history scrollbar and change events should be ignored.
    boolean _settingBar = false;

    
The history offset (from 0) such that the history lines (0, _histOffset - 1) will all fit onscreen if the lowest scrollbar position is _histOffset.
    protected int _histOffset = 0;

    
A guess of how many history lines fit onscreen at a time.
    protected int _historyExtent;

    
A guess as to the height of a subtitle (plus spacing).
    protected static final int SUBTITLE_HEIGHT_GUESS = 16;

    
The amount of space to insert between the icon and the text.
    protected static final int ICON_PADDING = 4;

    
The padding in each direction around the text to the edges of a chat 'bubble'.
    protected static final int PAD = .;
New to GrepCode? Check out our FAQ X