Skip to content

Commit b8b0af4

Browse files
authored
Merge pull request #49 from doubleSlashde/feature/#38_remember_last_position_on_screen
Feature/#38 remember last position on screen
2 parents a1c9474 + a4f99c4 commit b8b0af4

File tree

10 files changed

+353
-37
lines changed

10 files changed

+353
-37
lines changed

src/main/java/de/doubleslash/keeptime/Main.java

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import de.doubleslash.keeptime.common.FontProvider;
3434
import de.doubleslash.keeptime.common.Resources;
3535
import de.doubleslash.keeptime.common.Resources.RESOURCE;
36+
import de.doubleslash.keeptime.controller.Controller;
3637
import de.doubleslash.keeptime.model.Model;
3738
import de.doubleslash.keeptime.model.Project;
3839
import de.doubleslash.keeptime.model.Settings;
@@ -41,7 +42,6 @@
4142
import de.doubleslash.keeptime.viewpopup.GlobalScreenListener;
4243
import de.doubleslash.keeptime.viewpopup.ViewControllerPopup;
4344
import javafx.application.Application;
44-
import javafx.event.EventHandler;
4545
import javafx.fxml.FXMLLoader;
4646
import javafx.scene.Parent;
4747
import javafx.scene.Scene;
@@ -59,7 +59,6 @@
5959
import javafx.scene.paint.Color;
6060
import javafx.stage.Stage;
6161
import javafx.stage.StageStyle;
62-
import javafx.stage.WindowEvent;
6362

6463
@SpringBootApplication
6564
public class Main extends Application {
@@ -74,6 +73,8 @@ public class Main extends Application {
7473

7574
private Model model;
7675

76+
private Controller controller;
77+
7778
private ViewController viewController;
7879

7980
private GlobalScreenListener globalScreenListener;
@@ -87,6 +88,7 @@ public void init() throws Exception {
8788
springContext = SpringApplication.run(Main.class);
8889
// TODO test if everywhere is used the same model
8990
model = springContext.getBean(Model.class);
91+
controller = springContext.getBean(Controller.class);
9092
model.setSpringContext(springContext);
9193
}
9294

@@ -199,7 +201,12 @@ private void readSettings() {
199201
model.useHotkey.set(settings.isUseHotkey());
200202
model.displayProjectsRight.set(settings.isDisplayProjectsRight());
201203
model.hideProjectsOnMouseExit.set(settings.isHideProjectsOnMouseExit());
204+
model.screenSettings.proportionalX.set(settings.getWindowXProportion());
205+
model.screenSettings.proportionalY.set(settings.getWindowYProportion());
206+
model.screenSettings.screenHash.set(settings.getScreenHash());
207+
model.screenSettings.saveWindowPosition.set(settings.isSaveWindowPosition());
202208
model.remindIfNotesAreEmpty.set(settings.isRemindIfNotesAreEmpty());
209+
203210
}
204211

205212
private void initialisePopupUI(final Stage primaryStage) throws IOException {
@@ -233,13 +240,12 @@ private void initialisePopupUI(final Stage primaryStage) throws IOException {
233240

234241
private void initialiseUI(final Stage primaryStage) throws IOException {
235242
LOG.debug("Initialising main UI.");
236-
Pane mainPane;
237243

238244
// Load root layout from fxml file.
239245
final FXMLLoader loader = new FXMLLoader();
240246
loader.setLocation(Resources.getResource(RESOURCE.FXML_VIEW_LAYOUT));
241247
loader.setControllerFactory(springContext::getBean);
242-
mainPane = loader.load();
248+
final Pane mainPane = loader.load();
243249
primaryStage.initStyle(StageStyle.TRANSPARENT);
244250
// Show the scene containing the root layout.
245251
final Scene mainScene = new Scene(mainPane, Color.TRANSPARENT);
@@ -252,12 +258,8 @@ private void initialiseUI(final Stage primaryStage) throws IOException {
252258
primaryStage.setAlwaysOnTop(true);
253259
primaryStage.setResizable(false);
254260

255-
primaryStage.setOnCloseRequest(new EventHandler<WindowEvent>() {
256-
@Override
257-
public void handle(final WindowEvent event) {
258-
LOG.info("On close request");
259-
}
260-
});
261+
primaryStage.setOnCloseRequest(windowEvent -> LOG.info("On close request"));
262+
261263
primaryStage.show();
262264
viewController = loader.getController();
263265
// Give the controller access to the main app.
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
package de.doubleslash.keeptime.common;
2+
3+
import org.slf4j.Logger;
4+
import org.slf4j.LoggerFactory;
5+
6+
import javafx.geometry.Rectangle2D;
7+
import javafx.stage.Screen;
8+
9+
/**
10+
* Helper to handle screen coordinates. Converts between absolute coordinates to screen+proportional coordinates. E.g.
11+
* you have two screens, each with 1000px width. Absolute position gives you 1500px and this class will get you hash for
12+
* screen #2 and proportional value 0.5. <br>
13+
* Reference #38
14+
*/
15+
public class ScreenPosHelper {
16+
17+
private static final Logger LOG = LoggerFactory.getLogger(ScreenPosHelper.class);
18+
19+
private int screenHash;
20+
21+
private double absoluteX;
22+
private double absoluteY;
23+
24+
private double proportionalX;
25+
private double proportionalY;
26+
27+
public ScreenPosHelper(final int screenhash, final double proportionalX, final double proportionalY) {
28+
this.screenHash = screenhash;
29+
this.proportionalX = proportionalX;
30+
this.proportionalY = proportionalY;
31+
calcAbsolutePosition();
32+
}
33+
34+
public ScreenPosHelper(final double absoluteX, final double absoluteY) {
35+
this.absoluteX = absoluteX;
36+
this.absoluteY = absoluteY;
37+
calcProportionalPosition();
38+
}
39+
40+
public double getAbsoluteX() {
41+
return absoluteX;
42+
}
43+
44+
public void setAbsoluteX(final double absolutX) {
45+
this.absoluteX = absolutX;
46+
calcProportionalPosition();
47+
}
48+
49+
public double getAbsoluteY() {
50+
return absoluteY;
51+
}
52+
53+
public void setAbsoluteY(final double absolutY) {
54+
this.absoluteY = absolutY;
55+
calcProportionalPosition();
56+
}
57+
58+
public int getScreenHash() {
59+
return screenHash;
60+
}
61+
62+
public double getProportionalX() {
63+
return proportionalX;
64+
}
65+
66+
public double getProportionalY() {
67+
return proportionalY;
68+
}
69+
70+
public void resetPositionIfInvalid() {
71+
if (!isPositionValid()) {
72+
final Screen screen = getScreenWithHashOrPrimary(this.screenHash);
73+
final Rectangle2D screenBounds = screen.getBounds();
74+
LOG.warn(
75+
"Position is not in the range of the screen. Screen (hash '{}') range '{}'. Calculated positions '{}'/'{}'. Setting to '0'/'0'.",
76+
screen.hashCode(), screenBounds, absoluteX, absoluteY);
77+
proportionalX = 0;
78+
proportionalY = 0;
79+
absoluteX = 0;
80+
absoluteY = 0;
81+
}
82+
}
83+
84+
private void calcProportionalPosition() {
85+
Screen screen = Screen.getPrimary();
86+
for (final Screen s : Screen.getScreens()) {
87+
if (s.getBounds().contains(this.absoluteX, this.absoluteY)) {
88+
screen = s;
89+
break;
90+
}
91+
}
92+
this.screenHash = screen.hashCode();
93+
final Rectangle2D bounds = screen.getBounds();
94+
final double xInScreen = absoluteX - bounds.getMinX();
95+
this.proportionalX = xInScreen / bounds.getWidth();
96+
final double yInScreen = absoluteY - bounds.getMinY();
97+
this.proportionalY = yInScreen / bounds.getHeight();
98+
99+
}
100+
101+
private void calcAbsolutePosition() {
102+
final Screen screen = getScreenWithHashOrPrimary(this.screenHash);
103+
final Rectangle2D screenBounds = screen.getBounds();
104+
105+
this.absoluteX = screenBounds.getMinX() + (screenBounds.getWidth() * this.proportionalX);
106+
this.absoluteY = screenBounds.getMinY() + (screenBounds.getHeight() * this.proportionalY);
107+
}
108+
109+
private boolean isPositionValid() {
110+
final Screen screen = getScreenWithHashOrPrimary(this.screenHash);
111+
final Rectangle2D screenBounds = screen.getBounds();
112+
return screenBounds.contains(this.absoluteX, this.absoluteY);
113+
}
114+
115+
private Screen getScreenWithHashOrPrimary(final int screenHash) {
116+
for (final Screen s : Screen.getScreens()) {
117+
if (s.hashCode() == screenHash) {
118+
return s;
119+
}
120+
}
121+
LOG.warn("Could not find wanted screen with hash '{}'. Using primary.", screenHash);
122+
return Screen.getPrimary();
123+
}
124+
125+
}

src/main/java/de/doubleslash/keeptime/controller/Controller.java

Lines changed: 33 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@
3636
import de.doubleslash.keeptime.model.Settings;
3737
import de.doubleslash.keeptime.model.Work;
3838
import javafx.collections.ObservableList;
39-
import javafx.scene.paint.Color;
4039

4140
@Service
4241
public class Controller {
@@ -102,25 +101,24 @@ public void addNewProject(final Project project) {
102101
model.getProjectRepository().saveAll(changedProjects);
103102
}
104103

105-
public void updateSettings(final Color hoverBackgroundColor, final Color hoverFontColor,
106-
final Color defaultBackgroundColor, final Color defaultFontColor, final Color taskBarColor,
107-
final boolean useHotkey, final boolean displayProjectsRight, final boolean hideProjectsOnMouseExit,
108-
final boolean emptyNoteReminder) {
109-
// TODO create holder for all the properties (or reuse Settings.class?)
110-
final Settings settings = model.getSettingsRepository().findAll().get(0);
111-
settings.setTaskBarColor(taskBarColor);
112-
113-
settings.setDefaultBackgroundColor(defaultBackgroundColor);
114-
settings.setDefaultFontColor(defaultFontColor);
115-
116-
settings.setHoverBackgroundColor(hoverBackgroundColor);
117-
settings.setHoverFontColor(hoverFontColor);
118-
settings.setUseHotkey(useHotkey);
119-
settings.setDisplayProjectsRight(displayProjectsRight);
120-
settings.setHideProjectsOnMouseExit(hideProjectsOnMouseExit);
121-
settings.setRemindIfNotesAreEmpty(emptyNoteReminder);
122-
123-
model.getSettingsRepository().save(settings);
104+
public void updateSettings(final Settings newValuedSettings) {
105+
Settings settings = model.getSettingsRepository().findAll().get(0);
106+
107+
settings.setTaskBarColor(newValuedSettings.getTaskBarColor());
108+
settings.setDefaultBackgroundColor(newValuedSettings.getDefaultBackgroundColor());
109+
settings.setDefaultFontColor(newValuedSettings.getDefaultFontColor());
110+
settings.setHoverBackgroundColor(newValuedSettings.getHoverBackgroundColor());
111+
settings.setHoverFontColor(newValuedSettings.getHoverFontColor());
112+
settings.setUseHotkey(newValuedSettings.isUseHotkey());
113+
settings.setDisplayProjectsRight(newValuedSettings.isDisplayProjectsRight());
114+
settings.setHideProjectsOnMouseExit(newValuedSettings.isHideProjectsOnMouseExit());
115+
settings.setSaveWindowPosition(newValuedSettings.isSaveWindowPosition());
116+
settings.setWindowXProportion(newValuedSettings.getWindowXProportion());
117+
settings.setWindowYProportion(newValuedSettings.getWindowYProportion());
118+
settings.setScreenHash(newValuedSettings.getScreenHash());
119+
settings.setRemindIfNotesAreEmpty(newValuedSettings.isRemindIfNotesAreEmpty());
120+
121+
settings = model.getSettingsRepository().save(settings);
124122

125123
model.defaultBackgroundColor.set(settings.getDefaultBackgroundColor());
126124
model.defaultFontColor.set(settings.getDefaultFontColor());
@@ -130,13 +128,28 @@ public void updateSettings(final Color hoverBackgroundColor, final Color hoverFo
130128
model.useHotkey.set(settings.isUseHotkey());
131129
model.displayProjectsRight.set(settings.isDisplayProjectsRight());
132130
model.hideProjectsOnMouseExit.set(settings.isHideProjectsOnMouseExit());
131+
model.screenSettings.saveWindowPosition.set(settings.isSaveWindowPosition());
132+
model.screenSettings.proportionalX.set(settings.getWindowXProportion());
133+
model.screenSettings.proportionalY.set(settings.getWindowYProportion());
134+
model.screenSettings.screenHash.set(settings.getScreenHash());
133135
model.remindIfNotesAreEmpty.set(settings.isRemindIfNotesAreEmpty());
134136
}
135137

136138
@PreDestroy
137139
public void shutdown() {
138140
LOG.info("Controller shutdown");
141+
142+
LOG.info("Changing project to persist current work on shutdown.");
139143
changeProject(model.getIdleProject(), 0);
144+
145+
LOG.info("Updating settings to persist local changes on shutdown.");
146+
final Settings newSettings = new Settings(model.hoverBackgroundColor.get(), model.hoverFontColor.get(),
147+
model.defaultBackgroundColor.get(), model.defaultFontColor.get(), model.taskBarColor.get(),
148+
model.useHotkey.get(), model.displayProjectsRight.get(), model.hideProjectsOnMouseExit.get(),
149+
model.screenSettings.proportionalX.get(), model.screenSettings.proportionalY.get(),
150+
model.screenSettings.screenHash.get(), model.screenSettings.saveWindowPosition.get(),
151+
model.remindIfNotesAreEmpty.get());
152+
updateSettings(newSettings);
140153
}
141154

142155
public void deleteProject(final Project p) {

src/main/java/de/doubleslash/keeptime/model/Model.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,8 @@ public Model(final ProjectRepository projectRepository, final WorkRepository wor
8282

8383
public final ObjectProperty<Boolean> remindIfNotesAreEmpty = new SimpleObjectProperty<>(false);
8484

85+
public final ScreenSettings screenSettings = new ScreenSettings();
86+
8587
private ConfigurableApplicationContext springContext;
8688

8789
public void setWorkRepository(final WorkRepository workRepository) {
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package de.doubleslash.keeptime.model;
2+
3+
import javafx.beans.property.ObjectProperty;
4+
import javafx.beans.property.SimpleObjectProperty;
5+
6+
public class ScreenSettings {
7+
public final ObjectProperty<Boolean> saveWindowPosition = new SimpleObjectProperty<>(false);
8+
public final ObjectProperty<Double> proportionalX = new SimpleObjectProperty<>(0.5);
9+
public final ObjectProperty<Double> proportionalY = new SimpleObjectProperty<>(0.5);
10+
public final ObjectProperty<Integer> screenHash = new SimpleObjectProperty<>(0);
11+
}

src/main/java/de/doubleslash/keeptime/model/Settings.java

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,40 @@ public class Settings {
5959

6060
private boolean hideProjectsOnMouseExit;
6161

62+
private double windowXProportion;
63+
64+
private double windowYProportion;
65+
66+
private int windowScreenhash;
67+
68+
private boolean saveWindowPosition;
69+
6270
private boolean remindIfNotesAreEmpty;
6371

72+
public Settings() {
73+
}
74+
75+
public Settings(final Color hoverBackgroundColor, final Color hoverFontColor, final Color defaultBackgroundColor,
76+
final Color defaultFontColor, final Color taskBarColor, final boolean useHotkey,
77+
final boolean displayProjectsRight, final boolean hideProjectsOnMouseExit, final double windowPositionX,
78+
final double windowPositionY, final int screenHash, final boolean saveWindowPosition,
79+
final boolean remindIfNotesAreEmpty) {
80+
this.hoverBackgroundColor = hoverBackgroundColor;
81+
this.hoverFontColor = hoverFontColor;
82+
this.defaultBackgroundColor = defaultBackgroundColor;
83+
this.defaultFontColor = defaultFontColor;
84+
this.taskBarColor = taskBarColor;
85+
this.useHotkey = useHotkey;
86+
this.displayProjectsRight = displayProjectsRight;
87+
this.hideProjectsOnMouseExit = hideProjectsOnMouseExit;
88+
this.windowXProportion = windowPositionX;
89+
this.windowYProportion = windowPositionY;
90+
this.windowScreenhash = screenHash;
91+
this.saveWindowPosition = saveWindowPosition;
92+
this.remindIfNotesAreEmpty = remindIfNotesAreEmpty;
93+
94+
}
95+
6496
public long getId() {
6597
return id;
6698
}
@@ -129,6 +161,38 @@ public void setHideProjectsOnMouseExit(final boolean hideProjectsOnMouseExit) {
129161
this.hideProjectsOnMouseExit = hideProjectsOnMouseExit;
130162
}
131163

164+
public double getWindowXProportion() {
165+
return windowXProportion;
166+
}
167+
168+
public void setWindowXProportion(final double windowPositionX) {
169+
this.windowXProportion = windowPositionX;
170+
}
171+
172+
public double getWindowYProportion() {
173+
return windowYProportion;
174+
}
175+
176+
public void setWindowYProportion(final double windowPositionY) {
177+
this.windowYProportion = windowPositionY;
178+
}
179+
180+
public int getScreenHash() {
181+
return windowScreenhash;
182+
}
183+
184+
public void setScreenHash(final int screenHash) {
185+
this.windowScreenhash = screenHash;
186+
}
187+
188+
public boolean isSaveWindowPosition() {
189+
return saveWindowPosition;
190+
}
191+
192+
public void setSaveWindowPosition(final boolean saveWindowPosition) {
193+
this.saveWindowPosition = saveWindowPosition;
194+
}
195+
132196
public boolean isRemindIfNotesAreEmpty() {
133197
return remindIfNotesAreEmpty;
134198
}

0 commit comments

Comments
 (0)