Skip to content

Commit

Permalink
#28 Implementing remember window size & position.
Browse files Browse the repository at this point in the history
  • Loading branch information
albertus82 committed May 19, 2024
1 parent 25718f4 commit 53a7351
Show file tree
Hide file tree
Showing 8 changed files with 454 additions and 21 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.github.albertus82.acodec.common.resources;

import java.util.Locale;
import java.util.Optional;

import lombok.Getter;
import lombok.RequiredArgsConstructor;
Expand All @@ -14,4 +15,13 @@ public enum Language {

private final Locale locale;

public static Optional<Language> fromString(final String lang) {
for (final Language e : Language.values()) {
if (e.locale.getLanguage().equals(new Locale(lang).getLanguage())) {
return Optional.of(e);
}
}
return Optional.empty();
}

}
171 changes: 164 additions & 7 deletions src/main/java/io/github/albertus82/acodec/gui/CodecGui.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.Map;
import java.util.Properties;
import java.util.logging.Level;

import javax.naming.SizeLimitExceededException;
Expand All @@ -32,13 +33,16 @@
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.FileDialog;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Layout;
Expand All @@ -53,6 +57,7 @@
import io.github.albertus82.acodec.common.resources.ConfigurableMessages;
import io.github.albertus82.acodec.common.resources.Language;
import io.github.albertus82.acodec.common.util.BuildInfo;
import io.github.albertus82.acodec.gui.config.ApplicationConfig;
import io.github.albertus82.acodec.gui.listener.AlgorithmComboSelectionListener;
import io.github.albertus82.acodec.gui.listener.CharsetComboSelectionListener;
import io.github.albertus82.acodec.gui.listener.ExitListener;
Expand All @@ -63,13 +68,18 @@
import io.github.albertus82.acodec.gui.listener.ShellDropListener;
import io.github.albertus82.acodec.gui.listener.TextCopySelectionKeyListener;
import io.github.albertus82.acodec.gui.listener.TextSelectAllKeyListener;
import io.github.albertus82.acodec.gui.preference.Preference;
import io.github.albertus82.acodec.gui.resources.GuiMessages;
import io.github.albertus82.jface.EnhancedErrorDialog;
import io.github.albertus82.jface.Events;
import io.github.albertus82.jface.Multilanguage;
import io.github.albertus82.jface.SwtUtils;
import io.github.albertus82.jface.closeable.CloseableResource;
import io.github.albertus82.jface.i18n.LocalizedWidgets;
import io.github.albertus82.jface.preference.IPreferencesConfiguration;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.Setter;
import lombok.extern.java.Log;
Expand All @@ -78,6 +88,13 @@
@Getter
public class CodecGui extends ApplicationWindow implements Multilanguage {

public static final String SHELL_MAXIMIZED = "shell.maximized";
private static final String SHELL_SIZE_X = "shell.size.x";
private static final String SHELL_SIZE_Y = "shell.size.y";
private static final String SHELL_LOCATION_X = "shell.location.x";
private static final String SHELL_LOCATION_Y = "shell.location.y";
private static final Point POINT_ZERO = new Point(0, 0);

private static final int TEXT_LIMIT_CHARS = Character.MAX_VALUE;
private static final int TEXT_HEIGHT_MULTIPLIER = 4;

Expand All @@ -86,6 +103,13 @@ public class CodecGui extends ApplicationWindow implements Multilanguage {

private static final ConfigurableMessages messages = GuiMessages.INSTANCE;

@NoArgsConstructor(access = AccessLevel.PRIVATE)
public static class Defaults {
public static final boolean SHELL_MAXIMIZED = false;
}

private final IPreferencesConfiguration configuration = ApplicationConfig.getPreferencesConfiguration();

@NonNull
@Setter
private CodecMode mode = CodecMode.ENCODE;
Expand Down Expand Up @@ -121,6 +145,15 @@ public class CodecGui extends ApplicationWindow implements Multilanguage {
@NonNull
private GuiStatus status = GuiStatus.UNDEFINED;

/** Shell maximized status. May be null in some circumstances. */
private Boolean shellMaximized;

/** Shell size. May be null in some circumstances. */
private Point shellSize;

/** Shell location. May be null in some circumstances. */
private Point shellLocation;

private CodecGui() {
super(null);
}
Expand All @@ -129,7 +162,6 @@ private CodecGui() {
protected void configureShell(final Shell shell) {
super.configureShell(shell);
localizeWidget(shell, "gui.message.application.name");
shell.addShellListener(new ExitListener(this));
}

@Override
Expand Down Expand Up @@ -252,12 +284,65 @@ public void widgetSelected(final SelectionEvent e) {
return parent;
}

@Override
protected void handleShellCloseEvent() {
final Event event = new Event();
new ExitListener(this).handleEvent(event);
if (event.doit) {
super.handleShellCloseEvent();
}
}

@Override
protected void constrainShellSize() {
super.constrainShellSize();
final Shell shell = getShell();
shell.pack();
shell.setMinimumSize(shell.getSize());

final Point preferredSize = shell.getSize();
shell.setMinimumSize(preferredSize);

final Integer sizeX = configuration.getInt(SHELL_SIZE_X);
final Integer sizeY = configuration.getInt(SHELL_SIZE_Y);
if (sizeX != null && sizeY != null) {
shell.setSize(Math.max(sizeX, preferredSize.x), Math.max(sizeY, preferredSize.y));
}

final Integer locationX = configuration.getInt(SHELL_LOCATION_X);
final Integer locationY = configuration.getInt(SHELL_LOCATION_Y);
if (locationX != null && locationY != null) {
if (new Rectangle(locationX, locationY, shell.getSize().x, shell.getSize().y).intersects(shell.getDisplay().getBounds())) {
shell.setLocation(locationX, locationY);
}
else {
log.log(Level.WARNING, "Illegal shell location ({0}, {1}) for size ({2}).", new Object[] { locationX, locationY, shell.getSize() });
}
}

setMaximizedShellStatus();
}

private void setMaximizedShellStatus() {
if (configuration.getBoolean(SHELL_MAXIMIZED, Defaults.SHELL_MAXIMIZED)) {
getShell().setMaximized(true);
}
}

@Override
public int open() {
final int code = super.open();

final Shell shell = getShell();
final UpdateShellStatusListener listener = new UpdateShellStatusListener();
shell.addListener(SWT.Resize, listener);
shell.addListener(SWT.Move, listener);
shell.addListener(SWT.Activate, new MaximizeShellListener());

if (SwtUtils.isGtk3() == null || SwtUtils.isGtk3()) { // fixes invisible (transparent) shell bug with some Linux distibutions
setMaximizedShellStatus();
}

return code;
}

/* GUI entry point */
Expand All @@ -277,6 +362,7 @@ public static void main(final String... args) {
private static void start() {
Shell shell = null;
try {
ApplicationConfig.initialize(); // Load configuration and initialize the application
final CodecGui gui = new CodecGui();
gui.open(); // returns immediately
shell = gui.getShell(); // to be called after open!
Expand Down Expand Up @@ -441,11 +527,13 @@ public void evaluateInputText() {
public void setLanguage(@NonNull final Language language) {
messages.setLanguage(language);
final Shell shell = getShell();
shell.setRedraw(false);
updateLanguage();
shell.layout(true, true);
shell.setMinimumSize(shell.computeSize(SWT.DEFAULT, SWT.DEFAULT, true));
shell.setRedraw(true);
if (shell != null) {
shell.setRedraw(false);
updateLanguage();
shell.layout(true, true);
shell.setMinimumSize(shell.computeSize(SWT.DEFAULT, SWT.DEFAULT, true));
shell.setRedraw(true);
}
}

@Override
Expand Down Expand Up @@ -536,6 +624,75 @@ private int openMessageBox(@NonNull final String message, final int style) {
return messageBox.open();
}

private class MaximizeShellListener implements Listener {
private boolean firstTime = true;

@Override
public void handleEvent(final Event event) {
logEvent(event);
if (firstTime && !getShell().isDisposed() && configuration.getBoolean(SHELL_MAXIMIZED, Defaults.SHELL_MAXIMIZED)) {
firstTime = false;
getShell().setMaximized(true);
}
}
}

private static void logEvent(final Event event) {
log.log(Level.FINE, "{0} {1}", new Object[] { Events.getName(event), event });
}

private class UpdateShellStatusListener implements Listener {
@Override
public void handleEvent(final Event event) {
logEvent(event);
final Shell shell = getShell();
if (shell != null && !shell.isDisposed()) {
shellMaximized = shell.getMaximized();
if (Boolean.FALSE.equals(shellMaximized) && !POINT_ZERO.equals(shell.getSize())) {
shellSize = shell.getSize();
shellLocation = shell.getLocation();
}
}
log.log(Level.FINE, "shellMaximized: {0} - shellSize: {1} - shellLocation: {2}", new Object[] { shellMaximized, shellSize, shellLocation });
}
}

public void saveSettings() {
new Thread(() -> { // don't perform I/O in UI thread
try {
configuration.reload(); // make sure the properties are up-to-date
}
catch (final IOException e) {
log.log(Level.WARNING, "Cannot reload configuration:", e);
return; // abort
}
final Properties properties = configuration.getProperties();

properties.setProperty(Preference.LANGUAGE.getName(), messages.getLanguage().getLocale().getLanguage());

if (shellMaximized != null) {
properties.setProperty(SHELL_MAXIMIZED, Boolean.toString(shellMaximized));
}
if (shellSize != null) {
properties.setProperty(SHELL_SIZE_X, Integer.toString(shellSize.x));
properties.setProperty(SHELL_SIZE_Y, Integer.toString(shellSize.y));
}
if (shellLocation != null) {
properties.setProperty(SHELL_LOCATION_X, Integer.toString(shellLocation.x));
properties.setProperty(SHELL_LOCATION_Y, Integer.toString(shellLocation.y));
}

log.log(Level.FINE, "{0}", configuration);

try {
configuration.save(); // save configuration
}
catch (final IOException e) {
log.log(Level.WARNING, "Cannot save configuration:", e);
}
}, "Save settings").start();
}

public static String getApplicationName() {
return messages.get("gui.message.application.name");
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package io.github.albertus82.acodec.gui.config;

import java.io.File;
import java.io.IOException;
import java.io.UncheckedIOException;

import org.eclipse.jface.util.Util;

import io.github.albertus82.acodec.common.util.BuildInfo;
import io.github.albertus82.acodec.gui.resources.GuiMessages;
import io.github.albertus82.jface.preference.IPreferencesConfiguration;
import io.github.albertus82.jface.preference.PreferencesConfiguration;
import io.github.albertus82.util.InitializationException;
import io.github.albertus82.util.SystemUtils;
import io.github.albertus82.util.config.Configuration;
import io.github.albertus82.util.config.PropertiesConfiguration;

public class ApplicationConfig extends Configuration {

private static final String DIRECTORY_NAME = Util.isLinux() ? '.' + BuildInfo.getProperty("project.artifactId") : BuildInfo.getProperty("project.name");

public static final String APPDATA_DIRECTORY = SystemUtils.getOsSpecificLocalAppDataDir() + File.separator + DIRECTORY_NAME;

private static final String CFG_FILE_NAME = (Util.isLinux() ? BuildInfo.getProperty("project.artifactId") : BuildInfo.getProperty("project.name").replace(" ", "")) + ".cfg";

private static volatile ApplicationConfig instance; // NOSONAR Use a thread-safe type; adding "volatile" is not enough to make this field thread-safe. Use a thread-safe type; adding "volatile" is not enough to make this field thread-safe.
private static volatile IPreferencesConfiguration wrapper; // NOSONAR Use a thread-safe type; adding "volatile" is not enough to make this field thread-safe. Use a thread-safe type; adding "volatile" is not enough to make this field thread-safe.
private static int instanceCount = 0;

private final LanguageConfigAccessor languageConfigAccessor;

private ApplicationConfig() throws IOException {
super(new PropertiesConfiguration(DIRECTORY_NAME + File.separator + CFG_FILE_NAME, true));
final IPreferencesConfiguration pc = new PreferencesConfiguration(this);
languageConfigAccessor = new LanguageConfigAccessor(pc);
}

private static ApplicationConfig getInstance() {
if (instance == null) {
synchronized (ApplicationConfig.class) {
if (instance == null) { // The field needs to be volatile to prevent cache incoherence issues
try {
instance = new ApplicationConfig();
if (++instanceCount > 1) {
throw new IllegalStateException("Detected multiple instances of singleton " + instance.getClass());
}
}
catch (final IOException e) {
throw new UncheckedIOException(e);
}
}
}
}
return instance;
}

public static IPreferencesConfiguration getPreferencesConfiguration() {
if (wrapper == null) {
synchronized (ApplicationConfig.class) {
if (wrapper == null) { // The field needs to be volatile to prevent cache incoherence issues
wrapper = new PreferencesConfiguration(getInstance());
}
}
}
return wrapper;
}

public static void initialize() {
try {
final ApplicationConfig config = getInstance();
GuiMessages.INSTANCE.setLanguage(config.languageConfigAccessor.getLanguage());
}
catch (final RuntimeException e) {
throw new InitializationException(e);
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package io.github.albertus82.acodec.gui.config;

import java.util.Locale;

import io.github.albertus82.acodec.common.resources.Language;
import io.github.albertus82.acodec.gui.preference.Preference;
import io.github.albertus82.jface.preference.IPreferencesConfiguration;
import lombok.AccessLevel;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor(access = AccessLevel.PACKAGE)
public class LanguageConfigAccessor {

public static final String DEFAULT_LANGUAGE = Locale.getDefault().getLanguage();

@NonNull
private final IPreferencesConfiguration configuration;

public @NonNull Language getLanguage() {
return Language.fromString(configuration.getString(Preference.LANGUAGE, DEFAULT_LANGUAGE)).orElse(Language.ENGLISH);
}

}
Loading

0 comments on commit 53a7351

Please sign in to comment.