/*******************************************************************************
* Copyright (c) 2007-2015, D. Lutz and Elexis.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* D. Lutz - initial API and implementation
* Gerry Weirich - adapted for 2.1
* Niklaus Giger - small improvements, split into 20 classes
*
* Sponsors:
* Dr. Peter Schönbucher, Luzern
******************************************************************************/
package org.iatrix.widgets;
import java.util.Hashtable;
import java.util.List;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.SashForm;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.FocusAdapter;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.forms.widgets.FormToolkit;
import org.iatrix.data.KonsTextLock;
import org.iatrix.dialogs.ChooseKonsRevisionDialog;
import org.iatrix.util.Heartbeat;
import org.iatrix.util.Heartbeat.IatrixHeartListener;
import org.iatrix.views.JournalView;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ch.elexis.core.data.activator.CoreHub;
import ch.elexis.core.data.events.ElexisEventDispatcher;
import ch.elexis.core.data.util.Extensions;
import ch.elexis.core.ui.UiDesk;
import ch.elexis.core.ui.constants.ExtensionPointConstantsUi;
import ch.elexis.core.ui.icons.Images;
import ch.elexis.core.ui.text.EnhancedTextField;
import ch.elexis.core.ui.util.IKonsExtension;
import ch.elexis.core.ui.util.IKonsMakro;
import ch.elexis.core.ui.util.SWTHelper;
import ch.elexis.data.Anwender;
import ch.elexis.data.Konsultation;
import ch.elexis.data.PersistentObject;
import ch.rgw.tools.TimeTool;
import ch.rgw.tools.VersionedResource;
import ch.rgw.tools.VersionedResource.ResourceItem;
public class KonsText implements IJournalArea {
private static Konsultation actKons = null;
private static int konsTextSaverCount = 0;
private static Logger log = LoggerFactory.getLogger(org.iatrix.widgets.KonsText.class);
private static EnhancedTextField text;
private static Label lVersion = null;
private static Label lKonsLock = null;
private static KonsTextLock konsTextLock = null;
int displayedVersion;
private Action purgeAction;
private Action saveAction;
private Action chooseVersionAction;
private Action versionFwdAction;
private Action versionBackAction;
private static final String PATIENT_KEY = "org.iatrix.patient";
private boolean konsEditorHasFocus = false;
private final FormToolkit tk;
private Composite parent;
private Hashtable<String, IKonsExtension> hXrefs;
public KonsText(Composite parentComposite){
parent = parentComposite;
tk = UiDesk.getToolkit();
SashForm konsultationSash = new SashForm(parent, SWT.HORIZONTAL);
konsultationSash.setLayoutData(SWTHelper.getFillGridData(1, true, 1, true));
Composite konsultationTextComposite = tk.createComposite(konsultationSash);
konsultationTextComposite.setLayout(new GridLayout(1, true));
text = new EnhancedTextField(konsultationTextComposite);
hXrefs = new Hashtable<>();
@SuppressWarnings("unchecked")
List<IKonsExtension> listKonsextensions = Extensions.getClasses(
Extensions.getExtensions(ExtensionPointConstantsUi.KONSEXTENSION), "KonsExtension", //$NON-NLS-1$ //$NON-NLS-2$
false);
for (IKonsExtension x : listKonsextensions) {
String provider = x.connect(text);
hXrefs.put(provider, x);
}
@SuppressWarnings("unchecked")
List<IKonsMakro> makros = Extensions.getClasses(
Extensions.getExtensions(ExtensionPointConstantsUi.KONSEXTENSION), "KonsMakro", false); //$NON-NLS-1$
text.setExternalMakros(makros);
text.setLayoutData(SWTHelper.getFillGridData(1, true, 1, true));
makeActions();
text.getControl().addDisposeListener(new DisposeListener() {
@Override
public void widgetDisposed(DisposeEvent e){
logEvent("widgetDisposed removeKonsTextLock");
updateEintrag();
removeKonsTextLock();
konsEditorHasFocus = false;
}
});
text.getControl().addFocusListener(new FocusAdapter() {
@Override
public void focusGained(FocusEvent e){
logEvent("focusGained");
konsEditorHasFocus = true;
}
@Override
public void focusLost(FocusEvent e){
logEvent("focusLost updateEintrag");
updateEintrag();
konsEditorHasFocus = false;
}
});
Control control = text.getControl();
if (control instanceof StyledText) {
StyledText styledText = (StyledText) control;
styledText.addKeyListener(new KeyAdapter() {
@Override
public void keyReleased(KeyEvent e){
// create new consultation if required
// handleInitialKonsText();
}
});
}
tk.adapt(text);
lVersion = tk.createLabel(konsultationTextComposite, "<aktuell>");
lVersion.setLayoutData(SWTHelper.getFillGridData(1, true, 1, false));
lKonsLock = tk.createLabel(konsultationTextComposite, "");
lKonsLock.setLayoutData(SWTHelper.getFillGridData(1, true, 1, false));
lKonsLock.setForeground(lKonsLock.getDisplay().getSystemColor(SWT.COLOR_RED));
lKonsLock.setVisible(false);
registerUpdateHeartbeat();
}
public Action getPurgeAction(){
return purgeAction;
}
public Action getSaveAction(){
return saveAction;
}
public Action getChooseVersionAction(){
return chooseVersionAction;
}
public Action getVersionForwardAction(){
return versionFwdAction;
}
public Action getVersionBackAction(){
return versionBackAction;
}
public synchronized void updateEintrag(){
if (actKons != null) {
if (actKons.getFall() == null) {
return;
}
if (text.isDirty() || textChanged()) {
logEvent("updateEintrag " + actKons.getId());
if (hasKonsTextLock()) {
actKons.updateEintrag(text.getContentsAsXML(), false);
int new_version = actKons.getHeadVersion();
logEvent("updateEintrag saved rev. " + new_version + " "
+ text.getContentsPlaintext());
text.setDirty(false);
// update kons version label
// (we would get an objectChanged event, but this event isn't processed
// in case the kons text field has the focus.)
updateKonsVersionLabel();
JournalView.updateAllKonsAreas(actKons, KonsActions.ACTIVATE_KONS);
// ElexisEventDispatcher.fireSelectionEvent(actKons);
} else {
// should never happen...
if (konsTextLock == null) {
logEvent("updateEintrag Konsultation gesperrt. konsTextLock null.");
} else {
logEvent("updateEintrag Konsultation gesperrt. " + " key "
+ konsTextLock.getKey() + " lock " + konsTextLock.getLockValue());
SWTHelper.alert("Konsultation gesperrt",
"Der Text kann nicht gespeichert werden, weil die Konsultation durch einen anderen Benutzer gesperrt ist."
+ "(info: " + konsTextLock.getKey()
+ ". Dieses Problem ist ein Programmfehler. Bitte informieren Sie die Entwickler.)");
}
}
}
}
}
/**
* Check whether the text in the text field has changed compared to the database entry.
*
* @return true, if the text changed, false else
*/
private boolean textChanged(){
if (actKons == null) {
return false;
}
String dbEintrag = actKons.getEintrag().getHead();
String textEintrag = text.getContentsAsXML();
if (textEintrag != null) {
if (!textEintrag.equals(dbEintrag)) {
// text differs from db entry
logEvent("saved text != db entry");
return true;
}
}
return false;
}
private void updateKonsLockLabel(){
if (konsTextLock == null || hasKonsTextLock()) {
lKonsLock.setVisible(false);
lKonsLock.setText("");
} else {
Anwender user = konsTextLock.getLockValue().getUser();
StringBuilder text = new StringBuilder();
if (user != null && user.exists()) {
text.append(
"Konsultation wird von Benutzer \"" + user.getLabel() + "\" bearbeitet.");
} else {
text.append("Konsultation wird von anderem Benutzer bearbeitet.");
}
text.append(" Rechner \"" + konsTextLock.getLockValue().getHost() + "\".");
log.debug("updateKonsLockLabel: " + text.toString());
lKonsLock.setText(text.toString());
lKonsLock.setVisible(true);
}
lKonsLock.getParent().layout();
}
// helper method to create a KonsTextLock object in a save way
// should be called when a new konsultation is set
private synchronized void createKonsTextLock(){
// remove old lock
removeKonsTextLock();
if (actKons != null && CoreHub.actUser != null) {
konsTextLock = new KonsTextLock(actKons, CoreHub.actUser);
} else {
konsTextLock = null;
}
if (konsTextLock != null) {
konsTextLock.lock();
// boolean success = konsTextLock.lock();
// logEvent(
// "createKonsTextLock: konsText locked (" + success + ")" + konsTextLock.getKey());
}
}
// helper method to release a KonsTextLock
// should be called before a new konsultation is set
// or the program/view exits
private synchronized void removeKonsTextLock(){
if (konsTextLock != null) {
konsTextLock.unlock();
// boolean success = konsTextLock.unlock();
// logEvent(
// "removeKonsTextLock: konsText unlocked (" + success + ") " + konsTextLock.getKey());
konsTextLock = null;
}
}
/**
* Check whether we own the lock
*
* @return true, if we own the lock, false else
*/
private synchronized boolean hasKonsTextLock(){
return (konsTextLock != null && konsTextLock.isLocked());
}
@Override
public synchronized void visible(boolean mode){
}
private void makeActions(){
// Konsultationstext
purgeAction = new Action("Alte Eintragsversionen entfernen") {
@Override
public void run(){
actKons.purgeEintrag();
ElexisEventDispatcher.fireSelectionEvent(actKons);
}
};
versionBackAction = new Action("Vorherige Version") {
@Override
public void run(){
if (MessageDialog.openConfirm(
PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(),
"Konsultationstext ersetzen",
"Wollen Sie wirklich den aktuellen Konsultationstext gegen eine frühere Version desselben Eintrags ersetzen?")) {
setKonsText(actKons, displayedVersion - 1, false);
text.setDirty(true);
}
}
};
versionFwdAction = new Action("nächste Version") {
@Override
public void run(){
if (MessageDialog.openConfirm(
PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(),
"Konsultationstext ersetzen",
"Wollen Sie wirklich den aktuellen Konsultationstext gegen eine spätere Version desselben Eintrags ersetzen?")) {
setKonsText(actKons, displayedVersion + 1, false);
text.setDirty(true);
}
}
};
chooseVersionAction = new Action("Version wählen...") {
@Override
public void run(){
ChooseKonsRevisionDialog dlg = new ChooseKonsRevisionDialog(
PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(), actKons);
if (dlg.open() == ChooseKonsRevisionDialog.OK) {
int selectedVersion = dlg.getSelectedVersion();
if (MessageDialog.openConfirm(
PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(),
"Konsultationstext ersetzen",
"Wollen Sie wirklich den aktuellen Konsultationstext gegen die Version "
+ selectedVersion + " desselben Eintrags ersetzen?")) {
setKonsText(actKons, selectedVersion, false);
text.setDirty(true);
}
}
}
};
saveAction = new Action("Eintrag sichern") {
{
setImageDescriptor(Images.IMG_DISK.getImageDescriptor());
setToolTipText("Text explizit speichern");
}
@Override
public void run(){
logEvent("saveAction: ");
updateEintrag();
JournalView.updateAllKonsAreas(actKons, KonsActions.ACTIVATE_KONS);
}
};
};
private void updateKonsultation(boolean updateText){
if (actKons != null) {
if (updateText) {
setKonsText(actKons, actKons.getHeadVersion(), true);
}
logEvent("updateKonsultation: " + actKons.getId());
} else {
setKonsText(null, 0, true);
logEvent("updateKonsultation: null");
}
}
/**
* Aktuelle Konsultation setzen.
*
* Wenn eine Konsultation gesetzt wird stellen wir sicher, dass der gesetzte Patient zu dieser
* Konsultation gehoert. Falls nicht, wird ein neuer Patient gesetzt.
*
* @param putCaretToEnd
* if true, activate text field ant put caret to the end
*/
@Override
public synchronized void setKons(Konsultation k, KonsActions op){
if (op == KonsActions.SAVE_KONS) {
if (text.isDirty() || textChanged()) {
logEvent("setKons.SAVE_KONS text.isDirty or changed saving Kons from "
+ actKons.getDatum() + " is '" + text.getContentsPlaintext() + "'");
updateEintrag();
text.setData(PATIENT_KEY, null);
text.setText("saved kons");
removeKonsTextLock();
actKons = null; // Setting it to null made clicking twice for a kons in the kons history the kontext disapper
} else {
if (actKons != null && text != null) {
logEvent("setKons.SAVE_KONS nothing to save for Kons from " + actKons.getDatum()
+ " is '" + text.getContentsPlaintext() + "'");
}
}
return;
}
if (op == KonsActions.ACTIVATE_KONS) {
// make sure to unlock the kons edit field and release the lock
if (text != null && actKons != null) {
logEvent("setKons.ACTIVATE_KONS text.isDirty " + text.isDirty() + " textChanged "
+ textChanged() + " actKons vom: " + actKons.getDatum());
}
removeKonsTextLock();
if (k == null) {
actKons = k;
logEvent("setKons null");
} else {
logEvent("setKons " + (actKons == null ? "null" : actKons.getId()) +
" => " + k.getId());
actKons = k;
if (!actKons.isEditable(false)) {
// isEditable(true) would give feedback to user why consultation
// cannot be edited, but this often very shortlived as we create/switch
// to a newly created kons of today
logEvent("setKons actKons is not editable");
text.setEnabled(false);
setKonsText(k, 0, true);
updateKonsultation(true);
updateKonsLockLabel();
lVersion.setText(lVersion.getText() + " Nicht editierbar. (Keine Zugriffsrechte oder schon verrechnet)");
return;
} else {
text.setEnabled(true);
}
createKonsTextLock();
setKonsText(k, 0, true);
}
updateKonsultation(true);
updateKonsLockLabel();
updateKonsVersionLabel();
saveAction.setEnabled(konsTextLock == null || hasKonsTextLock());
}
}
/**
* Set the version label to reflect the current kons' latest version Called by: updateEintrag()
*/
private void updateKonsVersionLabel(){
if (actKons != null) {
int version = actKons.getHeadVersion();
logEvent("Update Version Label: " + version);
VersionedResource vr = actKons.getEintrag();
ResourceItem entry = vr.getVersion(version);
StringBuilder sb = new StringBuilder();
if (entry != null) {
String revisionTime = new TimeTool(entry.timestamp).toString(TimeTool.FULL_GER);
String revisionDate = new TimeTool(entry.timestamp).toString(TimeTool.DATE_GER);
if (!actKons.getDatum().equals(revisionDate)) {
sb.append("Kons vom " + actKons.getDatum() + ": ");
}
sb.append("rev. ").append(version).append(" vom ")
.append(revisionTime).append(" (")
.append(entry.remark).append(")");
}
lVersion.setText(sb.toString());
} else {
lVersion.setText("");
}
}
private synchronized void setKonsText(Konsultation b, int version, boolean putCaretToEnd){
if (b != null) {
String ntext = "";
if ((version >= 0) && (version <= b.getHeadVersion())) {
VersionedResource vr = b.getEintrag();
ResourceItem entry = vr.getVersion(version);
ntext = entry.data;
StringBuilder sb = new StringBuilder();
sb.append("rev. ").append(version).append(" vom ")
.append(new TimeTool(entry.timestamp).toString(TimeTool.FULL_GER)).append(" (")
.append(entry.remark).append(")");
lVersion.setText(sb.toString());
} else {
lVersion.setText("");
}
text.setText(PersistentObject.checkNull(ntext));
text.setKons(b);
text.setEnabled(hasKonsTextLock());
displayedVersion = version;
versionBackAction.setEnabled(version != 0);
versionFwdAction.setEnabled(version != b.getHeadVersion());
boolean locked = hasKonsTextLock();
int strlen = text.getContentsPlaintext().length();
int maxLen = strlen < 120 ? strlen : 120;
String label = (konsTextLock == null) ? "null " : konsTextLock.getLabel();
if (!locked)
logEvent("setKonsText availabee " + b.getId() + " " + label + " putCaretToEnd " + putCaretToEnd +
" " + lVersion.getText() + " '" + text.getContentsPlaintext().substring(0, maxLen) + "'");
else
logEvent("setKonsText (locked) " + b.getId() + " " + label + " putCaretToEnd " + putCaretToEnd +
" " + lVersion.getText() + " '" + text.getContentsPlaintext().substring(0, maxLen) + "'");
if (putCaretToEnd) {
// set focus and put caret at end of text
text.putCaretToEnd();
}
} else {
lVersion.setText("");
text.setText("");
text.setKons(null);
text.setEnabled(false);
displayedVersion = -1;
versionBackAction.setEnabled(false);
versionFwdAction.setEnabled(false);
logEvent("setKonsText null " + lVersion.getText() + " " + text.getContentsPlaintext());
}
}
private void logEvent(String msg){
StringBuilder sb = new StringBuilder(msg + ": ");
if (actKons == null) {
sb.append("actKons null");
} else {
sb.append(actKons.getId());
sb.append(" kons rev. " + actKons.getHeadVersion());
sb.append(" vom " + actKons.getDatum());
if (actKons.getFall() != null) {
sb.append(" " + actKons.getFall().getPatient().getPersonalia());
}
}
log.debug(sb.toString());
}
@Override
public synchronized void activation(boolean mode){
}
public synchronized void registerUpdateHeartbeat(){
Heartbeat heat = Heartbeat.getInstance();
heat.addListener(new IatrixHeartListener() {
private int konsTextSaverPeriod;
@Override
public void heartbeat(){
logEvent("Period: " + konsTextSaverPeriod);
if (!(konsTextSaverPeriod > 0)) {
// auto-save disabled
return;
}
// inv: konsTextSaverPeriod > 0
// increment konsTextSaverCount, but stay inside period
konsTextSaverCount++;
konsTextSaverCount %= konsTextSaverPeriod;
logEvent("konsTextSaverCount = " + konsTextSaverCount + " konsEditorHasFocus: "
+ konsEditorHasFocus);
if (konsTextSaverCount == 0) {
if (konsEditorHasFocus) {
logEvent("Auto Save Kons Text");
updateEintrag();
}
}
}
});
}
public String getPlainText(){
if (text == null ) {
return "";
}
return text.getContentsPlaintext();
}
}