/**
* Copyright (c) 2005-2017, KoLmafia development team
* http://kolmafia.sourceforge.net/
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* [1] Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* [2] Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* [3] Neither the name "KoLmafia" nor the names of its contributors may
* be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
package net.sourceforge.kolmafia.swingui;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.Locale;
import javax.swing.Box;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTabbedPane;
import javax.swing.JTextField;
import javax.swing.JToolBar;
import javax.swing.SwingConstants;
import org.jdesktop.swingx.JXCollapsiblePane;
import net.java.dev.spellcast.utilities.ChatBuffer;
import net.sourceforge.kolmafia.KoLGUIConstants;
import net.sourceforge.kolmafia.RequestThread;
import net.sourceforge.kolmafia.chat.ChatFormatter;
import net.sourceforge.kolmafia.chat.ChatManager;
import net.sourceforge.kolmafia.chat.ChatPoller;
import net.sourceforge.kolmafia.chat.ChatSender;
import net.sourceforge.kolmafia.chat.StyledChatBuffer;
import net.sourceforge.kolmafia.listener.Listener;
import net.sourceforge.kolmafia.listener.NamedListenerRegistry;
import net.sourceforge.kolmafia.preferences.Preferences;
import net.sourceforge.kolmafia.request.GenericRequest;
import net.sourceforge.kolmafia.request.MallSearchRequest;
import net.sourceforge.kolmafia.session.ContactManager;
import net.sourceforge.kolmafia.swingui.button.InvocationButton;
import net.sourceforge.kolmafia.swingui.listener.DefaultComponentFocusTraversalPolicy;
import net.sourceforge.kolmafia.swingui.listener.HyperlinkAdapter;
import net.sourceforge.kolmafia.swingui.listener.StickyListener;
import net.sourceforge.kolmafia.swingui.listener.ThreadedListener;
import net.sourceforge.kolmafia.swingui.widget.RequestPane;
import net.sourceforge.kolmafia.utilities.StringUtilities;
import net.sourceforge.kolmafia.webui.RelayLoader;
public class ChatFrame
extends GenericFrame
{
private static final GenericRequest PROFILER = new GenericRequest( "" );
private static final GenericRequest PVP_LOGGER = new GenericRequest( "" );
private static final SimpleDateFormat MARK_TIMESTAMP = new SimpleDateFormat( "HH:mm:ss", Locale.US );
private ChatPanel mainPanel;
private JComboBox nameClickSelect;
/**
* Constructs a new <code>ChatFrame</code> which is intended to be used for instant messaging to the specified
* contact.
*/
public ChatFrame( final String associatedContact )
{
super(
associatedContact == null || associatedContact.equals( "" ) ?
"Loathing Chat" :
associatedContact.startsWith( "/" ) ?
"Chat: " + associatedContact :
"Chat PM: " + associatedContact );
this.initialize( associatedContact );
// Add the standard chat options which the user
// simply clicks on for functionality.
JToolBar toolbarPanel = this.getToolbar();
if ( toolbarPanel != null )
{
// Add the name click options as a giant combo
// box, rather than a hidden menu.
this.nameClickSelect = new JComboBox();
this.nameClickSelect.addItem( "Name click shows player profile" );
this.nameClickSelect.addItem( "Name click opens blue message" );
this.nameClickSelect.addItem( "Name click sends kmail message" );
this.nameClickSelect.addItem( "Name click opens trade request" );
this.nameClickSelect.addItem( "Name click shows display case" );
this.nameClickSelect.addItem( "Name click shows ascension history" );
this.nameClickSelect.addItem( "Name click shows mall store" );
this.nameClickSelect.addItem( "Name click performs /whois" );
this.nameClickSelect.addItem( "Name click friends the player" );
this.nameClickSelect.addItem( "Name click baleets the player" );
toolbarPanel.add( this.nameClickSelect );
this.nameClickSelect.setSelectedIndex( 0 );
}
// Set the default size so that it doesn't appear super-small
// when it's first constructed
this.setSize( new Dimension( 500, 300 ) );
if ( this.mainPanel != null && associatedContact != null )
{
if ( associatedContact.startsWith( "/" ) )
{
this.setTitle( associatedContact );
}
else
{
this.setTitle( "private to " + associatedContact );
}
}
this.setFocusCycleRoot( true );
this.setFocusTraversalPolicy( new DefaultComponentFocusTraversalPolicy( this.mainPanel ) );
}
@Override
public void focusGained( FocusEvent e )
{
if ( this.mainPanel != null )
{
this.mainPanel.requestFocus();
}
}
@Override
public void focusLost( FocusEvent e )
{
}
@Override
public JToolBar getToolbar()
{
if ( !Preferences.getBoolean( "useChatToolbar" ) )
{
return null;
}
JToolBar toolbarPanel = super.getToolbar( true );
toolbarPanel.add( new InvocationButton( "/friends", "who2.gif", ChatManager.class, "checkFriends" ) );
toolbarPanel.add( Box.createHorizontalStrut( 10 ) );
toolbarPanel.add( new InvocationButton(
"Add Highlighting", "highlight1.gif", ChatFormatter.class, "addHighlighting" ) );
toolbarPanel.add( new InvocationButton(
"Remove Highlighting", "highlight2.gif", ChatFormatter.class, "removeHighlighting" ) );
this.wrapToolBar( toolbarPanel );
return toolbarPanel;
}
private void wrapToolBar(JToolBar toolbarPanel)
{
AwayPanel awayPanel = new AwayPanel();
awayPanel.setMinimumSize( new Dimension( 0, 0 ) ); // collapse all the way when hidden
awayPanel.setPreferredSize( new Dimension( 20, 20 ) );
JLabel label = new JLabel(
"<html><b>You're now in away mode; click to return to normal</b></html>", SwingConstants.CENTER );
label.setForeground( Color.blue.darker() );
label.setFont( KoLGUIConstants.DEFAULT_FONT );
label.setCursor( new Cursor( Cursor.HAND_CURSOR ) );
label.addMouseListener( awayPanel );
awayPanel.add( label );
awayPanel.setCollapsed( true );
JPanel wrapped = new JPanel( new BorderLayout() );
wrapped.add( toolbarPanel, BorderLayout.CENTER );
wrapped.add( awayPanel, BorderLayout.SOUTH );
//the NORTH element in the framePanel was the toolbar; pull it out and replace it with the "wrapped" panel
this.getFramePanel().remove( toolbarPanel );
this.getFramePanel().add( wrapped, BorderLayout.NORTH );
}
@Override
public Component getCenterComponent()
{
return this.getFramePanel();
}
@Override
public JTabbedPane getTabbedPane()
{
return null;
}
@Override
public boolean shouldAddStatusBar()
{
return false;
}
@Override
public boolean showInWindowMenu()
{
return false;
}
@Override
public void dispose()
{
String contact = this.getAssociatedContact();
if ( contact != null && contact.equals( ChatManager.getCurrentChannel() ) )
{
contact = null;
}
ChatManager.closeWindow( contact );
super.dispose();
}
/**
* Utility method called to initialize the frame. This method should be overridden, should a different means of
* initializing the content of the frame be needed.
*/
public void initialize( final String associatedContact )
{
this.mainPanel = new ChatPanel( associatedContact );
this.setCenterComponent( this.mainPanel );
}
/**
* Utility method for creating a single panel containing the chat display and the entry area. Note that calling this
* method changes the <code>RequestPane</code> returned by calling the <code>getChatDisplay()</code> method.
*/
public class ChatPanel
extends JPanel
implements FocusListener
{
private final ArrayList<String> commandHistory;
private int lastCommandIndex = 0;
private final JTextField entryField;
private final RequestPane chatDisplay;
private final String associatedContact;
public ChatPanel( final String associatedContact )
{
super( new BorderLayout() );
this.chatDisplay = new RequestPane();
this.chatDisplay.addHyperlinkListener( new ChatLinkClickedListener() );
this.associatedContact = associatedContact;
this.commandHistory = new ArrayList<String>();
ChatEntryListener listener = new ChatEntryListener();
JPanel entryPanel = new JPanel( new BorderLayout() );
this.entryField = new JTextField();
this.entryField.addKeyListener( listener );
JButton entryButton = new JButton( "chat" );
entryButton.addActionListener( listener );
entryPanel.add( this.entryField, BorderLayout.CENTER );
entryPanel.add( entryButton, BorderLayout.EAST );
ChatBuffer buffer = ChatManager.getBuffer( associatedContact );
JScrollPane scroller = buffer.addDisplay( this.chatDisplay );
scroller.getVerticalScrollBar().addAdjustmentListener( new StickyListener( buffer, this.chatDisplay, 200 ) );
this.add( scroller, BorderLayout.CENTER );
this.add( entryPanel, BorderLayout.SOUTH );
this.setFocusTraversalPolicy( new DefaultComponentFocusTraversalPolicy( this.entryField ) );
this.addFocusListener( this );
}
public void focusGained( FocusEvent e )
{
this.entryField.requestFocus();
}
public void focusLost( FocusEvent e )
{
}
public String getAssociatedContact()
{
return this.associatedContact;
}
@Override
public boolean hasFocus()
{
if ( this.entryField == null || this.chatDisplay == null )
{
return false;
}
return this.entryField.hasFocus() || this.chatDisplay.hasFocus();
}
/**
* An action listener responsible for sending the text contained within the entry panel to the KoL chat server
* for processing. This listener spawns a new request to the server which then handles everything that's needed.
*/
private class ChatEntryListener
extends ThreadedListener
{
@Override
protected void execute()
{
if ( this.isAction() )
{
this.submitChat();
return;
}
int keyCode = this.getKeyCode();
if ( keyCode == KeyEvent.VK_UP )
{
String text;
if ( ChatPanel.this.lastCommandIndex <= 0 )
{
ChatPanel.this.lastCommandIndex = -1;
text = "";
}
else
{
text = ChatPanel.this.commandHistory.get( --ChatPanel.this.lastCommandIndex );
}
ChatPanel.this.entryField.setText( text );
}
else if ( keyCode == KeyEvent.VK_DOWN )
{
String text;
if ( ChatPanel.this.lastCommandIndex + 1 >= ChatPanel.this.commandHistory.size() )
{
ChatPanel.this.lastCommandIndex = ChatPanel.this.commandHistory.size();
text = "";
}
else
{
text = ChatPanel.this.commandHistory.get( ++ChatPanel.this.lastCommandIndex );
}
ChatPanel.this.entryField.setText( text );
}
else if ( keyCode == KeyEvent.VK_ENTER )
{
this.submitChat();
}
}
@Override
protected boolean isValidKeyCode( int keyCode )
{
return keyCode == KeyEvent.VK_ENTER || keyCode == KeyEvent.VK_UP || keyCode == KeyEvent.VK_DOWN;
}
private void submitChat()
{
String message = ChatPanel.this.entryField.getText();
if ( message.equals( "" ) )
{
return;
}
ChatPanel.this.entryField.setText( "" );
StyledChatBuffer buffer = ChatManager.getBuffer( ChatPanel.this.associatedContact );
if ( message.startsWith( "/clear" ) || message.startsWith( "/cls" ) || message.equals( "clear" ) || message.equals( "cls" ) )
{
buffer.clear();
return;
}
if ( message.equals( "/m" ) || message.startsWith( "/mark" ) )
{
buffer.append( "<br><hr><center><font size=2>" + ChatFrame.MARK_TIMESTAMP.format( new Date() ) + "</font></center><br>" );
return;
}
if ( message.startsWith( "/?" ) || message.startsWith( "/help" ) )
{
RelayLoader.openSystemBrowser( "http://www.kingdomofloathing.com/doc.php?topic=chat_commands" );
return;
}
ChatPanel.this.commandHistory.add( message );
ChatPanel.this.lastCommandIndex = ChatPanel.this.commandHistory.size();
ChatSender.sendMessage( ChatPanel.this.associatedContact, message, false );
}
}
}
/**
* Returns the name of the contact associated with this frame.
*
* @return The name of the contact associated with this frame
*/
public String getAssociatedContact()
{
return this.mainPanel == null ? null : this.mainPanel.getAssociatedContact();
}
/**
* Returns whether or not the chat frame has focus.
*/
@Override
public boolean hasFocus()
{
return super.hasFocus() || this.mainPanel != null && this.mainPanel.hasFocus();
}
/**
* Action listener responsible for displaying private message window when a username is clicked, or opening the page
* in a browser if you're clicking something other than the username.
*/
private class ChatLinkClickedListener
extends HyperlinkAdapter
{
@Override
public void handleInternalLink( final String location )
{
if ( location.startsWith( "makeoffer" ) ||
location.startsWith( "counteroffer" ) ||
location.startsWith( "bet" ) ||
location.startsWith( "peevpee.php") ||
location.startsWith( "messages" ) )
{
RelayLoader.openSystemBrowser( location, true );
return;
}
int equalsIndex = location.indexOf( "=" );
if ( equalsIndex == -1 )
{
RelayLoader.openSystemBrowser( location, true );
return;
}
String[] locationSplit = new String[ 2 ];
locationSplit[ 0 ] = location.substring( 0, equalsIndex );
locationSplit[ 1 ] = location.substring( equalsIndex + 1 );
// First, determine the parameters inside of the
// location which will be passed to frame classes.
String playerId = locationSplit[ 1 ];
String playerName = ContactManager.getPlayerName( playerId );
// Next, determine the option which had been
// selected in the link-click.
int linkOption = ChatFrame.this.nameClickSelect != null ?
ChatFrame.this.nameClickSelect.getSelectedIndex(): 0;
String urlString = null;
switch ( linkOption )
{
case 1:
String bufferKey = ChatManager.getBufferKey( playerName );
ChatManager.openWindow( bufferKey, false );
return;
case 2:
Object[] parameters = new Object[]
{ playerName
};
GenericFrame.createDisplay( SendMessageFrame.class, parameters );
return;
case 3:
urlString = "makeoffer.php?towho=" + playerId;
break;
case 4:
urlString = "displaycollection.php?who=" + playerId;
break;
case 5:
urlString = "ascensionhistory.php?who=" + playerId;
break;
case 6:
GenericFrame.createDisplay( MallSearchFrame.class );
MallSearchFrame.searchMall( new MallSearchRequest( StringUtilities.parseInt( playerId ) ) );
return;
case 7:
ChatSender.sendMessage( playerName, "/whois", false );
return;
case 8:
ChatSender.sendMessage( playerName, "/friend", false );
return;
case 9:
ChatSender.sendMessage( playerName, "/baleet", false );
return;
default:
urlString = "showplayer.php?who=" + playerId;
break;
}
if ( Preferences.getBoolean( "chatLinksUseRelay" ) || !urlString.startsWith( "show" ) )
{
RelayLoader.openSystemBrowser( urlString );
}
else
{
ProfileFrame.showRequest( ChatFrame.PROFILER.constructURLString( urlString ) );
}
}
}
private class AwayPanel
extends JXCollapsiblePane
implements MouseListener, Listener, Runnable
{
public AwayPanel()
{
NamedListenerRegistry.registerNamedListener( "[chatAway]", this );
}
public void mouseClicked( MouseEvent arg0 )
{
//wake up
RequestThread.runInParallel( this, false );
}
public void mouseEntered( MouseEvent arg0 )
{
}
public void mouseExited( MouseEvent arg0 )
{
}
public void mousePressed( MouseEvent arg0 )
{
}
public void mouseReleased( MouseEvent arg0 )
{
}
public void update()
{
this.setCollapsed( !ChatPoller.isPaused() );
}
public void run()
{
ChatSender.sendMessage( null, "/listen", false );
}
}
}