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 76ee87bd3d issue #7456: TextComposite toolbar with find/replace and
plugin SPI (#7907)
76ee87bd3d is described below
commit 76ee87bd3dd155e4dd93016deab5d76c4297fe8c
Author: Matt Casters <[email protected]>
AuthorDate: Wed Aug 12 16:37:28 2026 +0200
issue #7456: TextComposite toolbar with find/replace and plugin SPI (#7907)
* issue #7456: TextComposite toolbar with find/replace and plugin SPI
Add an extensible toolbar on multi-line text editors (SQL, scripts, JSON,
logs, and so on) with built-in edit actions and find/replace, a styleType
constructor parameter for GuiToolbarElementFilter, developer documentation,
and a sample JSON pretty-print toolbar contribution.
* issue #7456: apply spotless formatting
* issue #7456: fix Javadoc link to Lombok-generated getStyleType
---
core/src/main/java/org/apache/hop/core/Props.java | 1 +
docs/hop-dev-manual/modules/ROOT/nav.adoc | 1 +
.../modules/ROOT/pages/gui-plugins-toolbars.adoc | 451 +++++++++++++++++++++
.../ROOT/pages/hopweb/hopweb-antipatterns.adoc | 3 +
docs/hop-dev-manual/modules/ROOT/pages/index.adoc | 1 +
.../modules/ROOT/pages/plugin-development.adoc | 3 +
.../workflow/actions/eval/ActionEvalDialog.java | 3 +-
.../ActionEvalTableContentDialog.java | 6 +-
.../ActionSendNagiosPassiveCheckDialog.java | 4 +-
.../actions/snmptrap/ActionSNMPTrapDialog.java | 6 +-
.../hop/workflow/actions/sql/ActionSqlDialog.java | 5 +-
.../actions/waitforsql/ActionWaitForSqlDialog.java | 3 +-
.../java/org/apache/hop/git/GitPerspective.java | 1 +
.../apache/hop/git/info/DiffStyledTextComp.java | 2 +-
.../git/info/GitInfoExplorerFileTypeHandler.java | 1 +
.../cassandrainput/CassandraInputDialog.java | 6 +-
.../mongodbdelete/MongoDbDeleteDialog.java | 4 +-
.../mongodbinput/MongoDbInputDialog.java | 6 +-
.../hop/workflow/actions/mql/ActionMqlDialog.java | 6 +-
.../salesforceinput/SalesforceInputDialog.java | 7 +-
.../databasejoin/DatabaseJoinDialog.java | 5 +-
.../transforms/drools/RulesAccumulatorDialog.java | 6 +-
.../transforms/drools/RulesExecutorDialog.java | 6 +-
.../dynamicsqlrow/DynamicSqlRowDialog.java | 3 +-
.../transforms/formula/editor/FormulaEditor.java | 6 +-
.../transforms/janino/editor/FormulaEditor.java | 6 +-
.../transforms/javafilter/JavaFilterDialog.java | 4 +-
.../UserDefinedJavaClassDialog.java | 3 +-
.../transforms/javascript/ScriptValuesDialog.java | 3 +-
.../ui/TextCompositeToolbarJsonFormatButton.java | 115 ++++++
.../ui/messages/messages_en_US.properties | 19 +
.../transforms/ldapinput/LdapInputDialog.java | 4 +-
.../transforms/regexeval/RegexEvalDialog.java | 4 +-
.../regexeval/RegexEvalHelperDialog.java | 6 +-
.../pipeline/transforms/script/ScriptDialog.java | 3 +-
.../hop/pipeline/transforms/sql/ExecSqlDialog.java | 5 +-
.../hop/pipeline/transforms/ssh/SshDialog.java | 6 +-
.../transforms/tableinput/TableInputDialog.java | 5 +-
.../transforms/writetolog/WriteToLogDialog.java | 6 +-
.../main/java/org/apache/hop/ui/core/PropsUi.java | 9 +
.../hop/ui/core/database/dialog/SqlEditor.java | 5 +-
.../hop/ui/core/dialog/FindReplaceDialog.java | 335 +++++++++++++++
.../ui/core/widget/JavaScriptStyledTextComp.java | 2 +-
.../hop/ui/core/widget/JavaStyledTextComp.java | 2 +-
.../hop/ui/core/widget/SQLStyledTextComp.java | 2 +-
.../hop/ui/core/widget/ScriptStyledTextComp.java | 2 +-
.../apache/hop/ui/core/widget/StyledTextComp.java | 90 +++-
.../apache/hop/ui/core/widget/StyledTextVar.java | 97 ++++-
.../apache/hop/ui/core/widget/TextComposite.java | 416 ++++++++++++++++++-
.../apache/hop/ui/core/widget/TextFindSupport.java | 106 +++++
.../apache/hop/ui/hopgui/dialog/NotePadDialog.java | 5 +-
.../delegates/HopGuiPipelineLogDelegate.java | 6 +-
.../delegates/HopGuiWorkflowLogDelegate.java | 6 +-
.../configuration/tabs/ConfigGuiOptionsTab.java | 17 +
.../perspective/execution/ExecutionLogPanel.java | 6 +-
.../core/dialog/messages/messages_en_US.properties | 14 +
.../core/widget/messages/messages_en_US.properties | 10 +
.../hop/ui/core/widget/TextFindSupportTest.java | 64 +++
58 files changed, 1856 insertions(+), 73 deletions(-)
diff --git a/core/src/main/java/org/apache/hop/core/Props.java
b/core/src/main/java/org/apache/hop/core/Props.java
index 1e3a780695..c9888f518e 100644
--- a/core/src/main/java/org/apache/hop/core/Props.java
+++ b/core/src/main/java/org/apache/hop/core/Props.java
@@ -78,6 +78,7 @@ public class Props implements Cloneable {
public static final String STRING_HIDE_VIEWPORT = "HideViewport";
public static final String STRING_SHOW_TABLE_VIEW_TOOLBAR =
"ShowTableViewToolbar";
+ public static final String STRING_SHOW_TEXT_COMPOSITE_TOOLBAR =
"ShowTextCompositeToolbar";
public static final String STRING_SHOW_EXIT_WARNING = "ShowExitWarning";
public static final String STRING_CUSTOM_PARAMETER = "CustomParameter";
diff --git a/docs/hop-dev-manual/modules/ROOT/nav.adoc
b/docs/hop-dev-manual/modules/ROOT/nav.adoc
index 4ebe410d66..9dc7cd1497 100644
--- a/docs/hop-dev-manual/modules/ROOT/nav.adoc
+++ b/docs/hop-dev-manual/modules/ROOT/nav.adoc
@@ -25,6 +25,7 @@ under the License.
* xref:integration-testing.adoc[Integration testing]
* xref:internationalisation.adoc[Internationalisation (i18n)]
* xref:plugin-development.adoc[Plugins Development]
+* xref:gui-plugins-toolbars.adoc[GUI plugins and toolbars]
* xref:marketplace-publish-plugin.adoc[Publishing a plugin to Nexus
(marketplace)]
* xref:engine-compatibility.adoc[Engine compatibility]
* xref:lineage.adoc[Lineage observation hub]
diff --git a/docs/hop-dev-manual/modules/ROOT/pages/gui-plugins-toolbars.adoc
b/docs/hop-dev-manual/modules/ROOT/pages/gui-plugins-toolbars.adoc
new file mode 100644
index 0000000000..ad857d76c8
--- /dev/null
+++ b/docs/hop-dev-manual/modules/ROOT/pages/gui-plugins-toolbars.adoc
@@ -0,0 +1,451 @@
+////
+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.
+////
+:description: How Hop GUI toolbars work, how to contribute buttons with
@GuiPlugin and @GuiToolbarElement, how to use GuiToolbarElementFilter, and how
TextComposite / TableView expose extensible toolbars to plugins.
+[[GuiPluginsToolbars-GuiPluginsAndToolbars]]
+= GUI plugins and toolbars
+
+Hop has a lightweight system for contributing UI actions from plugins without
hard-coding every button into the core GUI.
+This page explains how that system works for **toolbars** — the main way
plugins add icons above tables, multi-line text editors, and other widgets.
+
+You will learn:
+
+* How `@GuiPlugin` classes are discovered
+* How `@GuiToolbarElement` adds a toolbar button
+* How listeners receive the host widget (`TableView`, `TextComposite`, …)
+* How `@GuiToolbarElementFilter` shows or hides buttons (easy to miss)
+* How the **TextComposite** and **TableView** toolbars are designed for
extension (SQL formatters, JSON pretty-print, export, lineage, …)
+
+Related reading: xref:plugin-development.adoc[Plugin development],
xref:hopweb/hopweb-antipatterns.adoc[Hop Web antipatterns].
+
+== Mental model
+
+[source]
+----
+@GuiPlugin class
+ └── @GuiToolbarElement(root = "Some-Toolbar-Id", id = "...", image = "...")
+ └── static or instance method → invoked when the user clicks
+ └── @GuiToolbarElementFilter(parentId = "Some-Toolbar-Id") (optional)
+ └── static boolean method(String itemId, Object host) → show/hide
+----
+
+1. A **host** widget (for example `TextComposite` or `TableView`) creates a
toolbar with a fixed **root id** (`ID_TOOLBAR`).
+2. At startup, Hop scans every `@GuiPlugin` and registers methods annotated
with `@GuiToolbarElement` under that root id.
+3. When the host builds the toolbar, `GuiToolbarWidgets` loads all items for
that root, applies filters, and wires click listeners.
+4. On click, Hop prefers a **public static** method that takes the host object
as its single argument (the pattern plugins should use).
+
+== @GuiPlugin discovery
+
+Classes annotated with `@GuiPlugin` are registered as the `GuiPluginType`
plugin type.
+At GUI startup, `HopGuiEnvironment.initGuiPlugins()` reflects over every such
class and records:
+
+* `@GuiToolbarElement` methods → toolbar items
+* `@GuiToolbarElementFilter` methods → show/hide rules for a toolbar root
+* Also: menu elements, keyboard shortcuts, tabs, context actions, and similar
GUI contributions
+
+[source,java]
+----
+@GuiPlugin
+public class MyToolbarContributions {
+ // static methods with @GuiToolbarElement / @GuiToolbarElementFilter
+}
+----
+
+Put the class on the plugin classpath (under `plugins/…`) so the plugin system
can load it.
+`hop-ui` is typically a `provided` dependency for transform/action plugins
that already ship dialogs.
+
+IMPORTANT: On Hop Web, reflection at registration time loads every type that
appears in **method and field signatures** of `@GuiPlugin` classes.
+Do **not** put desktop-only SWT types (for example
`org.eclipse.swt.custom.StyledText`) in those signatures.
+Use abstractions such as `TextComposite` instead.
+See the `GuiPluginWebCompatibilityTest` and
xref:hopweb/hopweb-antipatterns.adoc[Hop Web antipatterns].
+
+== Contributing a toolbar button: @GuiToolbarElement
+
+Annotation (package `org.apache.hop.core.gui.plugin.toolbar`):
+
+[cols="1,3",options="header"]
+|===
+| Attribute | Meaning
+
+| `root`
+| **Toolbar root id** this item belongs to (must match the host’s `ID_TOOLBAR`
/ parent id).
+
+| `id`
+| Unique item id (use a stable, namespaced string so other plugins can refer
to or filter it).
+
+| `image`
+| SVG path loaded via the plugin classloader (for example `json-input.svg` or
`ui/images/search.svg`).
+
+| `toolTip`
+| Tooltip; use `i18n::Key.Name` for translation via `BaseMessages` / resource
bundles.
+
+| `label`
+| Optional text label (buttons are usually icon-only).
+
+| `type`
+| Defaults to `BUTTON`; also supports combo, text, label, checkbox, and so on.
+
+| `separator`
+| If `true`, draws a separator before this item.
+
+| `extraWidth` / `alignRight` / …
+| Layout hints for non-button types.
+|===
+
+=== Preferred listener shape (plugins)
+
+Use a **public static** method with one parameter: the host widget type (or a
supertype / interface).
+
+[source,java]
+----
+@GuiToolbarElement(
+ root = TextComposite.ID_TOOLBAR,
+ id = "textcomposite-toolbar-20010-format-json",
+ toolTip = "i18n::MyPlugin.FormatJson.ToolTip",
+ separator = true,
+ image = "json-input.svg")
+public static void formatJson(TextComposite text) {
+ // use text.getText(), text.insert(...), text.getStyleType(), ...
+}
+----
+
+`GuiToolbarWidgets` / `BaseGuiWidgets` resolve listeners as follows:
+
+1. If a host object was registered on the toolbar, look for a **static**
method whose single parameter is assignable from that host’s class.
+2. Otherwise fall back to an instance method on a plugin singleton (no-arg, or
with `Event`).
+
+Static methods that take the host object are the reliable pattern for
**per-widget** toolbars (`TableView`, `TextComposite`), because many instances
exist at once.
+
+=== Built-in items on the host class
+
+Hosts such as `TableView` and `TextComposite` are themselves `@GuiPlugin`
classes.
+Their built-in actions are also `@GuiToolbarElement` methods (sometimes static
helpers on the abstract base).
+Plugin ids should use higher numeric ranges (for example `20000+`) so they
sort after core items.
+
+== Showing or hiding buttons: @GuiToolbarElementFilter
+
+This annotation is powerful and easy to overlook.
+It is the supported way to **conditionally show** toolbar items (for example
only for SQL editors, only for database metadata lines, only when editable).
+
+=== Contract
+
+[source,java]
+----
+@GuiToolbarElementFilter(parentId = TextComposite.ID_TOOLBAR)
+public static boolean isButtonShown(String itemId, Object guiPluginInstance) {
+ // return true → show the item; false → hide it
+}
+----
+
+[cols="1,3",options="header"]
+|===
+| Rule | Detail
+
+| Annotation
+| `@GuiToolbarElementFilter(parentId = "<toolbar-root-id>")` — `parentId` must
match the toolbar `root` / host `ID_TOOLBAR`.
+
+| Method shape
+| **Must** be `public static boolean methodName(String itemId, Object
guiPluginInstance)`.
+Hop looks up the method with reflection using exactly `String.class` and
`Object.class`.
+A signature of `(String, TextComposite)` will **not** be found.
+
+| `itemId`
+| The `@GuiToolbarElement.id` of the item currently being decided.
+
+| `guiPluginInstance`
+| The **host** object registered with the toolbar (for example the
`TextComposite` or `TableView` instance), typed as `Object` — cast carefully.
+
+| Return value
+| `true` = show, `false` = hide.
+
+| Multiple filters
+| All registered filters for that toolbar root are consulted.
+If **any** filter returns `false` for an item, the item is hidden.
+|===
+
+=== Critical pitfall: do not hide everyone else’s buttons
+
+Filters run for **every** toolbar item under that root, not only for your
button.
+If your filter returns `false` for ids it does not own, you will hide undo,
cut, find, and every other contribution.
+
+Always start with:
+
+[source,java]
+----
+if (!MY_BUTTON_ID.equals(itemId)) {
+ return true; // leave all other items alone
+}
+// then decide for MY_BUTTON_ID only
+----
+
+=== Critical pitfall: filters run mid-construction
+
+`GuiToolbarElementFilter` methods are invoked when the host builds its toolbar
— for `TextComposite` that is **inside the base constructor**, before
subclasses create the underlying `Text` / `StyledText` widget.
+
+Safe in a filter:
+
+* Values set **before** `addToolbar()` (for example constructor `styleType`)
+* Identity / type of the host object (`instanceof TextComposite`)
+* Other fields already initialized on the host
+
+**Not** safe in a filter (defer to the button action instead):
+
+* `isEditable()`, `getText()`, `getSelectionText()`, caret position, and
similar methods that touch the child text control
+* Anything that assumes the host’s full construction has finished
+
+[source,java]
+----
+// Filter: only what is available at construction time
+@GuiToolbarElementFilter(parentId = TextComposite.ID_TOOLBAR)
+public static boolean isButtonShown(String buttonId, Object guiPluginInstance)
{
+ if (!MY_ID.equals(buttonId)) {
+ return true;
+ }
+ if (!(guiPluginInstance instanceof TextComposite text)) {
+ return false;
+ }
+ return TextComposite.STYLE_TYPE_JSON.equalsIgnoreCase(text.getStyleType());
+}
+
+// Action: runtime checks (editability, content, …)
+@GuiToolbarElement(root = TextComposite.ID_TOOLBAR, id = MY_ID, ...)
+public static void formatJson(TextComposite text) {
+ if (text == null || text.isDisposed() || !text.isEditable()) {
+ return;
+ }
+ // ...
+}
+----
+
+=== Working example (JSON format on TextComposite)
+
+From the JSON transform plugin (`TextCompositeToolbarJsonFormatButton`):
+
+[source,java]
+----
+@GuiToolbarElementFilter(parentId = TextComposite.ID_TOOLBAR)
+public static boolean isButtonShown(String buttonId, Object guiPluginInstance)
{
+ if (!ID_TOOLBAR_FORMAT_JSON.equals(buttonId)) {
+ return true;
+ }
+ if (!(guiPluginInstance instanceof TextComposite textComposite)) {
+ return false;
+ }
+ // styleType only — isEditable() is checked in formatJson() at click time
+ return
TextComposite.STYLE_TYPE_JSON.equalsIgnoreCase(textComposite.getStyleType());
+}
+----
+
+Another core example: `MetaSelectionLineClearDbCacheToolbarItem` filters a
clear-cache button so it only appears for database metadata lines.
+
+== Extensible host: TextComposite toolbar
+
+`org.apache.hop.ui.core.widget.TextComposite` is the multi-line editor used
for SQL, scripts, logs, formulas, JSON, and more.
+It exposes:
+
+[cols="1,2",options="header"]
+|===
+| API | Purpose
+
+| `TextComposite.ID_TOOLBAR` (`"TextComposite-Toolbar"`)
+| Root id for `@GuiToolbarElement.root` / filter `parentId`.
+
+| Built-in items
+| Undo/redo, cut/copy/paste, select all, find, find/replace (when the global
“show text editor toolbar” option is on).
+
+| Constructor `styleType` / `getStyleType()`
+| Semantic content type for plugins (must be set in the constructor; see
below).
+
+| `selectAll()`, `insert()`, `getText()`, `setText()`, …
+| Safe editing API that works on desktop and Hop Web backends.
+|===
+
+=== styleType (do not use instanceof)
+
+On Hop Web, dialogs often replace specialized classes with a plain
`StyledTextComp`.
+So `instanceof SQLStyledTextComp` is **false** on Hop Web even when the user
is editing SQL.
+Always use `getStyleType()` when deciding plugin behavior.
+
+IMPORTANT: Toolbar filters run when the `TextComposite` is **constructed**
(during `addToolbar()`).
+Pass `styleType` as a **constructor argument** so filters see the correct
value.
+Calling `setStyleType(...)` after construction does **not** rebuild the
toolbar or re-run filters.
+
+[source,java]
+----
+// Correct: style type is known before the toolbar is built
+wSql =
+ EnvironmentUtils.getInstance().isWeb()
+ ? new StyledTextComp(variables, shell, SWT.MULTI | ...,
TextComposite.STYLE_TYPE_SQL)
+ : new SQLStyledTextComp(variables, shell, SWT.MULTI | ...); //
defaults to SQL
+----
+
+Known style type constants:
+
+[cols="1,2",options="header"]
+|===
+| Constant | Typical use
+
+| `STYLE_TYPE_GENERIC`
+| Default / unspecified
+
+| `STYLE_TYPE_SQL`
+| SQL scripts and queries
+
+| `STYLE_TYPE_JAVASCRIPT` / `STYLE_TYPE_JAVA` / `STYLE_TYPE_SCRIPT`
+| Scripting languages
+
+| `STYLE_TYPE_JSON`
+| JSON documents or query payloads
+
+| `STYLE_TYPE_LOG` / `STYLE_TYPE_DIFF` / `STYLE_TYPE_REGEX` /
`STYLE_TYPE_FORMULA` / `STYLE_TYPE_TEXT`
+| Logs, diffs, expressions, free text
+
+| `STYLE_TYPE_CQL` / `STYLE_TYPE_SOQL` / `STYLE_TYPE_DROOLS`
+| Domain languages
+
+| Free-form strings
+| Plugins may introduce their own labels (for example `"Cypher"`) if needed
+|===
+
+Specialized subclasses set defaults in their constructors (`SQLStyledTextComp`
→ `SQL`, and so on).
+Call sites that use a plain `StyledTextComp` / `StyledTextVar` (including Hop
Web stand-ins) must pass the matching `styleType` constructor argument.
+
+=== Ideas enabled by this SPI
+
+Because any plugin can attach to `TextComposite.ID_TOOLBAR` and branch on
`styleType`, you can ship optional tooling without bloating core dialogs:
+
+* SQL pretty-print / format
+* SQL lineage or “explain”
+* Visual SQL builders
+* JSON / XML formatters (see the sample JSON format button)
+* Language-specific helpers (Cypher, SOQL, Drools, …)
+
+Keep heavy or optional features in plugins; keep the host widget small.
+
+=== Preferences and opt-out
+
+* Global: configuration option **Show text editor toolbar**
(`PropsUi.isShowTextCompositeToolbar()`).
+* Per instance: construct with `toolbarEnabled = false`, or pass item ids to
remove.
+
+== Extensible host: TableView toolbar
+
+`TableView` uses the same mechanism with root id `TableView.ID_TOOLBAR`
(`"TableView-Toolbar"`).
+
+Built-in items cover row insert/delete, clipboard, filter, navigate-to-column,
undo/redo, and more.
+Plugins contribute the same way — for example export-to-Excel / export-to-CSV
toolbar buttons in the Excel and text-file plugins:
+
+[source,java]
+----
+@GuiToolbarElement(
+ root = TableView.ID_TOOLBAR,
+ id = "tableview-toolbar-30000-export-to-excel",
+ toolTip = "i18n::ExcelWidget.ExportToolbarButton.ToolTip",
+ separator = true,
+ image = "excelwriter.svg")
+public static void export(TableView tableView) {
+ // ...
+}
+----
+
+== How the host builds the toolbar (for widget authors)
+
+If you own a new composite that should accept plugin buttons:
+
+. Choose a stable public root id, for example `public static final String
ID_TOOLBAR = "MyWidget-Toolbar";`
+. Annotate the host class (or a related `@GuiPlugin`) with built-in
`@GuiToolbarElement` methods if needed
+. On construction:
++
+[source,java]
+----
+toolbarWidgets = new GuiToolbarWidgets();
+// Register under the class name elements expect to resolve
+toolbarWidgets.registerGuiPluginObject(MyWidget.class.getName(), this);
+
+IToolbarContainer container =
+ ToolbarFacade.createToolbarContainer(this, SWT.WRAP | SWT.LEFT |
SWT.HORIZONTAL);
+toolbar = container.getControl();
+// layout toolbar at the top...
+toolbarWidgets.createToolbarWidgets(container, ID_TOOLBAR, removeToolItems);
+----
+. Prefer `ToolbarFacade` / `IToolbarContainer` so desktop (SWT `ToolBar`) and
Hop Web (composite row layout) share one path.
+
+== i18n for tooltips
+
+Prefer:
+
+[source,java]
+----
+toolTip = "i18n::MyClass.MyButton.ToolTip"
+----
+
+with a messages file under the plugin package, for example:
+
+[source]
+----
+plugins/.../src/main/resources/org/example/myplugin/messages/messages_en_US.properties
+----
+
+[source,properties]
+----
+MyClass.MyButton.ToolTip=Pretty-print JSON
+----
+
+Escape variable-like tokens in properties with single quotes when needed
(`'${VAR}'`) so they are not treated as interpolation.
+
+== Checklist for a new toolbar contribution
+
+* [ ] Class annotated with `@GuiPlugin`
+* [ ] Button method uses `@GuiToolbarElement(root = Host.ID_TOOLBAR, id = "…",
image = "…", toolTip = "i18n::…")`
+* [ ] Listener is `public static void name(HostType host)` (or a safe
supertype)
+* [ ] No desktop-only SWT types in the GuiPlugin class signatures
+* [ ] If visibility depends on context: add `@GuiToolbarElementFilter(parentId
= Host.ID_TOOLBAR)` with signature `(String, Object)`
+* [ ] Filter returns `true` for all item ids that are **not** yours
+* [ ] Filter only uses state available at construction (`styleType`, not
`isEditable()` / `getText()`)
+* [ ] Runtime checks (`isEditable()`, empty text, …) live in the action method
+* [ ] For `TextComposite`: pass `styleType` in the constructor; use
`getStyleType()` in filters/actions (not `instanceof`)
+* [ ] SVG image is on the plugin classpath; i18n key exists in
`messages_en_US.properties`
+* [ ] Manual check on desktop and, if relevant, Hop Web
+
+== Reference source locations
+
+[cols="1,2",options="header"]
+|===
+| Topic | Location
+
+| Toolbar widgets runtime
+| `ui/.../gui/GuiToolbarWidgets.java`, `BaseGuiWidgets.java`
+
+| Annotations
+| `core/.../gui/plugin/toolbar/GuiToolbarElement.java`,
`GuiToolbarElementFilter.java`
+
+| Registration scan
+| `ui/.../hopgui/HopGuiEnvironment.initGuiPlugins()`
+
+| Text editor host
+| `ui/.../widget/TextComposite.java`
+
+| Table host
+| `ui/.../widget/TableView.java`
+
+| Sample JSON format button
+| `plugins/transforms/json/.../ui/TextCompositeToolbarJsonFormatButton.java`
+
+| Sample table export buttons
+| `plugins/transforms/excel/.../TableViewExportToExcelToolbarButton.java`, CSV
equivalent under textfile
+
+| Metadata line filter example
+| `ui/.../database/MetaSelectionLineClearDbCacheToolbarItem.java`
+|===
diff --git
a/docs/hop-dev-manual/modules/ROOT/pages/hopweb/hopweb-antipatterns.adoc
b/docs/hop-dev-manual/modules/ROOT/pages/hopweb/hopweb-antipatterns.adoc
index d6e363f414..c30b5be8be 100644
--- a/docs/hop-dev-manual/modules/ROOT/pages/hopweb/hopweb-antipatterns.adoc
+++ b/docs/hop-dev-manual/modules/ROOT/pages/hopweb/hopweb-antipatterns.adoc
@@ -20,6 +20,9 @@ under the License.
Due to the
https://www.eclipse.org/rap/developers-guide/devguide.php?topic=rwt.html[differences
between RWT and SWT], there are some anti-patterns that should be avoided when
developing webSpoon and (webSpoon-) compatible plugins.
+GUI toolbar plugins (`@GuiPlugin`) must not put desktop-only SWT types in
method signatures; use abstractions such as `TextComposite` and content type
via `getStyleType()`.
+See xref:gui-plugins-toolbars.adoc[GUI plugins and toolbars].
+
== Avoid sharing session-unique instances among sessions
The *singleton pattern* is a design pattern where only one instance of a class
is created.
diff --git a/docs/hop-dev-manual/modules/ROOT/pages/index.adoc
b/docs/hop-dev-manual/modules/ROOT/pages/index.adoc
index f201572d8f..302daf3a99 100644
--- a/docs/hop-dev-manual/modules/ROOT/pages/index.adoc
+++ b/docs/hop-dev-manual/modules/ROOT/pages/index.adoc
@@ -23,6 +23,7 @@ under the License.
* xref:docker-build.adoc[Docker Build Script]
* xref:integration-testing.adoc[Integration Testing]
* xref:plugin-development.adoc[Plugin Development]
+* xref:gui-plugins-toolbars.adoc[GUI plugins and toolbars]
* xref:lineage.adoc[Lineage observation hub]
* xref:plugin-samples.adoc[Plugin Samples]
* xref:svg-files.adoc[SVG Files]
diff --git a/docs/hop-dev-manual/modules/ROOT/pages/plugin-development.adoc
b/docs/hop-dev-manual/modules/ROOT/pages/plugin-development.adoc
index 8873bd3a1c..41ff1e272a 100644
--- a/docs/hop-dev-manual/modules/ROOT/pages/plugin-development.adoc
+++ b/docs/hop-dev-manual/modules/ROOT/pages/plugin-development.adoc
@@ -20,6 +20,9 @@ under the License.
This page explains how to develop new plugins with references to make
development easy.
+For contributing **toolbar buttons** and other GUI-only extensions
(`@GuiPlugin`, `@GuiToolbarElement`, `@GuiToolbarElementFilter`), see
xref:gui-plugins-toolbars.adoc[GUI plugins and toolbars].
+That page covers the TextComposite and TableView extension points (SQL
formatters, JSON tools, table export, and similar).
+
== Plugin Registry
Hop keeps a central registry of all the available Hop plugins.
diff --git
a/plugins/actions/eval/src/main/java/org/apache/hop/workflow/actions/eval/ActionEvalDialog.java
b/plugins/actions/eval/src/main/java/org/apache/hop/workflow/actions/eval/ActionEvalDialog.java
index 15a89d9e37..733791ad47 100644
---
a/plugins/actions/eval/src/main/java/org/apache/hop/workflow/actions/eval/ActionEvalDialog.java
+++
b/plugins/actions/eval/src/main/java/org/apache/hop/workflow/actions/eval/ActionEvalDialog.java
@@ -97,7 +97,8 @@ public class ActionEvalDialog extends ActionDialog {
action,
shell,
SWT.MULTI | SWT.LEFT | SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL,
- false);
+ false,
+ TextComposite.STYLE_TYPE_JAVASCRIPT);
} else {
wScript =
new JavaScriptStyledTextComp(
diff --git
a/plugins/actions/evaluatetablecontent/src/main/java/org/apache/hop/workflow/actions/evaluatetablecontent/ActionEvalTableContentDialog.java
b/plugins/actions/evaluatetablecontent/src/main/java/org/apache/hop/workflow/actions/evaluatetablecontent/ActionEvalTableContentDialog.java
index ae17fb1ba1..bea65b9c93 100644
---
a/plugins/actions/evaluatetablecontent/src/main/java/org/apache/hop/workflow/actions/evaluatetablecontent/ActionEvalTableContentDialog.java
+++
b/plugins/actions/evaluatetablecontent/src/main/java/org/apache/hop/workflow/actions/evaluatetablecontent/ActionEvalTableContentDialog.java
@@ -34,6 +34,7 @@ import org.apache.hop.ui.core.dialog.BaseDialog;
import org.apache.hop.ui.core.dialog.MessageBox;
import org.apache.hop.ui.core.widget.MetaSelectionLine;
import org.apache.hop.ui.core.widget.StyledTextComp;
+import org.apache.hop.ui.core.widget.TextComposite;
import org.apache.hop.ui.core.widget.TextVar;
import org.apache.hop.ui.workflow.action.ActionDialog;
import org.apache.hop.workflow.WorkflowMeta;
@@ -408,7 +409,10 @@ public class ActionEvalTableContentDialog extends
ActionDialog {
wSql =
new StyledTextComp(
- action, wCustomGroup, SWT.MULTI | SWT.LEFT | SWT.BORDER |
SWT.H_SCROLL | SWT.V_SCROLL);
+ action,
+ wCustomGroup,
+ SWT.MULTI | SWT.LEFT | SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL,
+ TextComposite.STYLE_TYPE_SQL);
PropsUi.setLook(wSql, Props.WIDGET_STYLE_FIXED);
wSql.addModifyListener(lsMod);
FormData fdSql = new FormData();
diff --git
a/plugins/actions/sendnagiospassivecheck/src/main/java/org/apache/hop/workflow/actions/sendnagiospassivecheck/ActionSendNagiosPassiveCheckDialog.java
b/plugins/actions/sendnagiospassivecheck/src/main/java/org/apache/hop/workflow/actions/sendnagiospassivecheck/ActionSendNagiosPassiveCheckDialog.java
index 8f30594015..c7b3f710e1 100644
---
a/plugins/actions/sendnagiospassivecheck/src/main/java/org/apache/hop/workflow/actions/sendnagiospassivecheck/ActionSendNagiosPassiveCheckDialog.java
+++
b/plugins/actions/sendnagiospassivecheck/src/main/java/org/apache/hop/workflow/actions/sendnagiospassivecheck/ActionSendNagiosPassiveCheckDialog.java
@@ -29,6 +29,7 @@ import org.apache.hop.ui.core.dialog.MessageBox;
import org.apache.hop.ui.core.gui.GuiResource;
import org.apache.hop.ui.core.widget.LabelTextVar;
import org.apache.hop.ui.core.widget.StyledTextComp;
+import org.apache.hop.ui.core.widget.TextComposite;
import org.apache.hop.ui.workflow.action.ActionDialog;
import org.apache.hop.workflow.WorkflowMeta;
import org.apache.hop.workflow.action.IAction;
@@ -358,7 +359,8 @@ public class ActionSendNagiosPassiveCheckDialog extends
ActionDialog {
new StyledTextComp(
variables,
wMessageGroup,
- SWT.MULTI | SWT.LEFT | SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL);
+ SWT.MULTI | SWT.LEFT | SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL,
+ TextComposite.STYLE_TYPE_TEXT);
PropsUi.setLook(wMessage);
wMessage.addModifyListener(lsMod);
FormData fdMessage = new FormData();
diff --git
a/plugins/actions/snmptrap/src/main/java/org/apache/hop/workflow/actions/snmptrap/ActionSNMPTrapDialog.java
b/plugins/actions/snmptrap/src/main/java/org/apache/hop/workflow/actions/snmptrap/ActionSNMPTrapDialog.java
index b70cbde3be..0384158927 100644
---
a/plugins/actions/snmptrap/src/main/java/org/apache/hop/workflow/actions/snmptrap/ActionSNMPTrapDialog.java
+++
b/plugins/actions/snmptrap/src/main/java/org/apache/hop/workflow/actions/snmptrap/ActionSNMPTrapDialog.java
@@ -29,6 +29,7 @@ import org.apache.hop.ui.core.dialog.MessageBox;
import org.apache.hop.ui.core.gui.GuiResource;
import org.apache.hop.ui.core.widget.LabelTextVar;
import org.apache.hop.ui.core.widget.StyledTextComp;
+import org.apache.hop.ui.core.widget.TextComposite;
import org.apache.hop.ui.workflow.action.ActionDialog;
import org.apache.hop.workflow.WorkflowMeta;
import org.apache.hop.workflow.action.IAction;
@@ -356,7 +357,10 @@ public class ActionSNMPTrapDialog extends ActionDialog {
wMessage =
new StyledTextComp(
- action, wMessageGroup, SWT.MULTI | SWT.LEFT | SWT.BORDER |
SWT.H_SCROLL | SWT.V_SCROLL);
+ action,
+ wMessageGroup,
+ SWT.MULTI | SWT.LEFT | SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL,
+ TextComposite.STYLE_TYPE_TEXT);
PropsUi.setLook(wMessage);
wMessage.addModifyListener(lsMod);
FormData fdMessage = new FormData();
diff --git
a/plugins/actions/sql/src/main/java/org/apache/hop/workflow/actions/sql/ActionSqlDialog.java
b/plugins/actions/sql/src/main/java/org/apache/hop/workflow/actions/sql/ActionSqlDialog.java
index 9f15331761..d0bd68b3c2 100644
---
a/plugins/actions/sql/src/main/java/org/apache/hop/workflow/actions/sql/ActionSqlDialog.java
+++
b/plugins/actions/sql/src/main/java/org/apache/hop/workflow/actions/sql/ActionSqlDialog.java
@@ -269,7 +269,10 @@ public class ActionSqlDialog extends ActionDialog {
wSql =
EnvironmentUtils.getInstance().isWeb()
? new StyledTextComp(
- variables, shell, SWT.MULTI | SWT.LEFT | SWT.BORDER |
SWT.H_SCROLL | SWT.V_SCROLL)
+ variables,
+ shell,
+ SWT.MULTI | SWT.LEFT | SWT.BORDER | SWT.H_SCROLL |
SWT.V_SCROLL,
+ TextComposite.STYLE_TYPE_SQL)
: new SQLStyledTextComp(
variables, shell, SWT.MULTI | SWT.LEFT | SWT.BORDER |
SWT.H_SCROLL | SWT.V_SCROLL);
wSql.addLineStyleListener(getSqlReservedWords());
diff --git
a/plugins/actions/waitforsql/src/main/java/org/apache/hop/workflow/actions/waitforsql/ActionWaitForSqlDialog.java
b/plugins/actions/waitforsql/src/main/java/org/apache/hop/workflow/actions/waitforsql/ActionWaitForSqlDialog.java
index 1940beb5fa..2f5562492c 100644
---
a/plugins/actions/waitforsql/src/main/java/org/apache/hop/workflow/actions/waitforsql/ActionWaitForSqlDialog.java
+++
b/plugins/actions/waitforsql/src/main/java/org/apache/hop/workflow/actions/waitforsql/ActionWaitForSqlDialog.java
@@ -477,7 +477,8 @@ public class ActionWaitForSqlDialog extends ActionDialog {
? new StyledTextComp(
action,
wCustomGroup,
- SWT.MULTI | SWT.LEFT | SWT.BORDER | SWT.H_SCROLL |
SWT.V_SCROLL)
+ SWT.MULTI | SWT.LEFT | SWT.BORDER | SWT.H_SCROLL |
SWT.V_SCROLL,
+ TextComposite.STYLE_TYPE_SQL)
: new SQLStyledTextComp(
action,
wCustomGroup,
diff --git
a/plugins/misc/git/src/main/java/org/apache/hop/git/GitPerspective.java
b/plugins/misc/git/src/main/java/org/apache/hop/git/GitPerspective.java
index 66699922be..b84d9f7085 100644
--- a/plugins/misc/git/src/main/java/org/apache/hop/git/GitPerspective.java
+++ b/plugins/misc/git/src/main/java/org/apache/hop/git/GitPerspective.java
@@ -1678,6 +1678,7 @@ public class GitPerspective implements IHopPerspective {
wDiffStyled =
new DiffStyledTextComp(
hopGui.getVariables(), parent, SWT.MULTI | SWT.BORDER |
SWT.H_SCROLL | SWT.V_SCROLL);
+ // styleType STYLE_TYPE_DIFF is set by DiffStyledTextComp constructor
PropsUi.setLook(wDiffStyled, Props.WIDGET_STYLE_FIXED);
wDiff = wDiffStyled;
diff --git
a/plugins/misc/git/src/main/java/org/apache/hop/git/info/DiffStyledTextComp.java
b/plugins/misc/git/src/main/java/org/apache/hop/git/info/DiffStyledTextComp.java
index fdc444a7e0..4e28b98134 100644
---
a/plugins/misc/git/src/main/java/org/apache/hop/git/info/DiffStyledTextComp.java
+++
b/plugins/misc/git/src/main/java/org/apache/hop/git/info/DiffStyledTextComp.java
@@ -43,7 +43,7 @@ public class DiffStyledTextComp extends StyledTextVar {
new Color(Display.getDefault(), 153, 102, 0); // Brown/Orange
public DiffStyledTextComp(IVariables variables, Composite parent, int style)
{
- super(variables, parent, style, false, false); // No variable support
needed
+ super(variables, parent, style, false, false, true, STYLE_TYPE_DIFF); //
No variable support
// Set read-only and disable editing
getTextWidget().setEditable(false);
}
diff --git
a/plugins/misc/git/src/main/java/org/apache/hop/git/info/GitInfoExplorerFileTypeHandler.java
b/plugins/misc/git/src/main/java/org/apache/hop/git/info/GitInfoExplorerFileTypeHandler.java
index efca243100..32d2014f16 100644
---
a/plugins/misc/git/src/main/java/org/apache/hop/git/info/GitInfoExplorerFileTypeHandler.java
+++
b/plugins/misc/git/src/main/java/org/apache/hop/git/info/GitInfoExplorerFileTypeHandler.java
@@ -355,6 +355,7 @@ public class GitInfoExplorerFileTypeHandler extends
BaseExplorerFileTypeHandler
hopGui.getVariables(),
wDiffComposite,
SWT.MULTI | SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL);
+ // styleType STYLE_TYPE_DIFF is set by DiffStyledTextComp constructor
PropsUi.setLook(wDiffStyled, Props.WIDGET_STYLE_FIXED);
wDiffStyled.setLayoutData(fdDiff);
wDiff = wDiffStyled;
diff --git
a/plugins/tech/cassandra/src/main/java/org/apache/hop/pipeline/transforms/cassandrainput/CassandraInputDialog.java
b/plugins/tech/cassandra/src/main/java/org/apache/hop/pipeline/transforms/cassandrainput/CassandraInputDialog.java
index 599c033c0d..73c28dc984 100644
---
a/plugins/tech/cassandra/src/main/java/org/apache/hop/pipeline/transforms/cassandrainput/CassandraInputDialog.java
+++
b/plugins/tech/cassandra/src/main/java/org/apache/hop/pipeline/transforms/cassandrainput/CassandraInputDialog.java
@@ -37,6 +37,7 @@ import org.apache.hop.ui.core.dialog.PreviewRowsDialog;
import org.apache.hop.ui.core.dialog.ShowMessageDialog;
import org.apache.hop.ui.core.widget.MetaSelectionLine;
import org.apache.hop.ui.core.widget.StyledTextComp;
+import org.apache.hop.ui.core.widget.TextComposite;
import org.apache.hop.ui.core.widget.TextVar;
import org.apache.hop.ui.pipeline.dialog.PipelinePreviewProgressDialog;
import org.apache.hop.ui.pipeline.transform.BaseTransformDialog;
@@ -182,7 +183,10 @@ public class CassandraInputDialog extends
BaseTransformDialog {
wCql =
new StyledTextComp(
- variables, shell, SWT.MULTI | SWT.LEFT | SWT.BORDER | SWT.H_SCROLL
| SWT.V_SCROLL);
+ variables,
+ shell,
+ SWT.MULTI | SWT.LEFT | SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL,
+ TextComposite.STYLE_TYPE_CQL);
PropsUi.setLook(wCql, Props.WIDGET_STYLE_FIXED);
FormData fdCql = new FormData();
fdCql.left = new FormAttachment(0, 0);
diff --git
a/plugins/tech/mongodb/src/main/java/org/apache/hop/pipeline/transforms/mongodbdelete/MongoDbDeleteDialog.java
b/plugins/tech/mongodb/src/main/java/org/apache/hop/pipeline/transforms/mongodbdelete/MongoDbDeleteDialog.java
index 407f6febaa..2d94853b9d 100644
---
a/plugins/tech/mongodb/src/main/java/org/apache/hop/pipeline/transforms/mongodbdelete/MongoDbDeleteDialog.java
+++
b/plugins/tech/mongodb/src/main/java/org/apache/hop/pipeline/transforms/mongodbdelete/MongoDbDeleteDialog.java
@@ -48,6 +48,7 @@ import org.apache.hop.ui.core.widget.ColumnInfo;
import org.apache.hop.ui.core.widget.MetaSelectionLine;
import org.apache.hop.ui.core.widget.StyledTextComp;
import org.apache.hop.ui.core.widget.TableView;
+import org.apache.hop.ui.core.widget.TextComposite;
import org.apache.hop.ui.core.widget.TextVar;
import org.apache.hop.ui.pipeline.transform.BaseTransformDialog;
import org.bson.Document;
@@ -413,7 +414,8 @@ public class MongoDbDeleteDialog extends
BaseTransformDialog {
new StyledTextComp(
variables,
wFieldsComp,
- SWT.FULL_SELECTION | SWT.MULTI | SWT.BORDER | SWT.H_SCROLL |
SWT.V_SCROLL);
+ SWT.FULL_SELECTION | SWT.MULTI | SWT.BORDER | SWT.H_SCROLL |
SWT.V_SCROLL,
+ TextComposite.STYLE_TYPE_JSON);
PropsUi.setLook(wstJsonQueryView, Props.WIDGET_STYLE_FIXED);
wstJsonQueryView.addModifyListener(lsMod);
diff --git
a/plugins/tech/mongodb/src/main/java/org/apache/hop/pipeline/transforms/mongodbinput/MongoDbInputDialog.java
b/plugins/tech/mongodb/src/main/java/org/apache/hop/pipeline/transforms/mongodbinput/MongoDbInputDialog.java
index 1cc468a081..d2bd08b814 100644
---
a/plugins/tech/mongodb/src/main/java/org/apache/hop/pipeline/transforms/mongodbinput/MongoDbInputDialog.java
+++
b/plugins/tech/mongodb/src/main/java/org/apache/hop/pipeline/transforms/mongodbinput/MongoDbInputDialog.java
@@ -50,6 +50,7 @@ import org.apache.hop.ui.core.widget.ColumnInfo;
import org.apache.hop.ui.core.widget.MetaSelectionLine;
import org.apache.hop.ui.core.widget.StyledTextComp;
import org.apache.hop.ui.core.widget.TableView;
+import org.apache.hop.ui.core.widget.TextComposite;
import org.apache.hop.ui.core.widget.TextVar;
import org.apache.hop.ui.pipeline.dialog.PipelinePreviewProgressDialog;
import org.apache.hop.ui.pipeline.transform.BaseTransformDialog;
@@ -272,7 +273,10 @@ public class MongoDbInputDialog extends
BaseTransformDialog {
wJsonQuery =
new StyledTextComp(
- variables, wQueryComp, SWT.MULTI | SWT.LEFT | SWT.BORDER |
SWT.H_SCROLL | SWT.V_SCROLL);
+ variables,
+ wQueryComp,
+ SWT.MULTI | SWT.LEFT | SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL,
+ TextComposite.STYLE_TYPE_JSON);
PropsUi.setLook(wJsonQuery, Props.WIDGET_STYLE_FIXED);
wJsonQuery.addModifyListener(lsMod);
diff --git
a/plugins/tech/mongodb/src/main/java/org/apache/hop/workflow/actions/mql/ActionMqlDialog.java
b/plugins/tech/mongodb/src/main/java/org/apache/hop/workflow/actions/mql/ActionMqlDialog.java
index 93b44856d8..79eeb9f818 100644
---
a/plugins/tech/mongodb/src/main/java/org/apache/hop/workflow/actions/mql/ActionMqlDialog.java
+++
b/plugins/tech/mongodb/src/main/java/org/apache/hop/workflow/actions/mql/ActionMqlDialog.java
@@ -264,8 +264,10 @@ public class ActionMqlDialog extends ActionDialog {
wMql =
new StyledTextComp(
- variables, shell, SWT.MULTI | SWT.LEFT | SWT.BORDER | SWT.H_SCROLL
| SWT.V_SCROLL);
-
+ variables,
+ shell,
+ SWT.MULTI | SWT.LEFT | SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL,
+ TextComposite.STYLE_TYPE_JSON);
PropsUi.setLook(wMql, Props.WIDGET_STYLE_FIXED);
FormData fdMql = new FormData();
fdMql.left = new FormAttachment(0, 0);
diff --git
a/plugins/tech/salesforce/src/main/java/org/apache/hop/pipeline/transforms/salesforceinput/SalesforceInputDialog.java
b/plugins/tech/salesforce/src/main/java/org/apache/hop/pipeline/transforms/salesforceinput/SalesforceInputDialog.java
index 2be97edc74..8f117e1306 100644
---
a/plugins/tech/salesforce/src/main/java/org/apache/hop/pipeline/transforms/salesforceinput/SalesforceInputDialog.java
+++
b/plugins/tech/salesforce/src/main/java/org/apache/hop/pipeline/transforms/salesforceinput/SalesforceInputDialog.java
@@ -54,6 +54,7 @@ import org.apache.hop.ui.core.widget.LabelTextVar;
import org.apache.hop.ui.core.widget.MetaSelectionLine;
import org.apache.hop.ui.core.widget.StyledTextComp;
import org.apache.hop.ui.core.widget.TableView;
+import org.apache.hop.ui.core.widget.TextComposite;
import org.apache.hop.ui.core.widget.TextVar;
import org.apache.hop.ui.pipeline.dialog.PipelinePreviewProgressDialog;
import org.apache.hop.ui.pipeline.transform.ComponentSelectionListener;
@@ -441,7 +442,8 @@ public class SalesforceInputDialog extends
SalesforceTransformDialog {
new StyledTextComp(
variables,
wSettingsGroup,
- SWT.MULTI | SWT.LEFT | SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL);
+ SWT.MULTI | SWT.LEFT | SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL,
+ TextComposite.STYLE_TYPE_SOQL);
wCondition.setToolTipText(
BaseMessages.getString(PKG,
"SalesforceInputDialog.Condition.Tooltip"));
PropsUi.setLook(wCondition, Props.WIDGET_STYLE_FIXED);
@@ -514,7 +516,8 @@ public class SalesforceInputDialog extends
SalesforceTransformDialog {
new StyledTextComp(
variables,
wSettingsGroup,
- SWT.MULTI | SWT.LEFT | SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL);
+ SWT.MULTI | SWT.LEFT | SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL,
+ TextComposite.STYLE_TYPE_SOQL);
PropsUi.setLook(wQuery, Props.WIDGET_STYLE_FIXED);
wQuery.addModifyListener(lsMod);
FormData fdQuery = new FormData();
diff --git
a/plugins/transforms/databasejoin/src/main/java/org/apache/hop/pipeline/transforms/databasejoin/DatabaseJoinDialog.java
b/plugins/transforms/databasejoin/src/main/java/org/apache/hop/pipeline/transforms/databasejoin/DatabaseJoinDialog.java
index d11b0bd5a5..2454a5f1ff 100644
---
a/plugins/transforms/databasejoin/src/main/java/org/apache/hop/pipeline/transforms/databasejoin/DatabaseJoinDialog.java
+++
b/plugins/transforms/databasejoin/src/main/java/org/apache/hop/pipeline/transforms/databasejoin/DatabaseJoinDialog.java
@@ -168,7 +168,10 @@ public class DatabaseJoinDialog extends
BaseTransformDialog {
wSql =
EnvironmentUtils.getInstance().isWeb()
? new StyledTextComp(
- variables, shell, SWT.MULTI | SWT.LEFT | SWT.BORDER |
SWT.H_SCROLL | SWT.V_SCROLL)
+ variables,
+ shell,
+ SWT.MULTI | SWT.LEFT | SWT.BORDER | SWT.H_SCROLL |
SWT.V_SCROLL,
+ TextComposite.STYLE_TYPE_SQL)
: new SQLStyledTextComp(
variables, shell, SWT.MULTI | SWT.LEFT | SWT.BORDER |
SWT.H_SCROLL | SWT.V_SCROLL);
wSql.addLineStyleListener(getSqlReservedWords());
diff --git
a/plugins/transforms/drools/src/main/java/org/apache/hop/pipeline/transforms/drools/RulesAccumulatorDialog.java
b/plugins/transforms/drools/src/main/java/org/apache/hop/pipeline/transforms/drools/RulesAccumulatorDialog.java
index c77fda0590..50fcdd5b82 100644
---
a/plugins/transforms/drools/src/main/java/org/apache/hop/pipeline/transforms/drools/RulesAccumulatorDialog.java
+++
b/plugins/transforms/drools/src/main/java/org/apache/hop/pipeline/transforms/drools/RulesAccumulatorDialog.java
@@ -31,6 +31,7 @@ import org.apache.hop.ui.core.widget.ColumnInfo;
import org.apache.hop.ui.core.widget.ColumnsResizer;
import org.apache.hop.ui.core.widget.StyledTextComp;
import org.apache.hop.ui.core.widget.TableView;
+import org.apache.hop.ui.core.widget.TextComposite;
import org.apache.hop.ui.core.widget.TextVar;
import org.apache.hop.ui.pipeline.transform.BaseTransformDialog;
import org.eclipse.swt.SWT;
@@ -186,7 +187,10 @@ public class RulesAccumulatorDialog extends
BaseTransformDialog {
wRulesEditor =
new StyledTextComp(
- variables, wRulesComp, SWT.MULTI | SWT.LEFT | SWT.BORDER |
SWT.H_SCROLL | SWT.V_SCROLL);
+ variables,
+ wRulesComp,
+ SWT.MULTI | SWT.LEFT | SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL,
+ TextComposite.STYLE_TYPE_DROOLS);
PropsUi.setLook(wRulesEditor, Props.WIDGET_STYLE_FIXED);
FormData fdRulesEditor = new FormData();
diff --git
a/plugins/transforms/drools/src/main/java/org/apache/hop/pipeline/transforms/drools/RulesExecutorDialog.java
b/plugins/transforms/drools/src/main/java/org/apache/hop/pipeline/transforms/drools/RulesExecutorDialog.java
index a6e43e433e..4e301f3bde 100644
---
a/plugins/transforms/drools/src/main/java/org/apache/hop/pipeline/transforms/drools/RulesExecutorDialog.java
+++
b/plugins/transforms/drools/src/main/java/org/apache/hop/pipeline/transforms/drools/RulesExecutorDialog.java
@@ -31,6 +31,7 @@ import org.apache.hop.ui.core.widget.ColumnInfo;
import org.apache.hop.ui.core.widget.ColumnsResizer;
import org.apache.hop.ui.core.widget.StyledTextComp;
import org.apache.hop.ui.core.widget.TableView;
+import org.apache.hop.ui.core.widget.TextComposite;
import org.apache.hop.ui.core.widget.TextVar;
import org.apache.hop.ui.pipeline.transform.BaseTransformDialog;
import org.eclipse.swt.SWT;
@@ -189,7 +190,10 @@ public class RulesExecutorDialog extends
BaseTransformDialog {
wRulesEditor =
new StyledTextComp(
- variables, wRulesComp, SWT.MULTI | SWT.LEFT | SWT.BORDER |
SWT.H_SCROLL | SWT.V_SCROLL);
+ variables,
+ wRulesComp,
+ SWT.MULTI | SWT.LEFT | SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL,
+ TextComposite.STYLE_TYPE_DROOLS);
PropsUi.setLook(wRulesEditor, Props.WIDGET_STYLE_FIXED);
FormData fdRulesEditor = new FormData();
diff --git
a/plugins/transforms/dynamicsqlrow/src/main/java/org/apache/hop/pipeline/transforms/dynamicsqlrow/DynamicSqlRowDialog.java
b/plugins/transforms/dynamicsqlrow/src/main/java/org/apache/hop/pipeline/transforms/dynamicsqlrow/DynamicSqlRowDialog.java
index f7c1c67e8a..349f05e2f4 100644
---
a/plugins/transforms/dynamicsqlrow/src/main/java/org/apache/hop/pipeline/transforms/dynamicsqlrow/DynamicSqlRowDialog.java
+++
b/plugins/transforms/dynamicsqlrow/src/main/java/org/apache/hop/pipeline/transforms/dynamicsqlrow/DynamicSqlRowDialog.java
@@ -269,7 +269,8 @@ public class DynamicSqlRowDialog extends
BaseTransformDialog {
? new StyledTextComp(
variables,
wContent,
- SWT.MULTI | SWT.LEFT | SWT.BORDER | SWT.H_SCROLL |
SWT.V_SCROLL)
+ SWT.MULTI | SWT.LEFT | SWT.BORDER | SWT.H_SCROLL |
SWT.V_SCROLL,
+ TextComposite.STYLE_TYPE_SQL)
: new SQLStyledTextComp(
variables,
wContent,
diff --git
a/plugins/transforms/formula/src/main/java/org/apache/hop/pipeline/transforms/formula/editor/FormulaEditor.java
b/plugins/transforms/formula/src/main/java/org/apache/hop/pipeline/transforms/formula/editor/FormulaEditor.java
index a1c472a05b..3b4897af17 100644
---
a/plugins/transforms/formula/src/main/java/org/apache/hop/pipeline/transforms/formula/editor/FormulaEditor.java
+++
b/plugins/transforms/formula/src/main/java/org/apache/hop/pipeline/transforms/formula/editor/FormulaEditor.java
@@ -24,6 +24,7 @@ import org.apache.hop.pipeline.transforms.formula.FormulaMeta;
import org.apache.hop.pipeline.transforms.formula.function.FunctionDescription;
import org.apache.hop.pipeline.transforms.formula.function.FunctionLib;
import org.apache.hop.ui.core.widget.StyledTextComp;
+import org.apache.hop.ui.core.widget.TextComposite;
import org.eclipse.swt.SWT;
import org.eclipse.swt.browser.Browser;
import org.eclipse.swt.custom.SashForm;
@@ -222,7 +223,10 @@ public class FormulaEditor extends Dialog implements
KeyListener {
//
expressionEditor =
new StyledTextComp(
- variables, rightSash, SWT.MULTI | SWT.LEFT | SWT.BORDER |
SWT.H_SCROLL | SWT.V_SCROLL);
+ variables,
+ rightSash,
+ SWT.MULTI | SWT.LEFT | SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL,
+ TextComposite.STYLE_TYPE_FORMULA);
expressionEditor.setText(this.formula);
expressionEditor.addModifyListener(event -> setStyles());
expressionEditor.addKeyListener(this);
diff --git
a/plugins/transforms/janino/src/main/java/org/apache/hop/pipeline/transforms/janino/editor/FormulaEditor.java
b/plugins/transforms/janino/src/main/java/org/apache/hop/pipeline/transforms/janino/editor/FormulaEditor.java
index 8edcc9e5e9..38b1a99d3a 100644
---
a/plugins/transforms/janino/src/main/java/org/apache/hop/pipeline/transforms/janino/editor/FormulaEditor.java
+++
b/plugins/transforms/janino/src/main/java/org/apache/hop/pipeline/transforms/janino/editor/FormulaEditor.java
@@ -25,6 +25,7 @@ import org.apache.hop.pipeline.transforms.janino.JaninoMeta;
import org.apache.hop.pipeline.transforms.janino.function.FunctionDescription;
import org.apache.hop.pipeline.transforms.janino.function.FunctionLib;
import org.apache.hop.ui.core.widget.StyledTextComp;
+import org.apache.hop.ui.core.widget.TextComposite;
import org.eclipse.swt.SWT;
import org.eclipse.swt.browser.Browser;
import org.eclipse.swt.custom.SashForm;
@@ -223,7 +224,10 @@ public class FormulaEditor extends Dialog implements
KeyListener {
//
expressionEditor =
new StyledTextComp(
- variables, rightSash, SWT.MULTI | SWT.LEFT | SWT.BORDER |
SWT.H_SCROLL | SWT.V_SCROLL);
+ variables,
+ rightSash,
+ SWT.MULTI | SWT.LEFT | SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL,
+ TextComposite.STYLE_TYPE_FORMULA);
expressionEditor.setText(this.formula);
expressionEditor.addModifyListener(event -> setStyles());
expressionEditor.addKeyListener(this);
diff --git
a/plugins/transforms/janino/src/main/java/org/apache/hop/pipeline/transforms/javafilter/JavaFilterDialog.java
b/plugins/transforms/janino/src/main/java/org/apache/hop/pipeline/transforms/javafilter/JavaFilterDialog.java
index b4d9f006b3..754a0b451b 100644
---
a/plugins/transforms/janino/src/main/java/org/apache/hop/pipeline/transforms/javafilter/JavaFilterDialog.java
+++
b/plugins/transforms/janino/src/main/java/org/apache/hop/pipeline/transforms/javafilter/JavaFilterDialog.java
@@ -35,6 +35,7 @@ import org.apache.hop.ui.core.dialog.ErrorDialog;
import org.apache.hop.ui.core.dialog.MessageBox;
import org.apache.hop.ui.core.widget.ColumnInfo;
import org.apache.hop.ui.core.widget.StyledTextComp;
+import org.apache.hop.ui.core.widget.TextComposite;
import org.apache.hop.ui.pipeline.transform.BaseTransformDialog;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.CCombo;
@@ -171,7 +172,8 @@ public class JavaFilterDialog extends BaseTransformDialog {
new StyledTextComp(
variables,
wSettingsGroup,
- SWT.MULTI | SWT.LEFT | SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL);
+ SWT.MULTI | SWT.LEFT | SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL,
+ TextComposite.STYLE_TYPE_JAVA);
PropsUi.setLook(wCondition);
FormData fdCondition = new FormData();
fdCondition.top = new FormAttachment(wFalseTo, margin);
diff --git
a/plugins/transforms/janino/src/main/java/org/apache/hop/pipeline/transforms/userdefinedjavaclass/UserDefinedJavaClassDialog.java
b/plugins/transforms/janino/src/main/java/org/apache/hop/pipeline/transforms/userdefinedjavaclass/UserDefinedJavaClassDialog.java
index f61a4377b4..0a7e9bedbb 100644
---
a/plugins/transforms/janino/src/main/java/org/apache/hop/pipeline/transforms/userdefinedjavaclass/UserDefinedJavaClassDialog.java
+++
b/plugins/transforms/janino/src/main/java/org/apache/hop/pipeline/transforms/userdefinedjavaclass/UserDefinedJavaClassDialog.java
@@ -843,7 +843,8 @@ public class UserDefinedJavaClassDialog extends
BaseTransformDialog {
variables,
item.getParent(),
SWT.MULTI | SWT.LEFT | SWT.H_SCROLL | SWT.V_SCROLL,
- false);
+ false,
+ TextComposite.STYLE_TYPE_JAVA);
} else {
wScript =
new JavaStyledTextComp(
diff --git
a/plugins/transforms/javascript/src/main/java/org/apache/hop/pipeline/transforms/javascript/ScriptValuesDialog.java
b/plugins/transforms/javascript/src/main/java/org/apache/hop/pipeline/transforms/javascript/ScriptValuesDialog.java
index dc1c86b477..50820f9a63 100644
---
a/plugins/transforms/javascript/src/main/java/org/apache/hop/pipeline/transforms/javascript/ScriptValuesDialog.java
+++
b/plugins/transforms/javascript/src/main/java/org/apache/hop/pipeline/transforms/javascript/ScriptValuesDialog.java
@@ -602,7 +602,8 @@ public class ScriptValuesDialog extends BaseTransformDialog
{
variables,
item.getParent(),
SWT.MULTI | SWT.LEFT | SWT.H_SCROLL | SWT.V_SCROLL,
- false);
+ false,
+ TextComposite.STYLE_TYPE_JAVASCRIPT);
} else {
wScript =
new JavaScriptStyledTextComp(
diff --git
a/plugins/transforms/json/src/main/java/org/apache/hop/pipeline/transforms/ui/TextCompositeToolbarJsonFormatButton.java
b/plugins/transforms/json/src/main/java/org/apache/hop/pipeline/transforms/ui/TextCompositeToolbarJsonFormatButton.java
new file mode 100644
index 0000000000..9ec5262fc1
--- /dev/null
+++
b/plugins/transforms/json/src/main/java/org/apache/hop/pipeline/transforms/ui/TextCompositeToolbarJsonFormatButton.java
@@ -0,0 +1,115 @@
+/*
+ * 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.ui;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.hop.core.gui.plugin.GuiPlugin;
+import org.apache.hop.core.gui.plugin.toolbar.GuiToolbarElement;
+import org.apache.hop.core.gui.plugin.toolbar.GuiToolbarElementFilter;
+import org.apache.hop.core.json.HopJson;
+import org.apache.hop.i18n.BaseMessages;
+import org.apache.hop.ui.core.dialog.ErrorDialog;
+import org.apache.hop.ui.core.widget.TextComposite;
+import org.eclipse.swt.widgets.Shell;
+
+/**
+ * Sample TextComposite toolbar contribution: pretty-print JSON when the
editor's {@link
+ * TextComposite#getStyleType()} is {@link TextComposite#STYLE_TYPE_JSON}.
+ */
+@GuiPlugin
+public class TextCompositeToolbarJsonFormatButton {
+ private static final Class<?> PKG =
TextCompositeToolbarJsonFormatButton.class;
+
+ private static final String ID_TOOLBAR_FORMAT_JSON =
"textcomposite-toolbar-20010-format-json";
+
+ /**
+ * Only show this toolbar button when the content is JSON. Other toolbar
item IDs always return
+ * {@code true} so built-in buttons are not hidden.
+ *
+ * <p>Signature must be {@code (String, Object)} for {@link
GuiToolbarElementFilter} reflection.
+ *
+ * <p><strong>Do not call {@link TextComposite#isEditable()} (or {@code
getText()}, etc.)
+ * here.</strong> Filters run while the {@link TextComposite} constructor is
still building the
+ * toolbar, before the underlying text widget exists. Check editability in
the action method
+ * instead.
+ *
+ * @param buttonId the toolbar button id being evaluated
+ * @param guiPluginInstance the registered TextComposite instance (or other
owner)
+ * @return whether the button should be shown
+ */
+ @GuiToolbarElementFilter(parentId = TextComposite.ID_TOOLBAR)
+ public static boolean isButtonShown(String buttonId, Object
guiPluginInstance) {
+ if (!ID_TOOLBAR_FORMAT_JSON.equals(buttonId)) {
+ return true;
+ }
+ if (!(guiPluginInstance instanceof TextComposite textComposite)) {
+ return false;
+ }
+ // styleType is set via the constructor before addToolbar(); isEditable()
is not safe here.
+ return
TextComposite.STYLE_TYPE_JSON.equalsIgnoreCase(textComposite.getStyleType());
+ }
+
+ @GuiToolbarElement(
+ root = TextComposite.ID_TOOLBAR,
+ id = ID_TOOLBAR_FORMAT_JSON,
+ toolTip =
"i18n::TextCompositeToolbarJsonFormatButton.FormatJson.ToolTip",
+ separator = true,
+ image = "json-input.svg")
+ public static void formatJson(TextComposite textComposite) {
+ // Runtime checks (editability, disposed, …) belong here — not in the
toolbar filter.
+ if (textComposite == null || textComposite.isDisposed() ||
!textComposite.isEditable()) {
+ return;
+ }
+
+ Shell shell = textComposite.getShell();
+ String json = textComposite.getText();
+ if (StringUtils.isBlank(json)) {
+ return;
+ }
+
+ try {
+ ObjectMapper mapper = HopJson.newMapper();
+ JsonNode tree = mapper.readTree(json);
+ String formatted =
mapper.writerWithDefaultPrettyPrinter().writeValueAsString(tree);
+
+ // Jackson may leave a trailing newline; normalize so compare is stable
+ if (formatted.endsWith("\n") && !json.endsWith("\n")) {
+ formatted = formatted.substring(0, formatted.length() - 1);
+ }
+
+ if (formatted.equals(json)) {
+ return;
+ }
+
+ // selectAll + insert so StyledTextVar undo stack records the previous
value
+ textComposite.selectAll();
+ textComposite.insert(formatted);
+ textComposite.setCaretPosition(0);
+ textComposite.updateToolbar();
+ } catch (Exception e) {
+ new ErrorDialog(
+ shell,
+ BaseMessages.getString(PKG,
"TextCompositeToolbarJsonFormatButton.Error.Title"),
+ BaseMessages.getString(PKG,
"TextCompositeToolbarJsonFormatButton.Error.Message"),
+ e);
+ }
+ }
+}
diff --git
a/plugins/transforms/json/src/main/resources/org/apache/hop/pipeline/transforms/ui/messages/messages_en_US.properties
b/plugins/transforms/json/src/main/resources/org/apache/hop/pipeline/transforms/ui/messages/messages_en_US.properties
new file mode 100644
index 0000000000..58edcb147b
--- /dev/null
+++
b/plugins/transforms/json/src/main/resources/org/apache/hop/pipeline/transforms/ui/messages/messages_en_US.properties
@@ -0,0 +1,19 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+TextCompositeToolbarJsonFormatButton.FormatJson.ToolTip=Pretty-print JSON
+TextCompositeToolbarJsonFormatButton.Error.Title=JSON format error
+TextCompositeToolbarJsonFormatButton.Error.Message=The text is not valid JSON
and could not be formatted.
diff --git
a/plugins/transforms/ldap/src/main/java/org/apache/hop/pipeline/transforms/ldapinput/LdapInputDialog.java
b/plugins/transforms/ldap/src/main/java/org/apache/hop/pipeline/transforms/ldapinput/LdapInputDialog.java
index 88d8b82dbf..1a1f9bb5c7 100644
---
a/plugins/transforms/ldap/src/main/java/org/apache/hop/pipeline/transforms/ldapinput/LdapInputDialog.java
+++
b/plugins/transforms/ldap/src/main/java/org/apache/hop/pipeline/transforms/ldapinput/LdapInputDialog.java
@@ -43,6 +43,7 @@ import org.apache.hop.ui.core.widget.ComboVar;
import org.apache.hop.ui.core.widget.PasswordTextVar;
import org.apache.hop.ui.core.widget.StyledTextComp;
import org.apache.hop.ui.core.widget.TableView;
+import org.apache.hop.ui.core.widget.TextComposite;
import org.apache.hop.ui.core.widget.TextVar;
import org.apache.hop.ui.pipeline.dialog.PipelinePreviewProgressDialog;
import org.apache.hop.ui.pipeline.transform.BaseTransformDialog;
@@ -676,7 +677,8 @@ public class LdapInputDialog extends BaseTransformDialog {
variables,
wSearchGroup,
SWT.MULTI | SWT.LEFT | SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL,
- true);
+ true,
+ TextComposite.STYLE_TYPE_TEXT);
wFilterString.setToolTipText(
BaseMessages.getString(PKG, "LdapInputDialog.FilterString.Tooltip"));
PropsUi.setLook(wFilterString);
diff --git
a/plugins/transforms/regexeval/src/main/java/org/apache/hop/pipeline/transforms/regexeval/RegexEvalDialog.java
b/plugins/transforms/regexeval/src/main/java/org/apache/hop/pipeline/transforms/regexeval/RegexEvalDialog.java
index d3ee7ddc2d..0680952c73 100644
---
a/plugins/transforms/regexeval/src/main/java/org/apache/hop/pipeline/transforms/regexeval/RegexEvalDialog.java
+++
b/plugins/transforms/regexeval/src/main/java/org/apache/hop/pipeline/transforms/regexeval/RegexEvalDialog.java
@@ -37,6 +37,7 @@ import org.apache.hop.ui.core.widget.ColumnsResizer;
import org.apache.hop.ui.core.widget.LabelTextVar;
import org.apache.hop.ui.core.widget.StyledTextComp;
import org.apache.hop.ui.core.widget.TableView;
+import org.apache.hop.ui.core.widget.TextComposite;
import org.apache.hop.ui.pipeline.transform.BaseTransformDialog;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.CCombo;
@@ -276,7 +277,8 @@ public class RegexEvalDialog extends BaseTransformDialog {
new StyledTextComp(
variables,
wGeneralComp,
- SWT.MULTI | SWT.LEFT | SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL);
+ SWT.MULTI | SWT.LEFT | SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL,
+ TextComposite.STYLE_TYPE_REGEX);
wScript.setText(BaseMessages.getString(PKG,
"RegexEvalDialog.Script.Label"));
PropsUi.setLook(wScript, Props.WIDGET_STYLE_FIXED);
wScript.addModifyListener(lsMod);
diff --git
a/plugins/transforms/regexeval/src/main/java/org/apache/hop/pipeline/transforms/regexeval/RegexEvalHelperDialog.java
b/plugins/transforms/regexeval/src/main/java/org/apache/hop/pipeline/transforms/regexeval/RegexEvalHelperDialog.java
index 2965081129..06307e930a 100644
---
a/plugins/transforms/regexeval/src/main/java/org/apache/hop/pipeline/transforms/regexeval/RegexEvalHelperDialog.java
+++
b/plugins/transforms/regexeval/src/main/java/org/apache/hop/pipeline/transforms/regexeval/RegexEvalHelperDialog.java
@@ -31,6 +31,7 @@ import org.apache.hop.ui.core.dialog.MessageBox;
import org.apache.hop.ui.core.gui.GuiResource;
import org.apache.hop.ui.core.gui.WindowProperty;
import org.apache.hop.ui.core.widget.StyledTextComp;
+import org.apache.hop.ui.core.widget.TextComposite;
import org.apache.hop.ui.pipeline.transform.BaseTransformDialog;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.FormAttachment;
@@ -140,7 +141,10 @@ public class RegexEvalHelperDialog extends Dialog {
wRegExScript =
new StyledTextComp(
- variables, shell, SWT.MULTI | SWT.LEFT | SWT.BORDER | SWT.H_SCROLL
| SWT.V_SCROLL);
+ variables,
+ shell,
+ SWT.MULTI | SWT.LEFT | SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL,
+ TextComposite.STYLE_TYPE_REGEX);
PropsUi.setLook(wRegExScript);
FormData fdRegExScript = new FormData();
fdRegExScript.left = new FormAttachment(0, 0);
diff --git
a/plugins/transforms/script/src/main/java/org/apache/hop/pipeline/transforms/script/ScriptDialog.java
b/plugins/transforms/script/src/main/java/org/apache/hop/pipeline/transforms/script/ScriptDialog.java
index 0eb0075101..26482733a1 100644
---
a/plugins/transforms/script/src/main/java/org/apache/hop/pipeline/transforms/script/ScriptDialog.java
+++
b/plugins/transforms/script/src/main/java/org/apache/hop/pipeline/transforms/script/ScriptDialog.java
@@ -513,7 +513,8 @@ public class ScriptDialog extends BaseTransformDialog {
variables,
item.getParent(),
SWT.MULTI | SWT.LEFT | SWT.H_SCROLL | SWT.V_SCROLL,
- false);
+ false,
+ TextComposite.STYLE_TYPE_SCRIPT);
} else {
wScript =
new ScriptStyledTextComp(
diff --git
a/plugins/transforms/sql/src/main/java/org/apache/hop/pipeline/transforms/sql/ExecSqlDialog.java
b/plugins/transforms/sql/src/main/java/org/apache/hop/pipeline/transforms/sql/ExecSqlDialog.java
index 8e35d63e40..d9b18f579d 100644
---
a/plugins/transforms/sql/src/main/java/org/apache/hop/pipeline/transforms/sql/ExecSqlDialog.java
+++
b/plugins/transforms/sql/src/main/java/org/apache/hop/pipeline/transforms/sql/ExecSqlDialog.java
@@ -155,7 +155,10 @@ public class ExecSqlDialog extends BaseTransformDialog {
wSql =
EnvironmentUtils.getInstance().isWeb()
? new StyledTextComp(
- variables, shell, SWT.MULTI | SWT.LEFT | SWT.BORDER |
SWT.H_SCROLL | SWT.V_SCROLL)
+ variables,
+ shell,
+ SWT.MULTI | SWT.LEFT | SWT.BORDER | SWT.H_SCROLL |
SWT.V_SCROLL,
+ TextComposite.STYLE_TYPE_SQL)
: new SQLStyledTextComp(
variables, shell, SWT.MULTI | SWT.LEFT | SWT.BORDER |
SWT.H_SCROLL | SWT.V_SCROLL);
final List<String> sqlKeywords = getSqlReservedWords();
diff --git
a/plugins/transforms/ssh/src/main/java/org/apache/hop/pipeline/transforms/ssh/SshDialog.java
b/plugins/transforms/ssh/src/main/java/org/apache/hop/pipeline/transforms/ssh/SshDialog.java
index 1fbb665b25..9dfd89e673 100644
---
a/plugins/transforms/ssh/src/main/java/org/apache/hop/pipeline/transforms/ssh/SshDialog.java
+++
b/plugins/transforms/ssh/src/main/java/org/apache/hop/pipeline/transforms/ssh/SshDialog.java
@@ -38,6 +38,7 @@ import org.apache.hop.ui.core.dialog.PreviewRowsDialog;
import org.apache.hop.ui.core.gui.GuiResource;
import org.apache.hop.ui.core.widget.LabelTextVar;
import org.apache.hop.ui.core.widget.StyledTextComp;
+import org.apache.hop.ui.core.widget.TextComposite;
import org.apache.hop.ui.core.widget.TextVar;
import org.apache.hop.ui.pipeline.dialog.PipelinePreviewProgressDialog;
import org.apache.hop.ui.pipeline.transform.BaseTransformDialog;
@@ -533,7 +534,10 @@ public class SshDialog extends BaseTransformDialog {
wCommand =
new StyledTextComp(
- variables, wCommands, SWT.MULTI | SWT.LEFT | SWT.BORDER |
SWT.H_SCROLL | SWT.V_SCROLL);
+ variables,
+ wCommands,
+ SWT.MULTI | SWT.LEFT | SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL,
+ TextComposite.STYLE_TYPE_TEXT);
wCommand.setToolTipText(BaseMessages.getString(PKG,
"SSHDialog.Command.Tooltip"));
PropsUi.setLook(wCommand);
wCommand.addModifyListener(lsMod);
diff --git
a/plugins/transforms/tableinput/src/main/java/org/apache/hop/pipeline/transforms/tableinput/TableInputDialog.java
b/plugins/transforms/tableinput/src/main/java/org/apache/hop/pipeline/transforms/tableinput/TableInputDialog.java
index 667c1c961f..f32846160e 100644
---
a/plugins/transforms/tableinput/src/main/java/org/apache/hop/pipeline/transforms/tableinput/TableInputDialog.java
+++
b/plugins/transforms/tableinput/src/main/java/org/apache/hop/pipeline/transforms/tableinput/TableInputDialog.java
@@ -279,7 +279,10 @@ public class TableInputDialog extends BaseTransformDialog {
if (EnvironmentUtils.getInstance().isWeb()) {
wSqlComposite =
new StyledTextComp(
- variables, shell, SWT.MULTI | SWT.LEFT | SWT.BORDER |
SWT.H_SCROLL | SWT.V_SCROLL);
+ variables,
+ shell,
+ SWT.MULTI | SWT.LEFT | SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL,
+ TextComposite.STYLE_TYPE_SQL);
} else {
wSqlComposite =
new SQLStyledTextComp(
diff --git
a/plugins/transforms/writetolog/src/main/java/org/apache/hop/pipeline/transforms/writetolog/WriteToLogDialog.java
b/plugins/transforms/writetolog/src/main/java/org/apache/hop/pipeline/transforms/writetolog/WriteToLogDialog.java
index 8835f026ff..b6dea4d2b6 100644
---
a/plugins/transforms/writetolog/src/main/java/org/apache/hop/pipeline/transforms/writetolog/WriteToLogDialog.java
+++
b/plugins/transforms/writetolog/src/main/java/org/apache/hop/pipeline/transforms/writetolog/WriteToLogDialog.java
@@ -36,6 +36,7 @@ import org.apache.hop.ui.core.dialog.ErrorDialog;
import org.apache.hop.ui.core.widget.ColumnInfo;
import org.apache.hop.ui.core.widget.StyledTextComp;
import org.apache.hop.ui.core.widget.TableView;
+import org.apache.hop.ui.core.widget.TextComposite;
import org.apache.hop.ui.pipeline.transform.BaseTransformDialog;
import org.apache.hop.ui.pipeline.transform.ITableItemInsertListener;
import org.eclipse.swt.SWT;
@@ -172,7 +173,10 @@ public class WriteToLogDialog extends BaseTransformDialog {
wLogMessage =
new StyledTextComp(
- variables, shell, SWT.MULTI | SWT.LEFT | SWT.BORDER | SWT.H_SCROLL
| SWT.V_SCROLL);
+ variables,
+ shell,
+ SWT.MULTI | SWT.LEFT | SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL,
+ TextComposite.STYLE_TYPE_TEXT);
PropsUi.setLook(wLogMessage, Props.WIDGET_STYLE_FIXED);
FormData fdLogMessage = new FormData();
fdLogMessage.left = new FormAttachment(middle, 0);
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 2501ace11c..cd1119896b 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
@@ -627,6 +627,15 @@ public class PropsUi extends Props {
setProperty(STRING_SHOW_TABLE_VIEW_TOOLBAR, show ? YES : NO);
}
+ public boolean isShowTextCompositeToolbar() {
+ String show = getProperty(STRING_SHOW_TEXT_COMPOSITE_TOOLBAR, YES);
+ return YES.equalsIgnoreCase(show); // Default: show the toolbar
+ }
+
+ public void setShowTextCompositeToolbar(boolean show) {
+ setProperty(STRING_SHOW_TEXT_COMPOSITE_TOOLBAR, show ? YES : NO);
+ }
+
/**
* Maximum number of characters shown in a preview grid cell before it is
truncated (double-click
* the cell to see the full value). 0 disables truncation. Default 50.
diff --git
a/ui/src/main/java/org/apache/hop/ui/core/database/dialog/SqlEditor.java
b/ui/src/main/java/org/apache/hop/ui/core/database/dialog/SqlEditor.java
index d0d836dc4c..0a56f7033b 100644
--- a/ui/src/main/java/org/apache/hop/ui/core/database/dialog/SqlEditor.java
+++ b/ui/src/main/java/org/apache/hop/ui/core/database/dialog/SqlEditor.java
@@ -165,7 +165,10 @@ public class SqlEditor {
if (EnvironmentUtils.getInstance().isWeb()) {
wScript =
new StyledTextComp(
- variables, shell, SWT.MULTI | SWT.LEFT | SWT.BORDER |
SWT.H_SCROLL | SWT.V_SCROLL);
+ variables,
+ shell,
+ SWT.MULTI | SWT.LEFT | SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL,
+ TextComposite.STYLE_TYPE_SQL);
} else {
wScript =
new SQLStyledTextComp(
diff --git
a/ui/src/main/java/org/apache/hop/ui/core/dialog/FindReplaceDialog.java
b/ui/src/main/java/org/apache/hop/ui/core/dialog/FindReplaceDialog.java
new file mode 100644
index 0000000000..43b641ffab
--- /dev/null
+++ b/ui/src/main/java/org/apache/hop/ui/core/dialog/FindReplaceDialog.java
@@ -0,0 +1,335 @@
+/*
+ * 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.core.dialog;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.hop.i18n.BaseMessages;
+import org.apache.hop.ui.core.PropsUi;
+import org.apache.hop.ui.core.gui.GuiResource;
+import org.apache.hop.ui.core.gui.WindowProperty;
+import org.apache.hop.ui.core.widget.TextComposite;
+import org.apache.hop.ui.core.widget.TextFindSupport;
+import org.apache.hop.ui.pipeline.transform.BaseTransformDialog;
+import org.eclipse.swt.SWT;
+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.Dialog;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.Text;
+
+/**
+ * Find / find-and-replace dialog for multi-line {@link TextComposite} editors
(SQL, scripts, logs).
+ */
+public class FindReplaceDialog extends Dialog {
+ private static final Class<?> PKG = FindReplaceDialog.class;
+
+ private static String lastFind = "";
+ private static String lastReplace = "";
+ private static boolean lastCaseSensitive = false;
+
+ private final TextComposite textComposite;
+ private final boolean replaceMode;
+ private final PropsUi props;
+
+ private Shell shell;
+ private Text wFind;
+ private Text wReplace;
+ private Button wCaseSensitive;
+ private Label wlStatus;
+
+ private FindReplaceDialog(Shell parent, TextComposite textComposite, boolean
replaceMode) {
+ super(parent, SWT.NONE);
+ this.textComposite = textComposite;
+ this.replaceMode = replaceMode;
+ this.props = PropsUi.getInstance();
+ }
+
+ /**
+ * Open a find or find-and-replace dialog for the given text composite.
+ *
+ * @param parent parent shell
+ * @param textComposite target editor
+ * @param replaceMode when true, show replace controls
+ */
+ public static void open(Shell parent, TextComposite textComposite, boolean
replaceMode) {
+ if (parent == null || textComposite == null || textComposite.isDisposed())
{
+ return;
+ }
+ new FindReplaceDialog(parent, textComposite, replaceMode).openDialog();
+ }
+
+ private void openDialog() {
+ Shell parent = getParent();
+ shell = new Shell(parent, SWT.DIALOG_TRIM | SWT.RESIZE |
SWT.APPLICATION_MODAL | SWT.SHEET);
+ PropsUi.setLook(shell);
+ shell.setImage(GuiResource.getInstance().getImageSearch());
+ shell.setText(
+ BaseMessages.getString(
+ PKG,
+ replaceMode
+ ? "FindReplaceDialog.Shell.ReplaceTitle"
+ : "FindReplaceDialog.Shell.FindTitle"));
+
+ FormLayout formLayout = new FormLayout();
+ formLayout.marginWidth = PropsUi.getFormMargin();
+ formLayout.marginHeight = PropsUi.getFormMargin();
+ shell.setLayout(formLayout);
+
+ int margin = PropsUi.getMargin();
+ int middle = props.getMiddlePct();
+
+ // Find
+ Label wlFind = new Label(shell, SWT.RIGHT);
+ PropsUi.setLook(wlFind);
+ wlFind.setText(BaseMessages.getString(PKG,
"FindReplaceDialog.Find.Label"));
+ FormData fdlFind = new FormData();
+ fdlFind.left = new FormAttachment(0, 0);
+ fdlFind.top = new FormAttachment(0, margin);
+ fdlFind.right = new FormAttachment(middle, -margin);
+ wlFind.setLayoutData(fdlFind);
+
+ wFind = new Text(shell, SWT.SINGLE | SWT.LEFT | SWT.BORDER);
+ PropsUi.setLook(wFind);
+ FormData fdFind = new FormData();
+ fdFind.left = new FormAttachment(middle, 0);
+ fdFind.top = new FormAttachment(wlFind, 0, SWT.CENTER);
+ fdFind.right = new FormAttachment(100, 0);
+ wFind.setLayoutData(fdFind);
+
+ // Replace (optional)
+ Label wlReplace = null;
+ if (replaceMode) {
+ wlReplace = new Label(shell, SWT.RIGHT);
+ PropsUi.setLook(wlReplace);
+ wlReplace.setText(BaseMessages.getString(PKG,
"FindReplaceDialog.Replace.Label"));
+ FormData fdlReplace = new FormData();
+ fdlReplace.left = new FormAttachment(0, 0);
+ fdlReplace.top = new FormAttachment(wFind, margin);
+ fdlReplace.right = new FormAttachment(middle, -margin);
+ wlReplace.setLayoutData(fdlReplace);
+
+ wReplace = new Text(shell, SWT.SINGLE | SWT.LEFT | SWT.BORDER);
+ PropsUi.setLook(wReplace);
+ FormData fdReplace = new FormData();
+ fdReplace.left = new FormAttachment(middle, 0);
+ fdReplace.top = new FormAttachment(wlReplace, 0, SWT.CENTER);
+ fdReplace.right = new FormAttachment(100, 0);
+ wReplace.setLayoutData(fdReplace);
+ }
+
+ // Case sensitive
+ wCaseSensitive = new Button(shell, SWT.CHECK);
+ PropsUi.setLook(wCaseSensitive);
+ wCaseSensitive.setText(BaseMessages.getString(PKG,
"FindReplaceDialog.CaseSensitive.Label"));
+ FormData fdCase = new FormData();
+ fdCase.left = new FormAttachment(middle, 0);
+ fdCase.top = new FormAttachment(replaceMode ? wReplace : wFind, margin);
+ fdCase.right = new FormAttachment(100, 0);
+ wCaseSensitive.setLayoutData(fdCase);
+
+ // Status line
+ wlStatus = new Label(shell, SWT.LEFT);
+ PropsUi.setLook(wlStatus);
+ FormData fdStatus = new FormData();
+ fdStatus.left = new FormAttachment(0, 0);
+ fdStatus.right = new FormAttachment(100, 0);
+ fdStatus.top = new FormAttachment(wCaseSensitive, margin);
+ wlStatus.setLayoutData(fdStatus);
+
+ // Buttons
+ Button wFindNext = new Button(shell, SWT.PUSH);
+ wFindNext.setText(BaseMessages.getString(PKG,
"FindReplaceDialog.FindNext.Button"));
+ wFindNext.addListener(SWT.Selection, e -> findNext(true));
+
+ Button wFindPrev = new Button(shell, SWT.PUSH);
+ wFindPrev.setText(BaseMessages.getString(PKG,
"FindReplaceDialog.FindPrevious.Button"));
+ wFindPrev.addListener(SWT.Selection, e -> findNext(false));
+
+ Button wReplaceOne = null;
+ Button wReplaceAll = null;
+ if (replaceMode) {
+ wReplaceOne = new Button(shell, SWT.PUSH);
+ wReplaceOne.setText(BaseMessages.getString(PKG,
"FindReplaceDialog.Replace.Button"));
+ wReplaceOne.addListener(SWT.Selection, e -> replaceOne());
+ wReplaceOne.setEnabled(textComposite.isEditable());
+
+ wReplaceAll = new Button(shell, SWT.PUSH);
+ wReplaceAll.setText(BaseMessages.getString(PKG,
"FindReplaceDialog.ReplaceAll.Button"));
+ wReplaceAll.addListener(SWT.Selection, e -> replaceAll());
+ wReplaceAll.setEnabled(textComposite.isEditable());
+ }
+
+ Button wClose = new Button(shell, SWT.PUSH);
+ wClose.setText(BaseMessages.getString(PKG, "System.Button.Close"));
+ wClose.addListener(SWT.Selection, e -> close());
+
+ Button[] buttons;
+ if (replaceMode) {
+ buttons = new Button[] {wFindNext, wFindPrev, wReplaceOne, wReplaceAll,
wClose};
+ } else {
+ buttons = new Button[] {wFindNext, wFindPrev, wClose};
+ }
+ BaseTransformDialog.positionBottomButtons(shell, buttons, margin,
wlStatus);
+
+ // Defaults
+ String selection = textComposite.getSelectionText();
+ if (StringUtils.isNotEmpty(selection) && !selection.contains("\n")) {
+ wFind.setText(selection);
+ } else if (StringUtils.isNotEmpty(lastFind)) {
+ wFind.setText(lastFind);
+ }
+ if (replaceMode && wReplace != null) {
+ wReplace.setText(lastReplace != null ? lastReplace : "");
+ }
+ wCaseSensitive.setSelection(lastCaseSensitive);
+
+ shell.setDefaultButton(wFindNext);
+ wFind.selectAll();
+ wFind.setFocus();
+
+ BaseDialog.defaultShellHandling(shell, c -> findNext(true), c -> close(),
false);
+ }
+
+ private void findNext(boolean forward) {
+ if (textComposite.isDisposed()) {
+ close();
+ return;
+ }
+ String query = wFind.getText();
+ if (StringUtils.isEmpty(query)) {
+ setStatus(BaseMessages.getString(PKG,
"FindReplaceDialog.Status.EmptyFind"));
+ return;
+ }
+ rememberOptions();
+
+ String content = textComposite.getText();
+ boolean caseSensitive = wCaseSensitive.getSelection();
+ int caret = textComposite.getCaretPosition();
+ int selectionLen = textComposite.getSelectionCount();
+
+ int found;
+ if (forward) {
+ // Start after current selection when it matches (or after caret)
+ int from = caret;
+ if (selectionLen > 0) {
+ // Caret is typically at the end of the selection; start after it
+ from = Math.max(caret, caret); // caret already end in most cases
+ // If selection equals query, step past it
+ String selected = textComposite.getSelectionText();
+ if (matches(selected, query, caseSensitive)) {
+ from = caret; // end of selection
+ }
+ }
+ found = TextFindSupport.findNext(content, query, from, caseSensitive);
+ if (found < 0 && from > 0) {
+ // wrap
+ found = TextFindSupport.findNext(content, query, 0, caseSensitive);
+ }
+ } else {
+ int from = caret - selectionLen - 1;
+ if (from < 0) {
+ from = content.length();
+ }
+ found = TextFindSupport.findPrevious(content, query, from,
caseSensitive);
+ if (found < 0) {
+ found = TextFindSupport.findPrevious(content, query, content.length(),
caseSensitive);
+ }
+ }
+
+ if (found >= 0) {
+ textComposite.setSelection(found, found + query.length());
+ textComposite.setFocus();
+ setStatus("");
+ } else {
+ setStatus(BaseMessages.getString(PKG,
"FindReplaceDialog.Status.NotFound"));
+ }
+ }
+
+ private void replaceOne() {
+ if (textComposite.isDisposed() || !textComposite.isEditable()) {
+ return;
+ }
+ String query = wFind.getText();
+ if (StringUtils.isEmpty(query)) {
+ setStatus(BaseMessages.getString(PKG,
"FindReplaceDialog.Status.EmptyFind"));
+ return;
+ }
+ rememberOptions();
+ String selected = textComposite.getSelectionText();
+ if (matches(selected, query, wCaseSensitive.getSelection())) {
+ String replacement = wReplace != null ? wReplace.getText() : "";
+ textComposite.insert(replacement);
+ textComposite.updateToolbar();
+ }
+ findNext(true);
+ }
+
+ private void replaceAll() {
+ if (textComposite.isDisposed() || !textComposite.isEditable()) {
+ return;
+ }
+ String query = wFind.getText();
+ if (StringUtils.isEmpty(query)) {
+ setStatus(BaseMessages.getString(PKG,
"FindReplaceDialog.Status.EmptyFind"));
+ return;
+ }
+ rememberOptions();
+ String replacement = wReplace != null ? wReplace.getText() : "";
+ TextFindSupport.ReplaceAllResult result =
+ TextFindSupport.replaceAll(
+ textComposite.getText(), query, replacement,
wCaseSensitive.getSelection());
+ if (result.count() > 0) {
+ int caret = textComposite.getCaretPosition();
+ textComposite.setText(result.text());
+ textComposite.setCaretPosition(Math.min(caret, result.text().length()));
+ textComposite.updateToolbar();
+ }
+ setStatus(
+ BaseMessages.getString(
+ PKG, "FindReplaceDialog.Status.ReplaceAllCount",
Integer.toString(result.count())));
+ }
+
+ private static boolean matches(String value, String query, boolean
caseSensitive) {
+ if (value == null || query == null) {
+ return false;
+ }
+ return caseSensitive ? value.equals(query) : value.equalsIgnoreCase(query);
+ }
+
+ private void rememberOptions() {
+ lastFind = wFind.getText();
+ if (wReplace != null) {
+ lastReplace = wReplace.getText();
+ }
+ lastCaseSensitive = wCaseSensitive.getSelection();
+ }
+
+ private void setStatus(String message) {
+ if (wlStatus != null && !wlStatus.isDisposed()) {
+ wlStatus.setText(message != null ? message : "");
+ }
+ }
+
+ private void close() {
+ props.setScreen(new WindowProperty(shell));
+ shell.dispose();
+ }
+}
diff --git
a/ui/src/main/java/org/apache/hop/ui/core/widget/JavaScriptStyledTextComp.java
b/ui/src/main/java/org/apache/hop/ui/core/widget/JavaScriptStyledTextComp.java
index 2aa394043d..1d84c364fc 100644
---
a/ui/src/main/java/org/apache/hop/ui/core/widget/JavaScriptStyledTextComp.java
+++
b/ui/src/main/java/org/apache/hop/ui/core/widget/JavaScriptStyledTextComp.java
@@ -40,7 +40,7 @@ public class JavaScriptStyledTextComp extends StyledTextVar {
boolean varsSensitive,
boolean variableIconOnTop) {
- super(variables, parent, style, varsSensitive, variableIconOnTop);
+ super(variables, parent, style, varsSensitive, variableIconOnTop, true,
STYLE_TYPE_JAVASCRIPT);
}
@Override
diff --git
a/ui/src/main/java/org/apache/hop/ui/core/widget/JavaStyledTextComp.java
b/ui/src/main/java/org/apache/hop/ui/core/widget/JavaStyledTextComp.java
index 7f5d7c32a9..126262653d 100644
--- a/ui/src/main/java/org/apache/hop/ui/core/widget/JavaStyledTextComp.java
+++ b/ui/src/main/java/org/apache/hop/ui/core/widget/JavaStyledTextComp.java
@@ -40,7 +40,7 @@ public class JavaStyledTextComp extends StyledTextVar {
boolean varsSensitive,
boolean variableIconOnTop) {
- super(variables, parent, style, varsSensitive, variableIconOnTop);
+ super(variables, parent, style, varsSensitive, variableIconOnTop, true,
STYLE_TYPE_JAVA);
}
@Override
diff --git
a/ui/src/main/java/org/apache/hop/ui/core/widget/SQLStyledTextComp.java
b/ui/src/main/java/org/apache/hop/ui/core/widget/SQLStyledTextComp.java
index b87c80706c..a38d6bfa60 100644
--- a/ui/src/main/java/org/apache/hop/ui/core/widget/SQLStyledTextComp.java
+++ b/ui/src/main/java/org/apache/hop/ui/core/widget/SQLStyledTextComp.java
@@ -39,7 +39,7 @@ public class SQLStyledTextComp extends StyledTextVar {
boolean varsSensitive,
boolean variableIconOnTop) {
- super(variables, parent, style, varsSensitive, variableIconOnTop);
+ super(variables, parent, style, varsSensitive, variableIconOnTop, true,
STYLE_TYPE_SQL);
}
@Override
diff --git
a/ui/src/main/java/org/apache/hop/ui/core/widget/ScriptStyledTextComp.java
b/ui/src/main/java/org/apache/hop/ui/core/widget/ScriptStyledTextComp.java
index c02361904e..df64ebc60b 100644
--- a/ui/src/main/java/org/apache/hop/ui/core/widget/ScriptStyledTextComp.java
+++ b/ui/src/main/java/org/apache/hop/ui/core/widget/ScriptStyledTextComp.java
@@ -43,7 +43,7 @@ public class ScriptStyledTextComp extends StyledTextVar {
boolean varsSensitive,
boolean variableIconOnTop) {
- super(variables, parent, style, varsSensitive, variableIconOnTop);
+ super(variables, parent, style, varsSensitive, variableIconOnTop, true,
STYLE_TYPE_SCRIPT);
}
@Override
diff --git a/ui/src/main/java/org/apache/hop/ui/core/widget/StyledTextComp.java
b/ui/src/main/java/org/apache/hop/ui/core/widget/StyledTextComp.java
index 08f76e404c..cb1d9096da 100644
--- a/ui/src/main/java/org/apache/hop/ui/core/widget/StyledTextComp.java
+++ b/ui/src/main/java/org/apache/hop/ui/core/widget/StyledTextComp.java
@@ -32,8 +32,8 @@ import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.layout.FormAttachment;
-import org.eclipse.swt.layout.FormLayout;
import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Menu;
@@ -46,11 +46,22 @@ public class StyledTextComp extends TextComposite {
private final Menu popupMenu;
public StyledTextComp(IVariables variables, Composite parent, int style) {
- this(variables, parent, style, true, false);
+ this(variables, parent, style, true, false, true, STYLE_TYPE_GENERIC);
+ }
+
+ /** Construct with a semantic {@code styleType} (applied before the toolbar
is built). */
+ public StyledTextComp(IVariables variables, Composite parent, int style,
String styleType) {
+ this(variables, parent, style, true, false, true, styleType);
}
public StyledTextComp(IVariables variables, Composite parent, int style,
boolean varsSensitive) {
- this(variables, parent, style, varsSensitive, false);
+ this(variables, parent, style, varsSensitive, false, true,
STYLE_TYPE_GENERIC);
+ }
+
+ /** Construct with vars sensitivity and a semantic {@code styleType}. */
+ public StyledTextComp(
+ IVariables variables, Composite parent, int style, boolean
varsSensitive, String styleType) {
+ this(variables, parent, style, varsSensitive, false, true, styleType);
}
public StyledTextComp(
@@ -59,18 +70,61 @@ public class StyledTextComp extends TextComposite {
int args,
boolean varsSensitive,
boolean variableIconOnTop) {
+ this(variables, parent, args, varsSensitive, variableIconOnTop, true,
STYLE_TYPE_GENERIC);
+ }
- super(parent, SWT.NONE);
+ public StyledTextComp(
+ IVariables variables,
+ Composite parent,
+ int args,
+ boolean varsSensitive,
+ boolean variableIconOnTop,
+ String styleType) {
+ this(variables, parent, args, varsSensitive, variableIconOnTop, true,
styleType);
+ }
+
+ public StyledTextComp(
+ IVariables variables,
+ Composite parent,
+ int args,
+ boolean varsSensitive,
+ boolean variableIconOnTop,
+ boolean toolbarEnabled) {
+ this(
+ variables,
+ parent,
+ args,
+ varsSensitive,
+ variableIconOnTop,
+ toolbarEnabled,
+ STYLE_TYPE_GENERIC);
+ }
+
+ public StyledTextComp(
+ IVariables variables,
+ Composite parent,
+ int args,
+ boolean varsSensitive,
+ boolean variableIconOnTop,
+ boolean toolbarEnabled,
+ String styleType) {
+
+ super(parent, SWT.NONE, toolbarEnabled, styleType);
textWidget = new Text(this, args);
popupMenu = new Menu(parent.getShell(), SWT.POP_UP);
- this.setLayout(new FormLayout());
-
buildingStyledTextMenu(popupMenu);
+ Control top = getTopControl();
+
// Default layout without variables
textWidget.setLayoutData(
- new FormDataBuilder().top().left().right(100, 0).bottom(100,
0).result());
+ new FormDataBuilder()
+ .top(top != null ? new FormAttachment(top, 0) : new
FormAttachment(0, 0))
+ .left()
+ .right(100, 0)
+ .bottom(100, 0)
+ .result());
// Special layout for variables decorator
if (varsSensitive) {
@@ -81,7 +135,12 @@ public class StyledTextComp extends TextComposite {
PropsUi.setLook(wIcon);
wIcon.setToolTipText(BaseMessages.getString(PKG,
"StyledTextComp.tooltip.InsertVariable"));
wIcon.setImage(GuiResource.getInstance().getImageVariableMini());
- wIcon.setLayoutData(new FormDataBuilder().top().right(100,
0).result());
+ if (top != null) {
+ wIcon.setLayoutData(
+ new FormDataBuilder().top(new FormAttachment(top, 0)).right(100,
0).result());
+ } else {
+ wIcon.setLayoutData(new FormDataBuilder().top().right(100,
0).result());
+ }
textWidget.setLayoutData(
new FormDataBuilder()
.top(new FormAttachment(wIcon, 0, 0))
@@ -95,10 +154,15 @@ public class StyledTextComp extends TextComposite {
controlDecoration.setToolTipText(
BaseMessages.getString(PKG,
"StyledTextComp.tooltip.InsertVariable"));
PropsUi.setLook(controlDecoration);
- controlDecoration.setLayoutData(new FormDataBuilder().top().right(100,
0).result());
+ if (top != null) {
+ controlDecoration.setLayoutData(
+ new FormDataBuilder().top(new FormAttachment(top, 0)).right(100,
0).result());
+ } else {
+ controlDecoration.setLayoutData(new
FormDataBuilder().top().right(100, 0).result());
+ }
textWidget.setLayoutData(
new FormDataBuilder()
- .top()
+ .top(top != null ? new FormAttachment(top, 0) : new
FormAttachment(0, 0))
.left()
.right(new FormAttachment(controlDecoration, 0, 0))
.bottom(100, 0)
@@ -119,7 +183,8 @@ public class StyledTextComp extends TextComposite {
@Override
public void setCaretPosition(int offset) {
- // Does not exist in Text widget
+ // Text has no caret offset API; move the selection to a zero-width range.
+ textWidget.setSelection(offset);
}
@Override
@@ -233,6 +298,9 @@ public class StyledTextComp extends TextComposite {
@Override
public void setEnabled(boolean enabled) {
textWidget.setEnabled(enabled);
+ if (getToolbar() != null && !getToolbar().isDisposed()) {
+ getToolbar().setEnabled(enabled);
+ }
}
@Override
diff --git a/ui/src/main/java/org/apache/hop/ui/core/widget/StyledTextVar.java
b/ui/src/main/java/org/apache/hop/ui/core/widget/StyledTextVar.java
index 78e4805de0..95ff36842c 100644
--- a/ui/src/main/java/org/apache/hop/ui/core/widget/StyledTextVar.java
+++ b/ui/src/main/java/org/apache/hop/ui/core/widget/StyledTextVar.java
@@ -38,8 +38,8 @@ import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.layout.FormAttachment;
-import org.eclipse.swt.layout.FormLayout;
import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Listener;
@@ -59,11 +59,22 @@ public class StyledTextVar extends TextComposite {
private boolean fullSelection = false;
public StyledTextVar(IVariables variables, Composite parent, int style) {
- this(variables, parent, style, true, false);
+ this(variables, parent, style, true, false, true, STYLE_TYPE_GENERIC);
+ }
+
+ /** Construct with a semantic {@code styleType} (applied before the toolbar
is built). */
+ public StyledTextVar(IVariables variables, Composite parent, int style,
String styleType) {
+ this(variables, parent, style, true, false, true, styleType);
}
public StyledTextVar(IVariables variables, Composite parent, int style,
boolean varsSensitive) {
- this(variables, parent, style, varsSensitive, false);
+ this(variables, parent, style, varsSensitive, false, true,
STYLE_TYPE_GENERIC);
+ }
+
+ /** Construct with vars sensitivity and a semantic {@code styleType}. */
+ public StyledTextVar(
+ IVariables variables, Composite parent, int style, boolean
varsSensitive, String styleType) {
+ this(variables, parent, style, varsSensitive, false, true, styleType);
}
public StyledTextVar(
@@ -72,22 +83,67 @@ public class StyledTextVar extends TextComposite {
int style,
boolean varsSensitive,
boolean variableIconOnTop) {
+ this(variables, parent, style, varsSensitive, variableIconOnTop, true,
STYLE_TYPE_GENERIC);
+ }
+
+ public StyledTextVar(
+ IVariables variables,
+ Composite parent,
+ int style,
+ boolean varsSensitive,
+ boolean variableIconOnTop,
+ String styleType) {
+ this(variables, parent, style, varsSensitive, variableIconOnTop, true,
styleType);
+ }
+
+ public StyledTextVar(
+ IVariables variables,
+ Composite parent,
+ int style,
+ boolean varsSensitive,
+ boolean variableIconOnTop,
+ boolean toolbarEnabled) {
+ this(
+ variables,
+ parent,
+ style,
+ varsSensitive,
+ variableIconOnTop,
+ toolbarEnabled,
+ STYLE_TYPE_GENERIC);
+ }
- super(parent, SWT.NONE);
+ public StyledTextVar(
+ IVariables variables,
+ Composite parent,
+ int style,
+ boolean varsSensitive,
+ boolean variableIconOnTop,
+ boolean toolbarEnabled,
+ String styleType) {
+
+ super(parent, SWT.NONE, toolbarEnabled, styleType);
undoStack = new LinkedList<>();
redoStack = new LinkedList<>();
wText = new StyledText(this, style);
wPopupMenu = new Menu(parent.getShell(), SWT.POP_UP);
- this.setLayout(new FormLayout());
buildingStyledTextMenu(wPopupMenu);
addUndoRedoSupport();
+ Control top = getTopControl();
+
// Default layout without variables
- wText.setLayoutData(new FormDataBuilder().top().left().right(100,
0).bottom(100, 0).result());
+ wText.setLayoutData(
+ new FormDataBuilder()
+ .top(top != null ? new FormAttachment(top, 0) : new
FormAttachment(0, 0))
+ .left()
+ .right(100, 0)
+ .bottom(100, 0)
+ .result());
// Special layout for variables decorator
if (varsSensitive) {
@@ -97,7 +153,12 @@ public class StyledTextVar extends TextComposite {
PropsUi.setLook(wIcon);
wIcon.setToolTipText(BaseMessages.getString(PKG,
"StyledTextComp.tooltip.InsertVariable"));
wIcon.setImage(GuiResource.getInstance().getImageVariableMini());
- wIcon.setLayoutData(new FormDataBuilder().top().right(100,
0).result());
+ if (top != null) {
+ wIcon.setLayoutData(
+ new FormDataBuilder().top(new FormAttachment(top, 0)).right(100,
0).result());
+ } else {
+ wIcon.setLayoutData(new FormDataBuilder().top().right(100,
0).result());
+ }
wText.setLayoutData(
new FormDataBuilder()
.top(new FormAttachment(wIcon, 0, 0))
@@ -111,10 +172,15 @@ public class StyledTextVar extends TextComposite {
controlDecoration.setToolTipText(
BaseMessages.getString(PKG,
"StyledTextComp.tooltip.InsertVariable"));
PropsUi.setLook(controlDecoration);
- controlDecoration.setLayoutData(new FormDataBuilder().top().right(100,
0).result());
+ if (top != null) {
+ controlDecoration.setLayoutData(
+ new FormDataBuilder().top(new FormAttachment(top, 0)).right(100,
0).result());
+ } else {
+ controlDecoration.setLayoutData(new
FormDataBuilder().top().right(100, 0).result());
+ }
wText.setLayoutData(
new FormDataBuilder()
- .top()
+ .top(top != null ? new FormAttachment(top, 0) : new
FormAttachment(0, 0))
.left()
.right(new FormAttachment(controlDecoration, 0, 0))
.bottom(100, 0)
@@ -261,6 +327,9 @@ public class StyledTextVar extends TextComposite {
@Override
public void setEnabled(boolean enabled) {
wText.setEnabled(enabled);
+ if (getToolbar() != null && !getToolbar().isDisposed()) {
+ getToolbar().setEnabled(enabled);
+ }
// StyledText component does not get the "disabled" look, so it needs to
be applied explicitly
if (Display.getDefault() != null) {
wText.setBackground(
@@ -270,6 +339,16 @@ public class StyledTextVar extends TextComposite {
}
}
+ @Override
+ protected boolean canUndo() {
+ return !undoStack.isEmpty();
+ }
+
+ @Override
+ protected boolean canRedo() {
+ return !redoStack.isEmpty();
+ }
+
@Override
public void cut() {
wText.cut();
diff --git a/ui/src/main/java/org/apache/hop/ui/core/widget/TextComposite.java
b/ui/src/main/java/org/apache/hop/ui/core/widget/TextComposite.java
index d16a6c2df4..acc34aae23 100644
--- a/ui/src/main/java/org/apache/hop/ui/core/widget/TextComposite.java
+++ b/ui/src/main/java/org/apache/hop/ui/core/widget/TextComposite.java
@@ -17,25 +17,147 @@
package org.apache.hop.ui.core.widget;
+import java.util.ArrayList;
import java.util.List;
+import lombok.Getter;
+import lombok.Setter;
import org.apache.commons.lang3.StringUtils;
+import org.apache.hop.core.Props;
+import org.apache.hop.core.gui.plugin.GuiPlugin;
+import org.apache.hop.core.gui.plugin.toolbar.GuiToolbarElement;
import org.apache.hop.core.util.Utils;
import org.apache.hop.i18n.BaseMessages;
import org.apache.hop.ui.core.ConstUi;
+import org.apache.hop.ui.core.PropsUi;
+import org.apache.hop.ui.core.dialog.FindReplaceDialog;
import org.apache.hop.ui.core.gui.GuiResource;
+import org.apache.hop.ui.core.gui.GuiToolbarWidgets;
+import org.apache.hop.ui.core.gui.IToolbarContainer;
+import org.apache.hop.ui.hopgui.ToolbarFacade;
import org.eclipse.swt.SWT;
import org.eclipse.swt.dnd.Clipboard;
import org.eclipse.swt.dnd.TextTransfer;
import org.eclipse.swt.events.MenuDetectListener;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.layout.FormAttachment;
+import org.eclipse.swt.layout.FormData;
+import org.eclipse.swt.layout.FormLayout;
import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.MenuItem;
+/**
+ * Multi-line text editor composite used across Hop dialogs (SQL, scripts,
logs, etc.).
+ *
+ * <p>Supports an optional toolbar (undo/redo, clipboard, find/replace) that
plugins can extend via
+ * {@link GuiToolbarElement} with {@link #ID_TOOLBAR} as the root, the same
way {@link TableView}
+ * toolbars work.
+ *
+ * <p>Toolbar plugins receive a {@link TextComposite} instance. To know what
kind of text is being
+ * edited, use {@code getStyleType()} (e.g. {@link #STYLE_TYPE_SQL}) rather
than {@code instanceof}
+ * checks on subclasses — on Hop Web many specialized editors are replaced by
a plain {@link
+ * StyledTextComp} with the same style type set.
+ *
+ * <pre>{@code
+ * @GuiToolbarElement(root = TextComposite.ID_TOOLBAR, id = "...", image =
"...")
+ * public static void mySqlAction(TextComposite text) {
+ * if (!TextComposite.STYLE_TYPE_SQL.equals(text.getStyleType())) {
+ * return;
+ * }
+ * // ...
+ * }
+ * }</pre>
+ */
+@GuiPlugin
public abstract class TextComposite extends Composite {
private static final Class<?> PKG = StyledTextComp.class;
+ public static final String ID_TOOLBAR = "TextComposite-Toolbar";
+ public static final String ID_TOOLBAR_UNDO =
"textcomposite-toolbar-10000-undo";
+ public static final String ID_TOOLBAR_REDO =
"textcomposite-toolbar-10010-redo";
+ public static final String ID_TOOLBAR_CUT =
"textcomposite-toolbar-10100-cut";
+ public static final String ID_TOOLBAR_COPY =
"textcomposite-toolbar-10110-copy";
+ public static final String ID_TOOLBAR_PASTE =
"textcomposite-toolbar-10120-paste";
+ public static final String ID_TOOLBAR_SELECT_ALL =
"textcomposite-toolbar-10130-select-all";
+ public static final String ID_TOOLBAR_FIND =
"textcomposite-toolbar-10200-find";
+ public static final String ID_TOOLBAR_FIND_REPLACE =
"textcomposite-toolbar-10210-find-replace";
+
+ /** Default / unspecified multi-line text. */
+ public static final String STYLE_TYPE_GENERIC = "Generic";
+
+ /** SQL (or SQL-like) script. */
+ public static final String STYLE_TYPE_SQL = "SQL";
+
+ /** JavaScript source. */
+ public static final String STYLE_TYPE_JAVASCRIPT = "JavaScript";
+
+ /** Java source. */
+ public static final String STYLE_TYPE_JAVA = "Java";
+
+ /** Generic scripting language (engine may vary). */
+ public static final String STYLE_TYPE_SCRIPT = "Script";
+
+ /** Execution or application log output. */
+ public static final String STYLE_TYPE_LOG = "Log";
+
+ /** Unified / git-style diff. */
+ public static final String STYLE_TYPE_DIFF = "Diff";
+
+ /** Regular expression. */
+ public static final String STYLE_TYPE_REGEX = "Regex";
+
+ /** Formula / expression language. */
+ public static final String STYLE_TYPE_FORMULA = "Formula";
+
+ /** JSON document or query payload. */
+ public static final String STYLE_TYPE_JSON = "JSON";
+
+ /** Free-form human-readable text / message body. */
+ public static final String STYLE_TYPE_TEXT = "Text";
+
+ /** Cassandra Query Language. */
+ public static final String STYLE_TYPE_CQL = "CQL";
+
+ /** Salesforce Object Query Language. */
+ public static final String STYLE_TYPE_SOQL = "SOQL";
+
+ /** Drools rules. */
+ public static final String STYLE_TYPE_DROOLS = "Drools";
+
+ @Getter private final boolean toolbarEnabled;
+ @Getter @Setter private Control toolbar;
+ @Getter @Setter private GuiToolbarWidgets toolbarWidgets;
+
+ /**
+ * Semantic type of the text being edited (for toolbar plugins and similar
contributors). Defaults
+ * to {@link #STYLE_TYPE_GENERIC}. Prefer the {@code STYLE_TYPE_*}
constants; plugins may use
+ * other free-form values.
+ *
+ * <p><strong>Must be set via the constructor</strong> so that {@link
+ * org.apache.hop.core.gui.plugin.toolbar.GuiToolbarElementFilter} sees the
correct value when the
+ * toolbar is built. Calling {@link #setStyleType(String)} after
construction does not rebuild the
+ * toolbar or re-run filters.
+ */
+ @Getter private String styleType = STYLE_TYPE_GENERIC;
+
+ private final List<String> removeToolItems;
+ private final PropsUi props;
+
+ /**
+ * Sets the semantic style type of this editor.
+ *
+ * <p>Does <strong>not</strong> rebuild the toolbar or re-evaluate toolbar
filters. Prefer passing
+ * {@code styleType} to the constructor so plugins that filter on type (for
example JSON format)
+ * work correctly.
+ *
+ * @param styleType type label; {@code null} resets to {@link
#STYLE_TYPE_GENERIC}
+ */
+ public void setStyleType(String styleType) {
+ this.styleType = styleType != null ? styleType : STYLE_TYPE_GENERIC;
+ }
+
/**
* Constructs a new instance of this class given its parent and a style
value describing its
* behavior and appearance.
@@ -51,7 +173,212 @@ public abstract class TextComposite extends Composite {
* @throws IllegalArgumentException
*/
public TextComposite(Composite parent, int style) {
+ this(parent, style, true, new ArrayList<>(), STYLE_TYPE_GENERIC);
+ }
+
+ /**
+ * @param parent parent composite
+ * @param style SWT style for this composite (usually {@link SWT#NONE}; the
text control has its
+ * own style)
+ * @param toolbarEnabled whether a toolbar may be shown above the text
+ */
+ public TextComposite(Composite parent, int style, boolean toolbarEnabled) {
+ this(parent, style, toolbarEnabled, new ArrayList<>(), STYLE_TYPE_GENERIC);
+ }
+
+ /**
+ * @param parent parent composite
+ * @param style SWT style for this composite
+ * @param toolbarEnabled whether a toolbar may be shown above the text
+ * @param styleType semantic content type ({@link #STYLE_TYPE_SQL}, …); used
by toolbar filters
+ */
+ public TextComposite(Composite parent, int style, boolean toolbarEnabled,
String styleType) {
+ this(parent, style, toolbarEnabled, new ArrayList<>(), styleType);
+ }
+
+ /**
+ * @param parent parent composite
+ * @param style SWT style for this composite
+ * @param toolbarEnabled whether a toolbar may be shown above the text
+ * @param removeToolItems toolbar item IDs to hide for this instance
+ */
+ public TextComposite(
+ Composite parent, int style, boolean toolbarEnabled, List<String>
removeToolItems) {
+ this(parent, style, toolbarEnabled, removeToolItems, STYLE_TYPE_GENERIC);
+ }
+
+ /**
+ * Full constructor. {@code styleType} is applied <em>before</em> the
toolbar is created so {@link
+ * org.apache.hop.core.gui.plugin.toolbar.GuiToolbarElementFilter} methods
can read it.
+ *
+ * @param parent parent composite
+ * @param style SWT style for this composite
+ * @param toolbarEnabled whether a toolbar may be shown above the text
+ * @param removeToolItems toolbar item IDs to hide for this instance
+ * @param styleType semantic content type ({@link #STYLE_TYPE_SQL}, …)
+ */
+ public TextComposite(
+ Composite parent,
+ int style,
+ boolean toolbarEnabled,
+ List<String> removeToolItems,
+ String styleType) {
super(parent, style);
+ this.props = PropsUi.getInstance();
+ this.toolbarEnabled = toolbarEnabled;
+ this.removeToolItems =
+ removeToolItems != null ? new ArrayList<>(removeToolItems) : new
ArrayList<>();
+ this.styleType = styleType != null ? styleType : STYLE_TYPE_GENERIC;
+
+ FormLayout layout = new FormLayout();
+ layout.marginLeft = 0;
+ layout.marginRight = 0;
+ layout.marginTop = 0;
+ layout.marginBottom = 0;
+ setLayout(layout);
+
+ addToolbar();
+ }
+
+ /**
+ * Control that content (the text widget and variable icon) should attach
below, or {@code null}
+ * when no toolbar is shown.
+ */
+ public Control getTopControl() {
+ return toolbar;
+ }
+
+ protected void addToolbar() {
+ toolbarWidgets = new GuiToolbarWidgets();
+ // Register under TextComposite so toolbar listeners and plugin static
methods resolve this
+ // instance for every subclass (StyledTextComp, SQLStyledTextComp, …).
+ toolbarWidgets.registerGuiPluginObject(TextComposite.class.getName(),
this);
+
+ if (toolbarEnabled && props.isShowTextCompositeToolbar()) {
+ IToolbarContainer toolBarContainer =
+ ToolbarFacade.createToolbarContainer(this, SWT.WRAP | SWT.LEFT |
SWT.HORIZONTAL);
+ toolbar = toolBarContainer.getControl();
+ FormData fdToolBar = new FormData();
+ fdToolBar.left = new FormAttachment(0, 0);
+ fdToolBar.top = new FormAttachment(0, 0);
+ fdToolBar.right = new FormAttachment(100, 0);
+ toolbar.setLayoutData(fdToolBar);
+ PropsUi.setLook(toolbar, Props.WIDGET_STYLE_TOOLBAR);
+
+ toolbarWidgets.createToolbarWidgets(toolBarContainer, ID_TOOLBAR,
removeToolItems);
+ toolbar.pack();
+ }
+ }
+
+ /** Refresh enablement of built-in toolbar buttons based on selection and
editability. */
+ public void updateToolbar() {
+ if (toolbarWidgets == null || toolbar == null || toolbar.isDisposed()) {
+ return;
+ }
+ boolean editable = isEditable();
+ boolean hasSelection = getSelectionCount() > 0;
+ boolean canPaste = editable && checkPaste();
+
+ toolbarWidgets.enableToolbarItem(ID_TOOLBAR_UNDO, canUndo());
+ toolbarWidgets.enableToolbarItem(ID_TOOLBAR_REDO, canRedo());
+ toolbarWidgets.enableToolbarItem(ID_TOOLBAR_CUT, editable && hasSelection);
+ toolbarWidgets.enableToolbarItem(ID_TOOLBAR_COPY, hasSelection);
+ toolbarWidgets.enableToolbarItem(ID_TOOLBAR_PASTE, canPaste);
+ toolbarWidgets.enableToolbarItem(ID_TOOLBAR_SELECT_ALL, getCharCount() >
0);
+ toolbarWidgets.enableToolbarItem(ID_TOOLBAR_FIND, true);
+ toolbarWidgets.enableToolbarItem(ID_TOOLBAR_FIND_REPLACE, editable);
+ }
+
+ // --- Toolbar actions (static so they resolve for every TextComposite
subclass) ---
+
+ @GuiToolbarElement(
+ root = ID_TOOLBAR,
+ id = ID_TOOLBAR_UNDO,
+ image = "ui/images/undo.svg",
+ toolTip = "i18n::TextComposite.ToolBarWidget.Undo.ToolTip")
+ public static void toolbarUndo(TextComposite text) {
+ text.undo();
+ text.updateToolbar();
+ }
+
+ @GuiToolbarElement(
+ root = ID_TOOLBAR,
+ id = ID_TOOLBAR_REDO,
+ image = "ui/images/redo.svg",
+ toolTip = "i18n::TextComposite.ToolBarWidget.Redo.ToolTip")
+ public static void toolbarRedo(TextComposite text) {
+ text.redo();
+ text.updateToolbar();
+ }
+
+ @GuiToolbarElement(
+ root = ID_TOOLBAR,
+ id = ID_TOOLBAR_CUT,
+ image = "ui/images/cut.svg",
+ toolTip = "i18n::TextComposite.ToolBarWidget.Cut.ToolTip",
+ separator = true)
+ public static void toolbarCut(TextComposite text) {
+ text.cut();
+ text.updateToolbar();
+ }
+
+ @GuiToolbarElement(
+ root = ID_TOOLBAR,
+ id = ID_TOOLBAR_COPY,
+ image = "ui/images/copy.svg",
+ toolTip = "i18n::TextComposite.ToolBarWidget.Copy.ToolTip")
+ public static void toolbarCopy(TextComposite text) {
+ text.copy();
+ text.updateToolbar();
+ }
+
+ @GuiToolbarElement(
+ root = ID_TOOLBAR,
+ id = ID_TOOLBAR_PASTE,
+ image = "ui/images/paste.svg",
+ toolTip = "i18n::TextComposite.ToolBarWidget.Paste.ToolTip")
+ public static void toolbarPaste(TextComposite text) {
+ text.paste();
+ text.updateToolbar();
+ }
+
+ @GuiToolbarElement(
+ root = ID_TOOLBAR,
+ id = ID_TOOLBAR_SELECT_ALL,
+ image = "ui/images/select-all.svg",
+ toolTip = "i18n::TextComposite.ToolBarWidget.SelectAll.ToolTip")
+ public static void toolbarSelectAll(TextComposite text) {
+ text.selectAll();
+ text.updateToolbar();
+ }
+
+ @GuiToolbarElement(
+ root = ID_TOOLBAR,
+ id = ID_TOOLBAR_FIND,
+ image = "ui/images/search.svg",
+ toolTip = "i18n::TextComposite.ToolBarWidget.Find.ToolTip",
+ separator = true)
+ public static void toolbarFind(TextComposite text) {
+ text.find();
+ }
+
+ @GuiToolbarElement(
+ root = ID_TOOLBAR,
+ id = ID_TOOLBAR_FIND_REPLACE,
+ image = "ui/images/edit.svg",
+ toolTip = "i18n::TextComposite.ToolBarWidget.FindReplace.ToolTip")
+ public static void toolbarFindReplace(TextComposite text) {
+ text.findAndReplace();
+ }
+
+ /** Open the find dialog (Ctrl+F). */
+ public void find() {
+ FindReplaceDialog.open(getShell(), this, false);
+ }
+
+ /** Open the find and replace dialog (Ctrl+H). */
+ public void findAndReplace() {
+ FindReplaceDialog.open(getShell(), this, true);
}
public abstract void addModifyListener(ModifyListener lsMod);
@@ -262,6 +589,16 @@ public abstract class TextComposite extends Composite {
return false;
}
+ /** Whether undo is currently available. */
+ protected boolean canUndo() {
+ return false;
+ }
+
+ /** Whether redo is currently available. */
+ protected boolean canRedo() {
+ return false;
+ }
+
protected void buildingStyledTextMenu(Menu popupMenu) {
if (isSupportUnoRedo()) {
@@ -271,7 +608,12 @@ public abstract class TextComposite extends Composite {
undoItem.setImage(
GuiResource.getInstance()
.getImage("ui/images/undo.svg", ConstUi.SMALL_ICON_SIZE,
ConstUi.SMALL_ICON_SIZE));
- undoItem.addListener(SWT.Selection, event -> undo());
+ undoItem.addListener(
+ SWT.Selection,
+ event -> {
+ undo();
+ updateToolbar();
+ });
final MenuItem redoItem = new MenuItem(popupMenu, SWT.PUSH);
redoItem.setText(
@@ -279,7 +621,12 @@ public abstract class TextComposite extends Composite {
redoItem.setImage(
GuiResource.getInstance()
.getImage("ui/images/redo.svg", ConstUi.SMALL_ICON_SIZE,
ConstUi.SMALL_ICON_SIZE));
- redoItem.addListener(SWT.Selection, event -> redo());
+ redoItem.addListener(
+ SWT.Selection,
+ event -> {
+ redo();
+ updateToolbar();
+ });
new MenuItem(popupMenu, SWT.SEPARATOR);
}
@@ -290,7 +637,12 @@ public abstract class TextComposite extends Composite {
cutItem.setImage(
GuiResource.getInstance()
.getImage("ui/images/cut.svg", ConstUi.SMALL_ICON_SIZE,
ConstUi.SMALL_ICON_SIZE));
- cutItem.addListener(SWT.Selection, event -> cut());
+ cutItem.addListener(
+ SWT.Selection,
+ event -> {
+ cut();
+ updateToolbar();
+ });
final MenuItem copyItem = new MenuItem(popupMenu, SWT.PUSH);
copyItem.setText(
@@ -306,7 +658,12 @@ public abstract class TextComposite extends Composite {
pasteItem.setImage(
GuiResource.getInstance()
.getImage("ui/images/paste.svg", ConstUi.SMALL_ICON_SIZE,
ConstUi.SMALL_ICON_SIZE));
- pasteItem.addListener(SWT.Selection, event -> paste());
+ pasteItem.addListener(
+ SWT.Selection,
+ event -> {
+ paste();
+ updateToolbar();
+ });
new MenuItem(popupMenu, SWT.SEPARATOR);
@@ -318,7 +675,31 @@ public abstract class TextComposite extends Composite {
GuiResource.getInstance()
.getImage(
"ui/images/select-all.svg", ConstUi.SMALL_ICON_SIZE,
ConstUi.SMALL_ICON_SIZE));
- selectAllItem.addListener(SWT.Selection, event -> selectAll());
+ selectAllItem.addListener(
+ SWT.Selection,
+ event -> {
+ selectAll();
+ updateToolbar();
+ });
+
+ new MenuItem(popupMenu, SWT.SEPARATOR);
+
+ final MenuItem findItem = new MenuItem(popupMenu, SWT.PUSH);
+ findItem.setText(
+ OsHelper.customizeMenuitemText(BaseMessages.getString(PKG,
"WidgetDialog.Styled.Find")));
+ findItem.setImage(
+ GuiResource.getInstance()
+ .getImage("ui/images/search.svg", ConstUi.SMALL_ICON_SIZE,
ConstUi.SMALL_ICON_SIZE));
+ findItem.addListener(SWT.Selection, event -> find());
+
+ final MenuItem findReplaceItem = new MenuItem(popupMenu, SWT.PUSH);
+ findReplaceItem.setText(
+ OsHelper.customizeMenuitemText(
+ BaseMessages.getString(PKG, "WidgetDialog.Styled.FindReplace")));
+ findReplaceItem.setImage(
+ GuiResource.getInstance()
+ .getImage("ui/images/edit.svg", ConstUi.SMALL_ICON_SIZE,
ConstUi.SMALL_ICON_SIZE));
+ findReplaceItem.addListener(SWT.Selection, event -> findAndReplace());
addListener(
SWT.KeyDown,
@@ -328,21 +709,27 @@ public abstract class TextComposite extends Composite {
&& (event.stateMask & SWT.MOD1) != 0
&& (event.stateMask & SWT.MOD2) != 0) {
redo();
+ updateToolbar();
} else if (isSupportUnoRedo()
&& event.keyCode == 'z'
&& (event.stateMask & SWT.MOD1) != 0) {
undo();
+ updateToolbar();
} else if (event.keyCode == 'a' && (event.stateMask & SWT.MOD1) !=
0) {
selectAll();
+ updateToolbar();
} else if (event.keyCode == 'f' && (event.stateMask & SWT.MOD1) !=
0) {
- // TODO: implement FIND
- // find();
+ find();
+ event.doit = false;
} else if (event.keyCode == 'h' && (event.stateMask & SWT.MOD1) !=
0) {
- // TODO: implement FIND AND REPLACE
- // findAndReplace();
+ findAndReplace();
+ event.doit = false;
}
});
+ addListener(SWT.Modify, event -> updateToolbar());
+ addListener(SWT.Selection, event -> updateToolbar());
+
addMenuDetectListener(
event -> {
pasteItem.setEnabled(checkPaste());
@@ -353,8 +740,19 @@ public abstract class TextComposite extends Composite {
cutItem.setEnabled(false);
copyItem.setEnabled(false);
}
+ findReplaceItem.setEnabled(isEditable());
+ updateToolbar();
});
setMenu(popupMenu);
+
+ // Initial enablement once the text control exists
+ getDisplay()
+ .asyncExec(
+ () -> {
+ if (!isDisposed()) {
+ updateToolbar();
+ }
+ });
}
}
diff --git
a/ui/src/main/java/org/apache/hop/ui/core/widget/TextFindSupport.java
b/ui/src/main/java/org/apache/hop/ui/core/widget/TextFindSupport.java
new file mode 100644
index 0000000000..09468a7844
--- /dev/null
+++ b/ui/src/main/java/org/apache/hop/ui/core/widget/TextFindSupport.java
@@ -0,0 +1,106 @@
+/*
+ * 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.core.widget;
+
+import java.util.Locale;
+import org.apache.commons.lang3.StringUtils;
+
+/** Pure string helpers for find / replace in a {@link TextComposite}. */
+public final class TextFindSupport {
+
+ private TextFindSupport() {}
+
+ /**
+ * Find the next occurrence of {@code query} in {@code text} at or after
{@code from}.
+ *
+ * @return start index of the match, or -1 if not found
+ */
+ public static int findNext(String text, String query, int from, boolean
caseSensitive) {
+ if (StringUtils.isEmpty(text) || StringUtils.isEmpty(query)) {
+ return -1;
+ }
+ int start = Math.max(0, from);
+ if (start > text.length()) {
+ return -1;
+ }
+ if (caseSensitive) {
+ return text.indexOf(query, start);
+ }
+ return
text.toLowerCase(Locale.ROOT).indexOf(query.toLowerCase(Locale.ROOT), start);
+ }
+
+ /**
+ * Find the previous occurrence of {@code query} in {@code text} ending at
or before {@code from}.
+ *
+ * @return start index of the match, or -1 if not found
+ */
+ public static int findPrevious(String text, String query, int from, boolean
caseSensitive) {
+ if (StringUtils.isEmpty(text) || StringUtils.isEmpty(query) || from < 0) {
+ return -1;
+ }
+ int end = Math.min(text.length(), from);
+ if (caseSensitive) {
+ return text.lastIndexOf(query, end);
+ }
+ return
text.toLowerCase(Locale.ROOT).lastIndexOf(query.toLowerCase(Locale.ROOT), end);
+ }
+
+ /**
+ * Replace every occurrence of {@code query} with {@code replacement}.
+ *
+ * @return number of replacements performed
+ */
+ public static ReplaceAllResult replaceAll(
+ String text, String query, String replacement, boolean caseSensitive) {
+ if (StringUtils.isEmpty(text) || StringUtils.isEmpty(query)) {
+ return new ReplaceAllResult(text, 0);
+ }
+ String safeReplacement = replacement != null ? replacement : "";
+ if (caseSensitive) {
+ int count = 0;
+ int from = 0;
+ StringBuilder result = new StringBuilder();
+ int idx;
+ while ((idx = text.indexOf(query, from)) >= 0) {
+ result.append(text, from, idx);
+ result.append(safeReplacement);
+ from = idx + query.length();
+ count++;
+ }
+ result.append(text, from, text.length());
+ return new ReplaceAllResult(result.toString(), count);
+ }
+
+ String lowerText = text.toLowerCase(Locale.ROOT);
+ String lowerQuery = query.toLowerCase(Locale.ROOT);
+ int count = 0;
+ int from = 0;
+ StringBuilder result = new StringBuilder();
+ int idx;
+ while ((idx = lowerText.indexOf(lowerQuery, from)) >= 0) {
+ result.append(text, from, idx);
+ result.append(safeReplacement);
+ from = idx + query.length();
+ count++;
+ }
+ result.append(text, from, text.length());
+ return new ReplaceAllResult(result.toString(), count);
+ }
+
+ public record ReplaceAllResult(String text, int count) {}
+}
diff --git
a/ui/src/main/java/org/apache/hop/ui/hopgui/dialog/NotePadDialog.java
b/ui/src/main/java/org/apache/hop/ui/hopgui/dialog/NotePadDialog.java
index e7f54b5282..20c33452b9 100644
--- a/ui/src/main/java/org/apache/hop/ui/hopgui/dialog/NotePadDialog.java
+++ b/ui/src/main/java/org/apache/hop/ui/hopgui/dialog/NotePadDialog.java
@@ -28,6 +28,7 @@ import org.apache.hop.ui.core.dialog.BaseDialog;
import org.apache.hop.ui.core.gui.GuiResource;
import org.apache.hop.ui.core.gui.WindowProperty;
import org.apache.hop.ui.core.widget.StyledTextComp;
+import org.apache.hop.ui.core.widget.TextComposite;
import org.apache.hop.ui.pipeline.transform.BaseTransformDialog;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.CCombo;
@@ -274,8 +275,8 @@ public class NotePadDialog extends Dialog {
new StyledTextComp(
variables,
wNoteContentComp,
- SWT.MULTI | SWT.LEFT | SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL);
-
+ SWT.MULTI | SWT.LEFT | SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL,
+ TextComposite.STYLE_TYPE_TEXT);
wDesc.setText("");
// Standard widget look (theme-aware). Do not paint note fill/font colors
into the editor —
// those are canvas-only and break dark mode. Use a fixed-width font for
Markdown source.
diff --git
a/ui/src/main/java/org/apache/hop/ui/hopgui/file/pipeline/delegates/HopGuiPipelineLogDelegate.java
b/ui/src/main/java/org/apache/hop/ui/hopgui/file/pipeline/delegates/HopGuiPipelineLogDelegate.java
index 0ca0089aaa..2110d67393 100644
---
a/ui/src/main/java/org/apache/hop/ui/hopgui/file/pipeline/delegates/HopGuiPipelineLogDelegate.java
+++
b/ui/src/main/java/org/apache/hop/ui/hopgui/file/pipeline/delegates/HopGuiPipelineLogDelegate.java
@@ -134,7 +134,8 @@ public class HopGuiPipelineLogDelegate {
new StyledTextComp(
pipelineGraph.getVariables(),
pipelineLogComposite,
- SWT.READ_ONLY | SWT.BORDER | SWT.MULTI | SWT.V_SCROLL |
SWT.H_SCROLL);
+ SWT.READ_ONLY | SWT.BORDER | SWT.MULTI | SWT.V_SCROLL |
SWT.H_SCROLL,
+ TextComposite.STYLE_TYPE_LOG);
} else {
pipelineLogText =
new StyledTextVar(
@@ -142,7 +143,8 @@ public class HopGuiPipelineLogDelegate {
pipelineLogComposite,
SWT.READ_ONLY | SWT.BORDER | SWT.MULTI | SWT.V_SCROLL |
SWT.H_SCROLL,
false,
- false);
+ false,
+ TextComposite.STYLE_TYPE_LOG);
// Error highlighting is applied directly in HopGuiLogBrowser when
adding lines
}
PropsUi.setLook(pipelineLogText);
diff --git
a/ui/src/main/java/org/apache/hop/ui/hopgui/file/workflow/delegates/HopGuiWorkflowLogDelegate.java
b/ui/src/main/java/org/apache/hop/ui/hopgui/file/workflow/delegates/HopGuiWorkflowLogDelegate.java
index 6cec4b4d52..61a5a1c502 100644
---
a/ui/src/main/java/org/apache/hop/ui/hopgui/file/workflow/delegates/HopGuiWorkflowLogDelegate.java
+++
b/ui/src/main/java/org/apache/hop/ui/hopgui/file/workflow/delegates/HopGuiWorkflowLogDelegate.java
@@ -135,7 +135,8 @@ public class HopGuiWorkflowLogDelegate {
new StyledTextComp(
workflowGraph.getVariables(),
workflowLogComposite,
- SWT.READ_ONLY | SWT.BORDER | SWT.MULTI | SWT.V_SCROLL |
SWT.H_SCROLL);
+ SWT.READ_ONLY | SWT.BORDER | SWT.MULTI | SWT.V_SCROLL |
SWT.H_SCROLL,
+ TextComposite.STYLE_TYPE_LOG);
} else {
workflowLogText =
new StyledTextVar(
@@ -143,7 +144,8 @@ public class HopGuiWorkflowLogDelegate {
workflowLogComposite,
SWT.READ_ONLY | SWT.BORDER | SWT.MULTI | SWT.V_SCROLL |
SWT.H_SCROLL,
false,
- false);
+ false,
+ TextComposite.STYLE_TYPE_LOG);
// Error highlighting is applied directly in HopGuiLogBrowser when
adding lines
}
PropsUi.setLook(workflowLogText);
diff --git
a/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/configuration/tabs/ConfigGuiOptionsTab.java
b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/configuration/tabs/ConfigGuiOptionsTab.java
index 6d56069a6c..d0b24fe5b9 100644
---
a/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/configuration/tabs/ConfigGuiOptionsTab.java
+++
b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/configuration/tabs/ConfigGuiOptionsTab.java
@@ -117,6 +117,7 @@ public class ConfigGuiOptionsTab {
private Button wMetricsOnTransforms;
private Button wHideMenuBar;
private Button wShowTableViewToolbar;
+ private Button wShowTextCompositeToolbar;
private Text wMaxPreviewCellLength;
private Button wShowPreviewLineBreaks;
private Button wMetricsPanelShowUnits;
@@ -221,6 +222,9 @@ public class ConfigGuiOptionsTab {
wEnableInfiniteMove.setSelection(props.isInfiniteCanvasMoveEnabled());
wHideMenuBar.setSelection(props.isHidingMenuBar());
wShowTableViewToolbar.setSelection(props.isShowTableViewToolbar());
+ if (wShowTextCompositeToolbar != null &&
!wShowTextCompositeToolbar.isDisposed()) {
+
wShowTextCompositeToolbar.setSelection(props.isShowTextCompositeToolbar());
+ }
if (wMaxPreviewCellLength != null &&
!wMaxPreviewCellLength.isDisposed()) {
wMaxPreviewCellLength.setText(Integer.toString(props.getMaxPreviewCellLength()));
}
@@ -974,6 +978,16 @@ public class ConfigGuiOptionsTab {
margin);
lastTablesControl = wShowTableViewToolbar;
+ wShowTextCompositeToolbar =
+ createCheckbox(
+ tablesContent,
+ "EnterOptionsDialog.ShowTextCompositeToolbar.Label",
+ "EnterOptionsDialog.ShowTextCompositeToolbar.ToolTip",
+ props.isShowTextCompositeToolbar(),
+ lastTablesControl,
+ margin);
+ lastTablesControl = wShowTextCompositeToolbar;
+
// Maximum number of characters shown in a preview grid cell before it is
truncated.
Control[] maxPreviewCellLengthControls =
createTextField(
@@ -1431,6 +1445,9 @@ public class ConfigGuiOptionsTab {
props.setDarkMode(darkMode);
props.setHidingMenuBar(wHideMenuBar.getSelection());
props.setShowTableViewToolbar(wShowTableViewToolbar.getSelection());
+ if (wShowTextCompositeToolbar != null &&
!wShowTextCompositeToolbar.isDisposed()) {
+
props.setShowTextCompositeToolbar(wShowTextCompositeToolbar.getSelection());
+ }
props.setMaxPreviewCellLength(
Const.toInt(wMaxPreviewCellLength.getText(),
props.getMaxPreviewCellLength()));
props.setShowPreviewLineBreaksAsSymbols(wShowPreviewLineBreaks.getSelection());
diff --git
a/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/execution/ExecutionLogPanel.java
b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/execution/ExecutionLogPanel.java
index c224578193..0b41d96add 100644
---
a/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/execution/ExecutionLogPanel.java
+++
b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/execution/ExecutionLogPanel.java
@@ -107,7 +107,8 @@ public class ExecutionLogPanel {
new StyledTextComp(
Variables.getADefaultVariableSpace(),
composite,
- SWT.READ_ONLY | SWT.BORDER | SWT.MULTI | SWT.V_SCROLL |
SWT.H_SCROLL);
+ SWT.READ_ONLY | SWT.BORDER | SWT.MULTI | SWT.V_SCROLL |
SWT.H_SCROLL,
+ TextComposite.STYLE_TYPE_LOG);
} else {
logText =
new StyledTextVar(
@@ -115,7 +116,8 @@ public class ExecutionLogPanel {
composite,
SWT.READ_ONLY | SWT.BORDER | SWT.MULTI | SWT.V_SCROLL |
SWT.H_SCROLL,
false,
- false);
+ false,
+ TextComposite.STYLE_TYPE_LOG);
}
PropsUi.setLook(logText);
FormData fdText = new FormData();
diff --git
a/ui/src/main/resources/org/apache/hop/ui/core/dialog/messages/messages_en_US.properties
b/ui/src/main/resources/org/apache/hop/ui/core/dialog/messages/messages_en_US.properties
index 9701ebb5c2..ba462b6273 100644
---
a/ui/src/main/resources/org/apache/hop/ui/core/dialog/messages/messages_en_US.properties
+++
b/ui/src/main/resources/org/apache/hop/ui/core/dialog/messages/messages_en_US.properties
@@ -193,6 +193,20 @@ EnterOptionsDialog.ShowCanvasGrid.Label=Show canvas grid
EnterOptionsDialog.ShowCanvasGrid.ToolTip=If enabled, the canvas grid will be
visible
EnterOptionsDialog.ShowTableViewToolbar.Label=Show toolbar
EnterOptionsDialog.ShowTableViewToolbar.ToolTip=Enabling this option makes a
toolbar appear above all tables in the Hop GUI.
+EnterOptionsDialog.ShowTextCompositeToolbar.Label=Show text editor toolbar
+EnterOptionsDialog.ShowTextCompositeToolbar.ToolTip=Enabling this option makes
a toolbar appear above multi-line text editors (SQL, scripts, and similar
fields) with cut/copy/paste, undo/redo, and find/replace.
+FindReplaceDialog.CaseSensitive.Label=Case sensitive
+FindReplaceDialog.Find.Label=Find
+FindReplaceDialog.FindNext.Button=Find next
+FindReplaceDialog.FindPrevious.Button=Find previous
+FindReplaceDialog.Replace.Button=Replace
+FindReplaceDialog.Replace.Label=Replace with
+FindReplaceDialog.ReplaceAll.Button=Replace all
+FindReplaceDialog.Shell.FindTitle=Find
+FindReplaceDialog.Shell.ReplaceTitle=Find and replace
+FindReplaceDialog.Status.EmptyFind=Enter text to find
+FindReplaceDialog.Status.NotFound=Text not found
+FindReplaceDialog.Status.ReplaceAllCount={0} occurrence(s) replaced
EnterOptionsDialog.MaxPreviewCellLength.Label=Maximum grid cell length
EnterOptionsDialog.MaxPreviewCellLength.ToolTip=The maximum number of
characters shown in a single grid cell. Longer values are only shortened for
display\: click a cell to see its full value in place, or use the expand icon
to open it in the multi-line editor. Copying, exporting and saving always use
the full value. Set to 0 to disable shortening. This has no effect in the web
version.
EnterOptionsDialog.ShowPreviewLineBreaks.Label=Show line breaks as symbols in
grid cells
diff --git
a/ui/src/main/resources/org/apache/hop/ui/core/widget/messages/messages_en_US.properties
b/ui/src/main/resources/org/apache/hop/ui/core/widget/messages/messages_en_US.properties
index 27428e920e..2d4575ff85 100644
---
a/ui/src/main/resources/org/apache/hop/ui/core/widget/messages/messages_en_US.properties
+++
b/ui/src/main/resources/org/apache/hop/ui/core/widget/messages/messages_en_US.properties
@@ -109,8 +109,18 @@ TextVar.InternalVariable.Message=This is an internal
variable.
TextVar.tooltip.InsertVariable=Enter CTRL-SPACE to select a variable to insert
TextVar.tooltip.ExpandedInteger=This field accepts expanded integer values:\n-
Grouping: 100,000,000 or 100.000.000 or 100_000_000\n- Suffixes: 100k, 100m,
1g, 1b (\u00d71,000 / \u00d71,000,000 / \u00d71,000,000,000)\n- Fractional:
1.5m\n- Scientific: 1e8, 1x10^8, 10^8\nVariables ($'{'...'}') are resolved
before parsing.
TextVar.VariableValue.Message=The value of variable ''{0}'' is \: \n\n\t{1}\n\n
+TextComposite.ToolBarWidget.Copy.ToolTip=Copy selected text
+TextComposite.ToolBarWidget.Cut.ToolTip=Cut selected text
+TextComposite.ToolBarWidget.Find.ToolTip=Find text
+TextComposite.ToolBarWidget.FindReplace.ToolTip=Find and replace text
+TextComposite.ToolBarWidget.Paste.ToolTip=Paste text from clipboard
+TextComposite.ToolBarWidget.Redo.ToolTip=Redo the last action
+TextComposite.ToolBarWidget.SelectAll.ToolTip=Select all text
+TextComposite.ToolBarWidget.Undo.ToolTip=Undo the last action
WidgetDialog.Styled.Copy=Copy\tCtrl+C
WidgetDialog.Styled.Cut=Cut\tCtrl+X
+WidgetDialog.Styled.Find=Find\tCtrl+F
+WidgetDialog.Styled.FindReplace=Find/Replace\tCtrl+H
WidgetDialog.Styled.Paste=Paste\tCtrl+V
WidgetDialog.Styled.Redo=Redo\tCtrl+Shift+Z
WidgetDialog.Styled.SelectAll=Select &All\tCtrl+A
diff --git
a/ui/src/test/java/org/apache/hop/ui/core/widget/TextFindSupportTest.java
b/ui/src/test/java/org/apache/hop/ui/core/widget/TextFindSupportTest.java
new file mode 100644
index 0000000000..cef9d4e2f0
--- /dev/null
+++ b/ui/src/test/java/org/apache/hop/ui/core/widget/TextFindSupportTest.java
@@ -0,0 +1,64 @@
+/*
+ * 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.core.widget;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Test;
+
+class TextFindSupportTest {
+
+ @Test
+ void findNextCaseSensitive() {
+ String text = "Select From from FROM dual";
+ assertEquals(7, TextFindSupport.findNext(text, "From", 0, true));
+ assertEquals(12, TextFindSupport.findNext(text, "from", 0, true));
+ assertEquals(-1, TextFindSupport.findNext(text, "missing", 0, true));
+ }
+
+ @Test
+ void findNextCaseInsensitive() {
+ String text = "Select From dual";
+ assertEquals(7, TextFindSupport.findNext(text, "FROM", 0, false));
+ assertEquals(-1, TextFindSupport.findNext(text, "from", 8, false));
+ }
+
+ @Test
+ void findPrevious() {
+ String text = "aaa bbb aaa ccc";
+ assertEquals(8, TextFindSupport.findPrevious(text, "aaa", 10, true));
+ assertEquals(0, TextFindSupport.findPrevious(text, "aaa", 7, true));
+ assertEquals(-1, TextFindSupport.findPrevious(text, "aaa", -1, true));
+ }
+
+ @Test
+ void replaceAllCaseSensitive() {
+ TextFindSupport.ReplaceAllResult result =
+ TextFindSupport.replaceAll("aAa aAa aaa", "aAa", "X", true);
+ assertEquals(2, result.count());
+ assertEquals("X X aaa", result.text());
+ }
+
+ @Test
+ void replaceAllCaseInsensitive() {
+ TextFindSupport.ReplaceAllResult result =
+ TextFindSupport.replaceAll("Hello hello HELLO", "hello", "Hi", false);
+ assertEquals(3, result.count());
+ assertEquals("Hi Hi Hi", result.text());
+ }
+}