/*******************************************************************************
* Copyright (c) 2009-2012, G. Weirich 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
*
* Sponsoring:
* mediX Notfallpaxis, diepraxen Stauffacher AG, Zürich
*
* Contributors:
* G. Weirich - initial implementation
* M. Descher - dynamic adaptation of font size
*******************************************************************************/
package ch.elexis.agenda.ui;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.swt.SWT;
import org.eclipse.swt.dnd.DragSourceEvent;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
import ch.elexis.actions.Activator;
import ch.elexis.actions.AgendaActions;
import ch.elexis.agenda.Messages;
import ch.elexis.agenda.acl.ACLContributor;
import ch.elexis.agenda.data.Termin;
import ch.elexis.agenda.preferences.PreferenceConstants;
import ch.elexis.agenda.series.SerienTermin;
import ch.elexis.agenda.series.ui.SerienTerminDialog;
import ch.elexis.agenda.util.Plannables;
import ch.elexis.core.data.activator.CoreHub;
import ch.elexis.core.data.events.ElexisEventDispatcher;
import ch.elexis.core.ui.UiDesk;
import ch.elexis.core.ui.icons.Images;
import ch.elexis.core.ui.locks.AcquireLockBlockingUi;
import ch.elexis.core.ui.locks.ILockHandler;
import ch.elexis.core.ui.util.PersistentObjectDragSource;
import ch.elexis.core.ui.util.SWTHelper;
import ch.elexis.data.PersistentObject;
import ch.elexis.dialogs.TerminDialog;
public class TerminLabel extends Composite {
private Label lbl;
private Termin t;
private SerienTermin serienTermin;
private int column;
private Composite state;
private int originalFontHeightPixel, originalFontHeightPoint;
private FontData lblFontData;
IAgendaLayout ial;
Activator agenda = Activator.getDefault();
private IAction terminKuerzenAction, terminVerlaengernAction, terminAendernAction;
private GC lblGc;
private List<TerminLabel> overLapped;
/**
* Static map holding all fonts used by all TerminLabel instances.
*/
private static HashMap<Integer, Font> fontMap = new HashMap<Integer, Font>();
public TerminLabel(IAgendaLayout al){
super(al.getComposite(), SWT.BORDER);
ial = al;
makeActions();
GridLayout gl = new GridLayout(2, false);
gl.marginHeight = 1;
gl.marginWidth = 1;
setLayout(gl);
lbl = new Label(this, SWT.WRAP);
lblGc = new GC(lbl);
lblFontData = lbl.getFont().getFontData()[0];
originalFontHeightPoint = lblFontData.getHeight();
originalFontHeightPixel = lblGc.getFontMetrics().getHeight();
lblGc.dispose();
state = new Composite(this, SWT.NONE);
state.setLayoutData(new GridData());
lbl.addMouseListener(new MouseAdapter() {
@Override
public void mouseDoubleClick(MouseEvent e){
agenda.setActDate(getTermin().getDay());
agenda.setActResource(getTermin().getBereich());
AcquireLockBlockingUi.aquireAndRun(getTermin(), new ILockHandler() {
@Override
public void lockFailed(){
// do nothing
}
@Override
public void lockAcquired(){
if (getTermin().isRecurringDate()) {
new SerienTerminDialog(UiDesk.getTopShell(),
new SerienTermin(getTermin()))
.open();
} else {
new TerminDialog(getTermin()).open();
}
}
});
refresh();
}
@Override
public void mouseUp(MouseEvent e){
agenda.dispatchTermin(getTermin());
super.mouseUp(e);
}
@Override
public void mouseDown(MouseEvent e){
ElexisEventDispatcher.fireSelectionEvent(getTermin().getKontakt());
}
});
new PersistentObjectDragSource(lbl, new PersistentObjectDragSource.ISelectionRenderer() {
public List<PersistentObject> getSelection(){
ArrayList<PersistentObject> ret = new ArrayList<PersistentObject>();
ret.add(getTermin());
return ret;
}
}) {
@Override
public void dragFinished(DragSourceEvent event){
lbl.getParent().dispose();
if (getTermin() != null
&& getTermin().equals(PersistentObjectDragSource.getDraggedObject())) {
setDraggedObject(null);
}
}
};
new TerminLabelMenu();
}
@Override
public void dispose(){
if (getTermin() != null && getTermin().equals(PersistentObjectDragSource.getDraggedObject())
&& isVisible()) {
setVisible(false);
}
else {
super.dispose();
}
}
public void set(Termin tf, int col){
t = tf;
if (getTermin().isRecurringDate()) {
serienTermin = new SerienTermin(getTermin());
} else {
serienTermin = null;
}
this.column = col;
}
public TerminLabel(IAgendaLayout parent, Termin trm, int col){
this(parent);
set(trm, col);
}
public int getColumn(){
return column;
}
public Termin getTermin(){
return t;
}
public void updateActions(){
boolean canChangeAppointments = CoreHub.acl.request(ACLContributor.CHANGE_APPOINTMENTS);
terminKuerzenAction.setEnabled(canChangeAppointments);
terminVerlaengernAction.setEnabled(canChangeAppointments);
terminAendernAction.setEnabled(canChangeAppointments);
}
public void refresh(){
Termin appointment = getTermin();
// termin of type recurring uses the root appointments title
Termin rootAppointment = appointment;
if (serienTermin != null) {
rootAppointment = serienTermin.getRootTermin();
}
if (overLapped != null) {
setBackground(getDisplay().getSystemColor(SWT.COLOR_RED));
}
Color back = Plannables.getTypColor(appointment);
lbl.setBackground(back);
// l.setBackground(Desk.getColor(Desk.COL_GREY20));
lbl.setForeground(SWTHelper.getContrast(back));
// l.setForeground(Plannables.getStatusColor(t));
StringBuilder sb = new StringBuilder();
sb.append(appointment.isRecurringDate() ? "Terminserie\n" : "");
sb.append(appointment.getLabel()).append("\n").append(appointment.getGrund()); //$NON-NLS-1$
sb.append("\n--------\n").append(appointment.getStatusHistoryDesc()); //$NON-NLS-1$
String grund = rootAppointment.getGrund();
if (grund != null && !grund.isEmpty())
lbl.setText(rootAppointment.getTitle() + ", " + grund);
else
lbl.setText(rootAppointment.getTitle());
lbl.setLayoutData(SWTHelper.getFillGridData(1, true, 1, true));
lbl.setToolTipText(sb.toString());
int lx = ial.getLeftOffset()
+ (int) Math.round(getColumn() * (ial.getWidthPerColumn() + ial.getPadding()));
String startOfDayTimeInMinutes =
CoreHub.globalCfg.get(PreferenceConstants.AG_DAY_PRESENTATION_STARTS_AT, "0000");
int sodtHours = Integer.parseInt(startOfDayTimeInMinutes.substring(0, 2));
int sodtMinutes = Integer.parseInt(startOfDayTimeInMinutes.substring(2));
int sodtM = (sodtHours * 60);
sodtM += sodtMinutes;
String endOfDayTimeInMinutes =
CoreHub.globalCfg.get(PreferenceConstants.AG_DAY_PRESENTATION_ENDS_AT, "2359");
int eodtHours = Integer.parseInt(endOfDayTimeInMinutes.substring(0, 2));
int eodtMinutes = Integer.parseInt(endOfDayTimeInMinutes.substring(2));
int eodtM = (eodtHours * 60);
eodtM += eodtMinutes;
if ((appointment.getBeginn() < sodtM)
&& (appointment.getBeginn() + appointment.getDauer() < sodtM)) {
// skip this entry as begin and end are not visible
setBounds(0, 0, 0, 0);
return;
}
if ((appointment.getBeginn() > eodtM)
&& (appointment.getBeginn() + appointment.getDauer() > eodtM)) {
// skip this entry as begin and end are not visible
setBounds(0, 0, 0, 0);
return;
}
int ly = 0;
int lh = 0;
if (appointment.getBeginn() < sodtM) {
ly = (int) Math.round(appointment.getBeginn() * ial.getPixelPerMinute());
int diff =
((appointment.getDauer() - sodtM) < 0) ? appointment.getDauer()
: (appointment.getDauer() - sodtM);
diff += ((appointment.getBeginn() + diff) > eodtM)
? (appointment.getBeginn() + diff) - eodtM : 0;
lh = (int) Math.round(diff * ial.getPixelPerMinute());
} else {
int startMinute = appointment.getBeginn() - sodtM;
ly = (int) Math.round(startMinute * ial.getPixelPerMinute());
int ends = appointment.getBeginn() + appointment.getDauer();
int heigthDiff = ends - eodtM;
if (heigthDiff < 0)
heigthDiff = 0;
lh = (int) Math.round((appointment.getDauer() - heigthDiff) * ial.getPixelPerMinute());
}
int lw = (int) Math.round(ial.getWidthPerColumn());
// dynamic font size adaption
int newHeight = originalFontHeightPoint;
if (originalFontHeightPixel >= lh) {
int diffPixel = originalFontHeightPixel - lh;
int diffPoint = diffPixel * 72 / Display.getCurrent().getDPI().y;
newHeight = originalFontHeightPoint - (diffPoint + 1);
if (newHeight <= 0)
newHeight = originalFontHeightPoint;
// non-nice constant 1 :( you got a better solution?
}
lblFontData.setHeight(newHeight);
lbl.setFont(getLabelFont(lblFontData));
setBounds(lx, ly, lw, lh);
GridData gd = (GridData) state.getLayoutData();
gd.minimumWidth = 10;
gd.widthHint = 10;
gd.heightHint = lh;
state.setBackground(Plannables.getStatusColor(appointment));
state.setToolTipText(appointment.getStatus());
if (lbl.getMenu() == null) {
lbl.setMenu(ial.getContextMenuManager().createContextMenu(lbl));
}
layout();
}
private Font getLabelFont(FontData fontData){
Font font = fontMap.get(fontData.getHeight());
if (font == null) {
font = new Font(UiDesk.getDisplay(), fontData);
fontMap.put(fontData.getHeight(), font);
}
return font;
}
class TerminLabelMenu {
TerminLabelMenu(){
MenuManager contextMenuManager = new MenuManager();
contextMenuManager.add(AgendaActions.getTerminStatusAction());
contextMenuManager.add(terminKuerzenAction);
contextMenuManager.add(terminVerlaengernAction);
contextMenuManager.add(terminAendernAction);
contextMenuManager.add(AgendaActions.getDelTerminAction());
TerminLabel.this.lbl
.setMenu(contextMenuManager.createContextMenu(TerminLabel.this.lbl));
}
};
private void makeActions(){
terminAendernAction = new Action(Messages.TagesView_changeTermin) {
{
setImageDescriptor(Images.IMG_EDIT.getImageDescriptor());
setToolTipText(Messages.TagesView_changeThisTermin);
}
@Override
public void run(){
AcquireLockBlockingUi.aquireAndRun(getTermin(), new ILockHandler() {
@Override
public void lockFailed(){
// do nothing
}
@Override
public void lockAcquired(){
agenda.setActResource(getTermin().getBereich());
TerminDialog dlg = new TerminDialog(getTermin());
dlg.open();
}
});
refresh();
}
};
terminKuerzenAction = new Action(Messages.TagesView_shortenTermin) {
@Override
public void run(){
if (getTermin() != null) {
AcquireLockBlockingUi.aquireAndRun(getTermin(), new ILockHandler() {
@Override
public void lockFailed(){
// do nothing
}
@Override
public void lockAcquired(){
getTermin()
.setDurationInMinutes(getTermin().getDurationInMinutes() >> 1);
}
});
ElexisEventDispatcher.update(getTermin());
}
}
};
terminVerlaengernAction = new Action(Messages.TagesView_enlargeTermin) {
@Override
public void run(){
if (getTermin() != null) {
AcquireLockBlockingUi.aquireAndRun(getTermin(), new ILockHandler() {
@Override
public void lockFailed(){
// do nothing
}
@Override
public void lockAcquired(){
agenda.setActDate(getTermin().getDay());
Termin n = Plannables.getFollowingTermin(agenda.getActResource(),
agenda.getActDate(), getTermin());
if (n != null) {
getTermin().setEndTime(n.getStartTime());
}
}
});
refresh();
}
}
};
}
public static void checkAllCollisions(List<TerminLabel> tlabels){
tlabels.parallelStream().forEach(terminLabel -> terminLabel.checkCollision(tlabels));
}
private void checkCollision(List<TerminLabel> tlabels){
String checkBereich = getTermin().getBereich();
// use recurring termin for collision checking
Termin termin = getTermin();
for (TerminLabel otherLabel : tlabels) {
Termin otherTermin = otherLabel.getTermin();
if (otherLabel != this && otherTermin.getBereich().equals(checkBereich)) {
if (Plannables.isOverlapped(termin, otherTermin)) {
addOverlapped(otherLabel);
}
}
}
}
private void addOverlapped(TerminLabel otherLabel){
if (overLapped == null) {
overLapped = new ArrayList<TerminLabel>();
}
overLapped.add(otherLabel);
}
}