/*******************************************************************************
* Copyright (c) 2007-2013, 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
* G. Weirich - adapted to API- Changes
*
* Sponsors:
* Dr. Peter Schönbucher, Luzern
******************************************************************************/
package org.iatrix.widgets;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Layout;
import org.eclipse.swt.widgets.Sash;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.forms.events.HyperlinkAdapter;
import org.eclipse.ui.forms.events.HyperlinkEvent;
import org.eclipse.ui.forms.widgets.FormToolkit;
import org.eclipse.ui.forms.widgets.Hyperlink;
import org.eclipse.ui.forms.widgets.ILayoutExtension;
import org.iatrix.data.Problem;
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.interfaces.IVerrechenbar;
import ch.elexis.data.Konsultation;
import ch.elexis.data.PersistentObject;
import ch.elexis.data.Verrechnet;
import ch.rgw.tools.Money;
import ch.rgw.tools.StringTool;
/**
* Special composite with a special layout. Don't set a layout!
*
* @author danlutz
*/
public class KonsListComposite {
private static Logger log = LoggerFactory.getLogger(org.iatrix.widgets.KonsListComposite.class);
private final Composite composite;
private final FormToolkit toolkit;
private final Label loadingLabel;
private final List<WidgetRow> widgetRows;
private final Sash sashLeft;
private final Sash sashRight;
private static final String CFG_SASH_X_PERCENT_LEFT =
"org.iatrix/widgets/konslistcomposite/sash_x_percent_left";
private static final String CFG_SASH_X_PERCENT_RIGHT =
"org.iatrix/widgets/konslistcomposite/sash_x_percent_right";
private static final int SASH_X_DEFAULT_PERCENT_LEFT = 10;
private static final int SASH_X_DEFAULT_PERCENT_RIGHT = 75;
private static final int SASH_X_NOTSET = -1;
// current horizontal sash position.
private int currentSashXPercentLeft = SASH_X_NOTSET;
private int currentSashXPercentRight = SASH_X_NOTSET;
private static final String TEXT_NOT_SHOWN = "?";
private List<KonsData> konsultationen;
private Konsultation actKons;
private static LabelProvider verrechnetLabelProvider;
{
verrechnetLabelProvider = new LabelProvider() {
@Override
public String getText(Object element){
if (!(element instanceof Verrechnet)) {
return "";
}
Verrechnet verrechnet = (Verrechnet) element;
String name = verrechnet.getText();
IVerrechenbar verrechenbar = verrechnet.getVerrechenbar();
if (verrechenbar != null) {
String vClass = verrechenbar.getClass().getName();
if (vClass.equals("ch.elexis.data.TarmedLeistung")) {
String nick = ((PersistentObject) verrechnet.getVerrechenbar()).get("Nick");
if (!StringTool.isNothing(nick)) {
name = nick;
}
}
} else {
// verrechenbar is null
log.debug("Invalid Verrechenbar: " + verrechnet.getText());
}
StringBuilder sb = new StringBuilder();
int z = verrechnet.getZahl();
// TODO: Ersetzen durch errechnet.getStandardPreis() ??
Money preis = new Money(verrechnet.getEffPreis()).multiply(z);
sb.append(z).append(" ").append(name).append(" (").append(preis.getAmountAsString())
.append(")");
return sb.toString();
}
};
}
public KonsListComposite(Composite parent, FormToolkit toolkit){
composite = toolkit.createComposite(parent);
this.toolkit = toolkit;
composite.setLayout(new MyLayout());
loadingLabel = toolkit.createLabel(composite, "Lade Konsultationen...");
loadingLabel.setVisible(false);
widgetRows = new ArrayList<>();
sashLeft = new Sash(composite, SWT.VERTICAL);
sashLeft.setVisible(false);
sashLeft.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent event){
int sashX = event.x;
currentSashXPercentLeft = absoluteToPercent(composite.getSize().x, sashX);
CoreHub.localCfg.set(CFG_SASH_X_PERCENT_LEFT, currentSashXPercentLeft);
composite.layout();
}
});
sashRight = new Sash(composite, SWT.VERTICAL);
sashRight.setVisible(false);
sashRight.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent event){
int sashX = event.x;
currentSashXPercentRight = absoluteToPercent(composite.getSize().x, sashX);
CoreHub.localCfg.set(CFG_SASH_X_PERCENT_RIGHT, currentSashXPercentRight);
composite.layout();
}
});
}
public void setLayoutData(Object layoutData){
composite.setLayoutData(layoutData);
}
public void setKonsultationen(List<KonsData> konsultationen, Konsultation actKons){
this.konsultationen = konsultationen;
this.actKons = actKons;
refreshAllKons();
}
private void setLinkEnabled(String caller, WidgetRow row, Konsultation actKons) {
String msg = "";
Konsultation row_kons = null;
if (actKons != null) {
msg = "act: " + actKons.getId() + " " + actKons.getDatum();
}
if (row.konsData != null && row.konsData.konsultation != null) {
row_kons = row.konsData.konsultation;
msg += " row " + row_kons.getId() + " " + row_kons.getDatum() + (row.hTitle.getEnabled() ? " wasEnabled " : " wasDisabled");
}
if (actKons != null && row != null && row_kons != null) {
// System.out.println("hTitle for "+ row.hTitle.getText() + " " + row_kons.getId());
boolean konsEditable = row_kons.isEditable(false);
boolean disabled = row_kons.getId().equals(actKons.getId()) || !konsEditable;
if (disabled == row.hTitle.getEnabled()) {
// log.trace(caller + " hTitle for " + row.hTitle.getText() + " from "
// + msg + " konsEditable " + konsEditable + " => " + (disabled ? "disabled" : "ensabled"));
row.hTitle.setEnabled(!disabled);
}
} else if (row != null) {
boolean enabled =row.hTitle != null;
row.hTitle.setEnabled(enabled);
}
}
public void refeshHyperLinks(Konsultation selectedKons){
String konsString = selectedKons != null ? selectedKons.getId() + " " + selectedKons.getLabel() : "null";
log.debug("refeshHyperLinks for " + konsString + " on " + widgetRows.size() + " rows and redraw");
actKons = selectedKons;
for (WidgetRow row : widgetRows) {
setLinkEnabled("refeshHyperLinks", row, selectedKons);
}
composite.redraw();
}
// refresh layout and all elements
private void refreshAllKons(){
// clear all widget rows
for (WidgetRow row : widgetRows) {
row.setKonsData(null);
}
List<WidgetRow> availableRows = new ArrayList<>();
availableRows.addAll(widgetRows);
if (konsultationen != null) {
int j = 0;
for (KonsData konsData : konsultationen) {
WidgetRow row;
if (availableRows.size() > 0) {
row = availableRows.remove(0);
} else {
row = new WidgetRow(composite);
widgetRows.add(row);
}
j += 1;
row.hTitle.setData("TEST_COMP_NAME", "KG_Iatrix_klc_row_"+j + "_htitle"); // for Jubula
row.etf.setData("TEST_COMP_NAME", "KG_Iatrix_klc_row_"+j + "_text"); // for Jubula
row.verrechnung.setData("TEST_COMP_NAME", "KG_Iatrix_klc_row_"+j + "_verrechnung"); // for Jubula
row.problems.setData("TEST_COMP_NAME", "KG_Iatrix_klc_row_"+j + "_problems"); // for Jubula
row.setKonsData(konsData);
}
log.debug("refreshAllKons for " + widgetRows.size() + " rows and " + konsultationen.size() +" konsultationen ");
loadingLabel.setVisible(false);
sashLeft.setVisible(konsultationen.size() > 0);
sashRight.setVisible(konsultationen.size() > 0);
if (actKons == null && (actKons = konsultationen.get(0).konsultation) != null) {
log.debug("refreshAllKons for " + widgetRows.size() + " setting actKons to " + actKons.getId());
}
refeshHyperLinks(actKons);
} else {
loadingLabel.setVisible(false);
sashLeft.setVisible(false);
sashRight.setVisible(false);
}
composite.layout(true);
}
private int percentToAbsolute(int base, int percent){
return base * percent / 100;
}
private int absoluteToPercent(int base, int absolute){
return absolute * 100 / base;
}
/**
* This class encapsulates the required widgets for a row. It assumes that
*/
private class WidgetRow {
Hyperlink hTitle;
Label lFall;
Text problems;
EnhancedTextFieldRO etf;
Text verrechnung;
Label horizontalSeparator;
KonsData konsData;
// collect controls for disposal in dispose()
List<Control> controls;
WidgetRow(Composite parent){
/*
* Important: Add all created controls to "controls" for later disposal.
*/
controls = new ArrayList<>();
// header
hTitle = toolkit.createHyperlink(parent, "", SWT.NONE);
hTitle.addHyperlinkListener(new HyperlinkAdapter() {
@Override
public void linkActivated(HyperlinkEvent e){
if (actKons != null && konsData.konsultation != null) {
Konsultation selectedKons = (Konsultation) ElexisEventDispatcher.getSelected(Konsultation.class);
boolean enableFire = !konsData.konsultation.getId()
.contentEquals(selectedKons == null ? "" : selectedKons.getId());
if (enableFire) {
log.debug("fireSelectionEvent "+ konsData.konsultation.getId() + " "+ konsData.konsultation.getDatum());
ElexisEventDispatcher.fireSelectionEvent(konsData.konsultation);
} else {
refeshHyperLinks(selectedKons);
}
}
}
});
controls.add(hTitle);
lFall = toolkit.createLabel(parent, "");
controls.add(lFall);
problems = toolkit.createText(parent, "", SWT.MULTI | SWT.READ_ONLY);
controls.add(problems);
etf = new EnhancedTextFieldRO(parent);
controls.add(etf);
toolkit.adapt(etf);
verrechnung = toolkit.createText(parent, "", SWT.MULTI | SWT.READ_ONLY);
controls.add(verrechnung);
horizontalSeparator = toolkit.createLabel(parent, "", SWT.SEPARATOR | SWT.HORIZONTAL);
controls.add(horizontalSeparator);
konsData = null;
showControls(false);
}
public void setKonsData(KonsData konsData){
this.konsData = konsData;
if (konsData != null) {
showControls(true);
} else {
showControls(false);
}
refresh();
}
// set the text of the controls
private void refresh(){
if (konsData != null) {
hTitle.setText(konsData.konsTitle);
lFall.setText(konsData.fallTitle);
problems.setText(konsData.problemsText);
etf.setText(konsData.konsText);
verrechnung.setText(konsData.verrechnungenText);
} else {
hTitle.setText("");
lFall.setText("");
problems.setText("");
etf.setText("");
verrechnung.setText("");
}
}
void showControls(boolean visible){
for (Control control : controls) {
if (control != null) {
control.setVisible(visible);
}
}
}
}
public class MyLayout extends Layout implements ILayoutExtension {
private static final int TITLE_SPACING = 2;
private static final int ROW_SPACING = 4;
// size caches
private int minWidthCache = -1;
private int maxWidthCache = -1;
private Point labelSizeCache = null;
private Point rowSizeTotalCache = null;
// ILayoutExtension
/**
* Computes the minimum width of the parent. All widgets capable of word wrapping should
* return the width of the longest word that cannot be broken any further.
*
* @param parent
* the parent composite
* @param changed
* <code>true</code> if the cached information should be flushed,
* <code>false</code> otherwise.
* @return the minimum width of the parent composite
*/
@Override
public int computeMinimumWidth(Composite parent, boolean changed){
return computeMinimumMaximumWidth(parent, changed, false);
}
/**
* Computes the maximum width of the parent. All widgets capable of word wrapping should
* return the length of the entire text with wrapping turned off.
*
* @param parent
* the parent composite
* @param changed
* <code>true</code> if the cached information should be flushed,
* <code>false</code> otherwise.
* @return the maximum width of the parent composite
*/
@Override
public int computeMaximumWidth(Composite parent, boolean changed){
return computeMinimumMaximumWidth(parent, changed, true);
}
private int computeMinimumMaximumWidth(Composite parent, boolean changed, boolean max){
// use cached values
if (!changed) {
if (max) {
if (maxWidthCache != -1) {
return maxWidthCache;
}
} else {
if (minWidthCache != -1) {
return minWidthCache;
}
}
}
// clear caches
maxWidthCache = -1;
minWidthCache = -1;
// recalculate
int sashWidthLeft = sashLeft.computeSize(SWT.DEFAULT, SWT.DEFAULT).x;
int sashWidthRight = sashRight.computeSize(SWT.DEFAULT, SWT.DEFAULT).x;
int leftWidth = 0;
int middleWidth = 0;
int rightWidth = 0;
int totalWidth = 0;
for (WidgetRow row : widgetRows) {
if (row.konsData == null) {
// ignore
continue;
}
int width;
// for hTitle and lFall, min/max are identical
width = row.hTitle.computeSize(SWT.DEFAULT, SWT.DEFAULT, changed).x
+ row.lFall.computeSize(SWT.DEFAULT, SWT.DEFAULT, changed).x;
if (width > totalWidth) {
totalWidth = width;
}
// left control (problems)
if (max) {
width = row.problems.computeSize(SWT.DEFAULT, SWT.DEFAULT, true).x;
} else {
width = row.problems.computeSize(5, SWT.DEFAULT, true).x;
}
if (width > leftWidth) {
leftWidth = width;
}
// middle control (etf)
if (max) {
width = row.etf.computeSize(SWT.DEFAULT, SWT.DEFAULT, true).x;
} else {
width = row.etf.computeSize(5, SWT.DEFAULT).x;
}
if (width > middleWidth) {
middleWidth = width;
}
// right control (verrechnung)
if (max) {
width = row.verrechnung.computeSize(SWT.DEFAULT, SWT.DEFAULT, true).x;
} else {
width = row.verrechnung.computeSize(5, SWT.DEFAULT).x;
}
if (width > rightWidth) {
rightWidth = width;
}
}
int width = Math.max(totalWidth, leftWidth + middleWidth + rightWidth);
width += sashWidthLeft + sashWidthRight;
if (max) {
maxWidthCache = width;
} else {
minWidthCache = width;
}
return width;
}
@Override
protected Point computeSize(Composite composite, int wHint, int hHint, boolean flushCache){
Point size = layout(false, flushCache);
return size;
}
@Override
protected void layout(Composite composite, boolean flushCache){
layout(true, flushCache);
}
private Point layout(boolean move, boolean flushCache){
int width = composite.getSize().x;
if (loadingLabel.isVisible()) {
return layoutLoadingLabel(move, width, flushCache);
} else {
return layoutRows(move, width, flushCache);
}
}
private Point layoutLoadingLabel(boolean move, int width, boolean flushCache){
Point size;
if (!flushCache && labelSizeCache != null) {
size = new Point(labelSizeCache.x, labelSizeCache.y);
} else {
size = loadingLabel.computeSize(width, SWT.DEFAULT, flushCache);
labelSizeCache = new Point(size.x, size.y);
}
if (move) {
loadingLabel.setSize(size);
}
return size;
}
/**
* Set caches to null if flushCache is true Remain caches if flushCache is false
*
* @param flushCache
* true or false
*/
private void initializeCaches(boolean flushCache){
if (flushCache) {
rowSizeTotalCache = null;
}
}
private Point layoutRows(boolean move, int width, boolean flushCache){
initializeCaches(flushCache);
if (!move && !flushCache && rowSizeTotalCache != null) {
return new Point(rowSizeTotalCache.x, rowSizeTotalCache.y);
}
if (widgetRows == null) {
rowSizeTotalCache = new Point(0, 0);
return new Point(0, 0);
}
int sashWidthLeft = sashLeft.computeSize(SWT.DEFAULT, SWT.DEFAULT).x;
int sashWidthRight = sashRight.computeSize(SWT.DEFAULT, SWT.DEFAULT).x;
int sashXLeft;
if (currentSashXPercentLeft != SASH_X_NOTSET) {
sashXLeft = percentToAbsolute(width, currentSashXPercentLeft);
} else {
// not yet set
int cfgSashXPercentLeft =
CoreHub.localCfg.get(CFG_SASH_X_PERCENT_LEFT, SASH_X_NOTSET);
if (cfgSashXPercentLeft != SASH_X_NOTSET && cfgSashXPercentLeft < 100) {
sashXLeft = percentToAbsolute(width, cfgSashXPercentLeft);
} else {
// default
sashXLeft = percentToAbsolute(width, SASH_X_DEFAULT_PERCENT_LEFT);
}
}
int sashXRight;
if (currentSashXPercentRight != SASH_X_NOTSET) {
sashXRight = percentToAbsolute(width, currentSashXPercentRight);
} else {
// not yet set
int cfgSashXPercentRight =
CoreHub.localCfg.get(CFG_SASH_X_PERCENT_RIGHT, SASH_X_NOTSET);
if (cfgSashXPercentRight != SASH_X_NOTSET && cfgSashXPercentRight < 100) {
sashXRight = percentToAbsolute(width, cfgSashXPercentRight);
} else {
// default
sashXRight = percentToAbsolute(width, SASH_X_DEFAULT_PERCENT_RIGHT);
}
}
int leftX = 0;
int middleX = sashXLeft + sashWidthLeft;
int rightX = sashXRight + sashWidthRight;
int leftWidth = sashXLeft;
int middleWidth = sashXRight - middleX;
int rightWidth = width - rightX;
int y = 0;
for (WidgetRow row : widgetRows) {
if (row.konsData == null) {
// ignore
continue;
}
int currentHeight;
Point konsTitleSize = row.hTitle.computeSize(SWT.DEFAULT, SWT.DEFAULT, flushCache);
Point fallTitleSize = row.lFall.computeSize(SWT.DEFAULT, SWT.DEFAULT, flushCache);
int konsTitleWidth = konsTitleSize.x;
int fallTitleWidth = Math.min(width - konsTitleWidth, fallTitleSize.x);
currentHeight = Math.max(konsTitleSize.y, fallTitleSize.y);
if (move) {
row.hTitle.setBounds(leftX, y, konsTitleWidth, currentHeight);
row.lFall.setBounds(width - fallTitleWidth, y, fallTitleWidth, currentHeight);
// set z-order
row.hTitle.moveAbove(sashLeft);
row.lFall.moveAbove(sashRight);
}
y += currentHeight + TITLE_SPACING;
Point problemsSize = row.problems.computeSize(leftWidth, SWT.DEFAULT, flushCache);
Point etfSize = row.etf.computeSize(middleWidth, SWT.DEFAULT, flushCache);
Point verrechnungSize =
row.verrechnung.computeSize(rightWidth, SWT.DEFAULT, flushCache);
currentHeight = Math.max(Math.max(problemsSize.y, etfSize.y), verrechnungSize.y);
if (move) {
row.problems.setBounds(leftX, y, leftWidth, currentHeight);
row.etf.setBounds(middleX, y, middleWidth, currentHeight);
row.verrechnung.setBounds(rightX, y, rightWidth, currentHeight);
row.horizontalSeparator.setBounds(leftX, y + currentHeight, width, 1);
// set z-order
row.horizontalSeparator.moveAbove(sashLeft);
row.horizontalSeparator.moveAbove(sashRight);
}
y += currentHeight + 1 + ROW_SPACING; // grow including border
}
int height = y - ROW_SPACING; // the last ROW_SPACING is too much
if (move) {
sashLeft.setBounds(sashXLeft, 0, sashWidthLeft, height);
sashRight.setBounds(sashXRight, 0, sashWidthRight, height);
}
Point size = new Point(width, height);
rowSizeTotalCache = new Point(width, height);
return size;
}
}
public static class KonsData {
Konsultation konsultation;
boolean showCharges;
// cache fields
String konsTitle;
String fallTitle;
String problemsText;
String konsText;
String verrechnungenText;
public KonsData(Konsultation konsultation, boolean showCharges){
this.konsultation = konsultation;
this.showCharges = showCharges;
updateCacheFields();
}
private void updateCacheFields(){
if (konsultation != null) {
String lineSeparator = System.getProperty("line.separator");
konsTitle = konsultation.getLabel();
if (!konsultation.isEditable(false)) {
konsTitle = konsTitle + " Nicht editierbar (Zugriffsrechte/Verrechnet)";
}
fallTitle = konsultation.getFall().getLabel();
List<Problem> problems = Problem.getProblemsOfKonsultation(konsultation);
problemsText = assembleProblemsText(problems);
konsText = konsultation.getEintrag().getHead();
if (konsText == null) {
konsText = "";
}
if (showCharges) {
List<Verrechnet> leistungen = konsultation.getLeistungen();
List<String> leistungenLabels = replaceBlocks(leistungen);
StringBuffer sb = new StringBuffer();
boolean isFirst = true;
for (String leistungLabel : leistungenLabels) {
if (isFirst) {
isFirst = false;
} else {
sb.append(lineSeparator);
}
sb.append(leistungLabel);
}
verrechnungenText = sb.toString();
} else {
verrechnungenText = TEXT_NOT_SHOWN;
}
} else {
konsTitle = "";
fallTitle = "";
problemsText = "";
konsText = "";
verrechnungenText = "";
}
}
private String assembleProblemsText(List<Problem> problems){
String lineSeparator = System.getProperty("line.separator");
StringBuffer sb = new StringBuffer();
if (problems != null) {
boolean isFirst = true;
for (Problem problem : problems) {
if (isFirst) {
isFirst = false;
} else {
sb.append(lineSeparator);
}
sb.append(problem.getTitle());
}
}
return sb.toString();
}
private List<String> replaceBlocks(List<Verrechnet> leistungen){
List<String> labels = new ArrayList<>();
for (Verrechnet leistung : leistungen) {
labels.add(verrechnetLabelProvider.getText(leistung));
}
return labels;
}
}
}