You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cayenne.apache.org by aa...@apache.org on 2026/04/25 21:38:04 UTC

(cayenne) 07/15: Modeler cleanup

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

asf-gitbox-commits pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/cayenne.git

commit 550c2bb363f65e8a15c64aeee86a5ce70c99d8c6
Author: Andrus Adamchik <an...@objectstyle.com>
AuthorDate: Sat Apr 25 08:01:13 2026 -0400

    Modeler cleanup
    
    TextAdapter should be replaced with Cayenne*TextField
---
 .../modeler/swing/text/CayennePasswordField.java   |  32 +++-
 .../modeler/swing/text/CayenneTextArea.java        |  21 ++-
 .../swing/text/CayenneTextCommitListener.java      |  33 ++++
 .../modeler/swing/text/CayenneTextField.java       |  33 +++-
 .../modeler/swing/text/ValidatorTextAdapter.java   | 201 ---------------------
 .../project/editor/datadomain/DataDomainView.java  |  15 +-
 .../editor/datamap/cgen/CgenConfigController.java  |  10 +-
 .../editor/datamap/cgen/CgenConfigPanel.java       |  61 +++----
 .../editor/datamap/main/DataMapMainView.java       |  14 +-
 .../editor/dbentity/main/DbEntityMainView.java     |  23 +--
 .../main/PKCustomSequenceGeneratorPanel.java       |  14 +-
 .../editor/embeddable/main/EmbeddableMainView.java |  13 +-
 .../editor/objentity/main/ObjEntityMainView.java   |  28 +--
 .../editor/procedure/ProcedureQueryView.java       |  13 +-
 .../ui/project/editor/procedure/ProcedureTab.java  |  13 +-
 .../ui/project/editor/query/BaseQueryMainTab.java  |   6 +-
 .../editor/query/ObjectQueryPropertiesPanel.java   |   6 +-
 .../editor/query/RawQueryPropertiesPanel.java      |   6 +-
 .../project/editor/query/RootSelectionHandler.java |   2 +-
 .../editor/query/SelectPropertiesPanel.java        |  29 +--
 .../editor/query/ejbql/EjbqlQueryMainTab.java      |  14 +-
 .../query/ejbql/EjbqlQueryPropertiesPanel.java     |   6 +-
 .../query/selectquery/SelectQueryMainTab.java      |  32 +---
 .../query/sqltemplate/SQLTemplateMainTab.java      |  11 +-
 .../modeler/undo/JTextFieldUndoListener.java       |  11 +-
 .../cayenne/modeler/undo/TextCompoundEdit.java     |  11 --
 .../apache/cayenne/modeler/util/TextAdapter.java   | 154 ----------------
 27 files changed, 212 insertions(+), 600 deletions(-)

diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/swing/text/CayennePasswordField.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/swing/text/CayennePasswordField.java
index 0c6a0e23b..b1b36a40e 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/swing/text/CayennePasswordField.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/swing/text/CayennePasswordField.java
@@ -19,24 +19,29 @@
 
 package org.apache.cayenne.modeler.swing.text;
 
+import org.apache.cayenne.validation.ValidationException;
+
 import javax.swing.InputVerifier;
 import javax.swing.JComponent;
+import javax.swing.JOptionPane;
 import javax.swing.JPasswordField;
+import javax.swing.SwingUtilities;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
-import java.util.function.Consumer;
 
 /**
  * A {@link JPasswordField} that fires registered commit listeners whenever the field value
  * changes and is committed — either by pressing Enter (action) or by losing focus (input
  * verifier). Empty strings are normalized to {@code null} before listeners are notified.
  * Listeners are not notified when the committed value matches the previous committed value
- * (or the value most recently assigned via {@link #setText}).
+ * (or the value most recently assigned via {@link #setText}). Listeners may throw
+ * {@link ValidationException} to reject the input — the field reverts to the previously
+ * committed value and surfaces the message in an error dialog.
  */
 public class CayennePasswordField extends JPasswordField {
 
-    private final List<Consumer<String>> commitListeners;
+    private final List<CayenneTextCommitListener> commitListeners;
     private String lastCommittedValue;
 
     public CayennePasswordField() {
@@ -51,7 +56,7 @@ public class CayennePasswordField extends JPasswordField {
         });
     }
 
-    public void addCommitListener(Consumer<String> listener) {
+    public void addCommitListener(CayenneTextCommitListener listener) {
         commitListeners.add(listener);
     }
 
@@ -66,11 +71,22 @@ public class CayennePasswordField extends JPasswordField {
         String previous = this.lastCommittedValue;
         String normalized = normalize(getText());
 
-        if (!Objects.equals(normalized, previous)) {
-            this.lastCommittedValue = normalized;
-            for (Consumer<String> listener : commitListeners) {
-                listener.accept(normalized);
+        if (Objects.equals(normalized, previous)) {
+            return;
+        }
+
+        try {
+            for (CayenneTextCommitListener listener : commitListeners) {
+                listener.onCommit(normalized);
             }
+            this.lastCommittedValue = normalized;
+        } catch (ValidationException ex) {
+            setText(previous == null ? "" : previous);
+            JOptionPane.showMessageDialog(
+                    SwingUtilities.getWindowAncestor(this),
+                    ex.getUnlabeledMessage(),
+                    "Invalid value",
+                    JOptionPane.ERROR_MESSAGE);
         }
     }
 
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/swing/text/CayenneTextArea.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/swing/text/CayenneTextArea.java
index 3e21582ea..374d0ac56 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/swing/text/CayenneTextArea.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/swing/text/CayenneTextArea.java
@@ -19,21 +19,28 @@
 
 package org.apache.cayenne.modeler.swing.text;
 
+import org.apache.cayenne.validation.ValidationException;
+
 import javax.swing.JTextArea;
 import javax.swing.event.DocumentEvent;
 import javax.swing.event.DocumentListener;
 import java.util.ArrayList;
 import java.util.List;
-import java.util.function.Consumer;
 
 /**
  * A {@link JTextArea} that fires registered commit listeners on every text-content change
  * (insert / remove). Empty strings are normalized to {@code null} before listeners are
  * notified.
+ * <p>
+ * A listener may declare {@link ValidationException} for API symmetry with the other
+ * Cayenne text widgets, but unlike them this area fires per keystroke — there is no prior
+ * committed value to revert to and surfacing a dialog mid-typing would be hostile, so any
+ * thrown {@code ValidationException} is silently dropped here. Validation that reverts and
+ * prompts belongs on a commit-on-focus-lost widget such as {@link CayenneTextField}.
  */
 public class CayenneTextArea extends JTextArea {
 
-    private final List<Consumer<String>> commitListeners;
+    private final List<CayenneTextCommitListener> commitListeners;
 
     public CayenneTextArea() {
         commitListeners = new ArrayList<>(2);
@@ -54,15 +61,19 @@ public class CayenneTextArea extends JTextArea {
         });
     }
 
-    public void addCommitListener(Consumer<String> listener) {
+    public void addCommitListener(CayenneTextCommitListener listener) {
         commitListeners.add(listener);
     }
 
     private void fireCommit() {
         String text = getText();
         String value = (text != null && text.isEmpty()) ? null : text;
-        for (Consumer<String> listener : commitListeners) {
-            listener.accept(value);
+        for (CayenneTextCommitListener listener : commitListeners) {
+            try {
+                listener.onCommit(value);
+            } catch (ValidationException ignored) {
+                // see class javadoc — per-keystroke fire makes revert/dialog unsuitable
+            }
         }
     }
 }
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/swing/text/CayenneTextCommitListener.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/swing/text/CayenneTextCommitListener.java
new file mode 100644
index 000000000..978783774
--- /dev/null
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/swing/text/CayenneTextCommitListener.java
@@ -0,0 +1,33 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    https://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.cayenne.modeler.swing.text;
+
+import org.apache.cayenne.validation.ValidationException;
+
+/**
+ * Listener invoked when a Cayenne text widget commits a new value. Implementations may
+ * throw {@link ValidationException} to reject the input — the widget will revert to the
+ * previous committed value and surface the message to the user.
+ */
+@FunctionalInterface
+public interface CayenneTextCommitListener {
+
+    void onCommit(String value) throws ValidationException;
+}
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/swing/text/CayenneTextField.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/swing/text/CayenneTextField.java
index 988f58405..b58799d55 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/swing/text/CayenneTextField.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/swing/text/CayenneTextField.java
@@ -19,13 +19,16 @@
 
 package org.apache.cayenne.modeler.swing.text;
 
+import org.apache.cayenne.validation.ValidationException;
+
 import javax.swing.InputVerifier;
 import javax.swing.JComponent;
+import javax.swing.JOptionPane;
 import javax.swing.JTextField;
+import javax.swing.SwingUtilities;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
-import java.util.function.Consumer;
 
 /**
  * A {@link JTextField} that fires registered commit listeners whenever the field value
@@ -34,10 +37,14 @@ import java.util.function.Consumer;
  * When the {@code trim} property is enabled, leading and trailing whitespace is stripped
  * from the field on commit. Listeners are not notified when the committed value matches
  * the previous committed value (or the value most recently assigned via {@link #setText}).
+ * <p>
+ * Listeners may throw {@link ValidationException} to reject the input. The field then
+ * reverts to the previously committed value and shows the exception message in an error
+ * dialog.
  */
 public class CayenneTextField extends JTextField {
 
-    private final List<Consumer<String>> commitListeners;
+    private final List<CayenneTextCommitListener> commitListeners;
     private boolean trim;
     private String lastCommittedValue;
 
@@ -51,7 +58,7 @@ public class CayenneTextField extends JTextField {
         setColumns(columns);
     }
 
-    public void addCommitListener(Consumer<String> listener) {
+    public void addCommitListener(CayenneTextCommitListener listener) {
         commitListeners.add(listener);
     }
 
@@ -92,11 +99,23 @@ public class CayenneTextField extends JTextField {
             }
         }
 
-        if (!Objects.equals(normalized, previous)) {
-            this.lastCommittedValue = normalized;
-            for (Consumer<String> listener : commitListeners) {
-                listener.accept(normalized);
+        if (Objects.equals(normalized, previous)) {
+            return;
+        }
+
+        try {
+            for (CayenneTextCommitListener listener : commitListeners) {
+                listener.onCommit(normalized);
             }
+            this.lastCommittedValue = normalized;
+        } catch (ValidationException ex) {
+            // revert visible value to last committed; setText also resets lastCommittedValue
+            setText(previous == null ? "" : previous);
+            JOptionPane.showMessageDialog(
+                    SwingUtilities.getWindowAncestor(this),
+                    ex.getUnlabeledMessage(),
+                    "Invalid value",
+                    JOptionPane.ERROR_MESSAGE);
         }
     }
 
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/swing/text/ValidatorTextAdapter.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/swing/text/ValidatorTextAdapter.java
deleted file mode 100644
index 141a1ca82..000000000
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/swing/text/ValidatorTextAdapter.java
+++ /dev/null
@@ -1,201 +0,0 @@
-/*****************************************************************
- *   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
- *
- *    https://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.cayenne.modeler.swing.text;
-
-import org.apache.cayenne.modeler.util.TextAdapter;
-import org.apache.cayenne.validation.ValidationException;
-
-import javax.swing.*;
-import javax.swing.event.DocumentEvent;
-import javax.swing.event.DocumentListener;
-import javax.swing.text.JTextComponent;
-import java.awt.event.FocusEvent;
-import java.awt.event.FocusListener;
-import java.util.Timer;
-import java.util.TimerTask;
-
-/**
- * Text adapter with live validation, which is fired in VALIDATION_DELAY time.
- */
-public abstract class ValidatorTextAdapter extends TextAdapter {
-    /**
-     * Time between end of user input and validation firing
-     */
-    static final long VALIDATION_DELAY = 1500L;
-
-    /**
-     * Is the live-checking enabled for the text component
-     */
-    boolean liveCheckEnabled;
-
-    public ValidatorTextAdapter(JTextField textField) {
-        this(textField, true);
-    }
-
-    public ValidatorTextAdapter(JTextField textField, boolean liveCheckEnabled) {
-        super(textField, true);
-        setLiveCheckEnabled(liveCheckEnabled);
-        install(textField);
-    }
-
-    protected void install(JTextComponent textComponent) {
-        TimerScheduler ts = new TimerScheduler();
-
-        textComponent.getDocument().addDocumentListener(ts);
-        textComponent.addFocusListener(ts);
-    }
-
-    /**
-     * Live-checks if text is correct
-     * 
-     * @throws ValidationException
-     *             if the text is incorrect
-     */
-    protected abstract void validate(String text) throws ValidationException;
-
-    /**
-     * @return Is the live-checking enabled for the text component
-     */
-    public boolean isLiveCheckEnabled() {
-        return liveCheckEnabled;
-    }
-
-    /**
-     * Enables/disables live-checking
-     */
-    public void setLiveCheckEnabled(boolean b) {
-        liveCheckEnabled = b;
-    }
-
-    /**
-     * Task to be fired after some delay
-     */
-    class ValidationTimerTask extends TimerTask {
-        @Override
-        public void run() {
-            validate();
-        }
-    }
-
-    protected void validate() {
-        try {
-            validate(textComponent.getText());
-            clear();
-        } catch (ValidationException vex) {
-            textComponent.setBackground(errorColor);
-            textComponent.setToolTipText(wrapTooltip(vex.getUnlabeledMessage()));
-        }
-    }
-
-    /**
-     * Wraps the tooltip, making it multi-lined if needed. Current
-     * implementation uses HTML markup to break the lines
-     * 
-     * @param tooltip
-     *            single-line tooltip
-     * @return multi-line tooltip.
-     */
-    protected String wrapTooltip(String tooltip) {
-        tooltip = encodeHTMLAttribute(tooltip);
-        tooltip = tooltip.replaceAll(System.lineSeparator(), "<br>");
-
-        return "<html>" + tooltip + "</html>";
-    }
-
-/**
-     * Encodes a string so that it can be used as an attribute value in an HMTL document.
-     * Will do conversion of the greater/less signs and ampersands.
-     * 
-     * ***
-     * This method is almost a copy of Util.encodeXmlAttribute(), but it does not replace
-     * single quotes. So only '<', '>', '&', '"' chars will be replaced. 
-     */
-    public static String encodeHTMLAttribute(String str) {
-        if (str == null) {
-            return null;
-        }
-
-        int len = str.length();
-        if (len == 0) {
-            return str;
-        }
-
-        StringBuilder encoded = new StringBuilder();
-        for (int i = 0; i < len; i++) {
-            char c = str.charAt(i);
-            if (c == '<') {
-                encoded.append("&lt;");
-            } else if (c == '\"') {
-                encoded.append("&quot;");
-            } else if (c == '>') {
-                encoded.append("&gt;");
-            } else if (c == '&') {
-                encoded.append("&amp;");
-            } else {
-                encoded.append(c);
-            }
-        }
-
-        return encoded.toString();
-    }
-
-    /**
-     * Listener to user input, which fires validation timer
-     */
-    class TimerScheduler implements DocumentListener, FocusListener {
-        /**
-         * The timer, which fires validation after some delay
-         */
-        Timer validationTimer;
-
-        public void insertUpdate(DocumentEvent e) {
-            schedule();
-        }
-
-        public void changedUpdate(DocumentEvent e) {
-            schedule();
-        }
-
-        public void removeUpdate(DocumentEvent e) {
-            schedule();
-        }
-
-        void schedule() {
-            if (isLiveCheckEnabled()) {
-                if (validationTimer != null) {
-                    validationTimer.cancel();
-                }
-
-                clear();
-
-                validationTimer = new Timer("cayenne-validation-timer");
-                validationTimer.schedule(new ValidationTimerTask(), VALIDATION_DELAY);
-            }
-        }
-
-        public void focusGained(FocusEvent e) {
-        }
-
-        public void focusLost(FocusEvent e) {
-            if (validationTimer != null) {
-                validationTimer.cancel();
-            }
-        }
-    }
-}
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/datadomain/DataDomainView.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/datadomain/DataDomainView.java
index d8b665276..41981e760 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/datadomain/DataDomainView.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/datadomain/DataDomainView.java
@@ -30,14 +30,13 @@ import org.apache.cayenne.modeler.swing.JCayenneCheckBox;
 import org.apache.cayenne.modeler.ui.project.ProjectController;
 import org.apache.cayenne.modeler.event.display.DomainDisplayEvent;
 import org.apache.cayenne.modeler.event.display.DomainDisplayListener;
-import org.apache.cayenne.modeler.util.TextAdapter;
+import org.apache.cayenne.modeler.swing.text.CayenneUndoableTextField;
 import org.apache.cayenne.modeler.pref.RenamedPreferences;
 import org.apache.cayenne.util.Util;
 import org.apache.cayenne.validation.ValidationException;
 
 import javax.swing.JCheckBox;
 import javax.swing.JPanel;
-import javax.swing.JTextField;
 import java.awt.BorderLayout;
 import java.util.Map;
 import java.util.prefs.Preferences;
@@ -49,7 +48,7 @@ public class DataDomainView extends JPanel implements DomainDisplayListener {
 
     private final ProjectController controller;
 
-    protected TextAdapter name;
+    protected CayenneUndoableTextField name;
     protected JCheckBox objectValidation;
     protected JCheckBox sharedCache;
 
@@ -66,12 +65,8 @@ public class DataDomainView extends JPanel implements DomainDisplayListener {
     protected void initView() {
 
         // create widgets
-        this.name = new TextAdapter(new JTextField()) {
-
-            protected void updateModel(String text) {
-                setDomainName(text);
-            }
-        };
+        this.name = new CayenneUndoableTextField();
+        this.name.addCommitListener(this::setDomainName);
 
         this.objectValidation = new JCayenneCheckBox();
         this.sharedCache = new JCayenneCheckBox();
@@ -87,7 +82,7 @@ public class DataDomainView extends JPanel implements DomainDisplayListener {
 
         builder.addSeparator("DataDomain Configuration", cc.xywh(1, 1, 7, 1));
         builder.addLabel("DataDomain Name:", cc.xy(1, 3));
-        builder.add(name.getComponent(), cc.xywh(3, 3, 5, 1));
+        builder.add(name, cc.xywh(3, 3, 5, 1));
 
         builder.addLabel("Object Validation:", cc.xy(1, 5));
         builder.add(objectValidation, cc.xy(3, 5));
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/datamap/cgen/CgenConfigController.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/datamap/cgen/CgenConfigController.java
index e77d29b4e..254b3efa6 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/datamap/cgen/CgenConfigController.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/datamap/cgen/CgenConfigController.java
@@ -26,7 +26,7 @@ import org.apache.cayenne.modeler.ui.project.editor.datamap.cgen.templateeditor.
 import org.apache.cayenne.modeler.mvc.ChildController;
 import org.apache.cayenne.modeler.pref.DataMapDefaults;
 import org.apache.cayenne.modeler.pref.FSPath;
-import org.apache.cayenne.modeler.util.TextAdapter;
+import org.apache.cayenne.modeler.swing.text.CayenneUndoableTextField;
 import org.apache.cayenne.util.Util;
 
 import javax.swing.*;
@@ -147,7 +147,7 @@ public class CgenConfigController extends ChildController<CgenController> {
             parent.setCurrentClass(cgenConfiguration.getDataMap());
             parent.setSelected(true);
         }
-        view.getOutputFolder().updateModel();
+        view.applyOutputFolder(view.getOutputFolder().getText());
         view.getOutputPattern().setText(cgenConfiguration.getOutputPattern());
         view.getPairs().setSelected(cgenConfiguration.isMakePairs());
         view.getUsePackagePath().setSelected(cgenConfiguration.isUsePkgPath());
@@ -224,8 +224,8 @@ public class CgenConfigController extends ChildController<CgenController> {
      */
     public void selectOutputFolderAction() {
 
-        TextAdapter outputFolder = view.getOutputFolder();
-        String currentDir = outputFolder.getComponent().getText();
+        CayenneUndoableTextField outputFolder = view.getOutputFolder();
+        String currentDir = outputFolder.getText();
 
         JFileChooser chooser = new JFileChooser();
         chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
@@ -246,7 +246,7 @@ public class CgenConfigController extends ChildController<CgenController> {
             // update model
             String path = selected.getAbsolutePath();
             view.getOutputFolder().setText(path);
-            view.getOutputFolder().updateModel();
+            view.applyOutputFolder(path);
         }
     }
 
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/datamap/cgen/CgenConfigPanel.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/datamap/cgen/CgenConfigPanel.java
index 15210ae97..6422703b3 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/datamap/cgen/CgenConfigPanel.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/datamap/cgen/CgenConfigPanel.java
@@ -25,7 +25,6 @@ import com.jgoodies.forms.layout.FormLayout;
 import org.apache.cayenne.gen.CgenConfiguration;
 import org.apache.cayenne.gen.TemplateType;
 import org.apache.cayenne.modeler.swing.text.CayenneUndoableTextField;
-import org.apache.cayenne.modeler.util.TextAdapter;
 import org.apache.cayenne.modeler.swing.JCayenneCheckBox;
 import org.apache.cayenne.validation.ValidationException;
 
@@ -34,7 +33,6 @@ import javax.swing.JButton;
 import javax.swing.JCheckBox;
 import javax.swing.JLabel;
 import javax.swing.JPanel;
-import javax.swing.JTextField;
 import javax.swing.border.Border;
 import java.awt.BorderLayout;
 import java.nio.file.InvalidPathException;
@@ -43,7 +41,7 @@ import java.nio.file.Paths;
 
 public class CgenConfigPanel extends JPanel {
 
-    protected TextAdapter outputFolder;
+    protected CayenneUndoableTextField outputFolder;
     protected JButton selectOutputFolder;
     protected CgenController cgenController;
     private boolean isDataValid;
@@ -114,7 +112,7 @@ public class CgenConfigPanel extends JPanel {
         PanelBuilder builder = new PanelBuilder(layout);
         builder.setDefaultDialogBorder();
         builder.addLabel("Output Directory", cc.xyw(1, 1, 3));
-        builder.add(outputFolder.getComponent(), cc.xyw(1, 3, 7));
+        builder.add(outputFolder, cc.xyw(1, 3, 7));
         builder.add(selectOutputFolder, cc.xy(9, 3));
 
         // Advanced options panel
@@ -180,40 +178,41 @@ public class CgenConfigPanel extends JPanel {
             checkConfigDirty();
         });
 
-        this.outputFolder = new TextAdapter(new JTextField()) {
-            @Override
-            protected void updateModel(String text) throws ValidationException {
-                CgenConfiguration cgenByDataMap = getCgenConfig();
-                if (cgenByDataMap != null) {
-                    Path path;
-                    try {
-                        path = Paths.get(text);
-                    } catch (InvalidPathException e) {
-                        updateGenerateButton(false);
-                        throw new ValidationException(INVALID_PATH_MSG);
-                    }
-                    if (cgenByDataMap.getRootPath() == null && !path.isAbsolute()) {
-                        updateGenerateButton(false);
-                        throw new ValidationException(NEED_TO_SAVE_PROJECT_MSG);
-                    }
-                    cgenByDataMap.updateOutputPath(path);
-                    updateGenerateButton(true);
-                    checkConfigDirty();
-                }
-            }
-
+        this.outputFolder = new CayenneUndoableTextField() {
             @Override
             public void setText(String text) {
                 super.setText(text);
+                boolean valid = true;
                 try {
-                    Paths.get(text);
+                    Paths.get(text == null ? "" : text);
                 } catch (InvalidPathException e) {
-                    updateGenerateButton(false);
-                    throw new ValidationException(INVALID_PATH_MSG);
+                    valid = false;
                 }
-                updateGenerateButton(true);
+                updateGenerateButton(valid);
             }
         };
+        this.outputFolder.addCommitListener(this::applyOutputFolder);
+    }
+
+    void applyOutputFolder(String text) {
+        CgenConfiguration cgenByDataMap = getCgenConfig();
+        if (cgenByDataMap == null) {
+            return;
+        }
+        Path path;
+        try {
+            path = Paths.get(text == null ? "" : text);
+        } catch (InvalidPathException e) {
+            updateGenerateButton(false);
+            throw new ValidationException(INVALID_PATH_MSG);
+        }
+        if (cgenByDataMap.getRootPath() == null && !path.isAbsolute()) {
+            updateGenerateButton(false);
+            throw new ValidationException(NEED_TO_SAVE_PROJECT_MSG);
+        }
+        cgenByDataMap.updateOutputPath(path);
+        updateGenerateButton(true);
+        checkConfigDirty();
     }
 
     private void updateGenerateButton(boolean isDataValid){
@@ -299,7 +298,7 @@ public class CgenConfigPanel extends JPanel {
     }
 
 
-    public TextAdapter getOutputFolder() {
+    public CayenneUndoableTextField getOutputFolder() {
         return outputFolder;
     }
 
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/datamap/main/DataMapMainView.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/datamap/main/DataMapMainView.java
index 536184d3e..80072d432 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/datamap/main/DataMapMainView.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/datamap/main/DataMapMainView.java
@@ -39,7 +39,6 @@ import org.apache.cayenne.modeler.pref.DataMapDefaults;
 import org.apache.cayenne.modeler.swing.CellRenderers;
 import org.apache.cayenne.modeler.swing.text.CayenneUndoableTextField;
 import org.apache.cayenne.modeler.util.Comparators;
-import org.apache.cayenne.modeler.util.TextAdapter;
 import org.apache.cayenne.project.extension.info.ObjectInfo;
 import org.apache.cayenne.modeler.swing.JCayenneCheckBox;
 import org.apache.cayenne.modeler.swing.WidgetFactory;
@@ -52,7 +51,6 @@ import javax.swing.JCheckBox;
 import javax.swing.JComboBox;
 import javax.swing.JLabel;
 import javax.swing.JPanel;
-import javax.swing.JTextField;
 import java.awt.BorderLayout;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -65,7 +63,7 @@ public class DataMapMainView extends JPanel {
 
     private final ProjectController controller;
 
-    private final TextAdapter name;
+    private final CayenneUndoableTextField name;
     private final JLabel location;
     private final JComboBox<DataNodeDescriptor> nodeSelector;
     private final JCheckBox defaultLockType;
@@ -81,12 +79,8 @@ public class DataMapMainView extends JPanel {
         this.controller = controller;
 
         // create widgets
-        name = new TextAdapter(new JTextField()) {
-
-            protected void updateModel(String text) {
-                setDataMapName(text);
-            }
-        };
+        name = new CayenneUndoableTextField();
+        name.addCommitListener(this::setDataMapName);
 
         location = new JLabel();
         nodeSelector = WidgetFactory.createUndoableComboBox();
@@ -124,7 +118,7 @@ public class DataMapMainView extends JPanel {
         builder.setDefaultDialogBorder();
 
         builder.appendSeparator("DataMap Configuration");
-        builder.append("DataMap Name:", name.getComponent(), 2);
+        builder.append("DataMap Name:", name, 2);
         builder.append("File:", location, 3);
         builder.append("DataNode:", nodeSelector, 2);
         builder.append("Quote SQL Identifiers:", quoteSQLIdentifiers, 3);
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/dbentity/main/DbEntityMainView.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/dbentity/main/DbEntityMainView.java
index 1427b86ca..47bab7617 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/dbentity/main/DbEntityMainView.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/dbentity/main/DbEntityMainView.java
@@ -40,7 +40,6 @@ import org.apache.cayenne.modeler.event.display.EntityDisplayEvent;
 import org.apache.cayenne.modeler.swing.text.CayenneUndoableTextField;
 import org.apache.cayenne.modeler.ui.project.editor.datadomain.graph.action.ShowGraphEntityAction;
 import org.apache.cayenne.modeler.util.ExpressionConvertor;
-import org.apache.cayenne.modeler.util.TextAdapter;
 import org.apache.cayenne.project.extension.info.ObjectInfo;
 import org.apache.cayenne.util.Util;
 import org.apache.cayenne.validation.ValidationException;
@@ -59,10 +58,10 @@ public class DbEntityMainView extends JPanel implements ExistingSelectionProcess
 
     private final ProjectController controller;
 
-    private final TextAdapter name;
+    private final CayenneUndoableTextField name;
     private final CayenneUndoableTextField catalog;
     private final CayenneUndoableTextField schema;
-    private final TextAdapter qualifier;
+    private final CayenneUndoableTextField qualifier;
     private final CayenneUndoableTextField comment;
 
     private final JLabel catalogLabel;
@@ -95,11 +94,8 @@ public class DbEntityMainView extends JPanel implements ExistingSelectionProcess
         toolBar.add(actionManager.getAction(ShowGraphEntityAction.class).buildButton());
 
         // create widgets
-        name = new TextAdapter(new JTextField()) {
-            protected void updateModel(String text) {
-                setEntityName(text);
-            }
-        };
+        name = new CayenneUndoableTextField();
+        name.addCommitListener(this::setEntityName);
 
         catalogLabel = new JLabel("Catalog:");
         catalog = new CayenneUndoableTextField();
@@ -109,11 +105,8 @@ public class DbEntityMainView extends JPanel implements ExistingSelectionProcess
         schema = new CayenneUndoableTextField();
         schema.addCommitListener(this::setSchema);
 
-        qualifier = new TextAdapter(new JTextField()) {
-            protected void updateModel(String qualifier) {
-                setQualifier(qualifier);
-            }
-        };
+        qualifier = new CayenneUndoableTextField();
+        qualifier.addCommitListener(this::setQualifier);
 
         comment = new CayenneUndoableTextField();
         comment.addCommitListener(this::setComment);
@@ -134,10 +127,10 @@ public class DbEntityMainView extends JPanel implements ExistingSelectionProcess
         builder.setDefaultDialogBorder();
 
         builder.appendSeparator("DbEntity Configuration");
-        builder.append("DbEntity Name:", name.getComponent());
+        builder.append("DbEntity Name:", name);
         builder.append(catalogLabel, catalog);
         builder.append(schemaLabel, schema);
-        builder.append("Qualifier:", qualifier.getComponent());
+        builder.append("Qualifier:", qualifier);
         builder.append("Comment:", comment);
 
         builder.appendSeparator("Primary Key");
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/dbentity/main/PKCustomSequenceGeneratorPanel.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/dbentity/main/PKCustomSequenceGeneratorPanel.java
index 97a9077fe..a999276aa 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/dbentity/main/PKCustomSequenceGeneratorPanel.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/dbentity/main/PKCustomSequenceGeneratorPanel.java
@@ -23,14 +23,12 @@ import java.awt.BorderLayout;
 import java.awt.Font;
 
 import javax.swing.JLabel;
-import javax.swing.JTextField;
 
 import org.apache.cayenne.map.DbEntity;
 import org.apache.cayenne.map.DbKeyGenerator;
 import org.apache.cayenne.map.event.EntityEvent;
 import org.apache.cayenne.modeler.swing.text.CayenneUndoableTextField;
 import org.apache.cayenne.modeler.ui.project.ProjectController;
-import org.apache.cayenne.modeler.util.TextAdapter;
 import org.apache.cayenne.util.Util;
 import org.apache.cayenne.validation.ValidationException;
 
@@ -40,7 +38,7 @@ import com.jgoodies.forms.layout.FormLayout;
 public class PKCustomSequenceGeneratorPanel extends PKGeneratorPanel {
 
     protected CayenneUndoableTextField customPKName;
-    protected TextAdapter customPKSize;
+    protected CayenneUndoableTextField customPKSize;
 
     public PKCustomSequenceGeneratorPanel(ProjectController mediator) {
         super(mediator);
@@ -55,12 +53,8 @@ public class PKCustomSequenceGeneratorPanel extends PKGeneratorPanel {
 
         customPKName = new CayenneUndoableTextField();
         customPKName.addCommitListener(this::setPKName);
-        customPKSize = new TextAdapter(new JTextField()) {
-
-            protected void updateModel(String text) throws ValidationException {
-                setPKSize(text);
-            }
-        };
+        customPKSize = new CayenneUndoableTextField();
+        customPKSize.addCommitListener(this::setPKSize);
 
         // assemble
 
@@ -71,7 +65,7 @@ public class PKCustomSequenceGeneratorPanel extends PKGeneratorPanel {
         builder.setDefaultDialogBorder();
 
         builder.append("Sequence Name:", customPKName, 3);
-        builder.append("Cached PK Size:", customPKSize.getComponent());
+        builder.append("Cached PK Size:", customPKSize);
         builder.nextLine();
         builder.append("", note, 3);
 
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/embeddable/main/EmbeddableMainView.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/embeddable/main/EmbeddableMainView.java
index 122f2d1e6..2e8bead90 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/embeddable/main/EmbeddableMainView.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/embeddable/main/EmbeddableMainView.java
@@ -35,7 +35,6 @@ import org.apache.cayenne.modeler.event.display.EmbeddableDisplayEvent;
 import org.apache.cayenne.modeler.event.display.EmbeddableDisplayListener;
 import org.apache.cayenne.modeler.swing.text.CayenneUndoableTextField;
 import org.apache.cayenne.modeler.util.Comparators;
-import org.apache.cayenne.modeler.util.TextAdapter;
 import org.apache.cayenne.project.extension.info.ObjectInfo;
 import org.apache.cayenne.util.Util;
 import org.apache.cayenne.validation.ValidationException;
@@ -48,7 +47,7 @@ import java.util.Iterator;
 public class EmbeddableMainView extends JPanel implements EmbeddableDisplayListener {
 
     protected ProjectController mediator;
-    protected TextAdapter className;
+    protected CayenneUndoableTextField className;
     protected CayenneUndoableTextField comment;
 
     public EmbeddableMainView(ProjectController mediator) {
@@ -72,12 +71,8 @@ public class EmbeddableMainView extends JPanel implements EmbeddableDisplayListe
 
         add(toolBar, BorderLayout.NORTH);
 
-        className = new TextAdapter(new JTextField()) {
-            @Override
-            protected void updateModel(String text) {
-                setClassName(text);
-            }
-        };
+        className = new CayenneUndoableTextField();
+        className.addCommitListener(this::setClassName);
 
         comment = new CayenneUndoableTextField();
         comment.addCommitListener(this::setComment);
@@ -87,7 +82,7 @@ public class EmbeddableMainView extends JPanel implements EmbeddableDisplayListe
                 "");
         DefaultFormBuilder builder = new DefaultFormBuilder(layout);
         builder.setDefaultDialogBorder();
-        builder.append("Class Name:", className.getComponent(), 3);
+        builder.append("Class Name:", className, 3);
         builder.append("Comment:", comment, 3);
 
         add(builder.getPanel(), BorderLayout.CENTER);
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/objentity/main/ObjEntityMainView.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/objentity/main/ObjEntityMainView.java
index e1c6faf82..d9c5c2819 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/objentity/main/ObjEntityMainView.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/objentity/main/ObjEntityMainView.java
@@ -46,7 +46,6 @@ import org.apache.cayenne.modeler.swing.CellRenderers;
 import org.apache.cayenne.modeler.swing.text.CayenneUndoableTextField;
 import org.apache.cayenne.modeler.util.Comparators;
 import org.apache.cayenne.modeler.util.ExpressionConvertor;
-import org.apache.cayenne.modeler.util.TextAdapter;
 import org.apache.cayenne.modeler.swing.WidgetFactory;
 import org.apache.cayenne.modeler.swing.combo.AutoCompletion;
 import org.apache.cayenne.project.extension.info.ObjectInfo;
@@ -67,12 +66,12 @@ public class ObjEntityMainView extends JPanel implements ObjEntityDisplayListene
     private static final ObjEntity NO_INHERITANCE = new ObjEntity("Direct Mapping to Table/View");
 
     private final ProjectController controller;
-    private final TextAdapter name;
+    private final CayenneUndoableTextField name;
     private final CayenneUndoableTextField className;
 
     private final JLabel superclassLabel;
     private final CayenneUndoableTextField superClassName;
-    private final TextAdapter qualifier;
+    private final CayenneUndoableTextField qualifier;
     private final JComboBox<DbEntity> dbEntityCombo;
     private final JComboBox<ObjEntity> superEntityCombo;
     private final JCheckBox readOnly;
@@ -102,22 +101,14 @@ public class ObjEntityMainView extends JPanel implements ObjEntityDisplayListene
         add(toolBar, BorderLayout.NORTH);
 
         // create widgets
-        name = new TextAdapter(new JTextField()) {
-            @Override
-            protected void updateModel(String text) {
-                setEntityName(text);
-            }
-        };
+        name = new CayenneUndoableTextField();
+        name.addCommitListener(this::setEntityName);
         superClassName = new CayenneUndoableTextField();
         superClassName.addCommitListener(this::setSuperClassName);
         className = new CayenneUndoableTextField();
         className.addCommitListener(this::setClassName);
-        qualifier = new TextAdapter(new JTextField()) {
-            @Override
-            protected void updateModel(String text) {
-                setQualifier(text);
-            }
-        };
+        qualifier = new CayenneUndoableTextField();
+        qualifier.addCommitListener(this::setQualifier);
 
         dbEntityCombo = WidgetFactory.createComboBox();
         superEntityCombo = WidgetFactory.createComboBox();
@@ -149,7 +140,7 @@ public class ObjEntityMainView extends JPanel implements ObjEntityDisplayListene
         builder.setDefaultDialogBorder();
 
         builder.appendSeparator("ObjEntity Configuration");
-        builder.append("ObjEntity Name:", name.getComponent());
+        builder.append("ObjEntity Name:", name);
         builder.append("Inheritance:", superEntityCombo);
         builder.append(tableLabel, dbEntityCombo);
         builder.append("Comment:", comment);
@@ -158,7 +149,7 @@ public class ObjEntityMainView extends JPanel implements ObjEntityDisplayListene
         builder.append("Java Class:", className);
 
         superclassLabel = builder.append("Superclass:", superClassName);
-        builder.append("Qualifier:", qualifier.getComponent());
+        builder.append("Qualifier:", qualifier);
         builder.append("Read-Only:", readOnly);
         builder.append("Optimistic Locking:", optimisticLocking);
 
@@ -282,9 +273,6 @@ public class ObjEntityMainView extends JPanel implements ObjEntityDisplayListene
      * @param entity current entity
      */
     private void initFromModel(final ObjEntity entity) {
-        // TODO: this is a hack until we implement a real MVC
-        qualifier.getComponent().setBackground(Color.WHITE);
-
         name.setText(entity.getName());
         superClassName.setText(entity.getSuperClassName());
         className.setText(entity.getClassName());
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/procedure/ProcedureQueryView.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/procedure/ProcedureQueryView.java
index 2283049d3..486560352 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/procedure/ProcedureQueryView.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/procedure/ProcedureQueryView.java
@@ -38,7 +38,6 @@ import org.apache.cayenne.modeler.ui.project.ProjectController;
 import org.apache.cayenne.modeler.swing.CellRenderers;
 import org.apache.cayenne.modeler.swing.text.CayenneUndoableTextField;
 import org.apache.cayenne.modeler.util.Comparators;
-import org.apache.cayenne.modeler.util.TextAdapter;
 import org.apache.cayenne.project.extension.info.ObjectInfo;
 import org.apache.cayenne.query.CapsStrategy;
 import org.apache.cayenne.query.ProcedureQuery;
@@ -73,7 +72,7 @@ public class ProcedureQueryView extends JPanel {
     }
 
     protected ProjectController mediator;
-    protected TextAdapter name;
+    protected CayenneUndoableTextField name;
     protected CayenneUndoableTextField comment;
     protected JComboBox<Procedure> queryRoot;
     protected SelectPropertiesPanel properties;
@@ -87,12 +86,8 @@ public class ProcedureQueryView extends JPanel {
 
     private void initView() {
         // create widgets
-        name = new TextAdapter(new JTextField()) {
-            @Override
-            protected void updateModel(String text) {
-                setQueryName(text);
-            }
-        };
+        name = new CayenneUndoableTextField();
+        name.addCommitListener(this::setQueryName);
 
         comment = new CayenneUndoableTextField();
         comment.addCommitListener(this::setQueryComment);
@@ -111,7 +106,7 @@ public class ProcedureQueryView extends JPanel {
 
         builder.addSeparator("ProcedureQuery Settings", cc.xywh(1, 1, 3, 1));
         builder.addLabel("Query Name:", cc.xy(1, 3));
-        builder.add(name.getComponent(), cc.xy(3, 3));
+        builder.add(name, cc.xy(3, 3));
         builder.addLabel("Procedure:", cc.xy(1, 5));
         builder.add(queryRoot, cc.xy(3, 5));
         builder.addLabel("Comment:", cc.xy(1, 7));
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/procedure/ProcedureTab.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/procedure/ProcedureTab.java
index de602c2d7..c411a7d9e 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/procedure/ProcedureTab.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/procedure/ProcedureTab.java
@@ -32,7 +32,6 @@ import org.apache.cayenne.map.EntityResolver;
 import org.apache.cayenne.map.MappingNamespace;
 import org.apache.cayenne.modeler.event.model.ProcedureEvent;
 import org.apache.cayenne.modeler.swing.text.CayenneUndoableTextField;
-import org.apache.cayenne.modeler.util.TextAdapter;
 import org.apache.cayenne.project.extension.info.ObjectInfo;
 import org.apache.cayenne.modeler.swing.JCayenneCheckBox;
 import org.apache.cayenne.util.Util;
@@ -48,7 +47,7 @@ import java.util.EventObject;
 public class ProcedureTab extends JPanel implements ProcedureDisplayListener, ExistingSelectionProcessor {
 
     protected ProjectController eventController;
-    protected TextAdapter name;
+    protected CayenneUndoableTextField name;
     protected CayenneUndoableTextField schema;
     protected CayenneUndoableTextField catalog;
     protected CayenneUndoableTextField comment;
@@ -65,12 +64,8 @@ public class ProcedureTab extends JPanel implements ProcedureDisplayListener, Ex
     private void initView() {
         // create widgets
 
-        this.name = new TextAdapter(new JTextField()) {
-            @Override
-            protected void updateModel(String text) {
-                setProcedureName(text);
-            }
-        };
+        this.name = new CayenneUndoableTextField();
+        this.name.addCommitListener(this::setProcedureName);
 
         this.schema = new CayenneUndoableTextField();
         this.schema.addCommitListener(this::setSchema);
@@ -89,7 +84,7 @@ public class ProcedureTab extends JPanel implements ProcedureDisplayListener, Ex
         builder.setDefaultDialogBorder();
 
         builder.appendSeparator("Stored Procedure Configuration");
-        builder.append("Procedure Name:", name.getComponent());
+        builder.append("Procedure Name:", name);
         builder.append("Catalog:", catalog);
         builder.append("Schema:", schema);
         builder.append("Returns Value:", returnsValue);
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/query/BaseQueryMainTab.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/query/BaseQueryMainTab.java
index 83a2194b4..fd26304da 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/query/BaseQueryMainTab.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/query/BaseQueryMainTab.java
@@ -27,7 +27,7 @@ import org.apache.cayenne.map.ObjEntity;
 import org.apache.cayenne.map.QueryDescriptor;
 import org.apache.cayenne.modeler.ui.project.ProjectController;
 import org.apache.cayenne.modeler.swing.CellRenderers;
-import org.apache.cayenne.modeler.util.TextAdapter;
+import org.apache.cayenne.modeler.swing.text.CayenneUndoableTextField;
 import org.apache.cayenne.modeler.swing.WidgetFactory;
 import org.apache.cayenne.modeler.swing.combo.AutoCompletion;
 import org.apache.cayenne.util.Util;
@@ -38,7 +38,7 @@ import javax.swing.*;
 public abstract class BaseQueryMainTab extends JPanel {
 
     protected final ProjectController controller;
-    protected TextAdapter name;
+    protected CayenneUndoableTextField name;
     protected JComboBox<ObjEntity> queryRoot;
 
     protected BaseQueryMainTab(ProjectController controller) {
@@ -57,7 +57,7 @@ public abstract class BaseQueryMainTab extends JPanel {
         queryRoot.getEditor().getEditorComponent().addFocusListener(rootHandler);
     }
 
-    public TextAdapter getNameField() {
+    public CayenneUndoableTextField getNameField() {
         return name;
     }
 
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/query/ObjectQueryPropertiesPanel.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/query/ObjectQueryPropertiesPanel.java
index fb6e2ff52..a970a2c19 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/query/ObjectQueryPropertiesPanel.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/query/ObjectQueryPropertiesPanel.java
@@ -65,11 +65,11 @@ public class ObjectQueryPropertiesPanel extends SelectPropertiesPanel {
         builder.addLabel("Fetch Data Rows:", cc.xy(1, 9));
         builder.add(dataRows, cc.xy(3, 9));
         builder.addLabel("Fetch Offset, Rows:", cc.xy(1, 11));
-        builder.add(fetchOffset.getComponent(), cc.xy(3, 11));
+        builder.add(fetchOffset, cc.xy(3, 11));
         builder.addLabel("Fetch Limit, Rows:", cc.xy(1, 13));
-        builder.add(fetchLimit.getComponent(), cc.xy(3, 13));
+        builder.add(fetchLimit, cc.xy(3, 13));
         builder.addLabel("Page Size:", cc.xy(1, 15));
-        builder.add(pageSize.getComponent(), cc.xy(3, 15));
+        builder.add(pageSize, cc.xy(3, 15));
 
         this.setLayout(new BorderLayout());
         this.add(builder.getPanel(), BorderLayout.CENTER);
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/query/RawQueryPropertiesPanel.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/query/RawQueryPropertiesPanel.java
index 40ce08531..f141affce 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/query/RawQueryPropertiesPanel.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/query/RawQueryPropertiesPanel.java
@@ -83,11 +83,11 @@ public abstract class RawQueryPropertiesPanel extends SelectPropertiesPanel {
         builder.add(persistentObjects, cc.xy(3, 9));
         builder.add(entities, cc.xywh(5, 9, 3, 1));
         builder.addLabel("Fetch Offset, Rows:", cc.xy(1, 11));
-        builder.add(fetchOffset.getComponent(), cc.xywh(3, 11, 3, 1));
+        builder.add(fetchOffset, cc.xywh(3, 11, 3, 1));
         builder.addLabel("Fetch Limit, Rows:", cc.xy(1, 13));
-        builder.add(fetchLimit.getComponent(), cc.xywh(3, 13, 3, 1));
+        builder.add(fetchLimit, cc.xywh(3, 13, 3, 1));
         builder.addLabel("Page Size:", cc.xy(1, 15));
-        builder.add(pageSize.getComponent(), cc.xywh(3, 15, 3, 1));
+        builder.add(pageSize, cc.xywh(3, 15, 3, 1));
         return builder;
     }
 
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/query/RootSelectionHandler.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/query/RootSelectionHandler.java
index 8609c592e..0b801227a 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/query/RootSelectionHandler.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/query/RootSelectionHandler.java
@@ -101,6 +101,6 @@ class RootSelectionHandler implements FocusListener, ActionListener {
         String prefix = query.getRoot() == null ? "UntitledQuery" :
                 CellRenderers.asString(query.getRoot(), queryTab.getController().getSelectedDataMap()) + "Query";
 
-        return queryTab.getNameField().getComponent().getText().startsWith(prefix);
+        return queryTab.getNameField().getText().startsWith(prefix);
     }
 }
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/query/SelectPropertiesPanel.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/query/SelectPropertiesPanel.java
index 599b3b2b8..16ce4ce8a 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/query/SelectPropertiesPanel.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/query/SelectPropertiesPanel.java
@@ -30,14 +30,12 @@ import javax.swing.JComboBox;
 import javax.swing.JComponent;
 import javax.swing.JList;
 import javax.swing.JPanel;
-import javax.swing.JTextField;
 
 import org.apache.cayenne.modeler.event.model.QueryEvent;
 import org.apache.cayenne.modeler.Application;
 import org.apache.cayenne.modeler.swing.WidgetFactory;
 import org.apache.cayenne.modeler.swing.text.CayenneUndoableTextField;
 import org.apache.cayenne.modeler.ui.project.ProjectController;
-import org.apache.cayenne.modeler.util.TextAdapter;
 import org.apache.cayenne.query.QueryCacheStrategy;
 import org.apache.cayenne.map.QueryDescriptor;
 import org.apache.cayenne.query.QueryMetadata;
@@ -77,9 +75,9 @@ public abstract class SelectPropertiesPanel extends JPanel {
         cachePolicyLabels.put(QueryCacheStrategy.SHARED_CACHE,  SHARED_CACHE_LABEL);
     }
 
-    protected TextAdapter fetchOffset;
-    protected TextAdapter fetchLimit;
-    protected TextAdapter pageSize;
+    protected CayenneUndoableTextField fetchOffset;
+    protected CayenneUndoableTextField fetchLimit;
+    protected CayenneUndoableTextField pageSize;
     protected JComboBox<QueryCacheStrategy> cacheStrategy;
     protected CayenneUndoableTextField cacheGroups;
     protected JComponent cacheGroupsLabel;
@@ -93,23 +91,14 @@ public abstract class SelectPropertiesPanel extends JPanel {
     }
 
     protected void initView() {
-        fetchOffset = new TextAdapter(new JTextField(7)) {
-            protected void updateModel(String text) {
-                setFetchOffset(text);
-            }
-        };
+        fetchOffset = new CayenneUndoableTextField(7);
+        fetchOffset.addCommitListener(this::setFetchOffset);
 
-        fetchLimit = new TextAdapter(new JTextField(7)) {
-            protected void updateModel(String text) {
-                setFetchLimit(text);
-            }
-        };
+        fetchLimit = new CayenneUndoableTextField(7);
+        fetchLimit.addCommitListener(this::setFetchLimit);
 
-        pageSize = new TextAdapter(new JTextField(7)) {
-            protected void updateModel(String text) {
-                setPageSize(text);
-            }
-        };
+        pageSize = new CayenneUndoableTextField(7);
+        pageSize.addCommitListener(this::setPageSize);
 
         cacheStrategy = WidgetFactory.createUndoableComboBox();
         cacheStrategy.setRenderer(new CacheStrategyRenderer());
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/query/ejbql/EjbqlQueryMainTab.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/query/ejbql/EjbqlQueryMainTab.java
index f897a41f4..bf327314b 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/query/ejbql/EjbqlQueryMainTab.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/query/ejbql/EjbqlQueryMainTab.java
@@ -28,7 +28,6 @@ import org.apache.cayenne.map.EntityResolver;
 import org.apache.cayenne.map.MappingNamespace;
 import org.apache.cayenne.modeler.swing.text.CayenneUndoableTextField;
 import org.apache.cayenne.modeler.ui.project.ProjectController;
-import org.apache.cayenne.modeler.util.TextAdapter;
 import org.apache.cayenne.map.QueryDescriptor;
 import org.apache.cayenne.project.extension.info.ObjectInfo;
 import org.apache.cayenne.util.Util;
@@ -41,10 +40,9 @@ import com.jgoodies.forms.layout.FormLayout;
 public class EjbqlQueryMainTab extends JPanel{
 
     protected ProjectController mediator;
-    protected TextAdapter name;
+    protected CayenneUndoableTextField name;
     protected CayenneUndoableTextField comment;
     protected EjbqlQueryPropertiesPanel properties;
-    protected TextAdapter qualifier;
 
     public EjbqlQueryMainTab(ProjectController mediator) {
         this.mediator = mediator;
@@ -53,12 +51,8 @@ public class EjbqlQueryMainTab extends JPanel{
 
     private void initView() {
         // create widgets
-        name = new TextAdapter(new JTextField()) {
-            @Override
-            protected void updateModel(String text) {
-                setQueryName(text);
-            }
-        };
+        name = new CayenneUndoableTextField();
+        name.addCommitListener(this::setQueryName);
 
         comment = new CayenneUndoableTextField();
         comment.addCommitListener(this::setQueryComment);
@@ -73,7 +67,7 @@ public class EjbqlQueryMainTab extends JPanel{
         builder.setDefaultDialogBorder();
         builder.addSeparator("EJBQL Query Settings", cc.xywh(1, 1, 3, 1));
         builder.addLabel("Query Name:", cc.xy(1, 3));
-        builder.add(name.getComponent(), cc.xy(3, 3));
+        builder.add(name, cc.xy(3, 3));
         builder.addLabel("Comment:", cc.xy(1, 5));
         builder.add(comment, cc.xy(3, 5));
 
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/query/ejbql/EjbqlQueryPropertiesPanel.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/query/ejbql/EjbqlQueryPropertiesPanel.java
index a3d728ad8..d4a774e38 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/query/ejbql/EjbqlQueryPropertiesPanel.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/query/ejbql/EjbqlQueryPropertiesPanel.java
@@ -49,11 +49,11 @@ public class EjbqlQueryPropertiesPanel extends SelectPropertiesPanel {
         cacheGroupsLabel = builder.addLabel("Cache Group:", cc.xy(1, 7));
         builder.add(cacheGroups, cc.xywh(3, 7, 5, 1));
         builder.addLabel("Fetch Offset, Rows:", cc.xy(1, 9));
-        builder.add(fetchOffset.getComponent(), cc.xywh(3, 9, 3, 1));
+        builder.add(fetchOffset, cc.xywh(3, 9, 3, 1));
         builder.addLabel("Fetch Limit, Rows:", cc.xy(1, 11));
-        builder.add(fetchLimit.getComponent(), cc.xywh(3, 11, 3, 1));
+        builder.add(fetchLimit, cc.xywh(3, 11, 3, 1));
         builder.addLabel("Page Size:", cc.xy(1, 13));
-        builder.add(pageSize.getComponent(), cc.xywh(3, 13, 3, 1));
+        builder.add(pageSize, cc.xywh(3, 13, 3, 1));
         return builder;
     }
 
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/query/selectquery/SelectQueryMainTab.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/query/selectquery/SelectQueryMainTab.java
index 1d5bafdc9..2848333b1 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/query/selectquery/SelectQueryMainTab.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/query/selectquery/SelectQueryMainTab.java
@@ -28,7 +28,6 @@ import java.util.Iterator;
 
 import javax.swing.DefaultComboBoxModel;
 import javax.swing.JCheckBox;
-import javax.swing.JTextField;
 
 import org.apache.cayenne.modeler.event.model.QueryEvent;
 import org.apache.cayenne.exp.Expression;
@@ -44,8 +43,6 @@ import org.apache.cayenne.modeler.swing.text.CayenneUndoableTextField;
 import org.apache.cayenne.modeler.ui.project.ProjectController;
 import org.apache.cayenne.modeler.util.Comparators;
 import org.apache.cayenne.modeler.util.ExpressionConvertor;
-import org.apache.cayenne.modeler.util.TextAdapter;
-import org.apache.cayenne.modeler.swing.text.ValidatorTextAdapter;
 import org.apache.cayenne.project.extension.info.ObjectInfo;
 import org.apache.cayenne.util.CayenneMapEntry;
 import org.apache.cayenne.util.Util;
@@ -62,7 +59,7 @@ import com.jgoodies.forms.layout.FormLayout;
 public class SelectQueryMainTab extends BaseQueryMainTab {
 
     protected CayenneUndoableTextField comment;
-    protected TextAdapter qualifier;
+    protected CayenneUndoableTextField qualifier;
     protected JCheckBox distinct;
     protected ObjectQueryPropertiesPanel properties;
 
@@ -76,26 +73,11 @@ public class SelectQueryMainTab extends BaseQueryMainTab {
 
     private void initView() {
         // create widgets
-        name = new TextAdapter(new JTextField()) {
+        name = new CayenneUndoableTextField();
+        name.addCommitListener(this::setQueryName);
 
-            @Override
-            protected void updateModel(String text) {
-                setQueryName(text);
-            }
-        };
-
-        qualifier = new ValidatorTextAdapter(new JTextField()) {
-
-            @Override
-            protected void updateModel(String text) {
-                setQueryQualifier(text);
-            }
-
-            @Override
-            protected void validate(String text) throws ValidationException {
-                createQualifier(text);
-            }
-        };
+        qualifier = new CayenneUndoableTextField();
+        qualifier.addCommitListener(this::setQueryQualifier);
 
         comment = new CayenneUndoableTextField();
         comment.addCommitListener(this::setQueryComment);
@@ -114,11 +96,11 @@ public class SelectQueryMainTab extends BaseQueryMainTab {
 
         builder.addSeparator("SelectQuery Settings", cc.xywh(1, 1, 3, 1));
         builder.addLabel("Query Name:", cc.xy(1, 3));
-        builder.add(name.getComponent(), cc.xy(3, 3));
+        builder.add(name, cc.xy(3, 3));
         builder.addLabel("Query Root:", cc.xy(1, 5));
         builder.add(queryRoot, cc.xy(3, 5));
         builder.addLabel("Qualifier:", cc.xy(1, 7));
-        builder.add(qualifier.getComponent(), cc.xy(3, 7));
+        builder.add(qualifier, cc.xy(3, 7));
         builder.addLabel("Distinct:", cc.xy(1, 9));
         builder.add(distinct, cc.xy(3, 9));
         builder.addLabel("Comment:", cc.xy(1, 11));
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/query/sqltemplate/SQLTemplateMainTab.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/query/sqltemplate/SQLTemplateMainTab.java
index 821d23c08..cfe073d24 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/query/sqltemplate/SQLTemplateMainTab.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/query/sqltemplate/SQLTemplateMainTab.java
@@ -35,7 +35,6 @@ import org.apache.cayenne.modeler.swing.WidgetFactory;
 import org.apache.cayenne.modeler.swing.text.CayenneUndoableTextField;
 import org.apache.cayenne.modeler.ui.project.ProjectController;
 import org.apache.cayenne.modeler.util.Comparators;
-import org.apache.cayenne.modeler.util.TextAdapter;
 import org.apache.cayenne.project.extension.info.ObjectInfo;
 import org.apache.cayenne.query.CapsStrategy;
 import org.apache.cayenne.query.SQLTemplate;
@@ -85,12 +84,8 @@ public class SQLTemplateMainTab extends BaseQueryMainTab {
 
     private void initView() {
         // create widgets
-        name = new TextAdapter(new JTextField()) {
-            @Override
-            protected void updateModel(String text) {
-                setQueryName(text);
-            }
-        };
+        name = new CayenneUndoableTextField();
+        name.addCommitListener(this::setQueryName);
 
         comment = new CayenneUndoableTextField();
         comment.addCommitListener(this::setQueryComment);
@@ -107,7 +102,7 @@ public class SQLTemplateMainTab extends BaseQueryMainTab {
 
         builder.addSeparator("SQLTemplate Settings", cc.xywh(1, 1, 3, 1));
         builder.addLabel("Query Name:", cc.xy(1, 3));
-        builder.add(name.getComponent(), cc.xy(3, 3));
+        builder.add(name, cc.xy(3, 3));
         builder.addLabel("Comment:", cc.xy(1, 5));
         builder.add(comment, cc.xy(3, 5));
         builder.addLabel("Query Root:", cc.xy(1, 7));
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/undo/JTextFieldUndoListener.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/undo/JTextFieldUndoListener.java
index 08a9b3c6d..7c0fa4bc5 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/undo/JTextFieldUndoListener.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/undo/JTextFieldUndoListener.java
@@ -19,7 +19,6 @@
 package org.apache.cayenne.modeler.undo;
 
 import org.apache.cayenne.modeler.Application;
-import org.apache.cayenne.modeler.util.TextAdapter;
 
 import javax.swing.event.UndoableEditEvent;
 import javax.swing.event.UndoableEditListener;
@@ -35,18 +34,12 @@ public class JTextFieldUndoListener implements UndoableEditListener {
 
     private final JTextComponent editor;
     private CompoundEdit compoundEdit;
-    private TextAdapter adapter;
 
     private int lastOffset;
     private int lastLength;
 
     private boolean isKeyEdit;
 
-    public JTextFieldUndoListener(TextAdapter adapter) {
-        this(adapter.getComponent());
-        this.adapter = adapter;
-    }
-
     public JTextFieldUndoListener(JTextComponent editor) {
         this.editor = editor;
         this.editor.addFocusListener(new FocusAdapter() {
@@ -98,9 +91,7 @@ public class JTextFieldUndoListener implements UndoableEditListener {
         lastOffset = editor.getCaretPosition();
         lastLength = editor.getDocument().getLength();
 
-        compoundEdit = (this.adapter != null)
-                ? new TextCompoundEdit(adapter, this)
-                : new TextCompoundEdit(editor, this);
+        compoundEdit = new TextCompoundEdit(editor, this);
 
         if (isKeyEdit) {
             compoundEdit.addEdit(anEdit);
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/undo/TextCompoundEdit.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/undo/TextCompoundEdit.java
index 62b8ea9b4..d883b4dec 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/undo/TextCompoundEdit.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/undo/TextCompoundEdit.java
@@ -28,7 +28,6 @@ import org.apache.cayenne.modeler.ui.ModelerFrame;
 import org.apache.cayenne.modeler.ui.project.ProjectView;
 import org.apache.cayenne.modeler.ui.project.editor.EditorPanelView;
 import org.apache.cayenne.modeler.ui.project.editor.query.sqltemplate.SQLTemplateTabbedView;
-import org.apache.cayenne.modeler.util.TextAdapter;
 import org.apache.cayenne.query.EJBQLQuery;
 import org.apache.cayenne.query.SQLTemplate;
 
@@ -44,7 +43,6 @@ import javax.swing.undo.CompoundEdit;
 
 public class TextCompoundEdit extends CompoundEdit implements DocumentListener {
 
-    private TextAdapter adapter;
     private final JTextComponent editor;
 
     private final TreePath treePath;
@@ -56,11 +54,6 @@ public class TextCompoundEdit extends CompoundEdit implements DocumentListener {
 
     private final JTextFieldUndoListener listener;
 
-    public TextCompoundEdit(TextAdapter adapter, JTextFieldUndoListener listener) {
-        this(adapter.getComponent(), listener);
-        this.adapter = adapter;
-    }
-
     public TextCompoundEdit(JTextComponent editor, JTextFieldUndoListener listener) {
 
         this.editor = editor;
@@ -179,10 +172,6 @@ public class TextCompoundEdit extends CompoundEdit implements DocumentListener {
             die();
         }
 
-        if (adapter != null) {
-            adapter.updateModel();
-        }
-
         editor.requestFocusInWindow();
         editor.selectAll();
     }
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/util/TextAdapter.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/util/TextAdapter.java
deleted file mode 100644
index 9566c9286..000000000
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/util/TextAdapter.java
+++ /dev/null
@@ -1,154 +0,0 @@
-/*****************************************************************
- *   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
- *
- *    https://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.cayenne.modeler.util;
-
-import org.apache.cayenne.modeler.ui.project.validator.ProjectValidatorDialogView;
-import org.apache.cayenne.modeler.undo.JTextFieldUndoListener;
-import org.apache.cayenne.validation.ValidationException;
-
-import javax.swing.*;
-import javax.swing.event.DocumentEvent;
-import javax.swing.event.DocumentListener;
-import javax.swing.event.UndoableEditListener;
-import javax.swing.text.JTextComponent;
-import java.awt.*;
-
-/**
- * A validating adapter for JTextComponent. Implement {@link #updateModel(String)} initialize model on text change.
- */
-public abstract class TextAdapter {
-
-    protected Color defaultBGColor;
-    protected Color errorColor;
-
-    protected String defaultToolTip;
-    protected boolean modelUpdateDisabled;
-    protected UndoableEditListener undoableListener;
-
-    protected JTextComponent textComponent;
-
-    public TextAdapter(JTextField textField) {
-        this(textField, true);
-    }
-
-    public TextAdapter(JTextField textField, boolean checkOnEnter) {
-        this(textField, true, false);
-        if (checkOnEnter) {
-            textField.addActionListener(e -> updateModel());
-        }
-    }
-
-    public TextAdapter(JTextComponent textField) {
-        this(textField, false, true);
-    }
-
-    public TextAdapter(JTextComponent textComponent, boolean checkOnFocusLost, boolean checkOnTyping) {
-
-        this.errorColor = ProjectValidatorDialogView.WARNING_COLOR;
-        this.defaultBGColor = textComponent.getBackground();
-        this.defaultToolTip = textComponent.getToolTipText();
-        this.textComponent = textComponent;
-
-        this.undoableListener = new JTextFieldUndoListener(this);
-        this.textComponent.getDocument().addUndoableEditListener(this.undoableListener);
-
-        if (checkOnFocusLost) {
-            textComponent.setInputVerifier(new InputVerifier() {
-
-                public boolean verify(JComponent c) {
-                    SwingUtilities.invokeLater(() -> updateModel());
-                    // release focus after coloring the field...
-                    return true;
-                }
-            });
-        }
-
-        if (checkOnTyping) {
-            textComponent.getDocument().addDocumentListener(new DocumentListener() {
-
-                public void insertUpdate(DocumentEvent e) {
-                    verifyTextChange();
-                }
-
-                public void changedUpdate(DocumentEvent e) {
-                    verifyTextChange();
-                }
-
-                public void removeUpdate(DocumentEvent e) {
-                    verifyTextChange();
-                }
-
-                void verifyTextChange() {
-                    if (!modelUpdateDisabled) {
-                        updateModel();
-                    }
-                }
-            });
-        }
-    }
-
-    /**
-     * Updates bound model with document text.
-     */
-    protected abstract void updateModel(String text) throws ValidationException;
-
-    /**
-     * Returns internal text component.
-     */
-    public JTextComponent getComponent() {
-        return textComponent;
-    }
-
-    /**
-     * Sets the text of the underlying text field.
-     */
-    public void setText(String text) {
-        modelUpdateDisabled = true;
-
-        this.textComponent
-                .getDocument()
-                .removeUndoableEditListener(this.undoableListener);
-
-        try {
-            clear();
-            textComponent.setText(text);
-        } finally {
-            modelUpdateDisabled = false;
-            this.textComponent.getDocument().addUndoableEditListener(this.undoableListener);
-        }
-
-    }
-
-    public void updateModel() {
-        try {
-            updateModel(textComponent.getText());
-            clear();
-        } catch (ValidationException vex) {
-            textComponent.setBackground(errorColor);
-            textComponent.setToolTipText(vex.getUnlabeledMessage());
-        }
-    }
-
-    protected void clear() {
-        textComponent.setBackground(defaultBGColor);
-        textComponent.setToolTipText(defaultToolTip);
-    }
-
-}