de.rpgframework.jfx.pages.CharacterViewLayout Maven / Gradle / Ivy
package de.rpgframework.jfx.pages;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.System.Logger;
import java.lang.System.Logger.Level;
import java.util.ArrayList;
import java.util.List;
import java.util.ResourceBundle;
import org.prelle.javafx.AlertManager;
import org.prelle.javafx.ApplicationScreen;
import org.prelle.javafx.ApplicationScreenWithPages;
import org.prelle.javafx.CloseType;
import org.prelle.javafx.FlexibleApplication;
import org.prelle.javafx.ManagedDialog;
import org.prelle.javafx.SymbolIcon;
import de.rpgframework.ResourceI18N;
import de.rpgframework.character.Attachment;
import de.rpgframework.character.Attachment.Format;
import de.rpgframework.character.Attachment.Type;
import de.rpgframework.character.CharacterHandle;
import de.rpgframework.character.CharacterIOException;
import de.rpgframework.character.CharacterProvider;
import de.rpgframework.character.CharacterProviderLoader;
import de.rpgframework.character.RuleSpecificCharacterObject;
import de.rpgframework.core.BabylonEventBus;
import de.rpgframework.core.BabylonEventType;
import de.rpgframework.core.RoleplayingSystem;
import de.rpgframework.genericrpg.ToDoElement;
import de.rpgframework.genericrpg.chargen.CharacterController;
import de.rpgframework.genericrpg.chargen.CharacterGenerator;
import de.rpgframework.genericrpg.chargen.Rule;
import de.rpgframework.genericrpg.data.IAttribute;
import javafx.geometry.Insets;
import javafx.scene.control.Alert.AlertType;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.Tooltip;
import javafx.scene.layout.VBox;
/**
* View of a single character
*
*/
public abstract class CharacterViewLayout, C extends CharacterController> extends ApplicationScreenWithPages {
private final static Logger logger = System.getLogger(CharacterViewLayout.class.getPackageName());
private final static ResourceBundle RES = ResourceBundle.getBundle(CharacterViewLayout.class.getName());
protected C control;
protected CharacterHandle handle;
private RoleplayingSystem rules;
/** Button for finishing creation */
protected Button btnFinish;
/** Button for leaving without saving (CANCEL editing) */
private Button btnQuit;
private Button btnSave;
private Button btnSetting;
protected boolean dontShowConfirmationDialog;
//-------------------------------------------------------------------
/**
*/
public CharacterViewLayout(RoleplayingSystem rules) {
this.rules = rules;
getStyleClass().add("character-view-layout");
SymbolIcon icoAccept = new SymbolIcon("accept");
SymbolIcon icoQuit = new SymbolIcon("clear");
SymbolIcon icoSave = new SymbolIcon("save");
SymbolIcon icoSetting = new SymbolIcon("setting");
btnFinish = new Button(null, icoAccept);
btnFinish.setTooltip(new Tooltip(ResourceI18N.get(RES, "action.finishCreation")));
btnQuit = new Button(null, icoQuit);
btnQuit.setTooltip(new Tooltip(ResourceI18N.get(RES, "action.cancelEditing")));
btnSave = new Button(null, icoSave);
btnSave.setTooltip(new Tooltip(ResourceI18N.get(RES, "action.saveAndExit")));
btnSetting = new Button(null, icoSetting);
btnSetting.setTooltip(new Tooltip(ResourceI18N.get(RES, "action.settings")));
initButtonInteractivity();
extraButtonsTopProperty().addAll(btnQuit, btnSave, btnFinish);
extraButtonsBottomProperty().addAll(btnSetting);
}
//-------------------------------------------------------------------
@SuppressWarnings("rawtypes")
private void initButtonInteractivity() {
setCanBeLeftCallback( scr -> {
boolean allowed = userTriesToLeave();
logger.log(Level.WARNING, "Trying to leave is allowed = "+allowed);
return allowed;
});
btnFinish.setOnAction(ev -> {
logger.log(Level.INFO, "FINISH clicked");
if (((CharacterGenerator) control).canBeFinished()) {
if (control.getToDos().isEmpty()) {
if (confirmFINISH() == CloseType.OK) {
((CharacterGenerator) control).finish();
saveCharacter();
dontShowConfirmationDialog = true;
FlexibleApplication.getInstance().closeScreen(this);
}
} else {
if (confirmFINISHWarning() == CloseType.OK) {
((CharacterGenerator) control).finish();
dontShowConfirmationDialog = true;
saveCharacter();
FlexibleApplication.getInstance().closeScreen(this);
}
}
} else {
errorCannotFINISH();
}
});
btnSave.setOnAction(ev -> {
logger.log(Level.INFO, "SAVE clicked");
if (!saveCharacter())
return;
dontShowConfirmationDialog=true;
FlexibleApplication.getInstance().closeScreen(this);
});
btnQuit.setOnAction(ev -> {
logger.log(Level.INFO, "QUIT clicked");
if (confirmQUIT() == CloseType.OK) {
if (handle!=null) {
try {
reloadCharacter();
} catch (Exception e) {
logger.log(Level.ERROR, "Failed reloading character",e);
}
}
dontShowConfirmationDialog = true;
FlexibleApplication.getInstance().closeScreen(this);
}
});
btnSetting.setOnAction(ev -> {
logger.log(Level.INFO, "SETTINGS clicked");
List categories = new ArrayList<>();
categories.addAll(List.of(Rule.EffectOn.COMMON, Rule.EffectOn.UI));
if (control.getModel().isInCareerMode()) {
categories.add(0,Rule.EffectOn.CAREER);
} else {
categories.add(0,Rule.EffectOn.CHARGEN);
}
RuleSettingsPage rulePage = new RuleSettingsPage(control, categories);
ApplicationScreen screen = new ApplicationScreen(rulePage);
FlexibleApplication.getInstance().openScreen(screen);
});
}
//-------------------------------------------------------------------
public void setController(C value) {
if (value==null)
throw new NullPointerException("Controller may not be null");
this.control = value;
logger.log(Level.DEBUG, "Controller set to "+value);
}
//-------------------------------------------------------------------
private CloseType confirmQUIT() {
return AlertManager.showAlertAndCall(AlertType.CONFIRMATION,
ResourceI18N.get(RES, "confirm.quit.title"),
ResourceI18N.get(RES, "confirm.quit.content")
);
}
//-------------------------------------------------------------------
private CloseType confirmFINISH() {
return AlertManager.showAlertAndCall(AlertType.CONFIRMATION,
ResourceI18N.get(RES, "confirm.finish.title"),
ResourceI18N.get(RES, "confirm.finish.content")
);
}
//-------------------------------------------------------------------
private CloseType errorCannotFINISH() {
return AlertManager.showAlertAndCall(AlertType.ERROR,
ResourceI18N.get(RES, "error.finish.title"),
getToDoBox(ResourceI18N.get(RES, "error.finish.content"), control.getToDos())
);
}
//-------------------------------------------------------------------
private CloseType confirmFINISHWarning() {
return AlertManager.showAlertAndCall(AlertType.CONFIRMATION,
ResourceI18N.get(RES, "confirm.finishWarning.title"),
getToDoBox(ResourceI18N.get(RES, "confirm.finishWarning.content"), control.getToDos())
);
}
public abstract void startCreation(CharacterGenerator,?> wrapper);
public abstract void continueCreation(M model, CharacterHandle handle);
public abstract void resetCharacter(M model, CharacterHandle handle);
public abstract void edit(M model, CharacterHandle handle);
//-------------------------------------------------------------------
protected void showCharacterIOException(CharacterIOException e, RuleSpecificCharacterObject model) {
logger.log(Level.ERROR, "showCharacterIOException",e);
StringWriter out = new StringWriter();
if (e!=null)
e.printStackTrace(new PrintWriter(out));
String title = e.getMessage();
String explain = e.getValue();
int type = 2;
switch (e.getCode()) {
case ENCODING_FAILED:
title = ResourceI18N.get(RES,"error.encoding.title");
explain = ResourceI18N.format(RES,"error.encoding.content", System.getProperty("logdir")+"\\genesis-logs.txt");
break;
case DECODING_FAILED:
title = ResourceI18N.get(RES,"error.decoding.title");
explain = ResourceI18N.format(RES,"error.decoding.content", System.getProperty("logdir")+"\\genesis-logs.txt");
break;
case CHARACTER_WITH_THAT_NAME_EXISTS:
title = ResourceI18N.get(RES, "error.chario.already_exists.title");
explain = ResourceI18N.format(RES, "error.chario.already_exists.mess", model.getName(), e.getValue());
break;
case NO_FREE_ONLINE_CHARACTER_SLOTS:
title = ResourceI18N.get(RES, "error.chario.no_free_slots.title");
explain = ResourceI18N.get(RES, "error.chario.no_free_slots.mess");
type = 1;
break;
case NO_WRITE_PERMISSION:
title = ResourceI18N.get(RES, "error.chario.no_write_permission.title");
explain = ResourceI18N.format(RES, "error.chario.no_write_permission.mess", e.getValue());
break;
case SERVER_ERROR:
title = ResourceI18N.get(RES, "error.chario.server_error.title");
explain = ResourceI18N.get(RES, "error.chario.server_error.mess");
break;
case MISSING_SERVER_CONNECTION:
title = ResourceI18N.get(RES, "error.chario.missing_server_connection.title");
explain = ResourceI18N.get(RES, "error.chario.missing_server_connection.mess");
type = 1;
break;
case OTHER_ERROR:
title = ResourceI18N.get(RES, "error.chario.other_error.title");
explain = ResourceI18N.get(RES, "error.chario.other_error.mess");
break;
case FILESYSTEM_WRITE:
title = ResourceI18N.get(RES, "error.chario.filesystem_write.title");
explain = ResourceI18N.get(RES, "error.chario.filesystem_write.mess");
break;
case FILESYSTEM_READ:
title = ResourceI18N.get(RES, "error.chario.filesystem_read.title");
explain = ResourceI18N.get(RES, "error.chario.filesystem_read.mess");
break;
}
Label mess = new Label(explain);
mess.setWrapText(true);
BabylonEventBus.fireEvent(BabylonEventType.UI_MESSAGE, type, explain, e.getCause());
// AlertManager.showAlertAndCall(AlertType.ERROR, title, mess);
}
//-------------------------------------------------------------------
protected void showAnyException(Exception e, RuleSpecificCharacterObject model, String title, String message) {
StringWriter out = new StringWriter();
if (e!=null)
e.printStackTrace(new PrintWriter(out));
Label mess = new Label((message!=null)?message:e.getMessage());
Label trace = new Label(out.toString());
mess.setWrapText(true);
trace.setWrapText(true);
AlertManager.showAlertAndCall(AlertType.ERROR, title, new VBox(10, mess, trace));
}
//-------------------------------------------------------------------
/**
* @return NULL is closing is forbidden, CloseType otherwise
*/
@SuppressWarnings("rawtypes")
private boolean userTriesToLeave() {
logger.log(Level.INFO, "ENTER userTriesToLeave (dontShowConfirmation={0})", dontShowConfirmationDialog);
if (dontShowConfirmationDialog) {
dontShowConfirmationDialog=false;
return true;
}
try {
boolean generation = control instanceof CharacterGenerator;
logger.log(Level.INFO, "generation="+generation);
logger.log(Level.DEBUG, "warnings = "+ control.getToDos());
VBox toDos = getToDoBox(ResourceI18N.get(RES, "alert.finish_creation_with_warning.mess"), control.getToDos());
if (generation) {
// return saveInCreationMode(); // This leads to an exception when closing regularly
logger.log(Level.DEBUG, "Check if creation is finished");
if ( ((CharacterGenerator)control).canBeFinished() ) {
logger.log(Level.DEBUG, "finishing is okay");
VBox help = new VBox(5,
new Label(ResourceI18N.get(RES, "closetype.leavecreation.cancel")),
new Label(ResourceI18N.get(RES, "closetype.leavecreation.quit")),
new Label(ResourceI18N.get(RES, "closetype.leavecreation.save")),
new Label(ResourceI18N.get(RES, "closetype.leavecreation.finish"))
);
VBox dialogContent = new VBox(10, toDos, help);
String title;
// Generator has no more stoppers, but may have warnings
if ( control.getToDos().size()>0 ) {
// There are warnings
dialogContent = new VBox(10, toDos, help);
title = ResourceI18N.get(RES, "alert.finish_creation_with_warning.title");
} else {
title = ResourceI18N.get(RES, "alert.finish_creation_without_warning.title");
dialogContent = new VBox(10, help);
}
ManagedDialog dialog = new ManagedDialog(title, dialogContent,
CloseType.CANCEL, CloseType.QUIT, CloseType.SAVE, CloseType.FINISH);
//dialog.setOnAction(CloseType.SAVE, getOnBackAction());
CloseType result = AlertManager.showAlertAndCall(dialog);
logger.log(Level.DEBUG, "ManagedDialog closed with "+result);
switch (result) {
case CANCEL:
logger.log(Level.INFO, "CANCEL leaving creation");
return false;
case QUIT:
logger.log(Level.INFO, "QUIT creation without saving");
if (handle!=null)
reloadCharacter();
return true;
case SAVE:
logger.log(Level.INFO, "User wants to pause and leave");
saveCharacter();
return true;
case FINISH:
logger.log(Level.INFO, "User wants to finish and leave");
((CharacterGenerator)control).finish();
saveCharacter();
return true;
}
logger.log(Level.WARNING, "This should not happen");
return false;
// logger.log(Level.INFO, "User wants to leave and generator is finished - try to save character");
// ((CharacterGenerator)control).finish();
// saveCharacter();
} else {
logger.log(Level.INFO, "User wants to leave the generation early.");
CloseType result = AlertManager.showAlertAndCall(
AlertType.CONFIRMATION,
RES.getString("alert.cancel_creation.title"),
RES.getString("alert.cancel_creation.message")
);
return false;
}
} else {
// Leave career mode
logger.log(Level.INFO, "User wants to leave career mode");
logger.log(Level.DEBUG, "warnings = "+ control.getToDos());
CloseType result = AlertManager.showAlertAndCall(
ResourceI18N.get(RES, "alert.save_character.title"),
getToDoBox(ResourceI18N.get(RES, "alert.save_character.message"), control.getToDos()),
CloseType.YES, CloseType.NO, CloseType.CANCEL
);
if (result==CloseType.YES || result==CloseType.OK) {
logger.log(Level.DEBUG, "User confirmed saving character");
saveCharacter();
return true;
} else if (result==CloseType.CANCEL) {
logger.log(Level.DEBUG, "User cancelled leaving");
return false;
} else {
logger.log(Level.DEBUG, "User denied saving character - reload it");
reloadCharacter();
return true;
}
}
} finally {
logger.log(Level.INFO, "LEAVE userTriesToLeave");
}
}
//-------------------------------------------------------------------
protected abstract byte[] encodeCharacter(M model) throws CharacterIOException;
//-------------------------------------------------------------------
protected abstract M decodeCharacter(byte[] encoded) throws CharacterIOException;
//-------------------------------------------------------------------
protected boolean saveCharacter() {
logger.log(Level.DEBUG, "START: saveCharacter");
M model = control.getModel();
try {
/*
* 1. Convert character into Byte Buffer - or fail
*/
byte[] encoded = null;
try {
encoded = encodeCharacter(model);
logger.log(Level.DEBUG, "Successfully encoded character");
} catch (CharacterIOException e) {
showCharacterIOException(e, model);
return false;
}
/*
* 2. Use character service to save character
*/
try {
if (handle==null) {
logger.log(Level.INFO, "Trying to create new character named {0}", model.getName());
handle = CharacterProviderLoader.getCharacterProvider().createCharacter(model.getName(), rules);
// At this point, the CHAR_ADDED event has been sent
logger.log(Level.DEBUG, "Add model and description to handle {0}", handle.getUUID());
handle.setCharacter(model);
handle.setShortDescription( model.getShortDescription());
}
logger.log(Level.INFO, "Save character "+model.getName());
// Has the name been changed?
if (!handle.getName().equals(model.getName())) {
logger.log(Level.INFO, "Name changed from ''{0}'' to ''{1}''", handle.getName(), model.getName());
handle.setName(model.getName());
}
CharacterProvider charProv = CharacterProviderLoader.getCharacterProvider();
Attachment existAttach = charProv.getFirstAttachment(handle, Type.CHARACTER, Format.RULESPECIFIC);
if (existAttach==null) {
logger.log(Level.DEBUG, "Create new attachment");
charProv.addAttachment(handle, Type.CHARACTER, Format.RULESPECIFIC, handle.getName(), encoded);
} else {
logger.log(Level.DEBUG, "Modify existing attachment");
charProv.deleteAttachment(handle, existAttach);
existAttach.setData(encoded);
existAttach.setFilename(model.getName()+".xml");
charProv.modifyAttachment(handle, existAttach);
}
logger.log(Level.INFO, "Saved character "+model.getName()+" successfully");
} catch (IOException e) {
logger.log(Level.ERROR, "Failed saving character",e);
StringWriter out = new StringWriter();
e.printStackTrace(new PrintWriter(out));
AlertManager.showAlertAndCall(
AlertType.ERROR,
ResourceI18N.get(RES,"error.saving_character.title"),
ResourceI18N.format(RES,"error.saving_character.message", System.getProperty("logdir")+"\\genesis-logs.txt")+"\n"+out
);
return false;
}
// 3. Eventually rename
try {
if (handle!=null && !handle.getName().equals(model.getName())) {
logger.log(Level.INFO, "Character has been renamed");
CharacterProviderLoader.getCharacterProvider().renameCharacter(handle, model.getName());
}
} catch (IOException e) {
logger.log(Level.ERROR, "Renaming failed",e);
BabylonEventBus.fireEvent(BabylonEventType.UI_MESSAGE, 2, "Renaming failed: "+e);
}
/*
* 4. Update portrait
*/
logger.log(Level.DEBUG, "Update portrait");
CharacterProvider charServ = CharacterProviderLoader.getCharacterProvider();
try {
CharacterProvider charProv = CharacterProviderLoader.getCharacterProvider();
if (model.getImage()!=null && handle!=null) {
Attachment attach = charProv.getFirstAttachment(handle, Type.CHARACTER, Format.IMAGE);
if (attach!=null) {
logger.log(Level.INFO, "Update character image");
attach.setData(model.getImage());
charServ.modifyAttachment(handle, attach);
} else {
charServ.addAttachment(handle, Type.CHARACTER, Format.IMAGE, handle.getName(), model.getImage());
}
} else if (handle!=null) {
Attachment attach = charProv.getFirstAttachment(handle, Type.CHARACTER, Format.IMAGE);
if (attach!=null) {
logger.log(Level.INFO, "Delete old character image");
charServ.deleteAttachment(handle, attach);
}
}
} catch (IOException e) {
logger.log(Level.ERROR, "Failed modifying portrait attachment",e);
}
BabylonEventBus.fireEvent(BabylonEventType.CHAR_MODIFIED, handle);
} finally {
logger.log(Level.DEBUG, "STOP : saveCharacter");
}
return true;
}
//-------------------------------------------------------------------
protected void reloadCharacter() {
logger.log(Level.DEBUG, "START: reloadCharacter");
M model = control.getModel();
Attachment attach = null;
try {
/*
* 1. Load character attachment from storage
*/
byte[] encoded = null;
try {
attach = CharacterProviderLoader.getCharacterProvider().getFirstAttachment(handle, Type.CHARACTER, Format.RULESPECIFIC);
encoded = attach.getData();
logger.log(Level.DEBUG, " character reloaded from disk");
} catch (CharacterIOException e) {
showCharacterIOException(e, model);
return;
} catch (IOException e) {
logger.log(Level.ERROR, "Failed reloading character",e);
BabylonEventBus.fireEvent(BabylonEventType.UI_MESSAGE, 2, ResourceI18N.format(RES,"error.reloading_character.message", System.getProperty("logdir")+"\\commlink-logs.txt"), e, attach.getLocalFile());
return;
}
/*
* 2. Decode character
*/
try {
model = decodeCharacter(encoded);
handle.setCharacter(model);
logger.log(Level.INFO, "Loaded character "+model.getName()+" successfully");
} catch (Exception e) {
logger.log(Level.ERROR, "Failed decoding character",e);
BabylonEventBus.fireEvent(BabylonEventType.UI_MESSAGE, 2, ResourceI18N.format(RES,"error.decoding.message", System.getProperty("logdir")+"\\commlink-logs.txt"), e, attach.getLocalFile());
return;
}
/*
* 3. Process
*/
try {
resetCharacter(model, handle);
} catch (Exception e) {
logger.log(Level.ERROR, "Failed processing character",e);
e.printStackTrace();
BabylonEventBus.fireEvent(BabylonEventType.UI_MESSAGE, 1,ResourceI18N.format(RES,"error.processing_character.content", System.getProperty("logdir")+"\\commlink-logs.txt"), e, attach.getLocalFile());
return;
}
BabylonEventBus.fireEvent(BabylonEventType.CHAR_MODIFIED, handle);
} catch (Throwable e) {
logger.log(Level.ERROR, "Failed loading character",e);
StringWriter out = new StringWriter();
e.printStackTrace(new PrintWriter(out));
AlertManager.showAlertAndCall(
AlertType.ERROR,
ResourceI18N.get(RES,"error.loading_character.title"),
ResourceI18N.format(RES,"error.loading_character.message", System.getProperty("logdir")+"\\commlink-logs.txt")+"\n"+out
);
return;
} finally {
logger.log(Level.DEBUG, "STOP : reloadCharacter");
}
}
//-------------------------------------------------------------------
public static VBox getToDoBox(String mess, List todos) {
VBox bxToDos = new VBox(10);
Label lbMess = new Label(mess);
lbMess.setWrapText(true);
bxToDos.getChildren().add(lbMess);
VBox.setMargin(bxToDos, new Insets(0, 0, 20, 0));
for (ToDoElement todo : todos) {
Label label = new Label(todo.getMessage());
label.setWrapText(true);
label.setStyle("-fx-text-fill: textcolor-"+todo.getSeverity().name().toLowerCase());
bxToDos.getChildren().add(label);
}
return bxToDos;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy