PL-10579 Implement desktop Image component

This commit is contained in:
Daniil Tsarev 2018-03-27 11:18:11 +04:00
parent 3aae2bcf08
commit acfdc9deca
10 changed files with 888 additions and 0 deletions

View File

@ -89,6 +89,7 @@ public class DesktopComponentsFactory implements ComponentsFactory {
classes.put(FileMultiUploadField.NAME, DesktopFileMultiUploadField.class);
classes.put(TabSheet.NAME, DesktopTabSheet.class);
classes.put(Embedded.NAME, DesktopEmbedded.class);
classes.put(Image.NAME, DesktopImage.class);
classes.put(WidgetsTree.NAME, DesktopWidgetsTree.class);
classes.put(GroupBoxLayout.NAME, DesktopGroupBox.class);
classes.put(ProgressBar.NAME, DesktopProgressBar.class);

View File

@ -0,0 +1,53 @@
/*
* Copyright (c) 2008-2018 Haulmont.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.haulmont.cuba.desktop.gui.components;
import java.awt.image.BufferedImage;
public abstract class DesktopAbstractResource implements DesktopResource {
protected BufferedImage resource;
protected Runnable resourceUpdateHandler;
protected boolean hasSource;
@Override
public BufferedImage getResource() {
if (resource == null) {
createResource();
}
return resource;
}
protected boolean hasSource() {
return hasSource;
}
protected void fireResourceUpdateEvent() {
resource = null;
if (resourceUpdateHandler != null) {
resourceUpdateHandler.run();
}
}
protected void setResourceUpdatedHandler(Runnable resourceUpdated) {
this.resourceUpdateHandler = resourceUpdated;
}
protected abstract void createResource();
}

View File

@ -0,0 +1,60 @@
/*
* Copyright (c) 2008-2018 Haulmont.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.haulmont.cuba.desktop.gui.components;
import com.haulmont.cuba.gui.components.ResourceView;
public abstract class DesktopAbstractStreamSettingsResource extends DesktopAbstractResource
implements ResourceView.HasStreamSettings {
// just stub
protected long cacheTime = 1000 * 60 * 60 * 24;
// just stub
protected int bufferSize;
// just stub
protected String fileName;
@Override
public void setCacheTime(long cacheTime) {
this.cacheTime = cacheTime;
}
@Override
public long getCacheTime() {
return cacheTime;
}
@Override
public void setBufferSize(int bufferSize) {
this.bufferSize = bufferSize;
}
@Override
public int getBufferSize() {
return bufferSize;
}
@Override
public void setFileName(String fileName) {
this.fileName = fileName;
}
@Override
public String getFileName() {
return fileName;
}
}

View File

@ -0,0 +1,76 @@
/*
* Copyright (c) 2008-2018 Haulmont.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.haulmont.cuba.desktop.gui.components;
import com.haulmont.bali.util.Preconditions;
import com.haulmont.cuba.core.global.AppBeans;
import com.haulmont.cuba.core.global.Resources;
import com.haulmont.cuba.gui.components.ClasspathResource;
import javax.imageio.ImageIO;
import java.io.IOException;
import java.io.InputStream;
public class DesktopClasspathResource extends DesktopAbstractStreamSettingsResource
implements DesktopResource, ClasspathResource {
protected String path;
// just stub
protected String mimeType;
@Override
public ClasspathResource setPath(String path) {
Preconditions.checkNotNullArgument(path);
this.path = path;
hasSource = true;
fireResourceUpdateEvent();
return this;
}
@Override
public String getPath() {
return path;
}
@Override
protected void createResource() {
try {
InputStream stream = AppBeans.get(Resources.class).getResourceAsStream(path);
if (stream == null) {
resource = null;
return;
}
resource = ImageIO.read(stream);
} catch (IOException e) {
throw new RuntimeException("An error occurred while loading an image.", e);
}
}
@Override
public void setMimeType(String mimeType) {
this.mimeType = mimeType;
}
@Override
public String getMimeType() {
return mimeType;
}
}

View File

@ -0,0 +1,82 @@
/*
* Copyright (c) 2008-2018 Haulmont.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.haulmont.cuba.desktop.gui.components;
import com.haulmont.bali.util.Preconditions;
import com.haulmont.cuba.core.app.FileStorageService;
import com.haulmont.cuba.core.entity.FileDescriptor;
import com.haulmont.cuba.core.global.AppBeans;
import com.haulmont.cuba.core.global.FileStorageException;
import com.haulmont.cuba.gui.components.FileDescriptorResource;
import com.haulmont.cuba.gui.export.ByteArrayDataProvider;
import javax.imageio.ImageIO;
import java.io.IOException;
public class DesktopFileDescriptorResource extends DesktopAbstractStreamSettingsResource
implements DesktopResource, FileDescriptorResource {
protected static final String FILE_STORAGE_EXCEPTION_MESSAGE = "Can't create FileDescriptorResource. " +
"An error occurred while obtaining a file from the storage";
protected FileDescriptor fileDescriptor;
// just stub
protected String mimeType;
@Override
public FileDescriptorResource setFileDescriptor(FileDescriptor fileDescriptor) {
Preconditions.checkNotNullArgument(fileDescriptor);
this.fileDescriptor = fileDescriptor;
hasSource = true;
fireResourceUpdateEvent();
return this;
}
@Override
public FileDescriptor getFileDescriptor() {
return fileDescriptor;
}
@Override
protected void createResource() {
try {
byte[] file = AppBeans.get(FileStorageService.class)
.loadFile(fileDescriptor);
ByteArrayDataProvider provider = new ByteArrayDataProvider(file);
resource = ImageIO.read(provider.provide());
} catch (FileStorageException e) {
throw new RuntimeException(FILE_STORAGE_EXCEPTION_MESSAGE, e);
} catch (IOException e) {
throw new RuntimeException("An error occurred while loading an image.", e);
}
}
@Override
public void setMimeType(String mimeType) {
this.mimeType = mimeType;
}
@Override
public String getMimeType() {
return mimeType;
}
}

View File

@ -0,0 +1,56 @@
/*
* Copyright (c) 2008-2018 Haulmont.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.haulmont.cuba.desktop.gui.components;
import com.haulmont.bali.util.Preconditions;
import com.haulmont.cuba.gui.components.FileResource;
import javax.imageio.ImageIO;
import java.io.File;
import java.io.IOException;
public class DesktopFileResource extends DesktopAbstractStreamSettingsResource
implements DesktopResource, FileResource {
protected File file;
@Override
public FileResource setFile(File file) {
Preconditions.checkNotNullArgument(file);
this.file = file;
hasSource = true;
fireResourceUpdateEvent();
return this;
}
@Override
public File getFile() {
return file;
}
@Override
protected void createResource() {
try {
resource = ImageIO.read(file);
} catch (IOException e) {
throw new RuntimeException("An error occurred while loading an image.", e);
}
}
}

View File

@ -0,0 +1,397 @@
/*
* Copyright (c) 2008-2018 Haulmont.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.haulmont.cuba.desktop.gui.components;
import com.google.common.collect.ImmutableMap;
import com.haulmont.bali.events.EventRouter;
import com.haulmont.bali.util.Preconditions;
import com.haulmont.chile.core.model.MetaPropertyPath;
import com.haulmont.chile.core.model.utils.InstanceUtils;
import com.haulmont.cuba.core.entity.FileDescriptor;
import com.haulmont.cuba.core.global.AppBeans;
import com.haulmont.cuba.core.global.MetadataTools;
import com.haulmont.cuba.gui.GuiDevelopmentException;
import com.haulmont.cuba.gui.components.*;
import com.haulmont.cuba.gui.components.Image;
import com.haulmont.cuba.gui.data.Datasource;
import com.haulmont.cuba.gui.data.impl.WeakItemChangeListener;
import com.haulmont.cuba.gui.data.impl.WeakItemPropertyChangeListener;
import com.haulmont.cuba.gui.export.ByteArrayDataProvider;
import org.jdesktop.swingx.JXImageView;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.image.BufferedImage;
import java.io.InputStream;
import java.util.Map;
import java.util.function.Supplier;
public class DesktopImage extends DesktopAbstractComponent<JXImageView> implements Image {
protected static final Map<Class<? extends Resource>, Class<? extends Resource>> resourcesClasses;
static {
ImmutableMap.Builder<Class<? extends Resource>, Class<? extends Resource>> builder =
new ImmutableMap.Builder<>();
builder.put(UrlResource.class, DesktopUrlResource.class);
builder.put(ClasspathResource.class, DesktopClasspathResource.class);
builder.put(FileDescriptorResource.class, DesktopFileDescriptorResource.class);
builder.put(FileResource.class, DesktopFileResource.class);
builder.put(StreamResource.class, DesktopStreamResource.class);
resourcesClasses = builder.build();
}
protected Resource resource;
protected Runnable resourceUpdateHandler;
protected Datasource datasource;
protected MetaPropertyPath metaPropertyPath;
protected Datasource.ItemPropertyChangeListener itemPropertyChangeListener;
protected WeakItemPropertyChangeListener weakItemPropertyChangeListener;
protected Datasource.ItemChangeListener itemChangeListener;
protected WeakItemChangeListener weakItemChangeListener;
protected ScaleMode scaleMode = ScaleMode.NONE;
protected EventRouter eventRouter;
protected MouseListener mouseListener;
public DesktopImage() {
impl = new JXImageView();
impl.setDragEnabled(false);
impl.setEditable(false);
impl.setBackgroundPainter((g, object, width, height) ->
g.setBackground(Color.gray));
resourceUpdateHandler = () -> {
BufferedImage image = this.resource == null ?
null
: ((DesktopAbstractResource) this.resource).getResource();
impl.setImage(scaleImage(image));
};
}
@Override
public void setDatasource(Datasource datasource, String property) {
if ((datasource == null && property != null) || (datasource != null && property == null)) {
throw new IllegalArgumentException("Datasource and property should be either null or not null at the same time");
}
if (datasource == this.datasource && ((metaPropertyPath != null && metaPropertyPath.toString().equals(property)) ||
(metaPropertyPath == null && property == null))) {
return;
}
if (this.datasource != null) {
metaPropertyPath = null;
impl.setImage((java.awt.Image) null);
//noinspection unchecked
this.datasource.removeItemPropertyChangeListener(weakItemPropertyChangeListener);
weakItemPropertyChangeListener = null;
//noinspection unchecked
this.datasource.removeItemChangeListener(weakItemChangeListener);
weakItemChangeListener = null;
this.datasource = null;
}
if (datasource != null) {
//noinspection unchecked
this.datasource = datasource;
metaPropertyPath = AppBeans.get(MetadataTools.class)
.resolveMetaPropertyPathNN(datasource.getMetaClass(), property);
updateComponent();
itemPropertyChangeListener = e -> {
if (e.getProperty().equals(metaPropertyPath.toString())) {
updateComponent();
}
};
weakItemPropertyChangeListener = new WeakItemPropertyChangeListener(datasource, itemPropertyChangeListener);
//noinspection unchecked
this.datasource.addItemPropertyChangeListener(weakItemPropertyChangeListener);
itemChangeListener = e ->
updateComponent();
weakItemChangeListener = new WeakItemChangeListener(datasource, itemChangeListener);
//noinspection unchecked
datasource.addItemChangeListener(weakItemChangeListener);
}
}
protected void updateComponent() {
Object propertyValue = InstanceUtils.getValueEx(datasource.getItem(), metaPropertyPath.getPath());
Resource resource = createImageResource(propertyValue);
updateValue(resource);
}
protected void updateValue(Resource value) {
Resource oldValue = this.resource;
if (oldValue != null) {
((DesktopAbstractResource) oldValue).setResourceUpdatedHandler(null);
}
this.resource = value;
BufferedImage image = null;
if (value != null && ((DesktopAbstractResource) value).hasSource()) {
image = ((DesktopAbstractResource) value).getResource();
}
impl.setImage(scaleImage(image));
if (value != null) {
((DesktopAbstractResource) value).setResourceUpdatedHandler(resourceUpdateHandler);
}
getEventRouter().fireEvent(SourceChangeListener.class, SourceChangeListener::sourceChanged,
new SourceChangeEvent(this, oldValue, this.resource));
}
protected Resource createImageResource(Object propertyValue) {
if (propertyValue == null) {
return null;
}
if (propertyValue instanceof FileDescriptor) {
FileDescriptorResource imageResource = createResource(FileDescriptorResource.class);
imageResource.setFileDescriptor((FileDescriptor) propertyValue);
return imageResource;
}
if (propertyValue instanceof byte[]) {
StreamResource imageResource = createResource(StreamResource.class);
Supplier<InputStream> streamSupplier = () ->
new ByteArrayDataProvider((byte[]) propertyValue).provide();
imageResource.setStreamSupplier(streamSupplier);
return imageResource;
}
throw new GuiDevelopmentException(
"The Image component supports only FileDescriptor and byte[] datasource property value binding",
getFrame().getId());
}
@Override
public Datasource getDatasource() {
return datasource;
}
@Override
public MetaPropertyPath getMetaPropertyPath() {
return metaPropertyPath;
}
@Override
public ScaleMode getScaleMode() {
return scaleMode;
}
@Override
public void setScaleMode(ScaleMode scaleMode) {
Preconditions.checkNotNullArgument(scaleMode);
this.scaleMode = scaleMode;
DesktopAbstractResource desktopResource = (DesktopAbstractResource) this.resource;
if (desktopResource != null && desktopResource.hasSource()) {
java.awt.Image scaledImage = scaleImage(desktopResource.getResource());
impl.setImage(scaledImage);
}
}
protected java.awt.Image scaleImage(BufferedImage image) {
switch (scaleMode) {
case FILL:
return image.getScaledInstance(Math.round(getWidth()), Math.round(getHeight()), java.awt.Image.SCALE_FAST);
case CONTAIN:
case SCALE_DOWN:
int newH = Math.round(getHeight());
int newW = Math.round(getWidth());
float scaleCoef;
if (getHeight() > getWidth()) {
scaleCoef = getWidth() / image.getWidth();
newH = Math.round(image.getHeight() * scaleCoef);
} else {
scaleCoef = getHeight() / image.getHeight();
newW = Math.round(image.getWidth() * scaleCoef);
}
return image.getScaledInstance(newW, newH, java.awt.Image.SCALE_DEFAULT);
}
return image;
}
protected EventRouter getEventRouter() {
if (eventRouter == null) {
eventRouter = new EventRouter();
}
return eventRouter;
}
@Override
public void addClickListener(ClickListener listener) {
getEventRouter().addListener(ClickListener.class, listener);
if (mouseListener == null) {
mouseListener = new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
getEventRouter().fireEvent(ClickListener.class, ClickListener::onClick,
new ClickEvent(DesktopImage.this, convertMouseEvent(e)));
}
};
}
impl.addMouseListener(mouseListener);
}
@Override
public void removeClickListener(ClickListener listener) {
getEventRouter().removeListener(ClickListener.class, listener);
if (!getEventRouter().hasListeners(ClickListener.class)) {
impl.removeMouseListener(mouseListener);
}
}
@Override
public Resource getSource() {
return resource;
}
@Override
public void setSource(Resource resource) {
if (this.resource == null && resource == null
|| (this.resource != null && this.resource.equals(resource))) {
return;
}
updateValue(resource);
}
@Override
public <R extends Resource> R setSource(Class<R> type) {
R resource = createResource(type);
updateValue(resource);
return resource;
}
@Override
public <R extends Resource> R createResource(Class<R> type) {
if (ThemeResource.class.isAssignableFrom(type) || RelativePathResource.class.isAssignableFrom(type)) {
throw new UnsupportedOperationException("Theme and RelativePath Resources are not supported for desktop client.");
}
Class<? extends Resource> resourceClass = resourcesClasses.get(type);
if (resourceClass == null) {
throw new IllegalStateException(String.format("Can't find resource class for '%s'", type.getTypeName()));
}
try {
return type.cast(resourceClass.newInstance());
} catch (InstantiationException | IllegalAccessException e) {
throw new RuntimeException(String.format("Error creating the '%s' resource instance",
type.getTypeName()), e);
}
}
@Override
public void setAlternateText(String alternateText) {
impl.setToolTipText(alternateText);
}
@Override
public String getAlternateText() {
return impl.getToolTipText();
}
@Override
public void addSourceChangeListener(SourceChangeListener listener) {
getEventRouter().addListener(SourceChangeListener.class, listener);
}
@Override
public void removeSourceChangeListener(SourceChangeListener listener) {
getEventRouter().removeListener(SourceChangeListener.class, listener);
}
@Override
public String getDescription() {
return impl.getToolTipText();
}
@Override
public void setDescription(String description) {
impl.setToolTipText(description);
}
@Override
protected void setCaptionToComponent(String caption) {
super.setCaptionToComponent(caption);
requestContainerUpdate();
}
protected MouseEventDetails convertMouseEvent(MouseEvent event) {
MouseEventDetails.MouseButton button = null;
switch (event.getButton()) {
case 1:
button = MouseEventDetails.MouseButton.LEFT;
break;
case 2:
button = MouseEventDetails.MouseButton.RIGHT;
break;
case 3:
button = MouseEventDetails.MouseButton.MIDDLE;
break;
}
MouseEventDetails details = new MouseEventDetails();
details.setButton(button);
details.setClientX(event.getXOnScreen());
details.setClientY(event.getYOnScreen());
details.setAltKey(event.isAltDown());
details.setCtrlKey(event.isControlDown());
details.setMetaKey(event.isMetaDown());
details.setShiftKey(event.isShiftDown());
details.setDoubleClick(event.getClickCount() == 2);
details.setRelativeX(event.getX());
details.setRelativeY(event.getY());
return details;
}
}

View File

@ -0,0 +1,26 @@
/*
* Copyright (c) 2008-2018 Haulmont.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.haulmont.cuba.desktop.gui.components;
import com.haulmont.cuba.gui.components.Resource;
import java.awt.image.BufferedImage;
public interface DesktopResource extends Resource {
BufferedImage getResource();
}

View File

@ -0,0 +1,69 @@
/*
* Copyright (c) 2008-2018 Haulmont.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.haulmont.cuba.desktop.gui.components;
import com.haulmont.bali.util.Preconditions;
import com.haulmont.cuba.gui.components.StreamResource;
import javax.imageio.ImageIO;
import java.io.IOException;
import java.io.InputStream;
import java.util.function.Supplier;
public class DesktopStreamResource extends DesktopAbstractStreamSettingsResource
implements DesktopResource, StreamResource {
protected Supplier<InputStream> streamSupplier;
// just stub
protected String mimeType;
@Override
public StreamResource setStreamSupplier(Supplier<InputStream> streamSupplier) {
Preconditions.checkNotNullArgument(streamSupplier);
this.streamSupplier = streamSupplier;
hasSource = true;
fireResourceUpdateEvent();
return this;
}
@Override
public Supplier<InputStream> getStreamSupplier() {
return streamSupplier;
}
@Override
protected void createResource() {
try {
resource = ImageIO.read(streamSupplier.get());
} catch (IOException e) {
throw new RuntimeException("An error occurred while loading an image.", e);
}
}
@Override
public void setMimeType(String mimeType) {
this.mimeType = mimeType;
}
@Override
public String getMimeType() {
return mimeType;
}
}

View File

@ -0,0 +1,68 @@
/*
* Copyright (c) 2008-2018 Haulmont.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.haulmont.cuba.desktop.gui.components;
import com.haulmont.bali.util.Preconditions;
import com.haulmont.cuba.gui.components.UrlResource;
import javax.imageio.ImageIO;
import java.io.IOException;
import java.net.URL;
public class DesktopUrlResource extends DesktopAbstractResource
implements DesktopResource, UrlResource {
protected URL url;
// just stub
protected String mimeType;
@Override
protected void createResource() {
try {
resource = ImageIO.read(url);
} catch (IOException e) {
throw new RuntimeException("An error occurred while loading an image.", e);
}
}
@Override
public UrlResource setUrl(URL url) {
Preconditions.checkNotNullArgument(url);
this.url = url;
hasSource = true;
fireResourceUpdateEvent();
return this;
}
@Override
public URL getUrl() {
return url;
}
@Override
public void setMimeType(String mimeType) {
this.mimeType = mimeType;
}
@Override
public String getMimeType() {
return mimeType;
}
}