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

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


The following commit(s) were added to refs/heads/main by this push:
     new 2cd541279d support for html and pdf files in Hop Gui #6354 (#6365)
2cd541279d is described below

commit 2cd541279da8dcff5c34dcd256d21daf4cb00ec9
Author: Bart Maertens <[email protected]>
AuthorDate: Tue Jan 13 15:47:50 2026 +0000

    support for html and pdf files in Hop Gui #6354 (#6365)
    
    * add support for HTML and PDF files in Hop Gui. #6354
    
    * i18n updates. #6354
---
 .../transforms/types/HtmlExplorerFileType.java     |  87 ++++++++++
 .../types/HtmlExplorerFileTypeHandler.java         | 186 +++++++++++++++++++++
 .../transforms/types/HtmlOpenAsTextPlugin.java     |  86 ++++++++++
 .../transforms/types/PdfExplorerFileType.java      |  60 +++++++
 .../types/PdfExplorerFileTypeHandler.java          | 117 +++++++++++++
 .../hopgui/file/pipeline/HopPipelineFileType.java  |   5 +-
 .../explorer/config/ExplorerPerspectiveConfig.java |  11 ++
 .../config/ExplorerPerspectiveConfigPlugin.java    |  32 ++++
 .../java/org/apache/hop/ui/util/HelpUtils.java     |  24 ++-
 .../config/messages/messages_en_US.properties      |   3 +
 .../explorer/messages/messages_en_US.properties    |   3 +-
 ui/src/main/resources/ui/images/html.svg           |   9 +
 ui/src/main/resources/ui/images/pdf.svg            |   9 +
 13 files changed, 628 insertions(+), 4 deletions(-)

diff --git 
a/plugins/transforms/textfile/src/main/java/org/apache/hop/pipeline/transforms/types/HtmlExplorerFileType.java
 
b/plugins/transforms/textfile/src/main/java/org/apache/hop/pipeline/transforms/types/HtmlExplorerFileType.java
new file mode 100644
index 0000000000..3addf9545b
--- /dev/null
+++ 
b/plugins/transforms/textfile/src/main/java/org/apache/hop/pipeline/transforms/types/HtmlExplorerFileType.java
@@ -0,0 +1,87 @@
+/*
+ * 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.exception.HopException;
+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.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.capabilities.FileTypeCapabilities;
+import 
org.apache.hop.ui.hopgui.perspective.explorer.file.types.base.BaseExplorerFileType;
+
+@HopFileTypePlugin(
+    id = "HtmlExplorerFileType",
+    name = "HTML File Type",
+    description = "HTML file handling in the explorer perspective",
+    image = "ui/images/html.svg")
+public class HtmlExplorerFileType extends 
BaseExplorerFileType<HtmlExplorerFileTypeHandler> {
+
+  public HtmlExplorerFileType() {
+    super(
+        "HTML File",
+        ".html",
+        new String[] {"*.html", "*.htm"},
+        new String[] {"HTML files"},
+        FileTypeCapabilities.getCapabilities(
+            IHopFileType.CAPABILITY_SAVE,
+            IHopFileType.CAPABILITY_SAVE_AS,
+            IHopFileType.CAPABILITY_CLOSE,
+            IHopFileType.CAPABILITY_FILE_HISTORY,
+            IHopFileType.CAPABILITY_COPY,
+            IHopFileType.CAPABILITY_SELECT));
+  }
+
+  @Override
+  public HtmlExplorerFileTypeHandler createFileTypeHandler(
+      HopGui hopGui, ExplorerPerspective perspective, ExplorerFile file) {
+    return new HtmlExplorerFileTypeHandler(hopGui, perspective, file);
+  }
+
+  @Override
+  public IHopFileTypeHandler newFile(HopGui hopGui, IVariables 
parentVariableSpace)
+      throws HopException {
+    return new EmptyHopFileTypeHandler();
+  }
+
+  @Override
+  public HtmlExplorerFileTypeHandler openFile(HopGui hopGui, String filename, 
IVariables variables)
+      throws HopException {
+    String resolvedFilename = variables.resolve(filename);
+    if (resolvedFilename.toLowerCase().startsWith("http://";)
+        || resolvedFilename.toLowerCase().startsWith("https://";)) {
+      // Create handler without VFS checks for URLs
+      //
+      ExplorerFile explorerFile = new ExplorerFile();
+      explorerFile.setName(resolvedFilename);
+      explorerFile.setFilename(resolvedFilename);
+      explorerFile.setFileType(this);
+
+      ExplorerPerspective perspective = ExplorerPerspective.getInstance();
+      HtmlExplorerFileTypeHandler handler =
+          createFileTypeHandler(hopGui, perspective, explorerFile);
+      perspective.addFile(handler);
+      return handler;
+    }
+    return super.openFile(hopGui, filename, variables);
+  }
+}
diff --git 
a/plugins/transforms/textfile/src/main/java/org/apache/hop/pipeline/transforms/types/HtmlExplorerFileTypeHandler.java
 
b/plugins/transforms/textfile/src/main/java/org/apache/hop/pipeline/transforms/types/HtmlExplorerFileTypeHandler.java
new file mode 100644
index 0000000000..b408cea05e
--- /dev/null
+++ 
b/plugins/transforms/textfile/src/main/java/org/apache/hop/pipeline/transforms/types/HtmlExplorerFileTypeHandler.java
@@ -0,0 +1,186 @@
+/*
+ * 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 java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+import org.apache.commons.vfs2.FileObject;
+import org.apache.hop.core.Const;
+import org.apache.hop.core.exception.HopException;
+import org.apache.hop.core.logging.LogChannel;
+import org.apache.hop.core.vfs.HopVfs;
+import org.apache.hop.ui.core.PropsUi;
+import org.apache.hop.ui.core.dialog.MessageBox;
+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.types.base.BaseExplorerFileTypeHandler;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.browser.Browser;
+import org.eclipse.swt.layout.FormAttachment;
+import org.eclipse.swt.layout.FormData;
+import org.eclipse.swt.widgets.Composite;
+
+/** This handles an HTML file in the file explorer perspective: open, save, 
... */
+public class HtmlExplorerFileTypeHandler extends BaseExplorerFileTypeHandler {
+
+  private Browser wBrowser;
+  private String originalHtmlContent;
+
+  public HtmlExplorerFileTypeHandler(
+      HopGui hopGui, ExplorerPerspective perspective, ExplorerFile 
explorerFile) {
+    super(hopGui, perspective, explorerFile);
+  }
+
+  @Override
+  public void renderFile(Composite composite) {
+    // Render the file by showing the HTML content in a browser widget
+    //
+    wBrowser = new Browser(composite, SWT.NONE);
+    PropsUi.setLook(wBrowser);
+    FormData fdBrowser = new FormData();
+    fdBrowser.left = new FormAttachment(0, 0);
+    fdBrowser.right = new FormAttachment(100, 0);
+    fdBrowser.top = new FormAttachment(0, 0);
+    fdBrowser.bottom = new FormAttachment(100, 0);
+    wBrowser.setLayoutData(fdBrowser);
+
+    reload();
+  }
+
+  @Override
+  public void save() throws HopException {
+    try {
+      // Save the current HTML content
+      //
+      String filename = explorerFile.getFilename();
+
+      boolean fileExist = HopVfs.fileExists(filename);
+
+      // Save the HTML content
+      // Note: We save the original content since Browser widget doesn't 
easily expose
+      // edited content. For full editing support, a source view would be 
needed.
+      //
+      if (originalHtmlContent != null) {
+        try (OutputStream outputStream = HopVfs.getOutputStream(filename, 
false)) {
+          
outputStream.write(originalHtmlContent.getBytes(StandardCharsets.UTF_8));
+          outputStream.flush();
+        }
+      } else {
+        throw new HopException("No HTML content to save");
+      }
+
+      this.clearChanged();
+
+      // Update menu options, tab and tree item
+      updateGui();
+
+      // If we create a new file, refresh the explorer perspective tree
+      if (!fileExist) {
+        perspective.refresh();
+      }
+    } catch (Exception e) {
+      throw new HopException("Unable to save HTML file '" + 
explorerFile.getFilename() + "'", e);
+    }
+  }
+
+  @Override
+  public void saveAs(String filename) throws HopException {
+    try {
+      // Enforce file extension
+      if (!filename.toLowerCase().endsWith(".html") && 
!filename.toLowerCase().endsWith(".htm")) {
+        filename = filename + ".html";
+      }
+
+      // Normalize file name
+      filename = HopVfs.normalize(filename);
+
+      FileObject fileObject = HopVfs.getFileObject(filename);
+      if (fileObject.exists()) {
+        MessageBox box =
+            new MessageBox(hopGui.getActiveShell(), SWT.YES | SWT.NO | 
SWT.ICON_QUESTION);
+        box.setText("Overwrite?");
+        box.setMessage("Are you sure you want to overwrite file '" + filename 
+ "'?");
+        int answer = box.open();
+        if ((answer & SWT.YES) == 0) {
+          return;
+        }
+      }
+
+      setFilename(filename);
+
+      save();
+      hopGui.fileRefreshDelegate.register(filename, this);
+    } catch (Exception e) {
+      throw new HopException("Error validating file existence for '" + 
filename + "'", e);
+    }
+  }
+
+  @Override
+  public void reload() {
+    try {
+      String filename = explorerFile.getFilename();
+      if (filename.toLowerCase().startsWith("http://";)
+          || filename.toLowerCase().startsWith("https://";)) {
+        wBrowser.setUrl(filename);
+        clearChanged();
+        return;
+      }
+
+      // Read HTML content from file
+      String htmlContent = readTextFileContent("UTF-8");
+      originalHtmlContent = Const.NVL(htmlContent, "");
+
+      // Display HTML in browser widget
+      wBrowser.setText(originalHtmlContent);
+
+      // Clear any change flags since we just reloaded
+      clearChanged();
+    } catch (Exception e) {
+      LogChannel.UI.logError(
+          "Error reading contents of HTML file '" + explorerFile.getFilename() 
+ "'", e);
+      // Show error in browser
+      wBrowser.setText(
+          "<html><body><h1>Error loading HTML file</h1><p>"
+              + Const.NVL(e.getMessage(), "Unknown error")
+              + "</p></body></html>");
+    }
+  }
+
+  @Override
+  public void selectAll() {
+    // Browser widget doesn't support selectAll in the same way as Text widget
+    // Could use JavaScript: 
wBrowser.execute("document.execCommand('selectAll',
+    // false, null);");
+    // For now, do nothing
+  }
+
+  @Override
+  public void unselectAll() {
+    // Browser widget doesn't support unselectAll
+    // For now, do nothing
+  }
+
+  @Override
+  public void copySelectedToClipboard() {
+    // Browser widget doesn't directly support copy to clipboard
+    // Could use JavaScript: wBrowser.execute("document.execCommand('copy', 
false,
+    // null);");
+    // For now, do nothing
+  }
+}
diff --git 
a/plugins/transforms/textfile/src/main/java/org/apache/hop/pipeline/transforms/types/HtmlOpenAsTextPlugin.java
 
b/plugins/transforms/textfile/src/main/java/org/apache/hop/pipeline/transforms/types/HtmlOpenAsTextPlugin.java
new file mode 100644
index 0000000000..2d952e800b
--- /dev/null
+++ 
b/plugins/transforms/textfile/src/main/java/org/apache/hop/pipeline/transforms/types/HtmlOpenAsTextPlugin.java
@@ -0,0 +1,86 @@
+/*
+ * 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.gui.plugin.GuiPlugin;
+import org.apache.hop.core.gui.plugin.menu.GuiMenuElement;
+import org.apache.hop.core.vfs.HopVfs;
+import org.apache.hop.ui.hopgui.HopGui;
+import org.apache.hop.ui.hopgui.file.HopFileTypeRegistry;
+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;
+import org.apache.hop.ui.hopgui.perspective.explorer.file.IExplorerFileType;
+
+@GuiPlugin
+public class HtmlOpenAsTextPlugin {
+
+  @GuiMenuElement(
+      root = ExplorerPerspective.GUI_PLUGIN_CONTEXT_MENU_PARENT_ID,
+      id = "ExplorerPerspective-Html-OpenAsText",
+      label =
+          
"i18n:org.apache.hop.ui.hopgui.perspective.explorer:ExplorerPerspective.ToolbarElement.OpenAsText.Label",
+      image = "textfile.svg",
+      parentId = ExplorerPerspective.GUI_PLUGIN_CONTEXT_MENU_PARENT_ID,
+      separator = true)
+  public void openAsText() {
+    ExplorerPerspective perspective = ExplorerPerspective.getInstance();
+    ExplorerFile explorerFile = perspective.getSelectedFile();
+
+    if (explorerFile == null) {
+      return;
+    }
+
+    String filename = explorerFile.getFilename();
+    if (filename == null || !filename.toLowerCase().endsWith(".html")) {
+      return;
+    }
+
+    try {
+      HopGui hopGui = HopGui.getInstance();
+
+      // Find the text file type handler (by using a dummy .txt filename)
+      IHopFileType hopFileType = 
HopFileTypeRegistry.getInstance().findHopFileType("dummy.txt");
+
+      if (hopFileType instanceof IExplorerFileType) {
+        IExplorerFileType textFileType = (IExplorerFileType) hopFileType;
+
+        // Create a new ExplorerFile structure but force it to be the Text 
file type
+        ExplorerFile textExplorerFile = new ExplorerFile();
+        // Use the file URI to ensure a unique string key for the tab, but 
pointing to
+        // the same physical file
+        // This avoids the "Duplicate Tab" check in ExplorerPerspective while 
using a
+        // valid file path that won't crash Hop
+        String uniqueName = HopVfs.getFileObject(filename).getName().getURI();
+        textExplorerFile.setFilename(uniqueName);
+        textExplorerFile.setName(explorerFile.getName() + " (Text)");
+        textExplorerFile.setFileType(textFileType);
+
+        // Create the handler directly - BaseTextExplorerFileTypeHandler 
handles VFS
+        // URIs correctly
+        TextExplorerFileTypeHandler handler =
+            new TextExplorerFileTypeHandler(hopGui, perspective, 
textExplorerFile);
+
+        // Add to perspective (this will open a new tab due to unique URI 
string)
+        perspective.addFile(handler);
+      }
+    } catch (Exception e) {
+      HopGui.getInstance().getLog().logError("Error opening file as text", e);
+    }
+  }
+}
diff --git 
a/plugins/transforms/textfile/src/main/java/org/apache/hop/pipeline/transforms/types/PdfExplorerFileType.java
 
b/plugins/transforms/textfile/src/main/java/org/apache/hop/pipeline/transforms/types/PdfExplorerFileType.java
new file mode 100644
index 0000000000..cda98ad52d
--- /dev/null
+++ 
b/plugins/transforms/textfile/src/main/java/org/apache/hop/pipeline/transforms/types/PdfExplorerFileType.java
@@ -0,0 +1,60 @@
+/*
+ * 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.exception.HopException;
+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.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.capabilities.FileTypeCapabilities;
+import 
org.apache.hop.ui.hopgui.perspective.explorer.file.types.base.BaseExplorerFileType;
+
+@HopFileTypePlugin(
+    id = "PdfExplorerFileType",
+    name = "PDF File Type",
+    description = "PDF file handling in the explorer perspective",
+    image = "ui/images/pdf.svg")
+public class PdfExplorerFileType extends 
BaseExplorerFileType<PdfExplorerFileTypeHandler> {
+
+  public PdfExplorerFileType() {
+    super(
+        "PDF File",
+        ".pdf",
+        new String[] {"*.pdf"},
+        new String[] {"PDF files"},
+        FileTypeCapabilities.getCapabilities(
+            IHopFileType.CAPABILITY_CLOSE, 
IHopFileType.CAPABILITY_FILE_HISTORY));
+  }
+
+  @Override
+  public PdfExplorerFileTypeHandler createFileTypeHandler(
+      HopGui hopGui, ExplorerPerspective perspective, ExplorerFile file) {
+    return new PdfExplorerFileTypeHandler(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/PdfExplorerFileTypeHandler.java
 
b/plugins/transforms/textfile/src/main/java/org/apache/hop/pipeline/transforms/types/PdfExplorerFileTypeHandler.java
new file mode 100644
index 0000000000..e55ce630b8
--- /dev/null
+++ 
b/plugins/transforms/textfile/src/main/java/org/apache/hop/pipeline/transforms/types/PdfExplorerFileTypeHandler.java
@@ -0,0 +1,117 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hop.pipeline.transforms.types;
+
+import org.apache.hop.core.Const;
+import org.apache.hop.core.logging.LogChannel;
+import org.apache.hop.core.vfs.HopVfs;
+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.types.base.BaseExplorerFileTypeHandler;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.browser.Browser;
+import org.eclipse.swt.layout.FormAttachment;
+import org.eclipse.swt.layout.FormData;
+import org.eclipse.swt.widgets.Composite;
+
+/** This handles a PDF file in the file explorer perspective: open, view, ... 
*/
+public class PdfExplorerFileTypeHandler extends BaseExplorerFileTypeHandler {
+
+  private Browser wBrowser;
+
+  public PdfExplorerFileTypeHandler(
+      HopGui hopGui, ExplorerPerspective perspective, ExplorerFile 
explorerFile) {
+    super(hopGui, perspective, explorerFile);
+  }
+
+  @Override
+  public void renderFile(Composite composite) {
+    // Render the file by showing the PDF content in a browser widget
+    // The browser widget can display PDFs using the browser's built-in PDF 
viewer
+    //
+    wBrowser = new Browser(composite, SWT.NONE);
+    PropsUi.setLook(wBrowser);
+    FormData fdBrowser = new FormData();
+    fdBrowser.left = new FormAttachment(0, 0);
+    fdBrowser.right = new FormAttachment(100, 0);
+    fdBrowser.top = new FormAttachment(0, 0);
+    fdBrowser.bottom = new FormAttachment(100, 0);
+    wBrowser.setLayoutData(fdBrowser);
+
+    reload();
+  }
+
+  @Override
+  public void reload() {
+    try {
+      // Get the file URL and load it in the browser
+      // The browser widget will use its built-in PDF viewer to display the PDF
+      //
+      String filename = explorerFile.getFilename();
+
+      // Check if file exists
+      if (!HopVfs.fileExists(filename)) {
+        showError("File not found: " + filename);
+        return;
+      }
+
+      // Convert the filename to a file URL
+      // For local files, we need to use the file:// protocol
+      String fileUrl = HopVfs.getFileObject(filename).getURL().toString();
+
+      // Set the URL in the browser widget
+      wBrowser.setUrl(fileUrl);
+
+      // Clear any change flags since we just reloaded
+      clearChanged();
+    } catch (Exception e) {
+      LogChannel.UI.logError("Error loading PDF file '" + 
explorerFile.getFilename() + "'", e);
+      showError("Error loading PDF file: " + Const.NVL(e.getMessage(), 
"Unknown error"));
+    }
+  }
+
+  /**
+   * Show an error message in the browser widget
+   *
+   * @param message The error message to display
+   */
+  private void showError(String message) {
+    wBrowser.setText(
+        "<html><body><h1>Error loading PDF file</h1><p>" + message + 
"</p></body></html>");
+  }
+
+  @Override
+  public void selectAll() {
+    // Browser widget doesn't support selectAll for PDF content
+    // For now, do nothing
+  }
+
+  @Override
+  public void unselectAll() {
+    // Browser widget doesn't support unselectAll for PDF content
+    // For now, do nothing
+  }
+
+  @Override
+  public void copySelectedToClipboard() {
+    // Browser widget doesn't directly support copy to clipboard for PDF 
content
+    // For now, do nothing
+  }
+}
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 55713a7d72..6e126ec70a 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
@@ -119,7 +119,8 @@ public class HopPipelineFileType<T extends PipelineMeta> 
extends HopFileTypeBase
       filename = HopVfs.normalize(variables.resolve(filename));
 
       // See if the same pipeline isn't already open.
-      // Other file types we might allow to open more than once but not 
pipelines for now.
+      // Other file types we might allow to open more than once but not 
pipelines for
+      // now.
       //
       IHopFileTypeHandler fileTypeHandler =
           
HopGui.getExplorerPerspective().findFileTypeHandlerByFilename(filename);
@@ -200,7 +201,7 @@ public class HopPipelineFileType<T extends PipelineMeta> 
extends HopFileTypeBase
         return super.isHandledBy(filename, checkContent);
       }
     } catch (Exception e) {
-      throw new HopException("Unable to verify file handling of file '" + 
filename + "'", e);
+      return false;
     }
   }
 
diff --git 
a/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/config/ExplorerPerspectiveConfig.java
 
b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/config/ExplorerPerspectiveConfig.java
index e6c0175105..9c7b541949 100644
--- 
a/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/config/ExplorerPerspectiveConfig.java
+++ 
b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/config/ExplorerPerspectiveConfig.java
@@ -24,11 +24,13 @@ public class ExplorerPerspectiveConfig {
   private String lazyLoadingDepth;
   private String fileLoadingMaxSize;
   private Boolean fileExplorerVisibleByDefault;
+  private Boolean openingHelpFiles;
 
   public ExplorerPerspectiveConfig() {
     this.lazyLoadingDepth = "0";
     this.fileLoadingMaxSize = "16";
     this.fileExplorerVisibleByDefault = true;
+    this.openingHelpFiles = false;
   }
 
   public ExplorerPerspectiveConfig(ExplorerPerspectiveConfig config) {
@@ -36,6 +38,7 @@ public class ExplorerPerspectiveConfig {
     this.lazyLoadingDepth = config.lazyLoadingDepth;
     this.fileLoadingMaxSize = config.fileLoadingMaxSize;
     this.fileExplorerVisibleByDefault = config.fileExplorerVisibleByDefault;
+    this.openingHelpFiles = config.openingHelpFiles;
   }
 
   public String getLazyLoadingDepth() {
@@ -61,4 +64,12 @@ public class ExplorerPerspectiveConfig {
   public void setFileExplorerVisibleByDefault(Boolean 
fileExplorerVisibleByDefault) {
     this.fileExplorerVisibleByDefault = fileExplorerVisibleByDefault;
   }
+
+  public Boolean isOpeningHelpFiles() {
+    return openingHelpFiles != null ? openingHelpFiles : false;
+  }
+
+  public void setOpeningHelpFiles(Boolean openingHelpFiles) {
+    this.openingHelpFiles = openingHelpFiles;
+  }
 }
diff --git 
a/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/config/ExplorerPerspectiveConfigPlugin.java
 
b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/config/ExplorerPerspectiveConfigPlugin.java
index 831f6101b4..7df5487fc9 100644
--- 
a/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/config/ExplorerPerspectiveConfigPlugin.java
+++ 
b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/config/ExplorerPerspectiveConfigPlugin.java
@@ -50,6 +50,7 @@ public class ExplorerPerspectiveConfigPlugin
   private static final String WIDGET_ID_FILE_LOADING_MAX_SIZE = 
"10100-file-loading-max-size";
   private static final String WIDGET_ID_FILE_EXPLORER_VISIBLE_BY_DEFAULT =
       "10200-file-explorer-visible-by-default";
+  private static final String WIDGET_ID_OPEN_HELP_FILES = 
"10300-open-help-files";
 
   @GuiWidgetElement(
       id = WIDGET_ID_LAZY_LOADING_DEPTH,
@@ -86,6 +87,17 @@ public class ExplorerPerspectiveConfigPlugin
       description = "Show the file explorer panel by default in the explorer 
perspective")
   private Boolean fileExplorerVisibleByDefault = true;
 
+  @GuiWidgetElement(
+      id = WIDGET_ID_OPEN_HELP_FILES,
+      parentId = ConfigPluginOptionsTab.GUI_WIDGETS_PARENT_ID,
+      type = GuiElementType.CHECKBOX,
+      label = "i18n::ExplorerPerspectiveConfig.OpenHelpFiles.Label",
+      toolTip = "i18n::ExplorerPerspectiveConfig.OpenHelpFiles.Tooltip")
+  @CommandLine.Option(
+      names = {"-oh", "--open-help-in-tabs"},
+      description = "Open help files in Hop GUI tabs instead of external 
browser")
+  private Boolean openingHelpFiles;
+
   /**
    * Gets instance
    *
@@ -99,6 +111,7 @@ public class ExplorerPerspectiveConfigPlugin
     instance.fileLoadingMaxSize = config.getFileLoadingMaxSize();
     Boolean visibleByDefault = config.getFileExplorerVisibleByDefault();
     instance.fileExplorerVisibleByDefault = visibleByDefault != null ? 
visibleByDefault : true;
+    instance.openingHelpFiles = config.isOpeningHelpFiles();
 
     return instance;
   }
@@ -136,6 +149,13 @@ public class ExplorerPerspectiveConfigPlugin
         changed = true;
       }
 
+      if (openingHelpFiles != null) {
+        config.setOpeningHelpFiles(openingHelpFiles);
+        log.logBasic(
+            "Explorer perspective: open help files in tabs is set to '" + 
openingHelpFiles + "'");
+        changed = true;
+      }
+
       // Save to file if anything changed
       //
       if (changed) {
@@ -181,6 +201,10 @@ public class ExplorerPerspectiveConfigPlugin
           ExplorerPerspectiveConfigSingleton.getConfig()
               .setFileExplorerVisibleByDefault(fileExplorerVisibleByDefault);
           break;
+        case WIDGET_ID_OPEN_HELP_FILES:
+          openingHelpFiles = ((Button) control).getSelection();
+          
ExplorerPerspectiveConfigSingleton.getConfig().setOpeningHelpFiles(openingHelpFiles);
+          break;
         default:
           break;
       }
@@ -217,4 +241,12 @@ public class ExplorerPerspectiveConfigPlugin
   public void setFileExplorerVisibleByDefault(Boolean 
fileExplorerVisibleByDefault) {
     this.fileExplorerVisibleByDefault = fileExplorerVisibleByDefault;
   }
+
+  public Boolean isOpeningHelpFiles() {
+    return openingHelpFiles != null ? openingHelpFiles : false;
+  }
+
+  public void setOpeningHelpFiles(Boolean openingHelpFiles) {
+    this.openingHelpFiles = openingHelpFiles;
+  }
 }
diff --git a/ui/src/main/java/org/apache/hop/ui/util/HelpUtils.java 
b/ui/src/main/java/org/apache/hop/ui/util/HelpUtils.java
index d5dd2327ad..45ecae820c 100644
--- a/ui/src/main/java/org/apache/hop/ui/util/HelpUtils.java
+++ b/ui/src/main/java/org/apache/hop/ui/util/HelpUtils.java
@@ -23,6 +23,7 @@ import java.net.URLEncoder;
 import java.nio.charset.StandardCharsets;
 import org.apache.hop.core.HopEnvironment;
 import org.apache.hop.core.database.DatabasePluginType;
+import org.apache.hop.core.exception.HopException;
 import org.apache.hop.core.plugins.ActionPluginType;
 import org.apache.hop.core.plugins.IPlugin;
 import org.apache.hop.core.plugins.TransformPluginType;
@@ -33,6 +34,10 @@ import org.apache.hop.ui.core.PropsUi;
 import org.apache.hop.ui.core.dialog.ErrorDialog;
 import org.apache.hop.ui.core.dialog.MessageBox;
 import org.apache.hop.ui.core.gui.GuiResource;
+import org.apache.hop.ui.hopgui.HopGui;
+import org.apache.hop.ui.hopgui.file.HopFileTypeRegistry;
+import org.apache.hop.ui.hopgui.file.IHopFileType;
+import 
org.apache.hop.ui.hopgui.perspective.explorer.config.ExplorerPerspectiveConfigSingleton;
 import org.eclipse.swt.SWT;
 import org.eclipse.swt.layout.FormAttachment;
 import org.eclipse.swt.layout.FormData;
@@ -91,7 +96,11 @@ public class HelpUtils {
       try {
         String originalUrl = getDocUrl(plugin.getDocumentationUrl());
         String trackedUrl = appendUtmParameters(originalUrl);
-        EnvironmentUtils.getInstance().openUrl(trackedUrl);
+        if 
(ExplorerPerspectiveConfigSingleton.getConfig().isOpeningHelpFiles()) {
+          openHelpInTab(trackedUrl);
+        } else {
+          EnvironmentUtils.getInstance().openUrl(trackedUrl);
+        }
       } catch (Exception ex) {
         new ErrorDialog(shell, "Error", "Error opening URL", ex);
       }
@@ -143,4 +152,17 @@ public class HelpUtils {
 
     return URLEncoder.encode(field, StandardCharsets.UTF_8);
   }
+
+  private static void openHelpInTab(String url) throws HopException {
+    HopGui hopGui = HopGui.getInstance();
+    if (hopGui != null) {
+      IHopFileType htmlFileType = 
HopFileTypeRegistry.getInstance().findHopFileType("help.html");
+      if (htmlFileType != null) {
+        htmlFileType.openFile(hopGui, url, hopGui.getVariables());
+        return;
+      }
+    }
+    // Fallback
+    EnvironmentUtils.getInstance().openUrl(url);
+  }
 }
diff --git 
a/ui/src/main/resources/org/apache/hop/ui/hopgui/perspective/explorer/config/messages/messages_en_US.properties
 
b/ui/src/main/resources/org/apache/hop/ui/hopgui/perspective/explorer/config/messages/messages_en_US.properties
index 4afe25f5f6..7938d11c8f 100644
--- 
a/ui/src/main/resources/org/apache/hop/ui/hopgui/perspective/explorer/config/messages/messages_en_US.properties
+++ 
b/ui/src/main/resources/org/apache/hop/ui/hopgui/perspective/explorer/config/messages/messages_en_US.properties
@@ -21,3 +21,6 @@ ExplorerPerspectiveConfig.LazyLoading.Label=The initial depth 
to load not lazily
 ExplorerPerspectiveConfig.LazyLoading.Tooltip=The initial depth to load not 
lazily
 ExplorerPerspectiveConfig.FileExplorerVisible.Label=Show file explorer panel 
by default
 ExplorerPerspectiveConfig.FileExplorerVisible.Tooltip=When enabled, the file 
explorer panel (project tree) is shown by default when opening the explorer 
perspective
+
+ExplorerPerspectiveConfig.OpenHelpFiles.Label=Open help files in Hop GUI tabs 
instead of external browser
+ExplorerPerspectiveConfig.OpenHelpFiles.Tooltip=When checked, help links will 
open as a new tab within the Hop GUI using the internal HTML viewer. Unchecked 
will open the system default browser.
diff --git 
a/ui/src/main/resources/org/apache/hop/ui/hopgui/perspective/explorer/messages/messages_en_US.properties
 
b/ui/src/main/resources/org/apache/hop/ui/hopgui/perspective/explorer/messages/messages_en_US.properties
index 8782595422..f0af81d93a 100644
--- 
a/ui/src/main/resources/org/apache/hop/ui/hopgui/perspective/explorer/messages/messages_en_US.properties
+++ 
b/ui/src/main/resources/org/apache/hop/ui/hopgui/perspective/explorer/messages/messages_en_US.properties
@@ -53,4 +53,5 @@ ExplorerPerspective.ToolbarElement.Refresh.Tooltip=Refresh
 ExplorerPerspective.ToolbarElement.Rename.Tooltip=Rename the selected file
 ExplorerPerspective.ToolbarElement.ExpandAll.Tooltip=Expand all folders
 ExplorerPerspective.ToolbarElement.CollapseAll.Tooltip=Collapse all folders
-ExplorerPerspective.ToolbarElement.SelectOpenedFile.Tooltip=Select opened file
\ No newline at end of file
+ExplorerPerspective.ToolbarElement.SelectOpenedFile.Tooltip=Select opened file
+ExplorerPerspective.ToolbarElement.OpenAsText.Label=Open as text
diff --git a/ui/src/main/resources/ui/images/html.svg 
b/ui/src/main/resources/ui/images/html.svg
new file mode 100644
index 0000000000..80db07309f
--- /dev/null
+++ b/ui/src/main/resources/ui/images/html.svg
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg"; width="42" height="42" viewBox="0 0 42 
42">
+  <!-- Document background -->
+  <path fill="#E67E22" d="M6.608,2.564v36.867H34.49V8.996h-6.729V2.564"/>
+  <!-- Document outline and fold -->
+  <path fill="#D35400" 
d="M34.477,6.429h-4.029V2.583L27.745,0H3.921v42h33.257V9.008L34.477,6.429z 
M12.097,2.565h15.665v6.432h6.729v30.436H6.608V2.565h3.591"/>
+  <!-- HTML text -->
+  <text x="21" y="26" font-family="Arial, sans-serif" font-size="7" 
font-weight="bold" fill="#FFFFFF" text-anchor="middle">HTML</text>
+</svg>
diff --git a/ui/src/main/resources/ui/images/pdf.svg 
b/ui/src/main/resources/ui/images/pdf.svg
new file mode 100644
index 0000000000..688ea3333b
--- /dev/null
+++ b/ui/src/main/resources/ui/images/pdf.svg
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg"; width="42" height="42" viewBox="0 0 42 
42">
+  <!-- Document background -->
+  <path fill="#E74C3C" d="M6.608,2.564v36.867H34.49V8.996h-6.729V2.564"/>
+  <!-- Document outline and fold -->
+  <path fill="#C0392B" 
d="M34.477,6.429h-4.029V2.583L27.745,0H3.921v42h33.257V9.008L34.477,6.429z 
M12.097,2.565h15.665v6.432h6.729v30.436H6.608V2.565h3.591"/>
+  <!-- PDF text -->
+  <text x="21" y="28" font-family="Arial, sans-serif" font-size="10" 
font-weight="bold" fill="#FFFFFF" text-anchor="middle">PDF</text>
+</svg>


Reply via email to