├── .gitignore ├── .idea ├── .gitignore ├── artifacts │ └── MyTomato_jar.xml ├── compiler.xml ├── description.html ├── encodings.xml ├── gradle.xml ├── inspectionProfiles │ └── Project_Default.xml ├── libraries │ ├── bridj_0_7_0.xml │ ├── jl1_0_1.xml │ └── log4j_1_2_17.xml ├── misc.xml ├── modules.xml ├── uiDesigner.xml └── vcs.xml ├── MyTomato.iml ├── README.assets └── 1.jpg ├── README.md ├── lib ├── bridj-0.7.0.jar ├── fastjson-1.2.61.jar ├── jl1.0.1.jar └── log4j-1.2.17.jar ├── prefsdemo.xml ├── res ├── image │ └── tomato.png ├── json │ └── tomatoTaskData.json ├── properties │ └── log4j.properties └── sound │ ├── add_tasks_ahead_of_time.mp3 │ ├── bgm_Ticking.mp3 │ ├── bgm_WindThroughTrees.mp3 │ ├── old_add_tasks_ahead_of_time.mp3 │ ├── respite_finished.mp3 │ └── work_finished.mp3 └── src ├── META-INF └── MANIFEST.MF ├── app ├── Main.java ├── control │ ├── GirdColumn.java │ ├── GirdColumnFactory.java │ ├── GridPane.java │ ├── OnTopAlert.java │ ├── StackedPanes.java │ ├── TextWrapCell.java │ └── mytomato │ │ ├── GridPane.java │ │ ├── StackedPanes.java │ │ └── TitledPane.java ├── model │ └── TomatoTask.java ├── util │ ├── BriefReport.java │ ├── CountDown.java │ ├── DataManager.java │ ├── DateAndTime.java │ ├── GL.java │ ├── ListJson.java │ ├── MapJson.java │ ├── Mp3Player.java │ ├── PropertiesManager.java │ ├── ResGetter.java │ ├── TaskBarProgressbar.java │ └── TimeStringPolisher.java └── view │ ├── Close_512x512.png │ ├── Controller.java │ ├── DarkTheme.css │ ├── EditDialog.fxml │ ├── EditDialogControl.java │ ├── Edit_512x512.png │ ├── FinishDialog.fxml │ ├── FinishDialogController.java │ ├── MainLayout.fxml │ ├── MainLayoutController.java │ ├── PlusDialog.fxml │ ├── PlusDialogController.java │ ├── RootLayout.fxml │ ├── RootLayoutController.java │ ├── SettingDialog.fxml │ ├── SettingDialogController.java │ ├── StackedPanesController.java │ ├── Test.fxml │ ├── TestController.java │ ├── Trash_67981_512x512.png │ ├── modena.css │ └── stylesheet.css ├── sample ├── Parent.java ├── Sample.java └── test.fxml └── test ├── Demo.java ├── JavaStringFormat.java ├── LocalTimeTest.java ├── OldPlay.java ├── PathGetMain.java └── newPlay.java /.gitignore: -------------------------------------------------------------------------------- 1 | /out 2 | /.idea/workspace.xml 3 | /srcText/app/view/FinishDialog.fxml 4 | /res/properties/Settings.properties 5 | /res/log4j.properties 6 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Editor-based HTTP Client requests 5 | /httpRequests/ 6 | # Datasource local storage ignored files 7 | /dataSources/ 8 | /dataSources.local.xml 9 | -------------------------------------------------------------------------------- /.idea/artifacts/MyTomato_jar.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | $PROJECT_DIR$/../MyTomatoAPP 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /.idea/description.html: -------------------------------------------------------------------------------- 1 | Simple JavaFX 2.0 application that includes simple .fxml file with attached controller and Main class to quick start. Artifact to build JavaFX application is provided. 2 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /.idea/libraries/bridj_0_7_0.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/libraries/jl1_0_1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/libraries/log4j_1_2_17.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/uiDesigner.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /MyTomato.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /README.assets/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Andy-AO/MyTomato/04a157c8a4ce00f899a4e6ff263072942e6e9e7c/README.assets/1.jpg -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [MyTomato简介](https://github.com/Andy-AO/MyTomato) 2 | 3 | 用JavaFX写的跨平台个人番茄工作法工具,[开源免费](https://github.com/Andy-AO/MyTomato),轻量而纯粹,没有任何多余的功能。 4 | 5 | 已经作为主力番茄工作法工具使用接近1年,现有的方案基本上都是Electron,会占用大量资源,而且很多都收费。 6 | 7 | 看到 V2EX 上的那个帖子,所以准备贡献出来,给和我一样不太喜欢 Electron 的人用。 8 | 9 | 本来是自用的,能用就行,所以很长时间没改进了,后期可能会继续改进。 10 | 11 | [我的软件“wnr”上架了 Product Hunt! - V2EX](https://www.v2ex.com/t/698913#reply22) 12 | 13 | 14 | 15 | 16 | 17 | # 下载 18 | 19 | [Releases · Andy-AO/MyTomato](https://github.com/Andy-AO/MyTomato/releases) 20 | 21 | 解压后运行`MyTomato.jar`,需要Java8及以上环境。 22 | 23 | 理论上跨平台,但并未实际测试。 -------------------------------------------------------------------------------- /lib/bridj-0.7.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Andy-AO/MyTomato/04a157c8a4ce00f899a4e6ff263072942e6e9e7c/lib/bridj-0.7.0.jar -------------------------------------------------------------------------------- /lib/fastjson-1.2.61.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Andy-AO/MyTomato/04a157c8a4ce00f899a4e6ff263072942e6e9e7c/lib/fastjson-1.2.61.jar -------------------------------------------------------------------------------- /lib/jl1.0.1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Andy-AO/MyTomato/04a157c8a4ce00f899a4e6ff263072942e6e9e7c/lib/jl1.0.1.jar -------------------------------------------------------------------------------- /lib/log4j-1.2.17.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Andy-AO/MyTomato/04a157c8a4ce00f899a4e6ff263072942e6e9e7c/lib/log4j-1.2.17.jar -------------------------------------------------------------------------------- /prefsdemo.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /res/image/tomato.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Andy-AO/MyTomato/04a157c8a4ce00f899a4e6ff263072942e6e9e7c/res/image/tomato.png -------------------------------------------------------------------------------- /res/properties/log4j.properties: -------------------------------------------------------------------------------- 1 | ### 设置### 2 | log4j.rootLogger = debug,stdout,R 3 | 4 | ### 输出信息到控制台 ### 5 | log4j.appender.stdout = org.apache.log4j.ConsoleAppender 6 | log4j.appender.stdout.Target = System.out 7 | log4j.appender.stdout.layout = org.apache.log4j.PatternLayout 8 | log4j.appender.stdout.layout.ConversionPattern =%-5p [%t] %l%n%m%n%n 9 | 10 | log4j.appender.R=org.apache.log4j.RollingFileAppender 11 | log4j.appender.R.File=MyTomato.log 12 | log4j.appender.R.MaxFileSize=1024KB 13 | log4j.appender.R.MaxBackupIndex=5 14 | log4j.appender.R.layout=org.apache.log4j.PatternLayout 15 | log4j.appender.R.layout.ConversionPattern=%d{yyy-MM-dd HH:mm:ss} - %-5p [%t] %l%n%m%n%n -------------------------------------------------------------------------------- /res/sound/add_tasks_ahead_of_time.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Andy-AO/MyTomato/04a157c8a4ce00f899a4e6ff263072942e6e9e7c/res/sound/add_tasks_ahead_of_time.mp3 -------------------------------------------------------------------------------- /res/sound/bgm_Ticking.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Andy-AO/MyTomato/04a157c8a4ce00f899a4e6ff263072942e6e9e7c/res/sound/bgm_Ticking.mp3 -------------------------------------------------------------------------------- /res/sound/bgm_WindThroughTrees.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Andy-AO/MyTomato/04a157c8a4ce00f899a4e6ff263072942e6e9e7c/res/sound/bgm_WindThroughTrees.mp3 -------------------------------------------------------------------------------- /res/sound/old_add_tasks_ahead_of_time.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Andy-AO/MyTomato/04a157c8a4ce00f899a4e6ff263072942e6e9e7c/res/sound/old_add_tasks_ahead_of_time.mp3 -------------------------------------------------------------------------------- /res/sound/respite_finished.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Andy-AO/MyTomato/04a157c8a4ce00f899a4e6ff263072942e6e9e7c/res/sound/respite_finished.mp3 -------------------------------------------------------------------------------- /res/sound/work_finished.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Andy-AO/MyTomato/04a157c8a4ce00f899a4e6ff263072942e6e9e7c/res/sound/work_finished.mp3 -------------------------------------------------------------------------------- /src/META-INF/MANIFEST.MF: -------------------------------------------------------------------------------- 1 | Manifest-Version: 1.0 2 | Main-Class: app.Main 3 | 4 | -------------------------------------------------------------------------------- /src/app/Main.java: -------------------------------------------------------------------------------- 1 | package app; 2 | 3 | import app.control.OnTopAlert; 4 | import app.control.mytomato.StackedPanes; 5 | import app.util.*; 6 | import app.view.*; 7 | import javafx.application.Application; 8 | import javafx.application.Platform; 9 | import javafx.beans.value.ChangeListener; 10 | import javafx.beans.value.ObservableValue; 11 | import javafx.collections.FXCollections; 12 | import javafx.collections.ListChangeListener; 13 | import javafx.collections.ObservableList; 14 | import javafx.collections.ObservableMap; 15 | import javafx.event.EventHandler; 16 | import javafx.fxml.FXMLLoader; 17 | import javafx.scene.Scene; 18 | import javafx.scene.control.Alert; 19 | import javafx.scene.control.ButtonType; 20 | import javafx.scene.control.TabPane; 21 | import javafx.scene.image.Image; 22 | import javafx.scene.layout.AnchorPane; 23 | import javafx.scene.layout.BorderPane; 24 | import javafx.stage.Stage; 25 | import app.model.TomatoTask; 26 | import javafx.stage.WindowEvent; 27 | 28 | import java.io.File; 29 | import java.io.IOException; 30 | import java.net.URL; 31 | import java.time.Duration; 32 | import java.time.LocalDate; 33 | import java.util.HashMap; 34 | import java.util.List; 35 | 36 | public class Main extends Application { 37 | 38 | public static final int PRIMARY_STAGE_MIN_WIDTH = 415; 39 | private static final ObservableMap> TOMATO_TASKS_MAP = FXCollections.observableMap(new HashMap()); 40 | public static final Duration DEFAULT_WORK_DURATION = Duration.ofMinutes(25); 41 | public static final CountDown WORK_COUNT_DOWN = new CountDown(DEFAULT_WORK_DURATION); 42 | public static final Duration DEFAULT_RESPITE_DURATION = Duration.ofMinutes(5); 43 | public static final CountDown RESPITE_COUNT_DOWN = new CountDown(DEFAULT_RESPITE_DURATION); 44 | public static final Duration DEVELOPMENT_DURATION = Duration.ofSeconds(3); 45 | public static final Mp3Player WORK_DURATION_MP3_PLAYER = new Mp3Player(new File(ResGetter.getResFile(), "sound/bgm_Ticking.mp3")); 46 | public static final Mp3Player RESPITE_DURATION_MP3_PLAYER = new Mp3Player(new File(ResGetter.getResFile(), "sound/bgm_WindThroughTrees.mp3")); 47 | 48 | public static final Mp3Player WORK_FINISHED_MP3_PLAYER = new Mp3Player(new File(ResGetter.getResFile(), "sound/work_finished.mp3")); 49 | public static final Mp3Player RESPITE_FINISHED_MP3_PLAYER = new Mp3Player(new File(ResGetter.getResFile(), "sound/respite_finished.mp3")); 50 | 51 | public static final PropertiesManager PROPERTIES_MANAGER = PropertiesManager.getPropertiesManager(); 52 | 53 | private BorderPane rootLayout; 54 | private AnchorPane mainLayout; 55 | 56 | private Stage primaryStage; 57 | private Stage finishDialogStage = null; 58 | private Stage plusDialogStage = null; 59 | private Stage startSettingStage = null; 60 | 61 | private Image tomatoImage = new Image(ResGetter.getResURLString() + "image/tomato.png"); 62 | private AnchorPane editDialog; 63 | private EditDialogControl editDialogController; 64 | private Stage editDialogStage = null; 65 | private AnchorPane plusDialog; 66 | private PlusDialogController plusDialogController; 67 | private StackedPanesController stackedPanesController; 68 | private DataManager tomatoTaskDataMapJson; 69 | 70 | private AnchorPane finishDialog; 71 | private StackedPanes stackedPanes; 72 | private FinishDialogController finishDialogController; 73 | 74 | private MainLayoutController mainLayoutController; 75 | private RootLayoutController rootLayoutController; 76 | private TabPane settingDialog; 77 | private SettingDialogController settingDialogController; 78 | 79 | private ObservableList REDO_TOMATO_TASKS = FXCollections.observableArrayList(); 80 | 81 | private final File JSON_FILE = new File(ResGetter.getResFile(), "json\\tomatoTaskData.json"); 82 | 83 | public TabPane getSettingDialog() { 84 | return settingDialog; 85 | } 86 | 87 | public void setSettingDialog(TabPane settingDialog) { 88 | this.settingDialog = settingDialog; 89 | } 90 | 91 | public SettingDialogController getSettingDialogController() { 92 | return settingDialogController; 93 | } 94 | 95 | public void setSettingDialogController(SettingDialogController settingDialogController) { 96 | this.settingDialogController = settingDialogController; 97 | } 98 | 99 | 100 | public FinishDialogController getFinishDialogController() { 101 | return finishDialogController; 102 | } 103 | 104 | public void setFinishDialogController(FinishDialogController finishDialogController) { 105 | this.finishDialogController = finishDialogController; 106 | } 107 | 108 | public Stage getPrimaryStage() { 109 | return primaryStage; 110 | } 111 | 112 | public void setPrimaryStage(Stage primaryStage) { 113 | this.primaryStage = primaryStage; 114 | } 115 | 116 | public BorderPane getRootLayout() { 117 | return rootLayout; 118 | } 119 | 120 | public void setRootLayout(BorderPane rootLayout) { 121 | this.rootLayout = rootLayout; 122 | } 123 | 124 | public AnchorPane getMainLayout() { 125 | return mainLayout; 126 | } 127 | 128 | public void setMainLayout(AnchorPane mainLayout) { 129 | this.mainLayout = mainLayout; 130 | } 131 | 132 | 133 | public AnchorPane getFinishDialog() { 134 | return finishDialog; 135 | } 136 | 137 | public void setFinishDialog(AnchorPane finishDialog) { 138 | this.finishDialog = finishDialog; 139 | } 140 | 141 | public static ObservableMap> getTomatoTasksMap() { 142 | return TOMATO_TASKS_MAP; 143 | } 144 | 145 | public DataManager getTomatoTaskDataMapJson() { 146 | return tomatoTaskDataMapJson; 147 | } 148 | 149 | public MainLayoutController getMainLayoutController() { 150 | return mainLayoutController; 151 | } 152 | 153 | public void setMainLayoutController(MainLayoutController mainLayoutController) { 154 | this.mainLayoutController = mainLayoutController; 155 | } 156 | 157 | public RootLayoutController getRootLayoutController() { 158 | return rootLayoutController; 159 | } 160 | 161 | public void setRootLayoutController(RootLayoutController rootLayoutController) { 162 | this.rootLayoutController = rootLayoutController; 163 | } 164 | 165 | 166 | @Override 167 | public void start(Stage primaryStage) throws Exception { 168 | this.primaryStage = primaryStage; 169 | loadLayout(); 170 | intiTomatoTaskData(); 171 | setMainAndInitLayout(); 172 | initPrimaryStage(); 173 | this.primaryStage.show(); 174 | } 175 | 176 | private void setMainAndInitLayout() { 177 | mainLayoutController.setMainAndInit(this); 178 | finishDialogController.setMainAndInit(this); 179 | plusDialogController.setMainAndInit(this); 180 | rootLayoutController.setMainAndInit(this); 181 | settingDialogController.setMainAndInit(this); 182 | editDialogController.setMainAndInit(this); 183 | stackedPanesController.setMainAndInit(this); 184 | 185 | } 186 | 187 | private void loadLayout() throws IOException { 188 | 189 | 190 | loadRootLayout(); 191 | loadMainLayout(); 192 | loadFinishDialog(); 193 | loadPlusDialog(); 194 | loadSettingDialog(); 195 | loadEditDialog(); 196 | loadStackedPanes(); 197 | } 198 | 199 | 200 | public Main(TabPane settingDialog) { 201 | this.settingDialog = settingDialog; 202 | } 203 | 204 | private void loadSettingDialog() { 205 | try { 206 | FXMLLoader loader = new FXMLLoader(); 207 | URL name = getClass().getResource("view/SettingDialog.fxml"); 208 | loader.setLocation(name); 209 | settingDialog = loader.load(); 210 | settingDialogController = loader.getController(); 211 | } catch (IOException e) { 212 | GL.logger.debug("load SettingDialog fail.",e); 213 | } 214 | } 215 | 216 | private void loadEditDialog() { 217 | try { 218 | FXMLLoader loader = new FXMLLoader(); 219 | URL name = getClass().getResource("view/EditDialog.fxml"); 220 | loader.setLocation(name); 221 | editDialog = loader.load(); 222 | editDialogController = loader.getController(); 223 | } catch (IOException e) { 224 | GL.logger.warn(getClass().getSimpleName(),e); 225 | } 226 | } 227 | 228 | private void initPrimaryStage() { 229 | primaryStage.setTitle("MyTomato"); 230 | Scene primaryStageScene = new Scene(getRootLayout()); 231 | primaryStageScene.getStylesheets().add(getClass().getResource("view/stylesheet.css").toExternalForm()); 232 | primaryStage.setScene(primaryStageScene); 233 | primaryStage.setMinWidth(PRIMARY_STAGE_MIN_WIDTH); 234 | primaryStage.getIcons().add(tomatoImage); 235 | setPrimaryStageListener(); 236 | } 237 | 238 | private void setPrimaryStageListener() { 239 | primaryStage.focusedProperty().addListener(new ChangeListener() { 240 | @Override 241 | public void changed(ObservableValue isFocused, Boolean onHidden, Boolean onShown) { 242 | if (isFocused.getValue()) { 243 | mainLayoutController.initHeadText(); 244 | } 245 | } 246 | }); 247 | 248 | primaryStage.setOnCloseRequest(evt -> { 249 | // prevent window from closing 250 | evt.consume(); 251 | // execute own shutdown procedure 252 | boolean isClosed = onStageShutdown(primaryStage); 253 | if (isClosed) 254 | System.exit(0); 255 | }); 256 | } 257 | 258 | private void loadFinishDialog() { 259 | try { 260 | FXMLLoader loader = new FXMLLoader(); 261 | URL name = getClass().getResource("view/FinishDialog.fxml"); 262 | loader.setLocation(name); 263 | finishDialog = loader.load(); 264 | finishDialogController = loader.getController(); 265 | } catch (IOException e) { 266 | GL.logger.warn(getClass().getSimpleName(),e); 267 | } 268 | } 269 | 270 | public StackedPanesController getStackedPanesController() { 271 | return stackedPanesController; 272 | } 273 | 274 | public void setStackedPanesController(StackedPanesController stackedPanesController) { 275 | this.stackedPanesController = stackedPanesController; 276 | } 277 | 278 | public StackedPanes getStackedPanes() { 279 | return stackedPanes; 280 | } 281 | 282 | private void loadStackedPanes() { 283 | stackedPanesController = new StackedPanesController(); 284 | stackedPanes = stackedPanesController.createScrollPane(); 285 | } 286 | 287 | public Stage getPlusDialogStage() { 288 | return plusDialogStage; 289 | } 290 | 291 | public void setPlusDialogStage(Stage plusDialogStage) { 292 | this.plusDialogStage = plusDialogStage; 293 | } 294 | 295 | public AnchorPane getPlusDialog() { 296 | return plusDialog; 297 | } 298 | 299 | public void setPlusDialog(AnchorPane plusDialog) { 300 | this.plusDialog = plusDialog; 301 | } 302 | 303 | public PlusDialogController getPlusDialogController() { 304 | return plusDialogController; 305 | } 306 | 307 | public void setPlusDialogController(PlusDialogController plusDialogController) { 308 | this.plusDialogController = plusDialogController; 309 | } 310 | 311 | private void loadPlusDialog() { 312 | try { 313 | FXMLLoader loader = new FXMLLoader(); 314 | URL name = getClass().getResource("view/PlusDialog.fxml"); 315 | loader.setLocation(name); 316 | plusDialog = loader.load(); 317 | plusDialogController = loader.getController(); 318 | } catch (IOException e) { 319 | GL.logger.warn(getClass().getSimpleName(),e); 320 | } 321 | } 322 | 323 | public Stage getFinishDialogStage() { 324 | return finishDialogStage; 325 | } 326 | 327 | public void setFinishDialogStage(Stage finishDialogStage) { 328 | this.finishDialogStage = finishDialogStage; 329 | } 330 | 331 | public AnchorPane getEditDialog() { 332 | return editDialog; 333 | } 334 | 335 | public void setEditDialog(AnchorPane editDialog) { 336 | this.editDialog = editDialog; 337 | } 338 | 339 | public EditDialogControl getEditDialogController() { 340 | return editDialogController; 341 | } 342 | 343 | public void setEditDialogController(EditDialogControl editDialogController) { 344 | this.editDialogController = editDialogController; 345 | } 346 | 347 | public Stage getEditDialogStage() { 348 | return editDialogStage; 349 | } 350 | 351 | public void setEditDialogStage(Stage editDialogStage) { 352 | this.editDialogStage = editDialogStage; 353 | } 354 | 355 | public void startEditDialogAndWait(String Title) { 356 | if (editDialogStage == null) { 357 | editDialogStage = new Stage(); 358 | editDialogStage.initOwner(primaryStage); 359 | editDialogStage.setTitle(Title); 360 | editDialogStage.getIcons().add(tomatoImage); 361 | editDialogStage.setScene(new Scene(editDialog)); 362 | editDialogStage.setResizable(false); 363 | editDialogStage.setOnCloseRequest(evt -> { 364 | Platform.runLater(() -> { 365 | getMainLayoutController().getAddButton().setDisable(false); 366 | }); 367 | }); 368 | } 369 | Platform.runLater(() -> { 370 | getMainLayoutController().getAddButton().setDisable(true); 371 | }); 372 | 373 | editDialogStage.showAndWait(); 374 | } 375 | 376 | public void startFinishDialogAndWait() { 377 | if (finishDialogStage == null) { 378 | finishDialogStage = new Stage(); 379 | finishDialogStage.setAlwaysOnTop(true); 380 | finishDialogStage.initOwner(primaryStage); 381 | finishDialogStage.setTitle("请输入刚刚完成的任务"); 382 | finishDialogStage.getIcons().add(tomatoImage); 383 | finishDialogStage.setScene(new Scene(finishDialog)); 384 | finishDialogStage.setResizable(false); 385 | 386 | finishDialogStage.setOnHiding(new EventHandler() { 387 | @Override 388 | public void handle(WindowEvent event) { 389 | Platform.runLater(new Runnable() { 390 | @Override 391 | public void run() { 392 | getFinishDialogController().getTextField().setText(""); 393 | getPlusDialogController().getTextField().setText(""); 394 | 395 | } 396 | }); 397 | } 398 | }); 399 | 400 | finishDialogStage.setOnCloseRequest(evt -> { 401 | evt.consume(); 402 | onStageShutdown(finishDialogStage); 403 | }); 404 | } 405 | finishDialogStage.showAndWait(); 406 | } 407 | 408 | public void startPlusDialogAndWait() { 409 | if (plusDialogStage == null) { 410 | plusDialogStage = new Stage(); 411 | plusDialogStage.setAlwaysOnTop(true); 412 | plusDialogStage.initOwner(primaryStage); 413 | plusDialogStage.setTitle("请输入要提前添加的任务:"); 414 | plusDialogStage.getIcons().add(tomatoImage); 415 | plusDialogStage.setScene(new Scene(plusDialog)); 416 | plusDialogStage.setResizable(false); 417 | } 418 | plusDialogStage.showAndWait(); 419 | } 420 | 421 | public void startSettingDialogAndWait() { 422 | if (startSettingStage == null) { 423 | startSettingStage = new Stage(); 424 | startSettingStage.setAlwaysOnTop(true); 425 | startSettingStage.setTitle("SettingDialog"); 426 | startSettingStage.getIcons().add(tomatoImage); 427 | startSettingStage.setScene(new Scene(settingDialog)); 428 | startSettingStage.setResizable(false); 429 | } 430 | startSettingStage.showAndWait(); 431 | } 432 | 433 | private boolean onStageShutdown(Stage mainWindow) { 434 | // you could also use your logout window / whatever here instead 435 | Alert alert = new OnTopAlert(Alert.AlertType.NONE, "Really close the Windows?", ButtonType.YES, ButtonType.NO); 436 | alert.initOwner(mainWindow); 437 | if (alert.showAndWait().orElse(ButtonType.NO) == ButtonType.YES) { 438 | // you may need to close other windows or replace this with Platform.exit(); 439 | mainWindow.close(); 440 | return true; 441 | } else 442 | return false; 443 | } 444 | 445 | 446 | public Main() { 447 | 448 | } 449 | 450 | 451 | private void intiTomatoTaskData() { 452 | tomatoTaskDataMapJson = new MapJson(TOMATO_TASKS_MAP, JSON_FILE); 453 | tomatoTaskDataMapJson.read(); 454 | 455 | setTomatoTaskDataListener(); 456 | setRedoTomatoTaskDataListener(); 457 | } 458 | 459 | private void setRedoTomatoTaskDataListener() { 460 | REDO_TOMATO_TASKS.addListener((ListChangeListener.Change change) -> { 461 | if (change.next()) { 462 | if (REDO_TOMATO_TASKS.isEmpty()) { 463 | mainLayoutController.closeRedoBar(); 464 | } else { 465 | mainLayoutController.showRedoBarAndSleep(); 466 | } 467 | } 468 | 469 | }); 470 | } 471 | 472 | public ObservableList getREDO_TOMATO_TASKS() { 473 | return REDO_TOMATO_TASKS; 474 | } 475 | 476 | public void setREDO_TOMATO_TASKS(ObservableList REDO_TOMATO_TASKS) { 477 | this.REDO_TOMATO_TASKS = REDO_TOMATO_TASKS; 478 | } 479 | 480 | private void setTomatoTaskDataListener() { 481 | getStackedPanes().titledPaneItemsChangeProperty().addListener((observable, oldChange, newChange) -> { 482 | 483 | if (newChange != null) { 484 | if (newChange.next()) { 485 | List removedItems = newChange.getRemoved(); 486 | List addedSubList = newChange.getAddedSubList(); 487 | if (!removedItems.isEmpty()) { 488 | REDO_TOMATO_TASKS.clear(); 489 | REDO_TOMATO_TASKS.addAll(removedItems); 490 | } 491 | 492 | newChange.reset(); 493 | } 494 | } 495 | 496 | }); 497 | 498 | } 499 | 500 | 501 | private void loadMainLayout() throws IOException { 502 | FXMLLoader loader = new FXMLLoader(); 503 | URL name = getClass().getResource("view/MainLayout.fxml"); 504 | loader.setLocation(name); 505 | mainLayout = loader.load(); 506 | mainLayoutController = loader.getController(); 507 | } 508 | 509 | private void loadRootLayout() throws IOException { 510 | FXMLLoader loader = new FXMLLoader(); 511 | loader.setLocation(Main.class.getResource("view/RootLayout.fxml")); 512 | rootLayout = loader.load(); 513 | rootLayoutController = loader.getController(); 514 | } 515 | 516 | public static void main(String[] args) { 517 | setDefaultUncaughtExceptionHandler(); 518 | launch(args); 519 | } 520 | 521 | private static void setDefaultUncaughtExceptionHandler() { 522 | GL.logger.info("setDefaultUncaughtExceptionHandler!"); 523 | Thread.setDefaultUncaughtExceptionHandler((Thread t, Throwable e) -> { 524 | GL.logger.warn("UncaughtException",e); 525 | BriefReport.formatErrorAlert(e); 526 | }); 527 | } 528 | 529 | 530 | } 531 | -------------------------------------------------------------------------------- /src/app/control/GirdColumn.java: -------------------------------------------------------------------------------- 1 | package app.control; 2 | 3 | import javafx.scene.Node; 4 | 5 | public class GirdColumn { 6 | private final String name; 7 | private GirdColumnFactory nodeFactory; 8 | 9 | public GirdColumnFactory getNodeFactory() { 10 | return nodeFactory; 11 | } 12 | 13 | public void setNodeFactory(GirdColumnFactory nodeFactory) { 14 | this.nodeFactory = nodeFactory; 15 | } 16 | 17 | public GirdColumn(String name) { 18 | this.name = name; 19 | } 20 | } 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/app/control/GirdColumnFactory.java: -------------------------------------------------------------------------------- 1 | package app.control; 2 | 3 | import javafx.scene.Node; 4 | 5 | @FunctionalInterface 6 | public interface GirdColumnFactory{ 7 | Node generateNode(DataType data); 8 | } 9 | -------------------------------------------------------------------------------- /src/app/control/GridPane.java: -------------------------------------------------------------------------------- 1 | package app.control; 2 | 3 | import javafx.collections.FXCollections; 4 | import javafx.collections.ObservableList; 5 | import javafx.scene.Node; 6 | 7 | public class GridPane extends javafx.scene.layout.GridPane { 8 | protected ObservableList items; 9 | 10 | private ObservableList> columns = FXCollections.observableArrayList(); 11 | 12 | public void setItems(ObservableList items) { 13 | this.items = items; 14 | generateColumns(); 15 | } 16 | 17 | protected void generateColumns() { 18 | clearGrid(); 19 | int columnIndex = 0; 20 | for (GirdColumn girdColumn : columns) { 21 | int itemIndex = 0; 22 | for (DataType item : items) { 23 | Node node = girdColumn.getNodeFactory().generateNode(item); 24 | this.add(node,columnIndex,itemIndex); 25 | itemIndex++; 26 | } 27 | columnIndex++; 28 | } 29 | } 30 | 31 | private void clearGrid() { 32 | this.getChildren().clear(); 33 | } 34 | 35 | public GridPane() { 36 | } 37 | 38 | public ObservableList> getColumns() { 39 | return columns; 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/app/control/OnTopAlert.java: -------------------------------------------------------------------------------- 1 | package app.control; 2 | 3 | import javafx.scene.control.Alert; 4 | import javafx.scene.control.ButtonType; 5 | import javafx.stage.Stage; 6 | 7 | public class OnTopAlert extends Alert { 8 | public OnTopAlert(AlertType alertType) { 9 | super(alertType); 10 | setAlertAlwaysOnTop(this); 11 | } 12 | 13 | public OnTopAlert(AlertType alertType, String contentText, ButtonType... buttons) { 14 | super(alertType, contentText, buttons); 15 | setAlertAlwaysOnTop(this); 16 | } 17 | 18 | public static Stage getAlertStage(Alert alert) { 19 | return (Stage) alert.getDialogPane().getScene().getWindow(); 20 | } 21 | 22 | public static void setAlertAlwaysOnTop(Alert alert) { 23 | getAlertStage(alert).setAlwaysOnTop(true); 24 | getAlertStage(alert).toFront(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/app/control/StackedPanes.java: -------------------------------------------------------------------------------- 1 | package app.control; 2 | 3 | import app.util.GL; 4 | import javafx.beans.value.ChangeListener; 5 | import javafx.beans.value.ObservableValue; 6 | import javafx.geometry.Bounds; 7 | import javafx.scene.layout.VBox; 8 | 9 | public abstract class StackedPanes extends javafx.scene.control.ScrollPane{ 10 | 11 | //--------------------------------------- Field 12 | protected VBox vBox = new VBox(); 13 | //--------------------------------------- GS 14 | 15 | public StackedPanes() { 16 | this.setContent(vBox); 17 | this.viewportBoundsProperty().addListener((ov, oldBounds, bounds) -> vBox.setPrefWidth(bounds.getWidth())); 18 | } 19 | 20 | //--------------------------------------- Method 21 | 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/app/control/TextWrapCell.java: -------------------------------------------------------------------------------- 1 | package app.control; 2 | 3 | import javafx.application.Platform; 4 | import javafx.scene.control.TableCell; 5 | import javafx.scene.text.Text; 6 | 7 | public class TextWrapCell extends TableCell { 8 | public int instanceCounter = 0; 9 | public TextWrapCell() { 10 | instanceCounter++; 11 | } 12 | 13 | private Text textControl = null; 14 | public static final int CELL_TEXT_PAD = 20; 15 | 16 | private void wrap() { 17 | textControl.setWrappingWidth(getTableColumn().getWidth() - CELL_TEXT_PAD); 18 | } 19 | 20 | @Override 21 | public void updateItem(String item, boolean empty) { 22 | super.updateItem(item, empty); 23 | if (!isEmpty()) { 24 | if (textControl == null) { 25 | this.setWrapText(true); 26 | textControl = new Text(item); 27 | setGraphic(textControl); 28 | wrap(); 29 | getTableColumn().widthProperty().addListener((observable, oldValue, newValue) -> wrap()); 30 | } else { 31 | textControl.setText(item); 32 | } 33 | } 34 | 35 | 36 | } 37 | } -------------------------------------------------------------------------------- /src/app/control/mytomato/GridPane.java: -------------------------------------------------------------------------------- 1 | package app.control.mytomato; 2 | 3 | import app.Main; 4 | import app.control.GirdColumn; 5 | import app.model.TomatoTask; 6 | import javafx.application.Platform; 7 | import javafx.collections.ListChangeListener; 8 | import javafx.collections.ObservableList; 9 | import javafx.scene.Parent; 10 | import javafx.scene.control.Button; 11 | import javafx.scene.image.Image; 12 | import javafx.scene.image.ImageView; 13 | import javafx.scene.text.Text; 14 | 15 | 16 | public class GridPane extends app.control.GridPane { 17 | 18 | public static final int OTHER_CONSTRAINT_WIDTH = 170; 19 | private GirdColumn nameColumn; 20 | private GirdColumn startColumn; 21 | private GirdColumn endColumn; 22 | private GirdColumn deleteColumn; 23 | private GirdColumn editColumn; 24 | 25 | public void setItems(ObservableList items) { 26 | 27 | startColumn = new GirdColumn<>("start"); 28 | startColumn.setNodeFactory(data -> new Text(data.getStartTimeString())); 29 | this.getColumns().add(startColumn); 30 | 31 | endColumn = new GirdColumn<>("end"); 32 | endColumn.setNodeFactory(data -> new Text(data.getEndTimeString())); 33 | this.getColumns().add(endColumn); 34 | 35 | nameColumn = new GirdColumn<>("name"); 36 | nameColumn.setNodeFactory(data -> { 37 | Text text = new Text(data.getName()); 38 | text.setWrappingWidth(450); 39 | // System.out.println("getWidth():" + this.getWidth()); 40 | // 可以发现出现很多的 getWidth():0.0,这个意味着该getWidth()的功能已经失效?不管怎么样,设置固定宽度吧,还可能继续用一阵 41 | // text.setWrappingWidth(getWidth() - OTHER_CONSTRAINT_WIDTH); 42 | // widthProperty().addListener((observable, oldValue, newValue) -> Platform.runLater(() -> text.setWrappingWidth((Double) newValue - OTHER_CONSTRAINT_WIDTH))); 43 | 44 | return text; 45 | }); 46 | this.getColumns().add(nameColumn); 47 | 48 | deleteColumn = new GirdColumn<>("delete"); 49 | deleteColumn.setNodeFactory(data -> { 50 | Button deleteButton = new Button(); 51 | deleteButton.getStyleClass().add("delete-button"); 52 | deleteButton.setOnAction(event -> items.remove(data)); 53 | return deleteButton; 54 | }); 55 | this.getColumns().add(deleteColumn); 56 | 57 | editColumn = new GirdColumn<>("edit"); 58 | editColumn.setNodeFactory(data -> { 59 | Button editButton = new Button(); 60 | editButton.getStyleClass().add("edit-button"); 61 | editButton.setOnAction(event -> { 62 | getStackedPanes().setEditingGridPane(this); 63 | getStackedPanes().getStackedPanesController().main.getEditDialogController().loadSpecifiedTask(data); 64 | getStackedPanes().getStackedPanesController().main.startEditDialogAndWait("修改任务"); 65 | }); 66 | return editButton; 67 | }); 68 | 69 | this.getColumns().add(editColumn); 70 | 71 | super.setItems(items); 72 | 73 | addListenerToItems(); 74 | 75 | } 76 | 77 | private StackedPanes getStackedPanes() { 78 | Parent parent = this.getParent(); 79 | for (int i = 0;i) c -> { 91 | c.next(); 92 | generateColumns(); 93 | }); 94 | } 95 | 96 | public void refresh() { 97 | generateColumns(); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/app/control/mytomato/StackedPanes.java: -------------------------------------------------------------------------------- 1 | package app.control.mytomato; 2 | 3 | import app.model.TomatoTask; 4 | import app.view.StackedPanesController; 5 | import javafx.beans.property.SimpleObjectProperty; 6 | import javafx.collections.*; 7 | import javafx.scene.Node; 8 | import java.time.LocalDate; 9 | import java.util.ArrayList; 10 | import java.util.Collections; 11 | import java.util.Comparator; 12 | import java.util.List; 13 | 14 | public class StackedPanes extends app.control.StackedPanes { 15 | 16 | private GridPane focusedGridPane; 17 | 18 | public StackedPanes() { 19 | super(); 20 | vBox.getStyleClass().add("v-box"); 21 | } 22 | 23 | //--------------------------------------- Field+ 24 | StackedPanesController stackedPanesController; 25 | 26 | public StackedPanesController getStackedPanesController() { 27 | return stackedPanesController; 28 | } 29 | 30 | public void setStackedPanesController(StackedPanesController stackedPanesController) { 31 | this.stackedPanesController = stackedPanesController; 32 | } 33 | 34 | private SimpleObjectProperty> titledPaneItemsChange = new SimpleObjectProperty(); 35 | 36 | protected ObservableMap> itemsMap = null; 37 | protected ObservableList> itemsList = FXCollections.observableArrayList(); 38 | private Comparator comparatorTitledPane = new Comparator() { 39 | @Override 40 | public int compare(Node o1, Node o2) { 41 | TitledPane t1 = (TitledPane) o1; 42 | TitledPane t2 = (TitledPane) o2; 43 | int result = t1.getLOCAL_DATE().compareTo(t2.getLOCAL_DATE()); 44 | return result; 45 | } 46 | }; 47 | 48 | 49 | private Comparator> comparatorTomatoTaskList = (o1, o2) -> { 50 | boolean o1IsEmpty = o1.isEmpty(); 51 | boolean o2IsEmpty = o2.isEmpty(); 52 | if ((o1IsEmpty) && (o2IsEmpty)) { 53 | return 0; 54 | } else if (o1IsEmpty) { 55 | return -1; 56 | } else if (o2IsEmpty) { 57 | return 1; 58 | } 59 | return (o1.get(0).getDate().compareTo(o2.get(0).getDate())); 60 | }; 61 | 62 | //--------------------------------------- GS 63 | 64 | 65 | public ListChangeListener.Change getTitledPaneItemsChange() { 66 | return titledPaneItemsChange.get(); 67 | } 68 | 69 | public SimpleObjectProperty> titledPaneItemsChangeProperty() { 70 | return titledPaneItemsChange; 71 | } 72 | 73 | public void setTitledPaneItemsChange(ListChangeListener.Change titledPaneItemsChange) { 74 | this.titledPaneItemsChange.set(titledPaneItemsChange); 75 | } 76 | 77 | public ObservableMap> getItemsMap() { 78 | return itemsMap; 79 | } 80 | 81 | //--------------------------------------- Method 82 | 83 | public void setItemsMap(ObservableMap> itemsMap) { 84 | this.itemsMap = itemsMap; 85 | convertItemsMapToSortedList(); 86 | showItemsList(); 87 | itemsMap.addListener((MapChangeListener>) change -> { 88 | ObservableList addList = change.getValueAdded(); 89 | if (!addList.isEmpty()) { 90 | addTitledPane(addList); 91 | } 92 | }); 93 | } 94 | 95 | private void addTitledPane(ObservableList addList) { 96 | if (!addList.isEmpty()) { 97 | 98 | TitledPane titledPane = creatTitledPane(addList); 99 | 100 | List list = new ArrayList(vBox.getChildren()); 101 | list.add(titledPane); 102 | list.sort(comparatorTitledPane); 103 | Collections.reverse(list); 104 | vBox.getChildren().clear(); 105 | vBox.getChildren().addAll(list); 106 | 107 | } 108 | } 109 | 110 | 111 | public void removeTitledPane(TitledPane titledPane) { 112 | vBox.getChildren().remove(titledPane); 113 | } 114 | 115 | 116 | private void showItemsList() { 117 | this.itemsList.forEach((list) -> { 118 | if (!list.isEmpty()) { 119 | TitledPane titledPane = creatTitledPane(list); 120 | vBox.getChildren().add(titledPane); 121 | } 122 | }); 123 | } 124 | 125 | private TitledPane creatTitledPane(ObservableList list) { 126 | TitledPane titledPane = new TitledPane(list.get(0).getDate()); 127 | titledPane.setItems(list); 128 | list.addListener((ListChangeListener) change -> { 129 | 130 | if (change.next()) { 131 | List removedItems = change.getRemoved(); 132 | List addedSubList = change.getAddedSubList(); 133 | boolean sortAble = (!addedSubList.isEmpty()) | (!removedItems.isEmpty()); 134 | if(sortAble){ 135 | } 136 | change.reset(); 137 | } 138 | if (list.isEmpty()) { 139 | removeTitledPane(titledPane); 140 | } 141 | 142 | setTitledPaneItemsChange(change); 143 | 144 | }); 145 | 146 | 147 | return titledPane; 148 | } 149 | 150 | 151 | private void convertItemsMapToSortedList() { 152 | this.itemsMap.forEach((localDate, list) -> { 153 | itemsList.add(list); 154 | itemsList.sort(comparatorTomatoTaskList); 155 | }); 156 | Collections.reverse(itemsList); 157 | } 158 | 159 | 160 | public void addItems(TomatoTask... tomatoTasks) { 161 | for (int i = 0; i < tomatoTasks.length; i++) { 162 | addItem(tomatoTasks[i]); 163 | } 164 | } 165 | public void addItems(List items) { 166 | for (int i = 0; i < items.size(); i++) { 167 | addItem(items.get(i)); 168 | } 169 | } 170 | 171 | private void addItem(TomatoTask tomatoTask) { 172 | ObservableList list = itemsMap.get(tomatoTask.getDate()); 173 | if (list == null||list.isEmpty()) { 174 | ObservableList newList = FXCollections.observableArrayList(tomatoTask); 175 | itemsMap.put(tomatoTask.getDate(), newList); 176 | } else { 177 | list.add(tomatoTask); 178 | } 179 | } 180 | 181 | public GridPane getFocusedGridPane() { 182 | return focusedGridPane; 183 | } 184 | 185 | public void setEditingGridPane(GridPane focusedGridPane) { 186 | this.focusedGridPane = focusedGridPane; 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /src/app/control/mytomato/TitledPane.java: -------------------------------------------------------------------------------- 1 | package app.control.mytomato; 2 | 3 | import app.model.TomatoTask; 4 | import javafx.collections.ObservableList; 5 | import javafx.geometry.Insets; 6 | import javafx.scene.layout.AnchorPane; 7 | 8 | import java.time.LocalDate; 9 | 10 | public class TitledPane extends javafx.scene.control.TitledPane { 11 | 12 | //--------------------------------------- Field 13 | 14 | private final LocalDate LOCAL_DATE; 15 | 16 | public static final double TABLE_VIEW_MARGIN = 0; 17 | public static final int TABLE_VIEW_PADDING = 0; 18 | 19 | private AnchorPane anchorPane = new AnchorPane(); 20 | private GridPane gridPane = new GridPane(); 21 | 22 | private ObservableList items = null; 23 | //--------------------------------------- GS 24 | 25 | public void setItems(ObservableList list) { 26 | this.items = list; 27 | setAnchorPane(); 28 | gridPane.setItems(list); 29 | gridPane.getStyleClass().add("grid-pane"); 30 | this.setContent(anchorPane); 31 | } 32 | 33 | public LocalDate getLOCAL_DATE() { 34 | return LOCAL_DATE; 35 | } 36 | 37 | //--------------------------------------- Method 38 | 39 | public TitledPane(LocalDate localDate) { 40 | this.getStyleClass().add("title-pane"); 41 | this.LOCAL_DATE = localDate; 42 | setTitle(); 43 | } 44 | 45 | private void setTitle() { 46 | setText(LOCAL_DATE.toString()); 47 | } 48 | 49 | private void setAnchorPane() { 50 | setGridView(); 51 | anchorPane.getChildren().add(gridPane); 52 | anchorPane.setPadding(new Insets(TABLE_VIEW_PADDING)); 53 | } 54 | 55 | private void setGridView() { 56 | AnchorPane.setBottomAnchor(gridPane, TABLE_VIEW_MARGIN); 57 | AnchorPane.setLeftAnchor(gridPane, TABLE_VIEW_MARGIN); 58 | AnchorPane.setRightAnchor(gridPane, TABLE_VIEW_MARGIN); 59 | AnchorPane.setTopAnchor(gridPane, TABLE_VIEW_MARGIN); 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /src/app/model/TomatoTask.java: -------------------------------------------------------------------------------- 1 | package app.model; 2 | 3 | import app.util.CountDown; 4 | import javafx.beans.property.ObjectProperty; 5 | import javafx.beans.property.SimpleObjectProperty; 6 | import javafx.beans.property.SimpleStringProperty; 7 | import javafx.beans.property.StringProperty; 8 | 9 | import java.time.LocalDate; 10 | import java.time.LocalTime; 11 | 12 | import static app.util.DateAndTime.localDateToString; 13 | import static app.util.DateAndTime.localTimeToString; 14 | 15 | public class TomatoTask { 16 | 17 | 18 | private StringProperty name; 19 | private ObjectProperty startTime = new SimpleObjectProperty<>(); 20 | private ObjectProperty endTime = new SimpleObjectProperty<>(); 21 | private ObjectProperty date = new SimpleObjectProperty<>(); 22 | private StringProperty startTimeString; 23 | private StringProperty endTimeString; 24 | private StringProperty dateString; 25 | 26 | 27 | public LocalDate getDate() { 28 | return date.get(); 29 | } 30 | 31 | public ObjectProperty dateProperty() { 32 | return date; 33 | } 34 | 35 | public void setDate(LocalDate date) { 36 | this.date.set(date); 37 | } 38 | 39 | public String getDateString() { 40 | return dateString.get(); 41 | } 42 | 43 | public StringProperty dateStringProperty() { 44 | return dateString; 45 | } 46 | 47 | public void setDateString(String dateString) { 48 | this.dateString.set(dateString); 49 | } 50 | 51 | public LocalTime getEndTime() { 52 | return endTime.get(); 53 | } 54 | 55 | public ObjectProperty endTimeProperty() { 56 | return endTime; 57 | } 58 | 59 | public void setEndTime(LocalTime endTime) { 60 | this.endTime.set(endTime); 61 | } 62 | 63 | 64 | public TomatoTask(String name, CountDown countDown) { 65 | initTomatoTask(name, countDown.getStartTime(), countDown.getEndTime()); 66 | } 67 | 68 | public TomatoTask(String name, LocalTime startTime, LocalTime endTime) { 69 | initTomatoTask(name, startTime, endTime); 70 | } 71 | public TomatoTask(String name, LocalTime startTime, LocalTime endTime,LocalDate date) { 72 | initTomatoTask(name, startTime, endTime, date); 73 | } 74 | 75 | public void initTomatoTask(String name, LocalTime startTime, LocalTime endTime) { 76 | this.name = new SimpleStringProperty(name); 77 | 78 | addLockTimeListener(); 79 | setEndTime(endTime); 80 | setStartTime(startTime); 81 | setDate(LocalDate.now()); 82 | } 83 | 84 | public void initTomatoTask(String name, LocalTime startTime, LocalTime endTime,LocalDate date) { 85 | this.name = new SimpleStringProperty(name); 86 | 87 | addLockTimeListener(); 88 | setEndTime(endTime); 89 | setStartTime(startTime); 90 | setDate(date); 91 | } 92 | 93 | private void addLockTimeListener() { 94 | startTime.addListener((observable, oldValue, newValue) -> { 95 | LocalTime startTime = newValue; 96 | String startTimeString = localTimeToString(startTime); 97 | this.startTimeString = new SimpleStringProperty(startTimeString); 98 | }); 99 | endTime.addListener((observable, oldValue, newValue) -> { 100 | LocalTime endTime = newValue; 101 | String endTimeString = localTimeToString(endTime); 102 | this.endTimeString = new SimpleStringProperty(endTimeString); 103 | }); 104 | 105 | date.addListener((observable, oldValue, newValue) -> { 106 | LocalDate date = newValue; 107 | String dateString = localDateToString(date); 108 | this.dateString = new SimpleStringProperty(dateString); 109 | }); 110 | } 111 | 112 | @Override 113 | public String toString() { 114 | return "TomatoTask{" + 115 | "name=" + name + 116 | ", startTime=" + startTime + 117 | ", endTime=" + endTime + 118 | ", date=" + date + 119 | '}'; 120 | } 121 | 122 | public TomatoTask(String name, LocalTime endTime) { 123 | LocalTime startTime = LocalTime.now(); 124 | initTomatoTask(name, startTime, endTime); 125 | } 126 | 127 | public String getEndTimeString() { 128 | return endTimeString.get(); 129 | } 130 | 131 | public StringProperty endTimeStringProperty() { 132 | return endTimeString; 133 | } 134 | 135 | public void setEndTimeString(String endTimeString) { 136 | this.endTimeString.set(endTimeString); 137 | } 138 | 139 | public String getName() { 140 | return name.get(); 141 | } 142 | 143 | public StringProperty nameProperty() { 144 | return name; 145 | } 146 | 147 | public void setName(String name) { 148 | this.name.set(name); 149 | } 150 | 151 | public LocalTime getStartTime() { 152 | return startTime.get(); 153 | } 154 | 155 | public ObjectProperty startTimeProperty() { 156 | return startTime; 157 | } 158 | 159 | public void setStartTime(LocalTime startTime) { 160 | this.startTime.set(startTime); 161 | } 162 | 163 | public String getStartTimeString() { 164 | return startTimeString.get(); 165 | } 166 | 167 | public StringProperty startTimeStringProperty() { 168 | return startTimeString; 169 | } 170 | 171 | public void setStartTimeString(String startTimeString) { 172 | this.startTimeString.set(startTimeString); 173 | } 174 | 175 | } 176 | -------------------------------------------------------------------------------- /src/app/util/BriefReport.java: -------------------------------------------------------------------------------- 1 | package app.util; 2 | 3 | import app.control.OnTopAlert; 4 | import javafx.scene.control.Alert; 5 | 6 | import java.util.ArrayList; 7 | 8 | public class BriefReport { 9 | 10 | public static void formatErrorAlert(Throwable ex) { 11 | String contentText = getBriefReport(ex); 12 | Alert alert = new OnTopAlert(Alert.AlertType.WARNING, contentText); 13 | alert.showAndWait(); 14 | } 15 | 16 | private static String getBriefReport(Throwable ex) { 17 | ArrayList causes = getCauses(ex); 18 | String briefReport = ex.getMessage() + "\r\n"; 19 | for (Throwable throwable : causes) { 20 | briefReport += "Caused by: " + throwable.toString() + "\r\n"; 21 | } 22 | return briefReport; 23 | } 24 | 25 | private static ArrayList getCauses(Throwable ex) { 26 | ArrayList list = new ArrayList<>(); 27 | Throwable cause = ex; 28 | while (true) { 29 | cause = cause.getCause(); 30 | if (cause != null) 31 | list.add(cause); 32 | else 33 | break; 34 | } 35 | return list; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/app/util/CountDown.java: -------------------------------------------------------------------------------- 1 | package app.util; 2 | 3 | import javafx.beans.property.*; 4 | 5 | import java.time.Duration; 6 | import java.time.LocalTime; 7 | import java.util.Timer; 8 | import java.util.TimerTask; 9 | import java.util.concurrent.locks.ReentrantLock; 10 | import java.util.concurrent.locks.ReentrantReadWriteLock; 11 | 12 | public class CountDown { 13 | 14 | volatile private Duration duration; 15 | volatile private LocalTime startTime; 16 | volatile private LocalTime endTime; 17 | volatile private LocalTime currentTime; 18 | volatile private Duration sumDuration; 19 | volatile private Duration zeroDuration = Duration.ofSeconds(0); 20 | volatile private Duration currentDuration; 21 | 22 | volatile private SimpleBooleanProperty started = new SimpleBooleanProperty(false); 23 | volatile private SimpleBooleanProperty finished = new SimpleBooleanProperty(false); 24 | 25 | public static final int PERIOD_MSEC = 100; 26 | volatile private StringProperty textProgress = new SimpleStringProperty(""); 27 | volatile private DoubleProperty barProgress = new SimpleDoubleProperty(0); 28 | volatile private static Timer time; 29 | 30 | public static final long TIMER_DELAY = 0; 31 | private ReentrantLock startOrStopLock = new ReentrantLock(); 32 | private ReentrantLock startedLock = new ReentrantLock(); 33 | private ReentrantLock finishedLock = new ReentrantLock(); 34 | 35 | public StringProperty textProgressProperty() { 36 | return textProgress; 37 | } 38 | 39 | public void setTextProgress(String textProgress) { 40 | this.textProgress.set(textProgress); 41 | } 42 | 43 | public DoubleProperty barProgressProperty() { 44 | return barProgress; 45 | } 46 | 47 | public void setBarProgress(double barProgress) { 48 | this.barProgress.set(barProgress); 49 | } 50 | 51 | public CountDown(Duration duration) { 52 | this.duration = duration; 53 | } 54 | 55 | 56 | public SimpleBooleanProperty finishedProperty() { 57 | finishedLock.lock(); 58 | try { 59 | return finished; 60 | } finally { 61 | finishedLock.unlock(); 62 | } 63 | } 64 | 65 | public boolean getFinished() { 66 | finishedLock.lock(); 67 | try { 68 | return finished.get(); 69 | } finally { 70 | finishedLock.unlock(); 71 | } 72 | } 73 | 74 | public void setFinished(boolean finished) { 75 | finishedLock.lock(); 76 | try { 77 | this.finished.set(finished); 78 | } 79 | finally { 80 | finishedLock.unlock(); 81 | } 82 | } 83 | 84 | public SimpleBooleanProperty startedProperty() { 85 | finishedLock.lock(); 86 | try { 87 | return started; 88 | } finally { 89 | finishedLock.unlock(); 90 | } 91 | } 92 | 93 | public void setStarted(boolean started) { 94 | startedLock.lock(); 95 | try { 96 | this.started.set(started); 97 | } finally { 98 | startedLock.unlock(); 99 | } 100 | } 101 | 102 | public void setDuration(Duration duration) { 103 | this.duration = duration; 104 | } 105 | 106 | public LocalTime getEndTime() { 107 | return endTime; 108 | } 109 | 110 | public boolean isStarted() { 111 | startedLock.lock(); 112 | try { 113 | return started.get(); 114 | } finally { 115 | startedLock.unlock(); 116 | } 117 | } 118 | 119 | public void start() { 120 | startOrStopLock.lock(); 121 | try { 122 | if (isStarted()) { 123 | throw new RuntimeException("Already started. Cannot be started again."); 124 | } 125 | setStarted(true); 126 | initProgressData(); 127 | startTimer(); 128 | setFinished(false); 129 | } finally { 130 | startOrStopLock.unlock(); 131 | } 132 | } 133 | 134 | private void startTimer() { 135 | time = new Timer(); 136 | time.schedule(new TimerTask() { 137 | @Override 138 | public void run() { 139 | updateProgressText(); 140 | updateProgressBar(); 141 | checkProgress(); 142 | } 143 | }, TIMER_DELAY, PERIOD_MSEC); 144 | } 145 | 146 | public void cancel() { 147 | stop(false); 148 | } 149 | 150 | 151 | public void finish() { 152 | stop(true); 153 | } 154 | 155 | public void stop(boolean finished) { 156 | startOrStopLock.lock(); 157 | try { 158 | if (!isStarted()) { 159 | throw new RuntimeException("Already stopped. Cannot be stopped again."); 160 | } 161 | setStarted(false); 162 | progressReturnToZero(); 163 | time.cancel(); 164 | setFinished(finished); 165 | } finally { 166 | startOrStopLock.unlock(); 167 | } 168 | } 169 | 170 | private void checkProgress() { 171 | if (currentDuration.compareTo(zeroDuration) >= 0) { 172 | finish(); 173 | } 174 | } 175 | 176 | public static Timer getTime() { 177 | return time; 178 | } 179 | 180 | public static void setTime(Timer time) { 181 | CountDown.time = time; 182 | } 183 | 184 | private void initProgressData() { 185 | currentTime = LocalTime.now(); 186 | startTime = LocalTime.from(currentTime); 187 | endTime = LocalTime.from(currentTime).plus(duration); 188 | sumDuration = Duration.between(startTime, endTime); 189 | currentDuration = Duration.between(startTime, currentTime); 190 | } 191 | 192 | public static String formatDuration(Duration duration, Boolean alwaysPositive) { 193 | long seconds = duration.getSeconds(); 194 | long absSeconds = Math.abs(seconds); 195 | String positive = String.format( 196 | "%d:%02d:%02d", 197 | absSeconds / 3600, 198 | (absSeconds % 3600) / 60, 199 | absSeconds % 60); 200 | return (seconds < 0) & (!(alwaysPositive)) ? "-" + positive : positive; 201 | } 202 | 203 | public LocalTime getStartTime() { 204 | return startTime; 205 | } 206 | 207 | 208 | private void progressReturnToZero() { 209 | setTextProgress(""); 210 | setBarProgress(0); 211 | } 212 | 213 | private void updateProgressText() { 214 | currentTime = LocalTime.now(); 215 | currentDuration = Duration.between(endTime, currentTime); 216 | setTextProgress(CountDown.formatDuration(currentDuration, true)); 217 | } 218 | 219 | 220 | private void updateProgressBar() { 221 | long currentSec = Math.abs(currentDuration.toMillis()); 222 | long sumSec = Math.abs(sumDuration.toMillis()); 223 | double progress = (double) currentSec / (double) sumSec; 224 | setBarProgress(progress); 225 | } 226 | 227 | 228 | } 229 | -------------------------------------------------------------------------------- /src/app/util/DataManager.java: -------------------------------------------------------------------------------- 1 | package app.util; 2 | 3 | public interface DataManager { 4 | String write(); 5 | void read(); 6 | } 7 | -------------------------------------------------------------------------------- /src/app/util/DateAndTime.java: -------------------------------------------------------------------------------- 1 | package app.util; 2 | 3 | import java.time.LocalDate; 4 | import java.time.LocalTime; 5 | import java.time.format.DateTimeFormatter; 6 | 7 | public class DateAndTime { 8 | static String timeFormatterPattern = "HH:mm"; 9 | static DateTimeFormatter timeFormatter = 10 | DateTimeFormatter.ofPattern(timeFormatterPattern); 11 | static String dateFormatterPattern = "MM/dd"; 12 | static DateTimeFormatter dateFormatter = 13 | DateTimeFormatter.ofPattern(dateFormatterPattern); 14 | 15 | public static String localTimeToString(LocalTime localTime) { 16 | return localTime.format(timeFormatter); 17 | } 18 | 19 | public static String localDateToString(LocalDate localDate) { 20 | return localDate.format(dateFormatter); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/app/util/GL.java: -------------------------------------------------------------------------------- 1 | package app.util; 2 | 3 | import org.apache.log4j.Logger; 4 | import org.apache.log4j.PropertyConfigurator; 5 | 6 | 7 | public class GL { 8 | 9 | static public Logger logger = Logger.getLogger(GL.class); 10 | 11 | static { 12 | String resFileAbsolutePath = ResGetter.getResFile().getAbsolutePath(); 13 | PropertyConfigurator.configure(resFileAbsolutePath + "\\properties\\log4j.properties"); 14 | GL.logger.info("ResFile:" + resFileAbsolutePath); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/app/util/ListJson.java: -------------------------------------------------------------------------------- 1 | package app.util; 2 | 3 | import app.control.OnTopAlert; 4 | import app.model.TomatoTask; 5 | import com.alibaba.fastjson.JSON; 6 | import com.alibaba.fastjson.JSONObject; 7 | import javafx.scene.control.Alert; 8 | 9 | import java.io.*; 10 | import java.time.LocalDate; 11 | import java.time.LocalTime; 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | 15 | public class ListJson implements DataManager { 16 | 17 | private final String EMPTY_MAP_STRING = "[]"; 18 | private final File jsonFile; 19 | private List data; 20 | 21 | public List getData() { 22 | return data; 23 | } 24 | 25 | public ListJson(List data, File jsonFile) { 26 | GL.logger.info("JsonFile:" + jsonFile.getAbsolutePath()); 27 | this.jsonFile = jsonFile; 28 | this.data = data; 29 | } 30 | 31 | public String readString() { 32 | char[] json = new char[(int) jsonFile.length()]; 33 | String jsonString = ""; 34 | try (FileReader fileReader = new FileReader(jsonFile)) { 35 | fileReader.read(json); 36 | jsonString = new String(json); 37 | } catch (FileNotFoundException e) { 38 | System.err.println("Not Found JSON ,set table empty!"); 39 | jsonString = EMPTY_MAP_STRING; 40 | } catch (IOException e) { 41 | GL.logger.warn(getClass().getSimpleName(),e); 42 | } finally { 43 | return jsonString; 44 | } 45 | } 46 | 47 | public String write() { 48 | ArrayList tomatoTaskList = new ArrayList(data); 49 | String json = JSON.toJSONString(tomatoTaskList); 50 | try (FileWriter fileWriter = new FileWriter(jsonFile)) { 51 | fileWriter.write(json); 52 | } catch (IOException e) { 53 | GL.logger.warn(getClass().getSimpleName(),e); 54 | } finally { 55 | return json; 56 | } 57 | } 58 | 59 | public void read() { 60 | this.data.clear(); 61 | String jsonString = readString(); 62 | ArrayList jsonList = null; 63 | try { 64 | jsonList = JSON.parseObject(jsonString, ArrayList.class); 65 | jsonList.forEach(jsonMap -> mapToTask((JSONObject) jsonMap)); 66 | } catch (com.alibaba.fastjson.JSONException e) { 67 | GL.logger.warn(getClass().getSimpleName(),e); 68 | Alert alert = new OnTopAlert(Alert.AlertType.WARNING, "JSON file parse exception."); 69 | alert.showAndWait(); 70 | System.exit(1); 71 | } 72 | } 73 | 74 | private void mapToTask(JSONObject jsonMap) { 75 | String name = (String) jsonMap.get("name"); 76 | LocalTime endTime = LocalTime.parse(((String) jsonMap.get("endTime"))); 77 | LocalTime startTime = LocalTime.parse(((String) jsonMap.get("startTime"))); 78 | LocalDate date = LocalDate.parse(((String) jsonMap.get("date"))); 79 | data.add(new TomatoTask(name, startTime, endTime, date)); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/app/util/MapJson.java: -------------------------------------------------------------------------------- 1 | package app.util; 2 | 3 | import app.model.TomatoTask; 4 | import javafx.collections.FXCollections; 5 | import javafx.collections.ObservableList; 6 | import javafx.collections.ObservableMap; 7 | 8 | import java.io.File; 9 | import java.time.LocalDate; 10 | 11 | public class MapJson implements DataManager { 12 | 13 | private final ObservableList listData = FXCollections.observableArrayList(); 14 | private final ObservableMap> mapData; 15 | 16 | private final File jsonFile; 17 | 18 | private final ListJson listJson; 19 | 20 | public MapJson(ObservableMap> mapData, File jsonFile) { 21 | this.mapData = mapData; 22 | this.jsonFile = jsonFile; 23 | this.listJson = new ListJson(listData, jsonFile); 24 | } 25 | 26 | @Override 27 | public String write() { 28 | convertToList(); 29 | return listJson.write(); 30 | } 31 | 32 | @Override 33 | public void read() { 34 | listJson.read(); 35 | convertToMap(); 36 | } 37 | 38 | private void convertToMap() { 39 | listData.forEach((e) -> { 40 | if (mapData.get(e.getDate()) == null) { 41 | mapData.put(e.getDate(), FXCollections.observableArrayList()); 42 | } 43 | mapData.get(e.getDate()).add(e); 44 | }); 45 | } 46 | 47 | private void convertToList() { 48 | listData.clear(); 49 | mapData.forEach((k, v) -> { 50 | listData.addAll(v); 51 | }); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/app/util/Mp3Player.java: -------------------------------------------------------------------------------- 1 | package app.util; 2 | 3 | import javazoom.jl.decoder.JavaLayerException; 4 | import javazoom.jl.player.Player; 5 | 6 | import java.io.File; 7 | import java.io.FileInputStream; 8 | import java.io.FileNotFoundException; 9 | import java.util.concurrent.atomic.AtomicBoolean; 10 | 11 | public class Mp3Player { 12 | 13 | private File file; 14 | private FileInputStream fileInputStream; 15 | private Player player; 16 | private AtomicBoolean repeated = new AtomicBoolean(false); 17 | 18 | public Mp3Player(File file) { 19 | this.file = file; 20 | try { 21 | this.fileInputStream = new FileInputStream(this.file); 22 | player = new Player(fileInputStream); 23 | } catch (FileNotFoundException e) { 24 | GL.logger.warn(getClass().getSimpleName(),e); 25 | } catch (JavaLayerException e) { 26 | GL.logger.warn(getClass().getSimpleName(),e); 27 | } 28 | } 29 | 30 | private void repeatPlay() { 31 | repeated.set(true); 32 | while (repeated.get()) { 33 | play(); 34 | } 35 | } 36 | 37 | public void close() { 38 | repeated.set(false); 39 | player.close(); 40 | } 41 | 42 | public void playInNewThread() { 43 | new Thread(this::play,"playInNewThread").start(); 44 | } 45 | 46 | private void play() { 47 | try { 48 | this.fileInputStream = new FileInputStream(this.file); 49 | player = new Player(fileInputStream); 50 | player.play(); 51 | } catch (JavaLayerException e) { 52 | GL.logger.warn(getClass().getSimpleName(),e); 53 | } catch (FileNotFoundException e) { 54 | GL.logger.warn(getClass().getSimpleName(),e); 55 | } 56 | } 57 | 58 | public void repeatPlayInNewThread() { 59 | new Thread(this::repeatPlay,"repeatPlayInNewThread").start(); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/app/util/PropertiesManager.java: -------------------------------------------------------------------------------- 1 | package app.util; 2 | 3 | import java.io.*; 4 | import java.util.Properties; 5 | 6 | public class PropertiesManager { 7 | 8 | //--------------------------------------- Field 9 | 10 | private Properties settings = new Properties(); 11 | private static PropertiesManager propertiesManager = null; 12 | private File propertiesFile; 13 | private static final String PROPERTIES_DIR_PATH = "properties"; 14 | 15 | //--------------------------------------- Setter And Getter 16 | 17 | 18 | //--------------------------------------- Constructor 19 | 20 | private PropertiesManager() { 21 | initFile(); 22 | } 23 | 24 | //--------------------------------------- Method 25 | 26 | public static PropertiesManager getPropertiesManager() { 27 | if (propertiesManager == null) { 28 | propertiesManager = new PropertiesManager(); 29 | } 30 | return propertiesManager; 31 | } 32 | 33 | 34 | private void initFile() { 35 | File propertiesDir = new File(ResGetter.getResFile(),PROPERTIES_DIR_PATH); 36 | if (!propertiesDir.exists()) 37 | propertiesDir.mkdir(); 38 | propertiesFile = new File(propertiesDir, "Settings.properties"); 39 | createFileIfNotExist(); 40 | loadSettings(); 41 | } 42 | 43 | private void createFileIfNotExist() { 44 | if (!(propertiesFile.exists())) { 45 | try (OutputStream out = new FileOutputStream(propertiesFile)) { 46 | 47 | } catch (FileNotFoundException e) { 48 | GL.logger.warn(getClass().getSimpleName(),e); 49 | } catch (IOException e) { 50 | GL.logger.warn(getClass().getSimpleName(),e); 51 | } 52 | } 53 | } 54 | 55 | 56 | public String getProperty(String key, String defaultValue) { 57 | loadSettings(); 58 | return settings.getProperty(key,defaultValue); 59 | } 60 | 61 | private void loadSettings() { 62 | try (InputStream in = new FileInputStream(propertiesFile)) { 63 | settings.load(in); 64 | } catch (IOException e) { 65 | GL.logger.warn(getClass().getSimpleName(),e); 66 | } 67 | } 68 | 69 | public void setProperty(String key, String newValue) { 70 | settings.setProperty(key, newValue); 71 | storeSettings(); 72 | } 73 | 74 | private void storeSettings() { 75 | try (OutputStream out = new FileOutputStream(propertiesFile)) { 76 | settings.store(out, "Program Properties"); 77 | } catch (IOException e) { 78 | GL.logger.warn(getClass().getSimpleName(),e); 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/app/util/ResGetter.java: -------------------------------------------------------------------------------- 1 | package app.util; 2 | 3 | import app.Main; 4 | import app.control.OnTopAlert; 5 | import javafx.scene.control.Alert; 6 | 7 | import java.io.File; 8 | 9 | public class ResGetter { 10 | public static String getResURLString() { 11 | return getResURIString(); 12 | } 13 | 14 | public static String getResURIString() { 15 | File file = getResFile(); 16 | String URIString = file.toURI().toString(); 17 | return URIString; 18 | } 19 | 20 | private static String getJarDirPath() { 21 | String path = Main.class.getProtectionDomain().getCodeSource().getLocation().getPath(); 22 | if (System.getProperty("os.name").contains("dows")) { 23 | path = path.substring(1); 24 | } 25 | if (path.contains("jar")) { 26 | path = path.substring(0, path.lastIndexOf(".")); 27 | return path.substring(0, path.lastIndexOf("/")); 28 | } 29 | return path.replace("target/classes/", ""); 30 | } 31 | 32 | public static File getResFile() { 33 | File resFile = new File("res"); 34 | boolean resDirIsInWorkDir = resFile.exists() && resFile.isDirectory(); 35 | if (resDirIsInWorkDir) 36 | return resFile; 37 | else { 38 | String path = getJarDirPath(); 39 | resFile = new File(path, "res"); 40 | boolean resDirIsInJarDir = resFile.exists() && resFile.isDirectory(); 41 | if (resDirIsInJarDir) { 42 | return resFile; 43 | } else { 44 | Alert alert = new OnTopAlert(Alert.AlertType.WARNING, "res files dir is not found !"); 45 | alert.showAndWait(); 46 | System.exit(1); 47 | return resFile; 48 | } 49 | } 50 | } 51 | 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/app/util/TaskBarProgressbar.java: -------------------------------------------------------------------------------- 1 | package app.util; 2 | 3 | import com.sun.javafx.stage.StageHelper; 4 | import java.util.concurrent.ExecutorService; 5 | import java.util.concurrent.Executors; 6 | import javafx.stage.Stage; 7 | import org.bridj.Pointer; 8 | import org.bridj.cpp.com.COMRuntime; 9 | import org.bridj.cpp.com.shell.ITaskbarList3; 10 | 11 | public final class TaskBarProgressbar { 12 | 13 | private final Stage stage; 14 | 15 | private ExecutorService es; 16 | private ITaskbarList3 list; 17 | private Pointer hwnd; 18 | 19 | private TaskBarProgressbar() { 20 | stage = null; 21 | 22 | es = Executors.newSingleThreadExecutor(r -> { 23 | Thread t = new Thread(r); 24 | 25 | t.setDaemon(true); 26 | 27 | return t; 28 | }); 29 | 30 | es.execute(() -> { 31 | try { 32 | list = COMRuntime.newInstance(ITaskbarList3.class); 33 | } catch (ClassNotFoundException e) { 34 | GL.logger.warn(getClass().getSimpleName(),e); 35 | } 36 | }); 37 | } 38 | 39 | public TaskBarProgressbar(Stage stage) { 40 | this.stage = stage; 41 | 42 | es = Executors.newSingleThreadExecutor(r -> { 43 | Thread t = new Thread(r); 44 | 45 | t.setDaemon(true); 46 | 47 | return t; 48 | }); 49 | 50 | es.execute(() -> { 51 | try { 52 | list = COMRuntime.newInstance(ITaskbarList3.class); 53 | } catch (ClassNotFoundException e) { 54 | GL.logger.warn(getClass().getSimpleName(),e); 55 | } 56 | }); 57 | } 58 | 59 | public void close() { 60 | es.submit(() -> list.Release()); 61 | } 62 | 63 | public enum TaskBarProgressbarType { 64 | ERROR { 65 | @Override 66 | ITaskbarList3.TbpFlag getPair() { 67 | return ITaskbarList3.TbpFlag.TBPF_ERROR; 68 | } 69 | }, 70 | INDETERMINATE { 71 | @Override 72 | ITaskbarList3.TbpFlag getPair() { 73 | return ITaskbarList3.TbpFlag.TBPF_INDETERMINATE; 74 | } 75 | }, 76 | NOPROGRESS { 77 | @Override 78 | ITaskbarList3.TbpFlag getPair() { 79 | return ITaskbarList3.TbpFlag.TBPF_NOPROGRESS; 80 | } 81 | }, 82 | NORMAL { 83 | @Override 84 | ITaskbarList3.TbpFlag getPair() { 85 | return ITaskbarList3.TbpFlag.TBPF_NORMAL; 86 | } 87 | }, 88 | PAUSED { 89 | @Override 90 | ITaskbarList3.TbpFlag getPair() { 91 | return ITaskbarList3.TbpFlag.TBPF_PAUSED; 92 | } 93 | }; 94 | 95 | abstract ITaskbarList3.TbpFlag getPair(); 96 | } 97 | 98 | public void stopProgress() { 99 | long hwndVal = com.sun.glass.ui.Window.getWindows().get(StageHelper.getStages().indexOf(stage)).getNativeWindow(); 100 | hwnd = Pointer.pointerToAddress(hwndVal); 101 | 102 | es.execute(() -> { 103 | list.SetProgressState((Pointer) hwnd, TaskBarProgressbarType.NOPROGRESS.getPair()); 104 | }); 105 | } 106 | 107 | public void showIndeterminateProgress() { 108 | long hwndVal = com.sun.glass.ui.Window.getWindows().get(StageHelper.getStages().indexOf(stage)).getNativeWindow(); 109 | hwnd = Pointer.pointerToAddress(hwndVal); 110 | 111 | es.execute(() -> { 112 | list.SetProgressState((Pointer) hwnd, TaskBarProgressbarType.INDETERMINATE.getPair()); 113 | }); 114 | } 115 | 116 | public void showOtherProgress(long startValue, long endValue, TaskBarProgressbarType type) { 117 | long hwndVal = com.sun.glass.ui.Window.getWindows().get(StageHelper.getStages().indexOf(stage)).getNativeWindow(); 118 | hwnd = Pointer.pointerToAddress(hwndVal); 119 | 120 | es.execute(() -> { 121 | list.SetProgressValue((Pointer) hwnd, startValue, endValue); 122 | list.SetProgressState((Pointer) hwnd, type.getPair()); 123 | }); 124 | } 125 | 126 | public void showOtherProgress(double value, TaskBarProgressbarType type) { 127 | int endValue = 100; 128 | value *= 100; 129 | int startValue = (int) value; 130 | showOtherProgress(startValue, endValue, type); 131 | } 132 | 133 | public void showErrorProgress() { 134 | long hwndVal = com.sun.glass.ui.Window.getWindows().get(StageHelper.getStages().indexOf(stage)).getNativeWindow(); 135 | hwnd = Pointer.pointerToAddress(hwndVal); 136 | 137 | es.execute(() -> { 138 | list.SetProgressValue((Pointer) hwnd, 100, 100); 139 | list.SetProgressState((Pointer) hwnd, TaskBarProgressbarType.ERROR.getPair()); 140 | }); 141 | } 142 | 143 | public static void stopProgress(int windowIndex) { 144 | TaskBarProgressbar progressbar = new TaskBarProgressbar(); 145 | 146 | long hwndVal = com.sun.glass.ui.Window.getWindows().get(windowIndex).getNativeWindow(); 147 | progressbar.hwnd = Pointer.pointerToAddress(hwndVal); 148 | 149 | progressbar.es.execute(() -> { 150 | progressbar.list.SetProgressState((Pointer) progressbar.hwnd, TaskBarProgressbarType.NOPROGRESS.getPair()); 151 | }); 152 | } 153 | 154 | public static void showIndeterminateProgress(int windowIndex) { 155 | TaskBarProgressbar progressbar = new TaskBarProgressbar(); 156 | 157 | long hwndVal = com.sun.glass.ui.Window.getWindows().get(windowIndex).getNativeWindow(); 158 | progressbar.hwnd = Pointer.pointerToAddress(hwndVal); 159 | 160 | progressbar.es.execute(() -> { 161 | progressbar.list.SetProgressState((Pointer) progressbar.hwnd, TaskBarProgressbarType.INDETERMINATE.getPair()); 162 | }); 163 | } 164 | 165 | public static void showOtherProgress(int windowIndex, long startValue, long endValue, TaskBarProgressbarType type) { 166 | TaskBarProgressbar progressbar = new TaskBarProgressbar(); 167 | 168 | long hwndVal = com.sun.glass.ui.Window.getWindows().get(windowIndex).getNativeWindow(); 169 | progressbar.hwnd = Pointer.pointerToAddress(hwndVal); 170 | 171 | progressbar.es.execute(() -> { 172 | progressbar.list.SetProgressValue((Pointer) progressbar.hwnd, startValue, endValue); 173 | progressbar.list.SetProgressState((Pointer) progressbar.hwnd, type.getPair()); 174 | }); 175 | } 176 | 177 | public static void showErrorProgress(int windowIndex) { 178 | TaskBarProgressbar progressbar = new TaskBarProgressbar(); 179 | 180 | long hwndVal = com.sun.glass.ui.Window.getWindows().get(windowIndex).getNativeWindow(); 181 | progressbar.hwnd = Pointer.pointerToAddress(hwndVal); 182 | 183 | progressbar.es.execute(() -> { 184 | progressbar.list.SetProgressValue((Pointer) progressbar.hwnd, 100, 100); 185 | progressbar.list.SetProgressState((Pointer) progressbar.hwnd, TaskBarProgressbarType.ERROR.getPair()); 186 | }); 187 | } 188 | 189 | } 190 | -------------------------------------------------------------------------------- /src/app/util/TimeStringPolisher.java: -------------------------------------------------------------------------------- 1 | package app.util; 2 | 3 | import java.util.regex.Matcher; 4 | import java.util.regex.Pattern; 5 | 6 | public class TimeStringPolisher { 7 | 8 | private String srcText; 9 | public static final String CHECK_REGEX = "\\d+:\\d+"; 10 | public static final String SPLIT_REGEX = "\\d+"; 11 | 12 | private String minuteString; 13 | private String hourString; 14 | private int minute; 15 | private int hour; 16 | private String polishedText; 17 | 18 | public TimeStringPolisher(String srcText) { 19 | this.srcText = srcText; 20 | } 21 | 22 | public String polish(){ 23 | preCheck(); 24 | splitString(); 25 | parseToInt(); 26 | numberRangeCheck(); 27 | polishedText = String.format("%02d:%02d", hour, minute); 28 | return polishedText; 29 | } 30 | 31 | private void numberRangeCheck() { 32 | boolean isHourMatched = (hour >= 0) && (hour <= 23) ; 33 | if(!isHourMatched){ 34 | String hourFormat = "Text '%s' could not be parsed:valid values is 0-23."; 35 | throw new RuntimeException(String.format(hourFormat, hourString)); 36 | } 37 | 38 | boolean isMinuteMatched = (minute >= 0) && (minute <= 59); 39 | if(!isMinuteMatched){ 40 | String minuteFormat = "Text '%s' could not be parsed:valid values is 0-59."; 41 | throw new RuntimeException(String.format(minuteFormat, minuteString)); 42 | } 43 | } 44 | 45 | private void parseToInt() { 46 | hour = Integer.valueOf(hourString); 47 | minute = Integer.valueOf(minuteString); 48 | } 49 | 50 | private void splitString() { 51 | Pattern pattern = Pattern.compile(SPLIT_REGEX); 52 | Matcher matcher = pattern.matcher(srcText); 53 | matcher.find(); 54 | hourString = matcher.group(); 55 | matcher.find(); 56 | minuteString = matcher.group(); 57 | } 58 | 59 | private void preCheck(){ 60 | boolean isMatched = Pattern.matches(CHECK_REGEX, srcText); 61 | if (!isMatched) { 62 | String mes = String.format("Text'%s' could not be parsed:valid values is \\d+:\\d+(RegEx).", srcText); 63 | throw new RuntimeException(mes); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/app/view/Close_512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Andy-AO/MyTomato/04a157c8a4ce00f899a4e6ff263072942e6e9e7c/src/app/view/Close_512x512.png -------------------------------------------------------------------------------- /src/app/view/Controller.java: -------------------------------------------------------------------------------- 1 | package app.view; 2 | 3 | import app.Main; 4 | 5 | public abstract class Controller { 6 | public Main main; 7 | public void setMainAndInit(Main main) { 8 | this.main = main; 9 | } 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/app/view/DarkTheme.css: -------------------------------------------------------------------------------- 1 | .background { 2 | -fx-background-color: #1d1d1d; 3 | } 4 | 5 | .label { 6 | -fx-font-size: 11pt; 7 | -fx-font-family: "Segoe UI Semibold"; 8 | -fx-text-fill: white; 9 | -fx-opacity: 0.6; 10 | } 11 | 12 | .label-bright { 13 | -fx-font-size: 11pt; 14 | -fx-font-family: "Segoe UI Semibold"; 15 | -fx-text-fill: white; 16 | -fx-opacity: 1; 17 | } 18 | 19 | .label-header { 20 | -fx-font-size: 32pt; 21 | -fx-font-family: "Segoe UI Light"; 22 | -fx-text-fill: white; 23 | -fx-opacity: 1; 24 | } 25 | 26 | .table-view { 27 | -fx-base: #1d1d1d; 28 | -fx-control-inner-background: #1d1d1d; 29 | -fx-background-color: #1d1d1d; 30 | -fx-table-cell-border-color: transparent; 31 | -fx-table-header-border-color: transparent; 32 | -fx-padding: 5; 33 | } 34 | 35 | .table-view .column-header-background { 36 | -fx-background-color: transparent; 37 | } 38 | 39 | .table-view .column-header, .table-view .filler { 40 | -fx-size: 35; 41 | -fx-border-width: 0 0 1 0; 42 | -fx-background-color: transparent; 43 | -fx-border-color: 44 | transparent 45 | transparent 46 | derive(-fx-base, 80%) 47 | transparent; 48 | -fx-border-insets: 0 10 1 0; 49 | } 50 | 51 | .table-view .column-header .label { 52 | -fx-font-size: 20pt; 53 | -fx-font-family: "Segoe UI Light"; 54 | -fx-text-fill: white; 55 | -fx-alignment: center-left; 56 | -fx-opacity: 1; 57 | } 58 | 59 | .table-view:focused .table-row-cell:filled:focused:selected { 60 | -fx-background-color: -fx-focus-color; 61 | } 62 | 63 | .split-pane:horizontal > .split-pane-divider { 64 | -fx-border-color: transparent #1d1d1d transparent #1d1d1d; 65 | -fx-background-color: transparent, derive(#1d1d1d,20%); 66 | } 67 | 68 | .split-pane { 69 | -fx-padding: 1 0 0 0; 70 | } 71 | 72 | .menu-bar { 73 | -fx-background-color: derive(#1d1d1d,20%); 74 | } 75 | 76 | .context-menu { 77 | -fx-background-color: derive(#1d1d1d,50%); 78 | } 79 | 80 | .menu-bar .label { 81 | -fx-font-size: 14pt; 82 | -fx-font-family: "Segoe UI Light"; 83 | -fx-text-fill: white; 84 | -fx-opacity: 0.9; 85 | } 86 | 87 | .menu .left-container { 88 | -fx-background-color: black; 89 | } 90 | 91 | .text-field { 92 | -fx-font-size: 12pt; 93 | -fx-font-family: "Segoe UI Semibold"; 94 | } 95 | 96 | /* 97 | * Metro style Push Button 98 | * Author: Pedro Duque Vieira 99 | * http://pixelduke.wordpress.com/2012/10/23/jmetro-windows-8-controls-on-java/ 100 | */ 101 | .button { 102 | -fx-padding: 5 22 5 22; 103 | -fx-border-color: #e2e2e2; 104 | -fx-border-width: 2; 105 | -fx-background-radius: 0; 106 | -fx-background-color: #1d1d1d; 107 | -fx-font-family: "Segoe UI", Helvetica, Arial, sans-serif; 108 | -fx-font-size: 11pt; 109 | -fx-text-fill: #d8d8d8; 110 | -fx-background-insets: 0 0 0 0, 0, 1, 2; 111 | } 112 | 113 | .button:hover { 114 | -fx-background-color: #3a3a3a; 115 | } 116 | 117 | .button:pressed, .button:default:hover:pressed { 118 | -fx-background-color: white; 119 | -fx-text-fill: #1d1d1d; 120 | } 121 | 122 | .button:focused { 123 | -fx-border-color: white, white; 124 | -fx-border-width: 1, 1; 125 | -fx-border-style: solid, segments(1, 1); 126 | -fx-border-radius: 0, 0; 127 | -fx-border-insets: 1 1 1 1, 0; 128 | } 129 | 130 | .button:disabled, .button:default:disabled { 131 | -fx-opacity: 0.4; 132 | -fx-background-color: #1d1d1d; 133 | -fx-text-fill: white; 134 | } 135 | 136 | .button:default { 137 | -fx-background-color: -fx-focus-color; 138 | -fx-text-fill: #ffffff; 139 | } 140 | 141 | .button:default:hover { 142 | -fx-background-color: derive(-fx-focus-color,30%); 143 | } -------------------------------------------------------------------------------- /src/app/view/EditDialog.fxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |