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 b123f5d852 Add option to open files as text, make clear which files
can be opened, fixes #6529 (#6534)
b123f5d852 is described below
commit b123f5d8523e2e8d17eb730742e9c7bbd92da362
Author: Hans Van Akelyen <[email protected]>
AuthorDate: Tue Feb 10 12:30:12 2026 +0100
Add option to open files as text, make clear which files can be opened,
fixes #6529 (#6534)
---
.../apache/hop/core/util/BinaryDetectionUtil.java | 66 +++++++++
.../main/java/org/apache/hop/git/GitGuiPlugin.java | 33 ++++-
.../perspective/explorer/file/ParquetFileType.java | 5 +
.../perspective/explorer/file/ExcelFileType.java | 5 +
.../transforms/types/HtmlOpenAsTextPlugin.java | 48 +++----
.../main/java/org/apache/hop/ui/core/PropsUi.java | 7 +
.../org/apache/hop/ui/core/gui/GuiMenuWidgets.java | 26 +++-
.../org/apache/hop/ui/core/gui/GuiResource.java | 15 +++
.../apache/hop/ui/core/gui/GuiToolbarWidgets.java | 25 +++-
.../main/java/org/apache/hop/ui/hopgui/HopGui.java | 61 ++++++---
.../ui/hopgui/delegates/HopGuiFileDelegate.java | 6 +-
.../apache/hop/ui/hopgui/file/IHopFileType.java | 10 ++
.../hop/ui/hopgui/file/IHopFileTypeHandler.java | 11 ++
.../hop/ui/hopgui/file/empty/EmptyFileType.java | 5 +
.../perspective/explorer/ExplorerPerspective.java | 35 ++++-
.../explorer/file/types/ArchiveFileType.java | 5 +
.../explorer/file/types/FolderFileType.java | 5 +
.../explorer/file/types/GenericFileType.java | 5 +
.../types/base/BaseExplorerFileTypeHandler.java | 3 +-
.../RawExplorerFileType.java} | 94 ++++---------
.../RawExplorerFileTypeHandler.java} | 150 +++++++++++----------
.../text/BaseTextExplorerFileTypeHandler.java | 2 +-
22 files changed, 416 insertions(+), 206 deletions(-)
diff --git
a/core/src/main/java/org/apache/hop/core/util/BinaryDetectionUtil.java
b/core/src/main/java/org/apache/hop/core/util/BinaryDetectionUtil.java
new file mode 100644
index 0000000000..39c49ed5ae
--- /dev/null
+++ b/core/src/main/java/org/apache/hop/core/util/BinaryDetectionUtil.java
@@ -0,0 +1,66 @@
+/*
+ * 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.core.util;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Simple heuristic to detect if file content looks like text (Option A:
presence of null byte
+ * indicates binary).
+ */
+public final class BinaryDetectionUtil {
+
+ private static final int DEFAULT_MAX_BYTES = 8192;
+
+ private BinaryDetectionUtil() {}
+
+ /**
+ * Returns true if the content appears to be text (no null byte in the
sampled bytes). Returns
+ * false if a null byte is found or on I/O error (treat as binary to be
safe).
+ *
+ * @param inputStream stream to sample (will be read from; caller is
responsible for closing)
+ * @param maxBytesToSample maximum number of bytes to read (e.g. 8192)
+ * @return true if no null byte was found in the first maxBytesToSample
bytes, false otherwise
+ */
+ public static boolean looksLikeText(InputStream inputStream, int
maxBytesToSample) {
+ if (inputStream == null || maxBytesToSample <= 0) {
+ return false;
+ }
+ byte[] buf = new byte[Math.min(maxBytesToSample, 65536)];
+ try {
+ int n = inputStream.read(buf, 0, buf.length);
+ if (n <= 0) {
+ return true;
+ }
+ for (int i = 0; i < n; i++) {
+ if (buf[i] == 0) {
+ return false;
+ }
+ }
+ return true;
+ } catch (IOException e) {
+ return false;
+ }
+ }
+
+ /** Same as {@link #looksLikeText(InputStream, int)} with a default sample
size of 8192 bytes. */
+ public static boolean looksLikeText(InputStream inputStream) {
+ return looksLikeText(inputStream, DEFAULT_MAX_BYTES);
+ }
+}
diff --git
a/plugins/misc/git/src/main/java/org/apache/hop/git/GitGuiPlugin.java
b/plugins/misc/git/src/main/java/org/apache/hop/git/GitGuiPlugin.java
index 72c437050a..1f5434bfac 100644
--- a/plugins/misc/git/src/main/java/org/apache/hop/git/GitGuiPlugin.java
+++ b/plugins/misc/git/src/main/java/org/apache/hop/git/GitGuiPlugin.java
@@ -124,6 +124,14 @@ public class GitGuiPlugin
private final Color colorStagedModify;
private final Color colorUnstaged;
+ /** Muted variants when the explorer has already grayed the item
(non-openable file). */
+ private final Color colorStagedAddGray;
+
+ private final Color colorStagedModifyGray;
+ private final Color colorUnstagedGray;
+ private final Color colorIgnoredGray;
+ private final Color colorStagedUnchangedGray;
+
public static GitGuiPlugin getInstance() {
if (instance == null) {
instance = new GitGuiPlugin();
@@ -146,6 +154,12 @@ public class GitGuiPlugin
colorStagedUnchanged = GuiResource.getInstance().getColorBlack();
colorStagedAdd = GuiResource.getInstance().getColorDarkGreen();
+ colorStagedAddGray = GuiResource.getInstance().getColorDarkGreenMuted();
+ colorStagedModifyGray = GuiResource.getInstance().getColorLightBlueMuted();
+ colorUnstagedGray = GuiResource.getInstance().getColorRedMuted();
+ colorIgnoredGray = GuiResource.getInstance().getColorDarkGrayMuted();
+ colorStagedUnchangedGray = GuiResource.getInstance().getColorBlackMuted();
+
refreshChangedFiles();
}
@@ -866,6 +880,11 @@ public class GitGuiPlugin
// Normalize path
String absolutePath = getAbsoluteFilename(path);
+ // Use gray variants when the explorer has already grayed this item
(non-openable file)
+ Color systemDarkGray =
tree.getDisplay().getSystemColor(SWT.COLOR_DARK_GRAY);
+ Color currentFg = treeItem.getForeground();
+ boolean useGrayVariants = currentFg != null &&
currentFg.equals(systemDarkGray);
+
// Changed git file colored blue
UIFile file = changedFiles.get(absolutePath);
if (file != null) {
@@ -873,18 +892,24 @@ public class GitGuiPlugin
case ADD:
case COPY:
case RENAME:
- treeItem.setForeground(file.isStaged() ? colorStagedAdd :
colorUnstaged);
+ treeItem.setForeground(
+ file.isStaged()
+ ? (useGrayVariants ? colorStagedAddGray : colorStagedAdd)
+ : (useGrayVariants ? colorUnstagedGray : colorUnstaged));
break;
case MODIFY:
- treeItem.setForeground(file.isStaged() ? colorStagedModify :
colorUnstaged);
+ treeItem.setForeground(
+ file.isStaged()
+ ? (useGrayVariants ? colorStagedModifyGray :
colorStagedModify)
+ : (useGrayVariants ? colorUnstagedGray : colorUnstaged));
break;
case DELETE:
- treeItem.setForeground(colorStagedUnchanged);
+ treeItem.setForeground(useGrayVariants ? colorStagedUnchangedGray :
colorStagedUnchanged);
}
}
if (ignoredFiles.containsKey(absolutePath)) {
- treeItem.setForeground(colorIgnored);
+ treeItem.setForeground(useGrayVariants ? colorIgnoredGray :
colorIgnored);
}
}
diff --git
a/plugins/tech/parquet/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/file/ParquetFileType.java
b/plugins/tech/parquet/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/file/ParquetFileType.java
index d56ee868c7..09f7cbe5e2 100644
---
a/plugins/tech/parquet/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/file/ParquetFileType.java
+++
b/plugins/tech/parquet/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/file/ParquetFileType.java
@@ -125,4 +125,9 @@ public class ParquetFileType implements IHopFileType {
public String getFileTypeImage() {
return getClass().getAnnotation(HopFileTypePlugin.class).image();
}
+
+ @Override
+ public boolean supportsOpening() {
+ return false;
+ }
}
diff --git
a/plugins/transforms/excel/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/file/ExcelFileType.java
b/plugins/transforms/excel/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/file/ExcelFileType.java
index ddca3764cb..cf0c617caa 100644
---
a/plugins/transforms/excel/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/file/ExcelFileType.java
+++
b/plugins/transforms/excel/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/file/ExcelFileType.java
@@ -125,4 +125,9 @@ public class ExcelFileType implements IHopFileType {
public String getFileTypeImage() {
return getClass().getAnnotation(HopFileTypePlugin.class).image();
}
+
+ @Override
+ public boolean supportsOpening() {
+ return false;
+ }
}
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
index 2d952e800b..019d416e6a 100644
---
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
@@ -21,23 +21,20 @@ 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;
+import
org.apache.hop.ui.hopgui.perspective.explorer.file.types.raw.RawExplorerFileType;
@GuiPlugin
public class HtmlOpenAsTextPlugin {
@GuiMenuElement(
root = ExplorerPerspective.GUI_PLUGIN_CONTEXT_MENU_PARENT_ID,
- id = "ExplorerPerspective-Html-OpenAsText",
+ id = ExplorerPerspective.CONTEXT_MENU_OPEN_AS_TEXT,
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)
+ parentId = ExplorerPerspective.GUI_PLUGIN_CONTEXT_MENU_PARENT_ID)
public void openAsText() {
ExplorerPerspective perspective = ExplorerPerspective.getInstance();
ExplorerFile explorerFile = perspective.getSelectedFile();
@@ -47,38 +44,23 @@ public class HtmlOpenAsTextPlugin {
}
String filename = explorerFile.getFilename();
- if (filename == null || !filename.toLowerCase().endsWith(".html")) {
+ if (filename == null) {
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);
+ if (HopVfs.getFileObject(filename).isFolder()) {
+ return;
}
+ } catch (Exception e) {
+ HopGui.getInstance().getLog().logError("Error resolving selected item",
e);
+ return;
+ }
+
+ try {
+ HopGui hopGui = HopGui.getInstance();
+ RawExplorerFileType rawFileType = new RawExplorerFileType();
+ rawFileType.openFile(hopGui, filename, hopGui.getVariables());
} catch (Exception e) {
HopGui.getInstance().getLog().logError("Error opening file as text", e);
}
diff --git a/ui/src/main/java/org/apache/hop/ui/core/PropsUi.java
b/ui/src/main/java/org/apache/hop/ui/core/PropsUi.java
index 0a352e0726..1288fd77b2 100644
--- a/ui/src/main/java/org/apache/hop/ui/core/PropsUi.java
+++ b/ui/src/main/java/org/apache/hop/ui/core/PropsUi.java
@@ -1054,6 +1054,13 @@ public class PropsUi extends Props {
contrastingColors.put(new RGB(100, 100, 100), new RGB(215, 215, 215));
contrastingColors.put(new RGB(50, 50, 50), new RGB(235, 235, 235));
+ // Muted color variants for explorer (non-openable items); dark mode =
lighter on dark bg
+ contrastingColors.put(new RGB(85, 115, 85), new RGB(120, 155, 120)); //
DarkGreenMuted
+ contrastingColors.put(new RGB(75, 95, 165), new RGB(130, 150, 215)); //
LightBlueMuted
+ contrastingColors.put(new RGB(130, 85, 85), new RGB(175, 120, 120)); //
RedMuted
+ contrastingColors.put(new RGB(105, 105, 105), new RGB(145, 145, 145)); //
DarkGrayMuted
+ contrastingColors.put(new RGB(90, 90, 90), new RGB(135, 135, 135)); //
BlackMuted
+
// Add all the inverse color mappings as well
//
Map<RGB, RGB> inverse = new HashMap<>();
diff --git a/ui/src/main/java/org/apache/hop/ui/core/gui/GuiMenuWidgets.java
b/ui/src/main/java/org/apache/hop/ui/core/gui/GuiMenuWidgets.java
index 4b7581a8a7..17cdd43d43 100644
--- a/ui/src/main/java/org/apache/hop/ui/core/gui/GuiMenuWidgets.java
+++ b/ui/src/main/java/org/apache/hop/ui/core/gui/GuiMenuWidgets.java
@@ -31,6 +31,7 @@ import org.apache.hop.core.gui.plugin.menu.GuiMenuItem;
import org.apache.hop.core.logging.LogChannel;
import org.apache.hop.ui.core.ConstUi;
import org.apache.hop.ui.hopgui.file.IHopFileType;
+import org.apache.hop.ui.hopgui.file.IHopFileTypeHandler;
import org.apache.hop.ui.util.EnvironmentUtils;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Menu;
@@ -320,7 +321,12 @@ public class GuiMenuWidgets extends BaseGuiWidgets {
* @return The menu item or null if nothing is found
*/
public MenuItem enableMenuItem(IHopFileType fileType, String id, String
permission) {
- return enableMenuItem(fileType, id, permission, true);
+ return enableMenuItem(fileType, null, id, permission, true);
+ }
+
+ public MenuItem enableMenuItem(
+ IHopFileType fileType, IHopFileTypeHandler handler, String id, String
permission) {
+ return enableMenuItem(fileType, handler, id, permission, true);
}
/**
@@ -335,9 +341,23 @@ public class GuiMenuWidgets extends BaseGuiWidgets {
*/
public MenuItem enableMenuItem(
IHopFileType fileType, String id, String permission, boolean active) {
- MenuItem menuItem = menuItemMap.get(id);
+ return enableMenuItem(fileType, null, id, permission, active);
+ }
- boolean hasCapability = fileType.hasCapability(permission);
+ /**
+ * Enable or disable menu item based on capability. When handler is
non-null, uses handler's
+ * hasCapability (so handlers can disable e.g. Save for binary raw view);
otherwise uses file
+ * type.
+ */
+ public MenuItem enableMenuItem(
+ IHopFileType fileType,
+ IHopFileTypeHandler handler,
+ String id,
+ String permission,
+ boolean active) {
+ MenuItem menuItem = menuItemMap.get(id);
+ boolean hasCapability =
+ handler != null ? handler.hasCapability(permission) :
fileType.hasCapability(permission);
boolean enable = hasCapability && active;
if (menuItem != null && enable != menuItem.isEnabled()) {
menuItem.setEnabled(enable);
diff --git a/ui/src/main/java/org/apache/hop/ui/core/gui/GuiResource.java
b/ui/src/main/java/org/apache/hop/ui/core/gui/GuiResource.java
index 219c31c70c..188dddd6fc 100644
--- a/ui/src/main/java/org/apache/hop/ui/core/gui/GuiResource.java
+++ b/ui/src/main/java/org/apache/hop/ui/core/gui/GuiResource.java
@@ -107,6 +107,14 @@ public class GuiResource {
@Getter private Color colorHopTrue;
@Getter private Color colorDeprecated;
+ /** Muted variants for use on grayed (non-openable) explorer items; light
and dark mode aware. */
+ @Getter private Color colorDarkGreenMuted;
+
+ @Getter private Color colorLightBlueMuted;
+ @Getter private Color colorRedMuted;
+ @Getter private Color colorDarkGrayMuted;
+ @Getter private Color colorBlackMuted;
+
// Fonts
//
private ManagedFont fontDefault;
@@ -384,6 +392,13 @@ public class GuiResource {
colorHopTrue = new Color(display, props.contrastColor(12, 178, 15));
colorDeprecated = new Color(display, props.contrastColor(246, 196, 56));
+ // Muted variants for grayed (non-openable) explorer items; dark mode
variants in PropsUi
+ colorDarkGreenMuted = new Color(display, props.contrastColor(new RGB(85,
115, 85)));
+ colorLightBlueMuted = new Color(display, props.contrastColor(new RGB(75,
95, 165)));
+ colorRedMuted = new Color(display, props.contrastColor(new RGB(130, 85,
85)));
+ colorDarkGrayMuted = new Color(display, props.contrastColor(new RGB(105,
105, 105)));
+ colorBlackMuted = new Color(display, props.contrastColor(new RGB(90, 90,
90)));
+
// Load all images from files...
loadFonts();
loadCommonImages();
diff --git a/ui/src/main/java/org/apache/hop/ui/core/gui/GuiToolbarWidgets.java
b/ui/src/main/java/org/apache/hop/ui/core/gui/GuiToolbarWidgets.java
index 587978fce6..cde9d9150d 100644
--- a/ui/src/main/java/org/apache/hop/ui/core/gui/GuiToolbarWidgets.java
+++ b/ui/src/main/java/org/apache/hop/ui/core/gui/GuiToolbarWidgets.java
@@ -42,6 +42,7 @@ import org.apache.hop.ui.core.widget.svg.SvgLabelListener;
import org.apache.hop.ui.hopgui.TextSizeUtilFacade;
import org.apache.hop.ui.hopgui.ToolbarFacade;
import org.apache.hop.ui.hopgui.file.IHopFileType;
+import org.apache.hop.ui.hopgui.file.IHopFileTypeHandler;
import org.apache.hop.ui.util.EnvironmentUtils;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.CLabel;
@@ -702,7 +703,12 @@ public class GuiToolbarWidgets extends BaseGuiWidgets
implements IToolbarWidgetR
* @return The toolbar item or null if nothing is found
*/
public ToolItem enableToolbarItem(IHopFileType fileType, String id, String
permission) {
- return enableToolbarItem(fileType, id, permission, true);
+ return enableToolbarItem(fileType, null, id, permission, true);
+ }
+
+ public ToolItem enableToolbarItem(
+ IHopFileType fileType, IHopFileTypeHandler handler, String id, String
permission) {
+ return enableToolbarItem(fileType, handler, id, permission, true);
}
/**
@@ -717,8 +723,23 @@ public class GuiToolbarWidgets extends BaseGuiWidgets
implements IToolbarWidgetR
*/
public ToolItem enableToolbarItem(
IHopFileType fileType, String id, String permission, boolean active) {
+ return enableToolbarItem(fileType, null, id, permission, active);
+ }
+
+ /**
+ * Enable or disable toolbar item based on capability. When handler is
non-null, uses handler's
+ * hasCapability (so handlers can disable e.g. Save for binary raw view);
otherwise uses file
+ * type.
+ */
+ public ToolItem enableToolbarItem(
+ IHopFileType fileType,
+ IHopFileTypeHandler handler,
+ String id,
+ String permission,
+ boolean active) {
ToolItem item = findToolItem(id);
- boolean hasCapability = fileType.hasCapability(permission);
+ boolean hasCapability =
+ handler != null ? handler.hasCapability(permission) :
fileType.hasCapability(permission);
boolean enable = hasCapability && active;
if (item == null) {
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 7916529226..af27e96f05 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
@@ -1376,57 +1376,80 @@ public class HopGui
*/
public void handleFileCapabilities(
IHopFileType fileType, boolean changed, boolean running, boolean paused)
{
+ handleFileCapabilities(fileType, null, changed, running, paused);
+ }
+
+ /**
+ * Same as {@link #handleFileCapabilities(IHopFileType, boolean, boolean,
boolean)} but when
+ * handler is non-null, Save/SaveAs use the handler's capability (e.g.
disabled for binary raw
+ * view).
+ */
+ public void handleFileCapabilities(
+ IHopFileType fileType,
+ IHopFileTypeHandler handler,
+ boolean changed,
+ boolean running,
+ boolean paused) {
mainMenuWidgets.enableMenuItem(
- fileType, ID_MAIN_MENU_FILE_SAVE, IHopFileType.CAPABILITY_SAVE,
changed);
+ fileType, handler, ID_MAIN_MENU_FILE_SAVE,
IHopFileType.CAPABILITY_SAVE, changed);
mainMenuWidgets.enableMenuItem(
- fileType, ID_MAIN_MENU_FILE_SAVE_AS, IHopFileType.CAPABILITY_SAVE_AS);
+ fileType, handler, ID_MAIN_MENU_FILE_SAVE_AS,
IHopFileType.CAPABILITY_SAVE_AS);
mainMenuWidgets.enableMenuItem(
- fileType, ID_MAIN_MENU_FILE_EXPORT_TO_SVG,
IHopFileType.CAPABILITY_EXPORT_TO_SVG);
+ fileType, handler, ID_MAIN_MENU_FILE_EXPORT_TO_SVG,
IHopFileType.CAPABILITY_EXPORT_TO_SVG);
mainMenuWidgets.enableMenuItem(
- fileType, ID_MAIN_MENU_FILE_CLOSE, IHopFileType.CAPABILITY_CLOSE);
+ fileType, handler, ID_MAIN_MENU_FILE_CLOSE,
IHopFileType.CAPABILITY_CLOSE);
mainMenuWidgets.enableMenuItem(
- fileType, ID_MAIN_MENU_FILE_CLOSE_ALL, IHopFileType.CAPABILITY_CLOSE);
+ fileType, handler, ID_MAIN_MENU_FILE_CLOSE_ALL,
IHopFileType.CAPABILITY_CLOSE);
mainMenuWidgets.enableMenuItem(
- fileType, ID_MAIN_MENU_EDIT_SELECT_ALL,
IHopFileType.CAPABILITY_SELECT);
+ fileType, handler, ID_MAIN_MENU_EDIT_SELECT_ALL,
IHopFileType.CAPABILITY_SELECT);
mainMenuWidgets.enableMenuItem(
- fileType, ID_MAIN_MENU_EDIT_UNSELECT_ALL,
IHopFileType.CAPABILITY_SELECT);
+ fileType, handler, ID_MAIN_MENU_EDIT_UNSELECT_ALL,
IHopFileType.CAPABILITY_SELECT);
- mainMenuWidgets.enableMenuItem(fileType, ID_MAIN_MENU_EDIT_COPY,
IHopFileType.CAPABILITY_COPY);
mainMenuWidgets.enableMenuItem(
- fileType, ID_MAIN_MENU_EDIT_PASTE, IHopFileType.CAPABILITY_PASTE);
- mainMenuWidgets.enableMenuItem(fileType, ID_MAIN_MENU_EDIT_CUT,
IHopFileType.CAPABILITY_CUT);
+ fileType, handler, ID_MAIN_MENU_EDIT_COPY,
IHopFileType.CAPABILITY_COPY);
mainMenuWidgets.enableMenuItem(
- fileType, ID_MAIN_MENU_EDIT_DELETE, IHopFileType.CAPABILITY_DELETE);
+ fileType, handler, ID_MAIN_MENU_EDIT_PASTE,
IHopFileType.CAPABILITY_PASTE);
+ mainMenuWidgets.enableMenuItem(
+ fileType, handler, ID_MAIN_MENU_EDIT_CUT, IHopFileType.CAPABILITY_CUT);
+ mainMenuWidgets.enableMenuItem(
+ fileType, handler, ID_MAIN_MENU_EDIT_DELETE,
IHopFileType.CAPABILITY_DELETE);
mainMenuWidgets.enableMenuItem(
- fileType, ID_MAIN_MENU_RUN_START, IHopFileType.CAPABILITY_START,
!running);
+ fileType, handler, ID_MAIN_MENU_RUN_START,
IHopFileType.CAPABILITY_START, !running);
mainMenuWidgets.enableMenuItem(
- fileType, ID_MAIN_MENU_RUN_STOP, IHopFileType.CAPABILITY_STOP,
running);
+ fileType, handler, ID_MAIN_MENU_RUN_STOP,
IHopFileType.CAPABILITY_STOP, running);
+ mainMenuWidgets.enableMenuItem(
+ fileType,
+ handler,
+ ID_MAIN_MENU_RUN_PAUSE,
+ IHopFileType.CAPABILITY_PAUSE,
+ running && !paused);
mainMenuWidgets.enableMenuItem(
- fileType, ID_MAIN_MENU_RUN_PAUSE, IHopFileType.CAPABILITY_PAUSE,
running && !paused);
+ fileType, handler, ID_MAIN_MENU_RUN_RESUME,
IHopFileType.CAPABILITY_PAUSE, paused);
mainMenuWidgets.enableMenuItem(
- fileType, ID_MAIN_MENU_RUN_RESUME, IHopFileType.CAPABILITY_PAUSE,
paused);
+ fileType, handler, ID_MAIN_MENU_RUN_PREVIEW,
IHopFileType.CAPABILITY_PREVIEW);
mainMenuWidgets.enableMenuItem(
- fileType, ID_MAIN_MENU_RUN_PREVIEW, IHopFileType.CAPABILITY_PREVIEW);
- mainMenuWidgets.enableMenuItem(fileType, ID_MAIN_MENU_RUN_DEBUG,
IHopFileType.CAPABILITY_DEBUG);
+ fileType, handler, ID_MAIN_MENU_RUN_DEBUG,
IHopFileType.CAPABILITY_DEBUG);
mainMenuWidgets.enableMenuItem(
fileType,
+ handler,
ID_MAIN_MENU_EDIT_NAV_PREV,
IHopFileType.CAPABILITY_FILE_HISTORY,
getActivePerspective().hasNavigationPreviousFile());
mainMenuWidgets.enableMenuItem(
fileType,
+ handler,
ID_MAIN_MENU_EDIT_NAV_NEXT,
IHopFileType.CAPABILITY_FILE_HISTORY,
getActivePerspective().hasNavigationNextFile());
mainToolbarWidgets.enableToolbarItem(
- fileType, ID_MAIN_TOOLBAR_SAVE, IHopFileType.CAPABILITY_SAVE, changed);
+ fileType, handler, ID_MAIN_TOOLBAR_SAVE, IHopFileType.CAPABILITY_SAVE,
changed);
mainToolbarWidgets.enableToolbarItem(
- fileType, ID_MAIN_TOOLBAR_SAVE_AS, IHopFileType.CAPABILITY_SAVE_AS);
+ fileType, handler, ID_MAIN_TOOLBAR_SAVE_AS,
IHopFileType.CAPABILITY_SAVE_AS);
}
public IHopFileTypeHandler getActiveFileTypeHandler() {
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 80611e0814..94dd7ff987 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
@@ -121,10 +121,12 @@ public class HopGuiFileDelegate {
IHopFileTypeHandler fileTypeHandler = hopFile.openFile(hopGui, filename,
hopGui.getVariables());
if (fileTypeHandler != null) {
- hopGui.handleFileCapabilities(hopFile, fileTypeHandler.hasChanged(),
false, false);
+ hopGui.handleFileCapabilities(
+ hopFile, fileTypeHandler, fileTypeHandler.hasChanged(), false,
false);
if (EnvironmentUtils.getInstance().isWeb()) {
// Do it again to test
- hopGui.handleFileCapabilities(hopFile, fileTypeHandler.hasChanged(),
false, false);
+ hopGui.handleFileCapabilities(
+ hopFile, fileTypeHandler, fileTypeHandler.hasChanged(), false,
false);
}
// Also save the state of Hop GUI
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 be774fb1b8..c1c4c5002d 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
@@ -136,4 +136,14 @@ public interface IHopFileType {
* @return The path to the SVG file, a logo for this file type
*/
String getFileTypeImage();
+
+ /**
+ * Whether this file type supports opening (shows content in an editor).
When false, the Open
+ * action is disabled for files of this type. Folders are not affected.
+ *
+ * @return true if opening a file of this type shows an editor, false if it
would do nothing
+ */
+ default boolean supportsOpening() {
+ return true;
+ }
}
diff --git
a/ui/src/main/java/org/apache/hop/ui/hopgui/file/IHopFileTypeHandler.java
b/ui/src/main/java/org/apache/hop/ui/hopgui/file/IHopFileTypeHandler.java
index 1b3a6efd05..eb4acb1a80 100644
--- a/ui/src/main/java/org/apache/hop/ui/hopgui/file/IHopFileTypeHandler.java
+++ b/ui/src/main/java/org/apache/hop/ui/hopgui/file/IHopFileTypeHandler.java
@@ -163,4 +163,15 @@ public interface IHopFileTypeHandler extends
IActionContextHandlersProvider {
/** mark the file as deleted */
public default void markDeleted() {}
+
+ /**
+ * Whether this handler supports the given capability (e.g. Save). Defaults
to the file type's
+ * capability; handlers can override to disable per-instance (e.g. raw view
of a binary file).
+ *
+ * @param capability the capability constant from {@link IHopFileType}
+ * @return true if the capability is supported
+ */
+ default boolean hasCapability(String capability) {
+ return getFileType() != null && getFileType().hasCapability(capability);
+ }
}
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 f5da7370e6..e89b3c45c3 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
@@ -90,4 +90,9 @@ public class EmptyFileType implements IHopFileType {
public String getFileTypeImage() {
return "ui/images/file.svg";
}
+
+ @Override
+ public boolean supportsOpening() {
+ return false;
+ }
}
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
index 0eca5f8f81..a8109e4ff5 100644
---
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
@@ -128,6 +128,7 @@ import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Menu;
+import org.eclipse.swt.widgets.MenuItem;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;
import org.eclipse.swt.widgets.ToolBar;
@@ -177,6 +178,8 @@ public class ExplorerPerspective implements
IHopPerspective, TabClosable {
public static final String CONTEXT_MENU_COLLAPSE_ALL =
"ExplorerPerspective-ContextMenu-10070-CollapseAll";
public static final String CONTEXT_MENU_OPEN =
"ExplorerPerspective-ContextMenu-10100-Open";
+ public static final String CONTEXT_MENU_OPEN_AS_TEXT =
+ "ExplorerPerspective-ContextMenu-10101-OpenAsText";
public static final String CONTEXT_MENU_RENAME =
"ExplorerPerspective-ContextMenu-10300-Rename";
public static final String CONTEXT_MENU_COPY_NAME =
"ExplorerPerspective-ContextMenu-10400-CopyName";
@@ -511,7 +514,17 @@ public class ExplorerPerspective implements
IHopPerspective, TabClosable {
}
TreeItem[] selection = tree.getSelection();
-
menuWidgets.findMenuItem(CONTEXT_MENU_OPEN).setEnabled(selection.length == 1);
+ TreeItemFolder tif =
+ selection.length == 1 ? (TreeItemFolder) selection[0].getData()
: null;
+ boolean openSupported = tif != null && (tif.folder ||
tif.fileType.supportsOpening());
+ MenuItem openItem = menuWidgets.findMenuItem(CONTEXT_MENU_OPEN);
+ if (openItem != null) {
+ openItem.setEnabled(openSupported);
+ }
+ MenuItem openAsTextItem =
menuWidgets.findMenuItem(CONTEXT_MENU_OPEN_AS_TEXT);
+ if (openAsTextItem != null) {
+ openAsTextItem.setEnabled(selection.length == 1 && tif != null &&
!tif.folder);
+ }
menuWidgets.findMenuItem(CONTEXT_MENU_RENAME).setEnabled(selection.length == 1);
// Show the menu
@@ -1559,7 +1572,11 @@ public class ExplorerPerspective implements
IHopPerspective, TabClosable {
HopGui.getInstance()
.handleFileCapabilities(
- fileTypeHandler.getFileType(), fileTypeHandler.hasChanged(),
false, false);
+ fileTypeHandler.getFileType(),
+ fileTypeHandler,
+ fileTypeHandler.hasChanged(),
+ false,
+ false);
}
}
}
@@ -1955,6 +1972,15 @@ public class ExplorerPerspective implements
IHopPerspective, TabClosable {
TreeItem childItem = new TreeItem(item, SWT.NONE);
childItem.setText(childName);
setItemImage(childItem, fileType);
+
+ // Apply gray for non-openable files before paint listeners so
listeners (e.g. git) can
+ // use gray variants when they see this styling
+ if (!folder && !fileType.supportsOpening()) {
+
childItem.setForeground(hopGui.getDisplay().getSystemColor(SWT.COLOR_DARK_GRAY));
+ } else {
+ childItem.setForeground(null);
+ }
+
callPaintListeners(tree, childItem, childPath, childName);
setTreeItemData(childItem, childPath, childName, fileType, depth,
folder, true);
@@ -2355,14 +2381,15 @@ public class ExplorerPerspective implements
IHopPerspective, TabClosable {
}
boolean isFolderSelected = tif != null && tif.fileType instanceof
FolderFileType;
+ boolean openSupported = tif != null && (tif.folder ||
tif.fileType.supportsOpening());
toolBarWidgets.enableToolbarItem(TOOLBAR_ITEM_CREATE_FOLDER,
isFolderSelected);
- toolBarWidgets.enableToolbarItem(TOOLBAR_ITEM_OPEN, tif != null);
+ toolBarWidgets.enableToolbarItem(TOOLBAR_ITEM_OPEN, openSupported);
toolBarWidgets.enableToolbarItem(TOOLBAR_ITEM_DELETE, tif != null);
toolBarWidgets.enableToolbarItem(TOOLBAR_ITEM_RENAME, tif != null);
menuWidgets.enableMenuItem(CONTEXT_MENU_CREATE_FOLDER, isFolderSelected);
- menuWidgets.enableMenuItem(CONTEXT_MENU_OPEN, tif != null);
+ menuWidgets.enableMenuItem(CONTEXT_MENU_OPEN, openSupported);
menuWidgets.enableMenuItem(CONTEXT_MENU_DELETE, tif != null);
menuWidgets.enableMenuItem(CONTEXT_MENU_RENAME, tif != null);
menuWidgets.enableMenuItem(CONTEXT_MENU_COPY_NAME, tif != null);
diff --git
a/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/file/types/ArchiveFileType.java
b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/file/types/ArchiveFileType.java
index a9dcc0d560..07a184728f 100644
---
a/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/file/types/ArchiveFileType.java
+++
b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/file/types/ArchiveFileType.java
@@ -125,4 +125,9 @@ public class ArchiveFileType implements IHopFileType {
public String getFileTypeImage() {
return getClass().getAnnotation(HopFileTypePlugin.class).image();
}
+
+ @Override
+ public boolean supportsOpening() {
+ return false;
+ }
}
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
index bc1006d3d9..549b66c81d 100644
---
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
@@ -111,4 +111,9 @@ public class FolderFileType implements IHopFileType {
public String getFileTypeImage() {
return getClass().getAnnotation(HopFileTypePlugin.class).image();
}
+
+ @Override
+ public boolean supportsOpening() {
+ return false;
+ }
}
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
index 95b574d91a..2c7c005129 100644
---
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
@@ -107,4 +107,9 @@ public class GenericFileType implements IHopFileType {
public String getFileTypeImage() {
return "ui/images/file.svg";
}
+
+ @Override
+ public boolean supportsOpening() {
+ return false;
+ }
}
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
index fc3b1a406d..5c23fcd164 100644
---
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
@@ -169,7 +169,8 @@ public abstract class BaseExplorerFileTypeHandler
implements IExplorerFileTypeHa
.getDisplay()
.asyncExec(
() -> {
- hopGui.handleFileCapabilities(this.getFileType(),
this.hasChanged(), false, false);
+ hopGui.handleFileCapabilities(
+ this.getFileType(), this, this.hasChanged(), false, false);
perspective.updateTabItem(this);
perspective.updateTreeItem(this);
});
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/raw/RawExplorerFileType.java
similarity index 51%
copy from
ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/file/types/GenericFileType.java
copy to
ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/file/types/raw/RawExplorerFileType.java
index 95b574d91a..bb0090cbcb 100644
---
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/raw/RawExplorerFileType.java
@@ -13,61 +13,57 @@
* 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;
+package org.apache.hop.ui.hopgui.perspective.explorer.file.types.raw;
-import java.util.Collections;
-import java.util.List;
import java.util.Properties;
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 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.text.BaseTextExplorerFileType;
-// 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 "";
- }
+/**
+ * File type for viewing any file as raw text in the explorer. When content
looks like text, save is
+ * enabled; when binary (null byte in sample), view is read-only and save is
disabled. Never claims
+ * a file by extension ({@link #isHandledBy} returns false), so it is only
used when explicitly
+ * opening as raw (e.g. via "Open as text" context menu).
+ */
+public class RawExplorerFileType extends
BaseTextExplorerFileType<RawExplorerFileTypeHandler> {
- @Override
- public String[] getFilterExtensions() {
- return new String[0];
- }
+ private static final Properties CAPABILITIES =
+ FileTypeCapabilities.getCapabilities(
+ IHopFileType.CAPABILITY_SAVE,
+ IHopFileType.CAPABILITY_SAVE_AS,
+ IHopFileType.CAPABILITY_CLOSE,
+ IHopFileType.CAPABILITY_FILE_HISTORY,
+ IHopFileType.CAPABILITY_COPY,
+ IHopFileType.CAPABILITY_SELECT);
- @Override
- public String[] getFilterNames() {
- return new String[0];
+ public RawExplorerFileType() {
+ super("Raw File", "", new String[0], new String[0], CAPABILITIES);
}
@Override
- public Properties getCapabilities() {
- return new Properties();
+ public boolean isHandledBy(String filename, boolean checkContent) throws
HopException {
+ return false;
}
@Override
- public boolean hasCapability(String capability) {
- return false;
+ public String getFileTypeImage() {
+ return "ui/images/file.svg";
}
@Override
- public IHopFileTypeHandler openFile(
- HopGui hopGui, String filename, IVariables parentVariableSpace) throws
HopException {
- return new EmptyHopFileTypeHandler();
+ public RawExplorerFileTypeHandler createFileTypeHandler(
+ HopGui hopGui, ExplorerPerspective perspective, ExplorerFile file) {
+ return new RawExplorerFileTypeHandler(hopGui, perspective, file);
}
@Override
@@ -75,36 +71,4 @@ public class GenericFileType implements IHopFileType {
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/text/BaseTextExplorerFileTypeHandler.java
b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/file/types/raw/RawExplorerFileTypeHandler.java
similarity index 57%
copy from
ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/file/types/text/BaseTextExplorerFileTypeHandler.java
copy to
ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/file/types/raw/RawExplorerFileTypeHandler.java
index b3497659b0..c446fee551 100644
---
a/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/file/types/text/BaseTextExplorerFileTypeHandler.java
+++
b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/file/types/raw/RawExplorerFileTypeHandler.java
@@ -15,44 +15,54 @@
* limitations under the License.
*/
-package org.apache.hop.ui.hopgui.perspective.explorer.file.types.text;
+package org.apache.hop.ui.hopgui.perspective.explorer.file.types.raw;
-import java.io.OutputStream;
+import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import org.apache.commons.vfs2.FileObject;
import org.apache.hop.core.Const;
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.util.BinaryDetectionUtil;
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.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.types.base.BaseExplorerFileTypeHandler;
+import
org.apache.hop.ui.hopgui.perspective.explorer.file.types.text.BaseTextExplorerFileTypeHandler;
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;
-/** This handles a text file in the file explorer perspective: open, save, ...
*/
-public class BaseTextExplorerFileTypeHandler extends
BaseExplorerFileTypeHandler {
+/**
+ * Handler for viewing any file as raw text. When content looks like text (no
null byte in first
+ * 8KB), the file is editable and save/save-as are enabled; when binary, view
is read-only and save
+ * buttons are disabled.
+ */
+public class RawExplorerFileTypeHandler extends
BaseTextExplorerFileTypeHandler {
private Text wText;
- boolean reloadListener = false;
- public BaseTextExplorerFileTypeHandler(
+ /** True if file was detected as binary (null byte in sample); save is
disabled. */
+ private boolean binary;
+
+ public RawExplorerFileTypeHandler(
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);
+ binary = detectBinary();
+ int style = SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL;
+ if (binary) {
+ style |= SWT.READ_ONLY;
+ }
+ wText = new Text(composite, style);
PropsUi.setLook(wText, Props.WIDGET_STYLE_FIXED);
FormData fdText = new FormData();
fdText.left = new FormAttachment(0, 0);
@@ -61,48 +71,63 @@ public class BaseTextExplorerFileTypeHandler extends
BaseExplorerFileTypeHandler
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 various transform plugins
- // TODO: option to discard changes (reload from disk)
- // TODO: find in file feature, hook it up to the project find function
- //
+ if (!binary) {
+ wText.addModifyListener(
+ e -> {
+ if (reloadListener) {
+ this.setChanged();
+ perspective.updateGui();
+ }
+ });
+ }
+ reloadListener = false;
reload();
- // If the widget changes after this it's been changed by the user
- //
- wText.addModifyListener(
- e -> {
- if (reloadListener) {
- this.setChanged();
- perspective.updateGui();
- }
- });
+ reloadListener = !binary;
+ }
+
+ private boolean detectBinary() {
+ try {
+ FileObject file = HopVfs.getFileObject(getFilename(), getVariables());
+ if (!file.exists() || !file.isFile()) {
+ return false;
+ }
+ try (InputStream in = HopVfs.getInputStream(file)) {
+ return !BinaryDetectionUtil.looksLikeText(in);
+ }
+ } catch (Exception e) {
+ LogChannel.UI.logBasic(
+ getClass().getSimpleName(),
+ "Error sampling file for binary detection, treating as text",
+ e);
+ return false;
+ }
}
@Override
- public void save() throws HopException {
+ public boolean hasCapability(String capability) {
+ if (binary
+ && (IHopFileType.CAPABILITY_SAVE.equals(capability)
+ || IHopFileType.CAPABILITY_SAVE_AS.equals(capability))) {
+ return false;
+ }
+ return super.hasCapability(capability);
+ }
+ @Override
+ public void save() throws HopException {
+ if (binary) {
+ throw new HopException("Binary file cannot be saved as text.");
+ }
try {
- // Save the current explorer file ....
- //
String filename = explorerFile.getFilename();
-
boolean fileExist = HopVfs.fileExists(filename);
-
- // Save the file...
- //
- try (OutputStream outputStream = HopVfs.getOutputStream(filename,
false)) {
+ try (java.io.OutputStream outputStream =
HopVfs.getOutputStream(filename, false)) {
outputStream.write(wText.getText().getBytes(StandardCharsets.UTF_8));
outputStream.flush();
}
-
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();
}
@@ -113,50 +138,31 @@ public class BaseTextExplorerFileTypeHandler extends
BaseExplorerFileTypeHandler
@Override
public void saveAs(String filename) throws HopException {
- try {
-
- // Enforce file extension
- if
(!filename.toLowerCase().endsWith(this.getFileType().getDefaultFileExtension()))
{
- filename = filename + this.getFileType().getDefaultFileExtension();
- }
-
- // 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);
+ if (binary) {
+ throw new HopException("Binary file cannot be saved as text.");
}
+ filename = HopVfs.normalize(filename);
+ setFilename(filename);
+ save();
+ hopGui.fileRefreshDelegate.register(filename, this);
+ }
+
+ @Override
+ public boolean hasChanged() {
+ return !binary && super.hasChanged();
}
@Override
public void reload() {
try {
- // Disable the Modifylistener temporary
reloadListener = false;
String contents = readTextFileContent("UTF-8");
wText.setText(Const.NVL(contents, ""));
-
- // enable the Modifylistener temporary
- reloadListener = true;
} catch (Exception e) {
- LogChannel.UI.logError(
+ LogChannel.UI.logBasic(
"Error reading contents of file '" + explorerFile.getFilename() +
"'", e);
+ } finally {
+ reloadListener = !binary;
}
}
diff --git
a/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/file/types/text/BaseTextExplorerFileTypeHandler.java
b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/file/types/text/BaseTextExplorerFileTypeHandler.java
index b3497659b0..c0183deef5 100644
---
a/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/file/types/text/BaseTextExplorerFileTypeHandler.java
+++
b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/file/types/text/BaseTextExplorerFileTypeHandler.java
@@ -41,7 +41,7 @@ import org.eclipse.swt.widgets.Text;
public class BaseTextExplorerFileTypeHandler extends
BaseExplorerFileTypeHandler {
private Text wText;
- boolean reloadListener = false;
+ protected boolean reloadListener = false;
public BaseTextExplorerFileTypeHandler(
HopGui hopGui, ExplorerPerspective perspective, ExplorerFile
explorerFile) {