/**
* 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.data;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import de.chris_soft.fyllgen.GUI;
import de.chris_soft.fyllgen.Statics;
import de.chris_soft.fyllgen.export.ExportService;
import de.chris_soft.fyllgen.utilities.IOTools;
import de.chris_soft.fyllgen.utilities.PersonListSort;
import de.chris_soft.fyllgen.utilities.StatusProvider;
import de.chris_soft.fyllgen.utilities.SwtUtilities;
import de.chris_soft.fyllgen.utilities.ZipWritingEntryListener;
import de.chris_soft.fyllgen.widget.FamilyComposite;
import de.chris_soft.fyllgen.widget.FamilyModel;
import de.chris_soft.fyllgen.widget.FamilyModelStandard;
import de.chris_soft.fyllgen.widget.StatusLine;
import de.chris_soft.fyllgen.widget.listener.CurrentPersonChangeEvent;
import de.chris_soft.fyllgen.widget.listener.CurrentPersonChangeListener;
import de.chris_soft.fyllgen.widget.listener.CurrentPersonChanger;
/**
* Methoden rund um die Personendaten.
* @author Christian Packenius, Juni 2008.
*/
public class Family implements CurrentPersonChanger, ZipWritingEntryListener, StatusProvider {
/**
* Die Person, die aktuell angezeigt wird.
*/
private Person currentPerson = null;
/**
* Liste aller Personen.
*/
final List<Person> persons = new ArrayList<Person>();
/**
* Z�hlt die Anzahl der Speicherungen mit.
*/
private int saveIndex;
/**
* Liste aller Listener, die �ber einen Wechsel der aktuellen Person
* informiert werden wollen.
*/
private final List<CurrentPersonChangeListener> vCurrentPersonChangeListener = new ArrayList<CurrentPersonChangeListener>();
/**
* Zeigt an, ob die Familiendaten ge�ndert wurden (true) oder inzwischen
* (wieder) gespeichert sind (false).
*/
private boolean bDataChanged;
/**
* Wird auf <i>true</i> gesetzt, wenn das automatische Mailing mittendrin
* durch den Anwender unterbrochen wurde.
*/
boolean cancelAutomaticMailing;
/**
* Map, in der eine Imagedatei einer Person zugeordnet ist.
*/
final Map<File, Person> imagePersonMap = new HashMap<File, Person>();
/**
* K�mmert sich um alles rund um die aktuell dargestellte Person.
*/
public static final Family instance = new Family();
/**
* Das IO-Tool f�r die Familie.
*/
public final FamilyLoader loader = new FamilyLoader(this);
static {
// Genau diese Familie soll auch in der Statusleiste f�r Updates sorgen (das
// gilt f�r alle anderen nicht (z.B. die Merge-Familie)!).
StatusLine.addStatusProvider(instance);
}
/**
* Setzt die neu anzuzeigende Person.
* @param person Aktuelle Person, also jene, deren Daten im unteren Bereich
* angezeigt werden und die hier mit gr�nem Rahmen gekennzeichnet
* wird.
* @param askForViewChange (0) Kein Wechsel der Ansicht, (1) evtl. nach
* Wechsel fragen, (2) unbedingt wechseln falls notwendig.
*/
public void setCurrentPerson(Person person, int askForViewChange) {
// Erstmal pr�fen, ob wir eventuell das Modell anpassen m�ssen.
FamilyComposite familyComposite = GUI.instance.getFamilyComposite();
FamilyModel familyModel = familyComposite.getFamilyModel();
if (askForViewChange > 0 && !(familyModel instanceof FamilyModelStandard)) {
String quest = "Wollen Sie in die Standardansicht wechseln?";
if (askForViewChange == 2 || SwtUtilities.askYesNo(GUI.instance.shell, quest, "Ansichtenwechsel?")) {
familyComposite.setFamilyModel(new FamilyModelStandard(familyComposite));
}
}
currentPerson = person;
sendCurrentPersonChangeListenerEvent(new CurrentPersonChangeEvent(person));
// Alle Daten zur Person anzeigen.
refreshPerson();
// Auf den Namen gehen.
GUI.instance.setFocusToFirstWidget();
}
/**
* Zeigt alles nochmal an. Sinnvoll bei �nderungen der Optionen oder so.
*/
public void review() {
setCurrentPerson(getCurrentPerson(), 0);
}
/**
* Ermittelt die Person, die gerade bearbeitet wird.
* @return Aktuell zur Bearbeitung anstehende Person.
*/
public Person getCurrentPerson() {
if (persons.contains(currentPerson)) {
return currentPerson;
}
if (persons.size() > 0) {
return persons.get(0);
}
return null;
}
/**
* Person(en) auf der Oberfl�che aktualisieren.
*/
private void refreshPerson() {
// Anzeige der Daten der aktuellen Person.
GUI.instance.showPersons(currentPerson);
}
/**
* Liest alle Personen aus der Datei in das Programm ein.
* @param filename Name der einzulesenden Datei.
* @throws IOException
*/
public void readPersons(String filename) throws IOException {
if (filename == null) {
throw new IOException("Konnte keine Familiendatei finden!");
}
LastOpenedFamilyFile.setLastOpenedFamilyFile(filename);
loader.load(filename);
// Neue Datei wurde frisch ge�ffnet, ist also noch unver�ndert.
setChanged(false);
// Suchen, wer angezeigt werden soll.
Person person2show = null;
String xrefid = OptionData.instance.getString(OptionData.LAST_PERSONS_XREFID);
if (xrefid != null) {
person2show = getPersonFromXREFID(xrefid);
}
if (person2show == null) {
person2show = getPersonByName("Christian PACKENIUS");
if (person2show == null) {
person2show = getFirstPerson();
}
}
setCurrentPerson(person2show, 2);
// Falls eine Person schon l�nger keine E-Mail mehr erhalten hat, hier
// nachfragen, ob dies geschehen soll.
// Aber in einem eigenen Thread!
if (this == instance) {
// Nicht f�r Merge-Files!
ExportService.askForAutomaticMailing();
}
}
/**
* Sucht nach der Person, die den angegebenen String im Namen hat.
* @param namepart
* @return Gesuchte Person oder null, falls nicht gefunden.
*/
private Person getPersonByName(String namepart) {
namepart = namepart.toLowerCase();
for (Person person : persons) {
if (person.getValueView(Person.NAME).toLowerCase().indexOf(namepart) >= 0) {
return person;
}
}
return null;
}
/**
* Geht zur ersten Person in der Liste.
*/
public void showFirstPerson() {
setCurrentPerson(getFirstPerson(), 2);
}
/**
* Schreibt alle Personen aus dem Programm in die Datei.
* @param filename
* @throws IOException
*/
public void writePersons(String filename) throws IOException {
save(filename);
setChanged(false);
}
/**
* Speichert die Daten unter dem genannten Dateinamen ab. Dies hat keinerlei
* Einfluss auf das Changed-Flag!
* @param filename Dateiname zur Speicherung.
* @throws IOException
*/
public void save(String filename) throws IOException {
if (filename.toLowerCase().endsWith(".fgf")) {
filename = filename.substring(0, filename.length() - 4) + ".zip";
}
if (!filename.toLowerCase().endsWith(".zip")) {
filename += ".zip";
}
writeToZip(filename);
}
/**
* Erzeugt eine ZIP-datei mit allen Familiendaten (.fgf-Datei und Images).
*/
private void writeToZip(String filename) {
IOTools zip = new IOTools();
List<File> files = new ArrayList<File>();
List<String> names = new ArrayList<String>();
files.add(null);
names.add("family.fgf");
files.add(null);
names.add(Statics.OPTIONS_PROPERTIES);
List<File> images = getAllImages();
new File("images").mkdir();
for (File image : images) {
files.add(image);
names.add("images/" + image.getName());
}
// Noch als ZIP-Datei wegschreiben.
try {
zip.writeZipFile(filename, files, names, this);
}
catch (IOException exception) {
exception.printStackTrace();
SwtUtilities.sayError(GUI.instance.shell, "Fehler beim Schreiben der ZIP-Datei mit den Familiendaten!");
}
}
/**
* Schreibt die Familiendaten als *.fgf-Stream in die ZIP-Datei.
* @see de.chris_soft.fyllgen.utilities.ZipWritingEntryListener#fillOutputStreamForZipEntry(java.lang.String,
* java.io.OutputStream)
*/
public void fillOutputStreamForZipEntry(String entryName, OutputStream outStream) throws IOException {
if (entryName.endsWith(".fgf")) {
saveIndex++;
PrintStream out = new PrintStream(outStream);
// Personen wegschreiben.
for (Person person : persons) {
out.println("person");
person.writeData(out);
Person[] children = person.getChildren();
for (Person child : children) {
int c = persons.indexOf(child);
out.println("child");
out.println(c);
Relationship relship = person.getChildrenRelationship(child);
relship.writeData(out, saveIndex);
}
Person[] partners = person.getPartner();
for (Person partner : partners) {
int c = persons.indexOf(partner);
out.println("partner");
out.println(c);
Relationship relship = person.getPartnersRelationship(partner);
relship.writeData(out, saveIndex);
}
}
out.flush();
}
else if (entryName.equals(Statics.OPTIONS_PROPERTIES)) {
OptionData.instance.saveOptions(outStream);
outStream.flush();
}
else {
SwtUtilities.sayError(GUI.instance.shell, "Programmfehler #5776: " + entryName);
}
}
/**
* L�scht s�mtliche Dateien zur Familie. Wird am Programmende aufgerufen,
* nachdem die Daten ohnehin in der ZIP-Datei verschwunden sind.
*/
public void deleteUnzippedFiles() {
String filename = LastOpenedFamilyFile.getLastOpenedFamilyFile();
if (filename.toLowerCase().endsWith(".zip")) {
filename = filename.substring(0, filename.length() - 4) + ".fgf";
}
List<File> files = new ArrayList<File>();
files.add(new File(filename));
files.addAll(getAllImages());
for (File file : files) {
file.delete();
}
new File("images").delete();
}
/**
* Ermittelt eine Liste s�mtlicher Bilder zu Personen.
* @return Liste aller Bilddateien.
*/
public List<File> getAllImages() {
List<File> files = new ArrayList<File>();
for (File file : imagePersonMap.keySet()) {
if (!files.contains(file)) {
files.add(file);
}
}
return files;
}
/**
* Ermittelt die erste Person in der Liste der Personen.
* @return Erste Person in der Liste.
*/
public Person getFirstPerson() {
// Falls es keine Person gibt, eine leere erzeugen.
if (persons.size() == 0) {
createFirstDummyPerson();
}
// Diese Person zur�ck geben.
return persons.get(0);
}
/**
* Legt eine erste Person f�r die Familiendatei an.
*/
private void createFirstDummyPerson() {
Person person = new Person();
person.setValue(Person.NAME, "unbekannt");
persons.add(person);
}
/**
* F�gt der Liste der Personen eine weitere Person hinzu.
* @param person
*/
public void addNewPerson(Person person) {
if (!persons.contains(person)) {
persons.add(person);
setChanged(true);
}
}
/**
* Erzeugt ein Array mit allen vorhandenen Personen.
* @return Array aller Personen.
*/
public Person[] getPersonsArray() {
return persons.toArray(new Person[persons.size()]);
}
/**
* Creates an array of all persons which are related to the current person.
* @param useSpecialFilterRule User special "Vietor" rule to not export the
* Packenius data.
* @return Person array.
*/
public Person[] getCurrentPersonsFamilyArray(boolean useSpecialFilterRule) {
Person p0 = getCurrentPerson();
return getAnyPersonsFamilyArray(useSpecialFilterRule, p0);
}
/**
* Creates an array of all persons which are related to the given person.
* @param useSpecialFilterRule User special "Vietor" rule to not export the
* Packenius data.
* @param p0 Person to start with.
* @return Person array.
*/
public Person[] getAnyPersonsFamilyArray(boolean useSpecialFilterRule, Person p0) {
List<Person> list = new ArrayList<Person>();
list.add(p0);
// Die spezielle Filter-Regel wird hier genau dann gesetzt, wenn (a)
// Christian noch nicht entdeckt wurde und (b) Simone
// aktuell verarbeitet wird. Allerdings wird sie in diesem Fall auch nur bei
// der Bearbeitung von Christian verwendet.
boolean vietorPackeniusFilterFound = false;
boolean chrisFound = false;
for (int i = 0; i < list.size(); i++) {
Person person = list.get(i);
boolean sissiHere = person.getValue(Person.NAME).equals("Simone KNIPPRATH");
boolean chrisHere = person.getValue(Person.NAME).equals("Christian PACKENIUS");
// i > 0, weil Simone auch die volle Liste erhalten soll.
if (sissiHere && !chrisFound && i > 0) {
vietorPackeniusFilterFound = useSpecialFilterRule;
}
boolean doNotGoOn = chrisHere && vietorPackeniusFilterFound;
if (!doNotGoOn) {
for (Relationship relship : person.getAllRelationships()) {
Person relshipPerson = relship.getOtherPerson(person);
chrisFound = relshipPerson.getValue(Person.NAME).equals("Christian PACKENIUS");
if (!list.contains(relshipPerson)) {
list.add(relshipPerson);
}
}
}
}
return list.toArray(new Person[list.size()]);
}
/**
* Entfernt die aktuelle Person und zeigt wieder die erste an.
*/
public void removeCurrentPerson() {
Person curr = getCurrentPerson();
Person nextCurrent = removePerson(curr);
if (nextCurrent == null) {
showFirstPerson();
}
else {
setCurrentPerson(nextCurrent, 1);
}
}
/**
* Entfernt die angegebene Person und zeigt eine andere an.
* @param person2remove Zu l�schende Person.
* @return Irgendeine Person, die eine Beziehung zur gel�schten Person hatte.
*/
public Person removePerson(Person person2remove) {
Person nextCurrent = null;
for (Person person : person2remove.getChildren()) {
if (nextCurrent == null) {
nextCurrent = person;
}
person.removePerson(person2remove);
}
for (Person person : person2remove.getParents()) {
if (nextCurrent == null) {
nextCurrent = person;
}
person.removePerson(person2remove);
}
for (Person person : person2remove.getPartner()) {
if (nextCurrent == null) {
nextCurrent = person;
}
person.removePerson(person2remove);
}
persons.remove(person2remove);
setChanged(true);
if (nextCurrent == null) {
if (persons.size() > 0) {
nextCurrent = persons.get(0);
}
}
return nextCurrent;
}
/**
* F�gt einen weiteren Listener hinzu, der informiert werden m�chte, sobald
* sich die aktuelle Person �ndern.
* @param listener
*/
public void addCurrentPersonChangeListener(CurrentPersonChangeListener listener) {
// Den neuen Listener merken und ihm die aktuelle Person anzeigen.
vCurrentPersonChangeListener.add(listener);
listener.currentPersonChanged(new CurrentPersonChangeEvent(currentPerson));
}
/**
* Entfernt den angegebenen Listener wieder.
* @param listener
*/
public void removeCurrentPersonChangeListener(CurrentPersonChangeListener listener) {
vCurrentPersonChangeListener.remove(listener);
}
/**
* Den Event an alle Listener senden, dass sich die aktuelle Person ge�ndert
* hat.
* @param event
*/
private void sendCurrentPersonChangeListenerEvent(CurrentPersonChangeEvent event) {
for (CurrentPersonChangeListener listener : vCurrentPersonChangeListener) {
listener.currentPersonChanged(event);
}
}
/**
* Zeigt die �nderung oder Speicherung der Familiendaten an.
* @param b true: Es wurden Daten ge�ndert. false: Die Daten wurden
* gespeichert.
*/
public void setChanged(boolean b) {
if (b != bDataChanged) {
bDataChanged = b;
}
if (GUI.instance != null) {
GUI.instance.setShellTitle();
}
}
/**
* Ermittelt, ob die Daten der Familie ungespeichert und ge�ndert sind.
* @return true/false.
*/
public boolean getChanged() {
return bDataChanged;
}
/**
* Ermittelt eine Person anhand ihrer XREF-ID.
* @param xrefid
* @return Person oder null.
*/
public Person getPersonFromXREFID(String xrefid) {
for (Person person : persons) {
if (person.getValueView(Person.XREFID).equals(xrefid)) {
return person;
}
}
return null;
}
/**
* Ermittelt die x-te Person in der Liste.
* @param index
* @return Person.
*/
public Person getPersonFromIndex(int index) {
return persons.get(index);
}
/**
* Sortiert die Personen der Familie.
*/
public void sortPersons() {
PersonListSort.sort(persons);
}
/**
* L�scht rekursiv alle Personen, die fertig ge-merge-d wurden und zus�tzlich
* keine Verbindungen mehr zu nicht verarbeiteten Personen haben.
* @param list Leere Liste zu �bergeben. Sie enth�lt anschlie�end die
* gepr�ften Personen.
* @param person Zu pr�fende Person.
*/
public void removeFinishedMergePersons(List<Person> list, Person person) {
// Ist diese Person schon erledigt und noch nicht hier gepr�ft?
if (person.isFinishedMerge() && !list.contains(person)) {
list.add(person);
Relationship[] relships = person.getAllRelationships();
boolean bDeletePerson = relships.length == 0;
for (Relationship relship : relships) {
Person p2 = relship.getOtherPerson(person);
if (p2.isFinishedMerge()) {
// Beziehung darf doch nicht gel�scht werden, sie muss ja noch
// verglichen werden.
// person.removeRelationship(relship);
removeFinishedMergePersons(list, person);
}
}
// Wenn diese Person nun keine Verbindung mehr zu einer nicht
// abgeschlossenen Person hat, sie einfach l�schen.
if (bDeletePerson) {
removePerson(person);
// Wenn die gesamte zu mergende Familie leer ist, sie wieder l�schen.
if (persons.size() == 0) {
Statics.mergeFamily = null;
}
GUI.instance.setShellTitle();
}
}
}
/**
* Ermittelt den Index einer Person in der Familie. Nicht zu verwechseln mit
* der XREFID!
* @param person Person, deren Index gesucht wird.
* @return Index oder -1, falls Person nicht Teil dieser Familie.
*/
public int getPersonIndex(Person person) {
return persons.indexOf(person);
}
/**
* Entfernt eine Beziehung zwischen zwei Personen.
* @param mergeRelationship
*/
public void removeRelationship(Relationship mergeRelationship) {
mergeRelationship.partner1.removeRelationship(mergeRelationship);
setChanged(true);
}
/**
* Gibt die Anzahl der Personen innerhalb der Familie zur�ck.
* @return Anzahl der Personen.
*/
public int getPersonsCount() {
return persons.size();
}
/**
* Speichert die Daten zur MERGE-Family.
*/
public static void saveMergeData() {
// Die Familiendatei abspeichern oder l�schen, falls leer.
try {
if (Statics.mergeFamily != null) {
Statics.mergeFamily.writePersons(Statics.FAMILY2MERGE_ZIP);
}
else {
new File(Statics.FAMILY2MERGE_ZIP).delete();
}
}
catch (IOException e1) {
// Wohl nicht mehr zu �ndern.
}
}
/**
* Vermerkt, dass ein Bild einer Person zugeordnet ist.
* @param image Bild-Datei.
* @param person Person, zu der dieses Bild geh�rt.
*/
public void addImage(File image, Person person) {
imagePersonMap.put(image, person);
}
/**
* Ermittelt zu einem Image-File die dazugeh�rige Person.
* @param file Image-File.
* @return Person bzw. null.
*/
public Person getPersonByImage(File file) {
return imagePersonMap.get(file);
}
/**
* @see de.chris_soft.fyllgen.utilities.StatusProvider#getStatus()
*/
public String[] getStatus() {
String statusPersonCount = persons.size() + " Personen";
String statusImageCount = imagePersonMap.size() + " Portraitfotos";
return new String[] { statusPersonCount, statusImageCount };
}
/**
* Liefert eine Liste aller Personen zur�ck.
* @return Personenliste.
*/
public List<Person> getPersonsList() {
List<Person> list = new ArrayList<Person>();
list.addAll(persons);
return list;
}
}