Skip to content

Commit

Permalink
#753 Update the RichText editor status properly
Browse files Browse the repository at this point in the history
The rich text editor status is now only based on the Session status.
In the scope of kitalpha/capella, AbstractMDERichTextWidget is only used
from MDERichTextEditor.isDirty().
So MDERichTextWidget.isDirty() can be removed.

Nevertheless, to keep the lock of the EObject being edited, a "change"
Listener has been added to be notified when the content of the editor is
changed.
The first change will call widget.saveContents() to be sure that the
model is modified a soon as possible. (before it was done a little
differently with the "key" listener)

Like eclipse "Save", now "Save All" properly reinitialize
setDirtyStateUpdated(false).

#753
Signed-off-by: Laurent Fasani <[email protected]>
  • Loading branch information
lfasani authored and pdulth committed Mar 21, 2023
1 parent 555c902 commit 002018c
Show file tree
Hide file tree
Showing 6 changed files with 128 additions and 101 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2017, 2021 Thales Global Services S.A.S.
* Copyright (c) 2017, 2023 Thales Global Services S.A.S.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
Expand Down Expand Up @@ -131,18 +131,6 @@ public final void setSaveStrategy(SaveStrategy strategy) {
this.saveStrategy = strategy;
}

@Override
public boolean isDirty() {
EObject owner = getElement();
EStructuralFeature feature = getFeature();
String storedText = (String) owner.eGet(feature);
String text = getText();
if (storedText == null) {
return !"".equals(text);
}
return !storedText.equals(text);
}

@Override
public SaveStrategy getSaveStrategy() {
return this.saveStrategy;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2017, 2020 Thales Global Services S.A.S.
* Copyright (c) 2017, 2023 Thales Global Services S.A.S.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
Expand Down Expand Up @@ -117,11 +117,6 @@ public interface MDERichTextWidget extends PropertyChangeListener {
*/
void setEditable(boolean editable);

/**
* @return if the widget is in dirty mode
*/
boolean isDirty();

/**
* @return true if the editor is ready
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2017, 2020 Thales Global Services S.A.S.
* Copyright (c) 2017, 2023 Thales Global Services S.A.S.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
Expand Down Expand Up @@ -200,14 +200,6 @@ public boolean setBaseHrefPath(String baseHref) {
return forceEditorUpdate;
}

@Override
public boolean isDirty() {
if (isReady() && isEditable()) {
return super.isDirty();
}
return false;
}

@Override
public boolean isReady() {
return editorReady;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2017, 2020 Thales Global Services S.A.S.
* Copyright (c) 2017, 2023 Thales Global Services S.A.S.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
Expand Down Expand Up @@ -230,7 +230,7 @@ public void setMDERichTextEditorPartName() {
@Override
public boolean isDirty() {
if (!isDeactivate()) {
return doCheckWorkspaceResourceStatus(widget) || widget.isDirty();
return doCheckWorkspaceResourceStatus(widget);
}
return false;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2017, 2020 Thales Global Services S.A.S.
* Copyright (c) 2017, 2023 Thales Global Services S.A.S.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
Expand Down Expand Up @@ -132,40 +132,62 @@ public MDERichTextWidget createMinimalRichTextWidget(Composite parent){
removeUselessItemFromToolbar();

MDERichtextWidgetImpl widget = new MDERichtextWidgetImpl(parent, configuration) {
@Override
protected void installListeners() {
super.installListeners();
IExecutionListener executionListener = null;

// Since minimal rich text widget does not contribute a Saveable, it needs to listen to Save event itself
ICommandService commandSvc = PlatformUI.getWorkbench().getAdapter(ICommandService.class);
Command saveCommand = commandSvc.getCommand(IWorkbenchCommandConstants.FILE_SAVE);
saveCommand.addExecutionListener(new IExecutionListener() {
@Override
public void preExecute(final String commandId, final ExecutionEvent event) {
if (!isEditorDisposed() && hasFocus()) {
saveContent();
}
}
@Override
protected void installListeners() {
super.installListeners();

@Override
public void postExecuteSuccess(final String commandId, final Object returnValue) {
if (!isEditorDisposed() && hasFocus()) {
setDirtyStateUpdated(false);
}
}
executionListener = new IExecutionListener() {
@Override
public void preExecute(final String commandId, final ExecutionEvent event) {
if (!isEditorDisposed() && hasFocus()) {
saveContent();
}
}

@Override
public void postExecuteSuccess(final String commandId, final Object returnValue) {
if (!isEditorDisposed() && hasFocus()) {
setDirtyStateUpdated(false);
}
}

@Override
public void postExecuteFailure(final String commandId, final ExecutionException exception) {
// Do nothing
}

@Override
public void notHandled(final String commandId, final NotHandledException exception) {
// Do nothing
}
};

@Override
public void postExecuteFailure(final String commandId, final ExecutionException exception) {
// Do nothing
}
// Since minimal rich text widget does not contribute a Saveable, it needs to listen to Save/SaveAll
// events itself
ICommandService commandSvc = PlatformUI.getWorkbench().getAdapter(ICommandService.class);
Command saveCommand = commandSvc.getCommand(IWorkbenchCommandConstants.FILE_SAVE);
Command saveAllCommand = commandSvc.getCommand(IWorkbenchCommandConstants.FILE_SAVE_ALL);

saveCommand.addExecutionListener(executionListener);
saveAllCommand.addExecutionListener(executionListener);
}

@Override
public void dispose() {
if (executionListener != null) {
ICommandService commandSvc = PlatformUI.getWorkbench().getAdapter(ICommandService.class);
Command saveCommand = commandSvc.getCommand(IWorkbenchCommandConstants.FILE_SAVE);
Command saveAllCommand = commandSvc.getCommand(IWorkbenchCommandConstants.FILE_SAVE_ALL);

saveCommand.removeExecutionListener(executionListener);
saveAllCommand.removeExecutionListener(executionListener);
executionListener = null;
}

@Override
public void notHandled(final String commandId, final NotHandledException exception) {
// Do nothing
}
});
}
};
super.dispose();
} };

addToolbarItems(widget);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2017, 2020 Thales Global Services S.A.S.
* Copyright (c) 2017, 2023 Thales Global Services S.A.S.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
Expand All @@ -19,6 +19,7 @@
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.swt.browser.BrowserFunction;
import org.polarsys.kitalpha.richtext.common.impl.AbstractMDERichTextWidget;
import org.polarsys.kitalpha.richtext.common.intf.MDERichTextWidget;
import org.polarsys.kitalpha.richtext.nebula.widget.MDENebulaBasedRichTextWidget;
import org.polarsys.kitalpha.richtext.widget.editor.MDERichTextEditor;
Expand Down Expand Up @@ -65,10 +66,10 @@ public void createAllListeners(final MDENebulaBasedRichTextWidget widget) {
public void installAllListeners(final MDENebulaBasedRichTextWidget widget) {
installBeforePasteConfirmationDialogListener(widget);
installOpenLinkListener(widget);
installSaveListener(widget);
installFocusOutListener(widget);
installChangeNotificationHandlerListener(widget);
installChangeContentListener(widget);
installFocusEventListener(widget);
installFocusInListener(widget);
installDataReadyEventListener(widget);
installSetDataEventListener(widget);
}
Expand Down Expand Up @@ -191,7 +192,14 @@ public Object function(Object[] arguments) {
};
}

protected void installSaveListener(final MDENebulaBasedRichTextWidget widget) {
/**
* Inject java script that calls dedicated functions when the editor receives a focus out event.
*
* Note that, as defined in org\eclipse\nebula\widgets\richtext\resources\template.html, focusOut() is called on
* 'blur' event but only the last BrowserFunction is called(not all registered). So it is preferable to have a
* specific method.
*/
protected void installFocusOutListener(final MDENebulaBasedRichTextWidget widget) {
StringBuilder script = new StringBuilder();

script.append("CKEDITOR.instances.editor.on('blur', function () {");
Expand Down Expand Up @@ -236,44 +244,59 @@ protected void installChangeNotificationHandlerListener(final MDENebulaBasedRich
}

/**
* Listener that temporary saves the editor content when receiving a 'change'
* event.
*
* @param widget the rich text widget.
*/
* Listener that is called when the content of the editor changes.<br/>
* It temporarily saves the editor content when receiving the 'change' event the first time.
*/
protected void createChangeContentListener(final MDENebulaBasedRichTextWidget widget) {
new BrowserFunction(widget.getBrowser(), "firePropertyChangeEvent") { //$NON-NLS-1$
@Override
public Object function(Object[] arguments) {
if (!widget.isDirtyStateUpdated() && widget.isDirty()) {
// As a performance improvement, the saveContent is only
// called only if the dirty state of the widget is not
// updated
widget.saveContent();
widget.setDirtyStateUpdated(true);
}
return null;
}
};
}

protected void installChangeContentListener(final MDENebulaBasedRichTextWidget widget) {
StringBuilder script = new StringBuilder();

/**
* Notice that firePropertyChangeEvent() javascript function is defined
* MDERichTextEditor.
*/
script.append("CKEDITOR.instances.editor.on('key', function () {");
script.append("firePropertyChangeEvent();");
script.append("});");

if (!widget.executeScript(script.toString())) {
Activator.getDefault().getLog().log(new Status(IStatus.WARNING, Activator.PLUGIN_ID,
"Rich text widget cannot install firePropertyChangeEvent handler")); //$NON-NLS-1$
}
final boolean[] widgetSaveTriggeredByContentChangeDetection = { false };

// if supp (resp. backspace) key is used at the end (resp. beginning) of the text, the 'change' event is emitted
// and the widget is saved. But in this case isDirtyStateUpdated should be still be false because there has been non
// change<br/>
// That's why we call setDirtyStateUpdated only if a model change has been effectively be done.
widget.addPropertyChangeListener(evt -> {
if (evt.getSource() == widget && AbstractMDERichTextWidget.WIDGET_SAVED_PROP.equals(evt.getPropertyName())) {
widget.setDirtyStateUpdated(widgetSaveTriggeredByContentChangeDetection[0]);
}
});

new BrowserFunction(widget.getBrowser(), "onChangeEvent") { //$NON-NLS-1$
@Override
public Object function(Object[] arguments) {
if (!widget.isDirtyStateUpdated()) {
// In some case, saveContent is called in a context where the model is persisted. In this context,
// setDirtyStateUpdated(false) need to be called so that saveContent is called at the next editor change.
// But, by design we can not ensure that.
// So we ensure that we prevent further saveContent only if the saveContent is triggered by the edition.
widgetSaveTriggeredByContentChangeDetection[0] = true;
widget.saveContent();
widgetSaveTriggeredByContentChangeDetection[0] = false;
}
return null;
}
};
}

/**
* Inject java script that calls dedicated functions when the editor receives a 'change' in event.
*
* Note that, as defined in org\eclipse\nebula\widgets\richtext\resources\template.html, textModified() is called on
* 'change' event but only the last BrowserFunction is called(not all registered). So it is preferable to have a
* specific method.
*/
protected void installChangeContentListener(final MDENebulaBasedRichTextWidget widget) {
StringBuilder script = new StringBuilder();

script.append("CKEDITOR.instances.editor.on('change', function () {");
script.append("onChangeEvent();");
script.append("});");

if (!widget.executeScript(script.toString())) {
Activator.getDefault().getLog().log(new Status(IStatus.WARNING, Activator.PLUGIN_ID,
"Rich text widget cannot install onChangeEvent handler")); //$NON-NLS-1$
}
}

/**
* Listeners that resets the dirty state when receiving a 'focus' event.
*
Expand All @@ -290,7 +313,14 @@ public Object function(Object[] arguments) {
};
}

protected void installFocusEventListener(final MDENebulaBasedRichTextWidget widget) {
/**
* Inject java script that calls dedicated functions when the editor receives a 'focus' in event.
*
* Note that, as defined in org\eclipse\nebula\widgets\richtext\resources\template.html, focusIn() is called on
* 'focus' event but only the last BrowserFunction is called(not all registered). So it is preferable to have a
* specific method.
*/
protected void installFocusInListener(final MDENebulaBasedRichTextWidget widget) {
StringBuilder script = new StringBuilder();

script.append("CKEDITOR.instances.editor.on('focus', function () {");
Expand Down

0 comments on commit 002018c

Please sign in to comment.