Feel the passage of time even in Java

I feel the passage of time in Emacs-Qiita I felt that the idea was very good, but unfortunately I was not an Emacs user, so when I couldn't feel the flow of.

In order to feel the passage of time, I created a desktop accessory-like application in Java, which I'm used to. I have received permission from the original author, @zk_phi, to publish it, so I would like to introduce it here.

littlesky.gif

Installation

You can get the pre-built jar file (littlesky.jar) from here.

The source code is posted on GitHub> https://github.com/opengl-8080/little-sky

Operating environment

OS I have confirmed that it works on Windows 10.

I don't have a Mac environment at hand, so I'm not sure if it will work on a Mac. I'm not going to write OS-dependent processing, but if it doesn't work on Mac, please pull request.

JRE It should work with Java 8u40 and above (because you are using Dialog) .. I saw that it works with Java 9 as well.

Start-up

$ java -jar littlesky.jar

The configuration file is saved in the current directory at runtime (littlesky.xml).

The second and subsequent startups refer to the configuration file in the current directory. If you want to change the startup location, move the configuration file as well.

Setup at first startup

At the first startup, a dialog for entering location information is displayed.

littlesky.jpg

Enter the latitude (Latitude) and longitude (Longitude). (Used to obtain sunrise / sunset times and weather information)

Screen description

littlesky.jpg

As with the original, the sky icon (sky icon), time, and temperature information are displayed. The sky icon displays the moon phases when it is sunny, and the weather icons when it is raining or snowing.

The background color changes according to the time.

Context menu

littlesky.jpg

Right-click on the time to display the context menu. You can close the application and change the settings.

Use the weather function

As with the original, you can use the weather function using OpenWeatherMap.

Get the API key for OpenWeatherMap

Get your API key in OpenWeatherMap.

The API key can be obtained by registering an account on OpenWeatherMap. If you search for "OpenWeatherMap API key", you will find a lot of information.

Set API key

Select Options from the context menu to display a form for entering the API key.

littlesky.jpg

Enter the API key you obtained in [API Key] and click the [Save] button to save the settings.

Enable the weather function

littlesky.jpg

After entering the API key, you will be able to select Weather service> Start from the context menu. Select this to activate the weather function.

Once every 15 minutes, the weather, cloud volume, and temperature information will be acquired based on the set location information and reflected in the display.

Once you set the API key, the weather function will start automatically the next time you start the application.

If you want to stop the weather function, select Weather service> Stop from the context menu or empty the API key and save.

Proxy settings

In an environment where a proxy is required to access the Internet, proxy settings are required.

Open the settings dialog with Options in the context menu.

littlesky.jpg

In HTTP Proxy, enter the proxy settings.

If no port is entered, 80 is used by default. Enter the username and password only if the proxy requires authentication.

Display settings

You can change the display settings from [View] in the context menu.

littlesky.jpg

Always bring to the front

If you check [Always on top], the window will always be in the foreground of the desktop. The default is off.

Show / hide seconds

By switching the check of [Show seconds], you can show / hide the seconds of the time. The default is on.

Show / hide temperature

By switching the [Show temperature] check, you can show / hide the temperature. The default is on.

Show / hide the sky icon

By switching the check of [Show sky status icon], you can switch the display / non-display of the sky icon (moon phase / weather icon). The default is on.

What I learned

I was pointed out by Bukome, and I thought it was a little bad, so I'm sorry to add it later, but I would like to summarize what I learned while making this application.

Create a window without a window frame

package sample.javafx;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javafx.stage.StageStyle;

public class NoFrameWindow extends Application {

    public static void main(String[] args) {
        launch(NoFrameWindow.class, args);
    }
    
    @Override
    public void start(Stage primaryStage) throws Exception {
        Pane pane = new Pane();
        pane.setPrefWidth(200);
        pane.setPrefHeight(100);
        pane.setStyle("-fx-background-radius: 50; -fx-background-color: yellow;");

        Scene scene = new Scene(pane);
        scene.setFill(Color.TRANSPARENT);
        
        primaryStage.initStyle(StageStyle.TRANSPARENT);
        primaryStage.setScene(scene);
        primaryStage.show();
    }
}

Execution result

javafx.jpg

Description

--To create a window without a window frame, first specify StageStyle.TRANSPARENT in style of Stage. --This will make the window frame of the window disappear --However, with this alone, the background of Scene is not transparent. --If Scene is not transparent, even if you round the corners of the node, it will remain square in appearance. --Set Color.TRANSPARENT withsetFill ()to make Scene transparent --Then, if you round the corners of the node registered in Scene, you can create a window without a window frame of any shape as shown in ↑. --The rounded corners of the background can be specified with -fx-background-radius in CSS. --If you set it in the implementation, it will probably look like ↓

Set the background programmatically


import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.CornerRadii;
import javafx.scene.paint.Color;

...

    BackgroundFill backgroundFill = new BackgroundFill(Color.YELLOW, new CornerRadii(50), null);
    Background background = new Background(backgroundFill);
    pane.setBackground(background);

Allow windows to be dragged

If you remove the window frame, you cannot drag the window as it is. This implementation requires you to do your best on your own.

There are some similar questions when looking for Stackoverflow, and the implementation method is also introduced, so I will use it as it is.

Moving an undecorated stage in javafx 2 - Stack Overflow

package sample.javafx;

...

public class NoFrameWindow extends Application {

    ...
    
    private double mouseX;
    private double mouseY;
    
    @Override
    public void start(Stage primaryStage) throws Exception {
        Pane pane = new Pane();
        ...
        
        pane.setOnMousePressed(e -> {
            this.mouseX = primaryStage.getX() - e.getScreenX();
            this.mouseY = primaryStage.getY() - e.getScreenY();
        });
        pane.setOnMouseDragged(e -> {
            primaryStage.setX(e.getScreenX() + this.mouseX);
            primaryStage.setY(e.getScreenY() + this.mouseY);
        });

        ...
    }
}

Execution result

javafx.gif

Color interpolation between two colors

As for the background color of the sky, the background color is decided only for some key times (referring to the original implementation method). The color between a certain key time A and key time B interpolates the color between the two colors A and B.

Color interpolation between two colors is directly applied to the Color class, which is a JavaFX color class, as it is [interpolate (Color, double)](https://docs.oracle.com/javase/jp/8/javafx/ There is a method called api / javafx / scene / paint / Color.html # interpolate-javafx.scene.paint.Color-double-), and I used it.

ʻInterpolate ()calculates the color at the position of the ratio (value from0.0 to 1.0) specified by the second argument from the color that becomes the receiver to the color specified by the first argument. return. For example, color1.interporate (color2, 0.5)will calculate and return a value that is exactly betweencolor1 and color2 (0.5`).

The ratio is from the time set for each key and the current time [Duration] of Date & Time API (https://docs.oracle.com/javase/jp/8/docs/api/java/time/Duration.html) It was calculated using. The specific implementation is in the SkyColorGradation class.

Implementation utilizing JavaFX properties

Recently [I studied JavaFX hard](https://qiita.com/opengl-8080/items/51bef25aa05ecd929a3d#javafx-%E3%83%97%E3%83%AD%E3%83%91%E3%83% 86% E3% 82% A3) In this implementation, I tried to actively use the JavaFX properties I learned at that time.

Taking advantage of the fact that the observer pattern can be implemented simply by using JavaFX properties, the overall configuration looks like ↓.

javafx.png

For example, the time display is as follows.

model

ClockBase.java


package littlesky.model.clock;

import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.ReadOnlyObjectWrapper;

import java.time.LocalDate;
import java.time.LocalTime;

public class ClockBase implements Clock {

    protected final ReadOnlyObjectWrapper<LocalDate> date = new ReadOnlyObjectWrapper<>();
    protected final ReadOnlyObjectWrapper<LocalTime> time = new ReadOnlyObjectWrapper<>();

    @Override
    public LocalDate getDate() {
        return this.date.get();
    }

    @Override
    public LocalTime getTime() {
        return this.time.get();
    }

    @Override
    public ReadOnlyObjectProperty<LocalDate> dateProperty() {
        return this.date.getReadOnlyProperty();
    }

    @Override
    public ReadOnlyObjectProperty<LocalTime> timeProperty() {
        return this.time.getReadOnlyProperty();
    }
}

ReadTimeClock.java


package littlesky.model.clock;

import javafx.application.Platform;

import java.time.LocalDateTime;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class RealTimeClock extends ClockBase {
    
    public RealTimeClock() {
        this.updateDateTime();
    }
    
    public void start() {
        ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor((runnable) -> {
            Thread thread = new Thread(runnable);
            thread.setDaemon(true);
            return thread;
        });
        
        executor.scheduleAtFixedRate(() -> {
            Platform.runLater(this::updateDateTime);
        }, 0, 500, TimeUnit.MILLISECONDS);
    }
    
    private void updateDateTime() {
        LocalDateTime now = LocalDateTime.now();
        if (this.getDate() == null || !this.getDate().equals(now.toLocalDate())) {
            this.date.set(now.toLocalDate());
        }
        this.time.set(now.toLocalTime());
    }
}

This class counts the current time. It spawns a thread separate from the UI thread and counts the current time every 500 milliseconds.

For the current time, the date (date) and time (time) are disclosed to the outside using ReadOnlyObjectProperty, respectively.

ClockBase.java


    protected final ReadOnlyObjectWrapper<LocalDate> date = new ReadOnlyObjectWrapper<>();
    protected final ReadOnlyObjectWrapper<LocalTime> time = new ReadOnlyObjectWrapper<>();

    ...

    @Override
    public ReadOnlyObjectProperty<LocalDate> dateProperty() {
        return this.date.getReadOnlyProperty();
    }

    @Override
    public ReadOnlyObjectProperty<LocalTime> timeProperty() {
        return this.time.getReadOnlyProperty();
    }

ReadOnlyObjectWrapper is a class that can be used to expose read-only properties to the outside world. You can use this to prevent the value from being rewritten from outside the model.

One thing to note is that the value is updated in a thread separate from the UI thread, so you must always use Platform.runLater () to update the property value from the UI thread. .. If you update the value using runLater () in the model, the view side can use the property with confidence.

view

TimeLabelViewModel.java


package littlesky.view.main;

import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.beans.property.ReadOnlyStringProperty;
import javafx.beans.property.ReadOnlyStringWrapper;
...
import littlesky.model.clock.Clock;
import littlesky.model.option.ViewOptions;
...

import java.time.LocalTime;
import java.time.format.DateTimeFormatter;

import static littlesky.util.BindingBuilder.*;

public class TimeLabelViewModel {
    private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm:ss");
    private static final DateTimeFormatter formatterWithoutSeconds = DateTimeFormatter.ofPattern("HH:mm");
    private final ReadOnlyStringWrapper text = new ReadOnlyStringWrapper("00:00:00");
    ...
    
    public void bind(Clock clock, SkyColor skyColor, ViewOptions viewOptions) {
        this.text.bind(
            binding(clock.timeProperty(), viewOptions.showSecondsProperty())
            .computeValue(() -> this.formatClockTime(clock, viewOptions))
        );
        ...
    }
    
    private String formatClockTime(Clock clock, ViewOptions viewOptions) {
        LocalTime time = clock.getTime();
        return time.format(viewOptions.isShowSeconds() ? formatter : formatterWithoutSeconds);
    }
    
    ...
    
    public ReadOnlyStringProperty textProperty() {
        return this.text.getReadOnlyProperty();
    }

    ...
}

TimeLabelViewModel is a class that controls the time display.

In the bind () method, we define the value of the text (text property) to be displayed on the time label depending on Clock and ViewOptions.

    public void bind(Clock clock, SkyColor skyColor, ViewOptions viewOptions) {
        this.text.bind(
            binding(clock.timeProperty(), viewOptions.showSecondsProperty())
            .computeValue(() -> this.formatClockTime(clock, viewOptions))
        );
        ...
    }

--binding (...). ComputeValue (...) is a builder that allows you to write Binding instances with less description, and all you are doing is creating an anonymous class for ʻObjectBinding. is. --Implementation is [here](https://github.com/opengl-8080/little-sky/blob/master/src/main/java/littlesky/util/BindingBuilder.java) --Here it is defined that the time text (text property) depends on the timeproperty ofClock and the showSecondsproperty ofViewOptions. --And if either the value of time or showSecondschanges, theformatClockTime ()method is executed and the value of thetext` property is updated.

    private String formatClockTime(Clock clock, ViewOptions viewOptions) {
        LocalTime time = clock.getTime();
        return time.format(viewOptions.isShowSeconds() ? formatter : formatterWithoutSeconds);
    }

--formatClockTime () refers to the updated time, showSeconds to generate the time text to display on the screen. --Finally, the text property is exposed to the outside world as a read-only property, just like the model.

    private final ReadOnlyStringWrapper text = new ReadOnlyStringWrapper("00:00:00");

    ...

    public ReadOnlyStringProperty textProperty() {
        return this.text.getReadOnlyProperty();
    }

--The exposed text property is associated with the item on the screen in the controller.

controller

MainController.java


package littlesky.controller.main;

...

public class MainController implements Initializable {
    ...

    @FXML
    private Label timeLabel;

    ...

    @Override
    public void initialize(URL url, ResourceBundle resources) {
        ...
        this.replaceClockAndWeather(this.realTimeClock, this.openWeatherMap);
    }
    
    ...
    
    private void replaceClockAndWeather(Clock newClock, Weather weather) {
        ...

        TimeLabelViewModel timeLabelViewModel = new TimeLabelViewModel();
        timeLabelViewModel.bind(newClock, skyColor, this.options.getViewOptions());
        this.timeLabel.textProperty().bind(timeLabelViewModel.textProperty());

        ...
    }
    
    ...
}

--MainController is injected with timeLabel, which displays the time on the screen. --The text property of this timeLabel and the text property published by the previous TimeLabelViewModel are linked by bind ().

  1. The time is updated with RealTimeClock
  2. TimeLabelViewModel detects it and updates the displayed text, taking into account the display settings at that time.
  3. The text property of timeLabel, which was linked with the text property of TimeLavelViewModel, is updated and the screen display changes. -The model update is reflected in the view, such as

This is not the only correct answer, as it is a method that we arrived at by trial and error while implementing it. However, I feel that this method of using the JavaFX property monitoring mechanism as a way to propagate model changes to the view is not wrong in the direction.

** What I thought was good **

――I feel that the roles of each class are clearly separated. --Model updates main logic and values --View decides what to display based on model values --Control is a bridge between view and model ――I feel that the cooperation of these classes is kept simple by the JavaFX property monitoring mechanism. --The amount of code in the controller class has been reduced. --In JavaFX, inject a node object on the screen into the controller class --Therefore, it is easy to write the processing about the view in the controller class. ――However, as the number of items increased, the implementation of views increased, and I had the experience that the controller class quickly became bloated. ――Thanks to creating a view class this time and letting the controller bridge it, we were able to remove the processing about the view from the controller.

** Points I would like to improve **

--The model depends on the JavaFX API --Looking at the model layer from the DDD domain model idea, it seems that JavaFX's dependence on the API is a bit strange. --Separation from the infrastructure layer ――This time, all the roles of the infrastructure layer are treated as logic and plunged into the "model", but I feel that it is better to separate the ideals. ――But I wanted something that moves quickly, so I put it together in a "model" for the time being. ――I would like to think if it can be divided a little more neatly --Controller class mess ――Even though I was able to cut out the view processing, I feel that the processing such as initialization of the view class is messed up. ――I feel like I want to make it a little more refreshing (I have no idea)

Calculation of sunrise and sunset times

Regarding the calculation of sunrise and sunset times, I searched for a library in Java and found the following two.

The former calculates the times of sunrise and sunset, and the latter also calculates the position of the sun, the position of the moon, and the phase of the moon.

The former seems to have been unmaintained for several years, and the latter library seems to be better considering this use.

However, when I actually used it, the sunrise and sunset times were both calculated with reasonable accuracy (a few minutes of error), but the calculation of the moon phase by the latter library was considerably different from the actual one. It was.

So, in the end, I decided to use the former library to calculate the sunrise and sunset.

SunriseSunsetTime


package littlesky.model.sun;

import com.luckycatlabs.sunrisesunset.SunriseSunsetCalculator;
import com.luckycatlabs.sunrisesunset.dto.Location;
import littlesky.model.location.UserLocation;

import java.time.LocalDate;
import java.time.LocalTime;
import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;

public class SunriseSunsetTime {
    private final LocalDate localDate;
    private final SunriseSunsetCalculator calculator;
    private final TimeZone timeZone = TimeZone.getDefault();
    
    public SunriseSunsetTime(UserLocation userLocation, LocalDate localDate) {
        this.localDate = localDate;
        Location location = new Location(userLocation.getLatitude(), userLocation.getLongitude());
        this.calculator = new SunriseSunsetCalculator(location, this.timeZone);
    }
    
    public LocalTime sunriseTime() {
        Calendar today = this.toCalendar(this.localDate);
        Calendar sunriseTime = this.calculator.getOfficialSunriseCalendarForDate(today);

        return this.toLocalTime(sunriseTime);
    }
    
    public LocalTime sunsetTime() {
        Calendar today = this.toCalendar(this.localDate);
        Calendar sunsetTime = this.calculator.getOfficialSunsetCalendarForDate(today);

        return this.toLocalTime(sunsetTime);
    }
    
    private Calendar toCalendar(LocalDate localDate) {
        Date date = Date.from(localDate.atStartOfDay(this.timeZone.toZoneId()).toInstant());
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        return calendar;
    }
    
    private LocalTime toLocalTime(Calendar calendar) {
        return calendar.getTime().toInstant().atZone(this.timeZone.toZoneId()).toLocalTime();
    }
}

The date object that the library wants is of type Calendar, which may not have been maintained for several years. Since it is Java 8 here, it was necessary to convert it to and use it with the Date & Time API.

Lunar phase calculation

Then, what happened to the calculation of the moon phase? I couldn't find the library, so I decided to calculate it myself.

However, it is not possible to calculate the orbit, so

-Easy way to find the age of the moon (abbreviation of the age of the moon) (No.0250) -Age --Wikipedia -Metonic cycle-Wikipedia

With reference to this area, I wrote a program to find the age of the moon with reasonable accuracy.

Specifically, starting from the age of January 1, 1999 (13.17), the number of days until the current date is calculated, and the age of the moon progressing during that period is calculated in consideration of the Metonic cycle.

The implementation is in MoonPhase.

Recommended Posts

Feel the passage of time even in Java
Get the result of POST in Java
The story of writing Java in Emacs
The story of low-level string comparison in Java
[Java] Handling of JavaBeans in the method chain
The story of making ordinary Othello in Java
About the idea of anonymous classes in Java
The story of learning Java in the first programming
Measure the size of a folder in Java
[Socket communication (Java)] Impressions of implementing Socket communication in practice for the first time
The date time of java8 has been updated
Import files of the same hierarchy in Java
Get the URL of the HTTP redirect destination in Java
[Java] Get the file in the jar regardless of the environment
Change the storage quality of JPEG images in Java
Summarize the additional elements of the Optional class in Java 9
Implementation of gzip in java
Implementation of tri-tree in Java
Was done in the base year of the Java calendar week
Set the time zone in the JVM of your Azure application
Count the number of digits after the decimal point in Java
How to derive the last day of the month in Java
Access the network interface in Java
[Java] Delete the elements of List
Guess the character code in Java
[Java version] The story of serialization
Unzip the zip file in Java
Handling of time zones using Java
Time stamps in nanoseconds even in Swift!
List of members added in Java 9
Parsing the COTOHA API in Java
List of types added in Java 9
Order of processing in the program
The origin of Java lambda expressions
Implementation of like function in Java
Call the super method in Java
Examine the system information of AWS Lambda operating environment in Java
[Java] Get the dates of the past Monday and Sunday in order
Output the difference between each field of two objects in Java
Find out the list of fonts available in AWS Lambda + Java
The milliseconds to set in /lib/calendars.properties of Java jre is UTC
Examine the list of timezone IDs available in the Java ZoneId class
Get the public URL of a private Flickr file in Java
Let's create a TODO application in Java 5 Switch the display of TODO
How to get the length of an audio file in java
How to increment the value of Map in one line in Java
[Java] I participated in ABC-188 of Atcorder.
Implementation of DBlayer in Java (RDB, MySQL)
Dynamically increase the number of elements in a Java 2D array (multidimensional array)
Check the contents of the Java certificate store
For the time being, run the war file delivered in Java with Docker
Java reference to understand in the figure
Examine the memory usage of Java elements
Introduction to java for the first time # 2
The story of forgetting to close a file in Java and failing
[Java] Get the day of the specific day of the week
Try using the Stream API in Java
Call the Windows Notification API in Java
The identity of params [: id] in rails
Memo: [Java] Check the contents of the directory
I tried the new era in Java