From ca469fbca357cc57c56c6597e0f4044374457420 Mon Sep 17 00:00:00 2001 From: Carsten Englert Date: Thu, 28 Jul 2016 08:53:47 +0200 Subject: [PATCH] HmX module: allow pericope in input mode to be saved; validate configured syntactic function codes; fix some minor ui bugs --- .../hmx/core/ModelParseServiceImpl.java | 6 +- .../hmx/scitos/hmx/core/i18n/HmxMessage.java | 8 +- .../hmx/scitos/hmx/core/i18n/HmxMessage.xml | 7 +- .../scitos/hmx/core/i18n/HmxMessage_de.xml | 8 +- .../hmx/scitos/hmx/domain/model/Pericope.java | 16 +-- .../hmx/view/swing/HmxSwingProject.java | 5 - .../view/swing/ProjectViewServiceImpl.java | 3 - .../swing/components/SingleProjectView.java | 44 ++++++-- .../view/swing/components/TextInputPanel.java | 102 ++++++++++++------ .../swing/option/HmxLanguageOptionPanel.java | 60 ++++++++++- .../swing/option/SynFunctionConfigPanel.java | 65 ++++++++++- .../swing/option/SynFunctionTreeModel.java | 2 +- .../view/swing/AbstractScitosUiTest.java | 26 +++-- 13 files changed, 269 insertions(+), 83 deletions(-) diff --git a/scitos.hmx/scitos.hmx.core/src/main/java/org/hmx/scitos/hmx/core/ModelParseServiceImpl.java b/scitos.hmx/scitos.hmx.core/src/main/java/org/hmx/scitos/hmx/core/ModelParseServiceImpl.java index 200f6d3..365d752 100644 --- a/scitos.hmx/scitos.hmx.core/src/main/java/org/hmx/scitos/hmx/core/ModelParseServiceImpl.java +++ b/scitos.hmx/scitos.hmx.core/src/main/java/org/hmx/scitos/hmx/core/ModelParseServiceImpl.java @@ -476,14 +476,14 @@ public Entry> parseModelFromXml(final Document xml, final File newPericope.addNewPropositions(text, false); final Element connectablesTree = DomUtil.getChildElement(pericopeNode, ModelParseServiceImpl.TAG_RELATION_SUB_TREE); - if (connectablesTree != null) { + if (connectablesTree != null && !text.isEmpty()) { // gets a list of all Propositions contained in the pericope in order of appearence final Deque propositionsInOrder = new LinkedList(); Proposition nextProposition = newPericope.getPropositionAt(0); - do { + while (nextProposition != null) { propositionsInOrder.addLast(nextProposition); nextProposition = nextProposition.getFollowingConnectableProposition(); - } while (nextProposition != null); + } for (final Element topLevelConnectable : DomUtil.getChildElements(connectablesTree, ModelParseServiceImpl.TAG_CONNECTABLE)) { this.parseConnectableFromXml(topLevelConnectable, propositionsInOrder, compatibleRoleTranslator); } diff --git a/scitos.hmx/scitos.hmx.core/src/main/java/org/hmx/scitos/hmx/core/i18n/HmxMessage.java b/scitos.hmx/scitos.hmx.core/src/main/java/org/hmx/scitos/hmx/core/i18n/HmxMessage.java index d523674..6a1ec78 100644 --- a/scitos.hmx/scitos.hmx.core/src/main/java/org/hmx/scitos/hmx/core/i18n/HmxMessage.java +++ b/scitos.hmx/scitos.hmx.core/src/main/java/org/hmx/scitos/hmx/core/i18n/HmxMessage.java @@ -77,12 +77,6 @@ public enum HmxMessage implements ILocalizableMessage { TEXTINPUT_BEFORE_BUTTON("TextInput.AddText.Before"), /** text input view: button to add new propositions behind current text. */ TEXTINPUT_BEHIND_BUTTON("TextInput.AddText.Behind"), - /** confirmation question: proceed closing of new project in setup mode. */ - TEXTINPUT_QUIT_QUESTION("TextInput.Quit.DiscardInputQuestion"), - /** - * title of the {@link #TEXTINPUT_QUIT_QUESTION} dialog. - */ - TEXTINPUT_QUIT_TITLE("TextInput.Quit.DiscardInputTitle"), /** * title of the popup behind the {@link #MENUBAR_PROJECTINFO} entry. @@ -210,6 +204,7 @@ public enum HmxMessage implements ILocalizableMessage { PREFERENCES_GENERAL_INPUT_SHOW_SETTINGS("Preferences.View.TextInput.ShowSettings"), PREFERENCES_GENERAL_AUTHOR("Preferences.Analysis.ProjectInfo.DefaultAuthor"), PREFERENCES_GENERAL_LANGUAGE("Preferences.Analysis.TextInput.DefaultOriginLanguage"), + PREFERENCES_EDITINPROGRESS("Preferences.EditInProgress"), PREFERENCES_EXPORT("Preferences.Export"), PREFERENCES_EXPORT_ELEMENTCOLOR("Preferences.Export.Color"), @@ -275,6 +270,7 @@ public enum HmxMessage implements ILocalizableMessage { PREFERENCES_LANGUAGEFUNCTIONS_NAME("Preferences.OriginLanguageFunctions.Name"), PREFERENCES_LANGUAGEFUNCTIONS_NAME_MANDATORY("Preferences.OriginLanguageFunctions.Name.Mandatory"), PREFERENCES_LANGUAGEFUNCTIONS_CODE("Preferences.OriginLanguageFunctions.Code"), + PREFERENCES_LANGUAGEFUNCTIONS_CODE_UNIQUE("Preferences.OriginLanguageFunctions.Code.Unique"), PREFERENCES_LANGUAGEFUNCTIONS_CODE_MANDATORY("Preferences.OriginLanguageFunctions.Code.Mandatory"), PREFERENCES_LANGUAGEFUNCTIONS_UNDERLINE("Preferences.OriginLanguageFunctions.Underline"), PREFERENCES_LANGUAGEFUNCTIONS_DESCRIPTION("Preferences.OriginLanguageFunctions.Description"); diff --git a/scitos.hmx/scitos.hmx.core/src/main/resources/org/hmx/scitos/hmx/core/i18n/HmxMessage.xml b/scitos.hmx/scitos.hmx.core/src/main/resources/org/hmx/scitos/hmx/core/i18n/HmxMessage.xml index 3263387..836544a 100644 --- a/scitos.hmx/scitos.hmx.core/src/main/resources/org/hmx/scitos/hmx/core/i18n/HmxMessage.xml +++ b/scitos.hmx/scitos.hmx.core/src/main/resources/org/hmx/scitos/hmx/core/i18n/HmxMessage.xml @@ -117,7 +117,7 @@ is allowed to be checked. HmX Project - unsaved Default Author Default Origin Language - HmX – General + Cannot submit changes as there is a pending edit operation. HmX – Export Element Colors Non-Origin Text Font @@ -132,6 +132,7 @@ is allowed to be checked. Syntactical Function (italic) Syntactical Function (plain) Translation Text + HmX – General HmX – Languages Apply Changes Clone Language Model @@ -154,6 +155,8 @@ is allowed to be checked. Code The Code cannot be empty. It is being displayed as representation of a selected function in the Syntactical Analysis. + The Code values for syntactical functions in a single language must be unique. +''{0}'' contains at least one pair of duplicate Code values. Description Move Entry Down Move Entry Up @@ -210,8 +213,6 @@ All text parts divided by tabs will be treated as single clause items. Set the origin language: Only clause item functions deposited for the chosen language will be available in the syntactical analysis. - Do you really want to discard your input? - Discard Input? Hide Origin Language Show diff --git a/scitos.hmx/scitos.hmx.core/src/main/resources/org/hmx/scitos/hmx/core/i18n/HmxMessage_de.xml b/scitos.hmx/scitos.hmx.core/src/main/resources/org/hmx/scitos/hmx/core/i18n/HmxMessage_de.xml index 8fa0e5e..76ad22a 100644 --- a/scitos.hmx/scitos.hmx.core/src/main/resources/org/hmx/scitos/hmx/core/i18n/HmxMessage_de.xml +++ b/scitos.hmx/scitos.hmx.core/src/main/resources/org/hmx/scitos/hmx/core/i18n/HmxMessage_de.xml @@ -116,7 +116,8 @@ keine weiteren Satzgliedern befinden. HmX Projekt - ungespeichert Standard-Autor Standard-Ausgangssprache - HmX – Allgemein + Änderungen können nicht übernommen werden, +da der aktuelle Bearbeitungsvorgang noch nicht abgeschlossen ist. HmX – Export Elementfarben Schrift für nicht-ausgangssprachliche Texte @@ -131,6 +132,7 @@ keine weiteren Satzgliedern befinden. Syntakt. Funktion (kursiv) Syntakt. Funktion (normal) Übersetzungstext + HmX – Allgemein HmX – Syntaktisch Anwenden Klone Ausgangssprache @@ -157,6 +159,8 @@ keine weiteren Satzgliedern befinden. Kurzform Die Kurzform darf nicht leer sein. Sie wird in der Syntaktischen Analyse auf zugeordneten Elementen angezeigt. + Die Kurzform syntaktischer Funktionen innerhalb einer Sprache muss eindeutig sein. +''{0}'' enthält mindestens ein Paar doppelter Kurzformen. Beschreibung Funktion Der Funktionsbezeichner darf nicht leer sein. @@ -209,8 +213,6 @@ Jeder innerhalb einer Zeile durch Tabs getrennte Textabschnitt, wird im Weiteren Stellen Sie die Ausgangssprache ein: In der syntaktischen Analyse können nur Satzgliedfunktionen ausgewählt werden, die für die jeweilige Sprache hinterlegt sind. - Sollen die bisherigen Eingaben wirklich verworfen werden? - Eingaben verwerfen? Ausblenden Ausgangssprache Einblenden diff --git a/scitos.hmx/scitos.hmx.domain/src/main/java/org/hmx/scitos/hmx/domain/model/Pericope.java b/scitos.hmx/scitos.hmx.domain/src/main/java/org/hmx/scitos/hmx/domain/model/Pericope.java index b1a1f81..5af40aa 100755 --- a/scitos.hmx/scitos.hmx.domain/src/main/java/org/hmx/scitos/hmx/domain/model/Pericope.java +++ b/scitos.hmx/scitos.hmx.domain/src/main/java/org/hmx/scitos/hmx/domain/model/Pericope.java @@ -201,7 +201,7 @@ public List getFlatRelations() { final List result = new LinkedList(); // get the first Proposition AbstractConnectable currentFocus = this.getPropositionAt(0); - do { + while (currentFocus != null) { // get the highest relation over the current focused Proposition while (currentFocus.getSuperOrdinatedRelation() != null) { currentFocus = currentFocus.getSuperOrdinatedRelation(); @@ -211,7 +211,7 @@ public List getFlatRelations() { } // iterate whole pericope currentFocus = currentFocus.getFollowingConnectableProposition(); - } while (currentFocus != null); + } return result; } @@ -240,7 +240,7 @@ private void collectFlatRelations(final Relation subtreeRoot, final List> provideFunctions() { - return this.languageModel.provideFunctions(); + return this.languageModel == null ? Collections.>emptyList() : this.languageModel + .provideFunctions(); } /** @@ -379,10 +380,13 @@ public List getContainingList(final Proposition childProposition) { * @see #indexOfProposition(Proposition) */ public Proposition getPropositionAt(final int index) { + if (this.text.isEmpty()) { + return null; + } // get the first Proposition Proposition proposition = this.text.get(0); List priorChildren = proposition.getPriorChildren(); - while (priorChildren != null && !priorChildren.isEmpty()) { + while (!priorChildren.isEmpty()) { proposition = priorChildren.get(0); priorChildren = proposition.getPriorChildren(); } diff --git a/scitos.hmx/scitos.hmx.view/src/main/java/org/hmx/scitos/hmx/view/swing/HmxSwingProject.java b/scitos.hmx/scitos.hmx.view/src/main/java/org/hmx/scitos/hmx/view/swing/HmxSwingProject.java index 5507fa0..d240c6d 100644 --- a/scitos.hmx/scitos.hmx.view/src/main/java/org/hmx/scitos/hmx/view/swing/HmxSwingProject.java +++ b/scitos.hmx/scitos.hmx.view/src/main/java/org/hmx/scitos/hmx/view/swing/HmxSwingProject.java @@ -216,11 +216,6 @@ public boolean close() { @Override public boolean prepareForClosing() { - if (this.getModelObject().getText().isEmpty()) { - return MessageHandler.Choice.YES == MessageHandler.showConfirmDialog(HmxMessage.TEXTINPUT_QUIT_QUESTION.get(), - HmxMessage.TEXTINPUT_QUIT_TITLE.get()); - } - // if the project is in analysis mode it is possible to save if (!this.isSaved()) { final String dialogTitle = Message.PROJECT_CLOSE.get() + " - " + this.getTitle(); final MessageHandler.Choice choice = MessageHandler.showYesNoCancelDialog(Message.PROJECT_CLOSE_QUESTION.get(), dialogTitle); diff --git a/scitos.hmx/scitos.hmx.view/src/main/java/org/hmx/scitos/hmx/view/swing/ProjectViewServiceImpl.java b/scitos.hmx/scitos.hmx.view/src/main/java/org/hmx/scitos/hmx/view/swing/ProjectViewServiceImpl.java index 35b819c..909b975 100644 --- a/scitos.hmx/scitos.hmx.view/src/main/java/org/hmx/scitos/hmx/view/swing/ProjectViewServiceImpl.java +++ b/scitos.hmx/scitos.hmx.view/src/main/java/org/hmx/scitos/hmx/view/swing/ProjectViewServiceImpl.java @@ -25,7 +25,6 @@ import org.hmx.scitos.domain.IModel; import org.hmx.scitos.hmx.core.ModelHandlerImpl; -import org.hmx.scitos.hmx.core.i18n.HmxMessage; import org.hmx.scitos.hmx.core.option.HmxLanguageOption; import org.hmx.scitos.hmx.core.option.HmxRelationOption; import org.hmx.scitos.hmx.domain.model.LanguageModel; @@ -33,11 +32,9 @@ import org.hmx.scitos.hmx.domain.model.RelationModel; import org.hmx.scitos.hmx.view.swing.components.SingleProjectView; import org.hmx.scitos.view.ContextMenuBuilder; -import org.hmx.scitos.view.FileType; import org.hmx.scitos.view.IViewProject; import org.hmx.scitos.view.service.IProjectViewService; import org.hmx.scitos.view.swing.ScitosClient; -import org.hmx.scitos.view.swing.util.ViewUtil; /** * Implementation of the {@link IProjectViewService} for the AIS module. diff --git a/scitos.hmx/scitos.hmx.view/src/main/java/org/hmx/scitos/hmx/view/swing/components/SingleProjectView.java b/scitos.hmx/scitos.hmx.view/src/main/java/org/hmx/scitos/hmx/view/swing/components/SingleProjectView.java index 3998dff..a0ffc7f 100644 --- a/scitos.hmx/scitos.hmx.view/src/main/java/org/hmx/scitos/hmx/view/swing/components/SingleProjectView.java +++ b/scitos.hmx/scitos.hmx.view/src/main/java/org/hmx/scitos/hmx/view/swing/components/SingleProjectView.java @@ -38,10 +38,12 @@ import org.hmx.scitos.hmx.core.ILanguageModelProvider; import org.hmx.scitos.hmx.core.i18n.HmxMessage; import org.hmx.scitos.hmx.domain.ISemanticalRelationProvider; +import org.hmx.scitos.hmx.domain.model.ClauseItem; import org.hmx.scitos.hmx.domain.model.LanguageModel; import org.hmx.scitos.hmx.domain.model.Pericope; import org.hmx.scitos.hmx.domain.model.Proposition; import org.hmx.scitos.hmx.domain.model.RelationTemplate; +import org.hmx.scitos.hmx.view.IPericopeView; import org.hmx.scitos.hmx.view.swing.HmxSwingProject; import org.hmx.scitos.hmx.view.swing.elements.ProjectInfoDialog; import org.hmx.scitos.view.ScitosIcon; @@ -98,15 +100,43 @@ public SingleProjectView(final HmxSwingProject project, final ILanguageModelProv super(project, project.getModelObject(), new BorderLayout()); this.relationProvider = relationProvider; this.modelParseProvider = modelParseProvider; - if (this.getModel().getText().isEmpty()) { - this.activeView = new TextInputPanel(this, languageModelProvider); - } else { + if (this.containsAnalysisData()) { this.undoManager = new UndoManager(this.getModel()); this.activeView = new CombinedAnalysesPanel(this.getProject().getModelHandler(), this.relationProvider); + } else { + this.activeView = new TextInputPanel(this, true, languageModelProvider); } this.add(this.activeView); } + /** + * Check whether the current model contains any analysis related data - i.e. if information would be lost if the current model was displayed in a + * {@link TextInputPanel} rather than a {@link CombinedAnalysesPanel}. + * + * @return if any information besides the origin text is present + */ + boolean containsAnalysisData() { + if (!this.getModel().getFlatRelations().isEmpty()) { + // the semantical analysis has already been started + return true; + } + // we only have to check the top level propositions, if there is any non-top-level one we return true anyway + for (final Proposition singleProposition : this.getModel().getText()) { + if (!singleProposition.getPriorChildren().isEmpty() || !singleProposition.getLaterChildren().isEmpty() + || singleProposition.getPartAfterArrow() != null || singleProposition.getLabel() != null + || singleProposition.getSemTranslation() != null || singleProposition.getSynTranslation() != null + || singleProposition.getComment() != null) { + return true; + } + for (final ClauseItem singleItem : singleProposition) { + if (singleItem.getFunction() != null || singleItem.getFontStyle() != ClauseItem.Style.PLAIN || singleItem.getComment() != null) { + return true; + } + } + } + return false; + } + /** * Initialize the analysis by referring to the chosen origin language and {@link Font}, opening the analysis view with the syntactical analysis. * @@ -155,7 +185,7 @@ void goToTextInputView() { if (this.activeView instanceof CombinedAnalysesPanel) { this.submitChangesToModel(); this.remove(this.activeView); - this.activeView = new TextInputPanel(this, null); + this.activeView = new TextInputPanel(this, false, null); this.add(this.activeView); this.revalidate(); this.manageMenuOptions(); @@ -212,8 +242,10 @@ public void refresh() { @Override public void submitChangesToModel() { - if (this.activeView instanceof CombinedAnalysesPanel) { - ((CombinedAnalysesPanel) this.activeView).submitChangesToModel(); + if (this.activeView instanceof IPericopeView) { + ((IPericopeView) this.activeView).submitChangesToModel(); + } else if (this.activeView instanceof TextInputPanel) { + ((TextInputPanel) this.activeView).submitChangesToModel(); } } diff --git a/scitos.hmx/scitos.hmx.view/src/main/java/org/hmx/scitos/hmx/view/swing/components/TextInputPanel.java b/scitos.hmx/scitos.hmx.view/src/main/java/org/hmx/scitos/hmx/view/swing/components/TextInputPanel.java index 3f73cc1..ef4a984 100755 --- a/scitos.hmx/scitos.hmx.view/src/main/java/org/hmx/scitos/hmx/view/swing/components/TextInputPanel.java +++ b/scitos.hmx/scitos.hmx.view/src/main/java/org/hmx/scitos/hmx/view/swing/components/TextInputPanel.java @@ -30,6 +30,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; @@ -114,6 +115,10 @@ public final class TextInputPanel extends JPanel { * The {@link #originTextPane}'s undo manager. */ UndoManager undoManager; + /** + * Flag indicating whether this panel is used for initializing a {@link Pericope} - i.e. not just for prepending/appending additional text. + */ + private final boolean replaceCurrentText; /** * Constructor. @@ -121,14 +126,16 @@ public final class TextInputPanel extends JPanel { * @param parentView * the super ordinated main view representing the associated HermeneutiX project, in which this view is referred to a * {@code text-input mode} + * @param replaceCurrentText + * whether this panel is used for initializing a {@link Pericope} - i.e. not just for prepending/appending additional text. * @param languageModelProvider * provider of all selectable {@link LanguageModel}s */ - public TextInputPanel(final SingleProjectView parentView, final ILanguageModelProvider languageModelProvider) { + public TextInputPanel(final SingleProjectView parentView, final boolean replaceCurrentText, final ILanguageModelProvider languageModelProvider) { super(new GridBagLayout()); this.parentView = parentView; - final boolean newProject = parentView.getModel().getText().isEmpty(); - if (newProject) { + this.replaceCurrentText = replaceCurrentText; + if (this.replaceCurrentText) { this.languageModels = languageModelProvider.provideLanguageModels(); this.buttons = new JButton[] { new JButton(HmxMessage.TEXTINPUT_START_BUTTON.get()) }; this.buttons[0].addActionListener(new ActionListener() { @@ -178,26 +185,46 @@ public void actionPerformed(final ActionEvent event) { this.setBorder(BorderFactory.createEmptyBorder(0, 10, 0, 10)); // create the topic label at the top left - this.initTopicLabel(newProject); + this.initTopicLabel(); // create the text area for entering the origin text this.initOriginTextPane(this.buttons[0].getPreferredSize().height); // create the button to switch between visible and invisible settings this.initShowOrHideSettingsButton(); // creating the setting area - this.initSettingArea(newProject); + this.initSettingArea(); // set default setting visibility this.manageSettingVisibility(); + if (this.replaceCurrentText) { + final StringBuilder textBuilder = new StringBuilder(512); + for (final Proposition singleProposition : this.parentView.getModel().getFlatText()) { + final Iterator clauseItems = singleProposition.getItems().iterator(); + textBuilder.append(clauseItems.next().getOriginText()); + while (clauseItems.hasNext()) { + textBuilder.append(" ").append(clauseItems.next().getOriginText()); + } + textBuilder.append('\n'); + } + this.originTextPane.setText(textBuilder.toString().trim()); + } this.originTextPane.requestFocus(); } + /** + * Ensure all currently displayed changes are represented as such in the underlying model objects - in order to be able to save the current state. + */ + public void submitChangesToModel() { + if (this.replaceCurrentText) { + // apply the default selected language model and font selection (to actually allow something to be saved this early) + this.parentView.getModel().init(this.getPropositionTexts(), this.getLanguageModelSelection(), this.getFontSelection()); + } + // if we are currently adding text to an existing model, the unchanged model will be saved + } + /** * Initialize the topic label at the top left position. - * - * @param newProject - * if this is the initial text input, else it is just adding more text to an existing project */ - private void initTopicLabel(final boolean newProject) { - final HmxMessage topicKey = newProject ? HmxMessage.TEXTINPUT_TOPIC : HmxMessage.TEXTINPUT_TOPIC_ADD_PROPOSITIONS; + private void initTopicLabel() { + final HmxMessage topicKey = this.replaceCurrentText ? HmxMessage.TEXTINPUT_TOPIC : HmxMessage.TEXTINPUT_TOPIC_ADD_PROPOSITIONS; final JLabel topicLabel = new JLabel(topicKey.get()); topicLabel.setBorder(BorderFactory.createEmptyBorder(5, 0, 5, 10)); final GridBagConstraints constraints = new GridBagConstraints(); @@ -260,6 +287,7 @@ public void undoableEditHappened(final UndoableEditEvent event) { } private void manageMenuStatus() { + TextInputPanel.this.parentView.getProject().setSaved(false); TextInputPanel.this.parentView.getProject().manageMenuOptions(); } }; @@ -335,11 +363,8 @@ public void actionPerformed(final ActionEvent event) { /** * Initialize the setting side bar to the right of the button to set its visibility. - * - * @param newProject - * if this is the initial text input, else it is just adding more text to an existing project */ - private void initSettingArea(final boolean newProject) { + private void initSettingArea() { this.settingArea.setBorder(BorderFactory.createEmptyBorder(5, 10, 8, 0)); // initializes the combo box for choosing the origin language final GridBagConstraints constraints = new GridBagConstraints(); @@ -356,7 +381,7 @@ private void initSettingArea(final boolean newProject) { this.settingArea.add(this.createFontSizeSlider(), constraints); // initializes the short hint text String hint = HmxMessage.TEXTINPUT_HINT.get(); - if (newProject) { + if (this.replaceCurrentText) { hint += "\n\n\n" + HmxMessage.TEXTINPUT_WARNING.get(); } final JTextArea hintArea = new JTextArea(hint); @@ -390,15 +415,13 @@ private void initSettingArea(final boolean newProject) { this.add(this.settingArea, rightConstraints); // set default language - final String defaultLanguage; - if (newProject) { + String defaultLanguage = this.parentView.getModel().getLanguage(); + if (defaultLanguage == null) { defaultLanguage = (String) this.languageBox.getItemAt(0); - } else { - defaultLanguage = this.parentView.getModel().getLanguage(); } // listener on languageBox also presets the font type and size this.languageBox.setSelectedItem(defaultLanguage); - this.setFontSelection(newProject); + this.setFontSelection(); this.setOriginTextPaneOrientation(); } @@ -432,7 +455,8 @@ private JPanel createLanguageComboBox() { @Override public void actionPerformed(final ActionEvent event) { TextInputPanel.this.setOriginTextPaneOrientation(); - TextInputPanel.this.setFontSelection(false); + TextInputPanel.this.setFontSelection(); + TextInputPanel.this.parentView.getProject().setSaved(false); } }); languagePanel.add(this.languageBox); @@ -473,6 +497,7 @@ private JPanel createFontTypeComboBox() { @Override public void actionPerformed(final ActionEvent event) { TextInputPanel.this.setFontTypeAndStyle(); + TextInputPanel.this.parentView.getProject().setSaved(false); } }); final JPanel fontTypePanel = new JPanel(); @@ -483,11 +508,8 @@ public void actionPerformed(final ActionEvent event) { /** * Set the font type and size selection based on the currently chosen {@link LanguageModel}. - * - * @param applyRecommendedFont - * whether the model's current font setting should be ignored and replaced by a recommended one (if present) */ - void setFontSelection(final boolean applyRecommendedFont) { + void setFontSelection() { final Object selectedEntry = this.languageBox.getSelectedItem(); if (selectedEntry == null) { return; @@ -498,7 +520,7 @@ void setFontSelection(final boolean applyRecommendedFont) { } final Font currentFont = this.parentView.getModel().getFont(); int fontIndex; - if (applyRecommendedFont || currentFont == null) { + if (currentFont == null) { fontIndex = -1; } else { fontIndex = this.fontFamilyNames.indexOf(currentFont.getFamily()); @@ -537,6 +559,7 @@ private JPanel createFontSizeSlider() { public void stateChanged(final ChangeEvent event) { fontSizeDisplay.setText(String.valueOf(TextInputPanel.this.fontSizeSlider.getValue())); TextInputPanel.this.setFontTypeAndStyle(); + TextInputPanel.this.parentView.getProject().setSaved(false); } }); final GridBagConstraints horizontalSpan = new GridBagConstraints(); @@ -559,16 +582,33 @@ void setFontTypeAndStyle() { */ void startAnalysis() { if (this.containsText()) { - final Object selectedEntry = this.languageBox.getSelectedItem(); - if (selectedEntry != null) { - final LanguageModel selectedModel = this.languageModels.get(selectedEntry); - this.parentView.startAnalysis(this.getPropositionTexts(), selectedModel, new Font(this.fontTypeBox.getSelectedItem().toString(), - Font.PLAIN, this.fontSizeSlider.getValue())); + final LanguageModel selectedModel = this.getLanguageModelSelection(); + // the value could be null if no language model could be loaded (neither system defined nor user defined ones) on initialization + if (selectedModel != null) { + this.parentView.startAnalysis(this.getPropositionTexts(), selectedModel, this.getFontSelection()); } } // if the originTextArea is empty, do nothing } + /** + * Get the currently selected {@link LanguageModel} from the associated selection component. + * + * @return selected language model + */ + private LanguageModel getLanguageModelSelection() { + return this.languageModels.get(this.languageBox.getSelectedItem()); + } + + /** + * Get the currently selected origin text {@link Font} – as specified by the chosen font type and the size slider's current value. + * + * @return the currently defined + */ + private Font getFontSelection() { + return new Font(this.fontTypeBox.getSelectedItem().toString(), Font.PLAIN, this.fontSizeSlider.getValue()); + } + /** * Add the inserted origin text to the associated {@link Pericope}. * diff --git a/scitos.hmx/scitos.hmx.view/src/main/java/org/hmx/scitos/hmx/view/swing/option/HmxLanguageOptionPanel.java b/scitos.hmx/scitos.hmx.view/src/main/java/org/hmx/scitos/hmx/view/swing/option/HmxLanguageOptionPanel.java index 0efb8c1..ba4ce46 100644 --- a/scitos.hmx/scitos.hmx.view/src/main/java/org/hmx/scitos/hmx/view/swing/option/HmxLanguageOptionPanel.java +++ b/scitos.hmx/scitos.hmx.view/src/main/java/org/hmx/scitos/hmx/view/swing/option/HmxLanguageOptionPanel.java @@ -21,7 +21,10 @@ import java.awt.event.ActionEvent; import java.awt.event.ActionListener; +import java.text.MessageFormat; +import java.util.HashSet; import java.util.List; +import java.util.Set; import javax.swing.BorderFactory; import javax.swing.JButton; @@ -31,8 +34,13 @@ import org.hmx.scitos.hmx.core.i18n.HmxMessage; import org.hmx.scitos.hmx.core.option.HmxLanguageOption; +import org.hmx.scitos.hmx.domain.model.AbstractSyntacticalFunctionElement; import org.hmx.scitos.hmx.domain.model.LanguageModel; +import org.hmx.scitos.hmx.domain.model.SyntacticalFunction; +import org.hmx.scitos.hmx.domain.model.SyntacticalFunctionGroup; import org.hmx.scitos.view.ScitosIcon; +import org.hmx.scitos.view.swing.MessageHandler; +import org.hmx.scitos.view.swing.MessageHandler.MessageType; import org.hmx.scitos.view.swing.option.AbstractOptionPanel; /** @@ -85,8 +93,6 @@ public void actionPerformed(final ActionEvent event) { public void actionPerformed(final ActionEvent event) { languagePanelRow.forceShow(); switchToLanguagesRow.hide(); - HmxLanguageOptionPanel.this.languagePanel.getSelectedModel().reset(HmxLanguageOptionPanel.this.functionPanel.provideFunctions()); - HmxLanguageOptionPanel.this.languagePanel.fireSelectedModelRowUpdated(); switchToFunctionsRow.forceShow(); functionPanelRow.hide(); } @@ -99,6 +105,14 @@ public void actionPerformed(final ActionEvent event) { && HmxLanguageOptionPanel.this.languagePanel.isSelectedModelUserDefined()); } }); + this.functionPanel.addActionListener(new ActionListener() { + + @Override + public void actionPerformed(final ActionEvent event) { + HmxLanguageOptionPanel.this.languagePanel.getSelectedModel().reset(HmxLanguageOptionPanel.this.functionPanel.provideFunctions()); + HmxLanguageOptionPanel.this.languagePanel.fireSelectedModelRowUpdated(); + } + }); } @Override @@ -114,7 +128,47 @@ protected void validateInput() { @Override public boolean areChosenSettingsValid() { - return !this.languagePanel.isInEditMode() && !this.functionPanel.isInEditMode(); + if (this.languagePanel.isInEditMode() || this.functionPanel.isInEditMode()) { + MessageHandler.showMessage(HmxMessage.PREFERENCES_EDITINPROGRESS.get(), HmxMessage.PREFERENCES_LANGUAGE.get(), MessageType.INFO); + return false; + } + for (final LanguageModel singleModel : this.languagePanel.getUserModels()) { + final Set codes = new HashSet(); + for (final List singleTopLevelGroup : singleModel.provideFunctions()) { + if (this.containsDuplicateFunctionCode(singleTopLevelGroup, codes)) { + final String message = MessageFormat.format(HmxMessage.PREFERENCES_LANGUAGEFUNCTIONS_CODE_UNIQUE.get(), singleModel.getName()); + MessageHandler.showMessage(message, HmxMessage.PREFERENCES_LANGUAGE.get(), MessageType.WARN); + return false; + } + } + } + return true; + } + + /** + * Recursively determine whether two {@link SyntacticalFunction} elements are configured to have the same {@code code} value. + * + * @param functionElements + * the {@link SyntacticalFunction} and {@link SyntacticalFunctionGroup} elements to recursively check + * @param codes + * collection of already configured {@code code} values to add the checked values to + * @return if the {@code codes} collection already contained an encountered {@link SyntacticalFunction}'s {@code code} value + */ + private boolean containsDuplicateFunctionCode(final List functionElements, final Set codes) { + for (final AbstractSyntacticalFunctionElement singleElement : functionElements) { + if (singleElement instanceof SyntacticalFunction) { + // try to add code to duplicate-preventing set + if (!codes.add(((SyntacticalFunction) singleElement).getCode())) { + // adding the code to the set didn't change it, i.e. the set already contained this code + return true; + } + } else if (singleElement instanceof SyntacticalFunctionGroup + && this.containsDuplicateFunctionCode(((SyntacticalFunctionGroup) singleElement).getSubFunctions(), codes)) { + // feed back the duplicate found in a contained syntactical function element + return true; + } + } + return false; } @Override diff --git a/scitos.hmx/scitos.hmx.view/src/main/java/org/hmx/scitos/hmx/view/swing/option/SynFunctionConfigPanel.java b/scitos.hmx/scitos.hmx.view/src/main/java/org/hmx/scitos/hmx/view/swing/option/SynFunctionConfigPanel.java index b9b764f..b785a63 100644 --- a/scitos.hmx/scitos.hmx.view/src/main/java/org/hmx/scitos/hmx/view/swing/option/SynFunctionConfigPanel.java +++ b/scitos.hmx/scitos.hmx.view/src/main/java/org/hmx/scitos/hmx/view/swing/option/SynFunctionConfigPanel.java @@ -21,6 +21,7 @@ import java.awt.event.ActionEvent; import java.awt.event.ActionListener; +import java.util.ArrayList; import java.util.List; import javax.swing.AbstractAction; @@ -89,6 +90,8 @@ private enum ActionMode { private final JCheckBox underlineBox = new JCheckBox(HmxMessage.PREFERENCES_LANGUAGEFUNCTIONS_UNDERLINE.get()); /** Form input for the optional description/tooltip text associated with a function/group (maximum of 512 characters length). */ private final JTextArea descriptionArea = new JTextArea(new Validation(512)); + /** Registered external listeners for changes to the language model. */ + private final List actionListeners = new ArrayList(1); /** Constructor. */ public SynFunctionConfigPanel() { @@ -137,6 +140,37 @@ public boolean isInEditMode() { return this.nameInput.isEnabled(); } + /** + * Register the given listener to be notified whenever the displayed language model changed. + * + * @param listener + * the listener to notify + */ + public void addActionListener(final ActionListener listener) { + this.actionListeners.add(listener); + } + + /** + * Unregister the given listener from notifications regarding changes to the displayed language model. + * + * @param listener + * the listener to no longer notify + * @return whether the listener was registered before and has been removed successfully + */ + public boolean removeActionListener(final ActionListener listener) { + return this.actionListeners.remove(listener); + } + + /** + * Notify all registered action listeners of a change to the displayed language model (i.e. a function/group was added/changed/moved/removed). + */ + void triggerActionListeners() { + final ActionEvent event = new ActionEvent(this, ActionEvent.ACTION_PERFORMED, ""); + for (final ActionListener singleListener : this.actionListeners) { + singleListener.actionPerformed(event); + } + } + /** Initialize the tree table with its columns. */ private void initTable() { this.treeTable.setTableHeader(null); @@ -156,6 +190,7 @@ public void actionPerformed(final ActionEvent actionEvent) { final TreePath newPath = SynFunctionConfigPanel.this.functionModel.addSynFunctionRow(parentGroupPath); SynFunctionConfigPanel.this.treeTable.setSelectedPath(newPath); SynFunctionConfigPanel.this.handleFormAvailability(ActionMode.FUNCTION); + SynFunctionConfigPanel.this.triggerActionListeners(); } }); // Button: add function group to group (empty for function entries) @@ -168,7 +203,7 @@ public void actionPerformed(final ActionEvent actionEvent) { final TreePath newPath = SynFunctionConfigPanel.this.functionModel.addGroupRow(parentGroupPath); SynFunctionConfigPanel.this.treeTable.setSelectedPath(newPath); SynFunctionConfigPanel.this.handleFormAvailability(ActionMode.FUNCTION_GROUP); - + SynFunctionConfigPanel.this.triggerActionListeners(); } }); // Button: move function/group up @@ -177,8 +212,7 @@ public void actionPerformed(final ActionEvent actionEvent) { @Override public void actionPerformed(final ActionEvent actionEvent) { final int clickedRowIndex = Integer.valueOf(actionEvent.getActionCommand()).intValue(); - final TreePath entryPath = SynFunctionConfigPanel.this.treeTable.getPathForRow(clickedRowIndex); - SynFunctionConfigPanel.this.functionModel.moveEntry(entryPath, false); + SynFunctionConfigPanel.this.moveEntry(clickedRowIndex, false); } }); // Button: move function/group down @@ -187,8 +221,7 @@ public void actionPerformed(final ActionEvent actionEvent) { @Override public void actionPerformed(final ActionEvent actionEvent) { final int clickedRowIndex = Integer.valueOf(actionEvent.getActionCommand()).intValue(); - final TreePath entryPath = SynFunctionConfigPanel.this.treeTable.getPathForRow(clickedRowIndex); - SynFunctionConfigPanel.this.functionModel.moveEntry(entryPath, true); + SynFunctionConfigPanel.this.moveEntry(clickedRowIndex, true); } }); // Button: remove function/group @@ -199,6 +232,7 @@ public void actionPerformed(final ActionEvent actionEvent) { final int clickedRowIndex = Integer.valueOf(actionEvent.getActionCommand()).intValue(); final TreePath entryPath = SynFunctionConfigPanel.this.treeTable.getPathForRow(clickedRowIndex); SynFunctionConfigPanel.this.functionModel.removeEntry(entryPath); + SynFunctionConfigPanel.this.triggerActionListeners(); } }); this.treeTable.applyRowHeight(deleteColumn, ""); @@ -206,6 +240,25 @@ public void actionPerformed(final ActionEvent actionEvent) { this.treeTable.setVisibleRowCount(3); } + /** + * Move the group or entry at the designated path up or down by own step. + * + * @param rowIndex + * index of the relation template group or entry row to move + * @param moveDown + * whether the group or entry should be moved down; otherwise moved up + */ + void moveEntry(final int rowIndex, final boolean moveDown) { + final TreePath targetPath = this.treeTable.getPathForRow(rowIndex); + // determine those paths under the same parent that are currently expanded + final List expandedSiblings = this.treeTable.getExpandedChildren(targetPath.getParentPath()); + // actually move the indicated element + this.functionModel.moveEntry(targetPath, moveDown); + // reset the expanded state of the targetPath's siblings + this.treeTable.expandPaths(expandedSiblings); + this.triggerActionListeners(); + } + /** Initialize all the possible interactions with buttons and other components. */ private void initAvailableActions() { this.treeTable.addTreeSelectionListener(new TreeSelectionListener() { @@ -224,6 +277,7 @@ public void actionPerformed(final ActionEvent event) { SynFunctionConfigPanel.this.treeTable.expandPath(newEntry); SynFunctionConfigPanel.this.treeTable.setSelectedPath(newEntry); SynFunctionConfigPanel.this.handleFormAvailability(ActionMode.TABLE); + SynFunctionConfigPanel.this.triggerActionListeners(); } }); this.editTableSelectionButton.addActionListener(new ActionListener() { @@ -293,6 +347,7 @@ void applyFormChanges() { } this.functionModel.updatedRow(this.treeTable.getSelectedPath()); this.handleFormAvailability(ActionMode.TABLE); + this.triggerActionListeners(); } } diff --git a/scitos.hmx/scitos.hmx.view/src/main/java/org/hmx/scitos/hmx/view/swing/option/SynFunctionTreeModel.java b/scitos.hmx/scitos.hmx.view/src/main/java/org/hmx/scitos/hmx/view/swing/option/SynFunctionTreeModel.java index 39bb935..9d03c17 100644 --- a/scitos.hmx/scitos.hmx.view/src/main/java/org/hmx/scitos/hmx/view/swing/option/SynFunctionTreeModel.java +++ b/scitos.hmx/scitos.hmx.view/src/main/java/org/hmx/scitos/hmx/view/swing/option/SynFunctionTreeModel.java @@ -401,7 +401,7 @@ public void moveEntry(final TreePath targetPath, final boolean moveDown) { final UUID entryToSwitchWith = entrySiblings.set(indexToSwitchWith, entry); entrySiblings.set(index, entryToSwitchWith); } - this.modelSupport.firePathChanged(parentPath); + this.modelSupport.fireTreeStructureChanged(parentPath); } /** diff --git a/scitos.view/src/test/java/org/hmx/scitos/view/swing/AbstractScitosUiTest.java b/scitos.view/src/test/java/org/hmx/scitos/view/swing/AbstractScitosUiTest.java index e21cb96..582138f 100644 --- a/scitos.view/src/test/java/org/hmx/scitos/view/swing/AbstractScitosUiTest.java +++ b/scitos.view/src/test/java/org/hmx/scitos/view/swing/AbstractScitosUiTest.java @@ -26,10 +26,12 @@ import javax.swing.JPopupMenu; import org.assertj.swing.core.Robot; +import org.assertj.swing.core.matcher.JButtonMatcher; import org.assertj.swing.edt.FailOnThreadViolationRepaintManager; import org.assertj.swing.edt.GuiActionRunner; import org.assertj.swing.edt.GuiQuery; import org.assertj.swing.fixture.FrameFixture; +import org.assertj.swing.fixture.JButtonFixture; import org.assertj.swing.fixture.JPopupMenuFixture; import org.assertj.swing.fixture.JTabbedPaneFixture; import org.assertj.swing.fixture.JTreeFixture; @@ -106,14 +108,22 @@ protected void onSetUp() { * designated file type */ protected void createNewFile(final FileType type) { - // click on the 'create new file' button in the main tool bar - this.frame.toolBar().button(new ToolTipComponentMatcher(JButton.class, Message.MENUBAR_FILE_NEW.get(), true)).click(); - // check whether a popup is being shown - final JPopupMenu shownPopup = this.robot().findActivePopupMenu(); - // we're done if no show popup has been opened - if (shownPopup != null) { - // more than one module offering a 'create new file' feature is active, i.e. select the respective entry in the displayed popup - new JPopupMenuFixture(this.robot(), shownPopup).menuItem(type.getLocalizableName().get()).click(); + // check if the Welcome tab with its dedicated 'New File' buttons is visible + final JButton welcomeTabNewButton = + this.robot().finder().find(JButtonMatcher.withText(Message.MENUBAR_FILE_NEW.get() + " : " + type.getLocalizableName().get())); + if (welcomeTabNewButton.isVisible()) { + // click on the dedicated 'New File' button + new JButtonFixture(this.robot(), welcomeTabNewButton).click(); + } else { + // click on the 'create new file' button in the main tool bar + this.frame.toolBar().button(new ToolTipComponentMatcher(JButton.class, Message.MENUBAR_FILE_NEW.get(), true)).click(); + // check whether a popup is being shown + final JPopupMenu shownPopup = this.robot().findActivePopupMenu(); + // we're done if no show popup has been opened + if (shownPopup != null) { + // more than one module offering a 'create new file' feature is active, i.e. select the respective entry in the displayed popup + new JPopupMenuFixture(this.robot(), shownPopup).menuItem(type.getLocalizableName().get()).click(); + } } }