/*
* Copyright (c) 2014 Dennis Fischer.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Public License v3.0+
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/gpl.html
*
* Contributors: Dennis Fischer
*/
package de.chaosfisch.uploader.gui.controller;
import com.cathive.fx.guice.FXMLController;
import com.cathive.fx.guice.FxApplicationThread;
import com.cathive.fx.guice.GuiceFXMLLoader;
import com.google.common.base.Charsets;
import com.google.common.base.Strings;
import com.google.common.eventbus.Subscribe;
import com.google.common.io.Files;
import com.google.inject.Inject;
import de.chaosfisch.google.account.Account;
import de.chaosfisch.google.account.IAccountService;
import de.chaosfisch.google.account.events.AccountAdded;
import de.chaosfisch.google.account.events.AccountRemoved;
import de.chaosfisch.google.account.events.AccountUpdated;
import de.chaosfisch.google.youtube.playlist.IPlaylistService;
import de.chaosfisch.google.youtube.playlist.Playlist;
import de.chaosfisch.google.youtube.upload.IUploadService;
import de.chaosfisch.google.youtube.upload.Status;
import de.chaosfisch.google.youtube.upload.Upload;
import de.chaosfisch.google.youtube.upload.metadata.*;
import de.chaosfisch.google.youtube.upload.metadata.permissions.Comment;
import de.chaosfisch.google.youtube.upload.metadata.permissions.Permissions;
import de.chaosfisch.google.youtube.upload.metadata.permissions.ThreeD;
import de.chaosfisch.google.youtube.upload.metadata.permissions.Visibility;
import de.chaosfisch.services.ExtendedPlaceholders;
import de.chaosfisch.uploader.gui.renderer.AccountStringConverter;
import de.chaosfisch.uploader.gui.renderer.DialogHelper;
import de.chaosfisch.uploader.gui.renderer.PlaylistGridCell;
import de.chaosfisch.uploader.gui.renderer.TagTextArea;
import de.chaosfisch.uploader.template.ITemplateService;
import de.chaosfisch.uploader.template.Template;
import de.chaosfisch.uploader.template.events.TemplateAdded;
import de.chaosfisch.uploader.template.events.TemplateRemoved;
import javafx.application.Platform;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.concurrent.Task;
import javafx.event.ActionEvent;
import javafx.event.Event;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.scene.Node;
import javafx.scene.control.*;
import javafx.scene.input.*;
import javafx.scene.layout.GridPane;
import javafx.scene.paint.Color;
import javafx.stage.DirectoryChooser;
import javafx.stage.FileChooser;
import javafx.util.Callback;
import javafx.util.StringConverter;
import javafx.util.converter.DefaultStringConverter;
import jfxtras.labs.scene.control.CalendarTextField;
import jfxtras.labs.scene.control.grid.GridCell;
import jfxtras.labs.scene.control.grid.GridView;
import jfxtras.labs.scene.control.grid.GridViewBuilder;
import org.joda.time.DateTime;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.*;
@FXMLController
public class UploadController {
@FXML
public Label descriptionLabel;
@FXML
public Label tagLabel;
@FXML
public Label titleLabel;
@FXML
public CheckBox uploadAgeRestricted;
@FXML
public CheckBox uploadPublicStatsViewable;
@FXML
public CheckBox uploadGplus;
@FXML
public ChoiceBox<ThreeD> uploadThreeD;
@FXML
private ResourceBundle resources;
@FXML
private URL location;
@FXML
private ChoiceBox<Account> uploadAccount;
@FXML
private Button addUpload;
@FXML
private GridPane extendedSettingsGrid;
@FXML
private Slider gridWidthSlider;
@FXML
private GridPane monetizeGridPane;
@FXML
private ToggleButton monetizePartner;
@FXML
private Button openDefaultdir;
@FXML
private Button openEnddir;
@FXML
private Button openFiles;
@FXML
private Button openThumbnail;
@FXML
private TitledPane partnerPane;
@FXML
private ScrollPane playlistDropScrollpane;
@FXML
private GridPane playlistGrid;
@FXML
private ScrollPane playlistSourceScrollpane;
@FXML
private TextField previewTitle;
@FXML
private Button refreshPlaylists;
@FXML
private Button removeTemplate;
@FXML
private Button resetUpload;
@FXML
private Button saveTemplate;
@FXML
private ChoiceBox<Template> templates;
@FXML
private ChoiceBox<Category> uploadCategory;
@FXML
private ChoiceBox<Comment> uploadComment;
@FXML
private CheckBox uploadCommentvote;
@FXML
private TextField uploadDefaultdir;
@FXML
private TextArea uploadDescription;
@FXML
private CheckBox uploadEmbed;
@FXML
private TextField uploadEnddir;
@FXML
private CheckBox uploadFacebook;
@FXML
private ChoiceBox<File> uploadFile;
@FXML
private GridPane uploadGrid;
@FXML
private ChoiceBox<License> uploadLicense;
@FXML
private TextArea uploadMessage;
@FXML
private CheckBox uploadRate;
@FXML
public ScrollPane uploadTagsScrollpane;
@FXML
private TextField uploadThumbnail;
@FXML
private TextField uploadTitle;
@FXML
private CheckBox uploadTwitter;
@FXML
private ChoiceBox<Visibility> uploadVisibility;
@FXML
private TitledPane x2;
@FXML
private TitledPane x5;
private final CalendarTextField started = new CalendarTextField().withValue(Calendar.getInstance())
.withDateFormat(new SimpleDateFormat("dd.MM.yyyy HH:mm"));
private final CalendarTextField release = new CalendarTextField().withValue(Calendar.getInstance())
.withDateFormat(new SimpleDateFormat("dd.MM.yyyy HH:mm"));
private final GridView<Playlist> playlistSourcezone = GridViewBuilder.create(Playlist.class).build();
private final GridView<Playlist> playlistTargetzone = GridViewBuilder.create(Playlist.class).build();
@Inject
private FileChooser fileChooser;
@Inject
private DirectoryChooser directoryChooser;
@Inject
private IAccountService accountService;
@Inject
private IPlaylistService playlistService;
@Inject
private ITemplateService templateService;
@Inject
private IUploadService uploadService;
@Inject
private DialogHelper dialogHelper;
@Inject
private TagTextArea uploadTags;
@Inject
private GuiceFXMLLoader fxmlLoader;
private final ObservableList<File> filesList = FXCollections.observableArrayList();
private final ObservableList<Category> categoriesList = FXCollections.observableArrayList();
private final ObservableList<ThreeD> threedList = FXCollections.observableArrayList();
private final ObservableList<Account> accountsList = FXCollections.observableArrayList();
private final ObservableList<Template> templatesList = FXCollections.observableArrayList();
private final ObservableList<Visibility> visibilityList = FXCollections.observableArrayList();
private final ObservableList<Comment> commentsList = FXCollections.observableArrayList();
private final ObservableList<License> licensesList = FXCollections.observableArrayList();
private final ObservableList<Playlist> playlistSourceList = FXCollections.observableArrayList();
private final ObservableList<Playlist> playlistTargetList = FXCollections.observableArrayList();
private final SimpleStringProperty idProperty = new SimpleStringProperty();
private final SimpleObjectProperty<File> defaultDirProperty = new SimpleObjectProperty<>();
private final SimpleObjectProperty<File> enddirProperty = new SimpleObjectProperty<>();
private UploadMonetizationController uploadMonetizationController;
private UploadPartnerController uploadPartnerController;
private Upload uploadStore;
public UploadController() {
idProperty.setValue(null);
}
@FXML
void addUpload(final ActionEvent event) {
try {
dialogHelper.resetControlls(new Control[]{uploadAccount, uploadCategory, uploadDescription, uploadFile,
uploadThumbnail, uploadTitle});
uploadTags.getStyleClass().remove("input-invalid");
buildUpload();
// Cleanup (reset form)
filesList.remove(uploadFile.getValue());
uploadFile.getSelectionModel().selectNext();
idProperty.setValue(null);
} catch (final IllegalArgumentException e) {
handleUploadBuildException(e);
}
}
private void handleUploadBuildException(final IllegalArgumentException e) {
switch (e.getMessage()) {
case Upload.Validation.ACCOUNT:
uploadAccount.getStyleClass().add("input-invalid");
uploadAccount.setTooltip(TooltipBuilder.create()
.autoHide(true)
.text(resources.getString("validation.account"))
.build());
uploadAccount.getTooltip()
.show(uploadAccount, dialogHelper.getTooltipX(uploadAccount), dialogHelper.getTooltipY(uploadAccount));
break;
case Upload.Validation.CATEGORY:
uploadCategory.getStyleClass().add("input-invalid");
uploadCategory.setTooltip(TooltipBuilder.create()
.autoHide(true)
.text(resources.getString("validation.category"))
.build());
uploadCategory.getTooltip()
.show(uploadCategory, dialogHelper.getTooltipX(uploadCategory), dialogHelper.getTooltipY(uploadCategory));
break;
case Upload.Validation.DESCRIPTION_CHARACTERS:
uploadDescription.getStyleClass().add("input-invalid");
uploadDescription.setTooltip(TooltipBuilder.create()
.autoHide(true)
.text(resources.getString("validation.description.characters"))
.build());
uploadDescription.getTooltip()
.show(uploadDescription, dialogHelper.getTooltipX(uploadDescription), dialogHelper.getTooltipY(uploadDescription));
break;
case Upload.Validation.DESCRIPTION_SIZE:
uploadDescription.getStyleClass().add("input-invalid");
uploadDescription.setTooltip(TooltipBuilder.create()
.autoHide(true)
.text(resources.getString("validation.description"))
.build());
uploadDescription.getTooltip()
.show(uploadDescription, dialogHelper.getTooltipX(uploadDescription), dialogHelper.getTooltipY(uploadDescription));
break;
case Upload.Validation.FILE:
uploadFile.getStyleClass().add("input-invalid");
uploadFile.setTooltip(TooltipBuilder.create()
.autoHide(true)
.text(resources.getString("validation.filelist"))
.build());
uploadFile.getTooltip()
.show(uploadFile, dialogHelper.getTooltipX(uploadFile), dialogHelper.getTooltipY(uploadFile));
break;
case Upload.Validation.KEYWORD:
uploadTags.getStyleClass().add("input-invalid");
final Tooltip tooltip = TooltipBuilder.create()
.autoHide(true)
.text(resources.getString("validation.tags"))
.build();
tooltip.show(uploadTags, dialogHelper.getTooltipX(uploadTags), dialogHelper.getTooltipY(uploadTags));
break;
case Upload.Validation.THUMBNAIL:
case Upload.Validation.THUMBNAIL_SIZE:
uploadThumbnail.getStyleClass().add("input-invalid");
uploadThumbnail.setTooltip(TooltipBuilder.create()
.autoHide(true)
.text(resources.getString("validation.thumbnail"))
.build());
uploadThumbnail.getTooltip()
.show(uploadThumbnail, dialogHelper.getTooltipX(uploadThumbnail), dialogHelper.getTooltipY(uploadThumbnail));
break;
case Upload.Validation.TITLE:
case Upload.Validation.TITLE_SIZE:
case Upload.Validation.TITLE_CHARACTERS:
uploadTitle.getStyleClass().add("input-invalid");
uploadTitle.setTooltip(TooltipBuilder.create()
.autoHide(true)
.text(resources.getString("validation.title"))
.build());
uploadTitle.getTooltip()
.show(uploadTitle, dialogHelper.getTooltipX(uploadTitle), dialogHelper.getTooltipY(uploadTitle));
break;
case Upload.Validation.ENDDIR:
uploadEnddir.getStyleClass().add("input-invalid");
uploadEnddir.setTooltip(TooltipBuilder.create()
.autoHide(true)
.text(resources.getString("validation.enddir"))
.build());
uploadEnddir.getTooltip()
.show(uploadEnddir, dialogHelper.getTooltipX(uploadEnddir), dialogHelper.getTooltipY(uploadEnddir));
break;
}
}
private void buildUpload() {
final Upload upload = null == uploadStore ?
new Upload(uploadAccount.getValue(), uploadFile.getValue()) :
uploadStore;
toUpload(upload);
final Monetization monetization = upload.getMonetization();
if (!monetization.isPartner() && (monetization.isOverlay() || monetization.isTrueview() || monetization.isProduct())) {
monetization.setClaim(true);
}
if (null == upload.getId()) {
final Status status = new Status();
status.setArchived(false);
status.setFailed(false);
status.setRunning(false);
status.setLocked(false);
upload.setStatus(status);
uploadService.insert(upload);
} else {
final Status status = upload.getStatus();
status.setArchived(false);
status.setFailed(false);
status.setLocked(false);
upload.setStatus(status);
uploadService.update(upload);
}
}
@FXML
void openDefaultdir(final ActionEvent event) {
final File directory = directoryChooser.showDialog(null);
if (null != directory) {
directoryChooser.setInitialDirectory(directory);
fileChooser.setInitialDirectory(directory);
uploadDefaultdir.setText(directory.getAbsolutePath());
}
}
@FXML
void openEnddir(final ActionEvent event) {
final File directory = directoryChooser.showDialog(null);
if (null != directory) {
uploadEnddir.setText(directory.getAbsolutePath());
}
}
@FXML
void openFiles(final ActionEvent event) {
final List<File> files = fileChooser.showOpenMultipleDialog(null);
if (null != files && !files.isEmpty()) {
addUploadFiles(files);
}
}
@FXML
void openThumbnail(final ActionEvent event) {
final File file = fileChooser.showOpenDialog(null);
if (null != file) {
uploadThumbnail.setText(file.getAbsolutePath());
}
}
@FXML
void refreshPlaylists(final ActionEvent event) {
final Task<Void> task = new Task<Void>() {
@Override
protected Void call() throws Exception {
updateMessage("Loading playlists");
updateProgress(-1, -1);
playlistService.synchronizePlaylists(accountsList);
return null;
}
@Override
protected void failed() {
updateMessage("Failed loading playlists");
updateProgress(0, 0);
}
@Override
protected void succeeded() {
updateMessage("Playlists loaded.");
updateProgress(1, 1);
}
};
dialogHelper.registerBusyTask(task);
final Thread th = new Thread(task);
th.setDaemon(true);
th.start();
}
@FXML
void removeTemplate(final ActionEvent event) {
final Template template;
if (null != (template = templates.getSelectionModel().getSelectedItem())) {
templateService.delete(template);
}
}
@FXML
void resetUpload(final ActionEvent event) {
_reset();
}
private void _reset() {
fromTemplate(null == templates.getValue() ? ViewController.standardTemplate : templates.getValue());
}
@FXML
void saveTemplate(final ActionEvent event) {
if (null == templates.getValue()) {
return;
}
try {
final Template template = toTemplate(templates.getValue());
template.setAccount(uploadAccount.getValue());
template.setPlaylists(playlistTargetList);
templateService.update(template);
} catch (final IllegalArgumentException e) {
handleUploadBuildException(e);
}
}
@FXML
void togglePartner(final ActionEvent event) {
if (monetizePartner.isSelected()) {
monetizeGridPane.getChildren().retainAll(monetizePartner);
monetizeGridPane.add(uploadPartnerController.getNode(), 0, 0, GridPane.REMAINING, 1);
} else {
monetizeGridPane.getChildren().retainAll(monetizePartner);
monetizeGridPane.add(uploadMonetizationController.getNode(), 0, 0, GridPane.REMAINING, 1);
}
}
@FXML
void initialize() {
assert null != addUpload : "fx:id=\"addUpload\" was not injected: check your FXML file 'Upload.fxml'.";
assert null != extendedSettingsGrid : "fx:id=\"extendedSettingsGrid\" was not injected: check your FXML file 'Upload.fxml'.";
assert null != gridWidthSlider : "fx:id=\"gridWidthSlider\" was not injected: check your FXML file 'Upload.fxml'.";
assert null != monetizePartner : "fx:id=\"monetizePartner\" was not injected: check your FXML file 'Upload.fxml'.";
assert null != openDefaultdir : "fx:id=\"openDefaultdir\" was not injected: check your FXML file 'Upload.fxml'.";
assert null != openEnddir : "fx:id=\"openEnddir\" was not injected: check your FXML file 'Upload.fxml'.";
assert null != openFiles : "fx:id=\"openFiles\" was not injected: check your FXML file 'Upload.fxml'.";
assert null != openThumbnail : "fx:id=\"openThumbnail\" was not injected: check your FXML file 'Upload.fxml'.";
assert null != partnerPane : "fx:id=\"partnerPane\" was not injected: check your FXML file 'Upload.fxml'.";
assert null != playlistDropScrollpane : "fx:id=\"playlistDropScrollpane\" was not injected: check your FXML file 'Upload.fxml'.";
assert null != playlistGrid : "fx:id=\"playlistGrid\" was not injected: check your FXML file 'Upload.fxml'.";
assert null != playlistSourceScrollpane : "fx:id=\"playlistSourceScrollpane\" was not injected: check your FXML file 'Upload.fxml'.";
assert null != previewTitle : "fx:id=\"previewTitle\" was not injected: check your FXML file 'Upload.fxml'.";
assert null != refreshPlaylists : "fx:id=\"refreshPlaylists\" was not injected: check your FXML file 'Upload.fxml'.";
assert null != removeTemplate : "fx:id=\"removeTemplate\" was not injected: check your FXML file 'Upload.fxml'.";
assert null != resetUpload : "fx:id=\"resetUpload\" was not injected: check your FXML file 'Upload.fxml'.";
assert null != saveTemplate : "fx:id=\"saveTemplate\" was not injected: check your FXML file 'Upload.fxml'.";
assert null != templates : "fx:id=\"templates\" was not injected: check your FXML file 'Upload.fxml'.";
assert null != uploadAccount : "fx:id=\"uploadAccount\" was not injected: check your FXML file 'Upload.fxml'.";
assert null != uploadCategory : "fx:id=\"uploadCategory\" was not injected: check your FXML file 'Upload.fxml'.";
assert null != uploadComment : "fx:id=\"uploadComment\" was not injected: check your FXML file 'Upload.fxml'.";
assert null != uploadCommentvote : "fx:id=\"uploadCommentvote\" was not injected: check your FXML file 'Upload.fxml'.";
assert null != uploadDefaultdir : "fx:id=\"uploadDefaultdir\" was not injected: check your FXML file 'Upload.fxml'.";
assert null != uploadDescription : "fx:id=\"uploadDescription\" was not injected: check your FXML file 'Upload.fxml'.";
assert null != uploadEmbed : "fx:id=\"uploadEmbed\" was not injected: check your FXML file 'Upload.fxml'.";
assert null != uploadEnddir : "fx:id=\"uploadEnddir\" was not injected: check your FXML file 'Upload.fxml'.";
assert null != uploadFacebook : "fx:id=\"uploadFacebook\" was not injected: check your FXML file 'Upload.fxml'.";
assert null != uploadFile : "fx:id=\"uploadFile\" was not injected: check your FXML file 'Upload.fxml'.";
assert null != uploadGrid : "fx:id=\"uploadGrid\" was not injected: check your FXML file 'Upload.fxml'.";
assert null != uploadLicense : "fx:id=\"uploadLicense\" was not injected: check your FXML file 'Upload.fxml'.";
assert null != uploadMessage : "fx:id=\"uploadMessage\" was not injected: check your FXML file 'Upload.fxml'.";
assert null != uploadRate : "fx:id=\"uploadRate\" was not injected: check your FXML file 'Upload.fxml'.";
assert null != uploadTags : "fx:id=\"uploadTags\" was not injected: check your FXML file 'Upload.fxml'.";
assert null != uploadThumbnail : "fx:id=\"uploadThumbnail\" was not injected: check your FXML file 'Upload.fxml'.";
assert null != uploadTitle : "fx:id=\"uploadTitle\" was not injected: check your FXML file 'Upload.fxml'.";
assert null != uploadTwitter : "fx:id=\"uploadTwitter\" was not injected: check your FXML file 'Upload.fxml'.";
assert null != uploadVisibility : "fx:id=\"uploadVisibility\" was not injected: check your FXML file 'Upload.fxml'.";
assert null != uploadAgeRestricted : "fx:id=\"uploadAgeRestricted\" was not injected: check your FXML file 'Upload.fxml'.";
assert null != uploadPublicStatsViewable : "fx:id=\"uploadPublicStatsViewable\" was not injected: check your FXML file 'Upload.fxml'.";
assert null != uploadThreeD : "fx:id=\"uploadThreeD\" was not injected: check your FXML file 'Upload.fxml'.";
assert null != uploadGplus : "fx:id=\"uploadGplus\" was not injected: check your FXML file 'Upload.fxml'.";
assert null != x2 : "fx:id=\"x2\" was not injected: check your FXML file 'Upload.fxml'.";
assert null != x5 : "fx:id=\"x5\" was not injected: check your FXML file 'Upload.fxml'.";
initControls();
initCustomFactories();
initDragEventHandlers();
initBindings();
initData();
initSelection();
}
@Subscribe
public void onTemplateAdded(final TemplateAdded event) {
Platform.runLater(new Runnable() {
@Override
public void run() {
templatesList.add(event.getTemplate());
if (null == templates.getValue()) {
templates.getSelectionModel().selectFirst();
}
}
});
}
@Subscribe
public void onTemplateRemoved(final TemplateRemoved event) {
Platform.runLater(new Runnable() {
@Override
public void run() {
templatesList.remove(event.getTemplate());
if (event.getTemplate().equals(templates.getValue())) {
templates.setValue(null);
templates.getSelectionModel().selectFirst();
}
}
});
}
@Subscribe
public void onAccountAdded(final AccountAdded event) {
Platform.runLater(new Runnable() {
@Override
public void run() {
accountsList.add(event.getAccount());
if (null == uploadAccount.getValue()) {
uploadAccount.getSelectionModel().selectFirst();
}
refreshPlaylists(null);
}
});
}
@Subscribe
public void onAccountRemoved(final AccountRemoved event) {
Platform.runLater(new Runnable() {
@Override
public void run() {
accountsList.remove(event.getAccount());
if (event.getAccount().equals(uploadAccount.getValue())) {
playlistSourceList.clear();
playlistTargetList.clear();
uploadAccount.setValue(null);
uploadAccount.getSelectionModel().selectFirst();
}
}
});
}
@Subscribe
public void onAccountUpdated(final AccountUpdated event) {
if (!event.getAccount().equals(uploadAccount.getValue())) {
return;
}
Platform.runLater(new Runnable() {
@Override
public void run() {
if (!accountsList.contains(event.getAccount())) {
accountsList.add(event.getAccount());
}
if (null == uploadAccount.getValue()) {
uploadAccount.getSelectionModel().select(event.getAccount());
}
playlistSourceList.retainAll(event.getAccount().getPlaylists());
playlistTargetList.retainAll(event.getAccount().getPlaylists());
for (final Playlist playlist : event.getAccount().getPlaylists()) {
if (playlist.isHidden()) {
playlistSourceList.remove(playlist);
playlistTargetList.remove(playlist);
} else if (!playlistSourceList.contains(playlist) && !playlistTargetList.contains(playlist)) {
playlistSourceList.add(playlist);
}
}
}
});
}
private void initData() {
visibilityList.addAll(Visibility.values());
commentsList.addAll(Comment.values());
licensesList.addAll(License.values());
categoriesList.addAll(Category.values());
threedList.addAll(ThreeD.values());
accountsList.addAll(accountService.getAll());
templatesList.addAll(templateService.getAll());
}
private void initControls() {
extendedSettingsGrid.add(started, 1, 12, GridPane.REMAINING, 1);
extendedSettingsGrid.add(release, 1, 13, GridPane.REMAINING, 1);
uploadTagsScrollpane.setContent(uploadTags);
playlistSourceScrollpane.setContent(playlistSourcezone);
playlistDropScrollpane.setContent(playlistTargetzone);
final String formatTitle = resources.getString("label.title") + "\n%d/100";
previewTitle.textProperty().addListener(new ChangeListener<String>() {
@Override
public void changed(final ObservableValue<? extends String> observableValue, final String oldTitle, final String newTitle) {
final int length = newTitle.getBytes(Charsets.UTF_8).length;
if (Upload.Validation.MAX_TITLE_SIZE < length) {
titleLabel.setTextFill(Color.RED);
} else {
titleLabel.setTextFill(Color.BLACK);
}
titleLabel.setText(String.format(formatTitle, length));
}
});
final String formatDescription = resources.getString("label.description") + "\n%d/5000";
uploadDescription.textProperty().addListener(new ChangeListener<String>() {
@Override
public void changed(final ObservableValue<? extends String> observableValue, final String oldDescription, final String newDescription) {
final int length = newDescription.getBytes(Charsets.UTF_8).length;
if (Upload.Validation.MAX_DESCRIPTION_SIZE < length) {
descriptionLabel.setTextFill(Color.RED);
} else {
descriptionLabel.setTextFill(Color.BLACK);
}
descriptionLabel.setText(String.format(formatDescription, length));
}
});
final String formatTags = resources.getString("label.tags") + "\n%d/500";
uploadTags.tagsProperty().addListener(new ChangeListener<String>() {
@Override
public void changed(final ObservableValue<? extends String> observableValue, final String oldTags, final String newTags) {
final List<String> tags = TagParser.parse(newTags);
if (!TagParser.areTagsValid(tags)) {
tagLabel.setTextFill(Color.RED);
} else {
tagLabel.setTextFill(Color.BLACK);
}
tagLabel.setText(String.format(formatTags, TagParser.getLength(tags)));
}
});
}
private void initCustomFactories() {
playlistSourcezone.setCellFactory(new PlaylistCellFactory(playlistSourceList));
playlistTargetzone.setCellFactory(new PlaylistCellFactory(playlistTargetList));
uploadAccount.setConverter(new AccountStringConverter());
uploadFile.setConverter(new UploadFileListViewConverter());
idProperty.addListener(new UploadIdInvalidationListener());
try {
uploadPartnerController = fxmlLoader.load(getClass().getResource("/de/chaosfisch/uploader/view/UploadPartner.fxml"), resources)
.getController();
uploadMonetizationController = fxmlLoader.load(getClass().getResource("/de/chaosfisch/uploader/view/UploadMonetization.fxml"), resources)
.getController();
} catch (final IOException e) {
e.printStackTrace();
}
monetizeGridPane.add(uploadMonetizationController.getNode(), 0, 0, GridPane.REMAINING, 1);
monetizePartner.selectedProperty().addListener(new MonetizePartnerInvalidationListener());
uploadLicense.getSelectionModel().selectedItemProperty().addListener(new LicenseChangeListener());
uploadAccount.getSelectionModel().selectedItemProperty().addListener(new AccountChangeListener());
templates.getSelectionModel().selectedItemProperty().addListener(new ChangeListener<Template>() {
@Override
public void changed(final ObservableValue<? extends Template> observable, final Template oldValue, final Template newValue) {
_reset();
}
});
final PreviewTitleChangeListener titleChangeListener = new PreviewTitleChangeListener();
uploadFile.getSelectionModel().selectedItemProperty().addListener(titleChangeListener);
playlistTargetList.addListener(titleChangeListener);
uploadFile.setItems(filesList);
uploadCategory.setItems(categoriesList);
uploadAccount.setItems(accountsList);
templates.setItems(templatesList);
uploadVisibility.setItems(visibilityList);
uploadComment.setItems(commentsList);
uploadLicense.setItems(licensesList);
playlistTargetzone.setItems(playlistTargetList);
playlistSourcezone.setItems(playlistSourceList);
uploadThreeD.setItems(threedList);
}
private void initDragEventHandlers() {
final EventHandler<DragEvent> onDragOver = new DragOverCallback();
final EventHandler<DragEvent> onDragDropped = new DragDroppedCallback();
final EventHandler<DragEvent> onDragEntered = new DragEnteredCallback();
final EventHandler<DragEvent> onDragExited = new DragExitedCallback();
playlistTargetzone.setOnDragDropped(onDragDropped);
playlistTargetzone.setOnDragEntered(onDragEntered);
playlistTargetzone.setOnDragExited(onDragExited);
playlistTargetzone.setOnDragOver(onDragOver);
playlistSourcezone.setOnDragDropped(onDragDropped);
playlistSourcezone.setOnDragEntered(onDragEntered);
playlistSourcezone.setOnDragExited(onDragExited);
playlistSourcezone.setOnDragOver(onDragOver);
}
private void initTwitterFacebookBinding(final BooleanProperty booleanProperty) {
booleanProperty.bind(uploadVisibility.getSelectionModel()
.selectedIndexProperty()
.isNotEqualTo(3)
.and(uploadVisibility.getSelectionModel().selectedIndexProperty().isNotEqualTo(0)));
}
private void initBindings() {
release.disableProperty().bind(uploadVisibility.getSelectionModel().selectedIndexProperty().isNotEqualTo(3));
uploadMessage.disableProperty()
.bind(uploadVisibility.getSelectionModel()
.selectedIndexProperty()
.isNotEqualTo(3)
.and(uploadVisibility.getSelectionModel().selectedIndexProperty().isNotEqualTo(0)));
initTwitterFacebookBinding(uploadFacebook.disableProperty());
initTwitterFacebookBinding(uploadTwitter.disableProperty());
gridWidthSlider.minProperty().set(1280);
gridWidthSlider.maxProperty().set(2000);
playlistSourcezone.minHeightProperty().bind(playlistSourceScrollpane.heightProperty());
playlistSourcezone.prefWidthProperty().bind(playlistSourceScrollpane.widthProperty().subtract(10));
playlistSourcezone.cellWidthProperty().bind(gridWidthSlider.valueProperty().divide(9));
playlistSourcezone.cellHeightProperty().bind(gridWidthSlider.valueProperty().divide(16));
playlistTargetzone.minWidthProperty().bind(playlistDropScrollpane.widthProperty().subtract(5));
playlistTargetzone.prefHeightProperty().bind(playlistDropScrollpane.heightProperty());
playlistTargetzone.cellHeightProperty().set(68);
playlistTargetzone.cellWidthProperty().set(120);
previewTitle.textProperty().bindBidirectional(uploadTitle.textProperty(), new PreviewTitleStringConverter());
uploadDefaultdir.textProperty().bindBidirectional(defaultDirProperty, new DefaultDirStringConverter());
uploadEnddir.textProperty().bindBidirectional(enddirProperty, new DefaultDirStringConverter());
}
private Upload toUpload(final Upload upload) {
upload.setId(idProperty.getValue());
upload.setEnddir(Strings.isNullOrEmpty(uploadEnddir.getText()) ? null : enddirProperty.getValue());
upload.setFile(uploadFile.getValue());
upload.setThumbnail(Strings.isNullOrEmpty(uploadThumbnail.getText()) ?
null :
new File(uploadThumbnail.getText()));
upload.setDateTimeOfStart(null);
upload.setDateTimeOfRelease(null);
if (null != started.getValue()) {
upload.setDateTimeOfStart(new DateTime(started.getValue()));
}
if (uploadVisibility.getValue().equals(Visibility.SCHEDULED) && null != release.getValue()) {
upload.setDateTimeOfRelease(new DateTime(release.getValue()));
}
final Metadata metadata = new Metadata(uploadTitle.getText(), uploadCategory.getValue(), uploadDescription.getText(), uploadTags
.getTags(), uploadLicense.getValue());
final Permissions permissions = upload.getPermissions();
final Social social = upload.getSocial();
final Monetization monetization = upload.getMonetization();
monetization.setPartner(monetizePartner.isSelected());
toPermissions(permissions);
toSocial(social);
upload.setMetadata(metadata);
upload.setPermissions(permissions);
upload.setSocial(social);
upload.setMonetization(monetization);
upload.setPlaylists(playlistTargetList);
if (monetizePartner.isSelected()) {
uploadPartnerController.toUpload(upload);
} else {
uploadMonetizationController.toUpload(upload);
}
return upload;
}
private void toSocial(final Social social) {
social.setFacebook(uploadFacebook.isSelected());
social.setTwitter(uploadTwitter.isSelected());
social.setGplus(uploadGplus.isSelected());
social.setMessage(uploadMessage.getText());
}
private void toPermissions(final Permissions permissions) {
permissions.setCommentvote(uploadCommentvote.isSelected());
permissions.setComment(uploadComment.getValue());
permissions.setEmbed(uploadEmbed.isSelected());
permissions.setRate(uploadRate.isSelected());
permissions.setVisibility(uploadVisibility.getValue());
permissions.setAgeRestricted(uploadAgeRestricted.isSelected());
permissions.setPublicStatsViewable(uploadPublicStatsViewable.isSelected());
permissions.setThreedD(uploadThreeD.getValue());
}
private Template toTemplate(final Template template) {
template.setDefaultdir(Strings.isNullOrEmpty(uploadDefaultdir.getText()) ?
null :
defaultDirProperty.getValue());
template.setEnddir(Strings.isNullOrEmpty(uploadEnddir.getText()) ? null : enddirProperty.getValue());
template.setThumbnail(Strings.isNullOrEmpty(uploadThumbnail.getText()) ?
null :
new File(uploadThumbnail.getText()));
final Metadata metadata = new Metadata(uploadTitle.getText(), uploadCategory.getValue(), uploadDescription.getText(), uploadTags
.getTags(), uploadLicense.getValue());
final Permissions permissions = template.getPermissions();
final Social social = template.getSocial();
final Monetization monetization = template.getMonetization();
monetization.setPartner(monetizePartner.isSelected());
toPermissions(permissions);
toSocial(social);
template.setMetadata(metadata);
template.setPermissions(permissions);
template.setSocial(social);
template.setMonetization(monetization);
if (monetizePartner.isSelected()) {
uploadPartnerController.toTemplate(template);
} else {
uploadMonetizationController.toTemplate(template);
}
return template;
}
public void fromUpload(final Upload upload) {
_reset();
uploadStore = upload;
idProperty.setValue(upload.getId());
started.setValue(null == upload.getDateTimeOfStart() ?
Calendar.getInstance() :
upload.getDateTimeOfStart().toCalendar(Locale.getDefault()));
release.setValue(null == upload.getDateTimeOfRelease() ?
Calendar.getInstance() :
upload.getDateTimeOfRelease().toCalendar(Locale.getDefault()));
enddirProperty.setValue(upload.getEnddir());
uploadFile.getItems().add(upload.getFile());
uploadFile.getSelectionModel().select(upload.getFile());
uploadThumbnail.setText(null == upload.getThumbnail() ? "" : upload.getThumbnail().getAbsolutePath());
final Metadata metadata = null == upload.getMetadata() ? new Metadata() : upload.getMetadata();
final Permissions permissions = null == upload.getPermissions() ? new Permissions() : upload.getPermissions();
final Social social = null == upload.getSocial() ? new Social() : upload.getSocial();
fromMetadata(metadata);
fromPermissions(permissions);
fromSocial(social);
monetizePartner.setSelected(null != upload.getMonetization() && upload.getMonetization().isPartner());
if (monetizePartner.isSelected()) {
uploadPartnerController.fromUpload(upload);
} else {
uploadMonetizationController.fromUpload(upload);
}
uploadAccount.getSelectionModel().select(upload.getAccount());
if (null == uploadAccount.getValue()) {
uploadAccount.getSelectionModel().selectFirst();
}
if (null == uploadCategory.getValue()) {
uploadCategory.getSelectionModel().selectFirst();
}
final Iterator<Playlist> playlistIterator = playlistTargetList.iterator();
while (playlistIterator.hasNext()) {
final Playlist playlist = playlistIterator.next();
playlistSourceList.add(playlist);
playlistIterator.remove();
}
for (final Playlist playlist : upload.getPlaylists()) {
playlistTargetList.add(playlist);
playlistSourceList.remove(playlist);
}
}
private void fromMetadata(final Metadata metadata) {
uploadCategory.setValue(metadata.getCategory());
uploadDescription.setText(metadata.getDescription());
uploadTags.setTags(metadata.getKeywords());
uploadLicense.setValue(metadata.getLicense());
uploadTitle.setText(metadata.getTitle());
}
private void fromPermissions(final Permissions permissions) {
uploadCommentvote.setSelected(permissions.isCommentvote());
uploadComment.setValue(permissions.getComment());
uploadEmbed.setSelected(permissions.isEmbed());
uploadRate.setSelected(permissions.isRate());
uploadVisibility.setValue(permissions.getVisibility());
uploadPublicStatsViewable.setSelected(permissions.isPublicStatsViewable());
uploadAgeRestricted.setSelected(permissions.isAgeRestricted());
uploadThreeD.setValue(permissions.getThreedD());
}
private void fromTemplate(final Template template) {
if (null != template.getDefaultdir() && template.getDefaultdir().isDirectory()) {
fileChooser.setInitialDirectory(template.getDefaultdir());
directoryChooser.setInitialDirectory(template.getDefaultdir());
}
idProperty.setValue(null);
uploadStore = null;
enddirProperty.setValue(template.getEnddir());
uploadThumbnail.setText(null == template.getThumbnail() ? "" : template.getThumbnail().getAbsolutePath());
defaultDirProperty.setValue(template.getDefaultdir());
enddirProperty.setValue(template.getEnddir());
final Metadata metadata = null == template.getMetadata() ? new Metadata() : template.getMetadata();
final Permissions permissions = null == template.getPermissions() ?
new Permissions() :
template.getPermissions();
final Social social = null == template.getSocial() ? new Social() : template.getSocial();
fromMetadata(metadata);
fromPermissions(permissions);
fromSocial(social);
monetizePartner.setSelected(null != template.getMonetization() && template.getMonetization().isPartner());
if (monetizePartner.isSelected()) {
uploadPartnerController.fromTemplate(template);
} else {
uploadMonetizationController.fromTemplate(template);
}
uploadAccount.getSelectionModel().select(template.getAccount());
if (null == uploadAccount.getValue()) {
uploadAccount.getSelectionModel().selectFirst();
}
if (null == uploadCategory.getValue()) {
uploadCategory.getSelectionModel().selectFirst();
}
final Iterator<Playlist> playlistIterator = playlistTargetList.iterator();
while (playlistIterator.hasNext()) {
final Playlist playlist = playlistIterator.next();
playlistSourceList.add(playlist);
playlistIterator.remove();
}
if (null != templates.getValue() && null != templates.getValue().getPlaylists()) {
for (final Playlist playlist : templates.getValue().getPlaylists()) {
playlistTargetList.add(playlist);
playlistSourceList.remove(playlist);
}
}
playlistSourcezone.setItems(null);
playlistSourcezone.setItems(playlistSourceList);
playlistTargetzone.setItems(null);
playlistTargetzone.setItems(playlistTargetList);
}
private void fromSocial(final Social social) {
uploadFacebook.setSelected(social.isFacebook());
uploadTwitter.setSelected(social.isTwitter());
uploadGplus.setSelected(social.isGplus());
uploadMessage.setText(social.getMessage());
}
private void initSelection() {
final ChoiceBox<?>[] controls = new ChoiceBox[]{uploadVisibility, uploadComment, uploadLicense, uploadAccount,
uploadCategory, templates, uploadThreeD};
for (final ChoiceBox<?> comboBox : controls) {
comboBox.getSelectionModel().selectFirst();
}
}
void movePlaylistToDropzone(final int model) {
movePlaylist(model, playlistSourceList, playlistTargetList);
}
void removePlaylistFromDropzone(final int model) {
movePlaylist(model, playlistTargetList, playlistSourceList);
}
private void movePlaylist(final int model, final List<Playlist> from, final List<Playlist> to) {
if (0 <= model) {
to.add(from.get(model));
from.remove(model);
}
}
public void addUploadFiles(final List<File> files) {
filesList.clear();
filesList.addAll(files);
uploadFile.getSelectionModel().selectFirst();
if (null == uploadTitle.getText() || uploadTitle.getText().isEmpty()) {
uploadTitle.setText(Files.getNameWithoutExtension(files.get(0).getName()));
}
}
@FxApplicationThread
private void _triggerPlaylist() {
playlistTargetzone.setItems(null);
playlistTargetzone.setItems(playlistTargetList);
playlistSourcezone.setItems(null);
playlistSourcezone.setItems(playlistSourceList);
}
private final class AccountChangeListener implements ChangeListener<Account> {
@Override
public void changed(final ObservableValue<? extends Account> observable, final Account oldValue, final Account newValue) {
playlistSourceList.clear();
playlistTargetList.clear();
if (null != newValue) {
playlistSourceList.addAll(playlistService.fetchUnhiddenByAccount(newValue));
}
_triggerPlaylist();
}
}
private static final class DefaultDirStringConverter extends StringConverter<File> {
@Override
public String toString(final File file) {
return null == file ? null : file.getAbsolutePath();
}
@Override
public File fromString(final String path) {
return null == path ? null : new File(path);
}
}
private final class PreviewTitleStringConverter extends DefaultStringConverter {
final ExtendedPlaceholders extendedPlaceholders = new ExtendedPlaceholders(resources);
@Override
public String toString(final String value) {
extendedPlaceholders.setFile(null == uploadFile.getValue() ? null : uploadFile.getValue());
extendedPlaceholders.setPlaylists(playlistTargetList);
return extendedPlaceholders.replace(value);
}
}
private final class PreviewTitleChangeListener implements ListChangeListener<Playlist>, ChangeListener<File> {
@Override
public void onChanged(final Change<? extends Playlist> change) {
_change();
}
@Override
public void changed(final ObservableValue<? extends File> observableValue, final File file, final File file2) {
_change();
}
private void _change() {
final String value = uploadTitle.getText();
uploadTitle.setText("");
uploadTitle.setText(value);
}
}
private final class LicenseChangeListener implements ChangeListener<License> {
@Override
public void changed(final ObservableValue<? extends License> observable, final License oldValue, final License newValue) {
if (null == newValue) {
return;
}
switch (newValue) {
case CREATIVE_COMMONS:
partnerPane.setDisable(true);
partnerPane.getStyleClass().add("partnerPaneDisabled");
break;
case YOUTUBE:
partnerPane.setDisable(false);
partnerPane.getStyleClass().remove("partnerPaneDisabled");
break;
}
}
}
private final class MonetizePartnerInvalidationListener implements InvalidationListener {
@Override
public void invalidated(final Observable arg0) {
togglePartner(null);
}
}
private final class UploadIdInvalidationListener implements InvalidationListener {
@Override
public void invalidated(final Observable arg0) {
if (null == idProperty.getValue()) {
addUpload.setText(resources.getString("button.addUpload"));
addUpload.setId("addUpload");
} else {
addUpload.setText(resources.getString("button.saveUpload"));
addUpload.setId("saveUpload");
}
}
}
private static final class UploadFileListViewConverter extends StringConverter<File> {
@Override
public String toString(final File object) {
if (50 < object.getPath().length()) {
final String fileName = object.getPath();
return String.format("%s%s...%s", fileName.substring(0, fileName.indexOf(File.separatorChar, fileName.indexOf(File.separatorChar))), File.separator, fileName
.substring(fileName.lastIndexOf(File.separatorChar, fileName.length())));
}
return object.getPath();
}
@Override
public File fromString(final String string) {
throw new RuntimeException("This method is not implemented: uploadFile is readonly!");
}
}
private final class PlaylistCellFactory implements Callback<GridView<Playlist>, GridCell<Playlist>> {
private final ObservableList<Playlist> list;
public PlaylistCellFactory(final ObservableList<Playlist> list) {
this.list = list;
}
@Override
public GridCell<Playlist> call(final GridView<Playlist> playlistGridView) {
final PlaylistGridCell cell = new PlaylistGridCell();
cell.setOnDragDetected(new PlaylistDragDetected(playlistGridView, list, cell));
cell.setOnMouseClicked(new PlaylistMouseClicked(list, cell));
return cell;
}
}
private static final class PlaylistDragDetected implements EventHandler<Event> {
private final GridView<Playlist> gridView;
private final ObservableList<Playlist> playlists;
private final PlaylistGridCell cell;
public PlaylistDragDetected(final GridView<Playlist> gridView, final ObservableList<Playlist> playlists, final PlaylistGridCell cell) {
this.gridView = gridView;
this.playlists = playlists;
this.cell = cell;
}
@Override
public void handle(final Event event) {
final Dragboard db = gridView.startDragAndDrop(TransferMode.ANY);
final ClipboardContent content = new ClipboardContent();
content.putString(String.valueOf(playlists.indexOf(cell.itemProperty().get())));
db.setContent(content);
event.consume();
}
}
private final class PlaylistMouseClicked implements EventHandler<MouseEvent> {
private final ObservableList<Playlist> playlists;
private final PlaylistGridCell cell;
public PlaylistMouseClicked(final ObservableList<Playlist> playlists, final PlaylistGridCell cell) {
this.playlists = playlists;
this.cell = cell;
}
@Override
public void handle(final MouseEvent event) {
if (2 == event.getClickCount()) {
if (playlists.equals(playlistTargetList)) {
removePlaylistFromDropzone(playlists.indexOf(cell.itemProperty().get()));
} else if (playlists.equals(playlistSourceList)) {
movePlaylistToDropzone(playlists.indexOf(cell.itemProperty().get()));
}
}
}
}
private static final class DragExitedCallback implements EventHandler<DragEvent> {
@Override
public void handle(final DragEvent event) {
((Node) event.getTarget()).getParent().getParent().getParent().getStyleClass().clear();
((Node) event.getTarget()).getParent().getParent().getParent().getStyleClass().add("dropzone");
event.consume();
}
}
private final class DragEnteredCallback implements EventHandler<DragEvent> {
@Override
public void handle(final DragEvent event) {
if ((event.getGestureSource() != event.getTarget() && event.getTarget() == playlistTargetzone || event.getTarget() == playlistSourcezone) && event
.getDragboard()
.hasString()) {
((Node) event.getTarget()).getParent().getParent().getParent().getStyleClass().clear();
((Node) event.getTarget()).getParent().getParent().getParent().getStyleClass().add("dragentered");
}
event.consume();
}
}
private final class DragDroppedCallback implements EventHandler<DragEvent> {
@Override
public void handle(final DragEvent event) {
final Dragboard db = event.getDragboard();
boolean success = false;
if (db.hasString()) {
if (((Node) event.getTarget()).getParent() == playlistTargetzone && event.getGestureSource() != playlistTargetzone) {
movePlaylistToDropzone(Integer.parseInt(db.getString()));
success = true;
} else if (((Node) event.getTarget()).getParent() == playlistSourcezone && event.getGestureSource() != playlistSourcezone) {
removePlaylistFromDropzone(Integer.parseInt(db.getString()));
success = true;
}
}
event.setDropCompleted(success);
event.consume();
}
}
private static final class DragOverCallback implements EventHandler<DragEvent> {
@Override
public void handle(final DragEvent event) {
if (event.getGestureSource() != event.getTarget() && event.getDragboard().hasString()) {
event.acceptTransferModes(TransferMode.ANY);
}
event.consume();
}
}
}