All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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