/*
* Copyright 2010 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.gradle.gradleplugin.userinterface.swing.generic;
import org.gradle.BuildResult;
import org.gradle.StartParameter;
import org.gradle.foundation.ipc.gradle.ExecuteGradleCommandServerProtocol;
import org.gradle.foundation.output.FileLinkDefinitionLord;
import org.gradle.gradleplugin.foundation.GradlePluginLord;
import org.gradle.gradleplugin.foundation.request.Request;
import org.gradle.gradleplugin.userinterface.AlternateUIInteraction;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.JButton;
import javax.swing.AbstractAction;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Font;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.ActionEvent;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.io.File;
/**
* This is a panel that displays the results of executing a gradle command. It shows gradle's output as well as
* progress.
*
* @author mhunsicker
*/
public class OutputPanel extends JPanel implements ExecuteGradleCommandServerProtocol.ExecutionInteraction {
private OutputPanelParent parent;
private AlternateUIInteraction alternateUIInteraction;
private JPanel gradleOutputTextPanel;
private OutputTextPane gradleOutputTextPane;
private JPanel progressPanel;
private JLabel progressLabel;
private JProgressBar progressBar;
private JPanel statusPanel;
private JLabel statusLabel;
private JButton executeAgainButton;
private JLabel forceShowOutputButtonLabel; //a label that acts like a button
private boolean isBusy; //is this actively showing output?
private boolean isPending; //is this waitin got show output?
private boolean isPinned; //keeps this panel open and disallows it from being re-used.
private boolean showProgress = true;
private boolean onlyShowOutputOnErrors;
private Request request;
public interface OutputPanelParent {
public void removeOutputPanel( OutputPanel outputPanel );
void reportExecuteFinished( Request request, boolean wasSuccessful );
void executeAgain( Request request, OutputPanel outputPanel );
public FileLinkDefinitionLord getFileLinkDefinitionLord();
}
public OutputPanel( OutputPanelParent parent, AlternateUIInteraction alternateUIInteraction ) {
this.parent = parent;
this.alternateUIInteraction = alternateUIInteraction;
}
/**
Call this after initializing this, but after setting any additional swing properties (actually, just the font for now).
I really only added this as an optimization. Since we'll always be setting the font, I didn't want the various style
objects created only to be thrown away and re-created. This way, you can set the font before we create the styles.
*/
public void initialize() {
setupUI();
}
/**
* This is called whenever a new request is made. It associates this request with this output panel.
*
* @param request
* @param onlyShowOutputOnErrors
*/
public void setRequest( Request request, boolean onlyShowOutputOnErrors )
{
this.request = request;
if( request.forceOutputToBeShown() ) {
setOnlyShowOutputOnErrors(false);
}
else {
setOnlyShowOutputOnErrors( onlyShowOutputOnErrors );
}
//set this to indeterminate until we figure out how many tasks to execute.
progressBar.setIndeterminate( true );
progressBar.setStringPainted( false ); //And don't show '0%' in the mean time.
setPending(true);
showProgress(true); //make sure the progress is shown. It may have been turned off if we're reusing this component
appendGradleOutput( getPrefixText() );
}
/**
* Returns a string stating the command we're currently executing. This is placed at the beginning of
* the output text. This is called when we start and when the command is finished (where we replace all
* of our text with the total output)
*/
private String getPrefixText() {return "Executing command: \"" + request.getFullCommandLine() + "\"\n";}
public boolean isPinned() {
return isPinned;
}
public void setPinned(boolean pinned) {
isPinned = pinned;
}
public boolean isBusy() {
return isBusy;
}
protected void setBusy(boolean busy) {
isBusy = busy;
} //this should be the only way to isBusy.
public boolean isPending() {
return isPending;
}
private void setPending(boolean pending) {
isPending = pending;
if (isPending) {
statusLabel.setText("Waiting to execute");
}
progressBar.setVisible(!isPending);
}
public Request getRequest() {
return request;
}
private void setupUI() {
setLayout(new BorderLayout());
add(createInfoPanel(), BorderLayout.NORTH);
add(createGradleOutputPanel(), BorderLayout.CENTER);
}
private Component createGradleOutputPanel() {
gradleOutputTextPanel = new JPanel(new BorderLayout());
gradleOutputTextPane = new OutputTextPane( new OutputTextPane.Interaction()
{
public void fileClicked( File file, int line )
{
alternateUIInteraction.openFile( file, line );
}
}, alternateUIInteraction.doesSupportEditingOpeningFiles(), getFont(), parent.getFileLinkDefinitionLord() );
gradleOutputTextPanel.add( gradleOutputTextPane.asComponent(), BorderLayout.CENTER);
return gradleOutputTextPanel;
}
private Component createInfoPanel() {
JPanel panel = new JPanel();
panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
panel.add(createStatusPanel());
panel.add(createProgressPanel());
return panel;
}
private Component createProgressPanel() {
progressPanel = new JPanel(new BorderLayout());
progressLabel = new JLabel("Progress");
progressBar = new JProgressBar();
progressBar.setStringPainted(true);
progressPanel.add(progressBar, BorderLayout.NORTH);
progressPanel.add(progressLabel, BorderLayout.SOUTH);
progressPanel.setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2));
progressPanel.setVisible(false);
return progressPanel;
}
private Component createStatusPanel() {
statusPanel = new JPanel();
statusPanel.setLayout(new BoxLayout(statusPanel, BoxLayout.X_AXIS));
statusLabel = new JLabel();
executeAgainButton = Utility.createButton( OutputPanel.class, "/org/gradle/gradleplugin/userinterface/swing/generic/tabs/execute.png", "Execute Again", new AbstractAction()
{
public void actionPerformed( ActionEvent e )
{
parent.executeAgain( request, OutputPanel.this );
}
});
executeAgainButton.setVisible( false );
//this button is only shown when the output is hidden
forceShowOutputButtonLabel = new JLabel("Show Output");
forceShowOutputButtonLabel.addMouseListener(new MouseAdapter() {
public void mouseClicked(MouseEvent e) {
forciblyShowOutput();
}
public void mouseEntered(MouseEvent e) {
forceShowOutputButtonLabel.setForeground(UIManager.getColor("textHighlightText"));
}
public void mouseExited(MouseEvent e) {
forceShowOutputButtonLabel.setForeground(UIManager.getColor("Label.foreground"));
}
});
statusPanel.add( executeAgainButton );
statusPanel.add( Box.createHorizontalStrut( 2 ) );
statusPanel.add(statusLabel);
statusPanel.add(Box.createHorizontalGlue());
statusPanel.add(forceShowOutputButtonLabel);
statusPanel.setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2));
return statusPanel;
}
/**
* Call this if you're going to reuse this. it resets its output.
*/
public void reset() {
executeAgainButton.setVisible( false );
statusLabel.setText("");
statusLabel.setForeground(UIManager.getColor("Label.foreground"));
gradleOutputTextPane.setText("");
progressLabel.setText("");
}
/**
* Call this to append text to the gradle output field. We'll also move the caret to the end.
*
* @param text the text to add
*/
private void appendGradleOutput(final String text) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
gradleOutputTextPane.appendText( text );
}
});
}
private void setProgress( final String text, final float percentComplete) {
SwingUtilities.invokeLater( new Runnable()
{
public void run()
{
progressBar.setValue((int) percentComplete);
progressLabel.setText(text);
}
} );
}
/**
Notification that execution of a task or tasks has been started.
*/
public void reportExecutionStarted() {
SwingUtilities.invokeLater( new Runnable()
{
public void run()
{
setPending(false);
setBusy(true);
setProgress("Starting", 0);
if (showProgress) {
progressPanel.setVisible(true);
}
statusLabel.setText("Executing");
//give the user the option to override this.
forceShowOutputButtonLabel.setVisible(onlyShowOutputOnErrors);
}
} );
}
/**
* Notification of the total number of tasks that will be executed. This is called after reportExecutionStarted and before any tasks are executed.
*
* @param size the total number of tasks.
*/
public void reportNumberOfTasksToExecute( final int size )
{ //if we only have a single task, then the intire process will be indeterminately long (it'll just from 0 to 100)
SwingUtilities.invokeLater( new Runnable()
{
public void run()
{
boolean isIndeterminate = size == 1;
progressBar.setIndeterminate( isIndeterminate );
progressBar.setStringPainted( !isIndeterminate );
}
} );
}
/**
* Notification that execution of all tasks has completed. This is only called once at the end.
*
* @param wasSuccessful whether or not gradle encountered errors.
* @param buildResult contains more detailed information about the result of a build.
* @param output the text that gradle produced. May contain error information, but is usually just status.
*/
public void reportExecutionFinished(boolean wasSuccessful, BuildResult buildResult, String output) {
reportExecutionFinished(wasSuccessful, output, buildResult.getFailure());
}
/**
* Notification that execution of a task has completed. This is the task you initiated and not for each subtask or dependent task.
*
* @param wasSuccessful whether or not gradle encountered errors.
* @param output the text that gradle produced. May contain error information, but is usually just status.
*
* @param throwable
*/
public void reportExecutionFinished( final boolean wasSuccessful, final String output, final Throwable throwable) {
SwingUtilities.invokeLater( new Runnable()
{
public void run()
{
setPending(false); //this can be called before we actually get a start message if it fails early. This clears the pending flag so we know we can reuse it.
setBusy(false);
progressPanel.setVisible(false);
//Make the output equal to all of our output. There are some timing issues where we don't get the last live output from gradle.
//This 'output' is the entire text. This way we always get all output.
String newText = getPrefixText() + output;
gradleOutputTextPane.setText( newText );
//show the user the time we finished this.
SimpleDateFormat formatter = new SimpleDateFormat("h:mm:ss aa");
String formattedTime = formatter.format(Calendar.getInstance().getTime());
if (wasSuccessful) {
statusLabel.setText("Completed successfully at " + formattedTime);
appendGradleOutput("\nCompleted Successfully");
} else {
statusLabel.setText("Completed with errors at " + formattedTime);
statusLabel.setForeground(Color.red.darker());
//since errors occurred, show the output. If onlyShowOutputOnErrors is false, this textPanel will already be visible.
gradleOutputTextPanel.setVisible(true);
}
executeAgainButton.setVisible( true );
appendThrowable(throwable);
//lastly, if the text output is not visible, make the 'show output' button visible
forceShowOutputButtonLabel.setVisible(!gradleOutputTextPanel.isVisible());
parent.reportExecuteFinished( request, wasSuccessful );
}
} );
}
private void appendThrowable(Throwable throwable) {
if (throwable != null) {
String output = GradlePluginLord.getGradleExceptionMessage(throwable, StartParameter.ShowStacktrace.ALWAYS_FULL);
appendGradleOutput(output);
}
}
/**
* Notification that a single task has completed. Note: the task you kicked off probably executes other tasks.
*
* @param currentTaskName the task being executed
* @param percentComplete the percent complete of all the tasks that make up the task you requested.
*/
public void reportTaskStarted(String currentTaskName, float percentComplete) {
setProgress(currentTaskName, percentComplete);
}
public void reportTaskComplete(String currentTaskName, float percentComplete) {
setProgress(currentTaskName, percentComplete);
}
public void reportFatalError(String message) {
appendGradleOutput('\n' + message + "\n\nFailed.\n");
}
/**
* Report real-time output from gradle and its subsystems (such as ant).
*
* @param output a single line of text to show.
* @author mhunsicker
*/
public void reportLiveOutput(String output) {
appendGradleOutput(output);
}
/**
* Determines if this panel is ready to be reused. Currently, if its not busy or pinned, it can be reused.
*
* @author mhunsicker
*/
public boolean canBeReusedNow() {
return !isPending && !isBusy && !isPinned;
}
/**
* Call this to show progress. Some tasks have no useful progress, so this allows you to disable it.
*
* @param showProgress true to show a progress bar, false not to.
*/
private void showProgress(boolean showProgress) {
this.showProgress = showProgress;
progressPanel.setVisible(showProgress);
}
/**
* This overrides the onlyShowOutputOnErrors
*/
private void forciblyShowOutput() {
gradleOutputTextPanel.setVisible(true);
forceShowOutputButtonLabel.setVisible(false);
}
public void setOnlyShowOutputOnErrors(boolean value) {
this.onlyShowOutputOnErrors = value;
gradleOutputTextPanel.setVisible(!value);
}
public boolean getOnlyShowOutputOnErrors() {
return onlyShowOutputOnErrors;
}
public boolean close() {
if (request != null) //if we have a request, we can only close if it allows us to.
{
if (!request.cancel()) {
return false;
}
}
parent.removeOutputPanel( this );
setPinned(false); //unpin it when it is removed
return true;
}
/**
Sets the font for this component.
@param font the desired <code>Font</code> for this component
@beaninfo preferred: true
bound: true
attribute: visualUpdate true
description: The font for the component.
@see Component#getFont
*/
@Override
public void setFont( Font font )
{
super.setFont( font );
if( gradleOutputTextPane != null ) //this gets called by internal Swing APIs, so we may not have this yet.
{
gradleOutputTextPane.setFont( font );
}
}
}