/*
GanttProject is an opensource project management tool. License: GPL3
Copyright (C) 2003-2012 GanttProject Team
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 3
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package net.sourceforge.ganttproject.document.webdav;
import io.milton.http.exceptions.NotAuthorizedException;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.List;
import java.util.concurrent.ExecutionException;
import javax.swing.Action;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.SpringLayout;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
import javax.swing.UIManager;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import net.sourceforge.ganttproject.GPLogger;
import net.sourceforge.ganttproject.IGanttProject;
import net.sourceforge.ganttproject.action.CancelAction;
import net.sourceforge.ganttproject.action.GPAction;
import net.sourceforge.ganttproject.action.OkAction;
import net.sourceforge.ganttproject.document.webdav.WebDavResource.WebDavException;
import net.sourceforge.ganttproject.gui.UIFacade;
import net.sourceforge.ganttproject.gui.UIUtil;
import net.sourceforge.ganttproject.gui.options.OptionsPageBuilder;
import net.sourceforge.ganttproject.gui.options.SpringUtilities;
import net.sourceforge.ganttproject.language.GanttLanguage;
import net.sourceforge.ganttproject.util.collect.Pair;
import org.divxdede.swing.busy.JBusyComponent;
import org.jdesktop.swingx.JXHyperlink;
import org.jdesktop.swingx.JXList;
import biz.ganttproject.core.option.BooleanOption;
import biz.ganttproject.core.option.ChangeValueEvent;
import biz.ganttproject.core.option.ChangeValueListener;
import biz.ganttproject.core.option.DefaultStringOption;
import biz.ganttproject.core.option.EnumerationOption;
import biz.ganttproject.core.option.IntegerOption;
import biz.ganttproject.core.option.ListOption;
import biz.ganttproject.core.option.StringOption;
import com.google.common.base.Objects;
import com.google.common.base.Predicate;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
/**
* UI component for WebDAV operarions.
*
* @author dbarashev (Dmitry Barashev)
*/
class GanttURLChooser {
private static final GanttLanguage language = GanttLanguage.getInstance();
private final StringOption myUsername;
private final StringOption myPassword;
private final StringOption myPath;
private final IntegerOption myTimeout;
private SelectionListener mySelectionListener;
private final ListOption<WebDavServerDescriptor> myServers;
private final MiltonResourceFactory myWebDavFactory;
private final GPAction myReloadAction = new GPAction("fileChooser.reload") {
@Override
public void actionPerformed(ActionEvent event) {
if (!isEnabled()) {
return;
}
reloadFilesTable();
}
};
private final GPAction myUpAction = new GPAction("fileChooser.up") {
@Override
public void actionPerformed(ActionEvent event) {
try {
WebDavResource collection = tableModel.getCollection();
WebDavResource parent = collection.getParent();
if (parent != null && parent.exists() && parent.isCollection()) {
new ReloadWorker(parent).execute();
myPath.setValue(new URL(parent.getUrl()).getPath());
}
} catch (WebDavException e) {
showError(e);
} catch (MalformedURLException e) {
showError(e);
}
}
};
private final GPAction myLockAction = new GPAction("fileChooser.lock") {
@Override
public void actionPerformed(ActionEvent evt) {
WebDavResource resource = getSelectedResource();
if (resource == null) {
return;
}
try {
if (!resource.isLocked()) {
resource.lock(myTimeout.getValue() * 60);
reloadFilesTable();
}
} catch (WebDavException e) {
showError(e);
}
}
};
private final GPAction myUnlockAction = new GPAction("fileChooser.unlock") {
@Override
public void actionPerformed(ActionEvent evt) {
WebDavResource resource = getSelectedResource();
if (resource == null) {
return;
}
try {
if (resource.isLocked()) {
resource.unlock();
reloadFilesTable();
}
} catch (WebDavException e) {
showError(e);
}
}
};
private final GPAction myDeleteAction = new GPAction("fileChooser.delete") {
@Override
public void actionPerformed(ActionEvent evt) {
final WebDavResource resource = getSelectedResource();
if (resource == null) {
return;
}
myUiFacade.showOptionDialog(JOptionPane.QUESTION_MESSAGE, GanttLanguage.getInstance().getText("fileChooser.delete.question"), new Action[] {
new OkAction() {
@Override
public void actionPerformed(ActionEvent evt) {
try {
resource.delete();
} catch (WebDavException e) {
showError(e);
}
}
}, CancelAction.EMPTY
});
}
};
private final FilesTableModel tableModel = new FilesTableModel();
private JXList table;
private JButton myLockButton;
private final UIFacade myUiFacade;
private final IGanttProject myProject;
private JScrollPane myFilesComponent;
private WebDavUri myInitialUri;
private JLabel myPasswordLabel;
private OkAction myOkAction;
private final List<Runnable> myListenerRemovers = Lists.newArrayList();
private JBusyComponent<JComponent> myBusyComponent;
static interface SelectionListener {
public void setSelection(WebDavResource resource);
}
GanttURLChooser(IGanttProject project, UIFacade uiFacade, ListOption<WebDavServerDescriptor> servers, WebDavUri currentUri, StringOption username,
StringOption password, IntegerOption lockTimeoutOption, BooleanOption releaseLockOption,
MiltonResourceFactory webDavFactory) {
myProject = project;
myUiFacade = uiFacade;
myWebDavFactory = webDavFactory;
myPath = new DefaultStringOption("path");
myServers = servers;
myUsername = username;
myPassword = password;
myTimeout = lockTimeoutOption;
myInitialUri = currentUri;
myListenerRemovers.add(myServers.addChangeValueListener(new ChangeValueListener() {
@Override
public void changeValue(ChangeValueEvent event) {
myPath.setValue("");
updateUsernameAndPassword();
myReloadAction.actionPerformed(null);
}
}));
myListenerRemovers.add(myUsername.addChangeValueListener(new ChangeValueListener() {
@Override
public void changeValue(ChangeValueEvent event) {
if (myServers.getValue() != null) {
myServers.getValue().username = myUsername.getValue();
}
}
}));
myListenerRemovers.add(myPassword.addChangeValueListener(new ChangeValueListener() {
@Override
public void changeValue(ChangeValueEvent event) {
if (myServers.getValue() != null) {
myServers.getValue().password = myPassword.getValue();
}
}
}));
}
class ReloadWorker extends SwingWorker<Pair<WebDavResource, List<WebDavResource>>, Object> {
private WebDavResource myResource;
public ReloadWorker(WebDavResource resource) {
myResource = resource;
setProgressBar(true);
}
@Override
protected Pair<WebDavResource, List<WebDavResource>> doInBackground() throws Exception {
try {
WebDavResource resource = myResource;
if (resource.exists() && resource.isCollection()) {
return Pair.create(resource, readChildren(resource));
}
WebDavResource parent = resource.getParent();
if (parent.exists() && parent.isCollection()) {
return Pair.create(parent, readChildren(parent));
}
return null;
} catch (WebDavException e) {
showError(e);
return null;
}
}
private List<WebDavResource> readChildren(WebDavResource parent) throws WebDavException {
List<WebDavResource> children = Lists.newArrayList();
for (WebDavResource child : parent.getChildResources()) {
try {
if (child.exists()) {
children.add(child);
}
}
catch (WebDavException e) {
GPLogger.logToLogger(e);
}
}
return children;
}
@Override
protected void done() {
try {
Pair<WebDavResource, List<WebDavResource>> result = get();
if (result != null) {
tableModel.setCollection(result.first(), result.second());
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ExecutionException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
setProgressBar(false);
}
}
};
protected void reloadFilesTable() {
myWebDavFactory.clearCache();
myWebDavFactory.setCredentials(myUsername.getValue(), myPassword.getValue());
WebDavUri webDavUri = buildUrl();
if (webDavUri != null) {
new ReloadWorker(myWebDavFactory.createResource(webDavUri)).execute();
}
}
private boolean tryApplyUrl(WebDavUri currentUri) {
WebDavServerDescriptor savedServer = findSavedServer(currentUri.buildRootUrl());
if (savedServer == null) {
WebDavServerDescriptor server = new WebDavServerDescriptor(
Strings.isNullOrEmpty(currentUri.hostName) ? currentUri.hostUrl : currentUri.hostName, currentUri.buildRootUrl(), "");
myServers.addValue(server);
myServers.setValue(server);
} else if (!savedServer.equals(myServers.getValue())) {
myServers.setValue(savedServer);
}
myPath.setValue(currentUri.path);
return true;
}
private WebDavServerDescriptor findSavedServer(String domainUrl) {
for (WebDavServerDescriptor server : myServers.getValues()) {
if (server.getRootUrl().equals(domainUrl)) {
return server;
}
}
return null;
}
public JComponent createOpenDocumentUi(OkAction openAction) {
myOkAction = openAction;
return createComponent();
}
public JComponent createSaveDocumentUi(OkAction saveAction) {
myOkAction = saveAction;
return createComponent();
}
private WebDavUri buildUrl() {
WebDavServerDescriptor server= myServers.getValue();
if (server == null) {
return null;
}
String host = server.getRootUrl().trim();
while (host.endsWith("/")) {
host = host.substring(0, host.length() - 1);
}
String path = Objects.firstNonNull(myPath.getValue(), "");
if (!path.startsWith("/")) {
path = "/" + path;
}
return new WebDavUri(server.name, host, path);
}
private WebDavResource getSelectedResource() {
return (WebDavResource) table.getSelectedValue();
}
private JComponent createComponent() {
OptionsPageBuilder builder = new OptionsPageBuilder();
JPanel panel = new JPanel(new SpringLayout());
panel.add(new JLabel(language.getCorrectedLabel("webServer")));
EnumerationOption serverChoiceOption = myServers.asEnumerationOption();
panel.add(builder.createOptionComponent(null, serverChoiceOption));
panel.add(new JLabel());
panel.add(createUsernamePasswordPanel());
panel.add(new JLabel(language.getCorrectedLabel("fileFromServer")));
panel.add(builder.createOptionComponent(null, myPath));
myPath.addChangeValueListener(new ChangeValueListener() {
@Override
public void changeValue(ChangeValueEvent event) {
String value = (String) event.getNewValue();
if (value == null) {
return;
}
String lcValue = value.toLowerCase();
if (lcValue.startsWith("http://") || lcValue.startsWith("https://")) {
if (!tryApplyUrl(new WebDavUri(value))) {
myUpAction.setEnabled(value.split("/").length > 1);
}
}
}
});
addEmptyRow(panel);
{
JPanel filesTablePanel = new JPanel(new BorderLayout());
JButton refreshButton = new JButton(myReloadAction);
JButton upButton = new JButton(myUpAction);
myLockButton = new JButton(myLockAction);
panel.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(myUpAction.getKeyStroke(), myUpAction);
panel.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(myReloadAction.getKeyStroke(), myReloadAction);
panel.getActionMap().put(myUpAction, myUpAction);
panel.getActionMap().put(myReloadAction, myReloadAction);
upButton.setText("");
refreshButton.setText("");
Box filesHeaderBox = Box.createVerticalBox();
filesHeaderBox.add(upButton);
filesHeaderBox.add(Box.createVerticalStrut(5));
filesHeaderBox.add(refreshButton);
filesHeaderBox.add(Box.createVerticalGlue());
filesHeaderBox.add(new JButton(myDeleteAction));
filesHeaderBox.add(Box.createVerticalStrut(5));
filesHeaderBox.add(myLockButton);
filesTablePanel.add(filesHeaderBox, BorderLayout.EAST);
filesHeaderBox.setBorder(BorderFactory.createEmptyBorder(0, 3, 0, 0));
table = new JXList(tableModel);
table.setHighlighters(UIUtil.ZEBRA_HIGHLIGHTER);
table.setCellRenderer(new FilesCellRenderer());
table.addListSelectionListener(new ListSelectionListener() {
@Override
public void valueChanged(ListSelectionEvent e) {
WebDavResource resource = getSelectedResource();
if (resource == null) {
return;
}
onSelectionChanged(resource);
}
});
table.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
if (e.getClickCount() == 2) {
tryEnterCollection(table, tableModel);
}
}
});
table.addKeyListener(new KeyAdapter() {
@Override
public void keyReleased(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
tryEnterCollection(table, tableModel);
}
}
});
myFilesComponent = new JScrollPane(table);
myFilesComponent.setMaximumSize(table.getPreferredScrollableViewportSize());
filesTablePanel.add(myFilesComponent, BorderLayout.CENTER);
panel.add(new JLabel(language.getText("fileChooser.fileList")));
panel.add(filesTablePanel);
}
addEmptyRow(panel);
SpringUtilities.makeCompactGrid(panel, 6, 2, 0, 0, 10, 5);
panel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
panel.setMaximumSize(new Dimension(Integer.MAX_VALUE, panel.getPreferredSize().height));
Box properties = Box.createVerticalBox();
properties.add(panel);
properties.add(Box.createVerticalGlue());
myBusyComponent = new JBusyComponent<JComponent>(properties);
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
if (myInitialUri != null) {
tryApplyUrl(myInitialUri);
}
reloadFilesTable();
}
});
return myBusyComponent;
}
private void setProgressBar(boolean b) {
myBusyComponent.setBusy(b);
}
private Component createUsernamePasswordPanel() {
JPanel grid = new JPanel(new GridLayout(3, 2));
grid.add(new JLabel(language.getText("userName")));
final JLabel username = new JLabel(myUsername.getValue());
myUsername.addChangeValueListener(new ChangeValueListener() {
@Override
public void changeValue(ChangeValueEvent event) {
username.setText(myUsername.getValue() == null ? "" : myUsername.getValue());
}
});
grid.add(username);
grid.add(new JLabel(language.getText("password")));
myPasswordLabel = new JLabel(myPassword.getValue() == null ? "" : Strings.repeat("*", myPassword.getValue().length()));
myPassword.addChangeValueListener(new ChangeValueListener() {
@Override
public void changeValue(ChangeValueEvent event) {
UIUtil.clearErrorLabel(myPasswordLabel);
myPasswordLabel.setText(
myPassword.getValue() == null ? "" : Strings.repeat("*", myPassword.getValue().length()));
}
});
grid.add(myPasswordLabel);
grid.add(new JXHyperlink(new GPAction("webdav.configure") {
@Override
public void actionPerformed(ActionEvent arg0) {
WebDavOptionPageProvider optionPage = new WebDavOptionPageProvider();
optionPage.init(myProject, myUiFacade);
myUiFacade.createDialog(optionPage.buildPageComponent(), new Action[] {CancelAction.CLOSE}, "").show();
updateUsernameAndPassword();
myReloadAction.actionPerformed(null);
}
}));
UIUtil.walkComponentTree(grid, new Predicate<JComponent>() {
@Override
public boolean apply(JComponent input) {
input.setFont(input.getFont().deriveFont(input.getFont().getSize()*0.82f));
return true;
}
});
Box result = Box.createHorizontalBox();
result.add(grid);
result.add(Box.createHorizontalGlue());
return grid;
}
private void updateUsernameAndPassword() {
WebDavServerDescriptor server = myServers.getValue();
myUsername.setValue(server == null ? "" : server.username);
myPassword.setValue(server == null ? "" : server.password);
setWebDavActionsEnabled(
!Strings.isNullOrEmpty(myUsername.getValue()) && !Strings.isNullOrEmpty(myPassword.getValue()));
}
private void setWebDavActionsEnabled(boolean value) {
myDeleteAction.setEnabled(value);
myLockAction.setEnabled(value);
myReloadAction.setEnabled(value);
myUpAction.setEnabled(value);
myOkAction.setEnabled(value);
}
protected void onSelectionChanged(WebDavResource resource) {
if (mySelectionListener != null) {
mySelectionListener.setSelection(resource);
}
myPath.setValue(resource.getWebDavUri().path);
try {
if (resource.isLocked()) {
myLockButton.setAction(myUnlockAction);
} else {
myLockButton.setAction(myLockAction);
}
} catch (WebDavException e) {
GPLogger.logToLogger(e);
}
}
protected void tryEnterCollection(JXList table, FilesTableModel tableModel) {
WebDavResource resource = (WebDavResource) table.getSelectedValue();
try {
if (resource.isCollection()) {
new ReloadWorker(resource).execute();
}
} catch (WebDavException e) {
showError(e);
}
}
private static void addEmptyRow(JPanel form) {
form.add(Box.createRigidArea(new Dimension(1, 10)));
form.add(Box.createRigidArea(new Dimension(1, 10)));
}
WebDavUri getUrl() {
return buildUrl();
}
String getUsername() {
return myUsername.getValue();
}
String getPassword() {
return myPassword.getValue();
}
int getLockTimeout() {
return myTimeout.getValue();
}
void setSelectionListener(SelectionListener selectionListener) {
mySelectionListener = selectionListener;
}
StringOption getPathOption() {
return myPath;
}
void showError(Exception e) {
if (e.getCause() instanceof NotAuthorizedException) {
UIUtil.setupErrorLabel(myPasswordLabel, "Access denied");
setWebDavActionsEnabled(false);
}
GPLogger.log(e);
}
public void dispose() {
for (Runnable remover : myListenerRemovers) {
remover.run();
}
}
}