package org.syncany.gui.wizard;
import java.io.File;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.VerifyEvent;
import org.eclipse.swt.events.VerifyListener;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.DirectoryDialog;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.FileDialog;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Text;
import org.syncany.gui.Panel;
import org.syncany.gui.util.I18n;
import org.syncany.gui.util.SWTResourceManager;
import org.syncany.gui.util.WidgetDecorator;
import org.syncany.gui.wizard.PluginSettingsPanelOAuthHelper.Consumer;
import org.syncany.plugins.transfer.FileType;
import org.syncany.plugins.transfer.StorageException;
import org.syncany.plugins.transfer.TransferPlugin;
import org.syncany.plugins.transfer.TransferPluginOption;
import org.syncany.plugins.transfer.TransferPluginOption.ValidationResult;
import org.syncany.plugins.transfer.TransferPluginOptions;
import org.syncany.plugins.transfer.TransferSettings;
/**
* @author Philipp C. Heckel <philipp.heckel@gmail.com>
*/
public class PluginSettingsPanel extends Panel {
private static final Logger logger = Logger.getLogger(PluginSettingsPanel.class.getSimpleName());
private Label warningImageLabel;
private Label warningMessageLabel;
private TransferPlugin plugin;
private TransferSettings pluginSettings;
private static PluginSettingsPanelOAuthHelper pluginSettingsPanelOAuthHelper;
private Map<TransferPluginOption, Control> pluginOptionControlMap;
private Set<TransferPluginOption> invalidPluginOptions;
public PluginSettingsPanel(WizardDialog wizardParentDialog, Composite parent, int style) {
super(wizardParentDialog, parent, style);
}
@Override
public void dispose() {
logger.log(Level.INFO, "PluginSettingsPanel is about to get disposed, resetting OAuthhelper");
resetOAuthHelper();
}
public void init(TransferPlugin plugin) {
setPlugin(plugin);
resetOAuthHelper();
clearControls();
createControls();
}
private void setPlugin(TransferPlugin plugin) {
try {
this.plugin = plugin;
this.pluginSettings = plugin.createEmptySettings();
this.pluginOptionControlMap = new HashMap<>();
this.invalidPluginOptions = new HashSet<>();
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
private void clearControls() {
for (Control childComponent : getChildren()) {
childComponent.dispose();
}
}
private void resetOAuthHelper() {
if (pluginSettingsPanelOAuthHelper != null) {
pluginSettingsPanelOAuthHelper.reset(false);
pluginSettingsPanelOAuthHelper = null;
}
}
private void createControls() {
List<TransferPluginOption> pluginOptions = TransferPluginOptions.getOrderedOptions(pluginSettings.getClass());
// Main composite
GridLayout mainCompositeGridLayout = new GridLayout(3, false);
mainCompositeGridLayout.marginTop = 15;
mainCompositeGridLayout.marginLeft = 10;
mainCompositeGridLayout.marginRight = 20;
setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
setLayout(mainCompositeGridLayout);
// Title and description
Label titleLabel = new Label(this, SWT.WRAP);
titleLabel.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, true, false, 3, 1));
titleLabel.setText(I18n.getText("org.syncany.gui.wizard.PluginSettingsPanel.title", plugin.getName()));
WidgetDecorator.title(titleLabel);
// Create OAuth controls (if any)
createOAuthControls();
// Create fields
for (TransferPluginOption pluginOption : pluginOptions) {
if (pluginOption.isVisible()) {
createPluginOptionControl(pluginOption);
}
}
// Warning message and label
String warningImageResource = "/" + WizardDialog.class.getPackage().getName().replace(".", "/") + "/warning-icon.png";
Image warningImage = SWTResourceManager.getImage(warningImageResource);
warningImageLabel = new Label(this, SWT.NONE);
warningImageLabel.setImage(warningImage);
warningImageLabel.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, 1, 1));
warningImageLabel.setVisible(false);
warningMessageLabel = new Label(this, SWT.WRAP);
warningMessageLabel.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false, 2, 1));
warningMessageLabel.setVisible(false);
WidgetDecorator.bold(warningMessageLabel);
pack();
}
private void createOAuthControls() {
resetOAuthHelper();
PluginSettingsPanelOAuthHelper.Builder builder;
try {
builder = PluginSettingsPanelOAuthHelper.forSettings(pluginSettings);
}
catch (UnsupportedOperationException e) {
// ok plugin does not support oauth
return;
}
// OAuth help text
Label descriptionLabel = new Label(this, SWT.WRAP);
descriptionLabel.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, true, false, 3, 1));
descriptionLabel.setText(I18n.getText("org.syncany.gui.wizard.PluginSettingsPanel.oauth.description"));
WidgetDecorator.normal(descriptionLabel);
// Label "Token:"
GridData oAuthTokenLabelGridData = new GridData(SWT.LEFT, SWT.CENTER, false, false);
oAuthTokenLabelGridData.verticalIndent = 2;
oAuthTokenLabelGridData.horizontalSpan = 3;
Label oAuthTokenLabel = new Label(this, SWT.WRAP);
oAuthTokenLabel.setLayoutData(oAuthTokenLabelGridData);
oAuthTokenLabel.setText(I18n.getText("org.syncany.gui.wizard.PluginSettingsPanel.oauth.token"));
// Textfield "Token"
GridData oAuthTokenTextGridData = new GridData(SWT.FILL, SWT.CENTER, true, false);
oAuthTokenTextGridData.verticalIndent = 0;
oAuthTokenTextGridData.horizontalSpan = 2;
oAuthTokenTextGridData.minimumWidth = 200;
oAuthTokenTextGridData.grabExcessHorizontalSpace = true;
// Do not manage contents of the following GUI items, done by OAuthInformationManager
Text oAuthTokenText = new Text(this, SWT.BORDER);
oAuthTokenText.setLayoutData(oAuthTokenTextGridData);
oAuthTokenText.setBackground(WidgetDecorator.WHITE);
// Add 'Authorize ..' button for 'File' fields
Button oAuthAuthorizeButton = new Button(this, SWT.NONE);
oAuthAuthorizeButton.setText(I18n.getText("org.syncany.gui.wizard.PluginSettingsPanel.oauth.button.connecting")); // needs text for size
try {
pluginSettingsPanelOAuthHelper = builder
.withWarningHandler(new WarningHandler())
.withButton(oAuthAuthorizeButton)
.withText(oAuthTokenText)
.build();
}
catch (Exception e) {
throw new RuntimeException(e);
}
pluginSettingsPanelOAuthHelper.start();
}
private void createPluginOptionControl(final TransferPluginOption pluginOption) {
Field pluginField = pluginOption.getField();
// Label "Option X:"
GridData pluginOptionLabelGridData = new GridData(SWT.LEFT, SWT.CENTER, false, false);
pluginOptionLabelGridData.verticalIndent = 2;
pluginOptionLabelGridData.horizontalSpan = 3;
String pluginOptionLabelText = pluginOption.getDescription();
if (pluginOption.isSensitive()) {
pluginOptionLabelText += " " + ((pluginOption.isRequired())
? I18n.getText("org.syncany.gui.wizard.PluginSettingsPanel.pluginOptionLabelExt.notDisplayed")
: I18n.getText("org.syncany.gui.wizard.PluginSettingsPanel.pluginOptionLabelExt.notDisplayedOptional"));
}
else {
pluginOptionLabelText += (pluginOption.isRequired()) ? "" : " " + I18n.getText("org.syncany.gui.wizard.PluginSettingsPanel.pluginOptionLabelExt.optional");
}
Label pluginOptionLabel = new Label(this, SWT.WRAP);
pluginOptionLabel.setLayoutData(pluginOptionLabelGridData);
pluginOptionLabel.setText(pluginOptionLabelText);
Control pluginOptionControl;
if (pluginField.getType() == File.class) {
pluginOptionControl = createPluginOptionFileControl(pluginOption, pluginField);
}
else if (pluginField.getType() instanceof Class && ((Class<?>) pluginField.getType()).isEnum()) {
pluginOptionControl = createPluginOptionEnumControl(pluginOption, pluginField);
}
else {
pluginOptionControl = createPluginOptionTextControl(pluginOption, pluginField);
}
// Set cache
pluginOptionControlMap.put(pluginOption, pluginOptionControl);
}
private Control createPluginOptionFileControl(TransferPluginOption pluginOption, Field pluginField) {
// Create controls
Text pluginOptionValueText = createPluginOptionTextField(pluginOption, pluginField, 2);
createPluginOptionFileSelectButton(pluginOption, pluginOptionValueText);
return pluginOptionValueText;
}
private Control createPluginOptionEnumControl(TransferPluginOption pluginOption, Field pluginField) {
Combo pluginOptionCombo = new Combo(this, SWT.DROP_DOWN | SWT.BORDER | SWT.READ_ONLY);
pluginOptionCombo.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 2, 1));
for (Object enumValue : pluginField.getType().getEnumConstants()) {
pluginOptionCombo.add(enumValue.toString());
}
setPluginOptionEnumModifyListener(pluginOption, pluginOptionCombo);
selectPluginOptionEnumDefault(pluginOption, pluginField, pluginOptionCombo);
modifyPluginOptionEnum(pluginOption, pluginOptionCombo);
return pluginOptionCombo;
}
private void selectPluginOptionEnumDefault(TransferPluginOption pluginOption, Field pluginField, Combo pluginOptionCombo) {
try {
pluginField.setAccessible(true);
Object pluginFieldValue = pluginField.get(pluginSettings);
int pluginOptionComboIndex = getPluginOptionComboIndex(pluginOptionCombo, pluginFieldValue);
pluginOptionCombo.select(pluginOptionComboIndex);
}
catch (IllegalArgumentException | IllegalAccessException e) {
logger.log(Level.WARNING, "Could not extract the default value for the Enum. Selecting first value.");
pluginOptionCombo.select(0);
}
}
private int getPluginOptionComboIndex(Combo pluginOptionCombo, Object pluginFieldValue) {
if (pluginFieldValue != null) {
for (int i = 0; i < pluginOptionCombo.getItemCount(); i++) {
String comboEnumValue = pluginOptionCombo.getItem(i);
if (comboEnumValue.equals(pluginFieldValue.toString())) {
return i;
}
}
return 0;
}
else {
return 0;
}
}
private void setPluginOptionEnumModifyListener(final TransferPluginOption pluginOption, final Combo pluginOptionCombo) {
pluginOptionCombo.addModifyListener(new ModifyListener() {
@Override
public void modifyText(ModifyEvent e) {
modifyPluginOptionEnum(pluginOption, pluginOptionCombo);
}
});
}
private Control createPluginOptionTextControl(TransferPluginOption pluginOption, Field pluginField) {
return createPluginOptionTextField(pluginOption, pluginField, 3);
}
private Text createPluginOptionTextField(TransferPluginOption pluginOption, Field pluginField, int horizontalSpan) {
// Textfield "Option X"
GridData optionValueTextGridData = new GridData(SWT.FILL, SWT.CENTER, true, false);
optionValueTextGridData.verticalIndent = 0;
optionValueTextGridData.horizontalSpan = horizontalSpan;
optionValueTextGridData.minimumWidth = 200;
optionValueTextGridData.grabExcessHorizontalSpace = true;
int optionValueTextStyle = (pluginOption.isSensitive()) ? SWT.BORDER | SWT.PASSWORD : SWT.BORDER;
Text pluginOptionValueText = new Text(this, optionValueTextStyle);
pluginOptionValueText.setLayoutData(optionValueTextGridData);
pluginOptionValueText.setBackground(WidgetDecorator.WHITE);
setPluginOptionTextFieldDefaultValue(pluginOptionValueText, pluginField);
setPluginOptionTextFieldModifyListener(pluginOption, pluginOptionValueText);
setPluginOptionTextFieldVerifyListener(pluginOption, pluginOptionValueText);
WidgetDecorator.normal(pluginOptionValueText);
return pluginOptionValueText;
}
private Button createPluginOptionFileSelectButton(TransferPluginOption pluginOption, Text pluginOptionValueText) {
Button pluginOptionFileSelectButton = new Button(this, SWT.NONE);
pluginOptionFileSelectButton.setText(I18n.getText("org.syncany.gui.wizard.PluginSettingsPanel.selectFile"));
setPluginOptionFileSelectListener(pluginOption, pluginOptionValueText, pluginOptionFileSelectButton);
return pluginOptionFileSelectButton;
}
private void setPluginOptionFileSelectListener(final TransferPluginOption pluginOption, final Text pluginOptionValueText,
final Button pluginOptionFileSelectButton) {
pluginOptionFileSelectButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
onSelectFileClick(pluginOption, pluginOptionValueText);
}
});
}
private void setPluginOptionTextFieldDefaultValue(Text pluginOptionValueText, Field pluginField) {
try {
String defaultValue = pluginSettings.getField(pluginField.getName());
if (defaultValue != null && !defaultValue.isEmpty()) {
pluginOptionValueText.setText(defaultValue);
}
}
catch (StorageException e) {
throw new RuntimeException("Error creating controls.", e);
}
}
private void setPluginOptionTextFieldModifyListener(final TransferPluginOption pluginOption, final Text pluginOptionValueText) {
pluginOptionValueText.addModifyListener(new ModifyListener() {
@Override
public void modifyText(ModifyEvent e) {
modifyPluginOptionText(pluginOption, pluginOptionValueText);
}
});
}
private void modifyPluginOptionText(TransferPluginOption pluginOption, Text pluginOptionValueText) {
// Get value (empty is null)
String pluginOptionValue = pluginOptionValueText.getText();
if ("".equals(pluginOptionValue)) {
pluginOptionValue = null;
}
try {
// Set field (at least try to; fails if type mismatches)
if (!pluginOption.isSensitive()) {
logger.log(Level.INFO, "Setting field '" + pluginOption.getName() + "' with value '" + pluginOptionValue + "'");
}
pluginSettings.setField(pluginOption.getField().getName(), pluginOptionValue);
// Validate value (fails if content mismatches)
ValidationResult validationResult = pluginOption.isValid(pluginOptionValue);
switch (validationResult) {
case INVALID_NOT_SET:
if (pluginOption.isRequired()) {
invalidPluginOptions.add(pluginOption);
WidgetDecorator.markAsInvalid(pluginOptionValueText);
}
else {
invalidPluginOptions.remove(pluginOption);
WidgetDecorator.markAsValid(pluginOptionValueText);
}
break;
case INVALID_TYPE:
logger.log(Level.WARNING, " Invalid type in field '" + pluginOption.getName() + "'. This should be caught by verify listener!");
invalidPluginOptions.add(pluginOption);
WidgetDecorator.markAsInvalid(pluginOptionValueText);
break;
case VALID:
invalidPluginOptions.remove(pluginOption);
WidgetDecorator.markAsValid(pluginOptionValueText);
break;
}
}
catch (StorageException e) {
if (!pluginOption.isSensitive()) {
logger.log(Level.WARNING, "Cannot set field '" + pluginOption.getName() + "' with value '" + pluginOptionValue + "'", e);
}
else {
logger.log(Level.WARNING, "Cannot set field '" + pluginOption.getName() + "' with sensitive value.");
}
invalidPluginOptions.add(pluginOption);
WidgetDecorator.markAsInvalid(pluginOptionValueText);
}
}
private void modifyPluginOptionEnum(TransferPluginOption pluginOption, Combo pluginOptionCombo) {
try {
if (!pluginOption.isSensitive()) {
logger.log(Level.INFO, "Setting field '" + pluginOption.getName() + "' with value '" + pluginOptionCombo.getText() + "'");
}
pluginSettings.setField(pluginOption.getField().getName(), pluginOptionCombo.getText());
}
catch (StorageException e) {
throw new RuntimeException("Cannot set field '" + pluginOption.getName() + "' with value '" + pluginOptionCombo.getText()
+ "'. This is an ENUM, so this should not happen.", e);
}
}
private void setPluginOptionTextFieldVerifyListener(final TransferPluginOption pluginOption, final Text pluginOptionValueText) {
pluginOptionValueText.addVerifyListener(new VerifyListener() {
@Override
public void verifyText(VerifyEvent e) {
Text text = (Text) e.getSource();
// Get old text and create new text by using the VerifyEvent.text
final String oldValue = text.getText();
String newValue = oldValue.substring(0, e.start) + e.text + oldValue.substring(e.end);
// Validate correct type
ValidationResult validationResult = pluginOption.isValid(newValue);
e.doit = newValue.isEmpty() || validationResult != ValidationResult.INVALID_TYPE;
}
});
}
private void onSelectFileClick(TransferPluginOption pluginOption, Text pluginOptionValueText) {
if (pluginOption.getFileType() == FileType.FILE) {
String filterPath = new File(pluginOptionValueText.getText()).getParent();
FileDialog fileDialog = new FileDialog(getShell(), SWT.OPEN);
fileDialog.setFilterExtensions(new String[]{"*.*"});
fileDialog.setFilterPath(filterPath);
String selectedFile = fileDialog.open();
if (selectedFile != null && selectedFile.length() > 0) {
pluginOptionValueText.setText(selectedFile);
}
}
else {
DirectoryDialog directoryDialog = new DirectoryDialog(getShell());
directoryDialog.setFilterPath(pluginOptionValueText.getText());
String selectedFolder = directoryDialog.open();
if (selectedFolder != null && selectedFolder.length() > 0) {
pluginOptionValueText.setText(selectedFolder);
}
}
}
@Override
public boolean validatePanel() {
hideWarning();
logger.log(Level.INFO, "Validating settings panel ...");
// Validation order is important, because the validate*() methods
// also mark fields 'red'. Also: OAuth needs to be before
// cross-field dependencies!
boolean individualFieldsValid = validateIndividualFields();
boolean oAuthFieldsValid = validateOAuthToken();
return individualFieldsValid && oAuthFieldsValid && validateFieldDependencies();
}
private boolean validateIndividualFields() {
logger.log(Level.INFO, " - Validating individual fields ...");
for (Map.Entry<TransferPluginOption, Control> optionControlEntry : pluginOptionControlMap.entrySet()) {
TransferPluginOption pluginOption = optionControlEntry.getKey();
Control pluginOptionControl = optionControlEntry.getValue();
if (pluginOptionControl instanceof Text) {
modifyPluginOptionText(pluginOption, (Text) pluginOptionControl);
}
}
boolean validFields = invalidPluginOptions.size() == 0;
if (validFields) {
return true;
}
else {
showWarning(I18n.getText("org.syncany.gui.wizard.PluginSettingsPanel.errorFieldValidation"));
return false;
}
}
private boolean validateFieldDependencies() {
logger.log(Level.INFO, " - Validating field dependencies ...");
try {
pluginSettings.validateRequiredFields();
logger.log(Level.INFO, "Validation succeeded on panel.");
return true;
}
catch (StorageException e) {
showWarning(e.getMessage());
logger.log(Level.WARNING, "Validate error on panel.", e);
return false;
}
}
private boolean validateOAuthToken() {
return pluginSettingsPanelOAuthHelper == null || pluginSettingsPanelOAuthHelper.isSuccess();
}
private void showWarning(String warningStr) {
warningImageLabel.setVisible(true);
warningMessageLabel.setVisible(true);
warningMessageLabel.setText(warningStr);
}
private void hideWarning() {
warningImageLabel.setVisible(false);
warningMessageLabel.setVisible(false);
}
public TransferSettings getPluginSettings() {
return pluginSettings;
}
private class WarningHandler implements Consumer<String> {
@Override
public void accept(final String warning) {
Display.getDefault().asyncExec(new Runnable() {
@Override
public void run() {
PluginSettingsPanel.this.showWarning(warning);
}
});
}
}
}