package rabbitescape.ui.swing;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Frame;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.BorderFactory;
import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JScrollPane;
import javax.swing.JSpinner;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.SpinnerModel;
import javax.swing.SwingWorker;
import javax.swing.Timer;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import static rabbitescape.engine.i18n.Translation.t;
/**
* @brief GUI elements for retrieving levels from issues.
*/
public class GitHubIssueDialog extends JDialog implements ChangeListener
{
private static final long serialVersionUID = 1L;
private JTextField issueNameBox = new JTextField( "" );
private JTextArea issueTextBox = new JTextArea( "" );
private JTextArea issueWorldBox = new JTextArea( "" );
private JTextField statusBox = new JTextField(
t( "Attempting to contact github." ) );
private IssueSpinnerModel issueModel;
private WorldSpinnerModel worldModel;
private boolean choseWorld = false;
GitHubClient ghc = null;
public static enum Label
{
ALL,
BUG,
LEVEL
};
class IssueSpinnerModel implements SpinnerModel, GitHubPageFetcher
{
private int issueIndex = 0;
private ChangeListener changeListener;
private Label filterMode = Label.ALL;
IssueSpinnerModel()
{
}
/**
* @brief
*/
@Override
public void notifyAndFetch( String url,
String requestProperty,
String notification,
GitHubPageFetchNotifier ghpfn )
{
GitHubIssue ghi = issueModel.getCurrentIssue();
if ( null == ghi )
{
return;
}
final IssuePageFetchWorker<Void, Void> w =
new IssuePageFetchWorker<Void, Void>( url, requestProperty,
ghpfn );
w.execute();
statusBox.setText( notification );
// Let dt repaint statusBox
DotTic dt = new DotTic( w );
dt.start();
}
@Override
public void addChangeListener( ChangeListener cl )
{
changeListener = cl;
}
@Override
public Object getNextValue()
{
return getRelativeValue( -1 );
}
@Override
public Object getPreviousValue()
{
return getRelativeValue( +1 );
}
public Object getRelativeValue( int indexStep )
{
if ( null == ghc )
{
return null;
}
int newIssueIndex = issueIndex + indexStep;
GitHubIssue ghi = ghc.getIssue( newIssueIndex, this );
if ( null == ghi )
{
return null;
}
switch ( filterMode )
{
case BUG:
if ( !ghi.isBug() )
{
return getRelativeValue( indexStep
+ (int)Math.signum( indexStep ) );
}
break;
case LEVEL:
if ( !ghi.isLevel() )
{
return getRelativeValue( indexStep
+ (int)Math.signum( indexStep ) );
}
break;
case ALL:
}
return Integer.valueOf( ghi.getNumber() );
}
public GitHubIssue getCurrentIssue()
{
if ( null == ghc )
{
return null;
}
return ghc.getIssue( issueIndex, this );
}
@Override
public Object getValue()
{
GitHubIssue ghi = getCurrentIssue();
if ( null == ghi )
{
return null;
}
return Integer.valueOf( ghi.getNumber() );
}
@Override
public void removeChangeListener( ChangeListener arg0 )
{
throw new RuntimeException(); // not used
}
public void setFilter( Label filter )
{
filterMode = filter;
}
@Override
public void setValue( Object issueNumberIntegerObject )
{
Integer issueNumber = (Integer)issueNumberIntegerObject;
if ( null == issueNumber )
{
return;
}
issueIndex = ghc.getIndexOfNumber( issueNumber.intValue() );
// new issue may have different world index current
worldModel.setValue( worldModel.getValue() );
changeListener.stateChanged( new ChangeEvent( this ) );
}
}
class WorldSpinnerModel implements SpinnerModel
{
private ChangeListener changeListener;
@Override
public void addChangeListener( ChangeListener l )
{
changeListener = l;
}
@Override
public Object getNextValue()
{
GitHubIssue ghi = issueModel.getCurrentIssue();
if ( null == ghi )
{
return null;
}
int value = ghi.getCurrentWorldIndex() - 1;
return Integer.valueOf( value < 0 ? 0 : value );
}
@Override
public Object getPreviousValue()
{
GitHubIssue ghi = issueModel.getCurrentIssue();
if ( null == ghi )
{
return null;
}
int value = ghi.getCurrentWorldIndex() + 1;
// No bounds checking needed here, setValue's is OK.
return Integer.valueOf( value );
}
@Override
public Object getValue()
{
GitHubIssue ghi = issueModel.getCurrentIssue();
if ( null == ghi )
{
return null;
}
return Integer.valueOf( ghi.getCurrentWorldIndex() );
}
@Override
public void removeChangeListener( ChangeListener l )
{
throw new RuntimeException(); // Not used
}
@Override
public void setValue( Object WorldIndexIntegerObject )
{
Integer worldIndexInteger = (Integer)WorldIndexIntegerObject;
if ( null == worldIndexInteger )
{
return;
}
GitHubIssue ghi = issueModel.getCurrentIssue();
if ( null == ghi )
{
return;
}
if ( ghi.setCurrentWorldIndex( (int)worldIndexInteger ) )
{
changeListener.stateChanged( new ChangeEvent( this ) );
}
}
}
protected GitHubIssueDialog( Frame frame )
{
super( frame, true ); // arg2 sets modal
setTitle( t( "Retrieve level from the GitHub Rabbit Escape issue pages." ) );
GridBagConstraints gbc = new GridBagConstraints();
JPanel pane = new JPanel( new GridBagLayout() );
issueModel = new IssueSpinnerModel();
JSpinner issueSpinner = new JSpinner( issueModel );
issueSpinner.setPreferredSize( new Dimension( 60, 30 ) );
issueSpinner.addChangeListener( this );
worldModel = new WorldSpinnerModel();
JSpinner worldSpinner = new JSpinner( worldModel );
worldSpinner.setPreferredSize( new Dimension( 60, 30 ) );
worldSpinner.addChangeListener( this );
JRadioButton levelFilterButton = new JRadioButton( t( "Level" ) );
JRadioButton bugFilterButton = new JRadioButton( t( "Bug" ) );
JRadioButton allFilterButton = new JRadioButton( t( "All" ) );
ButtonGroup filterButtons = new ButtonGroup();
filterButtons.add( levelFilterButton );
filterButtons.add( bugFilterButton );
filterButtons.add( allFilterButton );
allFilterButton.doClick(); // set preselected filter
JButton fetchFollowButton = new JButton( t( "Fetch followup comments" ) );
JPanel spacerPanel = new JPanel();
// Ater packing, this gives the dialog it's width
spacerPanel.setPreferredSize( new Dimension( 500, 2 ) );
JButton okButton = new JButton( t( "OK" ) );
JButton cancelButton = new JButton( t( "Cancel" ) );
statusBox.setEditable( false );
issueNameBox.setEditable( false );
issueTextBox.setEditable( false );
JScrollPane issueTextScrollPane = new JScrollPane( issueTextBox );
issueTextScrollPane.setPreferredSize( new Dimension( 300, 300 ) );
issueTextBox.setBorder( BorderFactory.createLineBorder( Color.GRAY ) );
issueWorldBox.setEditable( false );
issueWorldBox.setBorder( BorderFactory.createLineBorder( Color.GRAY ) );
issueWorldBox.setFont( new Font( "monospaced", Font.PLAIN, 12 ) );
// After packing, this gives the dialog it's height
issueWorldBox.setPreferredSize( new Dimension( 2, 500 ) );
gbc.fill = GridBagConstraints.BOTH;
gbc.gridx = 0;
gbc.gridy = 0;
gbc.gridheight = 2;
pane.add( issueSpinner, gbc );
gbc.gridheight = 1;
gbc.gridx = 0;
gbc.gridy = 2;
pane.add( worldSpinner, gbc );
gbc.gridx = 1;
gbc.gridy = 0;
pane.add( levelFilterButton, gbc );
gbc.gridx = 1;
gbc.gridy = 1;
pane.add( bugFilterButton, gbc );
gbc.gridx = 1;
gbc.gridy = 2;
pane.add( allFilterButton, gbc );
gbc.gridx = 2;
gbc.gridy = 0;
pane.add( fetchFollowButton, gbc );
gbc.gridwidth = 3;
gbc.gridx = 3;
gbc.gridy = 0;
pane.add( statusBox, gbc );
gbc.gridwidth = 1;
gbc.gridwidth = 3;
gbc.gridx = 3;
gbc.gridy = 1;
pane.add( issueNameBox, gbc );
gbc.gridwidth = 1;
gbc.gridx = 3;
gbc.gridy = 2;
pane.add( spacerPanel, gbc );
gbc.fill = GridBagConstraints.NONE;
gbc.gridx = 4;
gbc.gridy = 2;
pane.add( okButton, gbc );
gbc.gridx = 5;
gbc.gridy = 2;
pane.add( cancelButton, gbc );
gbc.fill = GridBagConstraints.BOTH;
gbc.gridwidth = 3;
gbc.gridx = 0;
gbc.gridy = 3;
pane.add( issueTextScrollPane, gbc );
gbc.gridwidth = 1;
gbc.gridwidth = 3;
gbc.gridheight = 1;
gbc.gridx = 3;
gbc.gridy = 3;
pane.add( issueWorldBox, gbc );
gbc.gridwidth = 1;
gbc.gridheight = 1;
setLayout( new GridBagLayout() );
gbc.gridx = 0;
gbc.gridy = 0;
gbc.fill = GridBagConstraints.BOTH;
this.add( pane, gbc );
levelFilterButton.addActionListener( new ActionListener()
{
@Override
public void actionPerformed( ActionEvent e )
{
issueModel.setFilter( Label.LEVEL );
}
} );
bugFilterButton.addActionListener( new ActionListener()
{
@Override
public void actionPerformed( ActionEvent e )
{
issueModel.setFilter( Label.BUG );
}
} );
allFilterButton.addActionListener( new ActionListener()
{
@Override
public void actionPerformed( ActionEvent e )
{
issueModel.setFilter( Label.ALL );
}
} );
fetchFollowButton.addActionListener( new ActionListener()
{
@Override
public void actionPerformed( ActionEvent e )
{
fetchFollowupComments();
}
} );
okButton.addActionListener( new ActionListener()
{
@Override
public void actionPerformed( ActionEvent e )
{
choseWorld = true;
setVisible( false );
}
} );
cancelButton.addActionListener( new ActionListener()
{
@Override
public void actionPerformed( ActionEvent e )
{
setVisible( false );
}
} );
setResizable( false ); // / @TODO maybe make it resize so the
// worldTextBox expands
pack();
setLocationRelativeTo( frame );
stateChanged( null ); // show initial values for GUI items
ghc = new GitHubClient();
final InitFetchWorker<Void, Void> ifw = new InitFetchWorker<Void, Void>();
ifw.execute();
DotTic dt = new DotTic( (SwingWorker<Void, Void>)ifw );
dt.start();
setVisible( true );
}
public String getWorld()
{
GitHubIssue ghi = issueModel.getCurrentIssue();
if ( !choseWorld || ( null == ghi ) ) // user cancelled or maybe no inet
{
return null;
}
String wrappedWorld = ghi.getCurrentWorld();
if ( null == wrappedWorld )
{
return null;
}
return wrappedWorld;
}
public String generateFilename()
{
String title = issueModel.getCurrentIssue().getTitle();
if ( null == title )
{
title = t( "Issue has no title" );
}
title = title.replaceAll( "\\W", "" );
return title;
}
/**
* @brief github only sends the opening comment in te first instance.
*/
public void fetchFollowupComments()
{
GitHubIssue ghi = issueModel.getCurrentIssue();
if ( null == ghi )
{
return;
}
final FetchCommentsWorker<Void, Void> fcw = new FetchCommentsWorker<Void, Void>(
ghi );
fcw.execute();
statusBox.setText( t( "Fetching followup comments for" ) + " #"
+ ghi.getNumber()
+ "." );
// Let dt repaint statusBox
DotTic dt = new DotTic( fcw );
dt.start();
}
/**
* @brief Listens to the issue choosing spinner and world choosing spinner
*/
@Override
public void stateChanged( ChangeEvent e )
{
GitHubIssue ghi = issueModel.getCurrentIssue();
if ( null == ghi )
{
return;
}
issueNameBox.setText( ghi.getTitle() );
issueNameBox.repaint();
String worldText = ghi.getCurrentWorld(); // /
if ( null == worldText ) // some issues have no worlds
{
issueWorldBox.setText( "" );
}
{
issueWorldBox.setText( worldText );
}
issueWorldBox.repaint();
issueTextBox.setText( ghi.getBody() );
issueTextBox.repaint();
}
private final class DotTic implements ActionListener
{
private Timer timer;
private SwingWorker<Void, Void> worker;
public DotTic( SwingWorker<Void, Void> ifw )
{
timer = new Timer( 800, this );
worker = ifw;
}
public void start()
{
timer.start();
}
@Override
public void actionPerformed( ActionEvent event )
{
statusBox.setText( statusBox.getText() + "." );
statusBox.repaint();
if ( worker.isDone() )
{
timer.stop();
if ( !"".equals( ghc.getError() ) )
{
statusBox.setText( ghc.getError() );
}
else
{
statusBox.setText( statusBox.getText() + t( "Done." ) );
}
statusBox.repaint();
stateChanged( null ); // update info in dialog with new info
// from github
}
}
}
private final class InitFetchWorker<T, U> extends SwingWorker<Void, Void>
{
@Override
protected Void doInBackground() throws Exception
{
ghc.initialise();
return null;
}
}
private final class IssuePageFetchWorker<T, U>
extends
SwingWorker<Void, Void>
{
String u;
String rp;
GitHubPageFetchNotifier ghpfn;
public IssuePageFetchWorker( String url, String requestProperty,
GitHubPageFetchNotifier notifier )
{
u = url;
rp = requestProperty;
ghpfn = notifier;
}
@Override
protected Void doInBackground() throws Exception
{
String answer = HttpTools.get( u, rp );
ghpfn.setPage( answer );
return null;
}
}
private final class FetchCommentsWorker<T, U>
extends
SwingWorker<Void, Void>
{
private GitHubIssue ghi;
public FetchCommentsWorker( GitHubIssue i )
{
ghi = i;
}
@Override
protected Void doInBackground() throws Exception
{
ghi.fetchComments( ghc );
return null;
}
}
}