package greenfoot.guifx.export;
import bluej.Config;
import bluej.pkgmgr.Project;
import bluej.utility.Debug;
import bluej.utility.FXWorker;
import bluej.utility.Utility;
import bluej.utility.javafx.JavaFXUtil;
import static greenfoot.export.Exporter.ExportFunction;
import greenfoot.export.mygame.ExistingScenarioChecker;
import greenfoot.export.mygame.ExportInfo;
import greenfoot.export.mygame.MyGameClient;
import greenfoot.export.mygame.ScenarioInfo;
import greenfoot.export.ScenarioSaver;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javafx.beans.binding.BooleanBinding;
import javafx.geometry.HPos;
import javafx.geometry.Pos;
import javafx.scene.control.CheckBox;
import javafx.scene.control.Hyperlink;
import javafx.scene.control.Label;
import javafx.scene.control.PasswordField;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.image.Image;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.ColumnConstraints;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import org.apache.http.conn.ConnectTimeoutException;
import threadchecker.OnThread;
import threadchecker.Tag;
import javax.swing.*;
| Pane used for exporting to Greenfoot Gallery
|
| @author Michael Kolling
| @author Poul Henriksen
| @author Amjad Altadmri
|
@OnThread(Tag.FXPlatform)
public class ExportPublishTab
extends ExportTab{
private static final int IMAGE_WIDTH = 120;
private static final int IMAGE_HEIGHT = 70;
private static final String serverURL = ensureTrailingSlash(
Config.getPropString("greenfoot.gameserver.address", "http://www.greenfoot.org/"));
private static final String createAccountUrl =
Config.getPropString("greenfoot.gameserver.createAccount.address",
"http://www.greenfoot.org/users/new");
private static final String serverName =
Config.getPropString("greenfoot.gameserver.name", "Greenfoot Gallery");
private static final String helpLine = Config.getString("export.publish.help") + " " + serverName;
private static final String WITH_SOURCE_TAG = "with-source";
private final Label emptyLabel = new Label();
private final Label titleLabel = new Label(Config.getString("export.publish.title"));
private final Label updateLabel = new Label(Config.getString("export.publish.update"));
private final Label shortDescriptionLabel = new Label(Config.getString("export.publish.shortDescription"));
private final Label longDescriptionLabel = new Label(Config.getString("export.publish.longDescription"));
private TextField titleField;
private TextField shortDescriptionField;
private TextArea longDescriptionArea;
private TextArea updateArea;
private TextField urlField;
private TextField userNameField;
private PasswordField passwordField;
private ImageEditPane imagePane;
private CheckBox includeSource;
private CheckBox keepScenarioScreenshot;
private GridPane titleAndDescPane;
private ScrollPane updatePane;
private ScrollPane description;
private CheckBox[] popTags = new CheckBox[7];
private TextArea tagArea;
private Project project;
private boolean firstActivation = true;
private String publishedUserName;
private ExistingScenarioChecker scenarioChecker;
private boolean update = false;
private final ExportDialog exportDialog;
private final ScenarioSaver scenarioSaver;
private BooleanBinding userNameValidity;
private BooleanBinding passwordValidity;
private BooleanBinding titleValidity;
| Creates a new instance of ExportPublishTab
|
| @param project The project that will be shared.
| @param exportDialog The export dialog containing this tab.
| @param scenarioSaver The listener that will enable us to save the scenario when exporting.
| @param scenarioInfo The previously stored scenario info in the properties file.
|
public ExportPublishTab(Project project, ExportDialog exportDialog,
ScenarioSaver scenarioSaver, ScenarioInfo scenarioInfo)
{
super(scenarioInfo, "export-publish.png");
this.project = project;
this.exportDialog = exportDialog;
this.scenarioSaver = scenarioSaver;
buildContentPane();
applySharedStyle();
getContent().getStyleClass().add("export-publish-tab");
userNameValidity = userNameField.textProperty().isNotEmpty();
passwordValidity = passwordField.textProperty().isNotEmpty();
titleValidity = titleField.textProperty().isNotEmpty();
validProperty.bind(userNameValidity.and(passwordValidity).and(titleValidity));
JavaFXUtil.addChangeListenerPlatform(selectedProperty(), selected -> activated());
}
@Override
public ExportFunction getFunction()
{
return ExportFunction.PUBLISH;
}
| Get the image that is to be used as icon for this scenario.
|
| @return The image, or null if it couldn't be created.
|
public Image getImage()
{
return imagePane.getImage();
}
| Set the screenshot image.
|
| @param snapShot The snapshot to be put in the image pane.
|
public void setImage(Image snapShot)
{
imagePane.setImage(snapShot);
}
| Get the scenario title, specified in the title field.
|
public String getTitle()
{
if (titleField != null)
{
return titleField.getText();
}
return null;
}
| Return the short description string.
|
public String getShortDescription()
{
return shortDescriptionField.getText();
}
| Return the description string.
|
public String getLongDescription()
{
return longDescriptionArea.getText();
}
| Return the URL.
|
public String getURL()
{
return urlField.getText();
}
| Return the user name.
|
public String getUserName()
{
return userNameField.getText();
}
| Return the changes to update string (if applicable)
|
private String getUpdateDescription()
{
return updateArea != null ? updateArea.getText() : null;
}
| Return the password.
|
public String getPassword()
{
return passwordField.getText();
}
| True if the source code should be included.
|
public boolean isIncludeSource()
{
return includeSource.isSelected();
}
| True if the screenshot should *not* be overwritten; false if it should
|
private boolean isKeepSavedScreenshot()
{
return update && keepScenarioScreenshot.isSelected();
}
| Takes a list of tags select the popular ones placed as checkboxes,
| and format the rest in separate lines in the tagTextArea.
|
| @param tags a list of String tags.
|
private void processTags(List<String> tags)
{
for (CheckBox popTag : popTags)
{
if (tags.contains(popTag.getText()))
{
popTag.setSelected(true);
}
}
String newTags = tags.stream()
.filter(tag -> !WITH_SOURCE_TAG.equals(tag))
.filter(tag -> !getPopTagsTexts().contains(tag))
.collect(Collectors.joining(System.getProperty("line.separator")));
tagArea.setText(newTags);
}
| Returns a list of the text in the pop tags checkboxes.
|
private List getPopTagsTexts()
{
return Stream.of(popTags).map(CheckBox::getText).collect(Collectors.toList());
}
| Build the contents' pane.
|
private void buildContentPane()
{
Label publishInfoLabel = new Label(Config.getString("export.publish.info") + " " + serverName);
publishInfoLabel.getStyleClass().add("intro-label");
BorderPane.setAlignment(publishInfoLabel, Pos.CENTER);
BorderPane infoPane = new BorderPane(createScenarioPane(), publishInfoLabel,
getTagDisplay(), null, null);
infoPane.getStyleClass().add("info-pane");
setContent(new VBox(getHelpBox(), infoPane, getLoginPane()));
}
| Creates a login pane with a username and password and a create account option
| @return Login pane Component
|
private Pane getLoginPane()
{
Label loginLabel = new Label(Config.getString("export.publish.login"));
loginLabel.getStyleClass().add("intro-label");
Label usernameLabel = new Label(Config.getString("export.publish.username"));
userNameField = new TextField();
userNameField.setPrefColumnCount(10);
JavaFXUtil.addChangeListenerPlatform(userNameField.focusedProperty(), focused -> {
if (!focused)
{
checkForExistingScenario();
}
});
Label passwordLabel = new Label(Config.getString("export.publish.password"));
passwordField = new PasswordField();
passwordField.setPrefColumnCount(10);
Hyperlink createAccountLabel = new Hyperlink(Config.getString("export.publish.createAccount"));
createAccountLabel.getStyleClass().add("create-account-label");
createAccountLabel.setOnAction(event -> SwingUtilities.invokeLater(() -> Utility.openWebBrowser(createAccountUrl)));
HBox loginPane = new HBox(loginLabel,
usernameLabel, userNameField,
passwordLabel, passwordField,
createAccountLabel);
loginPane.getStyleClass().add("login-pane");
return loginPane;
}
| Build a help box with a link to appropriate help
| @return help box
|
private Pane getHelpBox()
{
Hyperlink serverLink = new Hyperlink(serverURL);
serverLink.setOnAction(event -> SwingUtilities.invokeLater(() -> Utility.openWebBrowser(serverURL)));
HBox helpBox = new HBox(new Label(helpLine + " ("), serverLink, new Label(")"));
helpBox.getStyleClass().add("help-box");
return helpBox;
}
| Set the tags in the UI from the given list (null if the server couldn't be contacted or
| didn't respond as expected).
|
| <p>Should be called from event thread
|
private void setPopularTags(List<String> tags)
{
if (tags == null)
{
popTags[0].setText("Unavailable");
for (int i = 1; i < popTags.length; i++)
{
popTags[i].setText("");
}
return;
}
int minLength = popTags.length < tags.size() ? popTags.length : tags.size();
for (int i = 0; i < minLength; i++)
{
CheckBox checkBox = popTags[i];
checkBox.setText(tags.get(i));
checkBox.setDisable(false);
processTags(getTags());
}
for (int i = minLength; i < popTags.length; i++)
{
popTags[i].setText("");
}
}
| Returns a list of the tags that the user chose for this scenario.
|
public List getTags()
{
ArrayList<String> tagList = Arrays.stream(popTags)
.filter(CheckBox::isSelected)
.map(CheckBox::getText)
.collect(Collectors.toCollection(ArrayList::new));
tagList.addAll(Arrays.asList(tagArea.getText().split("\\s+")));
if (isIncludeSource() && !tagList.contains(WITH_SOURCE_TAG))
{
tagList.add(WITH_SOURCE_TAG);
}
else if (!isIncludeSource())
{
tagList.remove(WITH_SOURCE_TAG);
}
return tagList;
}
| Loads details already stored for this scenario at previous publish (if they exist).
|
private void loadStoredScenarioInfo()
{
titleField.setText(scenarioInfo.getTitle());
shortDescriptionField.setText(scenarioInfo.getShortDescription());
longDescriptionArea.setText(scenarioInfo.getLongDescription());
urlField.setText(scenarioInfo.getUrl());
processTags(scenarioInfo.getTags());
lockScenario.setSelected(scenarioInfo.isLocked());
includeSource.setSelected(scenarioInfo.isIncludeSource());
setUpdate(!scenarioInfo.getTitle().isEmpty());
}
@Override
protected void updateInfoFromFields()
{
scenarioInfo.setTitle(getTitle());
scenarioInfo.setShortDescription(getShortDescription());
scenarioInfo.setLongDescription(getLongDescription());
scenarioInfo.setUrl(getURL());
scenarioInfo.setTags(getTags());
scenarioInfo.setLocked(isLockScenario());
scenarioInfo.setIncludeSource(isIncludeSource());
}
@Override
protected ExportInfo getExportInfo()
{
ExportInfo info = new ExportInfo(scenarioInfo);
info.setUpdateDescription(getUpdateDescription());
info.setUserName(getUserName());
info.setPassword(getPassword());
info.setImage(getImage());
info.setKeepSavedScreenshot(isKeepSavedScreenshot());
info.setUpdate(isUpdate());
return info;
}
private void checkForExistingScenario()
{
String userName = getUserName();
String title = getTitle();
if (userName == null || userName.equals(""))
{
return;
}
if (title == null || title.equals(""))
{
return;
}
if (scenarioChecker == null)
{
scenarioChecker = new ExistingScenarioChecker() {
@Override
public void scenarioExistenceCheckFailed(Exception reason)
{
}
@Override
public void scenarioExistenceChecked(ScenarioInfo info)
{
setUpdate(info != null);
}
};
}
scenarioChecker.startScenarioExistenceCheck(serverURL, userName, title);
}
| The first time this tab is activated we fetch the popular tags from the
| server (if possible).
|
| <p>And we load previously used values if they are stored.
|
private void activated()
{
if (firstActivation)
{
firstActivation = false;
userNameField.setText(Config.getPropString("publish.username", ""));
loadStoredScenarioInfo();
checkForExistingScenario();
FXWorker commonTagsLoader = new FXWorker()
{
@SuppressWarnings("unchecked")
@Override
public void finished()
{
setPopularTags((List<String>) getValue());
}
@Override
@OnThread(Tag.FXPlatform)
public void abort()
{
}
@Override
public Object construct()
{
MyGameClient client = new MyGameClient(null);
List<String> tags = null;
try {
String hostAddress = serverURL;
if (!hostAddress.endsWith("/"))
{
hostAddress += "/";
}
tags = client.getCommonTags(hostAddress, popTags.length + 1);
if (tags.contains(WITH_SOURCE_TAG))
{
tags.remove(WITH_SOURCE_TAG);
}
else if (!tags.isEmpty())
{
tags.remove(tags.size() - 1);
}
}
catch (ConnectTimeoutException ignored) {
}
catch (IOException e)
{
Debug.reportError("Error while publishing scenario", e);
}
return tags;
}
};
commonTagsLoader.start();
}
setExportButtonText();
}
| Sets the dialog export button's text to either update or share.
|
private void setExportButtonText()
{
exportDialog.setExportButtonText(
Config.getString(update ? "export.dialog.update" : "export.dialog.share"));
}
@Override
public boolean prePublish()
{
super.prePublish();
publishedUserName = getUserName();
return true;
}
@Override
public void postPublish(boolean success)
{
if (success)
{
scenarioSaver.doSave();
Config.putPropString("publish.username", publishedUserName);
setUpdate(true);
}
}
| Make sure a host name ends with a slash.
|
private static String ensureTrailingSlash(String hostname)
{
if (hostname.endsWith("/"))
{
return hostname;
}
return hostname + "/";
}
| Creates the scenario information display including information
| such as title, description, url. For an update (update = true),
| the displayed options are slightly different.
|
| @return The pane containing main scenario controls.
|
private Pane createScenarioPane()
{
titleAndDescPane = new GridPane();
titleAndDescPane.getStyleClass().add("title-desc-pane");
ColumnConstraints column1 = new ColumnConstraints();
column1.setPrefWidth(130);
column1.setHalignment(HPos.RIGHT);
ColumnConstraints column2 = new ColumnConstraints();
column2.setPrefWidth(220);
column2.setHgrow(Priority.ALWAYS);
column2.setHalignment(HPos.CENTER);
titleAndDescPane.getColumnConstraints().addAll(column1, column2);
VBox iconTextPane = new VBox(new Label(Config.getString("export.publish.image1")),
new Label(Config.getString("export.publish.image2")));
iconTextPane.getStyleClass().add("icon-text-pane");
imagePane = new ImageEditPane(IMAGE_WIDTH, IMAGE_HEIGHT);
titleAndDescPane.addRow(0, iconTextPane, imagePane);
keepScenarioScreenshot = new CheckBox(Config.getString("export.snapshot.label"));
keepScenarioScreenshot.setSelected(true);
JavaFXUtil.addChangeListenerPlatform(keepScenarioScreenshot.selectedProperty(),
selected -> imagePane.enableImageEditPanel(!selected));
titleField = new TextField(getTitle() != null ? getTitle() : project.getProjectName());
JavaFXUtil.addChangeListenerPlatform(titleField.focusedProperty(), focused -> {
if (!focused)
{
checkForExistingScenario();
}
});
updateLabel.setAlignment(Pos.TOP_LEFT);
updateArea = new TextArea();
updateArea.setPrefRowCount(5);
updateArea.setWrapText(true);
updatePane = new ScrollPane(updateArea);
updatePane.setFitToWidth(true);
GridPane.setVgrow(updatePane, Priority.ALWAYS);
shortDescriptionField = new TextField();
shortDescriptionLabel.setAlignment(Pos.TOP_LEFT);
longDescriptionArea = new TextArea();
longDescriptionArea.setPrefRowCount(5);
longDescriptionArea.setWrapText(true);
description = new ScrollPane(longDescriptionArea);
description.setFitToWidth(true);
GridPane.setVgrow(description, Priority.ALWAYS);
chooseNewOrUpdatedScenarioFields();
Label publishUrlLabel = new Label(Config.getString("export.publish.url"));
urlField = new TextField();
titleAndDescPane.addRow(4, publishUrlLabel, urlField);
includeSource = new CheckBox(Config.getString("export.publish.includeSource"));
includeSource.setSelected(false);
HBox sourceAndLockPane = new HBox(includeSource, lockScenario);
sourceAndLockPane.getStyleClass().add("source-lock-pane");
VBox scenarioPane = new VBox(titleAndDescPane, sourceAndLockPane);
scenarioPane.getStyleClass().add("scenario-pane");
return scenarioPane;
}
| Update the display according to whether this is an update of an existing
| scenario, or an upload of a new scenario. If there is an update,
| a "changes" description area is shown. If not there a short description
|* and long description areas are shown.
*/
private void chooseNewOrUpdatedScenarioFields()
{
titleAndDescPane.getChildren().removeAll(titleLabel, titleField);
|
|if (update)
|
|{
|
|titleAndDescPane.getChildren().removeAll(shortDescriptionLabel, shortDescriptionField,
|
|longDescriptionLabel, description);
|
|titleAndDescPane.addRow(1, emptyLabel, keepScenarioScreenshot);
|
|titleAndDescPane.addRow(2, titleLabel, titleField);
|
|titleAndDescPane.addRow(3, updateLabel, updatePane);
|
|}
|
|else
|
|{
|
|titleAndDescPane.getChildren().removeAll(emptyLabel, keepScenarioScreenshot,
|
|updateLabel, updatePane);
|
|titleAndDescPane.addRow(1, titleLabel, titleField);
|
|titleAndDescPane.addRow(2, shortDescriptionLabel, shortDescriptionField);
|
|titleAndDescPane.addRow(3, longDescriptionLabel, description);
|
|}
|
|imagePane.enableImageEditPanel(!update || !keepScenarioScreenshot.isSelected());
|
|}
|
|/**
| Creates the tag display with popular tags and an option to add tags
|
private Pane getTagDisplay()
{
Label popLabel = new Label(Config.getString("export.publish.tags.popular"));
VBox popPane = new VBox(popLabel);
popPane.getStyleClass().add("pop-pane");
for (int i = 0; i < popTags.length; i++)
{
CheckBox popTag = new CheckBox(Config.getString("export.publish.tags.loading"));
popTag.setDisable(true);
popTags[i] = popTag;
}
popPane.getChildren().addAll(popTags);
tagArea = new TextArea();
ScrollPane tagScroller = new ScrollPane(tagArea);
tagScroller.setPrefSize(125, 100);
tagScroller.setFitToWidth(true);
tagScroller.setFitToHeight(true);
VBox textPane = new VBox(new Label(Config.getString("export.publish.tags.additional1")),
new Label(Config.getString("export.publish.tags.additional2")), tagScroller);
VBox tagPane = new VBox(popPane, textPane);
tagPane.getStyleClass().add("tag-pane");
return tagPane;
}
| Returns true if it is an update and false if it is a first export
|
public boolean isUpdate()
{
return update;
}
| Specify whether this scenario will be updated, or is a new export.
|
private void setUpdate(boolean isUpdate)
{
if (this.update != isUpdate)
{
this.update = isUpdate;
setExportButtonText();
chooseNewOrUpdatedScenarioFields();
}
}
}
top,
use,
map,
class ExportPublishTab
. ExportPublishTab
. getFunction
. getImage
. setImage
. getTitle
. getShortDescription
. getLongDescription
. getURL
. getUserName
. getUpdateDescription
. getPassword
. isIncludeSource
. isKeepSavedScreenshot
. processTags
. getPopTagsTexts
. buildContentPane
. getLoginPane
. getHelpBox
. setPopularTags
. getTags
. loadStoredScenarioInfo
. updateInfoFromFields
. getExportInfo
. checkForExistingScenario
. scenarioExistenceCheckFailed
. scenarioExistenceChecked
. activated
. finished
. abort
. construct
. setExportButtonText
. prePublish
. postPublish
. ensureTrailingSlash
. createScenarioPane
. getTagDisplay
. isUpdate
. setUpdate
798 neLoCode
+ 70 LoComm