/*
* Copyright (c) 2011-2014 Julien Nicoulaud <julien.nicoulaud@gmail.com>
* Copyright (c) 2015 Vladimir Schneider <vladimir.schneider@gmail.com>
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.vladsch.idea.multimarkdown.editor;
import com.vladsch.idea.multimarkdown.settings.MultiMarkdownGlobalSettings;
import com.vladsch.idea.multimarkdown.settings.MultiMarkdownGlobalSettingsListener;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
import javax.swing.text.*;
import javax.swing.text.html.HTML;
import javax.swing.text.html.HTMLEditorKit;
import javax.swing.text.html.ImageView;
import java.awt.*;
import java.net.URL;
/**
* {@link HTMLEditorKit} that can display images with paths relative to the document.
*
* @author Roger Grantham (https://github.com/grantham)
* @author Julien Nicoulaud <julien.nicoulaud@gmail.com>
* @author Vladimir Schneider <vladimir.schneider@gmail.com>
* @since 0.8
*/
public class MultiMarkdownEditorKit extends HTMLEditorKit {
/**
* The document.
*/
protected float maxWidth;
protected MultiMarkdownGlobalSettingsListener globalSettingsListener;
public void setMaxWidth(float maxWidth) { this.maxWidth = maxWidth; }
public float getMaxWidth() { return maxWidth; }
/**
* Build a new instance of {@link MultiMarkdownEditorKit}.
*
*/
public MultiMarkdownEditorKit() {
maxWidth = MultiMarkdownGlobalSettings.getInstance().maxImgWidth.getValue();
MultiMarkdownGlobalSettings.getInstance().addListener(globalSettingsListener = new MultiMarkdownGlobalSettingsListener() {
public void handleSettingsChanged(@NotNull final MultiMarkdownGlobalSettings newSettings) {
maxWidth = MultiMarkdownGlobalSettings.getInstance().maxImgWidth.getValue();
}
});
}
/**
* Creates a copy of the editor kit.
*
* @return a new {@link MultiMarkdownEditorKit} instance
*/
@SuppressWarnings("CloneDoesntCallSuperClone")
@Override
public Object clone() {
return new MultiMarkdownEditorKit();
}
/**
* {@inheritDoc}
*/
@Override
public ViewFactory getViewFactory() {
return new MarkdownViewFactory(this);
}
/**
* An {@link HTMLFactory} that uses {@link MarkdownImageView} for images.
*
* @author Roger Grantham (https://github.com/grantham)
* @author Julien Nicoulaud <julien.nicoulaud@gmail.com>
* @author Vladimir Schneider <vladimir.schneider@gmail.com>
* @since 0.8
*/
private static class MarkdownViewFactory extends HTMLFactory {
private MultiMarkdownEditorKit editorKit;
private MarkdownViewFactory(MultiMarkdownEditorKit editorKit) {
this.editorKit = editorKit;
}
protected SimpleAttributeSet attributeSet;
@Override
public View create(Element elem) {
if (HTML.Tag.IMG.equals(elem.getAttributes().getAttribute(StyleConstants.NameAttribute))) {
return new MarkdownImageView(editorKit, elem);
}
return super.create(elem);
}
}
/**
* An {@link ImageView} that can resolve the image URL relative to the document.
*
* @author Roger Grantham (https://github.com/grantham)
* @author Vladimir Schneider <vladimir.schneider@gmail.com>
* @since 0.8
*/
protected static class MarkdownImageView extends ImageView {
private MultiMarkdownEditorKit editorKit;
private boolean scaled;
private MarkdownImageView(@NotNull MultiMarkdownEditorKit editorKit, @NotNull Element elem) {
super(elem);
this.editorKit = editorKit;
scaled = false;
}
/**
* Return a URL for the image source, or null if it could not be determined.
* <p/>
* Calls {@link javax.swing.text.html.ImageView#getImageURL()}, tries to resolve the relative if needed.
*
* @return a URL for the image source, or null if it could not be determined.
*/
@Override
public URL getImageURL() {
return super.getImageURL();
}
float width;
float height;
@Override
public float getPreferredSpan(int axis) {
if (!scaled) {
float width = super.getPreferredSpan(View.X_AXIS);
float height = super.getPreferredSpan(View.Y_AXIS);
if (width < 0 || height < 0) return super.getPreferredSpan(axis);
final float maxWidth = editorKit.getMaxWidth();
if (maxWidth > 0 && width > maxWidth) {
scaled = true;
this.width = maxWidth;
this.height = (int) (height * maxWidth / width);
// force refresh of the image size
View parent = getParent();
super.setParent(null);
super.setParent(parent);
} else {
this.width = super.getPreferredSpan(View.X_AXIS);
this.height = super.getPreferredSpan(View.Y_AXIS);
}
}
return axis == View.X_AXIS ? this.width : (axis == View.Y_AXIS ? this.height : 0);
}
/**
* Paints the View.
*
* @param g the rendering surface to use
* @param a the allocated region to render into
* @see View#paint
*/
@Override
public void paint(@NotNull Graphics g, @NotNull Shape a) {
float width = getPreferredSpan(View.X_AXIS);
float height = getPreferredSpan(View.Y_AXIS);
final float maxWidth = editorKit.getMaxWidth();
if (maxWidth > 0 && width > maxWidth) {
height = height * maxWidth / width;
width = maxWidth;
}
Rectangle rect = (a instanceof Rectangle) ? (Rectangle) a :
a.getBounds();
Rectangle clip = g.getClipBounds();
if (clip != null) {
g.clipRect(rect.x, rect.y,
rect.width,
rect.height);
}
Container host = getContainer();
Image img = getImage();
if (img != null) {
if (width > 0 && height > 0) {
// Draw the image
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2.drawImage(img, rect.x, rect.y, (int) width, (int) height, null);
}
} else {
Icon icon = getNoImageIcon();
if (icon != null) {
icon.paintIcon(host, g,
rect.x, rect.y);
}
}
if (clip != null) {
// Reset clip.
g.setClip(clip.x, clip.y, clip.width, clip.height);
}
}
}
}