You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hop.apache.org by ha...@apache.org on 2021/03/02 19:51:11 UTC

[incubator-hop] branch master updated: HOP-2575 : Create a file explorer perspective

This is an automated email from the ASF dual-hosted git repository.

hansva pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-hop.git


The following commit(s) were added to refs/heads/master by this push:
     new 6351158  HOP-2575 : Create a file explorer perspective
     new f906040  Merge pull request #660 from mattcasters/master
6351158 is described below

commit 6351158cd4ea91176716db31c3d5b14dc99c100e
Author: Matt Casters <ma...@gmail.com>
AuthorDate: Tue Mar 2 18:15:43 2021 +0100

    HOP-2575 : Create a file explorer perspective
---
 .../java/org/apache/hop/core/svg/SvgCache.java     |   7 +-
 .../hop/projects/xp/ExplorerPerspectiveRoot.java   |  58 ++
 .../transforms/types/JsonExplorerFileType.java     |  74 ++
 .../types/JsonExplorerFileTypeHandler.java         | 114 +++
 .../transforms/json/src/main/resources/json.svg    |  17 +
 .../sasinput/types/SasExplorerFileType.java        |  70 ++
 .../sasinput/types/SasExplorerFileTypeHandler.java | 100 +++
 .../src/main/resources/{SASInput.svg => SAS.svg}   |   2 +-
 .../sasinput/src/main/resources/SASInput.svg       |   1 +
 .../transforms/types/CsvExplorerFileType.java      |  68 ++
 .../types/CsvExplorerFileTypeHandler.java          |  78 ++
 .../transforms/types/TextExplorerFileType.java     |  68 ++
 .../types/TextExplorerFileTypeHandler.java         |  80 ++
 .../textfile/src/main/resources/textfile.svg       |  15 +
 .../transforms/xml/types/XmlExplorerFileType.java  |  68 ++
 .../xml/types/XmlExplorerFileTypeHandler.java      |  80 ++
 .../org/apache/hop/core/SwtUniversalImageSvg.java  |   6 +-
 .../hop/ui/core/metadata/MetadataFileType.java     |   4 +
 .../ui/core/metadata/MetadataFileTypeHandler.java  |   4 +-
 .../apache/hop/ui/core/vfs/HopVfsFileDialog.java   |   2 +-
 .../main/java/org/apache/hop/ui/hopgui/HopGui.java |   2 +-
 .../apache/hop/ui/hopgui/HopGuiExtensionPoint.java |   3 +
 .../ui/hopgui/delegates/HopGuiFileDelegate.java    |   8 +-
 .../apache/hop/ui/hopgui/file/HopFileTypeBase.java |  13 +-
 .../apache/hop/ui/hopgui/file/IHopFileType.java    |  59 +-
 .../hop/ui/hopgui/file/empty/EmptyFileType.java    |   7 +-
 .../hopgui/file/pipeline/HopPipelineFileType.java  |   6 +-
 .../hopgui/file/workflow/HopWorkflowFileType.java  |   6 +-
 .../hopgui/perspective/HopPerspectiveManager.java  |   4 +-
 .../hopgui/perspective/explorer/ExplorerFile.java  | 198 +++++
 .../perspective/explorer/ExplorerPerspective.java  | 812 +++++++++++++++++++++
 .../explorer/IExplorerFileContentCallback.java     |  24 +
 .../explorer/IExplorerFilePaintListener.java       |  31 +
 .../explorer/file/ExplorerFileType.java}           |  23 +-
 .../explorer/file/ExplorerFileTypeHandler.java     | 156 ++++
 .../perspective/explorer/file/FileDetails.java     |  63 ++
 .../explorer/file/IExplorerFileType.java           |  37 +
 .../explorer/file/IExplorerFileTypeHandler.java    |  33 +
 .../file/capabilities/FileTypeCapabilities.java    |  31 +
 .../explorer/file/types/FolderFileType.java        | 116 +++
 .../explorer/file/types/GenericFileType.java       | 111 +++
 .../file/types/base/BaseExplorerFileType.java      | 188 +++++
 .../types/base/BaseExplorerFileTypeHandler.java    | 246 +++++++
 .../file/types/log/LogExplorerFileType.java        |  69 ++
 .../file/types/log/LogExplorerFileTypeHandler.java |  83 +++
 .../file/types/svg/SvgExplorerFileType.java        |  71 ++
 .../file/types/svg/SvgExplorerFileTypeHandler.java |  92 +++
 47 files changed, 3347 insertions(+), 61 deletions(-)

diff --git a/core/src/main/java/org/apache/hop/core/svg/SvgCache.java b/core/src/main/java/org/apache/hop/core/svg/SvgCache.java
index bd3a82b..d13e501 100644
--- a/core/src/main/java/org/apache/hop/core/svg/SvgCache.java
+++ b/core/src/main/java/org/apache/hop/core/svg/SvgCache.java
@@ -31,6 +31,7 @@ import org.w3c.dom.svg.SVGDocument;
 import org.w3c.dom.svg.SVGSVGElement;
 
 import java.awt.geom.Rectangle2D;
+import java.io.FileInputStream;
 import java.io.InputStream;
 import java.util.HashMap;
 import java.util.Map;
@@ -71,9 +72,11 @@ public class SvgCache {
       String parser = XMLResourceDescriptor.getXMLParserClassName();
       SAXSVGDocumentFactory factory = new SAXSVGDocumentFactory( parser );
       InputStream svgStream = svgFile.getClassLoader().getResourceAsStream( svgFile.getFilename() );
-
       if ( svgStream == null ) {
-        throw new HopException( "Unable to find file '" + svgFile.getFilename() + "'" );
+
+        // Retry on the regular filesystem...
+        //
+        svgStream = new FileInputStream( svgFile.getFilename() );
       }
       SVGDocument svgDocument = factory.createSVGDocument( svgFile.getFilename(), svgStream );
       SVGSVGElement elSVG = svgDocument.getRootElement();
diff --git a/plugins/misc/projects/src/main/java/org/apache/hop/projects/xp/ExplorerPerspectiveRoot.java b/plugins/misc/projects/src/main/java/org/apache/hop/projects/xp/ExplorerPerspectiveRoot.java
new file mode 100644
index 0000000..c55c5cf
--- /dev/null
+++ b/plugins/misc/projects/src/main/java/org/apache/hop/projects/xp/ExplorerPerspectiveRoot.java
@@ -0,0 +1,58 @@
+/*
+ * 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.apache.hop.projects.xp;
+
+import org.apache.hop.core.exception.HopException;
+import org.apache.hop.core.extension.ExtensionPoint;
+import org.apache.hop.core.extension.IExtensionPoint;
+import org.apache.hop.core.logging.ILogChannel;
+import org.apache.hop.core.util.StringUtil;
+import org.apache.hop.core.variables.IVariables;
+import org.apache.hop.projects.config.ProjectsConfig;
+import org.apache.hop.projects.config.ProjectsConfigSingleton;
+import org.apache.hop.projects.project.ProjectConfig;
+import org.apache.hop.ui.core.gui.HopNamespace;
+import org.apache.hop.ui.hopgui.perspective.explorer.ExplorerPerspective;
+
+@ExtensionPoint(
+    id = "ExplorerPerspectiveRoot",
+    description = "Set the root folder and name of the current project in the explorer perspective",
+    extensionPointId = "HopGuiDetermineExplorerRoot")
+public class ExplorerPerspectiveRoot
+    implements IExtensionPoint<ExplorerPerspective.DetermineRootFolderExtension> {
+  @Override
+  public void callExtensionPoint(
+      ILogChannel log, IVariables variables, ExplorerPerspective.DetermineRootFolderExtension ext)
+      throws HopException {
+
+    // Get the current project...
+    //
+    String projectName = HopNamespace.getNamespace();
+    if ( StringUtil.isEmpty(projectName)) {
+      return;
+    }
+    ProjectsConfig config = ProjectsConfigSingleton.getConfig();
+    ProjectConfig projectConfig = config.findProjectConfig( projectName );
+    if (projectConfig==null) {
+      return;
+    }
+    ext.rootFolder = projectConfig.getProjectHome();
+    ext.rootName = projectName;
+  }
+}
diff --git a/plugins/transforms/json/src/main/java/org/apache/hop/pipeline/transforms/types/JsonExplorerFileType.java b/plugins/transforms/json/src/main/java/org/apache/hop/pipeline/transforms/types/JsonExplorerFileType.java
new file mode 100644
index 0000000..cddb7a8
--- /dev/null
+++ b/plugins/transforms/json/src/main/java/org/apache/hop/pipeline/transforms/types/JsonExplorerFileType.java
@@ -0,0 +1,74 @@
+/*
+ * 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.apache.hop.pipeline.transforms.types;
+
+import org.apache.hop.core.Props;
+import org.apache.hop.core.exception.HopException;
+import org.apache.hop.core.logging.LogChannel;
+import org.apache.hop.core.variables.IVariables;
+import org.apache.hop.ui.core.PropsUi;
+import org.apache.hop.ui.hopgui.HopGui;
+import org.apache.hop.ui.hopgui.file.HopFileTypePlugin;
+import org.apache.hop.ui.hopgui.file.IHopFileType;
+import org.apache.hop.ui.hopgui.file.IHopFileTypeHandler;
+import org.apache.hop.ui.hopgui.file.empty.EmptyHopFileTypeHandler;
+import org.apache.hop.ui.hopgui.perspective.explorer.ExplorerFile;
+import org.apache.hop.ui.hopgui.perspective.explorer.ExplorerPerspective;
+import org.apache.hop.ui.hopgui.perspective.explorer.file.IExplorerFileType;
+import org.apache.hop.ui.hopgui.perspective.explorer.file.capabilities.FileTypeCapabilities;
+import org.apache.hop.ui.hopgui.perspective.explorer.file.types.base.BaseExplorerFileType;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.layout.FormAttachment;
+import org.eclipse.swt.layout.FormData;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Text;
+
+import java.io.File;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+
+@HopFileTypePlugin(
+    id = "JsonExplorerFileType",
+    name = "JSON File Type",
+    description = "JSON file handling in the explorer perspective",
+    image = "json.svg")
+public class JsonExplorerFileType extends BaseExplorerFileType<JsonExplorerFileTypeHandler>
+    implements IExplorerFileType<JsonExplorerFileTypeHandler> {
+
+  public JsonExplorerFileType() {
+    super(
+        "JSON File",
+        ".json",
+        new String[] {"*.json"},
+        new String[] {"JSON files"},
+        FileTypeCapabilities.getCapabilities( IHopFileType.CAPABILITY_SAVE ));
+  }
+
+  @Override
+  public JsonExplorerFileTypeHandler createFileTypeHandler(
+      HopGui hopGui, ExplorerPerspective perspective, ExplorerFile file) {
+    return new JsonExplorerFileTypeHandler(hopGui, perspective, file);
+  }
+
+  @Override
+  public IHopFileTypeHandler newFile(HopGui hopGui, IVariables parentVariableSpace)
+      throws HopException {
+    return new EmptyHopFileTypeHandler();
+  }
+}
diff --git a/plugins/transforms/json/src/main/java/org/apache/hop/pipeline/transforms/types/JsonExplorerFileTypeHandler.java b/plugins/transforms/json/src/main/java/org/apache/hop/pipeline/transforms/types/JsonExplorerFileTypeHandler.java
new file mode 100644
index 0000000..6ed4da4
--- /dev/null
+++ b/plugins/transforms/json/src/main/java/org/apache/hop/pipeline/transforms/types/JsonExplorerFileTypeHandler.java
@@ -0,0 +1,114 @@
+/*
+ * 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.apache.hop.pipeline.transforms.types;
+
+import org.apache.hop.core.Props;
+import org.apache.hop.core.exception.HopException;
+import org.apache.hop.core.logging.LogChannel;
+import org.apache.hop.ui.core.PropsUi;
+import org.apache.hop.ui.hopgui.HopGui;
+import org.apache.hop.ui.hopgui.file.IHopFileTypeHandler;
+import org.apache.hop.ui.hopgui.perspective.explorer.ExplorerFile;
+import org.apache.hop.ui.hopgui.perspective.explorer.ExplorerPerspective;
+import org.apache.hop.ui.hopgui.perspective.explorer.file.IExplorerFileTypeHandler;
+import org.apache.hop.ui.hopgui.perspective.explorer.file.types.base.BaseExplorerFileTypeHandler;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.layout.FormAttachment;
+import org.eclipse.swt.layout.FormData;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Text;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+
+/** How do we handle a JSON file in file explorer perspective? */
+public class JsonExplorerFileTypeHandler extends BaseExplorerFileTypeHandler implements IExplorerFileTypeHandler {
+
+  private Text wText;
+
+  public JsonExplorerFileTypeHandler(
+      HopGui hopGui, ExplorerPerspective perspective, ExplorerFile explorerFile) {
+    super(hopGui, perspective, explorerFile);
+  }
+
+
+  @Override
+  public void renderFile(Composite composite) {
+    // Render the file by simply showing the file content as a text widget...
+    //
+    wText = new Text(composite, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL);
+    PropsUi.getInstance().setLook(wText, Props.WIDGET_STYLE_FIXED);
+    FormData fdText = new FormData();
+    fdText.left = new FormAttachment(0, 0);
+    fdText.right = new FormAttachment(100, 0);
+    fdText.top = new FormAttachment(0, 0);
+    fdText.bottom = new FormAttachment(100, 0);
+    wText.setLayoutData(fdText);
+
+    // TODO: add bottom section to show status, size, changed dates, cursor position...
+    // TODO: options for validation, pretty print, ...
+    // TODO: options for reading the file with a JSON Input transform
+    // TODO: option to discard changes (reload from disk)
+    // TODO: find in file feature, hook it up to the project find function
+    //
+
+    // Load the content of the JSON file...
+    //
+    File file = new File(explorerFile.getFilename());
+    if (file.exists()) {
+      try {
+        String contents = new String( Files.readAllBytes(file.toPath()), StandardCharsets.UTF_8);
+        wText.setText(contents);
+      } catch (Exception e) {
+        LogChannel.UI.logError(
+          "Error reading contents of file '" + explorerFile.getFilename() + "'", e);
+      }
+    }
+
+    // If the widget changes after this it's been changed by the user
+    //
+    wText.addModifyListener( e->explorerFile.setChanged() );
+  }
+
+
+  @Override
+  public void save() throws HopException {
+
+    try {
+      // Save the current explorer file ....
+      //
+      String filename = explorerFile.getFilename();
+
+      // Save the file...
+      //
+      try( OutputStream outputStream = new FileOutputStream( filename ) ) {
+        outputStream.write(wText.getText().getBytes( StandardCharsets.UTF_8 ));
+        outputStream.flush();
+      }
+
+      explorerFile.clearChanged();
+
+    } catch(Exception e) {
+      throw new HopException("Unable to save JSON file '"+explorerFile.getFilename()+"'", e);
+    }
+  }
+}
diff --git a/plugins/transforms/json/src/main/resources/json.svg b/plugins/transforms/json/src/main/resources/json.svg
new file mode 100644
index 0000000..717bdf4
--- /dev/null
+++ b/plugins/transforms/json/src/main/resources/json.svg
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   viewBox="0 0 14.464 14.466"
+   height="14.466"
+   width="14.464"
+   y="0px"
+   x="0px"
+   version="1.1">
+	<path
+     d="m 8.105,0.196 c 2.116,0.685 3.317,2.208 3.799,3.902 0.019,0.067 0.032,0.135 0.049,0.203 0.025,0.101 0.053,0.202 0.073,0.304 0.697,3.478 -1.441,7.386 -4.787,6.223 0,0 0.025,-0.013 0.034,-0.017 -10e-4,0 -0.002,0 -0.003,-10e-4 l -0.039,0.019 c 0,0 -2.143,-0.949 -2.143,-3.734 0,-2.636 2.143,-3.456 2.143,-3.456 0,0 0.042,0.016 0.113,0.05 0,0 -0.002,-10e-4 -0.002,-10e-4 C 7.314,3.673 7.292,3.662 7.275,3.653 7.261,3.647 7.232,3.632 7.232,3.632 2.69,2.717 1.198,9.593 4.302,12.89 c 0.735, [...]
+     style="fill:#3d6480" />
+  <path
+     d="M 7.232,14.465 C 5.993,14.229 5.024,13.655 4.302,12.889 1.198,9.592 2.689,2.716 7.232,3.631 c 0,0 0.029,0.015 0.043,0.021 0.018,0.009 0.039,0.02 0.067,0.035 0.001,0 0.002,0.001 0.002,0.001 0.436,0.231 2.038,1.233 2.038,3.526 0,2.552 -1.929,3.512 -2.109,3.596 -0.009,0.004 -0.034,0.017 -0.034,0.017 3.346,1.163 5.484,-2.745 4.787,-6.223 C 12.005,4.502 11.978,4.401 11.953,4.3 11.936,4.232 11.923,4.164 11.904,4.097 11.42,2.402 10.218,0.879 8.105,0.195 8.09,0.19 8.077,0.184 8.062,0.179 [...]
+     style="fill:#9fb2c0" />
+</svg>
diff --git a/plugins/transforms/sasinput/src/main/java/org/apache/hop/pipeline/transforms/sasinput/types/SasExplorerFileType.java b/plugins/transforms/sasinput/src/main/java/org/apache/hop/pipeline/transforms/sasinput/types/SasExplorerFileType.java
new file mode 100644
index 0000000..46a357f
--- /dev/null
+++ b/plugins/transforms/sasinput/src/main/java/org/apache/hop/pipeline/transforms/sasinput/types/SasExplorerFileType.java
@@ -0,0 +1,70 @@
+/*
+ * 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.apache.hop.pipeline.transforms.sasinput.types;
+
+import com.epam.parso.Column;
+import com.epam.parso.ColumnFormat;
+import com.epam.parso.impl.SasFileReaderImpl;
+import org.apache.hop.core.Const;
+import org.apache.hop.core.Props;
+import org.apache.hop.core.exception.HopException;
+import org.apache.hop.core.variables.IVariables;
+import org.apache.hop.core.vfs.HopVfs;
+import org.apache.hop.pipeline.transforms.sasinput.SasUtil;
+import org.apache.hop.ui.core.PropsUi;
+import org.apache.hop.ui.hopgui.HopGui;
+import org.apache.hop.ui.hopgui.file.HopFileTypePlugin;
+import org.apache.hop.ui.hopgui.file.IHopFileTypeHandler;
+import org.apache.hop.ui.hopgui.file.empty.EmptyHopFileTypeHandler;
+import org.apache.hop.ui.hopgui.perspective.explorer.ExplorerFile;
+import org.apache.hop.ui.hopgui.perspective.explorer.ExplorerPerspective;
+import org.apache.hop.ui.hopgui.perspective.explorer.file.IExplorerFileType;
+import org.apache.hop.ui.hopgui.perspective.explorer.file.types.base.BaseExplorerFileType;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.layout.FormAttachment;
+import org.eclipse.swt.layout.FormData;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Text;
+
+import java.io.InputStream;
+import java.util.List;
+import java.util.Properties;
+
+@HopFileTypePlugin(
+    id = "SasExplorerFileType",
+    name = "SAS File Type",
+    description = "SAS file handling in the explorer perspective",
+    image = "SAS.svg")
+public class SasExplorerFileType extends BaseExplorerFileType<SasExplorerFileTypeHandler>
+    implements IExplorerFileType<SasExplorerFileTypeHandler> {
+
+  public SasExplorerFileType() {
+    super(  "SAS file", ".sas7bdat", new String[] {"*.sas7bdat"}, new String[] {"SAS 7 BDAT files"}, new Properties() );
+  }
+
+  @Override
+  public IHopFileTypeHandler newFile(HopGui hopGui, IVariables parentVariableSpace)
+      throws HopException {
+    return new EmptyHopFileTypeHandler();
+  }
+
+  @Override public SasExplorerFileTypeHandler createFileTypeHandler( HopGui hopGui, ExplorerPerspective perspective, ExplorerFile file ) {
+    return new SasExplorerFileTypeHandler(hopGui, perspective, file);
+  }
+}
diff --git a/plugins/transforms/sasinput/src/main/java/org/apache/hop/pipeline/transforms/sasinput/types/SasExplorerFileTypeHandler.java b/plugins/transforms/sasinput/src/main/java/org/apache/hop/pipeline/transforms/sasinput/types/SasExplorerFileTypeHandler.java
new file mode 100644
index 0000000..789b1de
--- /dev/null
+++ b/plugins/transforms/sasinput/src/main/java/org/apache/hop/pipeline/transforms/sasinput/types/SasExplorerFileTypeHandler.java
@@ -0,0 +1,100 @@
+/*
+ * 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.apache.hop.pipeline.transforms.sasinput.types;
+
+import com.epam.parso.Column;
+import com.epam.parso.ColumnFormat;
+import com.epam.parso.impl.SasFileReaderImpl;
+import org.apache.hop.core.Const;
+import org.apache.hop.core.Props;
+import org.apache.hop.core.exception.HopException;
+import org.apache.hop.core.vfs.HopVfs;
+import org.apache.hop.pipeline.transforms.sasinput.SasUtil;
+import org.apache.hop.ui.core.PropsUi;
+import org.apache.hop.ui.hopgui.HopGui;
+import org.apache.hop.ui.hopgui.file.IHopFileTypeHandler;
+import org.apache.hop.ui.hopgui.perspective.explorer.ExplorerFile;
+import org.apache.hop.ui.hopgui.perspective.explorer.ExplorerPerspective;
+import org.apache.hop.ui.hopgui.perspective.explorer.file.IExplorerFileTypeHandler;
+import org.apache.hop.ui.hopgui.perspective.explorer.file.types.base.BaseExplorerFileTypeHandler;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.layout.FormAttachment;
+import org.eclipse.swt.layout.FormData;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Text;
+
+import java.io.InputStream;
+import java.util.List;
+
+/**
+ * How do we handle an SVG file in file explorer perspective?
+ */
+public class SasExplorerFileTypeHandler extends BaseExplorerFileTypeHandler implements IExplorerFileTypeHandler {
+
+  public SasExplorerFileTypeHandler( HopGui hopGui, ExplorerPerspective perspective, ExplorerFile explorerFile ) {
+    super( hopGui, perspective, explorerFile );
+  }
+
+  @Override
+  public void renderFile(Composite composite) {
+    // Render the file by simply showing the filename ...
+    // TODO: create a TableView based in the file content & load it up...
+    //
+    //
+    Text wText = new Text(composite, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL);
+    PropsUi.getInstance().setLook(wText, Props.WIDGET_STYLE_FIXED);
+    wText.setEditable( false );
+    FormData fdText = new FormData();
+    fdText.left = new FormAttachment(0, 0);
+    fdText.right = new FormAttachment(100, 0);
+    fdText.top = new FormAttachment(0, 0);
+    fdText.bottom = new FormAttachment(100, 0);
+    wText.setLayoutData(fdText);
+
+    String message = explorerFile.getFilename()+ Const.CR;
+    message+=Const.CR;
+
+    try {
+      try ( InputStream inputStream = HopVfs.getInputStream(explorerFile.getFilename())) {
+        SasFileReaderImpl sasFileReader = new SasFileReaderImpl(inputStream);
+
+        List<Column> columns = sasFileReader.getColumns();
+        for (int c = 0; c < columns.size(); c++) {
+          Column column = columns.get(c);
+          ColumnFormat format = column.getFormat();
+
+          int length = format.getWidth()==0 ? -1 : format.getWidth();
+          int precision = format.getPrecision()==0 ? -1 : format.getWidth();
+
+          message+="Column "+(c+1)+Const.CR;
+          message+="   Name      : "+Const.NVL(column.getName(), "")+Const.CR;
+          message+="   Type      : "+ SasUtil.getHopDataTypeDesc(column.getType())+Const.CR;
+          message+="   Length    : "+(length<0 ? "" : Integer.toString(length))+Const.CR;
+          message+="   Precision : "+(precision<0 ? "" : Integer.toString( precision ))+Const.CR;
+        }
+      } catch (Exception e) {
+        throw new HopException("Error reading from file: " + explorerFile.getFilename());
+      }
+    } catch(Exception e) {
+      message+= Const.CR+Const.getSimpleStackTrace( e );
+    }
+    wText.setText( message );
+  }
+
+}
diff --git a/plugins/transforms/sasinput/src/main/resources/SASInput.svg b/plugins/transforms/sasinput/src/main/resources/SAS.svg
similarity index 90%
copy from plugins/transforms/sasinput/src/main/resources/SASInput.svg
copy to plugins/transforms/sasinput/src/main/resources/SAS.svg
index 302f32b..7c42634 100644
--- a/plugins/transforms/sasinput/src/main/resources/SASInput.svg
+++ b/plugins/transforms/sasinput/src/main/resources/SAS.svg
@@ -6,11 +6,11 @@
     y="0px"
     viewBox="-9 -9 42 42"
     enable-background="new -9 -9 42 42"
+    width="42px" height="42px"
 >
   <polygon fill="#0e3a5a" points="0.9,23.1 0.9,0.9 23.2,0.9 23.2,13.9 24.9,12.2 24.9,-0.9 -0.8,-0.9 -0.8,24.9 11.8,24.9
 			13.6,23.1"/>
   <polygon fill="#c9e8fb" points="0.9,0.9 0.9,23.1 13.6,23.1 23.2,13.9 23.2,0.9"/>
-  <polygon fill="#0e3a5a" points="28.1,23.6 28.1,26.7 22.9,21.5 21.5,22.8 26.8,28.1 23.6,28.1 23.6,30 30,30 30,23.6"/>
   <path fill="#0e3a5a" d="M6.5,14.9c-0.6,0-1.4-0.1-2-0.4l0.1-0.7c0.6,0.2,1.2,0.3,1.9,0.3c1.1,0,1.3-0.3,1.3-1c0-0.9,0-1-1.4-1.3
 			c-1.6-0.4-1.8-0.7-1.8-2C4.6,8.6,5.1,8,6.7,8C7.3,8,8,8.1,8.5,8.2L8.4,9C7.9,8.8,7.3,8.8,6.7,8.8c-1.1,0-1.3,0.2-1.3,1
 			c0,0.9,0,1,1.3,1.3c1.8,0.4,1.9,0.7,1.9,2C8.6,14.2,8.3,14.9,6.5,14.9z"/>
diff --git a/plugins/transforms/sasinput/src/main/resources/SASInput.svg b/plugins/transforms/sasinput/src/main/resources/SASInput.svg
index 302f32b..3d6d052 100644
--- a/plugins/transforms/sasinput/src/main/resources/SASInput.svg
+++ b/plugins/transforms/sasinput/src/main/resources/SASInput.svg
@@ -6,6 +6,7 @@
     y="0px"
     viewBox="-9 -9 42 42"
     enable-background="new -9 -9 42 42"
+    width="42px" height="42px"
 >
   <polygon fill="#0e3a5a" points="0.9,23.1 0.9,0.9 23.2,0.9 23.2,13.9 24.9,12.2 24.9,-0.9 -0.8,-0.9 -0.8,24.9 11.8,24.9
 			13.6,23.1"/>
diff --git a/plugins/transforms/textfile/src/main/java/org/apache/hop/pipeline/transforms/types/CsvExplorerFileType.java b/plugins/transforms/textfile/src/main/java/org/apache/hop/pipeline/transforms/types/CsvExplorerFileType.java
new file mode 100644
index 0000000..fba8b5a
--- /dev/null
+++ b/plugins/transforms/textfile/src/main/java/org/apache/hop/pipeline/transforms/types/CsvExplorerFileType.java
@@ -0,0 +1,68 @@
+/*
+ * 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.apache.hop.pipeline.transforms.types;
+
+import org.apache.hop.core.Props;
+import org.apache.hop.core.exception.HopException;
+import org.apache.hop.core.logging.LogChannel;
+import org.apache.hop.core.variables.IVariables;
+import org.apache.hop.ui.core.PropsUi;
+import org.apache.hop.ui.hopgui.HopGui;
+import org.apache.hop.ui.hopgui.file.HopFileTypePlugin;
+import org.apache.hop.ui.hopgui.file.IHopFileTypeHandler;
+import org.apache.hop.ui.hopgui.file.empty.EmptyHopFileTypeHandler;
+import org.apache.hop.ui.hopgui.perspective.explorer.ExplorerFile;
+import org.apache.hop.ui.hopgui.perspective.explorer.ExplorerPerspective;
+import org.apache.hop.ui.hopgui.perspective.explorer.file.IExplorerFileType;
+import org.apache.hop.ui.hopgui.perspective.explorer.file.types.base.BaseExplorerFileType;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.layout.FormAttachment;
+import org.eclipse.swt.layout.FormData;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Text;
+
+import java.io.File;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.util.Properties;
+
+@HopFileTypePlugin(
+    id = "CsvExplorerFileType",
+    name = "JSON File Type",
+    description = "JSON file handling in the explorer perspective",
+    image = "textfile.svg")
+public class CsvExplorerFileType extends BaseExplorerFileType<TextExplorerFileTypeHandler>
+    implements IExplorerFileType<TextExplorerFileTypeHandler> {
+
+  public CsvExplorerFileType() {
+    super("CSV File", ".csv", new String[] {"*.csv"}, new String[] {"CSV files"}, new Properties());
+  }
+
+  @Override
+  public TextExplorerFileTypeHandler createFileTypeHandler(
+      HopGui hopGui, ExplorerPerspective perspective, ExplorerFile file) {
+    return new TextExplorerFileTypeHandler(hopGui, perspective, file);
+  }
+
+  @Override
+  public IHopFileTypeHandler newFile(HopGui hopGui, IVariables parentVariableSpace)
+      throws HopException {
+    return new EmptyHopFileTypeHandler();
+  }
+}
diff --git a/plugins/transforms/textfile/src/main/java/org/apache/hop/pipeline/transforms/types/CsvExplorerFileTypeHandler.java b/plugins/transforms/textfile/src/main/java/org/apache/hop/pipeline/transforms/types/CsvExplorerFileTypeHandler.java
new file mode 100644
index 0000000..26d7761
--- /dev/null
+++ b/plugins/transforms/textfile/src/main/java/org/apache/hop/pipeline/transforms/types/CsvExplorerFileTypeHandler.java
@@ -0,0 +1,78 @@
+/*
+ * 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.apache.hop.pipeline.transforms.types;
+
+import org.apache.hop.core.Props;
+import org.apache.hop.core.logging.LogChannel;
+import org.apache.hop.ui.core.PropsUi;
+import org.apache.hop.ui.hopgui.HopGui;
+import org.apache.hop.ui.hopgui.perspective.explorer.ExplorerFile;
+import org.apache.hop.ui.hopgui.perspective.explorer.ExplorerPerspective;
+import org.apache.hop.ui.hopgui.perspective.explorer.file.IExplorerFileTypeHandler;
+import org.apache.hop.ui.hopgui.perspective.explorer.file.types.base.BaseExplorerFileTypeHandler;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.layout.FormAttachment;
+import org.eclipse.swt.layout.FormData;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Text;
+
+import java.io.File;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+
+/** How do we handle an SVG file in file explorer perspective? */
+public class CsvExplorerFileTypeHandler extends BaseExplorerFileTypeHandler
+    implements IExplorerFileTypeHandler {
+
+  public CsvExplorerFileTypeHandler(
+      HopGui hopGui, ExplorerPerspective perspective, ExplorerFile explorerFile) {
+    super(hopGui, perspective, explorerFile);
+  }
+
+  @Override
+  public void renderFile(Composite composite) {
+    // Render the file by simply showing the file content as a text widget...
+    //
+    Text wText = new Text(composite, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL);
+    PropsUi.getInstance().setLook(wText, Props.WIDGET_STYLE_FIXED);
+    wText.setEditable(false);
+    FormData fdText = new FormData();
+    fdText.left = new FormAttachment(0, 0);
+    fdText.right = new FormAttachment(100, 0);
+    fdText.top = new FormAttachment(0, 0);
+    fdText.bottom = new FormAttachment(100, 0);
+    wText.setLayoutData(fdText);
+
+    // TODO: add bottom section to show status, size, cursor position...
+    //
+
+    // Load the content of the JSON file...
+    //
+    File file = new File(explorerFile.getFilename());
+    if (file.exists()) {
+      try {
+        String contents = new String(Files.readAllBytes(file.toPath()), StandardCharsets.UTF_8);
+        wText.setText(contents);
+      } catch (Exception e) {
+        LogChannel.UI.logError(
+            "Error reading contents of file '" + explorerFile.getFilename() + "'", e);
+      }
+    }
+  }
+}
diff --git a/plugins/transforms/textfile/src/main/java/org/apache/hop/pipeline/transforms/types/TextExplorerFileType.java b/plugins/transforms/textfile/src/main/java/org/apache/hop/pipeline/transforms/types/TextExplorerFileType.java
new file mode 100644
index 0000000..abe2e54
--- /dev/null
+++ b/plugins/transforms/textfile/src/main/java/org/apache/hop/pipeline/transforms/types/TextExplorerFileType.java
@@ -0,0 +1,68 @@
+/*
+ * 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.apache.hop.pipeline.transforms.types;
+
+import org.apache.hop.core.Props;
+import org.apache.hop.core.exception.HopException;
+import org.apache.hop.core.logging.LogChannel;
+import org.apache.hop.core.variables.IVariables;
+import org.apache.hop.ui.core.PropsUi;
+import org.apache.hop.ui.hopgui.HopGui;
+import org.apache.hop.ui.hopgui.file.HopFileTypePlugin;
+import org.apache.hop.ui.hopgui.file.IHopFileTypeHandler;
+import org.apache.hop.ui.hopgui.file.empty.EmptyHopFileTypeHandler;
+import org.apache.hop.ui.hopgui.perspective.explorer.ExplorerFile;
+import org.apache.hop.ui.hopgui.perspective.explorer.ExplorerPerspective;
+import org.apache.hop.ui.hopgui.perspective.explorer.file.IExplorerFileType;
+import org.apache.hop.ui.hopgui.perspective.explorer.file.types.base.BaseExplorerFileType;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.layout.FormAttachment;
+import org.eclipse.swt.layout.FormData;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Text;
+
+import java.io.File;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.util.Properties;
+
+@HopFileTypePlugin(
+    id = "TextExplorerFileType",
+    name = "TXT File Type",
+    description = "Text file handling in the explorer perspective",
+    image = "textfile.svg")
+public class TextExplorerFileType extends BaseExplorerFileType<TextExplorerFileTypeHandler>
+    implements IExplorerFileType<TextExplorerFileTypeHandler> {
+
+  public TextExplorerFileType() {
+    super("TXT File", ".txt", new String[] {"*.txt"}, new String[] {"TXT files"}, new Properties());
+  }
+
+  @Override
+  public TextExplorerFileTypeHandler createFileTypeHandler(
+      HopGui hopGui, ExplorerPerspective perspective, ExplorerFile file) {
+    return new TextExplorerFileTypeHandler(hopGui, perspective, file);
+  }
+
+  @Override
+  public IHopFileTypeHandler newFile(HopGui hopGui, IVariables parentVariableSpace)
+      throws HopException {
+    return new EmptyHopFileTypeHandler();
+  }
+}
diff --git a/plugins/transforms/textfile/src/main/java/org/apache/hop/pipeline/transforms/types/TextExplorerFileTypeHandler.java b/plugins/transforms/textfile/src/main/java/org/apache/hop/pipeline/transforms/types/TextExplorerFileTypeHandler.java
new file mode 100644
index 0000000..a2d1820
--- /dev/null
+++ b/plugins/transforms/textfile/src/main/java/org/apache/hop/pipeline/transforms/types/TextExplorerFileTypeHandler.java
@@ -0,0 +1,80 @@
+/*
+ * 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.apache.hop.pipeline.transforms.types;
+
+import org.apache.hop.core.Props;
+import org.apache.hop.core.logging.LogChannel;
+import org.apache.hop.ui.core.PropsUi;
+import org.apache.hop.ui.hopgui.HopGui;
+import org.apache.hop.ui.hopgui.file.IHopFileTypeHandler;
+import org.apache.hop.ui.hopgui.perspective.explorer.ExplorerFile;
+import org.apache.hop.ui.hopgui.perspective.explorer.ExplorerPerspective;
+import org.apache.hop.ui.hopgui.perspective.explorer.file.IExplorerFileTypeHandler;
+import org.apache.hop.ui.hopgui.perspective.explorer.file.types.base.BaseExplorerFileTypeHandler;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.layout.FormAttachment;
+import org.eclipse.swt.layout.FormData;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Text;
+
+import java.io.File;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+
+/**
+ * How do we handle an SVG file in file explorer perspective?
+ */
+public class TextExplorerFileTypeHandler extends BaseExplorerFileTypeHandler implements IExplorerFileTypeHandler {
+
+  public TextExplorerFileTypeHandler( HopGui hopGui, ExplorerPerspective perspective, ExplorerFile explorerFile ) {
+    super( hopGui, perspective, explorerFile );
+  }
+
+  @Override
+  public void renderFile(Composite composite) {
+    // Render the file by simply showing the TXT content as a text widget...
+    //
+    Text wText = new Text(composite, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL);
+    PropsUi.getInstance().setLook(wText, Props.WIDGET_STYLE_FIXED);
+    wText.setEditable( false );
+    FormData fdText = new FormData();
+    fdText.left = new FormAttachment(0, 0);
+    fdText.right = new FormAttachment(100, 0);
+    fdText.top = new FormAttachment(0, 0);
+    fdText.bottom = new FormAttachment(100, 0);
+    wText.setLayoutData(fdText);
+
+    // TODO: add bottom section to show status, size, cursor position...
+    //
+
+    // Load the content of the JSON file...
+    //
+    File file = new File(explorerFile.getFilename());
+    if (file.exists()) {
+      try {
+        String contents = new String( Files.readAllBytes(file.toPath()), StandardCharsets.UTF_8);
+        wText.setText(contents);
+      } catch (Exception e) {
+        LogChannel.UI.logError(
+          "Error reading contents of file '" + explorerFile.getFilename() + "'", e);
+      }
+    }
+  }
+
+}
diff --git a/plugins/transforms/textfile/src/main/resources/textfile.svg b/plugins/transforms/textfile/src/main/resources/textfile.svg
new file mode 100644
index 0000000..c261b1a
--- /dev/null
+++ b/plugins/transforms/textfile/src/main/resources/textfile.svg
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 17.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1"  xmlns="http://www.w3.org/2000/svg" x="0px" y="0px"
+     width="42px" height="42px" viewBox="0 0 42 42" enable-background="new 0 0 42 42" >
+<g>
+	<path fill="#C9E8FB" d="M24.932,13.053V8.795H11.553v24.411l11.515,0l6.162-6.162l-0.04-13.991H24.932z M17.652,24.783H14.45
+		v-0.801h3.201V24.783z M26.353,21.4H14.45V20.6h11.902V21.4z M26.353,18.018H14.45v-0.801h11.902V18.018z"/>
+  <polygon fill="#0E3A5A" points="30.89,13.063 29.181,11.354 26.633,11.354 26.633,8.806 24.922,7.096 9.853,7.096 9.853,34.905
+		21.369,34.905 23.068,33.206 11.553,33.206 11.553,8.795 24.932,8.795 24.932,13.053 29.19,13.053 29.23,27.044 30.925,25.348 	"/>
+  <rect x="14.45" y="17.217" fill="#0E3A5A" width="11.902" height="0.801"/>
+  <rect x="14.45" y="20.6" fill="#0E3A5A" width="11.902" height="0.801"/>
+  <rect x="14.45" y="23.982" fill="#0E3A5A" width="3.201" height="0.801"/>
+</g>
+</svg>
diff --git a/plugins/transforms/xml/src/main/java/org/apache/hop/pipeline/transforms/xml/types/XmlExplorerFileType.java b/plugins/transforms/xml/src/main/java/org/apache/hop/pipeline/transforms/xml/types/XmlExplorerFileType.java
new file mode 100644
index 0000000..f23d39d
--- /dev/null
+++ b/plugins/transforms/xml/src/main/java/org/apache/hop/pipeline/transforms/xml/types/XmlExplorerFileType.java
@@ -0,0 +1,68 @@
+/*
+ * 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.apache.hop.pipeline.transforms.xml.types;
+
+import org.apache.hop.core.Props;
+import org.apache.hop.core.exception.HopException;
+import org.apache.hop.core.logging.LogChannel;
+import org.apache.hop.core.variables.IVariables;
+import org.apache.hop.ui.core.PropsUi;
+import org.apache.hop.ui.hopgui.HopGui;
+import org.apache.hop.ui.hopgui.file.HopFileTypePlugin;
+import org.apache.hop.ui.hopgui.file.IHopFileTypeHandler;
+import org.apache.hop.ui.hopgui.file.empty.EmptyHopFileTypeHandler;
+import org.apache.hop.ui.hopgui.perspective.explorer.ExplorerFile;
+import org.apache.hop.ui.hopgui.perspective.explorer.ExplorerPerspective;
+import org.apache.hop.ui.hopgui.perspective.explorer.file.IExplorerFileType;
+import org.apache.hop.ui.hopgui.perspective.explorer.file.types.base.BaseExplorerFileType;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.layout.FormAttachment;
+import org.eclipse.swt.layout.FormData;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Text;
+
+import java.io.File;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.util.Properties;
+
+@HopFileTypePlugin(
+    id = "XmlExplorerFileType",
+    name = "XML File Type",
+    description = "XML file handling in the explorer perspective",
+    image = "add_xml.svg")
+public class XmlExplorerFileType extends BaseExplorerFileType<XmlExplorerFileTypeHandler>
+    implements IExplorerFileType<XmlExplorerFileTypeHandler> {
+
+  public XmlExplorerFileType() {
+    super("XML file", ".xml", new String[] {"*.xml"}, new String[] {"XML files"}, new Properties());
+  }
+
+  @Override
+  public IHopFileTypeHandler newFile(HopGui hopGui, IVariables parentVariableSpace)
+      throws HopException {
+    return new EmptyHopFileTypeHandler();
+  }
+
+  @Override
+  public XmlExplorerFileTypeHandler createFileTypeHandler(
+      HopGui hopGui, ExplorerPerspective perspective, ExplorerFile file) {
+    return new XmlExplorerFileTypeHandler(hopGui, perspective, file);
+  }
+}
diff --git a/plugins/transforms/xml/src/main/java/org/apache/hop/pipeline/transforms/xml/types/XmlExplorerFileTypeHandler.java b/plugins/transforms/xml/src/main/java/org/apache/hop/pipeline/transforms/xml/types/XmlExplorerFileTypeHandler.java
new file mode 100644
index 0000000..fd7393e
--- /dev/null
+++ b/plugins/transforms/xml/src/main/java/org/apache/hop/pipeline/transforms/xml/types/XmlExplorerFileTypeHandler.java
@@ -0,0 +1,80 @@
+/*
+ * 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.apache.hop.pipeline.transforms.xml.types;
+
+import org.apache.hop.core.Props;
+import org.apache.hop.core.logging.LogChannel;
+import org.apache.hop.ui.core.PropsUi;
+import org.apache.hop.ui.hopgui.HopGui;
+import org.apache.hop.ui.hopgui.file.IHopFileTypeHandler;
+import org.apache.hop.ui.hopgui.perspective.explorer.ExplorerFile;
+import org.apache.hop.ui.hopgui.perspective.explorer.ExplorerPerspective;
+import org.apache.hop.ui.hopgui.perspective.explorer.file.IExplorerFileTypeHandler;
+import org.apache.hop.ui.hopgui.perspective.explorer.file.types.base.BaseExplorerFileTypeHandler;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.layout.FormAttachment;
+import org.eclipse.swt.layout.FormData;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Text;
+
+import java.io.File;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+
+/**
+ * How do we handle an SVG file in file explorer perspective?
+ */
+public class XmlExplorerFileTypeHandler extends BaseExplorerFileTypeHandler implements IExplorerFileTypeHandler {
+
+  public XmlExplorerFileTypeHandler( HopGui hopGui, ExplorerPerspective perspective, ExplorerFile explorerFile ) {
+    super( hopGui, perspective, explorerFile );
+  }
+
+  @Override
+  public void renderFile(Composite composite) {
+    // Render the file by simply showing the XML content as a text widget...
+    //
+    Text wXml = new Text(composite, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL);
+    PropsUi.getInstance().setLook(wXml, Props.WIDGET_STYLE_FIXED);
+    wXml.setEditable(false);
+    FormData fdXml = new FormData();
+    fdXml.left = new FormAttachment(0, 0);
+    fdXml.right = new FormAttachment(100, 0);
+    fdXml.top = new FormAttachment(0, 0);
+    fdXml.bottom = new FormAttachment(100, 0);
+    wXml.setLayoutData(fdXml);
+
+    // TODO: add bottom section to show status, size, cursor position...
+    //
+
+    // Load the content of the XML file...
+    //
+    File file = new File(explorerFile.getFilename());
+    if (file.exists()) {
+      try {
+        String contents = new String( Files.readAllBytes(file.toPath()), StandardCharsets.UTF_8);
+        wXml.setText(contents);
+      } catch (Exception e) {
+        LogChannel.UI.logError(
+          "Error reading contents of file '" + explorerFile.getFilename() + "'", e);
+      }
+    }
+  }
+
+}
diff --git a/ui/src/main/java/org/apache/hop/core/SwtUniversalImageSvg.java b/ui/src/main/java/org/apache/hop/core/SwtUniversalImageSvg.java
index 7f5691a..36585e0 100644
--- a/ui/src/main/java/org/apache/hop/core/SwtUniversalImageSvg.java
+++ b/ui/src/main/java/org/apache/hop/core/SwtUniversalImageSvg.java
@@ -57,10 +57,14 @@ public class SwtUniversalImageSvg extends SwtUniversalImage {
   }
 
   public SwtUniversalImageSvg(SvgImage svg) {
+    this(svg, false);
+  }
+
+  public SwtUniversalImageSvg(SvgImage svg, boolean keepOriginal) {
     UserAgentAdapter userAgentAdapter = new UserAgentAdapter();
     GVTBuilder builder = new GVTBuilder();
 
-    if (PropsUi.getInstance().isDarkMode()) {
+    if (!keepOriginal && PropsUi.getInstance().isDarkMode()) {
       DOMImplementation domImplementation = SVGDOMImplementation.getDOMImplementation();
       SVGDocument clonedDocument =
           (SVGDocument) DOMUtilities.deepCloneDocument(svg.getDocument(), domImplementation);
diff --git a/ui/src/main/java/org/apache/hop/ui/core/metadata/MetadataFileType.java b/ui/src/main/java/org/apache/hop/ui/core/metadata/MetadataFileType.java
index 4a245bf..923d004 100644
--- a/ui/src/main/java/org/apache/hop/ui/core/metadata/MetadataFileType.java
+++ b/ui/src/main/java/org/apache/hop/ui/core/metadata/MetadataFileType.java
@@ -98,4 +98,8 @@ public class MetadataFileType implements IHopFileType {
     List<IGuiContextHandler> handlers = new ArrayList<>();
     return handlers;
   }
+
+  @Override public String getFileTypeImage() {
+    return "ui/images/metadata.svg";
+  }
 }
diff --git a/ui/src/main/java/org/apache/hop/ui/core/metadata/MetadataFileTypeHandler.java b/ui/src/main/java/org/apache/hop/ui/core/metadata/MetadataFileTypeHandler.java
index db4f1c2..897f3b4 100644
--- a/ui/src/main/java/org/apache/hop/ui/core/metadata/MetadataFileTypeHandler.java
+++ b/ui/src/main/java/org/apache/hop/ui/core/metadata/MetadataFileTypeHandler.java
@@ -31,7 +31,7 @@ import java.util.Map;
 
 public class MetadataFileTypeHandler implements IHopFileTypeHandler {
 
- private static final IHopFileType<?> fileType = new MetadataFileType();
+ private static final IHopFileType fileType = new MetadataFileType();
 
   public MetadataFileTypeHandler() {
 
@@ -48,7 +48,7 @@ public class MetadataFileTypeHandler implements IHopFileTypeHandler {
   @Override public void setName( String name ) {
   }
 
-  @Override public IHopFileType<?> getFileType() {
+  @Override public IHopFileType getFileType() {
     return fileType;
   }
 
diff --git a/ui/src/main/java/org/apache/hop/ui/core/vfs/HopVfsFileDialog.java b/ui/src/main/java/org/apache/hop/ui/core/vfs/HopVfsFileDialog.java
index f62019b..cdb51b8 100644
--- a/ui/src/main/java/org/apache/hop/ui/core/vfs/HopVfsFileDialog.java
+++ b/ui/src/main/java/org/apache/hop/ui/core/vfs/HopVfsFileDialog.java
@@ -917,7 +917,7 @@ public class HopVfsFileDialog implements IFileDialog, IDirectoryDialog {
 
   private Image getFileImage(FileObject file) {
     try {
-      IHopFileType<?> fileType =
+      IHopFileType fileType =
           HopFileTypeRegistry.getInstance().findHopFileType(file.getName().getBaseName());
       if (fileType != null) {
         IPlugin plugin =
diff --git a/ui/src/main/java/org/apache/hop/ui/hopgui/HopGui.java b/ui/src/main/java/org/apache/hop/ui/hopgui/HopGui.java
index 6a601ff..66e26c8 100644
--- a/ui/src/main/java/org/apache/hop/ui/hopgui/HopGui.java
+++ b/ui/src/main/java/org/apache/hop/ui/hopgui/HopGui.java
@@ -1054,7 +1054,7 @@ public class HopGui
    * @param running set this to true if the current file is running
    * @param paused set this to true if the current file is paused
    */
-  public void handleFileCapabilities(IHopFileType<?> fileType, boolean running, boolean paused) {
+  public void handleFileCapabilities(IHopFileType fileType, boolean running, boolean paused) {
 
     mainMenuWidgets.enableMenuItem(fileType, ID_MAIN_MENU_FILE_SAVE, IHopFileType.CAPABILITY_SAVE);
     mainMenuWidgets.enableMenuItem(
diff --git a/ui/src/main/java/org/apache/hop/ui/hopgui/HopGuiExtensionPoint.java b/ui/src/main/java/org/apache/hop/ui/hopgui/HopGuiExtensionPoint.java
index d38ec27..edb0260 100644
--- a/ui/src/main/java/org/apache/hop/ui/hopgui/HopGuiExtensionPoint.java
+++ b/ui/src/main/java/org/apache/hop/ui/hopgui/HopGuiExtensionPoint.java
@@ -21,6 +21,7 @@ import org.apache.hop.ui.hopgui.delegates.HopGuiDirectorySelectedExtension;
 import org.apache.hop.ui.hopgui.delegates.HopGuiFileDialogExtension;
 import org.apache.hop.ui.hopgui.delegates.HopGuiFileOpenedExtension;
 import org.apache.hop.ui.hopgui.file.pipeline.HopGuiPipelineGraph;
+import org.apache.hop.ui.hopgui.perspective.explorer.ExplorerPerspective;
 
 public enum HopGuiExtensionPoint {
 
@@ -31,6 +32,8 @@ public enum HopGuiExtensionPoint {
 
   HopGuiFileDirectoryDialog( "Called before a DirectoryDialog is presented", HopGuiFileDialogExtension.class ),
   HopGuiDirectorySelected( "Called after a folder is selected in the DirectoryDialog", HopGuiDirectorySelectedExtension.class ),
+
+  HopGuiDetermineExplorerRoot( "Determine the root folder of the explorer perspective", ExplorerPerspective.DetermineRootFolderExtension.class ),
   ;
 
   public String id;
diff --git a/ui/src/main/java/org/apache/hop/ui/hopgui/delegates/HopGuiFileDelegate.java b/ui/src/main/java/org/apache/hop/ui/hopgui/delegates/HopGuiFileDelegate.java
index f1f9164..2f2ea0c 100644
--- a/ui/src/main/java/org/apache/hop/ui/hopgui/delegates/HopGuiFileDelegate.java
+++ b/ui/src/main/java/org/apache/hop/ui/hopgui/delegates/HopGuiFileDelegate.java
@@ -94,7 +94,7 @@ public class HopGuiFileDelegate {
   public IHopFileTypeHandler fileOpen(String filename) throws Exception {
     HopFileTypeRegistry fileRegistry = HopFileTypeRegistry.getInstance();
 
-    IHopFileType<?> hopFile = fileRegistry.findHopFileType(filename);
+    IHopFileType hopFile = fileRegistry.findHopFileType(filename);
     if (hopFile == null) {
       throw new HopException(
           "We looked at "
@@ -122,7 +122,7 @@ public class HopGuiFileDelegate {
   public String fileSaveAs() {
     try {
       IHopFileTypeHandler typeHandler = getActiveFileTypeHandler();
-      IHopFileType<?> fileType = typeHandler.getFileType();
+      IHopFileType fileType = typeHandler.getFileType();
       if (!fileType.hasCapability(IHopFileType.CAPABILITY_SAVE_AS)) {
         return null;
       }
@@ -152,7 +152,7 @@ public class HopGuiFileDelegate {
   public void fileSave() {
     try {
       IHopFileTypeHandler typeHandler = getActiveFileTypeHandler();
-      IHopFileType<?> fileType = typeHandler.getFileType();
+      IHopFileType fileType = typeHandler.getFileType();
       if (fileType.hasCapability(IHopFileType.CAPABILITY_SAVE)) {
         if (StringUtils.isEmpty(typeHandler.getFilename())) {
           // Ask for the filename: saveAs
@@ -171,7 +171,7 @@ public class HopGuiFileDelegate {
     try {
       IHopPerspective perspective = hopGui.getActivePerspective();
       IHopFileTypeHandler typeHandler = getActiveFileTypeHandler();
-      IHopFileType<?> fileType = typeHandler.getFileType();
+      IHopFileType fileType = typeHandler.getFileType();
       if (fileType.hasCapability(IHopFileType.CAPABILITY_CLOSE)) {
         perspective.remove(typeHandler);
       }
diff --git a/ui/src/main/java/org/apache/hop/ui/hopgui/file/HopFileTypeBase.java b/ui/src/main/java/org/apache/hop/ui/hopgui/file/HopFileTypeBase.java
index 2c054d2..9e59816 100644
--- a/ui/src/main/java/org/apache/hop/ui/hopgui/file/HopFileTypeBase.java
+++ b/ui/src/main/java/org/apache/hop/ui/hopgui/file/HopFileTypeBase.java
@@ -25,7 +25,11 @@ import org.apache.hop.core.vfs.HopVfs;
 import org.apache.hop.core.xml.IXml;
 import java.util.Properties;
 
-public abstract class HopFileTypeBase<T extends IXml> implements IHopFileType<T> {
+public abstract class HopFileTypeBase implements IHopFileType {
+
+  @Override
+  public abstract String getName();
+
   @Override
   public abstract Properties getCapabilities();
 
@@ -37,10 +41,7 @@ public abstract class HopFileTypeBase<T extends IXml> implements IHopFileType<T>
     if ( obj == this ) {
       return true;
     }
-    if ( obj.getClass().equals( this.getClass() ) ) {
-      return true; // same class is enough
-    }
-    return false;
+    return obj.getClass().equals( this.getClass() ); // same class is enough
   }
 
   @Override
@@ -81,4 +82,6 @@ public abstract class HopFileTypeBase<T extends IXml> implements IHopFileType<T>
     }
     return "true".equalsIgnoreCase( available.toString() );
   }
+
+
 }
diff --git a/ui/src/main/java/org/apache/hop/ui/hopgui/file/IHopFileType.java b/ui/src/main/java/org/apache/hop/ui/hopgui/file/IHopFileType.java
index e7bc4f9..e48da0b 100644
--- a/ui/src/main/java/org/apache/hop/ui/hopgui/file/IHopFileType.java
+++ b/ui/src/main/java/org/apache/hop/ui/hopgui/file/IHopFileType.java
@@ -20,14 +20,14 @@ package org.apache.hop.ui.hopgui.file;
 import org.apache.hop.core.exception.HopException;
 import org.apache.hop.core.file.IHasFilename;
 import org.apache.hop.core.variables.IVariables;
-import org.apache.hop.core.xml.IXml;
 import org.apache.hop.ui.hopgui.HopGui;
 import org.apache.hop.ui.hopgui.context.IGuiContextHandler;
+import org.apache.hop.ui.hopgui.perspective.IHopPerspective;
 
 import java.util.List;
 import java.util.Properties;
 
-public interface IHopFileType<T extends IXml> {
+public interface IHopFileType {
 
   String CAPABILITY_NEW = "New";
   String CAPABILITY_SAVE = "Save";
@@ -48,31 +48,23 @@ public interface IHopFileType<T extends IXml> {
 
   String CAPABILITY_FILE_HISTORY = "FileHistory";
 
-  /**
-   * @return The name of this file type
-   */
+  /** @return The name of this file type */
   String getName();
 
   /**
    * Returns the default file extension in lowercase prefixed with dot (.xxx) for this file type.
-   * 
+   *
    * @return The default file extension
    */
   String getDefaultFileExtension();
-  
-  /**
-   * @return The file type extensions.
-   */
+
+  /** @return The file type extensions. */
   String[] getFilterExtensions();
 
-  /**
-   * @return The file names (matching the extensions)
-   */
+  /** @return The file names (matching the extensions) */
   String[] getFilterNames();
 
-  /**
-   * @return The capabilities of this file handler
-   */
+  /** @return The capabilities of this file handler */
   Properties getCapabilities();
 
   /**
@@ -81,31 +73,32 @@ public interface IHopFileType<T extends IXml> {
    * @param capability The capability to check
    * @return True if the capability is set to any non-null value
    */
-  boolean hasCapability( String capability );
+  boolean hasCapability(String capability);
 
   /**
    * Load and display the file
    *
-   * @param hopGui              The hop GUI to reference
-   * @param filename            The filename to load
+   * @param hopGui The hop GUI to reference
+   * @param filename The filename to load
    * @param parentVariableSpace The parent variablespace to inherit from
    * @return The hop file handler
    */
-  IHopFileTypeHandler openFile( HopGui hopGui, String filename, IVariables parentVariableSpace ) throws HopException;
+  IHopFileTypeHandler openFile(HopGui hopGui, String filename, IVariables parentVariableSpace)
+      throws HopException;
 
-  IHopFileTypeHandler newFile( HopGui hopGui, IVariables parentVariableSpace ) throws HopException;
+  IHopFileTypeHandler newFile(HopGui hopGui, IVariables parentVariableSpace) throws HopException;
 
   /**
-   * Look at the given file and see if it's handled by this type.
-   * Usually this is done by simply looking at the file extension.
-   * In rare cases we look at the content.
+   * Look at the given file and see if it's handled by this type. Usually this is done by simply
+   * looking at the file extension. In rare cases we look at the content.
    *
-   * @param filename     The filename
+   * @param filename The filename
    * @param checkContent True if we want to look inside the file content
    * @return true if this HopFile is handling the file
-   * @throws HopException In case something goes wrong like: file doesn't exist, a permission problem, ...
+   * @throws HopException In case something goes wrong like: file doesn't exist, a permission
+   *     problem, ...
    */
-  boolean isHandledBy( String filename, boolean checkContent ) throws HopException;
+  boolean isHandledBy(String filename, boolean checkContent) throws HopException;
 
   /**
    * Checks whether or not this file type supports the given metadata class
@@ -113,10 +106,18 @@ public interface IHopFileType<T extends IXml> {
    * @param metaObject The object to verify support for
    * @return
    */
-  boolean supportsFile( IHasFilename metaObject );
+  boolean supportsFile(IHasFilename metaObject);
 
   /**
-   * @return A list of context handlers allowing you to see all the actions that can be taken with the current file type. (CRUD, ...)
+   * @return A list of context handlers allowing you to see all the actions that can be taken with
+   *     the current file type. (CRUD, ...)
    */
   List<IGuiContextHandler> getContextHandlers();
+
+  /**
+   * The icon image for this file type
+   *
+   * @return The path to the SVG file, a logo for this file type
+   */
+  String getFileTypeImage();
 }
diff --git a/ui/src/main/java/org/apache/hop/ui/hopgui/file/empty/EmptyFileType.java b/ui/src/main/java/org/apache/hop/ui/hopgui/file/empty/EmptyFileType.java
index 2e8d3dd..6f45846 100644
--- a/ui/src/main/java/org/apache/hop/ui/hopgui/file/empty/EmptyFileType.java
+++ b/ui/src/main/java/org/apache/hop/ui/hopgui/file/empty/EmptyFileType.java
@@ -24,6 +24,7 @@ import org.apache.hop.ui.hopgui.HopGui;
 import org.apache.hop.ui.hopgui.context.IGuiContextHandler;
 import org.apache.hop.ui.hopgui.file.IHopFileTypeHandler;
 import org.apache.hop.ui.hopgui.file.IHopFileType;
+import org.apache.hop.ui.hopgui.perspective.explorer.IExplorerFilePaintListener;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -31,7 +32,7 @@ import java.util.Properties;
 
 public class EmptyFileType implements IHopFileType {
   @Override public String getName() {
-    return null;
+    return "-";
   }
   
   @Override public String getDefaultFileExtension() {
@@ -74,4 +75,8 @@ public class EmptyFileType implements IHopFileType {
     List<IGuiContextHandler> handlers = new ArrayList<>();
     return handlers;
   }
+
+  @Override public String getFileTypeImage() {
+    return "ui/images/file.svg";
+  }
 }
diff --git a/ui/src/main/java/org/apache/hop/ui/hopgui/file/pipeline/HopPipelineFileType.java b/ui/src/main/java/org/apache/hop/ui/hopgui/file/pipeline/HopPipelineFileType.java
index 78844c3..dda6055 100644
--- a/ui/src/main/java/org/apache/hop/ui/hopgui/file/pipeline/HopPipelineFileType.java
+++ b/ui/src/main/java/org/apache/hop/ui/hopgui/file/pipeline/HopPipelineFileType.java
@@ -52,7 +52,7 @@ import java.util.Properties;
   description = "The pipeline file information for the Hop GUI",
   image="ui/images/pipeline.svg"
 )
-public class HopPipelineFileType<T extends PipelineMeta> extends HopFileTypeBase<T> implements IHopFileType<T> {
+public class HopPipelineFileType<T extends PipelineMeta> extends HopFileTypeBase implements IHopFileType {
 
   public static final String PIPELINE_FILE_TYPE_DESCRIPTION = "Pipeline";
 
@@ -208,4 +208,8 @@ public class HopPipelineFileType<T extends PipelineMeta> extends HopFileTypeBase
     handlers.add( new GuiContextHandler( ACTION_ID_NEW_PIPELINE, Arrays.asList(newAction) ) );
     return handlers;
   }
+
+  @Override public String getFileTypeImage() {
+    return "ui/images/pipeline.svg";
+  }
 }
diff --git a/ui/src/main/java/org/apache/hop/ui/hopgui/file/workflow/HopWorkflowFileType.java b/ui/src/main/java/org/apache/hop/ui/hopgui/file/workflow/HopWorkflowFileType.java
index f97b198..5fe18e5 100644
--- a/ui/src/main/java/org/apache/hop/ui/hopgui/file/workflow/HopWorkflowFileType.java
+++ b/ui/src/main/java/org/apache/hop/ui/hopgui/file/workflow/HopWorkflowFileType.java
@@ -53,7 +53,7 @@ import java.util.Properties;
   description = "The workflow file information for the Hop GUI",
   image="ui/images/workflow.svg"
 )
-public class HopWorkflowFileType<T extends WorkflowMeta> extends HopFileTypeBase<T> implements IHopFileType<T> {
+public class HopWorkflowFileType<T extends WorkflowMeta> extends HopFileTypeBase implements IHopFileType {
 
   public static final String WORKFLOW_FILE_TYPE_DESCRIPTION = "Workflow";
 
@@ -216,4 +216,8 @@ public class HopWorkflowFileType<T extends WorkflowMeta> extends HopFileTypeBase
 
     return handlers;
   }
+
+  @Override public String getFileTypeImage() {
+    return "ui/images/workflow.svg";
+  }
 }
diff --git a/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/HopPerspectiveManager.java b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/HopPerspectiveManager.java
index 1edc30e..c2ea347 100644
--- a/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/HopPerspectiveManager.java
+++ b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/HopPerspectiveManager.java
@@ -79,9 +79,9 @@ public class HopPerspectiveManager {
    * @param fileMetadata
    * @return
    */
-  public IHopFileType<?> findFileTypeHandler( IHasFilename fileMetadata ) {
+  public IHopFileType findFileTypeHandler( IHasFilename fileMetadata ) {
     for ( IHopPerspective perspective : getPerspectives() ) {
-      for ( IHopFileType<?> fileType : perspective.getSupportedHopFileTypes() ) {
+      for ( IHopFileType fileType : perspective.getSupportedHopFileTypes() ) {
         if ( fileType.supportsFile( fileMetadata ) ) {
           return fileType;
         }
diff --git a/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/ExplorerFile.java b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/ExplorerFile.java
new file mode 100644
index 0000000..70e2992
--- /dev/null
+++ b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/ExplorerFile.java
@@ -0,0 +1,198 @@
+/*
+ * 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.apache.hop.ui.hopgui.perspective.explorer;
+
+import org.apache.hop.core.changed.IChanged;
+import org.apache.hop.core.listeners.IContentChangedListener;
+import org.apache.hop.ui.hopgui.file.IHopFileType;
+import org.apache.hop.ui.hopgui.file.IHopFileTypeHandler;
+import org.apache.hop.ui.hopgui.perspective.explorer.file.IExplorerFileType;
+import org.apache.hop.ui.hopgui.perspective.explorer.file.IExplorerFileTypeHandler;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.widgets.Composite;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+public class ExplorerFile {
+
+  private String name;
+  private Image tabImage;
+  private String filename;
+  private IExplorerFileType fileType;
+  private IExplorerFileTypeHandler fileTypeHandler;
+  private boolean changed;
+  private List<IContentChangedListener> contentChangedListeners;
+
+  public ExplorerFile() {
+    contentChangedListeners = new ArrayList<>();
+  }
+
+  public ExplorerFile( String name, Image tabImage, String filename, IExplorerFileType fileType, IExplorerFileTypeHandler fileTypeHandler ) {
+    this();
+    this.name = name;
+    this.tabImage = tabImage;
+    this.filename = filename;
+    this.fileType = fileType;
+    this.fileTypeHandler = fileTypeHandler;
+  }
+
+  @Override public boolean equals( Object o ) {
+    if ( this == o ) {
+      return true;
+    }
+    if ( o == null || getClass() != o.getClass() ) {
+      return false;
+    }
+    ExplorerFile that = (ExplorerFile) o;
+    return Objects.equals( filename, that.filename );
+  }
+
+  @Override public int hashCode() {
+    return Objects.hash( filename );
+  }
+
+  /**
+   * Gets tabName
+   *
+   * @return the name
+   */
+  public String getName() {
+    return name;
+  }
+
+  /**
+   * @param name The name to set
+   */
+  public void setName( String name ) {
+    this.name = name;
+  }
+
+  /**
+   * Gets tabImage
+   *
+   * @return value of tabImage
+   */
+  public Image getTabImage() {
+    return tabImage;
+  }
+
+  /**
+   * @param tabImage The tabImage to set
+   */
+  public void setTabImage( Image tabImage ) {
+    this.tabImage = tabImage;
+  }
+
+  /**
+   * Gets filename
+   *
+   * @return value of filename
+   */
+  public String getFilename() {
+    return filename;
+  }
+
+  /**
+   * @param filename The filename to set
+   */
+  public void setFilename( String filename ) {
+    this.filename = filename;
+  }
+
+  /**
+   * Gets fileType
+   *
+   * @return value of fileType
+   */
+  public IExplorerFileType getFileType() {
+    return fileType;
+  }
+
+  /**
+   * @param fileType The fileType to set
+   */
+  public void setFileType( IExplorerFileType fileType ) {
+    this.fileType = fileType;
+  }
+
+  /**
+   * Gets fileTypeHandler
+   *
+   * @return value of fileTypeHandler
+   */
+  public IExplorerFileTypeHandler getFileTypeHandler() {
+    return fileTypeHandler;
+  }
+
+  /**
+   * @param fileTypeHandler The fileTypeHandler to set
+   */
+  public void setFileTypeHandler( IExplorerFileTypeHandler fileTypeHandler ) {
+    this.fileTypeHandler = fileTypeHandler;
+  }
+
+  /**
+   * Gets changed
+   *
+   * @return value of changed
+   */
+  public boolean isChanged() {
+    return changed;
+  }
+
+  /**
+   * Flag the file as changed
+   */
+  public void setChanged() {
+    if (!changed) {
+      this.changed = true;
+      for (IContentChangedListener listener : contentChangedListeners) {
+        listener.contentChanged(this);
+      }
+    }
+  }
+
+  public void clearChanged() {
+    if (changed) {
+      this.changed = false;
+      for (IContentChangedListener listener : contentChangedListeners) {
+        listener.contentSafe(this);
+      }
+    }
+  }
+
+  /**
+   * Gets contentChangedListeners
+   *
+   * @return value of contentChangedListeners
+   */
+  public List<IContentChangedListener> getContentChangedListeners() {
+    return contentChangedListeners;
+  }
+
+  /**
+   * Be informed if content changed.
+   * @param listener
+   */
+  public void addContentChangedListener(IContentChangedListener listener) {
+    contentChangedListeners.add(listener);
+  }
+}
diff --git a/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/ExplorerPerspective.java b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/ExplorerPerspective.java
new file mode 100644
index 0000000..de85eb4
--- /dev/null
+++ b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/ExplorerPerspective.java
@@ -0,0 +1,812 @@
+/*
+ * 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.apache.hop.ui.hopgui.perspective.explorer;
+
+import org.apache.commons.vfs2.FileObject;
+import org.apache.hop.core.Const;
+import org.apache.hop.core.Props;
+import org.apache.hop.core.SwtUniversalImageSvg;
+import org.apache.hop.core.exception.HopException;
+import org.apache.hop.core.extension.ExtensionPointHandler;
+import org.apache.hop.core.gui.plugin.GuiPlugin;
+import org.apache.hop.core.gui.plugin.toolbar.GuiToolbarElement;
+import org.apache.hop.core.listeners.IContentChangedListener;
+import org.apache.hop.core.plugins.IPlugin;
+import org.apache.hop.core.plugins.PluginRegistry;
+import org.apache.hop.core.search.ISearchable;
+import org.apache.hop.core.svg.SvgCache;
+import org.apache.hop.core.svg.SvgCacheEntry;
+import org.apache.hop.core.svg.SvgFile;
+import org.apache.hop.core.svg.SvgImage;
+import org.apache.hop.core.vfs.HopVfs;
+import org.apache.hop.ui.core.ConstUi;
+import org.apache.hop.ui.core.PropsUi;
+import org.apache.hop.ui.core.dialog.ErrorDialog;
+import org.apache.hop.ui.core.gui.GuiResource;
+import org.apache.hop.ui.core.gui.GuiToolbarWidgets;
+import org.apache.hop.ui.core.widget.TabFolderReorder;
+import org.apache.hop.ui.core.widget.TreeMemory;
+import org.apache.hop.ui.core.widget.TreeToolTipSupport;
+import org.apache.hop.ui.hopgui.HopGui;
+import org.apache.hop.ui.hopgui.HopGuiExtensionPoint;
+import org.apache.hop.ui.hopgui.context.IGuiContextHandler;
+import org.apache.hop.ui.hopgui.file.HopFileTypePluginType;
+import org.apache.hop.ui.hopgui.file.IHopFileType;
+import org.apache.hop.ui.hopgui.file.IHopFileTypeHandler;
+import org.apache.hop.ui.hopgui.file.empty.EmptyFileType;
+import org.apache.hop.ui.hopgui.file.empty.EmptyHopFileTypeHandler;
+import org.apache.hop.ui.hopgui.perspective.HopPerspectivePlugin;
+import org.apache.hop.ui.hopgui.perspective.IHopPerspective;
+import org.apache.hop.ui.hopgui.perspective.TabItemHandler;
+import org.apache.hop.ui.hopgui.perspective.explorer.file.ExplorerFileType;
+import org.apache.hop.ui.hopgui.perspective.explorer.file.IExplorerFileType;
+import org.apache.hop.ui.hopgui.perspective.explorer.file.IExplorerFileTypeHandler;
+import org.apache.hop.ui.hopgui.perspective.explorer.file.types.GenericFileType;
+import org.apache.hop.ui.pipeline.transform.BaseTransformDialog;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.custom.CTabFolder;
+import org.eclipse.swt.custom.CTabFolder2Adapter;
+import org.eclipse.swt.custom.CTabFolderEvent;
+import org.eclipse.swt.custom.CTabItem;
+import org.eclipse.swt.custom.SashForm;
+import org.eclipse.swt.custom.TreeEditor;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.layout.FormAttachment;
+import org.eclipse.swt.layout.FormData;
+import org.eclipse.swt.layout.FormLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Event;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.ToolBar;
+import org.eclipse.swt.widgets.ToolItem;
+import org.eclipse.swt.widgets.Tree;
+import org.eclipse.swt.widgets.TreeItem;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@HopPerspectivePlugin(
+    id = "300-HopExplorerPerspective",
+    name = "File Explorer",
+    description = "The Hop Explorer Perspective",
+    image = "ui/images/folder.svg")
+@GuiPlugin(description = "A file explorer for your current project")
+public class ExplorerPerspective implements IHopPerspective {
+
+  private static final String FILE_EXPLORER_TREE = "File explorer tree";
+
+  public static final String GUI_PLUGIN_TOOLBAR_PARENT_ID = "ExplorerPerspective-Toolbar";
+
+  public static final String TOOLBAR_ITEM_EDIT = "ExplorerPerspective-Toolbar-10010-Edit";
+  public static final String TOOLBAR_ITEM_DUPLICATE = "ExplorerPerspective-Toolbar-10030-Duplicate";
+  public static final String TOOLBAR_ITEM_DELETE = "ExplorerPerspective-Toolbar-10040-Delete";
+  public static final String TOOLBAR_ITEM_REFRESH = "ExplorerPerspective-Toolbar-10100-Refresh";
+
+  private static ExplorerPerspective instance;
+
+  public static ExplorerPerspective getInstance() {
+    return instance;
+  }
+
+  private HopGui hopGui;
+  private SashForm sash;
+  private Tree tree;
+  private TreeEditor treeEditor;
+  private CTabFolder tabFolder;
+  private ToolBar toolBar;
+  private GuiToolbarWidgets toolBarWidgets;
+
+  private List<ExplorerFile> files = new ArrayList<>();
+
+  private final EmptyFileType emptyFileType;
+  private final ExplorerFileType explorerFileType;
+
+  private String rootFolder;
+  private String rootName;
+
+  private class TreeItemFolder {
+    public TreeItem treeItem;
+    public String path;
+    public String name;
+    public IHopFileType fileType;
+
+    public TreeItemFolder(TreeItem treeItem, String path, String name, IHopFileType fileType) {
+      this.treeItem = treeItem;
+      this.path = path;
+      this.name = name;
+      this.fileType = fileType;
+    }
+  }
+
+  private Map<String, TreeItemFolder> treeItemFolderMap;
+
+  private List<IExplorerFilePaintListener> filePaintListeners;
+
+  private List<IHopFileType> fileTypes;
+
+  private Map<String, Image> typeImageMap;
+
+  public ExplorerPerspective() {
+    instance = this;
+
+    this.emptyFileType = new EmptyFileType();
+    this.explorerFileType = new ExplorerFileType();
+
+    this.treeItemFolderMap = new HashMap<>();
+    this.filePaintListeners = new ArrayList<>();
+    this.typeImageMap = new HashMap<>();
+  }
+
+  @Override
+  public String getId() {
+    return "explorer-perspective";
+  }
+
+  @Override
+  public void activate() {
+    hopGui.setActivePerspective(this);
+  }
+
+  @Override
+  public void perspectiveActivated() {
+    this.refresh();
+    this.updateSelection();
+
+    // If all editor are closed
+    //
+    if (tabFolder.getItemCount() == 0) {
+      HopGui.getInstance().handleFileCapabilities(emptyFileType, false, false);
+    } else {
+      HopGui.getInstance().handleFileCapabilities(explorerFileType, false, false);
+    }
+  }
+
+  @Override
+  public boolean isActive() {
+    return hopGui.isActivePerspective(this);
+  }
+
+  @Override
+  public List<IHopFileType> getSupportedHopFileTypes() {
+    return Arrays.asList(explorerFileType);
+  }
+
+  @Override
+  public void initialize(HopGui hopGui, Composite parent) {
+    this.hopGui = hopGui;
+
+    determineRootFolderName(hopGui);
+    loadFileTypes();
+    loadTypeImages(parent);
+
+    // Split tree and editor
+    //
+    sash = new SashForm(parent, SWT.HORIZONTAL);
+    FormData fdSash = new FormData();
+    fdSash.left = new FormAttachment(0, 0);
+    fdSash.top = new FormAttachment(0, 0);
+    fdSash.right = new FormAttachment(100, 0);
+    fdSash.bottom = new FormAttachment(100, 0);
+    sash.setLayoutData(fdSash);
+
+    createTree(sash);
+    createTabFolder(sash);
+
+    sash.setWeights(new int[] {20, 80});
+
+    // TODO: Refresh the root folder when it comes back into focus and when it's needed
+    //
+
+  }
+
+  private void loadFileTypes() {
+    fileTypes = new ArrayList<>();
+    PluginRegistry registry = PluginRegistry.getInstance();
+    List<IPlugin> plugins = PluginRegistry.getInstance().getPlugins(HopFileTypePluginType.class);
+    for (IPlugin plugin : plugins) {
+      try {
+        IHopFileType fileType = (IHopFileType) registry.loadClass(plugin);
+        fileTypes.add(fileType);
+      } catch (Exception e) {
+        hopGui.getLog().logError("Unable to load file type plugin: " + plugin.getIds()[0], e);
+      }
+    }
+    // Keep as last in the list...
+    fileTypes.add(new GenericFileType());
+  }
+
+  private void loadTypeImages(Composite parentComposite) {
+    typeImageMap = new HashMap<>();
+    int iconSize = (int) (PropsUi.getInstance().getZoomFactor() * 16);
+
+    for (IHopFileType fileType : fileTypes) {
+      String imageFilename = fileType.getFileTypeImage();
+      if (imageFilename != null) {
+        try {
+          SvgCacheEntry svgCacheEntry =
+              SvgCache.loadSvg(new SvgFile(imageFilename, fileType.getClass().getClassLoader()));
+          SwtUniversalImageSvg imageSvg =
+              new SwtUniversalImageSvg(new SvgImage(svgCacheEntry.getSvgDocument()));
+          Image image = imageSvg.getAsBitmapForSize(hopGui.getDisplay(), iconSize, iconSize);
+          typeImageMap.put(fileType.getName(), image);
+        } catch (Exception e) {
+          hopGui
+              .getLog()
+              .logError(
+                  "Error loading image : '"
+                      + imageFilename
+                      + "' for type '"
+                      + fileType.getName()
+                      + "'",
+                  e);
+        }
+      }
+    }
+    // Properly dispose images when done...
+    //
+    parentComposite.addListener(
+        SWT.Dispose,
+        e -> {
+          for (Image image : typeImageMap.values()) {
+            image.dispose();
+          }
+        });
+  }
+
+  public class DetermineRootFolderExtension {
+    public HopGui hopGui;
+    public String rootFolder;
+    public String rootName;
+
+    public DetermineRootFolderExtension(HopGui hopGui, String rootFolder, String rootName) {
+      this.hopGui = hopGui;
+      this.rootFolder = rootFolder;
+      this.rootName = rootName;
+    }
+  }
+
+  public void determineRootFolderName(HopGui hopGui) {
+
+    rootFolder = hopGui.getVariables().getVariable("user.home");
+    rootName = "Home folder";
+
+    DetermineRootFolderExtension ext =
+        new DetermineRootFolderExtension(hopGui, rootFolder, rootName);
+    try {
+      ExtensionPointHandler.callExtensionPoint(
+          hopGui.getLog(),
+          hopGui.getVariables(),
+          HopGuiExtensionPoint.HopGuiDetermineExplorerRoot.id,
+          ext);
+      rootFolder = ext.rootFolder;
+      rootName = ext.rootName;
+    } catch (Exception e) {
+      new ErrorDialog(
+          getShell(), "Error", "Error getting root folder/name of explorer perspective", e);
+    }
+  }
+
+  protected void createTree(Composite parent) {
+    PropsUi props = PropsUi.getInstance();
+
+    // Create composite
+    //
+    Composite composite = new Composite(parent, SWT.BORDER);
+    FormLayout layout = new FormLayout();
+    layout.marginWidth = 0;
+    layout.marginHeight = 0;
+    composite.setLayout(layout);
+
+    // Create toolbar
+    //
+    toolBar = new ToolBar(composite, SWT.WRAP | SWT.LEFT | SWT.HORIZONTAL);
+    toolBarWidgets = new GuiToolbarWidgets();
+    toolBarWidgets.registerGuiPluginObject(this);
+    toolBarWidgets.createToolbarWidgets(toolBar, GUI_PLUGIN_TOOLBAR_PARENT_ID);
+    FormData layoutData = new FormData();
+    layoutData.left = new FormAttachment(0, 0);
+    layoutData.top = new FormAttachment(0, 0);
+    layoutData.right = new FormAttachment(100, 0);
+    toolBar.setLayoutData(layoutData);
+    toolBar.pack();
+    props.setLook(toolBar, Props.WIDGET_STYLE_TOOLBAR);
+
+    tree = new Tree(composite, SWT.SINGLE | SWT.H_SCROLL | SWT.V_SCROLL);
+    tree.setHeaderVisible(false);
+    tree.addListener(SWT.Selection, event -> updateSelection());
+    tree.addListener(SWT.DefaultSelection, this::openFile);
+    PropsUi.getInstance().setLook(tree);
+
+    FormData treeFormData = new FormData();
+    treeFormData.left = new FormAttachment(0, 0);
+    treeFormData.top = new FormAttachment(toolBar, 0);
+    treeFormData.right = new FormAttachment(100, 0);
+    treeFormData.bottom = new FormAttachment(100, 0);
+    tree.setLayoutData(treeFormData);
+
+    // Create Tree editor for rename
+    treeEditor = new TreeEditor(tree);
+    treeEditor.horizontalAlignment = SWT.LEFT;
+    treeEditor.grabHorizontal = true;
+
+    // Add on first level tooltip with metatada description
+    new TreeToolTipSupport(tree);
+
+    // Remember tree node expanded/Collapsed
+    TreeMemory.addTreeListener(tree, FILE_EXPLORER_TREE);
+  }
+
+  private void openFile(Event event) {
+    try {
+      if (event.item instanceof TreeItem) {
+        TreeItem item = (TreeItem) event.item;
+        TreeItemFolder tif = treeItemFolderMap.get(ConstUi.getTreePath(item, 0));
+        if (tif != null && tif.fileType != null) {
+          tif.fileType.openFile(hopGui, tif.path, hopGui.getVariables());
+          updateGui();
+        }
+      }
+    } catch (Exception e) {
+      new ErrorDialog(hopGui.getShell(), "Error", "Error opening file", e);
+    }
+  }
+
+  protected void createTabFolder(Composite parent) {
+    PropsUi props = PropsUi.getInstance();
+
+    tabFolder = new CTabFolder(parent, SWT.MULTI | SWT.BORDER);
+    tabFolder.addCTabFolder2Listener(
+        new CTabFolder2Adapter() {
+          @Override
+          public void close(CTabFolderEvent event) {
+            onTabClose(event);
+          }
+        });
+    tabFolder.addListener( SWT.Selection, event -> handleTabSelectionEvent( event ) );
+    props.setLook(tabFolder, Props.WIDGET_STYLE_TAB);
+
+    // Show/Hide tree
+    //
+    ToolBar toolBar = new ToolBar(tabFolder, SWT.FLAT);
+    final ToolItem item = new ToolItem(toolBar, SWT.PUSH);
+    item.setImage(GuiResource.getInstance().getImageMinimizePanel());
+    item.addListener(
+        SWT.Selection,
+        e -> {
+          if (sash.getMaximizedControl() == null) {
+            sash.setMaximizedControl(tabFolder);
+            item.setImage(GuiResource.getInstance().getImageMaximizePanel());
+          } else {
+            sash.setMaximizedControl(null);
+            item.setImage(GuiResource.getInstance().getImageMinimizePanel());
+          }
+        });
+    tabFolder.setTopRight(toolBar, SWT.RIGHT);
+
+    // Support reorder tab item
+    //
+    new TabFolderReorder(tabFolder);
+  }
+
+  /**
+   * Also select the corresponding file in the left hand tree...
+   *
+   * @param event
+   */
+  private void handleTabSelectionEvent( Event event ) {
+    if (event.item instanceof CTabItem) {
+      CTabItem tabItem = (CTabItem) event.item;
+      ExplorerFile explorerFile = (ExplorerFile) tabItem.getData();
+      selectInTree( explorerFile.getFilename() );
+    }
+  }
+
+  public CTabItem addFile(ExplorerFile explorerFile, IExplorerFileTypeHandler renderer) {
+    PropsUi props = PropsUi.getInstance();
+
+    // Create tab item
+    //
+    CTabItem tabItem = new CTabItem(tabFolder, SWT.CLOSE);
+    tabItem.setFont(tabFolder.getFont() );
+    tabItem.setText(Const.NVL(explorerFile.getName(), ""));
+    if (explorerFile.getTabImage() != null) {
+      tabItem.setImage(explorerFile.getTabImage());
+    } else {
+      tabItem.setImage(GuiResource.getInstance().getImageFile());
+    }
+    tabItem.setToolTipText(explorerFile.getFilename());
+    tabItem.setData(explorerFile);
+
+    // Set the tab bold if the file has changed and vice-versa
+    //
+    explorerFile.addContentChangedListener( new IContentChangedListener() {
+      @Override public void contentChanged( Object parentObject ) {
+        tabItem.setFont( GuiResource.getInstance().getFontBold() );
+      }
+
+      @Override public void contentSafe( Object parentObject ) {
+        tabItem.setFont( tabFolder.getFont() );
+      }
+    } );
+
+    // Create composite for editor and buttons
+    //
+    Composite composite = new Composite(tabFolder, SWT.NONE);
+    FormLayout layoutComposite = new FormLayout();
+    layoutComposite.marginWidth = Const.FORM_MARGIN;
+    layoutComposite.marginHeight = Const.FORM_MARGIN;
+    composite.setLayout(layoutComposite);
+    props.setLook(composite);
+
+    // This is usually done by the file type
+    //
+    renderer.renderFile( composite );
+
+    // Create file content area
+    //
+    Composite area = new Composite(composite, SWT.NONE);
+    FormLayout layoutArea = new FormLayout();
+    layoutArea.marginWidth = 0;
+    layoutArea.marginHeight = 0;
+    area.setLayout(layoutArea);
+    FormData fdArea = new FormData();
+    fdArea.left = new FormAttachment(0, 0);
+    fdArea.top = new FormAttachment(0, 0);
+    fdArea.right = new FormAttachment(100, 0);
+    fdArea.bottom = new FormAttachment(100, 0);
+
+    area.setLayoutData(fdArea);
+    props.setLook(area);
+
+    tabItem.setControl(composite);
+    tabItem.setData(explorerFile);
+
+    files.add(explorerFile);
+
+    // Activate perspective
+    //
+    this.activate();
+
+    updateGui();
+
+    // Switch to the tab
+    //
+    tabFolder.setSelection(tabItem);
+
+    selectInTree(explorerFile.getFilename());
+
+    return tabItem;
+  }
+
+  private void selectInTree( String filename ) {
+    for (TreeItemFolder tif : treeItemFolderMap.values()) {
+      if (tif.path.equals( filename )) {
+        tree.setSelection( tif.treeItem );
+        return;
+      }
+    }
+  }
+
+  public void setActiveFile(ExplorerFile file) {
+    for (CTabItem item : tabFolder.getItems()) {
+      if (item.getData().equals(file)) {
+        tabFolder.setSelection(item);
+        tabFolder.showItem(item);
+
+        HopGui.getInstance().handleFileCapabilities(explorerFileType, false, false);
+      }
+    }
+  }
+
+  public void closeFile(ExplorerFile explorerFile) {
+    for (CTabItem item : tabFolder.getItems()) {
+      if (item.getData().equals(explorerFile)) {
+        if (explorerFile.getFileTypeHandler().isCloseable()) {
+          item.dispose();
+        }
+      }
+    }
+  }
+
+  public ExplorerFile getActiveFile() {
+    if (tabFolder.getSelectionIndex() < 0) {
+      return null;
+    }
+
+    return (ExplorerFile) tabFolder.getSelection().getData();
+  }
+
+  @Override
+  public IHopFileTypeHandler getActiveFileTypeHandler() {
+    ExplorerFile explorerFile = getActiveFile();
+    if (explorerFile != null) {
+      return explorerFile.getFileTypeHandler();
+    }
+
+    return new EmptyHopFileTypeHandler();
+  }
+
+  @Override
+  public void setActiveFileTypeHandler(IHopFileTypeHandler fileTypeHandler) {
+    if (fileTypeHandler instanceof ExplorerFile) {
+      this.setActiveFile((ExplorerFile) fileTypeHandler);
+    }
+  }
+
+  protected void onTabClose(CTabFolderEvent event) {
+    CTabItem tabItem = (CTabItem) event.item;
+    ExplorerFile file = (ExplorerFile) tabItem.getData();
+
+    if (file.getFileTypeHandler().isCloseable()) {
+      files.remove(file);
+      tabItem.dispose();
+
+      // Refresh tree to remove bold
+      //
+      this.refresh();
+
+      // If all editor are closed
+      //
+      if (tabFolder.getItemCount() == 0) {
+        HopGui.getInstance().handleFileCapabilities(new EmptyFileType(), false, false);
+      }
+      updateGui();
+    } else {
+      // Ignore event if canceled
+      event.doit = false;
+    }
+  }
+
+  public void onNewFile() {}
+
+  boolean first = true;
+
+  @GuiToolbarElement(
+      root = GUI_PLUGIN_TOOLBAR_PARENT_ID,
+      id = TOOLBAR_ITEM_REFRESH,
+      toolTip = "Refresh",
+      image = "ui/images/refresh.svg")
+  public void refresh() {
+    try {
+      determineRootFolderName(hopGui);
+
+      tree.setRedraw(false);
+      tree.removeAll();
+      treeItemFolderMap.clear();
+
+      // Add the root element...
+      //
+      TreeItem rootItem = new TreeItem(tree, SWT.NONE);
+      rootItem.setText(Const.NVL(rootName, ""));
+      IHopFileType fileType = getFileType(rootFolder);
+      setItemImage(rootItem, fileType);
+      callPaintListeners(tree, rootItem, rootFolder, rootName, fileType);
+      addToFolderMap(rootItem, rootFolder, rootName, fileType);
+
+      refreshFolder(rootItem, rootFolder);
+
+      tree.setRedraw(true);
+
+      if (first) {
+        first = false;
+        // Set the top level items in the tree to be expanded
+        //
+        for (TreeItem item : tree.getItems()) {
+          item.setExpanded( true );
+          TreeMemory.getInstance().storeExpanded(FILE_EXPLORER_TREE, item, true);
+        }
+      } else {
+        TreeMemory.setExpandedFromMemory(tree, FILE_EXPLORER_TREE);
+      }
+    } catch (Exception e) {
+      new ErrorDialog(getShell(), "Error", "Error refreshing file explorer tree", e);
+    }
+  }
+
+  private void addToFolderMap(TreeItem treeItem, String path, String name, IHopFileType fileType) {
+    treeItemFolderMap.put(
+        ConstUi.getTreePath(treeItem, 0), new TreeItemFolder(treeItem, path, name, fileType));
+  }
+
+  private void setItemImage(TreeItem treeItem, IHopFileType fileType) {
+    Image image = typeImageMap.get(fileType.getName());
+    if (image != null) {
+      treeItem.setImage(image);
+    }
+  }
+
+  public Image getFileTypeImage(IHopFileType fileType) {
+    return typeImageMap.get(fileType.getName());
+  }
+
+  public IHopFileType getFileType(String path) throws HopException {
+
+    // TODO: get this list from the plugin registry...
+    //
+    for (IHopFileType hopFileType : fileTypes) {
+      // Only look at the extension of the file
+      //
+      if (hopFileType.isHandledBy(path, false)) {
+        return hopFileType;
+      }
+    }
+
+    return new EmptyFileType();
+  }
+
+  private void refreshFolder(TreeItem item, String path) {
+
+    try {
+      FileObject fileObject = HopVfs.getFileObject(path);
+      FileObject[] children = fileObject.getChildren();
+
+      // Sort by full path ascending
+      Arrays.sort(children, Comparator.comparing(Object::toString));
+
+      for (boolean folder : new boolean[] {true, false}) {
+        for (FileObject child : children) {
+          if (child.isHidden()) {
+            continue; // skip hidden files for now
+          }
+          if (child.isFolder() != folder) {
+            continue;
+          }
+
+          String childPath = child.getName().getPath();
+          String childName = child.getName().getBaseName();
+          IHopFileType fileType = getFileType(childPath);
+          TreeItem childItem = new TreeItem(item, SWT.NONE);
+          childItem.setText(childName);
+          setItemImage(childItem, fileType);
+          callPaintListeners(tree, childItem, childPath, childName, fileType);
+          addToFolderMap(childItem, childPath, childName, fileType);
+
+          // Recursively add children
+          //
+          if (child.isFolder()) {
+            refreshFolder(childItem, child.getName().getPath());
+          }
+        }
+      }
+
+    } catch (Exception e) {
+      TreeItem treeItem = new TreeItem(item, SWT.NONE);
+      treeItem.setText("!!Error refreshing folder!!");
+      hopGui.getLog().logError("Error refresh folder '" + path + "'", e);
+    }
+  }
+
+  private void callPaintListeners(
+      Tree tree, TreeItem treeItem, String path, String name, IHopFileType fileType) {
+    for (IExplorerFilePaintListener filePaintListener : filePaintListeners) {
+      filePaintListener.filePainted(tree, treeItem, path, name);
+    }
+  }
+
+  protected void updateSelection() {
+
+    String objectKey = null;
+    TreeItemFolder tif = null;
+
+    if (tree.getSelectionCount() > 0) {
+      TreeItem selectedItem = tree.getSelection()[0];
+      objectKey = ConstUi.getTreePath(selectedItem, 0);
+      tif = treeItemFolderMap.get(objectKey);
+    }
+
+    toolBarWidgets.enableToolbarItem(TOOLBAR_ITEM_EDIT, tif != null);
+    toolBarWidgets.enableToolbarItem(TOOLBAR_ITEM_DUPLICATE, tif != null);
+    toolBarWidgets.enableToolbarItem(TOOLBAR_ITEM_DELETE, tif != null);
+  }
+
+  @Override
+  public boolean remove(IHopFileTypeHandler typeHandler) {
+    if (typeHandler instanceof ExplorerFile) {
+      ExplorerFile file = (ExplorerFile) typeHandler;
+
+      if (file.getFileTypeHandler().isCloseable()) {
+
+        files.remove(file);
+
+        for (CTabItem item : tabFolder.getItems()) {
+          if (file.equals(item.getData())) {
+            item.dispose();
+          }
+        }
+
+        // Refresh tree to remove bold
+        //
+        this.refresh();
+
+        // If all editor are closed
+        //
+        if (tabFolder.getItemCount() == 0) {
+          HopGui.getInstance().handleFileCapabilities(new EmptyFileType(), false, false);
+        }
+      }
+    }
+
+    return false;
+  }
+
+  @Override
+  public List<TabItemHandler> getItems() {
+    return null;
+  }
+
+  @Override
+  public void navigateToPreviousFile() {
+    tabFolder.setSelection(tabFolder.getSelectionIndex() + 1);
+  }
+
+  @Override
+  public void navigateToNextFile() {
+    tabFolder.setSelection(tabFolder.getSelectionIndex() - 1);
+  }
+
+  @Override
+  public boolean hasNavigationPreviousFile() {
+    if (tabFolder.getItemCount() == 0) {
+      return false;
+    }
+    return tabFolder.getSelectionIndex() >= 1;
+  }
+
+  @Override
+  public boolean hasNavigationNextFile() {
+    if (tabFolder.getItemCount() == 0) {
+      return false;
+    }
+    return tabFolder.getSelectionIndex() < tabFolder.getItemCount();
+  }
+
+  @Override
+  public Control getControl() {
+    return sash;
+  }
+
+  protected Shell getShell() {
+    return hopGui.getShell();
+  }
+
+  @Override
+  public List<IGuiContextHandler> getContextHandlers() {
+    List<IGuiContextHandler> handlers = new ArrayList<>();
+    return handlers;
+  }
+
+  @Override
+  public List<ISearchable> getSearchables() {
+    List<ISearchable> searchables = new ArrayList<>();
+    return searchables;
+  }
+
+  public void updateGui() {
+    if (hopGui == null || toolBarWidgets == null || toolBar == null || toolBar.isDisposed()) {
+      return;
+    }
+    final IHopFileTypeHandler activeHandler = getActiveFileTypeHandler();
+    hopGui.getDisplay().asyncExec( () -> hopGui.handleFileCapabilities(activeHandler.getFileType(), false, false) );
+  }
+}
diff --git a/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/IExplorerFileContentCallback.java b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/IExplorerFileContentCallback.java
new file mode 100644
index 0000000..79d76c2
--- /dev/null
+++ b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/IExplorerFileContentCallback.java
@@ -0,0 +1,24 @@
+/*
+ * 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.apache.hop.ui.hopgui.perspective.explorer;
+
+public interface IExplorerFileContentCallback {
+
+  void getContent();
+}
diff --git a/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/IExplorerFilePaintListener.java b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/IExplorerFilePaintListener.java
new file mode 100644
index 0000000..c1a75c2
--- /dev/null
+++ b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/IExplorerFilePaintListener.java
@@ -0,0 +1,31 @@
+/*
+ * 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.apache.hop.ui.hopgui.perspective.explorer;
+
+import org.eclipse.swt.widgets.Tree;
+import org.eclipse.swt.widgets.TreeItem;
+
+/**
+ * This interface allows you to adjust the painting of a Tree Item in the explorer perspective
+ */
+public interface IExplorerFilePaintListener {
+
+  void filePainted( Tree tree, TreeItem treeItem, String path, String name );
+
+}
diff --git a/ui/src/main/java/org/apache/hop/ui/core/metadata/MetadataFileType.java b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/file/ExplorerFileType.java
similarity index 90%
copy from ui/src/main/java/org/apache/hop/ui/core/metadata/MetadataFileType.java
copy to ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/file/ExplorerFileType.java
index 4a245bf..c46ba33 100644
--- a/ui/src/main/java/org/apache/hop/ui/core/metadata/MetadataFileType.java
+++ b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/file/ExplorerFileType.java
@@ -15,24 +15,23 @@
  * limitations under the License.
  */
 
-package org.apache.hop.ui.core.metadata;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Properties;
+package org.apache.hop.ui.hopgui.perspective.explorer.file;
 
 import org.apache.hop.core.exception.HopException;
 import org.apache.hop.core.file.IHasFilename;
 import org.apache.hop.core.variables.IVariables;
-import org.apache.hop.metadata.api.IHopMetadata;
 import org.apache.hop.ui.hopgui.HopGui;
 import org.apache.hop.ui.hopgui.context.IGuiContextHandler;
 import org.apache.hop.ui.hopgui.file.IHopFileType;
 import org.apache.hop.ui.hopgui.file.IHopFileTypeHandler;
 
-public class MetadataFileType implements IHopFileType {
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Properties;
+
+public class ExplorerFileType implements IHopFileType {
   @Override public String getName() {
-    return "meta";
+    return "explorer";
   }
  
   @Override
@@ -79,11 +78,11 @@ public class MetadataFileType implements IHopFileType {
   }
 
   @Override public IHopFileTypeHandler openFile( HopGui hopGui, String filename, IVariables parentVariableSpace ) throws HopException {
-    return new MetadataFileTypeHandler();
+    return new ExplorerFileTypeHandler();
   }
 
   @Override public IHopFileTypeHandler newFile( HopGui hopGui, IVariables parentVariableSpace ) throws HopException {
-    return new MetadataFileTypeHandler();
+    return new ExplorerFileTypeHandler();
   }
 
   @Override public boolean isHandledBy( String filename, boolean checkContent ) throws HopException {
@@ -98,4 +97,8 @@ public class MetadataFileType implements IHopFileType {
     List<IGuiContextHandler> handlers = new ArrayList<>();
     return handlers;
   }
+
+  @Override public String getFileTypeImage() {
+    return "ui/images/exploreRepo.svg";
+  }
 }
diff --git a/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/file/ExplorerFileTypeHandler.java b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/file/ExplorerFileTypeHandler.java
new file mode 100644
index 0000000..6fab3df
--- /dev/null
+++ b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/file/ExplorerFileTypeHandler.java
@@ -0,0 +1,156 @@
+/*
+ * 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.apache.hop.ui.hopgui.perspective.explorer.file;
+
+import org.apache.hop.core.exception.HopException;
+import org.apache.hop.core.variables.IVariables;
+import org.apache.hop.core.variables.Variables;
+import org.apache.hop.ui.hopgui.context.IGuiContextHandler;
+import org.apache.hop.ui.hopgui.file.IHopFileType;
+import org.apache.hop.ui.hopgui.file.IHopFileTypeHandler;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+public class ExplorerFileTypeHandler implements IHopFileTypeHandler {
+
+  private static final IHopFileType fileType = new ExplorerFileType();
+
+  private String filename;
+
+  public ExplorerFileTypeHandler() {}
+
+  @Override
+  public Object getSubject() {
+    return null;
+  }
+
+  @Override
+  public String getName() {
+    return null;
+  }
+
+  @Override
+  public void setName(String name) {}
+
+  @Override
+  public IHopFileType getFileType() {
+    return fileType;
+  }
+
+  @Override
+  public String getFilename() {
+    return filename;
+  }
+
+  @Override
+  public void setFilename(String filename) {}
+
+  @Override
+  public void save() throws HopException {}
+
+  @Override
+  public void saveAs(String filename) throws HopException {}
+
+  @Override
+  public void start() {}
+
+  @Override
+  public void stop() {}
+
+  @Override
+  public void pause() {}
+
+  @Override
+  public void resume() {}
+
+  @Override
+  public void preview() {}
+
+  @Override
+  public void debug() {}
+
+  @Override
+  public void redraw() {}
+
+  @Override
+  public void updateGui() {}
+
+  @Override
+  public void selectAll() {}
+
+  @Override
+  public void unselectAll() {}
+
+  @Override
+  public void copySelectedToClipboard() {}
+
+  @Override
+  public void cutSelectedToClipboard() {}
+
+  @Override
+  public void deleteSelected() {}
+
+  @Override
+  public void pasteFromClipboard() {}
+
+  @Override
+  public boolean isCloseable() {
+    return true;
+  }
+
+  @Override
+  public void close() {}
+
+  @Override
+  public boolean hasChanged() {
+    return false;
+  }
+
+  @Override
+  public void undo() {}
+
+  @Override
+  public void redo() {}
+
+  @Override
+  public Map<String, Object> getStateProperties() {
+    return Collections.emptyMap();
+  }
+
+  @Override
+  public void applyStateProperties(Map<String, Object> stateProperties) {}
+
+  @Override
+  public List<IGuiContextHandler> getContextHandlers() {
+    List<IGuiContextHandler> handlers = new ArrayList<>();
+    return handlers;
+  }
+
+  /**
+   * Explorer doesn't have it's own variables. It should take it elsewhere.
+   *
+   * @return An empty variables set
+   */
+  @Override
+  public IVariables getVariables() {
+    return new Variables();
+  }
+}
diff --git a/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/file/FileDetails.java b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/file/FileDetails.java
new file mode 100644
index 0000000..2974e06
--- /dev/null
+++ b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/file/FileDetails.java
@@ -0,0 +1,63 @@
+/*
+ * 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.apache.hop.ui.hopgui.perspective.explorer.file;
+
+import org.apache.hop.ui.hopgui.file.IHopFileType;
+
+public class FileDetails {
+  private String path;
+  private IHopFileType hopFileType;
+
+  public FileDetails( String path, IHopFileType hopFileType ) {
+    this.path = path;
+    this.hopFileType = hopFileType;
+  }
+
+  /**
+   * Gets path
+   *
+   * @return value of path
+   */
+  public String getPath() {
+    return path;
+  }
+
+  /**
+   * @param path The path to set
+   */
+  public void setPath( String path ) {
+    this.path = path;
+  }
+
+  /**
+   * Gets hopFileType
+   *
+   * @return value of hopFileType
+   */
+  public IHopFileType getHopFileType() {
+    return hopFileType;
+  }
+
+  /**
+   * @param hopFileType The hopFileType to set
+   */
+  public void setHopFileType( IHopFileType hopFileType ) {
+    this.hopFileType = hopFileType;
+  }
+}
diff --git a/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/file/IExplorerFileType.java b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/file/IExplorerFileType.java
new file mode 100644
index 0000000..70dc227
--- /dev/null
+++ b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/file/IExplorerFileType.java
@@ -0,0 +1,37 @@
+/*
+ * 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.apache.hop.ui.hopgui.perspective.explorer.file;
+
+import org.apache.hop.ui.hopgui.HopGui;
+import org.apache.hop.ui.hopgui.file.IHopFileType;
+import org.apache.hop.ui.hopgui.perspective.explorer.ExplorerFile;
+import org.apache.hop.ui.hopgui.perspective.explorer.ExplorerPerspective;
+
+public interface IExplorerFileType<T extends IExplorerFileTypeHandler> extends IHopFileType {
+
+  /**
+   * Create a new file type handler for the given Hop GUI instance, perspective and file
+   *
+   * @param hopGui The Hop GUI
+   * @param perspective The perspective
+   * @param file The file
+   * @return
+   */
+  T createFileTypeHandler(HopGui hopGui, ExplorerPerspective perspective, ExplorerFile file);
+}
diff --git a/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/file/IExplorerFileTypeHandler.java b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/file/IExplorerFileTypeHandler.java
new file mode 100644
index 0000000..2ca3b94
--- /dev/null
+++ b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/file/IExplorerFileTypeHandler.java
@@ -0,0 +1,33 @@
+/*
+ * 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.apache.hop.ui.hopgui.perspective.explorer.file;
+
+import org.apache.hop.ui.hopgui.file.IHopFileTypeHandler;
+import org.apache.hop.ui.hopgui.perspective.explorer.ExplorerFile;
+import org.eclipse.swt.widgets.Composite;
+
+public interface IExplorerFileTypeHandler extends IHopFileTypeHandler {
+  /**
+   * Render a file on a composite
+   *
+   * @param composite The parent composite in a new tab in the explorer perspective
+   */
+  void renderFile( Composite composite);
+
+}
diff --git a/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/file/capabilities/FileTypeCapabilities.java b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/file/capabilities/FileTypeCapabilities.java
new file mode 100644
index 0000000..cc984e3
--- /dev/null
+++ b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/file/capabilities/FileTypeCapabilities.java
@@ -0,0 +1,31 @@
+/*
+ * 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.apache.hop.ui.hopgui.perspective.explorer.file.capabilities;
+
+import java.util.Properties;
+
+public class FileTypeCapabilities {
+  public static final Properties getCapabilities(String...capabilities) {
+    Properties properties = new Properties();
+    for (String capability : capabilities) {
+      properties.put(capability, "true");
+    }
+    return properties;
+  }
+}
diff --git a/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/file/types/FolderFileType.java b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/file/types/FolderFileType.java
new file mode 100644
index 0000000..2196fa6
--- /dev/null
+++ b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/file/types/FolderFileType.java
@@ -0,0 +1,116 @@
+/*
+ * 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.apache.hop.ui.hopgui.perspective.explorer.file.types;
+
+import org.apache.hop.core.exception.HopException;
+import org.apache.hop.core.file.IHasFilename;
+import org.apache.hop.core.variables.IVariables;
+import org.apache.hop.core.vfs.HopVfs;
+import org.apache.hop.ui.hopgui.HopGui;
+import org.apache.hop.ui.hopgui.context.IGuiContextHandler;
+import org.apache.hop.ui.hopgui.file.HopFileTypePlugin;
+import org.apache.hop.ui.hopgui.file.IHopFileType;
+import org.apache.hop.ui.hopgui.file.IHopFileTypeHandler;
+import org.apache.hop.ui.hopgui.file.empty.EmptyHopFileTypeHandler;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Properties;
+
+@HopFileTypePlugin(
+  id = "FolderFileType",
+  name = "Folder File Type",
+  description = "Folder handling in the explorer perspective",
+  image = "ui/images/folder.svg")
+
+public class FolderFileType implements IHopFileType {
+  @Override
+  public String getName() {
+    return "folder";
+  }
+
+  @Override
+  public String getDefaultFileExtension() {
+    return "";
+  }
+
+  @Override
+  public String[] getFilterExtensions() {
+    return new String[0];
+  }
+
+  @Override
+  public String[] getFilterNames() {
+    return new String[0];
+  }
+
+  @Override
+  public Properties getCapabilities() {
+    return new Properties();
+  }
+
+  @Override
+  public boolean hasCapability(String capability) {
+    return false;
+  }
+
+  @Override
+  public IHopFileTypeHandler openFile(
+      HopGui hopGui, String filename, IVariables parentVariableSpace) throws HopException {
+    return new EmptyHopFileTypeHandler();
+  }
+
+  @Override
+  public IHopFileTypeHandler newFile(HopGui hopGui, IVariables parentVariableSpace)
+      throws HopException {
+    return new EmptyHopFileTypeHandler();
+  }
+
+  /**
+   * See if this is a folder
+   *
+   * @param filename The filename
+   * @param checkContent True if we want to look inside the file content
+   * @return
+   * @throws HopException
+   */
+  @Override
+  public boolean isHandledBy(String filename, boolean checkContent) throws HopException {
+    try {
+      return HopVfs.getFileObject(filename).isFolder();
+    } catch (Exception e) {
+      throw new HopException("Error seeing if file '" + filename + "' is a folder", e);
+    }
+  }
+
+  @Override
+  public boolean supportsFile(IHasFilename metaObject) {
+    return false;
+  }
+
+  @Override
+  public List<IGuiContextHandler> getContextHandlers() {
+    return Collections.emptyList();
+  }
+
+  @Override
+  public String getFileTypeImage() {
+    return getClass().getAnnotation(HopFileTypePlugin.class).image();
+  }
+}
diff --git a/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/file/types/GenericFileType.java b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/file/types/GenericFileType.java
new file mode 100644
index 0000000..5003fe3
--- /dev/null
+++ b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/file/types/GenericFileType.java
@@ -0,0 +1,111 @@
+/*
+ * 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.apache.hop.ui.hopgui.perspective.explorer.file.types;
+
+import org.apache.hop.core.exception.HopException;
+import org.apache.hop.core.file.IHasFilename;
+import org.apache.hop.core.variables.IVariables;
+import org.apache.hop.core.vfs.HopVfs;
+import org.apache.hop.ui.hopgui.HopGui;
+import org.apache.hop.ui.hopgui.context.IGuiContextHandler;
+import org.apache.hop.ui.hopgui.file.IHopFileType;
+import org.apache.hop.ui.hopgui.file.IHopFileTypeHandler;
+import org.apache.hop.ui.hopgui.file.empty.EmptyHopFileTypeHandler;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Properties;
+
+// TODO: implement as plugin, move to text transform plugin
+//
+public class GenericFileType implements IHopFileType {
+  @Override
+  public String getName() {
+    return "Generic File";
+  }
+
+  @Override
+  public String getDefaultFileExtension() {
+    return "";
+  }
+
+  @Override
+  public String[] getFilterExtensions() {
+    return new String[0];
+  }
+
+  @Override
+  public String[] getFilterNames() {
+    return new String[0];
+  }
+
+  @Override
+  public Properties getCapabilities() {
+    return new Properties();
+  }
+
+  @Override
+  public boolean hasCapability(String capability) {
+    return false;
+  }
+
+  @Override
+  public IHopFileTypeHandler openFile(
+      HopGui hopGui, String filename, IVariables parentVariableSpace) throws HopException {
+    return new EmptyHopFileTypeHandler();
+  }
+
+  @Override
+  public IHopFileTypeHandler newFile(HopGui hopGui, IVariables parentVariableSpace)
+      throws HopException {
+    return new EmptyHopFileTypeHandler();
+  }
+
+  /**
+   * See if this is a generic file
+   *
+   * @param filename The filename
+   * @param checkContent True if we want to look inside the file content
+   * @return
+   * @throws HopException
+   */
+  @Override
+  public boolean isHandledBy(String filename, boolean checkContent) throws HopException {
+    try {
+      return HopVfs.getFileObject(filename).isFile();
+    } catch (Exception e) {
+      throw new HopException("Error seeing if file '" + filename + "' is a generic file", e);
+    }
+  }
+
+  @Override
+  public boolean supportsFile(IHasFilename metaObject) {
+    return false;
+  }
+
+  @Override
+  public List<IGuiContextHandler> getContextHandlers() {
+    return Collections.emptyList();
+  }
+
+  @Override
+  public String getFileTypeImage() {
+    return "ui/images/file.svg";
+  }
+}
diff --git a/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/file/types/base/BaseExplorerFileType.java b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/file/types/base/BaseExplorerFileType.java
new file mode 100644
index 0000000..d085fd8
--- /dev/null
+++ b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/file/types/base/BaseExplorerFileType.java
@@ -0,0 +1,188 @@
+/*
+ * 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.apache.hop.ui.hopgui.perspective.explorer.file.types.base;
+
+import org.apache.commons.vfs2.FileObject;
+import org.apache.hop.core.exception.HopException;
+import org.apache.hop.core.file.IHasFilename;
+import org.apache.hop.core.variables.IVariables;
+import org.apache.hop.core.vfs.HopVfs;
+import org.apache.hop.ui.hopgui.HopGui;
+import org.apache.hop.ui.hopgui.context.IGuiContextHandler;
+import org.apache.hop.ui.hopgui.file.HopFileTypeBase;
+import org.apache.hop.ui.hopgui.file.HopFileTypePlugin;
+import org.apache.hop.ui.hopgui.perspective.explorer.ExplorerFile;
+import org.apache.hop.ui.hopgui.perspective.explorer.ExplorerPerspective;
+import org.apache.hop.ui.hopgui.perspective.explorer.file.IExplorerFileType;
+import org.apache.hop.ui.hopgui.perspective.explorer.file.IExplorerFileTypeHandler;
+import org.eclipse.swt.custom.CTabItem;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Properties;
+
+public abstract class BaseExplorerFileType<T extends IExplorerFileTypeHandler>
+    extends HopFileTypeBase implements IExplorerFileType<T> {
+
+  private String name;
+  private String defaultFileExtension;
+  private String[] filterExtensions;
+  private String[] filterNames;
+  private Properties capabilities;
+
+  public BaseExplorerFileType() {}
+
+  public BaseExplorerFileType(
+      String name,
+      String defaultFileExtension,
+      String[] filterExtensions,
+      String[] filterNames,
+      Properties capabilities) {
+    this.name = name;
+    this.defaultFileExtension = defaultFileExtension;
+    this.filterExtensions = filterExtensions;
+    this.filterNames = filterNames;
+    this.capabilities = capabilities;
+  }
+
+  @Override
+  public boolean supportsFile(IHasFilename metaObject) {
+    return false;
+  }
+
+  @Override
+  public List<IGuiContextHandler> getContextHandlers() {
+    return Collections.emptyList();
+  }
+
+  /**
+   * Gets name
+   *
+   * @return value of name
+   */
+  @Override
+  public String getName() {
+    return name;
+  }
+
+  /** @param name The name to set */
+  public void setName(String name) {
+    this.name = name;
+  }
+
+  /**
+   * Gets defaultFileExtension
+   *
+   * @return value of defaultFileExtension
+   */
+  @Override
+  public String getDefaultFileExtension() {
+    return defaultFileExtension;
+  }
+
+  /** @param defaultFileExtension The defaultFileExtension to set */
+  public void setDefaultFileExtension(String defaultFileExtension) {
+    this.defaultFileExtension = defaultFileExtension;
+  }
+
+  /**
+   * Gets filterExtensions
+   *
+   * @return value of filterExtensions
+   */
+  @Override
+  public String[] getFilterExtensions() {
+    return filterExtensions;
+  }
+
+  /** @param filterExtensions The filterExtensions to set */
+  public void setFilterExtensions(String[] filterExtensions) {
+    this.filterExtensions = filterExtensions;
+  }
+
+  /**
+   * Gets filterNames
+   *
+   * @return value of filterNames
+   */
+  @Override
+  public String[] getFilterNames() {
+    return filterNames;
+  }
+
+  /** @param filterNames The filterNames to set */
+  public void setFilterNames(String[] filterNames) {
+    this.filterNames = filterNames;
+  }
+
+  /**
+   * Gets capabilities
+   *
+   * @return value of capabilities
+   */
+  @Override
+  public Properties getCapabilities() {
+    return capabilities;
+  }
+
+  /** @param capabilities The capabilities to set */
+  public void setCapabilities(Properties capabilities) {
+    this.capabilities = capabilities;
+  }
+
+  @Override
+  public String getFileTypeImage() {
+    return getClass().getAnnotation(HopFileTypePlugin.class).image();
+  }
+
+  @Override
+  public T openFile(HopGui hopGui, String filename, IVariables parentVariables)
+      throws HopException {
+
+    try {
+      FileObject fileObject = HopVfs.getFileObject(parentVariables.resolve(filename));
+      String name = fileObject.getName().getBaseName();
+
+      // Open the file in the explorer perspective
+      //
+      ExplorerPerspective perspective = ExplorerPerspective.getInstance();
+
+      ExplorerFile explorerFile = new ExplorerFile();
+      explorerFile.setName(name);
+      explorerFile.setFilename(filename);
+      explorerFile.setFileType(this);
+      explorerFile.setTabImage(perspective.getFileTypeImage(this));
+
+      T fileTypeHandler = createFileTypeHandler(hopGui, perspective, explorerFile);
+      explorerFile.setFileTypeHandler(fileTypeHandler);
+
+      CTabItem tabItem = perspective.addFile(explorerFile, fileTypeHandler);
+      explorerFile.setName(tabItem.getText());
+
+      return fileTypeHandler;
+    } catch (Exception e) {
+      throw new HopException(
+          "Error opening file '" + filename + "' in a new tab in the Explorer perspective", e);
+    }
+  }
+
+  @Override
+  public abstract T createFileTypeHandler(
+      HopGui hopGui, ExplorerPerspective perspective, ExplorerFile file);
+}
diff --git a/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/file/types/base/BaseExplorerFileTypeHandler.java b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/file/types/base/BaseExplorerFileTypeHandler.java
new file mode 100644
index 0000000..e9463cc
--- /dev/null
+++ b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/file/types/base/BaseExplorerFileTypeHandler.java
@@ -0,0 +1,246 @@
+/*
+ * 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.apache.hop.ui.hopgui.perspective.explorer.file.types.base;
+
+import org.apache.hop.core.exception.HopException;
+import org.apache.hop.core.variables.IVariables;
+import org.apache.hop.ui.core.dialog.ErrorDialog;
+import org.apache.hop.ui.hopgui.HopGui;
+import org.apache.hop.ui.hopgui.context.IGuiContextHandler;
+import org.apache.hop.ui.hopgui.file.IHopFileType;
+import org.apache.hop.ui.hopgui.file.IHopFileTypeHandler;
+import org.apache.hop.ui.hopgui.perspective.explorer.ExplorerFile;
+import org.apache.hop.ui.hopgui.perspective.explorer.ExplorerPerspective;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.widgets.MessageBox;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+public abstract class BaseExplorerFileTypeHandler implements IHopFileTypeHandler {
+
+  protected HopGui hopGui;
+  protected ExplorerPerspective perspective;
+  protected ExplorerFile explorerFile;
+
+  public BaseExplorerFileTypeHandler(
+      HopGui hopGui, ExplorerPerspective perspective, ExplorerFile explorerFile) {
+    this.hopGui = hopGui;
+    this.perspective = perspective;
+    this.explorerFile = explorerFile;
+  }
+
+  @Override
+  public List<IGuiContextHandler> getContextHandlers() {
+    return Collections.emptyList();
+  }
+
+  @Override
+  public Object getSubject() {
+    return explorerFile;
+  }
+
+  @Override
+  public String getName() {
+    return explorerFile.getName();
+  }
+
+  @Override
+  public void setName(String name) {
+    explorerFile.setName(name);
+  }
+
+  @Override
+  public IHopFileType getFileType() {
+    return explorerFile.getFileType();
+  }
+
+  @Override
+  public String getFilename() {
+    return explorerFile.getFilename();
+  }
+
+  @Override
+  public void setFilename(String filename) {
+    explorerFile.setFilename(filename);
+  }
+
+  @Override
+  public void save() throws HopException {
+    throw new HopException("Saving file '" + getFilename() + " is not implemented.");
+  }
+
+  @Override
+  public void saveAs(String filename) throws HopException {
+    throw new HopException("Saving file '" + getFilename() + " is not implemented.");
+  }
+
+  @Override
+  public void start() {
+    // Nothing to start
+  }
+
+  @Override
+  public void stop() {
+    // Nothing to stop
+  }
+
+  @Override
+  public void pause() {
+    // Nothing to pause
+  }
+
+  @Override
+  public void resume() {
+    // Nothing to resume
+  }
+
+  @Override
+  public void preview() {
+    // Nothing to preview
+  }
+
+  @Override
+  public void debug() {
+    // Nothing to debug
+  }
+
+  @Override
+  public void redraw() {}
+
+  @Override
+  public void updateGui() {}
+
+  @Override
+  public void selectAll() {}
+
+  @Override
+  public void unselectAll() {}
+
+  @Override
+  public void copySelectedToClipboard() {}
+
+  @Override
+  public void cutSelectedToClipboard() {}
+
+  @Override
+  public void deleteSelected() {}
+
+  @Override
+  public void pasteFromClipboard() {}
+
+  @Override
+  public boolean isCloseable() {
+    try {
+      if (explorerFile.isChanged()) {
+        MessageBox messageDialog =
+            new MessageBox(hopGui.getShell(), SWT.ICON_QUESTION | SWT.YES | SWT.NO | SWT.CANCEL);
+        messageDialog.setText("Save file?");
+        messageDialog.setMessage(
+            "Do you want to save file '" + explorerFile.getName() + "' before closing?");
+        int answer = messageDialog.open();
+        if ((answer & SWT.YES) != 0) {
+          save();
+          return true;
+        }
+        if ((answer & SWT.NO) != 0) {
+          // User doesn't want to save but close
+          return true;
+        }
+        return false;
+      }
+      return true;
+    } catch (Exception e) {
+      new ErrorDialog(hopGui.getShell(), "Error", "Error preparing file close of '"+explorerFile.getName()+"'", e);
+      return false;
+    }
+  }
+
+  @Override
+  public boolean hasChanged() {
+    return false;
+  }
+
+  @Override
+  public void undo() {}
+
+  @Override
+  public void redo() {}
+
+  @Override
+  public Map<String, Object> getStateProperties() {
+    return Collections.emptyMap();
+  }
+
+  @Override
+  public void close() {
+    perspective.closeFile(explorerFile);
+  }
+
+  @Override
+  public void applyStateProperties(Map<String, Object> stateProperties) {}
+
+  @Override
+  public IVariables getVariables() {
+    return hopGui.getVariables();
+  }
+
+  /**
+   * Gets hopGui
+   *
+   * @return value of hopGui
+   */
+  public HopGui getHopGui() {
+    return hopGui;
+  }
+
+  /** @param hopGui The hopGui to set */
+  public void setHopGui(HopGui hopGui) {
+    this.hopGui = hopGui;
+  }
+
+  /**
+   * Gets perspective
+   *
+   * @return value of perspective
+   */
+  public ExplorerPerspective getPerspective() {
+    return perspective;
+  }
+
+  /** @param perspective The perspective to set */
+  public void setPerspective(ExplorerPerspective perspective) {
+    this.perspective = perspective;
+  }
+
+  /**
+   * Gets explorerFile
+   *
+   * @return value of explorerFile
+   */
+  public ExplorerFile getExplorerFile() {
+    return explorerFile;
+  }
+
+  /** @param explorerFile The explorerFile to set */
+  public void setExplorerFile(ExplorerFile explorerFile) {
+    this.explorerFile = explorerFile;
+  }
+}
diff --git a/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/file/types/log/LogExplorerFileType.java b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/file/types/log/LogExplorerFileType.java
new file mode 100644
index 0000000..2cfdea5
--- /dev/null
+++ b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/file/types/log/LogExplorerFileType.java
@@ -0,0 +1,69 @@
+/*
+ * 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.apache.hop.ui.hopgui.perspective.explorer.file.types.log;
+
+import org.apache.hop.core.Props;
+import org.apache.hop.core.exception.HopException;
+import org.apache.hop.core.logging.LogChannel;
+import org.apache.hop.core.variables.IVariables;
+import org.apache.hop.ui.core.PropsUi;
+import org.apache.hop.ui.hopgui.HopGui;
+import org.apache.hop.ui.hopgui.file.HopFileTypePlugin;
+import org.apache.hop.ui.hopgui.file.IHopFileTypeHandler;
+import org.apache.hop.ui.hopgui.file.empty.EmptyHopFileTypeHandler;
+import org.apache.hop.ui.hopgui.perspective.explorer.ExplorerFile;
+import org.apache.hop.ui.hopgui.perspective.explorer.ExplorerPerspective;
+import org.apache.hop.ui.hopgui.perspective.explorer.file.IExplorerFileType;
+import org.apache.hop.ui.hopgui.perspective.explorer.file.types.base.BaseExplorerFileType;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.layout.FormAttachment;
+import org.eclipse.swt.layout.FormData;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Text;
+
+import java.io.File;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.util.Properties;
+
+@HopFileTypePlugin(
+    id = "LogExplorerFileType",
+    name = "LOG File Type",
+    description = "Log file handling in the explorer perspective",
+    image = "ui/images/log.svg")
+public class LogExplorerFileType extends BaseExplorerFileType<LogExplorerFileTypeHandler>
+    implements IExplorerFileType<LogExplorerFileTypeHandler> {
+
+  public LogExplorerFileType() {
+    super("Log File", ".log", new String[] {"*.log"}, new String[] {"Log files"}, new Properties());
+  }
+
+  @Override
+  public LogExplorerFileTypeHandler createFileTypeHandler(
+      HopGui hopGui, ExplorerPerspective perspective, ExplorerFile file) {
+    return new LogExplorerFileTypeHandler(hopGui, perspective, file);
+  }
+
+  @Override
+  public IHopFileTypeHandler newFile(HopGui hopGui, IVariables parentVariableSpace)
+      throws HopException {
+    // Not implemented
+    return new EmptyHopFileTypeHandler();
+  }
+}
diff --git a/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/file/types/log/LogExplorerFileTypeHandler.java b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/file/types/log/LogExplorerFileTypeHandler.java
new file mode 100644
index 0000000..25ab682
--- /dev/null
+++ b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/file/types/log/LogExplorerFileTypeHandler.java
@@ -0,0 +1,83 @@
+/*
+ * 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.apache.hop.ui.hopgui.perspective.explorer.file.types.log;
+
+import org.apache.hop.core.Props;
+import org.apache.hop.core.logging.LogChannel;
+import org.apache.hop.ui.core.PropsUi;
+import org.apache.hop.ui.hopgui.HopGui;
+import org.apache.hop.ui.hopgui.file.IHopFileTypeHandler;
+import org.apache.hop.ui.hopgui.perspective.explorer.ExplorerFile;
+import org.apache.hop.ui.hopgui.perspective.explorer.ExplorerPerspective;
+import org.apache.hop.ui.hopgui.perspective.explorer.file.IExplorerFileTypeHandler;
+import org.apache.hop.ui.hopgui.perspective.explorer.file.types.base.BaseExplorerFileTypeHandler;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.layout.FormAttachment;
+import org.eclipse.swt.layout.FormData;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Text;
+
+import java.io.File;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+
+/**
+ * How do we handle a log file in file explorer perspective?
+ */
+public class LogExplorerFileTypeHandler extends BaseExplorerFileTypeHandler implements IExplorerFileTypeHandler {
+
+  private Text wText;
+
+  public LogExplorerFileTypeHandler( HopGui hopGui, ExplorerPerspective perspective, ExplorerFile explorerFile ) {
+    super( hopGui, perspective, explorerFile );
+  }
+
+  @Override
+  public void renderFile(Composite composite) {
+    // Render the file by simply showing the file content as a text widget...
+    //
+    wText = new Text(composite, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL);
+    PropsUi.getInstance().setLook(wText, Props.WIDGET_STYLE_FIXED);
+    wText.setEditable( false );
+    FormData fdText = new FormData();
+    fdText.left = new FormAttachment(0, 0);
+    fdText.right = new FormAttachment(100, 0);
+    fdText.top = new FormAttachment(0, 0);
+    fdText.bottom = new FormAttachment(100, 0);
+    wText.setLayoutData(fdText);
+
+    // TODO: add bottom section to show status, size, cursor position...
+    // TODO: add a checkbox so that we can implement a "tail -f" log viewer
+    //
+
+    // Load the content of the JSON file...
+    //
+    File file = new File(explorerFile.getFilename());
+    if (file.exists()) {
+      try {
+        String contents = new String( Files.readAllBytes(file.toPath()), StandardCharsets.UTF_8);
+        wText.setText(contents);
+      } catch (Exception e) {
+        LogChannel.UI.logError(
+          "Error reading contents of file '" + explorerFile.getFilename() + "'", e);
+      }
+    }
+  }
+
+}
diff --git a/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/file/types/svg/SvgExplorerFileType.java b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/file/types/svg/SvgExplorerFileType.java
new file mode 100644
index 0000000..62493a8
--- /dev/null
+++ b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/file/types/svg/SvgExplorerFileType.java
@@ -0,0 +1,71 @@
+/*
+ * 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.apache.hop.ui.hopgui.perspective.explorer.file.types.svg;
+
+import org.apache.hop.core.SwtUniversalImageSvg;
+import org.apache.hop.core.exception.HopException;
+import org.apache.hop.core.logging.LogChannel;
+import org.apache.hop.core.svg.SvgCache;
+import org.apache.hop.core.svg.SvgCacheEntry;
+import org.apache.hop.core.svg.SvgFile;
+import org.apache.hop.core.svg.SvgImage;
+import org.apache.hop.core.variables.IVariables;
+import org.apache.hop.ui.hopgui.HopGui;
+import org.apache.hop.ui.hopgui.file.HopFileTypePlugin;
+import org.apache.hop.ui.hopgui.file.IHopFileTypeHandler;
+import org.apache.hop.ui.hopgui.file.empty.EmptyHopFileTypeHandler;
+import org.apache.hop.ui.hopgui.perspective.explorer.ExplorerFile;
+import org.apache.hop.ui.hopgui.perspective.explorer.ExplorerPerspective;
+import org.apache.hop.ui.hopgui.perspective.explorer.file.IExplorerFileType;
+import org.apache.hop.ui.hopgui.perspective.explorer.file.types.base.BaseExplorerFileType;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.PaintEvent;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.layout.FormAttachment;
+import org.eclipse.swt.layout.FormData;
+import org.eclipse.swt.widgets.Canvas;
+import org.eclipse.swt.widgets.Composite;
+
+import java.util.Properties;
+
+@HopFileTypePlugin(
+    id = "SvgExplorerFileType",
+    name = "SVG File Type",
+    description = "SVG file handling in the explorer perspective",
+    image = "ui/images/image.svg")
+public class SvgExplorerFileType extends BaseExplorerFileType<SvgExplorerFileTypeHandler>
+    implements IExplorerFileType<SvgExplorerFileTypeHandler> {
+
+  public SvgExplorerFileType() {
+    super("SVG file", ".svg", new String[] {"*.svg"}, new String[] {"SVG Files"}, new Properties());
+  }
+
+  @Override
+  public SvgExplorerFileTypeHandler createFileTypeHandler(
+      HopGui hopGui, ExplorerPerspective perspective, ExplorerFile file) {
+    return new SvgExplorerFileTypeHandler(hopGui, perspective, file);
+  }
+
+  @Override
+  public IHopFileTypeHandler newFile(HopGui hopGui, IVariables parentVariableSpace)
+      throws HopException {
+    return new EmptyHopFileTypeHandler();
+  }
+}
diff --git a/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/file/types/svg/SvgExplorerFileTypeHandler.java b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/file/types/svg/SvgExplorerFileTypeHandler.java
new file mode 100644
index 0000000..c4cb361
--- /dev/null
+++ b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/file/types/svg/SvgExplorerFileTypeHandler.java
@@ -0,0 +1,92 @@
+/*
+ * 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.apache.hop.ui.hopgui.perspective.explorer.file.types.svg;
+
+import org.apache.hop.core.SwtUniversalImageSvg;
+import org.apache.hop.core.logging.LogChannel;
+import org.apache.hop.core.svg.SvgCache;
+import org.apache.hop.core.svg.SvgCacheEntry;
+import org.apache.hop.core.svg.SvgFile;
+import org.apache.hop.core.svg.SvgImage;
+import org.apache.hop.ui.hopgui.HopGui;
+import org.apache.hop.ui.hopgui.file.IHopFileTypeHandler;
+import org.apache.hop.ui.hopgui.perspective.explorer.ExplorerFile;
+import org.apache.hop.ui.hopgui.perspective.explorer.ExplorerPerspective;
+import org.apache.hop.ui.hopgui.perspective.explorer.file.IExplorerFileTypeHandler;
+import org.apache.hop.ui.hopgui.perspective.explorer.file.types.base.BaseExplorerFileTypeHandler;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.PaintEvent;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.layout.FormAttachment;
+import org.eclipse.swt.layout.FormData;
+import org.eclipse.swt.widgets.Canvas;
+import org.eclipse.swt.widgets.Composite;
+
+/**
+ * How do we handle an SVG file in file explorer perspective?
+ */
+public class SvgExplorerFileTypeHandler extends BaseExplorerFileTypeHandler implements IExplorerFileTypeHandler {
+
+  public SvgExplorerFileTypeHandler( HopGui hopGui, ExplorerPerspective perspective, ExplorerFile explorerFile ) {
+    super( hopGui, perspective, explorerFile );
+  }
+
+  private static void paintControl( PaintEvent event, ExplorerFile explorerFile, Canvas canvas) {
+    // Render the SVG file...
+    //
+    Rectangle area = canvas.getBounds();
+
+    try {
+      SvgCacheEntry entry =
+        SvgCache.loadSvg(
+          new SvgFile(explorerFile.getFilename(), SvgExplorerFileType.class.getClassLoader()));
+      SwtUniversalImageSvg svg = new SwtUniversalImageSvg(new SvgImage(entry.getSvgDocument()), true);
+
+      float factorX = (float)area.width / entry.getWidth();
+      float factorY = (float)area.height / entry.getHeight();
+      float minFactor = Math.min(factorX, factorY);
+
+      int imageWidth = (int)(entry.getWidth()*minFactor);
+      int imageHeight = (int)(entry.getHeight()*minFactor);
+
+      Image image = svg.getAsBitmapForSize(canvas.getDisplay(), imageWidth, imageHeight);
+
+      event.gc.drawImage(image, 0, 0);
+    } catch (Exception e) {
+      LogChannel.GENERAL.logError("Error rendering SVG", e);
+    }
+  }
+
+  // Render the SVG file...
+  //
+  @Override
+  public void renderFile(Composite composite) {
+    final Canvas wCanvas = new Canvas(composite, SWT.NONE);
+    FormData fdCanvas = new FormData();
+    fdCanvas.left = new FormAttachment(0, 0);
+    fdCanvas.right = new FormAttachment(100, 0);
+    fdCanvas.top = new FormAttachment(0, 0);
+    fdCanvas.bottom = new FormAttachment(100, 0);
+    wCanvas.setLayoutData(fdCanvas);
+
+    wCanvas.addPaintListener(e -> paintControl(e, explorerFile, wCanvas));
+  }
+
+}