/**
* FyLLGen - A Java based tool for collecting and distributing family data
*
* Copyright (C) 2007-2011 Christian Packenius
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package de.chris_soft.fyllgen.widget.dialog;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.events.ShellEvent;
import org.eclipse.swt.events.ShellListener;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.ImageData;
import org.eclipse.swt.layout.FormAttachment;
import org.eclipse.swt.layout.FormData;
import org.eclipse.swt.layout.FormLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Dialog;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;
import de.chris_soft.fyllgen.GUI;
import de.chris_soft.fyllgen.data.Family;
import de.chris_soft.fyllgen.data.Person;
import de.chris_soft.fyllgen.utilities.PersonListSort;
import de.chris_soft.fyllgen.utilities.SwtConsts;
import de.chris_soft.fyllgen.utilities.SwtUtilities;
/**
* �ber diese Klasse wird ein Dialog erzeugt, in dem dann eine Person ausgew�hlt
* oder eine neue Person erzeugt werden kann. Personen k�nnen gesucht,
* ausgew�hlt oder neu erzeugt werden. Die Liste der Personen wird wie folgt
* erzeugt: Zun�chst werden alle vorhandenen Personen in die Liste aufgenommen,
* dann k�nnen vor dem �ffnen des Fensters beliebig viele Personen entfernt
* werden.
* @author Christian Packenius, Juni 2008.
*/
public class PersonChoiceShell extends Dialog implements ShellListener, SelectionListener, ModifyListener,
PaintListener {
/**
* Das eigentliche Fenster-Objekt.
*/
private Shell shell;
/**
* Button, um eine angew�hlte Person zu verwenden.
*/
private Button buttonUse;
/**
* Button, um eine neue Person zu erzeugen.
*/
private Button buttonNew;
/**
* Ein Textfeld und eine Liste, die eine offene ComboBox simulieren.
*/
private Text textNamePart;
/**
* SWT-Liste mit allen Personen.
*/
private org.eclipse.swt.widgets.List listNames;
/**
* Liste aller Personen.
*/
private List<Person> persons = new ArrayList<Person>();
/**
* Liste der Postfixe zu den Personen. Wird hinter dem Namen angezeigt.
*/
private List<String> personPostfixes = new ArrayList<String>();
/**
* Liste aller Stringdarstellungen der Personen innerhalb obiger Liste.
*/
private List<String> personStrings = new ArrayList<String>();
/**
* Liste aller aktuell angezeigten Personen (die dem Suchfilter im obigen
* Textfeld entsprechen).
*/
private List<Person> viewPersons = new ArrayList<Person>();
/**
* Falls der User eine Person ausgew�hlt hatte, steht sie anschlie�end hier
* drin.
*/
public Person personChoice = null;
/**
* Tempor�re Wahl einer Person - wird f�r eine Wahl zwischen den Eingaben
* verwendet.
*/
public Person tempChoice = null;
/**
* Falls der User eine neue Person erzeugen m�chte, steht hier der Name drin.
*/
public String newPersonName = null;
/**
* Angabe, ob immer nur ein Button angezeigt werden soll.
*/
private final boolean bShowOnlyOneButton;
/**
* Anfangsstring.
*/
private String starttext = "";
/**
* Canvas, in dem eventuell ein Image angezeigt wird.
*/
Canvas canvasImage;
/**
* Image, das durchweg anzuzeigen ist.
*/
private File image2viewFile;
/**
* Aktuelle Person, bevor der Dialog ge�ffnet wurde.
*/
private Person oldCurrentPerson;
private boolean guiChangeOnSelection;
/**
* Diese ID regelt das asynchrone Filtern der Personen w�hrend der Eingabe.
* Damit die Eingabe nicht stockt, wird bei jeder Ver�nderung der Eingabe
* dieser Z�hler hoch gesetzt. Dies sorgt daf�r, dass alte Filterthreads
* komplett abgebrochen werden und die neuen schnellstm�glichst gestartet
* werden.
*/
volatile int asyncID;
/**
* Konstruktor. Erzeugt das Fenster, ohne es anzuzeigen.
* @param parent Parent-Shell, �ber der dieser Dialog angezeigt wird.
* @param title Titel des Fensters.
* @param buttonUseText Text f�r den Use-Button.
* @param buttonNewText Text f�r den New-Button oder null, wenn es diesen
* nicht geben soll.
* @param buttonCount Anzahl der anzuzeigenden Buttons. Der zweite Button wird
* dann immer nur dann aktiviert, wenn der erste disabled wurde UND
* im Textfeld oben etwas steht UND buttonNewText nicht null ist.
* @param bWithoutPersons Falls true, werden keine Personen eingestellt,
* ansonsten erstmal alle.
* @param imageFile Image-Datei, die fest anzuzeigen ist.
* @param guiChangeOnSelection Angabe, ob beim Navigieren in der Liste die GUI
* jeweils angepasst werden soll.
*/
public PersonChoiceShell(Shell parent, String title, String buttonUseText, String buttonNewText, int buttonCount,
boolean bWithoutPersons, File imageFile, boolean guiChangeOnSelection) {
super(parent, 0);
image2viewFile = imageFile;
this.guiChangeOnSelection = guiChangeOnSelection;
// Liste aller Personen erzeugen.
oldCurrentPerson = Family.instance.getCurrentPerson();
if (!bWithoutPersons) {
Person[] personarray = Family.instance.getPersonsArray();
for (Person person : personarray) {
persons.add(person);
personPostfixes.add("");
}
}
// Die Personen sortieren lassen (nach Name).
PersonListSort.sort(persons);
shell = new Shell(parent, SWT.CLOSE | SWT.TITLE | SWT.APPLICATION_MODAL);
shell.setImage(GUI.instance.shellIcon);
shell.addShellListener(this);
shell.setText(title);
shell.setSize(400, 355); // H�he auf das Canvas hin abgestimmt!
// Noch mittig ins vorhandene Fenster setzen.
SwtUtilities.centerInParent(shell, parent);
// Shell hat innen einen Rahmen.
FormLayout shellLayout = new FormLayout();
shellLayout.marginBottom = shellLayout.marginLeft = shellLayout.marginRight = shellLayout.marginTop = 5;
shell.setLayout(shellLayout);
// Innen ein Composite.
Composite inner = new Composite(shell, 0);
FormLayout innerLayout = new FormLayout();
inner.setLayout(innerLayout);
FormData fd = new FormData();
fd.top = new FormAttachment(0, 0);
fd.left = new FormAttachment(0, 0);
fd.right = new FormAttachment(100, 0);
fd.bottom = new FormAttachment(100, 0);
inner.setLayoutData(fd);
bShowOnlyOneButton = buttonCount == 1 || buttonNewText == null;
// Button zum Verwenden der in der Liste angeklickten Person erzeugen.
buttonUse = new Button(inner, SWT.PUSH);
buttonUse.setText(buttonUseText);
fd = new FormData();
fd.left = new FormAttachment(0, 0);
if (!bShowOnlyOneButton) {
fd.right = new FormAttachment(50, -2);
}
else {
fd.right = new FormAttachment(100, 0);
}
fd.bottom = new FormAttachment(100, 0);
buttonUse.setLayoutData(fd);
buttonUse.addSelectionListener(this);
buttonUse.setEnabled(false);
if (buttonNewText != null) {
// Button zum Erzeugen einer neuen Person mit dem angegebenen Namen.
buttonNew = new Button(inner, SWT.PUSH);
buttonNew.setText(buttonNewText);
fd = new FormData();
if (bShowOnlyOneButton) {
buttonNew.setVisible(false);
fd.left = new FormAttachment(0, 0);
}
else {
fd.left = new FormAttachment(50, 2);
}
fd.right = new FormAttachment(100, 0);
fd.bottom = new FormAttachment(100, 0);
buttonNew.setLayoutData(fd);
buttonNew.addSelectionListener(this);
buttonNew.setEnabled(false);
}
// Textfeld, mit dem in der Liste selektiert werden kann.
textNamePart = new Text(inner, SWT.SINGLE | SWT.BORDER);
fd = new FormData();
fd.left = new FormAttachment(0, 0);
fd.right = new FormAttachment(100, 0);
fd.top = new FormAttachment(0, 0);
textNamePart.setLayoutData(fd);
textNamePart.addModifyListener(this);
// Darstellung eines Images.
canvasImage = new Canvas(inner, 0);
fd = new FormData();
fd.top = new FormAttachment(textNamePart, 5, SWT.BOTTOM);
fd.right = new FormAttachment(100, 0);
fd.bottom = new FormAttachment(buttonUse, -5, SWT.TOP);
fd.left = new FormAttachment(100, -97);
canvasImage.setLayoutData(fd);
canvasImage.addPaintListener(this);
// Liste, in der selektierte Personen stehen.
listNames = new org.eclipse.swt.widgets.List(inner, SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL);
fd = new FormData();
fd.left = new FormAttachment(0, 0);
fd.right = new FormAttachment(canvasImage, -5, SWT.LEFT);
fd.top = new FormAttachment(textNamePart, 5, SWT.BOTTOM);
fd.bottom = new FormAttachment(buttonUse, -5, SWT.TOP);
listNames.setLayoutData(fd);
listNames.addSelectionListener(this);
}
/**
* Shell sichtbar machen. Vorher aber alle Personen in die Liste einf�gen.
*/
public void open() {
viewPersons.clear();
int choice = -1;
int k = 0;
for (Person person : persons) {
viewPersons.add(person);
String s = person.toString() + personPostfixes.get(k++);
listNames.add(s);
if (person == personChoice) {
choice = listNames.getItemCount() - 1;
}
personStrings.add(s);
}
shell.open();
textNamePart.setText(starttext);
if (choice >= 0) {
listNames.setSelection(choice);
buttonUse.setEnabled(true);
listNames.forceFocus();
}
// Image schon mal zeichnen, falls notwendig oder erw�nscht.
canvasImage.redraw();
// Ereignis-Liste abarbeiten.
while (!shell.isDisposed()) {
if (!SwtConsts.display.readAndDispatch()) {
SwtConsts.display.sleep();
}
}
}
/**
* @see org.eclipse.swt.events.ShellListener#shellActivated(org.eclipse.swt.events.ShellEvent)
*/
public void shellActivated(ShellEvent e) {
// Ignorieren.
}
/**
* @see org.eclipse.swt.events.ShellListener#shellClosed(org.eclipse.swt.events.ShellEvent)
*/
public void shellClosed(ShellEvent e) {
shell.removeShellListener(this);
if (personChoice == null && newPersonName == null) {
Family.instance.setCurrentPerson(oldCurrentPerson, 1);
}
}
/**
* @see org.eclipse.swt.events.ShellListener#shellDeactivated(org.eclipse.swt.events.ShellEvent)
*/
public void shellDeactivated(ShellEvent e) {
// Ignorieren.
}
/**
* @see org.eclipse.swt.events.ShellListener#shellDeiconified(org.eclipse.swt.events.ShellEvent)
*/
public void shellDeiconified(ShellEvent e) {
// Ignorieren.
}
/**
* @see org.eclipse.swt.events.ShellListener#shellIconified(org.eclipse.swt.events.ShellEvent)
*/
public void shellIconified(ShellEvent e) {
// Ignorieren.
}
/**
* Ein Button wurde gedr�ckt.
* @see org.eclipse.swt.events.SelectionListener#widgetDefaultSelected(org.eclipse.swt.events.SelectionEvent)
*/
public void widgetDefaultSelected(SelectionEvent e) {
if (e.widget == listNames) {
int k = listNames.getSelectionIndex();
if (k >= 0) {
personChoice = viewPersons.get(k);
shell.dispose();
}
}
else {
testButtonsPressed(e);
}
}
/**
* Ein Button wurde gedr�ckt.
* @see org.eclipse.swt.events.SelectionListener#widgetSelected(org.eclipse.swt.events.SelectionEvent)
*/
public void widgetSelected(SelectionEvent e) {
if (e.widget == listNames) {
int k = listNames.getSelectionIndex();
boolean b = k >= 0;
buttonUse.setEnabled(b);
canvasImage.redraw();
if (b) {
shell.setDefaultButton(buttonUse);
Person person = viewPersons.get(k);
tempChoice = person;
if (guiChangeOnSelection) {
Family.instance.setCurrentPerson(person, 0);
}
}
}
else {
testButtonsPressed(e);
}
}
/**
* Pr�ft, ob einer der Buttons gedr�ckt wurde.
* @param e
*/
private void testButtonsPressed(SelectionEvent e) {
if (e.widget == buttonUse) {
personChoice = viewPersons.get(listNames.getSelectionIndex());
shell.dispose();
}
else if (buttonNew != null && e.widget == buttonNew) {
newPersonName = textNamePart.getText();
shell.dispose();
}
}
/**
* Der Text im Text-Feld wurde ge�ndert. Nun nur noch diejenigen Personen
* anzeigen, die diesen Text enthalten.
* @see org.eclipse.swt.events.ModifyListener#modifyText(org.eclipse.swt.events.ModifyEvent)
*/
public void modifyText(ModifyEvent e) {
// Alle eingegebenen W�rter ermitteln.
String text = textNamePart.getText().toLowerCase().trim();
String[] words = getLowercasedWordList(text);
// Alle Personen filtern und anschlie�end anzeigen.
++asyncID;
filterPersonsAndRefreshDialog(text, words, tempChoice, asyncID);
}
/**
* Die Personenliste wird hier asynchron gefiltert und dann im UI-Thread
* synchron wieder angezeigt.
*/
private synchronized void filterPersonsAndRefreshDialog(final String text, final String[] words, final Person choice,
final int localAsyncID) {
new Thread(new Runnable() {
public void run() {
// Alle m�glichen Personen pr�fen.
filterPersonsToView(words, localAsyncID);
// Nur Anzeigen, wenn w�hrend des Filterungsprozesses die Eingabe nicht
// ge�ndert wurde (und infolgedessen der Filterungsprozess ggf.
// abgebrochen wurde).
if (localAsyncID == asyncID) {
SwtConsts.display.syncExec(new Runnable() {
public void run() {
// Sicht l�schen und neu f�llen.
fillNamesList(choice, localAsyncID);
// Buttons (wieder) ordnen.
reEnableButtons(text);
// Auch Image neu (oder �berhaupt) anzeigen.
canvasImage.redraw();
}
});
}
}
}).start();
}
/**
* Erzeugt eine Liste aller eingegebenen W�rter (lowercase).
*/
private String[] getLowercasedWordList(String text) {
StringTokenizer st = new StringTokenizer(text);
int count = st.countTokens();
String[] words = new String[count];
for (int i = 0; i < count; i++) {
words[i] = st.nextToken();
}
return words;
}
/**
* Pr�ft, welche Personen auf Grund der Eingabe (noch/wieder/weiterhin)
* angezeigt werden sollen und welche nicht.
* @param localAsyncID
*/
void filterPersonsToView(String[] words, int localAsyncID) {
viewPersons.clear();
int size = persons.size();
for (int i = 0; i < size && localAsyncID == asyncID; i++) {
String singlePersonString = personStrings.get(i);
String lowerSinglePersonString = singlePersonString.toLowerCase();
// Pr�fen, ob alle Worte in diesem Personenstring vorkommen.
boolean bFound = true;
for (String word : words) {
if (lowerSinglePersonString.indexOf(word) < 0) {
bFound = false;
break;
}
}
// Falls vorhanden, dann weiter anzeigen.
if (bFound) {
Person person = persons.get(i);
// Falls dies die ehemals angew�hlte Person war, diese weiterhin
// anw�hlen.
viewPersons.add(person);
}
}
}
/**
* F�llt die Personenliste von Neuem.
* @param localAsyncID TODO
*/
void fillNamesList(Person choice, int localAsyncID) {
listNames.removeAll();
for (Person person : viewPersons) {
listNames.add(personStrings.get(persons.indexOf(person)));
if (person == choice) {
listNames.setSelection(listNames.getItemCount() - 1);
}
if (localAsyncID != asyncID) {
break;
}
}
}
/**
* Enabled oder disabled die Buttons im unteren Bereich.
*/
void reEnableButtons(String text) {
if (buttonNew != null) {
if (text.trim().length() > 0) {
shell.setDefaultButton(buttonNew);
buttonNew.setEnabled(true);
}
else {
buttonNew.setEnabled(false);
}
if (bShowOnlyOneButton) {
boolean bNoItems = listNames.getItemCount() == 0;
buttonNew.setVisible(bNoItems);
buttonUse.setVisible(!bNoItems);
}
}
buttonUse.setEnabled(listNames.getSelectionIndex() >= 0);
}
/**
* Entfernt eine Person aus der Liste der anzuzeigenden Personen.
* @param person2remove zu entfernende Person.
*/
public void removePerson(Person person2remove) {
int k = persons.indexOf(person2remove);
if (k >= 0) {
persons.remove(k);
personPostfixes.remove(k);
}
}
/**
* F�gt der Liste eine weitere Person hinzu.
* @param person2add
* @param postfix Zusatz hinter der Personenanzeige.
*/
public void addPerson(Person person2add, String postfix) {
postfix = postfix == null ? "" : postfix;
postfix = postfix.trim();
postfix = postfix.length() == 0 ? "" : " " + postfix;
if (!persons.contains(person2add)) {
persons.add(person2add);
personPostfixes.add(postfix);
}
}
/**
* Entfernt alle Personen aus der Liste, deren Alter zwischen (!) den
* angegebenen Datumswerten liegen.
* @param key Schl�ssel f�r den abzufragenden Datumswert.
* @param date1 Der kleinere (!) der beiden Datumswerte.
* @param date2 Der gr��ere (!) der beiden Datumswerte.
* @param dateAdder Array mit Werten f�r Tag, Monat und Jahr, die jedem Datum
* einer zu testenden Person hinzugef�gt werden.
*/
public void removePersonsBetweenDates(String key, String date1, String date2, int[] dateAdder) {
// Korrektur.
if (dateAdder == null) {
dateAdder = new int[] { 0, 0, 0 };
}
// Punkte in den Datumswerten suchen.
int p1_1 = date1.indexOf('.');
int p1_2 = date1.indexOf('.', p1_1 + 1);
int p2_1 = date2.indexOf('.');
int p2_2 = date2.indexOf('.', p2_1 + 1);
// Fragezeichen werden im ersten Datum als sehr hoch eingestuft und im
// zweiten als sehr tief.
// So wird verhindert, dass Fragezeichenwerte rausgeworfen werden.
// Datumswerte ermitteln.
int day1 = getDynamicInt(date1.substring(0, p1_1), 99);
int day2 = getDynamicInt(date2.substring(0, p2_1), 00);
int month1 = getDynamicInt(date1.substring(p1_1 + 1, p1_2), 99);
int month2 = getDynamicInt(date2.substring(p2_1 + 1, p2_2), 0);
int year1 = getDynamicInt(date1.substring(p1_2 + 1), 9999);
int year2 = getDynamicInt(date2.substring(p2_2 + 1), 0);
// Schleife �ber alle Personen, Vergleiche t�tigen und Person jeweils
// entfernen oder auch nicht.
final int max = Integer.MAX_VALUE;
for (int i = persons.size() - 1; i >= 0; i--) {
Person person = persons.get(i);
String date = person.getValueView(key);
// Notfalls ein gesch�tztes Datum verwenden.
if (key == Person.BIRTHDAY) {
if (date.endsWith(".?")) {
int year = person.getBirthYear(true);
if (year != Integer.MIN_VALUE) {
date = date.substring(0, date.length() - 1) + year;
}
}
}
p1_1 = date.indexOf('.');
p1_2 = date.indexOf('.', p1_1 + 1);
int year = getDynamicInt(date.substring(p1_2 + 1), max);
int month = getDynamicInt(date.substring(p1_1 + 1, p1_2), max);
int day = getDynamicInt(date.substring(0, p1_1), max);
if (day != max) {
day += dateAdder[0];
while (day > 31 && month != max) {
day -= 31;
month++;
}
}
if (month != max) {
month += dateAdder[1];
while (month > 12) {
month -= 12;
year++;
}
while (month < 1) {
month += 12;
year--;
}
}
if (year != max) {
year += dateAdder[2];
}
// Nun die Tests durchf�hren.
if (year != max) {
if (year1 < year && year < year2) {
// persons.remove(person);
removePerson(person);
}
else if (year1 == year || year2 == year) {
if (month != max) {
int ym = year * 100 + month;
int ym1 = year1 * 100 + month1;
int ym2 = year2 * 100 + month2;
if (ym1 < ym && ym < ym2) {
// persons.remove(person);
removePerson(person);
}
else if (month1 == month || month2 == month) {
if (day != max) {
int ymd = (year * 100 + month) * 100 + day;
int ymd1 = (year1 * 100 + month1) * 100 + day1;
int ymd2 = (year2 * 100 + month2) * 100 + day2;
if (ymd1 < ymd && ymd < ymd2) {
// persons.remove(person);
removePerson(person);
}
}
}
}
}
}
}
}
/**
* Ermittelt den int-Wert des Strings. Bei einem Fragezeichen wird der
* Defaultwert �bergeben.
*/
private int getDynamicInt(String s, int defaultValue) {
try {
return s.equals("?") ? defaultValue : Integer.parseInt(s);
}
catch (NumberFormatException nfe) {
return defaultValue;
}
}
/**
* Setzt einen initialen String in das Textfeld.
* @param text Anfangsstring.
*/
public void setStartString(String text) {
starttext = text;
}
/**
* Das Bild soll neu gezeichnet werden.
* @see org.eclipse.swt.events.PaintListener#paintControl(org.eclipse.swt.events.PaintEvent)
*/
public void paintControl(PaintEvent e) {
int index = listNames.getSelectionIndex();
String imagePath1 = image2viewFile == null ? null : image2viewFile.getAbsolutePath();
String imagePath2 = null;
if (index >= 0 && viewPersons.size() > index) {
Person person = viewPersons.get(index);
List<File> imList = person.getImageList();
if (imagePath1 == null && !imList.isEmpty()) {
imagePath1 = imList.remove(0).getAbsolutePath();
}
if (!imList.isEmpty()) {
imagePath2 = imList.remove(0).getAbsolutePath();
}
}
try {
if (imagePath1 != null) {
FileInputStream in = new FileInputStream(imagePath1);
Image image = new Image(SwtConsts.display, new ImageData(in));
in.close();
int y1 = imagePath2 == null ? 66 : 0;
e.gc.drawImage(image, 0, 0, image.getBounds().width, image.getBounds().height, 0, y1, 97, 128);
if (imagePath2 != null) {
in = new FileInputStream(imagePath2);
image = new Image(SwtConsts.display, new ImageData(in));
in.close();
e.gc.drawImage(image, 0, 0, image.getBounds().width, image.getBounds().height, 0, 128 + 5, 97, 128);
}
}
}
catch (IOException exception) {
// Dann wird halt kein Image angezeigt - ist nicht weiter tragisch!
}
}
}