diff --git a/ide/editor.kit/nbproject/project.xml b/ide/editor.kit/nbproject/project.xml
index 2d2b769dd485..8ac9979bb917 100644
--- a/ide/editor.kit/nbproject/project.xml
+++ b/ide/editor.kit/nbproject/project.xml
@@ -138,6 +138,12 @@
1.21
+
+ org.netbeans.modules.svg
+
+ 1.0
+
+
org.netbeans.modules.xml.core
diff --git a/ide/svg/build.xml b/ide/svg/build.xml
new file mode 100644
index 000000000000..bb0d0e752c64
--- /dev/null
+++ b/ide/svg/build.xml
@@ -0,0 +1,25 @@
+
+
+
+ Builds, tests, and runs the project org.netbeans.modules.svg
+
+
diff --git a/ide/svg/licenseinfo.xml b/ide/svg/licenseinfo.xml
new file mode 100644
index 000000000000..8abdf6d0d889
--- /dev/null
+++ b/ide/svg/licenseinfo.xml
@@ -0,0 +1,28 @@
+
+
+
+
+ src/org/netbeans/modules/svg/resources/svgLogo.png
+
+
+
+
diff --git a/ide/svg/manifest.mf b/ide/svg/manifest.mf
new file mode 100644
index 000000000000..f8fce48879a4
--- /dev/null
+++ b/ide/svg/manifest.mf
@@ -0,0 +1,5 @@
+Manifest-Version: 1.0
+AutoUpdate-Show-In-Client: false
+OpenIDE-Module: org.netbeans.modules.svg
+OpenIDE-Module-Localizing-Bundle: org/netbeans/modules/svg/Bundle.properties
+OpenIDE-Module-Specification-Version: 1.0
diff --git a/ide/svg/nbproject/project.properties b/ide/svg/nbproject/project.properties
new file mode 100644
index 000000000000..60729d846776
--- /dev/null
+++ b/ide/svg/nbproject/project.properties
@@ -0,0 +1,19 @@
+# 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.
+
+javac.release=17
+javac.compilerargs=-Xlint -Xlint:-serial
diff --git a/ide/svg/nbproject/project.xml b/ide/svg/nbproject/project.xml
new file mode 100644
index 000000000000..ecda4b1643f1
--- /dev/null
+++ b/ide/svg/nbproject/project.xml
@@ -0,0 +1,165 @@
+
+
+
+ org.netbeans.modules.apisupport.project
+
+
+ org.netbeans.modules.svg
+
+
+ org.netbeans.api.xml.ui
+
+
+
+ 1
+ 1.70
+
+
+
+ org.netbeans.core.ide
+
+
+
+ 1
+ 1.68
+
+
+
+ org.netbeans.core.multiview
+
+
+
+ 1
+ 1.71
+
+
+
+ org.netbeans.libs.jsvg
+
+
+
+ 1.22
+
+
+
+ org.netbeans.modules.xml
+
+
+
+ 1.60
+
+
+
+ org.netbeans.modules.xml.core
+
+
+
+ 2
+ 1.69
+
+
+
+ org.netbeans.spi.navigator
+
+
+
+ 1
+ 1.64
+
+
+
+ org.openide.awt
+
+
+
+ 7.94
+
+
+
+ org.openide.dialogs
+
+
+
+ 7.74
+
+
+
+ org.openide.filesystems
+
+
+
+ 9.39
+
+
+
+ org.openide.loaders
+
+
+
+ 7.96
+
+
+
+ org.openide.nodes
+
+
+
+ 7.71
+
+
+
+ org.openide.util
+
+
+
+ 9.34
+
+
+
+ org.openide.util.lookup
+
+
+
+ 8.60
+
+
+
+ org.openide.util.ui
+
+
+
+ 9.35
+
+
+
+ org.openide.windows
+
+
+
+ 6.103
+
+
+
+
+
+
+
diff --git a/ide/svg/src/org/netbeans/modules/svg/BackgroundMode.java b/ide/svg/src/org/netbeans/modules/svg/BackgroundMode.java
new file mode 100644
index 000000000000..6bcb91b0f048
--- /dev/null
+++ b/ide/svg/src/org/netbeans/modules/svg/BackgroundMode.java
@@ -0,0 +1,27 @@
+/*
+ * 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 org.netbeans.modules.svg;
+
+/**
+ *
+ * @author Christian Lenz
+ */
+public enum BackgroundMode {
+ DEFAULT, BLACK, WHITE, TRANSPARENT, DARK_TRANSPARENT
+}
diff --git a/ide/svg/src/org/netbeans/modules/svg/Bundle.properties b/ide/svg/src/org/netbeans/modules/svg/Bundle.properties
new file mode 100644
index 000000000000..9da8d38a378a
--- /dev/null
+++ b/ide/svg/src/org/netbeans/modules/svg/Bundle.properties
@@ -0,0 +1,23 @@
+# 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.
+
+
+OpenIDE-Module-Name=SVG Support
+
+## SVGDataObject.java
+CTL_SourceTabCaption=&Source
+ERR_SVGDocument=SVG-Document is not available
\ No newline at end of file
diff --git a/ide/svg/src/org/netbeans/modules/svg/SVGDataObject.java b/ide/svg/src/org/netbeans/modules/svg/SVGDataObject.java
new file mode 100644
index 000000000000..d916cd4c7b69
--- /dev/null
+++ b/ide/svg/src/org/netbeans/modules/svg/SVGDataObject.java
@@ -0,0 +1,220 @@
+/*
+ * 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 org.netbeans.modules.svg;
+
+import org.netbeans.core.spi.multiview.MultiViewElement;
+import org.netbeans.core.spi.multiview.text.MultiViewEditorElement;
+import org.netbeans.modules.xml.XMLDataObjectLook;
+import org.netbeans.modules.xml.cookies.DataObjectCookieManager;
+import org.netbeans.modules.xml.cookies.UpdateDocumentCookie;
+import org.netbeans.modules.xml.sync.DataObjectSyncSupport;
+import org.netbeans.modules.xml.sync.Synchronizator;
+import org.netbeans.spi.xml.cookies.CheckXMLSupport;
+import org.netbeans.spi.xml.cookies.DataObjectAdapters;
+import org.netbeans.spi.xml.cookies.ValidateXMLSupport;
+import org.openide.awt.ActionID;
+import org.openide.awt.ActionReference;
+import org.openide.awt.ActionReferences;
+import org.openide.awt.HtmlBrowser;
+import org.openide.cookies.ViewCookie;
+import org.openide.filesystems.*;
+import org.openide.loaders.*;
+import org.openide.nodes.CookieSet;
+import org.openide.nodes.Node;
+import org.openide.util.*;
+import org.openide.windows.TopComponent;
+import org.xml.sax.InputSource;
+
+/**
+ *
+ * @author Christian Lenz
+ */
+@NbBundle.Messages({
+ "LBL_SVG_LOADER=Files of SVG"
+})
+@MIMEResolver.ExtensionRegistration(
+ displayName = "#LBL_SVG_LOADER",
+ mimeType = SVGDataObject.MIME_TYPE,
+ extension = {"svg", "SVG"},
+ position = 21367
+)
+@DataObject.Registration(
+ mimeType = SVGDataObject.MIME_TYPE,
+ iconBase = "org/netbeans/modules/svg/resources/svgLogo.png",
+ displayName = "#LBL_SVG_LOADER",
+ position = 300
+)
+@ActionReferences({
+ @ActionReference(
+ id = @ActionID(category = "System", id = "org.openide.actions.OpenAction"),
+ path = "Loaders/image/svg+xml/Actions",
+ position = 100
+ ),
+ @ActionReference(
+ id = @ActionID(category = "System", id = "org.openide.actions.ViewAction"),
+ path = "Loaders/image/svg+xml/Actions",
+ position = 200,
+ separatorAfter = 400
+ ),
+ @ActionReference(
+ id = @ActionID(category = "XML", id = "org.netbeans.modules.xml.tools.actions.CheckAction"),
+ path = "Loaders/image/svg+xml/Actions",
+ position = 600
+ ),
+ @ActionReference(
+ id = @ActionID(category = "XML", id = "org.netbeans.modules.xml.tools.actions.ValidateAction"),
+ path = "Loaders/image/svg+xml/Actions",
+ position = 700,
+ separatorAfter = 750
+ ),
+ @ActionReference(
+ id = @ActionID(category = "Edit", id = "org.openide.actions.CutAction"),
+ path = "Loaders/image/svg+xml/Actions",
+ position = 800
+ ),
+ @ActionReference(
+ id = @ActionID(category = "Edit", id = "org.openide.actions.CopyAction"),
+ path = "Loaders/image/svg+xml/Actions",
+ position = 900
+ ),
+ @ActionReference(
+ id = @ActionID(category = "Edit", id = "org.openide.actions.PasteAction"),
+ path = "Loaders/image/svg+xml/Actions",
+ position = 1000,
+ separatorAfter = 1100
+ ),
+ @ActionReference(
+ id = @ActionID(category = "Edit", id = "org.openide.actions.DeleteAction"),
+ path = "Loaders/image/svg+xml/Actions",
+ position = 1200
+ ),
+ @ActionReference(
+ id = @ActionID(category = "System", id = "org.openide.actions.RenameAction"),
+ path = "Loaders/image/svg+xml/Actions",
+ position = 1300,
+ separatorAfter = 1400
+ ),
+ @ActionReference(
+ id = @ActionID(category = "System", id = "org.openide.actions.SaveAsTemplateAction"),
+ path = "Loaders/image/svg+xml/Actions",
+ position = 1500
+ ),
+ @ActionReference(
+ id = @ActionID(category = "System", id = "org.openide.actions.FileSystemAction"),
+ path = "Loaders/image/svg+xml/Actions",
+ position = 1550,
+ separatorAfter = 1600
+ ),
+ @ActionReference(
+ id = @ActionID(category = "System", id = "org.openide.actions.ToolsAction"),
+ path = "Loaders/image/svg+xml/Actions",
+ position = 1700
+ ),
+ @ActionReference(
+ id = @ActionID(category = "System", id = "org.openide.actions.PropertiesAction"),
+ path = "Loaders/image/svg+xml/Actions",
+ position = 1800
+ )
+})
+public final class SVGDataObject extends MultiDataObject implements XMLDataObjectLook, UpdateDocumentCookie {
+
+ public static final String MIME_TYPE = "image/svg+xml";
+ private final transient DataObjectCookieManager cookieManager;
+ private transient Synchronizator synchronizator;
+
+ public SVGDataObject(final FileObject fo, MultiFileLoader loader) throws DataObjectExistsException {
+ super(fo, loader);
+ registerEditor(MIME_TYPE, true);
+
+ CookieSet set = getCookieSet();
+ cookieManager = new DataObjectCookieManager(this, getCookieSet());
+
+ // add check and validate cookies
+ InputSource is = DataObjectAdapters.inputSource(this);
+ set.add(new CheckXMLSupport(is, CheckXMLSupport.DOCUMENT_MODE));
+ set.add(new ValidateXMLSupport(is));
+
+ CookieSet.Factory viewCookieFactory = new ViewCookieFactory();
+ set.add(ViewCookie.class, viewCookieFactory);
+ }
+
+ @Override
+ protected int associateLookup() {
+ return 1;
+ }
+
+ @Override
+ public DataObjectCookieManager getCookieManager() {
+ return cookieManager;
+ }
+
+ @Override
+ public synchronized Synchronizator getSyncInterface() {
+ if (synchronizator == null) {
+ synchronizator = new DataObjectSyncSupport(SVGDataObject.this);
+ }
+
+ return synchronizator;
+ }
+
+ @MultiViewElement.Registration(
+ displayName = "org.netbeans.modules.svg.Bundle#CTL_SourceTabCaption",
+ iconBase = "org/netbeans/modules/svg/resources/svgLogo.png",
+ persistenceType = TopComponent.PERSISTENCE_ONLY_OPENED,
+ preferredID = "SVG",
+ mimeType = MIME_TYPE,
+ position = 1
+ )
+ public static MultiViewEditorElement createMultiViewEditorElement(Lookup context) {
+ return new MultiViewEditorElement(context);
+ }
+
+ @Override
+ public void updateDocumentRoot() {
+ setModified(false);
+ getPrimaryFile();
+ }
+
+ private class ViewCookieFactory implements CookieSet.Factory {
+
+ @Override
+ public Node.Cookie createCookie(Class klass) {
+ if (klass == ViewCookie.class) {
+ return new ViewSupport(org.netbeans.modules.svg.SVGDataObject.this.getPrimaryEntry());
+ } else {
+ return null;
+ }
+ }
+ }
+
+ private static final class ViewSupport implements ViewCookie {
+
+ private final MultiDataObject.Entry primary;
+
+ public ViewSupport(MultiDataObject.Entry primary) {
+ this.primary = primary;
+ }
+
+ @Override
+ public void view() {
+ HtmlBrowser.URLDisplayer.getDefault().showURL(primary.getFile().toURL());
+ }
+
+ }
+}
diff --git a/ide/svg/src/org/netbeans/modules/svg/SVGPanel.java b/ide/svg/src/org/netbeans/modules/svg/SVGPanel.java
new file mode 100644
index 000000000000..a1b498a579dd
--- /dev/null
+++ b/ide/svg/src/org/netbeans/modules/svg/SVGPanel.java
@@ -0,0 +1,146 @@
+/*
+ * 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 org.netbeans.modules.svg;
+
+import com.github.weisj.jsvg.SVGDocument;
+import com.github.weisj.jsvg.SVGRenderingHints;
+import java.awt.*;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.swing.*;
+import org.openide.util.NbBundle;
+
+/**
+ *
+ * @author Christian Lenz
+ */
+public class SVGPanel extends JPanel {
+
+ private static final Logger LOG = Logger.getLogger(SVGViewerElement.class.getName());
+
+ private SVGDocument svgDocument;
+ private double scale = 1.0D;
+ private BackgroundMode bgMode = BackgroundMode.DEFAULT;
+
+ public void setSVGDocument(SVGDocument doc) {
+ this.svgDocument = doc;
+
+ repaint();
+ }
+
+ public void setScale(double scale) {
+ this.scale = scale;
+
+ repaint();
+ }
+
+ public void setBackgroundMode(BackgroundMode mode) {
+ this.bgMode = mode;
+
+ repaint();
+ }
+
+ @Override
+ protected void paintComponent(Graphics g) {
+ super.paintComponent(g);
+
+ Rectangle visibleRect = getVisibleRectangle();
+
+ switch (bgMode) {
+ case BLACK -> {
+ g.setColor(Color.BLACK);
+ g.fillRect(visibleRect.x, visibleRect.y, visibleRect.width, visibleRect.height);
+ }
+ case WHITE -> {
+ g.setColor(Color.WHITE);
+ g.fillRect(visibleRect.x, visibleRect.y, visibleRect.width, visibleRect.height);
+ }
+
+ case TRANSPARENT, DARK_TRANSPARENT ->
+ Utils.drawChestTilePattern(g, visibleRect, 20, bgMode == BackgroundMode.DARK_TRANSPARENT);
+ case DEFAULT -> {
+ g.setColor(getBackground());
+ g.fillRect(visibleRect.x, visibleRect.y, visibleRect.width, visibleRect.height);
+ }
+ }
+
+ Graphics2D g2d = (Graphics2D) g.create();
+
+ g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+ g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
+ g2d.setRenderingHint(SVGRenderingHints.KEY_MASK_CLIP_RENDERING, SVGRenderingHints.VALUE_MASK_CLIP_RENDERING_ACCURACY);
+
+ g2d.scale(scale, scale);
+
+ try {
+ svgDocument.render(this, g2d);
+ } catch (Exception ex) {
+ LOG.log(Level.INFO, ex.getMessage());
+
+ drawErrorMesage(g);
+ } finally {
+ g2d.dispose();
+ }
+ }
+
+ /**
+ * Gets the visible portion of the panel. If this panel is inside a
+ * JViewport, it returns the visible part, otherwise, it returns the full
+ * panel size.
+ *
+ * @return The visible rectangle for the panel.
+ */
+ private Rectangle getVisibleRectangle() {
+ JViewport viewport = getViewport();
+ if (viewport != null) {
+ return viewport.getViewRect();
+ }
+
+ return new Rectangle(0, 0, getWidth(), getHeight());
+ }
+
+ /**
+ * Finds the parent JViewport, if any, to get the visible part of this
+ * panel.
+ *
+ * @return The JViewport that contains this panel, or null if none is found.
+ */
+ private JViewport getViewport() {
+ Container parent = getParent();
+ while (parent != null) {
+ if (parent instanceof JViewport) {
+ return (JViewport) parent;
+ }
+
+ parent = parent.getParent();
+ }
+
+ return null;
+ }
+
+ private void drawErrorMesage(Graphics g) {
+ g.setColor(Color.RED);
+
+ FontMetrics fm = this.getFontMetrics(g.getFont());
+ String errMessage = NbBundle.getMessage(SVGPanel.class, "ERR_SVGDocument");
+ int stringWidth = fm.stringWidth(errMessage);
+
+ g.drawString(errMessage, (this.getWidth() - stringWidth) / 2, this.getHeight() / 2);
+ }
+}
diff --git a/ide/svg/src/org/netbeans/modules/svg/SVGViewerElement.java b/ide/svg/src/org/netbeans/modules/svg/SVGViewerElement.java
new file mode 100644
index 000000000000..70e1049a6ae5
--- /dev/null
+++ b/ide/svg/src/org/netbeans/modules/svg/SVGViewerElement.java
@@ -0,0 +1,382 @@
+/*
+ * 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 org.netbeans.modules.svg;
+
+import com.github.weisj.jsvg.SVGDocument;
+import com.github.weisj.jsvg.geometry.size.FloatSize;
+import com.github.weisj.jsvg.parser.SVGLoader;
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Point;
+import java.awt.Rectangle;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.swing.Action;
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JToolBar;
+import javax.swing.SwingConstants;
+import org.netbeans.core.spi.multiview.CloseOperationState;
+import org.netbeans.core.spi.multiview.MultiViewElement;
+import org.netbeans.core.spi.multiview.MultiViewElementCallback;
+import org.netbeans.modules.svg.toolbar.SVGViewerToolbar;
+import org.openide.awt.UndoRedo;
+import org.openide.filesystems.FileChangeAdapter;
+import org.openide.filesystems.FileChangeListener;
+import org.openide.filesystems.FileEvent;
+import org.openide.filesystems.FileObject;
+import org.openide.util.Lookup;
+import org.openide.util.NbBundle;
+import org.openide.util.NbBundle.Messages;
+import org.openide.util.lookup.Lookups;
+import org.openide.util.lookup.ProxyLookup;
+import org.openide.windows.TopComponent;
+
+/**
+ *
+ * @author Christian Lenz
+ */
+@MultiViewElement.Registration(
+ displayName = "#LBL_SVGViewer",
+ iconBase = "org/netbeans/modules/svg/resources/svgLogo.png",
+ mimeType = "image/svg+xml",
+ persistenceType = TopComponent.PERSISTENCE_NEVER,
+ preferredID = "SVGViewer",
+ position = 1100
+)
+@Messages("LBL_SVGViewer=Preview")
+public class SVGViewerElement implements MultiViewElement {
+
+ static {
+ // JSVG loader/parser is logging with SEVERE level which would land in the NB exception reporter
+ Logger.getLogger("com.github.weisj.jsvg").setLevel(Level.OFF);
+ }
+
+ private static final Logger LOG = Logger.getLogger(SVGViewerElement.class.getName());
+
+ private final SVGDataObject dataObject;
+ private final Lookup lookup;
+ private transient JToolBar toolbar;
+ private transient JComponent component;
+ private transient MultiViewElementCallback callback;
+
+ /**
+ * Component showing SVG.
+ */
+ private transient JPanel viewer;
+
+ private final SVGLoader svgLoader = new SVGLoader();
+ private final SVGPanel svgPanel = new SVGPanel();
+ private SVGDocument svgDocument = null;
+
+ private int imageHeight = 0;
+ private int imageWidth = 0;
+ private long imageSize = -1;
+
+ /**
+ * Scale of SVG.
+ */
+ private double scale = 1.0D;
+
+ /**
+ * Increase/decrease factor.
+ */
+ private final double changeFactor = Math.sqrt(2.0D);
+
+ private final FileChangeListener fcl = new FileChangeAdapter() {
+ @Override
+ public void fileChanged(FileEvent fe) {
+ updateView();
+ }
+ };
+
+ @SuppressWarnings({"this-escape", "LeakingThisInConstructor"})
+ public SVGViewerElement(Lookup lookup) {
+ this.dataObject = lookup.lookup(SVGDataObject.class);
+ this.lookup = new ProxyLookup(
+ Lookups.proxy(() -> dataObject.getLookup()),
+ Lookups.singleton(this)
+ );
+ }
+
+ @Override
+ public JComponent getVisualRepresentation() {
+ if (component == null) {
+ viewer = new JPanel();
+ component = viewer;
+ }
+
+ return component;
+ }
+
+ @Override
+ public JComponent getToolbarRepresentation() {
+ if (toolbar == null) {
+ toolbar = new SVGViewerToolbar().createToolbar(this);
+ }
+
+ return toolbar;
+ }
+
+ @Override
+ public Action[] getActions() {
+ return callback.createDefaultActions();
+ }
+
+ @Override
+ public Lookup getLookup() {
+ return lookup;
+ }
+
+ @Override
+ public void componentOpened() {
+ dataObject.getPrimaryFile().addFileChangeListener(fcl);
+
+ updateView();
+ }
+
+ @Override
+ public void componentClosed() {
+ dataObject.getPrimaryFile().removeFileChangeListener(fcl);
+ }
+
+ @Override
+ public void componentShowing() {
+ }
+
+ @Override
+ public void componentHidden() {
+ }
+
+ @Override
+ public void componentActivated() {
+ }
+
+ @Override
+ public void componentDeactivated() {
+ }
+
+ @Override
+ public UndoRedo getUndoRedo() {
+ return UndoRedo.NONE;
+ }
+
+ @Override
+ public void setMultiViewCallback(MultiViewElementCallback callback) {
+ this.callback = callback;
+ }
+
+ @Override
+ public CloseOperationState canCloseElement() {
+ return CloseOperationState.STATE_OK;
+ }
+
+ private void addMouseWheelListenerToViewer(JScrollPane scrollPane) {
+ svgPanel.addMouseWheelListener(e -> {
+ double oldScale = scale;
+
+ // Point in scrolled pane
+ Point visiblePoint = e.getPoint();
+
+ // "Picturepixel"
+ Point markedPoint = new Point(
+ (int) (visiblePoint.getX() / oldScale),
+ (int) (visiblePoint.getY() / oldScale));
+
+ int clicks = e.getWheelRotation();
+ int clicks_abs = Math.abs(clicks);
+ for (int i = 0; i < clicks_abs; i++) {
+ if (clicks < 0) {
+ zoomIn();
+ } else {
+ zoomOut();
+ }
+ }
+
+ double newScale = scale;
+
+ Point markedPointInRealSpace = new Point(
+ (int) (markedPoint.getX() * newScale),
+ (int) (markedPoint.getY() * newScale)
+ );
+
+ Rectangle r = scrollPane.getViewport().getViewRect();
+
+ r.setLocation(markedPointInRealSpace);
+ r.translate(-r.width / 2, -r.height / 2);
+
+ svgPanel.scrollRectToVisible(r);
+ });
+ }
+
+ private void showEmptyMessage(String message) {
+ JLabel label = new JLabel(message, SwingConstants.CENTER);
+ label.setForeground(Color.RED);
+
+ viewer.removeAll();
+ viewer.setLayout(new BorderLayout());
+ viewer.add(label, BorderLayout.CENTER);
+ viewer.revalidate();
+ viewer.repaint();
+ }
+
+ private void updateView() {
+ FileObject fo = dataObject.getPrimaryFile();
+
+ if (fo == null) {
+ return;
+ }
+
+ if (viewer == null) {
+ return;
+ }
+
+ svgDocument = svgLoader.load(fo.toURL());
+
+ if (svgDocument == null) {
+ showEmptyMessage(NbBundle.getMessage(SVGViewerElement.class, "ERR_SVGDocument"));
+
+ return;
+ }
+
+ FloatSize size = svgDocument.size();
+ int width = (int) size.width;
+ int height = (int) size.height;
+
+ imageWidth = width;
+ imageHeight = height;
+
+ imageSize = fo.getSize();
+
+ if (width <= 0 || height <= 0) {
+ LOG.log(Level.WARNING, "Invalid SVG dimensions: width={0}, height={1}", new Object[]{width, height});
+
+ return;
+ }
+
+ svgPanel.setPreferredSize(new Dimension(width, height));
+ svgPanel.setSVGDocument(svgDocument);
+
+ JScrollPane scrollPane = new JScrollPane(svgPanel);
+
+ addMouseWheelListenerToViewer(scrollPane);
+
+ viewer.removeAll();
+ viewer.setLayout(new BorderLayout());
+ viewer.add(scrollPane, BorderLayout.CENTER);
+ viewer.revalidate();
+ viewer.repaint();
+ }
+
+ public float getImageSize() {
+ return imageSize;
+ }
+
+ public float getImageWidth() {
+ return imageWidth;
+ }
+
+ public float getImageHeight() {
+ return imageHeight;
+ }
+
+ /**
+ * Perform zoom with specific proportion.
+ *
+ * @param fx numerator for scaled
+ * @param fy denominator for scaled
+ */
+ public void customZoom(int fx, int fy) {
+ double oldScale = scale;
+
+ scale = (double) fx / (double) fy;
+ if (!isNewSizeOK()) {
+ scale = oldScale;
+
+ return;
+ }
+
+ svgPanel.setScale(scale);
+
+ resizePanel();
+ }
+
+ /**
+ * Draws zoom in scaled image.
+ */
+ public void zoomIn() {
+ scale *= changeFactor;
+
+ if (isNewSizeOK()) {
+ svgPanel.setScale(scale);
+
+ resizePanel();
+ } else {
+ scale /= changeFactor;
+ }
+ }
+
+ /**
+ * Draws zoom out scaled image.
+ */
+ public void zoomOut() {
+ scale /= changeFactor;
+
+ if (isNewSizeOK()) {
+ svgPanel.setScale(scale);
+
+ resizePanel();
+ } else {
+ scale *= changeFactor;
+ }
+ }
+
+ public void changeBackground(BackgroundMode bgMode) {
+ svgPanel.setBackgroundMode(bgMode);
+
+ viewer.revalidate();
+ viewer.repaint();
+ }
+
+ /**
+ * Resizes panel.
+ */
+ private void resizePanel() {
+ int newWidth = (int) (svgDocument.size().getWidth() * scale);
+ int newHeight = (int) (svgDocument.size().getHeight() * scale);
+
+ svgPanel.setBounds(0, 0, newWidth, newHeight);
+ svgPanel.setPreferredSize(new Dimension(newWidth, newHeight));
+
+ viewer.revalidate();
+ viewer.repaint();
+ }
+
+ /**
+ * Tests new size of image. If image is smaller than minimum size(1x1)
+ * zooming will be not performed.
+ */
+ private boolean isNewSizeOK() {
+ return (scale * svgDocument.size().getWidth() > 1)
+ && (scale * svgDocument.size().getHeight() > 1);
+ }
+}
diff --git a/ide/svg/src/org/netbeans/modules/svg/Utils.java b/ide/svg/src/org/netbeans/modules/svg/Utils.java
new file mode 100644
index 000000000000..3cd6ce0e5181
--- /dev/null
+++ b/ide/svg/src/org/netbeans/modules/svg/Utils.java
@@ -0,0 +1,103 @@
+/*
+ * 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 org.netbeans.modules.svg;
+
+import java.awt.*;
+import java.awt.image.BufferedImage;
+
+/**
+ *
+ * @author Christian Lenz
+ */
+public class Utils {
+
+ /**
+ * Creates a pre-rendered chest like tile pattern as a BufferedImage.
+ *
+ * @param tileSize The size of each tile in the pattern.
+ * @param transparent Whether to add a transparent overlay to the pattern.
+ * @return The generated chest like pattern as a BufferedImage.
+ */
+ public static BufferedImage createChestTilePattern(int tileSize, boolean transparent) {
+ int patternSize = tileSize * 2;
+ BufferedImage image = new BufferedImage(patternSize, patternSize, BufferedImage.TYPE_INT_ARGB);
+ Graphics2D g2d = image.createGraphics();
+
+ drawSmallChestTilePattern(g2d, 0, 0, patternSize, patternSize, transparent);
+
+ return image;
+ }
+
+ /**
+ * Draws a repeating chest like tiled pattern within the specified visible
+ * rectangle.
+ *
+ * @param g The graphics object used for drawing.
+ * @param visibleRect The visible area to draw the pattern.
+ * @param tileSize The size of each tile in the pattern.
+ * @param transparent Whether to add a transparent overlay to the pattern.
+ */
+ public static void drawChestTilePattern(Graphics g, Rectangle visibleRect, int tileSize, boolean transparent) {
+ BufferedImage backgroundImage = createChestTilePattern(tileSize, transparent);
+ int imgWidth = backgroundImage.getWidth();
+ int imgHeight = backgroundImage.getHeight();
+
+ for (int y = visibleRect.y; y < visibleRect.y + visibleRect.height; y += imgHeight) {
+ for (int x = visibleRect.x; x < visibleRect.x + visibleRect.width; x += imgWidth) {
+ g.drawImage(backgroundImage, x, y, null);
+ }
+ }
+ }
+
+ /**
+ * Draws a small chest like tile pattern on a specified area.
+ *
+ * @param g2d The graphics object used for drawing.
+ * @param x The x-coordinate of the top-left corner.
+ * @param y The y-coordinate of the top-left corner.
+ * @param width The width of the area to draw the pattern.
+ * @param height The height of the area to draw the pattern.
+ * @param transparentOverlay Whether to add a transparent overlay on top of
+ * the pattern.
+ */
+ public static void drawSmallChestTilePattern(Graphics2D g2d, int x, int y, int width, int height, boolean transparentOverlay) {
+ int tileSize = width / 2;
+
+ try {
+ for (int row = 0; row < 2; row++) {
+ for (int col = 0; col < 2; col++) {
+ if ((row + col) % 2 == 0) {
+ g2d.setColor(Color.LIGHT_GRAY);
+ } else {
+ g2d.setColor(Color.WHITE);
+ }
+
+ g2d.fillRect(x + col * tileSize, y + row * tileSize, tileSize, tileSize);
+ }
+ }
+
+ if (transparentOverlay) {
+ g2d.setColor(new Color(0, 0, 0, 128)); // Semi-transparent black overlay (50% opacity)
+ g2d.fillRect(x, y, width, height);
+ }
+ } finally {
+ g2d.dispose();
+ }
+ }
+}
diff --git a/ide/svg/src/org/netbeans/modules/svg/navigation/Bundle.properties b/ide/svg/src/org/netbeans/modules/svg/navigation/Bundle.properties
new file mode 100644
index 000000000000..f4ea92e6350f
--- /dev/null
+++ b/ide/svg/src/org/netbeans/modules/svg/navigation/Bundle.properties
@@ -0,0 +1,23 @@
+# 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.
+
+# ImageNavigatorPanel
+Navigator_DisplayName=SVG preview
+Navigator_DisplayHint=Thumbnail
+ERR_IOFile=File is not available
+ERR_DataObject=Can't find the data object
+ERR_Thumbnail=Thumbnail is not available
diff --git a/ide/svg/src/org/netbeans/modules/svg/navigation/SVGNavigatorPanel.java b/ide/svg/src/org/netbeans/modules/svg/navigation/SVGNavigatorPanel.java
new file mode 100644
index 000000000000..614919106763
--- /dev/null
+++ b/ide/svg/src/org/netbeans/modules/svg/navigation/SVGNavigatorPanel.java
@@ -0,0 +1,209 @@
+/*
+ * 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 org.netbeans.modules.svg.navigation;
+
+import com.github.weisj.jsvg.SVGDocument;
+import com.github.weisj.jsvg.parser.SVGLoader;
+import java.util.Collection;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.swing.JComponent;
+import javax.swing.SwingUtilities;
+import org.netbeans.modules.svg.SVGDataObject;
+import org.netbeans.modules.svg.SVGViewerElement;
+import org.netbeans.spi.navigator.NavigatorPanel;
+import org.openide.filesystems.FileChangeAdapter;
+import org.openide.filesystems.FileChangeListener;
+import org.openide.filesystems.FileEvent;
+import org.openide.filesystems.FileObject;
+import org.openide.loaders.DataObject;
+import org.openide.loaders.DataObjectNotFoundException;
+import org.openide.util.*;
+
+/**
+ *
+ * @author christian lenz
+ */
+@NavigatorPanel.Registrations({
+ @NavigatorPanel.Registration(mimeType = "image/svg+xml", displayName = "#Navigator_DisplayName")
+})
+public class SVGNavigatorPanel implements NavigatorPanel {
+
+ static {
+ // JSVG loader/parser is logging with SEVERE level which would land in the NB exception reporter
+ Logger.getLogger("com.github.weisj.jsvg").setLevel(Level.OFF);
+ }
+
+ private static final Logger LOG = Logger.getLogger(SVGViewerElement.class.getName());
+
+ /**
+ * holds UI of this panel
+ */
+ private SVGPreviewPanel panelUI;
+ /**
+ * template for finding data in given context. Object used as example,
+ * replace with your own data source, for example JavaDataObject etc
+ */
+ private static final Lookup.Template MY_DATA = new Lookup.Template<>(SVGDataObject.class);
+ /**
+ * current context to work on
+ */
+ private Lookup.Result currentContext;
+ /**
+ * listener to context changes
+ */
+ private LookupListener contextListener;
+ /**
+ * Listens for changes on image file.
+ */
+ private FileChangeListener fileChangeListener;
+ private long lastSaveTime = -1;
+ private DataObject currentDataObject;
+ private static final RequestProcessor WORKER = new RequestProcessor(SVGNavigatorPanel.class.getName());
+
+ @Override
+ public String getDisplayName() {
+ return NbBundle.getMessage(SVGNavigatorPanel.class, "Navigator_DisplayName");
+ }
+
+ @Override
+ public String getDisplayHint() {
+ return NbBundle.getMessage(SVGNavigatorPanel.class, "Navigator_DisplayHint");
+ }
+
+ @Override
+ public JComponent getComponent() {
+ if (lastSaveTime == -1) {
+ lastSaveTime = System.currentTimeMillis();
+ }
+ if (panelUI == null) {
+ panelUI = new SVGPreviewPanel();
+ }
+ return panelUI;
+ }
+
+ @Override
+ public void panelActivated(Lookup context) {
+ // lookup context and listen to result to get notified about context changes
+ currentContext = context.lookup(MY_DATA);
+ currentContext.addLookupListener(getContextListener());
+ // get actual data and recompute content
+ Collection extends SVGDataObject> data = currentContext.allInstances();
+ currentDataObject = getDataObject(data);
+
+ if (currentDataObject == null) {
+ return;
+ }
+
+ if (fileChangeListener == null) {
+ fileChangeListener = new SVGFileChangeAdapter();
+ }
+ currentDataObject.getPrimaryFile().addFileChangeListener(fileChangeListener);
+ setNewContent(currentDataObject);
+ }
+
+ @Override
+ public void panelDeactivated() {
+ currentContext.removeLookupListener(getContextListener());
+ currentContext = null;
+ if (currentDataObject != null) {
+ currentDataObject.getPrimaryFile().removeFileChangeListener(fileChangeListener);
+ }
+ currentDataObject = null;
+ }
+
+ @Override
+ public Lookup getLookup() {
+ // go with default activated Node strategy
+ return null;
+ }
+
+ private void setNewContent(final DataObject dataObject) {
+ if (dataObject == null) {
+ return;
+ }
+
+ WORKER.post(() -> {
+ FileObject fo = dataObject.getPrimaryFile();
+
+ if (fo == null) {
+ return;
+ }
+
+ if (panelUI == null) {
+ getComponent();
+ }
+
+ SVGLoader svgLoader = new SVGLoader();
+ SVGDocument svgDocument = svgLoader.load(fo.toURL());
+
+ SwingUtilities.invokeLater(() -> panelUI.setSVG(svgDocument));
+ });
+ }
+
+ private DataObject getDataObject(Collection extends SVGDataObject> data) {
+ if (data.isEmpty()) {
+ return null;
+ } else {
+ return data.iterator().next();
+ }
+ }
+
+ /**
+ * Accessor for listener to context
+ */
+ private LookupListener getContextListener() {
+ if (contextListener == null) {
+ contextListener = new ContextListener();
+ }
+ return contextListener;
+ }
+
+ /**
+ * Listens to changes of context and triggers proper action
+ */
+ private class ContextListener implements LookupListener {
+
+ @Override
+ public void resultChanged(LookupEvent ev) {
+ currentDataObject = getDataObject(currentContext.allInstances());
+ setNewContent(currentDataObject);
+ }
+ }
+
+ private class SVGFileChangeAdapter extends FileChangeAdapter {
+
+ @Override
+ public void fileChanged(final FileEvent fe) {
+ if (fe.getTime() > lastSaveTime) {
+ lastSaveTime = System.currentTimeMillis();
+
+ // Refresh image viewer
+ SwingUtilities.invokeLater(() -> {
+ try {
+ currentDataObject = DataObject.find(fe.getFile());
+ setNewContent(currentDataObject);
+ } catch (DataObjectNotFoundException ex) {
+ LOG.log(Level.INFO, NbBundle.getMessage(SVGNavigatorPanel.class, "ERR_DataObject"));
+ }
+ });
+ }
+ }
+ }
+}
diff --git a/ide/svg/src/org/netbeans/modules/svg/navigation/SVGPreviewPanel.java b/ide/svg/src/org/netbeans/modules/svg/navigation/SVGPreviewPanel.java
new file mode 100644
index 000000000000..ed0f70769ba1
--- /dev/null
+++ b/ide/svg/src/org/netbeans/modules/svg/navigation/SVGPreviewPanel.java
@@ -0,0 +1,117 @@
+/*
+ * 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 org.netbeans.modules.svg.navigation;
+
+import com.github.weisj.jsvg.SVGDocument;
+import com.github.weisj.jsvg.SVGRenderingHints;
+import com.github.weisj.jsvg.geometry.size.FloatSize;
+import java.awt.Color;
+import java.awt.FontMetrics;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.RenderingHints;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.swing.JPanel;
+import javax.swing.UIManager;
+import org.netbeans.modules.svg.SVGViewerElement;
+import org.openide.awt.GraphicsUtils;
+import org.openide.util.NbBundle;
+
+/**
+ * JPanel used for SVG preview in Navigator window
+ *
+ * @author christian lenz
+ */
+public class SVGPreviewPanel extends JPanel {
+
+ private static final Logger LOG = Logger.getLogger(SVGViewerElement.class.getName());
+
+ private SVGDocument svgDocument;
+ private final int stringGapSize = 10;
+ private final Color background = UIManager.getColor("Table.background");
+ private final Color foreground = UIManager.getColor("Table.foreground");
+
+ public void setSVG(SVGDocument svgDoc) {
+ this.svgDocument = svgDoc;
+ this.setBackground(background);
+ this.revalidate();
+ this.repaint();
+ }
+
+ @Override
+ protected void paintComponent(Graphics g) {
+ GraphicsUtils.configureDefaultRenderingHints(g);
+ super.paintComponent(g);
+
+ if (svgDocument != null) {
+ g.setColor(foreground);
+
+ FloatSize size = svgDocument.size();
+ int originalWidth = (int) size.getWidth();
+ int originalHeight = (int) size.getHeight();
+ String sizes = "Dimensions: " + originalWidth + " x " + originalHeight;
+
+ g.drawString(sizes, (int) (this.getWidth() * 0.05), this.getHeight() - stringGapSize);
+
+ int thumbnailMaxSize = 100;
+
+ double widthRatio = (double) originalWidth / thumbnailMaxSize;
+ double heightRatio = (double) originalHeight / thumbnailMaxSize;
+ double ratio = 1.0;
+
+ if (widthRatio > 1 || heightRatio > 1) {
+ ratio = widthRatio > heightRatio ? widthRatio : heightRatio;
+ }
+
+ int scaledWidth = (int) (originalWidth / ratio);
+ int scaledHeight = (int) (originalHeight / ratio);
+ Graphics2D g2d = (Graphics2D) g.create((this.getWidth() - scaledWidth) / 2, (this.getHeight() - scaledHeight) / 2, scaledWidth, scaledHeight);
+
+ g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+ g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
+ g2d.setRenderingHint(SVGRenderingHints.KEY_MASK_CLIP_RENDERING, SVGRenderingHints.VALUE_MASK_CLIP_RENDERING_ACCURACY);
+
+ g2d.scale(1.0 / ratio, 1.0 / ratio);
+
+ try {
+ svgDocument.render(this, g2d);
+ } catch (Exception ex) {
+ LOG.log(Level.INFO, ex.getMessage());
+
+ drawErrorMessage(g);
+ } finally {
+ g2d.dispose();
+ }
+ } else {
+ drawErrorMessage(g);
+ }
+ }
+
+ private void drawErrorMessage(Graphics g) {
+ g.setColor(Color.RED);
+
+ FontMetrics fm = this.getFontMetrics(g.getFont());
+ String errMessage = NbBundle.getMessage(SVGPreviewPanel.class, "ERR_Thumbnail");
+ int stringWidth = fm.stringWidth(errMessage);
+
+ g.drawString(errMessage, (this.getWidth() - stringWidth) / 2, this.getHeight() / 2);
+ g.dispose();
+ }
+}
diff --git a/ide/svg/src/org/netbeans/modules/svg/resources/svgLogo.png b/ide/svg/src/org/netbeans/modules/svg/resources/svgLogo.png
new file mode 100644
index 000000000000..bf0dde524692
Binary files /dev/null and b/ide/svg/src/org/netbeans/modules/svg/resources/svgLogo.png differ
diff --git a/ide/svg/src/org/netbeans/modules/svg/resources/svgLogo.svg b/ide/svg/src/org/netbeans/modules/svg/resources/svgLogo.svg
new file mode 100644
index 000000000000..d089234f8cbf
--- /dev/null
+++ b/ide/svg/src/org/netbeans/modules/svg/resources/svgLogo.svg
@@ -0,0 +1,67 @@
+
+
+
\ No newline at end of file
diff --git a/ide/svg/src/org/netbeans/modules/svg/toolbar/BackgroundIcon.java b/ide/svg/src/org/netbeans/modules/svg/toolbar/BackgroundIcon.java
new file mode 100644
index 000000000000..2f77670774fc
--- /dev/null
+++ b/ide/svg/src/org/netbeans/modules/svg/toolbar/BackgroundIcon.java
@@ -0,0 +1,75 @@
+/*
+ * 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 org.netbeans.modules.svg.toolbar;
+
+import org.netbeans.modules.svg.BackgroundMode;
+
+import java.awt.*;
+import javax.swing.*;
+import org.netbeans.modules.svg.Utils;
+
+/**
+ *
+ * @author Christian Lenz
+ */
+public class BackgroundIcon implements Icon {
+
+ private final BackgroundMode bgMode;
+ private final Color defaultColor;
+
+ public BackgroundIcon(BackgroundMode bgMode, Color defaultColor) {
+ this.bgMode = bgMode;
+ this.defaultColor = defaultColor;
+ }
+
+ @Override
+ public void paintIcon(Component c, Graphics g, int x, int y) {
+ Graphics2D g2d = (Graphics2D) g;
+
+ int width = getIconWidth();
+ int height = getIconHeight();
+
+ switch (bgMode) {
+ case BLACK -> {
+ g2d.setColor(Color.BLACK);
+ g2d.fillRect(x, y, width, height);
+ }
+ case WHITE -> {
+ g2d.setColor(Color.WHITE);
+ g2d.fillRect(x, y, width, height);
+ }
+ case TRANSPARENT, DARK_TRANSPARENT ->
+ Utils.drawSmallChestTilePattern(g2d, x, y, width, height, bgMode == BackgroundMode.DARK_TRANSPARENT);
+ case DEFAULT -> {
+ g2d.setColor(defaultColor);
+ g2d.fillRect(x, y, width, height);
+ }
+ }
+ }
+
+ @Override
+ public int getIconWidth() {
+ return 16;
+ }
+
+ @Override
+ public int getIconHeight() {
+ return 16;
+ }
+}
diff --git a/ide/svg/src/org/netbeans/modules/svg/toolbar/Bundle.properties b/ide/svg/src/org/netbeans/modules/svg/toolbar/Bundle.properties
new file mode 100644
index 000000000000..7ef4939154d7
--- /dev/null
+++ b/ide/svg/src/org/netbeans/modules/svg/toolbar/Bundle.properties
@@ -0,0 +1,45 @@
+# 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.
+
+LBL_CustomZoomAction=Custom Zoom...
+MSG_InvalidValues=Invalid input values.
+
+ACSD_CustomZoomPanel=N/A
+ACSD_Toolbar=N/A
+ACSD_Out_BTN=N/A
+ACSD_In_BTN=N/A
+
+ACSN_Toolbar=Zoom Toolbar
+
+# mnemonic for action "Zoom In" (the action's name is not displayed, only the mnemonic is used in the UI)
+ACS_In_BTN_Mnem=I
+
+# mnemonic for action "Zoom Out" (the action's name is not displayed, only the mnemonic is used in the UI)
+ACS_Out_BTN_Mnem=O
+
+ACS_EnlargeText=N/A
+ACS_DecreaseText=N/A
+ACS_Zoom_BTN=N/A
+ACS_Background_BTN=N/A
+
+LBL_ChangeBackground=Use {0} Background
+
+# Labels for dimension and size
+LBL_ImageDimensions={0}x{1}
+LBL_ImageSizeBytes={0} Bytes
+LBL_ImageSizeKb={0} KB
+LBL_ImageSizeMb={0} MB
\ No newline at end of file
diff --git a/ide/svg/src/org/netbeans/modules/svg/toolbar/SVGViewerToolbar.java b/ide/svg/src/org/netbeans/modules/svg/toolbar/SVGViewerToolbar.java
new file mode 100644
index 000000000000..645bd230ba40
--- /dev/null
+++ b/ide/svg/src/org/netbeans/modules/svg/toolbar/SVGViewerToolbar.java
@@ -0,0 +1,191 @@
+/*
+ * 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 org.netbeans.modules.svg.toolbar;
+
+import java.awt.Color;
+import org.netbeans.modules.svg.toolbar.actions.CustomZoomAction;
+import org.netbeans.modules.svg.toolbar.actions.ZoomInAction;
+import java.awt.Dimension;
+import java.awt.event.ActionEvent;
+import java.text.DecimalFormat;
+import java.util.ArrayList;
+import java.util.Collection;
+import javax.swing.JButton;
+import javax.swing.JLabel;
+import javax.swing.JToolBar;
+import org.netbeans.modules.svg.BackgroundMode;
+import org.netbeans.modules.svg.SVGViewerElement;
+import org.netbeans.modules.svg.toolbar.actions.ZoomOutAction;
+import org.openide.util.ImageUtilities;
+import org.openide.util.NbBundle;
+import org.openide.util.actions.SystemAction;
+
+/**
+ *
+ * @author Christian Lenz
+ */
+public class SVGViewerToolbar {
+
+ /**
+ * collection of all buttons in the toolbar
+ */
+ private final Collection toolbarButtons = new ArrayList<>(11);
+ private SVGViewerElement svgViewerElement;
+ private final DecimalFormat formatter = new DecimalFormat("#.##"); //NOI18N
+ private CustomZoomAction customZoomAction;
+
+ public JToolBar createToolbar(SVGViewerElement svgViewerElement) {
+ this.svgViewerElement = svgViewerElement;
+
+ JToolBar toolBar = new JToolBar();
+
+ toolBar.putClientProperty("JToolBar.isRollover", Boolean.TRUE); //NOI18N
+ toolBar.setFloatable(false);
+ toolBar.setName(NbBundle.getMessage(SVGViewerToolbar.class, "ACSN_Toolbar"));
+ toolBar.getAccessibleContext().setAccessibleDescription(NbBundle.getMessage(SVGViewerToolbar.class, "ACSD_Toolbar"));
+
+ JButton outButton = new JButton(SystemAction.get(ZoomOutAction.class));
+ outButton.setIcon(ImageUtilities.loadImageIcon("org/netbeans/modules/image/zoomOut.gif", false));
+ outButton.setToolTipText(NbBundle.getMessage(ZoomOutAction.class, "LBL_ZoomOut"));
+ outButton.setMnemonic(NbBundle.getMessage(SVGViewerToolbar.class, "ACS_Out_BTN_Mnem").charAt(0));
+ outButton.getAccessibleContext().setAccessibleDescription(NbBundle.getMessage(SVGViewerToolbar.class, "ACSD_Out_BTN"));
+ outButton.setText("");
+ toolBar.add(outButton);
+ toolbarButtons.add(outButton);
+
+ JButton inButton = new JButton(SystemAction.get(ZoomInAction.class));
+ inButton.setToolTipText(NbBundle.getMessage(ZoomInAction.class, "LBL_ZoomIn"));
+ inButton.setMnemonic(NbBundle.getMessage(SVGViewerToolbar.class, "ACS_In_BTN_Mnem").charAt(0));
+ inButton.getAccessibleContext().setAccessibleDescription(NbBundle.getMessage(SVGViewerToolbar.class, "ACSD_In_BTN"));
+ inButton.setText("");
+ toolBar.add(inButton);
+ toolbarButtons.add(inButton);
+
+ toolBar.addSeparator(new Dimension(11, 0));
+
+ JButton button;
+ toolBar.add(button = createZoomButton(1, 1));
+ toolbarButtons.add(button);
+ toolBar.addSeparator(new Dimension(11, 0));
+ toolBar.add(button = createZoomButton(1, 3));
+ toolbarButtons.add(button);
+ toolBar.add(button = createZoomButton(1, 5));
+ toolbarButtons.add(button);
+ toolBar.add(button = createZoomButton(1, 7));
+ toolbarButtons.add(button);
+ toolBar.addSeparator(new Dimension(11, 0));
+ toolBar.add(button = createZoomButton(3, 1));
+ toolbarButtons.add(button);
+ toolBar.add(button = createZoomButton(5, 1));
+ toolbarButtons.add(button);
+ toolBar.add(button = createZoomButton(7, 1));
+ toolbarButtons.add(button);
+
+ toolBar.addSeparator(new Dimension(11, 0));
+
+ toolBar.add(button = createZoomButton());
+ toolbarButtons.add(button);
+
+ toolBar.addSeparator(new Dimension(11, 0));
+
+ // Backgrounds
+ toolBar.add(button = createCustomBackgroundButton(BackgroundMode.BLACK, Color.BLACK));
+ toolbarButtons.add(button);
+ toolBar.add(button = createCustomBackgroundButton(BackgroundMode.WHITE, Color.WHITE));
+ toolbarButtons.add(button);
+ toolBar.add(button = createCustomBackgroundButton(BackgroundMode.TRANSPARENT, Color.LIGHT_GRAY));
+ toolbarButtons.add(button);
+ toolBar.add(button = createCustomBackgroundButton(BackgroundMode.DARK_TRANSPARENT, Color.DARK_GRAY));
+ toolbarButtons.add(button);
+ toolBar.add(button = createCustomBackgroundButton(BackgroundMode.DEFAULT, Color.WHITE));
+ toolbarButtons.add(button);
+
+ // Image Dimension
+ toolBar.addSeparator(new Dimension(11, 0));
+ toolBar.add(new JLabel(NbBundle.getMessage(SVGViewerToolbar.class, "LBL_ImageDimensions", svgViewerElement.getImageWidth(), svgViewerElement.getImageHeight())));
+
+ // Image File Size in KB, MB
+ if (svgViewerElement.getImageSize() != -1) {
+ toolBar.addSeparator(new Dimension(11, 0));
+ double kb = 1024.0;
+ double mb = kb * kb;
+ final double size;
+ final String label;
+ if (svgViewerElement.getImageSize() >= mb) {
+ size = svgViewerElement.getImageSize() / mb;
+ label = "LBL_ImageSizeMb"; // NOI18N
+ } else if (svgViewerElement.getImageSize() >= kb) {
+ size = svgViewerElement.getImageSize() / kb;
+ label = "LBL_ImageSizeKb"; // NOI18N
+ } else {
+ size = svgViewerElement.getImageSize();
+ label = "LBL_ImageSizeBytes"; //NOI18N
+ }
+
+ toolBar.add(new JLabel(NbBundle.getMessage(SVGViewerToolbar.class, label, formatter.format(size))));
+ }
+ for (JButton jb : toolbarButtons) {
+ jb.setFocusable(false);
+ }
+ return toolBar;
+ }
+
+ /**
+ * Creates zoom button.
+ */
+ private JButton createZoomButton(final int xf, final int yf) {
+ // PENDING buttons should have their own icons.
+ JButton button = new JButton("" + xf + ":" + yf); // NOI18N
+ if (xf < yf) {
+ button.setToolTipText(NbBundle.getMessage(ZoomOutAction.class, "LBL_ZoomOut") + " " + xf + " : " + yf);
+ } else {
+ button.setToolTipText(NbBundle.getMessage(ZoomInAction.class, "LBL_ZoomIn") + " " + xf + " : " + yf);
+ }
+ button.getAccessibleContext().setAccessibleDescription(NbBundle.getMessage(SVGViewerToolbar.class, "ACS_Zoom_BTN"));
+ button.addActionListener((ActionEvent evt) -> {
+ svgViewerElement.customZoom(xf, yf);
+ });
+
+ return button;
+ }
+
+ private JButton createZoomButton() {
+ // PENDING buttons should have their own icons.
+ JButton button = new JButton(SystemAction.get(CustomZoomAction.class));
+ button.setToolTipText(NbBundle.getMessage(CustomZoomAction.class, "LBL_CustomZoom"));
+ button.getAccessibleContext().setAccessibleDescription(NbBundle.getMessage(SVGViewerToolbar.class, "ACS_Zoom_BTN"));
+
+ return button;
+ }
+
+ /**
+ * Creates custom background button.
+ */
+ private JButton createCustomBackgroundButton(BackgroundMode bgMode, Color color) {
+ final JButton button = new JButton(); // NOI18N
+ button.setIcon(new BackgroundIcon(bgMode, color));
+ button.setToolTipText(NbBundle.getMessage(SVGViewerToolbar.class, "LBL_ChangeBackground", bgMode.name()));
+ button.getAccessibleContext().setAccessibleDescription(NbBundle.getMessage(SVGViewerToolbar.class, "ACS_Background_BTN"));
+ button.addActionListener((ActionEvent evt) -> {
+ svgViewerElement.changeBackground(bgMode);
+ });
+
+ return button;
+ }
+}
diff --git a/ide/svg/src/org/netbeans/modules/svg/toolbar/actions/Bundle.properties b/ide/svg/src/org/netbeans/modules/svg/toolbar/actions/Bundle.properties
new file mode 100644
index 000000000000..438b6b046b85
--- /dev/null
+++ b/ide/svg/src/org/netbeans/modules/svg/toolbar/actions/Bundle.properties
@@ -0,0 +1,34 @@
+# 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.
+
+## Toolbbar action labels
+LBL_ZoomIn=Zoom In
+LBL_ZoomOut=Zoom Out
+LBL_CustomZoom=Customize Zoom
+LBL_EnlargeFactor=&Enlarge Factor:
+LBL_DecreaseFactor=&Decrease Factor:
+
+# CustomZoomAction
+LBL_XtoY=X:Y
+LBL_CustomZoomAction=Custom Zoom...
+MSG_InvalidValues=Invalid input values.
+
+ACSD_CustomZoomPanel=N/A
+
+ACS_EnlargeText=N/A
+
+ACS_DecreaseText=N/A
\ No newline at end of file
diff --git a/ide/svg/src/org/netbeans/modules/svg/toolbar/actions/CustomZoomAction.java b/ide/svg/src/org/netbeans/modules/svg/toolbar/actions/CustomZoomAction.java
new file mode 100644
index 000000000000..b71f2a7ba5ab
--- /dev/null
+++ b/ide/svg/src/org/netbeans/modules/svg/toolbar/actions/CustomZoomAction.java
@@ -0,0 +1,142 @@
+/*
+ * 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 org.netbeans.modules.svg.toolbar.actions;
+
+import java.awt.Dialog;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import org.netbeans.modules.svg.SVGViewerElement;
+
+import org.openide.*;
+import org.openide.awt.ActionID;
+import org.openide.awt.ActionRegistration;
+import org.openide.util.actions.CallableSystemAction;
+import org.openide.util.HelpCtx;
+import org.openide.util.Lookup;
+import org.openide.util.NbBundle;
+import org.openide.windows.TopComponent;
+
+/**
+ * Action that can always be invoked and work procedurally.
+ *
+ * @author Christian Lenz
+ */
+@ActionID(id = "org.netbeans.modules.svg.toolbar.CustomZoomAction", category = "View")
+@ActionRegistration(lazy = false, displayName = "#LBL_CustomZoom")
+public class CustomZoomAction extends CallableSystemAction {
+
+ /**
+ * Generated serial version UID.
+ */
+ static final long serialVersionUID = 8247068408606777895L;
+
+ /**
+ * Actually performs action.
+ */
+ @Override
+ public void performAction() {
+ TopComponent currentComponent = TopComponent.getRegistry().getActivated();
+ Lookup tcLookup = currentComponent != null ? currentComponent.getLookup() : null;
+ SVGViewerElement svgViewerElement = tcLookup != null ? tcLookup.lookup(SVGViewerElement.class) : null;
+ if (svgViewerElement != null) {
+ final Dialog[] dialogs = new Dialog[1];
+ final CustomZoomPanel zoomPanel = new CustomZoomPanel();
+
+ zoomPanel.setEnlargeFactor(1);
+ zoomPanel.setDecreaseFactor(1);
+
+ DialogDescriptor dd = new DialogDescriptor(
+ zoomPanel,
+ NbBundle.getMessage(CustomZoomAction.class, "LBL_CustomZoomAction"),
+ true,
+ DialogDescriptor.OK_CANCEL_OPTION,
+ DialogDescriptor.OK_OPTION,
+ new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent ev) {
+ if (ev.getSource() == DialogDescriptor.OK_OPTION) {
+ int enlargeFactor;
+ int decreaseFactor;
+
+ try {
+ enlargeFactor = zoomPanel.getEnlargeFactor();
+ decreaseFactor = zoomPanel.getDecreaseFactor();
+ } catch (NumberFormatException nfe) {
+ notifyInvalidInput();
+ return;
+ }
+
+ // Invalid values.
+ if (enlargeFactor == 0 || decreaseFactor == 0) {
+ notifyInvalidInput();
+ return;
+ }
+
+ svgViewerElement.customZoom(enlargeFactor, decreaseFactor);
+ dialogs[0].setVisible(false);
+ dialogs[0].dispose();
+ } else {
+ dialogs[0].setVisible(false);
+ dialogs[0].dispose();
+ }
+ }
+
+ private void notifyInvalidInput() {
+ DialogDisplayer.getDefault().notify(new NotifyDescriptor.Message(
+ NbBundle.getMessage(CustomZoomAction.class, "MSG_InvalidValues"),
+ NotifyDescriptor.ERROR_MESSAGE
+ ));
+ }
+
+ } // End of annonymnous ActionListener.
+ );
+ dialogs[0] = DialogDisplayer.getDefault().createDialog(dd);
+ dialogs[0].setVisible(true);
+ }
+ }
+
+ /**
+ * Gets action name. Implements superclass abstract method.
+ */
+ @Override
+ public String getName() {
+ return "";
+ }
+
+ /**
+ * Gets action help context. Implemenets superclass abstract method.
+ */
+ @Override
+ public HelpCtx getHelpCtx() {
+ return new HelpCtx("org.netbeans.modules.svg.toolbar.CustomZoomAction");
+ }
+
+ /**
+ * Gets icon resource. Overrides superclass method.
+ */
+ @Override
+ protected String iconResource() {
+ return "org/netbeans/modules/image/customZoom.gif"; // NOI18N
+ }
+
+ @Override
+ protected boolean asynchronous() {
+ return false;
+ }
+}
diff --git a/ide/svg/src/org/netbeans/modules/svg/toolbar/actions/CustomZoomPanel.form b/ide/svg/src/org/netbeans/modules/svg/toolbar/actions/CustomZoomPanel.form
new file mode 100644
index 000000000000..5d2623fe09c1
--- /dev/null
+++ b/ide/svg/src/org/netbeans/modules/svg/toolbar/actions/CustomZoomPanel.form
@@ -0,0 +1,100 @@
+
+
+
+
+
diff --git a/ide/svg/src/org/netbeans/modules/svg/toolbar/actions/CustomZoomPanel.java b/ide/svg/src/org/netbeans/modules/svg/toolbar/actions/CustomZoomPanel.java
new file mode 100644
index 000000000000..01221a6639c5
--- /dev/null
+++ b/ide/svg/src/org/netbeans/modules/svg/toolbar/actions/CustomZoomPanel.java
@@ -0,0 +1,157 @@
+/*
+ * 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 org.netbeans.modules.svg.toolbar.actions;
+
+import javax.swing.JPanel;
+import javax.swing.text.AttributeSet;
+import javax.swing.text.BadLocationException;
+import javax.swing.text.PlainDocument;
+
+import org.openide.util.NbBundle;
+
+/**
+ * This class define a panel for "Custom zoom" dialog.
+ *
+ * @author Christian Lenz
+ */
+public class CustomZoomPanel extends JPanel {
+
+ /**
+ * Creates new form CustomZoomPane
+ */
+ public CustomZoomPanel() {
+ initComponents();
+ initAccessibility();
+ }
+
+ /**
+ * This method is called from within the constructor to initialize the form.
+ * WARNING: Do NOT modify this code. The content of this method is always
+ * regenerated by the Form Editor.
+ */
+ // //GEN-BEGIN:initComponents
+ private void initComponents() {
+ java.awt.GridBagConstraints gridBagConstraints;
+
+ enlargeLabel = new javax.swing.JLabel();
+ enlargeText = new javax.swing.JTextField();
+ decreasingLabel = new javax.swing.JLabel();
+ decreaseText = new javax.swing.JTextField();
+
+ setLayout(new java.awt.GridBagLayout());
+
+ enlargeLabel.setLabelFor(enlargeText);
+ org.openide.awt.Mnemonics.setLocalizedText(enlargeLabel, NbBundle.getBundle(CustomZoomPanel.class).getString("LBL_EnlargeFactor")); // NOI18N
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
+ gridBagConstraints.insets = new java.awt.Insets(12, 12, 0, 0);
+ add(enlargeLabel, gridBagConstraints);
+
+ enlargeText.setDocument(new WholeNumberDocument());
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
+ gridBagConstraints.weightx = 1.0;
+ gridBagConstraints.insets = new java.awt.Insets(12, 11, 0, 11);
+ add(enlargeText, gridBagConstraints);
+
+ decreasingLabel.setLabelFor(decreaseText);
+ org.openide.awt.Mnemonics.setLocalizedText(decreasingLabel, NbBundle.getBundle(CustomZoomPanel.class).getString("LBL_DecreaseFactor")); // NOI18N
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 0;
+ gridBagConstraints.gridy = 1;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
+ gridBagConstraints.insets = new java.awt.Insets(5, 12, 11, 0);
+ add(decreasingLabel, gridBagConstraints);
+
+ decreaseText.setDocument(new WholeNumberDocument());
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 1;
+ gridBagConstraints.gridy = 1;
+ gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
+ gridBagConstraints.weightx = 1.0;
+ gridBagConstraints.insets = new java.awt.Insets(5, 11, 11, 11);
+ add(decreaseText, gridBagConstraints);
+ }// //GEN-END:initComponents
+
+ private void initAccessibility() {
+ getAccessibleContext().setAccessibleDescription(NbBundle.getBundle(CustomZoomPanel.class).getString("ACSD_CustomZoomPanel"));
+ enlargeText.getAccessibleContext().setAccessibleDescription(NbBundle.getBundle(CustomZoomPanel.class).getString("ACS_EnlargeText"));
+ decreaseText.getAccessibleContext().setAccessibleDescription(NbBundle.getBundle(CustomZoomPanel.class).getString("ACS_DecreaseText"));
+
+ }
+
+ public int getEnlargeFactor() {
+ return Integer.parseInt(enlargeText.getText());
+ }
+
+ public void setEnlargeFactor(int enlargeFactor) {
+ enlargeText.setText("" + enlargeFactor); // NOI18N
+ }
+
+ public int getDecreaseFactor() {
+ return Integer.parseInt(decreaseText.getText());
+ }
+
+ public void setDecreaseFactor(int decreaseFactor) {
+ decreaseText.setText("" + decreaseFactor); // NOI18N
+ }
+
+ // Variables declaration - do not modify//GEN-BEGIN:variables
+ private javax.swing.JTextField decreaseText;
+ private javax.swing.JLabel decreasingLabel;
+ private javax.swing.JLabel enlargeLabel;
+ private javax.swing.JTextField enlargeText;
+ // End of variables declaration//GEN-END:variables
+
+ /**
+ * Documnet which accepts only digit chars.
+ */
+ private static class WholeNumberDocument extends PlainDocument {
+
+ /**
+ * Overrides superclass method.
+ */
+ @Override
+ public void insertString(int offs, String str, AttributeSet a)
+ throws BadLocationException {
+ char[] source = str.toCharArray();
+ StringBuilder result = new StringBuilder();
+
+ for (int i = 0; i < source.length; i++) {
+ if (Character.isDigit(source[i])) {
+ result.append(source[i]);
+ } else {
+ if (Boolean.getBoolean("netbeans.debug.excpetions")) // NOI18N
+ {
+ System.err.println("Image: Trying insert non-digit in custom zoom action."); // NOI18N
+ }
+ }
+ }
+
+ // There has to be some number added.
+ if (result.length() == 0) {
+ return;
+ }
+
+ super.insertString(offs, result.toString(), a);
+ }
+
+ } // End of nested class WholeNumberDocument.
+
+}
diff --git a/ide/svg/src/org/netbeans/modules/svg/toolbar/actions/ZoomInAction.java b/ide/svg/src/org/netbeans/modules/svg/toolbar/actions/ZoomInAction.java
new file mode 100644
index 000000000000..a7ed9b95f418
--- /dev/null
+++ b/ide/svg/src/org/netbeans/modules/svg/toolbar/actions/ZoomInAction.java
@@ -0,0 +1,93 @@
+/*
+ * 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 org.netbeans.modules.svg.toolbar.actions;
+
+import org.netbeans.modules.svg.SVGViewerElement;
+import org.openide.awt.ActionID;
+import org.openide.awt.ActionRegistration;
+import org.openide.util.actions.CallableSystemAction;
+import org.openide.util.HelpCtx;
+import org.openide.util.Lookup;
+import org.openide.util.NbBundle;
+import org.openide.windows.TopComponent;
+
+/**
+ * Action which zooms in of an svg.
+ *
+ * @author Christian Lenz
+ */
+@ActionID(id = "org.netbeans.modules.svg.toolbar.ZoomInAction", category = "View")
+@ActionRegistration(lazy = false, displayName = "#LBL_ZoomIn")
+public class ZoomInAction extends CallableSystemAction {
+
+ /**
+ * Generated serial version UID.
+ */
+ static final long serialVersionUID = -8705899978543961455L;
+
+ /**
+ * Perform action.
+ */
+ @Override
+ public void performAction() {
+ TopComponent currentComponent = TopComponent.getRegistry().getActivated();
+ Lookup tcLookup = currentComponent != null ? currentComponent.getLookup() : null;
+ SVGViewerElement svgViewerElement = tcLookup != null ? tcLookup.lookup(SVGViewerElement.class) : null;
+ if (svgViewerElement != null) {
+ svgViewerElement.zoomIn();
+ }
+ }
+
+ /**
+ * Gets action name. Implements superclass abstract method.
+ */
+ @Override
+ public String getName() {
+ return NbBundle.getMessage(ZoomInAction.class, "LBL_ZoomIn"); //NOI18N
+ }
+
+ /**
+ * Gets action help context. Implemenets superclass abstract method.
+ */
+ @Override
+ public HelpCtx getHelpCtx() {
+ return HelpCtx.DEFAULT_HELP;
+ }
+
+ /**
+ * Overrides superclass method.
+ */
+ @Override
+ public boolean isEnabled() {
+ return true;
+ }
+
+ /**
+ * Gets icon resource. Overrides superclass method.
+ */
+ @Override
+ protected String iconResource() {
+ return "org/netbeans/modules/image/zoomIn.gif"; // NOI18N
+ }
+
+ @Override
+ protected boolean asynchronous() {
+ return false;
+ }
+}
diff --git a/ide/svg/src/org/netbeans/modules/svg/toolbar/actions/ZoomOutAction.java b/ide/svg/src/org/netbeans/modules/svg/toolbar/actions/ZoomOutAction.java
new file mode 100644
index 000000000000..f9baeed009bf
--- /dev/null
+++ b/ide/svg/src/org/netbeans/modules/svg/toolbar/actions/ZoomOutAction.java
@@ -0,0 +1,93 @@
+/*
+ * 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 org.netbeans.modules.svg.toolbar.actions;
+
+import org.netbeans.modules.svg.SVGViewerElement;
+import org.openide.awt.ActionID;
+import org.openide.awt.ActionRegistration;
+import org.openide.util.actions.CallableSystemAction;
+import org.openide.util.HelpCtx;
+import org.openide.util.Lookup;
+import org.openide.util.NbBundle;
+import org.openide.windows.TopComponent;
+
+/**
+ * Action which zooms out of an svg.
+ *
+ * @author Christian Lenz
+ */
+@ActionID(id = "org.netbeans.modules.svg.toolbar.ZoomOutAction", category = "View")
+@ActionRegistration(lazy = false, displayName = "#LBL_ZoomOut")
+public class ZoomOutAction extends CallableSystemAction {
+
+ /**
+ * Generated serial version UID.
+ */
+ static final long serialVersionUID = 1859897546585041051L;
+
+ /**
+ * Peforms action.
+ */
+ @Override
+ public void performAction() {
+ TopComponent currentComponent = TopComponent.getRegistry().getActivated();
+ Lookup tcLookup = currentComponent != null ? currentComponent.getLookup() : null;
+ SVGViewerElement svgViewerElement = tcLookup != null ? tcLookup.lookup(SVGViewerElement.class) : null;
+ if (svgViewerElement != null) {
+ svgViewerElement.zoomOut();
+ }
+ }
+
+ /**
+ * Gets name of action. Implements superclass abstract method.
+ */
+ @Override
+ public String getName() {
+ return NbBundle.getMessage(ZoomOutAction.class, "LBL_ZoomOut");
+ }
+
+ /**
+ * Gets help context for action. Implements superclass abstract method.
+ */
+ @Override
+ public org.openide.util.HelpCtx getHelpCtx() {
+ return HelpCtx.DEFAULT_HELP;
+ }
+
+ /**
+ * Overrides superclass method.
+ */
+ @Override
+ public boolean isEnabled() {
+ return true;
+ }
+
+ /**
+ * Gets icon resource. Overrides superclass method.
+ */
+ @Override
+ protected String iconResource() {
+ return "org/netbeans/modules/image/zoomOut.gif"; // NOI18N
+ }
+
+ @Override
+ protected boolean asynchronous() {
+ return false;
+ }
+}
diff --git a/ide/xml/nbproject/project.xml b/ide/xml/nbproject/project.xml
index 3661a55839f1..665103094ec9 100644
--- a/ide/xml/nbproject/project.xml
+++ b/ide/xml/nbproject/project.xml
@@ -333,6 +333,7 @@
+ org.netbeans.modules.svg
org.netbeans.modules.xml.schema
org.netbeans.modules.xml.tax
org.netbeans.modules.xml.tools
@@ -349,4 +350,4 @@
-
+
\ No newline at end of file
diff --git a/nbbuild/cluster.properties b/nbbuild/cluster.properties
index b9a79066f948..82f72697a5f1 100644
--- a/nbbuild/cluster.properties
+++ b/nbbuild/cluster.properties
@@ -493,6 +493,7 @@ nb.cluster.ide=\
spi.tasklist,\
spi.viewmodel,\
subversion,\
+ svg,\
swing.validation,\
target.iterator,\
tasklist.kit,\