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/05/05 22:21:37 UTC

(cayenne) 01/06: Modeler - controller free architecture

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 d77698ed2c4ec2511567a2f9b2b1bbd7810cba02
Author: Andrus Adamchik <an...@objectstyle.com>
AuthorDate: Tue May 5 15:48:58 2026 -0400

    Modeler - controller free architecture
---
 .claude/rules/modeler/modeler-architecture.md      |  24 +-
 .../org/apache/cayenne/modeler/Application.java    |  33 +-
 .../modeler/event/model/ProjectAfterSaveEvent.java |   8 +-
 .../cayenne/modeler/graph/BaseGraphBuilder.java    |  25 +-
 .../cayenne/modeler/graph/DbGraphBuilder.java      |  18 +-
 .../apache/cayenne/modeler/graph/GraphBuilder.java |   4 +-
 .../org/apache/cayenne/modeler/graph/GraphMap.java | 213 +++++---
 .../cayenne/modeler/graph/GraphRegistry.java       |  23 +-
 .../cayenne/modeler/graph/ObjGraphBuilder.java     |  18 +-
 .../cayenne/modeler/mvc/ChildController.java       |  55 --
 .../apache/cayenne/modeler/mvc/RootController.java | 118 -----
 .../apache/cayenne/modeler/project/CgenOps.java    |   2 +-
 .../ProjectDirtyListener.java}                     |  22 +-
 .../{ui => }/project/ProjectFileChangeTracker.java |  32 +-
 .../{ui => }/project/ProjectNavigationHistory.java |  28 +-
 .../modeler/{ui => }/project/ProjectPrefs.java     | 198 ++++----
 .../ProjectSession.java}                           | 148 +++---
 .../modeler/service/graph/GraphHandler.java        |  23 +-
 .../validator/ConfigurableProjectValidator.java    |   2 +-
 .../apache/cayenne/modeler/toolkit/AppDialog.java  | 105 ++++
 .../NavigateAction.java => toolkit/AppFrame.java}  |  25 +-
 .../AppPanel.java}                                 |  34 +-
 .../ProjectDialog.java}                            |  34 +-
 .../ProjectPanel.java}                             |  28 +-
 .../ProjectTabbedPane.java}                        |  31 +-
 .../apache/cayenne/modeler/toolkit/Renderers.java  |  14 +-
 .../cayenne/modeler/toolkit/dialog/CMDialog.java   |  90 ----
 .../modeler/toolkit/table/CMTableModel.java        |  16 +-
 .../org/apache/cayenne/modeler/ui/MainFrame.java   | 428 ++++++++++++++++
 .../ui/{ModelerMenuBar.java => MainMenuBar.java}   |   4 +-
 .../cayenne/modeler/ui/ModelerController.java      | 252 ---------
 .../apache/cayenne/modeler/ui/ModelerFrame.java    | 189 -------
 .../cayenne/modeler/ui/about/AboutDialog.java      |   5 +-
 .../modeler/ui/action/BaseViewEntityAction.java    |  10 +-
 .../modeler/ui/action/CollapseTreeAction.java      |   2 +-
 .../ui/action/ConfigurePreferencesAction.java      |   6 +-
 .../cayenne/modeler/ui/action/CopyAction.java      |  10 +-
 .../modeler/ui/action/CopyAttributeAction.java     |  10 +-
 .../ui/action/CopyAttributeRelationshipAction.java |   8 +-
 .../ui/action/CopyCallbackMethodAction.java        |   6 +-
 .../ui/action/CopyProcedureParameterAction.java    |   6 +-
 .../modeler/ui/action/CopyRelationshipAction.java  |   8 +-
 .../modeler/ui/action/CreateAttributeAction.java   |  63 ++-
 .../ui/action/CreateCallbackMethodAction.java      |  10 +-
 .../modeler/ui/action/CreateDataMapAction.java     |  23 +-
 .../modeler/ui/action/CreateDbEntityAction.java    |  18 +-
 .../modeler/ui/action/CreateEmbeddableAction.java  |  17 +-
 .../modeler/ui/action/CreateNodeAction.java        |  17 +-
 .../modeler/ui/action/CreateObjEntityAction.java   |  22 +-
 .../modeler/ui/action/CreateProcedureAction.java   |  19 +-
 .../ui/action/CreateProcedureParameterAction.java  |  25 +-
 .../modeler/ui/action/CreateQueryAction.java       |  15 +-
 .../ui/action/CreateRelationshipAction.java        |  43 +-
 .../modeler/ui/action/DBConnectionAwareAction.java |  15 +-
 .../ui/action/DbEntityCounterpartAction.java       |   2 +-
 .../modeler/ui/action/DbEntitySyncAction.java      |  20 +-
 .../action/DisableValidationInspectionAction.java  |   8 +-
 .../cayenne/modeler/ui/action/FilterAction.java    |   8 +-
 .../cayenne/modeler/ui/action/FindAction.java      |  76 +--
 .../modeler/ui/action/GenerateDBAction.java        |  13 +-
 .../modeler/ui/action/ImportDataMapAction.java     |   8 +-
 .../modeler/ui/action/ImportEOModelAction.java     |  49 +-
 .../ui/action/InferRelationshipsAction.java        |   9 +-
 .../modeler/ui/action/LinkDataMapAction.java       |  12 +-
 .../modeler/ui/action/LinkDataMapsAction.java      |  12 +-
 .../cayenne/modeler/ui/action/MigrateAction.java   |  22 +-
 .../modeler/ui/action/ModelerAbstractAction.java   |  10 +-
 .../cayenne/modeler/ui/action/NavigateAction.java  |   6 +-
 .../modeler/ui/action/NavigateBackwardAction.java  |   6 +-
 .../modeler/ui/action/NavigateForwardAction.java   |   6 +-
 .../modeler/ui/action/NewProjectAction.java        |   6 +-
 .../ui/action/ObjEntityCounterpartAction.java      |   2 +-
 .../modeler/ui/action/ObjEntitySyncAction.java     |  20 +-
 .../ui/action/ObjEntityToSuperEntityAction.java    |   2 +-
 .../modeler/ui/action/OpenProjectAction.java       |  18 +-
 .../cayenne/modeler/ui/action/PasteAction.java     |  58 +--
 .../cayenne/modeler/ui/action/ProjectAction.java   |  14 +-
 .../cayenne/modeler/ui/action/ProjectOpener.java   |   6 +-
 .../cayenne/modeler/ui/action/RemoveAction.java    | 246 ++++-----
 .../modeler/ui/action/RemoveAttributeAction.java   |  42 +-
 .../ui/action/RemoveCallbackMethodAction.java      |  19 +-
 .../ui/action/RemoveProcedureParameterAction.java  |  17 +-
 .../ui/action/RemoveRelationshipAction.java        |  30 +-
 .../cayenne/modeler/ui/action/RevertAction.java    |   4 +-
 .../cayenne/modeler/ui/action/SaveAction.java      |   8 +-
 .../cayenne/modeler/ui/action/SaveAsAction.java    |  21 +-
 .../modeler/ui/action/ShowLogConsoleAction.java    |   2 +-
 .../ui/action/ShowValidationConfigAction.java      |   8 +-
 .../ui/action/ShowValidationOptionAction.java      |   8 +-
 .../ui/action/UpdateValidationConfigAction.java    |   6 +-
 .../cayenne/modeler/ui/action/ValidateAction.java  |  37 +-
 .../InferRelationshipsController.java              | 439 ----------------
 .../autorelationship/InferRelationshipsDialog.java | 383 ++++++++++++--
 .../InferRelationshipsTabController.java           |  18 +-
 .../ui/confirmremove/ConfirmRemoveDialog.java      |   2 +-
 .../ui/datasource/DataSourceController.java        | 254 ---------
 .../modeler/ui/datasource/DataSourceDialog.java    | 278 ++++++++++
 .../modeler/ui/datasource/DataSourceView.java      |  82 ---
 .../ui/dbactionoptions/DbActionOptionsDialog.java  |  16 +-
 .../ui/dbgen/DBGeneratorOptionsController.java     | 300 -----------
 .../modeler/ui/dbgen/DBGeneratorOptionsDialog.java | 398 +++++++++++++++
 .../modeler/ui/dbgen/DBGeneratorOptionsView.java   | 228 ---------
 .../modeler/ui/dbgen/TableSelectorController.java  | 157 ------
 .../modeler/ui/dbgen/TableSelectorPanel.java       | 251 +++++++++
 .../modeler/ui/dbgen/TableSelectorView.java        | 140 -----
 ...onsController.java => MergerOptionsDialog.java} | 277 +++++-----
 .../modeler/ui/dbmerge/MergerOptionsView.java      | 138 -----
 ...torController.java => MergerTokenSelector.java} | 240 +++++----
 .../ui/dbmerge/MergerTokenSelectorView.java        |  99 ----
 .../modeler/ui/dbmerge/MergerTokenTableModel.java  |   6 +-
 .../ui/dbrelationship/DbJoinTableModel.java        |  10 +-
 ...ogController.java => DbRelationshipDialog.java} | 478 +++++++++--------
 .../dbrelationship/DbRelationshipDialogView.java   | 211 --------
 .../ui/entitysync/EntitySyncController.java        | 127 -----
 .../modeler/ui/entitysync/EntitySyncDialog.java    | 129 ++++-
 .../cayenne/modeler/ui/errors/ErrorDialog.java     |  15 +-
 .../apache/cayenne/modeler/ui/find/FindDialog.java | 190 +++++++
 .../modeler/ui/find/FindDialogController.java      | 106 ----
 .../cayenne/modeler/ui/find/FindDialogView.java    | 107 ----
 .../{LogConsoleController.java => LogConsole.java} | 139 ++---
 .../modeler/ui/logconsole/LogConsoleView.java      |  96 ----
 .../modeler/ui/preferences/PreferenceDialog.java   | 224 ++++++++
 .../ui/preferences/PreferenceDialogController.java | 147 ------
 .../ui/preferences/PreferenceDialogView.java       | 146 ------
 .../classpath/ClasspathPreferencesController.java  | 116 -----
 .../classpath/ClasspathPreferencesView.java        | 127 -----
 .../preferences/classpath/ClasspathPrefsPanel.java | 188 +++++++
 ...gController.java => MavenDependencyDialog.java} | 130 +++--
 .../classpath/maven/MavenDependencyDialogView.java |  96 ----
 ...ectorEditorView.java => DBConnectorEditor.java} |  77 ++-
 .../dbconnector/DBConnectorEditorController.java   |  79 ---
 .../DBConnectorPreferencesController.java          | 271 ----------
 .../dbconnector/DBConnectorPreferencesView.java    |  84 ---
 .../dbconnector/DBConnectorPrefsPanel.java         | 307 +++++++++++
 .../creator/DBConnectorCreatorController.java      | 131 -----
 .../creator/DBConnectorCreatorDialog.java          | 166 ++++++
 .../creator/DBConnectorCreatorView.java            |  82 ---
 .../DBConnectorDuplicatorController.java           | 125 -----
 .../duplicator/DBConnectorDuplicatorDialog.java    | 144 ++++++
 .../duplicator/DBConnectorDuplicatorView.java      |  61 ---
 .../general/GeneralPreferencesController.java      |  58 ---
 ...PreferencesView.java => GeneralPrefsPanel.java} |  73 +--
 .../more/MorePreferencesController.java            |  88 ----
 .../ui/preferences/more/MorePreferencesView.java   |  60 ---
 .../ui/preferences/more/MorePrefsPanel.java        | 123 +++++
 .../cayenne/modeler/ui/project/ProjectView.java    |  53 +-
 .../modeler/ui/project/editor/EditorPanelView.java |  48 +-
 .../editor/datadomain/DataDomainGeneratorsTab.java | 230 +++++++++
 .../datadomain/DataDomainGeneratorsView.java       | 113 -----
 .../DataDomainGeneratorsViewController.java        | 180 -------
 .../project/editor/datadomain/DataDomainView.java  |  52 +-
 ...nCgenController.java => DataDomainCgenTab.java} |  42 +-
 .../editor/datadomain/cgen/DataDomainCgenView.java |  47 --
 ...tController.java => DataDomainDbImportTab.java} |  22 +-
 .../dbimport/DataDomainDbImportView.java           |  32 --
 .../datadomain/graph/DataDomainGraphTab.java       |  32 +-
 .../graph/action/RemoveEntityAction.java           |   4 +-
 .../datadomain/graph/action/SaveAsImageAction.java |   4 +-
 .../graph/action/ShowGraphEntityAction.java        |  21 +-
 .../editor/datadomain/main/DataDomainMainView.java |  36 +-
 .../datadomain/validation/ValidationTab.java       | 149 +++++-
 .../validation/ValidationTabController.java        | 147 ------
 .../ui/project/editor/datamap/DataMapView.java     |  24 +-
 .../cgen/CgenArtefactSelectorController.java       | 194 -------
 .../datamap/cgen/CgenArtefactSelectorPanel.java    | 177 ++++++-
 .../editor/datamap/cgen/CgenConfigController.java  | 262 ----------
 .../editor/datamap/cgen/CgenConfigPanel.java       | 434 ++++++++++------
 .../ui/project/editor/datamap/cgen/CgenPane.java   | 133 -----
 .../cgen/{CgenController.java => CgenPanel.java}   | 565 +++++++++++----------
 .../cgen/templateeditor/EditorTemplateLoader.java  |   2 +-
 .../templateeditor/FindAndReplaceController.java   | 118 -----
 .../cgen/templateeditor/FindAndReplaceView.java    |  85 ----
 .../cgen/templateeditor/FindController.java        | 107 ----
 .../datamap/cgen/templateeditor/FindView.java      |  98 ----
 .../templateeditor/PreviewActionConfigurator.java  |  16 +-
 .../cgen/templateeditor/TemplateEditor.java        | 374 ++++++++++++++
 .../templateeditor/TemplateEditorController.java   | 267 ----------
 .../TemplateEditorFindAndReplaceDialog.java        | 118 +++++
 .../templateeditor/TemplateEditorFindDialog.java   | 132 +++++
 .../cgen/templateeditor/TemplateEditorView.java    | 207 --------
 .../datamap/dbimport/DbImportController.java       |  89 ----
 .../datamap/dbimport/DbImportResultDialog.java}    | 121 +++--
 .../editor/datamap/dbimport/DbImportView.java      |  25 +-
 .../dbimport/ReverseEngineeringConfigPanel.java    |  44 +-
 .../dbimport/ReverseEngineeringTreePanel.java      |  12 +-
 .../datamap/dbimport/action/DeleteNodeAction.java  |   4 +-
 .../dbimport/action/DragAndDropNodeAction.java     |   4 +-
 .../dbimport/action/GetDbConnectionAction.java     |   6 +-
 .../dbimport/action/LoadDbSchemaAction.java        |   6 +-
 .../dbimport/action/ModelerDbImportAction.java     |  26 +-
 .../dbimport/action/MoveImportNodeAction.java      |   6 +-
 .../datamap/dbimport/action/SortNodesAction.java   |   2 +-
 .../dbimport/action/TreeManipulationAction.java    |   6 +-
 .../dbimport/progress/DbImportProgressDialog.java  |  12 +-
 .../dbimport/runner/DbImportProjectSaver.java      |  13 +-
 .../dbimport/runner/DbSyncDbImportAction.java      |  13 +-
 .../dbimport/runner/ModelerDbImportModule.java     |   4 +-
 .../dbimport/runner/ModelerDbLoaderContext.java    |  14 +-
 .../dbimport/runner/ModelerDbLoaderDelegate.java   |   3 +-
 .../defaults/DefaultsPreferencesController.java    |  48 --
 ...ferencesView.java => DefaultsUpdateDialog.java} |  75 ++-
 .../editor/datamap/main/DataMapMainView.java       | 109 ++--
 ...ateController.java => CatalogUpdateDialog.java} |  59 +--
 .../main/locking/LockingUpdateController.java      | 106 ----
 .../datamap/main/locking/LockingUpdateDialog.java  | 150 ++++++
 .../datamap/main/locking/LockingUpdateView.java    | 100 ----
 ...ateController.java => PackageUpdateDialog.java} |  97 +---
 ...dateController.java => SchemaUpdateDialog.java} |  57 +--
 ...Controller.java => SuperclassUpdateDialog.java} |  64 +--
 .../editor/datanode/DataNodeController.java        | 321 ------------
 .../editor/datanode/DataNodeEditorPanel.java       | 338 ++++++++++++
 .../ui/project/editor/datanode/DataNodeView.java   | 129 -----
 ...eEditorController.java => DataSourcePanel.java} |  24 +-
 .../custom/CustomDataSourceEditorController.java   |  74 ---
 ...aSourceView.java => CustomDataSourcePanel.java} |  65 ++-
 .../datanode/jdbc/JDBCDataSourceController.java    | 142 ------
 .../editor/datanode/jdbc/JDBCDataSourcePanel.java  | 186 +++++++
 .../editor/datanode/jdbc/JDBCDataSourceView.java   | 111 ----
 .../ui/project/editor/dbentity/DbEntityView.java   |  27 +-
 .../editor/dbentity/main/DbEntityMainView.java     |  84 +--
 .../main/PKCustomSequenceGeneratorPanel.java       |  26 +-
 .../editor/dbentity/main/PKDBGeneratorPanel.java   |  10 +-
 .../dbentity/main/PKDefaultGeneratorPanel.java     |   6 +-
 .../editor/dbentity/main/PKGeneratorPanel.java     |  14 +-
 .../dbentity/properties/DbAttributePanel.java      |  36 +-
 .../dbentity/properties/DbAttributeTableModel.java |  14 +-
 .../properties/DbEntityPropertiesView.java         |  33 +-
 .../dbentity/properties/DbRelationshipPanel.java   |  50 +-
 .../properties/DbRelationshipTableModel.java       |  22 +-
 .../project/editor/embeddable/EmbeddableView.java  |  35 +-
 .../attributes/EmbeddableAttributeTableModel.java  |   8 +-
 .../attributes/EmbeddableAttributesView.java       |  74 +--
 .../editor/embeddable/main/EmbeddableMainView.java |  48 +-
 .../ui/project/editor/objentity/ObjEntityView.java |  24 +-
 ...Controller.java => ObjAttributeInfoDialog.java} | 498 ++++++++++--------
 .../attrinfo/ObjAttributeInfoDialogView.java       | 253 ---------
 .../OverrideEmbeddableAttributeTableModel.java     |   9 +-
 .../callbacks/CallbackDescriptorTableModel.java    |  10 +-
 .../callbacks/ObjEntityCallbacksView.java          |  53 +-
 .../classname/ClassNameUpdaterController.java      |  16 +-
 .../duplicates/DuplicatedAttributesDialog.java     |  30 +-
 .../editor/objentity/main/ObjEntityMainView.java   | 110 ++--
 .../DbRelationshipPathComboBoxEditor.java          |  12 +-
 .../objentity/properties/ObjAttributePanel.java    |  55 +-
 .../properties/ObjAttributeTableModel.java         |  34 +-
 .../properties/ObjEntityPropertiesView.java        |  20 +-
 .../objentity/properties/ObjRelationshipPanel.java |  44 +-
 .../properties/ObjRelationshipTableModel.java      |  12 +-
 ...troller.java => ObjRelationshipInfoDialog.java} | 471 +++++++++--------
 .../objentity/relinfo/ObjRelationshipInfoView.java | 213 --------
 .../editor/procedure/ProcedureParameterTab.java    |  35 +-
 .../procedure/ProcedureParameterTableModel.java    |   8 +-
 .../editor/procedure/ProcedureQueryView.java       |  54 +-
 .../ui/project/editor/procedure/ProcedureTab.java  |  54 +-
 .../editor/procedure/ProcedureTabbedView.java      |  29 +-
 .../ui/project/editor/query/BaseQueryMainTab.java  |  20 +-
 .../editor/query/ObjectQueryPropertiesPanel.java   |   8 +-
 .../editor/query/RawQueryPropertiesPanel.java      |  12 +-
 .../project/editor/query/RootSelectionHandler.java |   4 +-
 .../editor/query/SelectPropertiesPanel.java        |  22 +-
 .../editor/query/ejbql/EjbqlQueryMainTab.java      |  47 +-
 .../query/ejbql/EjbqlQueryPropertiesPanel.java     |   6 +-
 .../editor/query/ejbql/EjbqlQueryScriptsTab.java   |  19 +-
 .../editor/query/ejbql/EjbqlTabbedView.java        |  21 +-
 .../query/selectquery/SelectQueryMainTab.java      |  36 +-
 .../query/selectquery/SelectQueryOrderingTab.java  |  22 +-
 .../query/selectquery/SelectQueryPrefetchTab.java  |  14 +-
 .../query/selectquery/SelectQueryTabbedView.java   |  22 +-
 .../query/sqltemplate/SQLTemplateMainTab.java      |  34 +-
 .../query/sqltemplate/SQLTemplatePrefetchTab.java  |  24 +-
 .../query/sqltemplate/SQLTemplateScriptsTab.java   |  24 +-
 .../query/sqltemplate/SQLTemplateTabbedView.java   |  25 +-
 .../ui/project/querytype/QueryTypeController.java  | 129 -----
 .../ui/project/querytype/QueryTypeDialog.java      | 143 ++++++
 .../ui/project/querytype/QueryTypeView.java        | 117 -----
 ...ProjectTreeController.java => ProjectTree.java} | 466 ++++++++++-------
 .../modeler/ui/project/tree/ProjectTreeView.java   | 200 --------
 .../modeler/ui/project/tree/TreeDragSource.java    |  10 +-
 .../modeler/ui/project/tree/TreeDropTarget.java    |  10 +-
 .../tree/treefilter/TreeFilterController.java      | 125 -----
 .../project/tree/treefilter/TreeFilterPopup.java   | 101 +++-
 .../ui/project/validator/AttributeErrorMsg.java    |  12 +-
 .../ui/project/validator/DataMapErrorMsg.java      |   6 +-
 .../ui/project/validator/DataNodeErrorMsg.java     |   6 +-
 .../ui/project/validator/DomainErrorMsg.java       |   6 +-
 .../validator/EmbeddableAttributeErrorMsg.java     |   8 +-
 .../ui/project/validator/EmbeddableErrorMsg.java   |   6 +-
 .../ui/project/validator/EntityErrorMsg.java       |   8 +-
 .../ui/project/validator/ProcedureErrorMsg.java    |  10 +-
 .../validator/ProcedureParameterErrorMsg.java      |  12 +-
 ...DialogView.java => ProjectValidatorDialog.java} | 121 +++--
 .../ProjectValidatorDialogController.java          |  89 ----
 .../ui/project/validator/QueryErrorMsg.java        |  10 +-
 .../ui/project/validator/RelationshipErrorMsg.java |  12 +-
 .../validator/ValidationDisplayHandler.java        |  10 +-
 .../ui/validation/ValidationController.java        | 111 ----
 .../modeler/ui/validation/ValidationDialog.java    | 144 ++++++
 .../modeler/ui/validation/ValidationView.java      |  94 ----
 .../undo/BaseRemovePropertyUndoableEdit.java       |  10 +-
 .../cayenne/modeler/undo/CayenneUndoableEdit.java  |  10 +-
 .../undo/ChangePKGeneratorUndoableEdit.java        |  12 +-
 .../modeler/undo/CreateAttributeUndoableEdit.java  |  14 +-
 .../undo/CreateCallbackMethodUndoableEdit.java     |   6 +-
 .../modeler/undo/CreateDataMapUndoableEdit.java    |  10 +-
 .../modeler/undo/CreateDbEntityUndoableEdit.java   |   6 +-
 .../undo/CreateEmbAttributeUndoableEdit.java       |   6 +-
 .../modeler/undo/CreateEmbeddableUndoableEdit.java |   6 +-
 .../modeler/undo/CreateNodeUndoableEdit.java       |   8 +-
 .../modeler/undo/CreateObjEntityUndoableEdit.java  |   6 +-
 .../undo/CreateProcedureParameterUndoableEdit.java |   8 +-
 .../modeler/undo/CreateProcedureUndoableEdit.java  |   6 +-
 .../modeler/undo/CreateQueryUndoableEdit.java      |   6 +-
 .../undo/CreateRelationshipUndoableEdit.java       |  16 +-
 .../modeler/undo/DbEntitySyncUndoableEdit.java     |  14 +-
 .../modeler/undo/DbImportTreeUndoableEdit.java     |  16 +-
 .../modeler/undo/LinkDataMapUndoableEdit.java      |  10 +-
 .../modeler/undo/LinkDataMapsUndoableEdit.java     |  12 +-
 .../cayenne/modeler/undo/PasteUndoableEdit.java    |   6 +-
 .../modeler/undo/RelationshipUndoableEdit.java     |  10 +-
 .../modeler/undo/RemoveAttributeUndoableEdit.java  |  14 +-
 .../undo/RemoveCallbackMethodUndoableEdit.java     |   6 +-
 .../undo/RemoveRelationshipUndoableEdit.java       |  10 +-
 .../cayenne/modeler/undo/RemoveUndoableEdit.java   |  48 +-
 .../undo/UpdateValidationConfigUndoableEdit.java   |   6 +-
 .../modeler/util/NameGeneratorPreferences.java     |   2 +-
 .../templateeditor/EditorTemplateSaverTest.java    |   1 -
 .../PreviewActionConfiguratorTest.java             |  18 +-
 ...trollerTest.java => GeneralPrefsPanelTest.java} |   6 +-
 328 files changed, 10489 insertions(+), 13727 deletions(-)

diff --git a/.claude/rules/modeler/modeler-architecture.md b/.claude/rules/modeler/modeler-architecture.md
index 4ba3603cc..8685e4e97 100644
--- a/.claude/rules/modeler/modeler-architecture.md
+++ b/.claude/rules/modeler/modeler-architecture.md
@@ -4,17 +4,19 @@ paths:
 description: CayenneModeler UI components architecture
 ---
 
-CayenneModeler is a Swing application for visually managing Cayenne OR mapping files. 
+CayenneModeler is a Swing application for visually managing Cayenne OR mapping files.
 
-## CayenneModeler Architecture
-
-The architecture follows the MVP (Model-View-Presenter) pattern with own specifics:
-
-1. MVP instead of MVC ("presenter", not "controller")
-2. Not using interfaces for views (no goal to uncouple controllers from Swing yet)
-3. Views take their presenters in constructor to perform action bindings to the view controls. This keeps presenter
-   at least nominally Swing-agnostic, and in many cases removes the need for getters for the view parts.
-4. Each Presenter creates its own View. Parent Presenter can access that View to add to its own view.
-5. We are NOT yet using Presenter-level listeners for parent sake. We have a fixed hierarchy of Presenters, so this is an overkill.
+## CayenneModeler Architecture - Hierarchical Components
 
+Conventions:
 
+1. **Single class per screen** — view and logic merged. The Swing component IS the controller.
+2. **Hierarchical** — components compose other components; no separate controller tree.
+3. **Bases** (in `org.apache.cayenne.modeler.ui`): extend `AppFrame` for top-level `JFrame` windows, `AppDialog` for modal/modeless dialogs, `AppPanel` for embedded panels that need `Application` access. Plain `JPanel` is fine for self-contained widget panels with no app/session dependency. All three bases hold the `Application` reference and expose it via `app()`. Components that need `ProjectSession` in addition to the Application, have their own `Project*` bases.
+4. **Dependencies via constructor** — `Application` (and later `ProjectSession`) are passed in, exposed via `app()` / `session()` accessors. Do not use singletons or parent-walk-on-Swing-tree.
+5. **`private final` fields** for every widget, except those reassigned during the component's life (e.g. card-layout content swaps).
+6. **Layout vs. bindings vs. reactions** are separate methods on the component:
+   - `initLayout()` — pure visual structure, no listeners.
+   - `initBindings()` — listener wiring. Lambdas for short cases; **named private inner classes** for non-trivial listener bundles or grouped `Action`s (see `FindDialog.JumpToResultListener`).
+   - `onSessionAttached(session)` (when introduced) — subscribe to project events; reactions are methods on the component itself.
+7. **If a component grows past ~500 LOC, split it into smaller hierarchical sub-components** — do not split into view + controller.
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/Application.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/Application.java
index 13a386ca3..b681acd72 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/Application.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/Application.java
@@ -35,12 +35,11 @@ import org.apache.cayenne.modeler.pref.RecentProjectsPrefs;
 import org.apache.cayenne.modeler.service.action.GlobalActions;
 import org.apache.cayenne.modeler.service.classloader.ModelerClassLoader;
 import org.apache.cayenne.modeler.service.platform.PlatformInitializer;
-import org.apache.cayenne.modeler.ui.ModelerController;
+import org.apache.cayenne.modeler.ui.MainFrame;
 import org.apache.cayenne.modeler.ui.action.OpenProjectAction;
-import org.apache.cayenne.modeler.ui.logconsole.LogConsoleController;
+import org.apache.cayenne.modeler.ui.logconsole.LogConsole;
 import org.apache.cayenne.modeler.undo.CayenneUndoManager;
 import org.apache.cayenne.project.ConfigurationNodeParentGetter;
-import org.apache.cayenne.project.Project;
 import org.apache.cayenne.project.ProjectLoader;
 import org.apache.cayenne.project.ProjectSaver;
 import org.apache.cayenne.project.upgrade.UpgradeService;
@@ -66,8 +65,8 @@ public class Application {
 
     private final Injector injector;
     private final String name;
-    private LogConsoleController logConsoleController;
-    private ModelerController frameController;
+    private LogConsole logConsole;
+    private MainFrame frame;
     private CayenneUndoManager undoManager;
     private DBConnectors dbConnectors;
 
@@ -78,10 +77,6 @@ public class Application {
         this.name = (configuredName != null) ? configuredName : DEFAULT_APPLICATION_NAME;
     }
 
-    public Project getProject() {
-        return getFrameController().getProjectController().getProject();
-    }
-
     public String getName() {
         return name;
     }
@@ -138,17 +133,17 @@ public class Application {
         return undoManager;
     }
 
-    public ModelerController getFrameController() {
-        return frameController;
+    public MainFrame getFrame() {
+        return frame;
     }
 
-    public LogConsoleController getLogConsoleController() {
-        return logConsoleController;
+    public LogConsole getLogConsole() {
+        return logConsole;
     }
 
     public void startup(File initialProject) {
-        this.logConsoleController = new LogConsoleController(this);
-        ModelerLogFactory.setAppender(logConsoleController);
+        this.logConsole = new LogConsole(this);
+        ModelerLogFactory.setAppender(logConsole);
 
         getPreferencesRepository().runMigrations();
 
@@ -166,14 +161,14 @@ public class Application {
         }
 
         this.undoManager = new CayenneUndoManager(this);
-        this.frameController = new ModelerController(this);
+        this.frame = new MainFrame(this);
 
         // open up
-        frameController.onStartup();
+        frame.onStartup();
 
         // After prefs have been loaded, we can now show the console if needed
-        logConsoleController.showConsoleIfNeeded();
-        getFrameController().getView().setVisible(true);
+        logConsole.showConsoleIfNeeded();
+        getFrame().setVisible(true);
 
         if (initialProject == null) {
             initialProject = initialProjectFromPreferences();
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/event/model/ProjectAfterSaveEvent.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/event/model/ProjectAfterSaveEvent.java
index 01523a256..689266f7f 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/event/model/ProjectAfterSaveEvent.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/event/model/ProjectAfterSaveEvent.java
@@ -19,18 +19,18 @@
 
 package org.apache.cayenne.modeler.event.model;
 
-import org.apache.cayenne.modeler.ui.project.ProjectController;
+import org.apache.cayenne.modeler.project.ProjectSession;
 
 import java.util.EventObject;
 
 public class ProjectAfterSaveEvent extends EventObject {
 
-    public ProjectAfterSaveEvent(ProjectController source) {
+    public ProjectAfterSaveEvent(ProjectSession source) {
         super(source);
     }
 
     @Override
-    public ProjectController getSource() {
-        return (ProjectController) super.getSource();
+    public ProjectSession getSource() {
+        return (ProjectSession) super.getSource();
     }
 }
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/graph/BaseGraphBuilder.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/graph/BaseGraphBuilder.java
index b94f2574f..f07847fb4 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/graph/BaseGraphBuilder.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/graph/BaseGraphBuilder.java
@@ -47,7 +47,7 @@ import org.apache.cayenne.map.DataMap;
 import org.apache.cayenne.map.Entity;
 import org.apache.cayenne.map.Relationship;
 import org.apache.cayenne.modeler.Application;
-import org.apache.cayenne.modeler.ui.project.ProjectController;
+import org.apache.cayenne.modeler.project.ProjectSession;
 import org.apache.cayenne.modeler.service.action.GlobalActions;
 import org.apache.cayenne.modeler.ui.action.CreateAttributeAction;
 import org.apache.cayenne.modeler.ui.action.CreateRelationshipAction;
@@ -102,10 +102,7 @@ abstract class BaseGraphBuilder<E extends Entity<E, A, R>, A extends Attribute<E
      */
     protected List<DefaultGraphCell> createdObjects;
 
-    /**
-     * Current project controller
-     */
-    protected transient ProjectController mediator;
+    protected transient ProjectSession session;
 
     protected transient Entity<E, A, R> selectedEntity;
 
@@ -114,7 +111,7 @@ abstract class BaseGraphBuilder<E extends Entity<E, A, R>, A extends Attribute<E
     boolean undoEventsDisabled;
 
     @Override
-    public void buildGraph(ProjectController mediator, DataChannelDescriptor domain, boolean doLayout) {
+    public void buildGraph(ProjectSession session, DataChannelDescriptor domain, boolean doLayout) {
         if (graph != null) {
             // graph already built, exiting silently
             return;
@@ -124,7 +121,7 @@ abstract class BaseGraphBuilder<E extends Entity<E, A, R>, A extends Attribute<E
         GraphModel model = new DefaultGraphModel();
         graph.setModel(model);
 
-        setProjectController(mediator);
+        setProjectSession(session);
         setDataDomain(domain);
 
         GraphLayoutCache view = new GraphLayoutCache(model, new DefaultCellViewFactory());
@@ -362,7 +359,7 @@ abstract class BaseGraphBuilder<E extends Entity<E, A, R>, A extends Attribute<E
      * Creates popup menu
      */
     protected JPopupMenu createPopupMenu() {
-        Application application = mediator.getApplication();
+        Application application = session.app();
         GlobalActions globalActions = application.getActionManager();
 
         JPopupMenu menu = new JPopupMenu();
@@ -511,10 +508,10 @@ abstract class BaseGraphBuilder<E extends Entity<E, A, R>, A extends Attribute<E
         }
     }
 
-    public void setProjectController(ProjectController mediator) {
-        this.mediator = mediator;
+    public void setProjectSession(ProjectSession session) {
+        this.session = session;
 
-        mediator.addDataMapListener(this);
+        session.addDataMapListener(this);
     }
 
     public void setDataDomain(DataChannelDescriptor domain) {
@@ -528,7 +525,7 @@ abstract class BaseGraphBuilder<E extends Entity<E, A, R>, A extends Attribute<E
 
     @Override
     public void destroy() {
-        mediator.removeDataMapListener(this);
+        session.removeDataMapListener(this);
     }
 
     /**
@@ -602,9 +599,9 @@ abstract class BaseGraphBuilder<E extends Entity<E, A, R>, A extends Attribute<E
     public void undoableEditHappened(UndoableEditEvent e) {
         if (!undoEventsDisabled) {
             // graph has been modified
-            mediator.setDirty(true);
+            session.setDirty(true);
 
-            mediator.getApplication().getUndoManager().undoableEditHappened(e);
+            session.app().getUndoManager().undoableEditHappened(e);
         }
     }
 }
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/graph/DbGraphBuilder.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/graph/DbGraphBuilder.java
index 96ef364e9..3130c69a1 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/graph/DbGraphBuilder.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/graph/DbGraphBuilder.java
@@ -32,7 +32,7 @@ import org.apache.cayenne.modeler.event.model.DbAttributeEvent;
 import org.apache.cayenne.modeler.event.model.DbAttributeListener;
 import org.apache.cayenne.modeler.event.model.DbEntityListener;
 import org.apache.cayenne.modeler.event.model.DbRelationshipListener;
-import org.apache.cayenne.modeler.ui.project.ProjectController;
+import org.apache.cayenne.modeler.project.ProjectSession;
 import org.jgraph.graph.DefaultEdge;
 import org.jgraph.graph.DefaultGraphCell;
 import org.jgraph.graph.GraphConstants;
@@ -72,20 +72,20 @@ class DbGraphBuilder extends BaseGraphBuilder<DbEntity, DbAttribute, DbRelations
     }
 
     @Override
-    public void setProjectController(ProjectController mediator) {
-        super.setProjectController(mediator);
+    public void setProjectSession(ProjectSession session) {
+        super.setProjectSession(session);
 
-        mediator.addDbEntityListener(this);
-        mediator.addDbAttributeListener(this);
-        mediator.addDbRelationshipListener(this);
+        session.addDbEntityListener(this);
+        session.addDbAttributeListener(this);
+        session.addDbRelationshipListener(this);
     }
 
     public void destroy() {
         super.destroy();
 
-        mediator.removeDbEntityListener(this);
-        mediator.removeDbAttributeListener(this);
-        mediator.removeDbRelationshipListener(this);
+        session.removeDbEntityListener(this);
+        session.removeDbAttributeListener(this);
+        session.removeDbRelationshipListener(this);
     }
 
     public void dbEntityAdded(DbEntityEvent e) {
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/graph/GraphBuilder.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/graph/GraphBuilder.java
index 1b8564688..c837f8f86 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/graph/GraphBuilder.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/graph/GraphBuilder.java
@@ -24,7 +24,7 @@ import org.apache.cayenne.configuration.DataChannelDescriptor;
 import org.apache.cayenne.map.Attribute;
 import org.apache.cayenne.map.Entity;
 import org.apache.cayenne.map.Relationship;
-import org.apache.cayenne.modeler.ui.project.ProjectController;
+import org.apache.cayenne.modeler.project.ProjectSession;
 import org.apache.cayenne.util.XMLSerializable;
 import org.jgraph.JGraph;
 import org.jgraph.graph.DefaultGraphCell;
@@ -39,7 +39,7 @@ public interface GraphBuilder<E extends Entity<E, A, R>, A extends Attribute<E,
     /**
      * Builds graph
      */
-    void buildGraph(ProjectController mediator, DataChannelDescriptor domain, boolean layout);
+    void buildGraph(ProjectSession session, DataChannelDescriptor domain, boolean layout);
     
     /**
      * Invoked at destroying of the builder
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/graph/GraphMap.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/graph/GraphMap.java
index e84a59eea..aab42dbeb 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/graph/GraphMap.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/graph/GraphMap.java
@@ -1,78 +1,135 @@
-/*****************************************************************
- *   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.graph;
-
-import java.util.HashMap;
-
-import org.apache.cayenne.CayenneRuntimeException;
-import org.apache.cayenne.configuration.DataChannelDescriptor;
-import org.apache.cayenne.modeler.ui.project.ProjectController;
-
-/**
- * Map that stores graph builders <b>for a single domain</b> by their type 
- * and has additional methods to set currently selected graph and serialize to XML
- */
-public class GraphMap extends HashMap<GraphType, GraphBuilder> {
-    /**
-     * type that is currently selected
-     */
-    GraphType selectedType;
-    
-    /**
-     * Domain
-     */
-    DataChannelDescriptor domain;
-    
-    public GraphMap(DataChannelDescriptor domain) {
-        this.domain = domain;
-    }
-     
-    /**
-     * Returns domain
-     */
-    public DataChannelDescriptor getDomain() {
-        return domain;
-    }
-    
-    /**
-     * Returns type that is currently selected
-     */
-    public GraphType getSelectedType() {
-        return selectedType;
-    }
-    
-    /**
-     * Sets type that is currently selected
-     */
-    public void setSelectedType(GraphType selectedType) {
-        this.selectedType = selectedType;
-    }
-    
-    public GraphBuilder createGraphBuilder(ProjectController mediator, GraphType type, boolean doLayout) {
-        try {
-            GraphBuilder builder = type.getBuilderClass().getDeclaredConstructor().newInstance();
-            builder.buildGraph(mediator, domain, doLayout);
-            put(type, builder);
-
-            return builder;
-        } catch (Exception e) {
-            throw new CayenneRuntimeException("Could not instantiate GraphBuilder", e);
-        }
-    }
-}
+/*****************************************************************
+ *   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.graph;
+
+import java.util.EnumMap;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.swing.undo.UndoableEdit;
+
+import org.apache.cayenne.CayenneRuntimeException;
+import org.apache.cayenne.configuration.DataChannelDescriptor;
+import org.apache.cayenne.modeler.project.ProjectSession;
+import org.jgraph.graph.DefaultGraphCell;
+
+/**
+ * Map that stores graph builders <b>for a single domain</b> by their type
+ * and has additional methods to set currently selected graph and serialize to XML
+ */
+public class GraphMap extends HashMap<GraphType, GraphBuilder> {
+    /**
+     * type that is currently selected
+     */
+    GraphType selectedType;
+
+    /**
+     * Domain
+     */
+    DataChannelDescriptor domain;
+
+    /**
+     * Graph state parsed from the project XML and waiting for a {@link GraphBuilder}
+     * to be created — applied at first {@link #createGraphBuilder} for a given type.
+     * Kept off the live event bus so XML load doesn't subscribe to a not-yet-open project.
+     */
+    private final Map<GraphType, ParkedState> parkedStates = new EnumMap<>(GraphType.class);
+
+    public GraphMap(DataChannelDescriptor domain) {
+        this.domain = domain;
+    }
+
+    /**
+     * Returns domain
+     */
+    public DataChannelDescriptor getDomain() {
+        return domain;
+    }
+
+    /**
+     * Returns type that is currently selected
+     */
+    public GraphType getSelectedType() {
+        return selectedType;
+    }
+
+    /**
+     * Sets type that is currently selected
+     */
+    public void setSelectedType(GraphType selectedType) {
+        this.selectedType = selectedType;
+    }
+
+    public GraphBuilder createGraphBuilder(ProjectSession session, GraphType type, boolean doLayout) {
+        try {
+            ParkedState parked = parkedStates.remove(type);
+            GraphBuilder builder = type.getBuilderClass().getDeclaredConstructor().newInstance();
+            builder.buildGraph(session, domain, doLayout && parked == null);
+            if (parked != null) {
+                parked.applyTo(builder);
+            }
+            put(type, builder);
+
+            return builder;
+        } catch (Exception e) {
+            throw new CayenneRuntimeException("Could not instantiate GraphBuilder", e);
+        }
+    }
+
+    /**
+     * Stashes graph state parsed from the project XML for a given type. The state is
+     * applied to a freshly built {@link GraphBuilder} the first time
+     * {@link #createGraphBuilder} is invoked for that type.
+     */
+    public void parkParsedState(GraphType type, double scale, Map<String, Map<String, ?>> entityProperties) {
+        parkedStates.put(type, new ParkedState(scale, entityProperties));
+    }
+
+    /**
+     * Returns true if there is parsed state waiting to be applied for the given type.
+     */
+    public boolean hasParkedState(GraphType type) {
+        return parkedStates.containsKey(type);
+    }
+
+    private static final class ParkedState {
+        private final double scale;
+        private final Map<String, Map<String, ?>> entityProperties;
+
+        ParkedState(double scale, Map<String, Map<String, ?>> entityProperties) {
+            this.scale = scale;
+            this.entityProperties = entityProperties;
+        }
+
+        void applyTo(GraphBuilder builder) {
+            builder.getGraph().setScale(scale);
+
+            Map<DefaultGraphCell, Map<String, ?>> cellProperties = new HashMap<>();
+            for (Map.Entry<String, Map<String, ?>> entry : entityProperties.entrySet()) {
+                DefaultGraphCell cell = builder.getEntityCell(entry.getKey());
+                cellProperties.put(cell, entry.getValue());
+            }
+
+            // apply without polluting the undo stack
+            builder.getGraph().getGraphLayoutCache().getModel().removeUndoableEditListener(builder);
+            builder.getGraph().getGraphLayoutCache().edit(cellProperties, null, null, new UndoableEdit[0]);
+            builder.getGraph().getGraphLayoutCache().getModel().addUndoableEditListener(builder);
+        }
+    }
+}
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/graph/GraphRegistry.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/graph/GraphRegistry.java
index 6671c50c2..a5384de22 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/graph/GraphRegistry.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/graph/GraphRegistry.java
@@ -21,7 +21,7 @@ package org.apache.cayenne.modeler.graph;
 import org.apache.cayenne.configuration.DataChannelDescriptor;
 import org.apache.cayenne.modeler.event.model.DomainEvent;
 import org.apache.cayenne.modeler.event.model.DomainListener;
-import org.apache.cayenne.modeler.ui.project.ProjectController;
+import org.apache.cayenne.modeler.project.ProjectSession;
 import org.jgraph.JGraph;
 
 import java.util.HashMap;
@@ -47,7 +47,7 @@ public class GraphRegistry implements DomainListener {
     /**
      * Builds graph with specified type, or returns existing one
      */
-    public JGraph loadGraph(ProjectController mediator, DataChannelDescriptor domain, GraphType type) {
+    public JGraph loadGraph(ProjectSession session, DataChannelDescriptor domain, GraphType type) {
         GraphMap graphMap = graphMaps.get(domain);
         if (graphMap == null) {
             graphMap = new GraphMap(domain);
@@ -56,9 +56,14 @@ public class GraphRegistry implements DomainListener {
 
         GraphBuilder builder = graphMap.get(type);
         if (builder == null) {
-            builder = graphMap.createGraphBuilder(mediator, type, true);
-            
-            mediator.setDirty(true);
+            boolean fromParkedState = graphMap.hasParkedState(type);
+            builder = graphMap.createGraphBuilder(session, type, true);
+
+            // only mark dirty when this is a freshly auto-laid-out graph; restoring a
+            // graph parsed from the project file shouldn't dirty the project
+            if (!fromParkedState) {
+                session.setDirty(true);
+            }
         }
         
         //marking this builder as default
@@ -97,10 +102,10 @@ public class GraphRegistry implements DomainListener {
     }
     
     /**
-     * Removes all listeners (and itself) from ProjectController
+     * Removes all listeners (and itself) from ProjectSession
      */
-    public void unregister(ProjectController mediator) {
-        unregisterDomain((DataChannelDescriptor)mediator.getProject().getRootNode());
-        mediator.removeDomainListener(this);
+    public void unregister(ProjectSession session) {
+        unregisterDomain((DataChannelDescriptor)session.project().getRootNode());
+        session.removeDomainListener(this);
     }
 }
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/graph/ObjGraphBuilder.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/graph/ObjGraphBuilder.java
index 3eeec1e31..5d93ab235 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/graph/ObjGraphBuilder.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/graph/ObjGraphBuilder.java
@@ -31,7 +31,7 @@ import org.apache.cayenne.modeler.event.model.ObjAttributeEvent;
 import org.apache.cayenne.modeler.event.model.ObjAttributeListener;
 import org.apache.cayenne.modeler.event.model.ObjEntityListener;
 import org.apache.cayenne.modeler.event.model.ObjRelationshipListener;
-import org.apache.cayenne.modeler.ui.project.ProjectController;
+import org.apache.cayenne.modeler.project.ProjectSession;
 import org.jgraph.graph.AttributeMap;
 import org.jgraph.graph.DefaultEdge;
 import org.jgraph.graph.DefaultGraphCell;
@@ -120,20 +120,20 @@ class ObjGraphBuilder extends BaseGraphBuilder<ObjEntity, ObjAttribute, ObjRelat
     }
 
     @Override
-    public void setProjectController(ProjectController mediator) {
-        super.setProjectController(mediator);
+    public void setProjectSession(ProjectSession session) {
+        super.setProjectSession(session);
 
-        mediator.addObjEntityListener(this);
-        mediator.addObjAttributeListener(this);
-        mediator.addObjRelationshipListener(this);
+        session.addObjEntityListener(this);
+        session.addObjAttributeListener(this);
+        session.addObjRelationshipListener(this);
     }
 
     public void destroy() {
         super.destroy();
 
-        mediator.removeObjEntityListener(this);
-        mediator.removeObjAttributeListener(this);
-        mediator.removeObjRelationshipListener(this);
+        session.removeObjEntityListener(this);
+        session.removeObjAttributeListener(this);
+        session.removeObjRelationshipListener(this);
     }
 
     public void objEntityAdded(ObjEntityEvent e) {
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/mvc/ChildController.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/mvc/ChildController.java
deleted file mode 100644
index 3438588f4..000000000
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/mvc/ChildController.java
+++ /dev/null
@@ -1,55 +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.mvc;
-
-import java.awt.*;
-import java.util.Objects;
-
-/**
- * A superclass of non-root controllers.
- */
-public abstract class ChildController<P extends RootController> extends RootController {
-
-    protected final P parent;
-
-    protected ChildController(P parent) {
-        super(Objects.requireNonNull(parent).getApplication());
-        this.parent = parent;
-    }
-
-    /**
-     * Centers view on parent window.
-     */
-    protected void centerView() {
-        Window parentWindow = parent.getWindow();
-
-        Dimension parentSize = parentWindow.getSize();
-        Dimension windowSize = getView().getSize();
-        Point parentLocation = new Point(0, 0);
-        if (parentWindow.isShowing()) {
-            parentLocation = parentWindow.getLocationOnScreen();
-        }
-
-        int x = parentLocation.x + parentSize.width / 2 - windowSize.width / 2;
-        int y = parentLocation.y + parentSize.height / 2 - windowSize.height / 2;
-
-        getView().setLocation(x, y);
-    }
-}
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/mvc/RootController.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/mvc/RootController.java
deleted file mode 100644
index 170f3c0ce..000000000
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/mvc/RootController.java
+++ /dev/null
@@ -1,118 +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.mvc;
-
-import org.apache.cayenne.modeler.Application;
-import org.apache.cayenne.util.Util;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import javax.swing.*;
-import java.awt.*;
-import java.awt.event.ActionListener;
-import java.awt.event.KeyEvent;
-
-/**
- * A superclass of most controllers.
- */
-public abstract class RootController {
-
-    private static final Logger LOGGER = LoggerFactory.getLogger(RootController.class);
-
-    protected final Application application;
-
-    protected RootController(Application application) {
-        this.application = application;
-    }
-
-    public Application getApplication() {
-        return application;
-    }
-
-    public abstract Component getView();
-
-    /**
-     * Provides a visual indication of an execution error. This implementation logs an error and pops up a dialog
-     * window with error message.
-     */
-    protected void reportError(String title, Throwable th) {
-        th = Util.unwindException(th);
-        LOGGER.info("Error in " + getClass().getName(), th);
-
-        JOptionPane.showMessageDialog(
-                getView(),
-                th.getMessage(),
-                title,
-                JOptionPane.ERROR_MESSAGE);
-    }
-
-    /**
-     * If this view or a parent view is a JDialog, makes it closeable on ESC hit. Dialog
-     * "defaultCloseOperation" property is taken into account when processing ESC button
-     * click.
-     */
-    protected void makeCloseableOnEscape() {
-
-        Window window = getWindow();
-        if (!(window instanceof JDialog)) {
-            return;
-        }
-
-        final JDialog dialog = (JDialog) window;
-
-        KeyStroke escReleased = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0, true);
-        ActionListener closeAction = e -> {
-            if (dialog.isVisible()) {
-                switch (dialog.getDefaultCloseOperation()) {
-                    case JDialog.HIDE_ON_CLOSE:
-                        dialog.setVisible(false);
-                        break;
-                    case JDialog.DISPOSE_ON_CLOSE:
-                        dialog.setVisible(false);
-                        dialog.dispose();
-                        break;
-                    case JDialog.DO_NOTHING_ON_CLOSE:
-                    default:
-                        break;
-                }
-            }
-        };
-        dialog.getRootPane().registerKeyboardAction(
-                closeAction,
-                escReleased,
-                JComponent.WHEN_IN_FOCUSED_WINDOW);
-    }
-
-    /**
-     * Finds a Window in the view hierarchy.
-     */
-    protected Window getWindow() {
-        Component view = getView();
-        while (view != null) {
-            if (view instanceof Window) {
-                return (Window) view;
-            }
-
-            view = view.getParent();
-        }
-
-        return null;
-    }
-}
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/project/CgenOps.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/project/CgenOps.java
index 632c860c4..720e8a3db 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/project/CgenOps.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/project/CgenOps.java
@@ -22,7 +22,7 @@ public class CgenOps {
     }
 
     private static Path projectRoot(Application application) {
-        Project project = application.getProject();
+        Project project = application.getFrame().getProjectSession().project();
         if (project == null) {
             return null;
         }
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/event/model/ProjectAfterSaveEvent.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/project/ProjectDirtyListener.java
similarity index 70%
copy from modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/event/model/ProjectAfterSaveEvent.java
copy to modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/project/ProjectDirtyListener.java
index 01523a256..639a79957 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/event/model/ProjectAfterSaveEvent.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/project/ProjectDirtyListener.java
@@ -17,20 +17,14 @@
  *  under the License.
  ****************************************************************/
 
-package org.apache.cayenne.modeler.event.model;
+package org.apache.cayenne.modeler.project;
 
-import org.apache.cayenne.modeler.ui.project.ProjectController;
+/**
+ * Notifies a subscriber whenever the project's dirty flag transitions. Registered
+ * via {@link ProjectSession#addDirtyListener(ProjectDirtyListener)}.
+ */
+@FunctionalInterface
+public interface ProjectDirtyListener {
 
-import java.util.EventObject;
-
-public class ProjectAfterSaveEvent extends EventObject {
-
-    public ProjectAfterSaveEvent(ProjectController source) {
-        super(source);
-    }
-
-    @Override
-    public ProjectController getSource() {
-        return (ProjectController) super.getSource();
-    }
+    void onDirtyChanged(boolean wasDirty, boolean isDirty);
 }
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/ProjectFileChangeTracker.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/project/ProjectFileChangeTracker.java
similarity index 86%
rename from modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/ProjectFileChangeTracker.java
rename to modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/project/ProjectFileChangeTracker.java
index af0da31a2..219896e57 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/ProjectFileChangeTracker.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/project/ProjectFileChangeTracker.java
@@ -16,7 +16,7 @@
  *  specific language governing permissions and limitations
  *  under the License.
  ****************************************************************/
-package org.apache.cayenne.modeler.ui.project;
+package org.apache.cayenne.modeler.project;
 
 import org.apache.cayenne.CayenneRuntimeException;
 import org.apache.cayenne.configuration.DataChannelDescriptor;
@@ -47,15 +47,15 @@ class ProjectFileChangeTracker extends Thread {
     private static final long DEFAULT_DELAY = 4000;
 
     private final Map<URI, TrackedFile> files;
-    private final ProjectController controller;
+    private final ProjectSession session;
 
     private boolean paused;
     private boolean isShownChangeDialog;
     private boolean isShownRemoveDialog;
 
-    public ProjectFileChangeTracker(ProjectController controller) {
+    public ProjectFileChangeTracker(ProjectSession session) {
         this.files = new ConcurrentHashMap<>();
-        this.controller = controller;
+        this.session = session;
         setName("cayenne-modeler-file-change-tracker");
     }
 
@@ -67,7 +67,7 @@ class ProjectFileChangeTracker extends Thread {
 
         files.clear();
 
-        Project project = controller.getProject();
+        Project project = session.project();
 
         // check if project exists and has been saved at least once.
         if (project != null && project.getConfigurationResource() != null) {
@@ -96,40 +96,40 @@ class ProjectFileChangeTracker extends Thread {
                     + "Do you want to load the changes?")) {
 
                 // Currently we are reloading all project
-                if (controller.getProject() != null) {
+                if (session.project() != null) {
                     File fileDirectory;
                     try {
-                        fileDirectory = new File(controller.getProject().getConfigurationResource().getURL().toURI());
+                        fileDirectory = new File(session.project().getConfigurationResource().getURL().toURI());
                     } catch (URISyntaxException e) {
                         throw new CayenneRuntimeException("Unable to open project %s",
-                                e, controller.getProject().getConfigurationResource().getURL());
+                                e, session.project().getConfigurationResource().getURL());
                     }
-                    controller.getApplication()
+                    session.app()
                             .getActionManager()
                             .getAction(OpenProjectAction.class)
                             .openProject(fileDirectory);
                 }
             } else {
-                controller.setDirty(true);
+                session.setDirty(true);
             }
             isShownChangeDialog = false;
         });
     }
 
     private void doOnRemove() {
-        if (controller.getProject() != null) {
+        if (session.project() != null) {
 
             SwingUtilities.invokeLater(() -> {
                 isShownRemoveDialog = true;
-                FileDeletedDialog dialog = new FileDeletedDialog(controller.getApplication().getFrameController().getView());
+                FileDeletedDialog dialog = new FileDeletedDialog(session.app().getFrame());
                 dialog.show();
 
                 if (dialog.shouldSave()) {
-                    controller.getApplication().getActionManager().getAction(SaveAction.class).performAction(null);
+                    session.app().getActionManager().getAction(SaveAction.class).performAction(null);
                 } else if (dialog.shouldClose()) {
-                    controller.getApplication().getFrameController().onProjectClosed();
+                    session.app().getFrame().onProjectClosed();
                 } else {
-                    controller.setDirty(true);
+                    session.setDirty(true);
                 }
                 isShownRemoveDialog = false;
             });
@@ -137,7 +137,7 @@ class ProjectFileChangeTracker extends Thread {
     }
 
     private boolean showConfirmation(String message) {
-        int outcome = JOptionPane.showConfirmDialog(controller.getApplication().getFrameController().getView(), message, "File changed", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE);
+        int outcome = JOptionPane.showConfirmDialog(session.app().getFrame(), message, "File changed", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE);
         return outcome == JOptionPane.YES_OPTION;
     }
 
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/ProjectNavigationHistory.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/project/ProjectNavigationHistory.java
similarity index 88%
rename from modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/ProjectNavigationHistory.java
rename to modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/project/ProjectNavigationHistory.java
index 352707dd1..84787733b 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/ProjectNavigationHistory.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/project/ProjectNavigationHistory.java
@@ -17,7 +17,7 @@
  *  under the License.
  ****************************************************************/
 
-package org.apache.cayenne.modeler.ui.project;
+package org.apache.cayenne.modeler.project;
 
 import org.apache.cayenne.modeler.event.display.DataMapDisplayEvent;
 import org.apache.cayenne.modeler.event.display.DataNodeDisplayEvent;
@@ -59,7 +59,7 @@ public class ProjectNavigationHistory {
         recordEvent(history, e);
     }
 
-    public void replayLastEvent(ProjectController controller) {
+    public void replayLastEvent(ProjectSession session) {
 
         if (history.isEmpty()) {
             return;
@@ -67,18 +67,18 @@ public class ProjectNavigationHistory {
 
         DisplayEvent e = history.remove(history.size() - 1);
         recordEvent(replayHistory, e);
-        replay(controller, e);
+        replay(session, e);
     }
 
 
-    public void replayNextEvent(ProjectController controller) {
+    public void replayNextEvent(ProjectSession session) {
         if (replayHistory.isEmpty()) {
             return;
         }
 
         DisplayEvent e = replayHistory.remove(replayHistory.size() - 1);
         recordEvent(history, e);
-        replay(controller, e);
+        replay(session, e);
     }
 
     private void recordEvent(List<DisplayEvent> target, DisplayEvent e) {
@@ -95,23 +95,23 @@ public class ProjectNavigationHistory {
         this.currentEvent = e;
     }
 
-    private void replay(ProjectController controller, DisplayEvent e) {
+    private void replay(ProjectSession session, DisplayEvent e) {
         if (e instanceof ObjEntityDisplayEvent) {
-            controller.displayObjEntity((ObjEntityDisplayEvent) e);
+            session.displayObjEntity((ObjEntityDisplayEvent) e);
         } else if (e instanceof DbEntityDisplayEvent) {
-            controller.displayDbEntity((DbEntityDisplayEvent) e);
+            session.displayDbEntity((DbEntityDisplayEvent) e);
         } else if (e instanceof EmbeddableDisplayEvent) {
-            controller.displayEmbeddable((EmbeddableDisplayEvent) e);
+            session.displayEmbeddable((EmbeddableDisplayEvent) e);
         } else if (e instanceof ProcedureDisplayEvent) {
-            controller.displayProcedure((ProcedureDisplayEvent) e);
+            session.displayProcedure((ProcedureDisplayEvent) e);
         } else if (e instanceof QueryDisplayEvent) {
-            controller.displayQuery((QueryDisplayEvent) e);
+            session.displayQuery((QueryDisplayEvent) e);
         } else if (e instanceof DataMapDisplayEvent) {
-            controller.displayDataMap((DataMapDisplayEvent) e);
+            session.displayDataMap((DataMapDisplayEvent) e);
         } else if (e instanceof DataNodeDisplayEvent) {
-            controller.displayDataNode((DataNodeDisplayEvent) e);
+            session.displayDataNode((DataNodeDisplayEvent) e);
         } else if (e instanceof DomainDisplayEvent) {
-            controller.displayDomain((DomainDisplayEvent) e);
+            session.displayDomain((DomainDisplayEvent) e);
         }
     }
 
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/ProjectPrefs.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/project/ProjectPrefs.java
similarity index 76%
rename from modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/ProjectPrefs.java
rename to modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/project/ProjectPrefs.java
index 6cbd3ac40..1c80cb5e8 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/ProjectPrefs.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/project/ProjectPrefs.java
@@ -17,7 +17,7 @@
  *  under the License.
  ****************************************************************/
 
-package org.apache.cayenne.modeler.ui.project;
+package org.apache.cayenne.modeler.project;
 
 import org.apache.cayenne.configuration.ConfigurationNode;
 import org.apache.cayenne.configuration.DataChannelDescriptor;
@@ -107,61 +107,61 @@ public final class ProjectPrefs implements PreferenceAdapter {
         this.prefs = prefs;
     }
 
-    public void load(ProjectController controller) {
+    public void load(ProjectSession session) {
         Kind kind = parseKind(prefs.get(EVENT_KEY, ""));
         if (kind == null) {
             return;
         }
 
-        DataChannelDescriptor domain = (DataChannelDescriptor) controller.getProject().getRootNode();
+        DataChannelDescriptor domain = (DataChannelDescriptor) session.project().getRootNode();
         if (!domain.getName().equals(prefs.get(DOMAIN_KEY, ""))) {
             return;
         }
 
         switch (kind) {
             case domain:
-                controller.displayDomain(new DomainDisplayEvent(ProjectPrefs.class, domain));
+                session.displayDomain(new DomainDisplayEvent(ProjectPrefs.class, domain));
                 break;
             case dataNode:
-                restoreDataNode(controller, domain);
+                restoreDataNode(session, domain);
                 break;
             case dataMap:
-                restoreDataMap(controller, domain);
+                restoreDataMap(session, domain);
                 break;
             case objEntity:
             case dbEntity:
-                restoreEntity(controller, domain);
+                restoreEntity(session, domain);
                 break;
             case objAttributes:
             case dbAttributes:
-                restoreAttributes(controller, domain);
+                restoreAttributes(session, domain);
                 break;
             case objRelationships:
             case dbRelationships:
-                restoreRelationships(controller, domain);
+                restoreRelationships(session, domain);
                 break;
             case embeddable:
-                restoreEmbeddable(controller, domain);
+                restoreEmbeddable(session, domain);
                 break;
             case embeddableAttributes:
-                restoreEmbeddableAttributes(controller, domain);
+                restoreEmbeddableAttributes(session, domain);
                 break;
             case procedure:
-                restoreProcedure(controller, domain);
+                restoreProcedure(session, domain);
                 break;
             case procedureParameters:
-                restoreProcedureParameters(controller, domain);
+                restoreProcedureParameters(session, domain);
                 break;
             case query:
-                restoreQuery(controller, domain);
+                restoreQuery(session, domain);
                 break;
             case multipleObjects:
-                restoreMultipleObjects(controller, domain);
+                restoreMultipleObjects(session, domain);
                 break;
         }
     }
 
-    public void flush(ProjectController controller) {
+    public void flush(ProjectSession session) {
         if (prefs == null) {
             return;
         }
@@ -171,7 +171,7 @@ public final class ProjectPrefs implements PreferenceAdapter {
             // ignore
         }
 
-        Kind kind = leafKind(controller);
+        Kind kind = leafKind(session);
         if (kind == null) {
             return;
         }
@@ -179,58 +179,58 @@ public final class ProjectPrefs implements PreferenceAdapter {
 
         switch (kind) {
             case domain:
-                saveDomainPath(controller);
+                saveDomainPath(session);
                 break;
             case dataNode:
-                saveNodePath(controller);
+                saveNodePath(session);
                 break;
             case dataMap:
-                saveDataMapPath(controller);
+                saveDataMapPath(session);
                 break;
             case objEntity:
             case dbEntity:
-                saveEntityPath(controller);
+                saveEntityPath(session);
                 break;
             case objAttributes:
-                saveEntityPath(controller);
-                prefs.put(OBJ_ATTRS_KEY, joinNames(controller.getSelectedObjAttributes()));
+                saveEntityPath(session);
+                prefs.put(OBJ_ATTRS_KEY, joinNames(session.getSelectedObjAttributes()));
                 break;
             case dbAttributes:
-                saveEntityPath(controller);
-                prefs.put(DB_ATTRS_KEY, joinNames(controller.getSelectedDbAttributes()));
+                saveEntityPath(session);
+                prefs.put(DB_ATTRS_KEY, joinNames(session.getSelectedDbAttributes()));
                 break;
             case objRelationships:
-                saveEntityPath(controller);
-                prefs.put(OBJ_RELS_KEY, joinNames(controller.getSelectedObjRelationships()));
+                saveEntityPath(session);
+                prefs.put(OBJ_RELS_KEY, joinNames(session.getSelectedObjRelationships()));
                 break;
             case dbRelationships:
-                saveEntityPath(controller);
-                prefs.put(DB_RELS_KEY, joinNames(controller.getSelectedDbRelationships()));
+                saveEntityPath(session);
+                prefs.put(DB_RELS_KEY, joinNames(session.getSelectedDbRelationships()));
                 break;
             case embeddable:
-                saveEmbeddablePath(controller);
+                saveEmbeddablePath(session);
                 break;
             case embeddableAttributes:
-                saveEmbeddablePath(controller);
-                prefs.put(EMBEDDABLE_ATTRS_KEY, joinEmbeddableAttributeNames(controller.getSelectedEmbeddableAttributes()));
+                saveEmbeddablePath(session);
+                prefs.put(EMBEDDABLE_ATTRS_KEY, joinEmbeddableAttributeNames(session.getSelectedEmbeddableAttributes()));
                 break;
             case procedure:
-                saveProcedurePath(controller);
+                saveProcedurePath(session);
                 break;
             case procedureParameters:
-                saveProcedurePath(controller);
-                prefs.put(PROCEDURE_PARAMS_KEY, joinNames(controller.getSelectedProcedureParameters()));
+                saveProcedurePath(session);
+                prefs.put(PROCEDURE_PARAMS_KEY, joinNames(session.getSelectedProcedureParameters()));
                 break;
             case query:
-                saveQueryPath(controller);
+                saveQueryPath(session);
                 break;
             case multipleObjects:
-                saveMultipleObjects(controller);
+                saveMultipleObjects(session);
                 break;
         }
     }
 
-    private static Kind leafKind(ProjectController c) {
+    private static Kind leafKind(ProjectSession c) {
         if (c.getSelectedObjAttributes() != null && c.getSelectedObjAttributes().length > 0) {
             return Kind.objAttributes;
         }
@@ -292,37 +292,37 @@ public final class ProjectPrefs implements PreferenceAdapter {
 
     // -------------------- save helpers --------------------
 
-    private void saveDomainPath(ProjectController controller) {
-        prefs.put(DOMAIN_KEY, controller.getSelectedDataDomain().getName());
+    private void saveDomainPath(ProjectSession session) {
+        prefs.put(DOMAIN_KEY, session.getSelectedDataDomain().getName());
     }
 
-    private void saveNodePath(ProjectController controller) {
-        prefs.put(DOMAIN_KEY, controller.getSelectedDataDomain().getName());
-        prefs.put(NODE_KEY, controller.getSelectedDataNode().getName());
+    private void saveNodePath(ProjectSession session) {
+        prefs.put(DOMAIN_KEY, session.getSelectedDataDomain().getName());
+        prefs.put(NODE_KEY, session.getSelectedDataNode().getName());
     }
 
-    private void saveDataMapPath(ProjectController controller) {
-        DataChannelDescriptor domain = controller.getSelectedDataDomain();
+    private void saveDataMapPath(ProjectSession session) {
+        DataChannelDescriptor domain = session.getSelectedDataDomain();
         if (domain == null) {
             return;
         }
         prefs.put(DOMAIN_KEY, domain.getName());
-        DataNodeDescriptor node = controller.getSelectedDataNode();
+        DataNodeDescriptor node = session.getSelectedDataNode();
         prefs.put(NODE_KEY, node != null ? node.getName() : "");
-        DataMap dataMap = controller.getSelectedDataMap();
+        DataMap dataMap = session.getSelectedDataMap();
         if (dataMap != null) {
             prefs.put(DATA_MAP_KEY, dataMap.getName());
         }
     }
 
-    private void saveEntityPath(ProjectController controller) {
-        prefs.put(DOMAIN_KEY, controller.getSelectedDataDomain().getName());
-        DataNodeDescriptor node = controller.getSelectedDataNode();
+    private void saveEntityPath(ProjectSession session) {
+        prefs.put(DOMAIN_KEY, session.getSelectedDataDomain().getName());
+        DataNodeDescriptor node = session.getSelectedDataNode();
         prefs.put(NODE_KEY, node != null ? node.getName() : "");
-        prefs.put(DATA_MAP_KEY, controller.getSelectedDataMap().getName());
+        prefs.put(DATA_MAP_KEY, session.getSelectedDataMap().getName());
 
-        ObjEntity objEntity = controller.getSelectedObjEntity();
-        DbEntity dbEntity = controller.getSelectedDbEntity();
+        ObjEntity objEntity = session.getSelectedObjEntity();
+        DbEntity dbEntity = session.getSelectedDbEntity();
         if (objEntity != null) {
             prefs.put(OBJ_ENTITY_KEY, objEntity.getName());
         } else if (dbEntity != null) {
@@ -330,28 +330,28 @@ public final class ProjectPrefs implements PreferenceAdapter {
         }
     }
 
-    private void saveEmbeddablePath(ProjectController controller) {
-        prefs.put(DOMAIN_KEY, controller.getSelectedDataDomain().getName());
-        prefs.put(DATA_MAP_KEY, controller.getSelectedDataMap().getName());
-        prefs.put(EMBEDDABLE_KEY, controller.getSelectedEmbeddable().getClassName());
+    private void saveEmbeddablePath(ProjectSession session) {
+        prefs.put(DOMAIN_KEY, session.getSelectedDataDomain().getName());
+        prefs.put(DATA_MAP_KEY, session.getSelectedDataMap().getName());
+        prefs.put(EMBEDDABLE_KEY, session.getSelectedEmbeddable().getClassName());
     }
 
-    private void saveProcedurePath(ProjectController controller) {
-        prefs.put(DOMAIN_KEY, controller.getSelectedDataDomain().getName());
-        prefs.put(DATA_MAP_KEY, controller.getSelectedDataMap().getName());
-        prefs.put(PROCEDURE_KEY, controller.getSelectedProcedure().getName());
+    private void saveProcedurePath(ProjectSession session) {
+        prefs.put(DOMAIN_KEY, session.getSelectedDataDomain().getName());
+        prefs.put(DATA_MAP_KEY, session.getSelectedDataMap().getName());
+        prefs.put(PROCEDURE_KEY, session.getSelectedProcedure().getName());
     }
 
-    private void saveQueryPath(ProjectController controller) {
-        prefs.put(DOMAIN_KEY, controller.getSelectedDataDomain().getName());
-        prefs.put(DATA_MAP_KEY, controller.getSelectedDataMap().getName());
-        prefs.put(QUERY_KEY, controller.getSelectedQuery().getName());
+    private void saveQueryPath(ProjectSession session) {
+        prefs.put(DOMAIN_KEY, session.getSelectedDataDomain().getName());
+        prefs.put(DATA_MAP_KEY, session.getSelectedDataMap().getName());
+        prefs.put(QUERY_KEY, session.getSelectedQuery().getName());
     }
 
-    private void saveMultipleObjects(ProjectController controller) {
-        prefs.put(PARENT_OBJECT_KEY, nameOf(controller.getSelectedParentPath()));
+    private void saveMultipleObjects(ProjectSession session) {
+        prefs.put(PARENT_OBJECT_KEY, nameOf(session.getSelectedParentPath()));
 
-        ConfigurationNode[] paths = controller.getSelectedPaths();
+        ConfigurationNode[] paths = session.getSelectedPaths();
         if (paths == null) {
             prefs.put(MULTIPLE_OBJECTS_KEY, "");
             return;
@@ -369,24 +369,24 @@ public final class ProjectPrefs implements PreferenceAdapter {
 
     // -------------------- restore helpers --------------------
 
-    private void restoreDataNode(ProjectController controller, DataChannelDescriptor domain) {
+    private void restoreDataNode(ProjectSession session, DataChannelDescriptor domain) {
         DataNodeDescriptor node = domain.getNodeDescriptor(prefs.get(NODE_KEY, ""));
         if (node == null) {
             return;
         }
-        controller.displayDataNode(new DataNodeDisplayEvent(ProjectPrefs.class, domain, node));
+        session.displayDataNode(new DataNodeDisplayEvent(ProjectPrefs.class, domain, node));
     }
 
-    private void restoreDataMap(ProjectController controller, DataChannelDescriptor domain) {
+    private void restoreDataMap(ProjectSession session, DataChannelDescriptor domain) {
         DataMap dataMap = domain.getDataMap(prefs.get(DATA_MAP_KEY, ""));
         if (dataMap == null) {
             return;
         }
         DataNodeDescriptor node = domain.getNodeDescriptor(prefs.get(NODE_KEY, ""));
-        controller.displayDataMap(new DataMapDisplayEvent(ProjectPrefs.class, domain, dataMap, node));
+        session.displayDataMap(new DataMapDisplayEvent(ProjectPrefs.class, domain, dataMap, node));
     }
 
-    private void restoreEntity(ProjectController controller, DataChannelDescriptor domain) {
+    private void restoreEntity(ProjectSession session, DataChannelDescriptor domain) {
         DataMap dataMap = domain.getDataMap(prefs.get(DATA_MAP_KEY, ""));
         if (dataMap == null) {
             return;
@@ -396,15 +396,15 @@ public final class ProjectPrefs implements PreferenceAdapter {
             return;
         }
         if (entity instanceof ObjEntity) {
-            controller.displayObjEntity(new ObjEntityDisplayEvent(
+            session.displayObjEntity(new ObjEntityDisplayEvent(
                     ProjectPrefs.class, domain, dataMap, (ObjEntity) entity));
         } else if (entity instanceof DbEntity) {
-            controller.displayDbEntity(new DbEntityDisplayEvent(
+            session.displayDbEntity(new DbEntityDisplayEvent(
                     ProjectPrefs.class, domain, dataMap, (DbEntity) entity));
         }
     }
 
-    private void restoreAttributes(ProjectController controller, DataChannelDescriptor domain) {
+    private void restoreAttributes(ProjectSession session, DataChannelDescriptor domain) {
         DataMap dataMap = domain.getDataMap(prefs.get(DATA_MAP_KEY, ""));
         if (dataMap == null) {
             return;
@@ -419,22 +419,22 @@ public final class ProjectPrefs implements PreferenceAdapter {
             ObjEntity objEntity = (ObjEntity) entity;
             ObjAttribute[] objAttrs = new ObjAttribute[attrs.length];
             System.arraycopy(attrs, 0, objAttrs, 0, attrs.length);
-            controller.displayObjEntity(new ObjEntityDisplayEvent(
+            session.displayObjEntity(new ObjEntityDisplayEvent(
                     ProjectPrefs.class, domain, dataMap, objEntity));
-            controller.displayObjAttribute(new ObjAttributeDisplayEvent(
+            session.displayObjAttribute(new ObjAttributeDisplayEvent(
                     ProjectPrefs.class, domain, dataMap, objEntity, objAttrs));
         } else if (entity instanceof DbEntity) {
             DbEntity dbEntity = (DbEntity) entity;
             DbAttribute[] dbAttrs = new DbAttribute[attrs.length];
             System.arraycopy(attrs, 0, dbAttrs, 0, attrs.length);
-            controller.displayDbEntity(new DbEntityDisplayEvent(
+            session.displayDbEntity(new DbEntityDisplayEvent(
                     ProjectPrefs.class, domain, dataMap, dbEntity));
-            controller.displayDbAttribute(new DbAttributeDisplayEvent(
+            session.displayDbAttribute(new DbAttributeDisplayEvent(
                     ProjectPrefs.class, domain, dataMap, dbEntity, dbAttrs));
         }
     }
 
-    private void restoreRelationships(ProjectController controller, DataChannelDescriptor domain) {
+    private void restoreRelationships(ProjectSession session, DataChannelDescriptor domain) {
         DataMap dataMap = domain.getDataMap(prefs.get(DATA_MAP_KEY, ""));
         if (dataMap == null) {
             return;
@@ -449,22 +449,22 @@ public final class ProjectPrefs implements PreferenceAdapter {
             ObjEntity objEntity = (ObjEntity) entity;
             ObjRelationship[] objRels = new ObjRelationship[rels.length];
             System.arraycopy(rels, 0, objRels, 0, rels.length);
-            controller.displayObjEntity(new ObjEntityDisplayEvent(
+            session.displayObjEntity(new ObjEntityDisplayEvent(
                     ProjectPrefs.class, domain, dataMap, objEntity));
-            controller.displayObjRelationship(new ObjRelationshipDisplayEvent(
+            session.displayObjRelationship(new ObjRelationshipDisplayEvent(
                     ProjectPrefs.class, domain, dataMap, objEntity, objRels));
         } else if (entity instanceof DbEntity) {
             DbEntity dbEntity = (DbEntity) entity;
             DbRelationship[] dbRels = new DbRelationship[rels.length];
             System.arraycopy(rels, 0, dbRels, 0, rels.length);
-            controller.displayDbEntity(new DbEntityDisplayEvent(
+            session.displayDbEntity(new DbEntityDisplayEvent(
                     ProjectPrefs.class, domain, dataMap, dbEntity));
-            controller.displayDbRelationship(new DbRelationshipDisplayEvent(
+            session.displayDbRelationship(new DbRelationshipDisplayEvent(
                     ProjectPrefs.class, domain, dataMap, dbEntity, dbRels));
         }
     }
 
-    private void restoreEmbeddable(ProjectController controller, DataChannelDescriptor domain) {
+    private void restoreEmbeddable(ProjectSession session, DataChannelDescriptor domain) {
         DataMap dataMap = domain.getDataMap(prefs.get(DATA_MAP_KEY, ""));
         if (dataMap == null) {
             return;
@@ -473,10 +473,10 @@ public final class ProjectPrefs implements PreferenceAdapter {
         if (embeddable == null) {
             return;
         }
-        controller.displayEmbeddable(new EmbeddableDisplayEvent(ProjectPrefs.class, domain, dataMap, embeddable));
+        session.displayEmbeddable(new EmbeddableDisplayEvent(ProjectPrefs.class, domain, dataMap, embeddable));
     }
 
-    private void restoreEmbeddableAttributes(ProjectController controller, DataChannelDescriptor domain) {
+    private void restoreEmbeddableAttributes(ProjectSession session, DataChannelDescriptor domain) {
         DataMap dataMap = domain.getDataMap(prefs.get(DATA_MAP_KEY, ""));
         if (dataMap == null) {
             return;
@@ -485,14 +485,14 @@ public final class ProjectPrefs implements PreferenceAdapter {
         if (embeddable == null) {
             return;
         }
-        controller.displayEmbeddable(new EmbeddableDisplayEvent(ProjectPrefs.class, domain, dataMap, embeddable));
+        session.displayEmbeddable(new EmbeddableDisplayEvent(ProjectPrefs.class, domain, dataMap, embeddable));
 
         EmbeddableAttribute[] attrs = lookupEmbeddableAttributes(embeddable);
-        controller.displayEmbeddableAttribute(new EmbeddableAttributeDisplayEvent(
+        session.displayEmbeddableAttribute(new EmbeddableAttributeDisplayEvent(
                 ProjectPrefs.class, domain, dataMap, embeddable, attrs));
     }
 
-    private void restoreProcedure(ProjectController controller, DataChannelDescriptor domain) {
+    private void restoreProcedure(ProjectSession session, DataChannelDescriptor domain) {
         DataMap dataMap = domain.getDataMap(prefs.get(DATA_MAP_KEY, ""));
         if (dataMap == null) {
             return;
@@ -501,10 +501,10 @@ public final class ProjectPrefs implements PreferenceAdapter {
         if (procedure == null) {
             return;
         }
-        controller.displayProcedure(new ProcedureDisplayEvent(ProjectPrefs.class, domain, dataMap, procedure));
+        session.displayProcedure(new ProcedureDisplayEvent(ProjectPrefs.class, domain, dataMap, procedure));
     }
 
-    private void restoreProcedureParameters(ProjectController controller, DataChannelDescriptor domain) {
+    private void restoreProcedureParameters(ProjectSession session, DataChannelDescriptor domain) {
         DataMap dataMap = domain.getDataMap(prefs.get(DATA_MAP_KEY, ""));
         if (dataMap == null) {
             return;
@@ -513,14 +513,14 @@ public final class ProjectPrefs implements PreferenceAdapter {
         if (procedure == null) {
             return;
         }
-        controller.displayProcedure(new ProcedureDisplayEvent(ProjectPrefs.class, domain, dataMap, procedure));
+        session.displayProcedure(new ProcedureDisplayEvent(ProjectPrefs.class, domain, dataMap, procedure));
 
         ProcedureParameter[] params = lookupProcedureParameters(procedure);
-        controller.displayProcedureParameter(new ProcedureParameterDisplayEvent(
+        session.displayProcedureParameter(new ProcedureParameterDisplayEvent(
                 ProjectPrefs.class, domain, dataMap, procedure, params));
     }
 
-    private void restoreQuery(ProjectController controller, DataChannelDescriptor domain) {
+    private void restoreQuery(ProjectSession session, DataChannelDescriptor domain) {
         DataMap dataMap = domain.getDataMap(prefs.get(DATA_MAP_KEY, ""));
         if (dataMap == null) {
             return;
@@ -529,10 +529,10 @@ public final class ProjectPrefs implements PreferenceAdapter {
         if (query == null) {
             return;
         }
-        controller.displayQuery(new QueryDisplayEvent(ProjectPrefs.class, domain, dataMap, query));
+        session.displayQuery(new QueryDisplayEvent(ProjectPrefs.class, domain, dataMap, query));
     }
 
-    private void restoreMultipleObjects(ProjectController controller, DataChannelDescriptor domain) {
+    private void restoreMultipleObjects(ProjectSession session, DataChannelDescriptor domain) {
         String parentName = prefs.get(PARENT_OBJECT_KEY, "");
         ConfigurationNode parent;
         ConfigurationNode[] objects;
@@ -551,7 +551,7 @@ public final class ProjectPrefs implements PreferenceAdapter {
             objects = lookupMultipleObjects(domain);
         }
 
-        controller.displayMultipleObjects(new MultipleObjectsDisplayEvent(ProjectPrefs.class, parent, objects));
+        session.displayMultipleObjects(new MultipleObjectsDisplayEvent(ProjectPrefs.class, parent, objects));
     }
 
     // -------------------- name lookups --------------------
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/ProjectController.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/project/ProjectSession.java
similarity index 91%
rename from modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/ProjectController.java
rename to modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/project/ProjectSession.java
index ef43bb8c9..e1c82ccbb 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/ProjectController.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/project/ProjectSession.java
@@ -17,7 +17,7 @@
  *  under the License.
  ****************************************************************/
 
-package org.apache.cayenne.modeler.ui.project;
+package org.apache.cayenne.modeler.project;
 
 import org.apache.cayenne.configuration.ConfigurationNode;
 import org.apache.cayenne.configuration.DataChannelDescriptor;
@@ -35,14 +35,10 @@ import org.apache.cayenne.map.ObjRelationship;
 import org.apache.cayenne.map.Procedure;
 import org.apache.cayenne.map.ProcedureParameter;
 import org.apache.cayenne.map.QueryDescriptor;
+import org.apache.cayenne.modeler.Application;
 import org.apache.cayenne.modeler.event.display.*;
 import org.apache.cayenne.modeler.event.model.*;
-import org.apache.cayenne.modeler.mvc.ChildController;
 import org.apache.cayenne.modeler.service.action.GlobalActions;
-import org.apache.cayenne.modeler.ui.ModelerController;
-import org.apache.cayenne.modeler.ui.action.RevertAction;
-import org.apache.cayenne.modeler.ui.action.SaveAction;
-import org.apache.cayenne.modeler.ui.action.SaveAsAction;
 import org.apache.cayenne.modeler.ui.project.editor.objentity.callbacks.CallbackType;
 import org.apache.cayenne.modeler.ui.project.editor.objentity.callbacks.ObjCallbackMethod;
 import org.apache.cayenne.modeler.util.Comparators;
@@ -57,34 +53,44 @@ import java.util.EventListener;
 import java.util.List;
 import java.util.Objects;
 
+
+// TODO: deal with imports from "org.apache.cayenne.modeler.ui.project.editor.objentity.callbacks" package
+
 /**
- * A controller that works with the project tree, tracking selection and dispatching project events.
+ * Per-project state and event hub. Tracks selection, dispatches model/display events,
+ * manages dirty flag, navigation history, entity resolver, and project file change tracking.
+ *
+ * <p>Lives between project-open and project-close. Components that need to react to project
+ * changes (selection, model edits) subscribe to events on this class. Components that need
+ * to react to dirty-flag transitions register a {@link ProjectDirtyListener}.
  */
-public class ProjectController extends ChildController<ModelerController> {
+public class ProjectSession {
+
+    protected final Application app;
+    private final List<ProjectDirtyListener> dirtyListeners;
 
     private ProjectNavigationHistory navigationHistory;
     private EventListenerList listeners;
     private boolean dirty;
     private Project project;
-    private ProjectView projectView;
-    private ControllerState state;
+    private State state;
     private EntityResolver entityResolver;
     private ProjectFileChangeTracker fileChangeTracker;
 
-    public ProjectController(ModelerController parent) {
-        super(parent);
+    public ProjectSession(Application app) {
+        this.app = app;
+        this.dirtyListeners = new ArrayList<>();
     }
 
-    @Override
-    public ProjectView getView() {
-        return projectView;
+    public Application app() {
+        return app;
     }
 
-    public Project getProject() {
+    public Project project() {
         return project;
     }
 
-    public EntityResolver getEntityResolver() {
+    public EntityResolver entityResolver() {
         return entityResolver;
     }
 
@@ -121,7 +127,7 @@ public class ProjectController extends ChildController<ModelerController> {
         } else if (getSelectedPaths() != null) { // multiple objects
             ConfigurationNode[] paths = getSelectedPaths();
 
-            ConfigurationNodeParentGetter parentGetter = getApplication().getConfigurationNodeParentGetter();
+            ConfigurationNodeParentGetter parentGetter = app.getConfigurationNodeParentGetter();
             Object parent = parentGetter.getParent(paths[0]);
 
             List<ConfigurationNode> result = new ArrayList<>(Arrays.asList(paths));
@@ -147,7 +153,7 @@ public class ProjectController extends ChildController<ModelerController> {
         this.project = project;
 
         this.navigationHistory = new ProjectNavigationHistory();
-        this.state = new ControllerState();
+        this.state = new State();
         this.listeners = new EventListenerList();
 
         if (fileChangeTracker != null) {
@@ -162,7 +168,7 @@ public class ProjectController extends ChildController<ModelerController> {
         this.entityResolver = new EntityResolver();
         updateEntityResolver();
 
-        GlobalActions globalActions = application.getActionManager();
+        GlobalActions globalActions = app.getActionManager();
 
         addDataNodeDisplayListener(e -> globalActions.dataNodeSelected());
         addDataMapDisplayListener(e -> globalActions.dataMapSelected());
@@ -172,8 +178,6 @@ public class ProjectController extends ChildController<ModelerController> {
         addProcedureDisplayListener(e -> globalActions.procedureSelected());
         addMultipleObjectsDisplayListener(e -> globalActions.objectsSelected(e.getNodes()));
         addEmbeddableDisplayListener(e -> globalActions.embeddableSelected());
-
-        this.projectView = new ProjectView(this);
     }
 
     public void projectClosed() {
@@ -181,7 +185,6 @@ public class ProjectController extends ChildController<ModelerController> {
         setDirty(false);
 
         this.project = null;
-        this.projectView = null;
         this.entityResolver = null;
 
         if (fileChangeTracker != null) {
@@ -198,14 +201,14 @@ public class ProjectController extends ChildController<ModelerController> {
         if (project == null) {
             return;
         }
-        ProjectPrefs.of(getApplication().getPreferencesRepository(), project).flush(this);
+        ProjectPrefs.of(app.getPreferencesRepository(), project).flush(this);
     }
 
     public void restoreSelectionFromPrefs() {
         if (project == null) {
             return;
         }
-        ProjectPrefs.of(getApplication().getPreferencesRepository(), project).load(this);
+        ProjectPrefs.of(app.getPreferencesRepository(), project).load(this);
     }
 
     public boolean isDirty() {
@@ -438,10 +441,9 @@ public class ProjectController extends ChildController<ModelerController> {
         boolean changed = e.getDomain() != state.dataDomain || (
                 state.dataNode != null || state.dataMap != null || state.dbEntity != null || state.objEntity != null || state.procedure != null || state.query != null || state.embeddable != null
         );
-//        LOGGER.debug("displayDomain: {}{}", e.getDomain() != null ? e.getDomain().getName() : null, changed ? "" : ", ignored unchanged");
 
         if (changed) {
-            state = new ControllerState();
+            state = new State();
             state.dataDomain = e.getDomain();
             navigationHistory.recordEvent(e);
 
@@ -450,9 +452,9 @@ public class ProjectController extends ChildController<ModelerController> {
             }
 
             if (e.getDomain() == null) {
-                getApplication().getActionManager().projectOpened();
+                app.getActionManager().projectOpened();
             } else {
-                getApplication().getActionManager().domainSelected();
+                app.getActionManager().domainSelected();
             }
         }
     }
@@ -484,7 +486,7 @@ public class ProjectController extends ChildController<ModelerController> {
         );
 
         if (changed) {
-            state = new ControllerState();
+            state = new State();
             state.dataDomain = e.getDomain();
             state.dataNode = e.getDataNode();
             navigationHistory.recordEvent(e);
@@ -525,7 +527,7 @@ public class ProjectController extends ChildController<ModelerController> {
         );
 
         if (changed) {
-            state = new ControllerState();
+            state = new State();
             state.dataDomain = e.getDomain();
             state.dataNode = e.getDataNode();
             state.dataMap = e.getDataMap();
@@ -548,7 +550,7 @@ public class ProjectController extends ChildController<ModelerController> {
 
     public void fireProjectAfterSaveEvent(ProjectAfterSaveEvent e) {
         fileChangeTracker.reset();
-        getApplication().getPreferencesRepository().commitProject(project);
+        app.getPreferencesRepository().commitProject(project);
         for (ProjectAfterSaveListener eventListener : listeners.getListeners(ProjectAfterSaveListener.class)) {
             eventListener.projectSaved(e);
         }
@@ -717,7 +719,7 @@ public class ProjectController extends ChildController<ModelerController> {
         boolean changed = e.getEntity() != state.objEntity;
 
         if (changed) {
-            state = new ControllerState();
+            state = new State();
             state.dataDomain = e.getDomain();
             state.dataMap = e.getDataMap();
             state.objEntity = e.getEntity();
@@ -738,7 +740,7 @@ public class ProjectController extends ChildController<ModelerController> {
         boolean changed = e.getEmbeddable() != state.embeddable;
 
         if (changed) {
-            state = new ControllerState();
+            state = new State();
             state.dataDomain = e.getDomain();
             state.dataMap = e.getDataMap();
             state.embeddable = e.getEmbeddable();
@@ -758,7 +760,7 @@ public class ProjectController extends ChildController<ModelerController> {
         boolean changed = e.getQuery() != state.query;
 
         if (changed) {
-            state = new ControllerState();
+            state = new State();
             state.dataDomain = e.getDomain();
             state.dataMap = e.getDataMap();
             state.query = e.getQuery();
@@ -774,7 +776,7 @@ public class ProjectController extends ChildController<ModelerController> {
         boolean changed = e.getProcedure() != state.procedure;
 
         if (changed) {
-            state = new ControllerState();
+            state = new State();
             state.dataDomain = e.getDomain();
             state.dataMap = e.getDataMap();
             state.procedure = e.getProcedure();
@@ -795,7 +797,7 @@ public class ProjectController extends ChildController<ModelerController> {
 
         if (changed) {
             if (state.procedure != e.getProcedure()) {
-                state = new ControllerState();
+                state = new State();
                 state.dataDomain = e.getDomain();
                 state.dataMap = e.getDataMap();
                 state.procedure = e.getProcedure();
@@ -812,7 +814,7 @@ public class ProjectController extends ChildController<ModelerController> {
         boolean changed = e.getEntity() != state.dbEntity;
 
         if (changed) {
-            state = new ControllerState();
+            state = new State();
             state.dataDomain = e.getDomain();
             state.dataMap = e.getDataMap();
             state.dbEntity = e.getEntity();
@@ -856,7 +858,7 @@ public class ProjectController extends ChildController<ModelerController> {
 
         if (changed) {
             if (e.getEntity() != state.dbEntity) {
-                state = new ControllerState();
+                state = new State();
                 state.dataDomain = e.getDomain();
                 state.dataMap = e.getDataMap();
                 state.dbEntity = e.getEntity();
@@ -898,7 +900,7 @@ public class ProjectController extends ChildController<ModelerController> {
 
         if (changed) {
             if (e.getEntity() != state.objEntity) {
-                state = new ControllerState();
+                state = new State();
                 state.dataDomain = e.getDomain();
                 state.dataMap = e.getDataMap();
                 state.objEntity = e.getEntity();
@@ -917,7 +919,7 @@ public class ProjectController extends ChildController<ModelerController> {
 
         if (changed) {
             if (ev.getEmbeddable() != state.embeddable) {
-                state = new ControllerState();
+                state = new State();
                 state.dataDomain = ev.getDomain();
                 state.dataMap = ev.getDataMap();
                 state.embeddable = ev.getEmbeddable();
@@ -959,7 +961,7 @@ public class ProjectController extends ChildController<ModelerController> {
 
         if (changed) {
             if (e.getEntity() != state.dbEntity) {
-                state = new ControllerState();
+                state = new State();
                 state.dataDomain = e.getDomain();
                 state.dataMap = e.getDataMap();
                 state.dbEntity = e.getEntity();
@@ -997,7 +999,7 @@ public class ProjectController extends ChildController<ModelerController> {
     }
 
     public void displayMultipleObjects(MultipleObjectsDisplayEvent e) {
-        state = new ControllerState();
+        state = new State();
         state.paths = e.getNodes();
         state.parentPath = e.getParentNode();
 
@@ -1011,7 +1013,7 @@ public class ProjectController extends ChildController<ModelerController> {
 
         if (changed) {
             if (e.getEntity() != state.objEntity) {
-                state = new ControllerState();
+                state = new State();
                 state.dataDomain = e.getDomain();
                 state.dataMap = e.getDataMap();
                 state.objEntity = e.getEntity();
@@ -1033,18 +1035,23 @@ public class ProjectController extends ChildController<ModelerController> {
 
     public void setDirty(boolean dirty) {
         if (this.dirty != dirty) {
+            boolean wasDirty = this.dirty;
             this.dirty = dirty;
 
-            application.getActionManager().getAction(SaveAction.class).setEnabled(dirty);
-            application.getActionManager().getAction(SaveAsAction.class).setEnabled(dirty);
-            application.getActionManager().getAction(RevertAction.class).setEnabled(dirty);
-
-            if (dirty) {
-                parent.onProjectModified();
+            for (ProjectDirtyListener listener : dirtyListeners) {
+                listener.onDirtyChanged(wasDirty, dirty);
             }
         }
     }
 
+    public void addDirtyListener(ProjectDirtyListener listener) {
+        dirtyListeners.add(listener);
+    }
+
+    public void removeDirtyListener(ProjectDirtyListener listener) {
+        dirtyListeners.remove(listener);
+    }
+
     public CallbackType getSelectedCallbackType() {
         return state.callbackType;
     }
@@ -1153,32 +1160,31 @@ public class ProjectController extends ChildController<ModelerController> {
         }
     }
 
-    static class ControllerState {
-
-        private DataChannelDescriptor dataDomain;
-        private DataNodeDescriptor dataNode;
-        private DataMap dataMap;
-        private ObjEntity objEntity;
-        private DbEntity dbEntity;
-        private Embeddable embeddable;
+    static class State {
 
-        private EmbeddableAttribute[] embeddableAttributes;
+        DataChannelDescriptor dataDomain;
+        DataNodeDescriptor dataNode;
+        DataMap dataMap;
+        ObjEntity objEntity;
+        DbEntity dbEntity;
+        Embeddable embeddable;
 
-        private ObjAttribute[] objAttributes;
-        private DbAttribute[] dbAttributes;
-        private ObjRelationship[] objRelationships;
-        private DbRelationship[] dbRelationships;
+        EmbeddableAttribute[] embeddableAttributes;
 
-        private Procedure procedure;
-        private ProcedureParameter[] procedureParameters;
-        private QueryDescriptor query;
-        private ConfigurationNode[] paths;
-        private ConfigurationNode parentPath;
-        private CallbackType callbackType;
-        private ObjCallbackMethod[] callbackMethods;
+        ObjAttribute[] objAttributes;
+        DbAttribute[] dbAttributes;
+        ObjRelationship[] objRelationships;
+        DbRelationship[] dbRelationships;
 
-        public ControllerState() {
+        Procedure procedure;
+        ProcedureParameter[] procedureParameters;
+        QueryDescriptor query;
+        ConfigurationNode[] paths;
+        ConfigurationNode parentPath;
+        CallbackType callbackType;
+        ObjCallbackMethod[] callbackMethods;
 
+        State() {
             // life is much easier if these guys are never null
             embeddableAttributes = new EmbeddableAttribute[0];
             dbAttributes = new DbAttribute[0];
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/service/graph/GraphHandler.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/service/graph/GraphHandler.java
index 5c463b28f..d71abc5fc 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/service/graph/GraphHandler.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/service/graph/GraphHandler.java
@@ -21,17 +21,14 @@ package org.apache.cayenne.modeler.service.graph;
 import org.apache.cayenne.configuration.xml.DataChannelMetaData;
 import org.apache.cayenne.configuration.xml.NamespaceAwareNestedTagHandler;
 import org.apache.cayenne.modeler.Application;
-import org.apache.cayenne.modeler.graph.GraphBuilder;
 import org.apache.cayenne.modeler.graph.GraphMap;
 import org.apache.cayenne.modeler.graph.GraphRegistry;
 import org.apache.cayenne.modeler.graph.GraphType;
 import org.apache.cayenne.util.Util;
-import org.jgraph.graph.DefaultGraphCell;
 import org.xml.sax.Attributes;
 import org.xml.sax.ContentHandler;
 import org.xml.sax.SAXException;
 
-import javax.swing.undo.UndoableEdit;
 import java.util.HashMap;
 import java.util.Map;
 
@@ -52,6 +49,10 @@ class GraphHandler extends NamespaceAwareNestedTagHandler {
 
     public GraphHandler(NamespaceAwareNestedTagHandler parent, Application application, DataChannelMetaData metaData) {
         super(parent);
+        // Park parsed graph state in metaData. The live GraphBuilder (which subscribes
+        // to model events) is created later, when the user opens the Graph tab — by
+        // then the project lifecycle has reached the "open" state and the event bus
+        // is wired up.
         loaderContext.addDataChannelListener(dataChannelDescriptor -> {
             GraphRegistry registry = metaData.get(dataChannelDescriptor, GraphRegistry.class);
             if (registry == null) {
@@ -60,21 +61,7 @@ class GraphHandler extends NamespaceAwareNestedTagHandler {
             }
 
             GraphMap map = registry.getGraphMap(dataChannelDescriptor);
-            //apply changes
-            GraphBuilder builder = map.createGraphBuilder(
-                    application.getFrameController().getProjectController(), graphType, false);
-            builder.getGraph().setScale(scale);
-
-            // lookup
-            Map<DefaultGraphCell, Map<String, ?>> propertiesMap = new HashMap<>();
-            for (Map.Entry<String, Map<String, ?>> entry : GraphHandler.this.propertiesMap.entrySet()) {
-                DefaultGraphCell cell = builder.getEntityCell(entry.getKey());
-                propertiesMap.put(cell, entry.getValue());
-            }
-
-            builder.getGraph().getGraphLayoutCache().getModel().removeUndoableEditListener(builder);
-            builder.getGraph().getGraphLayoutCache().edit(propertiesMap, null, null, new UndoableEdit[0]);
-            builder.getGraph().getGraphLayoutCache().getModel().addUndoableEditListener(builder);
+            map.parkParsedState(graphType, scale, propertiesMap);
         });
     }
 
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/service/validator/ConfigurableProjectValidator.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/service/validator/ConfigurableProjectValidator.java
index 6093fa2dc..b25c7bf17 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/service/validator/ConfigurableProjectValidator.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/service/validator/ConfigurableProjectValidator.java
@@ -30,7 +30,7 @@ public class ConfigurableProjectValidator extends DefaultProjectValidator {
     public ConfigurableProjectValidator(@Inject Application application) {
         super(() -> {
             DataChannelMetaData metaData = application.getMetaData();
-            DataChannelDescriptor dataChannel = (DataChannelDescriptor) application.getProject().getRootNode();
+            DataChannelDescriptor dataChannel = (DataChannelDescriptor) application.getFrame().getProjectSession().project().getRootNode();
             return ValidationConfig.fromMetadata(metaData, dataChannel);
         });
     }
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/toolkit/AppDialog.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/toolkit/AppDialog.java
new file mode 100644
index 000000000..72138ed3f
--- /dev/null
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/toolkit/AppDialog.java
@@ -0,0 +1,105 @@
+/*****************************************************************
+ *   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.toolkit;
+
+import org.apache.cayenne.modeler.Application;
+import org.apache.cayenne.util.Util;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.swing.JComponent;
+import javax.swing.JDialog;
+import javax.swing.JOptionPane;
+import javax.swing.KeyStroke;
+import java.awt.Dimension;
+import java.awt.Point;
+import java.awt.Window;
+import java.awt.event.ActionListener;
+import java.awt.event.KeyEvent;
+
+/**
+ * Base for modeler dialogs that merge presentation and logic into a single component.
+ * Holds the {@link Application} reference and provides the small set of utilities
+ * dialogs typically need: centering on owner, ESC-to-close, error reporting.
+ */
+public abstract class AppDialog extends JDialog {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(AppDialog.class);
+
+    private final Application app;
+
+    protected AppDialog(Application app, Window owner, String title, ModalityType modality) {
+        super(owner, title, modality);
+        this.app = app;
+    }
+
+    protected Application app() {
+        return app;
+    }
+
+    protected void centerOnOwner() {
+        Window owner = getOwner();
+        if (owner == null) {
+            return;
+        }
+
+        Dimension ownerSize = owner.getSize();
+        Dimension size = getSize();
+        Point ownerLocation = owner.isShowing() ? owner.getLocationOnScreen() : new Point(0, 0);
+
+        int x = ownerLocation.x + ownerSize.width / 2 - size.width / 2;
+        int y = ownerLocation.y + ownerSize.height / 2 - size.height / 2;
+        setLocation(x, y);
+    }
+
+    /**
+     * Closes the dialog on ESC, honoring the dialog's defaultCloseOperation.
+     */
+    protected void makeCloseableOnEscape() {
+        KeyStroke escReleased = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0, true);
+        ActionListener closeAction = e -> {
+            if (!isVisible()) {
+                return;
+            }
+            switch (getDefaultCloseOperation()) {
+                case JDialog.HIDE_ON_CLOSE:
+                    setVisible(false);
+                    break;
+                case JDialog.DISPOSE_ON_CLOSE:
+                    setVisible(false);
+                    dispose();
+                    break;
+                case JDialog.DO_NOTHING_ON_CLOSE:
+                default:
+                    break;
+            }
+        };
+        getRootPane().registerKeyboardAction(closeAction, escReleased, JComponent.WHEN_IN_FOCUSED_WINDOW);
+    }
+
+    /**
+     * Logs the error and shows a modal error dialog anchored on this dialog.
+     */
+    protected void reportError(String title, Throwable t) {
+        Throwable unwound = Util.unwindException(t);
+        LOGGER.info("Error in " + getClass().getName(), unwound);
+        JOptionPane.showMessageDialog(this, unwound.getMessage(), title, JOptionPane.ERROR_MESSAGE);
+    }
+}
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/NavigateAction.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/toolkit/AppFrame.java
similarity index 66%
copy from modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/NavigateAction.java
copy to modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/toolkit/AppFrame.java
index 5901fdbca..c73303183 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/NavigateAction.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/toolkit/AppFrame.java
@@ -17,23 +17,26 @@
  *  under the License.
  ****************************************************************/
 
-package org.apache.cayenne.modeler.ui.action;
+package org.apache.cayenne.modeler.toolkit;
 
 import org.apache.cayenne.modeler.Application;
-import org.apache.cayenne.modeler.ui.project.ProjectController;
 
-import java.awt.event.ActionEvent;
+import javax.swing.JFrame;
 
-public abstract class NavigateAction extends ModelerAbstractAction {
+/**
+ * Base for modeler frames that merge presentation and logic into a single component.
+ * Holds the {@link Application} reference for code that needs access to application-wide
+ * services (preferences, action manager, etc.).
+ */
+public abstract class AppFrame extends JFrame {
 
-    protected NavigateAction(String name, Application application) {
-        super(name, application);
-    }
+    private final Application app;
 
-    protected abstract void navigate(ProjectController controller);
+    protected AppFrame(Application app) {
+        this.app = app;
+    }
 
-    @Override
-    public void performAction(ActionEvent e) {
-        navigate(getProjectController());
+    protected Application app() {
+        return app;
     }
 }
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/ConfigurePreferencesAction.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/toolkit/AppPanel.java
similarity index 58%
copy from modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/ConfigurePreferencesAction.java
copy to modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/toolkit/AppPanel.java
index 6e1ee60e1..4b5203604 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/ConfigurePreferencesAction.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/toolkit/AppPanel.java
@@ -17,30 +17,34 @@
  *  under the License.
  ****************************************************************/
 
-package org.apache.cayenne.modeler.ui.action;
+package org.apache.cayenne.modeler.toolkit;
 
 import org.apache.cayenne.modeler.Application;
-import org.apache.cayenne.modeler.ui.preferences.PreferenceDialogController;
 
-import java.awt.event.ActionEvent;
+import javax.swing.JPanel;
 
+/**
+ * Base for modeler panels that merge presentation and logic into a single component.
+ * Holds the {@link Application} reference for code that needs access to application-wide
+ * services (preferences, action manager, frame controller, etc.).
+ */
+public abstract class AppPanel extends JPanel {
 
-public class ConfigurePreferencesAction extends ModelerAbstractAction {
+    private final Application app;
 
-    public static String getActionName() {
-        return "Preferences";
+    protected AppPanel(Application app) {
+        this.app = app;
     }
 
-    public ConfigurePreferencesAction(Application application) {
-        super(getActionName(), application);
+    /**
+     * Convenience constructor for nested panels — copies the {@link Application} reference
+     * from a parent {@link AppPanel}.
+     */
+    protected AppPanel(AppPanel parent) {
+        this(parent.app);
     }
 
-    public void performAction(ActionEvent e) {
-        showPreferencesDialog();
+    protected Application app() {
+        return app;
     }
-
-    public void showPreferencesDialog() {
-        new PreferenceDialogController(application.getFrameController()).showLastSelectedAction();
-    }
-    
 }
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/ConfigurePreferencesAction.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/toolkit/ProjectDialog.java
similarity index 55%
copy from modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/ConfigurePreferencesAction.java
copy to modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/toolkit/ProjectDialog.java
index 6e1ee60e1..330b4ecf6 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/ConfigurePreferencesAction.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/toolkit/ProjectDialog.java
@@ -17,30 +17,28 @@
  *  under the License.
  ****************************************************************/
 
-package org.apache.cayenne.modeler.ui.action;
+package org.apache.cayenne.modeler.toolkit;
 
-import org.apache.cayenne.modeler.Application;
-import org.apache.cayenne.modeler.ui.preferences.PreferenceDialogController;
+import org.apache.cayenne.modeler.project.ProjectSession;
 
-import java.awt.event.ActionEvent;
+import java.awt.Window;
 
+/**
+ * Base for modeler dialogs that need access to the per-project {@link ProjectSession}
+ * (selection state, model events, dirty flag) in addition to the {@link
+ * org.apache.cayenne.modeler.Application}. Use {@link AppDialog} when only
+ * application-wide services are needed.
+ */
+public abstract class ProjectDialog extends AppDialog {
 
-public class ConfigurePreferencesAction extends ModelerAbstractAction {
+    private final ProjectSession session;
 
-    public static String getActionName() {
-        return "Preferences";
+    protected ProjectDialog(ProjectSession session, Window owner, String title, ModalityType modality) {
+        super(session.app(), owner, title, modality);
+        this.session = session;
     }
 
-    public ConfigurePreferencesAction(Application application) {
-        super(getActionName(), application);
+    protected ProjectSession session() {
+        return session;
     }
-
-    public void performAction(ActionEvent e) {
-        showPreferencesDialog();
-    }
-
-    public void showPreferencesDialog() {
-        new PreferenceDialogController(application.getFrameController()).showLastSelectedAction();
-    }
-    
 }
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/NavigateForwardAction.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/toolkit/ProjectPanel.java
similarity index 59%
copy from modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/NavigateForwardAction.java
copy to modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/toolkit/ProjectPanel.java
index 77d4e30f7..b0d88eff6 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/NavigateForwardAction.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/toolkit/ProjectPanel.java
@@ -17,24 +17,26 @@
  *  under the License.
  ****************************************************************/
 
-package org.apache.cayenne.modeler.ui.action;
+package org.apache.cayenne.modeler.toolkit;
 
-import org.apache.cayenne.modeler.Application;
-import org.apache.cayenne.modeler.ui.project.ProjectController;
+import org.apache.cayenne.modeler.project.ProjectSession;
 
-public class NavigateForwardAction extends NavigateAction {
+/**
+ * Base for modeler panels that need access to the per-project {@link ProjectSession}
+ * (selection state, model events, dirty flag) in addition to the {@link
+ * org.apache.cayenne.modeler.Application}. Use {@link AppPanel} when only
+ * application-wide services are needed.
+ */
+public abstract class ProjectPanel extends AppPanel {
 
-    public NavigateForwardAction(Application application) {
-        super("Move Forward", application);
-    }
+    private final ProjectSession session;
 
-    @Override
-    public String getIconName() {
-        return "icon-forward.png";
+    protected ProjectPanel(ProjectSession session) {
+        super(session.app());
+        this.session = session;
     }
 
-    @Override
-    protected void navigate(ProjectController controller) {
-        controller.rewindForward();
+    protected ProjectSession session() {
+        return session;
     }
 }
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/ConfigurePreferencesAction.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/toolkit/ProjectTabbedPane.java
similarity index 58%
copy from modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/ConfigurePreferencesAction.java
copy to modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/toolkit/ProjectTabbedPane.java
index 6e1ee60e1..5a8835a7c 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/ConfigurePreferencesAction.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/toolkit/ProjectTabbedPane.java
@@ -17,30 +17,31 @@
  *  under the License.
  ****************************************************************/
 
-package org.apache.cayenne.modeler.ui.action;
+package org.apache.cayenne.modeler.toolkit;
 
 import org.apache.cayenne.modeler.Application;
-import org.apache.cayenne.modeler.ui.preferences.PreferenceDialogController;
+import org.apache.cayenne.modeler.project.ProjectSession;
 
-import java.awt.event.ActionEvent;
+import javax.swing.JTabbedPane;
 
+/**
+ * Base for tabbed-pane containers in the modeler that need {@link ProjectSession} access
+ * (selection state, model events, dirty flag) plus the {@link Application} reference.
+ * Sibling of {@link ProjectPanel}, for editors whose root component is a {@code JTabbedPane}.
+ */
+public abstract class ProjectTabbedPane extends JTabbedPane {
 
-public class ConfigurePreferencesAction extends ModelerAbstractAction {
+    private final ProjectSession session;
 
-    public static String getActionName() {
-        return "Preferences";
+    protected ProjectTabbedPane(ProjectSession session) {
+        this.session = session;
     }
 
-    public ConfigurePreferencesAction(Application application) {
-        super(getActionName(), application);
+    protected Application app() {
+        return session.app();
     }
 
-    public void performAction(ActionEvent e) {
-        showPreferencesDialog();
+    protected ProjectSession session() {
+        return session;
     }
-
-    public void showPreferencesDialog() {
-        new PreferenceDialogController(application.getFrameController()).showLastSelectedAction();
-    }
-    
 }
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/toolkit/Renderers.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/toolkit/Renderers.java
index 994f55786..b459b8815 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/toolkit/Renderers.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/toolkit/Renderers.java
@@ -23,7 +23,7 @@ import org.apache.cayenne.map.DataMap;
 import org.apache.cayenne.map.Entity;
 import org.apache.cayenne.map.MappingNamespace;
 import org.apache.cayenne.modeler.toolkit.icon.IconFactory;
-import org.apache.cayenne.modeler.ui.project.ProjectController;
+import org.apache.cayenne.modeler.project.ProjectSession;
 import org.apache.cayenne.reflect.PropertyUtils;
 import org.apache.cayenne.util.CayenneMapEntry;
 
@@ -73,8 +73,8 @@ public final class Renderers {
     /**
      * Returns a TableCellRenderer to display Cayenne project entities with icons in table.
      */
-    public static TableCellRenderer entityTableRendererWithIcons(ProjectController mediator) {
-        return new EntityTableRenderer(mediator);
+    public static TableCellRenderer entityTableRendererWithIcons(ProjectSession session) {
+        return new EntityTableRenderer(session);
     }
 
     /**
@@ -232,10 +232,10 @@ public final class Renderers {
 
     final static class EntityTableRenderer extends DefaultTableCellRenderer {
 
-        private final ProjectController controller;
+        private final ProjectSession session;
 
-        public EntityTableRenderer(ProjectController controller) {
-            this.controller = controller;
+        public EntityTableRenderer(ProjectSession session) {
+            this.session = session;
         }
 
         public Component getTableCellRendererComponent(
@@ -247,7 +247,7 @@ public final class Renderers {
                 int column) {
 
             Object oldValue = value;
-            value = Renderers.asString(value, controller.getSelectedDataMap());
+            value = Renderers.asString(value, session.getSelectedDataMap());
 
             super.getTableCellRendererComponent(
                     table,
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/toolkit/dialog/CMDialog.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/toolkit/dialog/CMDialog.java
deleted file mode 100644
index 7b9e2e337..000000000
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/toolkit/dialog/CMDialog.java
+++ /dev/null
@@ -1,90 +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.toolkit.dialog;
-
-import javax.swing.*;
-import java.awt.*;
-import java.awt.event.ActionListener;
-import java.awt.event.KeyEvent;
-import java.awt.event.WindowEvent;
-
-/**
- * Superclass of CayenneModeler dialogs.
- */
-public class CMDialog extends JDialog {
-
-    public CMDialog(Frame owner) throws HeadlessException {
-        super(owner);
-    }
-
-    public CMDialog(Frame owner, String title, boolean modal) throws HeadlessException {
-        super(owner, title, modal);
-    }
-
-    public CMDialog(Dialog owner, String title, boolean modal) throws HeadlessException {
-        super(owner, title, modal);
-    }
-
-    /**
-     * Centers this dialog relative to the parent Window
-     */
-    public void centerWindow() {
-
-        Window parent = (Window) super.getParent();
-        Dimension parentSize = parent.getSize();
-        Dimension childSize = this.getSize();
-
-        Point parentLocation = new Point(0, 0);
-        if (parent.isShowing()) {
-            parentLocation = parent.getLocationOnScreen();
-        }
-
-        int x = parentLocation.x + parentSize.width / 2 - childSize.width / 2;
-        int y = parentLocation.y + parentSize.height / 2 - childSize.height / 2;
-
-        this.setLocation(x, y);
-    }
-
-    @Override
-    protected void dialogInit() {
-        super.dialogInit();
-
-        // make dialog closable on escape
-        // TODO: Note that if a dialog contains subcomponents
-        // that use ESC for their own purposes (like editable JTable or JComboBox),
-        // this code will still close the dialog  (e.g. not just an expanded
-        // ComboBox). To fix it see this advise (Swing is Fun!!):
-        //
-        //   http://www.eos.dk/pipermail/swing/2001-June/000789.html
-
-        KeyStroke escReleased = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0, true);
-        ActionListener closeAction = e -> {
-            if (CMDialog.this.isVisible()) {
-                WindowEvent windowClosing = new WindowEvent(CMDialog.this, WindowEvent.WINDOW_CLOSING);
-                CMDialog.super.processWindowEvent(windowClosing);
-            }
-        };
-
-        getRootPane().registerKeyboardAction(
-                closeAction,
-                escReleased,
-                JComponent.WHEN_IN_FOCUSED_WINDOW);
-    }
-}
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/toolkit/table/CMTableModel.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/toolkit/table/CMTableModel.java
index 0db1ef3be..01ca00ab1 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/toolkit/table/CMTableModel.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/toolkit/table/CMTableModel.java
@@ -20,7 +20,7 @@
 package org.apache.cayenne.modeler.toolkit.table;
 
 import org.apache.cayenne.CayenneRuntimeException;
-import org.apache.cayenne.modeler.ui.project.ProjectController;
+import org.apache.cayenne.modeler.project.ProjectSession;
 import org.apache.cayenne.util.Util;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -43,13 +43,13 @@ public abstract class CMTableModel<T> extends AbstractTableModel {
 
     private static final Logger LOGGER = LoggerFactory.getLogger(CMTableModel.class);
 
-    protected ProjectController controller;
+    protected ProjectSession session;
     protected Object eventSource;
     protected List<T> objectList;
 
-    protected CMTableModel(ProjectController controller, Object eventSource, List<T> objectList) {
+    protected CMTableModel(ProjectSession session, Object eventSource, List<T> objectList) {
         this.eventSource = eventSource;
-        this.controller = controller;
+        this.session = session;
         this.objectList = objectList;
     }
 
@@ -61,14 +61,14 @@ public abstract class CMTableModel<T> extends AbstractTableModel {
             if (!Util.nullSafeEquals(newVal, oldValue)) {
                 setUpdatedValueAt(newVal, row, col);
 
-                this.controller.getApplication()
+                this.session.app()
                         .getUndoManager()
                         .addEdit(new CMTableModelUndoableEdit(this, oldValue, newVal, row, col));
             }
         } catch (IllegalArgumentException e) {
             LOGGER.error("Error setting table model value", e);
             JOptionPane.showMessageDialog(
-                    controller.getApplication().getFrameController().getView(),
+                    session.app().getFrame(),
                     e.getMessage(),
                     "Invalid value",
                     JOptionPane.ERROR_MESSAGE);
@@ -97,8 +97,8 @@ public abstract class CMTableModel<T> extends AbstractTableModel {
     /**
      * Returns EventController object.
      */
-    public ProjectController getController() {
-        return controller;
+    public ProjectSession getSession() {
+        return session;
     }
 
     /**
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/MainFrame.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/MainFrame.java
new file mode 100644
index 000000000..3c15889ed
--- /dev/null
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/MainFrame.java
@@ -0,0 +1,428 @@
+/*****************************************************************
+ *   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.ui;
+
+import org.apache.cayenne.CayenneRuntimeException;
+import org.apache.cayenne.modeler.Application;
+import org.apache.cayenne.modeler.pref.RecentProjectsPrefs;
+import org.apache.cayenne.modeler.service.action.GlobalActions;
+import org.apache.cayenne.modeler.service.os.OperatingSystem;
+import org.apache.cayenne.modeler.toolkit.AppFrame;
+import org.apache.cayenne.modeler.toolkit.border.TopBorder;
+import org.apache.cayenne.modeler.toolkit.component.CMComponentGeometryPrefs;
+import org.apache.cayenne.modeler.toolkit.icon.IconFactory;
+import org.apache.cayenne.modeler.toolkit.splitpane.CMSplitPanePrefs;
+import org.apache.cayenne.modeler.ui.action.ExitAction;
+import org.apache.cayenne.modeler.ui.action.OpenProjectAction;
+import org.apache.cayenne.modeler.ui.action.RevertAction;
+import org.apache.cayenne.modeler.ui.action.SaveAction;
+import org.apache.cayenne.modeler.ui.action.SaveAsAction;
+import org.apache.cayenne.modeler.ui.action.ValidateAction;
+import org.apache.cayenne.modeler.project.ProjectSession;
+import org.apache.cayenne.modeler.ui.project.ProjectView;
+import org.apache.cayenne.modeler.ui.project.editor.datamap.dbimport.DbImportResultDialog;
+import org.apache.cayenne.modeler.ui.welcome.WelcomeScreen;
+import org.apache.cayenne.modeler.util.FileFilters;
+import org.apache.cayenne.project.Project;
+import org.apache.cayenne.project.validation.ProjectValidator;
+import org.apache.cayenne.validation.ValidationFailure;
+import org.apache.cayenne.validation.ValidationResult;
+
+import javax.swing.BorderFactory;
+import javax.swing.Box;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JSplitPane;
+import javax.swing.WindowConstants;
+import javax.swing.filechooser.FileFilter;
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.FlowLayout;
+import java.awt.Font;
+import java.awt.datatransfer.DataFlavor;
+import java.awt.datatransfer.Transferable;
+import java.awt.dnd.DropTarget;
+import java.awt.dnd.DropTargetAdapter;
+import java.awt.dnd.DropTargetDropEvent;
+import java.awt.event.ComponentAdapter;
+import java.awt.event.ComponentEvent;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.io.File;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Main frame of CayenneModeler GUI. Owns the {@link ProjectSession} for the lifetime of
+ * the modeler, mounts the {@link ProjectView} on project-open, and orchestrates project
+ * lifecycle (open / save / close).
+ */
+public class MainFrame extends AppFrame {
+
+    private static final int DEFAULT_DIVIDER_LOCATION = 400;
+
+    private final GlobalActions globalActions;
+    private final ProjectSession session;
+
+    private final JSplitPane splitPane;
+    private final CMSplitPanePrefs splitPanePrefs;
+    private final JLabel status;
+    private final WelcomeScreen welcomePanel;
+    private final MainMenuBar menuBar;
+    private final DbImportResultDialog dbImportResultDialog;
+
+    private ProjectView projectView;
+    private Component dockComponent;
+
+    public MainFrame(Application application) {
+        super(application);
+        this.globalActions = app().getActionManager();
+
+        setIconImage(IconFactory.buildIcon("CayenneModeler.png").getImage());
+        getContentPane().setLayout(new BorderLayout());
+        this.menuBar = new MainMenuBar(globalActions);
+        setJMenuBar(menuBar);
+        initToolbar();
+
+        status = new JLabel();
+        status.setFont(status.getFont().deriveFont(Font.PLAIN, 10));
+
+        splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
+        splitPane.setBorder(TopBorder.create());
+        splitPane.getInsets().left = 5;
+        splitPane.getInsets().right = 5;
+        splitPane.setResizeWeight(0.7);
+
+        this.splitPanePrefs = CMSplitPanePrefs.of(app().getPreferencesRepository(), "frame/splitPane");
+
+        JPanel statusBar = new JPanel(new FlowLayout(FlowLayout.LEFT, 3, 1));
+        statusBar.setBorder(TopBorder.create());
+        // add placeholder
+        statusBar.add(Box.createVerticalStrut(16));
+        statusBar.add(status);
+
+        if (getContentPane() instanceof JPanel) {
+            ((JPanel) getContentPane()).setBorder(BorderFactory.createEmptyBorder());
+        }
+        getContentPane().add(splitPane, BorderLayout.CENTER);
+        getContentPane().add(statusBar, BorderLayout.SOUTH);
+
+        this.welcomePanel = new WelcomeScreen(app());
+        this.menuBar.addRecentFileListener(welcomePanel);
+
+        fireRecentFileListChanged(); // start filling list in welcome screen and in menu
+
+        setProjectView(null);
+
+        app().getPlatformInitializer().setupMenus(this);
+
+        this.session = new ProjectSession(app());
+        this.session.addDirtyListener((wasDirty, isDirty) -> {
+            if (isDirty) {
+                onProjectModified();
+            }
+
+            globalActions.getAction(SaveAction.class).setEnabled(isDirty);
+            globalActions.getAction(SaveAsAction.class).setEnabled(isDirty);
+            globalActions.getAction(RevertAction.class).setEnabled(isDirty);
+        });
+
+        this.dbImportResultDialog = new DbImportResultDialog(app(), this);
+    }
+
+    public Application getApplication() {
+        return app();
+    }
+
+    public ProjectSession getProjectSession() {
+        return session;
+    }
+
+    public JLabel getStatus() {
+        return status;
+    }
+
+    public ProjectView getProjectView() {
+        return projectView;
+    }
+
+    public DbImportResultDialog getDbImportResultDialog() {
+        return dbImportResultDialog;
+    }
+
+    private void setProjectView(ProjectView projectView) {
+        int oldLocation = splitPane.getDividerLocation();
+
+        this.projectView = projectView;
+
+        if (projectView != null) {
+            splitPane.setTopComponent(projectView);
+        } else {
+            splitPane.setTopComponent(welcomePanel);
+        }
+
+        validate();
+        splitPane.setDividerLocation(oldLocation);
+    }
+
+    /**
+     * Plugs a component in the frame, between main area and status bar
+     */
+    public void setDockComponent(Component c) {
+        if (this.dockComponent != c) {
+
+            if (c != null) {
+                // re-bind: re-applies the saved divider location so the dock
+                // component lands at the user's last position, then resumes tracking
+                splitPanePrefs.bind(splitPane, DEFAULT_DIVIDER_LOCATION);
+            } else {
+
+                // unbind divider prefs around the swap: while one side of the split pane
+                // is missing, Swing's layout shifts the divider to maxLocation, and we
+                // don't want that transient value persisted or applied on the way back
+
+                splitPanePrefs.unbind(splitPane);
+            }
+
+            this.dockComponent = c;
+            splitPane.setBottomComponent(c);
+            splitPane.validate();
+        }
+    }
+
+    /**
+     * Notifies all listeners that recent file list has changed.
+     */
+    public void fireRecentFileListChanged() {
+        menuBar.fireRecentFileListChanged();
+    }
+
+    public void onStartup() {
+        setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
+
+        addWindowListener(new WindowAdapter() {
+            @Override
+            public void windowClosing(WindowEvent e) {
+                session.saveSelectionToPrefs();
+                app().getActionManager().getAction(ExitAction.class).exit();
+            }
+        });
+
+        // Register a hook to save the window position when quit via the app menu.
+        // This is in Mac OSX only.
+        if (OperatingSystem.getOS() == OperatingSystem.MAC_OS_X) {
+            Runnable runner = session::saveSelectionToPrefs;
+            Runtime.getRuntime().addShutdownHook(new Thread(runner, "Window Prefs Hook"));
+        }
+
+        new DropTarget(this, new DropTargetAdapter() {
+            @Override
+            public void drop(DropTargetDropEvent dtde) {
+                dtde.acceptDrop(dtde.getDropAction());
+                Transferable transferable = dtde.getTransferable();
+                dtde.dropComplete(processDropAction(transferable));
+            }
+        });
+
+        CMComponentGeometryPrefs.of(app().getPreferencesRepository(), "frame/geometry").bind(this, 1200, 720);
+
+        setVisible(true);
+    }
+
+    public void onProjectModified() {
+        setTitle("* - " + getProjectLocationString());
+    }
+
+    public void onProjectSaved() {
+        session.setDirty(false);
+        updateStatus("Project saved...");
+        setTitle(getProjectLocationString());
+    }
+
+    public void onProjectClosed() {
+        session.saveSelectionToPrefs();
+
+        setProjectView(null);
+        this.projectView = null;
+
+        // repaint is needed, since sometimes there is a
+        // trace from menu left on the screen
+        repaint();
+        setTitle("");
+
+        session.projectClosed();
+        app().getActionManager().projectClosed();
+
+        updateStatus("Project Closed...");
+    }
+
+    /**
+     * Handles project opening control. Updates main frame, then delegates control to child controllers.
+     */
+    public void onProjectOpened(Project project) {
+
+        session.projectOpened(project);
+        this.projectView = new ProjectView(session);
+        setTitle(getProjectLocationString());
+        setProjectView(projectView);
+
+        app().getActionManager().projectOpened();
+        session.restoreSelectionFromPrefs();
+
+        // do status update AFTER the project is actually opened...
+        if (project.getConfigurationResource() == null) {
+            updateStatus("New project created...");
+        } else {
+            updateStatus("Project opened...");
+            fireRecentFileListChanged();
+        }
+
+        // for validation purposes combine load failures with post-load validation (not
+        // sure if that'll cause duplicate messages?).
+        List<ValidationFailure> allFailures = new ArrayList<>();
+        Collection<ValidationFailure> loadFailures = project.getConfigurationTree().getLoadFailures();
+
+        if (!loadFailures.isEmpty()) {
+            // mark project as unsaved
+            project.setModified(true);
+            session.setDirty(true);
+            allFailures.addAll(loadFailures);
+        }
+
+        ProjectValidator projectValidator = app().getProjectValidator();
+        ValidationResult validationResult = projectValidator.validate(project.getRootNode());
+        allFailures.addAll(validationResult.getFailures());
+
+        if (!allFailures.isEmpty()) {
+            app().getActionManager().getAction(ValidateAction.class).showFailures(allFailures);
+        }
+    }
+
+    /**
+     * Adds path to the list of last opened projects in preferences.
+     */
+    public void addToLastProjListAction(File file) {
+        RecentProjectsPrefs.of(app().getPreferencesRepository()).addFile(file);
+    }
+
+    public void changePathInLastProjListAction(File oldFile, File newFile) {
+        RecentProjectsPrefs prefs = RecentProjectsPrefs.of(app().getPreferencesRepository());
+        prefs.removeFile(oldFile);
+        prefs.addFile(newFile);
+
+        fireRecentFileListChanged();
+    }
+
+    /**
+     * Performs status bar update with a message. Message will disappear in 6 seconds.
+     */
+    public void updateStatus(String message) {
+        status.setText(message);
+
+        // start message cleanup thread that would remove the message after X seconds
+        if (message != null && !message.trim().isEmpty()) {
+            new Thread(() -> {
+                try {
+                    Thread.sleep(6 * 10000);
+                } catch (InterruptedException ignore) {
+                }
+                if (message.equals(status.getText())) {
+                    updateStatus(null);
+                }
+            }).start();
+        }
+    }
+
+    private boolean processDropAction(Transferable transferable) {
+        List<File> fileList;
+        try {
+            fileList = (List<File>) transferable.getTransferData(DataFlavor.javaFileListFlavor);
+        } catch (Exception e) {
+            return false;
+        }
+
+        File transferFile = fileList.get(0);
+        if (transferFile.isFile()) {
+            FileFilter filter = FileFilters.getApplicationFilter();
+            if (filter.accept(transferFile)) {
+                app().getActionManager().getAction(OpenProjectAction.class).openProject(transferFile);
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    private String getProjectLocationString() {
+        if (session.project().getConfigurationResource() == null) {
+            return "[New Project]";
+        }
+        try {
+            File projectFile = new File(session.project().getConfigurationResource().getURL().toURI());
+            return projectFile.toString();
+        } catch (URISyntaxException e) {
+            throw new CayenneRuntimeException("Invalid project source URL", e);
+        }
+    }
+
+    private void initToolbar() {
+
+        MainToolBar toolBar = new MainToolBar(globalActions);
+        getContentPane().add(toolBar, BorderLayout.NORTH);
+
+        // Hide some buttons when frame is too small
+        int defaultBtnWidth = toolBar.getDefaultButtonWidth();
+        addComponentListener(new ComponentAdapter() {
+            private final int[] empty = {};
+            private final int[] all = {6, 7, 8, 9, 10, 11, 12, 13, 14};
+            private final int[] remove = {6, 7};
+            private final int[] removeAndCopy = {6, 7, 8, 9, 10, 11};
+            private final int[] undo = {12, 13, 14};
+            private final int[] undoAndCopy = {8, 9, 10, 11, 12, 13, 14};
+
+            @Override
+            public void componentResized(ComponentEvent e) {
+                int[] hidden, shown;
+                if (getSize().width < (13 * defaultBtnWidth + 300)) {
+                    hidden = all;
+                    shown = empty;
+                } else if (getSize().width < (16 * defaultBtnWidth + 300)) {
+                    hidden = removeAndCopy;
+                    shown = undo;
+                } else if (getSize().width < (18 * defaultBtnWidth + 300)) {
+                    hidden = remove;
+                    shown = undoAndCopy;
+                } else {
+                    hidden = empty;
+                    shown = all;
+                }
+
+                for (int i : hidden) {
+                    toolBar.getComponentAtIndex(i).setVisible(false);
+                }
+                for (int i : shown) {
+                    toolBar.getComponentAtIndex(i).setVisible(true);
+                }
+            }
+        });
+    }
+}
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/ModelerMenuBar.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/MainMenuBar.java
similarity index 98%
rename from modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/ModelerMenuBar.java
rename to modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/MainMenuBar.java
index b5ca28d8c..6d764683a 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/ModelerMenuBar.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/MainMenuBar.java
@@ -31,11 +31,11 @@ import java.io.File;
 import java.util.ArrayList;
 import java.util.List;
 
-class ModelerMenuBar extends JMenuBar {
+class MainMenuBar extends JMenuBar {
 
     private final List<RecentFileListListener> recentFileListeners;
 
-    ModelerMenuBar(GlobalActions globalActions) {
+    MainMenuBar(GlobalActions globalActions) {
         this.recentFileListeners = new ArrayList<>();
 
         JMenu fileMenu = new JMenu("File");
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/ModelerController.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/ModelerController.java
deleted file mode 100644
index 129925868..000000000
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/ModelerController.java
+++ /dev/null
@@ -1,252 +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.ui;
-
-import org.apache.cayenne.CayenneRuntimeException;
-import org.apache.cayenne.modeler.Application;
-import org.apache.cayenne.modeler.mvc.RootController;
-import org.apache.cayenne.modeler.toolkit.component.CMComponentGeometryPrefs;
-import org.apache.cayenne.modeler.pref.RecentProjectsPrefs;
-import org.apache.cayenne.modeler.service.os.OperatingSystem;
-import org.apache.cayenne.modeler.ui.action.ExitAction;
-import org.apache.cayenne.modeler.ui.action.OpenProjectAction;
-import org.apache.cayenne.modeler.ui.project.ProjectController;
-import org.apache.cayenne.modeler.ui.project.editor.datamap.dbimport.DbImportController;
-import org.apache.cayenne.modeler.ui.project.validator.ProjectValidatorDialogController;
-import org.apache.cayenne.modeler.util.FileFilters;
-import org.apache.cayenne.project.Project;
-import org.apache.cayenne.project.validation.ProjectValidator;
-import org.apache.cayenne.validation.ValidationFailure;
-import org.apache.cayenne.validation.ValidationResult;
-
-import javax.swing.*;
-import javax.swing.filechooser.FileFilter;
-import java.awt.datatransfer.DataFlavor;
-import java.awt.datatransfer.Transferable;
-import java.awt.dnd.DropTarget;
-import java.awt.dnd.DropTargetAdapter;
-import java.awt.dnd.DropTargetDropEvent;
-import java.awt.event.WindowAdapter;
-import java.awt.event.WindowEvent;
-import java.io.File;
-import java.net.URISyntaxException;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-
-/**
- * Controller of the main application frame.
- */
-public class ModelerController extends RootController {
-
-    private final ProjectController projectController;
-    private final ModelerFrame view;
-    private final DbImportController dbImportController;
-
-    public ModelerController(Application application) {
-        super(application);
-
-        this.view = new ModelerFrame(application);
-        application.getPlatformInitializer().setupMenus(view);
-        this.projectController = new ProjectController(this);
-        this.dbImportController = new DbImportController(this);
-    }
-
-    @Override
-    public ModelerFrame getView() {
-        return view;
-    }
-
-    public ProjectController getProjectController() {
-        return projectController;
-    }
-
-    private boolean processDropAction(Transferable transferable) {
-        List<File> fileList;
-        try {
-            fileList = (List<File>) transferable.getTransferData(DataFlavor.javaFileListFlavor);
-        } catch (Exception e) {
-            return false;
-        }
-
-        File transferFile = fileList.get(0);
-        if (transferFile.isFile()) {
-            FileFilter filter = FileFilters.getApplicationFilter();
-            if (filter.accept(transferFile)) {
-                application.getActionManager().getAction(OpenProjectAction.class).openProject(transferFile);
-                return true;
-            }
-        }
-
-        return false;
-    }
-
-    public void onStartup() {
-        view.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
-
-        view.addWindowListener(new WindowAdapter() {
-            @Override
-            public void windowClosing(WindowEvent e) {
-                projectController.saveSelectionToPrefs();
-                getApplication().getActionManager().getAction(ExitAction.class).exit();
-            }
-        });
-
-        // Register a hook to save the window position when quit via the app menu.
-        // This is in Mac OSX only.
-        if (OperatingSystem.getOS() == OperatingSystem.MAC_OS_X) {
-            Runnable runner = projectController::saveSelectionToPrefs;
-            Runtime.getRuntime().addShutdownHook(new Thread(runner, "Window Prefs Hook"));
-        }
-
-        new DropTarget(view, new DropTargetAdapter() {
-            @Override
-            public void drop(DropTargetDropEvent dtde) {
-                dtde.acceptDrop(dtde.getDropAction());
-                Transferable transferable = dtde.getTransferable();
-                dtde.dropComplete(processDropAction(transferable));
-            }
-        });
-
-
-        CMComponentGeometryPrefs.of(application.getPreferencesRepository(), "frame/geometry").bind(view, 1200, 720);
-
-        view.setVisible(true);
-    }
-
-    public void onProjectModified() {
-        view.setTitle("* - " + getProjectLocationString());
-    }
-
-    public void onProjectSaved() {
-        projectController.setDirty(false);
-        updateStatus("Project saved...");
-        view.setTitle(getProjectLocationString());
-    }
-
-    public void onProjectClosed() {
-        projectController.saveSelectionToPrefs();
-
-        // --- update view
-        view.setProjectView(null);
-
-        // repaint is needed, since sometimes there is a
-        // trace from menu left on the screen
-        view.repaint();
-        view.setTitle("");
-
-        projectController.projectClosed();
-        application.getActionManager().projectClosed();
-
-        updateStatus("Project Closed...");
-    }
-
-    /**
-     * Handles project opening control. Updates main frame, then delegates control to child controllers.
-     */
-    public void onProjectOpened(Project project) {
-
-        projectController.projectOpened(project);
-        view.setTitle(getProjectLocationString());
-        view.setProjectView(projectController.getView());
-
-        application.getActionManager().projectOpened();
-        projectController.restoreSelectionFromPrefs();
-
-        // do status update AFTER the project is actually opened...
-        if (project.getConfigurationResource() == null) {
-            updateStatus("New project created...");
-        } else {
-            updateStatus("Project opened...");
-            view.fireRecentFileListChanged();
-        }
-
-        // for validation purposes combine load failures with post-load validation (not
-        // sure if that'll cause duplicate messages?).
-        List<ValidationFailure> allFailures = new ArrayList<>();
-        Collection<ValidationFailure> loadFailures = project.getConfigurationTree().getLoadFailures();
-
-        if (!loadFailures.isEmpty()) {
-            // mark project as unsaved
-            project.setModified(true);
-            projectController.setDirty(true);
-            allFailures.addAll(loadFailures);
-        }
-
-        ProjectValidator projectValidator = getApplication().getProjectValidator();
-        ValidationResult validationResult = projectValidator.validate(project.getRootNode());
-        allFailures.addAll(validationResult.getFailures());
-
-        if (!allFailures.isEmpty()) {
-            new ProjectValidatorDialogController(projectController).showOnFailures(validationResult.getFailures());
-        }
-    }
-
-    /**
-     * Adds path to the list of last opened projects in preferences.
-     */
-    public void addToLastProjListAction(File file) {
-        RecentProjectsPrefs.of(application.getPreferencesRepository()).addFile(file);
-    }
-
-    public void changePathInLastProjListAction(File oldFile, File newFile) {
-        RecentProjectsPrefs prefs = RecentProjectsPrefs.of(application.getPreferencesRepository());
-        prefs.removeFile(oldFile);
-        prefs.addFile(newFile);
-
-        view.fireRecentFileListChanged();
-    }
-
-    /**
-     * Performs status bar update with a message. Message will disappear in 6 seconds.
-     */
-    public void updateStatus(String message) {
-        view.getStatus().setText(message);
-
-        // start message cleanup thread that would remove the message after X seconds
-        if (message != null && !message.trim().isEmpty()) {
-            new Thread(() -> {
-                try {
-                    Thread.sleep(6 * 10000);
-                } catch (InterruptedException ignore) {
-                }
-                if (message.equals(view.getStatus().getText())) {
-                    updateStatus(null);
-                }
-            }).start();
-        }
-    }
-
-    public DbImportController getDbImportController() {
-        return dbImportController;
-    }
-
-    protected String getProjectLocationString() {
-        if (projectController.getProject().getConfigurationResource() == null) {
-            return "[New Project]";
-        }
-        try {
-            File projectFile = new File(projectController.getProject().getConfigurationResource().getURL().toURI());
-            return projectFile.toString();
-        } catch (URISyntaxException e) {
-            throw new CayenneRuntimeException("Invalid project source URL", e);
-        }
-    }
-}
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/ModelerFrame.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/ModelerFrame.java
deleted file mode 100644
index 0e2242a40..000000000
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/ModelerFrame.java
+++ /dev/null
@@ -1,189 +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.ui;
-
-import org.apache.cayenne.modeler.Application;
-import org.apache.cayenne.modeler.toolkit.splitpane.CMSplitPanePrefs;
-import org.apache.cayenne.modeler.service.action.GlobalActions;
-import org.apache.cayenne.modeler.toolkit.border.TopBorder;
-import org.apache.cayenne.modeler.toolkit.icon.IconFactory;
-import org.apache.cayenne.modeler.ui.project.ProjectView;
-import org.apache.cayenne.modeler.ui.welcome.WelcomeScreen;
-
-import javax.swing.*;
-import java.awt.*;
-import java.awt.event.ComponentAdapter;
-import java.awt.event.ComponentEvent;
-
-/**
- * Main frame of CayenneModeler GUI
- */
-public class ModelerFrame extends JFrame {
-
-    private static final int DEFAULT_DIVIDER_LOCATION = 400;
-
-    private final GlobalActions globalActions;
-
-    private final JSplitPane splitPane;
-    private final CMSplitPanePrefs splitPanePrefs;
-    private final JLabel status;
-    private final WelcomeScreen welcomePanel;
-    private final ModelerMenuBar menuBar;
-
-    private ProjectView projectView;
-    private Component dockComponent;
-
-    public ModelerFrame(Application application) {
-        this.globalActions = application.getActionManager();
-
-        setIconImage(IconFactory.buildIcon("CayenneModeler.png").getImage());
-        getContentPane().setLayout(new BorderLayout());
-        this.menuBar = new ModelerMenuBar(globalActions);
-        setJMenuBar(menuBar);
-        initToolbar();
-
-        status = new JLabel();
-        status.setFont(status.getFont().deriveFont(Font.PLAIN, 10));
-
-        splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
-        splitPane.setBorder(TopBorder.create());
-        splitPane.getInsets().left = 5;
-        splitPane.getInsets().right = 5;
-        splitPane.setResizeWeight(0.7);
-
-        this.splitPanePrefs = CMSplitPanePrefs.of(application.getPreferencesRepository(), "frame/splitPane");
-
-        JPanel statusBar = new JPanel(new FlowLayout(FlowLayout.LEFT, 3, 1));
-        statusBar.setBorder(TopBorder.create());
-        // add placeholder
-        statusBar.add(Box.createVerticalStrut(16));
-        statusBar.add(status);
-
-        if (getContentPane() instanceof JPanel) {
-            ((JPanel) getContentPane()).setBorder(BorderFactory.createEmptyBorder());
-        }
-        getContentPane().add(splitPane, BorderLayout.CENTER);
-        getContentPane().add(statusBar, BorderLayout.SOUTH);
-
-        this.welcomePanel = new WelcomeScreen(application);
-        this.menuBar.addRecentFileListener(welcomePanel);
-
-        fireRecentFileListChanged(); // start filling list in welcome screen and in menu
-
-        setProjectView(null);
-    }
-
-    /**
-     * Plugs a component in the frame, between main area and status bar
-     */
-    public void setDockComponent(Component c) {
-        if (this.dockComponent != c) {
-
-            if (c != null) {
-                // re-bind: re-applies the saved divider location so the dock
-                // component lands at the user's last position, then resumes tracking
-                splitPanePrefs.bind(splitPane, DEFAULT_DIVIDER_LOCATION);
-            } else {
-
-                // unbind divider prefs around the swap: while one side of the split pane
-                // is missing, Swing's layout shifts the divider to maxLocation, and we
-                // don't want that transient value persisted or applied on the way back
-
-                splitPanePrefs.unbind(splitPane);
-            }
-
-            this.dockComponent = c;
-            splitPane.setBottomComponent(c);
-            splitPane.validate();
-        }
-    }
-
-    protected void initToolbar() {
-
-        MainToolBar toolBar = new MainToolBar(globalActions);
-        getContentPane().add(toolBar, BorderLayout.NORTH);
-
-        // Hide some buttons when frame is too small
-        int defaultBtnWidth = toolBar.getDefaultButtonWidth();
-        addComponentListener(new ComponentAdapter() {
-            private final int[] empty = {};
-            private final int[] all = {6, 7, 8, 9, 10, 11, 12, 13, 14};
-            private final int[] remove = {6, 7};
-            private final int[] removeAndCopy = {6, 7, 8, 9, 10, 11};
-            private final int[] undo = {12, 13, 14};
-            private final int[] undoAndCopy = {8, 9, 10, 11, 12, 13, 14};
-
-            @Override
-            public void componentResized(ComponentEvent e) {
-                int[] hidden, shown;
-                if (getSize().width < (13 * defaultBtnWidth + 300)) {
-                    hidden = all;
-                    shown = empty;
-                } else if (getSize().width < (16 * defaultBtnWidth + 300)) {
-                    hidden = removeAndCopy;
-                    shown = undo;
-                } else if (getSize().width < (18 * defaultBtnWidth + 300)) {
-                    hidden = remove;
-                    shown = undoAndCopy;
-                } else {
-                    hidden = empty;
-                    shown = all;
-                }
-
-                for (int i : hidden) {
-                    toolBar.getComponentAtIndex(i).setVisible(false);
-                }
-                for (int i : shown) {
-                    toolBar.getComponentAtIndex(i).setVisible(true);
-                }
-            }
-        });
-    }
-
-    public JLabel getStatus() {
-        return status;
-    }
-
-    public ProjectView getProjectView() {
-        return projectView;
-    }
-
-    public void setProjectView(ProjectView projectView) {
-        int oldLocation = splitPane.getDividerLocation();
-
-        this.projectView = projectView;
-
-        if (projectView != null) {
-            splitPane.setTopComponent(projectView);
-        } else {
-            splitPane.setTopComponent(welcomePanel);
-        }
-
-        validate();
-        splitPane.setDividerLocation(oldLocation);
-    }
-
-    /**
-     * Notifies all listeners that recent file list has changed
-     */
-    public void fireRecentFileListChanged() {
-        menuBar.fireRecentFileListChanged();
-    }
-}
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/about/AboutDialog.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/about/AboutDialog.java
index 2fa46ae71..ba7aaa76c 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/about/AboutDialog.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/about/AboutDialog.java
@@ -21,6 +21,7 @@ package org.apache.cayenne.modeler.ui.about;
 
 import org.apache.cayenne.modeler.Application;
 import org.apache.cayenne.modeler.toolkit.icon.IconFactory;
+import org.apache.cayenne.modeler.toolkit.AppFrame;
 import org.apache.cayenne.util.LocalizedStringsHandler;
 import org.apache.cayenne.util.Util;
 
@@ -40,7 +41,7 @@ import java.time.LocalDate;
 // Implementation note - the data displayed here is
 // static and very simple, so there is no need to implement complex Scope MVC
 // triad, though it might be beneficial to use strings file
-public class AboutDialog extends JFrame implements FocusListener, KeyListener, MouseListener {
+public class AboutDialog extends AppFrame implements FocusListener, KeyListener, MouseListener {
 
     private static final String ABOUT_STRING = "(c) 2001-%d Apache Software Foundation and individual authors.<br><br>https://cayenne.apache.org/<br>";
 
@@ -55,7 +56,7 @@ public class AboutDialog extends JFrame implements FocusListener, KeyListener, M
     }
 
     public AboutDialog(Application application) {
-
+        super(application);
         FlowLayout flowLayout = new FlowLayout();
         getContentPane().setLayout(flowLayout);
         getContentPane().setBackground(Color.WHITE);
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/BaseViewEntityAction.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/BaseViewEntityAction.java
index 91dd41e81..4bb32d6dd 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/BaseViewEntityAction.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/BaseViewEntityAction.java
@@ -58,22 +58,22 @@ public abstract class BaseViewEntityAction extends ModelerAbstractAction {
     public void navigateToEntity(Entity<?, ?, ?> entity) {
         TreePath path = buildTreePath(entity);
 
-        ProjectView view = application.getFrameController().getView().getProjectView();
+        ProjectView view = application.getFrame().getProjectView();
         view.getProjectTreeView().getSelectionModel().setSelectionPath(path);
 
-        DataChannelDescriptor domain = (DataChannelDescriptor) getProjectController().getProject().getRootNode();
+        DataChannelDescriptor domain = (DataChannelDescriptor) getProjectSession().project().getRootNode();
         if (entity instanceof DbEntity) {
-            getProjectController().displayDbEntity(new DbEntityDisplayEvent(
+            getProjectSession().displayDbEntity(new DbEntityDisplayEvent(
                     view.getProjectTreeView(), domain, entity.getDataMap(), (DbEntity) entity));
         } else if (entity instanceof ObjEntity) {
-            getProjectController().displayObjEntity(new ObjEntityDisplayEvent(
+            getProjectSession().displayObjEntity(new ObjEntityDisplayEvent(
                     view.getProjectTreeView(), domain, entity.getDataMap(), (ObjEntity) entity));
         }
     }
 
     private TreePath buildTreePath(Entity<?, ?, ?> entity) {
 
-        ProjectView view = application.getFrameController().getView().getProjectView();
+        ProjectView view = application.getFrame().getProjectView();
 
         DataChannelDescriptor domain = (DataChannelDescriptor) getCurrentProject().getRootNode();
 
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/CollapseTreeAction.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/CollapseTreeAction.java
index 6d3120c3f..c1adeeecb 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/CollapseTreeAction.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/CollapseTreeAction.java
@@ -40,6 +40,6 @@ public class CollapseTreeAction extends ModelerAbstractAction {
 
     @Override
     public void performAction(ActionEvent e) {
-        application.getFrameController().getProjectController().getView().getFilterController().treeExpOrCollPath(COLLAPSE);
+        application.getFrame().getProjectView().getFilterPopup().treeExpOrCollPath(COLLAPSE);
     }
 }
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/ConfigurePreferencesAction.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/ConfigurePreferencesAction.java
index 6e1ee60e1..41a7c2d34 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/ConfigurePreferencesAction.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/ConfigurePreferencesAction.java
@@ -20,7 +20,7 @@
 package org.apache.cayenne.modeler.ui.action;
 
 import org.apache.cayenne.modeler.Application;
-import org.apache.cayenne.modeler.ui.preferences.PreferenceDialogController;
+import org.apache.cayenne.modeler.ui.preferences.PreferenceDialog;
 
 import java.awt.event.ActionEvent;
 
@@ -40,7 +40,7 @@ public class ConfigurePreferencesAction extends ModelerAbstractAction {
     }
 
     public void showPreferencesDialog() {
-        new PreferenceDialogController(application.getFrameController()).showLastSelectedAction();
+        new PreferenceDialog(application, application.getFrame()).showLastSelectedAction();
     }
-    
+
 }
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/CopyAction.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/CopyAction.java
index e2dd39b50..d51a46e66 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/CopyAction.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/CopyAction.java
@@ -32,7 +32,7 @@ import org.apache.cayenne.map.Procedure;
 import org.apache.cayenne.map.ProcedureParameter;
 import org.apache.cayenne.map.QueryDescriptor;
 import org.apache.cayenne.modeler.Application;
-import org.apache.cayenne.modeler.ui.project.ProjectController;
+import org.apache.cayenne.modeler.project.ProjectSession;
 import org.apache.cayenne.modeler.ui.project.editor.objentity.callbacks.ObjCallbackMethod;
 import org.apache.cayenne.modeler.toolkit.copypaste.CMTransferable;
 
@@ -80,9 +80,9 @@ public class CopyAction extends ModelerAbstractAction {
      */
     @Override
     public void performAction(ActionEvent e) {
-        ProjectController controller = getProjectController();
+        ProjectSession session = getProjectSession();
 
-        Object content = copy(controller);
+        Object content = copy(session);
 
         if (content != null) {
             Clipboard sysClip = Toolkit.getDefaultToolkit().getSystemClipboard();
@@ -96,8 +96,8 @@ public class CopyAction extends ModelerAbstractAction {
     /**
      * Detects selected objects and returns them
      */
-    public Object copy(ProjectController controller) {
-        return controller.getSelectedObject();
+    public Object copy(ProjectSession session) {
+        return session.getSelectedObject();
     }
 
     /**
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/CopyAttributeAction.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/CopyAttributeAction.java
index e74549956..77fbc0f5b 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/CopyAttributeAction.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/CopyAttributeAction.java
@@ -24,7 +24,7 @@ import org.apache.cayenne.configuration.ConfigurationNode;
 import org.apache.cayenne.map.Attribute;
 import org.apache.cayenne.map.EmbeddableAttribute;
 import org.apache.cayenne.modeler.Application;
-import org.apache.cayenne.modeler.ui.project.ProjectController;
+import org.apache.cayenne.modeler.project.ProjectSession;
 
 /**
  * Action for copying attribute(s)
@@ -68,13 +68,13 @@ public class CopyAttributeAction extends CopyAction implements MultipleObjectsAc
     }
 
     @Override
-    public Object copy(ProjectController controller) {
-        Object[] attrs = getProjectController().getSelectedObjAttributes();
+    public Object copy(ProjectSession session) {
+        Object[] attrs = getProjectSession().getSelectedObjAttributes();
         if (attrs == null || attrs.length == 0) {
-            attrs = getProjectController().getSelectedDbAttributes();
+            attrs = getProjectSession().getSelectedDbAttributes();
         }
         if (attrs == null || attrs.length == 0) {
-            attrs = getProjectController().getSelectedEmbeddableAttributes();
+            attrs = getProjectSession().getSelectedEmbeddableAttributes();
         }
 
         if (attrs != null && attrs.length > 0) {
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/CopyAttributeRelationshipAction.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/CopyAttributeRelationshipAction.java
index f89ac4637..51a325639 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/CopyAttributeRelationshipAction.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/CopyAttributeRelationshipAction.java
@@ -20,7 +20,7 @@ package org.apache.cayenne.modeler.ui.action;
 
 import org.apache.cayenne.configuration.ConfigurationNode;
 import org.apache.cayenne.modeler.Application;
-import org.apache.cayenne.modeler.ui.project.ProjectController;
+import org.apache.cayenne.modeler.project.ProjectSession;
 import org.apache.cayenne.modeler.ui.project.editor.dbentity.properties.DbAttributePanel;
 import org.apache.cayenne.modeler.ui.project.editor.objentity.properties.ObjAttributePanel;
 import javax.swing.JComponent;
@@ -57,11 +57,11 @@ public class CopyAttributeRelationshipAction extends CopyAction implements Multi
         }
     }
 
-    public Object copy(ProjectController controller) {
+    public Object copy(ProjectSession session) {
         if (currentSelectedPanel instanceof ObjAttributePanel || currentSelectedPanel instanceof DbAttributePanel) {
-            return copyAttributeAction.copy(controller);
+            return copyAttributeAction.copy(session);
         } else {
-            return copyRelationshipAction.copy(controller);
+            return copyRelationshipAction.copy(session);
         }
     }
 
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/CopyCallbackMethodAction.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/CopyCallbackMethodAction.java
index 02f24b02a..109b5e46b 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/CopyCallbackMethodAction.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/CopyCallbackMethodAction.java
@@ -22,7 +22,7 @@ import java.util.Arrays;
 
 import org.apache.cayenne.configuration.ConfigurationNode;
 import org.apache.cayenne.modeler.Application;
-import org.apache.cayenne.modeler.ui.project.ProjectController;
+import org.apache.cayenne.modeler.project.ProjectSession;
 import org.apache.cayenne.modeler.ui.project.editor.objentity.callbacks.ObjCallbackMethod;
 
 public class CopyCallbackMethodAction extends CopyAction implements MultipleObjectsAction {
@@ -60,8 +60,8 @@ public class CopyCallbackMethodAction extends CopyAction implements MultipleObje
     }
 
     @Override
-    public Object copy(ProjectController controller) {
-        Object[] methods = getProjectController().getSelectedCallbackMethods();
+    public Object copy(ProjectSession session) {
+        Object[] methods = getProjectSession().getSelectedCallbackMethods();
 
         if (methods != null && methods.length > 0) {
             return Arrays.asList(methods);
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/CopyProcedureParameterAction.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/CopyProcedureParameterAction.java
index e57b70bb2..83dbe3049 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/CopyProcedureParameterAction.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/CopyProcedureParameterAction.java
@@ -23,7 +23,7 @@ import java.util.Arrays;
 import org.apache.cayenne.configuration.ConfigurationNode;
 import org.apache.cayenne.map.ProcedureParameter;
 import org.apache.cayenne.modeler.Application;
-import org.apache.cayenne.modeler.ui.project.ProjectController;
+import org.apache.cayenne.modeler.project.ProjectSession;
 
 /**
  * Action for copying procedure parameter(s)
@@ -62,8 +62,8 @@ public class CopyProcedureParameterAction extends CopyAction implements Multiple
     }
     
     @Override
-    public Object copy(ProjectController controller) {
-        Object[] params = getProjectController().getSelectedProcedureParameters();
+    public Object copy(ProjectSession session) {
+        Object[] params = getProjectSession().getSelectedProcedureParameters();
         
         if (params != null && params.length > 0) {
             return Arrays.asList(params);
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/CopyRelationshipAction.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/CopyRelationshipAction.java
index d87901673..ffdd79ef6 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/CopyRelationshipAction.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/CopyRelationshipAction.java
@@ -23,7 +23,7 @@ import java.util.Arrays;
 import org.apache.cayenne.configuration.ConfigurationNode;
 import org.apache.cayenne.map.Relationship;
 import org.apache.cayenne.modeler.Application;
-import org.apache.cayenne.modeler.ui.project.ProjectController;
+import org.apache.cayenne.modeler.project.ProjectSession;
 
 /**
  * Action for copying relationship(s)
@@ -63,10 +63,10 @@ public class CopyRelationshipAction extends CopyAction implements MultipleObject
     }
 
     @Override
-    public Object copy(ProjectController controller) {
-        Object[] rels = getProjectController().getSelectedObjRelationships();
+    public Object copy(ProjectSession session) {
+        Object[] rels = getProjectSession().getSelectedObjRelationships();
         if (rels == null || rels.length == 0) {
-            rels = getProjectController().getSelectedDbRelationships();
+            rels = getProjectSession().getSelectedDbRelationships();
         }
 
         if (rels != null && rels.length > 0) {
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/CreateAttributeAction.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/CreateAttributeAction.java
index 57d175d0e..81de8b4c2 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/CreateAttributeAction.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/CreateAttributeAction.java
@@ -35,9 +35,8 @@ import org.apache.cayenne.map.ObjEntity;
 import org.apache.cayenne.modeler.event.model.ObjAttributeEvent;
 import org.apache.cayenne.modeler.event.model.DbAttributeEvent;
 import org.apache.cayenne.modeler.event.model.EmbeddableAttributeEvent;
-import org.apache.cayenne.modeler.event.model.ModelEvent;
 import org.apache.cayenne.modeler.Application;
-import org.apache.cayenne.modeler.ui.project.ProjectController;
+import org.apache.cayenne.modeler.project.ProjectSession;
 import org.apache.cayenne.modeler.event.display.DbAttributeDisplayEvent;
 import org.apache.cayenne.modeler.event.display.EmbeddableAttributeDisplayEvent;
 import org.apache.cayenne.modeler.event.display.ObjAttributeDisplayEvent;
@@ -49,43 +48,43 @@ import java.awt.event.ActionEvent;
 
 public class CreateAttributeAction extends ModelerAbstractAction {
 
-    static void fireEmbeddableAttributeEvent(Object src, ProjectController controller, Embeddable embeddable, EmbeddableAttribute attr) {
+    static void fireEmbeddableAttributeEvent(Object src, ProjectSession session, Embeddable embeddable, EmbeddableAttribute attr) {
 
-        controller.fireEmbeddableAttributeEvent(EmbeddableAttributeEvent.ofAdd(src, attr, embeddable));
+        session.fireEmbeddableAttributeEvent(EmbeddableAttributeEvent.ofAdd(src, attr, embeddable));
 
-        DataChannelDescriptor domain = (DataChannelDescriptor) controller.getProject().getRootNode();
+        DataChannelDescriptor domain = (DataChannelDescriptor) session.project().getRootNode();
         EmbeddableAttributeDisplayEvent e = new EmbeddableAttributeDisplayEvent(
-                src, domain, controller.getSelectedDataMap(), embeddable, attr);
+                src, domain, session.getSelectedDataMap(), embeddable, attr);
 
-        controller.displayEmbeddableAttribute(e);
+        session.displayEmbeddableAttribute(e);
     }
 
     /**
      * Fires events when an obj attribute was added
      */
-    static void fireObjAttributeEvent(Object src, ProjectController controller, DataMap map, ObjEntity objEntity,
+    static void fireObjAttributeEvent(Object src, ProjectSession session, DataMap map, ObjEntity objEntity,
                                       ObjAttribute attr) {
 
-        controller.fireObjAttributeEvent(ObjAttributeEvent.ofAdd(src, attr, objEntity));
+        session.fireObjAttributeEvent(ObjAttributeEvent.ofAdd(src, attr, objEntity));
 
-        DataChannelDescriptor domain = (DataChannelDescriptor) controller.getProject().getRootNode();
+        DataChannelDescriptor domain = (DataChannelDescriptor) session.project().getRootNode();
 
         ObjAttributeDisplayEvent ade = new ObjAttributeDisplayEvent(src, domain, map, objEntity, attr);
 
-        controller.displayObjAttribute(ade);
+        session.displayObjAttribute(ade);
     }
 
     /**
      * Fires events when a db attribute was added
      */
-    static void fireDbAttributeEvent(Object src, ProjectController controller, DataMap map, DbEntity dbEntity,
+    static void fireDbAttributeEvent(Object src, ProjectSession session, DataMap map, DbEntity dbEntity,
                                      DbAttribute attr) {
-        controller.fireDbAttributeEvent(DbAttributeEvent.ofAdd(src, attr, dbEntity));
+        session.fireDbAttributeEvent(DbAttributeEvent.ofAdd(src, attr, dbEntity));
 
-        DataChannelDescriptor domain = (DataChannelDescriptor) controller.getProject().getRootNode();
+        DataChannelDescriptor domain = (DataChannelDescriptor) session.project().getRootNode();
         DbAttributeDisplayEvent ade = new DbAttributeDisplayEvent(src, domain, map, dbEntity, attr);
 
-        controller.displayDbAttribute(ade);
+        session.displayDbAttribute(ade);
     }
 
     public CreateAttributeAction(Application application) {
@@ -102,10 +101,10 @@ public class CreateAttributeAction extends ModelerAbstractAction {
      */
     @Override
     public void performAction(ActionEvent e) {
-        ProjectController controller = getProjectController();
+        ProjectSession session = getProjectSession();
 
-        if (getProjectController().getSelectedEmbeddable() != null) {
-            Embeddable embeddable = controller.getSelectedEmbeddable();
+        if (getProjectSession().getSelectedEmbeddable() != null) {
+            Embeddable embeddable = session.getSelectedEmbeddable();
 
             EmbeddableAttribute attr = new EmbeddableAttribute();
             attr.setName(NameBuilder
@@ -115,50 +114,50 @@ public class CreateAttributeAction extends ModelerAbstractAction {
             createEmbAttribute(embeddable, attr);
 
             application.getUndoManager().addEdit(
-                    new CreateEmbAttributeUndoableEdit(getProjectController(), embeddable, new EmbeddableAttribute[]{attr}));
+                    new CreateEmbAttributeUndoableEdit(getProjectSession(), embeddable, new EmbeddableAttribute[]{attr}));
         }
 
-        if (getProjectController().getSelectedObjEntity() != null) {
+        if (getProjectSession().getSelectedObjEntity() != null) {
 
-            ObjEntity objEntity = controller.getSelectedObjEntity();
+            ObjEntity objEntity = session.getSelectedObjEntity();
 
             ObjAttribute attr = new ObjAttribute();
             attr.setName(NameBuilder.builder(attr, objEntity).name());
 
-            createObjAttribute(controller.getSelectedDataMap(), objEntity, attr);
+            createObjAttribute(session.getSelectedDataMap(), objEntity, attr);
 
             application.getUndoManager().addEdit(
-                    new CreateAttributeUndoableEdit(controller, (DataChannelDescriptor) controller.getProject().getRootNode(),
-                            controller.getSelectedDataMap(), objEntity, attr));
-        } else if (getProjectController().getSelectedDbEntity() != null) {
-            DbEntity dbEntity = getProjectController().getSelectedDbEntity();
+                    new CreateAttributeUndoableEdit(session, (DataChannelDescriptor) session.project().getRootNode(),
+                            session.getSelectedDataMap(), objEntity, attr));
+        } else if (getProjectSession().getSelectedDbEntity() != null) {
+            DbEntity dbEntity = getProjectSession().getSelectedDbEntity();
 
             DbAttribute attr = new DbAttribute();
             attr.setName(NameBuilder.builder(attr, dbEntity).name());
             attr.setType(TypesMapping.NOT_DEFINED);
             attr.setEntity(dbEntity);
 
-            createDbAttribute(controller.getSelectedDataMap(), dbEntity, attr);
+            createDbAttribute(session.getSelectedDataMap(), dbEntity, attr);
 
             application.getUndoManager().addEdit(
-                    new CreateAttributeUndoableEdit(controller, (DataChannelDescriptor) controller.getProject().getRootNode(),
-                            controller.getSelectedDataMap(), dbEntity, attr));
+                    new CreateAttributeUndoableEdit(session, (DataChannelDescriptor) session.project().getRootNode(),
+                            session.getSelectedDataMap(), dbEntity, attr));
         }
     }
 
     public void createEmbAttribute(Embeddable embeddable, EmbeddableAttribute attr) {
         embeddable.addAttribute(attr);
-        fireEmbeddableAttributeEvent(this, getProjectController(), embeddable, attr);
+        fireEmbeddableAttributeEvent(this, getProjectSession(), embeddable, attr);
     }
 
     public void createObjAttribute(DataMap map, ObjEntity objEntity, ObjAttribute attr) {
         objEntity.addAttribute(attr);
-        fireObjAttributeEvent(this, getProjectController(), map, objEntity, attr);
+        fireObjAttributeEvent(this, getProjectSession(), map, objEntity, attr);
     }
 
     public void createDbAttribute(DataMap map, DbEntity dbEntity, DbAttribute attr) {
         dbEntity.addAttribute(attr);
-        fireDbAttributeEvent(this, getProjectController(), map, dbEntity, attr);
+        fireDbAttributeEvent(this, getProjectSession(), map, dbEntity, attr);
     }
 
     /**
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/CreateCallbackMethodAction.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/CreateCallbackMethodAction.java
index 493cea11a..42ad3c6fe 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/CreateCallbackMethodAction.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/CreateCallbackMethodAction.java
@@ -43,7 +43,7 @@ public class CreateCallbackMethodAction extends ModelerAbstractAction {
      * @return CallbackMap instance where to create a method
      */
     public CallbackMap getCallbackMap() {
-        return getProjectController().getSelectedObjEntity().getCallbackMap();
+        return getProjectSession().getSelectedObjEntity().getCallbackMap();
     }
 
     @Override
@@ -53,17 +53,17 @@ public class CreateCallbackMethodAction extends ModelerAbstractAction {
 
     @Override
     public final void performAction(ActionEvent e) {
-        CallbackType callbackType = getProjectController().getSelectedCallbackType();
+        CallbackType callbackType = getProjectSession().getSelectedCallbackType();
 
         String methodName = NameBuilder
-                .builderForCallbackMethod(getProjectController().getSelectedObjEntity())
+                .builderForCallbackMethod(getProjectSession().getSelectedObjEntity())
                 .baseName(toMethodName(callbackType.getType()))
                 .name();
 
         createCallbackMethod(callbackType, methodName);
         application.getUndoManager().addEdit(
                 new CreateCallbackMethodUndoableEdit(
-                        getProjectController(),
+                        getProjectSession(),
                         callbackType,
                         methodName));
     }
@@ -75,7 +75,7 @@ public class CreateCallbackMethodAction extends ModelerAbstractAction {
 
         CallbackMethodEvent ce = CallbackMethodEvent.ofAdd(this, methodName);
 
-        getProjectController().fireCallbackMethodEvent(ce);
+        getProjectSession().fireCallbackMethodEvent(ce);
     }
 
     private String toMethodName(LifecycleEvent event) {
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/CreateDataMapAction.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/CreateDataMapAction.java
index 3950e0d61..93f2b937e 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/CreateDataMapAction.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/CreateDataMapAction.java
@@ -24,9 +24,8 @@ import org.apache.cayenne.configuration.DataChannelDescriptor;
 import org.apache.cayenne.configuration.DataNodeDescriptor;
 import org.apache.cayenne.dbsync.naming.NameBuilder;
 import org.apache.cayenne.map.DataMap;
-import org.apache.cayenne.modeler.event.model.ModelEvent;
 import org.apache.cayenne.modeler.Application;
-import org.apache.cayenne.modeler.ui.project.ProjectController;
+import org.apache.cayenne.modeler.project.ProjectSession;
 import org.apache.cayenne.modeler.event.display.DataMapDisplayEvent;
 import org.apache.cayenne.modeler.event.model.DataMapEvent;
 import org.apache.cayenne.modeler.event.model.DataNodeEvent;
@@ -39,22 +38,22 @@ import java.awt.event.ActionEvent;
  */
 public class CreateDataMapAction extends ModelerAbstractAction {
     
-    public static void onMapCreated(Object src, ProjectController controller, DataMap map) {
+    public static void onMapCreated(Object src, ProjectSession session, DataMap map) {
 
-        DataChannelDescriptor domain = (DataChannelDescriptor) controller.getProject().getRootNode();
+        DataChannelDescriptor domain = (DataChannelDescriptor) session.project().getRootNode();
         map.setDataChannelDescriptor(domain);
         domain.getDataMaps().add(map);
 
-        DataNodeDescriptor node = controller.getSelectedDataNode();
+        DataNodeDescriptor node = session.getSelectedDataNode();
         if (node != null && !node.getDataMapNames().contains(map.getName())) {
             node.getDataMapNames().add(map.getName());
-            controller.fireDataNodeEvent(DataNodeEvent.ofChange(src, node));
+            session.fireDataNodeEvent(DataNodeEvent.ofChange(src, node));
         }
 
-        controller.fireDataMapEvent(DataMapEvent.ofAdd(src, map));
+        session.fireDataMapEvent(DataMapEvent.ofAdd(src, map));
 
         DataMapDisplayEvent displayEvent = new DataMapDisplayEvent(src, domain, map, node, true);
-        controller.displayDataMap(displayEvent);
+        session.displayDataMap(displayEvent);
     }
 
     public CreateDataMapAction(Application application) {
@@ -68,15 +67,15 @@ public class CreateDataMapAction extends ModelerAbstractAction {
 
     public void performAction(ActionEvent e) {
 
-        DataChannelDescriptor dataChannelDescriptor = (DataChannelDescriptor) getProjectController()
-                .getProject()
+        DataChannelDescriptor dataChannelDescriptor = (DataChannelDescriptor) getProjectSession()
+                .project()
                 .getRootNode();
 
         DataMap map = new DataMap();
         map.setName(NameBuilder.builder(map, dataChannelDescriptor).name());
-        onMapCreated(this, getProjectController(), map);
+        onMapCreated(this, getProjectSession(), map);
 
-        application.getUndoManager().addEdit(new CreateDataMapUndoableEdit(getProjectController(), dataChannelDescriptor, map));
+        application.getUndoManager().addEdit(new CreateDataMapUndoableEdit(getProjectSession(), dataChannelDescriptor, map));
     }
 
     /**
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/CreateDbEntityAction.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/CreateDbEntityAction.java
index 8fff35aa0..6d22d47e0 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/CreateDbEntityAction.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/CreateDbEntityAction.java
@@ -28,23 +28,23 @@ import org.apache.cayenne.map.Entity;
 import org.apache.cayenne.modeler.Application;
 import org.apache.cayenne.modeler.event.display.DbEntityDisplayEvent;
 import org.apache.cayenne.modeler.event.model.DbEntityEvent;
-import org.apache.cayenne.modeler.ui.project.ProjectController;
+import org.apache.cayenne.modeler.project.ProjectSession;
 import org.apache.cayenne.modeler.undo.CreateDbEntityUndoableEdit;
 
 import java.awt.event.ActionEvent;
 
 public class CreateDbEntityAction extends ModelerAbstractAction {
 
-    static void onDbEntityCreated(Object src, ProjectController controller, DbEntity entity) {
-        controller.fireDbEntityEvent(DbEntityEvent.ofAdd(src, entity));
+    static void onDbEntityCreated(Object src, ProjectSession session, DbEntity entity) {
+        session.fireDbEntityEvent(DbEntityEvent.ofAdd(src, entity));
         DbEntityDisplayEvent displayEvent = new DbEntityDisplayEvent(
                 src,
-                (DataChannelDescriptor) controller.getProject().getRootNode(),
-                controller.getSelectedDataMap(),
+                (DataChannelDescriptor) session.project().getRootNode(),
+                session.getSelectedDataMap(),
                 entity,
                 true,
                 false);
-        controller.displayDbEntity(displayEvent);
+        session.displayDbEntity(displayEvent);
     }
 
     public CreateDbEntityAction(Application application) {
@@ -60,12 +60,12 @@ public class CreateDbEntityAction extends ModelerAbstractAction {
      * Creates new DbEntity, adds it to the current DataMap, fires DbEntityEvent and DbEntityDisplayEvent.
      */
     public void performAction(ActionEvent e) {
-        DataMap map = getProjectController().getSelectedDataMap();
+        DataMap map = getProjectSession().getSelectedDataMap();
         DbEntity entity = new DbEntity();
         entity.setName(NameBuilder.builder(entity, map).name());
         createEntity(map, entity);
 
-        application.getUndoManager().addEdit(new CreateDbEntityUndoableEdit(getProjectController(), map, entity));
+        application.getUndoManager().addEdit(new CreateDbEntityUndoableEdit(getProjectSession(), map, entity));
     }
 
     /**
@@ -76,7 +76,7 @@ public class CreateDbEntityAction extends ModelerAbstractAction {
         entity.setCatalog(map.getDefaultCatalog());
         entity.setSchema(map.getDefaultSchema());
         map.addDbEntity(entity);
-        onDbEntityCreated(this, getProjectController(), entity);
+        onDbEntityCreated(this, getProjectSession(), entity);
     }
 
     /**
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/CreateEmbeddableAction.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/CreateEmbeddableAction.java
index 0c19a84d6..d220b1d89 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/CreateEmbeddableAction.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/CreateEmbeddableAction.java
@@ -25,9 +25,8 @@ import org.apache.cayenne.map.DataMap;
 import org.apache.cayenne.map.Embeddable;
 import org.apache.cayenne.map.ObjEntity;
 import org.apache.cayenne.modeler.event.model.EmbeddableEvent;
-import org.apache.cayenne.modeler.event.model.ModelEvent;
 import org.apache.cayenne.modeler.Application;
-import org.apache.cayenne.modeler.ui.project.ProjectController;
+import org.apache.cayenne.modeler.project.ProjectSession;
 import org.apache.cayenne.modeler.event.display.EmbeddableDisplayEvent;
 import org.apache.cayenne.modeler.undo.CreateEmbeddableUndoableEdit;
 
@@ -37,20 +36,20 @@ public class CreateEmbeddableAction extends ModelerAbstractAction {
 
     static void fireEmbeddableEvent(
             Object src,
-            ProjectController controller,
+            ProjectSession session,
             DataMap dataMap,
             Embeddable embeddable) {
 
-        controller.fireEmbeddableEvent(
+        session.fireEmbeddableEvent(
                 EmbeddableEvent.ofAdd(src, embeddable),
                 dataMap);
         EmbeddableDisplayEvent displayEvent = new EmbeddableDisplayEvent(
                 src,
-                (DataChannelDescriptor) controller.getProject().getRootNode(),
+                (DataChannelDescriptor) session.project().getRootNode(),
                 dataMap,
                 embeddable,
                 true);
-        controller.displayEmbeddable(displayEvent);
+        session.displayEmbeddable(displayEvent);
 
     }
 
@@ -65,7 +64,7 @@ public class CreateEmbeddableAction extends ModelerAbstractAction {
 
     @Override
     public void performAction(ActionEvent e) {
-        DataMap dataMap = getProjectController().getSelectedDataMap();
+        DataMap dataMap = getProjectSession().getSelectedDataMap();
 
         Embeddable embeddable = new Embeddable();
         String baseName = NameBuilder.builder(embeddable, dataMap).name();
@@ -73,12 +72,12 @@ public class CreateEmbeddableAction extends ModelerAbstractAction {
         embeddable.setClassName(nameWithPackage);
         createEmbeddable(dataMap, embeddable);
 
-        application.getUndoManager().addEdit(new CreateEmbeddableUndoableEdit(getProjectController(), dataMap, embeddable));
+        application.getUndoManager().addEdit(new CreateEmbeddableUndoableEdit(getProjectSession(), dataMap, embeddable));
     }
 
     public void createEmbeddable(DataMap dataMap, Embeddable embeddable) {
         dataMap.addEmbeddable(embeddable);
-        fireEmbeddableEvent(this, getProjectController(), dataMap, embeddable);
+        fireEmbeddableEvent(this, getProjectSession(), dataMap, embeddable);
     }
 
     /**
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/CreateNodeAction.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/CreateNodeAction.java
index 10fdec912..422adf497 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/CreateNodeAction.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/CreateNodeAction.java
@@ -24,11 +24,10 @@ import org.apache.cayenne.configuration.ConfigurationNode;
 import org.apache.cayenne.configuration.DataChannelDescriptor;
 import org.apache.cayenne.configuration.DataNodeDescriptor;
 import org.apache.cayenne.configuration.DataSourceDescriptor;
-import org.apache.cayenne.modeler.ui.project.ProjectController;
+import org.apache.cayenne.modeler.project.ProjectSession;
 import org.apache.cayenne.modeler.event.model.DataNodeEvent;
 import org.apache.cayenne.configuration.runtime.XMLPoolingDataSourceFactory;
 import org.apache.cayenne.dbsync.naming.NameBuilder;
-import org.apache.cayenne.modeler.event.model.ModelEvent;
 import org.apache.cayenne.modeler.Application;
 import org.apache.cayenne.modeler.event.display.DataNodeDisplayEvent;
 import org.apache.cayenne.modeler.undo.CreateNodeUndoableEdit;
@@ -37,11 +36,11 @@ import java.awt.event.ActionEvent;
 
 public class CreateNodeAction extends ModelerAbstractAction {
 
-    public static void createDataNode(Object src, ProjectController controller, DataNodeDescriptor node) {
-        DataChannelDescriptor domain = (DataChannelDescriptor) controller.getProject().getRootNode();
+    public static void createDataNode(Object src, ProjectSession session, DataNodeDescriptor node) {
+        DataChannelDescriptor domain = (DataChannelDescriptor) session.project().getRootNode();
         domain.getNodeDescriptors().add(node);
-        controller.fireDataNodeEvent(DataNodeEvent.ofAdd(src, node));
-        controller.displayDataNode(new DataNodeDisplayEvent(src, domain, node));
+        session.fireDataNodeEvent(DataNodeEvent.ofAdd(src, node));
+        session.displayDataNode(new DataNodeDisplayEvent(src, domain, node));
     }
 
     public CreateNodeAction(Application application) {
@@ -56,8 +55,8 @@ public class CreateNodeAction extends ModelerAbstractAction {
     @Override
     public void performAction(ActionEvent e) {
         DataNodeDescriptor node = buildDataNode();
-        createDataNode(this, getProjectController(), node);
-        application.getUndoManager().addEdit(new CreateNodeUndoableEdit(getProjectController(), node));
+        createDataNode(this, getProjectSession(), node);
+        application.getUndoManager().addEdit(new CreateNodeUndoableEdit(getProjectSession(), node));
     }
 
 
@@ -74,7 +73,7 @@ public class CreateNodeAction extends ModelerAbstractAction {
      * any events.
      */
     public DataNodeDescriptor buildDataNode() {
-        DataChannelDescriptor domain = (DataChannelDescriptor) getProjectController().getProject().getRootNode();
+        DataChannelDescriptor domain = (DataChannelDescriptor) getProjectSession().project().getRootNode();
 
         DataNodeDescriptor node = buildDataNode(domain);
         node.setDataSourceDescriptor(new DataSourceDescriptor());
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/CreateObjEntityAction.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/CreateObjEntityAction.java
index 8cda0a0d5..64a4410ae 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/CreateObjEntityAction.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/CreateObjEntityAction.java
@@ -31,7 +31,7 @@ import org.apache.cayenne.map.ObjEntity;
 import org.apache.cayenne.modeler.Application;
 import org.apache.cayenne.modeler.event.display.ObjEntityDisplayEvent;
 import org.apache.cayenne.modeler.event.model.ObjEntityEvent;
-import org.apache.cayenne.modeler.ui.project.ProjectController;
+import org.apache.cayenne.modeler.project.ProjectSession;
 import org.apache.cayenne.modeler.undo.CreateObjEntityUndoableEdit;
 import org.apache.cayenne.util.DeleteRuleUpdater;
 
@@ -41,18 +41,18 @@ public class CreateObjEntityAction extends ModelerAbstractAction {
 
     static void onObjEntityCreated(
             Object src,
-            ProjectController controller,
+            ProjectSession session,
             DataMap dataMap,
             ObjEntity entity) {
-        controller.fireObjEntityEvent(ObjEntityEvent.ofAdd(src, entity));
+        session.fireObjEntityEvent(ObjEntityEvent.ofAdd(src, entity));
         ObjEntityDisplayEvent displayEvent = new ObjEntityDisplayEvent(
                 src,
-                (DataChannelDescriptor) controller.getProject().getRootNode(),
+                (DataChannelDescriptor) session.project().getRootNode(),
                 dataMap,
                 entity,
                 true,
                 false);
-        controller.displayObjEntity(displayEvent);
+        session.displayObjEntity(displayEvent);
     }
 
     public CreateObjEntityAction(Application application) {
@@ -71,9 +71,9 @@ public class CreateObjEntityAction extends ModelerAbstractAction {
     }
 
     protected void createObjEntity() {
-        ProjectController controller = getProjectController();
+        ProjectSession session = getProjectSession();
 
-        DataMap dataMap = controller.getSelectedDataMap();
+        DataMap dataMap = session.getSelectedDataMap();
         ObjEntity entity = new ObjEntity();
         entity.setName(NameBuilder.builder(entity, dataMap).name());
 
@@ -81,7 +81,7 @@ public class CreateObjEntityAction extends ModelerAbstractAction {
         entity.setSuperClassName(dataMap.getDefaultSuperclass());
         entity.setDeclaredLockType(dataMap.getDefaultLockType());
 
-        DbEntity dbEntity = controller.getSelectedDbEntity();
+        DbEntity dbEntity = session.getSelectedDbEntity();
         if (dbEntity != null) {
             entity.setDbEntity(dbEntity);
 
@@ -104,14 +104,14 @@ public class CreateObjEntityAction extends ModelerAbstractAction {
         merger.addEntityMergeListener(DeleteRuleUpdater.getEntityMergeListener());
         merger.synchronizeWithDbEntity(entity);
 
-        onObjEntityCreated(this, controller, dataMap, entity);
+        onObjEntityCreated(this, session, dataMap, entity);
 
-        application.getUndoManager().addEdit(new CreateObjEntityUndoableEdit(getProjectController(), dataMap, entity));
+        application.getUndoManager().addEdit(new CreateObjEntityUndoableEdit(getProjectSession(), dataMap, entity));
     }
 
     public void createObjEntity(DataMap dataMap, ObjEntity entity) {
         dataMap.addObjEntity(entity);
-        onObjEntityCreated(this, getProjectController(), dataMap, entity);
+        onObjEntityCreated(this, getProjectSession(), dataMap, entity);
     }
 
     /**
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/CreateProcedureAction.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/CreateProcedureAction.java
index bbe9ba0b3..87753f08d 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/CreateProcedureAction.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/CreateProcedureAction.java
@@ -25,9 +25,8 @@ import org.apache.cayenne.modeler.event.model.ProcedureEvent;
 import org.apache.cayenne.dbsync.naming.NameBuilder;
 import org.apache.cayenne.map.DataMap;
 import org.apache.cayenne.map.Procedure;
-import org.apache.cayenne.modeler.event.model.ModelEvent;
 import org.apache.cayenne.modeler.Application;
-import org.apache.cayenne.modeler.ui.project.ProjectController;
+import org.apache.cayenne.modeler.project.ProjectSession;
 import org.apache.cayenne.modeler.event.display.ProcedureDisplayEvent;
 import org.apache.cayenne.modeler.undo.CreateProcedureUndoableEdit;
 
@@ -41,15 +40,15 @@ public class CreateProcedureAction extends ModelerAbstractAction {
     /**
      * Fires events when a procedure was added
      */
-    static void fireProcedureEvent(Object src, ProjectController controller, DataMap dataMap, Procedure procedure) {
-        controller.fireProcedureEvent(ProcedureEvent.ofAdd(src, procedure));
+    static void fireProcedureEvent(Object src, ProjectSession session, DataMap dataMap, Procedure procedure) {
+        session.fireProcedureEvent(ProcedureEvent.ofAdd(src, procedure));
         ProcedureDisplayEvent displayEvent = new ProcedureDisplayEvent(
                 src,
-                (DataChannelDescriptor) controller.getProject().getRootNode(),
-                controller.getSelectedDataMap(),
+                (DataChannelDescriptor) session.project().getRootNode(),
+                session.getSelectedDataMap(),
                 procedure,
                 true);
-        controller.displayProcedure(displayEvent);
+        session.displayProcedure(displayEvent);
     }
 
     public CreateProcedureAction(Application application) {
@@ -58,20 +57,20 @@ public class CreateProcedureAction extends ModelerAbstractAction {
 
     @Override
     public void performAction(ActionEvent e) {
-        DataMap map = getProjectController().getSelectedDataMap();
+        DataMap map = getProjectSession().getSelectedDataMap();
 
         Procedure procedure = new Procedure();
         procedure.setName(NameBuilder.builder(procedure, map).name());
         createProcedure(map, procedure);
 
-        application.getUndoManager().addEdit(new CreateProcedureUndoableEdit(getProjectController(), map, procedure));
+        application.getUndoManager().addEdit(new CreateProcedureUndoableEdit(getProjectSession(), map, procedure));
     }
 
     public void createProcedure(DataMap map, Procedure procedure) {
         procedure.setSchema(map.getDefaultSchema());
         procedure.setCatalog(map.getDefaultCatalog());
         map.addProcedure(procedure);
-        fireProcedureEvent(this, getProjectController(), map, procedure);
+        fireProcedureEvent(this, getProjectSession(), map, procedure);
     }
 
     /**
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/CreateProcedureParameterAction.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/CreateProcedureParameterAction.java
index abd2e2fce..4494ea333 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/CreateProcedureParameterAction.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/CreateProcedureParameterAction.java
@@ -25,9 +25,8 @@ import org.apache.cayenne.modeler.event.model.ProcedureParameterEvent;
 import org.apache.cayenne.dbsync.naming.NameBuilder;
 import org.apache.cayenne.map.Procedure;
 import org.apache.cayenne.map.ProcedureParameter;
-import org.apache.cayenne.modeler.event.model.ModelEvent;
 import org.apache.cayenne.modeler.Application;
-import org.apache.cayenne.modeler.ui.project.ProjectController;
+import org.apache.cayenne.modeler.project.ProjectSession;
 import org.apache.cayenne.modeler.event.display.ProcedureParameterDisplayEvent;
 import org.apache.cayenne.modeler.undo.CreateProcedureParameterUndoableEdit;
 
@@ -44,14 +43,14 @@ public class CreateProcedureParameterAction extends ModelerAbstractAction {
      */
     static void fireProcedureParameterEvent(
             Object src,
-            ProjectController controller,
+            ProjectSession session,
             Procedure procedure,
             ProcedureParameter parameter) {
-        controller.fireProcedureParameterEvent(ProcedureParameterEvent.ofAdd(src, parameter));
+        session.fireProcedureParameterEvent(ProcedureParameterEvent.ofAdd(src, parameter));
 
-        controller.displayProcedureParameter(new ProcedureParameterDisplayEvent(src,
-                (DataChannelDescriptor) controller.getProject().getRootNode(),
-                controller.getSelectedDataMap(),
+        session.displayProcedureParameter(new ProcedureParameterDisplayEvent(src,
+                (DataChannelDescriptor) session.project().getRootNode(),
+                session.getSelectedDataMap(),
                 procedure,
                 parameter));
     }
@@ -66,18 +65,18 @@ public class CreateProcedureParameterAction extends ModelerAbstractAction {
      */
     @Override
     public void performAction(ActionEvent e) {
-        ProjectController controller = getProjectController();
+        ProjectSession session = getProjectSession();
 
-        if (getProjectController().getSelectedProcedure() != null) {
-            Procedure procedure = getProjectController().getSelectedProcedure();
+        if (getProjectSession().getSelectedProcedure() != null) {
+            Procedure procedure = getProjectSession().getSelectedProcedure();
             ProcedureParameter parameter = new ProcedureParameter();
             parameter.setName(NameBuilder.builder(parameter, procedure).name());
 
             createProcedureParameter(procedure, parameter);
 
             application.getUndoManager().addEdit(
-                    new CreateProcedureParameterUndoableEdit(controller,
-                            (DataChannelDescriptor) controller.getProject().getRootNode(), controller.getSelectedDataMap(),
+                    new CreateProcedureParameterUndoableEdit(session,
+                            (DataChannelDescriptor) session.project().getRootNode(), session.getSelectedDataMap(),
                             procedure, parameter
                     )
             );
@@ -86,7 +85,7 @@ public class CreateProcedureParameterAction extends ModelerAbstractAction {
 
     public void createProcedureParameter(Procedure procedure, ProcedureParameter parameter) {
         procedure.addCallParameter(parameter);
-        fireProcedureParameterEvent(this, getProjectController(), procedure, parameter);
+        fireProcedureParameterEvent(this, getProjectSession(), procedure, parameter);
     }
 
     /**
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/CreateQueryAction.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/CreateQueryAction.java
index a9fda660f..6281cf90b 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/CreateQueryAction.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/CreateQueryAction.java
@@ -23,10 +23,9 @@ import org.apache.cayenne.configuration.DataChannelDescriptor;
 import org.apache.cayenne.modeler.event.model.QueryEvent;
 import org.apache.cayenne.map.DataMap;
 import org.apache.cayenne.map.QueryDescriptor;
-import org.apache.cayenne.modeler.event.model.ModelEvent;
 import org.apache.cayenne.modeler.Application;
-import org.apache.cayenne.modeler.ui.project.ProjectController;
-import org.apache.cayenne.modeler.ui.project.querytype.QueryTypeController;
+import org.apache.cayenne.modeler.project.ProjectSession;
+import org.apache.cayenne.modeler.ui.project.querytype.QueryTypeDialog;
 import org.apache.cayenne.modeler.event.display.QueryDisplayEvent;
 
 import java.awt.event.ActionEvent;
@@ -48,21 +47,21 @@ public class CreateQueryAction extends ModelerAbstractAction {
     }
 
     protected void createQuery() {
-        new QueryTypeController(getProjectController()).startupAction();
+        new QueryTypeDialog(getProjectSession(), application.getFrame()).open();
     }
     
     public void createQuery(DataChannelDescriptor domain, DataMap dataMap, QueryDescriptor query) {
         dataMap.addQueryDescriptor(query);
         // notify listeners
-        fireQueryEvent(this, getProjectController(), domain, dataMap, query);
+        fireQueryEvent(this, getProjectSession(), domain, dataMap, query);
     }
      
     /**
      * Fires events when a query was added
      */
-    public static void fireQueryEvent(Object src, ProjectController mediator, DataChannelDescriptor domain,
+    public static void fireQueryEvent(Object src, ProjectSession session, DataChannelDescriptor domain,
             DataMap dataMap, QueryDescriptor query) {
-        mediator.fireQueryEvent(QueryEvent.ofAdd(src, query, dataMap));
-        mediator.displayQuery(new QueryDisplayEvent(src, domain, dataMap, query));
+        session.fireQueryEvent(QueryEvent.ofAdd(src, query, dataMap));
+        session.displayQuery(new QueryDisplayEvent(src, domain, dataMap, query));
     }
 }
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/CreateRelationshipAction.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/CreateRelationshipAction.java
index 16ef6dd3c..d248046bb 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/CreateRelationshipAction.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/CreateRelationshipAction.java
@@ -29,11 +29,10 @@ import org.apache.cayenne.map.ObjRelationship;
 import org.apache.cayenne.map.Relationship;
 import org.apache.cayenne.modeler.event.model.ObjRelationshipEvent;
 import org.apache.cayenne.modeler.event.model.DbRelationshipEvent;
-import org.apache.cayenne.modeler.event.model.ModelEvent;
 import org.apache.cayenne.modeler.Application;
-import org.apache.cayenne.modeler.ui.project.ProjectController;
-import org.apache.cayenne.modeler.ui.dbrelationship.DbRelationshipDialogController;
-import org.apache.cayenne.modeler.ui.project.editor.objentity.relinfo.ObjRelationshipInfoController;
+import org.apache.cayenne.modeler.project.ProjectSession;
+import org.apache.cayenne.modeler.ui.dbrelationship.DbRelationshipDialog;
+import org.apache.cayenne.modeler.ui.project.editor.objentity.relinfo.ObjRelationshipInfoDialog;
 import org.apache.cayenne.modeler.event.display.DbRelationshipDisplayEvent;
 import org.apache.cayenne.modeler.event.display.ObjRelationshipDisplayEvent;
 import org.apache.cayenne.util.DeleteRuleUpdater;
@@ -45,28 +44,28 @@ public class CreateRelationshipAction extends ModelerAbstractAction {
     /**
      * Fires events when a obj rel was added
      */
-    static void fireObjRelationshipEvent(Object src, ProjectController mediator, ObjEntity objEntity,
+    static void fireObjRelationshipEvent(Object src, ProjectSession session, ObjEntity objEntity,
                                          ObjRelationship rel) {
 
-        mediator.fireObjRelationshipEvent(ObjRelationshipEvent.ofAdd(src, rel, objEntity));
+        session.fireObjRelationshipEvent(ObjRelationshipEvent.ofAdd(src, rel, objEntity));
 
-        DataChannelDescriptor domain = (DataChannelDescriptor) mediator.getProject().getRootNode();
-        ObjRelationshipDisplayEvent rde = new ObjRelationshipDisplayEvent(src, domain, mediator.getSelectedDataMap(), objEntity, rel);
+        DataChannelDescriptor domain = (DataChannelDescriptor) session.project().getRootNode();
+        ObjRelationshipDisplayEvent rde = new ObjRelationshipDisplayEvent(src, domain, session.getSelectedDataMap(), objEntity, rel);
 
-        mediator.displayObjRelationship(rde);
+        session.displayObjRelationship(rde);
     }
 
     /**
      * Fires events when a db rel was added
      */
-    static void fireDbRelationshipEvent(Object src, ProjectController mediator, DbEntity dbEntity, DbRelationship rel) {
+    static void fireDbRelationshipEvent(Object src, ProjectSession session, DbEntity dbEntity, DbRelationship rel) {
 
-        mediator.fireDbRelationshipEvent(DbRelationshipEvent.ofAdd(src, rel, dbEntity));
+        session.fireDbRelationshipEvent(DbRelationshipEvent.ofAdd(src, rel, dbEntity));
 
-        DataChannelDescriptor domain = (DataChannelDescriptor) mediator.getProject().getRootNode();
-        DbRelationshipDisplayEvent rde = new DbRelationshipDisplayEvent(src, domain, mediator.getSelectedDataMap(), dbEntity, rel);
+        DataChannelDescriptor domain = (DataChannelDescriptor) session.project().getRootNode();
+        DbRelationshipDisplayEvent rde = new DbRelationshipDisplayEvent(src, domain, session.getSelectedDataMap(), dbEntity, rel);
 
-        mediator.displayDbRelationship(rde);
+        session.displayDbRelationship(rde);
     }
 
     public CreateRelationshipAction(Application application) {
@@ -83,18 +82,18 @@ public class CreateRelationshipAction extends ModelerAbstractAction {
      */
     @Override
     public void performAction(ActionEvent e) {
-        ObjEntity objEnt = getProjectController().getSelectedObjEntity();
+        ObjEntity objEnt = getProjectSession().getSelectedObjEntity();
         if (objEnt != null) {
 
-            new ObjRelationshipInfoController(getProjectController())
+            new ObjRelationshipInfoDialog(getProjectSession(), application.getFrame())
                     .createRelationship(objEnt)
                     .startupAction();
 
         } else {
-            DbEntity dbEnt = getProjectController().getSelectedDbEntity();
+            DbEntity dbEnt = getProjectSession().getSelectedDbEntity();
             if (dbEnt != null) {
 
-                new DbRelationshipDialogController(getProjectController())
+                new DbRelationshipDialog(getProjectSession(), application.getFrame())
                         .createNewRelationship(dbEnt)
                         .startUp();
             }
@@ -102,20 +101,20 @@ public class CreateRelationshipAction extends ModelerAbstractAction {
     }
 
     public void createObjRelationship(ObjEntity objEntity, ObjRelationship rel) {
-        ProjectController mediator = getProjectController();
+        ProjectSession session = getProjectSession();
 
         rel.setSourceEntity(objEntity);
         DeleteRuleUpdater.updateObjRelationship(rel);
 
         objEntity.addRelationship(rel);
-        fireObjRelationshipEvent(this, mediator, objEntity, rel);
+        fireObjRelationshipEvent(this, session, objEntity, rel);
     }
 
     public void createDbRelationship(DbEntity dbEntity, DbRelationship rel) {
-        ProjectController mediator = getProjectController();
+        ProjectSession session = getProjectSession();
         dbEntity.addRelationship(rel);
 
-        fireDbRelationshipEvent(this, mediator, dbEntity, rel);
+        fireDbRelationshipEvent(this, session, dbEntity, rel);
     }
 
     /**
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/DBConnectionAwareAction.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/DBConnectionAwareAction.java
index 833a5fdc3..128e6a115 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/DBConnectionAwareAction.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/DBConnectionAwareAction.java
@@ -21,12 +21,12 @@ package org.apache.cayenne.modeler.ui.action;
 
 import org.apache.cayenne.map.DataMap;
 import org.apache.cayenne.modeler.Application;
-import org.apache.cayenne.modeler.ui.datasource.DataSourceController;
+import org.apache.cayenne.modeler.ui.datasource.DataSourceDialog;
 import org.apache.cayenne.modeler.dbconnector.DBConnector;
 import org.apache.cayenne.modeler.pref.DataMapPrefs;
 
 /**
- * Base action that provides DBConnectionInfo for the current DataMap or calls {@link DataSourceController} dialog to
+ * Base action that provides DBConnectionInfo for the current DataMap or calls {@link DataSourceDialog} dialog to
  * create one.
  */
 public abstract class DBConnectionAwareAction extends ModelerAbstractAction {
@@ -39,7 +39,7 @@ public abstract class DBConnectionAwareAction extends ModelerAbstractAction {
 
         DBConnector connector = getConnectionInfoFromPreferences(dataMap);
         if (connector == null) {
-            DataSourceController controller = getDataSourceController(title);
+            DataSourceDialog controller = getDataSourceController(title);
             if (controller == null) {
                 return null;
             }
@@ -50,8 +50,11 @@ public abstract class DBConnectionAwareAction extends ModelerAbstractAction {
         return connector;
     }
 
-    protected DataSourceController getDataSourceController(String title) {
-        DataSourceController connectWizard = new DataSourceController(getProjectController(), title);
+    protected DataSourceDialog getDataSourceController(String title) {
+        DataSourceDialog connectWizard = new DataSourceDialog(
+                getProjectSession(),
+                application.getFrame(),
+                title);
         if (!connectWizard.startupAction()) {
             return null;
         }
@@ -63,7 +66,7 @@ public abstract class DBConnectionAwareAction extends ModelerAbstractAction {
         return defaults != null ? defaults.getConnector() : null;
     }
 
-    protected void saveConnector(DataMap dataMap, DataSourceController controller) {
+    protected void saveConnector(DataMap dataMap, DataSourceDialog controller) {
         dataMapPrefs(dataMap).setConnector(controller.getConnector());
     }
 
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/DbEntityCounterpartAction.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/DbEntityCounterpartAction.java
index c2db957f7..698630865 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/DbEntityCounterpartAction.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/DbEntityCounterpartAction.java
@@ -37,7 +37,7 @@ public class DbEntityCounterpartAction extends BaseViewEntityAction {
 
     @Override
     protected ObjEntity getEntity() {
-        DbEntity dbEntity = getProjectController().getSelectedDbEntity();
+        DbEntity dbEntity = getProjectSession().getSelectedDbEntity();
         if (dbEntity == null) {
             return null;
         }
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/DbEntitySyncAction.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/DbEntitySyncAction.java
index 7d67aa1a7..11c3a3d47 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/DbEntitySyncAction.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/DbEntitySyncAction.java
@@ -27,10 +27,9 @@ import org.apache.cayenne.map.DbEntity;
 import org.apache.cayenne.map.DbRelationship;
 import org.apache.cayenne.map.ObjEntity;
 import org.apache.cayenne.modeler.event.model.ObjEntityEvent;
-import org.apache.cayenne.modeler.event.model.ModelEvent;
 import org.apache.cayenne.modeler.Application;
-import org.apache.cayenne.modeler.ui.project.ProjectController;
-import org.apache.cayenne.modeler.ui.entitysync.EntitySyncController;
+import org.apache.cayenne.modeler.project.ProjectSession;
+import org.apache.cayenne.modeler.ui.entitysync.EntitySyncDialog;
 import org.apache.cayenne.modeler.undo.DbEntitySyncUndoableEdit;
 
 import javax.swing.*;
@@ -66,8 +65,8 @@ public class DbEntitySyncAction extends ModelerAbstractAction {
     }
 
     protected void syncDbEntity() {
-        ProjectController mediator = getProjectController();
-        DbEntity dbEntity = mediator.getSelectedDbEntity();
+        ProjectSession session = getProjectSession();
+        DbEntity dbEntity = session.getSelectedDbEntity();
 
         if (dbEntity != null) {
 
@@ -76,7 +75,10 @@ public class DbEntitySyncAction extends ModelerAbstractAction {
                 return;
             }
 
-            EntityMergeSupport merger = new EntitySyncController(application.getFrameController(), dbEntity).createMerger();
+            EntityMergeSupport merger = new EntitySyncDialog(
+                    application,
+                    application.getFrame(),
+                    dbEntity).createMerger();
 
             if (merger == null) {
                 return;
@@ -84,8 +86,8 @@ public class DbEntitySyncAction extends ModelerAbstractAction {
 
             merger.setNameGenerator(new PreserveRelationshipNameGenerator());
 
-            DbEntitySyncUndoableEdit undoableEdit = new DbEntitySyncUndoableEdit(mediator,
-                    (DataChannelDescriptor) mediator.getProject().getRootNode(), mediator.getSelectedDataMap());
+            DbEntitySyncUndoableEdit undoableEdit = new DbEntitySyncUndoableEdit(session,
+                    (DataChannelDescriptor) session.project().getRootNode(), session.getSelectedDataMap());
 
             // filter out inherited entities, as we need to add attributes only to the roots
             filterInheritedEntities(entities);
@@ -108,7 +110,7 @@ public class DbEntitySyncAction extends ModelerAbstractAction {
                 }
 
                 if (merger.synchronizeWithDbEntity(entity)) {
-                    mediator.fireObjEntityEvent(ObjEntityEvent.ofChange(this, entity));
+                    session.fireObjEntityEvent(ObjEntityEvent.ofChange(this, entity));
                     hasChanges = true;
                 }
 
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/DisableValidationInspectionAction.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/DisableValidationInspectionAction.java
index 1b57f4c03..c97b0096e 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/DisableValidationInspectionAction.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/DisableValidationInspectionAction.java
@@ -31,19 +31,15 @@ public class DisableValidationInspectionAction extends UpdateValidationConfigAct
     private static final String ACTION_NAME = "Disable inspection";
     private static final String INSPECTION_PARAM = "inspection";
 
-    private final ValidateAction validationAction;
-
     public DisableValidationInspectionAction(Application application) {
         super(ACTION_NAME, application);
-        validationAction = new ValidateAction(application);
-
         setUndoable(false);
     }
 
     @Override
     public void performAction(ActionEvent e) {
         Inspection inspection = (Inspection) getValue(INSPECTION_PARAM);
-        DataChannelDescriptor dataChannel = (DataChannelDescriptor) application.getProject().getRootNode();
+        DataChannelDescriptor dataChannel = (DataChannelDescriptor) application.getFrame().getProjectSession().project().getRootNode();
         ValidationConfig config = ValidationConfig.fromMetadata(application.getMetaData(), dataChannel);
 
         EnumSet<Inspection> enabledInspections = EnumSet.copyOf(config.getEnabledInspections());
@@ -51,7 +47,7 @@ public class DisableValidationInspectionAction extends UpdateValidationConfigAct
         putConfig(new ValidationConfig(enabledInspections));
         super.performAction(e);
 
-        validationAction.performAction(e);
+        application.getActionManager().getAction(ValidateAction.class).performAction(e);
     }
 
     public DisableValidationInspectionAction putInspection(Inspection inspection) {
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/FilterAction.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/FilterAction.java
index 542b3860b..31609a4e2 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/FilterAction.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/FilterAction.java
@@ -43,11 +43,9 @@ public class FilterAction extends ModelerAbstractAction {
     public void performAction(ActionEvent e) {
         JButton source = (JButton) e.getSource();
         TreeFilterPopup dialog = application
-                .getFrameController()
-                .getProjectController()
-                .getView()
-                .getFilterController()
-                .getView();
+                .getFrame()
+                .getProjectView()
+                .getFilterPopup();
 
         dialog.pack();
         dialog.show(source, 0, source.getHeight());
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/FindAction.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/FindAction.java
index a030f37ac..3a6dd070d 100755
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/FindAction.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/FindAction.java
@@ -52,11 +52,11 @@ import org.apache.cayenne.modeler.event.display.ObjRelationshipDisplayEvent;
 import org.apache.cayenne.modeler.event.display.ProcedureDisplayEvent;
 import org.apache.cayenne.modeler.event.display.ProcedureParameterDisplayEvent;
 import org.apache.cayenne.modeler.event.display.QueryDisplayEvent;
-import org.apache.cayenne.modeler.ui.find.FindDialogController;
-import org.apache.cayenne.modeler.ui.project.ProjectController;
+import org.apache.cayenne.modeler.ui.find.FindDialog;
+import org.apache.cayenne.modeler.project.ProjectSession;
 import org.apache.cayenne.modeler.ui.project.ProjectView;
 import org.apache.cayenne.modeler.ui.project.tree.ProjectTreeModel;
-import org.apache.cayenne.modeler.ui.project.tree.ProjectTreeView;
+import org.apache.cayenne.modeler.ui.project.tree.ProjectTree;
 
 import javax.swing.*;
 import javax.swing.tree.DefaultMutableTreeNode;
@@ -123,7 +123,7 @@ public class FindAction extends ModelerAbstractAction {
         } else if (searchResults.size() == 1) {
             jumpToResult(searchResults.iterator().next(), application);
         } else {
-            new FindDialogController(application.getFrameController(), searchResults).startupAction();
+            new FindDialog(application, application.getFrame(), searchResults).open();
         }
     }
 
@@ -135,31 +135,31 @@ public class FindAction extends ModelerAbstractAction {
      * Navigate to search result
      */
     public static void jumpToResult(FindAction.SearchResultEntry searchResultEntry, Application application) {
-        DataChannelDescriptor domain = (DataChannelDescriptor) application.getProject().getRootNode();
-        ProjectController controller = application.getFrameController().getProjectController();
-        ProjectView projectView = controller.getView();
+        DataChannelDescriptor domain = (DataChannelDescriptor) application.getFrame().getProjectSession().project().getRootNode();
+        ProjectSession session = application.getFrame().getProjectSession();
+        ProjectView projectView = application.getFrame().getProjectView();
 
         if (searchResultEntry.getObject() instanceof Entity) {
-            jumpToEntityResult((Entity<?, ?, ?>) searchResultEntry.getObject(), projectView, domain, controller);
+            jumpToEntityResult((Entity<?, ?, ?>) searchResultEntry.getObject(), projectView, domain, session);
         } else if (searchResultEntry.getObject() instanceof QueryDescriptor) {
-            jumpToQueryResult((QueryDescriptor) searchResultEntry.getObject(), projectView, domain, controller);
+            jumpToQueryResult((QueryDescriptor) searchResultEntry.getObject(), projectView, domain, session);
         } else if (searchResultEntry.getObject() instanceof Embeddable) {
-            jumpToEmbeddableResult((Embeddable) searchResultEntry.getObject(), projectView, domain, controller);
+            jumpToEmbeddableResult((Embeddable) searchResultEntry.getObject(), projectView, domain, session);
         } else if (searchResultEntry.getObject() instanceof EmbeddableAttribute) {
-            jumpToEmbeddableAttributeResult((EmbeddableAttribute) searchResultEntry.getObject(), projectView, domain, controller);
+            jumpToEmbeddableAttributeResult((EmbeddableAttribute) searchResultEntry.getObject(), projectView, domain, session);
         } else if (searchResultEntry.getObject() instanceof Attribute || searchResultEntry.getObject() instanceof Relationship) {
-            jumpToAttributeResult(searchResultEntry, projectView, domain, controller);
+            jumpToAttributeResult(searchResultEntry, projectView, domain, session);
         } else if (searchResultEntry.getObject() instanceof Procedure) {
-            jumpToProcedureResult((Procedure) searchResultEntry.getObject(), projectView, domain, controller);
+            jumpToProcedureResult((Procedure) searchResultEntry.getObject(), projectView, domain, session);
         } else if (searchResultEntry.getObject() instanceof ProcedureParameter) {
-            jumpToProcedureResult((ProcedureParameter) searchResultEntry.getObject(), projectView, domain, controller);
+            jumpToProcedureResult((ProcedureParameter) searchResultEntry.getObject(), projectView, domain, session);
         }
     }
 
     private List<SearchResultEntry> search(String searchStr) {
         Pattern pattern = Pattern.compile(searchStr, Pattern.CASE_INSENSITIVE);
         List<SearchResultEntry> result = new ArrayList<>();
-        for (DataMap dataMap : ((DataChannelDescriptor) getProjectController().getProject().getRootNode()).getDataMaps()) {
+        for (DataMap dataMap : ((DataChannelDescriptor) getProjectSession().project().getRootNode()).getDataMaps()) {
             searchInQueryDescriptors(pattern, result, dataMap);
             searchInEmbeddables(pattern, result, dataMap);
             searchInDbEntities(pattern, result, dataMap);
@@ -260,7 +260,7 @@ public class FindAction extends ModelerAbstractAction {
     }
 
     private static void jumpToAttributeResult(SearchResultEntry searchResultEntry, ProjectView projectView, DataChannelDescriptor domain,
-                                              ProjectController controller) {
+                                              ProjectSession session) {
         DataMap map;
         Entity<?, ?, ?> entity;
         if (searchResultEntry.getObject() instanceof Attribute) {
@@ -276,25 +276,25 @@ public class FindAction extends ModelerAbstractAction {
             DbAttributeDisplayEvent event = new DbAttributeDisplayEvent(
                     projectView.getProjectTreeView(), domain, map, (DbEntity) entity,
                     (DbAttribute) searchResultEntry.getObject());
-            controller.displayDbAttribute(event);
+            session.displayDbAttribute(event);
             projectView.getEditorPanel().getDbDetailView().repaint();
         } else if (searchResultEntry.getObject() instanceof ObjAttribute) {
             ObjAttributeDisplayEvent event = new ObjAttributeDisplayEvent(
                     projectView.getProjectTreeView(), domain, map, (ObjEntity) entity,
                     (ObjAttribute) searchResultEntry.getObject());
-            controller.displayObjAttribute(event);
+            session.displayObjAttribute(event);
             projectView.getEditorPanel().getObjDetailView().repaint();
         } else if (searchResultEntry.getObject() instanceof DbRelationship) {
             DbRelationshipDisplayEvent event = new DbRelationshipDisplayEvent(
                     projectView.getProjectTreeView(), domain, map, (DbEntity) entity,
                     (DbRelationship) searchResultEntry.getObject());
-            controller.displayDbRelationship(event);
+            session.displayDbRelationship(event);
             projectView.getEditorPanel().getDbDetailView().repaint();
         } else if (searchResultEntry.getObject() instanceof ObjRelationship) {
             ObjRelationshipDisplayEvent event = new ObjRelationshipDisplayEvent(
                     projectView.getProjectTreeView(), domain, map, (ObjEntity) entity,
                     (ObjRelationship) searchResultEntry.getObject());
-            controller.displayObjRelationship(event);
+            session.displayObjRelationship(event);
             projectView.getEditorPanel().getObjDetailView().repaint();
         }
     }
@@ -303,65 +303,65 @@ public class FindAction extends ModelerAbstractAction {
             EmbeddableAttribute attribute,
             ProjectView projectView,
             DataChannelDescriptor domain,
-            ProjectController controller) {
+            ProjectSession session) {
 
         Embeddable embeddable = attribute.getEmbeddable();
         DataMap map = embeddable.getDataMap();
         buildAndSelectTreePath(map, embeddable, projectView);
         EmbeddableAttributeDisplayEvent event = new EmbeddableAttributeDisplayEvent(
                 projectView.getProjectTreeView(), domain, map, embeddable, attribute);
-        controller.displayEmbeddableAttribute(event);
+        session.displayEmbeddableAttribute(event);
         projectView.getEditorPanel().getEmbeddableView().repaint();
     }
 
     private static void jumpToEmbeddableResult(Embeddable embeddable, ProjectView projectView, DataChannelDescriptor domain,
-                                               ProjectController controller) {
+                                               ProjectSession session) {
         DataMap map = embeddable.getDataMap();
         buildAndSelectTreePath(map, embeddable, projectView);
         EmbeddableDisplayEvent event = new EmbeddableDisplayEvent(
                 projectView.getProjectTreeView(), domain, map, embeddable, true);
-        controller.displayEmbeddable(event);
+        session.displayEmbeddable(event);
     }
 
-    private static void jumpToQueryResult(QueryDescriptor queryDescriptor, ProjectView projectView, DataChannelDescriptor domain, ProjectController controller) {
+    private static void jumpToQueryResult(QueryDescriptor queryDescriptor, ProjectView projectView, DataChannelDescriptor domain, ProjectSession session) {
         DataMap map = queryDescriptor.getDataMap();
         buildAndSelectTreePath(map, queryDescriptor, projectView);
         QueryDisplayEvent event = new QueryDisplayEvent(projectView.getProjectTreeView(), domain, map, queryDescriptor);
-        controller.displayQuery(event);
+        session.displayQuery(event);
     }
 
-    private static void jumpToEntityResult(Entity<?, ?, ?> entity, ProjectView projectView, DataChannelDescriptor domain, ProjectController controller) {
+    private static void jumpToEntityResult(Entity<?, ?, ?> entity, ProjectView projectView, DataChannelDescriptor domain, ProjectSession session) {
         DataMap map = entity.getDataMap();
         buildAndSelectTreePath(map, entity, projectView);
 
         if (entity instanceof ObjEntity) {
             ObjEntityDisplayEvent event = new ObjEntityDisplayEvent(
                     projectView.getProjectTreeView(), domain, map, (ObjEntity) entity, true, false);
-            controller.displayObjEntity(event);
+            session.displayObjEntity(event);
         } else if (entity instanceof DbEntity) {
             DbEntityDisplayEvent event = new DbEntityDisplayEvent(
                     projectView.getProjectTreeView(), domain, map, (DbEntity) entity, true, false);
-            controller.displayDbEntity(event);
+            session.displayDbEntity(event);
         }
     }
 
     private static void jumpToProcedureResult(Procedure procedure, ProjectView projectView, DataChannelDescriptor domain,
-                                              ProjectController controller) {
+                                              ProjectSession session) {
         DataMap map = procedure.getDataMap();
         buildAndSelectTreePath(map, procedure, projectView);
         ProcedureDisplayEvent event = new ProcedureDisplayEvent(projectView.getProjectTreeView(), domain, map, procedure);
-        controller.displayProcedure(event);
+        session.displayProcedure(event);
         projectView.getEditorPanel().getProcedureView().repaint();
     }
 
     private static void jumpToProcedureResult(ProcedureParameter parameter, ProjectView projectView, DataChannelDescriptor domain,
-                                              ProjectController controller) {
+                                              ProjectSession session) {
         Procedure procedure = parameter.getProcedure();
         DataMap map = procedure.getDataMap();
         buildAndSelectTreePath(map, procedure, projectView);
         ProcedureParameterDisplayEvent event =
                 new ProcedureParameterDisplayEvent(projectView.getProjectTreeView(), domain, map, procedure, parameter);
-        controller.displayProcedureParameter(event);
+        session.displayProcedureParameter(event);
         projectView.getEditorPanel().getProcedureView().repaint();
     }
 
@@ -369,8 +369,8 @@ public class FindAction extends ModelerAbstractAction {
      * Builds a tree path for a given path and make selection in it
      */
     private static void buildAndSelectTreePath(DataMap map, Object object, ProjectView projectView) {
-        ProjectTreeView projectTreeView = projectView.getProjectTreeView();
-        ProjectTreeModel treeModel = (ProjectTreeModel) projectTreeView.getModel();
+        ProjectTree projectTree = projectView.getProjectTreeView();
+        ProjectTreeModel treeModel = (ProjectTreeModel) projectTree.getModel();
 
         DefaultMutableTreeNode[] mutableTreeNodes = new DefaultMutableTreeNode[]{
                 treeModel.getRootNode(),
@@ -379,10 +379,10 @@ public class FindAction extends ModelerAbstractAction {
         };
 
         TreePath treePath = new TreePath(mutableTreeNodes);
-        if (!projectTreeView.isExpanded(treePath.getParentPath())) {
-            projectTreeView.expandPath(treePath.getParentPath());
+        if (!projectTree.isExpanded(treePath.getParentPath())) {
+            projectTree.expandPath(treePath.getParentPath());
         }
-        projectTreeView.getSelectionModel().setSelectionPath(treePath);
+        projectTree.getSelectionModel().setSelectionPath(treePath);
     }
 
     /**
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/GenerateDBAction.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/GenerateDBAction.java
index 09ba51fa5..35492fc2a 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/GenerateDBAction.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/GenerateDBAction.java
@@ -22,7 +22,7 @@ package org.apache.cayenne.modeler.ui.action;
 import org.apache.cayenne.configuration.DataChannelDescriptor;
 import org.apache.cayenne.map.DataMap;
 import org.apache.cayenne.modeler.Application;
-import org.apache.cayenne.modeler.ui.dbgen.DBGeneratorOptionsController;
+import org.apache.cayenne.modeler.ui.dbgen.DBGeneratorOptionsDialog;
 import org.apache.cayenne.project.Project;
 
 import java.awt.event.ActionEvent;
@@ -45,16 +45,19 @@ public class GenerateDBAction extends ModelerAbstractAction {
     public void performAction(ActionEvent e) {
 
         Collection<DataMap> dataMaps;
-        DataMap dataMap = getProjectController().getSelectedDataMap();
+        DataMap dataMap = getProjectSession().getSelectedDataMap();
 
         if (dataMap != null) {
             dataMaps = new ArrayList<>();
             dataMaps.add(dataMap);
         } else {
-            Project project = getProjectController().getProject();
+            Project project = getProjectSession().project();
             dataMaps = ((DataChannelDescriptor) project.getRootNode()).getDataMaps();
         }
-        new DBGeneratorOptionsController(getProjectController(), "Generate DB Schema: Options", dataMaps)
-                .startupAction();
+        new DBGeneratorOptionsDialog(
+                getProjectSession(),
+                application.getFrame(),
+                "Generate DB Schema: Options",
+                dataMaps).startupAction();
     }
 }
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/ImportDataMapAction.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/ImportDataMapAction.java
index d370188f1..d529f3271 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/ImportDataMapAction.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/ImportDataMapAction.java
@@ -63,7 +63,7 @@ public class ImportDataMapAction extends ModelerAbstractAction {
     }
 
     protected void importDataMap() {
-        File dataMapFile = selectDataMap(application.getFrameController().getView());
+        File dataMapFile = selectDataMap(application.getFrame());
         if (dataMapFile == null) {
             return;
         }
@@ -75,7 +75,7 @@ public class ImportDataMapAction extends ModelerAbstractAction {
             DataMapLoader loader = application.getDataMapLoader();
             newMap = loader.load(new URLResource(url));
 
-            ConfigurationNode root = getProjectController().getProject().getRootNode();
+            ConfigurationNode root = getProjectSession().project().getRootNode();
             newMap.setName(NameBuilder
                     .builder(newMap, root)
                     .baseName(newMap.getName())
@@ -88,10 +88,10 @@ public class ImportDataMapAction extends ModelerAbstractAction {
                 newMap.setConfigurationSource(dataMapResource);
             }
 
-            CreateDataMapAction.onMapCreated(application.getFrameController().getView(), getProjectController(), newMap);
+            CreateDataMapAction.onMapCreated(application.getFrame(), getProjectSession(), newMap);
         } catch (Exception ex) {
             LOGGER.info("Error importing DataMap.", ex);
-            JOptionPane.showMessageDialog(application.getFrameController().getView(), "Error reading DataMap: " + ex.getMessage(),
+            JOptionPane.showMessageDialog(application.getFrame(), "Error reading DataMap: " + ex.getMessage(),
                     "Can't Open DataMap", JOptionPane.OK_OPTION);
         }
     }
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/ImportEOModelAction.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/ImportEOModelAction.java
index 39d67e5fa..e66b73231 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/ImportEOModelAction.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/ImportEOModelAction.java
@@ -46,7 +46,6 @@ import org.apache.cayenne.map.ObjEntity;
 import org.apache.cayenne.map.QueryDescriptor;
 import org.apache.cayenne.modeler.event.model.ObjEntityEvent;
 import org.apache.cayenne.modeler.event.model.DbEntityEvent;
-import org.apache.cayenne.modeler.event.model.ModelEvent;
 import org.apache.cayenne.modeler.Application;
 import org.apache.cayenne.modeler.event.display.DataMapDisplayEvent;
 import org.apache.cayenne.modeler.event.display.DataNodeDisplayEvent;
@@ -54,7 +53,7 @@ import org.apache.cayenne.modeler.event.model.DataNodeEvent;
 import org.apache.cayenne.modeler.event.model.QueryEvent;
 import org.apache.cayenne.modeler.toolkit.filechooser.CMFileChooserPrefs;
 import org.apache.cayenne.modeler.ui.errors.ErrorsController;
-import org.apache.cayenne.modeler.ui.project.ProjectController;
+import org.apache.cayenne.modeler.project.ProjectSession;
 import org.apache.cayenne.modeler.util.FileFilters;
 import org.apache.cayenne.wocompat.EOModelProcessor;
 import org.slf4j.Logger;
@@ -126,7 +125,7 @@ public class ImportEOModelAction extends ModelerAbstractAction {
      */
     protected void importEOModel() {
         JFileChooser fileChooser = getEOModelChooser();
-        int status = fileChooser.showOpenDialog(application.getFrameController().getView());
+        int status = fileChooser.showOpenDialog(application.getFrame());
 
         if (status == JFileChooser.APPROVE_OPTION) {
 
@@ -135,7 +134,7 @@ public class ImportEOModelAction extends ModelerAbstractAction {
                 file = file.getParentFile();
             }
 
-            DataMap currentMap = getProjectController().getSelectedDataMap();
+            DataMap currentMap = getProjectSession().getSelectedDataMap();
 
             try {
                 URL url = file.toURI().toURL();
@@ -206,19 +205,19 @@ public class ImportEOModelAction extends ModelerAbstractAction {
                 dsi.setUserName(keyAsString(connection, "username"));
             }
 
-            DataChannelDescriptor domain = (DataChannelDescriptor) getProjectController()
-                    .getProject()
+            DataChannelDescriptor domain = (DataChannelDescriptor) getProjectSession()
+                    .project()
                     .getRootNode();
             domain.getNodeDescriptors().add(node);
 
             // send events after the node creation is complete
-            getProjectController().fireDataNodeEvent(
+            getProjectSession().fireDataNodeEvent(
                     DataNodeEvent.ofAdd(this, node));
-            getProjectController().displayDataNode(
+            getProjectSession().displayDataNode(
                     new DataNodeDisplayEvent(
                             this,
-                            (DataChannelDescriptor) getProjectController()
-                                    .getProject()
+                            (DataChannelDescriptor) getProjectSession()
+                                    .project()
                                     .getRootNode(),
                             node));
         }
@@ -236,7 +235,7 @@ public class ImportEOModelAction extends ModelerAbstractAction {
      */
     protected void addDataMap(DataMap map, DataMap currentMap) {
 
-        ProjectController controller = getProjectController();
+        ProjectSession session = getProjectSession();
 
         if (currentMap != null) {
             // merge with existing map... have to memorize map state before and after
@@ -254,57 +253,57 @@ public class ImportEOModelAction extends ModelerAbstractAction {
             Collection<DbEntity> newDE = new ArrayList<>(currentMap.getDbEntities());
             Collection<QueryDescriptor> newQueries = new ArrayList<>(currentMap.getQueryDescriptors());
 
-            Object src = application.getFrameController().getView();
+            Object src = application.getFrame();
 
             // 1. ObjEntities
             Collection<ObjEntity> addedOE = new ArrayList<>(newOE);
             addedOE.removeAll(originalOE);
             for (ObjEntity e : addedOE) {
-                controller.fireObjEntityEvent(ObjEntityEvent.ofAdd(src, e));
+                session.fireObjEntityEvent(ObjEntityEvent.ofAdd(src, e));
             }
 
             Collection<ObjEntity> removedOE = new ArrayList<>(originalOE);
             removedOE.removeAll(newOE);
             for (ObjEntity e : removedOE) {
-                controller.fireObjEntityEvent(ObjEntityEvent.ofRemove(src, e));
+                session.fireObjEntityEvent(ObjEntityEvent.ofRemove(src, e));
             }
 
             // 2. DbEntities
             Collection<DbEntity> addedDE = new ArrayList<>(newDE);
             addedDE.removeAll(originalDE);
             for (DbEntity e : addedDE) {
-                controller.fireDbEntityEvent(DbEntityEvent.ofAdd(src, e));
+                session.fireDbEntityEvent(DbEntityEvent.ofAdd(src, e));
             }
 
             Collection<DbEntity> removedDE = new ArrayList<>(originalDE);
             removedDE.removeAll(newDE);
             for (DbEntity e : removedDE) {
-                controller.fireDbEntityEvent(DbEntityEvent.ofRemove(src, e));
+                session.fireDbEntityEvent(DbEntityEvent.ofRemove(src, e));
             }
 
             // 3. queries
             Collection<QueryDescriptor> addedQueries = new ArrayList<>(newQueries);
             addedQueries.removeAll(originalQueries);
             for (QueryDescriptor q : addedQueries) {
-                controller.fireQueryEvent(QueryEvent.ofAdd(src, q));
+                session.fireQueryEvent(QueryEvent.ofAdd(src, q));
             }
 
             Collection<QueryDescriptor> removedQueries = new ArrayList<>(originalQueries);
             removedQueries.removeAll(newQueries);
             for (QueryDescriptor q : removedQueries) {
-                controller.fireQueryEvent(QueryEvent.ofRemove(src, q));
+                session.fireQueryEvent(QueryEvent.ofRemove(src, q));
             }
 
-            controller.displayDataMap(new DataMapDisplayEvent(
-                    application.getFrameController().getView(),
-                    (DataChannelDescriptor) controller
-                            .getProject()
+            session.displayDataMap(new DataMapDisplayEvent(
+                    application.getFrame(),
+                    (DataChannelDescriptor) session
+                            .project()
                             .getRootNode(),
                     map,
-                    controller.getSelectedDataNode()));
+                    session.getSelectedDataNode()));
         } else {
             // fix DataMap name, as there maybe a map with the same name already
-            ConfigurationNode root = controller.getProject().getRootNode();
+            ConfigurationNode root = session.project().getRootNode();
             map.setName(NameBuilder
                     .builder(map, root)
                     .baseName(map.getName())
@@ -312,7 +311,7 @@ public class ImportEOModelAction extends ModelerAbstractAction {
 
             // side effect of this operation is that if a node was created, this DataMap
             // will be linked with it...
-            CreateDataMapAction.onMapCreated(application.getFrameController().getView(), getProjectController(), map);
+            CreateDataMapAction.onMapCreated(application.getFrame(), getProjectSession(), map);
 
         }
     }
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/InferRelationshipsAction.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/InferRelationshipsAction.java
index aa9922a94..000c3d01c 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/InferRelationshipsAction.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/InferRelationshipsAction.java
@@ -20,7 +20,7 @@ package org.apache.cayenne.modeler.ui.action;
 
 import org.apache.cayenne.map.DataMap;
 import org.apache.cayenne.modeler.Application;
-import org.apache.cayenne.modeler.ui.autorelationship.InferRelationshipsController;
+import org.apache.cayenne.modeler.ui.autorelationship.InferRelationshipsDialog;
 
 import java.awt.event.ActionEvent;
 
@@ -32,9 +32,12 @@ public class InferRelationshipsAction extends ModelerAbstractAction {
 
     @Override
     public void performAction(ActionEvent e) {
-        DataMap dataMap = getProjectController().getSelectedDataMap();
+        DataMap dataMap = getProjectSession().getSelectedDataMap();
         if (dataMap != null) {
-            new InferRelationshipsController(application.getFrameController(), dataMap).startup();
+            new InferRelationshipsDialog(
+                    getProjectSession(),
+                    application.getFrame(),
+                    dataMap).open();
         }
     }
 }
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/LinkDataMapAction.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/LinkDataMapAction.java
index a6239fff1..744e12757 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/LinkDataMapAction.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/LinkDataMapAction.java
@@ -23,7 +23,7 @@ import org.apache.cayenne.configuration.DataNodeDescriptor;
 import org.apache.cayenne.modeler.event.model.DataNodeEvent;
 import org.apache.cayenne.map.DataMap;
 import org.apache.cayenne.modeler.Application;
-import org.apache.cayenne.modeler.ui.project.ProjectController;
+import org.apache.cayenne.modeler.project.ProjectSession;
 import org.apache.cayenne.modeler.undo.LinkDataMapUndoableEdit;
 
 import java.awt.event.ActionEvent;
@@ -46,8 +46,8 @@ public class LinkDataMapAction extends ModelerAbstractAction {
             return;
         }
 
-        ProjectController mediator = getProjectController();
-        DataChannelDescriptor dataChannelDescriptor = (DataChannelDescriptor) mediator.getProject().getRootNode();
+        ProjectSession session = getProjectSession();
+        DataChannelDescriptor dataChannelDescriptor = (DataChannelDescriptor) session.project().getRootNode();
         Collection<DataNodeDescriptor> unlinkedNodes = new ArrayList<>();
 
         // unlink map from any nodes
@@ -56,7 +56,7 @@ public class LinkDataMapAction extends ModelerAbstractAction {
         for (DataNodeDescriptor nextNode : dataChannelDescriptor.getNodeDescriptors()) {
             if (nextNode.getDataMapNames().contains(map.getName())) {
                 nextNode.getDataMapNames().remove(map.getName());
-                mediator.fireDataNodeEvent(DataNodeEvent.ofChange(this, nextNode));
+                session.fireDataNodeEvent(DataNodeEvent.ofChange(this, nextNode));
                 unlinkedNodes.add(nextNode);
             }
         }
@@ -66,11 +66,11 @@ public class LinkDataMapAction extends ModelerAbstractAction {
             node.getDataMapNames().add(map.getName());
 
             // announce DataNode change
-            mediator.fireDataNodeEvent(DataNodeEvent.ofChange(this, node));
+            session.fireDataNodeEvent(DataNodeEvent.ofChange(this, node));
         }
 
         application.getUndoManager().addEdit(
-                new LinkDataMapUndoableEdit(mediator, map, node, unlinkedNodes));
+                new LinkDataMapUndoableEdit(session, map, node, unlinkedNodes));
     }
 
     @Override
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/LinkDataMapsAction.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/LinkDataMapsAction.java
index 519d3949c..13ea83394 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/LinkDataMapsAction.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/LinkDataMapsAction.java
@@ -23,7 +23,7 @@ import org.apache.cayenne.configuration.DataNodeDescriptor;
 import org.apache.cayenne.modeler.event.model.DataNodeEvent;
 import org.apache.cayenne.map.DataMap;
 import org.apache.cayenne.modeler.Application;
-import org.apache.cayenne.modeler.ui.project.ProjectController;
+import org.apache.cayenne.modeler.project.ProjectSession;
 import org.apache.cayenne.modeler.undo.LinkDataMapsUndoableEdit;
 
 import java.awt.event.ActionEvent;
@@ -43,11 +43,11 @@ public class LinkDataMapsAction extends ModelerAbstractAction {
 
     @Override
     public void performAction(ActionEvent e) {
-        ProjectController mediator = getProjectController();
-        DataChannelDescriptor dataChannelDescriptor = (DataChannelDescriptor) mediator.getProject().getRootNode();
+        ProjectSession session = getProjectSession();
+        DataChannelDescriptor dataChannelDescriptor = (DataChannelDescriptor) session.project().getRootNode();
 
         Collection<String> linkedDataMaps = new ArrayList<>();
-        DataNodeDescriptor dataNodeDescriptor = mediator.getSelectedDataNode();
+        DataNodeDescriptor dataNodeDescriptor = session.getSelectedDataNode();
         for (DataNodeDescriptor dataNodeDesc : dataChannelDescriptor.getNodeDescriptors()) {
             linkedDataMaps.addAll(dataNodeDesc.getDataMapNames());
         }
@@ -55,12 +55,12 @@ public class LinkDataMapsAction extends ModelerAbstractAction {
         for (DataMap dataMap : dataChannelDescriptor.getDataMaps()) {
             if (!linkedDataMaps.contains(dataMap.getName())) {
                 dataNodeDescriptor.getDataMapNames().add(dataMap.getName());
-                mediator.fireDataNodeEvent(DataNodeEvent.ofChange(this, dataNodeDescriptor));
+                session.fireDataNodeEvent(DataNodeEvent.ofChange(this, dataNodeDescriptor));
             }
         }
 
         application.getUndoManager().addEdit(
-                new LinkDataMapsUndoableEdit(mediator, dataNodeDescriptor, linkedDataMaps));
+                new LinkDataMapsUndoableEdit(session, dataNodeDescriptor, linkedDataMaps));
     }
 
 }
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/MigrateAction.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/MigrateAction.java
index 3ca518615..c0e8f3c78 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/MigrateAction.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/MigrateAction.java
@@ -23,9 +23,9 @@ import org.apache.cayenne.dbsync.merge.factory.MergerTokenFactoryProvider;
 import org.apache.cayenne.dbsync.reverse.dbload.DbLoader;
 import org.apache.cayenne.map.DataMap;
 import org.apache.cayenne.modeler.Application;
-import org.apache.cayenne.modeler.ui.datasource.DataSourceController;
+import org.apache.cayenne.modeler.ui.datasource.DataSourceDialog;
 import org.apache.cayenne.modeler.ui.dbactionoptions.DbActionOptionsDialog;
-import org.apache.cayenne.modeler.ui.dbmerge.MergerOptionsController;
+import org.apache.cayenne.modeler.ui.dbmerge.MergerOptionsDialog;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -55,12 +55,12 @@ public class MigrateAction extends DBConnectionAwareAction {
 
     public void performAction(ActionEvent e) {
 
-        DataSourceController connectWizard = getDataSourceController("Migrate DB Schema: Connect to Database");
+        DataSourceDialog connectWizard = getDataSourceController("Migrate DB Schema: Connect to Database");
         if(connectWizard == null) {
             return;
         }
 
-        DataMap map = getProjectController().getSelectedDataMap();
+        DataMap map = getProjectSession().getSelectedDataMap();
         if (map == null) {
             throw new IllegalStateException("No current DataMap selected.");
         }
@@ -77,8 +77,9 @@ public class MigrateAction extends DBConnectionAwareAction {
         MergerTokenFactoryProvider mergerTokenFactoryProvider = application.getMergerTokenFactoryProvider();
 
         // ... show dialog...
-        new MergerOptionsController(
-                getProjectController(),
+        new MergerOptionsDialog(
+                getProjectSession(),
+                application.getFrame(),
                 "Migrate DB Schema: Options",
                 connectWizard.getConnector(),
                 map, selectedCatalog, selectedSchema, mergerTokenFactoryProvider).startupAction();
@@ -88,13 +89,14 @@ public class MigrateAction extends DBConnectionAwareAction {
                                                  String currentCatalog, String currentSchema, int command) {
         dialogShown = true;
         if (command == DbActionOptionsDialog.SELECT) {
-            return new DbActionOptionsDialog(application.getFrameController().getView(), "Migrate DB Schema: Select Catalog and Schema",
+            return new DbActionOptionsDialog(application, application.getFrame(),
+                    "Migrate DB Schema: Select Catalog and Schema",
                     catalogs, schemas, currentCatalog, currentSchema);
         }
         return null;
     }
 
-    protected DbActionOptionsDialog loaderOptionDialog(DataSourceController connectWizard) {
+    protected DbActionOptionsDialog loaderOptionDialog(DataSourceDialog connectWizard) {
 
         // use this catalog as the default...
         List<String> catalogs;
@@ -116,7 +118,7 @@ public class MigrateAction extends DBConnectionAwareAction {
             }
         } catch (Exception ex) {
             JOptionPane.showMessageDialog(
-                    application.getFrameController().getView(),
+                    application.getFrame(),
                     ex.getMessage(),
                     "Error loading schemas dialog",
                     JOptionPane.ERROR_MESSAGE);
@@ -141,7 +143,7 @@ public class MigrateAction extends DBConnectionAwareAction {
     }
 
     @SuppressWarnings("unchecked")
-    private List<String> getCatalogs(DataSourceController connectWizard, Connection connection) throws Exception {
+    private List<String> getCatalogs(DataSourceDialog connectWizard, Connection connection) throws Exception {
         if(!connectWizard.getAdapter().supportsCatalogsOnReverseEngineering()) {
             return (List<String>) Collections.EMPTY_LIST;
         }
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/ModelerAbstractAction.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/ModelerAbstractAction.java
index 3d854446a..9a5e57c15 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/ModelerAbstractAction.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/ModelerAbstractAction.java
@@ -24,7 +24,7 @@ import org.apache.cayenne.configuration.ConfigurationNode;
 import org.apache.cayenne.modeler.Application;
 import org.apache.cayenne.modeler.toolkit.icon.IconFactory;
 import org.apache.cayenne.modeler.ui.errors.ErrorsController;
-import org.apache.cayenne.modeler.ui.project.ProjectController;
+import org.apache.cayenne.modeler.project.ProjectSession;
 import org.apache.cayenne.project.Project;
 import org.apache.cayenne.util.Util;
 
@@ -72,7 +72,7 @@ public abstract class ModelerAbstractAction extends AbstractAction {
     }
 
     protected Project getCurrentProject() {
-        return application.getFrameController().getProjectController().getProject();
+        return application.getFrame().getProjectSession().project();
     }
 
     /**
@@ -135,10 +135,10 @@ public abstract class ModelerAbstractAction extends AbstractAction {
     }
 
     /**
-     * Returns current project controller.
+     * Returns the current {@link ProjectSession}.
      */
-    public ProjectController getProjectController() {
-        return application.getFrameController().getProjectController();
+    public ProjectSession getProjectSession() {
+        return application.getFrame().getProjectSession();
     }
 
     /**
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/NavigateAction.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/NavigateAction.java
index 5901fdbca..808fcf9f5 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/NavigateAction.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/NavigateAction.java
@@ -20,7 +20,7 @@
 package org.apache.cayenne.modeler.ui.action;
 
 import org.apache.cayenne.modeler.Application;
-import org.apache.cayenne.modeler.ui.project.ProjectController;
+import org.apache.cayenne.modeler.project.ProjectSession;
 
 import java.awt.event.ActionEvent;
 
@@ -30,10 +30,10 @@ public abstract class NavigateAction extends ModelerAbstractAction {
         super(name, application);
     }
 
-    protected abstract void navigate(ProjectController controller);
+    protected abstract void navigate(ProjectSession session);
 
     @Override
     public void performAction(ActionEvent e) {
-        navigate(getProjectController());
+        navigate(getProjectSession());
     }
 }
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/NavigateBackwardAction.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/NavigateBackwardAction.java
index db8d855b7..c74ad964c 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/NavigateBackwardAction.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/NavigateBackwardAction.java
@@ -20,7 +20,7 @@
 package org.apache.cayenne.modeler.ui.action;
 
 import org.apache.cayenne.modeler.Application;
-import org.apache.cayenne.modeler.ui.project.ProjectController;
+import org.apache.cayenne.modeler.project.ProjectSession;
 
 public class NavigateBackwardAction extends NavigateAction {
 
@@ -34,7 +34,7 @@ public class NavigateBackwardAction extends NavigateAction {
     }
 
     @Override
-    protected void navigate(ProjectController controller) {
-        controller.rewindBackwards();
+    protected void navigate(ProjectSession session) {
+        session.rewindBackwards();
     }
 }
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/NavigateForwardAction.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/NavigateForwardAction.java
index 77d4e30f7..72e386f2b 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/NavigateForwardAction.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/NavigateForwardAction.java
@@ -20,7 +20,7 @@
 package org.apache.cayenne.modeler.ui.action;
 
 import org.apache.cayenne.modeler.Application;
-import org.apache.cayenne.modeler.ui.project.ProjectController;
+import org.apache.cayenne.modeler.project.ProjectSession;
 
 public class NavigateForwardAction extends NavigateAction {
 
@@ -34,7 +34,7 @@ public class NavigateForwardAction extends NavigateAction {
     }
 
     @Override
-    protected void navigate(ProjectController controller) {
-        controller.rewindForward();
+    protected void navigate(ProjectSession session) {
+        session.rewindForward();
     }
 }
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/NewProjectAction.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/NewProjectAction.java
index 091f2e1ca..b9b448ace 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/NewProjectAction.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/NewProjectAction.java
@@ -23,7 +23,7 @@ import org.apache.cayenne.configuration.ConfigurationTree;
 import org.apache.cayenne.configuration.DataChannelDescriptor;
 import org.apache.cayenne.dbsync.naming.NameBuilder;
 import org.apache.cayenne.modeler.Application;
-import org.apache.cayenne.modeler.ui.ModelerController;
+import org.apache.cayenne.modeler.ui.MainFrame;
 import org.apache.cayenne.modeler.event.display.DomainDisplayEvent;
 import org.apache.cayenne.project.Project;
 
@@ -51,7 +51,7 @@ public class NewProjectAction extends ProjectAction {
     @Override
     public void performAction(ActionEvent e) {
 
-        ModelerController controller = application.getFrameController();
+        MainFrame controller = application.getFrame();
         // Save and close (if needed) currently open project.
         if (getCurrentProject() != null && !closeProject(true)) {
             return;
@@ -69,6 +69,6 @@ public class NewProjectAction extends ProjectAction {
         controller.onProjectOpened(project);
 
         // select default domain
-        getProjectController().displayDomain(new DomainDisplayEvent(this, dataChannelDescriptor));
+        getProjectSession().displayDomain(new DomainDisplayEvent(this, dataChannelDescriptor));
     }
 }
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/ObjEntityCounterpartAction.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/ObjEntityCounterpartAction.java
index a7b86e0fe..726bb9c09 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/ObjEntityCounterpartAction.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/ObjEntityCounterpartAction.java
@@ -39,7 +39,7 @@ public class ObjEntityCounterpartAction extends BaseViewEntityAction {
 
     @Override
     protected DbEntity getEntity() {
-        ObjEntity objEntity = getProjectController().getSelectedObjEntity();
+        ObjEntity objEntity = getProjectSession().getSelectedObjEntity();
         if (objEntity == null) {
             return null;
         }
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/ObjEntitySyncAction.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/ObjEntitySyncAction.java
index 46e30bb10..fcbc3315e 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/ObjEntitySyncAction.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/ObjEntitySyncAction.java
@@ -23,10 +23,9 @@ import org.apache.cayenne.configuration.DataChannelDescriptor;
 import org.apache.cayenne.dbsync.merge.context.EntityMergeSupport;
 import org.apache.cayenne.map.ObjEntity;
 import org.apache.cayenne.modeler.event.model.ObjEntityEvent;
-import org.apache.cayenne.modeler.event.model.ModelEvent;
 import org.apache.cayenne.modeler.Application;
-import org.apache.cayenne.modeler.ui.project.ProjectController;
-import org.apache.cayenne.modeler.ui.entitysync.EntitySyncController;
+import org.apache.cayenne.modeler.project.ProjectSession;
+import org.apache.cayenne.modeler.ui.entitysync.EntitySyncDialog;
 import org.apache.cayenne.modeler.event.display.ObjEntityDisplayEvent;
 
 import javax.swing.*;
@@ -65,11 +64,14 @@ public class ObjEntitySyncAction extends ModelerAbstractAction {
     }
 
     protected void syncObjEntity() {
-        ProjectController mediator = getProjectController();
-        ObjEntity entity = mediator.getSelectedObjEntity();
+        ProjectSession session = getProjectSession();
+        ObjEntity entity = session.getSelectedObjEntity();
 
         if (entity != null && entity.getDbEntity() != null) {
-            EntityMergeSupport merger = new EntitySyncController(application.getFrameController(), entity).createMerger();
+            EntityMergeSupport merger = new EntitySyncDialog(
+                    application,
+                    application.getFrame(),
+                    entity).createMerger();
 
             if (merger == null) {
                 return;
@@ -78,11 +80,11 @@ public class ObjEntitySyncAction extends ModelerAbstractAction {
             merger.setNameGenerator(new DbEntitySyncAction.PreserveRelationshipNameGenerator());
 
             if (merger.synchronizeWithDbEntity(entity)) {
-                mediator
+                session
                         .fireObjEntityEvent(ObjEntityEvent.ofChange(this, entity));
-                mediator.displayObjEntity(new ObjEntityDisplayEvent(
+                session.displayObjEntity(new ObjEntityDisplayEvent(
                         this,
-                        (DataChannelDescriptor) mediator.getProject().getRootNode(),
+                        (DataChannelDescriptor) session.project().getRootNode(),
                         entity.getDataMap(),
                         entity));
             }
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/ObjEntityToSuperEntityAction.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/ObjEntityToSuperEntityAction.java
index dfee3e61e..d61ed381e 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/ObjEntityToSuperEntityAction.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/ObjEntityToSuperEntityAction.java
@@ -34,7 +34,7 @@ public class ObjEntityToSuperEntityAction extends BaseViewEntityAction {
 
     @Override
     protected ObjEntity getEntity() {
-        ObjEntity objEntity = getProjectController().getSelectedObjEntity();
+        ObjEntity objEntity = getProjectSession().getSelectedObjEntity();
         if (objEntity == null) {
             return null;
         }
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/OpenProjectAction.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/OpenProjectAction.java
index 307239114..b635e08d5 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/OpenProjectAction.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/OpenProjectAction.java
@@ -20,7 +20,7 @@
 package org.apache.cayenne.modeler.ui.action;
 
 import org.apache.cayenne.modeler.Application;
-import org.apache.cayenne.modeler.ui.ModelerController;
+import org.apache.cayenne.modeler.ui.MainFrame;
 import org.apache.cayenne.modeler.ui.errors.ErrorsController;
 import org.apache.cayenne.project.Project;
 import org.apache.cayenne.project.upgrade.UpgradeMetaData;
@@ -87,7 +87,7 @@ public class OpenProjectAction extends ProjectAction {
     public void performAction(ActionEvent e) {
 
         // Save and close (if needed) currently open project.
-        if (getProjectController() != null && !checkSaveOnClose()) {
+        if (getProjectSession() != null && !checkSaveOnClose()) {
             return;
         }
 
@@ -118,7 +118,7 @@ public class OpenProjectAction extends ProjectAction {
 
         if (f != null) {
             // by now if the project is unsaved, this has been a user choice...
-            if (getProjectController() != null && !closeProject(false)) {
+            if (getProjectSession() != null && !closeProject(false)) {
                 return;
             }
 
@@ -135,14 +135,14 @@ public class OpenProjectAction extends ProjectAction {
         try {
             if (!file.exists()) {
                 JOptionPane.showMessageDialog(
-                        application.getFrameController().getView(),
+                        application.getFrame(),
                         "Can't open project - file \"" + file.getPath() + "\" does not exist",
                         "Can't Open Project",
                         JOptionPane.ERROR_MESSAGE);
                 return;
             }
 
-            ModelerController controller = application.getFrameController();
+            MainFrame controller = application.getFrame();
             controller.addToLastProjListAction(file);
 
             URL url = file.toURI().toURL();
@@ -156,7 +156,7 @@ public class OpenProjectAction extends ProjectAction {
                     if (modelerVersion == null) {
                         modelerVersion = "";
                     }
-                    JOptionPane.showMessageDialog(application.getFrameController().getView(),
+                    JOptionPane.showMessageDialog(application.getFrame(),
                             "Open the project in the older Modeler " + modelerVersion
                                     + " to do an intermediate upgrade\nbefore you can upgrade to latest version.",
                             "Can't Upgrade Project", JOptionPane.ERROR_MESSAGE);
@@ -164,7 +164,7 @@ public class OpenProjectAction extends ProjectAction {
                     return;
 
                 case DOWNGRADE_NEEDED:
-                    JOptionPane.showMessageDialog(application.getFrameController().getView(),
+                    JOptionPane.showMessageDialog(application.getFrame(),
                             "Can't open project - it was created using a newer version of the Modeler",
                             "Can't Open Project", JOptionPane.ERROR_MESSAGE);
                     closeProject(false);
@@ -187,7 +187,7 @@ public class OpenProjectAction extends ProjectAction {
         }
     }
 
-    private Project openProjectResourse(Resource resource, ModelerController controller) {
+    private Project openProjectResourse(Resource resource, MainFrame controller) {
         Project project = application.getProjectLoader().loadProject(resource);
         controller.onProjectOpened(project);
         return project;
@@ -196,7 +196,7 @@ public class OpenProjectAction extends ProjectAction {
     private boolean processUpgrades() {
         // need an upgrade
         int returnCode = JOptionPane.showConfirmDialog(
-                application.getFrameController().getView(),
+                application.getFrame(),
                 "Project needs an upgrade to a newer version. Upgrade?",
                 "Upgrade Needed",
                 JOptionPane.YES_NO_OPTION);
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/PasteAction.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/PasteAction.java
index 9e4c990c7..28d4e22e6 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/PasteAction.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/PasteAction.java
@@ -38,8 +38,8 @@ import org.apache.cayenne.map.ProcedureParameter;
 import org.apache.cayenne.map.QueryDescriptor;
 import org.apache.cayenne.modeler.Application;
 import org.apache.cayenne.modeler.ui.errors.ErrorsController;
-import org.apache.cayenne.modeler.ui.project.ProjectController;
-import org.apache.cayenne.modeler.ui.project.querytype.QueryTypeController;
+import org.apache.cayenne.modeler.project.ProjectSession;
+import org.apache.cayenne.modeler.ui.project.querytype.QueryTypeDialog;
 import org.apache.cayenne.modeler.ui.project.editor.objentity.callbacks.ObjCallbackMethod;
 import org.apache.cayenne.modeler.event.model.CallbackMethodEvent;
 import org.apache.cayenne.modeler.undo.PasteCompoundUndoableEdit;
@@ -100,17 +100,17 @@ public class PasteAction extends ModelerAbstractAction implements FlavorListener
             Object content = Toolkit.getDefaultToolkit().getSystemClipboard().getData(
                     CMTransferable.CAYENNE_FLAVOR);
 
-            Object currentObject = getProjectController().getSelectedObject();
+            Object currentObject = getProjectSession().getSelectedObject();
 
             if (content instanceof DataMap) {
-                currentObject = getProjectController().getProject().getRootNode();
+                currentObject = getProjectSession().project().getRootNode();
             }
 
             if (content != null && currentObject != null) {
-                DataChannelDescriptor domain = (DataChannelDescriptor) getProjectController()
-                        .getProject()
+                DataChannelDescriptor domain = (DataChannelDescriptor) getProjectSession()
+                        .project()
                         .getRootNode();
-                DataMap map = getProjectController().getSelectedDataMap();
+                DataMap map = getProjectSession().getSelectedDataMap();
 
                 UndoableEdit undoableEdit;
                 if (content instanceof List) {
@@ -119,7 +119,7 @@ public class PasteAction extends ModelerAbstractAction implements FlavorListener
                     for (Object o : (List) content) {
                         paste(currentObject, o);
                         undoableEdit.addEdit(new PasteUndoableEdit(
-                                getProjectController(),
+                                getProjectSession(),
                                 domain,
                                 map,
                                 currentObject,
@@ -127,7 +127,7 @@ public class PasteAction extends ModelerAbstractAction implements FlavorListener
                     }
                 } else {
                     paste(currentObject, content);
-                    undoableEdit = new PasteUndoableEdit(getProjectController(), domain, map, currentObject, content);
+                    undoableEdit = new PasteUndoableEdit(getProjectSession(), domain, map, currentObject, content);
                 }
 
                 application.getUndoManager().addEdit(undoableEdit);
@@ -140,9 +140,9 @@ public class PasteAction extends ModelerAbstractAction implements FlavorListener
     }
 
     private void paste(Object where, Object content) {
-        paste(where, content, (DataChannelDescriptor) getProjectController()
-                .getProject()
-                .getRootNode(), getProjectController().getSelectedDataMap());
+        paste(where, content, (DataChannelDescriptor) getProjectSession()
+                .project()
+                .getRootNode(), getProjectSession().getSelectedDataMap());
     }
 
     /**
@@ -154,14 +154,14 @@ public class PasteAction extends ModelerAbstractAction implements FlavorListener
             DataChannelDescriptor dataChannelDescriptor,
             DataMap map) {
 
-        ProjectController controller = getProjectController();
+        ProjectSession session = getProjectSession();
 
         /**
          * Add a little intelligence - if a tree leaf is selected, we can paste to a
          * parent datamap
          */
         if (isTreeLeaf(where) && isTreeLeaf(content)) {
-            where = controller.getSelectedDataMap();
+            where = session.getSelectedDataMap();
         }
 
         if ((where instanceof DataChannelDescriptor || where instanceof DataNodeDescriptor)
@@ -268,7 +268,7 @@ public class PasteAction extends ModelerAbstractAction implements FlavorListener
                 }
             }
 
-            CreateDataMapAction.onMapCreated(this, getProjectController(), dataMap);
+            CreateDataMapAction.onMapCreated(this, getProjectSession(), dataMap);
         } else if (where instanceof DataMap) {
             // paste DbEntity to DataMap
             final DataMap dataMap = ((DataMap) where);
@@ -285,7 +285,7 @@ public class PasteAction extends ModelerAbstractAction implements FlavorListener
                         .name());
 
                 dataMap.addDbEntity(dbEntity);
-                CreateDbEntityAction.onDbEntityCreated(this, controller, dbEntity);
+                CreateDbEntityAction.onDbEntityCreated(this, session, dbEntity);
             } else if (content instanceof ObjEntity) {
                 // paste ObjEntity to DataMap
                 ObjEntity objEntity = (ObjEntity) content;
@@ -297,7 +297,7 @@ public class PasteAction extends ModelerAbstractAction implements FlavorListener
                 dataMap.addObjEntity(objEntity);
                 CreateObjEntityAction.onObjEntityCreated(
                         this,
-                        controller,
+                        session,
                         dataMap,
                         objEntity);
             } else if (content instanceof Embeddable) {
@@ -312,7 +312,7 @@ public class PasteAction extends ModelerAbstractAction implements FlavorListener
                 dataMap.addEmbeddable(embeddable);
                 CreateEmbeddableAction.fireEmbeddableEvent(
                         this,
-                        controller,
+                        session,
                         dataMap,
                         embeddable);
             } else if (content instanceof QueryDescriptor) {
@@ -326,7 +326,7 @@ public class PasteAction extends ModelerAbstractAction implements FlavorListener
                 query.setDataMap(dataMap);
 
                 dataMap.addQueryDescriptor(query);
-                QueryTypeController.fireQueryEvent(this, controller, dataMap, query);
+                QueryTypeDialog.fireQueryEvent(this, session, dataMap, query);
             } else if (content instanceof Procedure) {
                 // paste Procedure to DataMap
                 Procedure procedure = (Procedure) content;
@@ -339,7 +339,7 @@ public class PasteAction extends ModelerAbstractAction implements FlavorListener
                 dataMap.addProcedure(procedure);
                 CreateProcedureAction.fireProcedureEvent(
                         this,
-                        controller,
+                        session,
                         dataMap,
                         procedure);
             }
@@ -355,7 +355,7 @@ public class PasteAction extends ModelerAbstractAction implements FlavorListener
                         .name());
 
                 dbEntity.addAttribute(attr);
-                CreateAttributeAction.fireDbAttributeEvent(this, controller, controller
+                CreateAttributeAction.fireDbAttributeEvent(this, session, session
                         .getSelectedDataMap(), dbEntity, attr);
             } else if (content instanceof DbRelationship) {
                 DbRelationship rel = (DbRelationship) content;
@@ -368,7 +368,7 @@ public class PasteAction extends ModelerAbstractAction implements FlavorListener
                 dbEntity.addRelationship(rel);
                 CreateRelationshipAction.fireDbRelationshipEvent(
                         this,
-                        controller,
+                        session,
                         dbEntity,
                         rel);
             }
@@ -384,7 +384,7 @@ public class PasteAction extends ModelerAbstractAction implements FlavorListener
                         .name());
 
                 objEntity.addAttribute(attr);
-                CreateAttributeAction.fireObjAttributeEvent(this, controller, controller
+                CreateAttributeAction.fireObjAttributeEvent(this, session, session
                         .getSelectedDataMap(), objEntity, attr);
             } else if (content instanceof ObjRelationship) {
                 ObjRelationship rel = (ObjRelationship) content;
@@ -397,7 +397,7 @@ public class PasteAction extends ModelerAbstractAction implements FlavorListener
                 objEntity.addRelationship(rel);
                 CreateRelationshipAction.fireObjRelationshipEvent(
                         this,
-                        controller,
+                        session,
                         objEntity,
                         rel);
             } else if (content instanceof ObjCallbackMethod) {
@@ -410,12 +410,12 @@ public class PasteAction extends ModelerAbstractAction implements FlavorListener
                         .name());
 
                 objEntity.getCallbackMap()
-                        .getCallbackDescriptor(controller.getSelectedCallbackType().getType())
+                        .getCallbackDescriptor(session.getSelectedCallbackType().getType())
                         .addCallbackMethod(method.getName());
 
                 CallbackMethodEvent ce = CallbackMethodEvent.ofAdd(this, method.getName());
 
-                getProjectController().fireCallbackMethodEvent(ce);
+                getProjectSession().fireCallbackMethodEvent(ce);
             }
         } else if (where instanceof Embeddable) {
             final Embeddable embeddable = (Embeddable) where;
@@ -431,7 +431,7 @@ public class PasteAction extends ModelerAbstractAction implements FlavorListener
                 embeddable.addAttribute(attr);
                 CreateAttributeAction.fireEmbeddableAttributeEvent(
                         this,
-                        controller,
+                        session,
                         embeddable,
                         attr);
             }
@@ -452,7 +452,7 @@ public class PasteAction extends ModelerAbstractAction implements FlavorListener
                 procedure.addCallParameter(param);
                 CreateProcedureParameterAction.fireProcedureParameterEvent(
                         this,
-                        controller,
+                        session,
                         procedure,
                         param);
             }
@@ -498,7 +498,7 @@ public class PasteAction extends ModelerAbstractAction implements FlavorListener
                 content = ((List) content).get(0);
             }
 
-            Object currentObject = getProjectController().getSelectedObject();
+            Object currentObject = getProjectSession().getSelectedObject();
 
             if (currentObject == null) {
                 return false;
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/ProjectAction.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/ProjectAction.java
index 5f5a9700d..97645583d 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/ProjectAction.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/ProjectAction.java
@@ -20,7 +20,7 @@
 package org.apache.cayenne.modeler.ui.action;
 
 import org.apache.cayenne.modeler.Application;
-import org.apache.cayenne.modeler.ui.project.ProjectController;
+import org.apache.cayenne.modeler.project.ProjectSession;
 import org.apache.cayenne.modeler.ui.unsavedchanges.UnsavedChangesDialog;
 
 import java.awt.event.ActionEvent;
@@ -57,7 +57,7 @@ public class ProjectAction extends ModelerAbstractAction {
      */
     public boolean closeProject(boolean checkUnsaved) {
         // check if there is a project...
-        if (getProjectController() == null || getProjectController().getProject() == null) {
+        if (getProjectSession() == null || getProjectSession().project() == null) {
             return true;
         }
 
@@ -66,7 +66,7 @@ public class ProjectAction extends ModelerAbstractAction {
         }
 
         application.getUndoManager().discardAllEdits();
-        application.getFrameController().onProjectClosed();
+        application.getFrame().onProjectClosed();
 
         return true;
     }
@@ -75,9 +75,9 @@ public class ProjectAction extends ModelerAbstractAction {
      * Returns false if cancel closing the window, true otherwise.
      */
     public boolean checkSaveOnClose() {
-        ProjectController projectController = getProjectController();
-        if (projectController != null && projectController.isDirty()) {
-            UnsavedChangesDialog dialog = new UnsavedChangesDialog(application.getFrameController().getView());
+        ProjectSession session = getProjectSession();
+        if (session != null && session.isDirty()) {
+            UnsavedChangesDialog dialog = new UnsavedChangesDialog(application.getFrame());
             dialog.show();
 
             if (dialog.shouldCancel()) {
@@ -93,7 +93,7 @@ public class ProjectAction extends ModelerAbstractAction {
                         .getActionManager()
                         .getAction(SaveAction.class)
                         .actionPerformed(e);
-                if (projectController.isDirty()) {
+                if (session.isDirty()) {
                     // save was canceled... do not close
                     return false;
                 }
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/ProjectOpener.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/ProjectOpener.java
index f7d030f5d..d652d5e51 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/ProjectOpener.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/ProjectOpener.java
@@ -79,7 +79,7 @@ class ProjectOpener extends JFileChooser {
         File selectedDir;
 
         while (true) {
-            int status = showDialog(application.getFrameController().getView(), "Select");
+            int status = showDialog(application.getFrame(), "Select");
             selectedDir = getSelectedFile();
             if (status != JFileChooser.APPROVE_OPTION || selectedDir == null) {
                 LOGGER.info("Save canceled.");
@@ -95,7 +95,7 @@ class ProjectOpener extends JFileChooser {
             // check for overwrite
             File projectFile = new File(selectedDir, location);
             if (projectFile.exists()) {
-                OverwriteDialog dialog = new OverwriteDialog(projectFile, application.getFrameController().getView());
+                OverwriteDialog dialog = new OverwriteDialog(projectFile, application.getFrame());
                 dialog.show();
 
                 if (dialog.shouldOverwrite()) {
@@ -129,7 +129,7 @@ class ProjectOpener extends JFileChooser {
         // default to App projects
         setFileFilter(FileFilters.getApplicationFilter());
 
-        int status = showOpenDialog(application.getFrameController().getView());
+        int status = showOpenDialog(application.getFrame());
         if (status != JFileChooser.APPROVE_OPTION) {
             return null;
         }
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/RemoveAction.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/RemoveAction.java
index 1b92a8fbc..2a7c580a8 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/RemoveAction.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/RemoveAction.java
@@ -55,7 +55,7 @@ import org.apache.cayenne.modeler.event.model.ProcedureParameterEvent;
 import org.apache.cayenne.modeler.event.model.QueryEvent;
 import org.apache.cayenne.modeler.project.DataMapOps;
 import org.apache.cayenne.modeler.ui.confirmremove.ConfirmRemoveDialog;
-import org.apache.cayenne.modeler.ui.project.ProjectController;
+import org.apache.cayenne.modeler.project.ProjectSession;
 import org.apache.cayenne.modeler.ui.project.editor.objentity.callbacks.CallbackType;
 import org.apache.cayenne.modeler.ui.project.editor.objentity.callbacks.ObjCallbackMethod;
 import org.apache.cayenne.modeler.undo.RemoveAttributeUndoableEdit;
@@ -119,72 +119,72 @@ public class RemoveAction extends ModelerAbstractAction {
      */
     public void performAction(ActionEvent e, boolean allowAsking) {
 
-        ProjectController controller = getProjectController();
+        ProjectSession session = getProjectSession();
         ConfirmRemoveDialog dialog = getConfirmDeleteDialog(allowAsking);
         
-        if (controller.getSelectedObjEntity() != null) {
-            if (dialog.shouldDelete("ObjEntity", controller.getSelectedObjEntity().getName())) {
+        if (session.getSelectedObjEntity() != null) {
+            if (dialog.shouldDelete("ObjEntity", session.getSelectedObjEntity().getName())) {
 
                 application.getUndoManager()
-                        .addEdit(new RemoveUndoableEdit(controller, controller.getSelectedDataMap(), controller.getSelectedObjEntity()));
-                removeObjEntity(controller.getSelectedDataMap(), controller.getSelectedObjEntity());
+                        .addEdit(new RemoveUndoableEdit(session, session.getSelectedDataMap(), session.getSelectedObjEntity()));
+                removeObjEntity(session.getSelectedDataMap(), session.getSelectedObjEntity());
             }
-        } else if (controller.getSelectedDbEntity() != null) {
-            if (dialog.shouldDelete("DbEntity", controller.getSelectedDbEntity().getName())) {
+        } else if (session.getSelectedDbEntity() != null) {
+            if (dialog.shouldDelete("DbEntity", session.getSelectedDbEntity().getName())) {
 
                 application.getUndoManager()
-                        .addEdit(new RemoveUndoableEdit(controller, controller.getSelectedDataMap(), controller.getSelectedDbEntity()));
-                removeDbEntity(controller.getSelectedDataMap(), controller.getSelectedDbEntity());
+                        .addEdit(new RemoveUndoableEdit(session, session.getSelectedDataMap(), session.getSelectedDbEntity()));
+                removeDbEntity(session.getSelectedDataMap(), session.getSelectedDbEntity());
             }
-        } else if (controller.getSelectedQuery() != null) {
-            if (dialog.shouldDelete("query", controller.getSelectedQuery().getName())) {
+        } else if (session.getSelectedQuery() != null) {
+            if (dialog.shouldDelete("query", session.getSelectedQuery().getName())) {
 
                 application.getUndoManager()
-                        .addEdit(new RemoveUndoableEdit(controller, controller.getSelectedDataMap(), controller.getSelectedQuery()));
-                removeQuery(controller.getSelectedDataMap(), controller.getSelectedQuery());
+                        .addEdit(new RemoveUndoableEdit(session, session.getSelectedDataMap(), session.getSelectedQuery()));
+                removeQuery(session.getSelectedDataMap(), session.getSelectedQuery());
             }
-        } else if (controller.getSelectedProcedure() != null) {
-            if (dialog.shouldDelete("procedure", controller.getSelectedProcedure().getName())) {
+        } else if (session.getSelectedProcedure() != null) {
+            if (dialog.shouldDelete("procedure", session.getSelectedProcedure().getName())) {
 
                 application.getUndoManager()
-                        .addEdit(new RemoveUndoableEdit(controller, controller.getSelectedDataMap(), controller.getSelectedProcedure()));
-                removeProcedure(controller.getSelectedDataMap(), controller.getSelectedProcedure());
+                        .addEdit(new RemoveUndoableEdit(session, session.getSelectedDataMap(), session.getSelectedProcedure()));
+                removeProcedure(session.getSelectedDataMap(), session.getSelectedProcedure());
             }
-        } else if (controller.getSelectedEmbeddable() != null) {
-            if (dialog.shouldDelete("embeddable", controller.getSelectedEmbeddable().getClassName())) {
+        } else if (session.getSelectedEmbeddable() != null) {
+            if (dialog.shouldDelete("embeddable", session.getSelectedEmbeddable().getClassName())) {
 
                 application.getUndoManager()
-                        .addEdit(new RemoveUndoableEdit(controller, controller.getSelectedDataMap(), controller.getSelectedEmbeddable()));
-                removeEmbeddable(controller.getSelectedDataMap(), controller.getSelectedEmbeddable());
+                        .addEdit(new RemoveUndoableEdit(session, session.getSelectedDataMap(), session.getSelectedEmbeddable()));
+                removeEmbeddable(session.getSelectedDataMap(), session.getSelectedEmbeddable());
             }
-        } else if (controller.getSelectedDataMap() != null) {
-            if (dialog.shouldDelete("data map", controller.getSelectedDataMap().getName())) {
+        } else if (session.getSelectedDataMap() != null) {
+            if (dialog.shouldDelete("data map", session.getSelectedDataMap().getName())) {
 
                 // In context of Data node just remove from Data Node
-                if (controller.getSelectedDataNode() != null) {
+                if (session.getSelectedDataNode() != null) {
                     application.getUndoManager()
-                            .addEdit(new RemoveUndoableEdit(controller, controller.getSelectedDataNode(),
-                                    controller.getSelectedDataMap()));
-                    removeDataMapFromDataNode(controller.getSelectedDataNode(), controller.getSelectedDataMap());
+                            .addEdit(new RemoveUndoableEdit(session, session.getSelectedDataNode(),
+                                    session.getSelectedDataMap()));
+                    removeDataMapFromDataNode(session.getSelectedDataNode(), session.getSelectedDataMap());
                 } else {
                     // Not under Data Node, remove completely
                     application.getUndoManager()
-                            .addEdit(new RemoveUndoableEdit(controller, controller.getSelectedDataMap()));
-                    removeDataMap(controller.getSelectedDataMap());
+                            .addEdit(new RemoveUndoableEdit(session, session.getSelectedDataMap()));
+                    removeDataMap(session.getSelectedDataMap());
                 }
             }
-        } else if (controller.getSelectedDataNode() != null) {
-            if (dialog.shouldDelete("data node", controller.getSelectedDataNode().getName())) {
+        } else if (session.getSelectedDataNode() != null) {
+            if (dialog.shouldDelete("data node", session.getSelectedDataNode().getName())) {
 
                 application.getUndoManager()
-                        .addEdit(new RemoveUndoableEdit(controller, controller.getSelectedDataNode()));
-                removeDataNode(controller.getSelectedDataNode());
+                        .addEdit(new RemoveUndoableEdit(session, session.getSelectedDataNode()));
+                removeDataNode(session.getSelectedDataNode());
             }
-        } else if (controller.getSelectedPaths() != null) { // multiple deletion
+        } else if (session.getSelectedPaths() != null) { // multiple deletion
             if (dialog.shouldDelete("selected objects")) {
 
-                ConfigurationNode[] paths = controller.getSelectedPaths();
-                ConfigurationNode parentPath = controller.getSelectedParentPath();
+                ConfigurationNode[] paths = session.getSelectedPaths();
+                ConfigurationNode parentPath = session.getSelectedParentPath();
 
                 CompoundEdit compoundEdit = new RemoveCompoundUndoableEdit();
                 for (ConfigurationNode path : paths) {
@@ -194,130 +194,130 @@ public class RemoveAction extends ModelerAbstractAction {
 
                 application.getUndoManager().addEdit(compoundEdit);
             }
-        } else if(controller.getSelectedCallbackMethods().length > 0) {
-            removeMethods(controller, dialog, getProjectController().getSelectedCallbackMethods());
-        } else if(controller.getSelectedObjRelationships().length > 0) {
-      		removeObjRelationships(controller, dialog, getProjectController().getSelectedObjRelationships());
-        } else if(controller.getSelectedDbRelationships().length > 0) {
-      		removeDBRelationships(controller, dialog, getProjectController().getSelectedDbRelationships());
-        } else if(controller.getSelectedObjAttributes().length > 0) {
-      		removeObjAttributes(controller, dialog, getProjectController().getSelectedObjAttributes());
-        } else if(controller.getSelectedEmbeddableAttributes().length > 0) {
-      		removeEmbAttributes(controller, dialog, getProjectController().getSelectedEmbeddableAttributes());
-        } else if(controller.getSelectedDbAttributes().length > 0) {
-        	removeDbAttributes(controller, dialog, getProjectController().getSelectedDbAttributes());
-        } else if(controller.getSelectedProcedureParameters().length > 0) {
-        	removeProcedureParameters(controller.getSelectedProcedure(), controller.getSelectedProcedureParameters());
+        } else if(session.getSelectedCallbackMethods().length > 0) {
+            removeMethods(session, dialog, getProjectSession().getSelectedCallbackMethods());
+        } else if(session.getSelectedObjRelationships().length > 0) {
+      		removeObjRelationships(session, dialog, getProjectSession().getSelectedObjRelationships());
+        } else if(session.getSelectedDbRelationships().length > 0) {
+      		removeDBRelationships(session, dialog, getProjectSession().getSelectedDbRelationships());
+        } else if(session.getSelectedObjAttributes().length > 0) {
+      		removeObjAttributes(session, dialog, getProjectSession().getSelectedObjAttributes());
+        } else if(session.getSelectedEmbeddableAttributes().length > 0) {
+      		removeEmbAttributes(session, dialog, getProjectSession().getSelectedEmbeddableAttributes());
+        } else if(session.getSelectedDbAttributes().length > 0) {
+        	removeDbAttributes(session, dialog, getProjectSession().getSelectedDbAttributes());
+        } else if(session.getSelectedProcedureParameters().length > 0) {
+        	removeProcedureParameters(session.getSelectedProcedure(), session.getSelectedProcedureParameters());
         }
 
     }
 
     private void removeProcedureParameters(Procedure procedure, ProcedureParameter[] parameters) {
-        ProjectController mediator = getProjectController();
+        ProjectSession session = getProjectSession();
         for (ProcedureParameter parameter : parameters) {
             procedure.removeCallParameter(parameter.getName());
-            ProcedureParameterEvent e = ProcedureParameterEvent.ofRemove(application.getFrameController().getView(), parameter);
-            mediator.fireProcedureParameterEvent(e);
+            ProcedureParameterEvent e = ProcedureParameterEvent.ofRemove(application.getFrame(), parameter);
+            session.fireProcedureParameterEvent(e);
         }
     }
     
-    private void removeEmbAttributes(ProjectController controller, ConfirmRemoveDialog dialog, EmbeddableAttribute[] embAttrs) {
+    private void removeEmbAttributes(ProjectSession session, ConfirmRemoveDialog dialog, EmbeddableAttribute[] embAttrs) {
     	if (embAttrs != null && embAttrs.length > 0) {
         	if ((embAttrs.length == 1 && dialog.shouldDelete("DbAttribute", embAttrs[0].getName()))
                     || (embAttrs.length > 1 && dialog.shouldDelete("selected DbAttributes"))) {
 
-        		Embeddable embeddable = controller.getSelectedEmbeddable();
+        		Embeddable embeddable = session.getSelectedEmbeddable();
 
                 application.getUndoManager()
-                        .addEdit(new RemoveAttributeUndoableEdit(controller,embeddable, embAttrs));
+                        .addEdit(new RemoveAttributeUndoableEdit(session,embeddable, embAttrs));
 
                 for (EmbeddableAttribute attrib : embAttrs) {
                     embeddable.removeAttribute(attrib.getName());
-                    EmbeddableAttributeEvent e = EmbeddableAttributeEvent.ofRemove(application.getFrameController().getView(),
+                    EmbeddableAttributeEvent e = EmbeddableAttributeEvent.ofRemove(application.getFrame(),
                             attrib, embeddable);
-                    controller.fireEmbeddableAttributeEvent(e);
+                    session.fireEmbeddableAttributeEvent(e);
                 }
 
-                DataMapOps.removeBrokenObjToDbMappings(controller.getSelectedDataMap());
+                DataMapOps.removeBrokenObjToDbMappings(session.getSelectedDataMap());
         	}
     	}
 	}
 
-	private void removeObjAttributes(ProjectController controller, ConfirmRemoveDialog dialog, ObjAttribute[] objAttrs) {
+	private void removeObjAttributes(ProjectSession session, ConfirmRemoveDialog dialog, ObjAttribute[] objAttrs) {
     	if (objAttrs != null && objAttrs.length > 0) {
         	if ((objAttrs.length == 1 && dialog.shouldDelete("DbAttribute", objAttrs[0].getName()))
                     || (objAttrs.length > 1 && dialog.shouldDelete("selected DbAttributes"))) {
 
-        		ObjEntity entity = controller.getSelectedObjEntity();
+        		ObjEntity entity = session.getSelectedObjEntity();
 
-                application.getUndoManager().addEdit(new RemoveAttributeUndoableEdit(controller,entity, objAttrs));
+                application.getUndoManager().addEdit(new RemoveAttributeUndoableEdit(session,entity, objAttrs));
 
                 for (ObjAttribute attrib : objAttrs) {
                     entity.removeAttribute(attrib.getName());
-                    ObjAttributeEvent e = ObjAttributeEvent.ofRemove(application.getFrameController().getView(), attrib, entity);
-                    controller.fireObjAttributeEvent(e);
+                    ObjAttributeEvent e = ObjAttributeEvent.ofRemove(application.getFrame(), attrib, entity);
+                    session.fireObjAttributeEvent(e);
                 }
 
-                DataMapOps.removeBrokenObjToDbMappings(controller.getSelectedDataMap());
+                DataMapOps.removeBrokenObjToDbMappings(session.getSelectedDataMap());
         	}
     	}
 	}
 
-	private void removeDbAttributes(ProjectController controller, ConfirmRemoveDialog dialog, DbAttribute[] dbAttrs) {
+	private void removeDbAttributes(ProjectSession session, ConfirmRemoveDialog dialog, DbAttribute[] dbAttrs) {
     	if (dbAttrs != null && dbAttrs.length > 0) {
         	if ((dbAttrs.length == 1 && dialog.shouldDelete("DbAttribute", dbAttrs[0].getName()))
                     || (dbAttrs.length > 1 && dialog.shouldDelete("selected DbAttributes"))) {
 
-        		DbEntity entity = controller.getSelectedDbEntity();
+        		DbEntity entity = session.getSelectedDbEntity();
 
                 application.getUndoManager()
-                        .addEdit(new RemoveAttributeUndoableEdit(controller,entity, dbAttrs));
+                        .addEdit(new RemoveAttributeUndoableEdit(session,entity, dbAttrs));
 
                 for (DbAttribute attrib : dbAttrs) {
                     entity.removeAttribute(attrib.getName());
-                DbAttributeEvent e = DbAttributeEvent.ofRemove(application.getFrameController().getView(), attrib, entity);
-                    controller.fireDbAttributeEvent(e);
+                DbAttributeEvent e = DbAttributeEvent.ofRemove(application.getFrame(), attrib, entity);
+                    session.fireDbAttributeEvent(e);
                 }
 
-                DataMapOps.removeBrokenObjToDbMappings(controller.getSelectedDataMap());
+                DataMapOps.removeBrokenObjToDbMappings(session.getSelectedDataMap());
         	}
     	}
     }
     
-    private void removeDBRelationships(ProjectController controller, ConfirmRemoveDialog dialog, DbRelationship[] dbRels) {
+    private void removeDBRelationships(ProjectSession session, ConfirmRemoveDialog dialog, DbRelationship[] dbRels) {
 		if (dbRels != null && dbRels.length > 0) {
 			if ((dbRels.length == 1 && dialog.shouldDelete("DbRelationship", dbRels[0].getName()))
 					|| (dbRels.length > 1 && dialog.shouldDelete("selected DbRelationships"))) {
-				DbEntity entity = controller.getSelectedDbEntity();
+				DbEntity entity = session.getSelectedDbEntity();
 				
 				for (DbRelationship rel : dbRels) {
 					entity.removeRelationship(rel.getName());
-					DbRelationshipEvent e = DbRelationshipEvent.ofRemove(application.getFrameController().getView(), rel, entity);
-					controller.fireDbRelationshipEvent(e);
+					DbRelationshipEvent e = DbRelationshipEvent.ofRemove(application.getFrame(), rel, entity);
+					session.fireDbRelationshipEvent(e);
 				}
 
-                DataMapOps.removeBrokenObjToDbMappings(controller.getSelectedDataMap());
-				application.getUndoManager().addEdit(new RemoveRelationshipUndoableEdit(controller,entity, dbRels));
+                DataMapOps.removeBrokenObjToDbMappings(session.getSelectedDataMap());
+				application.getUndoManager().addEdit(new RemoveRelationshipUndoableEdit(session,entity, dbRels));
 			}
 		}
 	}
 
-	private void removeObjRelationships(ProjectController controller, ConfirmRemoveDialog dialog, ObjRelationship[] rels) {
+	private void removeObjRelationships(ProjectSession session, ConfirmRemoveDialog dialog, ObjRelationship[] rels) {
 		if ((rels.length == 1 && dialog.shouldDelete("ObjRelationship", rels[0].getName()))
 				|| (rels.length > 1 && dialog.shouldDelete("selected ObjRelationships"))) {
-			ObjEntity entity = controller.getSelectedObjEntity();
+			ObjEntity entity = session.getSelectedObjEntity();
 			for (ObjRelationship rel : rels) {
 				entity.removeRelationship(rel.getName());
-ObjRelationshipEvent e = ObjRelationshipEvent.ofRemove(application.getFrameController().getView(), rel, entity);
-				controller.fireObjRelationshipEvent(e);
+ObjRelationshipEvent e = ObjRelationshipEvent.ofRemove(application.getFrame(), rel, entity);
+				session.fireObjRelationshipEvent(e);
 			}
-			application.getUndoManager().addEdit(new RemoveRelationshipUndoableEdit(controller,entity, rels));
+			application.getUndoManager().addEdit(new RemoveRelationshipUndoableEdit(session,entity, rels));
 		}		
 	}
 
-	private void removeMethods(ProjectController mediator, ConfirmRemoveDialog dialog, ObjCallbackMethod[] methods) {
-    	CallbackMap callbackMap = mediator.getSelectedObjEntity().getCallbackMap();
-    	CallbackType callbackType = mediator.getSelectedCallbackType();
+	private void removeMethods(ProjectSession session, ConfirmRemoveDialog dialog, ObjCallbackMethod[] methods) {
+    	CallbackMap callbackMap = session.getSelectedObjEntity().getCallbackMap();
+    	CallbackType callbackType = session.getSelectedCallbackType();
 
         if ((methods.length == 1 && dialog.shouldDelete("callback method", methods[0].getName()))
         	|| (methods.length > 1 && dialog.shouldDelete("selected callback methods"))) {
@@ -326,17 +326,17 @@ ObjRelationshipEvent e = ObjRelationshipEvent.ofRemove(application.getFrameContr
                     
                 CallbackMethodEvent ce = CallbackMethodEvent.ofRemove(this, callbackMethod.getName());
                     
-                mediator.fireCallbackMethodEvent(ce);
+                session.fireCallbackMethodEvent(ce);
             }
             
-            application.getUndoManager().addEdit(new RemoveCallbackMethodUndoableEdit(getProjectController(), callbackType, methods));
+            application.getUndoManager().addEdit(new RemoveCallbackMethodUndoableEdit(getProjectSession(), callbackType, methods));
         }		
 	}
 
 	public void removeDataMap(DataMap map) {
-        ProjectController mediator = getProjectController();
-        DataChannelDescriptor domain = (DataChannelDescriptor) mediator.getProject().getRootNode();
-        DataMapEvent e = DataMapEvent.ofRemove(application.getFrameController().getView(), map);
+        ProjectSession session = getProjectSession();
+        DataChannelDescriptor domain = (DataChannelDescriptor) session.project().getRootNode();
+        DataMapEvent e = DataMapEvent.ofRemove(application.getFrame(), map);
 
         domain.getDataMaps().remove(map);
         if (map.getConfigurationSource() != null) {
@@ -351,64 +351,64 @@ ObjRelationshipEvent e = ObjRelationshipEvent.ofRemove(application.getFrameContr
             }
         }
        
-        mediator.fireDataMapEvent(e);
+        session.fireDataMapEvent(e);
     }
 
     public void removeDataNode(DataNodeDescriptor node) {
-        ProjectController mediator = getProjectController();
-        DataChannelDescriptor domain = (DataChannelDescriptor) mediator.getProject().getRootNode();
-        DataNodeEvent e = DataNodeEvent.ofRemove(application.getFrameController().getView(), node);
+        ProjectSession session = getProjectSession();
+        DataChannelDescriptor domain = (DataChannelDescriptor) session.project().getRootNode();
+        DataNodeEvent e = DataNodeEvent.ofRemove(application.getFrame(), node);
 
         domain.getNodeDescriptors().remove(node);
-        mediator.fireDataNodeEvent(e);
+        session.fireDataNodeEvent(e);
     }
 
     /**
      * Removes current DbEntity from its DataMap and fires "remove" EntityEvent.
      */
     public void removeDbEntity(DataMap map, DbEntity ent) {
-        ProjectController mediator = getProjectController();
+        ProjectSession session = getProjectSession();
 
-        DbEntityEvent e = DbEntityEvent.ofRemove(application.getFrameController().getView(), ent);
+        DbEntityEvent e = DbEntityEvent.ofRemove(application.getFrame(), ent);
 
         map.removeDbEntity(ent.getName(), true);
-        mediator.fireDbEntityEvent(e);
+        session.fireDbEntityEvent(e);
     }
 
     /**
      * Removes current Query from its DataMap and fires "remove" QueryEvent.
      */
     public void removeQuery(DataMap map, QueryDescriptor query) {
-        ProjectController mediator = getProjectController();
+        ProjectSession session = getProjectSession();
 
-        QueryEvent e = QueryEvent.ofRemove(application.getFrameController().getView(), query, map);
+        QueryEvent e = QueryEvent.ofRemove(application.getFrame(), query, map);
 
         map.removeQueryDescriptor(query.getName());
-        mediator.fireQueryEvent(e);
+        session.fireQueryEvent(e);
     }
 
     /**
      * Removes current Procedure from its DataMap and fires "remove" ProcedureEvent.
      */
     public void removeProcedure(DataMap map, Procedure procedure) {
-        ProjectController mediator = getProjectController();
+        ProjectSession session = getProjectSession();
 
-        ProcedureEvent e = ProcedureEvent.ofRemove(application.getFrameController().getView(), procedure);
+        ProcedureEvent e = ProcedureEvent.ofRemove(application.getFrame(), procedure);
 
         map.removeProcedure(procedure.getName());
-        mediator.fireProcedureEvent(e);
+        session.fireProcedureEvent(e);
     }
 
     /**
      * Removes current object entity from its DataMap.
      */
     public void removeObjEntity(DataMap map, ObjEntity entity) {
-        ProjectController mediator = getProjectController();
+        ProjectSession session = getProjectSession();
 
-    ObjEntityEvent e = ObjEntityEvent.ofRemove(application.getFrameController().getView(), entity);
+    ObjEntityEvent e = ObjEntityEvent.ofRemove(application.getFrame(), entity);
 
         map.removeObjEntity(entity.getName(), true);
-        mediator.fireObjEntityEvent(e);
+        session.fireObjEntityEvent(e);
 
         // remove queries that depend on entity
         // TODO: (Andrus, 09/09/2005) show warning dialog?
@@ -426,23 +426,23 @@ ObjRelationshipEvent e = ObjRelationshipEvent.ofRemove(application.getFrameContr
     }
 
     public void removeEmbeddable(DataMap map, Embeddable embeddable) {
-        ProjectController mediator = getProjectController();
+        ProjectSession session = getProjectSession();
 
-        EmbeddableEvent e = EmbeddableEvent.ofRemove(application.getFrameController().getView(), embeddable);
+        EmbeddableEvent e = EmbeddableEvent.ofRemove(application.getFrame(), embeddable);
 
         map.removeEmbeddable(embeddable.getClassName());
-        mediator.fireEmbeddableEvent(e, map);
+        session.fireEmbeddableEvent(e, map);
     }
 
     public void removeDataMapFromDataNode(DataNodeDescriptor node, DataMap map) {
-        ProjectController mediator = getProjectController();
+        ProjectSession session = getProjectSession();
 
-        DataNodeEvent e = DataNodeEvent.ofChange(application.getFrameController().getView(), node);
+        DataNodeEvent e = DataNodeEvent.ofChange(application.getFrame(), node);
 
         node.getDataMapNames().remove(map.getName());
 
         // Force reloading of the data node in the browse view
-        mediator.fireDataNodeEvent(e);
+        session.fireDataNodeEvent(e);
     }
 
     /**
@@ -470,33 +470,33 @@ ObjRelationshipEvent e = ObjRelationshipEvent.ofRemove(application.getFrameContr
 
         UndoableEdit undo = null;
 
-        ProjectController controller = getProjectController();
+        ProjectSession session = getProjectSession();
         if (object instanceof DataMap) {
             if (parentObject instanceof DataNodeDescriptor) {
-                undo = new RemoveUndoableEdit(controller, (DataNodeDescriptor) parentObject, (DataMap) object);
+                undo = new RemoveUndoableEdit(session, (DataNodeDescriptor) parentObject, (DataMap) object);
                 removeDataMapFromDataNode((DataNodeDescriptor) parentObject, (DataMap) object);
             } else {
                 // Not under Data Node, remove completely
-                undo = new RemoveUndoableEdit(controller, (DataMap) object);
+                undo = new RemoveUndoableEdit(session, (DataMap) object);
                 removeDataMap((DataMap) object);
             }
         } else if (object instanceof DataNodeDescriptor) {
-            undo = new RemoveUndoableEdit(controller, (DataNodeDescriptor) object);
+            undo = new RemoveUndoableEdit(session, (DataNodeDescriptor) object);
             removeDataNode((DataNodeDescriptor) object);
         } else if (object instanceof DbEntity) {
-            undo = new RemoveUndoableEdit(controller, ((DbEntity) object).getDataMap(), (DbEntity) object);
+            undo = new RemoveUndoableEdit(session, ((DbEntity) object).getDataMap(), (DbEntity) object);
             removeDbEntity(((DbEntity) object).getDataMap(), (DbEntity) object);
         } else if (object instanceof ObjEntity) {
-            undo = new RemoveUndoableEdit(controller, ((ObjEntity) object).getDataMap(), (ObjEntity) object);
+            undo = new RemoveUndoableEdit(session, ((ObjEntity) object).getDataMap(), (ObjEntity) object);
             removeObjEntity(((ObjEntity) object).getDataMap(), (ObjEntity) object);
         } else if (object instanceof QueryDescriptor) {
-            undo = new RemoveUndoableEdit(controller, ((QueryDescriptor) object).getDataMap(), (QueryDescriptor) object);
+            undo = new RemoveUndoableEdit(session, ((QueryDescriptor) object).getDataMap(), (QueryDescriptor) object);
             removeQuery(((QueryDescriptor) object).getDataMap(), (QueryDescriptor) object);
         } else if (object instanceof Procedure) {
-            undo = new RemoveUndoableEdit(controller, ((Procedure) object).getDataMap(), (Procedure) object);
+            undo = new RemoveUndoableEdit(session, ((Procedure) object).getDataMap(), (Procedure) object);
             removeProcedure(((Procedure) object).getDataMap(), (Procedure) object);
         } else if (object instanceof Embeddable) {
-            undo = new RemoveUndoableEdit(controller, ((Embeddable) object).getDataMap(), (Embeddable) object);
+            undo = new RemoveUndoableEdit(session, ((Embeddable) object).getDataMap(), (Embeddable) object);
             removeEmbeddable(((Embeddable) object).getDataMap(), (Embeddable) object);
         }
 
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/RemoveAttributeAction.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/RemoveAttributeAction.java
index b32304938..ad467af1a 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/RemoveAttributeAction.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/RemoveAttributeAction.java
@@ -35,7 +35,7 @@ import org.apache.cayenne.modeler.event.model.ObjAttributeEvent;
 import org.apache.cayenne.modeler.project.DataMapOps;
 import org.apache.cayenne.modeler.project.ObjEntityOps;
 import org.apache.cayenne.modeler.ui.confirmremove.ConfirmRemoveDialog;
-import org.apache.cayenne.modeler.ui.project.ProjectController;
+import org.apache.cayenne.modeler.project.ProjectSession;
 import org.apache.cayenne.modeler.undo.RemoveAttributeUndoableEdit;
 
 import java.awt.event.ActionEvent;
@@ -70,11 +70,11 @@ public class RemoveAttributeAction extends RemoveAction implements MultipleObjec
     @Override
     public void performAction(ActionEvent e, boolean allowAsking) {
         ConfirmRemoveDialog dialog = getConfirmDeleteDialog(allowAsking);
-        ProjectController mediator = getProjectController();
+        ProjectSession session = getProjectSession();
 
-        EmbeddableAttribute[] embAttrs = getProjectController().getSelectedEmbeddableAttributes();
-        ObjAttribute[] objAttrs = getProjectController().getSelectedObjAttributes();
-        DbAttribute[] dbAttrs = getProjectController().getSelectedDbAttributes();
+        EmbeddableAttribute[] embAttrs = getProjectSession().getSelectedEmbeddableAttributes();
+        ObjAttribute[] objAttrs = getProjectSession().getSelectedObjAttributes();
+        DbAttribute[] dbAttrs = getProjectSession().getSelectedDbAttributes();
 
         
         if (embAttrs != null && embAttrs.length > 0) {
@@ -84,10 +84,10 @@ public class RemoveAttributeAction extends RemoveAction implements MultipleObjec
                     || (embAttrs.length > 1 && dialog
                             .shouldDelete("selected EmbAttributes"))) {
 
-                Embeddable embeddable = mediator.getSelectedEmbeddable();
+                Embeddable embeddable = session.getSelectedEmbeddable();
 
                 application.getUndoManager().addEdit(
-                        new RemoveAttributeUndoableEdit(mediator,embeddable, embAttrs));
+                        new RemoveAttributeUndoableEdit(session,embeddable, embAttrs));
 
                 removeEmbeddableAttributes(embeddable, embAttrs);
 
@@ -97,9 +97,9 @@ public class RemoveAttributeAction extends RemoveAction implements MultipleObjec
                     .getName()))
                     || (objAttrs.length > 1 && dialog.shouldDelete("selected ObjAttributes"))) {
 
-                ObjEntity entity = mediator.getSelectedObjEntity();
+                ObjEntity entity = session.getSelectedObjEntity();
 
-                application.getUndoManager().addEdit(new RemoveAttributeUndoableEdit(mediator,entity, objAttrs));
+                application.getUndoManager().addEdit(new RemoveAttributeUndoableEdit(session,entity, objAttrs));
 
                 removeObjAttributes(entity, objAttrs);
             }
@@ -108,42 +108,42 @@ public class RemoveAttributeAction extends RemoveAction implements MultipleObjec
         			.getName()))
                     || (dbAttrs.length > 1 && dialog.shouldDelete("selected DbAttributes"))) {
 
-        		DbEntity entity = mediator.getSelectedDbEntity();
+        		DbEntity entity = session.getSelectedDbEntity();
 
-                application.getUndoManager().addEdit(new RemoveAttributeUndoableEdit(mediator,entity, dbAttrs));
+                application.getUndoManager().addEdit(new RemoveAttributeUndoableEdit(session,entity, dbAttrs));
 
-                removeDbAttributes(mediator.getSelectedDataMap(), entity, dbAttrs);
+                removeDbAttributes(session.getSelectedDataMap(), entity, dbAttrs);
         	}
         }
     }
 
     public void removeDbAttributes(DataMap dataMap, DbEntity entity, DbAttribute[] attribs) {
-        ProjectController mediator = getProjectController();
+        ProjectSession session = getProjectSession();
 
         for (DbAttribute attrib : attribs) {
             entity.removeAttribute(attrib.getName());
 
             DbAttributeEvent e = DbAttributeEvent.ofRemove(
-                    application.getFrameController().getView(),
+                    application.getFrame(),
                     attrib,
                     entity);
 
-            mediator.fireDbAttributeEvent(e);
+            session.fireDbAttributeEvent(e);
         }
 
         DataMapOps.removeBrokenObjToDbMappings(dataMap);
     }
 
     public void removeObjAttributes(ObjEntity entity, ObjAttribute[] attribs) {
-        ProjectController mediator = getProjectController();
+        ProjectSession session = getProjectSession();
 
         for (ObjAttribute attrib : attribs) {
             entity.removeAttribute(attrib.getName());
             ObjAttributeEvent e = ObjAttributeEvent.ofRemove(
-                    application.getFrameController().getView(),
+                    application.getFrame(),
                     attrib,
                     entity);
-            mediator.fireObjAttributeEvent(e);
+            session.fireObjAttributeEvent(e);
 
             Collection<ObjEntity> objEntities = ObjEntityOps.subentities(e.getEntity());
             for (ObjEntity objEntity: objEntities) {
@@ -153,12 +153,12 @@ public class RemoveAttributeAction extends RemoveAction implements MultipleObjec
     }
 
     public void removeEmbeddableAttributes(Embeddable embeddable, EmbeddableAttribute[] attrs) {
-        ProjectController mediator = getProjectController();
+        ProjectSession session = getProjectSession();
 
         for (EmbeddableAttribute attrib : attrs) {
             embeddable.removeAttribute(attrib.getName());
-            EmbeddableAttributeEvent e = EmbeddableAttributeEvent.ofRemove(application.getFrameController().getView(), attrib, embeddable);
-            mediator.fireEmbeddableAttributeEvent(e);
+            EmbeddableAttributeEvent e = EmbeddableAttributeEvent.ofRemove(application.getFrame(), attrib, embeddable);
+            session.fireEmbeddableAttributeEvent(e);
         }
     }
 }
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/RemoveCallbackMethodAction.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/RemoveCallbackMethodAction.java
index ea2717c4e..1f63dbaff 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/RemoveCallbackMethodAction.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/RemoveCallbackMethodAction.java
@@ -18,9 +18,8 @@
  ****************************************************************/
 package org.apache.cayenne.modeler.ui.action;
 
-import org.apache.cayenne.modeler.event.model.ModelEvent;
 import org.apache.cayenne.modeler.Application;
-import org.apache.cayenne.modeler.ui.project.ProjectController;
+import org.apache.cayenne.modeler.project.ProjectSession;
 import org.apache.cayenne.modeler.ui.confirmremove.ConfirmRemoveDialog;
 import org.apache.cayenne.modeler.ui.project.editor.objentity.callbacks.CallbackType;
 import org.apache.cayenne.modeler.ui.project.editor.objentity.callbacks.ObjCallbackMethod;
@@ -51,7 +50,7 @@ public class RemoveCallbackMethodAction extends RemoveAction implements Multiple
     public void performAction(ActionEvent e, boolean allowAsking) {
         ConfirmRemoveDialog dialog = getConfirmDeleteDialog(allowAsking);
 
-        ObjCallbackMethod[] methods = getProjectController().getSelectedCallbackMethods();
+        ObjCallbackMethod[] methods = getProjectSession().getSelectedCallbackMethods();
 
         if ((methods.length == 1 && dialog.shouldDelete("callback method", methods[0].getName()))
                 || (methods.length > 1 && dialog.shouldDelete("selected callback methods"))) {
@@ -60,29 +59,29 @@ public class RemoveCallbackMethodAction extends RemoveAction implements Multiple
     }
 
     private void removeCallbackMethods() {
-        ProjectController mediator = getProjectController();
-        CallbackType callbackType = mediator.getSelectedCallbackType();
+        ProjectSession session = getProjectSession();
+        CallbackType callbackType = session.getSelectedCallbackType();
 
-        ObjCallbackMethod[] callbackMethods = mediator.getSelectedCallbackMethods();
+        ObjCallbackMethod[] callbackMethods = session.getSelectedCallbackMethods();
 
         for (ObjCallbackMethod callbackMethod : callbackMethods) {
             removeCallbackMethod(callbackType, callbackMethod.getName());
         }
 
-        application.getUndoManager().addEdit(new RemoveCallbackMethodUndoableEdit(getProjectController(), callbackType, callbackMethods));
+        application.getUndoManager().addEdit(new RemoveCallbackMethodUndoableEdit(getProjectSession(), callbackType, callbackMethods));
     }
 
     public void removeCallbackMethod(CallbackType callbackType, String method) {
-        ProjectController controller = getProjectController();
+        ProjectSession session = getProjectSession();
 
-        getProjectController().getSelectedObjEntity()
+        getProjectSession().getSelectedObjEntity()
                 .getCallbackMap()
                 .getCallbackDescriptor(callbackType.getType())
                 .removeCallbackMethod(method);
 
         CallbackMethodEvent e = CallbackMethodEvent.ofRemove(this, method);
 
-        controller.fireCallbackMethodEvent(e);
+        session.fireCallbackMethodEvent(e);
     }
 }
 
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/RemoveProcedureParameterAction.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/RemoveProcedureParameterAction.java
index 841632828..2bc8e5f44 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/RemoveProcedureParameterAction.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/RemoveProcedureParameterAction.java
@@ -23,9 +23,8 @@ import org.apache.cayenne.configuration.ConfigurationNode;
 import org.apache.cayenne.modeler.event.model.ProcedureParameterEvent;
 import org.apache.cayenne.map.Procedure;
 import org.apache.cayenne.map.ProcedureParameter;
-import org.apache.cayenne.modeler.event.model.ModelEvent;
 import org.apache.cayenne.modeler.Application;
-import org.apache.cayenne.modeler.ui.project.ProjectController;
+import org.apache.cayenne.modeler.project.ProjectSession;
 import org.apache.cayenne.modeler.ui.confirmremove.ConfirmRemoveDialog;
 
 import java.awt.event.ActionEvent;
@@ -64,7 +63,7 @@ public class RemoveProcedureParameterAction extends RemoveAction implements Mult
     public void performAction(ActionEvent e, boolean allowAsking) {
         ConfirmRemoveDialog dialog = getConfirmDeleteDialog(allowAsking);
 
-        ProcedureParameter[] params = getProjectController()
+        ProcedureParameter[] params = getProjectSession()
                 .getSelectedProcedureParameters();
         if (params.length > 0) {
             if ((params.length == 1 && dialog.shouldDelete(
@@ -78,23 +77,23 @@ public class RemoveProcedureParameterAction extends RemoveAction implements Mult
     }
 
     protected void removeProcedureParameters() {
-        ProjectController mediator = getProjectController();
-        ProcedureParameter[] parameters = mediator.getSelectedProcedureParameters();
-        removeProcedureParameters(mediator.getSelectedProcedure(), parameters);
+        ProjectSession session = getProjectSession();
+        ProcedureParameter[] parameters = session.getSelectedProcedureParameters();
+        removeProcedureParameters(session.getSelectedProcedure(), parameters);
     }
 
     public void removeProcedureParameters(
             Procedure procedure,
             ProcedureParameter[] parameters) {
-        ProjectController mediator = getProjectController();
+        ProjectSession session = getProjectSession();
 
         for (ProcedureParameter parameter : parameters) {
 
             procedure.removeCallParameter(parameter.getName());
 
-            ProcedureParameterEvent e = ProcedureParameterEvent.ofRemove(application.getFrameController().getView(), parameter);
+            ProcedureParameterEvent e = ProcedureParameterEvent.ofRemove(application.getFrame(), parameter);
 
-            mediator.fireProcedureParameterEvent(e);
+            session.fireProcedureParameterEvent(e);
         }
     }
 }
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/RemoveRelationshipAction.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/RemoveRelationshipAction.java
index 3488292f7..410331414 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/RemoveRelationshipAction.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/RemoveRelationshipAction.java
@@ -30,7 +30,7 @@ import org.apache.cayenne.modeler.event.model.DbRelationshipEvent;
 import org.apache.cayenne.modeler.event.model.ObjRelationshipEvent;
 import org.apache.cayenne.modeler.project.DataMapOps;
 import org.apache.cayenne.modeler.ui.confirmremove.ConfirmRemoveDialog;
-import org.apache.cayenne.modeler.ui.project.ProjectController;
+import org.apache.cayenne.modeler.project.ProjectSession;
 import org.apache.cayenne.modeler.undo.RemoveRelationshipUndoableEdit;
 
 import java.awt.event.ActionEvent;
@@ -67,57 +67,57 @@ public class RemoveRelationshipAction extends RemoveAction implements MultipleOb
 	@Override
 	public void performAction(ActionEvent e, boolean allowAsking) {
 		ConfirmRemoveDialog dialog = getConfirmDeleteDialog(allowAsking);
-		ProjectController mediator = getProjectController();
+		ProjectSession session = getProjectSession();
 
-		ObjRelationship[] rels = getProjectController()
+		ObjRelationship[] rels = getProjectSession()
 				.getSelectedObjRelationships();
 		if (rels != null && rels.length > 0) {
 			if ((rels.length == 1 && dialog.shouldDelete("ObjRelationship",
 					rels[0].getName()))
 					|| (rels.length > 1 && dialog
 							.shouldDelete("selected ObjRelationships"))) {
-				ObjEntity entity = mediator.getSelectedObjEntity();
+				ObjEntity entity = session.getSelectedObjEntity();
 				removeObjRelationships(entity, rels);
-				application.getUndoManager().addEdit(new RemoveRelationshipUndoableEdit(mediator,entity, rels));
+				application.getUndoManager().addEdit(new RemoveRelationshipUndoableEdit(session,entity, rels));
 			}
 		} else {
-			DbRelationship[] dbRels = getProjectController()
+			DbRelationship[] dbRels = getProjectSession()
 					.getSelectedDbRelationships();
 			if (dbRels != null && dbRels.length > 0) {
 				if ((dbRels.length == 1 && dialog.shouldDelete(
 						"DbRelationship", dbRels[0].getName()))
 						|| (dbRels.length > 1 && dialog
 								.shouldDelete("selected DbRelationships"))) {
-					DbEntity entity = mediator.getSelectedDbEntity();
+					DbEntity entity = session.getSelectedDbEntity();
 					removeDbRelationships(entity, dbRels);
-					application.getUndoManager().addEdit(new RemoveRelationshipUndoableEdit(mediator,entity, dbRels));
+					application.getUndoManager().addEdit(new RemoveRelationshipUndoableEdit(session,entity, dbRels));
 				}
 			}
 		}
 	}
 
 	public void removeObjRelationships(ObjEntity entity, ObjRelationship[] rels) {
-		ProjectController controller = getProjectController();
+		ProjectSession session = getProjectSession();
 
 		for (ObjRelationship rel : rels) {
 			entity.removeRelationship(rel.getName());
-			ObjRelationshipEvent e = ObjRelationshipEvent.ofRemove(application.getFrameController().getView(),
+			ObjRelationshipEvent e = ObjRelationshipEvent.ofRemove(application.getFrame(),
 					rel, entity);
-			controller.fireObjRelationshipEvent(e);
+			session.fireObjRelationshipEvent(e);
 		}
 	}
 
 	public void removeDbRelationships(DbEntity entity, DbRelationship[] rels) {
-		ProjectController controller = getProjectController();
+		ProjectSession session = getProjectSession();
 
 		for(int i = 0; i < rels.length; i++) {
 			rels[i] = entity.getRelationship(rels[i].getName());
 			entity.removeRelationship(rels[i].getName());
-			DbRelationshipEvent e = DbRelationshipEvent.ofRemove(application.getFrameController().getView(),
+			DbRelationshipEvent e = DbRelationshipEvent.ofRemove(application.getFrame(),
 					rels[i], entity);
-			controller.fireDbRelationshipEvent(e);
+			session.fireDbRelationshipEvent(e);
 		}
 
-		DataMapOps.removeBrokenObjToDbMappings(controller.getSelectedDataMap());
+		DataMapOps.removeBrokenObjToDbMappings(session.getSelectedDataMap());
 	}
 }
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/RevertAction.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/RevertAction.java
index 0161d5630..b93619d24 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/RevertAction.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/RevertAction.java
@@ -20,7 +20,7 @@
 package org.apache.cayenne.modeler.ui.action;
 
 import org.apache.cayenne.modeler.Application;
-import org.apache.cayenne.modeler.ui.ModelerController;
+import org.apache.cayenne.modeler.ui.MainFrame;
 import org.apache.cayenne.project.Project;
 
 import java.awt.event.ActionEvent;
@@ -42,7 +42,7 @@ public class RevertAction extends ModelerAbstractAction {
 
         boolean isNew = project.getConfigurationResource() == null;
 
-        ModelerController controller = application.getFrameController();
+        MainFrame controller = application.getFrame();
 
         // close ... don't use OpenProjectAction close method as it will ask for save, we don't want that here
         controller.onProjectClosed();
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/SaveAction.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/SaveAction.java
index 90e8b2d8f..08b3187e2 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/SaveAction.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/SaveAction.java
@@ -57,15 +57,15 @@ public class SaveAction extends SaveAsAction {
 
         File oldProjectFile = new File(p.getConfigurationResource().getURL().toURI());
 
-        getProjectController().pauseFileChangeTracking();
+        getProjectSession().pauseFileChangeTracking();
         ProjectSaver saver = application.getProjectSaver();
         saver.save(p);
 
         File newProjectFile = new File(p.getConfigurationResource().getURL().toURI());
-        application.getFrameController().changePathInLastProjListAction(oldProjectFile, newProjectFile);
-        application.getFrameController().getView().fireRecentFileListChanged();
+        application.getFrame().changePathInLastProjListAction(oldProjectFile, newProjectFile);
+        application.getFrame().fireRecentFileListChanged();
 
-        getProjectController().fireProjectAfterSaveEvent(new ProjectAfterSaveEvent(getProjectController()));
+        getProjectSession().fireProjectAfterSaveEvent(new ProjectAfterSaveEvent(getProjectSession()));
 
         return true;
     }
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/SaveAsAction.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/SaveAsAction.java
index 9626dfce0..d9bbbe804 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/SaveAsAction.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/SaveAsAction.java
@@ -24,7 +24,6 @@ import org.apache.cayenne.configuration.ConfigurationNode;
 import org.apache.cayenne.modeler.Application;
 import org.apache.cayenne.modeler.event.model.ProjectBeforeSaveEvent;
 import org.apache.cayenne.modeler.event.model.ProjectAfterSaveEvent;
-import org.apache.cayenne.modeler.ui.project.validator.ProjectValidatorDialogController;
 import org.apache.cayenne.project.Project;
 import org.apache.cayenne.project.ProjectSaver;
 import org.apache.cayenne.project.validation.ProjectValidator;
@@ -73,22 +72,22 @@ public class SaveAsAction extends ModelerAbstractAction {
         }
 
         if (projectDir.exists() && !projectDir.canWrite()) {
-            JOptionPane.showMessageDialog(application.getFrameController().getView(), "Can't save project - unable to write to file \""
+            JOptionPane.showMessageDialog(application.getFrame(), "Can't save project - unable to write to file \""
                     + projectDir.getPath() + "\"", "Can't Save Project", JOptionPane.ERROR_MESSAGE);
             return false;
         }
 
-        getProjectController().pauseFileChangeTracking();
+        getProjectSession().pauseFileChangeTracking();
 
         URLResource res = new URLResource(projectDir.toURI().toURL());
         ProjectSaver saver = application.getProjectSaver();
         saver.saveAs(p, res);
 
         File file = new File(p.getConfigurationResource().getURL().toURI());
-        application.getFrameController().addToLastProjListAction(file);
-        application.getFrameController().getView().fireRecentFileListChanged();
+        application.getFrame().addToLastProjListAction(file);
+        application.getFrame().fireRecentFileListChanged();
 
-        getProjectController().fireProjectAfterSaveEvent(new ProjectAfterSaveEvent(getProjectController()));
+        getProjectSession().fireProjectAfterSaveEvent(new ProjectAfterSaveEvent(getProjectSession()));
 
         return true;
     }
@@ -107,7 +106,7 @@ public class SaveAsAction extends ModelerAbstractAction {
         ProjectValidator projectValidator = application.getProjectValidator();
         ValidationResult validationResult = projectValidator.validate(getCurrentProject().getRootNode());
 
-        getProjectController().fireProjectBeforeSaveEvent(new ProjectBeforeSaveEvent(SaveAsAction.class));
+        getProjectSession().fireProjectBeforeSaveEvent(new ProjectBeforeSaveEvent(SaveAsAction.class));
 
         try {
             if (!saveAll()) {
@@ -117,12 +116,12 @@ public class SaveAsAction extends ModelerAbstractAction {
             throw new CayenneRuntimeException("Error on save", ex);
         }
 
-        application.getFrameController().onProjectSaved();
+        application.getFrame().onProjectSaved();
 
         // If there were errors or warnings at validation, display them
         if (!validationResult.getFailures().isEmpty()) {
-            new ProjectValidatorDialogController(getProjectController())
-                    .showOnFailures(validationResult.getFailures());
+            application.getActionManager().getAction(ValidateAction.class)
+                    .showFailures(validationResult.getFailures());
         }
     }
 
@@ -136,7 +135,7 @@ public class SaveAsAction extends ModelerAbstractAction {
             return false;
         }
 
-        Project project = application.getProject();
+        Project project = application.getFrame().getProjectSession().project();
         return project != null && project.isModified();
     }
 }
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/ShowLogConsoleAction.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/ShowLogConsoleAction.java
index ed4a70c08..1792d7122 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/ShowLogConsoleAction.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/ShowLogConsoleAction.java
@@ -38,7 +38,7 @@ public class ShowLogConsoleAction extends ModelerAbstractAction {
 
     @Override
     public void performAction(ActionEvent e) {
-        application.getLogConsoleController().toggle();
+        application.getLogConsole().toggle();
         putValue(Action.SELECTED_KEY, isConsoleShown());
     }
 
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/ShowValidationConfigAction.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/ShowValidationConfigAction.java
index 0cf9177ac..fbb320016 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/ShowValidationConfigAction.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/ShowValidationConfigAction.java
@@ -20,7 +20,7 @@ package org.apache.cayenne.modeler.ui.action;
 
 import org.apache.cayenne.configuration.DataChannelDescriptor;
 import org.apache.cayenne.modeler.Application;
-import org.apache.cayenne.modeler.ui.project.ProjectController;
+import org.apache.cayenne.modeler.project.ProjectSession;
 import org.apache.cayenne.modeler.event.display.DomainDisplayEvent;
 
 import java.awt.event.ActionEvent;
@@ -33,8 +33,8 @@ public class ShowValidationConfigAction extends ModelerAbstractAction {
 
     @Override
     public void performAction(ActionEvent e) {
-        ProjectController controller = getProjectController();
-        DataChannelDescriptor dataChannel = (DataChannelDescriptor) controller.getProject().getRootNode();
-        controller.displayDomain(new DomainDisplayEvent(this, dataChannel));
+        ProjectSession session = getProjectSession();
+        DataChannelDescriptor dataChannel = (DataChannelDescriptor) session.project().getRootNode();
+        session.displayDomain(new DomainDisplayEvent(this, dataChannel));
     }
 }
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/ShowValidationOptionAction.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/ShowValidationOptionAction.java
index 99e479c86..a27ba44c1 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/ShowValidationOptionAction.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/ShowValidationOptionAction.java
@@ -20,7 +20,7 @@ package org.apache.cayenne.modeler.ui.action;
 
 import org.apache.cayenne.configuration.DataChannelDescriptor;
 import org.apache.cayenne.modeler.Application;
-import org.apache.cayenne.modeler.ui.project.ProjectController;
+import org.apache.cayenne.modeler.project.ProjectSession;
 import org.apache.cayenne.modeler.event.display.ValidationConfigDisplayEvent;
 import org.apache.cayenne.project.validation.Inspection;
 
@@ -38,9 +38,9 @@ public class ShowValidationOptionAction extends ShowValidationConfigAction {
     public void performAction(ActionEvent e) {
         super.performAction(e);
         Inspection inspection = (Inspection) getValue(INSPECTION_PARAM);
-        ProjectController controller = getProjectController();
-        DataChannelDescriptor dataChannel = (DataChannelDescriptor) controller.getProject().getRootNode();
-        controller.displayValidationConfig(new ValidationConfigDisplayEvent(this, dataChannel, inspection));
+        ProjectSession session = getProjectSession();
+        DataChannelDescriptor dataChannel = (DataChannelDescriptor) session.project().getRootNode();
+        session.displayValidationConfig(new ValidationConfigDisplayEvent(this, dataChannel, inspection));
     }
 
     public ShowValidationConfigAction putInspection(Inspection inspection) {
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/UpdateValidationConfigAction.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/UpdateValidationConfigAction.java
index d146544c2..8d05358c4 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/UpdateValidationConfigAction.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/UpdateValidationConfigAction.java
@@ -54,16 +54,16 @@ public class UpdateValidationConfigAction extends ModelerAbstractAction {
     @Override
     public void performAction(ActionEvent e) {
         DataChannelMetaData metaData = application.getMetaData();
-        DataChannelDescriptor dataChannel = ((DataChannelDescriptor) application.getProject().getRootNode());
+        DataChannelDescriptor dataChannel = ((DataChannelDescriptor) application.getFrame().getProjectSession().project().getRootNode());
         ValidationConfig config = (ValidationConfig) getValue(CONFIG_PARAM);
         ValidationConfig oldConfig = ValidationConfig.fromMetadata(metaData, dataChannel);
         metaData.add(dataChannel, config);
 
         if (undoable) {
             CayenneUndoManager undoManager = application.getUndoManager();
-            undoManager.addEdit(new UpdateValidationConfigUndoableEdit(getProjectController(), oldConfig, config));
+            undoManager.addEdit(new UpdateValidationConfigUndoableEdit(getProjectSession(), oldConfig, config));
         }
-        getProjectController().fireDomainEvent(DomainEvent.ofChange(e.getSource(), dataChannel));
+        getProjectSession().fireDomainEvent(DomainEvent.ofChange(e.getSource(), dataChannel));
     }
 
     public UpdateValidationConfigAction putConfig(ValidationConfig config) {
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/ValidateAction.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/ValidateAction.java
index 804f3f9b6..ce5dbf4b6 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/ValidateAction.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/ValidateAction.java
@@ -20,8 +20,9 @@
 package org.apache.cayenne.modeler.ui.action;
 
 import org.apache.cayenne.modeler.Application;
-import org.apache.cayenne.modeler.ui.project.validator.ProjectValidatorDialogController;
+import org.apache.cayenne.modeler.ui.project.validator.ProjectValidatorDialog;
 import org.apache.cayenne.project.validation.ProjectValidator;
+import org.apache.cayenne.validation.ValidationFailure;
 import org.apache.cayenne.validation.ValidationResult;
 
 import javax.swing.*;
@@ -29,12 +30,17 @@ import java.awt.*;
 import java.awt.event.ActionEvent;
 import java.awt.event.InputEvent;
 import java.awt.event.KeyEvent;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.util.List;
 
 /**
  * UI action that performs full project validation.
  */
 public class ValidateAction extends ModelerAbstractAction {
 
+    private ProjectValidatorDialog dialog;
+
     public ValidateAction(Application application) {
         super("Validate Project", application);
     }
@@ -55,10 +61,35 @@ public class ValidateAction extends ModelerAbstractAction {
                 .getRootNode());
 
         if (!validationResult.getFailures().isEmpty()) {
-            new ProjectValidatorDialogController(getProjectController()).showOnFailures(validationResult.getFailures());
+            showFailures(validationResult.getFailures());
         }
         else {
-            ProjectValidatorDialogController.showOnSuccess(application);
+            disposeDialog();
+            ProjectValidatorDialog.showOnSuccess(application);
+        }
+    }
+
+    /**
+     * Displays the given failures, reusing the existing validator dialog if one is open.
+     * Used both by this action and by other callers that need to surface failures (project
+     * open, save-as) so that at most one dialog is visible at a time.
+     */
+    public void showFailures(List<ValidationFailure> failures) {
+        if (dialog == null || !dialog.isDisplayable()) {
+            dialog = new ProjectValidatorDialog(getProjectSession(), application.getFrame());
+            dialog.addWindowListener(new WindowAdapter() {
+                @Override
+                public void windowClosed(WindowEvent e) {
+                    dialog = null;
+                }
+            });
+        }
+        dialog.showOnFailures(failures);
+    }
+
+    private void disposeDialog() {
+        if (dialog != null && dialog.isDisplayable()) {
+            dialog.dispose();
         }
     }
 }
\ No newline at end of file
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/autorelationship/InferRelationshipsController.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/autorelationship/InferRelationshipsController.java
deleted file mode 100644
index 19b345185..000000000
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/autorelationship/InferRelationshipsController.java
+++ /dev/null
@@ -1,439 +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.ui.autorelationship;
-
-import org.apache.cayenne.dbsync.naming.ObjectNameGenerator;
-import org.apache.cayenne.map.DataMap;
-import org.apache.cayenne.map.DbAttribute;
-import org.apache.cayenne.map.DbEntity;
-import org.apache.cayenne.map.DbJoin;
-import org.apache.cayenne.map.DbRelationship;
-import org.apache.cayenne.modeler.event.model.DbRelationshipEvent;
-import org.apache.cayenne.modeler.mvc.ChildController;
-import org.apache.cayenne.modeler.mvc.RootController;
-import org.apache.cayenne.modeler.service.classloader.ModelerClassLoader;
-import org.apache.cayenne.modeler.ui.project.ProjectController;
-import org.apache.cayenne.modeler.undo.CreateRelationshipUndoableEdit;
-import org.apache.cayenne.modeler.undo.InferRelationshipsUndoableEdit;
-import org.apache.cayenne.modeler.util.NameGeneratorPreferences;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import javax.swing.*;
-import java.awt.*;
-import java.beans.PropertyChangeListener;
-import java.beans.PropertyChangeSupport;
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Set;
-import java.util.function.Predicate;
-
-public class InferRelationshipsController extends ChildController<RootController> {
-
-    private static final Logger LOGGER = LoggerFactory.getLogger(InferRelationshipsController.class);
-    public static final String SELECTED_PROPERTY = "selected";
-
-    protected DataMap dataMap;
-    protected List<InferredRelationship> inferredRelationships;
-    protected List<DbEntity> entities;
-    protected Set<InferredRelationship> selectedEntities;
-    protected int index;
-    protected ObjectNameGenerator strategy;
-    private final InferRelationshipsTabController entitySelector;
-    private InferRelationshipsDialog view;
-    private PropertyChangeSupport propertyChangeSupport;
-
-    public InferRelationshipsController(RootController parent, DataMap dataMap) {
-        super(parent);
-        this.dataMap = dataMap;
-        this.entities = new ArrayList<>(dataMap.getDbEntities());
-        this.selectedEntities = new HashSet<>();
-        this.strategy = createNamingStrategy(NameGeneratorPreferences
-                .getInstance()
-                .getLastUsedStrategies(application)
-                .get(0));
-        setNamingStrategy(strategy);
-        setRelationships();
-        this.entitySelector = new InferRelationshipsTabController(this);
-    }
-
-
-    public void setRelationships() {
-        inferredRelationships = new ArrayList<>();
-
-        for (DbEntity entity : entities) {
-            createRelationships(entity);
-        }
-
-        createJoins();
-        createNames();
-    }
-
-    protected void createRelationships(DbEntity entity) {
-
-        for (DbAttribute attribute : entity.getAttributes()) {
-
-            String name = attribute.getName();
-            if (name.length() < 4) {
-                continue;
-            }
-
-            if (!name.substring(name.length() - 3).equalsIgnoreCase("_ID")) {
-                continue;
-            }
-
-            String baseName = name.substring(0, name.length() - 3);
-            for (DbEntity targetEntity : entities) {
-                // TODO: should we handle relationships to self??
-                if (targetEntity == entity) {
-                    continue;
-                }
-
-                if (baseName.equalsIgnoreCase(targetEntity.getName())
-                        && !attribute.isPrimaryKey()
-                        && !targetEntity.getAttributes().isEmpty()) {
-
-                    if (!attribute.isForeignKey()) {
-                        InferredRelationship myir = new InferredRelationship();
-                        myir.setSource(entity);
-                        myir.setTarget(targetEntity);
-                        inferredRelationships.add(myir);
-                    }
-                    createReversRelationship(targetEntity, entity);
-                }
-            }
-        }
-    }
-
-    public void createReversRelationship(DbEntity eSourse, DbEntity eTarget) {
-        InferredRelationship myir = new InferredRelationship();
-        for (DbRelationship relationship : eSourse.getRelationships()) {
-            for (DbJoin join : relationship.getJoins()) {
-                if (join.getSource().getEntity().equals(eSourse) && join.getTarget().getEntity().equals(eTarget)) {
-                    return;
-                }
-            }
-        }
-        myir.setSource(eSourse);
-        myir.setTarget(eTarget);
-        inferredRelationships.add(myir);
-    }
-
-    protected DbAttribute getJoinAttribute(DbEntity sEntity, DbEntity tEntity) {
-        if (sEntity.getAttributes().size() == 1) {
-            return sEntity.getAttributes().iterator().next();
-        } else {
-            for (DbAttribute attr : sEntity.getAttributes()) {
-                if (attr.getName().equalsIgnoreCase(tEntity.getName() + "_ID")) {
-                    return attr;
-                }
-            }
-
-            for (DbAttribute attr : sEntity.getAttributes()) {
-                if ((attr.getName().equalsIgnoreCase(sEntity.getName() + "_ID"))
-                        && (!attr.isPrimaryKey())) {
-                    return attr;
-                }
-            }
-
-            for (DbAttribute attr : sEntity.getAttributes()) {
-                if (attr.isPrimaryKey()) {
-                    return attr;
-                }
-            }
-        }
-        return null;
-    }
-
-    protected void createJoins() {
-        Iterator<InferredRelationship> it = inferredRelationships.iterator();
-        while (it.hasNext()) {
-            InferredRelationship inferred = it.next();
-
-            DbAttribute src = getJoinAttribute(inferred.getSource(), inferred.getTarget());
-            if (src == null) {
-                // TODO: andrus 03/28/2010 this is pretty inefficient I guess... We should
-                // check for this condition earlier. See CAY-1405 for the map that caused
-                // this issue
-                it.remove();
-                continue;
-            }
-
-            DbAttribute target = getJoinAttribute(inferred.getTarget(), inferred
-                    .getSource());
-            if (target == null) {
-                // TODO: andrus 03/28/2010 this is pretty inefficient I guess... We should
-                // check for this condition earlier. See CAY-1405 for the map that caused
-                // this issue
-                it.remove();
-                continue;
-            }
-
-            inferred.setJoinSource(src);
-            if (src.isPrimaryKey()) {
-                inferred.setToMany(true);
-            }
-
-            inferred.setJoinTarget(target);
-        }
-    }
-
-    protected void createNames() {
-
-
-        for (InferredRelationship myir : inferredRelationships) {
-
-            DbRelationship localRelationship = new DbRelationship();
-            localRelationship.setToMany(myir.isToMany());
-
-            if (myir.getJoinSource().isPrimaryKey()) {
-
-                localRelationship.addJoin(
-                        new DbJoin(localRelationship, myir.getJoinSource().getName(), myir.getJoinTarget().getName())
-                );
-                localRelationship.setSourceEntity(myir.getSource());
-                localRelationship.setTargetEntityName(myir.getTarget().getName());
-            } else {
-                localRelationship.addJoin(
-                        new DbJoin(localRelationship, myir.getJoinTarget().getName(), myir.getJoinSource().getName())
-                );
-                localRelationship.setSourceEntity(myir.getTarget());
-                localRelationship.setTargetEntityName(myir.getSource().getName());
-            }
-
-            myir.setName(strategy.relationshipName(localRelationship));
-        }
-    }
-
-    public boolean isSelected(InferredRelationship entity) {
-        return selectedEntities.contains(entity);
-    }
-
-    public void setSelected(InferredRelationship entity, boolean selectedFlag) {
-        if (selectedFlag) {
-            if (selectedEntities.add(entity)) {
-                firePropertyChange();
-            }
-        } else {
-            if (selectedEntities.remove(entity)) {
-                firePropertyChange();
-            }
-        }
-    }
-
-    private void firePropertyChange() {
-        if (propertyChangeSupport != null) {
-            propertyChangeSupport.firePropertyChange(InferRelationshipsController.SELECTED_PROPERTY, null, null);
-        }
-    }
-
-    private void addPropertyChangeListener(PropertyChangeListener listener) {
-
-        if (propertyChangeSupport == null) {
-            propertyChangeSupport = new PropertyChangeSupport(this);
-        }
-
-        propertyChangeSupport.addPropertyChangeListener(InferRelationshipsController.SELECTED_PROPERTY, listener);
-    }
-
-    public int getSelectedEntitiesSize() {
-        return selectedEntities.size();
-    }
-
-    public List<InferredRelationship> getEntities() {
-        return inferredRelationships;
-    }
-
-    public DataMap getDataMap() {
-        return dataMap;
-    }
-
-    public void setNamingStrategy(ObjectNameGenerator namestr) {
-        strategy = namestr;
-    }
-
-
-    public ObjectNameGenerator createNamingStrategy(String strategyClass) {
-        try {
-            ModelerClassLoader classLoader = application.getClassLoader();
-
-            return classLoader.loadClass(ObjectNameGenerator.class, strategyClass).getDeclaredConstructor().newInstance();
-        } catch (Throwable th) {
-            LOGGER.error("Error in " + getClass().getName(), th);
-
-            JOptionPane.showMessageDialog(
-                    view,
-                    "Naming Strategy Initialization Error: " + th.getMessage(),
-                    "Naming Strategy Initialization Error",
-                    JOptionPane.ERROR_MESSAGE);
-            return null;
-        }
-    }
-
-    @Override
-    public Component getView() {
-        return view;
-    }
-
-    public void startup() {
-        // show dialog even on empty DataMap, as custom generation may still take
-        // advantage of it
-
-        view = new InferRelationshipsDialog(application, entitySelector.getView());
-        initBindings();
-
-        view.pack();
-        view.setModal(true);
-        centerView();
-        makeCloseableOnEscape();
-        view.setVisible(true);
-
-    }
-
-    protected void initBindings() {
-        view.getCancelButton().addActionListener(e -> cancelAction());
-        view.getGenerateButton().addActionListener(e -> generateAction());
-        addPropertyChangeListener(evt -> entitySelectedAction());
-        view.getStrategyCombo().addActionListener(e -> strategyComboAction());
-    }
-
-    public void entitySelectedAction() {
-        int size = getSelectedEntitiesSize();
-        String label;
-
-        if (size == 0) {
-            label = "No DbRelationships selected";
-        } else if (size == 1) {
-            label = "One DbRelationships selected";
-        } else {
-            label = size + " DbRelationships selected";
-        }
-
-        view.getEntityCount().setText(label);
-    }
-
-    public void strategyComboAction() {
-        try {
-
-            String strategyClass = (String) view.getStrategyCombo().getSelectedItem();
-
-            this.strategy = createNamingStrategy(strategyClass);
-
-            // Be user-friendly and update preferences with specified strategy
-            if (strategy == null) {
-                return;
-            }
-            NameGeneratorPreferences.getInstance().addToLastUsedStrategies(application, strategyClass);
-            view.getStrategyCombo().setModel(
-                    new DefaultComboBoxModel<>(NameGeneratorPreferences.getInstance().getLastUsedStrategies(application)));
-        } catch (Throwable th) {
-            LOGGER.error("Error in " + getClass().getName(), th);
-            return;
-        }
-
-        setNamingStrategy(strategy);
-        createNames();
-        entitySelector.initBindings();
-        view.setChoice(InferRelationshipsDialog.SELECT);
-    }
-
-    public void cancelAction() {
-        view.dispose();
-    }
-
-    public void generateAction() {
-
-        ProjectController mediator = application.getFrameController().getProjectController();
-        InferRelationshipsUndoableEdit undoableEdit = new InferRelationshipsUndoableEdit();
-
-        for (InferredRelationship temp : selectedEntities) {
-            DbRelationship rel = new DbRelationship(uniqueRelName(temp.getSource(), temp
-                    .getName()));
-
-            DbRelationshipEvent e = DbRelationshipEvent.ofAdd(application.getFrameController().getView(), rel, temp
-                    .getSource());
-            mediator.fireDbRelationshipEvent(e);
-
-            rel.setSourceEntity(temp.getSource());
-            rel.setTargetEntityName(temp.getTarget());
-            DbJoin join = new DbJoin(rel, temp.getJoinSource().getName(), temp
-                    .getJoinTarget()
-                    .getName());
-            rel.addJoin(join);
-            rel.setToMany(temp.isToMany());
-            temp.getSource().addRelationship(rel);
-
-            undoableEdit.addEdit(new CreateRelationshipUndoableEdit(application.getFrameController().getProjectController(), temp.getSource(), new DbRelationship[]{rel}));
-        }
-        JOptionPane.showMessageDialog(view, getSelectedEntitiesSize() + " relationships generated");
-        view.dispose();
-    }
-
-    private String uniqueRelName(DbEntity entity, String preferredName) {
-        int currentSuffix = 1;
-        String relName = preferredName;
-
-        while (entity.getRelationship(relName) != null
-                || entity.getAttribute(relName) != null) {
-            relName = preferredName + currentSuffix;
-            currentSuffix++;
-        }
-        return relName;
-    }
-
-    public String getJoin(InferredRelationship irItem) {
-        return irItem.getJoinSource().getName()
-                + " : "
-                + irItem.getJoinTarget().getName();
-    }
-
-    public String getToMany(InferredRelationship irItem) {
-        if (irItem.isToMany()) {
-            return "to many";
-        } else {
-            return "to one";
-        }
-    }
-
-    public boolean updateSelection(Predicate<InferredRelationship> predicate) {
-        boolean modified = false;
-
-        for (InferredRelationship entity : inferredRelationships) {
-            boolean select = predicate.test(entity);
-
-            if (select) {
-                if (selectedEntities.add(entity)) {
-                    modified = true;
-                }
-            } else {
-                if (selectedEntities.remove(entity)) {
-                    modified = true;
-                }
-            }
-        }
-
-        if (modified) {
-            firePropertyChange();
-        }
-
-        return modified;
-    }
-}
\ No newline at end of file
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/autorelationship/InferRelationshipsDialog.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/autorelationship/InferRelationshipsDialog.java
index c6ed372a8..a45f3d44a 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/autorelationship/InferRelationshipsDialog.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/autorelationship/InferRelationshipsDialog.java
@@ -18,56 +18,108 @@
  ****************************************************************/
 package org.apache.cayenne.modeler.ui.autorelationship;
 
-import org.apache.cayenne.modeler.Application;
+import org.apache.cayenne.dbsync.naming.ObjectNameGenerator;
+import org.apache.cayenne.map.DataMap;
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.map.DbJoin;
+import org.apache.cayenne.map.DbRelationship;
+import org.apache.cayenne.modeler.event.model.DbRelationshipEvent;
+import org.apache.cayenne.modeler.service.classloader.ModelerClassLoader;
+import org.apache.cayenne.modeler.toolkit.ProjectDialog;
+import org.apache.cayenne.modeler.project.ProjectSession;
+import org.apache.cayenne.modeler.undo.CreateRelationshipUndoableEdit;
+import org.apache.cayenne.modeler.undo.InferRelationshipsUndoableEdit;
 import org.apache.cayenne.modeler.util.NameGeneratorPreferences;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import javax.swing.Box;
 import javax.swing.DefaultComboBoxModel;
 import javax.swing.JButton;
 import javax.swing.JComboBox;
-import javax.swing.JDialog;
 import javax.swing.JLabel;
+import javax.swing.JOptionPane;
 import javax.swing.JPanel;
 import java.awt.BorderLayout;
-import java.awt.Component;
 import java.awt.Container;
 import java.awt.FlowLayout;
+import java.awt.Window;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.function.Predicate;
 
-public class InferRelationshipsDialog extends JDialog {
+/**
+ * Modal dialog that scans a {@link DataMap}'s DbEntities for likely missing
+ * DbRelationships (FK columns named "{TARGET}_ID") and offers the user a list to confirm.
+ */
+public class InferRelationshipsDialog extends ProjectDialog {
 
+    private static final Logger LOGGER = LoggerFactory.getLogger(InferRelationshipsDialog.class);
+    public static final String SELECTED_PROPERTY = "selected";
     public static final int SELECT = 1;
     public static final int CANCEL = 0;
 
-    protected int choice;
+    private final DataMap dataMap;
+    private final List<InferredRelationship> inferredRelationships = new ArrayList<>();
+    private final List<DbEntity> entities;
+    private final Set<InferredRelationship> selectedEntities = new HashSet<>();
+    private final InferRelationshipsTabController entitySelector;
 
-    protected JButton generateButton;
-    protected JButton cancelButton;
-    protected JLabel entityCount;
-    protected JLabel strategyLabel;
+    private final JButton generateButton;
+    private final JButton cancelButton;
+    private final JLabel entityCount;
+    private final JComboBox<String> strategyCombo;
 
-    protected JComboBox<String> strategyCombo;
+    private ObjectNameGenerator strategy;
+    private PropertyChangeSupport propertyChangeSupport;
+
+    @SuppressWarnings("unused")
+    private int choice = CANCEL;
+
+    public InferRelationshipsDialog(ProjectSession session, Window owner, DataMap dataMap) {
+        super(session, owner, "Infer Relationships", ModalityType.APPLICATION_MODAL);
+        this.dataMap = dataMap;
+        this.entities = new ArrayList<>(dataMap.getDbEntities());
+        this.strategy = createNamingStrategy(NameGeneratorPreferences
+                .getInstance()
+                .getLastUsedStrategies(app())
+                .get(0));
+        setRelationships();
 
-    public InferRelationshipsDialog(Application application, Component entitySelectorPanel) {
-        super(application.getFrameController().getView());
         this.generateButton = new JButton("Create DbRelationships");
         this.cancelButton = new JButton("Cancel");
         this.entityCount = new JLabel("No DbRelationships selected");
-        entityCount.setFont(entityCount.getFont().deriveFont(10f));
+        this.entityCount.setFont(entityCount.getFont().deriveFont(10f));
+        this.strategyCombo = new JComboBox<>();
+        this.strategyCombo.setEditable(true);
 
-        getRootPane().setDefaultButton(generateButton);
+        this.entitySelector = new InferRelationshipsTabController(this);
 
-        this.strategyCombo = new JComboBox<>();
-        strategyCombo.setEditable(true);
-        this.strategyLabel = new JLabel("Naming Strategy:  ");
+        initLayout();
+        initBindings();
+    }
+
+    public void open() {
+        // show dialog even on empty DataMap, as custom generation may still take advantage of it
+        pack();
+        centerOnOwner();
+        makeCloseableOnEscape();
+        setVisible(true);
+    }
+
+    private void initLayout() {
+        getRootPane().setDefaultButton(generateButton);
 
-        // assemble
         JPanel strategyPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
-        strategyPanel.add(strategyLabel);
+        strategyPanel.add(new JLabel("Naming Strategy:  "));
         strategyPanel.add(strategyCombo);
 
-        JPanel messages = new JPanel(new BorderLayout());
-        messages.add(entityCount, BorderLayout.WEST);
-
         JPanel buttons = new JPanel(new FlowLayout(FlowLayout.RIGHT));
         buttons.add(entityCount);
         buttons.add(Box.createHorizontalStrut(50));
@@ -77,34 +129,289 @@ public class InferRelationshipsDialog extends JDialog {
         Container contentPane = getContentPane();
         contentPane.setLayout(new BorderLayout());
         contentPane.add(strategyPanel, BorderLayout.NORTH);
-        contentPane.add(entitySelectorPanel, BorderLayout.CENTER);
+        contentPane.add(entitySelector.getView(), BorderLayout.CENTER);
         contentPane.add(buttons, BorderLayout.SOUTH);
 
-        this.choice = CANCEL;
+        strategyCombo.setModel(new DefaultComboBoxModel<>(
+                NameGeneratorPreferences.getInstance().getLastUsedStrategies(app())));
+    }
 
-        strategyCombo.setModel(new DefaultComboBoxModel(
-                NameGeneratorPreferences.getInstance().getLastUsedStrategies(application)));
+    private void initBindings() {
+        cancelButton.addActionListener(e -> dispose());
+        generateButton.addActionListener(e -> generateAction());
+        addSelectionChangeListener(evt -> entitySelectedAction());
+        strategyCombo.addActionListener(e -> strategyComboAction());
+    }
+
+    // ---------- model methods (formerly on the controller) ----------
 
-        setTitle("Infer Relationships");
+    public List<InferredRelationship> getEntities() {
+        return inferredRelationships;
     }
 
-    public void setChoice(int choice) {
-        this.choice = choice;
+    public DataMap getDataMap() {
+        return dataMap;
     }
 
-    public JButton getCancelButton() {
-        return cancelButton;
+    public boolean isSelected(InferredRelationship rel) {
+        return selectedEntities.contains(rel);
     }
 
-    public JButton getGenerateButton() {
-        return generateButton;
+    public void setSelected(InferredRelationship rel, boolean selectedFlag) {
+        if (selectedFlag) {
+            if (selectedEntities.add(rel)) firePropertyChange();
+        } else {
+            if (selectedEntities.remove(rel)) firePropertyChange();
+        }
     }
 
-    public JLabel getEntityCount() {
-        return entityCount;
+    public int getSelectedEntitiesSize() {
+        return selectedEntities.size();
+    }
+
+    public boolean updateSelection(Predicate<InferredRelationship> predicate) {
+        boolean modified = false;
+        for (InferredRelationship rel : inferredRelationships) {
+            if (predicate.test(rel)) {
+                if (selectedEntities.add(rel)) modified = true;
+            } else {
+                if (selectedEntities.remove(rel)) modified = true;
+            }
+        }
+        if (modified) firePropertyChange();
+        return modified;
+    }
+
+    public String getJoin(InferredRelationship rel) {
+        return rel.getJoinSource().getName() + " : " + rel.getJoinTarget().getName();
+    }
+
+    public String getToMany(InferredRelationship rel) {
+        return rel.isToMany() ? "to many" : "to one";
+    }
+
+    private void firePropertyChange() {
+        if (propertyChangeSupport != null) {
+            propertyChangeSupport.firePropertyChange(SELECTED_PROPERTY, null, null);
+        }
+    }
+
+    private void addSelectionChangeListener(PropertyChangeListener listener) {
+        if (propertyChangeSupport == null) {
+            propertyChangeSupport = new PropertyChangeSupport(this);
+        }
+        propertyChangeSupport.addPropertyChangeListener(SELECTED_PROPERTY, listener);
+    }
+
+    private void setRelationships() {
+        inferredRelationships.clear();
+        for (DbEntity entity : entities) {
+            createRelationships(entity);
+        }
+        createJoins();
+        createNames();
+    }
+
+    private void createRelationships(DbEntity entity) {
+        for (DbAttribute attribute : entity.getAttributes()) {
+            String name = attribute.getName();
+            if (name.length() < 4) {
+                continue;
+            }
+            if (!name.substring(name.length() - 3).equalsIgnoreCase("_ID")) {
+                continue;
+            }
+
+            String baseName = name.substring(0, name.length() - 3);
+            for (DbEntity targetEntity : entities) {
+                // TODO: should we handle relationships to self??
+                if (targetEntity == entity) {
+                    continue;
+                }
+                if (baseName.equalsIgnoreCase(targetEntity.getName())
+                        && !attribute.isPrimaryKey()
+                        && !targetEntity.getAttributes().isEmpty()) {
+
+                    if (!attribute.isForeignKey()) {
+                        InferredRelationship myir = new InferredRelationship();
+                        myir.setSource(entity);
+                        myir.setTarget(targetEntity);
+                        inferredRelationships.add(myir);
+                    }
+                    createReverseRelationship(targetEntity, entity);
+                }
+            }
+        }
+    }
+
+    private void createReverseRelationship(DbEntity eSource, DbEntity eTarget) {
+        InferredRelationship myir = new InferredRelationship();
+        for (DbRelationship relationship : eSource.getRelationships()) {
+            for (DbJoin join : relationship.getJoins()) {
+                if (join.getSource().getEntity().equals(eSource) && join.getTarget().getEntity().equals(eTarget)) {
+                    return;
+                }
+            }
+        }
+        myir.setSource(eSource);
+        myir.setTarget(eTarget);
+        inferredRelationships.add(myir);
+    }
+
+    private DbAttribute getJoinAttribute(DbEntity sEntity, DbEntity tEntity) {
+        if (sEntity.getAttributes().size() == 1) {
+            return sEntity.getAttributes().iterator().next();
+        }
+        for (DbAttribute attr : sEntity.getAttributes()) {
+            if (attr.getName().equalsIgnoreCase(tEntity.getName() + "_ID")) {
+                return attr;
+            }
+        }
+        for (DbAttribute attr : sEntity.getAttributes()) {
+            if (attr.getName().equalsIgnoreCase(sEntity.getName() + "_ID") && !attr.isPrimaryKey()) {
+                return attr;
+            }
+        }
+        for (DbAttribute attr : sEntity.getAttributes()) {
+            if (attr.isPrimaryKey()) {
+                return attr;
+            }
+        }
+        return null;
+    }
+
+    private void createJoins() {
+        Iterator<InferredRelationship> it = inferredRelationships.iterator();
+        while (it.hasNext()) {
+            InferredRelationship inferred = it.next();
+
+            DbAttribute src = getJoinAttribute(inferred.getSource(), inferred.getTarget());
+            if (src == null) {
+                // see CAY-1405 for the map that caused this issue
+                it.remove();
+                continue;
+            }
+
+            DbAttribute target = getJoinAttribute(inferred.getTarget(), inferred.getSource());
+            if (target == null) {
+                it.remove();
+                continue;
+            }
+
+            inferred.setJoinSource(src);
+            if (src.isPrimaryKey()) {
+                inferred.setToMany(true);
+            }
+            inferred.setJoinTarget(target);
+        }
+    }
+
+    private void createNames() {
+        for (InferredRelationship myir : inferredRelationships) {
+            DbRelationship localRelationship = new DbRelationship();
+            localRelationship.setToMany(myir.isToMany());
+
+            if (myir.getJoinSource().isPrimaryKey()) {
+                localRelationship.addJoin(new DbJoin(localRelationship,
+                        myir.getJoinSource().getName(), myir.getJoinTarget().getName()));
+                localRelationship.setSourceEntity(myir.getSource());
+                localRelationship.setTargetEntityName(myir.getTarget().getName());
+            } else {
+                localRelationship.addJoin(new DbJoin(localRelationship,
+                        myir.getJoinTarget().getName(), myir.getJoinSource().getName()));
+                localRelationship.setSourceEntity(myir.getTarget());
+                localRelationship.setTargetEntityName(myir.getSource().getName());
+            }
+
+            myir.setName(strategy.relationshipName(localRelationship));
+        }
+    }
+
+    private ObjectNameGenerator createNamingStrategy(String strategyClass) {
+        try {
+            ModelerClassLoader classLoader = app().getClassLoader();
+            return classLoader.loadClass(ObjectNameGenerator.class, strategyClass)
+                    .getDeclaredConstructor().newInstance();
+        } catch (Throwable th) {
+            LOGGER.error("Error in " + getClass().getName(), th);
+            JOptionPane.showMessageDialog(
+                    this,
+                    "Naming Strategy Initialization Error: " + th.getMessage(),
+                    "Naming Strategy Initialization Error",
+                    JOptionPane.ERROR_MESSAGE);
+            return null;
+        }
+    }
+
+    private void entitySelectedAction() {
+        int size = getSelectedEntitiesSize();
+        String label;
+        if (size == 0) {
+            label = "No DbRelationships selected";
+        } else if (size == 1) {
+            label = "One DbRelationships selected";
+        } else {
+            label = size + " DbRelationships selected";
+        }
+        entityCount.setText(label);
+    }
+
+    private void strategyComboAction() {
+        try {
+            String strategyClass = (String) strategyCombo.getSelectedItem();
+            this.strategy = createNamingStrategy(strategyClass);
+
+            if (strategy == null) {
+                return;
+            }
+            // be user-friendly: update preferences with the chosen strategy
+            NameGeneratorPreferences.getInstance().addToLastUsedStrategies(app(), strategyClass);
+            strategyCombo.setModel(new DefaultComboBoxModel<>(
+                    NameGeneratorPreferences.getInstance().getLastUsedStrategies(app())));
+        } catch (Throwable th) {
+            LOGGER.error("Error in " + getClass().getName(), th);
+            return;
+        }
+
+        createNames();
+        entitySelector.initBindings();
+        choice = SELECT;
+    }
+
+    private void generateAction() {
+        InferRelationshipsUndoableEdit undoableEdit = new InferRelationshipsUndoableEdit();
+
+        for (InferredRelationship temp : selectedEntities) {
+            DbRelationship rel = new DbRelationship(uniqueRelName(temp.getSource(), temp.getName()));
+
+            DbRelationshipEvent e = DbRelationshipEvent.ofAdd(
+                    app().getFrame(), rel, temp.getSource());
+            session().fireDbRelationshipEvent(e);
+
+            rel.setSourceEntity(temp.getSource());
+            rel.setTargetEntityName(temp.getTarget());
+            DbJoin join = new DbJoin(rel,
+                    temp.getJoinSource().getName(),
+                    temp.getJoinTarget().getName());
+            rel.addJoin(join);
+            rel.setToMany(temp.isToMany());
+            temp.getSource().addRelationship(rel);
+
+            undoableEdit.addEdit(new CreateRelationshipUndoableEdit(
+                    session(), temp.getSource(), new DbRelationship[]{rel}));
+        }
+        JOptionPane.showMessageDialog(this, getSelectedEntitiesSize() + " relationships generated");
+        dispose();
     }
 
-    public JComboBox<String> getStrategyCombo() {
-        return strategyCombo;
+    private String uniqueRelName(DbEntity entity, String preferredName) {
+        int currentSuffix = 1;
+        String relName = preferredName;
+        while (entity.getRelationship(relName) != null
+                || entity.getAttribute(relName) != null) {
+            relName = preferredName + currentSuffix;
+            currentSuffix++;
+        }
+        return relName;
     }
-}
\ No newline at end of file
+}
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/autorelationship/InferRelationshipsTabController.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/autorelationship/InferRelationshipsTabController.java
index 51ac96019..08e6cbaed 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/autorelationship/InferRelationshipsTabController.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/autorelationship/InferRelationshipsTabController.java
@@ -18,31 +18,33 @@
  ****************************************************************/
 package org.apache.cayenne.modeler.ui.autorelationship;
 
-import org.apache.cayenne.modeler.mvc.ChildController;
 import org.apache.cayenne.modeler.toolkit.table.TableSizer;
 
 import javax.swing.table.AbstractTableModel;
-import java.awt.*;
 import java.util.List;
 
-public class InferRelationshipsTabController extends ChildController<InferRelationshipsController> {
+/**
+ * Builds and binds the entity-selection table for {@link InferRelationshipsDialog}.
+ * Lives as a sibling helper rather than an inner class to keep the dialog scannable.
+ */
+public class InferRelationshipsTabController {
 
     private static final String[] COLUMN_HEADERS = {"", "Source", "Target", "Join", "Name", "To Many"};
     private static final Class<?>[] COLUMN_CLASSES = {
             Boolean.class, String.class, String.class, String.class, String.class, String.class
     };
 
+    private final InferRelationshipsDialog parent;
     private final InferRelationshipsPanel view;
     private AbstractTableModel tableModel;
 
-    public InferRelationshipsTabController(InferRelationshipsController parent) {
-        super(parent);
+    public InferRelationshipsTabController(InferRelationshipsDialog parent) {
+        this.parent = parent;
         this.view = new InferRelationshipsPanel();
         initBindings();
     }
 
-    @Override
-    public Component getView() {
+    public InferRelationshipsPanel getView() {
         return view;
     }
 
@@ -108,6 +110,4 @@ public class InferRelationshipsTabController extends ChildController<InferRelati
             tableModel.fireTableDataChanged();
         }
     }
-
 }
-
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/confirmremove/ConfirmRemoveDialog.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/confirmremove/ConfirmRemoveDialog.java
index 240d3823f..ff4eac655 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/confirmremove/ConfirmRemoveDialog.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/confirmremove/ConfirmRemoveDialog.java
@@ -56,7 +56,7 @@ public class ConfirmRemoveDialog {
         JOptionPane pane = new JOptionPane(message, JOptionPane.QUESTION_MESSAGE);
         pane.setOptions(new Object[]{DELETE, "Cancel"});
         pane.setInitialValue(DELETE);
-        pane.createDialog(application.getFrameController().getView(), "Confirm Delete").setVisible(true);
+        pane.createDialog(application.getFrame(), "Confirm Delete").setVisible(true);
 
         shouldDelete = DELETE.equals(pane.getValue());
 
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/datasource/DataSourceController.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/datasource/DataSourceController.java
deleted file mode 100644
index 14dc8cc77..000000000
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/datasource/DataSourceController.java
+++ /dev/null
@@ -1,254 +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.ui.datasource;
-
-import org.apache.cayenne.dba.DbAdapter;
-import org.apache.cayenne.modeler.service.classloader.ModelerClassLoader;
-import org.apache.cayenne.modeler.ui.preferences.dbconnector.DBConnectorEditorController;
-import org.apache.cayenne.modeler.ui.project.ProjectController;
-import org.apache.cayenne.modeler.ui.preferences.PreferenceDialogController;
-import org.apache.cayenne.modeler.mvc.ChildController;
-import org.apache.cayenne.modeler.dbconnector.DBConnector;
-import org.apache.cayenne.modeler.dbconnector.DBConnectors;
-import org.apache.cayenne.modeler.pref.DataMapPrefs;
-import org.apache.cayenne.modeler.pref.GeneralPrefs;
-
-import javax.sql.DataSource;
-import javax.swing.*;
-import java.awt.*;
-import java.sql.Connection;
-import java.sql.SQLException;
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.LinkedHashMap;
-import java.util.Map;
-import java.util.Set;
-
-public class DataSourceController extends ChildController<ProjectController> {
-
-    private final DataSourceView view;
-    private final DBConnectorEditorController editorController;
-
-    private Map<String, DBConnector> connectors;
-    private String dataSourceKey;
-    // a clone of an object selected from the dropdown, as we need to allow local temporary modifications
-    private DBConnector connector;
-    private DbAdapter adapter;
-    private DataSource dataSource;
-    private boolean canceled;
-
-    public DataSourceController(ProjectController parent, String title) {
-        super(parent);
-
-        this.connector = new DBConnector();
-        this.editorController = new DBConnectorEditorController(this);
-        this.view = new DataSourceView(
-                this,
-                getApplication().getFrameController().getView(),
-                editorController.getView());
-        this.view.setTitle(title);
-    }
-
-    private void initFavouriteDataSource() {
-        String favouriteDataSource = GeneralPrefs.of(parent.getApplication().getPreferencesRepository()).getFavouriteDataSource();
-        if (favouriteDataSource != null && connectors.containsKey(favouriteDataSource)) {
-            setSelectedDataSource(favouriteDataSource);
-            view.selectDataSource(dataSourceKey);
-        }
-    }
-
-    private DBConnector getConnectionInfoFromPreferences() {
-        DataMapPrefs dataMapPrefs = DataMapPrefs.of(parent.getApplication().getPreferencesRepository(), parent.getSelectedDataMap());
-        DBConnector connector = dataMapPrefs.getConnector();
-        return connector != null ? connector : new DBConnector();
-    }
-
-    void setSelectedDataSource(String dataSourceKey) {
-        this.dataSourceKey = dataSourceKey;
-
-        // update a clone object that will be used to obtain connection...
-        DBConnector currentConnector = connectors.get(dataSourceKey);
-        if (currentConnector != null) {
-            currentConnector.copyTo(connector);
-        } else {
-            connector = new DBConnector();
-        }
-        editorController.setConnector(connector);
-    }
-
-    /**
-     * Main action method that pops up a dialog asking for user selection.
-     * Returns true if the selection was confirmed, false - if canceled.
-     */
-    public boolean startupAction() {
-        this.canceled = true;
-        refreshDataSources();
-        initFavouriteDataSource();
-
-        final DataMapPrefs dataMapPrefs = DataMapPrefs.of(parent.getApplication().getPreferencesRepository(), parent.getSelectedDataMap());
-        if (dataMapPrefs.hasDbAdapter()) {
-            getConnectionInfoFromPreferences().copyTo(connector);
-        }
-        view.pack();
-        view.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
-        view.setModal(true);
-        editorController.setConnector(connector);
-        makeCloseableOnEscape();
-        centerView();
-        view.setVisible(true);
-
-        return !canceled;
-    }
-
-    public DBConnector getConnector() {
-        return connector;
-    }
-
-    /**
-     * Tests that the entered information is valid and can be used to open a
-     * connection. Does not store the open connection.
-     */
-    void okClicked() {
-        DBConnector info = getConnector();
-        ModelerClassLoader classLoader = getApplication().getClassLoader();
-
-        // doing connection testing...
-        try {
-            try {
-                this.adapter = info.makeAdapter(classLoader, getApplication().getDbAdapterFactory());
-                this.dataSource = info.makeDataSource(classLoader);
-            } catch (SQLException ignore) {
-                showNoConnectorDialog("Unable to load driver '" + info.getJdbcDriver() + "'");
-                return;
-            }
-
-            // Test connection
-            try (Connection connection = dataSource.getConnection()) {
-            }
-        } catch (Throwable th) {
-            reportError("Connection Error", th);
-            return;
-        }
-        onClose(false);
-    }
-
-    void cancelClicked() {
-        onClose(true);
-    }
-
-    protected void onClose(final boolean canceled) {
-        // set success flag, and unblock the caller...
-        this.canceled = canceled;
-        view.dispose();
-        if (!canceled) {
-            GeneralPrefs.of(parent.getApplication().getPreferencesRepository()).setFavouriteDataSource(dataSourceKey);
-        }
-    }
-
-    /**
-     * Opens preferences panel to allow configuration of DataSource presets.
-     */
-    void dataSourceConfigClicked() {
-        DBConnectors registry = getApplication().getDbConnectors();
-        Set<String> before = new HashSet<>(registry.getAll().keySet());
-
-        PreferenceDialogController prefs = new PreferenceDialogController(this);
-        prefs.showDBConnectorEditorAction(dataSourceKey);
-
-        // auto-select any newly-added DataSource (last new wins, matching prior commit-order behavior)
-        for (String name : registry.getAll().keySet()) {
-            if (!before.contains(name)) {
-                dataSourceKey = name;
-            }
-        }
-        refreshDataSources();
-    }
-
-    /**
-     * Opens preferences panel to allow configuration of classpath.
-     */
-    public void classPathConfigAction() {
-        PreferenceDialogController prefs = new PreferenceDialogController(this);
-        prefs.showClassPathEditorAction();
-        refreshDataSources();
-    }
-
-    @Override
-    public Component getView() {
-        return view;
-    }
-
-    private void refreshDataSources() {
-        DBConnectors registry = getApplication().getDbConnectors();
-        this.connectors = registry.getAll();
-
-        // 1.2 migration fix - update data source adapter names
-        final String _12package = "org.objectstyle.cayenne.";
-        for (Map.Entry<String, DBConnector> e : new LinkedHashMap<>(connectors).entrySet()) {
-            DBConnector info = e.getValue();
-            if (info.getDbAdapter() != null && info.getDbAdapter().startsWith(_12package)) {
-                info.setDbAdapter("org.apache.cayenne." + info.getDbAdapter().substring(_12package.length()));
-                registry.put(e.getKey(), info);
-            }
-        }
-
-        final String[] keys = connectors.keySet().toArray(new String[0]);
-        Arrays.sort(keys);
-        view.setDataSources(keys);
-
-        String key = null;
-        if (dataSourceKey == null || !connectors.containsKey(dataSourceKey)) {
-            if (keys.length > 0) {
-                key = keys[0];
-            }
-        }
-
-        setSelectedDataSource(key != null ? key : dataSourceKey);
-        view.selectDataSource(dataSourceKey);
-    }
-
-    protected void showNoConnectorDialog(String message) {
-        String[] options = {"Setup driver", "Cancel"};
-
-        int selection = JOptionPane.showOptionDialog(view,
-                message,
-                "Configuration error",
-                JOptionPane.OK_CANCEL_OPTION,
-                JOptionPane.ERROR_MESSAGE,
-                null,
-                options,
-                options[0]);
-
-        if (selection == 0) {
-            classPathConfigAction();
-        }
-    }
-
-    public DataSource getDataSource() {
-        return dataSource;
-    }
-
-    /**
-     * Returns configured DbAdapter.
-     */
-    public DbAdapter getAdapter() {
-        return adapter;
-    }
-}
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/datasource/DataSourceDialog.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/datasource/DataSourceDialog.java
new file mode 100644
index 000000000..21311de5f
--- /dev/null
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/datasource/DataSourceDialog.java
@@ -0,0 +1,278 @@
+/*****************************************************************
+ *   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.ui.datasource;
+
+import com.jgoodies.forms.builder.PanelBuilder;
+import com.jgoodies.forms.layout.CellConstraints;
+import com.jgoodies.forms.layout.FormLayout;
+import org.apache.cayenne.dba.DbAdapter;
+import org.apache.cayenne.modeler.dbconnector.DBConnector;
+import org.apache.cayenne.modeler.dbconnector.DBConnectors;
+import org.apache.cayenne.modeler.pref.DataMapPrefs;
+import org.apache.cayenne.modeler.pref.GeneralPrefs;
+import org.apache.cayenne.modeler.service.classloader.ModelerClassLoader;
+import org.apache.cayenne.modeler.toolkit.ProjectDialog;
+import org.apache.cayenne.modeler.ui.preferences.PreferenceDialog;
+import org.apache.cayenne.modeler.ui.preferences.dbconnector.DBConnectorEditor;
+import org.apache.cayenne.modeler.project.ProjectSession;
+
+import javax.sql.DataSource;
+import javax.swing.DefaultComboBoxModel;
+import javax.swing.JButton;
+import javax.swing.JComboBox;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import java.awt.BorderLayout;
+import java.awt.FlowLayout;
+import java.awt.Window;
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * Modal dialog for selecting/editing a saved DB Connector and testing the resulting
+ * connection. Returns true from {@link #startupAction()} when the user chose a working
+ * connector and clicked Continue, false on Cancel or test failure.
+ */
+public class DataSourceDialog extends ProjectDialog {
+
+    private final DBConnectorEditor editor;
+    private final JComboBox<String> dataSources;
+    private final JButton configButton;
+    private final JButton okButton;
+    private final JButton cancelButton;
+
+    private Map<String, DBConnector> connectors;
+    private String dataSourceKey;
+    // a clone of the object selected from the dropdown — local edits don't bleed into the registry
+    private DBConnector connector = new DBConnector();
+    private DbAdapter adapter;
+    private DataSource dataSource;
+    private boolean canceled;
+
+    public DataSourceDialog(ProjectSession session, Window owner, String title) {
+        super(session, owner, title, ModalityType.APPLICATION_MODAL);
+        this.editor = new DBConnectorEditor();
+        this.dataSources = new JComboBox<>();
+        this.configButton = new JButton("...");
+        this.configButton.setToolTipText("configure local DataSource");
+        this.okButton = new JButton("Continue");
+        this.cancelButton = new JButton("Cancel");
+
+        initLayout();
+        initBindings();
+    }
+
+    /**
+     * Pops up the dialog, blocks until closed, returns true if the user confirmed
+     * a working connection.
+     */
+    public boolean startupAction() {
+        canceled = true;
+        refreshDataSources();
+        initFavouriteDataSource();
+
+        DataMapPrefs dataMapPrefs = DataMapPrefs.of(app().getPreferencesRepository(), session().getSelectedDataMap());
+        if (dataMapPrefs.hasDbAdapter()) {
+            getConnectionInfoFromPreferences().copyTo(connector);
+        }
+
+        editor.setConnector(connector);
+
+        pack();
+        setDefaultCloseOperation(DISPOSE_ON_CLOSE);
+        centerOnOwner();
+        makeCloseableOnEscape();
+        setVisible(true);
+
+        return !canceled;
+    }
+
+    public DBConnector getConnector() {
+        return connector;
+    }
+
+    public DataSource getDataSource() {
+        return dataSource;
+    }
+
+    public DbAdapter getAdapter() {
+        return adapter;
+    }
+
+    private void initLayout() {
+        getRootPane().setDefaultButton(okButton);
+
+        CellConstraints cc = new CellConstraints();
+        PanelBuilder builder = new PanelBuilder(new FormLayout(
+                "20dlu:grow, pref, 3dlu, fill:max(150dlu;pref), 3dlu, fill:20dlu",
+                "p"));
+        builder.setDefaultDialogBorder();
+        builder.addLabel("Saved DataSources:", cc.xy(2, 1));
+        builder.add(dataSources, cc.xy(4, 1));
+        builder.add(configButton, cc.xy(6, 1));
+
+        JPanel buttons = new JPanel(new FlowLayout(FlowLayout.RIGHT));
+        buttons.add(cancelButton);
+        buttons.add(okButton);
+
+        getContentPane().setLayout(new BorderLayout());
+        getContentPane().add(builder.getPanel(), BorderLayout.NORTH);
+        getContentPane().add(editor, BorderLayout.CENTER);
+        getContentPane().add(buttons, BorderLayout.SOUTH);
+    }
+
+    private void initBindings() {
+        dataSources.addActionListener(e -> {
+            Object sel = dataSources.getSelectedItem();
+            setSelectedDataSource(sel != null ? sel.toString() : null);
+        });
+        cancelButton.addActionListener(e -> onClose(true));
+        okButton.addActionListener(e -> okClicked());
+        configButton.addActionListener(e -> dataSourceConfigClicked());
+    }
+
+    private void setSelectedDataSource(String dataSourceKey) {
+        this.dataSourceKey = dataSourceKey;
+
+        DBConnector currentConnector = connectors.get(dataSourceKey);
+        if (currentConnector != null) {
+            currentConnector.copyTo(connector);
+        } else {
+            connector = new DBConnector();
+        }
+        editor.setConnector(connector);
+    }
+
+    private void initFavouriteDataSource() {
+        String favourite = GeneralPrefs.of(app().getPreferencesRepository()).getFavouriteDataSource();
+        if (favourite != null && connectors.containsKey(favourite)) {
+            setSelectedDataSource(favourite);
+            dataSources.setSelectedItem(dataSourceKey);
+        }
+    }
+
+    private DBConnector getConnectionInfoFromPreferences() {
+        DataMapPrefs dataMapPrefs = DataMapPrefs.of(app().getPreferencesRepository(), session().getSelectedDataMap());
+        DBConnector c = dataMapPrefs.getConnector();
+        return c != null ? c : new DBConnector();
+    }
+
+    private void okClicked() {
+        DBConnector info = getConnector();
+        ModelerClassLoader classLoader = app().getClassLoader();
+
+        // doing connection testing...
+        try {
+            try {
+                this.adapter = info.makeAdapter(classLoader, app().getDbAdapterFactory());
+                this.dataSource = info.makeDataSource(classLoader);
+            } catch (SQLException ignore) {
+                showNoConnectorDialog("Unable to load driver '" + info.getJdbcDriver() + "'");
+                return;
+            }
+
+            // test connection
+            try (Connection connection = dataSource.getConnection()) {
+                // no-op, just verify open
+            }
+        } catch (Throwable th) {
+            reportError("Connection Error", th);
+            return;
+        }
+        onClose(false);
+    }
+
+    private void onClose(boolean canceled) {
+        this.canceled = canceled;
+        dispose();
+        if (!canceled) {
+            GeneralPrefs.of(app().getPreferencesRepository()).setFavouriteDataSource(dataSourceKey);
+        }
+    }
+
+    private void dataSourceConfigClicked() {
+        DBConnectors registry = app().getDbConnectors();
+        java.util.Set<String> before = new java.util.HashSet<>(registry.getAll().keySet());
+
+        new PreferenceDialog(app(), app().getFrame()).showDBConnectorEditorAction(dataSourceKey);
+
+        // auto-select any newly-added DataSource (last new wins, matching prior commit-order behavior)
+        for (String name : registry.getAll().keySet()) {
+            if (!before.contains(name)) {
+                dataSourceKey = name;
+            }
+        }
+        refreshDataSources();
+    }
+
+    private void classPathConfigAction() {
+        new PreferenceDialog(app(), app().getFrame()).showClassPathEditorAction();
+        refreshDataSources();
+    }
+
+    private void refreshDataSources() {
+        DBConnectors registry = app().getDbConnectors();
+        this.connectors = registry.getAll();
+
+        // 1.2 migration fix - update data source adapter names
+        String oldPackage = "org.objectstyle.cayenne.";
+        for (Map.Entry<String, DBConnector> e : new LinkedHashMap<>(connectors).entrySet()) {
+            DBConnector info = e.getValue();
+            if (info.getDbAdapter() != null && info.getDbAdapter().startsWith(oldPackage)) {
+                info.setDbAdapter("org.apache.cayenne." + info.getDbAdapter().substring(oldPackage.length()));
+                registry.put(e.getKey(), info);
+            }
+        }
+
+        String[] keys = connectors.keySet().toArray(new String[0]);
+        Arrays.sort(keys);
+        dataSources.setModel(new DefaultComboBoxModel<>(keys));
+
+        String key = null;
+        if (dataSourceKey == null || !connectors.containsKey(dataSourceKey)) {
+            if (keys.length > 0) {
+                key = keys[0];
+            }
+        }
+
+        setSelectedDataSource(key != null ? key : dataSourceKey);
+        dataSources.setSelectedItem(dataSourceKey);
+    }
+
+    private void showNoConnectorDialog(String message) {
+        String[] options = {"Setup driver", "Cancel"};
+
+        int selection = JOptionPane.showOptionDialog(this,
+                message,
+                "Configuration error",
+                JOptionPane.OK_CANCEL_OPTION,
+                JOptionPane.ERROR_MESSAGE,
+                null,
+                options,
+                options[0]);
+
+        if (selection == 0) {
+            classPathConfigAction();
+        }
+    }
+}
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/datasource/DataSourceView.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/datasource/DataSourceView.java
deleted file mode 100644
index 41882b0bc..000000000
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/datasource/DataSourceView.java
+++ /dev/null
@@ -1,82 +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.ui.datasource;
-
-import com.jgoodies.forms.builder.PanelBuilder;
-import com.jgoodies.forms.layout.CellConstraints;
-import com.jgoodies.forms.layout.FormLayout;
-
-import javax.swing.*;
-import java.awt.*;
-
-
-public class DataSourceView extends JDialog {
-
-    private final JComboBox<String> dataSources;
-
-    public DataSourceView(DataSourceController controller, Frame parent, Component editorView) {
-        super(parent);
-
-        this.dataSources = new JComboBox<>();
-        JButton configButton = new JButton("...");
-        configButton.setToolTipText("configure local DataSource");
-        JButton okButton = new JButton("Continue");
-        JButton cancelButton = new JButton("Cancel");
-
-        getRootPane().setDefaultButton(okButton);
-
-        dataSources.addActionListener(e -> {
-            Object sel = dataSources.getSelectedItem();
-            controller.setSelectedDataSource(sel != null ? sel.toString() : null);
-        });
-        cancelButton.addActionListener(e -> controller.cancelClicked());
-        okButton.addActionListener(e -> controller.okClicked());
-        configButton.addActionListener(e -> controller.dataSourceConfigClicked());
-
-        CellConstraints cc = new CellConstraints();
-        PanelBuilder builder = new PanelBuilder(new FormLayout(
-                "20dlu:grow, pref, 3dlu, fill:max(150dlu;pref), 3dlu, fill:20dlu",
-                "p"));
-        builder.setDefaultDialogBorder();
-
-        builder.addLabel("Saved DataSources:", cc.xy(2, 1));
-        builder.add(dataSources, cc.xy(4, 1));
-        builder.add(configButton, cc.xy(6, 1));
-
-        JPanel buttons = new JPanel(new FlowLayout(FlowLayout.RIGHT));
-        buttons.add(cancelButton);
-        buttons.add(okButton);
-
-        getContentPane().setLayout(new BorderLayout());
-        getContentPane().add(builder.getPanel(), BorderLayout.NORTH);
-        getContentPane().add(editorView, BorderLayout.CENTER);
-        getContentPane().add(buttons, BorderLayout.SOUTH);
-
-        setTitle("DB Connection Info");
-    }
-
-    public void setDataSources(String[] keys) {
-        dataSources.setModel(new DefaultComboBoxModel<>(keys));
-    }
-
-    public void selectDataSource(String key) {
-        dataSources.setSelectedItem(key);
-    }
-}
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/dbactionoptions/DbActionOptionsDialog.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/dbactionoptions/DbActionOptionsDialog.java
index ddc572a4f..1a8c3a397 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/dbactionoptions/DbActionOptionsDialog.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/dbactionoptions/DbActionOptionsDialog.java
@@ -21,7 +21,8 @@ package org.apache.cayenne.modeler.ui.dbactionoptions;
 
 import com.jgoodies.forms.builder.DefaultFormBuilder;
 import com.jgoodies.forms.layout.FormLayout;
-import org.apache.cayenne.modeler.toolkit.dialog.CMDialog;
+import org.apache.cayenne.modeler.Application;
+import org.apache.cayenne.modeler.toolkit.AppDialog;
 
 import javax.swing.DefaultComboBoxModel;
 import javax.swing.JButton;
@@ -31,11 +32,11 @@ import javax.swing.JLabel;
 import javax.swing.JPanel;
 import java.awt.BorderLayout;
 import java.awt.FlowLayout;
-import java.awt.Frame;
 import java.awt.HeadlessException;
+import java.awt.Window;
 import java.util.Collection;
 
-public class DbActionOptionsDialog extends CMDialog {
+public class DbActionOptionsDialog extends AppDialog {
 
     public static final int CANCEL = 0;
     public static final int SELECT = 1;
@@ -49,17 +50,18 @@ public class DbActionOptionsDialog extends CMDialog {
     private JButton cancelButton;
     protected JPanel buttons;
 
-    public DbActionOptionsDialog(Frame owner, String title, Collection<String> catalogs, Collection<String> schemas,
+    public DbActionOptionsDialog(Application application, Window owner, String title,
+                                 Collection<String> catalogs, Collection<String> schemas,
                                  String currentCatalog, String currentSchema) throws HeadlessException {
-        super(owner, title, false);
+        super(application, owner, title, ModalityType.APPLICATION_MODAL);
+        makeCloseableOnEscape();
         init();
         initController();
         initFromModel(catalogs, schemas, currentCatalog, currentSchema);
 
         pack();
         setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
-        setModal(true);
-        centerWindow();
+        centerOnOwner();
     }
 
     protected void init() {
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/dbgen/DBGeneratorOptionsController.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/dbgen/DBGeneratorOptionsController.java
deleted file mode 100644
index 8500a5950..000000000
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/dbgen/DBGeneratorOptionsController.java
+++ /dev/null
@@ -1,300 +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.ui.dbgen;
-
-import org.apache.cayenne.access.DbGenerator;
-import org.apache.cayenne.dba.DbAdapter;
-import org.apache.cayenne.log.NoopJdbcEventLogger;
-import org.apache.cayenne.map.DataMap;
-import org.apache.cayenne.modeler.ui.project.ProjectController;
-import org.apache.cayenne.modeler.ui.validation.ValidationController;
-import org.apache.cayenne.modeler.ui.datasource.DataSourceController;
-import org.apache.cayenne.modeler.mvc.ChildController;
-import org.apache.cayenne.modeler.dbconnector.DBConnector;
-import org.apache.cayenne.validation.ValidationResult;
-
-import javax.swing.*;
-import java.awt.*;
-import java.io.File;
-import java.io.FileWriter;
-import java.io.IOException;
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Iterator;
-
-
-public class DBGeneratorOptionsController extends ChildController<ProjectController> {
-
-    private static final String JDBC_ADAPTER = "org.apache.cayenne.dba.JdbcAdapter";
-
-    protected final DBGeneratorOptionsView view;
-
-    protected DBConnector connector;
-    protected Collection<DataMap> dataMaps;
-    protected DBGeneratorPrefs generatorDefaults;
-    protected Collection<DbGenerator> generators;
-    protected String textForSQL;
-
-    protected TableSelectorController tables;
-
-    public DBGeneratorOptionsController(ProjectController parent, String title, Collection<DataMap> dataMaps) {
-        super(parent);
-
-        this.dataMaps = dataMaps;
-        this.tables = new TableSelectorController(parent);
-        this.connector = new DBConnector();
-        this.connector.setAllowDataSourceFailure(true);
-        this.generatorDefaults = DBGeneratorPrefs.of(
-                parent.getApplication().getPreferencesRepository(),
-                parent.getProject());
-
-        this.view = new DBGeneratorOptionsView(
-                this,
-                tables.getView(),
-                generatorDefaults.getCreateFK(),
-                generatorDefaults.getCreatePK(),
-                generatorDefaults.getCreateTables(),
-                generatorDefaults.getDropPK(),
-                generatorDefaults.getDropTables());
-        this.view.setTitle(title);
-
-        connector.setDbAdapter(view.getSelectedAdapter());
-
-        tables.updateTables(dataMaps);
-        prepareGenerator();
-        applyOptionsToGenerators();
-        createSQL();
-        refreshView();
-    }
-
-    @Override
-    public Component getView() {
-        return view;
-    }
-
-    private void applyOptionsToGenerators() {
-        boolean createFK = view.isCreateFkSelected();
-        boolean createPK = view.isCreatePkSelected();
-        boolean createTables = view.isCreateTablesSelected();
-        boolean dropPK = view.isDropPkSelected();
-        boolean dropTables = view.isDropTablesSelected();
-        for (DbGenerator generator : generators) {
-            generator.setShouldCreateFKConstraints(createFK);
-            generator.setShouldCreatePKSupport(createPK);
-            generator.setShouldCreateTables(createTables);
-            generator.setShouldDropPKSupport(dropPK);
-            generator.setShouldDropTables(dropTables);
-        }
-    }
-
-    /**
-     * Creates new internal DbGenerator instance.
-     */
-    protected void prepareGenerator() {
-        try {
-            DbAdapter adapter = connector.makeAdapter(getApplication().getClassLoader(), getApplication().getDbAdapterFactory());
-            generators = new ArrayList<>();
-            for (DataMap dataMap : dataMaps) {
-                this.generators.add(new DbGenerator(
-                        adapter,
-                        dataMap,
-                        tables.getExcludedTables(),
-                        null,
-                        NoopJdbcEventLogger.getInstance()));
-            }
-        } catch (Exception ex) {
-            reportError("Error loading adapter", ex);
-        }
-    }
-
-    /**
-     * Returns SQL statements generated for selected schema generation options.
-     */
-    protected void createSQL() {
-        // convert them to string representation for display
-        StringBuilder buf = new StringBuilder();
-        for (DbGenerator generator : generators) {
-            Iterator<String> it = generator.configuredStatements().iterator();
-            String batchTerminator = generator.getAdapter().getBatchTerminator();
-
-            String lineEnd = (batchTerminator != null)
-                    ? "\n" + batchTerminator + "\n\n"
-                    : "\n\n";
-
-            while (it.hasNext()) {
-                buf.append(it.next()).append(lineEnd);
-            }
-        }
-
-        textForSQL = buf.toString();
-    }
-
-    protected void refreshView() {
-        view.setEnabled(connector != null);
-        view.setSqlPreview(textForSQL);
-    }
-
-    // ===============
-    // View callbacks
-    // ===============
-
-    void refreshSqlClicked() {
-        refreshSQLAction();
-    }
-
-    void adapterChanged(String adapter) {
-        connector.setDbAdapter(adapter);
-        refreshSQLAction();
-    }
-
-    void sqlTabSelected() {
-        // assumes that some tables were checked/unchecked... not very efficient
-        refreshGeneratorAction();
-    }
-
-    void generateClicked() {
-        generateSchemaAction();
-    }
-
-    void saveSqlClicked() {
-        storeSQLAction();
-    }
-
-    void cancelClicked() {
-        view.dispose();
-    }
-
-    void windowClosed() {
-        generatorDefaults.save(
-                view.isCreateFkSelected(),
-                view.isCreatePkSelected(),
-                view.isCreateTablesSelected(),
-                view.isDropPkSelected(),
-                view.isDropTablesSelected());
-    }
-
-    // ===============
-    // Actions
-    // ===============
-
-    /**
-     * Starts options dialog.
-     */
-    public void startupAction() {
-        view.pack();
-        view.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
-        view.setModal(true);
-        makeCloseableOnEscape();
-        centerView();
-        view.setVisible(true);
-    }
-
-    public void refreshGeneratorAction() {
-        prepareGenerator();
-        refreshSQLAction();
-    }
-
-    /**
-     * Updates a text area showing generated SQL.
-     */
-    public void refreshSQLAction() {
-        // sync combo to reflect current connectionInfo (e.g. after generateSchemaAction replaces it)
-        String adapter = connector.getDbAdapter();
-        view.selectAdapter(adapter);
-        // mirror the displayed value back to the model (was: setSelectedItem returns "JdbcAdapter" placeholder when null)
-        connector.setDbAdapter(adapter != null ? adapter : JDBC_ADAPTER);
-        prepareGenerator();
-        applyOptionsToGenerators();
-        createSQL();
-        view.setSqlPreview(textForSQL);
-    }
-
-    /**
-     * Performs configured schema operations via DbGenerator.
-     */
-    public void generateSchemaAction() {
-
-        DataSourceController connectWizard = new DataSourceController(parent, "Generate DB Schema: Connect to Database");
-        if (!connectWizard.startupAction()) {
-            return;
-        }
-
-        this.connector = connectWizard.getConnector();
-
-        refreshGeneratorAction();
-
-        Collection<ValidationResult> failures = new ArrayList<>();
-
-        // sanity check...
-        for (DbGenerator generator : generators) {
-            if (generator.isEmpty(true)) {
-                JOptionPane.showMessageDialog(view, "Nothing to generate.");
-                return;
-            }
-
-            try {
-                generator.runGenerator(connectWizard.getDataSource());
-                failures.add(generator.getFailures());
-            } catch (Throwable th) {
-                reportError("Schema Generation Error", th);
-            }
-        }
-
-        if (failures.isEmpty()) {
-            JOptionPane.showMessageDialog(view, "Schema Generation Complete.");
-        } else {
-            new ValidationController(this)
-                    .startupAction(
-                            "Schema Generation Complete",
-                            "Schema generation finished. The following problem(s) were ignored.",
-                            failures);
-        }
-    }
-
-    /**
-     * Allows user to save generated SQL in a file.
-     */
-    public void storeSQLAction() {
-        JFileChooser fc = new JFileChooser();
-        fc.setDialogType(JFileChooser.SAVE_DIALOG);
-        fc.setDialogTitle("Save SQL Script");
-
-        File projectDir = new File(getApplication()
-                .getProject()
-                .getConfigurationResource()
-                .getURL()
-                .getPath());
-        fc.setCurrentDirectory(projectDir);
-        if (fc.showSaveDialog(view) == JFileChooser.APPROVE_OPTION) {
-            refreshGeneratorAction();
-            try {
-                File file = fc.getSelectedFile();
-                FileWriter fw = new FileWriter(file);
-                PrintWriter pw = new PrintWriter(fw);
-                pw.print(textForSQL);
-                pw.flush();
-                pw.close();
-            } catch (IOException ex) {
-                reportError("Error Saving SQL", ex);
-            }
-        }
-    }
-}
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/dbgen/DBGeneratorOptionsDialog.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/dbgen/DBGeneratorOptionsDialog.java
new file mode 100644
index 000000000..06d7f2f8d
--- /dev/null
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/dbgen/DBGeneratorOptionsDialog.java
@@ -0,0 +1,398 @@
+/*****************************************************************
+ *   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.ui.dbgen;
+
+import com.jgoodies.forms.builder.PanelBuilder;
+import com.jgoodies.forms.factories.Borders;
+import com.jgoodies.forms.layout.CellConstraints;
+import com.jgoodies.forms.layout.FormLayout;
+import org.apache.cayenne.access.DbGenerator;
+import org.apache.cayenne.dba.DbAdapter;
+import org.apache.cayenne.log.NoopJdbcEventLogger;
+import org.apache.cayenne.map.DataMap;
+import org.apache.cayenne.modeler.dbconnector.DBConnector;
+import org.apache.cayenne.modeler.toolkit.border.TopBorder;
+import org.apache.cayenne.modeler.toolkit.ProjectDialog;
+import org.apache.cayenne.modeler.ui.datasource.DataSourceDialog;
+import org.apache.cayenne.modeler.project.ProjectSession;
+import org.apache.cayenne.modeler.ui.validation.ValidationDialog;
+import org.apache.cayenne.modeler.util.DbAdapterInfo;
+import org.apache.cayenne.validation.ValidationResult;
+
+import javax.swing.Box;
+import javax.swing.DefaultComboBoxModel;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JComboBox;
+import javax.swing.JFileChooser;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTabbedPane;
+import javax.swing.JTextArea;
+import javax.swing.ScrollPaneConstants;
+import javax.swing.SwingConstants;
+import java.awt.BorderLayout;
+import java.awt.Container;
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.GridLayout;
+import java.awt.Window;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+
+/**
+ * Modal wizard for generating the database from a set of DataMaps. Two tabs: SQL options
+ * (CRUD checkboxes + adapter + previewed SQL) and Tables (per-table include/exclude).
+ */
+public class DBGeneratorOptionsDialog extends ProjectDialog {
+
+    private static final String JDBC_ADAPTER = "org.apache.cayenne.dba.JdbcAdapter";
+
+    private final Collection<DataMap> dataMaps;
+    private final TableSelectorPanel tables;
+    private final DBGeneratorPrefs generatorDefaults;
+
+    private final JTextArea sqlPreview;
+    private final JCheckBox dropTables;
+    private final JCheckBox createTables;
+    private final JCheckBox createFK;
+    private final JCheckBox createPK;
+    private final JCheckBox dropPK;
+    private final JComboBox<String> adapters;
+    private final JTabbedPane tabs;
+    private final JButton generateButton;
+    private final JButton cancelButton;
+    private final JButton saveSqlButton;
+
+    private DBConnector connector;
+    private Collection<DbGenerator> generators;
+    private String textForSQL;
+
+    // True while pushing a programmatic adapter selection — suppresses the action
+    // listener so it doesn't recurse back into refreshSQLAction.
+    private boolean updatingAdapterProgrammatically;
+
+    public DBGeneratorOptionsDialog(ProjectSession session, Window owner,
+                                    String title, Collection<DataMap> dataMaps) {
+        super(session, owner, title, ModalityType.APPLICATION_MODAL);
+        this.dataMaps = dataMaps;
+        this.tables = new TableSelectorPanel(app());
+
+        this.connector = new DBConnector();
+        this.connector.setAllowDataSourceFailure(true);
+        this.generatorDefaults = DBGeneratorPrefs.of(app().getPreferencesRepository(), session.project());
+
+        // create widgets — set initial state before wiring listeners so we
+        // don't fire spurious refresh events during construction.
+        this.createFK = new JCheckBox("Create FK Support");
+        this.createFK.setSelected(generatorDefaults.getCreateFK());
+        this.createPK = new JCheckBox("Create Primary Key Support");
+        this.createPK.setSelected(generatorDefaults.getCreatePK());
+        this.createTables = new JCheckBox("Create Tables");
+        this.createTables.setSelected(generatorDefaults.getCreateTables());
+        this.dropPK = new JCheckBox("Drop Primary Key Support");
+        this.dropPK.setSelected(generatorDefaults.getDropPK());
+        this.dropTables = new JCheckBox("Drop Tables");
+        this.dropTables.setSelected(generatorDefaults.getDropTables());
+
+        this.generateButton = new JButton("Generate");
+        this.cancelButton = new JButton("Cancel");
+        this.saveSqlButton = new JButton("Save SQL");
+
+        this.tabs = new JTabbedPane(SwingConstants.TOP);
+        this.adapters = new JComboBox<>();
+        this.adapters.setEditable(true);
+        this.adapters.setModel(new DefaultComboBoxModel<>(DbAdapterInfo.getStandardAdapters()));
+        this.adapters.setSelectedIndex(0);
+
+        this.sqlPreview = new JTextArea();
+        this.sqlPreview.setEditable(false);
+        this.sqlPreview.setLineWrap(true);
+        this.sqlPreview.setWrapStyleWord(true);
+
+        connector.setDbAdapter(selectedAdapter());
+
+        initLayout();
+        initBindings();
+
+        tables.updateTables(dataMaps);
+        prepareGenerator();
+        applyOptionsToGenerators();
+        createSQL();
+        setEnabled(connector != null);
+        sqlPreview.setText(textForSQL);
+    }
+
+    public void startupAction() {
+        pack();
+        setDefaultCloseOperation(DISPOSE_ON_CLOSE);
+        centerOnOwner();
+        makeCloseableOnEscape();
+        setVisible(true);
+    }
+
+    private void initLayout() {
+        getRootPane().setDefaultButton(generateButton);
+
+        JPanel optionsPane = new JPanel(new GridLayout(3, 2));
+        optionsPane.add(dropTables);
+        optionsPane.add(createTables);
+        optionsPane.add(new JLabel());
+        optionsPane.add(createFK);
+        optionsPane.add(dropPK);
+        optionsPane.add(createPK);
+
+        JPanel sqlTextPanel = new JPanel(new BorderLayout());
+        sqlTextPanel.add(new JScrollPane(
+                sqlPreview,
+                ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
+                ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED), BorderLayout.CENTER);
+
+        JPanel adapterPanel = new JPanel(new BorderLayout());
+        adapterPanel.add(adapters);
+
+        CellConstraints cc = new CellConstraints();
+        PanelBuilder builder = new PanelBuilder(new FormLayout(
+                "fill:min(50dlu;pref):grow",
+                "p, 3dlu, p, 9dlu, p, 3dlu, p, 3dlu, p, 3dlu, fill:40dlu:grow"));
+        builder.addSeparator("Options", cc.xywh(1, 1, 1, 1));
+        builder.add(optionsPane, cc.xy(1, 3, "left,fill"));
+        builder.addSeparator("Adapter", cc.xywh(1, 5, 1, 1));
+        builder.add(adapterPanel, cc.xy(1, 7));
+        builder.addSeparator("Generated SQL", cc.xywh(1, 9, 1, 1));
+        builder.add(sqlTextPanel, cc.xy(1, 11));
+        builder.setBorder(Borders.DIALOG_BORDER);
+
+        tabs.addTab("SQL Options", builder.getPanel());
+        tabs.addTab("Tables", new JScrollPane(
+                tables,
+                ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
+                ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED));
+
+        // pack() needs a sane preferred size for a decent default
+        tabs.setPreferredSize(new Dimension(450, 500));
+        tabs.setFocusable(false);
+
+        JPanel buttonsPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
+        buttonsPanel.add(saveSqlButton);
+        buttonsPanel.add(Box.createHorizontalStrut(20));
+        buttonsPanel.add(cancelButton);
+        buttonsPanel.add(generateButton);
+        buttonsPanel.setBorder(TopBorder.create());
+
+        Container contentPane = getContentPane();
+        contentPane.setLayout(new BorderLayout());
+        contentPane.add(tabs, BorderLayout.CENTER);
+        contentPane.add(buttonsPanel, BorderLayout.SOUTH);
+    }
+
+    private void initBindings() {
+        createFK.addActionListener(e -> refreshSQLAction());
+        createPK.addActionListener(e -> refreshSQLAction());
+        createTables.addActionListener(e -> refreshSQLAction());
+        dropPK.addActionListener(e -> refreshSQLAction());
+        dropTables.addActionListener(e -> refreshSQLAction());
+
+        adapters.addActionListener(e -> {
+            if (updatingAdapterProgrammatically) return;
+            Object sel = adapters.getSelectedItem();
+            connector.setDbAdapter(JDBC_ADAPTER.equals(sel) ? null : (String) sel);
+            refreshSQLAction();
+        });
+
+        generateButton.addActionListener(e -> generateSchemaAction());
+        saveSqlButton.addActionListener(e -> storeSQLAction());
+        cancelButton.addActionListener(e -> dispose());
+
+        // refresh SQL when the user switches back to the SQL Options tab
+        tabs.addChangeListener(e -> {
+            if (tabs.getSelectedIndex() == 0) {
+                refreshGeneratorAction();
+            }
+        });
+
+        addWindowListener(new WindowAdapter() {
+            @Override
+            public void windowClosed(WindowEvent e) {
+                generatorDefaults.save(
+                        createFK.isSelected(),
+                        createPK.isSelected(),
+                        createTables.isSelected(),
+                        dropPK.isSelected(),
+                        dropTables.isSelected());
+            }
+        });
+    }
+
+    private void applyOptionsToGenerators() {
+        boolean cFK = createFK.isSelected();
+        boolean cPK = createPK.isSelected();
+        boolean cT = createTables.isSelected();
+        boolean dPK = dropPK.isSelected();
+        boolean dT = dropTables.isSelected();
+        for (DbGenerator generator : generators) {
+            generator.setShouldCreateFKConstraints(cFK);
+            generator.setShouldCreatePKSupport(cPK);
+            generator.setShouldCreateTables(cT);
+            generator.setShouldDropPKSupport(dPK);
+            generator.setShouldDropTables(dT);
+        }
+    }
+
+    private void prepareGenerator() {
+        try {
+            DbAdapter adapter = connector.makeAdapter(app().getClassLoader(), app().getDbAdapterFactory());
+            generators = new ArrayList<>();
+            for (DataMap dataMap : dataMaps) {
+                generators.add(new DbGenerator(
+                        adapter,
+                        dataMap,
+                        tables.getExcludedTables(),
+                        null,
+                        NoopJdbcEventLogger.getInstance()));
+            }
+        } catch (Exception ex) {
+            reportError("Error loading adapter", ex);
+        }
+    }
+
+    private void createSQL() {
+        StringBuilder buf = new StringBuilder();
+        for (DbGenerator generator : generators) {
+            Iterator<String> it = generator.configuredStatements().iterator();
+            String batchTerminator = generator.getAdapter().getBatchTerminator();
+            String lineEnd = (batchTerminator != null)
+                    ? "\n" + batchTerminator + "\n\n"
+                    : "\n\n";
+
+            while (it.hasNext()) {
+                buf.append(it.next()).append(lineEnd);
+            }
+        }
+        textForSQL = buf.toString();
+    }
+
+    private void refreshGeneratorAction() {
+        prepareGenerator();
+        refreshSQLAction();
+    }
+
+    private void refreshSQLAction() {
+        // sync combo to reflect current connector (e.g. after generateSchemaAction replaces it)
+        String adapter = connector.getDbAdapter();
+        selectAdapter(adapter);
+        // mirror the displayed value back to the model (was: setSelectedItem returns "JdbcAdapter" placeholder when null)
+        connector.setDbAdapter(adapter != null ? adapter : JDBC_ADAPTER);
+        prepareGenerator();
+        applyOptionsToGenerators();
+        createSQL();
+        sqlPreview.setText(textForSQL);
+    }
+
+    private void generateSchemaAction() {
+        DataSourceDialog connectWizard = new DataSourceDialog(
+                session(),
+                app().getFrame(),
+                "Generate DB Schema: Connect to Database");
+        if (!connectWizard.startupAction()) {
+            return;
+        }
+
+        this.connector = connectWizard.getConnector();
+        refreshGeneratorAction();
+
+        Collection<ValidationResult> failures = new ArrayList<>();
+
+        for (DbGenerator generator : generators) {
+            if (generator.isEmpty(true)) {
+                JOptionPane.showMessageDialog(this, "Nothing to generate.");
+                return;
+            }
+
+            try {
+                generator.runGenerator(connectWizard.getDataSource());
+                failures.add(generator.getFailures());
+            } catch (Throwable th) {
+                reportError("Schema Generation Error", th);
+            }
+        }
+
+        if (failures.isEmpty()) {
+            JOptionPane.showMessageDialog(this, "Schema Generation Complete.");
+        } else {
+            new ValidationDialog(
+                    app(),
+                    app().getFrame(),
+                    "Schema Generation Complete",
+                    "Schema generation finished. The following problem(s) were ignored.",
+                    failures).open();
+        }
+    }
+
+    private void storeSQLAction() {
+        JFileChooser fc = new JFileChooser();
+        fc.setDialogType(JFileChooser.SAVE_DIALOG);
+        fc.setDialogTitle("Save SQL Script");
+
+        File projectDir = new File(app()
+                .getFrame().getProjectSession().project()
+                .getConfigurationResource()
+                .getURL()
+                .getPath());
+        fc.setCurrentDirectory(projectDir);
+        if (fc.showSaveDialog(this) == JFileChooser.APPROVE_OPTION) {
+            refreshGeneratorAction();
+            try {
+                File file = fc.getSelectedFile();
+                FileWriter fw = new FileWriter(file);
+                PrintWriter pw = new PrintWriter(fw);
+                pw.print(textForSQL);
+                pw.flush();
+                pw.close();
+            } catch (IOException ex) {
+                reportError("Error Saving SQL", ex);
+            }
+        }
+    }
+
+    private void selectAdapter(String adapter) {
+        String display = adapter != null ? adapter : JDBC_ADAPTER;
+        updatingAdapterProgrammatically = true;
+        try {
+            adapters.setSelectedItem(display);
+        } finally {
+            updatingAdapterProgrammatically = false;
+        }
+    }
+
+    private String selectedAdapter() {
+        Object sel = adapters.getSelectedItem();
+        return sel != null ? sel.toString() : null;
+    }
+}
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/dbgen/DBGeneratorOptionsView.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/dbgen/DBGeneratorOptionsView.java
deleted file mode 100644
index bdb47689a..000000000
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/dbgen/DBGeneratorOptionsView.java
+++ /dev/null
@@ -1,228 +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.ui.dbgen;
-
-import java.awt.BorderLayout;
-import java.awt.Component;
-import java.awt.Container;
-import java.awt.Dimension;
-import java.awt.FlowLayout;
-import java.awt.GridLayout;
-import java.awt.event.WindowAdapter;
-import java.awt.event.WindowEvent;
-
-import javax.swing.Box;
-import javax.swing.DefaultComboBoxModel;
-import javax.swing.JButton;
-import javax.swing.JCheckBox;
-import javax.swing.JComboBox;
-import javax.swing.JDialog;
-import javax.swing.JLabel;
-import javax.swing.JPanel;
-import javax.swing.JScrollPane;
-import javax.swing.JTabbedPane;
-import javax.swing.JTextArea;
-import javax.swing.ScrollPaneConstants;
-import javax.swing.SwingConstants;
-
-import com.jgoodies.forms.builder.PanelBuilder;
-import com.jgoodies.forms.factories.Borders;
-import com.jgoodies.forms.layout.CellConstraints;
-import com.jgoodies.forms.layout.FormLayout;
-import org.apache.cayenne.modeler.toolkit.border.TopBorder;
-import org.apache.cayenne.modeler.util.DbAdapterInfo;
-
-/**
- * Wizard for generating the database from the data map.
- */
-public class DBGeneratorOptionsView extends JDialog {
-
-    private static final String JDBC_ADAPTER = "org.apache.cayenne.dba.JdbcAdapter";
-
-    private final JTextArea sql;
-    private final JCheckBox dropTables;
-    private final JCheckBox createTables;
-    private final JCheckBox createFK;
-    private final JCheckBox createPK;
-    private final JCheckBox dropPK;
-    private final JComboBox<String> adapters;
-
-    // True while the controller is pushing a programmatic adapter selection;
-    // suppresses the action listener so it doesn't recurse back into the controller.
-    private boolean updatingAdapterProgrammatically;
-
-    public DBGeneratorOptionsView(DBGeneratorOptionsController controller,
-                                  Component tablesView,
-                                  boolean createFK, boolean createPK, boolean createTables,
-                                  boolean dropPK, boolean dropTables) {
-
-        // create widgets — set initial state before wiring listeners so we
-        // don't fire spurious refresh events during construction.
-        this.createFK = new JCheckBox("Create FK Support");
-        this.createFK.setSelected(createFK);
-        this.createPK = new JCheckBox("Create Primary Key Support");
-        this.createPK.setSelected(createPK);
-        this.createTables = new JCheckBox("Create Tables");
-        this.createTables.setSelected(createTables);
-        this.dropPK = new JCheckBox("Drop Primary Key Support");
-        this.dropPK.setSelected(dropPK);
-        this.dropTables = new JCheckBox("Drop Tables");
-        this.dropTables.setSelected(dropTables);
-
-        JButton generateButton = new JButton("Generate");
-        getRootPane().setDefaultButton(generateButton);
-        JButton cancelButton = new JButton("Cancel");
-        JButton saveSqlButton = new JButton("Save SQL");
-
-        JTabbedPane tabs = new JTabbedPane(SwingConstants.TOP);
-        this.adapters = new JComboBox<>();
-        adapters.setEditable(true);
-        adapters.setModel(new DefaultComboBoxModel<>(DbAdapterInfo.getStandardAdapters()));
-        adapters.setSelectedIndex(0);
-
-        this.sql = new JTextArea();
-        sql.setEditable(false);
-        sql.setLineWrap(true);
-        sql.setWrapStyleWord(true);
-
-        // wire listeners
-        this.createFK.addActionListener(e -> controller.refreshSqlClicked());
-        this.createPK.addActionListener(e -> controller.refreshSqlClicked());
-        this.createTables.addActionListener(e -> controller.refreshSqlClicked());
-        this.dropPK.addActionListener(e -> controller.refreshSqlClicked());
-        this.dropTables.addActionListener(e -> controller.refreshSqlClicked());
-
-        adapters.addActionListener(e -> {
-            if (updatingAdapterProgrammatically) return;
-            Object sel = adapters.getSelectedItem();
-            controller.adapterChanged(JDBC_ADAPTER.equals(sel) ? null : (String) sel);
-        });
-
-        generateButton.addActionListener(e -> controller.generateClicked());
-        saveSqlButton.addActionListener(e -> controller.saveSqlClicked());
-        cancelButton.addActionListener(e -> controller.cancelClicked());
-
-        // refresh SQL if different tables were selected
-        tabs.addChangeListener(e -> {
-            if (tabs.getSelectedIndex() == 0) {
-                controller.sqlTabSelected();
-            }
-        });
-
-        addWindowListener(new WindowAdapter() {
-            @Override
-            public void windowClosed(WindowEvent e) {
-                controller.windowClosed();
-            }
-        });
-
-        // assemble
-        JPanel optionsPane = new JPanel(new GridLayout(3, 2));
-        optionsPane.add(this.dropTables);
-        optionsPane.add(this.createTables);
-        optionsPane.add(new JLabel());
-        optionsPane.add(this.createFK);
-        optionsPane.add(this.dropPK);
-        optionsPane.add(this.createPK);
-
-        JPanel sqlTextPanel = new JPanel(new BorderLayout());
-        sqlTextPanel.add(new JScrollPane(
-                sql,
-                ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
-                ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED), BorderLayout.CENTER);
-
-        JPanel adapterPanel = new JPanel(new BorderLayout());
-        adapterPanel.add(adapters);
-
-        CellConstraints cc = new CellConstraints();
-        PanelBuilder builder = new PanelBuilder(new FormLayout(
-                "fill:min(50dlu;pref):grow",
-                "p, 3dlu, p, 9dlu, p, 3dlu, p, 3dlu, p, 3dlu, fill:40dlu:grow"));
-        builder.addSeparator("Options", cc.xywh(1, 1, 1, 1));
-        builder.add(optionsPane, cc.xy(1, 3, "left,fill"));
-        builder.addSeparator("Adapter", cc.xywh(1, 5, 1, 1));
-        builder.add(adapterPanel, cc.xy(1, 7));
-        builder.addSeparator("Generated SQL", cc.xywh(1, 9, 1, 1));
-        builder.add(sqlTextPanel, cc.xy(1, 11));
-        builder.setBorder(Borders.DIALOG_BORDER);
-
-        tabs.addTab("SQL Options", builder.getPanel());
-        tabs.addTab("Tables", new JScrollPane(
-                tablesView,
-                ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
-                ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED));
-
-        // we need the right preferred size so that dialog "pack()" produces decent
-        // default size...
-        tabs.setPreferredSize(new Dimension(450, 500));
-        tabs.setFocusable(false);
-
-        JPanel buttonsPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
-        buttonsPanel.add(saveSqlButton);
-        buttonsPanel.add(Box.createHorizontalStrut(20));
-        buttonsPanel.add(cancelButton);
-        buttonsPanel.add(generateButton);
-        buttonsPanel.setBorder(TopBorder.create());
-
-        Container contentPane = getContentPane();
-        contentPane.setLayout(new BorderLayout());
-        contentPane.add(tabs, BorderLayout.CENTER);
-        contentPane.add(buttonsPanel, BorderLayout.SOUTH);
-    }
-
-    public void setSqlPreview(String sql) {
-        this.sql.setText(sql);
-    }
-
-    public void selectAdapter(String adapter) {
-        String display = adapter != null ? adapter : JDBC_ADAPTER;
-        updatingAdapterProgrammatically = true;
-        try {
-            adapters.setSelectedItem(display);
-        } finally {
-            updatingAdapterProgrammatically = false;
-        }
-    }
-
-    public String getSelectedAdapter() {
-        Object sel = adapters.getSelectedItem();
-        return sel != null ? sel.toString() : null;
-    }
-
-    public boolean isCreateFkSelected() {
-        return createFK.isSelected();
-    }
-
-    public boolean isCreatePkSelected() {
-        return createPK.isSelected();
-    }
-
-    public boolean isCreateTablesSelected() {
-        return createTables.isSelected();
-    }
-
-    public boolean isDropPkSelected() {
-        return dropPK.isSelected();
-    }
-
-    public boolean isDropTablesSelected() {
-        return dropTables.isSelected();
-    }
-}
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/dbgen/TableSelectorController.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/dbgen/TableSelectorController.java
deleted file mode 100644
index ae1d68b20..000000000
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/dbgen/TableSelectorController.java
+++ /dev/null
@@ -1,157 +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.ui.dbgen;
-
-import org.apache.cayenne.map.DataMap;
-import org.apache.cayenne.map.DbAttribute;
-import org.apache.cayenne.map.DbEntity;
-import org.apache.cayenne.map.DbRelationship;
-import org.apache.cayenne.modeler.ui.project.ProjectController;
-import org.apache.cayenne.modeler.mvc.ChildController;
-import org.apache.cayenne.project.Project;
-import org.apache.cayenne.project.validation.ProjectValidator;
-import org.apache.cayenne.validation.ValidationFailure;
-import org.apache.cayenne.validation.ValidationResult;
-
-import java.awt.*;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-public class TableSelectorController extends ChildController<ProjectController> {
-
-    protected final TableSelectorView view;
-
-    protected List<DbEntity> tables;
-    protected int permanentlyExcludedCount;
-    protected Map<String, DbEntity> excludedTables;
-    protected List<DbEntity> selectableTablesList;
-    protected Map<String, String> validationMessages;
-
-    public TableSelectorController(ProjectController parent) {
-        super(parent);
-        this.excludedTables = new HashMap<>();
-        this.selectableTablesList = new ArrayList<>();
-        this.validationMessages = new HashMap<>();
-        this.view = new TableSelectorView(this);
-    }
-
-    public Component getView() {
-        return view;
-    }
-
-    public Collection<DbEntity> getExcludedTables() {
-        return excludedTables.values();
-    }
-
-    public List<DbEntity> getTables() {
-        return tables;
-    }
-
-    public boolean isIncluded(DbEntity entity) {
-        return !excludedTables.containsKey(entity.getName());
-    }
-
-    public void setIncluded(DbEntity entity, boolean b) {
-        if (b) {
-            excludedTables.remove(entity.getName());
-        } else {
-            excludedTables.put(entity.getName(), entity);
-        }
-        tableSelectedAction();
-    }
-
-    public String getProblem(DbEntity entity) {
-        return validationMessages.get(entity.getName());
-    }
-
-    public void tableSelectedAction() {
-        int unselectedCount = excludedTables.size() - permanentlyExcludedCount;
-        if (unselectedCount == selectableTablesList.size()) {
-            view.setCheckAll(false);
-        } else if (unselectedCount == 0) {
-            view.setCheckAll(true);
-        }
-    }
-
... 27213 lines suppressed ...