From aa6dee90bea053baf51cfb58fb3cf147357cd82d Mon Sep 17 00:00:00 2001 From: lensferno Date: Fri, 26 Mar 2021 17:05:06 +0800 Subject: [PATCH] =?UTF-8?q?=E9=87=8D=E7=BD=AE=E9=A1=B9=E7=9B=AE=E7=BB=93?= =?UTF-8?q?=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 9 + LICENSE | 165 ++++++++ README.md | 68 ++++ dogename.iml | 33 ++ pom.xml | 109 ++++++ res/icon.ico | Bin 0 -> 95518 bytes res/top.png | Bin 0 -> 23121 bytes res/usage.md | 229 +++++++++++ res/you-want.md | 41 ++ src/main/java/log4j2.xml | 34 ++ .../me/lensferno/dogename/DataReleaser.java | 21 ++ src/main/java/me/lensferno/dogename/Main.java | 66 ++++ .../me/lensferno/dogename/choose/Chooser.java | 356 ++++++++++++++++++ .../dogename/configs/ConfigLoader.java | 212 +++++++++++ .../dogename/configs/ConfigValuesBean.java | 4 + .../dogename/configs/MainConfig.java | 242 ++++++++++++ .../dogename/configs/VoiceConfig.java | 75 ++++ .../adapters/BooleanPropertyAdapter.java | 27 ++ .../adapters/DoublePropertyAdapter.java | 27 ++ .../adapters/IntegerPropertyAdapter.java | 26 ++ .../adapters/StringPropertyAdapter.java | 26 ++ .../controllers/GushiciPaneController.java | 39 ++ .../controllers/HistoryPaneController.java | 126 +++++++ .../controllers/HitokotoPaneController.java | 38 ++ .../controllers/MainInterfaceController.java | 337 +++++++++++++++++ .../controllers/MiniPaneController.java | 191 ++++++++++ .../NameManagerPaneController.java | 191 ++++++++++ .../NumberSettingsPaneController.java | 39 ++ .../controllers/OcrPaneController.java | 93 +++++ .../ProgramInfoPaneController.java | 137 +++++++ .../controllers/SettingsPaneController.java | 157 ++++++++ .../VoiceSettingsPaneController.java | 85 +++++ .../WindowListeners/MoveWindowByMouse.java | 33 ++ .../WindowListeners/MoveWindowByTouch.java | 34 ++ .../me/lensferno/dogename/data/History.java | 92 +++++ .../me/lensferno/dogename/data/NameData.java | 351 +++++++++++++++++ .../java/me/lensferno/dogename/ocr/Ocr.java | 101 +++++ .../lensferno/dogename/ocr/ScreenCapture.java | 205 ++++++++++ .../dogename/resources/LibLicense.txt | 27 ++ .../dogename/resources/LicenseText.java | 6 + .../resources/MainInterfaceImage.java | 5 + .../dogename/resources/dogename.java | 5 + .../lensferno/dogename/sayings/Gushici.java | 86 +++++ .../lensferno/dogename/sayings/Hitokoto.java | 163 ++++++++ .../lensferno/dogename/utils/Clipboard.java | 32 ++ .../lensferno/dogename/utils/DialogMaker.java | 113 ++++++ .../dogename/utils/FileProcessor.java | 25 ++ .../dogename/utils/HtmlRequseter.java | 70 ++++ .../me/lensferno/dogename/voice/Token.java | 53 +++ .../dogename/voice/TokenManager.java | 174 +++++++++ .../lensferno/dogename/voice/VoicePlayer.java | 195 ++++++++++ src/main/resources/META-INF/MANIFEST.MF | 3 + .../FXMLs/AdvancedVoiceSettingsPane.fxml | 53 +++ .../lensferno/dogename/FXMLs/GushiciPane.fxml | 25 ++ .../lensferno/dogename/FXMLs/HistoryPane.fxml | 48 +++ .../dogename/FXMLs/HitokotoPane.fxml | 25 ++ .../dogename/FXMLs/MainInterface.fxml | 107 ++++++ .../me/lensferno/dogename/FXMLs/MiniPane.fxml | 30 ++ .../dogename/FXMLs/NameListPane.fxml | 9 + .../dogename/FXMLs/NameManagerPane.fxml | 80 ++++ .../dogename/FXMLs/NumberSettingPane.fxml | 36 ++ .../me/lensferno/dogename/FXMLs/OcrPane.fxml | 84 +++++ .../dogename/FXMLs/ProgramInfoPane.fxml | 58 +++ .../dogename/FXMLs/SettingsPane.fxml | 140 +++++++ .../dogename/FXMLs/VoiceSettingsPane.fxml | 61 +++ 65 files changed, 5732 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 dogename.iml create mode 100644 pom.xml create mode 100644 res/icon.ico create mode 100644 res/top.png create mode 100644 res/usage.md create mode 100644 res/you-want.md create mode 100644 src/main/java/log4j2.xml create mode 100644 src/main/java/me/lensferno/dogename/DataReleaser.java create mode 100644 src/main/java/me/lensferno/dogename/Main.java create mode 100644 src/main/java/me/lensferno/dogename/choose/Chooser.java create mode 100644 src/main/java/me/lensferno/dogename/configs/ConfigLoader.java create mode 100644 src/main/java/me/lensferno/dogename/configs/ConfigValuesBean.java create mode 100644 src/main/java/me/lensferno/dogename/configs/MainConfig.java create mode 100644 src/main/java/me/lensferno/dogename/configs/VoiceConfig.java create mode 100644 src/main/java/me/lensferno/dogename/configs/adapters/BooleanPropertyAdapter.java create mode 100644 src/main/java/me/lensferno/dogename/configs/adapters/DoublePropertyAdapter.java create mode 100644 src/main/java/me/lensferno/dogename/configs/adapters/IntegerPropertyAdapter.java create mode 100644 src/main/java/me/lensferno/dogename/configs/adapters/StringPropertyAdapter.java create mode 100644 src/main/java/me/lensferno/dogename/controllers/GushiciPaneController.java create mode 100644 src/main/java/me/lensferno/dogename/controllers/HistoryPaneController.java create mode 100644 src/main/java/me/lensferno/dogename/controllers/HitokotoPaneController.java create mode 100644 src/main/java/me/lensferno/dogename/controllers/MainInterfaceController.java create mode 100644 src/main/java/me/lensferno/dogename/controllers/MiniPaneController.java create mode 100644 src/main/java/me/lensferno/dogename/controllers/NameManagerPaneController.java create mode 100644 src/main/java/me/lensferno/dogename/controllers/NumberSettingsPaneController.java create mode 100644 src/main/java/me/lensferno/dogename/controllers/OcrPaneController.java create mode 100644 src/main/java/me/lensferno/dogename/controllers/ProgramInfoPaneController.java create mode 100644 src/main/java/me/lensferno/dogename/controllers/SettingsPaneController.java create mode 100644 src/main/java/me/lensferno/dogename/controllers/VoiceSettingsPaneController.java create mode 100644 src/main/java/me/lensferno/dogename/controllers/WindowListeners/MoveWindowByMouse.java create mode 100644 src/main/java/me/lensferno/dogename/controllers/WindowListeners/MoveWindowByTouch.java create mode 100644 src/main/java/me/lensferno/dogename/data/History.java create mode 100644 src/main/java/me/lensferno/dogename/data/NameData.java create mode 100644 src/main/java/me/lensferno/dogename/ocr/Ocr.java create mode 100644 src/main/java/me/lensferno/dogename/ocr/ScreenCapture.java create mode 100644 src/main/java/me/lensferno/dogename/resources/LibLicense.txt create mode 100644 src/main/java/me/lensferno/dogename/resources/LicenseText.java create mode 100644 src/main/java/me/lensferno/dogename/resources/MainInterfaceImage.java create mode 100644 src/main/java/me/lensferno/dogename/resources/dogename.java create mode 100644 src/main/java/me/lensferno/dogename/sayings/Gushici.java create mode 100644 src/main/java/me/lensferno/dogename/sayings/Hitokoto.java create mode 100644 src/main/java/me/lensferno/dogename/utils/Clipboard.java create mode 100644 src/main/java/me/lensferno/dogename/utils/DialogMaker.java create mode 100644 src/main/java/me/lensferno/dogename/utils/FileProcessor.java create mode 100644 src/main/java/me/lensferno/dogename/utils/HtmlRequseter.java create mode 100644 src/main/java/me/lensferno/dogename/voice/Token.java create mode 100644 src/main/java/me/lensferno/dogename/voice/TokenManager.java create mode 100644 src/main/java/me/lensferno/dogename/voice/VoicePlayer.java create mode 100644 src/main/resources/META-INF/MANIFEST.MF create mode 100644 src/main/resources/me/lensferno/dogename/FXMLs/AdvancedVoiceSettingsPane.fxml create mode 100644 src/main/resources/me/lensferno/dogename/FXMLs/GushiciPane.fxml create mode 100644 src/main/resources/me/lensferno/dogename/FXMLs/HistoryPane.fxml create mode 100644 src/main/resources/me/lensferno/dogename/FXMLs/HitokotoPane.fxml create mode 100644 src/main/resources/me/lensferno/dogename/FXMLs/MainInterface.fxml create mode 100644 src/main/resources/me/lensferno/dogename/FXMLs/MiniPane.fxml create mode 100644 src/main/resources/me/lensferno/dogename/FXMLs/NameListPane.fxml create mode 100644 src/main/resources/me/lensferno/dogename/FXMLs/NameManagerPane.fxml create mode 100644 src/main/resources/me/lensferno/dogename/FXMLs/NumberSettingPane.fxml create mode 100644 src/main/resources/me/lensferno/dogename/FXMLs/OcrPane.fxml create mode 100644 src/main/resources/me/lensferno/dogename/FXMLs/ProgramInfoPane.fxml create mode 100644 src/main/resources/me/lensferno/dogename/FXMLs/SettingsPane.fxml create mode 100644 src/main/resources/me/lensferno/dogename/FXMLs/VoiceSettingsPane.fxml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..dc49a05 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +/out +/files +/caches +/target +*.token +/target/ +/log/ +/logs/ +.idea \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..b14ca0a --- /dev/null +++ b/LICENSE @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/README.md b/README.md new file mode 100644 index 0000000..810d374 --- /dev/null +++ b/README.md @@ -0,0 +1,68 @@ + +# 一个用来点名的玩意/A simple guy to choose names + +![图片加载不出来欸~](https://github.com/lensferno/dogename/raw/main/res/top.png "哟嚯!") + +看下面 + + +## 介绍/Introduce +这是一个基于Java语言,以Google Material Design(Google MD)为设计风格的,用来点名的东西。 + +基本上不会有什么大更新,就是改善代码而已了ヾ§  ̄▽)ゞ + +由于时间仓促,加上还是JAVA的新手,因此代码和算法写的很烂,望谅解ヽ(╯▽╰)ノ + +------------ + + +#### 用法介绍 +[去去去➡](https://github.com/lensferno/dogename/blob/main/res/usage.md) + +## 截图/Screenshot: + +#### [来来来~](https://github.com/lensferno/dogename/blob/main/res/you-want.md "我要康康") + + + +## 下载/Download: +[来这里](https://github.com/lensferno/dogename/releases "这里") + + +## 使用到的第三方库/Third-party library used: + +- [JFoenix](https://github.com/jfoenixadmin/JFoenix "JFoenix")(8.0.4),UI界面库。 + +- [Apache Commons Codec](http://commons.apache.org/proper/commons-codec/ "Apache Commons Codec")(1.11),用于Base64解码。 + +- [Gson](https://github.com/google/gson "Gson")(2.8.5),用于Json数据的解析与生成。 + +- [OkHttp3](https://github.com/square/okhttp "OkHttp")(4.2.2),用于语音部分的数据请求。 + +- [gushici](https://github.com/xenv/gushici/ "古诗词")项目提供的古诗词接口和数据。 + +- “每日一句话”的数据来自[一言](https://hitokoto.cn/ "一言") + +- 本项目还使用了 百度AI SDK 来实现OCR功能。 + +------ + +#### License信息 + +本程序使用[GNU Lesser General Public License](http://www.gnu.org/licenses/lgpl-3.0.html "LGPL") (LGPL,version 3)开源许可证, + + + +遵循[Apache License 2.0](http://www.apache.org/licenses/LICENSE-2.0 "Apache License 2.0")使用了以下库: + +- [JFoenix](https://github.com/jfoenixadmin/JFoenix "JFoenix")(8.0.4) + +- [Apache Commons Codec](http://commons.apache.org/proper/commons-codec/ "Apache Commons Codec")(1.11) + +- [Gson](https://github.com/google/gson "Gson")(2.8.5) + +- [OkHttp3](https://github.com/square/okhttp "OkHttp")(4.2.2) + + + + diff --git a/dogename.iml b/dogename.iml new file mode 100644 index 0000000..bad4288 --- /dev/null +++ b/dogename.iml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..2cba667 --- /dev/null +++ b/pom.xml @@ -0,0 +1,109 @@ + + + 4.0.0 + + me.lensferno.dogename + dogename + 3.3 + + + 1.8 + 1.8 + + + + + + + org.apache.maven.plugins + maven-jar-plugin + 2.4 + + + me.lensferno.dogename.Main + + + + + + + + + + + com.google.code.gson + gson + 2.8.6 + + + + + commons-codec + commons-codec + 1.14 + + + + + com.jfoenix + jfoenix + 8.0.9 + + + + + org.apache.logging.log4j + log4j-core + 2.13.2 + + + + + commons-io + commons-io + 2.6 + + + + + com.squareup.okhttp3 + okhttp + 4.2.2 + + + + + com.googlecode.soundlibs + mp3spi + 1.9.5.4 + + + + com.baidu.aip + java-sdk + 4.12.0 + + + + + + + + central + aliyun maven + http://maven.aliyun.com/nexus/content/groups/public/ + default + + + true + + + + false + + + + + \ No newline at end of file diff --git a/res/icon.ico b/res/icon.ico new file mode 100644 index 0000000000000000000000000000000000000000..5e66e547b17d0f1c56c0ecf7b9c4d60aeda7110c GIT binary patch literal 95518 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 literal 0 HcmV?d00001 diff --git a/res/top.png b/res/top.png new file mode 100644 index 0000000000000000000000000000000000000000..052635ed1fe83c9ba051179a63004010e2a8105f GIT binary patch literal 23121 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 VpU$Czo~Zx;002ovPDHLkV1lF#C{X|a literal 0 HcmV?d00001 diff --git a/res/usage.md b/res/usage.md new file mode 100644 index 0000000..dda968f --- /dev/null +++ b/res/usage.md @@ -0,0 +1,229 @@ +# 我该如何使用Dogename?(适用于最新的版本) + +*哈哈,终于开始写了,我都说鸽鸽我呀是不会咕的嘛*~ ╮(‵▽′)╭ + +此文章是Dogename的使用说明,它将指导您如何更好地使用这个点名工具。 + +> *使用一样新东西之前看看使用说明是个好习惯。* 🤔 *——我说的* + +这个使用说明分为以下几个部分:(点击可直达) + +### 1. 安装&开始使用 +- [从哪弄来?](#从哪弄来) +- [怎么配置环境?(很简单的!)](#怎么配置环境) +- [运行&使用](#运行与使用) + +### 2. 名单管理 +- [添加一个名字](#添加一个名字) +- [懒人:批量添加名字(特别棒!)](#批量添加名字) + - 文字识别法 + - 复制粘贴法 +- [删除名字](#删除名字) +- [导入&导出名单](#名单的导入和导出) +- [打乱名字](#打乱名字) + +### 3. 选项设置 +- [点名方面的](#点名方面的设置) + - 点名规则 + - 挑选次数 + - 挑选速度 + - 使用/不使用随机性更强的Java SecureRandom + - 开启/不开启语言播报 +- [语音报方面的](#语音播报方面的设置) + + +### 4. 其他小功能的介绍&说明 +- [历史记录](#历史记录) +- [小窗模式](#小窗模式) + + + + + + +-------------------------------------------- + +被夹在分割线之中不得动弹~ + +-------------------------------------------- + + + + + + + +# 开始:使用说明 + + + +## 1. 安装&开始使用 + + + +### 从哪弄来 +- 呃嗯嗯嗯嗯嗯🤔...怎么说呢,既然你能来到这里,基本上你已经有Dogename了吧? + +- 如果还真没有,那就去这里吧:[Github](https://github.com/eatenid/dogename/releases) +> 说一声: +> 文件“dogename.jar”为主程序文件。 +> “dogename_with_jre”之类的文件是带有Java运行库的整合版本,如果您下载的是这个整合包,那么您就不必再配置运行环境,跳过b步骤了。 + +不过在中国大陆内,Github的访问速度普遍偏慢,因此建议您下载不带运行环境的包(dogeneme_no_jre),或者去下载页(releases页)给出的其他下载方式。 + +### 怎么配置环境 +本程序是用Java语言写成的,因此您的计算机需要有合适的Java运行环境才能运行Dogename。(跟Minecraft一样的道理) + +- 如果您是从本程序过来的,那么恭喜,您不用配置环境了!🥳 + +- 如果您下载的是附带有Java运行环境的Dogename,那么您也不需要配置Java运行环境。 + +- 如果您都不是以上,您可以到[Java官网](https://www.java.com/)下载并安装,安装完成后,就好了。 + +### 运行与使用 +- 很简单,如果您的Java环境已经没问题了,就可以直接运行“dogename.exe”开始玩耍。 +- 您也可以运行“dogename_hide.exe”来隐式运行dogename(就是不会有黑框框显示出来),不过直接使用“dogename.exe”可以*在一定程度上*帮助您排查为何不能运行(如果您按照说明去做*一般*不会出现这种情况)。 +- 成功运行后,点击“安排一下”即可开始点名,不过此时还没有任何名字在名单中,因此您需要至少添加一个名字(或者先使用“数字模式”玩玩)。下面讲讲述如何添加一个或多个名字。 + + + +-------- + + + +## 2. 名单管理 + +首先,您需要点击主界面上的“名单管理”按钮,呼出名单管理面板,以便进行操作。此时名单列表会列出已经添加的名字。 + +### 添加一个或几个名字 + +1. 在名字输入框中输入想要添加的名字,如果输入多行名字,则可以同时添加对个名字(每一行识别为一个名字)。 +2. 输入完成后,点击"添加"按钮,即可在名单列表中看到刚刚添加进去的名字。 + +### 批量添加名字 +> 在上面“添加一个或几个名字”提到过,如果一行输入了一个名字,就可以同时添加多个名字(每一行识别为一个名字)。 +因此,根据这个原理,可以实现批量添加名字。 + +以下提供两种批量添加名字的方法: + +*(您也可以自己摸索其他的批量添加方法,只要最后能够使得名字输入框中每行拥有一个名字即可)* + + +##### 1. 通过Dogename自带的OCR(文字识别)功能来截取屏幕上的名字。 +- 点击名单管理面板上的“从屏幕上截取名字”,打开OCR模块。 +- 打开其他含有欲添加名单的文件*(例如Excel表格、Word文档、座位表图片等或者其他显示了名字的程序)*。 +- 然后切换到Dogename的文字识别窗口(注意请使显示了名字的界面保持显示),点击“截屏并开始识别”按钮。此时Dogename的界面将会被隐藏,以便进行截图操作。等待大约0.5秒,屏幕将会出现跟随鼠标位置的竖线和横线,这是方便您剪裁定位的。 +- 点击鼠标左键,选取*含有名字的区域*,松开鼠标后,将会完成截屏操作并开始识别。 +- 识别完成后,文字识别界面左侧的文本框将会显示出识别出的名字。如果文字关系清楚,识别出来的结果就是一行一个名字。如果不是,您可以手动修改,使得*一行只有一个名字* +- 修改完成后,将识别结果复制下来,粘贴在名字输入框中。点击“添加”按钮,即可完成添加。 + +下面的动图演示了如何进行这一操作: + + + + +![如果图片没有显示出来的话请直接打开:https://www.example.com](https://www.example.com/gif.gif) + + + + +##### 2. 从Word、Excel等含有表格式名单列表的地方复制粘贴。 +- 如果因为网络不畅或者其他原因无法使用文字识别功能,您可以使用这种办法。 +- 在其中的表格里,如果竖着选中文本,复制粘贴到名单输入框后一般就能够一行一个名字。点击“添加”按钮,即可批量添加上名字。 + +下面的动图演示了如何进行这一操作: + + + + +![如果图片没有显示出来的话请直接打开:https://www.example.com](https://www.example.com/gif.gif) + + + + +### 删除名字 +- 很简单,只需要在名单列表中选中欲删除的名字,点击“删除”按钮即可。 +- 点击“删除所有”按钮会删除名单列表中的所有名字。 +- 注意:除非已经备份好名单,否则无法撤销,请小心操作。 + +### 名单的导入和导出 +- 点击“备份”按钮,选择将要保存名单的地方,输入好文件名,点击确定,即可保存。 +- 点击“恢复”按钮,选择导出过的名单文件,点击确定,即可导入。 +注意:**导入名单会直接覆盖现有名单**,因此建议您在导入之前备份好现有名单。 + +### 打乱名字 +- 点击“打乱顺序”按钮,将会把名单中的名字随机排列,在*一定程度上(不是很严格地)*提高随机性。 + + + +-------- + + + +## 3. 选项设置 + + + +### 点名的设置 + +这里讲介绍一下几个方面的设置: +1. 点名规则 +2. 挑选次数 +3. 挑选速度 +4. 使用/不使用随机性更强的Java SecureRandom +5. 开启/不开启语言播报 + + + +-------------这是分割线------------- + + + +##### 1. 点名规则 +- 通过设置此项,可以按以下规则进行挑选: + - ①被点过的名字还要被点 + - ②被点过的名字不会再被点(默认使用) + - ~~娱乐模式*(①模式下可用)* ~~ *(已移除)* + - 机会均等*(②模式下可用,默认使用)* + +介绍: +- **娱乐模式**:旧称“套路模式”,勾选后会使被点过的名字在挑选列表中多出现4到5次,增加再次被点中的几率。 +**注意:仅在勾选此模式后点中的名字才会被多增加4到5次,不勾选时选中的名字不受影响。 +退出后会自动重置,不影响下次使用。** + +- **机会均等**:勾选“机会均等”后,将会保存已点过的的名字和数字到文件中,下次启动时仍不会被点到,直到全部名字或数字被点完 或点击“机会均等”的“重置”按钮。 +**注意:仅保存“这次点过就不点了”模式下选中的名字或数字。*** + + +##### 2. 挑选次数 +- 您可以调节名字随机滚动的次数,滚动显示多少个名字后,才是真正被选中的名字。 +--范围:3至200次 +--默认值:随机次数(范围:100至250次) +~~小问题:这个次数是不怎么准确的~~😓 + +##### 3. 挑选速度 +- 通过调节滑动条,您可以设置名字滚动的速度。数值越大速度越快。若调节为0,则显示一个名字后,会过100ms才显示下一个名字,直至出现正真被挑选中的名字。默认值:80 (20ms) + +##### 4. 设置是否使用Java SecureRandom +- 开启后,将使用Java SecureRandom来获得较强随机性的随机数,而不是普通的随机数算法。默认开启。如果您觉得开启后点名明显变卡(虽说不太可能),可以尝试关闭。 + +##### 5.设置是否开启语言播报 +- 开启后,点中名字后将会把被选中的名字用语音播放出来。默认开启。需要网络支持。如果因为各种原因无法/没必要播放,可以关闭这个功能。 + + +### 语音播报的设置 +- 在这里,你可以设置播报人,以及其语速和语调(默认都是5)。 + + +-------- + + + +## 4. 其他小功能的介绍&说明 + +#### 1. 历史记录 +- 在这里,你可以查看最近2000条点名记录。当记录超过2000条时,将会自动清空历史记录,重新从0开始记录,当然,您也可以手动清空。 +- 在搜索框输入文字后,点击“向上查找”或者“向下查找”按钮,可以查找出包含有您想要搜索的文字的记录。 +~~(该功能现阶段状况:容易出bug~~😫~~,搜寻替代方案ing...)~~ +#### 2. 小窗模式 +- 点击主界面的“小窗模式”按钮,可以将窗口变成小版界面,仅保留“安排一下”和“恢复”按钮,以及一个用于显示名字的文本。这个时候窗口总在最前端,拖动非按钮区域可以移动窗口。点击“恢复”按钮即可恢复原来的窗口状态。 \ No newline at end of file diff --git a/res/you-want.md b/res/you-want.md new file mode 100644 index 0000000..097aa71 --- /dev/null +++ b/res/you-want.md @@ -0,0 +1,41 @@ +# 你想瞅一瞅的截图 +您看好了啊🧐 + +*此截图为Windows 10下的效果,不同的系统**可能**会有不同的效果* + +*这里的截图不是最新的版本,因此可能会有一定的差别,请以实物为准* + +## 1. 第一界面 +#### a. 古诗词: + +![gushi.png](https://i.loli.net/2020/05/04/1epMwgTNjC9X4lL.png) + +#### b. 一言: + +![hitokoto.png](https://i.loli.net/2020/05/04/95z3XM4lICvxiPQ.png) + +## 2. 主界面 +#### a. 未挑选时: + +![main_interfoce.png](https://i.loli.net/2020/05/04/WfnsbLTqkcBvhCZ.png) + +#### b. 选中一个名字: +![main_interface_chose.png](https://i.loli.net/2020/05/04/xvVuIzC51OsXjN3.png) + +## 3. “迷你模式”界面 +![mini_pane.png](https://i.loli.net/2020/05/04/y2rbYRHdfl8aQ1e.png) + +## 4. “名单管理”界面 +![name_mamager_pane.png](https://i.loli.net/2020/05/04/7JHnDdCKqgo3Ska.png) + +## 5. 设置界面 +![settings_pane.png](https://i.loli.net/2020/05/04/5FlSLMxGN6iDtTz.png) + +## 6. OCR模块界面 +![ocr.png](https://i.loli.net/2020/05/04/QosZRfKagl1IjNC.png) + +## 7. “程序信息”界面 +![info_pane.png](https://i.loli.net/2020/05/04/i8dHWVvpTCbylzj.png) + +## 8. 还有更多的吗? +[下载](https://github.com/eatenid/dogename/releases "下载")下来看看吧😀 \ No newline at end of file diff --git a/src/main/java/log4j2.xml b/src/main/java/log4j2.xml new file mode 100644 index 0000000..92d39c3 --- /dev/null +++ b/src/main/java/log4j2.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/java/me/lensferno/dogename/DataReleaser.java b/src/main/java/me/lensferno/dogename/DataReleaser.java new file mode 100644 index 0000000..4bc428e --- /dev/null +++ b/src/main/java/me/lensferno/dogename/DataReleaser.java @@ -0,0 +1,21 @@ +package me.lensferno.dogename; + +import me.lensferno.dogename.resources.MainInterfaceImage; +import me.lensferno.dogename.resources.dogename; +import org.apache.commons.codec.binary.Base64; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; + +public class DataReleaser { + + public static InputStream getDogenameStream(){ + return new ByteArrayInputStream(Base64.decodeBase64(dogename.data)); + } + + public static InputStream getMainPicStream(){ + return new ByteArrayInputStream(Base64.decodeBase64(MainInterfaceImage.data)); + } + + +} diff --git a/src/main/java/me/lensferno/dogename/Main.java b/src/main/java/me/lensferno/dogename/Main.java new file mode 100644 index 0000000..ec00d41 --- /dev/null +++ b/src/main/java/me/lensferno/dogename/Main.java @@ -0,0 +1,66 @@ +package me.lensferno.dogename; + +import javafx.application.Application; +import javafx.fxml.FXMLLoader; +import javafx.scene.Parent; +import javafx.scene.Scene; +import javafx.stage.Stage; +import me.lensferno.dogename.configs.ConfigLoader; +import me.lensferno.dogename.controllers.MainInterfaceController; +import me.lensferno.dogename.sayings.Gushici; +import me.lensferno.dogename.sayings.Hitokoto; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.Random; + +public class Main extends Application { + + Logger log = LogManager.getLogger(); + + public static void main(String[] args){ launch(args);} + + @Override + public void start(Stage primaryStage) { + FXMLLoader fxmlLoader; + Parent parent; + + try{ + fxmlLoader=new FXMLLoader(getClass().getResource("/me/lensferno/dogename/FXMLs/MainInterface.fxml")); + parent=fxmlLoader.load(); + }catch (Exception e){ + log.error("Error to load main interface FXML :"+e.toString()); + return; + } + + Scene scene=new Scene(parent,990,700); + primaryStage.setTitle("DogeName 叁号姬"); + primaryStage.setScene(scene); + + primaryStage.show(); + + ConfigLoader configLoader=new ConfigLoader(); + + MainInterfaceController mainInterfaceController=fxmlLoader.getController(); + + mainInterfaceController.setToggleGroup(); + + mainInterfaceController.setUpConfig(configLoader); + + mainInterfaceController.bindProperties(); + + mainInterfaceController.setImg(DataReleaser.getMainPicStream()); + + primaryStage.setOnCloseRequest(event -> configLoader.writeAllConfigToFile(configLoader.getMainConfigLocation(),configLoader.getVoiceConfigLocation())); + + if (mainInterfaceController.getMainConfig().isShowSaying()) { + if (new Random().nextBoolean()){ + new Gushici().showGushici(mainInterfaceController.getRootPane(),mainInterfaceController.getTopBar()); + }else { + new Hitokoto().showHitokoto(mainInterfaceController.getRootPane(),mainInterfaceController.getTopBar()); + } + } + + } + +} \ No newline at end of file diff --git a/src/main/java/me/lensferno/dogename/choose/Chooser.java b/src/main/java/me/lensferno/dogename/choose/Chooser.java new file mode 100644 index 0000000..19574cb --- /dev/null +++ b/src/main/java/me/lensferno/dogename/choose/Chooser.java @@ -0,0 +1,356 @@ +package me.lensferno.dogename.choose; + +import com.jfoenix.controls.JFXButton; +import javafx.animation.AnimationTimer; +import javafx.beans.property.StringProperty; +import me.lensferno.dogename.configs.VoiceConfig; +import me.lensferno.dogename.data.History; +import me.lensferno.dogename.data.NameData; +import me.lensferno.dogename.voice.Token; +import me.lensferno.dogename.voice.VoicePlayer; + +import java.security.SecureRandom; +import java.util.Random; +import java.util.logging.Logger; + + /* + +┴┬┴┬/ ̄\_/ ̄\ +┬┴┬┴▏  ▏▔▔▔▔\ +┴┬┴/\ /      ﹨ +┬┴∕       /   ) +┴┬▏        ●  ▏ +┬┴▏           ▔█  +┴◢██◣     \___/ +┬█████◣       /   +┴█████████████◣ +◢██████████████▆▄ +█◤◢██◣◥█████████◤\ +◥◢████ ████████◤   \ +┴█████ ██████◤      ﹨ +┬│   │█████◤        ▏ +┴│   │              ▏ +┬ ∕    ∕    /▔▔▔\     ∕ +┴/___/﹨   ∕     ﹨  /\ +┬┴┬┴┬┴\    \      ﹨/   ﹨ +┴┬┴┬┴┬┴ \___\     ﹨/▔\﹨ ▔\ +▲△▲▲╓╥╥╥╥╥╥╥╥\   ∕  /▔﹨/▔﹨ + **╠╬╬╬╬╬╬╬╬*﹨  /  // + + */ +//一坨屎山,有待修改😒 + +public final class Chooser { + + Logger log =Logger.getLogger("ChooserLogger"); + + final int UPPER_LABEL_ID = 1; + final int UNDER_LABEL_ID = 2; + + + Token token; + VoicePlayer voicePlayer; + + VoiceConfig voiceConfig; + + Random random =new Random(); + + public short minNumber; + public short maxNumber; + + boolean voicePlay=true; + + int totalMaxCount =120; + int cycleMaxCount =0; + + int totalCount =0; + int totalCycleCount =0; + + int shownLabelId =1; + + String chosenName; + + boolean cycleEnd =true; + boolean ignoreTimesOut=false; + boolean ignorePast=true; + + boolean equalMode=true; + + boolean forceStop =false; + + boolean newAlgo=true; + + public JFXButton anpaiBtn; + + public short speed; + + NameData nameData; + + boolean isRunning; + + StringProperty upLabelText; + StringProperty downLabelText; + + History history; + + String speaker,speakSpeed,intonation; + + void writeIgnoreList(){ + nameData.writeIgnoreList(""); + } + + + AnimationTimer timer =new AnimationTimer() { + @Override + public void handle(long now) { + + if(forceStop){ + totalCount = totalMaxCount +1; + } + + try{ + Thread.sleep(speed); + }catch (Exception e){e.printStackTrace(); } + + if(totalCount >= totalMaxCount){ + if(!nameData.getIgnoreNameList().contains(chosenName)||!ignorePast||forceStop){ + + forceStop=false; + + if(ignorePast) + nameData.getIgnoreNameList().add(chosenName); + if(equalMode) + writeIgnoreList(); + + cycleEnd=true; + totalCount =0; + totalCycleCount =0; + ignoreTimesOut=false; + + switch (shownLabelId){ + case UPPER_LABEL_ID:{ + + if(downLabelText.get().contains("→")||downLabelText.get().contains("←")) + downLabelText.set(downLabelText.get().replace("→ ","").replace(" ←","")); + + upLabelText.set("→ "+chosenName+" ←"); + + break; + } + case UNDER_LABEL_ID:{ + if(upLabelText.get().contains("→")||upLabelText.get().contains("←")) + upLabelText.set(upLabelText.get().replace("→ ","").replace(" ←","")); + + downLabelText.set("→ "+chosenName+" ←"); + + break; + } + } + isRunning=false; + anpaiBtn.setText("安排一下"); + stop(); + System.gc(); + history.addHistory(chosenName); + if(voicePlay) + voicePlayer.playVoice(chosenName,speaker,intonation,speakSpeed); + return; + }else + ignoreTimesOut=true; + + + + } + if(totalCycleCount >= cycleMaxCount &&!ignoreTimesOut){ + cycleEnd=true; + totalCycleCount =0; + } + + if(cycleEnd){ + //times=(int)(1+Math.random()*(chosenTime-already)); + cycleMaxCount =1+random.nextInt(totalMaxCount - totalCount +1); + cycleEnd=false; + //showWhich=(int)(1+Math.random()*2); + shownLabelId =1+random.nextInt(2); + } + + + + switch (shownLabelId){ + case UPPER_LABEL_ID:{ + chosenName= nameData.randomGet(); + upLabelText.set(chosenName); + totalCount++; + totalCycleCount++; + break; + } + + case UNDER_LABEL_ID:{ + chosenName= nameData.randomGet(); + downLabelText.set(chosenName); + totalCount++; + totalCycleCount++; + break; + } + } + + + } + }; + //--------------------------------------------------------------------------------------- + SecureRandom secRandom =new SecureRandom(); + AnimationTimer numbTimer =new AnimationTimer() { + @Override + public void handle(long now) { + + if(forceStop){ + totalCount = totalMaxCount +1; + } + + try{ + Thread.sleep(speed); + }catch (Exception e){e.printStackTrace(); } + + if(totalCount >= totalMaxCount){ + if(!nameData.getIgnoreNumberList().contains(chosenName)||!ignorePast||forceStop){ + + forceStop=false; + + if(ignorePast) + nameData.getIgnoreNumberList().add(chosenName); + if(equalMode) + writeIgnoreList(); + + cycleEnd=true; + totalCount =0; + totalCycleCount =0; + ignoreTimesOut=false; + + + switch (shownLabelId){ + case UPPER_LABEL_ID:{ + if(downLabelText.get().contains("→")||downLabelText.get().contains("←")) + downLabelText.set(downLabelText.get().replace("→ ","").replace(" ←","")); + + upLabelText.set("→ "+chosenName+" ←"); + + break; + } + case UNDER_LABEL_ID:{ + if(upLabelText.get().contains("→")||upLabelText.get().contains("←")) + upLabelText.set(upLabelText.get().replace("→ ","").replace(" ←","")); + + downLabelText.set("→ "+chosenName+" ←"); + + break; + } + } + isRunning=false; + anpaiBtn.setText("安排一下"); + stop(); + System.gc(); + history.addHistory(chosenName); + if(voicePlay) + voicePlayer.playVoice(chosenName,speaker,intonation,speakSpeed); + return; + }else + ignoreTimesOut=true; + + } + + shownLabelId =1+random.nextInt(2); + //speed=(short)(65+random.nextInt(100)); + + switch (shownLabelId){ + case UPPER_LABEL_ID:{ + if(newAlgo) + chosenName=String.valueOf(minNumber+random.nextInt(maxNumber-minNumber+1)); + else + chosenName=String.valueOf(minNumber+secRandom.nextInt(maxNumber-minNumber+1)); + + upLabelText.set(chosenName); + totalCount++; + totalCycleCount++; + break; + } + + case UNDER_LABEL_ID:{ + if(newAlgo) + chosenName=String.valueOf(minNumber+random.nextInt(maxNumber-minNumber+1)); + else + chosenName=String.valueOf(minNumber+secRandom.nextInt(maxNumber-minNumber+1)); + + downLabelText.set(chosenName); + totalCount++; + totalCycleCount++; + break; + } + } + + + } + }; + + public void forceStop(){ + + } + + + + public void run(short speed,int chosenTime,boolean ignorePast,boolean equalMode,boolean voicePlay){ + + this.speed = speed; + this.totalMaxCount = chosenTime; + this.ignorePast = ignorePast; + this.equalMode = equalMode; + this.voicePlay = voicePlay; + + isRunning=true; + timer.start(); + } + + public void run(short maxNumber,short minNumber,short speed,int chosenTime,boolean ignorePast,boolean equalMode,boolean voicePlay){ + this.maxNumber = maxNumber; + this.minNumber = minNumber; + this.speed = speed; + this.totalMaxCount = chosenTime; + this.ignorePast = ignorePast; + this.equalMode = equalMode; + this.voicePlay = voicePlay; + + isRunning=true; + numbTimer.start(); + } + + public void set(StringProperty upLabelText, StringProperty downLabelText, JFXButton anpaiBtn, History history, NameData nameData, Token token, VoiceConfig voiceConfig){ + + this.upLabelText=upLabelText; + this.downLabelText=downLabelText; + + this.anpaiBtn = anpaiBtn; + this.history = history; + this.nameData = nameData; + + this.token=token; + this.voicePlayer=new VoicePlayer(token); + + this.voiceConfig=voiceConfig; + + this.speaker=String.valueOf(voiceConfig.getSpeaker()); + this.speakSpeed=String.valueOf(voiceConfig.getSpeed()); + this.intonation=String.valueOf(voiceConfig.getIntonation()); + this.speaker=voiceConfig.getSpeaker(); + } + + + public void setChoose(JFXButton anpaiBtn) { + this.anpaiBtn = anpaiBtn; + } + + public boolean isRunning() { + return isRunning; + } + + public void setForceStop(boolean forceStop) { + this.forceStop = forceStop; + } +} diff --git a/src/main/java/me/lensferno/dogename/configs/ConfigLoader.java b/src/main/java/me/lensferno/dogename/configs/ConfigLoader.java new file mode 100644 index 0000000..ce759d2 --- /dev/null +++ b/src/main/java/me/lensferno/dogename/configs/ConfigLoader.java @@ -0,0 +1,212 @@ +package me.lensferno.dogename.configs; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import javafx.beans.property.*; +import me.lensferno.dogename.configs.adapters.BooleanPropertyAdapter; +import me.lensferno.dogename.configs.adapters.DoublePropertyAdapter; +import me.lensferno.dogename.configs.adapters.IntegerPropertyAdapter; +import me.lensferno.dogename.configs.adapters.StringPropertyAdapter; +import org.apache.commons.io.IOUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.*; +import java.nio.charset.StandardCharsets; + +public class ConfigLoader { + + Logger log = LogManager.getLogger(); + + //ConfigValuesBean config; + private MainConfig mainConfig; + private VoiceConfig voiceConfig; + + private final String mainConfigLocation = "files"+ File.separator+"Config.json"; + private final String voiceConfigLocation = "files"+ File.separator+"VoiceConfig.json"; + + public String getMainConfigLocation() { + return mainConfigLocation; + } + + public String getVoiceConfigLocation() { + return voiceConfigLocation; + } + + public MainConfig getMainConfig() { + return mainConfig; + } + + public MainConfig readConfigFromFile(String fileLocation){ + + //property属性应该要自定义一个json适配器才能解析出来 + Gson gson=new GsonBuilder() + .registerTypeAdapter(SimpleBooleanProperty.class,new BooleanPropertyAdapter()) + .registerTypeAdapter(SimpleIntegerProperty.class,new IntegerPropertyAdapter()) + .registerTypeAdapter(SimpleStringProperty.class,new StringPropertyAdapter()) + .registerTypeAdapter(SimpleDoubleProperty.class,new DoublePropertyAdapter()) + .setPrettyPrinting() + .create(); + + String ConfigJSON; + + try{ + File configFile=new File(fileLocation); + if(!configFile.exists()){ + configFile.getParentFile().mkdirs(); + configFile.createNewFile(); + mainConfig=new MainConfig(); + writeMainConfigToFile(mainConfigLocation); + return mainConfig; + } + InputStream inputStream=new FileInputStream(configFile); + ConfigJSON=IOUtils.toString(inputStream, StandardCharsets.UTF_8); + + mainConfig=gson.fromJson(ConfigJSON,MainConfig.class); + + if (mainConfig == null) { + mainConfig=new MainConfig(); + writeMainConfigToFile(mainConfigLocation); + return mainConfig; + } + + }catch (Exception e){ + log.error("Error to load config file:"+e+"\nUse Default config."); + + mainConfig=new MainConfig(); + writeMainConfigToFile(mainConfigLocation); + return mainConfig; + } + + return this.mainConfig; + } + + public VoiceConfig readVoiceConfigFromFile(String fileLocation){ + + //property属性应该要自定义一个json适配器才能解析出来 + Gson gson=new GsonBuilder() + .registerTypeAdapter(SimpleBooleanProperty.class,new BooleanPropertyAdapter()) + .registerTypeAdapter(SimpleIntegerProperty.class,new IntegerPropertyAdapter()) + .registerTypeAdapter(SimpleStringProperty.class,new StringPropertyAdapter()) + .registerTypeAdapter(SimpleDoubleProperty.class,new DoublePropertyAdapter()) + .setPrettyPrinting() + .create(); + + String ConfigJSON; + + try{ + File configFile=new File(fileLocation); + if(!configFile.exists()){ + configFile.getParentFile().mkdirs(); + configFile.createNewFile(); + + voiceConfig=new VoiceConfig(); + writeVoiceConfigToFile(voiceConfigLocation); + return voiceConfig; + } + InputStream inputStream=new FileInputStream(configFile); + ConfigJSON=IOUtils.toString(inputStream, StandardCharsets.UTF_8); + + writeVoiceConfigToFile(voiceConfigLocation); + voiceConfig=gson.fromJson(ConfigJSON,VoiceConfig.class); + if (voiceConfig == null) { + voiceConfig=new VoiceConfig(); + writeVoiceConfigToFile(voiceConfigLocation); + return voiceConfig; + } + + }catch (Exception e){ + log.error("Error to load voice config file:"+e+"\nUse Default voice config."); + + voiceConfig=new VoiceConfig(); + writeVoiceConfigToFile(voiceConfigLocation); + return voiceConfig; + } + + return this.voiceConfig; + } + + // + public MainConfig setValuesToProperty(){ + //mainconfig.set..(config.get..) + //...so on + // + return this.mainConfig; + } + + private String toJSON(MainConfig config){ + + Gson gson=new GsonBuilder() + .registerTypeAdapter(SimpleBooleanProperty.class,new BooleanPropertyAdapter()) + .registerTypeAdapter(SimpleIntegerProperty.class,new IntegerPropertyAdapter()) + .registerTypeAdapter(SimpleStringProperty.class,new StringPropertyAdapter()) + .setPrettyPrinting() + .create(); + + return gson.toJson(config); + } + + private String VoiceConfigtoJSON(VoiceConfig config){ + + Gson gson=new GsonBuilder() + .registerTypeAdapter(SimpleDoubleProperty.class,new DoublePropertyAdapter()) + .setPrettyPrinting() + .create(); + + return gson.toJson(config); + } + + public void writeAllConfigToFile(String outputLocation, String voiceConfigFile){ + File outputFile = new File(outputLocation); + + try{ + if(! outputFile.exists()){ + outputFile.getParentFile().mkdirs(); + outputFile.createNewFile(); + } + OutputStream stream=new FileOutputStream(outputFile); + IOUtils.write(toJSON(this.mainConfig).getBytes(StandardCharsets.UTF_8),stream); + + OutputStream voiceConfigFileStream=new FileOutputStream(voiceConfigFile); + IOUtils.write(VoiceConfigtoJSON(this.voiceConfig).getBytes(StandardCharsets.UTF_8),voiceConfigFileStream); + + }catch (Exception e){ + log.error("Error in writing all config:"+e); + } + } + + public void writeMainConfigToFile(String outputLocation){ + File outputFile = new File(outputLocation); + + try{ + if(! outputFile.exists()){ + outputFile.getParentFile().mkdirs(); + outputFile.createNewFile(); + } + + OutputStream stream=new FileOutputStream(outputFile); + IOUtils.write(toJSON(this.mainConfig).getBytes(StandardCharsets.UTF_8),stream); + + }catch (Exception e){ + log.error("Error in writing main config:"+e); + } + } + + public void writeVoiceConfigToFile(String voiceConfigFile){ + File outputFile = new File(voiceConfigFile); + + try{ + + if(! outputFile.exists()) { + outputFile.getParentFile().mkdirs(); + outputFile.createNewFile(); + } + OutputStream voiceConfigFileStream=new FileOutputStream(voiceConfigFile); + IOUtils.write(VoiceConfigtoJSON(this.voiceConfig).getBytes(StandardCharsets.UTF_8),voiceConfigFileStream); + + }catch (Exception e){ + log.error("Error in writing voice config:"+e); + } + } + +} diff --git a/src/main/java/me/lensferno/dogename/configs/ConfigValuesBean.java b/src/main/java/me/lensferno/dogename/configs/ConfigValuesBean.java new file mode 100644 index 0000000..b5aa7aa --- /dev/null +++ b/src/main/java/me/lensferno/dogename/configs/ConfigValuesBean.java @@ -0,0 +1,4 @@ +package me.lensferno.dogename.configs; + +public class ConfigValuesBean { +} diff --git a/src/main/java/me/lensferno/dogename/configs/MainConfig.java b/src/main/java/me/lensferno/dogename/configs/MainConfig.java new file mode 100644 index 0000000..ea56552 --- /dev/null +++ b/src/main/java/me/lensferno/dogename/configs/MainConfig.java @@ -0,0 +1,242 @@ +package me.lensferno.dogename.configs; + +import com.google.gson.annotations.Expose; +import javafx.beans.property.*; + +public class MainConfig { + + // ConfigValuesBean configValuesBean =new ConfigValuesBean(); + + // ---------------------- Default values --------------------------------------------------------- + + @Expose + private final String NEWEST_CONFIG_VERSION = "2"; + + private String currentConfigVersion = null; + + @Expose + public final boolean DEFAULT_NAME_CHOOSE = true; + + public final int METHOD_NAME = 0; // 名字挑选法 + public final int METHOD_NUMBER = 1; // 数字挑选法 + + public final int DEFAULT_CYCLE_TIMES = 120; // 默认轮回次数:120 + public final int DEFAULT_SPEED = 80; // 默认速度:20ms,对应滑动条80的位置 + + public final boolean DEFAULT_RANDOM_TIMES = true; // 默认挑选轮回次数是否随机:ture + + public final boolean DEFAULT_IGNORE_PAST = true; // 默认忽略已经点过的名字:ture + + public final boolean DEFAULT_EQUAL_MODE = true; // 默认开启"机会均等" + + public final boolean DEFAULT_NEW_ALGO = true; // 默认使用新算法"Java sec random" + public final boolean DEFAULT_VOICE_PLAY = true; // 默认使用语音播报 + + public final boolean DEFAULT_SHOW_SAYING = true; + + // ----------------------Properties---------------------------------------------------------------- + + private SimpleBooleanProperty nameChooseProperty; + + private SimpleBooleanProperty randomTimesProperty; // 挑选次数是否随机 + private SimpleBooleanProperty ignorePastProperty; // 是否忽略已经被点过的名字 + + private SimpleIntegerProperty chooseMethodProperty; // 挑选方式: 0->名字挑选法 1->数字挑选法 + private SimpleIntegerProperty cycleTimesProperty; // 挑选轮回次数,旧称"chosenTime" + + private SimpleIntegerProperty speedProperty; // 速度 + + private SimpleStringProperty minNumberProperty; // 最小值 + private SimpleStringProperty maxNumberProperty; // 最大值 + + private SimpleBooleanProperty equalModeProperty; // 是否开启"机会均等" + + private SimpleBooleanProperty newAlgoProperty; // 是否使用新算法 + private SimpleBooleanProperty voicePlayProperty; // 是否使用语音播报 + + private SimpleBooleanProperty showSaying; + + // -------------------------- 初始化 -------------------------------------------------------------- + public MainConfig() { + randomTimesProperty = new SimpleBooleanProperty(DEFAULT_RANDOM_TIMES); + ignorePastProperty = new SimpleBooleanProperty(DEFAULT_IGNORE_PAST); + + chooseMethodProperty = new SimpleIntegerProperty(METHOD_NAME); + nameChooseProperty = new SimpleBooleanProperty(DEFAULT_NAME_CHOOSE); + + cycleTimesProperty = new SimpleIntegerProperty(DEFAULT_CYCLE_TIMES); + + speedProperty = new SimpleIntegerProperty(DEFAULT_SPEED); + + minNumberProperty = new SimpleStringProperty("0"); + maxNumberProperty = new SimpleStringProperty("10"); + + equalModeProperty = new SimpleBooleanProperty(DEFAULT_EQUAL_MODE); + + newAlgoProperty = new SimpleBooleanProperty(DEFAULT_NEW_ALGO); + voicePlayProperty = new SimpleBooleanProperty(DEFAULT_VOICE_PLAY); + + showSaying = new SimpleBooleanProperty(DEFAULT_SHOW_SAYING); + + currentConfigVersion = NEWEST_CONFIG_VERSION; + } + + // -------------------------- Getters and Setters --------------------------------------------- + + public boolean isNameChooseProperty() { + return nameChooseProperty.get(); + } + + public SimpleBooleanProperty nameChoosePropertyProperty() { + return nameChooseProperty; + } + + public void setNameChooseProperty(boolean nameChooseProperty) { + this.nameChooseProperty.set(nameChooseProperty); + } + + public boolean isRandomTimesProperty() { + return randomTimesProperty.get(); + } + + public SimpleBooleanProperty randomTimesPropertyProperty() { + return randomTimesProperty; + } + + public void setRandomTimesProperty(boolean randomTimesProperty) { + this.randomTimesProperty.set(randomTimesProperty); + } + + public boolean isIgnorePastProperty() { + return ignorePastProperty.get(); + } + + public SimpleBooleanProperty ignorePastPropertyProperty() { + return ignorePastProperty; + } + + public void setIgnorePastProperty(boolean ignorePastProperty) { + this.ignorePastProperty.set(ignorePastProperty); + } + + public int getChooseMethodProperty() { + return chooseMethodProperty.get(); + } + + public SimpleIntegerProperty chooseMethodPropertyProperty() { + return chooseMethodProperty; + } + + public void setChooseMethodProperty(int chooseMethodProperty) { + this.chooseMethodProperty.set(chooseMethodProperty); + } + + public int getCycleTimesProperty() { + return cycleTimesProperty.get(); + } + + public SimpleIntegerProperty cycleTimesPropertyProperty() { + return cycleTimesProperty; + } + + public void setCycleTimesProperty(int cycleTimesProperty) { + this.cycleTimesProperty.set(cycleTimesProperty); + } + + public int getSpeedProperty() { + return speedProperty.get(); + } + + public SimpleIntegerProperty speedPropertyProperty() { + return speedProperty; + } + + public void setSpeedProperty(int speedProperty) { + this.speedProperty.set(speedProperty); + } + + public String getMinNumberProperty() { + return minNumberProperty.get(); + } + + public SimpleStringProperty minNumberPropertyProperty() { + return minNumberProperty; + } + + public void setMinNumberProperty(String minNumberProperty) { + this.minNumberProperty.set(minNumberProperty); + } + + public String getMaxNumberProperty() { + return maxNumberProperty.get(); + } + + public SimpleStringProperty maxNumberPropertyProperty() { + return maxNumberProperty; + } + + public void setMaxNumberProperty(String maxNumberProperty) { + this.maxNumberProperty.set(maxNumberProperty); + } + + public boolean isEqualModeProperty() { + return equalModeProperty.get(); + } + + public SimpleBooleanProperty equalModePropertyProperty() { + return equalModeProperty; + } + + public void setEqualModeProperty(boolean equalModeProperty) { + this.equalModeProperty.set(equalModeProperty); + } + + public boolean isNewAlgoProperty() { + return newAlgoProperty.get(); + } + + public SimpleBooleanProperty newAlgoPropertyProperty() { + return newAlgoProperty; + } + + public void setNewAlgoProperty(boolean newAlgoProperty) { + this.newAlgoProperty.set(newAlgoProperty); + } + + public boolean isVoicePlayProperty() { + return voicePlayProperty.get(); + } + + public SimpleBooleanProperty voicePlayPropertyProperty() { + return voicePlayProperty; + } + + public void setVoicePlayProperty(boolean voicePlayProperty) { + this.voicePlayProperty.set(voicePlayProperty); + } + + public boolean isShowSaying() { + return showSaying.get(); + } + + public SimpleBooleanProperty showSayingProperty() { + return showSaying; + } + + public void setShowSaying(boolean showSaying) { + this.showSaying.set(showSaying); + } + + public String getCurrentConfigVersion() { + return currentConfigVersion; + } + + public void setCurrentConfigVersion(String currentConfigVersion) { + this.currentConfigVersion = currentConfigVersion; + } + + public String getNewestConfigVersion() { + return NEWEST_CONFIG_VERSION; + } + +} diff --git a/src/main/java/me/lensferno/dogename/configs/VoiceConfig.java b/src/main/java/me/lensferno/dogename/configs/VoiceConfig.java new file mode 100644 index 0000000..aa4be98 --- /dev/null +++ b/src/main/java/me/lensferno/dogename/configs/VoiceConfig.java @@ -0,0 +1,75 @@ +package me.lensferno.dogename.configs; + +import javafx.beans.property.SimpleDoubleProperty; +import javafx.event.ActionEvent; +import javafx.fxml.FXML; + +public class VoiceConfig { + + private String speaker; + private int selectedSpeaker; + + + //度小宇=1,度小美=0,度逍遥=3,度丫丫=4 + //度博文=106,度小童=110,度小萌=111,度米朵=103,度小娇=5 + + private SimpleDoubleProperty speed; + private SimpleDoubleProperty intonation; + + public String getSpeaker() { + return speaker; + } + + public void setSpeaker(String speaker) { + this.speaker = speaker; + } + + public final int DEFAULT_SPEED=5; + public final int DEFAULT_INTONATION=5; + + public VoiceConfig(){ + selectedSpeaker=0; + speaker="1"; + speed=new SimpleDoubleProperty(DEFAULT_SPEED); + intonation=new SimpleDoubleProperty(DEFAULT_INTONATION); + } + + public int getSelectedSpeaker() { + return selectedSpeaker; + } + + public void setSelectedSpeaker(int selectedSpeaker) { + this.selectedSpeaker = selectedSpeaker; + } + public double getSpeed() { + return speed.get(); + } + + public SimpleDoubleProperty speedProperty() { + return speed; + } + + public void setSpeed(double speed) { + this.speed.set(speed); + } + + public double getIntonation() { + return intonation.get(); + } + + public SimpleDoubleProperty intonationProperty() { + return intonation; + } + + public void setIntonation(double intonation) { + this.intonation.set(intonation); + } + + @FXML + void showAdvancedVoiceSettings(ActionEvent event) { + + } + + + +} diff --git a/src/main/java/me/lensferno/dogename/configs/adapters/BooleanPropertyAdapter.java b/src/main/java/me/lensferno/dogename/configs/adapters/BooleanPropertyAdapter.java new file mode 100644 index 0000000..6c87d4a --- /dev/null +++ b/src/main/java/me/lensferno/dogename/configs/adapters/BooleanPropertyAdapter.java @@ -0,0 +1,27 @@ +package me.lensferno.dogename.configs.adapters; + +import com.google.gson.*; +import javafx.beans.property.SimpleBooleanProperty; + +import java.lang.reflect.Type; + +public class BooleanPropertyAdapter implements JsonSerializer, JsonDeserializer { + + @Override + public SimpleBooleanProperty deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException { + if(jsonElement==null) { + throw new JsonParseException("Json is wrong."); + }else { + return new SimpleBooleanProperty(jsonElement.getAsBoolean()); + } + } + + @Override + public JsonElement serialize(SimpleBooleanProperty simpleBooleanProperty, Type type, JsonSerializationContext jsonSerializationContext) { + if(simpleBooleanProperty==null){ + throw new JsonParseException("Json is wrong."); + }else { + return new JsonPrimitive(simpleBooleanProperty.get()); + } + } +} diff --git a/src/main/java/me/lensferno/dogename/configs/adapters/DoublePropertyAdapter.java b/src/main/java/me/lensferno/dogename/configs/adapters/DoublePropertyAdapter.java new file mode 100644 index 0000000..7038884 --- /dev/null +++ b/src/main/java/me/lensferno/dogename/configs/adapters/DoublePropertyAdapter.java @@ -0,0 +1,27 @@ +package me.lensferno.dogename.configs.adapters; + +import com.google.gson.*; +import javafx.beans.property.SimpleDoubleProperty; + +import java.lang.reflect.Type; + +public class DoublePropertyAdapter implements JsonSerializer, JsonDeserializer { + + @Override + public SimpleDoubleProperty deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException { + if(jsonElement==null) { + throw new JsonParseException("Json is wrong."); + }else { + return new SimpleDoubleProperty(jsonElement.getAsDouble()); + } + } + + @Override + public JsonElement serialize(SimpleDoubleProperty simpleDoubleProperty, Type type, JsonSerializationContext jsonSerializationContext) { + if(simpleDoubleProperty==null){ + throw new JsonParseException("Json is wrong."); + }else { + return new JsonPrimitive(simpleDoubleProperty.get()); + } + } +} diff --git a/src/main/java/me/lensferno/dogename/configs/adapters/IntegerPropertyAdapter.java b/src/main/java/me/lensferno/dogename/configs/adapters/IntegerPropertyAdapter.java new file mode 100644 index 0000000..3ee6351 --- /dev/null +++ b/src/main/java/me/lensferno/dogename/configs/adapters/IntegerPropertyAdapter.java @@ -0,0 +1,26 @@ +package me.lensferno.dogename.configs.adapters; + +import com.google.gson.*; +import javafx.beans.property.SimpleIntegerProperty; + +import java.lang.reflect.Type; + +public class IntegerPropertyAdapter implements JsonSerializer, JsonDeserializer { + @Override + public SimpleIntegerProperty deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException { + if(jsonElement==null) { + throw new JsonParseException("Json is wrong."); + }else { + return new SimpleIntegerProperty(jsonElement.getAsInt()); + } + } + + @Override + public JsonElement serialize(SimpleIntegerProperty simpleIntegerProperty, Type type, JsonSerializationContext jsonSerializationContext) { + if(simpleIntegerProperty==null){ + throw new JsonParseException("Json is wrong."); + }else { + return new JsonPrimitive(simpleIntegerProperty.get()); + } + } +} diff --git a/src/main/java/me/lensferno/dogename/configs/adapters/StringPropertyAdapter.java b/src/main/java/me/lensferno/dogename/configs/adapters/StringPropertyAdapter.java new file mode 100644 index 0000000..0bbf7d5 --- /dev/null +++ b/src/main/java/me/lensferno/dogename/configs/adapters/StringPropertyAdapter.java @@ -0,0 +1,26 @@ +package me.lensferno.dogename.configs.adapters; + +import com.google.gson.*; +import javafx.beans.property.SimpleStringProperty; + +import java.lang.reflect.Type; + +public class StringPropertyAdapter implements JsonSerializer, JsonDeserializer { + @Override + public SimpleStringProperty deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException { + if(jsonElement==null) { + throw new JsonParseException("Json is wrong."); + }else { + return new SimpleStringProperty(jsonElement.getAsString()); + } + } + + @Override + public JsonElement serialize(SimpleStringProperty simpleStringProperty, Type type, JsonSerializationContext jsonSerializationContext) { + if(simpleStringProperty==null) { + throw new JsonParseException("Json is wrong."); + }else { + return new JsonPrimitive(simpleStringProperty.get()); + } + } +} diff --git a/src/main/java/me/lensferno/dogename/controllers/GushiciPaneController.java b/src/main/java/me/lensferno/dogename/controllers/GushiciPaneController.java new file mode 100644 index 0000000..de2e948 --- /dev/null +++ b/src/main/java/me/lensferno/dogename/controllers/GushiciPaneController.java @@ -0,0 +1,39 @@ +package me.lensferno.dogename.controllers; + +import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; +import javafx.scene.layout.VBox; +import javafx.scene.text.Text; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + + +public class GushiciPaneController extends VBox { + + Logger log= LogManager.getLogger(); + @FXML + public Text contentText; + + @FXML + public Text contentType; + + @FXML + public Text contentInfo; + + public GushiciPaneController (String content,String title,String author,String type){ + + FXMLLoader loader=new FXMLLoader(getClass().getResource("/me/lensferno/dogename/FXMLs/GushiciPane.fxml")); + loader.setRoot(this); + loader.setController(this); + try { + loader.load(); + }catch(Exception e){ + log.error("Error to load Gushici pane FXML: "+e.toString()); + + //e.printStackTrace(); + } + contentText.setText("“"+content+"”"); + contentInfo.setText("《"+title+"》"+"——"+author); + contentType.setText(type); + } +} diff --git a/src/main/java/me/lensferno/dogename/controllers/HistoryPaneController.java b/src/main/java/me/lensferno/dogename/controllers/HistoryPaneController.java new file mode 100644 index 0000000..d1a0328 --- /dev/null +++ b/src/main/java/me/lensferno/dogename/controllers/HistoryPaneController.java @@ -0,0 +1,126 @@ +package me.lensferno.dogename.controllers; + +import com.jfoenix.controls.*; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; +import javafx.scene.layout.Pane; +import javafx.scene.layout.VBox; +import me.lensferno.dogename.utils.DialogMaker; +import me.lensferno.dogename.data.History; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +public class HistoryPaneController extends VBox { + Logger log= LogManager.getLogger(HitokotoPaneController.class); + + History history; + Pane rootPane; + + public static final ObservableList shownHistoryList = FXCollections.observableArrayList(); + + public HistoryPaneController(History history, Pane rootPane){ + FXMLLoader loader=new FXMLLoader(getClass().getResource("/me/lensferno/dogename/FXMLs/HistoryPane.fxml")); + loader.setRoot(this); + loader.setController(this); + this.history=history; + this.rootPane=rootPane; + + try { + loader.load(); + shownHistoryList.setAll(history.getHistoryList()); + historyList.setItems(shownHistoryList); + searchBar.textProperty().addListener((observable, oldValue, newValue) -> pointer = 0); + + }catch(Exception e){ + log.error("Error in loading history Fxml:"+e.toString()); + } + } + + @FXML + private JFXListView historyList; + + @FXML + private JFXTextField searchBar; + + @FXML + private JFXButton previousBtn; + + @FXML + private JFXButton nextBtn; + + private void pointOutSearchResult(int pointer){ + historyList.getSelectionModel().select(pointer); + } + + int pointer=0; + + @FXML + void upSearch(ActionEvent event) { + + String searchText=searchBar.getText(); + String[] historyArrayList =history.getHistoryList().toArray(new String[0]); + + if (historyArrayList.length==0){ + return; + } + + if (pointer>historyArrayList.length-1||pointer<0){ + pointer=historyArrayList.length-1; + } + + while (!historyArrayList[pointer].contains(searchText)){ + pointer--; + if (pointer<0){ + pointer=historyArrayList.length-1; + return; + } + } + pointOutSearchResult(pointer); + pointer--; + if (pointer<0){ + pointer=historyArrayList.length-1; + } + + } + + @FXML + void downSearch(ActionEvent event) { + + String searchText=searchBar.getText(); + String[] historyArrayList =history.getHistoryList().toArray(new String[0]); + + if (historyArrayList.length==0){ + return; + } + + if (pointer>historyArrayList.length-1||pointer<0){ + pointer=0; + } + + while (!historyArrayList[pointer].contains(searchText)){ + pointer++; + if (pointer { + this.history.clearHistory(); + pointer=0; + }); + } + +} diff --git a/src/main/java/me/lensferno/dogename/controllers/HitokotoPaneController.java b/src/main/java/me/lensferno/dogename/controllers/HitokotoPaneController.java new file mode 100644 index 0000000..7f97b2b --- /dev/null +++ b/src/main/java/me/lensferno/dogename/controllers/HitokotoPaneController.java @@ -0,0 +1,38 @@ +package me.lensferno.dogename.controllers; + +import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; +import javafx.scene.layout.VBox; +import javafx.scene.text.Text; + +import java.util.logging.Logger; + +public class HitokotoPaneController extends VBox { + + Logger log=Logger.getLogger("HitokotoPaneLogger"); + + @FXML + private Text hitokotoContent; + + @FXML + private Text contentType; + + @FXML + private Text contentInfo; + + public HitokotoPaneController(String hitokoto, String from,String author,String creator,String type){ + + FXMLLoader loader=new FXMLLoader(getClass().getResource("/me/lensferno/dogename/FXMLs/HitokotoPane.fxml")); + loader.setRoot(this); + loader.setController(this); + try { + loader.load(); + }catch(Exception e){ + log.warning("Error to load Gushici pane FXML: "+e.toString()); + //e.printStackTrace(); + } + hitokotoContent.setText("『 "+hitokoto+" 』"); + contentInfo.setText("出自:"+from+"  |  作者:"+author+"  |  上传者:"+creator); + contentType.setText(type); + } +} diff --git a/src/main/java/me/lensferno/dogename/controllers/MainInterfaceController.java b/src/main/java/me/lensferno/dogename/controllers/MainInterfaceController.java new file mode 100644 index 0000000..2ac503a --- /dev/null +++ b/src/main/java/me/lensferno/dogename/controllers/MainInterfaceController.java @@ -0,0 +1,337 @@ +package me.lensferno.dogename.controllers; + +import com.jfoenix.controls.*; +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; +import javafx.scene.Parent; +import javafx.scene.Scene; +import javafx.scene.control.*; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.layout.Pane; +import javafx.stage.Stage; +import javafx.stage.StageStyle; +import me.lensferno.dogename.utils.DialogMaker; +import me.lensferno.dogename.choose.Chooser; +import me.lensferno.dogename.configs.ConfigLoader; +import me.lensferno.dogename.configs.MainConfig; +import me.lensferno.dogename.configs.VoiceConfig; +import me.lensferno.dogename.data.History; +import me.lensferno.dogename.data.NameData; +import me.lensferno.dogename.ocr.Ocr; +import me.lensferno.dogename.voice.Token; +import me.lensferno.dogename.voice.TokenManager; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.HashSet; +import java.util.Random; + +public final class MainInterfaceController { + + public JFXTextArea message; + + //ConfigLoader configLoader=new ConfigLoader(); + + Token token; + TokenManager tokenManager=new TokenManager(); + + Ocr ocrTool=null; + + History history=new History(); + + @FXML + private Pane rootPane; + + @FXML + private JFXRadioButton nameChoose; + + @FXML + private JFXButton showHistoryBtn; + + @FXML + private JFXRadioButton numbChoose; + + @FXML + private JFXButton anPaiBtn; + + @FXML + private JFXButton anPaiX10Btn; + + @FXML + private Pane mainPane; + + @FXML + private Label topBar; + + @FXML + private JFXButton showNameMangerButton; + + @FXML + private Label chosen_1; + + @FXML + private ImageView mainView; + + @FXML + private JFXButton miniModeBtn; + + @FXML + private Label chosen_2; + + public MainInterfaceController(){ + history.loadHistory(); + nameData.readIgnoreList(); + tokenManager.init(); + this.ignoreNameList=nameData.getIgnoreNameList(); + this.ignoreNumberList=nameData.getIgnoreNumberList(); + if(tokenManager.getTokenStatus().equals("ok")){ + token=tokenManager.getToken(); + } + } + + MainConfig mainConfig; + VoiceConfig voiceConfig; + + public void bindProperties(){ + nameChoose.selectedProperty().bindBidirectional(mainConfig.nameChoosePropertyProperty()); + //mainConfig.nameChoosePropertyProperty().not() + + numbChoose.selectedProperty().bind(mainConfig.nameChoosePropertyProperty().not()); + numbChoose.selectedProperty().unbind(); + +/* + mainConfig.nameChoosePropertyProperty().addListener((observable, oldValue, newValue) -> { + //numbChoose.selectedProperty().unbind(); + numbChoose.setSelected(oldValue); + });*/ + + } + + public void setImg(InputStream stream){ + mainView.setImage(new Image(stream)); + } + + public void setUpConfig(ConfigLoader configLoader){ + mainConfig=configLoader.readConfigFromFile("files"+ File.separator +"Config.json"); + voiceConfig=configLoader.readVoiceConfigFromFile("files"+ File.separator +"VoiceConfig.json"); + } + + @FXML + void showProgramInfo(ActionEvent event) { + new DialogMaker(rootPane).createDialogWithOneBtn("程序信息",new ProgramInfoPaneController(rootPane)); + } + + + @FXML + void showNameManger(ActionEvent event) { + + if (chooser.isRunning()){ + new DialogMaker(rootPane).createMessageDialog("(・。・)","安排中......\n为保证运行的稳定,此时还不能进行该操作哦。"); + return; + } + NameManagerPaneController nameManagerPaneController =new NameManagerPaneController(nameData,rootPane,ocrTool); + new DialogMaker(rootPane).createDialogWithOneBtn("名单管理",nameManagerPaneController); + } + + + + @FXML + void showNunberSetting(ActionEvent event) { + + if (chooser.isRunning()){ + new DialogMaker(rootPane).createMessageDialog("(・。・)","安排中......\n为保证运行的稳定,此时还不能进行该操作哦。"); + return; + } + NumberSettingsPaneController numberSettingsPaneController =new NumberSettingsPaneController(nameData); + numberSettingsPaneController.bindProperties(mainConfig); + new DialogMaker(rootPane).createDialogWithOneBtn("调整数字",numberSettingsPaneController); + + } + + Logger log= LogManager.getLogger(); + + @FXML + void miniMode(ActionEvent event) { + FXMLLoader loader=new FXMLLoader(getClass().getResource("/me/lensferno/dogename/FXMLs/MiniPane.fxml")); + Parent parent; + try { + parent=loader.load(); + } catch (IOException e) { + log.error("Error in loading MiniPane Fxml:"+e); + return; + } + + Scene miniScene=new Scene(parent,300,134); + Stage miniStage=new Stage(); + miniStage.setScene(miniScene); + miniStage.initStyle(StageStyle.UNDECORATED); + + MiniPaneController miniPaneController=loader.getController(); + miniPaneController.setBase(history,nameData,token,voiceConfig,mainConfig); + + Stage currentStage=(Stage)anPaiBtn.getScene().getWindow(); + miniPaneController.setForwStage(currentStage); + + miniPaneController.setCurrentStage(miniStage); + miniPaneController.setCurrentScene(miniScene); + + miniPaneController.setListeners(); + + miniStage.show(); + currentStage.close(); + + } + + @FXML + void showSettings(ActionEvent event) { + + SettingsPaneController settingsPaneController =new SettingsPaneController(); + + settingsPaneController.setToggleGroup(); + settingsPaneController.bindProperties(mainConfig); + + settingsPaneController.setVoiceConfig(voiceConfig); + + settingsPaneController.setRootPane(rootPane); + + settingsPaneController.setNameData(nameData); + + new DialogMaker(rootPane).createDialogWithOneBtn("更多设置",settingsPaneController); + } + + @FXML + void showHistory(ActionEvent event) { + + HistoryPaneController historyPaneController =new HistoryPaneController(history,rootPane); + + new DialogMaker(rootPane).createDialogWithOneBtn("历史记录",historyPaneController); + } + + Random random=new Random(); + + HashSet ignoreNameList; + HashSet ignoreNumberList; + + NameData nameData=new NameData(); + //boolean isRunning=false; + + Chooser chooser=new Chooser(); + + @FXML + void anPai() { + + if(chooser.isRunning()){ + chooser.setForceStop(true); + anPaiBtn.setText("安排一下"); + return; + } + + if(mainConfig.isRandomTimesProperty()) { + mainConfig.setCycleTimesProperty(100+random.nextInt(151)); + } + + if(mainConfig.isNameChooseProperty()){ + runNameMode(chooser); + }else { + runNumberMode(chooser); + } + + } + + + public void setToggleGroup(){ + ToggleGroup toggleGroup =new ToggleGroup(); + nameChoose.setToggleGroup(toggleGroup); + numbChoose.setToggleGroup(toggleGroup); + } + + private void runNameMode(Chooser chooser){ + + if(nameData.isEmpty()){ + new DialogMaker(rootPane).createMessageDialog("哦霍~","现在名单还是空的捏~请前往名单管理添加名字 或 使用数字挑选法。"); + return; + } + + if((nameData.getIgnoreNameList().size()>=nameData.getSize())&&mainConfig.isIgnorePastProperty()){ + + if(mainConfig.isEqualModeProperty()) { + new DialogMaker(rootPane).createDialogWithOKAndCancel("啊?", "全部名字都被点完啦!\n要把名字的忽略列表重置吗?", e ->nameData.clearNameIgnoreList()); + }else { + new DialogMaker(rootPane).createMessageDialog("啊?", "全部名字都被点完啦!\n请多添加几个名字 或 点击“机会均等”的“重置”按钮。"); + } + return; + } + + anPaiBtn.setText("不玩了!"); + + chooser.set(chosen_1.textProperty(),chosen_2.textProperty(),anPaiBtn,history,nameData,token,voiceConfig); + + chooser.run( + (short)(100-mainConfig.getSpeedProperty()) , + mainConfig.getCycleTimesProperty(), + mainConfig.isIgnorePastProperty(), + mainConfig.isEqualModeProperty(), + mainConfig.isVoicePlayProperty() + ); + + } + + + private void runNumberMode(Chooser chooser){ + + try{ + + int minNumber=Integer.parseInt(mainConfig.getMinNumberProperty()); + int maxNumber=Integer.parseInt(mainConfig.getMaxNumberProperty()); + + if(maxNumber-minNumber<=0){ + new DialogMaker(rootPane).createMessageDialog("嗯哼?","数字要前小后大啊~"); + return; + } + + if(nameData.getIgnoreNumberList().size()>=(maxNumber-minNumber+1) && mainConfig.isIgnorePastProperty()){ + if(mainConfig.isEqualModeProperty()) { + new DialogMaker(rootPane).createDialogWithOKAndCancel("啊?", "全部数字都被点完啦!\n要把数字的忽略列表重置吗?", e ->nameData.clearNumberIgnoreList()); + }else { + new DialogMaker(rootPane).createMessageDialog("啊?", "全部数字都被点完啦!\n请扩大数字范围 或 点击“机会均等”的“重置”按钮。"); + } + return; + } + + }catch (Exception e){ + new DialogMaker(rootPane).createMessageDialog("嗯哼?","输入个有效的数字啊~"); + return; + } + + anPaiBtn.setText("不玩了!"); + + chooser.set(chosen_1.textProperty(),chosen_2.textProperty(),anPaiBtn,history,nameData,token,voiceConfig); + + chooser.run( + Short.parseShort(mainConfig.getMaxNumberProperty()), + Short.parseShort(mainConfig.getMinNumberProperty()), + (short)(100-mainConfig.getSpeedProperty()) , + mainConfig.getCycleTimesProperty(), + mainConfig.isIgnorePastProperty(), + mainConfig.isEqualModeProperty(), + mainConfig.isVoicePlayProperty() + ); + } + + public Label getTopBar() { + return topBar; + } + + public Pane getRootPane() { + return rootPane; + } + + public MainConfig getMainConfig() { + return mainConfig; + } +} diff --git a/src/main/java/me/lensferno/dogename/controllers/MiniPaneController.java b/src/main/java/me/lensferno/dogename/controllers/MiniPaneController.java new file mode 100644 index 0000000..d8cd9ba --- /dev/null +++ b/src/main/java/me/lensferno/dogename/controllers/MiniPaneController.java @@ -0,0 +1,191 @@ +package me.lensferno.dogename.controllers; + +import com.jfoenix.controls.JFXButton; +import javafx.event.ActionEvent; +import javafx.event.EventHandler; +import javafx.fxml.FXML; +import javafx.scene.Scene; +import javafx.scene.control.Label; +import javafx.scene.input.MouseEvent; +import javafx.scene.input.TouchEvent; +import javafx.scene.layout.Pane; +import javafx.stage.Stage; +import me.lensferno.dogename.utils.DialogMaker; +import me.lensferno.dogename.choose.Chooser; +import me.lensferno.dogename.configs.MainConfig; +import me.lensferno.dogename.configs.VoiceConfig; +import me.lensferno.dogename.controllers.WindowListeners.MoveWindowByMouse; +import me.lensferno.dogename.controllers.WindowListeners.MoveWindowByTouch; +import me.lensferno.dogename.data.History; +import me.lensferno.dogename.data.NameData; +import me.lensferno.dogename.voice.Token; + +import java.util.Random; + +public class MiniPaneController { + + @FXML + private Label chosenNameLabel; + + @FXML + private JFXButton anPaiBtn; + + @FXML + private JFXButton miniModeBtn; + + Stage forwStage; + + public Stage getForwStage() { + return forwStage; + } + + public void setForwStage(Stage forwStage) { + this.forwStage = forwStage; + } + + private Random random=new Random(); + private NameData nameData; + private Pane rootPane; + private History history; + private Token token; + private VoiceConfig voiceConfig; + + Stage currentStage; + Scene currentScene; + + public void setCurrentScene(Scene currentScene) { + this.currentScene = currentScene; + } + + public void setCurrentStage(Stage currentStage) { + this.currentStage = currentStage; + } + + public void setBase(History history, NameData nameData, Token token, VoiceConfig voiceConfig, MainConfig mainConfig){ + + this.history = history; + this.nameData = nameData; + + this.token=token; + + this.voiceConfig=voiceConfig; + + this.mainConfig=mainConfig; + + } + + @FXML + void recoverMode(ActionEvent event) { + this.forwStage.show(); + currentStage.close(); + } + + public void setListeners(){ + EventHandler mouseHandler=new MoveWindowByMouse(currentStage); + chosenNameLabel.setOnMousePressed(mouseHandler); + chosenNameLabel.setOnMouseDragged(mouseHandler); + + EventHandler touchHandler=new MoveWindowByTouch(currentStage); + chosenNameLabel.setOnTouchPressed(touchHandler); + chosenNameLabel.setOnTouchMoved(touchHandler); + + anPaiBtn.setOnMousePressed(mouseHandler); + anPaiBtn.setOnMouseDragged(mouseHandler); + + anPaiBtn.setOnTouchPressed(touchHandler); + anPaiBtn.setOnTouchMoved(touchHandler); + + miniModeBtn.setOnMousePressed(mouseHandler); + miniModeBtn.setOnMouseDragged(mouseHandler); + + miniModeBtn.setOnTouchPressed(touchHandler); + miniModeBtn.setOnTouchMoved(touchHandler); + } + + + + private MainConfig mainConfig; + Chooser chooser=new Chooser(); + + @FXML + void anPai() { + if(chooser.isRunning()){ + chooser.setForceStop(true); + anPaiBtn.setText("安排一下"); + return; + } + + if(mainConfig.isRandomTimesProperty()) { + mainConfig.setCycleTimesProperty(100+random.nextInt(151)); + } + + if(mainConfig.isNameChooseProperty()){ + runNameMode(chooser); + }else { + runNumberMode(chooser); + } + + } + + + private void runNameMode(Chooser chooser){ + + if(nameData.isEmpty()){ + return; + } + + if((nameData.getIgnoreNameList().size()>=nameData.getSize())&&mainConfig.isIgnorePastProperty()){ + + return; + } + + anPaiBtn.setText("不玩了!"); + + chooser.set(chosenNameLabel.textProperty(),chosenNameLabel.textProperty(),anPaiBtn,history,nameData,token,voiceConfig); + + chooser.run( + (short)(100-mainConfig.getSpeedProperty()) , + mainConfig.getCycleTimesProperty(), + mainConfig.isIgnorePastProperty(), + mainConfig.isEqualModeProperty(), + mainConfig.isVoicePlayProperty() + ); + + } + + + private void runNumberMode(Chooser chooser){ + + try{ + + int minNumber=Integer.parseInt(mainConfig.getMinNumberProperty()); + int maxNumber=Integer.parseInt(mainConfig.getMaxNumberProperty()); + + if(maxNumber-minNumber<=0){ + return; + } + + if(nameData.getIgnoreNumberList().size()>=(maxNumber-minNumber+1) && mainConfig.isIgnorePastProperty()){ + return; + } + + }catch (Exception e){ + new DialogMaker(rootPane).createMessageDialog("嗯哼?","倒是输入个有效的数字啊~"); + return; + } + + anPaiBtn.setText("不玩了!"); + + chooser.set(chosenNameLabel.textProperty(),chosenNameLabel.textProperty(),anPaiBtn,history,nameData,token,voiceConfig); + + chooser.run( + Short.parseShort(mainConfig.getMaxNumberProperty()), + Short.parseShort(mainConfig.getMinNumberProperty()), + (short)(100-mainConfig.getSpeedProperty()) , + mainConfig.getCycleTimesProperty(), + mainConfig.isIgnorePastProperty(), + mainConfig.isEqualModeProperty(), + mainConfig.isVoicePlayProperty() + ); + } +} diff --git a/src/main/java/me/lensferno/dogename/controllers/NameManagerPaneController.java b/src/main/java/me/lensferno/dogename/controllers/NameManagerPaneController.java new file mode 100644 index 0000000..443496f --- /dev/null +++ b/src/main/java/me/lensferno/dogename/controllers/NameManagerPaneController.java @@ -0,0 +1,191 @@ +package me.lensferno.dogename.controllers; + +import com.jfoenix.controls.*; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; +import javafx.scene.Parent; +import javafx.scene.Scene; +import javafx.scene.layout.Pane; +import javafx.scene.layout.VBox; +import javafx.stage.FileChooser; +import javafx.stage.Stage; +import me.lensferno.dogename.utils.DialogMaker; +import me.lensferno.dogename.data.NameData; +import me.lensferno.dogename.ocr.Ocr; +import me.lensferno.dogename.utils.Clipboard; + +import java.io.File; +import java.util.logging.Logger; + +public class NameManagerPaneController extends VBox { + + NameData nameData; + Pane rootPane; + Ocr ocrTool; + + Logger log = Logger.getLogger("NameManagerPaneLOgger"); + + public static final ObservableList shownNameList = FXCollections.observableArrayList(); + + public NameManagerPaneController(NameData nameData, Pane rootPane, Ocr ocrTool){ + FXMLLoader loader=new FXMLLoader(getClass().getResource("/me/lensferno/dogename/FXMLs/NameManagerPane.fxml")); + loader.setRoot(this); + loader.setController(this); + try { + loader.load(); + }catch(Exception e){ + e.printStackTrace(); + } + this.nameData=nameData; + this.rootPane=rootPane; + + shownNameList.setAll(nameData.getNameList()); + this.nameList.setItems(shownNameList); + + this.ocrTool=ocrTool; + } + + @FXML + private JFXListView nameList; + + @FXML + private JFXButton deleteAll; + + @FXML + private JFXButton addName; + + @FXML + private JFXButton deleteName; + + @FXML + private JFXTextArea inputName; + + + @FXML + void deleteName(ActionEvent event) { + + new DialogMaker(rootPane).createDialogWithOKAndCancel( + "问一下", + "真的要这个名字吗?该操作无法撤销,除非您已经备份了名单。", + e -> { + String deletedName = nameList.getSelectionModel().getSelectedItems().get(0); + + nameData.delete(deletedName); + shownNameList.remove(deletedName); + + nameData.saveToFile(); + System.gc(); + }); + + } + + @FXML + void deleteAllName(ActionEvent event) { + new DialogMaker(rootPane).createDialogWithOKAndCancel( + "问一下", + "真的要删掉所有名字吗?该操作无法撤销,除非您已经备份了名单。", + e -> { + //delete all name + nameData.deleteAll(); + shownNameList.setAll(nameData.getNameList()); + nameData.saveToFile(); + }); + + } + + @FXML + void makeMass(ActionEvent event) { + nameData.makeMass(); + shownNameList.clear(); + shownNameList.setAll(nameData.getNameList()); + nameData.saveToFile(); + } + + @FXML + void exoprtNameList(ActionEvent event) { + FileChooser fileChooser = new FileChooser(); + fileChooser.setInitialFileName("nameList.txt"); + fileChooser.setTitle("想保存到哪?"); + File file = fileChooser.showSaveDialog(rootPane.getScene().getWindow()); + nameData.exportNameList(file); + System.gc(); + } + + @FXML + void importNameList(ActionEvent event) { + + new DialogMaker(rootPane).createDialogWithOKAndCancel( + "问一下", + "导入恢复名单会覆盖当前已有的名单,是否继续?", + event1 -> { + FileChooser fileChooser = new FileChooser(); + fileChooser.setTitle("告诉我在哪?"); + File file = fileChooser.showOpenDialog(rootPane.getScene().getWindow()); + + nameData.importNameList(file); + shownNameList.clear(); + shownNameList.setAll(nameData.getNameList()); + + nameData.clearNameIgnoreList(); + nameData.clearNumberIgnoreList(); + + nameData.saveToFile(); + System.gc(); + }); + + } + + @FXML + void addName(ActionEvent event) { + + if(inputName.getText().equals("")){ + new DialogMaker(rootPane).createMessageDialog("诶诶诶~","输入框怎么是空的呢?"); + return; + } + + nameData.add(inputName.getText()); + + shownNameList.clear(); + shownNameList.setAll(nameData.getNameList()); + + nameData.saveToFile(); + inputName.clear(); + System.gc(); + } + + @FXML + void addNameFromScreen(ActionEvent event) { + FXMLLoader fxmlLoader; + Parent parent; + + Stage stage=new Stage(); + + try{ + fxmlLoader=new FXMLLoader(getClass().getResource("/me/lensferno/dogename/FXMLs/OcrPane.fxml")); + parent=fxmlLoader.load(); + }catch (Exception e){ + log.warning("Error to load main interface FXML :"+e.toString()); + return; + } + + Scene scene=new Scene(parent,515,604); + stage.setTitle("Ocr模块"); + stage.setScene(scene); + log.fine("窗口加载完成"); + + OcrPaneController ocrPaneController= fxmlLoader.getController(); + ocrPaneController.setpStage((Stage)inputName.getScene().getWindow()); + stage.show(); + + } + + @FXML + void copyTo(ActionEvent event) { + inputName.setText(inputName.getText()+ Clipboard.getClipboardString()); + } + + +} diff --git a/src/main/java/me/lensferno/dogename/controllers/NumberSettingsPaneController.java b/src/main/java/me/lensferno/dogename/controllers/NumberSettingsPaneController.java new file mode 100644 index 0000000..128b3b6 --- /dev/null +++ b/src/main/java/me/lensferno/dogename/controllers/NumberSettingsPaneController.java @@ -0,0 +1,39 @@ +package me.lensferno.dogename.controllers; + +import com.jfoenix.controls.JFXTextField; +import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; +import javafx.scene.layout.VBox; +import me.lensferno.dogename.configs.MainConfig; +import me.lensferno.dogename.data.NameData; + +public class NumberSettingsPaneController extends VBox { + NameData nameData; + public NumberSettingsPaneController(NameData nameData){ + FXMLLoader loader=new FXMLLoader(getClass().getResource("/me/lensferno/dogename/FXMLs/NumberSettingPane.fxml")); + loader.setRoot(this); + loader.setController(this); + try { + loader.load(); + }catch(Exception e){ + e.printStackTrace(); + } + this.nameData=nameData; + } + + @FXML + private JFXTextField minValueField; + + @FXML + private JFXTextField maxValueField; + + public void bindProperties(MainConfig mainConfig){ + + minValueField.textProperty().bindBidirectional(mainConfig.minNumberPropertyProperty()); + minValueField.textProperty().addListener((observable, oldValue, newValue) -> nameData.clearNumberIgnoreList() ); + + maxValueField.textProperty().bindBidirectional(mainConfig.maxNumberPropertyProperty()); + maxValueField.textProperty().addListener((observable, oldValue, newValue) -> nameData.clearNumberIgnoreList() ); + } + +} diff --git a/src/main/java/me/lensferno/dogename/controllers/OcrPaneController.java b/src/main/java/me/lensferno/dogename/controllers/OcrPaneController.java new file mode 100644 index 0000000..e4a05e0 --- /dev/null +++ b/src/main/java/me/lensferno/dogename/controllers/OcrPaneController.java @@ -0,0 +1,93 @@ +package me.lensferno.dogename.controllers; + +import com.jfoenix.controls.JFXSpinner; +import javafx.application.Platform; +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.scene.control.TextArea; +import javafx.scene.text.Text; +import javafx.stage.Stage; +import me.lensferno.dogename.ocr.Ocr; +import me.lensferno.dogename.ocr.ScreenCapture; +import me.lensferno.dogename.utils.Clipboard; + +public class OcrPaneController { + + Ocr ocr; + + @FXML + private TextArea ocrText; + + @FXML + private Text statusText; + + @FXML + private JFXSpinner loadingSpinner; + + Stage pStage; + + public void setpStage(Stage pStage) { + this.pStage = pStage; + } + + @FXML + void addNew(ActionEvent event) { + + Stage stage=(Stage)ocrText.getScene().getWindow(); + stage.hide(); + pStage.hide(); + + //等待系统动画结束 + try { + Thread.sleep(500); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + boolean captureSuccess= ScreenCapture.getScreenCapture(); + + if(!captureSuccess){ + statusText.setText("状态:截屏失败。"); + System.out.println("状态:截屏失败。"); + pStage.show(); + stage.show(); + return; + } + loadingSpinner.setVisible(true); + + if(ocr==null){ + ocr=new Ocr(); + ocr.init(); + } + + new Thread(()->{ + boolean ocrSuccrss=ocr.identifyPrecisely(ScreenCapture.SCREEN_CAPTURE_LOCA); + + if (ocrSuccrss) { + + Platform.runLater(()->{ + ocrText.setText(ocrText.getText()+ocr.getResult()); + statusText.setText("状态:成功。"); + System.out.println("状态:成功。"); + loadingSpinner.setVisible(false); + + }); + }else { + Platform.runLater(()->{ + ocrText.setText(ocr.getResult()); + statusText.setText("状态:失败。"); + System.out.println("状态:失败。"); + loadingSpinner.setVisible(false); + + }); + } + }).start(); + pStage.show(); + stage.show(); + } + + @FXML + void copyText(ActionEvent event) { + Clipboard.copyToClipboard(ocrText.getText()); + } +} diff --git a/src/main/java/me/lensferno/dogename/controllers/ProgramInfoPaneController.java b/src/main/java/me/lensferno/dogename/controllers/ProgramInfoPaneController.java new file mode 100644 index 0000000..0e399d7 --- /dev/null +++ b/src/main/java/me/lensferno/dogename/controllers/ProgramInfoPaneController.java @@ -0,0 +1,137 @@ +package me.lensferno.dogename.controllers; + +import com.jfoenix.controls.JFXButton; +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; +import javafx.scene.control.TextArea; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.layout.Pane; +import javafx.scene.layout.VBox; +import javafx.scene.text.Font; +import javafx.scene.text.FontWeight; +import javafx.scene.text.Text; +import me.lensferno.dogename.DataReleaser; +import me.lensferno.dogename.utils.DialogMaker; +import me.lensferno.dogename.resources.LicenseText; + +import java.awt.*; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; + +public class ProgramInfoPaneController extends VBox { + + @FXML + public ImageView dogeView; + + Pane rootPane; + + public ProgramInfoPaneController(Pane rootPane){ + FXMLLoader loader=new FXMLLoader(getClass().getResource("/me/lensferno/dogename/FXMLs/ProgramInfoPane.fxml")); + loader.setRoot(this); + loader.setController(this); + try { + loader.load(); + }catch(Exception e){ + e.printStackTrace(); + } + Image dogeImage=new Image(DataReleaser.getDogenameStream()); + dogeView.setImage(dogeImage); + this.rootPane=rootPane; + } + + + @FXML + void showLicense(ActionEvent event) { + TextArea textArea=new TextArea(LicenseText.text); + textArea.setFont(Font.font("Microsoft YaHei",14)); + textArea.setMinWidth(600); + textArea.setPrefHeight(400); + textArea.setEditable(false); + new DialogMaker(rootPane).createDialogWithOneBtn("开源协议(LGPL v3)",textArea); + } + + @FXML + void showLibLicense(ActionEvent event) { + TextArea textArea=new TextArea(LicenseText.libLicense); + textArea.setFont(Font.font("Microsoft YaHei",14)); + textArea.setMinWidth(600); + textArea.setPrefHeight(400); + textArea.setEditable(false); + new DialogMaker(rootPane).createDialogWithOneBtn("其他开源许可",textArea); + } + + @FXML + void showHelp(ActionEvent event) { + + JFXButton YesButton = new JFXButton("好的~去吧去吧"); + YesButton.setFont(Font.font("Microsoft YaHei", FontWeight.BOLD,14)); + YesButton.setPrefWidth(160); + YesButton.setPrefHeight(40); + YesButton.addEventHandler(ActionEvent.ACTION,e -> jumpToHelp()); + + JFXButton cancelButton = new JFXButton("算了算了"); + cancelButton.setFont(Font.font("Microsoft YaHei", FontWeight.BOLD,14)); + cancelButton.setPrefWidth(100); + cancelButton.setPrefHeight(40); + + Text messageText=new Text("即将跳转到本程序Github页面上的使用帮助,是否继续?"); + messageText.setFont(Font.font("Microsoft YaHei",14)); + + new DialogMaker(rootPane).createDialog("查看帮助",messageText,cancelButton,YesButton); + + } + + private void jumpToHelp(){ + try { + Desktop.getDesktop().browse(new URI("https://github.com/lensferno/dogename/blob/main/res/usage.md")); + } catch (IOException | URISyntaxException e) { + e.printStackTrace(); + } + } + + @FXML + void viewCode(ActionEvent event) { + JFXButton githubButton = new JFXButton("前往Github查看"); + githubButton.setFont(Font.font("Microsoft YaHei", FontWeight.BOLD,14)); + githubButton.setPrefWidth(150); + githubButton.setPrefHeight(40); + githubButton.addEventHandler(ActionEvent.ACTION,e -> jumpToGithub()); + + JFXButton giteeButton = new JFXButton("前往Gitee查看"); + giteeButton.setFont(Font.font("Microsoft YaHei", FontWeight.BOLD,14)); + giteeButton.setPrefWidth(150); + giteeButton.setPrefHeight(40); + giteeButton.addEventHandler(ActionEvent.ACTION,e -> jumpToGitee()); + + JFXButton cancelButton = new JFXButton("哪都不去"); + cancelButton.setFont(Font.font("Microsoft YaHei", FontWeight.BOLD,14)); + cancelButton.setPrefWidth(100); + cancelButton.setPrefHeight(40); + + Text messageText=new Text("Dogename在Github和码云(Gitee)都发布有代码和介绍。\n您想去哪里?\nGithub:将跳转到https://github.com/lensferno/dogename\nGitee:将跳转到https://gitee.com/lensferno/dogename"); + messageText.setFont(Font.font("Microsoft YaHei",14)); + + new DialogMaker(rootPane).createDialog("查看源代码",messageText,cancelButton,githubButton,giteeButton); + + } + + private void jumpToGithub(){ + try { + Desktop.getDesktop().browse(new URI("https://github.com/lensferno/dogename")); + } catch (IOException | URISyntaxException e) { + e.printStackTrace(); + } + } + + private void jumpToGitee(){ + try { + Desktop.getDesktop().browse(new URI("https://gitee.com/lensferno/dogename")); + } catch (IOException | URISyntaxException e) { + e.printStackTrace(); + } + } + +} diff --git a/src/main/java/me/lensferno/dogename/controllers/SettingsPaneController.java b/src/main/java/me/lensferno/dogename/controllers/SettingsPaneController.java new file mode 100644 index 0000000..8065f8f --- /dev/null +++ b/src/main/java/me/lensferno/dogename/controllers/SettingsPaneController.java @@ -0,0 +1,157 @@ +package me.lensferno.dogename.controllers; + +import com.jfoenix.controls.JFXCheckBox; +import com.jfoenix.controls.JFXRadioButton; +import com.jfoenix.controls.JFXSlider; +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; +import javafx.scene.control.ToggleGroup; +import javafx.scene.layout.Pane; +import javafx.scene.layout.VBox; +import me.lensferno.dogename.utils.DialogMaker; +import me.lensferno.dogename.configs.MainConfig; +import me.lensferno.dogename.configs.VoiceConfig; +import me.lensferno.dogename.data.NameData; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + + +public class SettingsPaneController extends VBox { + + @FXML + private JFXCheckBox showSayingBtn; + + @FXML + private JFXCheckBox newAlgoBtn; + + @FXML + private JFXSlider cycleTimesBar; + + @FXML + private JFXCheckBox voicePlayBtn; + + @FXML + private JFXSlider speedBar; + + @FXML + private JFXCheckBox equalModeBtn; + + @FXML + private JFXRadioButton ignoreOnce; + + @FXML + private JFXRadioButton chooseOnce; + + @FXML + private JFXRadioButton randomTimes; + + @FXML + private JFXRadioButton fixedTimes; + + MainConfig mainConfig; + VoiceConfig voiceConfig; + + Pane rootPane; + + NameData nameData; + + Logger log = LogManager.getLogger("SettingsPaneControllerLogger"); + + public SettingsPaneController(){ + FXMLLoader loader=new FXMLLoader(getClass().getResource("/me/lensferno/dogename/FXMLs/SettingsPane.fxml")); + loader.setRoot(this); + loader.setController(this); + try { + loader.load(); + }catch(Exception e){ + log.warn("Error to load settings pane FXML: "+e.toString()); + } + } + + public void setMainConfig(MainConfig mainConfig) { + this.mainConfig = mainConfig; + } + + public void setVoiceConfig(VoiceConfig voiceConfig) { + this.voiceConfig = voiceConfig; + } + + public void setRootPane(Pane rootPane){ + this.rootPane=rootPane; + } + + public void bindProperties(MainConfig mainConfig){ + setMainConfig(mainConfig); + + ignoreOnce.selectedProperty().bindBidirectional(mainConfig.ignorePastPropertyProperty()); + chooseOnce.setSelected(!mainConfig.isIgnorePastProperty()); + + randomTimes.selectedProperty().bindBidirectional(mainConfig.randomTimesPropertyProperty()); + fixedTimes.setSelected(!mainConfig.isRandomTimesProperty()); + + equalModeBtn.selectedProperty().bindBidirectional(mainConfig.equalModePropertyProperty()); + + newAlgoBtn.selectedProperty().bindBidirectional(mainConfig.newAlgoPropertyProperty()); + + voicePlayBtn.selectedProperty().bindBidirectional(mainConfig.voicePlayPropertyProperty()); + + cycleTimesBar.valueProperty().bindBidirectional(mainConfig.cycleTimesPropertyProperty()); + + speedBar.valueProperty().bindBidirectional(mainConfig.speedPropertyProperty()); + + showSayingBtn.selectedProperty().bindBidirectional(mainConfig.showSayingProperty()); + + mainConfig.ignorePastPropertyProperty().addListener((observable, oldValue, isIgnorePast) -> { + if(!isIgnorePast) + { + //如果 忽略被点过的名字 被取消后就把机会均等模式的按钮给取消掉 + equalModeBtn.setSelected(false); + } + }); + } + + public void setToggleGroup(){ + ToggleGroup pastGroup=new ToggleGroup(); + chooseOnce.setToggleGroup(pastGroup); + ignoreOnce.setToggleGroup(pastGroup); + + ToggleGroup fixedTimesGroup=new ToggleGroup(); + randomTimes.setToggleGroup(fixedTimesGroup); + fixedTimes.setToggleGroup(fixedTimesGroup); + } + + @FXML + void showVoiceSettingsPane(ActionEvent event) { + VoiceSettingsPaneController voiceSettingsPaneController=new VoiceSettingsPaneController(); + voiceSettingsPaneController.bindPropertied(voiceConfig); + new DialogMaker(rootPane).createDialogWithOneBtn("语音设置",voiceSettingsPaneController); + } + + @FXML + void showEqualMode(ActionEvent event) { + new DialogMaker(rootPane).createMessageDialog("什么?", + "//有待补充。;-)"); + + } + + @FXML + void clearIgnoreList(ActionEvent event) { + new DialogMaker(rootPane).createDialogWithOKAndCancel("真的吗?","真的要重置吗?",(e)->{ + nameData.clearNumberIgnoreList(); + nameData.clearNameIgnoreList(); + }); + } + + @FXML + void equalBtnAction(ActionEvent event) { + if(!mainConfig.isIgnorePastProperty()){ + equalModeBtn.setSelected(false); + new DialogMaker(rootPane).createMessageDialog("且慢","无法在“概率均分”的模式下使用,如需使用请在“人人有份”模式下启用。"); + } + } + + public void setNameData(NameData nameData) { + this.nameData = nameData; + } +} diff --git a/src/main/java/me/lensferno/dogename/controllers/VoiceSettingsPaneController.java b/src/main/java/me/lensferno/dogename/controllers/VoiceSettingsPaneController.java new file mode 100644 index 0000000..4ac0631 --- /dev/null +++ b/src/main/java/me/lensferno/dogename/controllers/VoiceSettingsPaneController.java @@ -0,0 +1,85 @@ +package me.lensferno.dogename.controllers; + +import com.jfoenix.controls.JFXComboBox; +import com.jfoenix.controls.JFXSlider; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; +import javafx.scene.layout.VBox; +import me.lensferno.dogename.configs.VoiceConfig; + +import java.util.logging.Logger; + +public class VoiceSettingsPaneController extends VBox { + Logger log = Logger.getLogger("VoiceSettingsPaneControllerLogger"); + + VoiceConfig voiceConfig=new VoiceConfig(); + + + @FXML + private JFXSlider intonationBar; + + @FXML + private JFXComboBox speakerSelectBar; + + @FXML + private JFXSlider voiceSpeedBar; + + public static final ObservableList shownSpeakerList = FXCollections.observableArrayList(); + + private final String[] speakers={ + "1","0","3","4", + "106","110","111","103","5"}; + //度小宇=1,度小美=0,度逍遥=3,度丫丫=4 + //度博文=106,度小童=110,度小萌=111,度米朵=103,度小娇=5 + + public VoiceSettingsPaneController() { + FXMLLoader loader = new FXMLLoader(getClass().getResource("/me/lensferno/dogename/FXMLs/VoiceSettingsPane.fxml")); + loader.setRoot(this); + loader.setController(this); + try { + loader.load(); + } catch (Exception e) { + log.warning("Error to load settings pane FXML: " + e.toString()); + } + if(shownSpeakerList.isEmpty()) { + shownSpeakerList.addAll( + "度小宇=1", "度小美=0", "度逍遥=3", "度丫丫=4", + "度博文=106", "度小童=110", "度小萌=111", "度米朵=103", "度小娇=5"); + } + + speakerSelectBar.setItems(shownSpeakerList); + } + + public void bindPropertied(VoiceConfig voiceConfig){ + + this.voiceConfig=voiceConfig; + + //speakerSelectBar. + + speakerSelectBar.selectionModelProperty().addListener((observable, oldValue, newValue) -> { + System.out.println("what?"+newValue.getSelectedIndex()); + this.voiceConfig.setSpeaker(speakers[newValue.getSelectedIndex()]); + this.voiceConfig.setSelectedSpeaker(newValue.getSelectedIndex()); + }); + + //speakerSelectBar.setValue(this.voiceConfig.getSpeaker()); + + voiceSpeedBar.valueProperty().bindBidirectional(voiceConfig.speedProperty()); + intonationBar.valueProperty().bindBidirectional(voiceConfig.intonationProperty()); + + speakerSelectBar.getSelectionModel().select(this.voiceConfig.getSelectedSpeaker()); + } + + @FXML + void showAdvancedVoiceSettings(ActionEvent event) { + + } + + + +} + + diff --git a/src/main/java/me/lensferno/dogename/controllers/WindowListeners/MoveWindowByMouse.java b/src/main/java/me/lensferno/dogename/controllers/WindowListeners/MoveWindowByMouse.java new file mode 100644 index 0000000..6e839c7 --- /dev/null +++ b/src/main/java/me/lensferno/dogename/controllers/WindowListeners/MoveWindowByMouse.java @@ -0,0 +1,33 @@ +package me.lensferno.dogename.controllers.WindowListeners; + +import javafx.event.EventHandler; +import javafx.scene.input.MouseEvent; +import javafx.stage.Stage; + +public class MoveWindowByMouse implements EventHandler { + + private Stage primaryStage; + private double oldStageX; + private double oldStageY; + private double oldScreenX; + private double oldScreenY; + + public MoveWindowByMouse(Stage stage) { + this.primaryStage = stage; + } + + @Override + public void handle(MouseEvent e) { + if (e.getEventType() == MouseEvent.MOUSE_PRESSED) { + this.oldStageX = this.primaryStage.getX(); + this.oldStageY = this.primaryStage.getY(); + this.oldScreenX = e.getScreenX(); + this.oldScreenY = e.getScreenY(); + + } else if (e.getEventType() == MouseEvent.MOUSE_DRAGGED) { + this.primaryStage.setX(e.getScreenX() - this.oldScreenX + this.oldStageX); + this.primaryStage.setY(e.getScreenY() - this.oldScreenY + this.oldStageY); + } + + } +} diff --git a/src/main/java/me/lensferno/dogename/controllers/WindowListeners/MoveWindowByTouch.java b/src/main/java/me/lensferno/dogename/controllers/WindowListeners/MoveWindowByTouch.java new file mode 100644 index 0000000..130dbb7 --- /dev/null +++ b/src/main/java/me/lensferno/dogename/controllers/WindowListeners/MoveWindowByTouch.java @@ -0,0 +1,34 @@ +package me.lensferno.dogename.controllers.WindowListeners; + +import javafx.event.EventHandler; +import javafx.scene.input.TouchEvent; +import javafx.stage.Stage; + +public class MoveWindowByTouch implements EventHandler { + + private Stage primaryStage; + private double oldStageX; + private double oldStageY; + private double oldScreenX; + private double oldScreenY; + + public MoveWindowByTouch(Stage stage) { + this.primaryStage = stage; + } + + @Override + public void handle(TouchEvent e) { + if (e.getEventType() == TouchEvent.TOUCH_PRESSED) { + this.oldStageX = this.primaryStage.getX(); + this.oldStageY = this.primaryStage.getY(); + this.oldScreenX = e.getTouchPoint().getScreenX(); + this.oldScreenY = e.getTouchPoint().getScreenY(); + + } else if (e.getEventType() == TouchEvent.TOUCH_MOVED) { + this.primaryStage.setX(e.getTouchPoint().getScreenX() - this.oldScreenX + this.oldStageX); + this.primaryStage.setY(e.getTouchPoint().getScreenY() - this.oldScreenY + this.oldStageY); + } + + } +} + diff --git a/src/main/java/me/lensferno/dogename/data/History.java b/src/main/java/me/lensferno/dogename/data/History.java new file mode 100644 index 0000000..f4bbd18 --- /dev/null +++ b/src/main/java/me/lensferno/dogename/data/History.java @@ -0,0 +1,92 @@ +package me.lensferno.dogename.data; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.*; +import java.util.ArrayList; + +public class History { + + Logger log = LogManager.getLogger(); + + + private String HISTORY_FILE; + public static final String separator=File.separator; + + ArrayList history; + + public void loadHistory(){ + + HISTORY_FILE="files"+separator+"history.data"; + + try { + File historyFile = new File(HISTORY_FILE); + if (!historyFile.exists()) { + historyFile.getParentFile().mkdirs(); + historyFile.createNewFile(); + + history = new ArrayList<>(); + writeHistory(); + return; + } + + ObjectInputStream ois = new ObjectInputStream(new FileInputStream(historyFile)); + history = (ArrayList) ois.readObject(); + + } catch (EOFException e){ + history =new ArrayList<>(); + log.warn("History file is empty."); + writeHistory(); + }catch (Exception e) { + history = new ArrayList<>(); + log.error("Failed to load history file:"+e.toString()); + e.printStackTrace(); + } + } + + public ArrayList getHistoryList(){ + return history; + } + + public void addHistory(String name){ + if(history.size()>2000) { + history.clear(); + } + history.add((history.size() + 1) +". "+name); + + writeHistory(); + } + + private void writeHistory(){ + + HISTORY_FILE="files"+separator+"history.data"; + File historyFile=new File(HISTORY_FILE); + + try{ + + if (!historyFile.exists()) { + historyFile.createNewFile(); + } + + ObjectOutputStream oos =new ObjectOutputStream(new FileOutputStream(historyFile)); + oos.writeObject(history); + oos.close(); + }catch (Exception e){ + log.error("Error in writing history file:"+e); + } + } + + public int downSearch(){ + return 1; + } + + public int upSearch(){ + return 1; + } + + public void clearHistory(){ + this.history.clear(); + } + +} diff --git a/src/main/java/me/lensferno/dogename/data/NameData.java b/src/main/java/me/lensferno/dogename/data/NameData.java new file mode 100644 index 0000000..05f7ae0 --- /dev/null +++ b/src/main/java/me/lensferno/dogename/data/NameData.java @@ -0,0 +1,351 @@ +package me.lensferno.dogename.data; + +import com.google.gson.Gson; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.*; +import java.nio.charset.StandardCharsets; +import java.security.SecureRandom; +import java.util.*; + +public class NameData { + + Logger log = LogManager.getLogger("NameDataLogger"); + + private List nameList; + + private List chooseList; + private int listSize = 0; + + + HashSet ignoreNameList=new HashSet<>(); + + HashSet ignoreNumberList=new HashSet<>(); + + File dataFile ;//=new File("namelist.data"); + + boolean newAlgo=true; + SecureRandom secRandom =new SecureRandom(); + + //不做注释了,自己慢慢看。:) + + File nameIgnoreFile =new File("files"+File.separator+"IgnoredNameList.data"); + File numbIgnoreFile =new File("files"+File.separator+"IgnoredNumberList.data"); + + public void writeIgnoreList(String switchy){ + + if(!switchy.equals("not name")) { + try { + ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(nameIgnoreFile)); + oos.writeObject(ignoreNameList); + oos.close(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + if(!switchy.equals("not number")) { + try { + ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(numbIgnoreFile)); + oos.writeObject(ignoreNumberList); + oos.close(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + } + + + public void readIgnoreList(){ + + try{ + + if(!nameIgnoreFile.exists()){ + nameIgnoreFile.getParentFile().mkdirs(); + nameIgnoreFile.createNewFile(); + ignoreNameList= new HashSet<>(); + writeIgnoreList("not number"); + return; + } + + ObjectInputStream ois =new ObjectInputStream(new FileInputStream(nameIgnoreFile)); + this.ignoreNameList=(HashSet)ois.readObject(); + + }catch (EOFException e){ + ignoreNameList=new HashSet<>(); + log.warn("Past name list is empty."); + writeIgnoreList("not number"); + }catch (Exception e){ + ignoreNameList=new HashSet<>(); + writeIgnoreList("not number"); + log.warn("Failed to load past name list:"+e.toString()); + e.printStackTrace(); + } + + try{ + + if(!numbIgnoreFile.exists()){ + numbIgnoreFile.getParentFile().mkdirs(); + numbIgnoreFile.createNewFile(); + ignoreNumberList= new HashSet<>(); + writeIgnoreList("not name"); + return; + } + + ObjectInputStream ois =new ObjectInputStream(new FileInputStream(numbIgnoreFile)); + this.ignoreNumberList=(HashSet)ois.readObject(); + + }catch (EOFException e){ + ignoreNumberList=new HashSet<>(); + log.warn("Ignored number list is empty."); + writeIgnoreList("not name"); + }catch (Exception e){ + ignoreNumberList=new HashSet<>(); + log.warn("Failed to load ignored number list"); + writeIgnoreList("not name"); + e.printStackTrace(); + } + + log.info("There are "+ignoreNameList.size()+" names and "+ignoreNumberList.size()+" numbers ignored."); + } + + + public void clearNameIgnoreList(){ + ignoreNameList.clear(); + writeIgnoreList("not number"); + } + + public void clearNumberIgnoreList(){ + ignoreNumberList.clear(); + writeIgnoreList("not name"); + } + + + public HashSet getIgnoreNameList() { + return ignoreNameList; + } + + public void setIgnoreNameList(HashSet ignoreNameList) { + this.ignoreNameList = ignoreNameList; + } + + public HashSet getIgnoreNumberList() { + return ignoreNumberList; + } + + public void setIgnoreNumberList(HashSet ignoreNumberList) { + this.ignoreNumberList = ignoreNumberList; + } + + + public List getNameList() { + return nameList; + } + + public void exportNameList(File path) { + if(path!=null) { + try{ + FileOutputStream oos =new FileOutputStream(path); + oos.write(new Gson().toJson(nameList).getBytes(StandardCharsets.UTF_8)); + oos.close(); + log.info("Exported list to:"+path.getPath()); + }catch (Exception e){log.warn("error in export namelist: "+e.toString());e.printStackTrace();} + + } + } + + public void importNameList(File path) { + if(path!=null) { + + try{ + FileInputStream fis =new FileInputStream(path); + String temp; + BufferedReader bis=new BufferedReader(new InputStreamReader(fis, StandardCharsets.UTF_8)); + StringBuilder sb=new StringBuilder(); + + while ((temp = bis.readLine()) != null) { + sb.append(temp); + sb.append("\n"); + } + + nameList=new Gson().fromJson(sb.toString(),List.class); + log.info("Imported list from:"+path.getPath()); + }catch (Exception e){log.warn("error in import namelist:"+e.toString());e.printStackTrace();} + + } + + } + + public void makeMass() { + + HashSet alreadyList = new HashSet<>(); + List tempList = new LinkedList<>(); + int i; + Random random = new Random(); + while (tempList.size() < nameList.size()) { + i = random.nextInt(nameList.size()); + while (alreadyList.contains(i)) + i = random.nextInt(nameList.size()); + tempList.add(nameList.get(i)); + alreadyList.add(i); + } + nameList.clear(); + nameList.addAll(tempList); + + } + + //------------------------------------------------------ + public void setNewAlgo(boolean newAlgo) { + this.newAlgo=newAlgo; + if(newAlgo) + System.out.println("[INFO]Use SecureRandom"); + else + System.out.println("[INFO]Not use SecureRandom"); + } + //------------------------------------------------------ + + public NameData(){ + + if(System.getProperty("os.name").toLowerCase().contains("window")) + dataFile=new File("files\\Namelist.data"); + else + dataFile=new File("files/Namelist.data"); + + File oldDataFile=new File("D:\\dogename\\files\\data"); + + try{ + + if(oldDataFile.exists()) { + ObjectInputStream ois =new ObjectInputStream(new FileInputStream(oldDataFile)); + this.nameList=(ArrayList)ois.readObject(); + + listSize=nameList.size(); + this.chooseList=new ArrayList<>(nameList); + ois.close(); + oldDataFile.delete(); + saveToFile(); + return; + } + + if(!dataFile.exists()){ + dataFile.getParentFile().mkdirs(); + dataFile.createNewFile(); + nameList= new ArrayList<>(); + saveToFile(); + return; + } + + ObjectInputStream ois =new ObjectInputStream(new FileInputStream(dataFile)); + this.nameList=(ArrayList)ois.readObject(); + + log.info(nameList.size()+" names loaded."); + + listSize=nameList.size(); + this.chooseList=new ArrayList<>(nameList); + + }catch (EOFException EOFe){ + nameList=new ArrayList<>(); + chooseList=new ArrayList<>(); + log.warn("Data file is empty."); + saveToFile(); + }catch (Exception e){ + nameList=new ArrayList<>(); + chooseList=new ArrayList<>(); + saveToFile(); + log.warn("Failed to load data file."); + e.printStackTrace(); + } + + } + + //------------------------------------------------------ + public void add(String text){ + String[] splitedText; + + if(text.contains("\n")&&text.contains("\r")){//-----------windows + splitedText=text.split("\r\n"); + nameList.addAll(Arrays.asList(splitedText)); + }else if(text.contains("\n")){//--------------------------linux,unix + splitedText=text.split("\n"); + nameList.addAll(Arrays.asList(splitedText)); + }else if(text.contains("\r")){//--------------------------macos + splitedText=text.split("\r"); + nameList.addAll(Arrays.asList(splitedText)); + }else { + nameList.add(text); + listSize=nameList.size(); + } + chooseList=new ArrayList<>(nameList); + System.gc(); + } + //------------------------------------------------------ + public String get(int i){ + if(i(nameList); + listSize=nameList.size(); + System.gc(); + + + } + + //------------------------------------------------------ + public boolean isEmpty(){ + return nameList.isEmpty(); + } + + Random random =new Random(); + + //------------------------------------------------------ + public String randomGet(){ + if(newAlgo) + return nameList.get(secRandom.nextInt(nameList.size())); + else + return nameList.get(random.nextInt(nameList.size())); + } + + //------------------------------------------------------ + public String[] getAll(){ + return nameList.toArray(new String[0]); + } + + //------------------------------------------------------ + public void saveToFile(){ + + try{ + ObjectOutputStream oos =new ObjectOutputStream(new FileOutputStream(dataFile)); + oos.writeObject(nameList); + oos.close(); + }catch (Exception e){ + e.printStackTrace(); + } + + } + + //------------------------------------------------------ + public void deleteAll(){ + nameList.clear(); + chooseList.clear(); + } + + //------------------------------------------------------ +} diff --git a/src/main/java/me/lensferno/dogename/ocr/Ocr.java b/src/main/java/me/lensferno/dogename/ocr/Ocr.java new file mode 100644 index 0000000..9e28b67 --- /dev/null +++ b/src/main/java/me/lensferno/dogename/ocr/Ocr.java @@ -0,0 +1,101 @@ +package me.lensferno.dogename.ocr; + +import com.baidu.aip.ocr.AipOcr; +import org.json.JSONArray; +import org.json.JSONObject; + +import java.io.File; +import java.util.HashMap; + +public class Ocr { + + public static final String APP_ID = "17411446"; + public static final String API_KEY = "R2ggZhk6nB7ORE4Ozy9iAPdc"; + public static final String SECRET_KEY = "9f6ECgrltz9v1rww2hQm3EQOl1FFHLGx"; + + AipOcr client = new AipOcr(APP_ID, API_KEY, SECRET_KEY); + + public void init(){ + client.setConnectionTimeoutInMillis(2000); + client.setSocketTimeoutInMillis(60000); + } + + String result; + int resultNum=0; + + public boolean identifyPrecisely(String imgFileLoca){ + HashMap params=new HashMap<>(); + params.put("probability","false"); + JSONObject respondJSON=client.accurateGeneral("files"+ File.separator+"ocrCache.png",params); + + if (respondJSON==null){ + result="错误:返回了空的数据。"; + return false; + } + if(!respondJSON.has("words_result")){ + String errorCode=respondJSON.getString("error_code"); + result=findErrorMsg(errorCode); + return false; + } + + resultNum=respondJSON.getInt("words_result_num"); + System.out.println("total result:"+resultNum); + JSONArray resultArray=respondJSON.getJSONArray("words_result"); + + StringBuffer stringBuffer=new StringBuffer(); + + for(int i=0;i{ + + //gushiciJSON=getGushici(); + + String content, title, author, type; + + if((gushiciJSON=getGushici())!=null){ + GushiciData gushiciData=new Gson().fromJson(gushiciJSON,GushiciData.class); + content=gushiciData.getContent(); + title=gushiciData.getTitle(); + author=gushiciData.getAuthor(); + type=gushiciData.getType(); + Platform.runLater(()->{ + topBar.setText(content+" ——"+author+"《"+title+"》"); + GushiciPaneController gushiciPaneController=new GushiciPaneController(content, title, author, type); + new DialogMaker(rootPane).createDialogWithOneBtn("每日古诗词",gushiciPaneController); + }); + } + }).start(); + + } + + static class GushiciData{ + + private String content;//诗歌的内容 + + @SerializedName("origin") + private String title;//诗歌的标题 + + private String author;//诗歌作者 + + @SerializedName("category") + private String type;//诗歌类型 + + public void setContent(String content) { + this.content = content; + } + public String getContent() { + return content; + } + + public void setTitle(String title) { + this.title = title; + } + public String getTitle() { + return title; + } + + public void setAuthor(String author) { + this.author = author; + } + public String getAuthor() { + return author; + } + + public void setType(String type) { + this.type = type; + } + public String getType() { + return type; + } + } +} diff --git a/src/main/java/me/lensferno/dogename/sayings/Hitokoto.java b/src/main/java/me/lensferno/dogename/sayings/Hitokoto.java new file mode 100644 index 0000000..3846a9a --- /dev/null +++ b/src/main/java/me/lensferno/dogename/sayings/Hitokoto.java @@ -0,0 +1,163 @@ +package me.lensferno.dogename.sayings; + +import com.google.gson.Gson; +import com.google.gson.annotations.SerializedName; +import javafx.application.Platform; +import javafx.scene.control.Label; +import javafx.scene.layout.Pane; +import me.lensferno.dogename.utils.DialogMaker; +import me.lensferno.dogename.controllers.HitokotoPaneController; +import me.lensferno.dogename.utils.HtmlRequseter; + +public class Hitokoto { + + + private final String HITOKOTO_API ="https://v1.hitokoto.cn/"; + + String hitokotoJSON = null; + + private String getHitokoto() { + return HtmlRequseter.getHtml(HITOKOTO_API,false); + } + + public void showHitokoto(Pane rootPane, Label topBar){ + + new Thread(()->{ + + //hitokotoJSON = getHitokoto(); + + String hitokoto, from,author,creator, type; + + if((hitokotoJSON = getHitokoto())!=null){ + HitokotoData hitokotoData=new Gson().fromJson(hitokotoJSON, HitokotoData.class); + + hitokoto=hitokotoData.getHitokoto(); + from=hitokotoData.getFrom(); + author=hitokotoData.getAuthor(); + creator=hitokotoData.getCreator(); + type=hitokotoData.getType(); + + Platform.runLater(()->{ + topBar.setText("《"+from+"》:"+hitokoto+" ("+author+")"); + HitokotoPaneController hitokotoPaneController=new HitokotoPaneController(hitokoto, from,author,creator, type); + new DialogMaker(rootPane).createDialogWithOneBtn("每日一句话",hitokotoPaneController); + }); + } + }).start(); + } + + static class HitokotoData { + + private int id; + private String hitokoto; + private String type; + private String from; + + @SerializedName("from_who") + private String author; + + private String creator; + public void setId(int id) { + this.id = id; + } + public int getId() { + return id; + } + + public void setHitokoto(String hitokoto) { + this.hitokoto = hitokoto; + } + public String getHitokoto() { + return hitokoto; + } + + public void setType(String type) { + this.type = type; + } + + /** + * Type: + * a 动画 + * b 漫画 + * c 游戏 + * d 文学 + * e 原创 + * f 来自网络 + * g 其他 + * h 影视 + * i 诗词 + * j 网易云 + * k 哲学 + * l 抖机灵 + * + * @return + */ + + public String getType() { + switch (type){ + case "a" : + type="动画"; + break; + case "b" : + type="漫画"; + break; + case "c" : + type="游戏"; + break; + case "d" : + type="文学"; + break; + case "e" : + type="原创"; + break; + case "f" : + type="来自网络"; + break; + case "g" : + type="其他"; + break; + case "h" : + type="影视"; + break; + case "i" : + type="诗词"; + break; + case "j" : + type="网易云"; + break; + case "k" : + type="哲学"; + break; + case "l" : + type="抖机灵"; + break; + default: + type="未知"; + + } + return type; + } + + public void setFrom(String from) { + this.from = from; + } + public String getFrom() { + return from; + } + + public void setAuthor(String author) { + this.author = author; + } + public String getAuthor() { + return author; + } + + public void setCreator(String creator) { + this.creator = creator; + } + public String getCreator() { + return creator; + } + + } +} diff --git a/src/main/java/me/lensferno/dogename/utils/Clipboard.java b/src/main/java/me/lensferno/dogename/utils/Clipboard.java new file mode 100644 index 0000000..7081e24 --- /dev/null +++ b/src/main/java/me/lensferno/dogename/utils/Clipboard.java @@ -0,0 +1,32 @@ +package me.lensferno.dogename.utils; + +import java.awt.*; +import java.awt.datatransfer.DataFlavor; +import java.awt.datatransfer.StringSelection; +import java.awt.datatransfer.Transferable; + +public class Clipboard { + + public static void copyToClipboard(String text) { + java.awt.datatransfer.Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); + Transferable trans = new StringSelection(text); + clipboard.setContents(trans, null); + } + + public static String getClipboardString() { + java.awt.datatransfer.Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); + Transferable trans = clipboard.getContents(null); + + if (trans != null) { + if (trans.isDataFlavorSupported(DataFlavor.stringFlavor)) { + try { + return (String) trans.getTransferData(DataFlavor.stringFlavor); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + return null; + } +} diff --git a/src/main/java/me/lensferno/dogename/utils/DialogMaker.java b/src/main/java/me/lensferno/dogename/utils/DialogMaker.java new file mode 100644 index 0000000..b8587f3 --- /dev/null +++ b/src/main/java/me/lensferno/dogename/utils/DialogMaker.java @@ -0,0 +1,113 @@ +package me.lensferno.dogename.utils; + +import com.jfoenix.controls.JFXButton; +import com.jfoenix.controls.JFXDialog; +import com.jfoenix.controls.JFXDialogLayout; +import javafx.beans.NamedArg; +import javafx.event.ActionEvent; +import javafx.event.EventHandler; +import javafx.geometry.Pos; +import javafx.scene.Node; +import javafx.scene.control.Label; +import javafx.scene.layout.Pane; +import javafx.scene.layout.StackPane; +import javafx.scene.paint.Paint; +import javafx.scene.text.Font; +import javafx.scene.text.FontWeight; +import javafx.scene.text.Text; + + +public class DialogMaker { + Pane rootPane; + + public DialogMaker(@NamedArg("rootPane") Pane rootPane){ + this.rootPane=rootPane; + } + + + JFXDialog dialog; + + public void createMessageDialog(@NamedArg("title") String title, @NamedArg("message") String message){ + JFXButton OKButton = new JFXButton("了解!"); + OKButton.setFont(Font.font("Microsoft YaHei",FontWeight.BOLD,12)); + OKButton.setPrefWidth(60); + OKButton.setPrefHeight(30); + + Text messageText=new Text(message); + messageText.setFont(Font.font("Microsoft YaHei",14)); + + createDialog(title,messageText,OKButton); + } + + //创建只有一个按钮的dialog + public void createDialogWithOneBtn(@NamedArg("title") String title, @NamedArg("theBody") Node body){ + //dialog.setPrefHeight(rootPane.getPrefHeight()); + //dialog.setPrefWidth(rootPane.getPrefWidth()); + + JFXButton OKButton = new JFXButton("好的!"); + OKButton.setFont(Font.font("Microsoft YaHei",FontWeight.BOLD,12)); + OKButton.setPrefWidth(60); + OKButton.setPrefHeight(30); + + createDialog(title,body,OKButton); + + dialog.show(); + } + + //创建有OK和cancel按钮的dialog + public void createDialogWithOKAndCancel(@NamedArg("title") String title, @NamedArg("message") String message, @NamedArg("OKEvent") EventHandler OKEvent){ + //dialog.setPrefHeight(rootPane.getPrefHeight()); + //dialog.setPrefWidth(rootPane.getPrefWidth()); + + JFXButton CancelButton = new JFXButton("手滑了"); + CancelButton.setFont(Font.font("Microsoft YaHei",FontWeight.BOLD,12)); + CancelButton.setPrefWidth(60); + CancelButton.setPrefHeight(30); + + JFXButton OKButton = new JFXButton("是!"); + OKButton.setFont(Font.font("Microsoft YaHei",FontWeight.BOLD,12)); + OKButton.setPrefWidth(60); + OKButton.setPrefHeight(30); + OKButton.setTextFill(Paint.valueOf("red")); + OKButton.addEventHandler(ActionEvent.ACTION,e -> {dialog.close();}); + OKButton.addEventHandler(ActionEvent.ACTION,OKEvent); + + Text messageText=new Text(message); + messageText.setFont(Font.font("Microsoft YaHei",14)); + + createDialog(title,messageText,CancelButton,OKButton); + + dialog.show(); + } + + public void createDialog(@NamedArg("title") String title, @NamedArg("theBody") Node body, @NamedArg("buttons") JFXButton...buttons){ + + JFXDialogLayout content = new JFXDialogLayout(); + + Label titleLabel=new Label(title); + titleLabel.setFont(Font.font("Microsoft YaHei", FontWeight.BOLD,20)); + content.setHeading(titleLabel); + + content.setBody(body); + content.setAlignment(Pos.CENTER); + + StackPane tempPane=new StackPane(); + tempPane.setPrefHeight(rootPane.getPrefHeight()); + tempPane.setPrefWidth(rootPane.getPrefWidth()); + rootPane.getChildren().add(tempPane); + + dialog = new JFXDialog(tempPane,content,JFXDialog.DialogTransition.TOP); + + dialog.setOnDialogClosed(event -> rootPane.getChildren().remove(tempPane)); + + for (JFXButton button : buttons) { + button.addEventHandler(ActionEvent.ACTION, e -> { + dialog.close(); + }); + } + + content.setActions(buttons); + + dialog.show(); + } +} diff --git a/src/main/java/me/lensferno/dogename/utils/FileProcessor.java b/src/main/java/me/lensferno/dogename/utils/FileProcessor.java new file mode 100644 index 0000000..94152ab --- /dev/null +++ b/src/main/java/me/lensferno/dogename/utils/FileProcessor.java @@ -0,0 +1,25 @@ +package me.lensferno.dogename.utils; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; + +public class FileProcessor { + + static Logger log = LogManager.getLogger(); + + public static void writeFile(byte[] bytes, File file){ + try { + FileOutputStream fileOutputStream=new FileOutputStream(file); + fileOutputStream.write(bytes); + fileOutputStream.close(); + } catch (FileNotFoundException e) { + e.printStackTrace(); + }catch (Exception e){ + log.error("Error in writting file"+"\""+file.getName()+"\""+":"+e); + } + } +} diff --git a/src/main/java/me/lensferno/dogename/utils/HtmlRequseter.java b/src/main/java/me/lensferno/dogename/utils/HtmlRequseter.java new file mode 100644 index 0000000..eb52641 --- /dev/null +++ b/src/main/java/me/lensferno/dogename/utils/HtmlRequseter.java @@ -0,0 +1,70 @@ +package me.lensferno.dogename.utils; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.URL; +import java.net.URLConnection; +import java.nio.charset.StandardCharsets; +import java.util.zip.GZIPInputStream; +import java.util.zip.Inflater; +import java.util.zip.InflaterInputStream; + +public class HtmlRequseter { + + static Logger log = LogManager.getLogger(); + + public static String getHtml(String address,boolean output) + { + StringBuilder sb = new StringBuilder(); + try { + BufferedReader bis; + URL url = new URL(address); + URLConnection conn = url.openConnection(); + conn.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.80 Safari/537.36"); + + conn.setRequestProperty("Accept-Encoding", "gzip,deflate"); + conn.connect(); + + System.out.println("--------------------------------------------------------------------"); + System.out.println("[INFO]Getting:"+conn.getURL()); + System.out.println("[INFO]Content compress type:"+conn.getContentEncoding()); + + InputStream is = conn.getInputStream(); + String connEncoding=conn.getContentEncoding(); + + if(connEncoding==null) + connEncoding="none"; + + switch (connEncoding) { + case "deflate": + InflaterInputStream deflate = new InflaterInputStream(is, new Inflater(true)); + bis = new BufferedReader(new InputStreamReader(deflate, StandardCharsets.UTF_8)); + break; + case "gzip": + GZIPInputStream gzip = new GZIPInputStream(is); + bis = new BufferedReader(new InputStreamReader(gzip, StandardCharsets.UTF_8)); + break; + default: + bis = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8)); + break; + } + String temp; + while ((temp = bis.readLine()) != null) { + sb.append(temp); + sb.append("\n"); + } + }catch(Exception e){ + log.error("Error in getting HTML:"+e); + return null; + } + + if(output) + System.out.println("[INFO]Got:"+sb.toString()); + + return sb.toString(); + } +} diff --git a/src/main/java/me/lensferno/dogename/voice/Token.java b/src/main/java/me/lensferno/dogename/voice/Token.java new file mode 100644 index 0000000..443e4a1 --- /dev/null +++ b/src/main/java/me/lensferno/dogename/voice/Token.java @@ -0,0 +1,53 @@ +package me.lensferno.dogename.voice; + +import com.google.gson.annotations.SerializedName; + +import java.io.Serializable; + +public class Token implements Serializable { + + /* +返回 +需要根据 Content-Type的头部来确定是否服务端合成成功。 + +如果合成成功,返回的Content-Type以“audio”开头 + +aue =3 ,返回为二进制mp3文件,具体header信息 Content-Type: audio/mp3; +aue =4 ,返回为二进制pcm文件,具体header信息 Content-Type:audio/basic;codec=pcm;rate=16000;channel=1 +aue =5 ,返回为二进制pcm文件,具体header信息 Content-Type:audio/basic;codec=pcm;rate=8000;channel=1 +aue =6 ,返回为二进制wav文件,具体header信息 Content-Type: audio/wav; +如果合成出现错误,则会返回json文本,具体header信息为:Content-Type: application/json。其中sn字段主要用于DEBUG追查问题,如果出现问题,可以提供sn帮助确认问题。 + */ + + private long expTime; + + @SerializedName("access_token") + private String accessToken; + @SerializedName("expires_in") + private long expiresIn; + + public void setExpTime() { + this.expTime = System.currentTimeMillis() + (expiresIn - 3600) * 1000; + } + + public boolean isTokenTimeOut() { + return System.currentTimeMillis() > expTime; + } + + public void setAccessToken(String accessToken) { + this.accessToken = accessToken; + } + + public String getAccessToken() { + return accessToken; + } + + public void setExpiresIn(long expiresIn) { + this.expiresIn = expiresIn; + } + + public long getExpiresIn() { + return expiresIn; + } + +} diff --git a/src/main/java/me/lensferno/dogename/voice/TokenManager.java b/src/main/java/me/lensferno/dogename/voice/TokenManager.java new file mode 100644 index 0000000..56ae2cf --- /dev/null +++ b/src/main/java/me/lensferno/dogename/voice/TokenManager.java @@ -0,0 +1,174 @@ +package me.lensferno.dogename.voice; + +import com.google.gson.Gson; +import me.lensferno.dogename.utils.HtmlRequseter; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.*; +import java.net.HttpURLConnection; +import java.net.URL; + +public class TokenManager { + + Logger log= LogManager.getLogger(); + + public static final String separator=File.separator; + + String cachedVoicePath="caches\\voice\\"; + + final private int TOKEN_NULL = -2; + final private int TOKEN_EXPIRED = -1; + final private int TOKEN_OK = 0; + + final String API_KEY="dIHCtamVdD0ERO1yyFir2iI4"; + final String SEC_KEY="HmpBQY3gG4PyZ0cmudnCbMeoMcMejuuW"; + + final String TOKEN_API_URL ="https://openapi.baidu.com/oauth/2.0/token"; + + File tokenFile=new File("API_voice.token"); + + private Token token=null; + + String tokenStatus="ok"; + + private void updateTokenStatus(int statusCode){ + switch(statusCode){ + case TOKEN_OK: + tokenStatus="ok"; + break; + + case TOKEN_EXPIRED: + if(netAvailable()){ refreshToken();} + + if(checkTokenAvailable()!=0){ tokenStatus="not ok";} + break; + + case TOKEN_NULL: + if(netAvailable()){ refreshToken();} + + if(checkTokenAvailable()!=0){ tokenStatus="not ok";} + break; + + default : + tokenStatus="not ok"; + break; + } + } + + private void refreshToken(){ + + fetchToken(); + writeToken(); + } + + + public void init(){ + + if(tokenFile.exists()){ + loadToken(); + updateTokenStatus(checkTokenAvailable()); + + }else{ + + if(netAvailable()){ + + refreshToken(); + + updateTokenStatus(checkTokenAvailable()); + + }else { + tokenStatus="not ok"; + } + + } + } + + public String getTokenStatus() { + return tokenStatus; + } + + public Token getToken() { + return token; + } + + private int checkTokenAvailable() { + + //token是空的就返回-2 + if (token == null || token.getAccessToken() == null) { + log.info("Token was null"); + return -2; + } + + //token过期了就返回-1 + if (token.isTokenTimeOut()) { + log.info("Token expired."); + return -1; + } + + //正常的话就返回0 + log.info("Token OK."); + return 0; + } + + + void fetchToken(){ + try{ + token=new Gson().fromJson( + HtmlRequseter.getHtml( + TOKEN_API_URL + + "?grant_type=client_credentials&client_id=" + API_KEY + + "&client_secret=" + SEC_KEY, + true) + ,Token.class); + + token.setExpTime(); + }catch (Exception e){ + log.error("Error to get Token:"+e); + token=null; + } + } + + private boolean netAvailable(){ + try { + + URL sourcesURL = new URL("http://www.baidu.com"); + HttpURLConnection connection = (HttpURLConnection) sourcesURL.openConnection(); + connection.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36"); + connection.connect(); + + InputStream stream = connection.getInputStream(); + stream.read(); + stream.close(); + + return true; + }catch (Exception e){ + log.info("Network is not available."); + return false; + } + } + + private void loadToken(){ + ObjectInputStream ois; + try{ + ois =new ObjectInputStream(new FileInputStream(tokenFile)); + this.token =(Token) ois.readObject(); + ois.close(); + }catch (Exception e){ + log.error("Error in loading Token:"+e); + this.token=null; + } + } + + private void writeToken(){ + + try{ + ObjectOutputStream oos =new ObjectOutputStream(new FileOutputStream(tokenFile)); + oos.writeObject(token); + oos.close(); + }catch (Exception e){ + log.error("Error in writing Token:"+e); + } + } + +} diff --git a/src/main/java/me/lensferno/dogename/voice/VoicePlayer.java b/src/main/java/me/lensferno/dogename/voice/VoicePlayer.java new file mode 100644 index 0000000..6871076 --- /dev/null +++ b/src/main/java/me/lensferno/dogename/voice/VoicePlayer.java @@ -0,0 +1,195 @@ +package me.lensferno.dogename.voice; + +import javazoom.spi.mpeg.sampled.file.MpegAudioFileReader; +import me.lensferno.dogename.utils.FileProcessor; +import okhttp3.*; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.jetbrains.annotations.NotNull; + +import javax.sound.sampled.*; +import java.io.File; +import java.io.IOException; +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.net.URLEncoder; +import java.util.Random; +import java.util.concurrent.TimeUnit; + +public class VoicePlayer { + + public static final String separator=File.separator; + + Logger log= LogManager.getLogger("VoicePlayerLogger"); + + private final String VOICE_API="https://tsn.baidu.com/text2audio"; + + Token token; + + public VoicePlayer(Token token){ + this.token=token; + + } + + String cachedVoicePath="caches"+separator+"voice"+separator; + + File cacheDir =new File(cachedVoicePath); + + OkHttpClient okHttpClient=new OkHttpClient.Builder() + .connectTimeout(10, TimeUnit.SECONDS) + .writeTimeout(10,TimeUnit.SECONDS) + .readTimeout(10,TimeUnit.SECONDS) + .build(); + + + public void playVoice(String name,String speaker,String intonation,String speed) { + + String cachedVoiceName; + cachedVoiceName = name + "_" + speaker+ "_" + speed +"_"+intonation; + + File cachedVoice = new File(cachedVoicePath + cachedVoiceName + ".mp3"); + + if (!cachedVoice.exists()) { + System.out.println("Voice of "+cachedVoice+" not exists,fetch from network."); + getVoiceData(name,speaker,speed,intonation,cachedVoice); + //playSound(cachedVoice); + + } else { + new Thread(() -> { + System.out.println("Voice of "+cachedVoice+" exists,playing cache."); + playSound(cachedVoice); + }).start(); + } + + } + + private void getVoiceData(String name,String speaker,String speed,String intonation,File cachedVoice){ + + new Thread(() -> { + try{ + + FormBody formBody=new FormBody.Builder() + .add("tex",URLEncoder.encode(name,"utf-8")) + .add("tok",token.getAccessToken()) + .add("cuid",getMACAddress()) + .add("ctp","1") + .add("lan","zh") + .add("spd",speed) + .add("per",speaker) + .add("pit",intonation) + .add("aue","3") + .build(); + + Request request=new Request.Builder() + .url(VOICE_API) + .header("User-Agent","Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36") + .post(formBody) + .build(); + + + okHttpClient.newCall(request).enqueue(new Callback() { + @Override + public void onFailure(@NotNull Call call, @NotNull IOException e) { + log.warn("Failed to get voice:"+e.toString()); + } + + @Override + public void onResponse(@NotNull Call call, @NotNull Response response) { + if(response.header("Content-type").contains("json")){ + log.warn("Request error:"+response.toString()); + }else { + boolean success=cacheVoiceFile(response,cachedVoice); + if (success) { + log.info("cache voice of "+name+" to file "+cachedVoice.getPath()+" success"); + playSound(cachedVoice); + } + } + } + }); + + }catch(Exception e){ + e.printStackTrace(); + } + + }).start(); + } + + private boolean cacheVoiceFile(Response response,File cacheVoice){ + + try { + + if(!cacheDir.exists()) + cacheDir.mkdirs(); + + if(!cacheVoice.exists()) + cacheVoice.createNewFile(); + + //FileOutputStream cacheFile=new FileOutputStream(cacheVoice); + FileProcessor.writeFile(response.body().bytes(),cacheVoice); + //IOUtils.write(response.body().bytes(),cacheFile); + return true; + } catch (Exception e) { + e.printStackTrace(); + log.warn("Error to cache voice file:"+e.toString()); + return false; + } + + } + + + private void playSound(File file) { + + try { + //使用 mp3spi 解码 mp3 音频文件 + MpegAudioFileReader mp = new MpegAudioFileReader(); + AudioInputStream stream = mp.getAudioInputStream(file); + AudioFormat baseFormat = stream.getFormat(); + //设定输出格式为pcm格式的音频文件 + AudioFormat format = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, baseFormat.getSampleRate(), 16, baseFormat.getChannels(), baseFormat.getChannels() * 2, baseFormat.getSampleRate(), false); + // 输出到音频 + stream = AudioSystem.getAudioInputStream(format, stream); + AudioFormat target = stream.getFormat(); + DataLine.Info dinfo = new DataLine.Info(SourceDataLine.class, target, AudioSystem.NOT_SPECIFIED); + SourceDataLine line; + int len; + line = (SourceDataLine) AudioSystem.getLine(dinfo); + line.open(target); + line.start(); + byte[] buffer = new byte[1024]; + while ((len = stream.read(buffer)) > 0) { + line.write(buffer, 0, len); + } + line.drain(); + line.stop(); + line.close(); + } catch (Exception e) { + log.warn("Error to play voice audio:"+e.toString()); + e.printStackTrace(); + } + } + + + private static String getMACAddress() { + + try{ + + byte[] mac = NetworkInterface.getByInetAddress(InetAddress.getLocalHost()).getHardwareAddress(); + + StringBuffer sb = new StringBuffer(); + + for (int i = 0; i < mac.length; i++) { + if (i != 0) { + sb.append("-"); + } + String s = Integer.toHexString(mac[i] & 0xFF); + sb.append(s.length() == 1 ? 0 + s : s); + } + return sb.toString().toUpperCase(); + + }catch (Exception e){ + return String.valueOf(new Random().nextLong()); + } + + } + +} diff --git a/src/main/resources/META-INF/MANIFEST.MF b/src/main/resources/META-INF/MANIFEST.MF new file mode 100644 index 0000000..ccf713c --- /dev/null +++ b/src/main/resources/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Main-Class: me.lensferno.dogename.Main + diff --git a/src/main/resources/me/lensferno/dogename/FXMLs/AdvancedVoiceSettingsPane.fxml b/src/main/resources/me/lensferno/dogename/FXMLs/AdvancedVoiceSettingsPane.fxml new file mode 100644 index 0000000..a4be1ce --- /dev/null +++ b/src/main/resources/me/lensferno/dogename/FXMLs/AdvancedVoiceSettingsPane.fxml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/me/lensferno/dogename/FXMLs/GushiciPane.fxml b/src/main/resources/me/lensferno/dogename/FXMLs/GushiciPane.fxml new file mode 100644 index 0000000..9d1bdeb --- /dev/null +++ b/src/main/resources/me/lensferno/dogename/FXMLs/GushiciPane.fxml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/me/lensferno/dogename/FXMLs/HistoryPane.fxml b/src/main/resources/me/lensferno/dogename/FXMLs/HistoryPane.fxml new file mode 100644 index 0000000..6d8ebdf --- /dev/null +++ b/src/main/resources/me/lensferno/dogename/FXMLs/HistoryPane.fxml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/me/lensferno/dogename/FXMLs/HitokotoPane.fxml b/src/main/resources/me/lensferno/dogename/FXMLs/HitokotoPane.fxml new file mode 100644 index 0000000..b0dc929 --- /dev/null +++ b/src/main/resources/me/lensferno/dogename/FXMLs/HitokotoPane.fxml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/me/lensferno/dogename/FXMLs/MainInterface.fxml b/src/main/resources/me/lensferno/dogename/FXMLs/MainInterface.fxml new file mode 100644 index 0000000..60f439c --- /dev/null +++ b/src/main/resources/me/lensferno/dogename/FXMLs/MainInterface.fxml @@ -0,0 +1,107 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/me/lensferno/dogename/FXMLs/MiniPane.fxml b/src/main/resources/me/lensferno/dogename/FXMLs/MiniPane.fxml new file mode 100644 index 0000000..e281f91 --- /dev/null +++ b/src/main/resources/me/lensferno/dogename/FXMLs/MiniPane.fxml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/me/lensferno/dogename/FXMLs/NameListPane.fxml b/src/main/resources/me/lensferno/dogename/FXMLs/NameListPane.fxml new file mode 100644 index 0000000..d91cfb2 --- /dev/null +++ b/src/main/resources/me/lensferno/dogename/FXMLs/NameListPane.fxml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/main/resources/me/lensferno/dogename/FXMLs/NameManagerPane.fxml b/src/main/resources/me/lensferno/dogename/FXMLs/NameManagerPane.fxml new file mode 100644 index 0000000..4611d9b --- /dev/null +++ b/src/main/resources/me/lensferno/dogename/FXMLs/NameManagerPane.fxml @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/me/lensferno/dogename/FXMLs/NumberSettingPane.fxml b/src/main/resources/me/lensferno/dogename/FXMLs/NumberSettingPane.fxml new file mode 100644 index 0000000..7b57910 --- /dev/null +++ b/src/main/resources/me/lensferno/dogename/FXMLs/NumberSettingPane.fxml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/me/lensferno/dogename/FXMLs/OcrPane.fxml b/src/main/resources/me/lensferno/dogename/FXMLs/OcrPane.fxml new file mode 100644 index 0000000..394fdc9 --- /dev/null +++ b/src/main/resources/me/lensferno/dogename/FXMLs/OcrPane.fxml @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/me/lensferno/dogename/FXMLs/ProgramInfoPane.fxml b/src/main/resources/me/lensferno/dogename/FXMLs/ProgramInfoPane.fxml new file mode 100644 index 0000000..6da20e8 --- /dev/null +++ b/src/main/resources/me/lensferno/dogename/FXMLs/ProgramInfoPane.fxml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/me/lensferno/dogename/FXMLs/SettingsPane.fxml b/src/main/resources/me/lensferno/dogename/FXMLs/SettingsPane.fxml new file mode 100644 index 0000000..6d6954f --- /dev/null +++ b/src/main/resources/me/lensferno/dogename/FXMLs/SettingsPane.fxml @@ -0,0 +1,140 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/me/lensferno/dogename/FXMLs/VoiceSettingsPane.fxml b/src/main/resources/me/lensferno/dogename/FXMLs/VoiceSettingsPane.fxml new file mode 100644 index 0000000..2f80b09 --- /dev/null +++ b/src/main/resources/me/lensferno/dogename/FXMLs/VoiceSettingsPane.fxml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +