Skip to content

Commit

Permalink
Fix issue #7 by finishing undo/redo implementation in HermeneutiX module
Browse files Browse the repository at this point in the history
  • Loading branch information
CarstenWickner committed Sep 12, 2017
1 parent 9131c4f commit d63ccc6
Show file tree
Hide file tree
Showing 13 changed files with 341 additions and 232 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
public abstract class AbstractModelHandler<M extends IModel<M>> implements IModelHandler<M> {

/** The managed model object. */
private final M model;
private M model;
/** The listeners that are notified when a model change occurs. */
private final List<ModelChangeListener> listeners;

Expand All @@ -56,6 +56,10 @@ public M getModel() {
return this.model;
}

protected void setModel(final M model) {
this.model = model;
}

@Override
public void addModelChangeListener(final ModelChangeListener listener) {
if (!CollectionUtil.containsInstance(this.listeners, listener)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,13 @@
*/
public interface HmxModelHandler extends IModelHandler<Pericope> {

/**
* Replace the managed model object with the given one, e.g. for use in an undo or redo action.
*
* @param model new managed model object
*/
void resetModel(Pericope model);

/**
* Set the title, author, and comment of the whole managed model, as well as the font used for the origin text.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,12 @@ public ModelHandlerImpl(final Pericope model) {
super(model);
}

@Override
public void resetModel(final Pericope model) {
this.setModel(model);
this.notifyListeners(this.getModel(), false);
}

@Override
public void setMetaData(final String title, final String author, final String comment, final String originTextFontFamily,
final int originTextFontSize) {
Expand Down Expand Up @@ -155,7 +161,7 @@ public void indentPropositionUnderParent(final Proposition target, final Proposi
/**
* Check if the {@link Proposition}s in the given order, have no {@link Proposition}s between them that are on the same or higher level. This
* ignores the scenario where both {@link Proposition}s have the same parent or one of the two is the parent (or parent's parent...) of the other.
*
*
* @param propOne
* leading {@link Proposition} to check
* @param propTwo
Expand Down Expand Up @@ -352,7 +358,7 @@ private boolean checkForConnection(final Proposition prop1, final Proposition pr
* Merge the given {@link Proposition}s by appending the {@code secondPart} to the {@code firstPart} WITHOUT REMOVING the {@code secondPart} from
* its parent, which needs to be called separately. This method assumes both {@link Proposition}s being adjacent to one another and preserves any
* (other) child {@link Proposition}s and handles potentially affected {@link Proposition} parts or enclosed children.
*
*
* @param prop1Part
* leading {@link Proposition} to receive the other {@link Proposition}'s {@link ClauseItem}s, translations, and child
* {@link Proposition}s
Expand Down Expand Up @@ -473,7 +479,7 @@ private void mergeConnectedPropositionsIntoOne(final Proposition firstPart, fina

/**
* Combine the two texts by separating them with the given character. If one of the texts is {@code null}, the other one is returned.
*
*
* @param textOne
* leading text to be merged
* @param textTwo
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,10 @@
import org.hmx.scitos.hmx.domain.model.AbstractConnectable;
import org.hmx.scitos.hmx.domain.model.Proposition;
import org.hmx.scitos.hmx.view.swing.elements.AbstractCommentable;
import org.hmx.scitos.view.swing.IUndoManagedView;

/** Generic interface of a user view regardless of its actual implementation. */
public interface IPericopeView extends ISemanticalRelationProvider {
public interface IPericopeView extends ISemanticalRelationProvider, IUndoManagedView {

/**
* Getter for the model handler, responsible for all actual model changes and manager of any model change events.
Expand All @@ -37,9 +38,6 @@ public interface IPericopeView extends ISemanticalRelationProvider {
*/
HmxModelHandler getModelHandler();

/** Ensure all pending changes are completed before any new model changes are being applied. */
void submitChangesToModel();

/**
* Collect the list of selected {@link Proposition}s in the syntactical analysis, if it is currently active (i.e. displayed).
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,11 @@ public void deactivate() {
*/
public abstract void repaintPericope();

/**
* Ensure that any pending changes have been submitted to the model handler for proper processing.
*/
public abstract void submitChangesToModel();

/**
* Getter for the {@link ModelChangeListener} handling updates of this panel.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@
import javax.swing.JTextPane;
import javax.swing.ScrollPaneConstants;
import javax.swing.UIManager;
import javax.swing.event.AncestorEvent;
import javax.swing.event.AncestorListener;
import org.hmx.scitos.core.UndoManager;
import org.hmx.scitos.domain.ModelChangeListener;
import org.hmx.scitos.domain.ModelEvent;

import org.hmx.scitos.domain.util.ComparisonUtil;
import org.hmx.scitos.hmx.core.HmxModelHandler;
Expand All @@ -55,14 +60,18 @@
/**
* The view representation of a {@link Pericope} in the analysis mode – containing both the syntactical and semantical analysis.
*/
public final class CombinedAnalysesPanel extends JPanel implements IPericopeView {
public final class CombinedAnalysesPanel extends JPanel implements IPericopeView, ModelChangeListener {

/** The represented project's model handler instance. */
private final HmxModelHandler modelHandler;
/**
* The provider of available semantical {@link RelationTemplate}s, to be offered via the elements' context menus.
*/
private final ISemanticalRelationProvider relationProvider;
/** The undo manager for the whole model. */
private final UndoManager<Pericope> undoManager;
/** Flag indicating that an un-do or re-do operation is currently in progress. */
private boolean undoInProgress = false;

/** The button to switch between the syntactical and semantical analysis. */
private final JButton switchButton;
Expand All @@ -78,21 +87,19 @@ public final class CombinedAnalysesPanel extends JPanel implements IPericopeView
private final SemAnalysisPanel semAnalysisView;
/** The view component representing the syntactical analysis. */
private final SynAnalysisPanel synAnalysisView;
/** The input area at the bottom of the view, allowing the display and modification of a selected element's comment. */
private final JTextPane commentArea;

/**
* The currently active analysis: either {@link #semAnalysisView} or {@link #synAnalysisView}.
* The input area at the bottom of the view, allowing the display and modification of a selected element's comment.
*/
private JPanel activeAnalysisView;
private final JTextPane commentArea;

/**
* The most recently selected commentable model element currently associated with the {@link #commentArea}.
*/
private AbstractCommentable<?> lastSelectedCommentable;

/**
* Constructor.
*
*
* @param modelHandler
* the represented project's model handler instance
* @param relationProvider
Expand All @@ -102,12 +109,58 @@ public CombinedAnalysesPanel(final HmxModelHandler modelHandler, final ISemantic
super(new GridBagLayout());
this.modelHandler = modelHandler;
this.relationProvider = relationProvider;
this.undoManager = new UndoManager<Pericope>(this.modelHandler.getModel());
this.addAncestorListener(new AncestorListener() {
@Override
public void ancestorAdded(final AncestorEvent event) {
// ensure logging of model change events by the UndoManager
modelHandler.addModelChangeListener(CombinedAnalysesPanel.this);
}

@Override
public void ancestorRemoved(final AncestorEvent event) {
/*
* unregister UndoManager as long as nothing is shown (nothing can be changed); this is to avoid multiple of these listeners if the
* respective tabs are being closed and re-opened repeatedly
*/
modelHandler.removeModelChangeListener(CombinedAnalysesPanel.this);
}

@Override
public void ancestorMoved(final AncestorEvent event) {
// we don't care about any movement
}
});

this.setBorder(null);
// initialize the commentArea to be reachable by commentable components
this.commentArea = new ScaledTextPane();
// build the analysis views representing the pericope
this.semAnalysisView = new SemAnalysisPanel(this);
this.synAnalysisView = new SynAnalysisPanel(this);

this.switchButton = new JButton() {

@Override
public void updateUI() {
super.updateUI();
this.setFont(UIManager.getFont("Button.font"));
}
};
// initialize the possible button icons
this.semButtonIcon = new VTextIcon(this.switchButton, HmxMessage.ANALYSIS_SEMANTICAL_BUTTON.get(), VTextIcon.Rotate.NONE);
this.synButtonIcon = new VTextIcon(this.switchButton, HmxMessage.ANALYSIS_SYNTACTICAL_BUTTON.get(), VTextIcon.Rotate.NONE);

// default: always start with the syntactical analysis
this.switchButton.setIcon(this.semButtonIcon);
this.initView();
this.synAnalysisView.activate();
}

/**
* Initialize the whole layout.
*/
private void initView() {
// arrange the analysis views and the button for switching between them
final JPanel topArea = new JPanel(new GridBagLayout());

Expand All @@ -120,14 +173,6 @@ public CombinedAnalysesPanel(final HmxModelHandler modelHandler, final ISemantic
doubleSpan.gridy = 0;
topArea.add(this.semAnalysisView, doubleSpan);
// add the button for switching between the analysis to the mid
this.switchButton = new JButton() {

@Override
public void updateUI() {
super.updateUI();
this.setFont(UIManager.getFont("Button.font"));
}
};
this.switchButton.addActionListener(new ActionListener() {

@Override
Expand All @@ -142,12 +187,6 @@ public void actionPerformed(final ActionEvent event) {
constraints.gridy = 0;
topArea.add(this.switchButton, constraints);

// initialize the possible button icons
this.semButtonIcon = new VTextIcon(this.switchButton, HmxMessage.ANALYSIS_SEMANTICAL_BUTTON.get(), VTextIcon.Rotate.NONE);
this.synButtonIcon = new VTextIcon(this.switchButton, HmxMessage.ANALYSIS_SYNTACTICAL_BUTTON.get(), VTextIcon.Rotate.NONE);

// default: always start with the syntactical analysis
this.switchButton.setIcon(this.semButtonIcon);
// add the syntactical analysis to the right
doubleSpan.gridx = 2;
doubleSpan.gridy = 0;
Expand All @@ -173,46 +212,54 @@ public void actionPerformed(final ActionEvent event) {
splitArea.setBorder(null);
splitArea.setResizeWeight(1);
this.add(splitArea, doubleSpan);
}

// default: always start with the syntactical analysis
this.synAnalysisView.activate();
this.activeAnalysisView = this.synAnalysisView;
@Override
public void modelChanged(final ModelEvent<?> event) {
// ignore change event thrown by the own undo/redo action
if (!this.undoInProgress) {
this.undoManager.undoableEditHappened(this.modelHandler.getModel());
}
}

/**
* Change the active analysis view by setting their visibility to make sure that only one analysis view is visible; and clear the shown comment.
*/
void changeActiveAnalysisView() {
// clear comment area
this.handleSelectedCommentable(null);
// make sure only one analysis view is visible at the end
if (this.activeAnalysisView == this.semAnalysisView) {
this.activeAnalysisView = this.synAnalysisView;
if (this.semAnalysisView.isShowing()) {
this.synAnalysisView.activate();
this.semAnalysisView.deactivate();
this.switchButton.setIcon(this.semButtonIcon);
} else {
this.activeAnalysisView = this.semAnalysisView;
this.semAnalysisView.activate();
this.synAnalysisView.deactivate();
this.switchButton.setIcon(this.synButtonIcon);
}
// clear comment area
this.commentArea.setText(null);
}

@Override
public List<AbstractConnectable> getSelectedConnectables(final AbstractConnectable defaultSelected) {
if (this.semAnalysisView.isVisible()) {
if (this.semAnalysisView.isShowing()) {
return this.semAnalysisView.getChecked(defaultSelected);
}
return defaultSelected == null ? Collections.<AbstractConnectable>emptyList() : Collections.singletonList(defaultSelected);
if (defaultSelected == null) {
return Collections.emptyList();
}
return Collections.singletonList(defaultSelected);
}

@Override
public List<Proposition> getSelectedPropositions(final Proposition defaultSelected) {
if (this.synAnalysisView.isVisible()) {
return this.synAnalysisView.getChecked(defaultSelected);
}
return defaultSelected == null ? Collections.<Proposition>emptyList() : Collections.singletonList(defaultSelected);
if (defaultSelected == null) {
return Collections.emptyList();
}
return Collections.singletonList(defaultSelected);
}

@Override
Expand Down Expand Up @@ -246,17 +293,70 @@ public HmxModelHandler getModelHandler() {

@Override
public void submitChangesToModel() {
final AbstractAnalysisPanel activeAnalysisView = this.getActiveAnalysisView();
if (activeAnalysisView != null) {
activeAnalysisView.submitChangesToModel();
}
// also take care of any newly entered comment specifically
this.handleSelectedCommentable(null);
}

/**
* Fully rebuild the currently displayed representation of the {@link Pericope}.
*/
@Override
public void refresh() {
final AbstractAnalysisPanel activeAnalysisView = this.getActiveAnalysisView();
if (activeAnalysisView != null) {
activeAnalysisView.repaintPericope();
}
}

/**
* Determine the currently active analysis view (either {@link #synAnalysisView} or {@link #semAnalysisView}).
*
* @return currently active/displayed analysisView
*/
private AbstractAnalysisPanel getActiveAnalysisView() {
if (this.synAnalysisView.isShowing()) {
this.synAnalysisView.repaintPericope();
} else if (this.semAnalysisView.isShowing()) {
this.semAnalysisView.repaintPericope();
return this.synAnalysisView;
}
if (this.semAnalysisView.isShowing()) {
return this.semAnalysisView;
}
return null;
}

@Override
public boolean canUndo() {
return this.undoManager.canUndo();
}

@Override
public boolean canRedo() {
return this.undoManager.canRedo();
}

@Override
public void undo() {
// ensure that the any pending change is being reverted instead of the previously submit one
this.submitChangesToModel();
this.undoInProgress = true;
try {
this.getModelHandler().resetModel(this.undoManager.undo());
} finally {
this.undoInProgress = false;
}
}

@Override
public void redo() {
// ignore any potentially pending change here (otherwise "Redo" might not be allowed anymore)
this.undoInProgress = true;
try {
this.getModelHandler().resetModel(this.undoManager.redo());
} finally {
this.undoInProgress = false;
}
}

Expand Down
Loading

0 comments on commit d63ccc6

Please sign in to comment.