diff --git a/.github/workflows/statistics.yml b/.github/workflows/statistics.yml index 95bcd1862..50abfaf81 100644 --- a/.github/workflows/statistics.yml +++ b/.github/workflows/statistics.yml @@ -19,8 +19,8 @@ jobs: - name: Commit and Push generated files run: | if [ -n "$(git status --porcelain)" ]; then - git config --local user.email "action@github.com" - git config --local user.name "github-actions" + git config --local user.email "nathan.vincent.2.718@gmail.com" + git config --local user.name "nathancheshire" git add . git commit -m "update statistics badges" -m "`date`" git push -u origin main diff --git a/README.md b/README.md index dc5f0c4ff..5ce71a0ab 100644 --- a/README.md +++ b/README.md @@ -1,102 +1 @@ -Logo - -![](actions/output/tagline.png) - -![](actions/output/author.png) - -![](actions/output/stats.png) - -![](actions/output/total.png) - -## What is Cyder - -Funny you should ask this question, I'm asked it quite a lot and usually fail to give a comprehensive and elegant -answer. The best I can do is something along the lines of "Cyder is a multi-purpose, desktop manager, GUI tool." It is -written using a custom Swing UI library which was built on top of lightweight Swing components. No modern GUI -dependencies such as [FlatLaf](https://github.com/JFormDesigner/FlatLaf), [MaterialFX](https://github.com/palexdev/MaterialFX) -or [FXML](https://openjfx.io/) were used, thus all Cyder components are closely related to [java/awt/Component.java](https://developer.classpath.org/doc/java/awt/Component-source.html). - -Some examples of what you can do with Cyder include: - -* Downloading Audio from a YouTube video, playlist, uuid, or link -* Image transforms, markup, and painting -* Evaluating mathematical expressions as simple as 2 * 2 or as complex as sin(e^pi*cos(64^cos(e^-1))) -* Visualizing algorithms such as A*, Graham Scan, Game of Life -* Converting audio files between formats such as wav and mp3 -* Playing local audio files with the ability to "dreamify" -* Hashing inputs with a nice hashing widget using algorithms such as MD5, SHA1, and SHA256 -* Reading, writing, and storing notes -* Demonstrating how Perlin noise works in both 2D and 3D with a visualizer -* Converting between temperature formats such as Kelvin, Fahrenheit, and Celsius -* Storing and running shortcuts -* Playing games such as hangman or nxn tic-tac-toe - -## Screenshots - -
-Cyder Console -Liminal Cyder -
- -
-Audio Widget -

-Audio Player -

-

-Audio Player Search -

-
- -
-Weather Widget -Weather -
- -
-Paint Widget -Paint widget -Paint widget controls -
- -
-Pathfinding Visualizer -

-Pathfinding Visualizer -

-
- -
-Game of Life Widget -

-Conway's Game of Life -

-
- -
-Perlin Terrain Visualizer -

-Perlin widget -

-
- -## Usage and Setup - -To get started with Cyder, first download your favorite Java IDE such as IntelliJ, NetBeans, Eclipse, etc. You'll then -want to make sure the IDE supports gradle operations. Next, clone Cyder -via `git clone https://github.com/NathanCheshire/Cyder.git --depth 1`. If you don't absolutely require the entire git -history, I highly recommend shallow cloning as the extensive git history is quit large. Now load the project in your IDE -and allow the gradle setup task to run and the IDE to synchronize. Now you'll be able to run Cyder by a runtime -configuration which invokes the main method inside of `Cyder.java`. Once started, Cyder should recognize there are no -users found and prompt for the creation of a user. Go ahead and create an account now. - -For development purposes, you may want to add three props within a props file: - -1. `autocypher` set to true. -2. `autocypher_name` set to your user's username. -3. `autocypher_password` set to your user's hashed password (hash your password once using SHA256). - -These props should be annotated with the `@no_log` annotation to ensure their values do not appear in any log files. -Additionally, your the props file containing your password should be added to your .gitignore file to avoid VCS -tracking. Cyder double hashes passwords to help prevent rainbow table lookups. However, leaving your singly-hashed -SHA256 autocypher password exposed leaves you more prone to attacks. +# CyderUtils diff --git a/dynamic/users/b83a7183-83c0-3203-917e-f187ebcfc6a1/Backgrounds/Default.png b/dynamic/users/b83a7183-83c0-3203-917e-f187ebcfc6a1/Backgrounds/Default.png deleted file mode 100644 index 1501b34bc..000000000 Binary files a/dynamic/users/b83a7183-83c0-3203-917e-f187ebcfc6a1/Backgrounds/Default.png and /dev/null differ diff --git a/dynamic/users/b83a7183-83c0-3203-917e-f187ebcfc6a1/Music/AlbumArt/Future - Solitaires (Audio) ft. Travis Scott.png b/dynamic/users/b83a7183-83c0-3203-917e-f187ebcfc6a1/Music/AlbumArt/Future - Solitaires (Audio) ft. Travis Scott.png deleted file mode 100644 index e9675705f..000000000 Binary files a/dynamic/users/b83a7183-83c0-3203-917e-f187ebcfc6a1/Music/AlbumArt/Future - Solitaires (Audio) ft. Travis Scott.png and /dev/null differ diff --git a/dynamic/users/b83a7183-83c0-3203-917e-f187ebcfc6a1/Music/AlbumArt/Justine Skye - Collide.png b/dynamic/users/b83a7183-83c0-3203-917e-f187ebcfc6a1/Music/AlbumArt/Justine Skye - Collide.png deleted file mode 100644 index d83b47b17..000000000 Binary files a/dynamic/users/b83a7183-83c0-3203-917e-f187ebcfc6a1/Music/AlbumArt/Justine Skye - Collide.png and /dev/null differ diff --git a/dynamic/users/b83a7183-83c0-3203-917e-f187ebcfc6a1/Music/AlbumArt/Logic - Cocaine.png b/dynamic/users/b83a7183-83c0-3203-917e-f187ebcfc6a1/Music/AlbumArt/Logic - Cocaine.png deleted file mode 100644 index 00edf5988..000000000 Binary files a/dynamic/users/b83a7183-83c0-3203-917e-f187ebcfc6a1/Music/AlbumArt/Logic - Cocaine.png and /dev/null differ diff --git a/dynamic/users/b83a7183-83c0-3203-917e-f187ebcfc6a1/Music/Future - Solitaires (Audio) ft. Travis Scott.mp3 b/dynamic/users/b83a7183-83c0-3203-917e-f187ebcfc6a1/Music/Future - Solitaires (Audio) ft. Travis Scott.mp3 deleted file mode 100644 index 3b193599a..000000000 Binary files a/dynamic/users/b83a7183-83c0-3203-917e-f187ebcfc6a1/Music/Future - Solitaires (Audio) ft. Travis Scott.mp3 and /dev/null differ diff --git a/dynamic/users/b83a7183-83c0-3203-917e-f187ebcfc6a1/Music/Justine Skye - Collide.mp3 b/dynamic/users/b83a7183-83c0-3203-917e-f187ebcfc6a1/Music/Justine Skye - Collide.mp3 deleted file mode 100644 index 04e4d6b0e..000000000 Binary files a/dynamic/users/b83a7183-83c0-3203-917e-f187ebcfc6a1/Music/Justine Skye - Collide.mp3 and /dev/null differ diff --git a/dynamic/users/b83a7183-83c0-3203-917e-f187ebcfc6a1/Music/Justine Skye - Collide_Dreamy.mp3 b/dynamic/users/b83a7183-83c0-3203-917e-f187ebcfc6a1/Music/Justine Skye - Collide_Dreamy.mp3 deleted file mode 100644 index d07847099..000000000 Binary files a/dynamic/users/b83a7183-83c0-3203-917e-f187ebcfc6a1/Music/Justine Skye - Collide_Dreamy.mp3 and /dev/null differ diff --git a/dynamic/users/b83a7183-83c0-3203-917e-f187ebcfc6a1/Music/Logic - Cocaine.mp3 b/dynamic/users/b83a7183-83c0-3203-917e-f187ebcfc6a1/Music/Logic - Cocaine.mp3 deleted file mode 100644 index 301fe6b00..000000000 Binary files a/dynamic/users/b83a7183-83c0-3203-917e-f187ebcfc6a1/Music/Logic - Cocaine.mp3 and /dev/null differ diff --git a/dynamic/users/b83a7183-83c0-3203-917e-f187ebcfc6a1/Userdata.json b/dynamic/users/b83a7183-83c0-3203-917e-f187ebcfc6a1/Userdata.json deleted file mode 100644 index 9359c0080..000000000 --- a/dynamic/users/b83a7183-83c0-3203-917e-f187ebcfc6a1/Userdata.json +++ /dev/null @@ -1,55 +0,0 @@ -{ - "username": "Nathan", - "password": "1b0a051deaadc2e793bfe185cd81a74b9c56f75bd713f28507d1abd99332a28", - "fontName": "Agency FB", - "fontSize": 28, - "foregroundColorHexCode": "F0F0F0", - "backgroundColorHexCode": "0F0F0F", - "introMusic": false, - "debugStats": false, - "randomBackgroundOnStart": false, - "drawOutputBorder": false, - "drawInputBorder": false, - "playHourlyChimes": true, - "silenceErrors": false, - "fullscreen": false, - "drawOutputFill": false, - "drawInputFill": false, - "drawConsoleClock": true, - "showConsoleClockSeconds": false, - "filterChat": true, - "lastSessionStart": 1678648029812, - "minimizeOnClose": false, - "typingAnimation": true, - "showBusyAnimation": false, - "roundedFrameBorders": false, - "frameColorHexCode": "0F0F0F", - "consoleClockFormat": "EEEEEEEEE h:mm:ssaa", - "playTypingSound": false, - "youtubeUuid": "aaaaaaaaaaa", - "capsMode": false, - "loggedIn": true, - "showAudioTotalLength": false, - "persistNotifications": false, - "doAnimations": true, - "compactTextMode": false, - "wrapNativeShell": false, - "drawWeatherMap": true, - "paintClockWidgetHourLabels": true, - "showClockWidgetSecondHand": true, - "screenStat": { - "consoleX": 510, - "consoleY": 122, - "consoleWidth": 800, - "consoleHeight": 800, - "monitor": 1, - "consoleOnTop": false, - "consoleDirection": "TOP" - }, - "mappedExecutables": { - "mappedExecutables": [] - }, - "shownWelcomeMessage": true, - "accountCreationTime": 0, - "audioPlayerVolumePercent": 68 -} \ No newline at end of file diff --git a/markdown/StartupArchitecture.md b/markdown/StartupArchitecture.md deleted file mode 100644 index be2e593ae..000000000 --- a/markdown/StartupArchitecture.md +++ /dev/null @@ -1,22 +0,0 @@ -# Startup Architecture - -by Nathan Cheshire - -Last updated: 22-09-09 - -## Architecture - -
- -![](./images/StartupArchitecture.drawio.png) - -## Notes - -- jvm args are stored but not logged until sufficient subroutines start (second purple path) The "log jvm entry" from - the initialize logger path (blue) refers to the fact that any log file's first tag is always "JVM_ENTRY" along with - the username of the OS' user -- the watchdog may be disabled via a prop -- the splash screen is shown as early as it can (I know it looks like it is rather late in the process) -- there are a few more branches for the `determine cyder entry` path (red), but they are not worth showing. All you need - to know is that all branches (should) lead to an exit or showing the login screen as a failsafe if the console cannot - be shown diff --git a/markdown/WidgetCreationGuide.md b/markdown/WidgetCreationGuide.md deleted file mode 100644 index 50475a946..000000000 --- a/markdown/WidgetCreationGuide.md +++ /dev/null @@ -1,166 +0,0 @@ -# Creating a Cyder Widget - -By Nate Cheshire - -Last updated: 9-30-21 - -## Getting started - -Making your own widget in Cyder is intended to be as easy and modern as possible. With the UI library as your skeleton, -the styling up to you, and the `utilities` package full of rich and useful methods for logic implementation, the freedom -to manipulate Cyder is limitless. - -## The Cyder UI library - -Lots of components exist within Cyder's `ui` package. The most important is the `CyderFrame`. The CyderFrame is -constructed as an extension of `JFrame` that is undecorated with it's own drag MouseListeners, menu bar, menu icons, and -minimize and disposal animations. Other components include the `CyderTextField`, `CyderButton`, `CyderLabel`, and many -more. In the rare case a needed ui component is needed for your specific widget that isn't implemented yet, feel free to -make a separate PR and add the component to Cyder yourself (ensure you follow [EJ -](https://www.amazon.com/Effective-Java-Joshua-Bloch/dp/0134685997) items as usual). - -## Setting up the frame - -First, create a class in the `widgets` package following the same naming standard: `MyWidgetWidget`. Now you must decide -if you want to allow multiple instances of your widget or not. - -### Option 1: Multiple Instances - -Multiple instances are allowed for your widget. Start off your class by making the default constructor private -and make sure to include the following call to Cyder's `Logger` class: `Logger.log(LoggerTag.OBJECT_CREATION, this);`. Now -create a `public static MyClass` method named `getInstance()` to return a new instance of your class. - -To allow your widget to be found and triggered via the `ReflectionUtil` widget finder and validator, you need to -create a `public static void` method named `showGui()`. Additionally, this method must be annotated with the `@Widget` -annotation to allow it to be discovered. The annotation requires a single string or list of strings to allow a user to -trigger it as well as a description. Additionally, since multiple instances are allowed, this method should ONLY invoke -the following: `getInstance().showGui()`. Thus, after following these steps, your class should look like the following: - -```java -/** -* An example widget. -*/ -class MyWidget { - /** - * Hide default constructor. - */ - private MyWidget() { - Logger.log(LoggerTag.OBJECT_CREATION, this); - } - - /** - * Allow multiple instances of widget. - */ - public static MyWidget getInstance() { - return new MyWidget(); - } - - /** - * showGui standard. - */ - @Widget(triggers = "my trigger", description = "My widget description") - public static void showGui() { - getInstance().innerShowGui(); - } - - /** - * Construct widgets (method is public to allow invoking aside from widget finder). - */ - public void innerShowGui() { - // constructing the widget - } -} -``` - -### Option 2: Singular Instance - -If a singular instance is desired, restrict the default constructor by making it private. To follow good programming -practices set forth by [Effective Java](https://www.amazon.com/Effective-Java-Joshua-Bloch/dp/0134685997), include the -following throw statement in the private -constructor: `throw new IllegalMethodException(CyderStrings.attemptedInstantiation)`. Note that `IllegalMethodException` -is simply an exception extending `IllegalArgumentException`. - -To allow your widget to be found and triggered via the `ReflectionUtil` widget finder and validator, you need to -create a `public static void` method named `showGui()`. Additionally, this method must be annotated with the `@Widget` -annotation to allow it to be discovered. The annotation requires a single string or list of strings to allow a user to -trigger it as well as a description. Thus, after following these steps, your class should look like the following: - -```java -/** -* An example widget. -*/ -class MyWidget { - /** - * Suppress default constructor. - */ - private MyWidget() { - throw new IllegalMethodException(CyderStrings.attemptedInstantiation); - } - - /** - * The only way this widget can be invoked is via the trigger(s). - */ - @Widget(triggers = "my trigger", description = "My widget description") - public static void showGui() { - // building the widget - } -} - -``` - -## Adding components - -Now that the preliminaries are out of the way, you may begin building the widget itself. First, you'll want to create -a `CyderFrame` object and initialize it with a width and height. After that, make sure to set the title as well as the -title position if a center title is desired. - -Calls: - -```java -CyderFrame cyderFrame = new CyderFrame(600, 600); -cyderFrame.setTitle("My Title"); -``` - -Now comes the fun part, building the rest of the UI. As stated previously make sure to use already built Cyder -components before attempting to make your own. If the rare case of needing your own custom UI component does come about, -create a separate PR for your ui element implementation. - -Adding a component to the frame: - -```java -CyderButton cyderButton = new CyderButton("Button"); -cyderButton.setBounds((600 - 200) / 2, (600 - 40 + CyderDragLabel.DEFAULT_HEIGHT) / 2, 200, 40); -cyderButton.addActionListener(e -> { - // your logic here or a lambda to a class level private method - cyderFrame.notify("Hello World!"); -}); -cyderFrame.getContentPane().add(cyderButton); -``` - -If you want to have a bit of fun with the UI and not use the default absolute layout, take a look at the `layouts` -package for layouts such as the `CyderFlowLayout` and `CyderGridLayout`. They work in the way you'd expect but are, IMHO, -much easier and intuitive than Swing layouts. - -## Logic - -You have essentially two options for logic as the widget construction should be as minimal as possible. First, you -can simply invoke a method within or outside of the class. Alternatively, you could use a lambda for the action listener -to run the needed logic to drive your widget. The logic implementation is essentially all up to you, just make sure to -follow proper Java standards and remember to write javadocs for all non-private members. - -Make sure to check the `utilities` package as it includes copious utility classes for performing operations needed throughout -Cyder. - -## Finishing calls - -To finalize your frame, make sure to set the frame's visibility and location. Typically in Cyder, component's locations -are set relatively to the current dominant frame. You don't have to worry about manually invoking these calls, however. `CyderFrame` -takes care of this for you via the method `finalizeAndShow()` which performs the required actions and checks for you. Additionally, -if you don't want Cyder to determine where to place the frame, you can provide a `Component` to set the frame relative to or provide -a `Point` to set the top left of the frame to. - -```java -cyderFrame.finalizeAndShow(); -``` - -To view the complete version of this widget, see [widgets/ExampleWidget.java](https://github.com/NathanCheshire/Cyder/blob/main/cyder/src/cyder/widgets/ExampleWidget.java). diff --git a/markdown/diagrams/StartupArchitecture.drawio b/markdown/diagrams/StartupArchitecture.drawio deleted file mode 100644 index 509056391..000000000 --- a/markdown/diagrams/StartupArchitecture.drawio +++ /dev/null @@ -1 +0,0 @@ -7V1bc6M6Ev41fkyKO/hxJpnL1p5Tc3az16ctBWTDDEYcJOLk/PqVuBlbsq2TgCV5UpWqGIExfPq61d3qlhbu3eb5SwXK9FeUwHzhWMnzwr1fOI7tOc6C/VnJS9sSeX7bsK6ypLto1/CQ/QG7RqtrrbME4r0LCUI5ycr9xhgVBYzJXhuoKrTdv2yF8v1fLcEacg0PMcj51n9nCUm7t/CtXftXmK3T/pdtqzuzAf3FXQNOQYK2oyb308K9qxAi7afN8x3MGXg9Lu33Ph85OzxYBQsi8wX81f/L/75+I3fx377+Vj9uPn1L7m9sr73NE8jr7o27pyUvPQQwoYh0h6giKVqjAuSfdq0fK1QXCWS/Y9Gj3TW/IFTSRps2foeEvHTdC2qCaFNKNnl3ln+X7vUwqqsYnnqBjhOgWkNy4rrutdi7jH6gQ+oLRBtIqhd6QQVzQLKn/d4HHYnWw3U7nOmHDuo/AzuH+venDXu8or/7uAP24d2mGYEPJWhg2VKp24dyleX5HcpR1XzXXa1gEMe0HZMK/YCjM0m4fLSsU+A/wYrA55Nw9Wd7kvdS3h1udyIzXJKOxKUXo+kB9g3ntSPJa08rXjvhTwK7E+iFOwd7jta0odUq9GXwFSgVO1CuVQLD6e1J0tvXit2u6UaKLOyuqxXuPOw5AgnDoELltBolATBaCTVKEEfwcTWPRnGWyjWK6QOmL0ltvcZL3joECSM2fM4I63qEflzBgOmGyukdGU7vQJLeoVb09tyfBHZPL7eeNw+zIiMZyBlCjkVt8jWsrmDc9HzlimVpOMNDSYZHehHcdAdIFvZALw+ItxL3FAt+wQRuZjHM1VguvnJXv/8xY5keSTJ9qRfRfxbYA71w5+10TF+B+UJbQOI0YdFE482WwFOuVfiJH7PovZSkdz99qwm9Q9MjiLK4h3pFEHkrHT7DuCbMaClgDDEGzdPg+pGCS7ICTmu9QDvxYSjSM8sgdEEwj54JHeV6xjGc773+OK9obK0Ib/P2C07Rlr1ymQOcXsEoGlnK2W16eKsn7Xl26xXfikyPukgDH+k1jtq83bgbSHG9WmVxxsC4tpE0umQikfXPhw/3/sd/fPnr07eU4v6vb/jveiXIHbDxlTLgyCofL1TKed6GSSCB1YZymzbfvSQslj5HylwUQ3HI6zHyWeh7nuwW25bkejDbuGq6endk542cycPqzVc/VBV4GV1QoqwgeHTn31jDjgPe8oADkT/uxPPXe9ZBp7dPsKPA8CpvkEOPV4Gkop9E1PgFPMJ8vztBnq0L+jmm3Ukl1v3IZCSLQf6hO7HJkqRlDsTZH+CxuR/jTgcfvbn/ceHfC6lxmsiH0jjkiXe/shinYouk1Lr17T7rq08aaY/+HDN2XdlfglYrTDl6KLOTdJivQmwbRfyf7vvNwX/Zwa3fH94/j0/ev3RHFxB3VzMn0eOn0VYgxzqL1ECqKWTKC3zbLJly+B5L4BPMUcnw/8zbHynaPNZ4jum2fUGdwBJxvcNRxecMkUhgh0Sz2SH8bASlGPVpBIOO/vAOc5mDoafaznN5b7IRm710cXprq00iz1FMpRIV7FSRLFgRVd38z3B5Df0hmwQ6X3+YngTa5y2fH4j1SgN1+UBiQ3Y4UJ2eyZK2geVWMPJnuSC0MnngdnLaD+pam9xn13R305W2P/XK4nJFeYrfWfUso32BSKPuTSS552lHct6WqWBTRAGaL3cZWxY1ZBH7t9nUrSFNx90MEwP7ILB06wOBu2WWounTnM/nQ+uVttg/9wj4bVZ2qdCM9Y1R2WYaTehEKRlew33We054y7tRw+oAY94v/bngN736wpOtLvI0q7/g64vWsIBVa0i2XhWzIw3U7t6BdldfCtDf2FyWyyale3rljHq827qtstZ5wnGWNaEEEy2YwwiB59u3jkCZi2juRbfhXPrc54dTw5gum8bYh4d1YTrvqY7XVxBPQevP89DRTpfrlG3xqnJo2QQjX68Eo/65BQUA6LELDcTUce3iwEcqGfWnfBRpR3nTg7++bPDX18s59fngb4yKOK+TJtoLWPCl81ONo7ltedrx3PRory/rjPp6TXL4vDNKeY7RMLGR1GWedbMeuTBx1ADCO/r5pXzU8Y+sNF+1uJp6R4Hp0a5Aei0dvaJdAa9gMCQ1ozpBiOSZoCRd/zSBw/lS9SXpgXgVAAZsDKjiZg7pmZUA9If9cAZPPeyh6VGXQDbqEuoVdQl4i3ELGr6vKA/pD27JDXyicNz8XsMa3liNwln0E0rmDa2HKkdYrx5clPvHwwIlyludYxzMngzMl1Uxpk9Qh7IxgFCvGEDIxwAquMrbeFejS1ZZkwcW5wBj5hlZlN9xyu67KXO4aYvuHphM1OXDUHv3RqmQKLabXfmIitgd0fT1fFJhukEfyhr0oV4Gfcgb9LDAdcUCBBQ+VqcwEogUPLUn+iTJpiyPPkmKkrdaoCoEwZdYzeGyghCZvmpMKL3anWbLxvAe108kCGGgUBCExdhKaP+2urZJSrZ7sZBYdulI6dXk8iLsH/e9f84stnepinph93jv3XOme5Yqu8fAqt1Laze1q9/wiYbC1cyMG+i52WL1C5hFpgddI9mISKRXRCR6j4gci4iIFiO6sCNoenpcJBsRifSKiETvERG9BMH0gHkkHRHRq3Q0eo+IaCUIS+MFQXqdes3sJH5y4siKjFmzcAZrhRSgNs2OpKwIGL09106Nu2CdF4PLzlMv+fGZmqkgz9ljn90K0wDID9MbNYD8xEiAs2Jd56Bq2I8JKGIT7X/bs/dBD5TnIi35lJgKrjNMmoF1hbrVBk0H2le/f3QvbyJ6UzuHKfZvD+wuuNH3ZUlHTYrJFYAfKk+HsdUYMZdfwLALaEotc69XvZjtGDjXM3cX6bUXu+0YON8zdxcpcyKEMz6Big4SIjA/8oFKoEPzJOHiHaR0ajp676CzHRQpHU36Bx0ZxCWsVqjaLIZlEidYNU4LEzhSPv852Hvjqk1Wf98UbL4UYJPFRkIdHPrUGkDNz6qNlv2sMayuAulQ+b5Ng/k1JnUOQXFVMKuPXdj8pMCI0PS56EhzFVhHygNyndl0DGsC8XUgvVQfFLL5GZcR0ikokrcvzKwF1rZ1yTFRnCmqR8UBC2h3J+2pXVdXYHiLsbhUSvXp5xx1BSAEbspmtWYKWfxSpk34v8ZtDXgCH+t1IxJsv8lpKsHPb4k1/WT7wZQXRUK1WNhKAjY6uKnSa1TavtIKBMFwrHavKuntcvY75gT9jgqMdWv3a1ppuK2OuGDEFvaMpgI153AkEjAxZGorfBxOvpTtXCXujikFzj09Qt1QibNcTyMhO82tvawB6ndjJkRvWHZ9XxDxD0jitBcVFVtqHiZs29aSX5dqKTAYlnMZDD3T9LGjw4n1Vm9rSRgG3oUU1+kHFdrR48KGsoJPGapxnzu2ZmkdbRYfC1Jx/TezJb0nVhMIybAwnj7epmOqFTC5MHmSRoDSOkWHj+IunCAnHTZ7HRn8XqP+xA1uUPtAL7Dt8rnBrj9PP63Z/9ZYb29GH669X3vqOk2MM9JKLYxlv09WJ7A3tkYGh/idrm6Qk5VLW6lxLti/+4qN85ZlJyTHWfZL32ohKqepNe6zbnvNxtqYcm9Z1abHMNc7mB6W4p1mh5T5EfyfAQHsi5+es7cuIqgc8cg9QNwRIB4KEA/nsvV4FYVTtG31AEb5mzf4nQBxgZLiOkEecVs14jZP8Q5y6twwt+bKALcE0wQXBdw1KjV6mqDmiVClTGwgUBvUFIzB12s3tfQ8aTdF+x6H9g6HYCfho7M+72J4XAyPxbsvJIb8QJXAJ5g3ZeOaxanfNGRxIThbdZzaEwRHd6kFKn2QCeBeBodGsADuudwOMdwyez4VyYeqonYaHVfYggpZfIrUr1cfr45pjMDzBeD1ba8dNY4Vm9uWd7CDSKsAu+/teoa7VcS5n/ZBB7dYcHeaapgS7N+4AlneFJVeo6XhHSkpGUdonL0ucXS3NKS2VN5JboEKOI/Ynh31j6WtqRJbbvCSFVsJDTC33MrM4r5JXQ9W43AwdYrXa6eUVNHF9l5JF/Vank/YxHXMVoe8Ti1/LOFvp+WDfn8L9f4kPawQm3HcXU5N1vRXlLBO+PR/ \ No newline at end of file diff --git a/markdown/images/StartupArchitecture.drawio.png b/markdown/images/StartupArchitecture.drawio.png deleted file mode 100644 index 387c79fe5..000000000 Binary files a/markdown/images/StartupArchitecture.drawio.png and /dev/null differ diff --git a/resources/ffmpeg.zip b/resources/ffmpeg.zip deleted file mode 100644 index b83661389..000000000 Binary files a/resources/ffmpeg.zip and /dev/null differ diff --git a/resources/ffplay.zip b/resources/ffplay.zip deleted file mode 100644 index 6cf9f825c..000000000 Binary files a/resources/ffplay.zip and /dev/null differ diff --git a/resources/ffprobe.zip b/resources/ffprobe.zip deleted file mode 100644 index 17387edbf..000000000 Binary files a/resources/ffprobe.zip and /dev/null differ diff --git a/resources/youtube-dl.zip b/resources/youtube-dl.zip deleted file mode 100644 index e66ecd6a5..000000000 Binary files a/resources/youtube-dl.zip and /dev/null differ diff --git a/src/main/java/cyder/audio/CPlayer.java b/src/main/java/cyder/audio/CPlayer.java index 8e599d429..2229d7a93 100644 --- a/src/main/java/cyder/audio/CPlayer.java +++ b/src/main/java/cyder/audio/CPlayer.java @@ -8,11 +8,10 @@ import cyder.logging.LogTag; import cyder.logging.Logger; import cyder.threads.CyderThreadRunner; +import javazoom.jl.decoder.JavaLayerException; import javazoom.jl.player.Player; -import java.io.BufferedInputStream; -import java.io.File; -import java.io.FileInputStream; +import java.io.*; import java.util.ArrayList; import java.util.Objects; @@ -84,12 +83,11 @@ public void play() { player = new Player(bis); player.play(); if (!canceled) onCompletionCallback.forEach(Runnable::run); - } catch (Exception e) { - ExceptionHandler.handle(e); + } catch (FileNotFoundException | JavaLayerException e) { + e.printStackTrace(); } finally { closeResources(); playing = false; - Console.INSTANCE.revalidateAudioMenuVisibility(); } }, audioFile.getAbsolutePath()); } @@ -120,8 +118,8 @@ private void closeResources() { bis = null; if (fis != null) fis.close(); fis = null; - } catch (Exception e) { - ExceptionHandler.handle(e); + } catch (IOException e) { + e.printStackTrace(); } } diff --git a/src/main/java/cyder/audio/player/AudioLocationUpdater.java b/src/main/java/cyder/audio/player/AudioLocationUpdater.java deleted file mode 100644 index dac56653a..000000000 --- a/src/main/java/cyder/audio/player/AudioLocationUpdater.java +++ /dev/null @@ -1,260 +0,0 @@ -package cyder.audio.player; - -import com.google.common.base.Preconditions; -import cyder.audio.AudioUtil; -import cyder.files.FileUtil; -import cyder.handlers.internal.ExceptionHandler; -import cyder.threads.CyderThreadRunner; -import cyder.threads.ThreadUtil; -import cyder.time.TimeUtil; -import cyder.user.UserDataManager; - -import javax.swing.*; -import java.io.File; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicReference; - -/** - * The class to update the audio location label and progress bar. - */ -final class AudioLocationUpdater { - /** - * The thread for setting up the props during object construction. - */ - private static final String SETUP_PROPS_THREAD_NAME = "AudioLocationUpdater setupProps Thread"; - - /** - * The timeout between progress label and slider updates. - */ - private static final int TIMEOUT = 100; - - /** - * Whether this AudioLocationUpdater has been killed. - */ - private boolean killed; - - /** - * The label this AudioLocationUpdater should update to display the seconds in. - */ - private final JLabel secondsInLabel; - - /** - * The label this AudioLocationUpdater should update to display the seconds remaining. - */ - private final JLabel secondsLeftLabel; - - /** - * The current frame view the audio player is in. - */ - private final AtomicReference currentFrameView; - - /** - * The current audio file of the audio player. - */ - private final AtomicReference currentAudioFile; - - /** - * Whether the slider is currently under a mouse pressed event. - */ - private final AtomicBoolean sliderPressed; - - /** - * The slider value to update. - */ - private final JSlider slider; - - /** - * The total milliseconds of the audio file this location updater was given. - */ - private long totalMilliSeconds; - - /** - * The number of milliseconds in to the audio file. - */ - private long milliSecondsIn; - - /** - * Whether the update thread has been started yet. - */ - private boolean started; - - /** - * Whether the seconds in value should be updated. - */ - private boolean timerPaused = true; - - /** - * The value passed to the updateEffectLabel method last. - */ - private int lastSecondsIn; - - /** - * Constructs a new audio location label to update for the provided progress bar. - * - * @param secondsInLabel the label to display how many seconds of the audio has played - * @param secondsLeftLabel the label to display how many seconds are remaining - * @param currentFrameView the audio player's atomic reference to the current frame view - * @param currentAudioFile the audio player's current audio file - * @param sliderPressed whether the provided slider is currently under a mouse pressed event - */ - public AudioLocationUpdater(JLabel secondsInLabel, JLabel secondsLeftLabel, - AtomicReference currentFrameView, - AtomicReference currentAudioFile, AtomicBoolean sliderPressed, - JSlider slider) { - this.secondsInLabel = Preconditions.checkNotNull(secondsInLabel); - this.secondsLeftLabel = Preconditions.checkNotNull(secondsLeftLabel); - this.currentFrameView = Preconditions.checkNotNull(currentFrameView); - this.currentAudioFile = Preconditions.checkNotNull(currentAudioFile); - this.sliderPressed = Preconditions.checkNotNull(sliderPressed); - this.slider = Preconditions.checkNotNull(slider); - - setupProps(); - } - - /** - * Determines the audio total length and updates the label in preparation for the update thread to start. - */ - private void setupProps() { - secondsInLabel.setText(""); - secondsLeftLabel.setText(""); - slider.setValue(0); - - if (!sliderPressed.get()) { - updateSlider(); - } - - CyderThreadRunner.submit(() -> { - try { - File file = currentAudioFile.get(); - this.totalMilliSeconds = AudioUtil.getMillisFfprobe(file); - updateEffectLabel((int) (Math.floor(milliSecondsIn / TimeUtil.millisInSecond)), false); - startUpdateThread(); - } catch (Exception e) { - ExceptionHandler.handle(e); - } - }, SETUP_PROPS_THREAD_NAME); - } - - /** - * Starts the thread to update the inner label - * - * @throws IllegalStateException if this method has already been invoked - */ - private void startUpdateThread() { - if (started) throw new IllegalStateException("Update thread already started"); - - started = true; - - String filename = "AudioLocationUpdater"; - try { - filename = FileUtil.getFilename(currentAudioFile.get()); - } catch (Exception e) { - ExceptionHandler.handle(e); - } - String threadName = filename + " Progress Label Thread"; - CyderThreadRunner.submit(() -> { - while (!killed) { - ThreadUtil.sleep(TIMEOUT); - - milliSecondsIn = AudioPlayer.getMillisecondsIn(); - int newSecondsIn = (int) (milliSecondsIn / TimeUtil.millisInSecond); - - if (!timerPaused && currentFrameView.get() != AudioPlayer.View.MINI) { - if (!sliderPressed.get()) { - updateEffectLabel(newSecondsIn, false); - updateSlider(); - } - } - } - }, threadName); - } - - /** - * Ends the updation of the label text. - */ - public void kill() { - killed = true; - } - - /** - * Stops incrementing the secondsIn value. - */ - public void pauseTimer() { - timerPaused = true; - } - - /** - * Starts incrementing the secondsIn value. - */ - public void resumeTimer() { - timerPaused = false; - } - - /** - * Forces an update of both labels and the slider. - * - * @param userTriggered whether this even was triggered by a user or automatically - */ - public void update(boolean userTriggered) { - updateEffectLabel((int) (Math.floor(milliSecondsIn / TimeUtil.millisInSecond)), userTriggered); - updateSlider(); - } - - /** - * Updates the encapsulated label with the time in to the current audio file. - * - * @param secondsIn the seconds into the current audio file - * @param userTriggered whether this update was invoked by a user or automatically - */ - private void updateEffectLabel(int secondsIn, boolean userTriggered) { - long milliSecondsLeft = totalMilliSeconds - secondsIn * 1000L; - int secondsLeft = (int) (milliSecondsLeft / TimeUtil.millisInSecond); - - if (secondsLeft < 0 || (secondsIn < lastSecondsIn && !userTriggered)) { - return; - } - - lastSecondsIn = secondsIn; - secondsInLabel.setText(formatMillisProxy((int) (secondsIn * 1000L))); - - boolean totalLength = UserDataManager.INSTANCE.shouldShowAudioTotalLength(); - if (totalLength) { - int displayMillis = (int) (Math.round(totalMilliSeconds / TimeUtil.millisInSecond) * 1000); - secondsLeftLabel.setText(formatMillisProxy(displayMillis)); - } else { - secondsLeftLabel.setText(formatMillisProxy((int) (secondsLeft * 1000L))); - } - } - - /** - * The proxy method to ensure "0s" is shown instead of "0ms" for the progress labels. - * - * @param millis the milliseconds to format - * @return the formatted milliseconds value - */ - private String formatMillisProxy(int millis) { - if (millis == 0) return "0s"; - else return TimeUtil.formatMillis(millis); - } - - /** - * Updates the reference slider's value. - */ - private void updateSlider() { - float percentIn = (float) milliSecondsIn / totalMilliSeconds; - - if (!killed) { - slider.setValue(Math.round(percentIn * slider.getMaximum())); - slider.repaint(); - } - } - - /** - * Sets the percent in to the current audio. - * - * @param percentIn the percent in to the current audio - */ - public void setPercentIn(float percentIn) { - this.milliSecondsIn = (int) (totalMilliSeconds * percentIn); - } -} diff --git a/src/main/java/cyder/audio/player/AudioPlayer.java b/src/main/java/cyder/audio/player/AudioPlayer.java deleted file mode 100644 index 4ee7ea689..000000000 --- a/src/main/java/cyder/audio/player/AudioPlayer.java +++ /dev/null @@ -1,2807 +0,0 @@ -package cyder.audio.player; - -import com.google.common.base.Preconditions; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.Range; -import com.google.common.util.concurrent.AtomicDouble; -import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.ListenableFuture; -import com.google.errorprone.annotations.CanIgnoreReturnValue; -import cyder.annotations.CyderAuthor; -import cyder.annotations.SuppressCyderInspections; -import cyder.annotations.Vanilla; -import cyder.annotations.Widget; -import cyder.audio.AudioUtil; -import cyder.console.Console; -import cyder.constants.CyderColors; -import cyder.constants.CyderFonts; -import cyder.constants.CyderRegexPatterns; -import cyder.constants.CyderUrls; -import cyder.enumerations.CyderInspection; -import cyder.enumerations.Dynamic; -import cyder.enumerations.Extension; -import cyder.exceptions.FatalException; -import cyder.exceptions.IllegalMethodException; -import cyder.files.FileUtil; -import cyder.getter.GetFileBuilder; -import cyder.getter.GetInputBuilder; -import cyder.getter.GetterUtil; -import cyder.handlers.external.ImageViewer; -import cyder.handlers.internal.ExceptionHandler; -import cyder.handlers.internal.InformHandler; -import cyder.math.NumberUtil; -import cyder.messaging.MessagingUtil; -import cyder.network.NetworkUtil; -import cyder.strings.CyderStrings; -import cyder.strings.StringUtil; -import cyder.threads.CyderThreadFactory; -import cyder.threads.CyderThreadRunner; -import cyder.threads.ThreadUtil; -import cyder.ui.UiUtil; -import cyder.ui.button.CyderButton; -import cyder.ui.button.CyderIconButton; -import cyder.ui.drag.CyderDragLabel; -import cyder.ui.drag.button.ChangeSizeButton; -import cyder.ui.drag.button.CloseButton; -import cyder.ui.drag.button.MinimizeButton; -import cyder.ui.drag.button.PinButton; -import cyder.ui.field.CyderCaret; -import cyder.ui.field.CyderModernTextField; -import cyder.ui.frame.CyderFrame; -import cyder.ui.frame.enumerations.MenuType; -import cyder.ui.frame.notification.NotificationBuilder; -import cyder.ui.label.CyderLabel; -import cyder.ui.pane.CyderOutputPane; -import cyder.ui.pane.CyderScrollPane; -import cyder.ui.slider.CyderSliderUi; -import cyder.ui.slider.ThumbShape; -import cyder.user.UserDataManager; -import cyder.user.UserFile; -import cyder.utils.*; -import cyder.youtube.YouTubeAudioDownload; -import cyder.youtube.YouTubeConstants; -import cyder.youtube.YouTubeUtil; -import cyder.youtube.parsers.YouTubeSearchResultPage; -import cyder.youtube.parsers.YouTubeVideo; - -import javax.imageio.ImageIO; -import javax.sound.sampled.AudioSystem; -import javax.sound.sampled.FloatControl; -import javax.sound.sampled.Port; -import javax.swing.*; -import javax.swing.border.LineBorder; -import javax.swing.text.DefaultCaret; -import javax.swing.text.Document; -import java.awt.*; -import java.awt.event.*; -import java.awt.image.BufferedImage; -import java.io.BufferedReader; -import java.io.File; -import java.io.IOException; -import java.io.InputStreamReader; -import java.net.URL; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.time.Instant; -import java.util.ArrayList; -import java.util.Optional; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicLong; -import java.util.concurrent.atomic.AtomicReference; - -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkNotNull; -import static cyder.audio.AudioIcons.*; - -/** - * An audio player widget which can also download YouTube video audio and thumbnails. - */ -@Vanilla -@CyderAuthor -@SuppressCyderInspections(CyderInspection.VanillaInspection) -public final class AudioPlayer { - /** - * The available views for this widget. - */ - enum View { - /** - * All ui elements visible. - */ - FULL, - - /** - * Album art hidden. - */ - HIDDEN_ALBUM_ART, - - /** - * Mini audio player mode. - */ - MINI, - - /** - * Searching YouTube for a video's audio to download. - */ - SEARCH, - } - - /** - * Primary ui control actions. - */ - private enum LastAction { - /** - * The user pressed play. - */ - Play, - - /** - * The user pressed skip back or skip forward. - */ - Skip, - - /** - * The audio was skipped - */ - Pause, - - /** - * The user changed the audio location. - */ - Scrub, - - /** - * An audio file was chosen using the file chooser menu option. - */ - FileChosen, - - /** - * Something else not yet handled. - */ - Unknown, - } - - /** - * The width and height of the audio frame. - */ - private static final int defaultFrameLength = 600; - - /** - * The width and height of the album art label. - */ - private static final int albumArtSize = 300; - - /** - * The number to subtract from the frame height when the frame is in mini mode. - */ - private static final int miniFrameHeightOffset = 150; - - /** - * The number to subtract from the frame height when the frame is in hidden art mode. - */ - private static final int hiddenArtHeightOffset = 40; - - /** - * The width of a primary ui control row. - */ - private static final int fullRowWidth = (int) (albumArtSize * 1.5); - - /** - * The height of a primary ui control row. - */ - private static final int fullRowHeight = 40; - - /** - * The label used to hold the album art or default album art if no album - * art exists if the audio player is in the standard audio view. - */ - private static final JLabel albumArtLabel = new JLabel(); - - /** - * The border width of black borders placed on some ui components. - */ - private static final int borderWidth = 3; - - /** - * The file representing the default album art to use if the frame is - * in the standard audio view and the current audio file has no linked album art. - */ - private static final File defaultAlbumArt = StaticUtil.getStaticResource("Default.png"); - - /** - * The default text to display for the audio title label. - */ - public static final String DEFAULT_AUDIO_TITLE = "No Audio Playing"; - - /** - * The label to display the current audio title. - */ - private static final JLabel audioTitleLabel = new JLabel("", SwingConstants.CENTER); - - /** - * The container to hold the audioTitleLabel used for animations like Spotify if the text overflows. - */ - private static final JLabel audioTitleLabelContainer = new JLabel(); - - /** - * The label to display the seconds in. - */ - private static final CyderLabel secondsInLabel = new CyderLabel(); - - /** - * The label to display the seconds remaining or total audio length. - */ - private static final CyderLabel totalSecondsLabel = new CyderLabel(); - - /** - * The default value for the audio volume slider. - */ - private static final int DEFAULT_AUDIO_SLIDER_VALUE = 50; - - /** - * The audio volume slider. - */ - private static final JSlider audioVolumeSlider = new JSlider( - JSlider.HORIZONTAL, 0, 100, DEFAULT_AUDIO_SLIDER_VALUE); - - /** - * The default value for the audio location slider. - */ - private static final int DEFAULT_LOCATION_SLIDER_VALUE = 0; - - /** - * The min value for the audio location slider. - */ - private static final int DEFAULT_LOCATION_SLIDER_MIN_VALUE = 0; - - /** - * The max value for the audio location slider. - */ - private static final int DEFAULT_LOCATION_SLIDER_MAX_VALUE = 450; - - /** - * The audio location slider. - */ - private static final JSlider audioLocationSlider = new JSlider( - JSlider.HORIZONTAL, DEFAULT_LOCATION_SLIDER_MIN_VALUE, - DEFAULT_LOCATION_SLIDER_MAX_VALUE, DEFAULT_LOCATION_SLIDER_VALUE); - - /** - * The ui for the audio volume slider. - */ - private static final CyderSliderUi audioVolumeSliderUi = new CyderSliderUi(audioVolumeSlider); - - /** - * the ui for the audio location slider. - */ - private static final CyderSliderUi audioLocationSliderUi = new CyderSliderUi(audioLocationSlider); - - /** - * The audio volume percent label which appears on change of the audio volume. - */ - private static final CyderLabel audioVolumePercentLabel = new CyderLabel(""); - - /** - * The size of the primary audio control buttons. - */ - private static final Dimension CONTROL_BUTTON_SIZE = new Dimension(30, 30); - - /** - * The play last audio icon button. - */ - private static final CyderIconButton lastAudioButton = - new CyderIconButton.Builder("Last", lastIcon, lastIconHover) - .setClickAction(AudioPlayer::handleLastAudioButtonClick).build(); - - /** - * The play next audio icon button. - */ - private static final CyderIconButton nextAudioButton = - new CyderIconButton.Builder("Next", nextIcon, nextIconHover) - .setClickAction(AudioPlayer::handleNextAudioButtonClick).build(); - - /** - * The repeat audio icon button. - */ - private static final CyderIconButton repeatAudioButton = - new CyderIconButton.Builder("Repeat", repeatIcon, repeatIconHover) - .setClickAction(AudioPlayer::handleRepeatButtonClick) - .setToggleButton(true).build(); - - /** - * The shuffle audio icon button. - */ - private static final CyderIconButton shuffleAudioButton = - new CyderIconButton.Builder("Shuffle", shuffleIcon, shuffleIconHover) - .setClickAction(AudioPlayer::handleShuffleButtonClick) - .setToggleButton(true).build(); - - /** - * The current frame view the audio player is in. - */ - private static final AtomicReference currentView = new AtomicReference<>(View.FULL); - - /** - * The frame background color. - */ - public static final Color BACKGROUND_COLOR = new Color(8, 23, 52); - - /** - * The background color of the track in front of the slider thumb. - */ - private static final Color trackNewColor = new Color(45, 45, 45); - - /** - * The list of songs to play next before sequentially proceeding to the next valid audio file. - */ - private static final ArrayList audioFileQueue = new ArrayList<>(); - - /** - * The current audio file we are at. - */ - private static final AtomicReference currentAudioFile = new AtomicReference<>(); - - /** - * Whether the audio location slider is currently pressed. - */ - private static final AtomicBoolean audioLocationSliderPressed = new AtomicBoolean(false); - - /** - * The thumb size for the sliders. - */ - private static final int THUMB_SIZE = 25; - - /** - * The value to increment the radius of the sliders by on click events. - */ - private static final int BIG_THUMB_INC = 5; - - /** - * The thumb size for the sliders on click events. - */ - private static final int BIG_THUMB_SIZE = THUMB_SIZE + 2 * BIG_THUMB_INC; - - /** - * The audio progress bar animation controller. - */ - private static final AudioProgressBarAnimator audioProgressBarAnimator - = new AudioProgressBarAnimator(audioLocationSlider, audioLocationSliderUi); - - /** - * The stroke for the audio volume and location sliders. - */ - private static final BasicStroke sliderStroke = new BasicStroke(2.0f); - - /** - * The location of a quick single click audio location request. - */ - private static final AtomicDouble possiblePercentRequest = new AtomicDouble(Long.MAX_VALUE); - - /** - * The time of a quick single click audio location request mouse initial press event. - */ - private static final AtomicLong possiblePercentRequestTime = new AtomicLong(Long.MAX_VALUE); - - /** - * The window of time between the possible percent request initial - * press and release to perform the percent request. - */ - private static final int POSSIBLE_PERCENT_REQUEST_WINDOW = 100; - - /** - * The total percent of a completed YouTube audio download. - */ - private static final float completedProgress = 100.0f; - - /** - * The switch view mode text. - */ - private static final String SWITCH_VIEW_MODE = "Switch view mode"; - - /** - * Whether the frame is currently being setup. - */ - private static final AtomicBoolean settingUpFrame = new AtomicBoolean(); - - /** - * The name of the audio player preliminary handler thread. - */ - private static final String audioPlayerPreliminaryHandlerThreadName = "AudioPlayer Preliminary Handler"; - - /** - * The getter util instance used to get input from the user - * for exporting waveform files and for choosing local audio files. - */ - private static final GetterUtil getterUtil = GetterUtil.getInstance(); - - /** - * The name of the default audio mp3 file and album art png file. - */ - private static final String defaultAudioFileName = "Logic - Cocaine"; - - /** - * The animator object for the audio volume percent. - * This is set upon the frame appearing and is only killed when the widget is killed. - */ - private static AudioVolumeLabelAnimator audioVolumeLabelAnimator; - - /** - * The animator object for the audio location label. - * This is set and the previous object killed whenever a new audio file is initiated. - */ - private static AudioLocationUpdater audioLocationUpdater; - - /** - * The scrolling title label to display and scroll the current - * audio title if it exceeds the parent container's bounds. - */ - private static ScrollingTitleLabel scrollingTitleLabel; - - /** - * The last action invoked by the user. - */ - private static LastAction lastAction = LastAction.Unknown; - - /** - * The actual object that plays audio. - */ - private static InnerAudioPlayer innerAudioPlayer; - - /** - * The default album art image. - */ - private static BufferedImage defaultAlbumArtImage; - - static { - try { - defaultAlbumArtImage = ImageUtil.read(defaultAlbumArt); - } catch (Exception ignored) {} - } - - /** - * The audio player frame. - */ - private static CyderFrame audioPlayerFrame; - - /** - * The album art directory for the current Cyder user. - */ - private static File currentUserAlbumArtDir; - - /** - * The play pause icon button. - */ - private static JButton playPauseButton; - - /** - * Suppress default constructor. - */ - private AudioPlayer() { - throw new IllegalMethodException(CyderStrings.ATTEMPTED_INSTANTIATION); - } - - /** - * Allow widget to be found via reflection. - */ - @Widget(triggers = {"mp3", "wav", "music", "audio"}, description = "An advanced audio playing widget") - public static void showGui() { - File userMusicDir = Dynamic.buildDynamic(Dynamic.USERS.getFileName(), - Console.INSTANCE.getUuid(), UserFile.MUSIC.getName()); - - File[] userMusicFiles = userMusicDir.listFiles((dir, name) -> - FileUtil.isSupportedAudioExtension(OsUtil.buildFile(userMusicDir.getAbsolutePath(), name))); - - if (userMusicFiles != null && userMusicFiles.length > 0) { - showGui(ArrayUtil.getRandomElement(userMusicFiles)); - } else { - if (OsUtil.isWindows()) { - Optional optionalMp3File = AudioUtil.getFirstMp3FileForWindowsUser(); - optionalMp3File.ifPresent(AudioPlayer::showGui); - if (optionalMp3File.isPresent()) return; - } - - showGui(cloneDefaultAudioForCurrentUser()); - } - } - - /** - * Shows the audio player gui starting with the provided mp3 file. - * The loading of the widget is performed in a separate thread to avoid freezing the UI thread. - * - * @param mp3File the mp3 file to show the audio player frame on - * @throws NullPointerException if mp3File is null - * @throws IllegalArgumentException if mp3File does not exist, or is not a supported audio file - */ - public static void showGui(File mp3File) { - checkNotNull(mp3File); - checkArgument(mp3File.exists()); - checkArgument(FileUtil.isSupportedAudioExtension(mp3File)); - - if (settingUpFrame.get()) return; - settingUpFrame.compareAndSet(false, true); - - CyderThreadFactory threadFactory = new CyderThreadFactory("AudioPlayer loader, mp3File: " + mp3File); - Executors.newSingleThreadExecutor(threadFactory).submit(() -> { - currentAudioFile.set(mp3File); - audioDreamified.set(isCurrentAudioDreamy()); - - if (isWidgetOpen()) { - if (currentView.get() == View.SEARCH) onBackPressedFromSearchView(); - boolean audioPlaying = isAudioPlaying(); - if (audioPlaying) pauseAudio(); - revalidateAfterAudioFileChange(); - innerAudioPlayer = new InnerAudioPlayer(currentAudioFile.get()); - innerAudioPlayer.setLocation(0); - audioLocationUpdater.setPercentIn(0f); - audioLocationUpdater.update(false); - if (audioPlaying) playAudio(); - return; - } - - cacheAudioLengthsOfCurrentDirectory(); - - currentUserAlbumArtDir = Dynamic.buildDynamic(Dynamic.USERS.getFileName(), - Console.INSTANCE.getUuid(), UserFile.MUSIC.getName(), UserFile.ALBUM_ART); - - audioPlayerFrame = new CyderFrame.Builder() - .setTitle(DEFAULT_FRAME_TITLE) - .setSize(new Dimension(defaultFrameLength, defaultFrameLength)) - .setBackgroundColor(BACKGROUND_COLOR) - .setBackgroundIconFromColor(BACKGROUND_COLOR) - .build(); - refreshFrameTitle(); - addChangeSizeButtonToTopDragLabel(); - audioPlayerFrame.setMenuType(MenuType.PANEL); - audioPlayerFrame.setMenuButtonShown(true); - audioPlayerFrame.addWindowListener(new WindowAdapter() { - @Override - public void windowClosing(WindowEvent e) { - onFrameClosingOrClosed(); - } - - @Override - public void windowClosed(WindowEvent e) { - onFrameClosingOrClosed(); - } - }); - installFrameMenuItems(); - - /* - All components which will ever be on the frame for the audio player are added now and their sizes set. - The bounds are set in the view switcher. - The sizes are almost never set outside the construction below. - */ - - albumArtLabel.setSize(albumArtSize, albumArtSize); - albumArtLabel.setOpaque(true); - albumArtLabel.setBackground(BACKGROUND_COLOR); - albumArtLabel.setBorder(new LineBorder(Color.BLACK, borderWidth)); - audioPlayerFrame.getContentPane().add(albumArtLabel); - - albumArtLabel.add(dreamyLabel); - dreamyLabel.setSize(albumArtLabel.getSize()); - dreamyLabel.setFont(dreamyLabel.getFont().deriveFont(150f)); - dreamyLabel.setVisible(false); - - audioTitleLabelContainer.setSize(fullRowWidth, fullRowHeight); - audioTitleLabel.setSize(fullRowWidth, fullRowHeight); - audioTitleLabel.setText(DEFAULT_AUDIO_TITLE); - audioTitleLabel.setFont(CyderFonts.DEFAULT_FONT_SMALL); - audioTitleLabel.setForeground(CyderColors.vanilla); - - audioTitleLabelContainer.add(audioTitleLabel, SwingConstants.CENTER); - audioPlayerFrame.getContentPane().add(audioTitleLabelContainer); - - shuffleAudioButton.setSize(CONTROL_BUTTON_SIZE); - audioPlayerFrame.getContentPane().add(shuffleAudioButton); - - lastAudioButton.setSize(CONTROL_BUTTON_SIZE); - audioPlayerFrame.getContentPane().add(lastAudioButton); - - /* - Note to maintainers: play pause button is special and is the only one of the primary control buttons - initialized here. The others are initialized using CyderIconButtons as class level final members. - */ - - playPauseButton = new JButton(); - refreshPlayPauseButtonIcon(); - playPauseButton.setFocusPainted(false); - playPauseButton.setFocusable(true); - playPauseButton.setOpaque(false); - playPauseButton.setContentAreaFilled(false); - playPauseButton.setBorderPainted(false); - playPauseButton.setVisible(true); - playPauseButton.addMouseListener(new MouseAdapter() { - @Override - public void mouseClicked(MouseEvent e) { - handlePlayPauseButtonClick(); - } - - @Override - public void mouseEntered(MouseEvent e) { - playPauseButton.setIcon(isAudioPlaying() ? pauseIconHover : playIconHover); - } - - @Override - public void mouseExited(MouseEvent e) { - playPauseButton.setIcon(isAudioPlaying() ? pauseIcon : playIcon); - } - }); - playPauseButton.addFocusListener(new FocusAdapter() { - @Override - public void focusGained(FocusEvent e) { - playPauseButton.setIcon(isAudioPlaying() ? pauseIconHover : playIconHover); - } - - @Override - public void focusLost(FocusEvent e) { - playPauseButton.setIcon(isAudioPlaying() ? pauseIcon : playIcon); - } - }); - playPauseButton.addKeyListener(new KeyAdapter() { - @Override - public void keyPressed(KeyEvent e) { - if (e.getKeyCode() == KeyEvent.VK_ENTER) { - handlePlayPauseButtonClick(); - } - } - }); - playPauseButton.setSize(CONTROL_BUTTON_SIZE); - audioPlayerFrame.getContentPane().add(playPauseButton); - - setupFocusTraversal(); - - nextAudioButton.setSize(CONTROL_BUTTON_SIZE); - audioPlayerFrame.getContentPane().add(nextAudioButton); - - repeatAudioButton.setSize(CONTROL_BUTTON_SIZE); - audioPlayerFrame.getContentPane().add(repeatAudioButton); - - audioLocationSliderUi.setThumbStroke(sliderStroke); - audioLocationSliderUi.setThumbShape(ThumbShape.CIRCLE); - audioLocationSliderUi.setThumbRadius(25); - audioLocationSliderUi.setThumbFillColor(CyderColors.vanilla); - audioLocationSliderUi.setThumbOutlineColor(CyderColors.vanilla); - audioLocationSliderUi.setRightThumbColor(trackNewColor); - audioLocationSliderUi.setLeftThumbColor(CyderColors.vanilla); - audioLocationSliderUi.setTrackStroke(sliderStroke); - audioLocationSliderUi.setAnimationEnabled(true); - audioLocationSliderUi.setAnimationLen(75); - - audioLocationSlider.setSize(fullRowWidth, fullRowHeight); - audioLocationSlider.setMinorTickSpacing(1); - audioLocationSlider.setValue(DEFAULT_LOCATION_SLIDER_VALUE); - audioLocationSlider.setMinimum(DEFAULT_LOCATION_SLIDER_MIN_VALUE); - audioLocationSlider.setMaximum(DEFAULT_LOCATION_SLIDER_MAX_VALUE); - audioLocationSlider.setUI(audioLocationSliderUi); - audioLocationSlider.setPaintTicks(false); - audioLocationSlider.setPaintLabels(false); - audioLocationSlider.setVisible(true); - audioLocationSlider.addChangeListener(e -> { - if (audioTotalLength == unknownAudioLength || audioTotalLength == 0) { - audioTotalLength = FileUtil.getTotalBytes(currentAudioFile.get()); - } - - if (audioLocationUpdater != null) { - audioLocationUpdater.setPercentIn((float) audioLocationSlider.getValue() - / audioLocationSlider.getMaximum()); - audioLocationUpdater.update(true); - } - }); - audioLocationSlider.addMouseListener(new MouseAdapter() { - @Override - public void mousePressed(MouseEvent e) { - audioLocationSliderPressed.set(true); - - audioLocationSliderUi.setThumbRadius(BIG_THUMB_SIZE); - audioLocationSlider.repaint(); - - possiblePercentRequest.set(e.getX() / (float) audioLocationSlider.getWidth()); - possiblePercentRequestTime.set(System.currentTimeMillis()); - } - - @Override - public void mouseReleased(MouseEvent e) { - if (audioLocationSliderPressed.get()) { - audioLocationSliderPressed.set(false); - - boolean wasPlaying = isAudioPlaying(); - - if (wasPlaying) { - float newPercentIn = - (float) audioLocationSlider.getValue() / audioLocationSlider.getMaximum(); - - if (System.currentTimeMillis() - possiblePercentRequestTime.get() - < POSSIBLE_PERCENT_REQUEST_WINDOW) { - newPercentIn = (float) possiblePercentRequest.get(); - possiblePercentRequest.set(Long.MAX_VALUE); - possiblePercentRequestTime.set(Long.MAX_VALUE); - } - - long resumeLocation = (long) (newPercentIn * innerAudioPlayer.getTotalAudioLength()); - - audioLocationUpdater.pauseTimer(); - pauseAudio(); - - innerAudioPlayer = new InnerAudioPlayer(currentAudioFile.get()); - innerAudioPlayer.setLocation(resumeLocation); - audioLocationUpdater.setPercentIn(newPercentIn); - - playAudio(); - } else { - if (audioTotalLength == unknownAudioLength || audioTotalLength == 0) { - audioTotalLength = FileUtil.getTotalBytes(currentAudioFile.get()); - } - - float newPercentIn = - (float) audioLocationSlider.getValue() / audioLocationSlider.getMaximum(); - long resumeLocation = (long) (newPercentIn * audioTotalLength); - - innerAudioPlayer = new InnerAudioPlayer(currentAudioFile.get()); - innerAudioPlayer.setLocation(resumeLocation); - audioLocationUpdater.setPercentIn(newPercentIn); - audioLocationUpdater.update(true); - } - } - - audioLocationSliderUi.setThumbRadius(THUMB_SIZE); - audioLocationSlider.repaint(); - } - }); - audioLocationSlider.setOpaque(false); - audioLocationSlider.setFocusable(false); - audioLocationSlider.repaint(); - audioPlayerFrame.getContentPane().add(audioLocationSlider); - - secondsInLabel.setSize(fullRowWidth / 4, fullRowHeight); - secondsInLabel.setText(""); - secondsInLabel.setHorizontalAlignment(JLabel.LEFT); - secondsInLabel.setForeground(CyderColors.vanilla); - audioPlayerFrame.getContentPane().add(secondsInLabel); - secondsInLabel.setFocusable(false); - - totalSecondsLabel.setSize(fullRowWidth / 4, fullRowHeight); - totalSecondsLabel.setText(""); - totalSecondsLabel.setHorizontalAlignment(JLabel.RIGHT); - totalSecondsLabel.setForeground(CyderColors.vanilla); - audioPlayerFrame.getContentPane().add(totalSecondsLabel); - totalSecondsLabel.setFocusable(false); - - audioLocationUpdater = new AudioLocationUpdater(secondsInLabel, totalSecondsLabel, currentView, - currentAudioFile, audioLocationSliderPressed, audioLocationSlider); - - audioVolumeSliderUi.setThumbStroke(sliderStroke); - audioVolumeSliderUi.setThumbShape(ThumbShape.CIRCLE); - audioVolumeSliderUi.setThumbRadius(25); - audioVolumeSliderUi.setThumbFillColor(CyderColors.vanilla); - audioVolumeSliderUi.setThumbOutlineColor(CyderColors.vanilla); - audioVolumeSliderUi.setRightThumbColor(trackNewColor); - audioVolumeSliderUi.setLeftThumbColor(CyderColors.vanilla); - audioVolumeSliderUi.setTrackStroke(sliderStroke); - - audioVolumePercentLabel.setForeground(CyderColors.vanilla); - audioVolumePercentLabel.setSize(100, 40); - audioVolumePercentLabel.setVisible(false); - audioPlayerFrame.getContentPane().add(audioVolumePercentLabel); - - if (audioVolumeLabelAnimator != null) audioVolumeLabelAnimator.kill(); - audioVolumeLabelAnimator = new AudioVolumeLabelAnimator(audioVolumePercentLabel); - - audioVolumeSlider.setSize(fullRowWidth, fullRowHeight); - audioPlayerFrame.getContentPane().add(audioVolumeSlider); - audioVolumeSlider.setUI(audioVolumeSliderUi); - audioVolumeSlider.setMinimum(0); - audioVolumeSlider.setMaximum(100); - audioVolumeSlider.setPaintTicks(false); - audioVolumeSlider.setPaintLabels(false); - audioVolumeSlider.setVisible(true); - audioVolumeSlider.setValue(UserDataManager.INSTANCE.getAudioPlayerVolumePercent()); - audioVolumeSlider.addChangeListener(e -> { - if (uiLocked) return; - - int percent = audioVolumeSlider.getValue(); - UserDataManager.INSTANCE.setAudioPlayerVolumePercent(percent); - - refreshAudioLine(); - audioVolumePercentLabel.setText(percent + "%"); - audioVolumeLabelAnimator.onValueChanged(); - }); - audioVolumeSlider.addMouseListener(new MouseAdapter() { - @Override - public void mousePressed(MouseEvent e) { - audioVolumeSliderUi.setThumbRadius(BIG_THUMB_SIZE); - audioVolumeSlider.repaint(); - } - - @Override - public void mouseReleased(MouseEvent e) { - audioVolumeSliderUi.setThumbRadius(THUMB_SIZE); - audioVolumeSlider.repaint(); - } - }); - audioVolumeSlider.setOpaque(false); - audioVolumeSlider.setToolTipText("Volume"); - audioVolumeSlider.setFocusable(false); - audioVolumeSlider.repaint(); - refreshAudioLine(); - - setAudioPlayerComponentsVisible(false); - setupAndShowFrameView(View.FULL); - audioPlayerFrame.finalizeAndShow(); - Console.INSTANCE.revalidateAudioMenuVisibility(); - - if (!AudioUtil.ffmpegInstalled() || !AudioUtil.youTubeDlInstalled()) { - downloadFfmpegAndYouTubeDl(); - } else { - settingUpFrame.compareAndSet(true, false); - } - }); - } - - /** - * Adds the change size button to the top drag label. - */ - private static void addChangeSizeButtonToTopDragLabel() { - Preconditions.checkState(audioPlayerFrame.getTopDragLabel().getRightButtonList().size() == 3); - - ChangeSizeButton changeSizeButton = new ChangeSizeButton(); - changeSizeButton.setToolTipText(SWITCH_VIEW_MODE); - changeSizeButton.setClickAction(() -> { - switch (currentView.get()) { - case FULL -> setupAndShowFrameView(View.HIDDEN_ALBUM_ART); - case HIDDEN_ALBUM_ART -> setupAndShowFrameView(View.MINI); - case MINI -> setupAndShowFrameView(View.FULL); - case SEARCH -> onBackPressedFromSearchView(); - } - }); - audioPlayerFrame.getTopDragLabel().addRightButton(changeSizeButton, 1); - } - - /** - * The actions to invoke when the frame is closing or closed. Caught via the {@link WindowListener}. - */ - private static void onFrameClosingOrClosed() { - // no other pre/post close window Runnables - // should be added or window listeners - killAndCloseWidget(); - } - - /** - * Sets up the focus traversal system for the primary control components. - */ - private static void setupFocusTraversal() { - ImmutableList buttons = audioPlayerFrame.getTopDragLabel().getRightButtonList(); - MinimizeButton minimizeButton = (MinimizeButton) buttons.get(0); - ChangeSizeButton changeSizeButton = (ChangeSizeButton) buttons.get(1); - PinButton pinButton = (PinButton) buttons.get(2); - CloseButton closeButton = (CloseButton) buttons.get(3); - - shuffleAudioButton.addFocusListener(new FocusAdapter() { - @Override - public void focusLost(FocusEvent e) { - if (currentView.get() != View.SEARCH) lastAudioButton.requestFocus(); - } - }); - lastAudioButton.addFocusListener(new FocusAdapter() { - @Override - public void focusLost(FocusEvent e) { - if (currentView.get() != View.SEARCH) playPauseButton.requestFocus(); - } - }); - playPauseButton.addFocusListener(new FocusAdapter() { - @Override - public void focusLost(FocusEvent e) { - if (currentView.get() != View.SEARCH) nextAudioButton.requestFocus(); - } - }); - nextAudioButton.addFocusListener(new FocusAdapter() { - @Override - public void focusLost(FocusEvent e) { - if (currentView.get() != View.SEARCH) repeatAudioButton.requestFocus(); - } - }); - repeatAudioButton.addFocusListener(new FocusAdapter() { - @Override - public void focusLost(FocusEvent e) { - if (currentView.get() != View.SEARCH) minimizeButton.requestFocus(); - } - }); - - minimizeButton.setFocusPaintable(true); - changeSizeButton.setFocusPaintable(true); - pinButton.setFocusPaintable(true); - closeButton.setFocusPaintable(true); - - minimizeButton.addFocusLostAction(() -> { - if (currentView.get() != View.SEARCH) changeSizeButton.requestFocus(); - }); - changeSizeButton.addFocusLostAction(() -> { - if (currentView.get() != View.SEARCH) pinButton.requestFocus(); - }); - pinButton.addFocusLostAction(() -> { - if (currentView.get() != View.SEARCH) closeButton.requestFocus(); - }); - } - - /** - * Attempts to download FFmpeg and YouTube-dl for downloading and processing audio files. - */ - private static void downloadFfmpegAndYouTubeDl() { - CyderThreadRunner.submit(() -> { - try { - lockUi(); - - audioPlayerFrame.notify("Attempting to download FFmpeg and YouTube-dl"); - - Future passedPreliminaries = attemptToDownloadFfmpegAndYoutubeDl(); - while (!passedPreliminaries.isDone()) Thread.onSpinWait(); - - // wait to start playing if downloading - if (!passedPreliminaries.get()) { - audioPlayerFrame.revokeAllNotifications(); - - /* - Note to maintainers, cannot cache this because relative to is the frame. - */ - new InformHandler.Builder("Could not download necessary " - + "binaries. Try to install both ffmpeg and YouTube-dl and try again") - .setTitle("Network Error") - .setRelativeTo(audioPlayerFrame) - .setPostCloseAction(() -> { - killAndCloseWidget(); - NetworkUtil.openUrl(CyderUrls.FFMPEG_INSTALLATION); - NetworkUtil.openUrl(CyderUrls.YOUTUBE_DL_INSTALLATION); - }).inform(); - } else { - audioPlayerFrame.revokeAllNotifications(); - unlockUi(); - audioPlayerFrame.notify("Successfully downloaded necessary binaries"); - settingUpFrame.compareAndSet(true, false); - } - } catch (Exception e) { - unlockUi(); - settingUpFrame.compareAndSet(true, false); - ExceptionHandler.handle(e); - } - }, audioPlayerPreliminaryHandlerThreadName); - } - - /** - * Sets the visibility of all audio player components to the value of visible. - * - * @param visible whether to set audio player components to visible - */ - public static void setAudioPlayerComponentsVisible(boolean visible) { - albumArtLabel.setVisible(visible); - - audioTitleLabel.setVisible(visible); - audioTitleLabelContainer.setVisible(visible); - - shuffleAudioButton.setVisible(visible); - lastAudioButton.setVisible(visible); - playPauseButton.setVisible(visible); - nextAudioButton.setVisible(visible); - repeatAudioButton.setVisible(visible); - - secondsInLabel.setVisible(visible); - totalSecondsLabel.setVisible(visible); - - audioVolumeSlider.setVisible(visible); - audioLocationSlider.setVisible(visible); - } - - /** - * Clones the default audio file and album art to the user's music directory. - * - * @return the copied audio file - * @throws FatalException if copying either of the files fails - */ - @CanIgnoreReturnValue - private static File cloneDefaultAudioForCurrentUser() { - File audioFile = StaticUtil.getStaticResource(defaultAudioFileName + Extension.MP3); - File audioDirectory = UserFile.MUSIC.getFilePointer(); - File newAudioFile = OsUtil.buildFile(UserFile.MUSIC.getFilePointer() - .getAbsolutePath(), audioFile.getName()); - - File albumArtFile = StaticUtil.getStaticResource(defaultAudioFileName + Extension.PNG); - File albumArtDirectory = OsUtil.buildFile(audioDirectory.getAbsolutePath(), UserFile.ALBUM_ART); - File newArtFile = OsUtil.buildFile(albumArtDirectory.getAbsolutePath(), albumArtFile.getName()); - - try { - Files.copy(audioFile.toPath(), newAudioFile.toPath()); - Files.copy(albumArtFile.toPath(), newArtFile.toPath()); - } catch (IOException e) { - throw new FatalException(e.getMessage()); - } - - return newAudioFile; - } - - /** - * The thread factory for the {@link #cacheAudioLengthsOfCurrentDirectory()} method. - */ - private static final CyderThreadFactory audioLengthsOfCurrentDirectoryCacherThreadFactory = - new CyderThreadFactory("AudioPlayer neighboring audio files length calculation cacher"); - - /** - * The future task of the {@link #cacheAudioLengthsOfCurrentDirectory()} method. - */ - private static ListenableFuture audioLengthsOfCurrentDirectoryCacher; - - /** - * Starts a new thread to cache the length of all audio files returned by {@link #getValidAudioFiles()}. - */ - private static void cacheAudioLengthsOfCurrentDirectory() { - if (audioLengthsOfCurrentDirectoryCacher != null) { - audioLengthsOfCurrentDirectoryCacher.cancel(true); - } - - audioLengthsOfCurrentDirectoryCacher = Futures.submit(() -> getValidAudioFiles().forEach(audioFile -> { - try { - AudioUtil.getMillisFfprobe(audioFile); - } catch (Exception ignored) { - // Don't care in this scenario - } - }), Executors.newSingleThreadExecutor(audioLengthsOfCurrentDirectoryCacherThreadFactory)); - } - - /** - * Whether the UI is locked. - */ - private static boolean uiLocked; - - /** - * Locks the UI components of the audio player. - */ - public static void lockUi() { - uiLocked = true; - audioPlayerFrame.setMenuButtonShown(false); - } - - /** - * Unlocks the UI components of the audio player. - */ - public static void unlockUi() { - uiLocked = false; - audioPlayerFrame.setMenuButtonShown(true); - } - - /** - * Attempts to download ffmpeg and youtube-dl. - * - * @return whether ffmpeg and youtube-dl were downloaded successfully. - */ - @SuppressWarnings("RedundantIfStatement") /* Readability */ - private static Future attemptToDownloadFfmpegAndYoutubeDl() { - CyderThreadFactory factory = new CyderThreadFactory("AudioPlayer Preliminary Handler"); - return Executors.newSingleThreadExecutor(factory).submit(() -> { - if (!AudioUtil.youTubeDlInstalled()) { - Future downloadedYoutubeDl = AudioUtil.downloadYoutubeDl(); - while (!downloadedYoutubeDl.isDone()) Thread.onSpinWait(); - if (!downloadedYoutubeDl.get()) return false; - } - - if (!AudioUtil.ffmpegInstalled()) { - Future ffmpegDownloaded = AudioUtil.downloadFfmpegStack(); - while (!ffmpegDownloaded.isDone()) Thread.onSpinWait(); - if (!ffmpegDownloaded.get()) return false; - } - - return true; - }); - } - - /** - * Whether the wav exporter menu option is locked. - */ - private static final AtomicBoolean wavExporterLocked = new AtomicBoolean(); - - /** - * Whether the mp3 exporter menu option is locked. - */ - private static final AtomicBoolean mp3ExporterLocked = new AtomicBoolean(); - - /** - * Whether the waveform exporter menu option is locked. - */ - private static final AtomicBoolean waveformExporterLocked = new AtomicBoolean(); - - /** - * Whether the audio file chooser menu option is locked. - */ - private static final AtomicBoolean chooseFileLocked = new AtomicBoolean(); - - /** - * Whether the dreamify menu option is locked. - */ - private static final AtomicBoolean dreamifierLocked = new AtomicBoolean(); - - /** - * Whether the current audio is dreamified. - */ - private static final AtomicBoolean audioDreamified = new AtomicBoolean(); - - /** - * Whether the current frame view is the search view. - */ - private static final AtomicBoolean onSearchView = new AtomicBoolean(); - - /** - * Installs all the menu options on the AudioPlayer frame. - */ - private static void installFrameMenuItems() { - audioPlayerFrame.clearMenuItems(); - - audioPlayerFrame.addMenuItem("Export wav", AudioPlayer::onExportWavMenuItemPressed); - audioPlayerFrame.addMenuItem("Export mp3", AudioPlayer::onExportMp3MenuItemPressed); - audioPlayerFrame.addMenuItem("Waveform", AudioPlayer::onWaveformExporterMenuItemPressed); - audioPlayerFrame.addMenuItem("Search", AudioPlayer::onSearchMenuItemPressed, onSearchView); - audioPlayerFrame.addMenuItem("Local Audio", AudioPlayer::onLocalAudioFileMenuItemPressed); - audioPlayerFrame.addMenuItem("Dreamify", AudioPlayer::onDreamifyMenuItemPressed, audioDreamified); - } - - /** - * The menu item to export the current audio as a wav. - */ - private static void onExportWavMenuItemPressed() { - if (wavExporterLocked.get() || uiLocked) return; - - if (FileUtil.validateExtension(currentAudioFile.get(), Extension.WAV.getExtension())) { - audioPlayerFrame.notify("This file is already a wav"); - } else if (FileUtil.validateExtension(currentAudioFile.get(), Extension.MP3.getExtension())) { - CyderThreadRunner.submit(() -> { - Future> wavConvertedFile = AudioUtil.mp3ToWav(currentAudioFile.get()); - - wavExporterLocked.set(true); - - while (!wavConvertedFile.isDone()) { - Thread.onSpinWait(); - } - - wavExporterLocked.set(false); - - try { - if (wavConvertedFile.get().isPresent()) { - File moveTo = Dynamic.buildDynamic( - Dynamic.USERS.getFileName(), - Console.INSTANCE.getUuid(), - UserFile.MUSIC.getName(), - FileUtil.getFilename(wavConvertedFile.get().get()) + Extension.WAV.getExtension()); - - Files.copy(Paths.get(wavConvertedFile.get().get().getAbsolutePath()), - Paths.get(moveTo.getAbsolutePath())); - - audioPlayerFrame.notify("Saved \"" - + moveTo.getName() + "\" to your music directory"); - } else { - audioPlayerFrame.notify("Could not convert \"" - + currentAudioFile.get().getName() + "\" to a wav at this time"); - } - } catch (Exception e) { - ExceptionHandler.handle(e); - } - }, "Wav exporter"); - } else { - throw new IllegalArgumentException("Unsupported audio format: " + currentAudioFile.get().getName()); - } - } - - /** - * The menu item for exporting the current audio as an mp3. - */ - private static void onExportMp3MenuItemPressed() { - if (mp3ExporterLocked.get() || uiLocked) return; - - if (FileUtil.validateExtension(currentAudioFile.get(), Extension.MP3.getExtension())) { - audioPlayerFrame.notify("This file is already an mp3"); - } else if (FileUtil.validateExtension(currentAudioFile.get(), Extension.WAV.getExtension())) { - CyderThreadRunner.submit(() -> { - Future> mp3ConvertedFile = AudioUtil.wavToMp3(currentAudioFile.get()); - - mp3ExporterLocked.set(true); - - while (!mp3ConvertedFile.isDone()) { - Thread.onSpinWait(); - } - - mp3ExporterLocked.set(false); - - try { - if (mp3ConvertedFile.get().isPresent()) { - File moveTo = Dynamic.buildDynamic( - Dynamic.USERS.getFileName(), - Console.INSTANCE.getUuid(), - UserFile.MUSIC.getName(), - FileUtil.getFilename(mp3ConvertedFile.get().get()) + Extension.MP3.getExtension()); - - Files.copy(Paths.get(mp3ConvertedFile.get().get().getAbsolutePath()), - Paths.get(moveTo.getAbsolutePath())); - - audioPlayerFrame.notify("Saved \"" - + moveTo.getName() + "\" to your music directory"); - } else { - audioPlayerFrame.notify("Could not convert \"" - + currentAudioFile.get().getName() + "\" to an mp3 at this time"); - } - } catch (Exception e) { - ExceptionHandler.handle(e); - } - }, "Mp3 exporter"); - } else { - throw new IllegalArgumentException("Unsupported audio format: " + currentAudioFile.get().getName()); - } - } - - /** - * The logic for the export waveform menu option. - */ - private static void onWaveformExporterMenuItemPressed() { - if (waveformExporterLocked.get() || uiLocked) return; - - CyderThreadRunner.submit(() -> { - getterUtil.closeAllGetInputFrames(); - Optional optionalSaveName = getterUtil.getInput( - new GetInputBuilder("Export Waveform", "Enter a name to export the waveform as") - .setRelativeTo(audioPlayerFrame) - .setSubmitButtonText("Save to files") - .setInitialFieldText(FileUtil.getFilename(getCurrentAudio()) + "_waveform")); - if (optionalSaveName.isEmpty()) return; - String saveName = optionalSaveName.get(); - - if (FileUtil.isValidFilename(saveName)) { - File saveFile = Dynamic.buildDynamic( - Dynamic.USERS.getFileName(), - Console.INSTANCE.getUuid(), - UserFile.FILES.getName(), - saveName + Extension.PNG.getExtension()); - - Future waveform = MessagingUtil.generateLargeWaveform(currentAudioFile.get()); - - waveformExporterLocked.set(true); - - while (!waveform.isDone()) { - Thread.onSpinWait(); - } - - waveformExporterLocked.set(false); - - try { - ImageIO.write(waveform.get(), Extension.PNG.getExtensionWithoutPeriod(), - saveFile.getAbsoluteFile()); - audioPlayerFrame.notify(new NotificationBuilder("Saved waveform to your files directory") - .setOnKillAction(() -> ImageViewer.getInstance(saveFile).showGui())); - } catch (Exception e) { - ExceptionHandler.handle(e); - audioPlayerFrame.notify("Could not save waveform at this time"); - } - } else { - audioPlayerFrame.notify("Invalid filename for " + OsUtil.OPERATING_SYSTEM_NAME); - } - - }, "AudioPlayer Waveform Exporter"); - } - - /** - * The menu item for searching YouTube for songs. - */ - private static void onSearchMenuItemPressed() { - if (onSearchView.get()) { - onBackPressedFromSearchView(); - } else { - onSearchView.set(true); - constructSearchYouTubeView(); - } - } - - /** - * The menu item for choosing a local audio file. - */ - private static void onLocalAudioFileMenuItemPressed() { - if (chooseFileLocked.get() || uiLocked) return; - - CyderThreadRunner.submit(() -> { - chooseFileLocked.set(true); - getterUtil.closeAllGetFileFrames(); - - GetFileBuilder builder = new GetFileBuilder("Local audio file chooser") - .setAllowFolderSubmission(false) - .setAllowFileSubmission(true) - .setAllowableFileExtensions(ImmutableList.of(Extension.MP3.getExtension())) - .setRelativeTo(audioPlayerFrame); - - Optional optionalFile = getterUtil.getFile(builder); - if (optionalFile.isEmpty()) return; - File chosenFile = optionalFile.get(); - boolean differentDirectory = !chosenFile.getParentFile().equals(getCurrentAudio().getParentFile()); - chooseFileLocked.set(false); - if (differentDirectory) cacheAudioLengthsOfCurrentDirectory(); - lastAction = LastAction.FileChosen; - if (currentView.get() == View.SEARCH) onBackPressedFromSearchView(); - boolean audioPlaying = isAudioPlaying(); - if (audioPlaying) pauseAudio(); - currentAudioFile.set(chosenFile); - revalidateAfterAudioFileChange(); - if (audioPlaying) playAudio(); - }, "AudioPlayer Local File Chooser"); - } - - /** - * A callback used when dreamifying an audio file finishes. - * - * @param dreamyAudio the dreamified audio file to play - */ - private static void dreamyAudioCallback(File dreamyAudio) { - float percentIn = (float) audioLocationSlider.getValue() / audioLocationSlider.getMaximum(); - boolean audioPlaying = isAudioPlaying(); - if (audioPlaying) pauseAudio(); - - currentAudioFile.set(dreamyAudio); - - revalidateAfterAudioFileChange(); - - innerAudioPlayer = new InnerAudioPlayer(dreamyAudio); - innerAudioPlayer.setLocation((long) (percentIn * FileUtil.getTotalBytes(dreamyAudio))); - audioLocationUpdater.setPercentIn(percentIn); - audioLocationUpdater.update(false); - - if (audioPlaying) playAudio(); - dreamifierLocked.set(false); - audioPlayerFrame.notify("Successfully dreamified audio"); - } - - /** - * The item menu to toggle between dreamify states of an audio file. - */ - private static void onDreamifyMenuItemPressed() { - if (dreamifierLocked.get() || uiLocked) return; - if (currentAudioFile.get() == null) return; - - String currentAudioFilename = FileUtil.getFilename(currentAudioFile.get()); - - if (currentAudioFilename.endsWith(AudioUtil.DREAMY_SUFFIX)) { - String nonDreamyName = currentAudioFilename.substring(0, - currentAudioFilename.length() - AudioUtil.DREAMY_SUFFIX.length()); - - if (!attemptToFindAndPlayAudioFileWithName(nonDreamyName)) - audioPlayerFrame.notify("Could not find audio's non-dreamy equivalent"); - return; - } - - File userMusicDir = Dynamic.buildDynamic(Dynamic.USERS.getFileName(), - Console.INSTANCE.getUuid(), UserFile.MUSIC.getName()); - - String dreamyAudioFileName = currentAudioFilename + AudioUtil.DREAMY_SUFFIX; - if (userMusicDir.exists() && attemptToFindAndPlayAudioFileWithName(dreamyAudioFileName)) return; - - String threadName = "Audio Dreamifier: " + currentAudioFilename; - CyderThreadRunner.submit(() -> { - audioPlayerFrame.notify(new NotificationBuilder("Dreamifying" - + CyderStrings.space + CyderStrings.quote + FileUtil.getFilename(currentAudioFile.get()) - + CyderStrings.quote).setViewDuration(10000)); - dreamifierLocked.set(true); - - Future> dreamifiedAudioFuture = AudioUtil.dreamifyAudio(currentAudioFile.get()); - while (!dreamifiedAudioFuture.isDone()) Thread.onSpinWait(); - Optional dreamifiedAudio = Optional.empty(); - - try { - dreamifiedAudio = dreamifiedAudioFuture.get(); - } catch (Exception ignored) {} - - if (dreamifiedAudio.isEmpty()) { - dreamifyFailed(); - return; - } - - File dreamifiedFile = dreamifiedAudio.get(); - File targetFile = Dynamic.buildDynamic( - Dynamic.USERS.getFileName(), - Console.INSTANCE.getUuid(), - UserFile.MUSIC.getName(), - dreamifiedFile.getName()); - - try { - Path source = dreamifiedFile.toPath(); - Path targetDirectory = targetFile.toPath(); - - Files.copy(source, targetDirectory); - } catch (Exception ignored) { - dreamifyFailed(); - return; - } - - dreamyAudioCallback(targetFile); - }, threadName); - } - - /** - * Attempts to find the audio file in the current directory with the provided name. - * If found, and if audio was playing, this audio file is played - * - * @param audioFileName the name of the audio file to look for - * @return whether the audio was found and handled - */ - private static boolean attemptToFindAndPlayAudioFileWithName(String audioFileName) { - Optional optionalNonDreamyAudioFile = getValidAudioFiles().stream() - .filter(validAudioFile -> FileUtil.getFilename(validAudioFile).equals(audioFileName)) - .findFirst(); - if (optionalNonDreamyAudioFile.isPresent()) { - File nonDreamyAudioFile = optionalNonDreamyAudioFile.get(); - - float percentIn = (float) audioLocationSlider.getValue() / audioLocationSlider.getMaximum(); - boolean audioPlaying = isAudioPlaying(); - if (audioPlaying) pauseAudio(); - - currentAudioFile.set(nonDreamyAudioFile); - revalidateAfterAudioFileChange(); - - innerAudioPlayer = new InnerAudioPlayer(nonDreamyAudioFile); - innerAudioPlayer.setLocation((long) (percentIn * FileUtil.getTotalBytes(nonDreamyAudioFile))); - audioLocationUpdater.setPercentIn(percentIn); - audioLocationUpdater.update(false); - - if (audioPlaying) playAudio(); - audioDreamified.set(isCurrentAudioDreamy()); - audioPlayerFrame.revalidateMenu(); - } - - return optionalNonDreamyAudioFile.isPresent(); - } - - /** - * Performs calls necessary when a requested dreamify audio call failed. - */ - private static void dreamifyFailed() { - audioDreamified.set(false); - dreamifierLocked.set(false); - audioPlayerFrame.revalidateMenu(); - audioPlayerFrame.revokeAllNotifications(); - audioPlayerFrame.notify("Could not dreamify audio at this time"); - } - - /** - * The padding used between component rows. - */ - private static final int yComponentPadding = 20; - - /** - * The width and height used for the primary control buttons. - */ - private static final int primaryButtonWidth = 30; - - /** - * The spacing between the primary buttons. - */ - private static final int primaryButtonSpacing = (int) ((1.5 * albumArtSize - 5 * 30) / 6); - - /** - * Sets component visibilities and locations based on the provided frame view. - * - * @param view the requested frame view - */ - private static void setupAndShowFrameView(View view) { - setAudioPlayerComponentsVisible(false); - setYouTubeSearchViewComponentsVisible(false); - - switch (view) { - case FULL -> { - setAudioPlayerComponentsVisible(true); - currentView.set(View.FULL); - audioPlayerFrame.setSize(defaultFrameLength, defaultFrameLength); - - // set location of all components needed - int xOff = defaultFrameLength / 2 - albumArtSize / 2; - int yOff = CyderDragLabel.DEFAULT_HEIGHT; - yOff += 20; - albumArtLabel.setLocation(xOff, yOff); - yOff += albumArtSize + yComponentPadding; - refreshAlbumArt(); - - // xOff of rest of components is len s.t. the total - // width is 1.5x the width of the album art label - xOff = (int) (defaultFrameLength / 2 - (1.5 * albumArtSize) / 2); - refreshAudioTitleLabel(); - audioTitleLabelContainer.setLocation(xOff, yOff); - yOff += 40 + yComponentPadding; - shuffleAudioButton.setLocation(xOff + primaryButtonSpacing, yOff); - lastAudioButton.setLocation(xOff + primaryButtonSpacing * 2 + primaryButtonWidth, yOff); - playPauseButton.setLocation(xOff + primaryButtonSpacing * 3 + primaryButtonWidth * 2, yOff); - nextAudioButton.setLocation(xOff + primaryButtonSpacing * 4 + primaryButtonWidth * 3, yOff); - repeatAudioButton.setLocation(xOff + primaryButtonSpacing * 5 + primaryButtonWidth * 4, yOff); - yOff += 30 + yComponentPadding; - audioLocationSlider.setLocation(xOff, yOff); - secondsInLabel.setLocation(xOff, yOff + 20); - totalSecondsLabel.setLocation(xOff + fullRowWidth - fullRowWidth / 4, yOff + 20); - audioVolumePercentLabel.setLocation(defaultFrameLength / 2 - audioVolumePercentLabel.getWidth() / 2, - yOff + 35); - yOff += 40 + yComponentPadding; - audioVolumeSlider.setLocation(xOff, yOff); - } - case HIDDEN_ALBUM_ART -> { - setAudioPlayerComponentsVisible(true); - audioPlayerFrame.setSize(defaultFrameLength, defaultFrameLength - - albumArtSize - hiddenArtHeightOffset); - albumArtLabel.setVisible(false); - albumArtLabel.setBorder(null); - - // set location of all components needed - int xOff; - int yOff = CyderDragLabel.DEFAULT_HEIGHT + 10; - - // xOff of rest of components is s.t. the total width is 1.5x width of album art label - xOff = (int) (defaultFrameLength / 2 - (1.5 * albumArtSize) / 2); - refreshAudioTitleLabel(); - audioTitleLabelContainer.setLocation(xOff, yOff); - yOff += 40 + yComponentPadding; - shuffleAudioButton.setLocation(xOff + primaryButtonSpacing, yOff); - lastAudioButton.setLocation(xOff + primaryButtonSpacing * 2 + primaryButtonWidth, yOff); - playPauseButton.setLocation(xOff + primaryButtonSpacing * 3 + primaryButtonWidth * 2, yOff); - nextAudioButton.setLocation(xOff + primaryButtonSpacing * 4 + primaryButtonWidth * 3, yOff); - repeatAudioButton.setLocation(xOff + primaryButtonSpacing * 5 + primaryButtonWidth * 4, yOff); - yOff += 30 + yComponentPadding; - audioLocationSlider.setLocation(xOff, yOff); - secondsInLabel.setLocation(xOff, yOff + 20); - totalSecondsLabel.setLocation(xOff + fullRowWidth - fullRowWidth / 4, yOff + 20); - - audioVolumePercentLabel.setLocation( - defaultFrameLength / 2 - audioVolumePercentLabel.getWidth() / 2, yOff + 35); - yOff += 40 + yComponentPadding; - audioVolumeSlider.setLocation(xOff, yOff); - currentView.set(View.HIDDEN_ALBUM_ART); - } - case MINI -> { - currentView.set(View.MINI); - setAudioPlayerComponentsVisible(true); - audioPlayerFrame.setSize(defaultFrameLength, defaultFrameLength - - albumArtSize - miniFrameHeightOffset); - albumArtLabel.setVisible(false); - albumArtLabel.setBorder(null); - - // set location of all components needed - int xOff; - int yOff = CyderDragLabel.DEFAULT_HEIGHT + 10; - - // xOff of rest of components is s.t. the total width is 1.5x width of album art label - xOff = (int) (defaultFrameLength / 2 - (1.5 * albumArtSize) / 2); - refreshAudioTitleLabel(); - yOff += 40 + yComponentPadding; - shuffleAudioButton.setLocation(xOff + primaryButtonSpacing, yOff); - lastAudioButton.setLocation(xOff + primaryButtonSpacing * 2 + primaryButtonWidth, yOff); - playPauseButton.setLocation(xOff + primaryButtonSpacing * 3 + primaryButtonWidth * 2, yOff); - nextAudioButton.setLocation(xOff + primaryButtonSpacing * 4 + primaryButtonWidth * 3, yOff); - repeatAudioButton.setLocation(xOff + primaryButtonSpacing * 5 + primaryButtonWidth * 4, yOff); - secondsInLabel.setVisible(false); - audioVolumeSlider.setVisible(false); - totalSecondsLabel.setVisible(false); - audioLocationSlider.setVisible(false); - } - default -> throw new IllegalArgumentException("Unsupported frame view to switch to: " + view); - } - } - - /** - * The default frame title. - */ - private static final String DEFAULT_FRAME_TITLE = "Audio Player"; - - /** - * Refreshes the audio frame title. - */ - private static void refreshFrameTitle() { - File audioFile = currentAudioFile.get(); - audioPlayerFrame.setTitle(audioFile == null ? DEFAULT_FRAME_TITLE : getDisplayNameForAudio(audioFile)); - } - - /** - * Returns the name for the provided audio file to display to the user - * both as the audio frame title and on the scrolling audio label. - * - * @param audioFile the audio file - * @return the display name for the audio file - */ - public static String getDisplayNameForAudio(File audioFile) { - String name = isAudioFileDreamy(audioFile) - ? getUserReadableNameForDreamyAudio(audioFile) - : FileUtil.getFilename(audioFile); - return StringUtil.capsFirstWord(StringUtil.getTrimmedText(name)); - } - - /** - * The label to place over the album art if the audio has been dreamified. - */ - private static final CyderLabel dreamyLabel = new CyderLabel("D"); - - /** - * Attempts to find and set the album art label to the current audio file's album art if it originates - * from a user's audio files with a linked audio file album art. Otherwise, the label is set to the - * default album art. - */ - private static void refreshAlbumArt() { - if (currentView.get() != View.FULL) { - albumArtLabel.setVisible(false); - return; - } - - String name = FileUtil.getFilename(currentAudioFile.get()); - boolean dreamy = isCurrentAudioDreamy(); - - if (name.endsWith(AudioUtil.DREAMY_SUFFIX)) { - name = name.substring(0, name.length() - AudioUtil.DREAMY_SUFFIX.length()); - dreamy = true; - } - - File albumArtFilePng = OsUtil.buildFile(currentUserAlbumArtDir.getAbsolutePath(), - name + Extension.PNG.getExtension()); - File albumArtFileJpg = OsUtil.buildFile(currentUserAlbumArtDir.getAbsolutePath(), - name + Extension.JPG.getExtension()); - - ImageIcon customAlbumArt = null; - - try { - if (albumArtFilePng.exists()) { - customAlbumArt = new ImageIcon(ImageUtil.read(albumArtFilePng)); - } else if (albumArtFileJpg.exists()) { - customAlbumArt = new ImageIcon(ImageUtil.read(albumArtFileJpg)); - } else { - customAlbumArt = new ImageIcon(ImageUtil.read(defaultAlbumArt)); - } - } catch (Exception e) { - ExceptionHandler.handle(e); - } - - ImageIcon regularIcon = ImageUtil.ensureFitsInBounds(customAlbumArt, - new Dimension(albumArtSize, albumArtSize)); - if (dreamy) { - - ImageIcon distortedIcon = ImageUtil.toImageIcon( - ImageUtil.grayscaleImage(ImageUtil.toBufferedImage(regularIcon))); - - albumArtLabel.setIcon(distortedIcon); - - dreamyLabel.setVisible(true); - - audioPlayerFrame.setCustomTaskbarIcon(distortedIcon); - } else { - albumArtLabel.setIcon(regularIcon); - audioPlayerFrame.setCustomTaskbarIcon(regularIcon); - - dreamyLabel.setVisible(false); - } - - albumArtLabel.repaint(); - - Console.INSTANCE.revalidateConsoleTaskbarMenu(); - } - - /** - * Returns whether the current audio file is a dreamy audio file. - * - * @return whether the current audio file is a dreamy audio file - */ - private static boolean isCurrentAudioDreamy() { - return isAudioFileDreamy(currentAudioFile.get()); - } - - /** - * Returns whether the provided audio file is dreamy. - * - * @param audioFile the audio file - * @return whether the provided audio file is dreamy - */ - private static boolean isAudioFileDreamy(File audioFile) { - Preconditions.checkNotNull(audioFile); - Preconditions.checkArgument(audioFile.exists()); - Preconditions.checkArgument(audioFile.isFile()); - Preconditions.checkArgument(FileUtil.isSupportedAudioExtension(audioFile)); - - return FileUtil.getFilename(audioFile).endsWith(AudioUtil.DREAMY_SUFFIX); - } - - /** - * Returns the user readable version for the provided dreamy audio file. - * - * @param audioFile the audio file - * @return the user readable version for the provided dreamy audio file - */ - private static String getUserReadableNameForDreamyAudio(File audioFile) { - Preconditions.checkNotNull(audioFile); - Preconditions.checkArgument(audioFile.exists()); - Preconditions.checkArgument(audioFile.isFile()); - Preconditions.checkArgument(FileUtil.isSupportedAudioExtension(audioFile)); - Preconditions.checkArgument(isAudioFileDreamy(audioFile)); - - String name = FileUtil.getFilename(audioFile); - return name.substring(0, name.length() - AudioUtil.DREAMY_SUFFIX.length()) + CyderStrings.space + "(dreamy)"; - } - - /** - * Terminates the current ScrollingTitleLabel object controlling the title label - * in the title label container and creates a new instance based on the current audio file's title. - */ - static void refreshAudioTitleLabel() { - String text = getDisplayNameForAudio(currentAudioFile.get()); - - // end old object - if (scrollingTitleLabel != null) { - // if the same title then do not update - if (scrollingTitleLabel.localTitle().equals(text)) return; - scrollingTitleLabel.kill(); - scrollingTitleLabel = null; - } - - int textWidth = StringUtil.getAbsoluteMinWidth(text, audioTitleLabel.getFont()); - textWidth = Math.max(textWidth, ScrollingTitleLabel.MIN_WIDTH); - - int textHeight = StringUtil.getMinHeight(text, audioTitleLabel.getFont()); - int parentWidth = audioTitleLabel.getParent().getWidth(); - int parentHeight = audioTitleLabel.getParent().getHeight(); - - if (textWidth > parentWidth) { - scrollingTitleLabel = new ScrollingTitleLabel(audioTitleLabel, text); - } else { - audioTitleLabel.setBounds(parentWidth / 2 - textWidth / 2, - parentHeight / 2 - textHeight / 2, textWidth, textHeight); - audioTitleLabel.setText(text); - } - } - - /** - * Refreshes the audio progress label and total length. - */ - private static void refreshAudioProgressLabel() { - if (currentView.get() == View.MINI) { - return; - } - - if (audioLocationUpdater != null) { - audioLocationUpdater.kill(); - } - - audioLocationUpdater = new AudioLocationUpdater(secondsInLabel, totalSecondsLabel, currentView, - currentAudioFile, audioLocationSliderPressed, audioLocationSlider); - } - - /** - * Returns a list of valid audio files within the current directory. - */ - private static ImmutableList getValidAudioFiles() { - checkNotNull(currentAudioFile); - - ArrayList ret = new ArrayList<>(); - - File parentDirectory = currentAudioFile.get().getParentFile(); - - if (parentDirectory.exists()) { - File[] siblings = parentDirectory.listFiles(); - - if (siblings != null && siblings.length > 0) { - for (File sibling : siblings) { - if (FileUtil.isSupportedAudioExtension(sibling)) { - ret.add(sibling); - } - } - } - } - - return ImmutableList.copyOf(ret); - } - - /** - * Refreshes the icon of the play/pause button. - */ - static void refreshPlayPauseButtonIcon() { - if (isAudioPlaying()) { - playPauseButton.setIcon(pauseIcon); - playPauseButton.setToolTipText("Pause"); - } else { - playPauseButton.setIcon(playIcon); - playPauseButton.setToolTipText("Play"); - } - - Console.INSTANCE.revalidateAudioMenuVisibility(); - } - - /** - * Returns whether audio is playing. - * - * @return whether audio is playing - */ - public static boolean isAudioPlaying() { - return innerAudioPlayer != null && innerAudioPlayer.isPlaying(); - } - - /** - * Returns whether the widget is open. - * - * @return whether the widget is open - */ - public static boolean isWidgetOpen() { - return audioPlayerFrame != null; - } - - /** - * Refreshes the audio volume based on the audio volume slider. - */ - public static void refreshAudioLine() { - try { - if (AudioSystem.isLineSupported(Port.Info.SPEAKER)) { - Port outline = (Port) AudioSystem.getLine(Port.Info.SPEAKER); - outline.open(); - FloatControl volumeControl = (FloatControl) outline.getControl(FloatControl.Type.VOLUME); - volumeControl.setValue((float) (audioVolumeSlider.getValue() * 0.001)); - } - - if (AudioSystem.isLineSupported(Port.Info.HEADPHONE)) { - Port outline = (Port) AudioSystem.getLine(Port.Info.HEADPHONE); - outline.open(); - FloatControl volumeControl = (FloatControl) outline.getControl(FloatControl.Type.VOLUME); - volumeControl.setValue((float) (audioVolumeSlider.getValue() * 0.001)); - } - } catch (Exception ex) { - ExceptionHandler.handle(ex); - } - } - - /** - * Handles a click from the play/pause button. - */ - public static void handlePlayPauseButtonClick() { - if (shouldSuppressClick()) { - return; - } - - checkNotNull(currentAudioFile); - checkArgument(!uiLocked); - - if (isAudioPlaying()) { - pauseAudio(); - } else { - playAudio(); - } - - Console.INSTANCE.revalidateAudioMenuVisibility(); - } - - /** - * Starts playing the current audio file. - */ - private static void playAudio() { - try { - // object created outside - if (innerAudioPlayer != null && !innerAudioPlayer.isKilled()) { - innerAudioPlayer.play(); - lastAction = LastAction.Play; - audioLocationUpdater.resumeTimer(); - audioProgressBarAnimator.setState(AudioProgressBarAnimator.State.RUNNING); - } - // resume - else if (lastAction == LastAction.Pause) { - innerAudioPlayer = new InnerAudioPlayer(currentAudioFile.get()); - lastAction = LastAction.Play; - innerAudioPlayer.setLocation(pauseLocation); - innerAudioPlayer.play(); - audioLocationUpdater.resumeTimer(); - audioProgressBarAnimator.setState(AudioProgressBarAnimator.State.RUNNING); - } - // spin off object - else if (innerAudioPlayer == null) { - innerAudioPlayer = new InnerAudioPlayer(currentAudioFile.get()); - lastAction = LastAction.Play; - innerAudioPlayer.play(); - audioLocationUpdater.resumeTimer(); - audioProgressBarAnimator.setState(AudioProgressBarAnimator.State.RUNNING); - } - // standard play - else { - lastAction = LastAction.Play; - innerAudioPlayer.play(); - audioLocationUpdater.resumeTimer(); - audioProgressBarAnimator.setState(AudioProgressBarAnimator.State.RUNNING); - } - - pauseLocation = unknownPauseLocation; - pauseLocationMillis = unknownPauseLocation; - } catch (Exception e) { - ExceptionHandler.handle(e); - } - } - - /** - * A call back for {@link InnerAudioPlayer}s to invoke when they naturally conclude and are not killed. - */ - static void playAudioCallback() { - if (lastAction != LastAction.Play) return; - - if (innerAudioPlayer != null) { - innerAudioPlayer.kill(); - innerAudioPlayer = null; - } - - if (repeatAudio.get()) { - innerAudioPlayer = new InnerAudioPlayer(currentAudioFile.get()); - audioLocationUpdater.setPercentIn(0); - audioLocationUpdater.update(false); - playAudio(); - } else if (!audioFileQueue.isEmpty()) { - currentAudioFile.set(audioFileQueue.remove(0)); - innerAudioPlayer = new InnerAudioPlayer(currentAudioFile.get()); - revalidateAfterAudioFileChange(); - playAudio(); - } else if (shuffleAudio.get()) { - currentAudioFile.set(getValidAudioFiles().get(getRandomAudioIndex())); - innerAudioPlayer = new InnerAudioPlayer(currentAudioFile.get()); - revalidateAfterAudioFileChange(); - playAudio(); - } else { - int currentIndex = getCurrentAudioIndex(); - int nextIndex = currentIndex + 1 == getValidAudioFiles().size() ? 0 : currentIndex + 1; - currentAudioFile.set(getValidAudioFiles().get(nextIndex)); - innerAudioPlayer = new InnerAudioPlayer(currentAudioFile.get()); - revalidateAfterAudioFileChange(); - playAudio(); - } - - } - - /* - * The location the previous InnerAudioPlayer was killed at, if available. - */ - private static long pauseLocation; - - /** - * The location in milliseconds the previous InnerAudioPlayer was paused at. - */ - private static long pauseLocationMillis; - - /** - * A magic number to denote an undefined pause location. - */ - private static final long unknownPauseLocation = -1L; - - /** - * A magic number used to denote an unknown audio length; - */ - private static final long unknownAudioLength = -1; - - /** - * The total length of the current (paused or playing) audio. - */ - private static long audioTotalLength = unknownAudioLength; - - /** - * Returns the location in milliseconds into the current audio file. - * - * @return the location in milliseconds into the current audio file - */ - public static long getMillisecondsIn() { - return innerAudioPlayer != null ? innerAudioPlayer.getMillisecondsIn() : pauseLocationMillis; - } - - /** - * Pauses playback of the current audio file if {@link #innerAudioPlayer} is not null. - */ - private static void pauseAudio() { - if (innerAudioPlayer == null) return; - - audioTotalLength = innerAudioPlayer.getTotalAudioLength(); - pauseLocationMillis = innerAudioPlayer.getMillisecondsIn(); - audioProgressBarAnimator.setState(AudioProgressBarAnimator.State.PAUSED); - pauseLocation = innerAudioPlayer.kill(); - innerAudioPlayer = null; - lastAction = LastAction.Pause; - audioLocationUpdater.pauseTimer(); - refreshPlayPauseButtonIcon(); - } - - /** - * The range a pause location must fall within in order for a skip back action to occur. Any value outside - * of the first 5000ms will cause the current audio to be restarted on a skip back invocation. - */ - private static final Range millisecondsIntoAudioToSkipBack = Range.closed(0L, 5000L); - - /** - * Handles a click from the last button. - */ - public static void handleLastAudioButtonClick() { - if (shouldSuppressClick()) return; - checkNotNull(currentAudioFile); - checkArgument(!uiLocked); - - lastAction = LastAction.Skip; - - boolean wasPlayingAudio = isAudioPlaying(); - if (wasPlayingAudio) pauseAudio(); - - if (!millisecondsIntoAudioToSkipBack.contains(pauseLocationMillis)) { - audioLocationUpdater.pauseTimer(); - audioLocationUpdater.setPercentIn(0); - innerAudioPlayer = new InnerAudioPlayer(currentAudioFile.get()); - audioLocationUpdater.update(false); - } else { - int currentIndex = getCurrentAudioIndex(); - int lastIndex = currentIndex == 0 ? getValidAudioFiles().size() - 1 : currentIndex - 1; - - currentAudioFile.set(getValidAudioFiles().get(lastIndex)); - - innerAudioPlayer = new InnerAudioPlayer(currentAudioFile.get()); - audioLocationUpdater.update(false); - audioLocationSliderUi.resetAnimation(); - revalidateAfterAudioFileChange(); - } - - if (wasPlayingAudio) playAudio(); - } - - /** - * Handles a click from the next audio button. - */ - public static void handleNextAudioButtonClick() { - if (shouldSuppressClick()) return; - checkNotNull(currentAudioFile); - checkArgument(!uiLocked); - - lastAction = LastAction.Skip; - - boolean shouldPlay = isAudioPlaying(); - - pauseAudio(); - - int currentIndex = getCurrentAudioIndex(); - int nextIndex = currentIndex == getValidAudioFiles().size() - 1 ? 0 : currentIndex + 1; - - if (shuffleAudio.get()) { - nextIndex = getRandomAudioIndex(); - } - - currentAudioFile.set(getValidAudioFiles().get(nextIndex)); - innerAudioPlayer = new InnerAudioPlayer(currentAudioFile.get()); - audioLocationSliderUi.resetAnimation(); - - revalidateAfterAudioFileChange(); - - if (shouldPlay) { - playAudio(); - } - } - - /** - * Refreshes the valid audio files and returns the index of the current audio file. - * - * @return the index of the current audio file - */ - private static int getCurrentAudioIndex() { - ImmutableList validAudioFiles = getValidAudioFiles(); - - int currentIndex = 0; - - for (int i = 0 ; i < validAudioFiles.size() ; i++) { - if (validAudioFiles.get(i).getAbsolutePath().equals(currentAudioFile.get().getAbsolutePath())) { - currentIndex = i; - break; - } - } - - return currentIndex; - } - - /** - * Whether the current audio should be repeated on conclusion. - */ - private static final AtomicBoolean repeatAudio = new AtomicBoolean(false); - - /** - * Handles a click from the repeat button. - */ - public static void handleRepeatButtonClick() { - checkNotNull(currentAudioFile); - checkArgument(!uiLocked); - - boolean repeatAudioValue = repeatAudio.get(); - repeatAudio.compareAndSet(repeatAudioValue, !repeatAudioValue); - } - - /** - * Whether the next audio file should be chosen at random upon completion of the current audio file. - */ - private static final AtomicBoolean shuffleAudio = new AtomicBoolean(false); - - /** - * Handles a click of the shuffle button. - */ - public static void handleShuffleButtonClick() { - checkNotNull(currentAudioFile); - checkArgument(!uiLocked); - - boolean shuffleAudioValue = shuffleAudio.get(); - shuffleAudio.compareAndSet(shuffleAudioValue, !shuffleAudioValue); - } - - /** - * Adds the audio to the beginning of the audio file queue. - * - * @param audioFile the audio file to add to the beginning of the queue - */ - public static void playAudioNext(File audioFile) { - checkNotNull(audioFile); - checkArgument(audioFile.exists()); - - if (!isWidgetOpen()) { - showGui(audioFile); - } else { - audioFileQueue.add(0, audioFile); - } - } - - /** - * Adds the audio to the end of the audio file queue. - * - * @param audioFile the audio file to add to the end of the queue - */ - public static void playAudioLast(File audioFile) { - checkNotNull(audioFile); - checkArgument(audioFile.exists()); - - if (!isWidgetOpen()) { - showGui(audioFile); - } else { - audioFileQueue.add(audioFile); - } - } - - /** - * Returns the current audio file open by the AudioPlayer. - * - * @return the current audio file open by the AudioPlayer - */ - public static File getCurrentAudio() { - return currentAudioFile.get(); - } - - /** - * Resets all objects and closes the audio player widget. - */ - private static void killAndCloseWidget() { - if (isAudioPlaying()) pauseAudio(); - - currentAudioFile.set(null); - - if (audioPlayerFrame != null) { - audioPlayerFrame.dispose(true); - audioPlayerFrame = null; - } - - if (audioVolumeLabelAnimator != null) { - audioVolumeLabelAnimator.kill(); - audioVolumeLabelAnimator = null; - } - - if (audioLocationUpdater != null) { - audioLocationUpdater.kill(); - audioLocationUpdater = null; - } - - if (scrollingTitleLabel != null) { - scrollingTitleLabel.kill(); - scrollingTitleLabel = null; - } - - if (innerAudioPlayer != null) { - innerAudioPlayer.kill(); - innerAudioPlayer = null; - } - - Console.INSTANCE.revalidateAudioMenuVisibility(); - } - - /** - * The time in ms to delay primary ui interactions, that of play, pause, and skip. - */ - private static final int primaryActionThrottleDelay = 50; - - /** - * The last time a ui action was permitted. - */ - private static Instant lastActionTime = Instant.ofEpochMilli(0); - - /** - * Returns whether to suppress a requested ui action such as a button click. - * - * @return whether to suppress a requested ui action such as a button click - */ - private static boolean shouldSuppressClick() { - Instant now = Instant.now(); - if (now.minusMillis(lastActionTime.toEpochMilli()).toEpochMilli() >= primaryActionThrottleDelay) { - lastActionTime = now; - return false; - } else { - return true; - } - } - - /** - * Revalidates necessary components following an audio file change. - */ - private static void revalidateAfterAudioFileChange() { - refreshFrameTitle(); - refreshAudioTitleLabel(); - refreshAlbumArt(); - refreshAudioProgressLabel(); - - audioDreamified.set(isCurrentAudioDreamy()); - - audioPlayerFrame.revalidateMenuIfVisible(); - } - - /** - * Returns a random index of the validAudioFiles list. - * - * @return a random index of the validAudioFiles list - */ - private static int getRandomAudioIndex() { - return NumberUtil.getRandomIndex(0, getValidAudioFiles().size(), getCurrentAudioIndex()); - } - - // ---------------------------------- - // Search View components and methods - // ---------------------------------- - - private static final AtomicBoolean searchYouTubeViewLocked = new AtomicBoolean(false); - - /** - * The list of search results previously found. - */ - private static final ArrayList searchResults = new ArrayList<>(); - - /** - * The length of the thumbnails printed to the search view {@link #searchResultsPane}. - */ - private static final int searchViewThumbnailLength = 250; - - /** - * The default information label text. - */ - private static final String DEFAULT_INFORMATION_LABEL_TEXT = "Search YouTube using the above field"; - - /** - * The color used as the background for the search results scroll and information label. - */ - private static final Color searchViewScrollBackground = new Color(30, 30, 30); - - /** - * The width of the search view components excluding the scroll pane. - */ - private static final int searchViewComponentWidth = 300; - - /** - * The text pane used to display YouTube search results. - */ - private static JTextPane searchResultsPane; - - /** - * The printing util used for printing out search results to the scroll pane. - */ - private static StringUtil searchResultsPrintingUtil; - - /** - * The scroll pane for the search results pane. - */ - private static CyderScrollPane searchResultsScroll; - - /** - * The search button for the search view two. - */ - private static CyderButton searchViewSearchButton; - - /** - * The button used to go back to the main audio page. - */ - private static CyderButton searchBackButton; - - /** - * The information label placed in the center of the search pane for displaying progress when a search is underway. - */ - private static CyderLabel informationLabel; - - /** - * The search field for downloading audio. - */ - private static CyderModernTextField searchField; - - /** - * The previously searched text. - */ - private static String previousSearch; - - /** - * The previous location of the search scroll pane. - */ - private static int previousScrollLocation; - - /** - * Performs operations necessary to transitioning from the search view to the {@link View#FULL} view. - */ - private static void onBackPressedFromSearchView() { - previousScrollLocation = searchResultsScroll.getVerticalScrollBar().getValue(); - onSearchView.set(false); - setYouTubeSearchViewComponentsVisible(false); - audioPlayerFrame.hideMenu(); - setupAndShowFrameView(View.FULL); - } - - /** - * Constructs the search view where a user can search for and download audio from YouTube. - */ - private static void constructSearchYouTubeView() { - if (uiLocked || searchYouTubeViewLocked.get()) return; - - currentView.set(View.SEARCH); - - searchYouTubeViewLocked.set(true); - - setAudioPlayerComponentsVisible(false); - audioVolumePercentLabel.setVisible(false); - - audioPlayerFrame.hideMenu(); - - int yOff = 50; - - audioPlayerFrame.setSize(defaultFrameLength, defaultFrameLength); - - searchField = new CyderModernTextField(); - searchField.setHorizontalAlignment(JTextField.CENTER); - searchField.setFont(CyderFonts.DEFAULT_FONT_SMALL); - searchField.setForeground(CyderColors.vanilla); - searchField.setUnderlineColor(CyderColors.vanilla); - searchField.setRippleColor(CyderColors.regularPurple); - searchField.setCaret(new CyderCaret(CyderColors.vanilla)); - searchField.setBackground(BACKGROUND_COLOR); - searchField.setBounds((audioPlayerFrame.getWidth() - searchViewComponentWidth) / 2, yOff, - searchViewComponentWidth, 40); - searchField.setBorder(BorderFactory.createMatteBorder(0, 0, 4, 0, CyderColors.vanilla)); - audioPlayerFrame.getContentPane().add(searchField); - - yOff += 50; - - searchViewSearchButton = new CyderButton("Search"); - searchViewSearchButton.setBorder(BorderFactory.createEmptyBorder()); - searchViewSearchButton.setBackground(CyderColors.regularPurple); - searchViewSearchButton.setForeground(CyderColors.vanilla); - searchViewSearchButton.setFont(CyderFonts.DEFAULT_FONT); - searchViewSearchButton.setBounds((audioPlayerFrame.getWidth() - searchViewComponentWidth) / 2 + 50, yOff, - searchViewComponentWidth - 50, 40); - audioPlayerFrame.getContentPane().add(searchViewSearchButton); - searchField.addActionListener(e -> searchAndUpdate()); - searchViewSearchButton.addActionListener(e -> searchAndUpdate()); - - searchBackButton = new CyderButton(" < "); - searchBackButton.setBorder(BorderFactory.createEmptyBorder()); - searchBackButton.setBackground(CyderColors.regularPurple); - searchBackButton.setToolTipText("Back"); - searchBackButton.setForeground(CyderColors.vanilla); - searchBackButton.setFont(CyderFonts.DEFAULT_FONT); - searchBackButton.setBounds((audioPlayerFrame.getWidth() - searchViewComponentWidth) / 2, yOff, 40, 40); - audioPlayerFrame.getContentPane().add(searchBackButton); - searchBackButton.addActionListener(e -> onBackPressedFromSearchView()); - - yOff += 60; - - searchResultsPane = new JTextPane(); - searchResultsPane.setEditable(false); - searchResultsPane.setAutoscrolls(false); - searchResultsPane.setBounds((audioPlayerFrame.getWidth() - fullRowWidth) / 2, - yOff, fullRowWidth, audioPlayerFrame.getWidth() - 20 - yOff); - searchResultsPane.setFocusable(true); - searchResultsPane.setOpaque(false); - searchResultsPane.setBackground(Color.white); - searchResultsPane.setAlignmentX(Component.CENTER_ALIGNMENT); - searchResultsPane.setAlignmentY(Component.CENTER_ALIGNMENT); - - DefaultCaret searchResultsPaneCaret = (DefaultCaret) searchResultsPane.getCaret(); - searchResultsPaneCaret.setUpdatePolicy(DefaultCaret.NEVER_UPDATE); - - UiUtil.setJTextPaneDocumentAlignment(searchResultsPane, UiUtil.JTextPaneAlignment.CENTER); - - searchResultsScroll = new CyderScrollPane(searchResultsPane); - searchResultsScroll.getViewport().setAutoscrolls(false); - searchResultsScroll.setThumbSize(8); - searchResultsScroll.getViewport().setOpaque(false); - searchResultsScroll.setFocusable(true); - searchResultsScroll.setOpaque(true); - searchResultsScroll.setThumbColor(CyderColors.regularPink); - searchResultsScroll.setBorder(new LineBorder(Color.black, 4)); - searchResultsScroll.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); - searchResultsScroll.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS); - searchResultsScroll.setBounds((audioPlayerFrame.getWidth() - fullRowWidth) / 2, - yOff, fullRowWidth, audioPlayerFrame.getWidth() - 20 - yOff); - searchResultsScroll.setBackground(searchViewScrollBackground); - - informationLabel = new CyderLabel(); - informationLabel.setForeground(CyderColors.vanilla); - informationLabel.setFont(CyderFonts.DEFAULT_FONT); - informationLabel.setBackground(searchViewScrollBackground); - informationLabel.setOpaque(true); - informationLabel.setBorder(new LineBorder(Color.black, 4)); - informationLabel.setBounds((audioPlayerFrame.getWidth() - fullRowWidth) / 2, - yOff, fullRowWidth, audioPlayerFrame.getWidth() - 20 - yOff); - audioPlayerFrame.getContentPane().add(informationLabel); - - audioPlayerFrame.getContentPane().add(searchResultsScroll); - searchResultsPane.revalidate(); - - if (lastSearchResultsPage != null) { - searchResultsPane.setDocument(lastSearchResultsPage); - searchField.setText(previousSearch); - hideInformationLabel(); - - /* - Note to maintainers: this is a necessary bodge for the intended functionality to work. - */ - searchResultsScroll.getVerticalScrollBar().setValue(previousScrollLocation); - searchResultsScroll.getVerticalScrollBar().setValue(previousScrollLocation); - } else { - showInformationLabel(DEFAULT_INFORMATION_LABEL_TEXT); - searchResultsScroll.getVerticalScrollBar().setValue(0); - } - - searchResultsPrintingUtil = new StringUtil(new CyderOutputPane(searchResultsPane)); - - searchYouTubeViewLocked.set(false); - - searchField.addFocusListener(new FocusAdapter() { - @Override - public void focusLost(FocusEvent e) { - if (currentView.get() == View.SEARCH) searchResultsScroll.requestFocus(); - } - }); - MinimizeButton minimizeButton = (MinimizeButton) audioPlayerFrame.getTopDragLabel().getRightButton(0); - searchResultsScroll.addFocusListener(new FocusAdapter() { - @Override - public void focusLost(FocusEvent e) { - if (currentView.get() == View.SEARCH) minimizeButton.requestFocus(); - } - }); - - searchField.requestFocus(); - } - - /** - * Sets the YouTube search view components to the visibility specified. - * - * @param visible whether the YouTube search view components should be visible - */ - @SuppressWarnings("SameParameterValue") - private static void setYouTubeSearchViewComponentsVisible(boolean visible) { - if (searchField == null) return; - searchField.setVisible(visible); - searchViewSearchButton.setVisible(visible); - searchBackButton.setVisible(visible); - informationLabel.setVisible(visible); - searchResultsPane.setVisible(visible); - searchResultsScroll.setVisible(visible); - } - - /** - * Hides the information label. - */ - private static void hideInformationLabel() { - informationLabel.setVisible(false); - informationLabel.setText(""); - } - - /** - * Shows the information label with the provided text. - * - * @param text the text for the information label - */ - private static void showInformationLabel(String text) { - informationLabel.setText(text); - informationLabel.setVisible(true); - } - - /** - * The number of search results to grab when searching YouTube. - */ - private static final int numSearchResults = 10; - - /** - * A search result object to hold data in the results scroll pane. - */ - private static record YoutubeSearchResult(String uuid, - String title, - String description, - String channel, - BufferedImage bi) {} - - /** - * The string used for the information label when a YouTube query is triggered. - */ - private static final String SEARCHING = "Searching..."; - - /** - * The string use for download buttons. - */ - private static final String DOWNLOAD = "Download"; - - /** - * The string use for download buttons during a mouse over event when the download is in progress. - */ - private static final String CANCEL = "Cancel"; - - /** - * The button text for when a button should trigger an audio play event. - */ - private static final String PLAY = "Play"; - - /** - * The button text for when a download is concluding and would typically say 100%. - */ - private static final String FINISHING = "Finishing"; - - /** - * The information label text used for when no search results are found. - */ - private static final String NO_RESULTS = "No results found"; - - /** - * The formatting results string. - */ - private static final String FORMATTING_RESULTS = "Formatting results..."; - - /** - * The last search result output. - */ - private static Document lastSearchResultsPage; - - /** - * Searches YouTube for the provided text and updates the results pane with videos found. - */ - private static void searchAndUpdate() { - String rawFieldText = searchField.getText(); - if (StringUtil.isNullOrEmpty(rawFieldText) || rawFieldText.equalsIgnoreCase(previousSearch)) return; - - // Trim and replace multiple spaces with one - String fieldText = rawFieldText.trim().replaceAll(CyderRegexPatterns.whiteSpaceRegex, CyderStrings.space); - previousSearch = fieldText; - - String threadName = "YouTube Searcher, search: " + CyderStrings.quote + fieldText + CyderStrings.quote; - CyderThreadRunner.submit(() -> { - showInformationLabel(SEARCHING); - - Optional youTubeSearchResultPage = - getSearchResults(YouTubeUtil.buildYouTubeApiV3SearchQuery(numSearchResults, fieldText)); - - if (youTubeSearchResultPage.isEmpty()) { - showInformationLabel(NO_RESULTS); - return; - } - - searchResults.clear(); - - for (YouTubeVideo video : youTubeSearchResultPage.get().getItems()) { - BufferedImage image = ImageUtil.ensureFitsInBounds( - YouTubeUtil.getMaxResolutionSquareThumbnail(video.getId().getVideoId()) - .orElse(defaultAlbumArtImage), - new Dimension(albumArtSize, albumArtSize)); - - searchResults.add(new YoutubeSearchResult( - video.getId().getVideoId(), - video.getSnippet().getTitle(), - video.getSnippet().getDescription(), - video.getSnippet().getChannelTitle(), - image)); - } - - // if user has searched for something else while getting the search results, don't update pane - if (!fieldText.equals(searchField.getText())) { - hideInformationLabel(); - return; - } - - searchResultsScroll.setVisible(false); - searchResultsPane.setVisible(false); - searchResultsPane.setText(""); - - showInformationLabel(FORMATTING_RESULTS); - - searchResults.forEach(result -> { - Optional alreadyExistsOptional = AudioUtil.getCurrentUserMusicFileWithName(result.title); - boolean alreadyExists = alreadyExistsOptional.isPresent(); - - printSearchResultToPane(result); - - String url = YouTubeUtil.buildVideoUrl(result.uuid); - YouTubeAudioDownload youTubeAudioDownload = new YouTubeAudioDownload(); - youTubeAudioDownload.setVideoLink(url); - AtomicReference downloadable = new AtomicReference<>(youTubeAudioDownload); - AtomicBoolean mouseEntered = new AtomicBoolean(false); - - CyderButton downloadButton = new CyderButton(); - downloadButton.setLeftTextPadding(StringUtil.generateSpaces(5)); - downloadButton.setRightTextPadding(StringUtil.generateSpaces(4)); - downloadButton.setText(alreadyExists ? PLAY : DOWNLOAD); - downloadButton.setBackground(CyderColors.regularPurple); - downloadButton.setForeground(CyderColors.vanilla); - downloadButton.setBorder(BorderFactory.createEmptyBorder()); - downloadButton.setFont(CyderFonts.DEFAULT_FONT.deriveFont(26f)); - downloadButton.setSize(searchViewComponentWidth, 40); - downloadButton.addActionListener(e -> CyderThreadRunner.submit(() -> { - if (downloadable.get().isDownloading()) { - downloadable.get().cancel(); - } else if (alreadyExists) { - onPlayDownloadedAudioPressedFromSearchView(alreadyExistsOptional.get()); - } else if (downloadable.get().isDownloaded()) { - onPlayDownloadedAudioPressedFromSearchView(downloadable.get().getAudioDownloadFile()); - } else { - if (downloadable.get().isCanceled()) { - downloadable.set(new YouTubeAudioDownload()); - downloadable.get().setVideoLink(url); - downloadable.get().setOnCanceledCallback(() -> downloadButton.setText(DOWNLOAD)); - downloadable.get().setOnDownloadedCallback(() -> { - downloadButton.setText(PLAY); - downloadButton.addActionListener(event -> onPlayDownloadedAudioPressedFromSearchView( - downloadable.get().getAudioDownloadFile())); - }); - } - - downloadable.get().downloadAudioAndThumbnail(); - startDownloadUpdater(downloadable, downloadButton, mouseEntered); - } - }, "AudioPlayer search downloader, audio: " + result.title())); - downloadButton.addMouseListener(generateDownloadButtonMouseListener(downloadable, - mouseEntered, downloadButton, alreadyExists)); - downloadable.get().setOnCanceledCallback(() -> downloadButton.setText(DOWNLOAD)); - downloadable.get().setOnDownloadedCallback(() -> { - downloadButton.setText(PLAY); - downloadButton.addActionListener(e -> - onPlayDownloadedAudioPressedFromSearchView(downloadable.get().getAudioDownloadFile())); - }); - - searchResultsPrintingUtil.printlnComponent(downloadButton); - searchResultsPrintingUtil.println(CyderStrings.newline); - }); - - searchResultsPane.setCaretPosition(0); - searchResultsScroll.setVisible(true); - searchResultsPane.setVisible(true); - hideInformationLabel(); - - lastSearchResultsPage = searchResultsPane.getDocument(); - }, threadName); - } - - /** - * Switches to the main audio player view and plays the provided audio. - * - * @param audio the audio file to play after switching to the main view - */ - private static void onPlayDownloadedAudioPressedFromSearchView(File audio) { - if (isAudioPlaying()) pauseAudio(); - - currentAudioFile.set(audio); - innerAudioPlayer = new InnerAudioPlayer(currentAudioFile.get()); - revalidateAfterAudioFileChange(); - onBackPressedFromSearchView(); - - playAudio(); - } - - /** - * Constructs and prints the title and channel labels for the provided YouTube search result. - * - * @param result the YouTube search result record - */ - private static void printSearchResultToPane(YoutubeSearchResult result) { - JLabel imageLabel = new JLabel(ImageUtil.toImageIcon(result.bi)); - imageLabel.setSize(searchViewThumbnailLength, searchViewThumbnailLength); - imageLabel.setHorizontalAlignment(JLabel.CENTER); - imageLabel.setBorder(new LineBorder(Color.black, 4)); - searchResultsPrintingUtil.printlnComponent(imageLabel); - - searchResultsPrintingUtil.println(CyderStrings.newline); - - CyderLabel titleLabel = new CyderLabel(result.title); - titleLabel.setForeground(CyderColors.vanilla); - titleLabel.setHorizontalAlignment(JLabel.CENTER); - searchResultsPrintingUtil.printlnComponent(titleLabel); - - CyderLabel channelLabel = new CyderLabel(result.channel); - channelLabel.setForeground(CyderColors.vanilla); - channelLabel.setHorizontalAlignment(JLabel.CENTER); - searchResultsPrintingUtil.printlnComponent(channelLabel); - - searchResultsPrintingUtil.println(CyderStrings.newline); - } - - /** - * Constructs a download button mouse listener using the provided props. - * - * @param downloadable a reference to the download button's linked downloadable - * @param mouseEntered an atomic boolean to determine when the mouse is inside of the button - * @param downloadButton the download button itself - * @param alreadyExists whether the audio this button is linked to has already been downloaded - * @return a download button mouse listener - */ - private static MouseAdapter generateDownloadButtonMouseListener( - AtomicReference downloadable, - AtomicBoolean mouseEntered, - CyderButton downloadButton, - boolean alreadyExists) { - return new MouseAdapter() { - @Override - public void mouseEntered(MouseEvent e) { - mouseEntered.set(true); - - if (downloadable.get().isDownloading()) { - downloadButton.setText(CANCEL); - } - } - - @Override - public void mouseExited(MouseEvent e) { - mouseEntered.set(false); - if (alreadyExists) return; - - if (downloadable.get().isDownloading() && !downloadable.get().isCanceled()) { - float progress = downloadable.get().getDownloadableProgress(); - - if (progress == completedProgress) { - downloadButton.setText(FINISHING); - } else { - downloadButton.setText(progress + "%"); - } - } else if (downloadable.get().isDownloaded()) { - downloadButton.setText(PLAY); - } else { - downloadButton.setText(DOWNLOAD); - } - } - }; - } - - /** - * Starts the download updater to update the download button based on the current progress. - * - * @param downloadable the YouTube download - * @param downloadButton the download button - * @param mouseEntered whether the mouse is currently in the button - */ - private static void startDownloadUpdater(AtomicReference downloadable, - CyderButton downloadButton, - AtomicBoolean mouseEntered) { - String threadName = "YouTube audio downloader, name: " + CyderStrings.quote - + downloadable.get().getDownloadableName() + CyderStrings.quote; - CyderThreadRunner.submit(() -> { - while (!downloadable.get().isDone()) { - if (!mouseEntered.get()) { - float progress = downloadable.get().getDownloadableProgress(); - - if (progress == completedProgress) { - downloadButton.setText(FINISHING); - } else { - downloadButton.setText(progress + "%"); - } - } - - ThreadUtil.sleep(YouTubeConstants.DOWNLOAD_UPDATE_DELAY); - } - }, threadName); - } - - /** - * Returns the search results for a particular url query. - * - * @param url the constructed url to get YouTube video results - * @return the YoutubeSearchResultPage object if present, empty optional else - */ - private static Optional getSearchResults(String url) { - try (BufferedReader reader = new BufferedReader(new InputStreamReader(new URL(url).openStream()))) { - return Optional.of(SerializationUtil.fromJson(reader, YouTubeSearchResultPage.class)); - } catch (Exception e) { - ExceptionHandler.handle(e); - } - - return Optional.empty(); - } -} \ No newline at end of file diff --git a/src/main/java/cyder/audio/player/AudioProgressBarAnimator.java b/src/main/java/cyder/audio/player/AudioProgressBarAnimator.java deleted file mode 100644 index 98a28e600..000000000 --- a/src/main/java/cyder/audio/player/AudioProgressBarAnimator.java +++ /dev/null @@ -1,134 +0,0 @@ -package cyder.audio.player; - -import com.google.common.base.Preconditions; -import cyder.threads.CyderThreadRunner; -import cyder.threads.ThreadUtil; -import cyder.ui.slider.CyderSliderUi; - -import javax.swing.*; - -/** - * An encapsulation class for incrementing the audio progress bar's animation increment. - */ -final class AudioProgressBarAnimator { - /** - * The delay between update calls while the animation is in the {@link State#RUNNING} state. - */ - private static final int animationDelay = 2; - - /** - * The possible states for this animator. - */ - public enum State { - /** - * Not running; the animation will not update. - */ - STOPPED, - - /** - * Running; the animation is updated every {@link #animationDelay}ms. - */ - RUNNING, - - /** - * The animation is paused and can be resumed from the current point. - */ - PAUSED, - } - - /** - * The slider this progress bar animator controls. - */ - private final JSlider slider; - - /** - * The ui belonging to the slider. - */ - private final CyderSliderUi sliderUi; - - /** - * The current state of the animator. - */ - private State state = State.STOPPED; - - /** - * Constructs a new AudioProgressBarAnimator object. - * - * @param slider the JSlider to animate - * @param sliderUi the ui of the slider to update the animation - */ - public AudioProgressBarAnimator(JSlider slider, CyderSliderUi sliderUi) { - this.slider = Preconditions.checkNotNull(slider); - this.sliderUi = Preconditions.checkNotNull(sliderUi); - } - - /** - * Returns the delay between animation updates. - * - * @return the delay between animation updates - */ - public int getAnimationDelay() { - return animationDelay; - } - - /** - * Returns the slider this progress bar animator controls. - * - * @return the slider this progress bar animator controls - */ - public JSlider getSlider() { - return slider; - } - - /** - * Returns the ui of the slider this progress bar animator controls. - * - * @return the ui of the slider this progress bar animator controls - */ - public CyderSliderUi getSliderUi() { - return sliderUi; - } - - /** - * Returns the current state of this progress bar animator. - * - * @return the current state of this progress bar animator - */ - public State getState() { - return state; - } - - /** - * Sets the current state of this progress bar animator. - * - * @param state the current state of this progress bar animator - */ - public void setState(State state) { - Preconditions.checkNotNull(state); - if (this.state == state) return; - - this.state = state; - - switch (state) { - case STOPPED -> sliderUi.resetAnimation(); - case RUNNING -> startAnimation(); - case PAUSED -> {} - default -> throw new IllegalArgumentException("Invalid Animation State: " + state); - } - } - - /** - * Starts the animation of the controlled slider. - * The animation proceeds until {@link #state} is set to a value other than {@link State#RUNNING}. - */ - private void startAnimation() { - String threadName = "Audio Location Slider Animation Updater, slider=" + slider + ", ui" + sliderUi; - CyderThreadRunner.submit(() -> { - while (state == State.RUNNING) { - sliderUi.incrementAnimation(); - slider.repaint(); - ThreadUtil.sleep(animationDelay); - } - }, threadName); - } -} diff --git a/src/main/java/cyder/audio/player/AudioVolumeLabelAnimator.java b/src/main/java/cyder/audio/player/AudioVolumeLabelAnimator.java deleted file mode 100644 index 044a76aa9..000000000 --- a/src/main/java/cyder/audio/player/AudioVolumeLabelAnimator.java +++ /dev/null @@ -1,176 +0,0 @@ -package cyder.audio.player; - -import com.google.common.base.Preconditions; -import cyder.threads.CyderThreadRunner; -import cyder.threads.ThreadUtil; - -import javax.swing.*; -import java.awt.*; -import java.time.Duration; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicLong; - -/** - * A class to control the visibility of the audio volume level label and perform animations. - */ -final class AudioVolumeLabelAnimator { - /** - * The thread name for the thread which waits for the proper time to pass before invoking the animate out method. - */ - private static final String audioVolumeLabelAnimatorAnimateOutWaiterThreadName = - "Audio Volume Label Animator Animate Out Waiter"; - - /** - * The thread name for the animate in thread. - */ - private static final String animateInAudioVolumeLabelThreadName = "Animate In Audio Volume Label"; - - /** - * The thread name for the animate out thread. - */ - private static final String animateOutAudioVolumeLabelThreadName = "Animate Out Audio Volume Label"; - - /** - * The minimum size a font can be during an animation. - * For the animate in animation, this is the starting font size, - * for the animate out animation, this is the ending font size. - */ - private static final int minFontSize = 0; - - /** - * The animation step for changing the font size, both increasing and decreasing. - */ - private static final int animationStep = 2; - - /** - * The time between animation increments. - */ - private static final Duration animationSleepTime = Duration.ofMillis(10); - - /** - * The time between condition checks for the audio volume label animator waiter thread. - */ - private static final Duration waitToAnimateOutSleepTime = Duration.ofMillis(50); - - /** - * The maximum time the audio volume percent label can remain visible for before an animate out call. - */ - private static final Duration maxVisibleTime = Duration.ofSeconds(3); - - /** - * Whether the animate in animation is currently underway. - */ - private final AtomicBoolean animatingIn = new AtomicBoolean(); - - /** - * Whether the animate out animation is currently underway. - */ - private final AtomicBoolean animatingOut = new AtomicBoolean(); - - /** - * Whether the animate out waiter thread is currently running. - */ - private final AtomicBoolean animateOutWaiterRunning = new AtomicBoolean(); - - /** - * The time remaining before the label is animated out. - */ - private final AtomicLong millisUntilAnimateOut = new AtomicLong(maxVisibleTime.toMillis()); - - /** - * The label to display the audio progress on when needed. - */ - private final JLabel audioVolumePercentLabel; - - /** - * Whether this audio volume label animator has been killed. - */ - private boolean killed; - - /** - * Constructs a new AudioVolumeLabelAnimator. - * - * @param audioVolumePercentLabel the label to animate - */ - public AudioVolumeLabelAnimator(JLabel audioVolumePercentLabel) { - Preconditions.checkNotNull(audioVolumePercentLabel); - - this.audioVolumePercentLabel = audioVolumePercentLabel; - } - - /** - * The actions to invoke when the slider is changed. - */ - public void onValueChanged() { - if (!audioVolumePercentLabel.isVisible()) { - if (animatingIn.get()) return; - animateIn(); - } else { - millisUntilAnimateOut.set(maxVisibleTime.toMillis()); - if (!animateOutWaiterRunning.get()) { - animateOutWaiterRunning.set(true); - CyderThreadRunner.submit(() -> { - while (millisUntilAnimateOut.get() > 0) { - millisUntilAnimateOut.getAndAdd(-waitToAnimateOutSleepTime.toMillis()); - ThreadUtil.sleep(waitToAnimateOutSleepTime.toMillis()); - } - animateOut(); - }, audioVolumeLabelAnimatorAnimateOutWaiterThreadName); - } - } - } - - /** - * Kills this object. - */ - public void kill() { - killed = true; - animatingIn.set(false); - animatingOut.set(false); - } - - /** - * Performs the animate in animation. - */ - private void animateIn() { - if (animatingIn.get() || killed) return; - animatingIn.set(true); - - CyderThreadRunner.submit(() -> { - audioVolumePercentLabel.setVisible(true); - Font font = audioVolumePercentLabel.getFont(); - int animateToFontSize = font.getSize(); - for (int fontSize = minFontSize ; fontSize <= animateToFontSize ; fontSize += animationStep) { - if (!animatingIn.get() || killed) return; - audioVolumePercentLabel.setFont(font.deriveFont((float) fontSize)); - ThreadUtil.sleep(animationSleepTime.toMillis()); - } - - audioVolumePercentLabel.setFont(font); - animatingIn.set(false); - millisUntilAnimateOut.set(maxVisibleTime.toMillis()); - }, animateInAudioVolumeLabelThreadName); - } - - /** - * Performs the animate out animation. - */ - private void animateOut() { - if (animatingOut.get() || killed) return; - animatingOut.set(true); - - CyderThreadRunner.submit(() -> { - Font font = audioVolumePercentLabel.getFont(); - for (int fontSize = font.getSize() ; fontSize >= minFontSize ; fontSize -= animationStep) { - if (!animatingOut.get() || killed) return; - audioVolumePercentLabel.setFont(font.deriveFont((float) fontSize)); - ThreadUtil.sleep(animationSleepTime.toMillis()); - } - - audioVolumePercentLabel.setVisible(false); - audioVolumePercentLabel.setFont(font); - animatingOut.set(false); - animateOutWaiterRunning.set(false); - }, animateOutAudioVolumeLabelThreadName); - } -} diff --git a/src/main/java/cyder/audio/player/InnerAudioPlayer.java b/src/main/java/cyder/audio/player/InnerAudioPlayer.java deleted file mode 100644 index ffc72a226..000000000 --- a/src/main/java/cyder/audio/player/InnerAudioPlayer.java +++ /dev/null @@ -1,241 +0,0 @@ -package cyder.audio.player; - -import com.google.common.base.Preconditions; -import com.google.errorprone.annotations.CanIgnoreReturnValue; -import cyder.audio.AudioUtil; -import cyder.console.Console; -import cyder.files.FileUtil; -import cyder.handlers.internal.ExceptionHandler; -import cyder.strings.CyderStrings; -import cyder.threads.CyderThreadRunner; -import javazoom.jl.player.Player; - -import java.io.BufferedInputStream; -import java.io.File; -import java.io.FileInputStream; - -/** - * An audio playing class which simply plays an audio file and returns the audio location when killed. - */ -final class InnerAudioPlayer { - /** - * The amount to offset a pause request by so that a sequential play - * request sounds like it was paused at that instant. - */ - private static final int PAUSE_AUDIO_REACTION_OFFSET = 10000; - - /** - * The name of the setup thread for getting the milliseconds of the audio file. - */ - private static final String SETUP_THREAD_NAME = "InnerAudioPlayer Setup Thread"; - - /** - * The maximum number of times an instance of this class can - * attempt to play itself after a failure before quitting. - */ - private static final int maxAttemptedSelfStarts = 10; - - /** - * The one and only file this audio player can play. - */ - private final File audioFile; - - /** - * Whether this object - */ - private boolean killed; - - /** - * The location the current audio file was paused/stopped at. - */ - private long pauseLocation; - - /** - * The total audio length of the current audio file. - */ - private long totalAudioLength; - - /** - * The total number of milliseconds in this audio file. - */ - private int totalMilliSeconds = 0; - - /** - * The audio player used to play audio. - */ - private Player audioPlayer; - - /** - * The file input stream used to calculate byte values outside of the play method. - */ - private FileInputStream fis; - - /** - * The number of times this audio player has attempted to invoke {@link #play()} - * from within play after an exception was thrown. - */ - private int selfStarts = 0; - - /** - * Constructs a new InnerAudioPlay. - * - * @param audioFile the audio file for this object to handle - */ - public InnerAudioPlayer(File audioFile) { - Preconditions.checkNotNull(audioFile); - Preconditions.checkArgument(audioFile.exists()); - - this.audioFile = audioFile; - - AudioPlayer.refreshAudioTitleLabel(); - initializeMillis(); - } - - /** - * Initializes the milliseconds of {@link #audioFile}. - */ - private void initializeMillis() { - CyderThreadRunner.submit(() -> { - try { - this.totalMilliSeconds = AudioUtil.getMillisFfprobe(audioFile); - } catch (Exception e) { - ExceptionHandler.handle(e); - this.totalMilliSeconds = 0; - } - }, SETUP_THREAD_NAME); - } - - /** - * Starts playing the provided audio file at the optionally provided location. - */ - public void play() { - try { - fis = new FileInputStream(audioFile); - totalAudioLength = fis.available(); - long bytesSkipped = fis.skip(Math.max(0, pauseLocation)); - - if (fis.available() != totalAudioLength - bytesSkipped) { - throw new IllegalStateException("FileInputStream failed to skip requested bytes: " + bytesSkipped); - } - - BufferedInputStream bis = new BufferedInputStream(fis); - - Console.INSTANCE.revalidateAudioMenuVisibility(); - - audioPlayer = new Player(bis); - - String threadName = "AudioPlayer Play Audio Thread" + CyderStrings.space + CyderStrings.openingBracket - + FileUtil.getFilename(audioFile) + CyderStrings.closingBracket; - CyderThreadRunner.submit(() -> { - try { - audioPlayer.play(); - - Console.INSTANCE.revalidateAudioMenuVisibility(); - - FileUtil.closeIfNotNull(fis); - FileUtil.closeIfNotNull(bis); - audioPlayer = null; - if (!killed) AudioPlayer.playAudioCallback(); - } catch (Exception possiblyIgnored) { - if (selfStarts < maxAttemptedSelfStarts) { - selfStarts++; - play(); - } else { - ExceptionHandler.handle(possiblyIgnored); - } - } - }, threadName); - - AudioPlayer.refreshPlayPauseButtonIcon(); - } catch (Exception ignored) {} - } - - /** - * Returns whether this object is playing audio. - * - * @return whether this object is playing audio - */ - public boolean isPlaying() { - return audioPlayer != null; - } - - /** - * Pauses the audio player. - */ - public void stop() { - audioPlayer.close(); - } - - /** - * Returns whether this inner audio player has been killed. - * - * @return whether this inner audio player has been killed - */ - public boolean isKilled() { - return killed; - } - - /** - * Kills the player if playing audio and returns the location to resume a new player object at. - * - * @return the location in bytes to resume a new player object at if desired - */ - @CanIgnoreReturnValue - public long kill() { - long resumeLocation; - try { - resumeLocation = totalAudioLength - fis.available() - PAUSE_AUDIO_REACTION_OFFSET; - } catch (Exception ignored) { - resumeLocation = 0L; - } - - this.killed = true; - if (audioPlayer != null) audioPlayer.close(); - fis = null; - - return resumeLocation; - } - - /** - * Returns the total computed audio length (bytes). - * - * @return the total computed audio length (bytes) - */ - public long getTotalAudioLength() { - return totalAudioLength; - } - - /** - * Sets the location this player should start playing at when {@link InnerAudioPlayer#play()} is invoked. - * - * @param pauseLocation the location this player should start from - */ - public void setLocation(long pauseLocation) { - this.pauseLocation = pauseLocation; - } - - /** - * Returns the percent into the current audio this player object is. - * - * @return the percent into the current audio this player object is - */ - public float getPercentIn() { - float percentIn; - try { - percentIn = (totalAudioLength - fis.available()) / (float) totalAudioLength; - } catch (Exception ignored) { - percentIn = 0f; - } - - return percentIn; - } - - /** - * Returns the milliseconds into the current audio this player object is. - * - * @return the milliseconds into the current audio this player object is - */ - public long getMillisecondsIn() { - return (long) (totalMilliSeconds * getPercentIn()); - } -} diff --git a/src/main/java/cyder/audio/player/ScrollingTitleLabel.java b/src/main/java/cyder/audio/player/ScrollingTitleLabel.java deleted file mode 100644 index ec5b34d2d..000000000 --- a/src/main/java/cyder/audio/player/ScrollingTitleLabel.java +++ /dev/null @@ -1,157 +0,0 @@ -package cyder.audio.player; - -import com.google.common.base.Preconditions; -import cyder.handlers.internal.ExceptionHandler; -import cyder.strings.CyderStrings; -import cyder.strings.StringUtil; -import cyder.threads.CyderThreadRunner; -import cyder.threads.ThreadUtil; - -import javax.swing.*; -import java.util.concurrent.atomic.AtomicBoolean; - -/** - * An animator class to scroll an overflowing title label within a parent container back and forth in the viewport. - */ -final class ScrollingTitleLabel { - /** - * The minimum width of the title label. - */ - public static final int MIN_WIDTH = 100; - - /** - * The timeout to sleep for before checking for title scroll label being terminated. - */ - private static final int SLEEP_WITH_CHECKS_TIMEOUT = 50; - - /** - * The timeout between moving the label from one side to the opposite side. - */ - private static final int SIDE_TO_SIDE_TIMEOUT = 5000; - - /** - * The timeout between starting the initial timeout. - */ - private static final int INITIAL_TIMEOUT = 3000; - - /** - * The timeout between movement increments of the title label. - */ - private static final int MOVEMENT_TIMEOUT = 25; - - /** - * The label this scrolling label is controlling. - */ - private final JLabel effectLabel; - - /** - * The title for the label to contain. - */ - private final String localTitle; - - /** - * Whether this scrolling title label object has been killed. - */ - private final AtomicBoolean killed = new AtomicBoolean(); - - /** - * Constructs and begins the scrolling title label animation using the - * provided label, its parent, and the provided text as the title. - * - * @param effectLabel the label to move in its parent container - * @param localTitle the title of the label - */ - public ScrollingTitleLabel(JLabel effectLabel, String localTitle) { - Preconditions.checkNotNull(effectLabel); - Preconditions.checkNotNull(localTitle); - - this.effectLabel = effectLabel; - this.localTitle = localTitle; - this.effectLabel.setText(localTitle); - animateIfNecessary(); - } - - /** - * Starts the scrolling animation if necessary. - * Otherwise, the label is centered in the parent container. - */ - private void animateIfNecessary() { - try { - int parentWidth = effectLabel.getParent().getWidth(); - int parentHeight = effectLabel.getParent().getHeight(); - - int textWidth = StringUtil.getMinWidth(localTitle, effectLabel.getFont()); - int textHeight = StringUtil.getMinHeight(localTitle, effectLabel.getFont()); - - effectLabel.setSize(Math.max(textWidth, MIN_WIDTH), parentHeight); - - if (textWidth > parentWidth) { - effectLabel.setLocation(0, 0); - - startScrollingThread(textWidth, parentWidth, localTitle); - } else { - effectLabel.setLocation(parentWidth / 2 - Math.max(textWidth, MIN_WIDTH) / 2, - parentHeight / 2 - textHeight / 2); - } - } catch (Exception e) { - ExceptionHandler.handle(e); - } - } - - /** - * Returns the text the label being controlled contains. - * - * @return the text the label being controlled contains - */ - public String localTitle() { - return effectLabel.getText(); - } - - /** - * Kills the current scrolling title label. - */ - public void kill() { - killed.set(true); - } - - /** - * Starts the thread to animate the scrolling. - * - * @param textWidth the full width of text displayed in the label - * @param parentWidth the width (should be less than textWidth) of the parent container - * @param labelText the text on the label - */ - private void startScrollingThread(int textWidth, int parentWidth, String labelText) { - String threadName = "Scrolling title label animator" + CyderStrings.space - + CyderStrings.openingBracket + labelText + CyderStrings.closingBracket; - CyderThreadRunner.submit(() -> { - try { - ThreadUtil.sleepWithChecks(INITIAL_TIMEOUT, SLEEP_WITH_CHECKS_TIMEOUT, killed); - - while (!killed.get()) { - int translatedDistance = 0; - - while (translatedDistance < textWidth - parentWidth) { - if (killed.get()) break; - effectLabel.setLocation(effectLabel.getX() - 1, effectLabel.getY()); - ThreadUtil.sleep(MOVEMENT_TIMEOUT); - translatedDistance++; - } - - ThreadUtil.sleepWithChecks(SIDE_TO_SIDE_TIMEOUT, SLEEP_WITH_CHECKS_TIMEOUT, killed); - - while (translatedDistance > 0) { - if (killed.get()) break; - effectLabel.setLocation(effectLabel.getX() + 1, effectLabel.getY()); - ThreadUtil.sleep(MOVEMENT_TIMEOUT); - translatedDistance--; - } - - ThreadUtil.sleepWithChecks(SIDE_TO_SIDE_TIMEOUT, SLEEP_WITH_CHECKS_TIMEOUT, killed); - } - } catch (Exception e) { - ExceptionHandler.handle(e); - } - }, threadName); - } -} diff --git a/src/main/java/cyder/audio/player/package-info.java b/src/main/java/cyder/audio/player/package-info.java deleted file mode 100644 index d0e59fa86..000000000 --- a/src/main/java/cyder/audio/player/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -/** - * Classes for playing audio via the {@link cyder.audio.player.AudioPlayer}. - */ -package cyder.audio.player; diff --git a/src/main/java/cyder/console/Console.java b/src/main/java/cyder/console/Console.java deleted file mode 100644 index 14998f70b..000000000 --- a/src/main/java/cyder/console/Console.java +++ /dev/null @@ -1,3690 +0,0 @@ -package cyder.console; - -import com.google.common.base.Preconditions; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.Lists; -import com.google.errorprone.annotations.CanIgnoreReturnValue; -import cyder.annotations.ForReadability; -import cyder.audio.AudioIcons; -import cyder.audio.GeneralAudioPlayer; -import cyder.audio.player.AudioPlayer; -import cyder.bounds.BoundsString; -import cyder.bounds.BoundsUtil; -import cyder.constants.CyderColors; -import cyder.constants.CyderFonts; -import cyder.constants.CyderRegexPatterns; -import cyder.enumerations.Direction; -import cyder.enumerations.Dynamic; -import cyder.enumerations.ExitCondition; -import cyder.enumerations.SystemPropertyKey; -import cyder.exceptions.FatalException; -import cyder.files.FileUtil; -import cyder.handlers.input.BaseInputHandler; -import cyder.handlers.input.TestHandler; -import cyder.handlers.internal.ExceptionHandler; -import cyder.logging.LogTag; -import cyder.logging.Logger; -import cyder.login.LoginHandler; -import cyder.managers.CyderVersionManager; -import cyder.managers.ProgramModeManager; -import cyder.managers.RobotManager; -import cyder.math.AngleUtil; -import cyder.math.GeometryUtil; -import cyder.math.NumberUtil; -import cyder.meta.CyderSplash; -import cyder.meta.ProgramState; -import cyder.meta.ProgramStateManager; -import cyder.network.NetworkUtil; -import cyder.props.Props; -import cyder.strings.StringUtil; -import cyder.threads.CyderThreadFactory; -import cyder.threads.CyderThreadRunner; -import cyder.threads.IgnoreThread; -import cyder.threads.ThreadUtil; -import cyder.time.SpecialDay; -import cyder.time.TimeUtil; -import cyder.ui.UiConstants; -import cyder.ui.UiUtil; -import cyder.ui.drag.CyderDragLabel; -import cyder.ui.drag.button.*; -import cyder.ui.field.CyderCaret; -import cyder.ui.frame.CyderFrame; -import cyder.ui.frame.enumerations.ScreenPosition; -import cyder.ui.frame.enumerations.TitlePosition; -import cyder.ui.frame.notification.NotificationBuilder; -import cyder.ui.label.CyderLabel; -import cyder.ui.pane.CyderOutputPane; -import cyder.ui.pane.CyderScrollPane; -import cyder.user.UserDataManager; -import cyder.user.UserEditor; -import cyder.user.UserFile; -import cyder.user.UserUtil; -import cyder.user.data.MappedExecutable; -import cyder.user.data.ScreenStat; -import cyder.utils.*; - -import javax.swing.*; -import javax.swing.border.LineBorder; -import javax.swing.text.StyledDocument; -import java.awt.*; -import java.awt.event.*; -import java.awt.image.BufferedImage; -import java.io.File; -import java.text.SimpleDateFormat; -import java.time.Duration; -import java.time.Instant; -import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.concurrent.Semaphore; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.stream.Collectors; -import java.util.stream.IntStream; -import java.util.stream.Stream; - -import static cyder.console.ConsoleConstants.*; -import static cyder.strings.CyderStrings.*; - -/** - * Singleton of components that represent the GUI way a user - * interacts with Cyder and its functions. - */ -@SuppressWarnings({"FieldCanBeLocal", "ImmutableEnumChecker"}) /* member clarity, enum used as singleton */ -public enum Console { - /** - * The Console singleton. - */ - INSTANCE; - - /** - * Log when the console singleton is constructed - * (enums are constructed when they are first referenced). - */ - Console() { - Logger.log(LogTag.OBJECT_CREATION, "Console singleton constructed"); - DEFAULT_INTRO_MUSIC = StaticUtil.getStaticResource("ride.mp3"); - introTheme = StaticUtil.getStaticResource("introtheme.mp3"); - chimeFile = StaticUtil.getStaticResource("chime.mp3"); - } - - /** - * The hashmap of frame secret ids to the frame object. Any caller passing a valid hash which maps - * to a frame taskbar exception has the power to remove it. - */ - private final ConcurrentHashMap frameTaskbarExceptions = new ConcurrentHashMap<>(); - - /** - * The UUID of the user currently associated with the Console. - */ - private String uuid; - - /** - * The Console's CyderFrame instance. - */ - private CyderFrame consoleCyderFrame; - - /** - * The input handler linked to the Console's IO. - */ - private BaseInputHandler baseInputHandler; - - /** - * The Console output scroll pane. - */ - private CyderScrollPane outputScroll; - - /** - * The Console output TextPane controlled by the scroll pane. - */ - private JTextPane outputArea; - - /** - * The JTextPane used for the console menu. - */ - private JTextPane menuPane; - - /** - * The input field for the Console. This is a password field - * in case we ever want to obfuscate the text in the future. - */ - private JPasswordField inputField; - - /** - * The menu button for the console frame. - */ - private MenuButton menuButton; - - /** - * The menu button for the audio controls. - */ - private MenuButton toggleAudioControls; - - /** - * The close button for the drag label right button list. - */ - private CloseButton closeButton; - - /** - * The change size button for the drag label right button list. - */ - private ChangeSizeButton changeSizeButton; - - /** - * The default focus owner for focus to default to when no focused components can be found. - */ - private Component defaultFocusOwner; - - /** - * The label used for the Cyder taskbar. - */ - private JLabel menuLabel; - - /** - * The scroll pane for the active frames. - */ - private CyderScrollPane menuScroll; - - /** - * The audio menu parent label - */ - private JLabel audioControlsLabel; - - /** - * The button label used to indicate if audio is playing - */ - private JLabel playPauseAudioLabel; - - /** - * Whether the console is closed. - */ - private final AtomicBoolean consoleClosed = new AtomicBoolean(true); - - /** - * The current bash string to use for the start of the input field. - */ - private String consoleBashString; - - /** - * The command list used for scrolling. - */ - private final ArrayList commandList = new ArrayList<>(); - - /** - * The index of the command in the command history list we are at. - */ - private int commandIndex; - - /** - * The last direction performed upon the most recent switch background call. - */ - private Direction lastSlideDirection = Direction.LEFT; - - /** - * The current orientation of the Console. - */ - private Direction consoleDir = Direction.TOP; - - /** - * The last direction the console was oriented in. - */ - private Direction lastConsoleDir = consoleDir; - - /** - * The list of recognized backgrounds that the Console may switch to. - */ - private final ArrayList backgrounds = new ArrayList<>(); - - /** - * The index of the background we are currently at in the backgrounds list. - */ - private int backgroundIndex; - - /** - * Performs Console setup routines before constructing - * the frame and setting its visibility, location, and size. - * - * @param uuid the uuid of the user to be linked to this instance of the console - * @throws IllegalStateException if the Console was left open - */ - public void initializeAndLaunch(String uuid) { - Preconditions.checkNotNull(uuid); - Preconditions.checkArgument(!uuid.isEmpty()); - Preconditions.checkState(isClosed()); - consoleClosed.set(false); - - this.uuid = uuid; - - initializeStaticUtilConstants(); - - UserDataManager.INSTANCE.initialize(uuid); - UserUtil.logoutAllUsers(); - UserDataManager.INSTANCE.setLoggedIn(true); - UserUtil.deleteInvalidBackgrounds(uuid); - - NetworkUtil.startHighPingChecker(); - - reloadBackgrounds(); - - resetMembers(); - - CyderColors.refreshGuiThemeColor(); - - ConsoleIcon consoleIcon = determineConsoleIconAndDimensions(); - - setupConsoleCyderFrame(consoleIcon); - - refreshConsoleSuperTitle(); - - installConsoleResizing(); - - installOutputArea(); - installInputField(); - - baseInputHandler = new BaseInputHandler(outputArea); - - setupButtonEnterInputMap(); - - installRightDragLabelButtons(); - installLeftDragLabelButtons(); - - initializeBusyIcon(); - - generateAudioMenu(); - installConsoleClock(); - - installConsolePinnedWindowListeners(); - - startExecutors(); - - /* - Note to maintainers: we only close splash here, all other frames are disposed on logout - which is the only way this launch method is invoked more than once for an instance of Cyder. - - The login frame is disposed elsewhere as well. Thus, any frames left open are warnings or - popups from validation subroutines which the user (hopefully developer) should read and dismiss themselves. - */ - CyderSplash.INSTANCE.fastDispose(); - - if (!isFullscreen()) restoreFromPreviousScreenStat(consoleIcon); - finalizeFrameAndInputOutputBounds(); - - performSpecialDayChecks(); - - if (UserDataManager.INSTANCE.shouldShowDebugStats()) showDebugStats(); - - checkForTestingMode(); - - performTimingChecks(); - - introMusicCheck(); - } - - /** - * Initializes any resources loaded from the {@link StaticUtil}. - */ - private void initializeStaticUtilConstants() { - - } - - /** - * Finalizes the frame's location and bounds and the bounds/focus - * ownership of the input and output fields. - */ - private void finalizeFrameAndInputOutputBounds() { - consoleCyderFrame.finalizeAndShowCurrentLocation(); - consoleCyderFrame.toFront(); - - revalidateInputAndOutputBounds(true); - - setInputFieldCaretPositionToEnd(); - } - - /** - * Checks for testing mode from the props which will invoke all - * public static void methods found annotated with {@link cyder.annotations.CyderTest}. - */ - private void checkForTestingMode() { - String currentProgramMode = ProgramModeManager.INSTANCE.getProgramMode().getName(); - Logger.log(LogTag.CONSOLE_LOAD, openingBracket + OsUtil.getOsUsername() - + closingBracket + space + openingBracket + currentProgramMode + closingBracket); - if (Props.testingMode.getValue()) TestHandler.invokeDefaultTests(); - } - - /** - * Performs timing checks when the console loads such as figuring out how long it has been - * since the user last started Cyder and informing them with a welcome back notification if it - * has been a while. The console load time is also printed to the output area. - */ - private void performTimingChecks() { - // Session start logic - long lastStart = UserDataManager.INSTANCE.getLastSessionStart(); - long millisSinceLastStart = System.currentTimeMillis() - lastStart; - if (TimeUtil.millisToDays(millisSinceLastStart) > ACCEPTABLE_DAYS_WITHOUT_USE) { - String username = UserDataManager.INSTANCE.getUsername(); - consoleCyderFrame.notify("Welcome back, " + username); - Logger.log(LogTag.DEBUG, "Last start by" + space + username - + space + TimeUtil.formatMillis(millisSinceLastStart) + space + "ago"); - } - UserDataManager.INSTANCE.setLastSessionStart(System.currentTimeMillis()); - - // Welcome message logic - if (!UserDataManager.INSTANCE.hasShownWelcomeMessage()) { - String boldUsername = HtmlUtil.applyBold(UserDataManager.INSTANCE.getUsername()); - String notifyText = "Welcome to Cyder, " + boldUsername + "! Type \"help\" for command assists"; - titleNotify(HtmlUtil.surroundWithHtmlTags(notifyText), - CyderFonts.DEFAULT_FONT_LARGE, Duration.ofMillis(6000)); - UserDataManager.INSTANCE.setShownWelcomeMessage(true); - } - - // Year anniversary logic - long accountActiveTime = System.currentTimeMillis() - UserDataManager.INSTANCE.getAccountCreationTime(); - int days = (int) Math.floor(TimeUtil.millisToDays(accountActiveTime)); - if (days % (int) TimeUtil.daysInYear == 0) { - int years = (int) (days / TimeUtil.daysInYear); - titleNotify("You've been using Cyder for " + years + " years! That's a long time, thank you.", - CyderFonts.DEFAULT_FONT_LARGE, Duration.ofMillis(4000)); - } - - // Load time logic - long loadTime = Instant.now().minusMillis(consoleLoadStartTime.toEpochMilli()).toEpochMilli(); - if (initialConsoleLoad) { - loadTime = JvmUtil.getRuntime(); - initialConsoleLoad = false; - } - baseInputHandler.println("Console loaded in " + TimeUtil.formatMillis(loadTime)); - } - - private boolean initialConsoleLoad = true; - - /** - * The time at which the console load was started. - */ - private Instant consoleLoadStartTime; - - /** - * Sets the time at which teh console load was started to now. - */ - public void setConsoleLoadStartTime() { - consoleLoadStartTime = Instant.now(); - } - - /** - * The ratio of the busy width length to the console width. - */ - private final int busyIconToConsoleWidthRatio = 8; - - /** - * The height of the busy icon. - */ - private final int busyIconHeight = 3; - - /** - * The busy icon animation increment. - */ - private final int busyIconAnimationIncrement = 2; - - /** - * The busy icon animation delay. - */ - private final int busyIconAnimationDelay = 4; - - /** - * The delay the busy icon stops between sliding from the right to left and the left to the right. - */ - private final Duration busyIconAnimationTransitionDelay = Duration.ofMillis(500); - - /** - * The busy icon for the console. - */ - private JLabel busyIcon; - - /** - * Whether the busy icon has been initialized. - */ - private boolean busyIconInitialized = false; - - /** - * Whether the busy icon should be showed currently. - */ - private final AtomicBoolean shouldShowBusyAnimation = new AtomicBoolean(); - - /** - * The starting point of the busy icon. - */ - private final Point busyIconStartingPoint = new Point(0, 0); - - /** - * Initializes the console busy icon. - */ - private void initializeBusyIcon() { - if (busyIconInitialized) return; - busyIconInitialized = true; - - int busyIconWidth = consoleCyderFrame.getWidth() / busyIconToConsoleWidthRatio; - busyIcon = new JLabel() { - @Override - public void paint(Graphics g) { - g.setColor(CyderColors.vanilla); - g.fillRect(0, 0, busyIconWidth, busyIconHeight); - } - }; - busyIcon.setBounds(0, 0, busyIconWidth, busyIconHeight); - consoleCyderFrame.getTopDragLabel().add(busyIcon); - - busyIcon.repaint(); - busyIcon.setVisible(false); - } - - /** - * Shows the busy animation starting from the beginning - */ - private void showBusyAnimation() { - Preconditions.checkState(!shouldShowBusyAnimation.get()); - - shouldShowBusyAnimation.set(true); - busyIcon.setLocation(busyIconStartingPoint); - busyIcon.repaint(); - busyIcon.setVisible(true); - - CyderThreadRunner.submit(() -> { - OUTER: - while (shouldShowBusyAnimation.get()) { - while (busyIcon.getX() + busyIcon.getWidth() < consoleCyderFrame.getWidth()) { - busyIcon.setSize(consoleCyderFrame.getWidth() / busyIconToConsoleWidthRatio, busyIconHeight); - busyIcon.setLocation(busyIcon.getX() + busyIconAnimationIncrement, 0); - busyIcon.repaint(); - ThreadUtil.sleep(busyIconAnimationDelay); - if (!shouldShowBusyAnimation.get()) break OUTER; - } - - ThreadUtil.sleep(busyIconAnimationTransitionDelay.toMillis()); - - while (busyIcon.getX() > 0) { - if (busyIcon.getX() + busyIcon.getWidth() > consoleCyderFrame.getWidth()) { - busyIcon.setLocation(consoleCyderFrame.getWidth() - busyIcon.getWidth(), 0); - } - - busyIcon.setSize(consoleCyderFrame.getWidth() / busyIconToConsoleWidthRatio, busyIconHeight); - busyIcon.setLocation(busyIcon.getX() - busyIconAnimationIncrement, 0); - busyIcon.repaint(); - ThreadUtil.sleep(busyIconAnimationDelay); - if (!shouldShowBusyAnimation.get()) break OUTER; - } - - ThreadUtil.sleep(busyIconAnimationTransitionDelay.toMillis()); - } - - busyIcon.setVisible(false); - }, IgnoreThread.ConsoleBusyAnimation.getName()); - } - - /** - * Hides the busy animation if currently visible. - */ - public void hideBusyAnimation() { - shouldShowBusyAnimation.set(false); - } - - /** - * Resets private variables to their default state. - */ - @ForReadability - private void resetMembers() { - consoleBashString = UserDataManager.INSTANCE.getUsername() + BASH_STRING_PREFIX; - - lastSlideDirection = DEFAULT_CONSOLE_DIRECTION; - consoleDir = DEFAULT_CONSOLE_DIRECTION; - - commandIndex = 0; - - menuLabel = null; - - commandList.clear(); - currentActiveFrames.clear(); - } - - /** - * Sets up the console cyder frame and performs all subsequent calls on the object. - * - * @param consoleIcon the console icon record to use for the direct props - */ - private void setupConsoleCyderFrame(ConsoleIcon consoleIcon) { - consoleCyderFrame = new CyderFrame.Builder() - .setWidth((int) consoleIcon.dimension().getWidth()) - .setHeight((int) consoleIcon.dimension().getHeight()) - .setBackgroundIcon(consoleIcon.background()) - .build(); - consoleCyderFrame.addPostSetBoundsRunnable(() -> { - revalidateInputAndOutputBounds(); - revalidateConsoleMenuBounds(); - revalidateAudioMenuBounds(); - consoleCyderFrame.revalidateMenu(); - revalidateTitleNotify(); - }); - consoleCyderFrame.addPreMinimizeAndIconifyAction(this::saveScreenStat); - consoleCyderFrame.addPreCloseAction(() -> { - outputArea.setFocusable(false); - outputScroll.setFocusable(false); - UiUtil.disposeAllFrames(true, consoleCyderFrame); - }); - - // It is intended that the console and splash never show up in the taskbar - String consoleKey = SecurityUtil.generateUuid(); - String splashKey = SecurityUtil.generateUuid(); - frameTaskbarExceptions.put(consoleKey, consoleCyderFrame); - frameTaskbarExceptions.put(splashKey, CyderSplash.INSTANCE.getSplashFrame()); - - consoleCyderFrame.setBackground(Color.black); - consoleCyderFrame.addEndDragEventCallback(this::saveScreenStat); - consoleCyderFrame.setDraggingEnabled(!UserDataManager.INSTANCE.isFullscreen()); - consoleCyderFrame.addWindowListener(consoleWindowAdapter); - - getConsoleCyderFrameContentPane().setToolTipText( - FileUtil.getFilename(getCurrentBackground().getReferenceFile().getName())); - - consoleCyderFrame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); - - consoleCyderFrame.setPaintCyderFrameTitleOnSuperCall(false); - consoleCyderFrame.setPaintSuperTitle(true); - - consoleCyderFrame.setShouldAnimateOpacity(!isFullscreen()); - } - - /** - * The record used for determining the console background icon and the corresponding width and height. - */ - private record ConsoleIcon(ImageIcon background, Dimension dimension) {} - - /** - * Determines the initial console background icon. - * - * @return a record containing the initial console background icon and the dimensions of the icon - */ - @ForReadability - private ConsoleIcon determineConsoleIconAndDimensions() { - int width; - int height; - ImageIcon icon; - - boolean randomBackground = UserDataManager.INSTANCE.shouldChooseRandomBackground(); - if (randomBackground && reloadAndGetBackgrounds().size() > 1) { - backgroundIndex = NumberUtil.generateRandomInt(backgrounds.size() - 1); - } - - boolean fullscreen = UserDataManager.INSTANCE.isFullscreen(); - if (fullscreen) { - int monitorId = UserDataManager.INSTANCE.getScreenStat().getMonitor(); - Rectangle monitorBounds = UiUtil.getGraphicsDevice(monitorId).getDefaultConfiguration().getBounds(); - - width = (int) monitorBounds.getWidth(); - height = (int) monitorBounds.getHeight(); - - icon = new ImageIcon(ImageUtil.resizeImage(width, height, getCurrentBackground().getReferenceFile())); - } else { - BufferedImage bi = getCurrentBackground().generateBufferedImage(); - if (bi == null) throw new FatalException("Generated buffered image is null"); - - width = bi.getWidth(); - height = bi.getHeight(); - icon = new ImageIcon(ImageUtil.getRotatedImage(getCurrentBackground() - .getReferenceFile().toString(), getConsoleDirection())); - } - - if (width == 0 || height == 0) { - throw new FatalException("Could not construct background dimension"); - } - - return new ConsoleIcon(icon, new Dimension(width, height)); - } - - /** - * Refreshes the console super title, that of displaying "Version Cyder [Nathan]". - */ - public void refreshConsoleSuperTitle() { - consoleCyderFrame.setTitle(CyderVersionManager.INSTANCE.getVersion() - + space - + CyderVersionManager.INSTANCE.getProgramName() - + space + dash + space - + UserDataManager.INSTANCE.getUsername()); - } - - /** - * The value to indicate a frame is not pinned to the console. - */ - private static final int FRAME_NOT_PINNED = Integer.MIN_VALUE; - - /** - * The mouse motion adapter for frame pinned window logic. - */ - private final MouseMotionAdapter consolePinnedMouseMotionAdapter = new MouseMotionAdapter() { - @Override - public void mouseDragged(MouseEvent e) { - if (consoleCyderFrame == null - || !consoleCyderFrame.isFocusable() - || !consoleCyderFrame.isDraggingEnabled()) return; - - UiUtil.getCyderFrames().stream().filter(frame -> frame.isPinnedToConsole() - && !frame.isConsole() - && frame.getRelativeX() != FRAME_NOT_PINNED - && frame.getRelativeY() != FRAME_NOT_PINNED) - .forEach(frame -> UiUtil.requestFramePosition( - new Point(consoleCyderFrame.getX() + frame.getRelativeX(), - consoleCyderFrame.getY() + frame.getRelativeY()), frame - )); - } - }; - - /** - * The mouse adapter for frame pinned window logic. - */ - private final MouseAdapter consolePinnedMouseAdapter = new MouseAdapter() { - @Override - public void mousePressed(MouseEvent e) { - if (consoleCyderFrame == null || !consoleCyderFrame.isFocusable() - || !consoleCyderFrame.isDraggingEnabled()) return; - - UiUtil.getCyderFrames().stream() - .filter(frame -> frame.isPinnedToConsole() && !frame.isConsole()) - .forEach(frame -> { - if (GeometryUtil.rectanglesOverlap( - consoleCyderFrame.getBounds(), frame.getBounds())) { - frame.setRelativeX(-consoleCyderFrame.getX() + frame.getX()); - frame.setRelativeY(-consoleCyderFrame.getY() + frame.getY()); - } else { - frame.setRelativeX(FRAME_NOT_PINNED); - frame.setRelativeY(FRAME_NOT_PINNED); - } - }); - } - }; - - /** - * Adds the pinned window logic listeners to the console. - */ - @ForReadability - private void installConsolePinnedWindowListeners() { - consoleCyderFrame.addDragListener(consolePinnedMouseMotionAdapter); - consoleCyderFrame.addDragLabelMouseListener(consolePinnedMouseAdapter); - } - - /** - * Revalidates the bounds of the custom console menu and the audio controls menu. - */ - private void revalidateConsoleMenuBounds() { - if (UiUtil.notNullAndVisible(menuLabel)) { - menuLabel.setBounds((int) consoleMenuShowingPoint.getX(), (int) consoleMenuShowingPoint.getY(), - TASKBAR_MENU_WIDTH, calculateMenuHeight()); - } - } - - /** - * The y value for the audio menu after animated on. - */ - private static final int audioMenuLabelShowingY = CyderDragLabel.DEFAULT_HEIGHT - 2; - - /** - * Revalidates the audio menu bounds. - */ - public void revalidateAudioMenuBounds() { - if (UiUtil.notNullAndVisible(audioControlsLabel)) { - audioControlsLabel.setBounds(calculateAudioMenuX(), audioMenuLabelShowingY, - AUDIO_MENU_LABEL_WIDTH, AUDIO_MENU_LABEL_HEIGHT); - } - } - - /** - * Revalidates the bounds of the input field and output area based off - * of the current console size and the menu state. - */ - private void revalidateInputAndOutputBounds() { - revalidateInputAndOutputBounds(false); - } - - /** - * Revalidates the bounds of the input field and output area based off - * of the current console size and the menu state. - * - * @param ignoreMenuLabel whether to ignore the menu label and treat it as invisible - */ - private void revalidateInputAndOutputBounds(boolean ignoreMenuLabel) { - if (outputScroll != null && inputField != null) { - int w = consoleCyderFrame.getWidth(); - int h = consoleCyderFrame.getHeight(); - - int menuLabelEndX = (UiUtil.notNullAndVisible(menuLabel) && !ignoreMenuLabel) - ? 2 + menuLabel.getWidth() : 0; - - outputScroll.setBounds(menuLabelEndX + FIELD_X_PADDING, - CyderDragLabel.DEFAULT_HEIGHT + FIELD_Y_PADDING, - w - menuLabelEndX - 2 * FIELD_X_PADDING, - h - INPUT_FIELD_HEIGHT - FIELD_Y_PADDING * 3 - CyderDragLabel.DEFAULT_HEIGHT); - - inputField.setBounds(menuLabelEndX + FIELD_X_PADDING, - outputScroll.getY() + FIELD_Y_PADDING + outputScroll.getHeight(), - w - 2 * FIELD_X_PADDING - menuLabelEndX, INPUT_FIELD_HEIGHT); - } - } - - /** - * Sets up the output area and output scroll and adds it to the console. - */ - @ForReadability - private void installOutputArea() { - outputArea = new JTextPane() { - @Override - public void setBounds(int x, int y, int w, int h) { - StyledDocument sd = outputArea.getStyledDocument(); - int pos = outputArea.getCaretPosition(); - super.setBounds(x, y, w, h); - outputArea.setStyledDocument(sd); - outputArea.setCaretPosition(pos); - } - }; - - outputArea.setEditable(false); - outputArea.setCaretColor(UserDataManager.INSTANCE.getForegroundColor()); - outputArea.setCaret(new CyderCaret(UserDataManager.INSTANCE.getForegroundColor())); - outputArea.setAutoscrolls(true); - - outputArea.setFocusable(true); - outputArea.setSelectionColor(CyderColors.selectionColor); - outputArea.setOpaque(false); - outputArea.setBackground(CyderColors.empty); - outputArea.setForeground(UserDataManager.INSTANCE.getForegroundColor()); - outputArea.setFont(generateUserFont()); - - installOutputAreaListeners(); - - outputScroll = new CyderScrollPane(outputArea, - ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED, - ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED) { - @Override - public void setBounds(int x, int y, int w, int h) { - super.setBounds(x, y, w, h); - - if (outputArea != null) { - int pos = outputArea.getCaretPosition(); - outputArea.setStyledDocument(outputArea.getStyledDocument()); - outputArea.setCaretPosition(pos); - } - } - }; - outputScroll.setThumbColor(CyderColors.regularPink); - outputScroll.getViewport().setOpaque(false); - outputScroll.setOpaque(false); - outputScroll.setFocusable(true); - boolean outputBorder = UserDataManager.INSTANCE.shouldDrawOutputBorder(); - Color color = UserDataManager.INSTANCE.getBackgroundColor(); - outputScroll.setBorder(outputBorder - ? new LineBorder(color, FIELD_BORDER_THICKNESS, false) - : BorderFactory.createEmptyBorder()); - - boolean outputFill = UserDataManager.INSTANCE.shouldDrawOutputFill(); - if (outputFill) { - outputArea.setOpaque(true); - outputArea.setBackground(color); - outputArea.repaint(); - outputArea.revalidate(); - } - - consoleCyderFrame.getContentPane().add(outputScroll); - } - - /** - * Sets up the input field and adds it to the console. - */ - @ForReadability - private void installInputField() { - inputField = new JPasswordField(); - - inputField.setEchoChar((char) 0); - inputField.setText(consoleBashString); - boolean inputBorder = UserDataManager.INSTANCE.shouldDrawInputBorder(); - Color backgroundColor = UserDataManager.INSTANCE.getBackgroundColor(); - inputField.setBorder(inputBorder - ? new LineBorder(backgroundColor, FIELD_BORDER_THICKNESS, false) - : BorderFactory.createEmptyBorder()); - inputField.setSelectionColor(CyderColors.selectionColor); - setInputFieldCaretPositionToEnd(); - - inputField.setOpaque(false); - inputField.setCaretColor(UserDataManager.INSTANCE.getForegroundColor()); - inputField.setCaret(new CyderCaret(UserDataManager.INSTANCE.getForegroundColor())); - inputField.setForeground(UserDataManager.INSTANCE.getForegroundColor()); - inputField.setFont(generateUserFont()); - - installInputFieldListeners(); - - boolean inputFill = UserDataManager.INSTANCE.shouldDrawInputFill(); - if (inputFill) { - inputField.setOpaque(true); - inputField.setBackground(UserDataManager.INSTANCE.getBackgroundColor()); - inputField.repaint(); - inputField.revalidate(); - } - - consoleCyderFrame.getContentPane().add(inputField); - - defaultFocusOwner = inputField; - } - - /** - * Sets up the input map to allow the drag label buttons to be triggered via the enter key. - */ - @ForReadability - private void setupButtonEnterInputMap() { - InputMap inputMap = (InputMap) UIManager.get(BUTTON_INPUT_FOCUS_MAP_KEY); - inputMap.put(KeyStroke.getKeyStroke(ENTER), PRESSED); - inputMap.put(KeyStroke.getKeyStroke(RELEASED_ENTER), RELEASED); - } - - /** - * Sets up the console location, size, background direction, and pin button state - * based on the saved stats from the previous session. - * - * @param consoleIcon the console icon to use as the background - */ - @SuppressWarnings("SuspiciousNameCombination") /* Switching with and height vars */ - private void restoreFromPreviousScreenStat(ConsoleIcon consoleIcon) { - ScreenStat requestedConsoleStats = UserDataManager.INSTANCE.getScreenStat(); - Direction consoleDirection = requestedConsoleStats.getConsoleDirection(); - - boolean onTop = requestedConsoleStats.isConsoleOnTop(); - PinButton.PinState state = onTop ? PinButton.PinState.CONSOLE_PINNED : PinButton.PinState.DEFAULT; - consoleCyderFrame.getTopDragLabel().getPinButton().setState(state); - - double consoleIconWidth = consoleIcon.dimension().getWidth(); - double consoleIconHeight = consoleIcon.dimension().getHeight(); - if (Direction.isHorizontal(consoleDirection)) { - double tmp = consoleIconHeight; - consoleIconHeight = consoleIconWidth; - consoleIconWidth = tmp; - } - - int requestedConsoleWidth = requestedConsoleStats.getConsoleWidth(); - int requestedConsoleHeight = requestedConsoleStats.getConsoleHeight(); - - if (requestedConsoleWidth <= consoleIconWidth - && requestedConsoleHeight <= consoleIconHeight - && requestedConsoleWidth >= MINIMUM_SIZE.width - && requestedConsoleHeight >= MINIMUM_SIZE.height) { - consoleCyderFrame.setSize(requestedConsoleWidth, requestedConsoleHeight); - } - - consoleDir = requestedConsoleStats.getConsoleDirection(); - ImageIcon rotated = getCurrentRotatedConsoleBackground(); - ImageIcon resized = ImageUtil.resizeImage(rotated, requestedConsoleWidth, requestedConsoleHeight); - consoleCyderFrame.setBackground(resized); - - int requestedConsoleX = requestedConsoleStats.getConsoleX(); - int requestedConsoleY = requestedConsoleStats.getConsoleY(); - Point relocatedSplashCenter = CyderSplash.INSTANCE.getRelocatedCenterPoint(); - if (relocatedSplashCenter != null) { - requestedConsoleX = (int) (relocatedSplashCenter.getX() - consoleCyderFrame.getWidth() / 2); - requestedConsoleY = (int) (relocatedSplashCenter.getY() - consoleCyderFrame.getHeight() / 2); - } - // This is more so to push the frame into bounds if any part was out of bounds on the requested monitor. - UiUtil.requestFramePosition(new Point(requestedConsoleX, requestedConsoleY), consoleCyderFrame); - } - - /** - * The tooltip of the alternate background button. - */ - private static final String ALTERNATE_BACKGROUND = "Alternate Background"; - - /** - * The tooltip of the audio menu button. - */ - private static final String AUDIO_MENU = "Audio Menu"; - - /** - * The text for the only one background notification builder. - */ - private static final String onlyOneBackgroundNotificationText = "You only have one background image. " - + "Try adding more via the user editor"; - - /** - * The builder for when the alternate background buttons is pressed when only one background is present. - */ - private static final NotificationBuilder onlyOneBackgroundNotificationBuilder - = new NotificationBuilder(onlyOneBackgroundNotificationText) - .setViewDuration(5000) - .setOnKillAction(() -> UserEditor.showGui(UserEditor.Page.FILES)); - - /** - * Installs the right drag label buttons for the console frame. - */ - private void installRightDragLabelButtons() { - // Remove default close button - consoleCyderFrame.getTopDragLabel().removeRightButton(2); - // Add custom close button - closeButton = new CloseButton(); - closeButton.setFocusPaintable(true); - closeButton.setClickAction(() -> { - if (UserDataManager.INSTANCE.shouldMinimizeOnClose()) { - UiUtil.minimizeAllFrames(); - } else { - releaseResourcesAndCloseFrame(true); - } - }); - closeButton.addFocusListener(new FocusAdapter() { - @Override - public void focusLost(FocusEvent e) { - if (e.getCause() == FocusEvent.Cause.TRAVERSAL_BACKWARD) { - changeSizeButton.requestFocus(); - } else { - outputArea.requestFocus(); - } - } - }); - consoleCyderFrame.getTopDragLabel().addRightButton(closeButton, 2); - - // Remove default minimize button - consoleCyderFrame.getTopDragLabel().removeRightButton(0); - // Add custom minimize button - MinimizeButton minimizeButton = new MinimizeButton(consoleCyderFrame); - minimizeButton.setFocusPaintable(true); - consoleCyderFrame.getTopDragLabel().addRightButton(minimizeButton, 0); - - // Remove default pin button - consoleCyderFrame.getTopDragLabel().removeRightButton(1); - // Add custom pin button - PinButton pinButton = new PinButton(consoleCyderFrame); - pinButton.setFocusPaintable(true); - pinButton.addClickAction(this::saveScreenStat); - consoleCyderFrame.getTopDragLabel().setPinButton(pinButton); - consoleCyderFrame.getTopDragLabel().addRightButton(pinButton, 1); - - changeSizeButton = new ChangeSizeButton(); - changeSizeButton.setFocusPaintable(true); - changeSizeButton.setToolTipText(ALTERNATE_BACKGROUND); - changeSizeButton.setClickAction(this::attemptToSwitchBackground); - consoleCyderFrame.getTopDragLabel().addRightButton(changeSizeButton, 2); - - toggleAudioControls = new MenuButton(); - toggleAudioControls.setFocusPaintable(true); - toggleAudioControls.setToolTipText(AUDIO_MENU); - toggleAudioControls.setClickAction(() -> { - if (audioControlsLabel.isVisible()) { - animateOutAudioControls(); - } else { - animateInAudioControls(); - } - }); - toggleAudioControls.setVisible(false); - consoleCyderFrame.getTopDragLabel().addRightButton(toggleAudioControls, 0); - } - - /** - * Installs the left drag label buttons for the console frame. - */ - @ForReadability - private void installLeftDragLabelButtons() { - menuButton = new MenuButton(); - menuButton.setFocusPaintable(true); - menuButton.setClickAction(this::onMenuButtonClicked); - menuButton.addKeyListener(menuButtonKeyAdapter); - menuButton.addFocusGainedAction(this::removeFocusFromTaskbarMenuIcons); - menuButton.addFocusLostAction(this::removeFocusFromTaskbarMenuIcons); - consoleCyderFrame.getTopDragLabel().addLeftButton(menuButton, 0); - } - - /** - * Sets up and adds the console clock to the top drag label. - */ - @ForReadability - private void installConsoleClock() { - consoleCyderFrame.setTitlePosition(TitlePosition.CENTER); - consoleCyderFrame.setCyderFrameTitle(""); - consoleCyderFrame.setTitleLabelFont(CONSOLE_CLOCK_FONT); - } - - /** - * The key used for the debug lines abstract action. - */ - private static final String DEBUG_LINES = "debuglines"; - - /** - * The key used for the forced exit abstract action. - */ - private static final String FORCED_EXIT = "forcedexit"; - - /** - * Installs all the input field listeners. - */ - private void installInputFieldListeners() { - inputField.addKeyListener(inputFieldKeyAdapter); - inputField.addKeyListener(commandScrolling); - inputField.addMouseWheelListener(fontSizerListener); - inputField.addActionListener(inputFieldActionListener); - inputField.addFocusListener(inputFieldFocusAdapter); - - KeyStroke debugKeystroke = KeyStroke.getKeyStroke(KeyEvent.VK_Z, - InputEvent.CTRL_DOWN_MASK + InputEvent.ALT_DOWN_MASK); - inputField.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(debugKeystroke, DEBUG_LINES); - inputField.getActionMap().put(DEBUG_LINES, UiUtil.generateAbstractAction(() -> { - debugLinesShown.set(!debugLinesShown.get()); - refreshDebugLines(); - })); - - KeyStroke exitKeystroke = KeyStroke.getKeyStroke(KeyEvent.VK_F4, InputEvent.ALT_DOWN_MASK); - inputField.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(exitKeystroke, FORCED_EXIT); - inputField.getActionMap().put(FORCED_EXIT, UiUtil.generateAbstractAction( - () -> OsUtil.exit(ExitCondition.ForcedImmediateExit))); - } - - /** - * The state of debug lines. - */ - private final AtomicBoolean debugLinesShown = new AtomicBoolean(false); - - /** - * Refreshes the state of the debug lines depending on {@link #debugLinesShown}. - */ - private void refreshDebugLines() { - getInputHandler().println((debugLinesShown.get() ? "Drawing" : "Erasing") + " debug lines"); - UiUtil.getCyderFrames().forEach(frame -> frame.toggleDebugLines(debugLinesShown.get())); - } - - /** - * Installs all the output area listeners. - */ - private void installOutputAreaListeners() { - outputArea.addFocusListener(outputAreaFocusAdapter); - outputArea.addMouseWheelListener(fontSizerListener); - } - - /** - * Sets up resizing for the console. - */ - private void installConsoleResizing() { - consoleCyderFrame.initializeResizing(); - consoleCyderFrame.setResizable(true); - consoleCyderFrame.setBackgroundResizing(true); - consoleCyderFrame.setMinimumSize(MINIMUM_SIZE); - consoleCyderFrame.setSnapSize(SNAP_SIZE); - - refreshConsoleMaxSize(); - } - - /** - * Refreshes the maximum size of the console. - */ - private void refreshConsoleMaxSize() { - if (getCurrentBackground().getReferenceFile() != null) { - ImageIcon currentIcon = getCurrentBackground().generateImageIcon(); - int w = currentIcon.getIconWidth(); - int h = currentIcon.getIconHeight(); - - if (Direction.isHorizontal(getConsoleDirection())) { - consoleCyderFrame.setMaximumSize(new Dimension(h, w)); - } else { - consoleCyderFrame.setMaximumSize(new Dimension(w, h)); - } - } else { - consoleCyderFrame.setMaximumSize(consoleCyderFrame.getSize()); - } - } - - /** - * The window adapter for window iconification/de-iconification actions. - */ - private final WindowAdapter consoleWindowAdapter = new WindowAdapter() { - /** - * {@inheritDoc} - */ - @Override - public void windowDeiconified(WindowEvent e) { - inputField.requestFocus(); - setInputFieldCaretPositionToEnd(); - } - - /** - * {@inheritDoc} - */ - @Override - public void windowClosed(WindowEvent e) { - if (consoleClosed.get()) return; - - if (UserDataManager.INSTANCE.shouldMinimizeOnClose()) { - UiUtil.minimizeAllFrames(); - } else { - Console.INSTANCE.releaseResourcesAndCloseFrame(true); - } - } - - /** - * {@inheritDoc} - */ - @Override - public void windowOpened(WindowEvent e) { - inputField.requestFocus(); - } - }; - - /** - * The chime audio file. - */ - private final File chimeFile; - - /** - * The last hour a chime sound was played at. - */ - private final AtomicInteger lastChimeHour = new AtomicInteger(-1); - - /** - * The frequency to check the clock for a chime. - */ - private static final int CHIME_CHECKER_FREQUENCY = 50; - - /** - * The clock refresh frequency. - */ - private static final int CLOCK_REFRESH_SLEEP_TIME = 200; - - /** - * The frequency to check for console disposal in the clock refresh thread. - */ - private static final int CLOCK_CHECK_FREQUENCY = 50; - - /** - * The frequency to check for whether the busy animation should be shown. - */ - private static final int busyAnimationSleepTime = 3000; - - /** - * The frequency to check for console disposal in the busy animation checker thread. - */ - private static final int busyAnimationCheckFrequency = 50; - - /** - * Begins the console checker executors/threads. - */ - @ForReadability - private void startExecutors() { - CyderThreadRunner.submit(() -> { - try { - while (true) { - if (!isClosed()) { - LocalDateTime now = LocalDateTime.now(); - int min = now.getMinute(); - int sec = now.getSecond(); - int hour = now.getHour(); - - if (min == 0 && sec == 0 && lastChimeHour.get() != hour) { - if (UserDataManager.INSTANCE.shouldPlayHourlyChimes()) { - GeneralAudioPlayer.playAudio(chimeFile); - lastChimeHour.set(hour); - } - } - - ThreadUtil.sleep(CHIME_CHECKER_FREQUENCY); - } - } - } catch (Exception e) { - ExceptionHandler.handle(e); - } - }, IgnoreThread.HourlyChimeChecker.getName()); - - CyderThreadRunner.submit(() -> { - while (true) { - if (!isClosed()) { - try { - refreshClockText(); - ThreadUtil.sleepWithChecks(CLOCK_REFRESH_SLEEP_TIME, CLOCK_CHECK_FREQUENCY, consoleClosed); - } catch (Exception ignored) { - // Don't care - } - } - } - }, IgnoreThread.ConsoleClockUpdater.getName()); - - CyderThreadRunner.submit(() -> { - try { - while (true) { - boolean busyIcon = UserDataManager.INSTANCE.shouldShowBusyAnimation(); - if (!isClosed() && busyIcon) { - if (ThreadUtil.threadsIndicateCyderBusy()) { - shouldShowBusyAnimation.set(false); - } else if (!shouldShowBusyAnimation.get()) { - showBusyAnimation(); - } - } - - ThreadUtil.sleepWithChecks(busyAnimationSleepTime, busyAnimationCheckFrequency, consoleClosed); - } - } catch (Exception e) { - ExceptionHandler.handle(e); - } - }, IgnoreThread.CyderBusyChecker.getName()); - } - - /** - * The thread name of the debug stat finder. - */ - private static final String DEBUG_STAT_FINDER_THREAD_NAME = "Debug Stat Finder"; - - /** - * The number of days without Cyder use which can pass without a welcome back notification. - */ - private static final int ACCEPTABLE_DAYS_WITHOUT_USE = 2; - - /** - * Checks today against all the values of {@link SpecialDay}. - * If any are today, the notification message is shown to the user. - */ - private void performSpecialDayChecks() { - SpecialDay.getSpecialDaysOfToday().forEach( - specialDay -> consoleCyderFrame.notify(specialDay.getNotificationMessage())); - } - - /** - * Shows the debug stats, that being the following: - *
    - *
  • {@link StatUtil#getSystemProperties()}
  • - *
  • {@link StatUtil#getComputerMemorySpaces()}
  • - *
  • {@link SystemPropertyKey#values()}
  • - *
  • {@link StatUtil#getDebugProps()}
  • - *
- */ - private void showDebugStats() { - CyderThreadRunner.submit(() -> { - try { - StatUtil.getSystemProperties().forEach(property -> getInputHandler().println(property)); - StatUtil.getComputerMemorySpaces().forEach(property -> getInputHandler().println(property)); - Arrays.stream(SystemPropertyKey.values()).forEach(property -> - getInputHandler().println(property.getProperty())); - - Future futureStats = StatUtil.getDebugProps(); - while (!futureStats.isDone()) Thread.onSpinWait(); - StatUtil.DebugStats stats = futureStats.get(); - - stats.lines().forEach(line -> getInputHandler().println(line)); - getInputHandler().println(stats.countryFlag()); - } catch (Exception e) { - ExceptionHandler.handle(e); - } - }, DEBUG_STAT_FINDER_THREAD_NAME); - } - - /** - * The default intro music to play if enabled and no user music is present. - */ - private final File DEFAULT_INTRO_MUSIC; - - /** - * The Cyder intro theme file. - */ - private final File introTheme; - - /** - * The thread name of the intro music grayscale checker. - */ - private static final String INTRO_MUSIC_CHECKER_THREAD_NAME = "Intro Music Checker"; - - /** - * Determines what audio to play at the beginning of the Console startup. - */ - private void introMusicCheck() { - if (UserDataManager.INSTANCE.shouldPlayIntroMusic()) { - performIntroMusic(); - } else if (CyderVersionManager.INSTANCE.isReleased()) { - grayscaleImageCheck(); - } - } - - /** - * Plays music from the user's music folder if a file is present. Otherwise, the default intro music is played. - */ - @ForReadability - private void performIntroMusic() { - ArrayList musicList = new ArrayList<>(); - - File userMusicDir = Dynamic.buildDynamic( - Dynamic.USERS.getFileName(), uuid, UserFile.MUSIC.getName()); - - File[] files = userMusicDir.listFiles(); - if (files != null && files.length > 0) { - Arrays.stream(files).forEach(file -> { - if (FileUtil.isSupportedAudioExtension(file)) { - musicList.add(file); - } - }); - } - - if (!musicList.isEmpty()) { - int randomFileIndex = NumberUtil.generateRandomInt(files.length - 1); - GeneralAudioPlayer.playAudio(files[randomFileIndex]); - } else { - GeneralAudioPlayer.playAudio(DEFAULT_INTRO_MUSIC); - } - } - - /** - * Checks for a grayscale image and plays a grayscale song if true. - */ - @ForReadability - private void grayscaleImageCheck() { - CyderThreadRunner.submit(() -> { - try { - if (ImageUtil.isGrayscale(ImageUtil.read(getCurrentBackground().getReferenceFile()))) { - int grayscaleAudioRandomIndex = NumberUtil.generateRandomInt(GRAYSCALE_AUDIO_PATHS.size() - 1); - GeneralAudioPlayer.playAudio(GRAYSCALE_AUDIO_PATHS.get(grayscaleAudioRandomIndex)); - } else { - GeneralAudioPlayer.playAudio(introTheme); - } - } catch (Exception e) { - ExceptionHandler.handle(e); - } - }, INTRO_MUSIC_CHECKER_THREAD_NAME); - } - - /** - * The focus adapter for the output area field. - */ - private final FocusAdapter outputAreaFocusAdapter = new FocusAdapter() { - @Override - public void focusGained(FocusEvent e) { - Color color = UserDataManager.INSTANCE.getBackgroundColor(); - outputScroll.setBorder(new LineBorder(color, 3)); - } - - @Override - public void focusLost(FocusEvent e) { - boolean outputBorder = UserDataManager.INSTANCE.shouldDrawOutputBorder(); - if (!outputBorder) { - outputScroll.setBorder(BorderFactory.createEmptyBorder()); - } - - if (e.getCause() == FocusEvent.Cause.TRAVERSAL_BACKWARD) { - closeButton.requestFocus(); - } else { - inputField.requestFocusInWindow(); - setInputFieldCaretPositionToEnd(); - } - } - }; - - /** - * The focus adapter for the input field. - */ - private final FocusAdapter inputFieldFocusAdapter = new FocusAdapter() { - @Override - public void focusLost(FocusEvent e) { - super.focusLost(e); - currentFocusedMenuItemIndex = 0; - } - }; - - /** - * The index of the current focused menu item index. - */ - private int currentFocusedMenuItemIndex = -1; - - /** - * Focuses the next menu item. - */ - private void focusNextTaskbarMenuItem() { - focusTaskbarMenuItem(currentFocusedMenuItemIndex + 1); - } - - /** - * Focuses the last last menu item - */ - private void focusPreviousTaskbarMenuItem() { - focusTaskbarMenuItem(currentFocusedMenuItemIndex - 1); - } - - /** - * Removes focus from the previous focused taskbar menu item and focuses the one at the requested index if valid. - * - * @param index the index of the taskbar menu item to focus - */ - private void focusTaskbarMenuItem(int index) { - ImmutableList state = ImmutableList.copyOf( - Stream.of(currentFrameMenuItems, currentMappedExeItems, currentDefaultMenuItems) - .flatMap(Collection::stream) - .collect(Collectors.toList())); - - if (state.size() == 0) return; - - // Remove focus from previous item if possible - if (currentFocusedMenuItemIndex != -1) { - state.get(currentFocusedMenuItemIndex).setFocused(false); - } - - // Wrap around if out of bounds - if (index < 0) { - index = state.size() - 1; - } else if (index > state.size() - 1) { - index = 0; - } - - // Give and paint focus on new item - currentFocusedMenuItemIndex = index; - state.get(currentFocusedMenuItemIndex).setFocused(true); - reinstallCurrentTaskbarIcons(); - } - - /** - * Removes focus from any and task menu taskbar items - */ - private void removeFocusFromTaskbarMenuIcons() { - currentFocusedMenuItemIndex = -1; - - if (menuLabel == null) return; - - Stream.of(currentFrameMenuItems, currentMappedExeItems, - currentDefaultMenuItems) - .flatMap(Collection::stream) - .toList().forEach(icon -> icon.setFocused(false)); - - if (menuLabel.isVisible()) { - reinstallCurrentTaskbarIcons(); - } - } - - /** - * The logic for when the menu button is pressed. - */ - private void onMenuButtonClicked() { - Point menuButtonPointOnScreen = menuButton.getLocationOnScreen(); - Rectangle menuButtonBoundsOnScreen = new Rectangle( - (int) menuButtonPointOnScreen.getX(), - (int) menuButtonPointOnScreen.getY(), - menuButton.getWidth(), - menuButton.getHeight()); - - boolean mouseTriggered = GeometryUtil.pointInOrOnRectangle( - MouseInfo.getPointerInfo().getLocation(), menuButtonBoundsOnScreen); - - // if there's a focused item and the item was not a mouse click - if (currentFocusedMenuItemIndex != -1 && !mouseTriggered) { - ImmutableList.copyOf(Stream.of(currentFrameMenuItems, currentMappedExeItems, currentDefaultMenuItems) - .flatMap(Collection::stream) - .collect(Collectors.toList())) - .get(currentFocusedMenuItemIndex).runRunnable(); - return; - } - - if (menuLabel == null) { - generateConsoleMenu(); - } - - if (menuLabel.isVisible()) { - minimizeMenu(); - return; - } - - animateInMenuLabel(); - } - - /** - * The name of the animating thread for the console input and output fields. - */ - private static final String CONSOLE_FIELDS_ANIMATOR_THREAD_NAME = "Console Fields Animator"; - - /** - * The name of the thread for animating in the menu label. - */ - private static final String MINIMIZE_MENU_THREAD_NAME = "Minimize Console Menu Thread"; - - /** - * The increment in pixels for the menu label and fields for animations. - */ - private static final int menuAnimationIncrement = 8; - - /** - * The delay in ms for the menu and fields animations. - */ - private static final int menuAnimationDelayMs = 5; - - /** - * The x value the fields should be animated to when the menu label is animating in. - */ - private static final int fieldsEnterAnimateToX = TASKBAR_MENU_WIDTH + 2 + 15; - - /** - * Animates the menu label into the frame and animates the output area and input fields - * to smaller sizes to account for the space taken by the menu. - */ - private void animateInMenuLabel() { - CyderThreadRunner.submit(() -> { - menuLabel.setLocation(consoleMenuHiddenPoint); - menuLabel.setVisible(true); - int y = menuLabel.getY(); - - for (int i = (int) consoleMenuHiddenPoint.getX() - ; i < consoleMenuShowingPoint.getX() ; i += menuAnimationIncrement) { - menuLabel.setLocation(i, y); - ThreadUtil.sleep(menuAnimationDelayMs); - } - - menuLabel.setLocation(consoleMenuShowingPoint); - - revalidateInputAndOutputBounds(); - }, MINIMIZE_MENU_THREAD_NAME); - - CyderThreadRunner.submit(() -> { - int outputScrollY = outputScroll.getY(); - int outputScrollWidth = outputScroll.getWidth(); - int outputScrollHeight = outputScroll.getHeight(); - - int inputFieldY = inputField.getY(); - int inputFieldWidth = inputField.getWidth(); - int inputFieldHeight = inputField.getHeight(); - - for (int x = inputField.getX() ; x < fieldsEnterAnimateToX ; x += menuAnimationIncrement) { - outputScroll.setBounds(x, outputScrollY, outputScrollWidth, outputScrollHeight); - inputField.setBounds(x, inputFieldY, inputFieldWidth, inputFieldHeight); - - ThreadUtil.sleep(menuAnimationDelayMs); - } - - revalidateInputAndOutputBounds(); - }, CONSOLE_FIELDS_ANIMATOR_THREAD_NAME); - } - - /** - * The key adapter for the menu button to allow "focusing" taskbar items. - */ - private final KeyAdapter menuButtonKeyAdapter = new KeyAdapter() { - @Override - public void keyReleased(KeyEvent e) { - int code = e.getKeyCode(); - - if (KeyCodeUtil.downOrRight(code)) { - focusNextTaskbarMenuItem(); - } else if (KeyCodeUtil.upOrLeft(code)) { - focusPreviousTaskbarMenuItem(); - } - } - }; - - /** - * The current active frames to generate TaskbarIcons for the console's menu. - */ - private final ArrayList currentActiveFrames = new ArrayList<>(); - - /** - * The current taskbar menu frame items. - */ - private ImmutableList currentFrameMenuItems = ImmutableList.of(); - - /** - * The current taskbar menu mapped exe items. - */ - private ImmutableList currentMappedExeItems = ImmutableList.of(); - - /** - * The current taskbar default menu items. - */ - private ImmutableList currentDefaultMenuItems = ImmutableList.of(); - - /** - * Refreshes the taskbar icons based on the frames currently in the frame list. - */ - private synchronized void installMenuTaskbarIcons() { - lockMenuTaskbarInstallation(); - - boolean compactMode = UserDataManager.INSTANCE.compactTextMode(); - - UiUtil.setJTextPaneDocumentAlignment(menuPane, compactMode - ? UiUtil.JTextPaneAlignment.LEFT : UiUtil.JTextPaneAlignment.CENTER); - - ImmutableList frameMenuItems = getCurrentFrameTaskbarIcons(compactMode); - ImmutableList mappedExeItems = getMappedExeTaskbarIcons(compactMode); - ImmutableList defaultMenuItems = getDefaultTaskbarIcons(compactMode); - - if (!differentMenuState(frameMenuItems, mappedExeItems, defaultMenuItems)) { - unlockMenuTaskbarInstallation(); - return; - } - - currentFrameMenuItems = frameMenuItems; - currentMappedExeItems = mappedExeItems; - currentDefaultMenuItems = defaultMenuItems; - - reinstallCurrentTaskbarIcons(); - unlockMenuTaskbarInstallation(); - } - - /** - * The semaphore used to lock invocation of the {@link #installMenuTaskbarIcons()} method. - */ - private final Semaphore menuTaskbarLockingSemaphore = new Semaphore(1); - - /** - * Locks invocation of the {@link #installMenuTaskbarIcons()} method. - */ - private void lockMenuTaskbarInstallation() { - try { - menuTaskbarLockingSemaphore.acquire(); - } catch (InterruptedException e) { - ExceptionHandler.handle(e); - } - } - - /** - * Unlocks invocation of the {@link #installMenuTaskbarIcons()} method. - */ - private void unlockMenuTaskbarInstallation() { - menuTaskbarLockingSemaphore.release(); - } - - /** - * Returns the current frame taskbar icon items. - * - * @param compactMode whether the menu should be laid out in compact mode - * @return the current frame taskbar icon items - */ - private synchronized ImmutableList getCurrentFrameTaskbarIcons(boolean compactMode) { - ArrayList ret = new ArrayList<>(); - - if (!currentActiveFrames.isEmpty()) { - Lists.reverse(currentActiveFrames).forEach(currentFrame -> { - TaskbarIcon.Builder builder = new TaskbarIcon.Builder(currentFrame.getTitle()) - .setCompact(compactMode) - .setFocused(false) - .setBorderColor(currentFrame.getTaskbarIconBorderColor()) - .setRunnable(UiUtil.generateCommonFrameTaskbarIconRunnable(currentFrame)); - - currentFrame.getCustomTaskbarIcon().ifPresent(builder::setCustomIcon); - ret.add(builder.build()); - }); - } - - return ImmutableList.copyOf(ret); - } - - /** - * Returns the mapped exe taskbar icon items. - * - * @param compactMode whether the menu should be laid out in compact mode - * @return the current mapped exe taskbar icon items - */ - private ImmutableList getMappedExeTaskbarIcons(boolean compactMode) { - Collection exes = UserDataManager.INSTANCE.getMappedExecutables().getExecutables(); - ArrayList ret = new ArrayList<>(); - - if (!exes.isEmpty()) { - exes.forEach(exe -> { - Runnable runnable = () -> { - FileUtil.openResource(exe.getFilepath(), true); - exe.displayInvokedNotification(); - }; - - ret.add(new TaskbarIcon.Builder(exe.getName()) - .setFocused(false) - .setCompact(compactMode) - .setRunnable(runnable) - .setBorderColor(CyderColors.vanilla) - .build()); - }); - } - - return ImmutableList.copyOf(ret); - } - - /** - * Whether the last send action for the user editor frame was {@link CyderFrame#toFront()}. - */ - private final AtomicBoolean sentToFront = new AtomicBoolean(); - - /** - * The action for when the preferences default taskbar icon is clicked. - */ - private void onPrefsMenuItemClicked() { - if (UserEditor.isOpen()) { - if (UserEditor.isMinimized()) { - UserEditor.restore(); - } else { - if (sentToFront.get()) { - UserEditor.getEditUserFrame().toBack(); - sentToFront.set(false); - } else { - UserEditor.getEditUserFrame().toFront(); - sentToFront.set(true); - } - } - } else { - UserEditor.showGui(); - } - - revalidateConsoleTaskbarMenu(); - } - - /** - * The tooltip and label text for the preferences default taskbar icon. - */ - private static final String PREFERENCES = "Preferences"; - - /** - * The tooltip and label text for the logout default taskbar icon. - */ - private static final String LOGOUT = "Logout"; - - /** - * The default compact taskbar icons. - */ - private final ImmutableList compactDefaultTaskbarIcons = ImmutableList.of( - new TaskbarIcon.Builder(PREFERENCES) - .setFocused(false) - .setCompact(true) - .setRunnable(this::onPrefsMenuItemClicked) - .setBorderColor(CyderColors.taskbarDefaultColor) - .build(), - new TaskbarIcon.Builder(LOGOUT) - .setFocused(false) - .setCompact(true) - .setRunnable(this::logoutCurrentUserAndShowLoginFrame) - .setBorderColor(CyderColors.taskbarDefaultColor) - .build() - ); - - /** - * The default non-compact taskbar icons. - */ - private final ImmutableList nonCompactDefaultTaskbarIcons = constructNonCompactDefaultTaskbarIcons(); - - /** - * Constructs and returns the non-compact, default taskbar icons. - * - * @return the non-compact, default taskbar icons - */ - private ImmutableList constructNonCompactDefaultTaskbarIcons() { - try { - TaskbarIcon prefsTaskbarIcon = new TaskbarIcon.Builder(PREFERENCES) - .setFocused(false) - .setCompact(false) - .setRunnable(this::onPrefsMenuItemClicked) - .setBorderColor(CyderColors.taskbarDefaultColor) - .build(); - - TaskbarIcon logoutTaskbarIcon = new TaskbarIcon.Builder(LOGOUT) - .setFocused(false) - .setCompact(false) - .setRunnable(this::logoutCurrentUserAndShowLoginFrame) - .setBorderColor(CyderColors.taskbarDefaultColor) - .build(); - - return ImmutableList.of(prefsTaskbarIcon, logoutTaskbarIcon); - } catch (Exception e) { - throw new FatalException(e.getMessage()); - } - } - - /** - * Returns the default taskbar icon items. - * - * @param compactMode whether the menu should be laid out in compact mode - * @return the default taskbar icon items - */ - private ImmutableList getDefaultTaskbarIcons(boolean compactMode) { - return compactMode ? compactDefaultTaskbarIcons : nonCompactDefaultTaskbarIcons; - } - - /** - * Returns whether the provided new taskbar icons comprehensive list is different from the previous menu state. - * - * @param frameMenuItems the new proposed taskbar frame items - * @param mappedExeItems the new proposed taskbar mapped exe items - * @param defaultMenuItems the new proposed taskbar default menu items - * @return whether the provided new taskbar icons comprehensive list is different from the previous menu state - */ - private synchronized boolean differentMenuState( - ImmutableList frameMenuItems, ImmutableList mappedExeItems, - ImmutableList defaultMenuItems) { - - ImmutableList newState = ImmutableList.copyOf( - Stream.of(frameMenuItems, mappedExeItems, defaultMenuItems) - .flatMap(Collection::stream) - .collect(Collectors.toList())); - - ImmutableList previousState = ImmutableList.copyOf( - Stream.of(currentFrameMenuItems, currentMappedExeItems, currentDefaultMenuItems) - .flatMap(Collection::stream) - .collect(Collectors.toList())); - - if (previousState.size() == 0) return true; - - if (newState.size() != previousState.size()) return true; - - for (int i = 0 ; i < newState.size() ; i++) { - if (!newState.get(i).equals(previousState.get(i))) { - return true; - } - } - - return false; - } - - /** - * The listener used when input is first handled. - */ - private final ActionListener inputFieldActionListener = new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - String input = String.valueOf(inputField.getPassword()) - .substring(consoleBashString.length()) - .trim().replace(consoleBashString, ""); - - if (StringUtil.isNullOrEmpty(input)) { - resetInputField(); - return; - } - - if (shouldAddToCommandList(input)) commandList.add(input); - commandIndex = commandList.size(); - baseInputHandler.handle(input); - - resetInputField(); - } - }; - - /** - * Returns whether the provided user input should be added to the command list. - * - * @param input the user input to add to the command list if the proper conditions are met - * @return whether the provided user input should be added to the command list - */ - @ForReadability - private boolean shouldAddToCommandList(String input) { - return commandList.isEmpty() || !commandList.get(commandList.size() - 1).equals(input); - } - - /** - * Sets the input field text to the console bash string and the caret to the end. - */ - @ForReadability - private void resetInputField() { - inputField.setText(consoleBashString); - inputField.setCaretPosition(consoleBashString.length()); - } - - /** - * The increment in degrees for a barrel roll. - */ - private static final int DEGREE_INCREMENT = 2; - - /** - * The delay between barrel roll increments. - */ - private static final int BARREL_ROLL_DELAY = 2; - - /** - * The thread name for the barrel roll animator. - */ - private static final String BARREL_ROLL_THREAD_NAME = "Console Barrel Roll Thread"; - - /** - * The object to synchronize on when performing a console barrel roll. - */ - private static final Object barrelRollLockingObject = new Object(); - - /** - * Performs a barrel roll on the console frame. - */ - public void barrelRoll() { - synchronized (barrelRollLockingObject) { - CyderThreadRunner.submit(() -> { - BufferedImage currentBi = getCurrentBackground().generateBufferedImage(); - BufferedImage masterImage = ImageUtil.resizeImage(currentBi, currentBi.getType(), - consoleCyderFrame.getWidth(), consoleCyderFrame.getHeight()); - for (int i = 0 ; i <= AngleUtil.THREE_SIXTY_DEGREES ; i += DEGREE_INCREMENT) { - BufferedImage rotated = ImageUtil.rotateImage(masterImage, i); - getConsoleCyderFrameContentPane().setIcon(new ImageIcon(rotated)); - ThreadUtil.sleep(BARREL_ROLL_DELAY); - } - - getConsoleCyderFrameContentPane().setIcon(ImageUtil.toImageIcon(masterImage)); - }, BARREL_ROLL_THREAD_NAME); - } - } - - /** - * Returns the height of the console menu based on the current frame height. - * - * @return the height of the console menu based on the current frame height - */ - private int calculateMenuHeight() { - return consoleCyderFrame.getHeight() - CyderDragLabel.DEFAULT_HEIGHT - CyderFrame.BORDER_LEN; - } - - /** - * The point the console menu is set at and animated to when visible. - */ - private static final Point consoleMenuShowingPoint = new Point(2, CyderDragLabel.DEFAULT_HEIGHT - 2); - - /** - * The point the console menu is set at before animating to the visible point. - */ - private static final Point consoleMenuHiddenPoint = new Point(-150, CyderDragLabel.DEFAULT_HEIGHT - 2); - - /** - * The padding between the menu label and the menu scroll panel on the horizontal axis. - */ - private static final int menuScrollHorizontalPadding = 5; - - /** - * The padding between the menu label and the menu scroll panel on the vertical axis. - */ - private static final int menuScrollVerticalPadding = 5; - - /** - * The output pane for the console taskbar menu. - */ - private CyderOutputPane menuPaneOutputPane; - - /** - * Revalidates the taskbar menu bounds and re-installs the icons. - */ - private void generateConsoleMenu() { - if (menuLabel != null) { - menuLabel.setVisible(false); - } - - menuLabel = new JLabel(); - revalidateConsoleMenuSize(); - menuLabel.setOpaque(true); - menuLabel.setBackground(CyderColors.getGuiThemeColor()); - menuLabel.setFocusable(false); - menuLabel.setVisible(false); - menuLabel.setBorder(new LineBorder(Color.black, 5)); - consoleCyderFrame.getIconPane().add(menuLabel, JLayeredPane.MODAL_LAYER); - - menuPane = new JTextPane(); - menuPane.setEditable(false); - menuPane.setAutoscrolls(false); - menuPane.setFocusable(false); - menuPane.setOpaque(false); - menuPane.setBackground(CyderColors.getGuiThemeColor()); - - menuPaneOutputPane = new CyderOutputPane(menuPane); - - menuScroll = new CyderScrollPane(menuPane); - menuScroll.setThumbSize(5); - menuScroll.getViewport().setOpaque(false); - menuScroll.setFocusable(false); - menuScroll.setOpaque(false); - menuScroll.setThumbColor(CyderColors.regularPink); - menuScroll.setBackground(CyderColors.getGuiThemeColor()); - menuScroll.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); - menuScroll.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS); - - int width = menuLabel.getWidth() - 2 * menuScrollHorizontalPadding; - int height = menuLabel.getHeight() - 2 * menuScrollVerticalPadding; - menuScroll.setBounds(menuScrollHorizontalPadding, menuScrollVerticalPadding, width, height); - menuLabel.add(menuScroll); - - installMenuTaskbarIcons(); - } - - /** - * Revalidates the console menu size. - */ - private void revalidateConsoleMenuSize() { - if (menuLabel != null) menuLabel.setSize(TASKBAR_MENU_WIDTH, calculateMenuHeight()); - } - - /** - * Clears the taskbar menu pane and re-prints the current taskbar icons from the three lists. - */ - private void reinstallCurrentTaskbarIcons() { - boolean compactMode = UserDataManager.INSTANCE.compactTextMode(); - - menuPaneOutputPane.printlnMenuSeparator(); - - menuPane.setText(""); - - menuPaneOutputPane.getStringUtil().newline(!compactMode); - - currentFrameMenuItems.forEach(frameItem -> { - frameItem.generateTaskbarIcon(); - menuPaneOutputPane.getStringUtil().printlnComponent(frameItem.getTaskbarIcon()); - menuPaneOutputPane.getStringUtil().newline(!compactMode); - }); - - if (currentFrameMenuItems.size() > 0 && !compactMode) { - menuPaneOutputPane.printlnMenuSeparator(); - } - - currentMappedExeItems.forEach(mappedExe -> { - mappedExe.generateTaskbarIcon(); - menuPaneOutputPane.getStringUtil().printlnComponent(mappedExe.getTaskbarIcon()); - menuPaneOutputPane.getStringUtil().newline(!compactMode); - }); - - if (currentMappedExeItems.size() > 0 && !compactMode) { - menuPaneOutputPane.printlnMenuSeparator(); - } - - currentDefaultMenuItems.forEach(taskbarIcon -> { - taskbarIcon.generateTaskbarIcon(); - menuPaneOutputPane.getStringUtil().printlnComponent(taskbarIcon.getTaskbarIcon()); - menuPaneOutputPane.getStringUtil().newline(!compactMode); - }); - - menuPane.setCaretPosition(0); - } - - /** - * Removes the provided frame reference from the taskbar frame list. - * - * @param frame the frame reference to remove from the taskbar frame list - */ - public void removeTaskbarIcon(CyderFrame frame) { - Preconditions.checkNotNull(frame); - - if (currentActiveFrames.contains(frame)) { - currentActiveFrames.remove(frame); - revalidateConsoleTaskbarMenu(); - } - } - - /** - * Adds the provided frame reference to the taskbar frame list and revalidates the taskbar. - * - * @param associatedFrame the frame reference to add to the taskbar list - */ - public void addTaskbarIcon(CyderFrame associatedFrame) { - Preconditions.checkNotNull(associatedFrame); - - if (isClosed() || frameTaskbarExceptions.containsValue(associatedFrame)) return; - - if (!currentActiveFrames.contains(associatedFrame)) { - currentActiveFrames.add(associatedFrame); - revalidateConsoleTaskbarMenu(); - } - } - - /** - * The thread name for the input and output fields out animator thread. - */ - private static final String CONSOLE_FIELDS_OUT_ANIMATOR_THREAD_NAME = "Console Field Out Animator"; - - /** - * The animate to x value when animating the fields left with the menu minimize animation. - */ - private static final int minFieldAnimateToX = 15; - - /** - * Animates the taskbar menu away. - */ - private void minimizeMenu() { - Preconditions.checkState(menuLabel.isVisible()); - - CyderThreadRunner.submit(() -> { - int outputScrollY = outputScroll.getY(); - int outputScrollWidth = outputScroll.getWidth(); - int outputScrollHeight = outputScroll.getHeight(); - - int inputFieldY = inputField.getY(); - int inputFieldWidth = inputField.getWidth(); - int inputFieldHeight = inputField.getHeight(); - - for (int i = inputField.getX() ; i > minFieldAnimateToX ; i -= menuAnimationIncrement) { - outputScroll.setBounds(i, outputScrollY, outputScrollWidth + 1, outputScrollHeight); - inputField.setBounds(i, inputFieldY, inputFieldWidth + 1, inputFieldHeight); - - ThreadUtil.sleep(menuAnimationDelayMs); - } - - revalidateInputAndOutputBounds(true); - }, CONSOLE_FIELDS_OUT_ANIMATOR_THREAD_NAME); - - CyderThreadRunner.submit(() -> { - menuLabel.setLocation(consoleMenuShowingPoint); - int y = menuLabel.getY(); - - for (int i = 0 ; i > consoleMenuHiddenPoint.getX() ; i -= menuAnimationIncrement) { - menuLabel.setLocation(i, y); - ThreadUtil.sleep(menuAnimationDelayMs); - } - - menuLabel.setLocation(consoleMenuHiddenPoint); - menuLabel.setVisible(false); - - revalidateInputAndOutputBounds(); - }, MINIMIZE_MENU_THREAD_NAME); - } - - /** - * The input field key adapter used for thread escaping and console rotation. - */ - private final KeyAdapter inputFieldKeyAdapter = new KeyAdapter() { - @Override - public void keyPressed(KeyEvent e) { - if (KeyCodeUtil.isControlC(e)) baseInputHandler.escapeThreads(); - - int caretPosition = outputArea.getCaretPosition(); - - if (KeyCodeUtil.isControlAltDown(e)) { - setConsoleDirection(Direction.BOTTOM); - outputArea.setCaretPosition(caretPosition); - } else if (KeyCodeUtil.isControlAltRight(e)) { - setConsoleDirection(Direction.RIGHT); - outputArea.setCaretPosition(caretPosition); - } else if (KeyCodeUtil.isControlAltUp(e)) { - setConsoleDirection(Direction.TOP); - outputArea.setCaretPosition(caretPosition); - } else if (KeyCodeUtil.isControlAltLeft(e)) { - setConsoleDirection(Direction.LEFT); - outputArea.setCaretPosition(caretPosition); - } - } - - @Override - public void keyTyped(KeyEvent e) { - if (isBackspace(e) && wouldRemoveBashStringContents()) { - e.consume(); - inputField.setText(consoleBashString); - return; - } - - if (inputField.getCaretPosition() < consoleBashString.toCharArray().length) { - ensureFullBashStringPresent(); - setInputFieldCaretPositionToEnd(); - } else { - ensureStartsWithBashString(); - } - - super.keyTyped(e); - ensureFullBashStringPresent(); - } - - @ForReadability - private boolean wouldRemoveBashStringContents() { - return String.valueOf(inputField.getPassword()).trim().equals(consoleBashString.trim()); - } - - @ForReadability - private boolean isBackspace(KeyEvent e) { - return e.getKeyChar() == KeyEvent.VK_BACK_SPACE; - } - - @ForReadability - private void ensureStartsWithBashString() { - String text = new String(inputField.getPassword()); - if (!text.startsWith(consoleBashString)) { - inputField.setText(consoleBashString + text.replace(consoleBashString, "")); - setCaretPositionAtBashStringLength(); - } - } - - @ForReadability - private void ensureFullBashStringPresent() { - if (inputField.getPassword().length < consoleBashString.length()) { - inputField.setText(consoleBashString); - setCaretPositionAtBashStringLength(); - } - } - - @ForReadability - private void setCaretPositionAtBashStringLength() { - inputField.setCaretPosition(consoleBashString.length()); - } - }; - - /** - * Sets the caret position of the input field to the end. - */ - private void setInputFieldCaretPositionToEnd() { - inputField.setCaretPosition(inputField.getPassword().length); - } - - /** - * The key listener for input field to control command scrolling. - */ - private final KeyListener commandScrolling = new KeyAdapter() { - @Override - public void keyPressed(KeyEvent e) { - if (!controlAltNotPressed(e)) return; - - if (KeyCodeUtil.up(e.getKeyCode())) { - attemptScrollUp(); - } else if (KeyCodeUtil.down(e.getKeyCode())) { - attemptScrollDown(); - } - - checkForSpecialFunctionKeys(e); - } - - @ForReadability - private void attemptScrollUp() { - if (commandIndex - 1 >= 0) { - commandIndex -= 1; - inputField.setText(consoleBashString + commandList.get(commandIndex) - .replace(consoleBashString, "")); - } - } - - @ForReadability - private void attemptScrollDown() { - if (commandIndex + 1 < commandList.size()) { - commandIndex += 1; - inputField.setText(consoleBashString + commandList.get(commandIndex) - .replace(consoleBashString, "")); - } else if (commandIndex + 1 == commandList.size()) { - commandIndex += 1; - inputField.setText(consoleBashString); - } - } - - @ForReadability - private void checkForSpecialFunctionKeys(KeyEvent e) { - int code = e.getKeyCode(); - - IntStream.range(KeyEvent.VK_F13, KeyEvent.VK_F24 + 1).forEach(specialCode -> { - if (code == specialCode) { - int functionKey = (code - KeyEvent.VK_F13 + SPECIAL_FUNCTION_KEY_CODE_OFFSET); - baseInputHandler.println("Interesting F" + functionKey + " key"); - - if (functionKey == F_17_KEY_CODE) { - GeneralAudioPlayer.playAudio(F_17_MUSIC_FILE); - } - } - }); - } - - @ForReadability - private boolean controlAltNotPressed(KeyEvent e) { - return (e.getModifiersEx() & InputEvent.CTRL_DOWN_MASK) == 0 - && ((e.getModifiersEx() & InputEvent.ALT_DOWN_MASK) == 0); - } - }; - - /** - * The magic number that denotes the mouse wheel is being scrolled up. - */ - private static final int WHEEL_UP = -1; - - /** - * The MouseWheelListener used for increasing/decreasing the - * font size for input field and output area. - */ - @SuppressWarnings("MagicConstant") /* Font metric is always checked */ - private final MouseWheelListener fontSizerListener = e -> { - if (!e.isControlDown()) { - outputArea.getParent().dispatchEvent(e); - inputField.getParent().dispatchEvent(e); - return; - } - - int fontSize = UserDataManager.INSTANCE.getFontSize(); - if (e.getWheelRotation() == WHEEL_UP) { - fontSize++; - } else { - fontSize--; - } - - int maxFontSize = Props.maxFontSize.getValue(); - int minFontSize = Props.minFontSize.getValue(); - if (fontSize > maxFontSize || fontSize < minFontSize) return; - - String fontName = UserDataManager.INSTANCE.getFontName(); - int fontMetric = FontUtil.getFontMetricFromProps(); - - Font newFont = new Font(fontName, fontMetric, fontSize); - if (FontUtil.isValidFontMetric(fontMetric)) { - inputField.setFont(newFont); - outputArea.setFont(newFont); - - UserDataManager.INSTANCE.setFontSize(fontSize); - baseInputHandler.refreshPrintedLabels(); - } - }; - - /** - * Returns the uuid of the current user. - * - * @return the uuid of the current user - */ - public String getUuid() { - return uuid; - } - - /** - * Get the desired user font in combination with the set font metric and font size. - * - * @return the font to use for the input and output areas - */ - @SuppressWarnings("MagicConstant") /* Font metric is always checked before use */ - public Font generateUserFont() { - String fontName = UserDataManager.INSTANCE.getFontName(); - int fontMetric = FontUtil.getFontMetricFromProps(); - int fontSize = UserDataManager.INSTANCE.getFontSize(); - - if (!FontUtil.isValidFontMetric(fontMetric)) fontMetric = Font.BOLD; - - return new Font(fontName, fontMetric, fontSize); - } - - // ---------------- - // Background logic - // ---------------- - - /** - * Initializes the backgrounds associated with the current user. - * Also attempts to find the background index of the Console current background if it exists. - */ - public void reloadBackgrounds() { - try { - ArrayList backgroundFiles = new ArrayList<>(); - - File[] backgroundFilesArr = Dynamic.buildDynamic( - Dynamic.USERS.getFileName(), uuid, - UserFile.BACKGROUNDS.getName()).listFiles(); - if (backgroundFilesArr != null && backgroundFilesArr.length > 0) { - Arrays.stream(backgroundFilesArr) - .filter(FileUtil::isSupportedImageExtension) - .forEach(backgroundFiles::add); - } - - if (backgroundFiles.isEmpty()) { - Logger.log(LogTag.SYSTEM_IO, "No backgrounds found for user " - + uuid + ", creating default background"); - backgroundFiles.add(UserUtil.createDefaultBackground(uuid)); - } - - backgrounds.clear(); - - backgroundFiles.forEach(backgroundFile -> backgrounds.add(new ConsoleBackground(backgroundFile))); - revalidateBackgroundIndex(); - } catch (Exception ex) { - ExceptionHandler.handle(ex); - } - } - - /** - * Reloads the background files and returns the resulting list of found backgrounds. - * - * @return list of found backgrounds - */ - public ArrayList reloadAndGetBackgrounds() { - reloadBackgrounds(); - return backgrounds; - } - - /** - * Revalidates the index that the current background is at after - * refreshing due to a possible background list change. - */ - private void revalidateBackgroundIndex() { - if (consoleCyderFrame == null) { - backgroundIndex = 0; - return; - } - - JLabel contentLabel = getConsoleCyderFrameContentPane(); - if (contentLabel == null) { - backgroundIndex = 0; - return; - } - - String filename = contentLabel.getToolTipText(); - if (StringUtil.isNullOrEmpty(filename)) { - backgroundIndex = 0; - return; - } - - for (int i = 0 ; i < backgrounds.size() ; i++) { - if (FileUtil.getFilename(backgrounds.get(i).getReferenceFile()).equals(filename)) { - backgroundIndex = i; - return; - } - } - - backgroundIndex = 0; - } - - /** - * Sets the background to the provided file in the user's backgrounds directory provided it exists. - * - * @param backgroundFile the background file to set the console to - */ - public void setBackgroundFile(File backgroundFile) { - Preconditions.checkNotNull(backgroundFile); - Preconditions.checkArgument(backgroundFile.exists()); - - setBackgroundFile(backgroundFile, false); - } - - /** - * Sets the background to the provided file in the user's backgrounds directory provided it exists. - * - * @param backgroundFile the background file to set the console to - * @param maintainSizeAndCenter whether to maintain the current console frame size and center - */ - public void setBackgroundFile(File backgroundFile, boolean maintainSizeAndCenter) { - Preconditions.checkNotNull(backgroundFile); - Preconditions.checkArgument(backgroundFile.exists()); - - reloadBackgrounds(); - - for (int i = 0 ; i < backgrounds.size() ; i++) { - if (backgrounds.get(i).getReferenceFile().getAbsolutePath() - .equals(backgroundFile.getAbsolutePath())) { - setBackgroundIndex(i, maintainSizeAndCenter); - return; - } - } - - throw new IllegalArgumentException("Provided file not found in user's backgrounds directory: " - + backgroundFile.getAbsolutePath()); - } - - /** - * Simply sets the background to the provided icon without having a reference file. - * Please ensure the icon size is the same as the current background's. - * - * @param icon the icon to set to the background of the console - */ - public void setBackground(ImageIcon icon) { - Preconditions.checkNotNull(icon); - Preconditions.checkArgument(icon.getIconWidth() == consoleCyderFrame.getWidth()); - Preconditions.checkArgument(icon.getIconHeight() == consoleCyderFrame.getHeight()); - - consoleCyderFrame.setBackground(icon); - } - - /** - * Sets the background index to the provided index - * if valid and switches to that background. - * - * @param index the index to switch the console background to - */ - @SuppressWarnings("unused") - private void setBackgroundIndex(int index) { - setBackgroundIndex(index, false); - } - - /** - * Sets the background index to the provided index - * if valid and switches to that background. - * - * @param index the index to switch the console background to - * @param maintainSizeAndCenter whether to maintain the current console frame size and center - */ - private void setBackgroundIndex(int index, boolean maintainSizeAndCenter) { - reloadBackgrounds(); - - if (index < 0 || index > backgrounds.size() - 1) return; - - Dimension originalSize = consoleCyderFrame.getSize(); - Point center = consoleCyderFrame.getCenterPointOnScreen(); - - backgroundIndex = index; - - ImageIcon imageIcon = switch (consoleDir) { - case LEFT -> new ImageIcon(ImageUtil.rotateImage( - backgrounds.get(backgroundIndex).generateBufferedImage(), -AngleUtil.NINETY_DEGREES)); - case RIGHT -> new ImageIcon(ImageUtil.rotateImage( - backgrounds.get(backgroundIndex).generateBufferedImage(), AngleUtil.NINETY_DEGREES)); - case TOP -> getCurrentBackground().generateImageIcon(); - case BOTTOM -> new ImageIcon(ImageUtil.rotateImage( - backgrounds.get(backgroundIndex).generateBufferedImage(), AngleUtil.ONE_EIGHTY_DEGREES)); - }; - - consoleCyderFrame.setBackground(imageIcon); - - if (maintainSizeAndCenter) { - consoleCyderFrame.setSize(originalSize); - consoleCyderFrame.setCenterPoint(center); - } else { - consoleCyderFrame.setSize(imageIcon.getIconWidth(), imageIcon.getIconHeight()); - consoleCyderFrame.setLocation((int) (center.getX() - (imageIcon.getIconWidth()) / 2), - (int) (center.getY() - (imageIcon.getIconHeight()) / 2)); - } - - // Tooltip based on image name - getConsoleCyderFrameContentPane().setToolTipText( - FileUtil.getFilename(getCurrentBackground().getReferenceFile().getName())); - - revalidateInputAndOutputBounds(); - inputField.requestFocus(); - revalidateConsoleTaskbarMenu(); - } - - /** - * Returns the current background. - * - * @return the current background - */ - public ConsoleBackground getCurrentBackground() { - return backgrounds.get(backgroundIndex); - } - - /** - * Whether the background switching is locked meaning an animation is currently underway. - */ - private final AtomicBoolean backgroundSwitchingLocked = new AtomicBoolean(false); - - /** - * The name of the thread which animates the background switch. - */ - private static final String CONSOLE_BACKGROUND_SWITCHER_THREAD_NAME = "Console Background Switcher"; - - /** - * Switches backgrounds to the next background in the list via a sliding animation. - * The Console will remain in fullscreen mode if in fullscreen mode as well as maintain - * whatever size it was at before a background switch was requested. - */ - @SuppressWarnings("UnnecessaryDefault") - private void attemptToSwitchBackground() { - if (backgroundSwitchingLocked.get()) return; - if (backgrounds.size() == 1) { - consoleCyderFrame.notify(onlyOneBackgroundNotificationBuilder); - return; - } - backgroundSwitchingLocked.set(true); - - ImageIcon nextBackground = (backgroundIndex + 1 == backgrounds.size() - ? backgrounds.get(0).generateImageIcon() - : backgrounds.get(backgroundIndex + 1).generateImageIcon()); - - backgroundIndex = backgroundIndex + 1 == backgrounds.size() ? 0 : backgroundIndex + 1; - - int width = nextBackground.getIconWidth(); - int height = nextBackground.getIconHeight(); - - if (isFullscreen()) { - width = (int) consoleCyderFrame.getMonitorBounds().getWidth(); - height = (int) consoleCyderFrame.getMonitorBounds().getHeight(); - nextBackground = ImageUtil.resizeImage(nextBackground, width, height); - } else if (consoleDir == Direction.LEFT) { - width = nextBackground.getIconHeight(); - height = nextBackground.getIconWidth(); - nextBackground = ImageUtil.rotateImage(nextBackground, -AngleUtil.NINETY_DEGREES); - } else if (consoleDir == Direction.RIGHT) { - width = nextBackground.getIconHeight(); - height = nextBackground.getIconWidth(); - nextBackground = ImageUtil.rotateImage(nextBackground, AngleUtil.NINETY_DEGREES); - } else if (consoleDir == Direction.BOTTOM) { - width = nextBackground.getIconWidth(); - height = nextBackground.getIconHeight(); - nextBackground = ImageUtil.rotateImage(nextBackground, AngleUtil.ONE_EIGHTY_DEGREES); - } - - JLabel contentPane = getConsoleCyderFrameContentPane(); - contentPane.setToolTipText(FileUtil.getFilename(getCurrentBackground().getReferenceFile().getName())); - - ImageIcon nextBackFinal = nextBackground; - ImageIcon oldBack = ImageUtil.resizeImage((ImageIcon) contentPane.getIcon(), width, height); - - Point originalCenter = consoleCyderFrame.getCenterPointOnScreen(); - consoleCyderFrame.setSize(width, height); - - // Bump frame into bounds if new size pushed part out of bounds - UiUtil.requestFramePosition(new Point((int) originalCenter.getX() - width / 2, - (int) originalCenter.getY() - height / 2), consoleCyderFrame); - - ImageIcon combinedIcon = switch (lastSlideDirection) { - case LEFT -> ImageUtil.combineImages(oldBack, nextBackground, Direction.BOTTOM); - case RIGHT -> ImageUtil.combineImages(oldBack, nextBackground, Direction.TOP); - case TOP -> ImageUtil.combineImages(oldBack, nextBackground, Direction.LEFT); - case BOTTOM -> ImageUtil.combineImages(oldBack, nextBackground, Direction.RIGHT); - default -> throw new IllegalStateException("Invalid last slide direction: " + lastSlideDirection); - }; - - // Revalidate bounds for icon label and icon pane - consoleCyderFrame.refreshBackground(); - - // Determine this slide direction - Direction nextSlideDirection; - switch (lastSlideDirection) { - case LEFT -> nextSlideDirection = Direction.TOP; - case RIGHT -> nextSlideDirection = Direction.BOTTOM; - case TOP -> nextSlideDirection = Direction.RIGHT; - case BOTTOM -> nextSlideDirection = Direction.LEFT; - default -> throw new IllegalStateException("Invalid last slide direction: " + lastSlideDirection); - } - - // Set dimensions - switch (nextSlideDirection) { - case TOP -> contentPane.setBounds(CyderFrame.FRAME_RESIZING_LEN, CyderFrame.FRAME_RESIZING_LEN, - combinedIcon.getIconWidth(), combinedIcon.getIconHeight()); - case BOTTOM -> contentPane.setBounds(CyderFrame.FRAME_RESIZING_LEN, -combinedIcon.getIconHeight() / 2, - combinedIcon.getIconWidth(), combinedIcon.getIconHeight()); - case RIGHT -> contentPane.setBounds(-combinedIcon.getIconWidth() / 2, CyderFrame.FRAME_RESIZING_LEN, - combinedIcon.getIconWidth(), combinedIcon.getIconHeight()); - case LEFT -> contentPane.setBounds(combinedIcon.getIconWidth() / 2, CyderFrame.FRAME_RESIZING_LEN, - combinedIcon.getIconWidth(), combinedIcon.getIconHeight()); - } - - // set to combined icon - contentPane.setIcon(combinedIcon); - - boolean wasDraggable = consoleCyderFrame.isDraggingEnabled(); - consoleCyderFrame.disableDragging(); - - boolean outputAreaWasFocusable = outputArea.isFocusable(); - outputArea.setFocusable(false); - - Executors.newSingleThreadExecutor(new CyderThreadFactory(CONSOLE_BACKGROUND_SWITCHER_THREAD_NAME)) - .submit(() -> { - int timeout = - isFullscreen() ? fullscreenBackgroundAnimationTimeout : defaultBackgroundAnimationTimeout; - int increment = isFullscreen() ? fullscreenBackgroundAnimationIncrement : - defaultBackgroundAnimationIncrement; - - switch (nextSlideDirection) { - case TOP -> { - for (int i = 0 ; i >= -consoleCyderFrame.getHeight() ; i -= increment) { - ThreadUtil.sleep(timeout); - contentPane.setLocation(consoleCyderFrame.getContentPane().getX(), i); - } - lastSlideDirection = nextSlideDirection; - } - case BOTTOM -> { - for (int i = -consoleCyderFrame.getHeight() ; i <= 0 ; i += increment) { - ThreadUtil.sleep(timeout); - contentPane.setLocation(consoleCyderFrame.getContentPane().getX(), i); - } - lastSlideDirection = nextSlideDirection; - } - case RIGHT -> { - for (int i = -consoleCyderFrame.getWidth() ; i <= 0 ; i += increment) { - ThreadUtil.sleep(timeout); - contentPane.setLocation(i, consoleCyderFrame.getContentPane().getY()); - } - lastSlideDirection = nextSlideDirection; - } - case LEFT -> { - for (int i = 0 ; i >= -consoleCyderFrame.getWidth() ; i -= increment) { - ThreadUtil.sleep(timeout); - contentPane.setLocation(i, consoleCyderFrame.getContentPane().getY()); - } - lastSlideDirection = nextSlideDirection; - } - } - - consoleCyderFrame.setBackground(nextBackFinal); - contentPane.setIcon(nextBackFinal); - - consoleCyderFrame.refreshBackground(); - consoleCyderFrame.getContentPane().revalidate(); - - refreshConsoleMaxSize(); - - consoleCyderFrame.setDraggingEnabled(wasDraggable); - - revalidateMaintainFullscreenOrDirection(); - - defaultFocusOwner.requestFocus(); - - outputArea.setFocusable(outputAreaWasFocusable); - - backgroundSwitchingLocked.set(false); - }); - } - - /** - * Whether chams (chameleon mode) is currently active. - */ - private final AtomicBoolean chamsActive = new AtomicBoolean(); - - /** - * Sets the console orientation and refreshes the frame. - * This action exits fullscreen mode if active. - * - * @param consoleDirection the direction the background is to face - */ - private void setConsoleDirection(Direction consoleDirection) { - boolean maintainConsoleSize = consoleDirection != consoleDir; - lastConsoleDir = consoleDir; - consoleDir = consoleDirection; - - // If chams, reset to what background should be first - if (chamsActive.get()) { - revalidate(true, false, true); - chamsActive.set(false); - return; - } - - UserDataManager.INSTANCE.setFullscreen(false); - revalidate(true, false, maintainConsoleSize); - revalidateConsoleMenuSize(); - saveScreenStat(); - } - - /** - * Returns the current console direction. - * - * @return the current console direction - */ - private Direction getConsoleDirection() { - return consoleDir; - } - - /** - * Refreshes the console, bounds, orientation, and fullscreen mode. - * - * @param fullscreen whether to set the frame to fullscreen mode - */ - public void setFullscreen(boolean fullscreen) { - try { - UserDataManager.INSTANCE.setFullscreen(fullscreen); - - if (fullscreen) { - consoleDir = Direction.TOP; - consoleCyderFrame.setShouldAnimateOpacity(false); - revalidate(false, true); - } else { - consoleCyderFrame.setShouldAnimateOpacity(true); - revalidate(true, false); - } - } catch (Exception e) { - ExceptionHandler.handle(e); - } - } - - @ForReadability - private boolean isFullscreen() { - return UserDataManager.INSTANCE.isFullscreen(); - } - - /** - * Returns the input handler associated with the Console. - * - * @return the input handler associated with the Console - */ - public BaseInputHandler getInputHandler() { - return baseInputHandler; - } - - // -------------------- - // Command history mods - // -------------------- - - /** - * Wipes all command history and sets the command index back to 0. - */ - public void clearCommandHistory() { - commandList.clear(); - commandIndex = 0; - } - - // ---------- - // Ui getters - // ---------- - - /** - * Returns the JTextPane associated with the Console. - * - * @return the JTextPane associated with the Console - */ - public JTextPane getOutputArea() { - return outputArea; - } - - /** - * Returns the JScrollPane associated with the Console. - * - * @return the JScrollPane associated with the Console - */ - public CyderScrollPane getOutputScroll() { - return outputScroll; - } - - /** - * Returns the input JTextField associated with the Console. - * - * @return the input JTextField associated with the Console - */ - public JTextField getInputField() { - return inputField; - } - - /** - * Invokes the {@link #revalidate(boolean, boolean)} method, maintaining either full screen or direction, - * not both. Maintaining fullscreen takes precedent over maintaining the console direction. - */ - public void revalidateMaintainFullscreenOrDirection() { - boolean fullscreen = isFullscreen(); - revalidate(!fullscreen, fullscreen); - } - - /** - * Revalidates the Console size, bounds, background, menu, clock, audio menu, draggable property, etc. - * based on the current background. Note that maintainDirection trumps maintainFullscreen. - * - * @param maintainDirection whether to maintain the console direction - * @param maintainFullscreen whether to maintain fullscreen mode - */ - public void revalidate(boolean maintainDirection, boolean maintainFullscreen) { - revalidate(maintainDirection, maintainFullscreen, false); - } - - /** - * Returns the current console background accounting for the console direction. - * - * @return the current console background accounting for the console direction - */ - public ImageIcon getCurrentRotatedConsoleBackground() { - return switch (consoleDir) { - case TOP -> getCurrentBackground().generateImageIcon(); - case LEFT -> new ImageIcon(ImageUtil.getRotatedImage( - getCurrentBackground().getReferenceFile().getAbsolutePath(), Direction.LEFT)); - case RIGHT -> new ImageIcon(ImageUtil.getRotatedImage( - getCurrentBackground().getReferenceFile().getAbsolutePath(), Direction.RIGHT)); - case BOTTOM -> new ImageIcon(ImageUtil.getRotatedImage( - getCurrentBackground().getReferenceFile().getAbsolutePath(), Direction.BOTTOM)); - }; - } - - /** - * Revalidates the following console properties: - *
    - *
  • Whether the frame should animate opacity on click events
  • - *
  • Background
  • - *
  • Background size
  • - *
  • Ensuring the frame position is completely in bounds of the monitor
  • - *
  • Input and output bounds
  • - *
  • Console max size
  • - *
  • Console menu
  • - *
  • Whether dragging is enabled
  • - *
  • Menu bounds, including the audio menu
  • - *
- *

- * Order of priority is as follows: maintainDirection, maintainFullscreen. - * Neither of these affect maintainConsoleSize. - * - * @param maintainDirection whether to maintain the console direction - * @param maintainFullscreen whether to maintain fullscreen mode - * @param maintainConsoleSize whether to maintain the currently set size of the console - */ - public void revalidate(boolean maintainDirection, boolean maintainFullscreen, boolean maintainConsoleSize) { - Point originalCenter = consoleCyderFrame.getCenterPointOnScreen(); - - ImageIcon background; - - if (maintainDirection) { - background = getCurrentRotatedConsoleBackground(); - - UserDataManager.INSTANCE.setFullscreen(false); - consoleCyderFrame.setShouldAnimateOpacity(true); - } else if (maintainFullscreen && UserDataManager.INSTANCE.isFullscreen()) { - // Setup fullscreen on current monitor - background = ImageUtil.resizeImage(getCurrentBackground().generateImageIcon(), - (int) consoleCyderFrame.getMonitorBounds().getWidth(), - (int) consoleCyderFrame.getMonitorBounds().getHeight()); - consoleCyderFrame.setShouldAnimateOpacity(false); - } else { - background = getCurrentBackground().generateImageIcon(); - } - - int width = consoleCyderFrame.getWidth(); - int height = consoleCyderFrame.getHeight(); - - if (maintainConsoleSize) { - switch (consoleDir) { - case TOP, BOTTOM -> { - if (Direction.isHorizontal(lastConsoleDir)) { - background = ImageUtil.resizeImage(background, height, width); - } else { - background = ImageUtil.resizeImage(background, width, height); - } - } - case LEFT, RIGHT -> { - if (Direction.isHorizontal(lastConsoleDir)) { - background = ImageUtil.resizeImage(background, width, height); - } else { - background = ImageUtil.resizeImage(background, height, width); - } - } - } - } - - int w = background.getIconWidth(); - int h = background.getIconHeight(); - - consoleCyderFrame.setSize(w, h); - consoleCyderFrame.setBackground(background); - - int topLeftX = (int) originalCenter.getX() - w / 2; - int topLeftY = (int) originalCenter.getY() - h / 2; - UiUtil.requestFramePosition(new Point(topLeftX, topLeftY), consoleCyderFrame); - - revalidateInputAndOutputBounds(); - - refreshConsoleMaxSize(); - - // This takes care of offset of input field and output area too - revalidateConsoleTaskbarMenu(); - - consoleCyderFrame.refreshBackground(); - consoleCyderFrame.setDraggingEnabled(!isFullscreen()); - - revalidateConsoleTaskbarMenu(); - revalidateAudioMenuBounds(); - } - - /** - * Returns the CyderFrame used for the Console. - * - * @return the CyderFrame used for the Console - */ - public CyderFrame getConsoleCyderFrame() { - return consoleCyderFrame; - } - - // -------------------------------- - // menu generation and revalidation - // -------------------------------- - - /** - * Sets the visibility of the audio controls button to true. - */ - public void showAudioButton() { - toggleAudioControls.setVisible(true); - } - - /** - * Revalidates the console menu bounds and places - * it where it in the proper spot depending on if it is shown. - * The taskbar icons are also regenerated and shown. - */ - public void revalidateConsoleTaskbarMenu() { - if (consoleClosed.get() || menuLabel == null) return; - - installMenuTaskbarIcons(); - - // revalidate bounds if needed and change icon - if (menuLabel.isVisible()) { - menuLabel.setBounds( - (int) consoleMenuShowingPoint.getX(), - (int) consoleMenuShowingPoint.getY(), - TASKBAR_MENU_WIDTH, - calculateMenuHeight()); - - int width = menuLabel.getWidth() - 2 * menuScrollHorizontalPadding; - int height = menuLabel.getHeight() - 2 * menuScrollVerticalPadding; - menuScroll.setBounds(menuScrollHorizontalPadding, menuScrollVerticalPadding, width, height); - } - - revalidateInputAndOutputBounds(); - } - - /** - * The name of the console audio menu minimizer thread. - */ - private static final String CONSOLE_AUDIO_MENU_MINIMIZER_THREAD_NAME = "Console Audio Menu Minimizer"; - - /** - * The increment for audio menu animations. - */ - private static final int audioMenuAnimationIncrement = 8; - - /** - * The delay for audio menu animations. - */ - private static final int audioMenuAnimationDelayMs = 10; - - /** - * Smoothly animates out the console audio controls. - */ - private void animateOutAudioControls() { - CyderThreadRunner.submit(() -> { - for (int i = audioControlsLabel.getY() ; i > -AUDIO_MENU_LABEL_HEIGHT ; i -= audioMenuAnimationIncrement) { - audioControlsLabel.setLocation(consoleCyderFrame.getWidth() - - audioControlsLabel.getWidth() - AUDIO_MENU_X_OFFSET, i); - ThreadUtil.sleep(audioMenuAnimationDelayMs); - } - audioControlsLabel.setVisible(false); - }, CONSOLE_AUDIO_MENU_MINIMIZER_THREAD_NAME); - } - - /** - * Smooth animates out and removes the audio controls button. - */ - public void animateOutAndRemoveAudioControls() { - CyderThreadRunner.submit(() -> { - for (int i = audioControlsLabel.getY() ; i > -AUDIO_MENU_LABEL_HEIGHT ; i -= audioMenuAnimationIncrement) { - audioControlsLabel.setLocation(consoleCyderFrame.getWidth() - - audioControlsLabel.getWidth() - AUDIO_MENU_X_OFFSET, i); - ThreadUtil.sleep(audioMenuAnimationDelayMs); - } - audioControlsLabel.setVisible(false); - removeAudioControls(); - }, CONSOLE_AUDIO_MENU_MINIMIZER_THREAD_NAME); - } - - /** - * Smoothly animates in the audio controls. - */ - private void animateInAudioControls() { - CyderThreadRunner.submit(() -> { - generateAudioMenu(); - - int y = CyderDragLabel.DEFAULT_HEIGHT - AUDIO_MENU_LABEL_HEIGHT; - audioControlsLabel.setLocation(calculateAudioMenuX(), y); - - audioControlsLabel.setVisible(true); - - for (int i = y ; i < CyderDragLabel.DEFAULT_HEIGHT - 2 ; i += audioMenuAnimationIncrement) { - audioControlsLabel.setLocation(calculateAudioMenuX(), i); - ThreadUtil.sleep(audioMenuAnimationDelayMs); - } - - audioControlsLabel.setLocation(calculateAudioMenuX(), CyderDragLabel.DEFAULT_HEIGHT - 2); - }, CONSOLE_AUDIO_MENU_MINIMIZER_THREAD_NAME); - } - - /** - * Revalidates the visibility audio menu and the play/pause button based on if audio is playing. - */ - public void revalidateAudioMenuVisibility() { - if (!AudioPlayer.isWidgetOpen() && !GeneralAudioPlayer.isGeneralAudioPlaying()) { - if (audioControlsLabel.isVisible()) { - animateOutAndRemoveAudioControls(); - } else { - removeAudioControls(); - } - } else { - if (!audioControlsLabel.isVisible()) { - audioControlsLabel.setLocation(audioControlsLabel.getX(), -AUDIO_MENU_LABEL_HEIGHT); - toggleAudioControls.setVisible(true); - } - - revalidateAudioMenuPlayPauseButton(); - } - } - - /** - * Revalidates the play pause audio label button icon. - */ - @ForReadability - private void revalidateAudioMenuPlayPauseButton() { - boolean playing = GeneralAudioPlayer.generalOrAudioPlayerAudioPlaying(); - playPauseAudioLabel.setIcon(playing ? AudioIcons.pauseIcon : AudioIcons.playIcon); - } - - /** - * Hides the audio controls menu and toggle button. - */ - private void removeAudioControls() { - audioControlsLabel.setVisible(false); - toggleAudioControls.setVisible(false); - consoleCyderFrame.getTopDragLabel().refreshRightButtons(); - } - - /** - * The number of audio menu buttons. - */ - private static final int AUDIO_MENU_BUTTONS = 3; - - /** - * The size of each audio menu button. - */ - private static final int AUDIO_MENU_BUTTON_SIZE = 30; - - /** - * The height of the audio menu. - */ - private static final int AUDIO_MENU_LABEL_HEIGHT = 40; - - /** - * The padding between buttons for the audio menu. - */ - private static final int AUDIO_MENU_X_PADDING = 10; - - /** - * The audio menu button y padding. - */ - private static final int AUDIO_MENU_BUTTON_Y_PADDING = (AUDIO_MENU_LABEL_HEIGHT - AUDIO_MENU_BUTTON_SIZE) / 2; - - /** - * The width of the audio menu. - */ - private static final int AUDIO_MENU_LABEL_WIDTH = AUDIO_MENU_BUTTON_SIZE * AUDIO_MENU_BUTTONS - + AUDIO_MENU_X_PADDING * (AUDIO_MENU_BUTTONS + 1); - - /** - * The offset between the end of the audio menu label and the end of the console frame. - */ - private static final int AUDIO_MENU_X_OFFSET = CyderFrame.BORDER_LEN + 1; - - /** - * The tooltip for the previous audio menu button. - */ - private static final String PREVIOUS = "Previous"; - - /** - * The tooltip for the play pause audio menu button. - */ - private static final String PLAY_PAUSE = "Play/Pause"; - - /** - * The tooltip for the skip audio menu button. - */ - private static final String SKIP = "Skip"; - - /** - * Returns the x value to place the audio menu at. - * - * @return the x value to place the audio menu at - */ - private int calculateAudioMenuX() { - return consoleCyderFrame.getWidth() - AUDIO_MENU_LABEL_WIDTH - AUDIO_MENU_X_OFFSET; - } - - /** - * Generates the audio menu label and the button components. - */ - private void generateAudioMenu() { - audioControlsLabel = new JLabel(); - audioControlsLabel.setBounds( - calculateAudioMenuX(), - -AUDIO_MENU_LABEL_HEIGHT, - AUDIO_MENU_LABEL_WIDTH, - AUDIO_MENU_LABEL_HEIGHT); - audioControlsLabel.setOpaque(true); - audioControlsLabel.setBackground(CyderColors.getGuiThemeColor()); - audioControlsLabel.setBorder(new LineBorder(Color.black, 5)); - audioControlsLabel.setVisible(false); - consoleCyderFrame.getIconPane().add(audioControlsLabel, JLayeredPane.MODAL_LAYER); - - int currentX = AUDIO_MENU_X_PADDING; - - JLabel lastMusicLabel = new JLabel(); - lastMusicLabel.setBounds(currentX, AUDIO_MENU_BUTTON_Y_PADDING, - AUDIO_MENU_BUTTON_SIZE, AUDIO_MENU_BUTTON_SIZE); - lastMusicLabel.setIcon(AudioIcons.lastIcon); - lastMusicLabel.setToolTipText(PREVIOUS); - lastMusicLabel.addMouseListener(new MouseAdapter() { - @Override - public void mouseClicked(MouseEvent e) { - if (AudioPlayer.isWidgetOpen()) { - AudioPlayer.handleLastAudioButtonClick(); - } - } - - @Override - public void mouseEntered(MouseEvent e) { - lastMusicLabel.setIcon(AudioIcons.lastIconHover); - } - - @Override - public void mouseExited(MouseEvent e) { - lastMusicLabel.setIcon(AudioIcons.lastIcon); - } - }); - lastMusicLabel.setVisible(true); - lastMusicLabel.setOpaque(false); - audioControlsLabel.add(lastMusicLabel); - - currentX += AUDIO_MENU_X_PADDING + AUDIO_MENU_BUTTON_SIZE; - - playPauseAudioLabel = new JLabel(); - playPauseAudioLabel.setBounds(currentX, AUDIO_MENU_BUTTON_Y_PADDING, - AUDIO_MENU_BUTTON_SIZE, AUDIO_MENU_BUTTON_SIZE); - playPauseAudioLabel.setToolTipText(PLAY_PAUSE); - playPauseAudioLabel.addMouseListener(new MouseAdapter() { - @Override - public void mouseClicked(MouseEvent e) { - if (AudioPlayer.isWidgetOpen()) { - AudioPlayer.handlePlayPauseButtonClick(); - } - if (GeneralAudioPlayer.isGeneralAudioPlaying()) { - GeneralAudioPlayer.stopGeneralAudio(); - } - } - - @Override - public void mouseEntered(MouseEvent e) { - if (GeneralAudioPlayer.generalOrAudioPlayerAudioPlaying()) { - playPauseAudioLabel.setIcon(AudioIcons.pauseIconHover); - } else { - playPauseAudioLabel.setIcon(AudioIcons.playIconHover); - } - } - - @Override - public void mouseExited(MouseEvent e) { - if (GeneralAudioPlayer.generalOrAudioPlayerAudioPlaying()) { - playPauseAudioLabel.setIcon(AudioIcons.pauseIcon); - } else { - playPauseAudioLabel.setIcon(AudioIcons.playIcon); - } - } - }); - playPauseAudioLabel.setVisible(true); - playPauseAudioLabel.setOpaque(false); - audioControlsLabel.add(playPauseAudioLabel); - - revalidateAudioMenuPlayPauseButton(); - - audioControlsLabel.add(playPauseAudioLabel); - - currentX += AUDIO_MENU_X_PADDING + AUDIO_MENU_BUTTON_SIZE; - - JLabel nextMusicLabel = new JLabel(); - nextMusicLabel.setBounds(currentX, AUDIO_MENU_BUTTON_Y_PADDING, AUDIO_MENU_BUTTON_SIZE, AUDIO_MENU_BUTTON_SIZE); - nextMusicLabel.setIcon(AudioIcons.nextIcon); - audioControlsLabel.add(nextMusicLabel); - nextMusicLabel.setToolTipText(SKIP); - nextMusicLabel.addMouseListener(new MouseAdapter() { - @Override - public void mouseClicked(MouseEvent e) { - if (AudioPlayer.isWidgetOpen()) { - AudioPlayer.handleNextAudioButtonClick(); - } - } - - @Override - public void mouseEntered(MouseEvent e) { - nextMusicLabel.setIcon(AudioIcons.nextIconHover); - } - - @Override - public void mouseExited(MouseEvent e) { - nextMusicLabel.setIcon(AudioIcons.nextIcon); - } - }); - nextMusicLabel.setVisible(true); - nextMusicLabel.setOpaque(false); - - audioControlsLabel.add(nextMusicLabel); - } - - /** - * Revalidates the background colors of the console menus (audio or taskbar) that are active. - */ - public void revalidateMenuBackgrounds() { - if (UiUtil.notNullAndVisible(menuLabel)) { - menuLabel.setBackground(CyderColors.getGuiThemeColor()); - audioControlsLabel.repaint(); - menuScroll.setBackground(CyderColors.getGuiThemeColor()); - menuScroll.repaint(); - } - - if (audioControlsLabel != null && audioControlsLabel.isVisible()) { - audioControlsLabel.setBackground(CyderColors.getGuiThemeColor()); - audioControlsLabel.repaint(); - } - } - - /** - * Sets the console to a provided ScreenPosition and moves any pinned CyderFrame windows with it. - * - * @param screenPosition the screen position to move the Console to - */ - public void setLocationOnScreen(ScreenPosition screenPosition) { - Preconditions.checkNotNull(screenPosition); - - consoleCyderFrame.setLocationOnScreen(screenPosition); - - getPinnedFrames().forEach(relativeFrame -> UiUtil.requestFramePosition( - new Point(relativeFrame.xOffset() + consoleCyderFrame.getX(), - relativeFrame.yOffset() + consoleCyderFrame.getY()), - relativeFrame.frame())); - } - - /** - * The record used for frames pinned to the console. - */ - private record RelativeFrame(CyderFrame frame, int xOffset, int yOffset) { - } - - /** - * Returns a list of all frames that are pinned to the Console. - * - * @return a list of all frames that are pinned to the Console - */ - private ArrayList getPinnedFrames() { - ArrayList ret = new ArrayList<>(); - - ImmutableList.copyOf(UiUtil.getCyderFrames().stream() - .filter(frame -> frame.isConsole() && !frame.isConsole() - && GeometryUtil.rectanglesOverlap(consoleCyderFrame.getBounds(), frame.getBounds())) - .collect(Collectors.toList())).forEach(frame -> { - int xOffset = frame.getX() - consoleCyderFrame.getX(); - int yOffset = frame.getY() - consoleCyderFrame.getY(); - ret.add(new RelativeFrame(frame, xOffset, yOffset)); - }); - - return ret; - } - - /** - * Refreshes the consoleCyderFrame painted title to display the console clock in the specified pattern if enabled. - */ - public void refreshClockText() { - boolean showClock = UserDataManager.INSTANCE.shouldDrawConsoleClock(); - if (!showClock) { - consoleCyderFrame.setCyderFrameTitle(""); - return; - } - - String regularSecondTime = TimeUtil.consoleSecondTime(); - String regularNoSecondTime = TimeUtil.consoleNoSecondTime(); - String userConfiguredTime = TimeUtil.getFormattedTime( - new SimpleDateFormat(UserDataManager.INSTANCE.getConsoleClockFormat())); - - // No custom pattern so take into account showSeconds - if (userConfiguredTime.equalsIgnoreCase(regularSecondTime) - || userConfiguredTime.equalsIgnoreCase(regularNoSecondTime)) { - boolean showSeconds = UserDataManager.INSTANCE.shouldShowConsoleClockSeconds(); - userConfiguredTime = showSeconds ? regularSecondTime : regularNoSecondTime; - } - - consoleCyderFrame.setCyderFrameTitle(userConfiguredTime); - } - - /** - * Logs out the current user and revokes user management from the {@link UserDataManager}. - */ - public void logoutCurrentUser() { - Logger.log(LogTag.LOGOUT, UserDataManager.INSTANCE.getUsername()); - UserDataManager.INSTANCE.setLoggedIn(false); - UserDataManager.INSTANCE.removeManagement(); - NetworkUtil.terminateHighPingChecker(); - uuid = null; - } - - /** - * Logs out the current user and shows the login frame - * relative to the Console's location before it was closed. - */ - public void logoutCurrentUserAndShowLoginFrame() { - Point centerPoint = consoleCyderFrame.getCenterPointOnScreen(); - UiUtil.disposeAllFrames(true); - releaseResourcesAndCloseFrame(false); - logoutCurrentUser(); - LoginHandler.showGui(centerPoint); - } - - /** - * Performs the following actions and then exits the program if instructed to: - *

    - *
  • Saves the user's screen stat
  • - *
  • Stops all audio
  • - *
  • Deactivates the base input handler
  • - *
  • Closes the console frame if open
  • - *
  • Exits the program if invokeExit is true. - * If the ConsoleFrame is currently open, this occurs after the closing animation completes
  • - *
- * - * @param invokeExit whether to invoke a program exit after the above actions - */ - public void releaseResourcesAndCloseFrame(boolean invokeExit) { - if (consoleClosed.get()) return; - consoleClosed.set(true); - - saveScreenStat(); - GeneralAudioPlayer.stopAllAudio(); - - if (baseInputHandler != null) { - baseInputHandler.deactivate(); - baseInputHandler = null; - } - - if (invokeExit) { - if (consoleCyderFrame.isDisposed()) { - OsUtil.exit(ExitCondition.StandardControlledExit); - } else { - consoleCyderFrame.addPostCloseAction(() -> OsUtil.exit(ExitCondition.StandardControlledExit)); - consoleCyderFrame.dispose(); - } - } - } - - /** - * Returns whether the Console is closed. - * - * @return whether the Console is closed - */ - public boolean isClosed() { - return consoleClosed.get(); - } - - /** - * Saves the console's position and window stats to the currently logged-in user's json file. - */ - public void saveScreenStat() { - if (consoleCyderFrame == null || consoleCyderFrame.isDisposed()) return; - if (consoleCyderFrame.getState() == UiConstants.FRAME_ICONIFIED) return; - if (uuid == null) return; - - ScreenStat screenStat = UserDataManager.INSTANCE.getScreenStat(); - screenStat.setConsoleWidth(consoleCyderFrame.getWidth()); - screenStat.setConsoleHeight(consoleCyderFrame.getHeight()); - screenStat.setConsoleOnTop(consoleCyderFrame.isAlwaysOnTop()); - screenStat.setMonitor(Integer.parseInt(consoleCyderFrame.getGraphicsConfiguration() - .getDevice().getIDstring().replaceAll(CyderRegexPatterns.nonNumberRegex, ""))); - - screenStat.setConsoleX(consoleCyderFrame.getX()); - screenStat.setConsoleY(consoleCyderFrame.getY()); - - screenStat.setConsoleDirection(consoleDir); - - UserDataManager.INSTANCE.setScreenStat(screenStat); - UserDataManager.INSTANCE.writeUser(); - } - - // ------- - // Dancing - // ------- - - /** - * A record for a frame to dance. - */ - private record RestoreFrame(CyderFrame frame, int restoreX, int restoreY, boolean draggingWasEnabled) { - /** - * Restores the encapsulated frame's original location and re-enables - * dragging if it was enabled prior to dancing. - */ - public void restore() { - frame.setLocation(restoreX, restoreY); - - if (draggingWasEnabled) { - frame.enableDragging(); - } - } - } - - /** - * Invokes dance in a synchronous way on all CyderFrame instances. - */ - public void dance() { - ArrayList restoreFrames = new ArrayList<>(); - UiUtil.getCyderFrames().forEach(frame -> { - restoreFrames.add(new RestoreFrame(frame, frame.getX(), frame.getY(), frame.isDraggingEnabled())); - frame.disableDragging(); - }); - - ProgramStateManager.INSTANCE.setCurrentProgramState(ProgramState.DANCING); - - while (ProgramStateManager.INSTANCE.getCurrentProgramState() == ProgramState.DANCING) { - if (allFramesFinishedDancing()) break; - - UiUtil.getCyderFrames().forEach(CyderFrame::danceStep); - } - - stopDancing(); - restoreFrames.forEach(RestoreFrame::restore); - } - - /** - * Ends the dancing sequence if ongoing. - */ - public void stopDancing() { - ProgramStateManager.INSTANCE.setCurrentProgramState(ProgramState.NORMAL); - UiUtil.getCyderFrames().forEach(CyderFrame::resetDancing); - } - - /** - * Returns whether all frames have completed a dance iteration. - * - * @return whether all frames have completed a dance iteration - */ - private boolean allFramesFinishedDancing() { - for (CyderFrame frame : UiUtil.getCyderFrames()) { - if (!frame.isDancingFinished()) { - return false; - } - } - - return true; - } - - /** - * Sets the background of the console to whatever is behind it. - * This was the original implementation of frame chams functionality before - * the windows were actually set to be transparent. - */ - public void originalChams() { - try { - CyderFrame frame = getConsoleCyderFrame(); - Rectangle monitorBounds = frame.getMonitorBounds(); - - consoleCyderFrame.setVisible(false); - BufferedImage capture = RobotManager.INSTANCE.getRobot().createScreenCapture(monitorBounds); - consoleCyderFrame.setVisible(true); - - int x = (int) (Math.abs(monitorBounds.getX()) + frame.getX()); - int y = (int) (Math.abs(monitorBounds.getY()) + frame.getY()); - capture = ImageUtil.cropImage(capture, x, y, frame.getWidth(), frame.getHeight()); - - setBackground(ImageUtil.toImageIcon(capture)); - chamsActive.set(true); - } catch (Exception e) { - ExceptionHandler.handle(e); - } - } - - /** - * Returns the console's content pane. - * - * @return the console's content pane - */ - public JLabel getConsoleCyderFrameContentPane() { - return ((JLabel) (consoleCyderFrame.getContentPane())); - } - - /** - * A semaphore to ensure only one title notification is ever visible. - */ - private final Semaphore titleNotifyLock = new Semaphore(1); - - /** - * The label used for title notifications. - */ - private final CyderLabel titleNotifyLabel = new CyderLabel(); - - /** - * The minimum acceptable time for a title notify invocation. - */ - private static final int minimumTitleNotifyVisibleTime = 1000; - - /** - * Paints a label with the provided possibly html-formatted string over the - * Console for the provided number of milliseconds - * - * @param htmlString the string to display, may or may not be formatted using html - * @param labelFont the font to use for the label - * @param visibleDuration the duration in ms the notification should be visible for - */ - public void titleNotify(String htmlString, Font labelFont, Duration visibleDuration) { - Preconditions.checkNotNull(htmlString); - Preconditions.checkNotNull(labelFont); - Preconditions.checkNotNull(visibleDuration); - Preconditions.checkArgument(visibleDuration.toMillis() > minimumTitleNotifyVisibleTime); - - CyderThreadRunner.submit(() -> { - try { - titleNotifyLock.acquire(); - - titleNotifyLabel.setFont(labelFont); - titleNotifyLabel.setOpaque(true); - titleNotifyLabel.setVisible(true); - titleNotifyLabel.setForeground(CyderColors.defaultDarkModeTextColor); - titleNotifyLabel.setBackground(CyderColors.darkModeBackgroundColor); - - BoundsString boundsString = BoundsUtil.widthHeightCalculation(htmlString, - labelFont, consoleCyderFrame.getWidth()); - - int containerWidth = boundsString.getWidth(); - int containerHeight = boundsString.getHeight(); - - if (containerHeight + 2 * titleNotificationPadding > consoleCyderFrame.getHeight() - || containerWidth + 2 * titleNotificationPadding > consoleCyderFrame.getWidth()) { - consoleCyderFrame.inform(htmlString, "Console Notification"); - return; - } - - Point center = consoleCyderFrame.getCenterPointOnFrame(); - - titleNotifyLabel.setText(HtmlUtil.addCenteringToHtml(boundsString.getText())); - titleNotifyLabel.setBounds( - (int) (center.getX() - titleNotificationPadding - containerWidth / 2), - (int) (center.getY() - titleNotificationPadding - containerHeight / 2), - containerWidth, containerHeight); - consoleCyderFrame.getContentPane().add(titleNotifyLabel, JLayeredPane.POPUP_LAYER); - consoleCyderFrame.repaint(); - - ThreadUtil.sleep(visibleDuration.toMillis()); - - titleNotifyLabel.setVisible(false); - consoleCyderFrame.remove(titleNotifyLabel); - titleNotifyLabel.setText(""); - - titleNotifyLock.release(); - } catch (Exception e) { - ExceptionHandler.handle(e); - } - }, "Console Title Notify: " + htmlString); - } - - /** - * Revalidates the bounds of the title label notify if one is underway. - */ - public void revalidateTitleNotify() { - if (consoleCyderFrame == null || titleNotifyLabel.getText().isEmpty()) return; - - int w = titleNotifyLabel.getWidth(); - int h = titleNotifyLabel.getHeight(); - - Point center = consoleCyderFrame.getCenterPointOnFrame(); - - w = (int) (center.getX() - titleNotificationPadding - w / 2); - h = (int) (center.getY() - titleNotificationPadding - h / 2); - titleNotifyLabel.setLocation(w, h); - - consoleCyderFrame.repaint(); - } - - /** - * Adds the provided frame to {@link #frameTaskbarExceptions}. - * - * @param frame the frame to add as an exception - * @return the hash needed to remove the provided frame from the taskbar exceptions map - */ - @CanIgnoreReturnValue - public String addToFrameTaskbarExceptions(CyderFrame frame) { - Preconditions.checkNotNull(frame); - Preconditions.checkArgument(!frameTaskbarExceptions.contains(frame)); - - String hash = SecurityUtil.generateUuid(); - frameTaskbarExceptions.put(hash, frame); - return hash; - } - - /** - * Removes the provided frame from {@link #frameTaskbarExceptions} if it is contained. - * - * @param securityHash the hash returned when {@link #addToFrameTaskbarExceptions(CyderFrame)} was called - */ - public void removeFrameTaskbarException(String securityHash) { - Preconditions.checkNotNull(securityHash); - Preconditions.checkArgument(!securityHash.isEmpty()); - - frameTaskbarExceptions.remove(securityHash); - } -} diff --git a/src/main/java/cyder/console/ConsoleBackground.java b/src/main/java/cyder/console/ConsoleBackground.java deleted file mode 100644 index 1a28d4bc0..000000000 --- a/src/main/java/cyder/console/ConsoleBackground.java +++ /dev/null @@ -1,142 +0,0 @@ -package cyder.console; - -import com.google.common.base.Preconditions; -import com.google.errorprone.annotations.Immutable; -import cyder.exceptions.FatalException; -import cyder.files.FileUtil; -import cyder.handlers.internal.ExceptionHandler; -import cyder.logging.LogTag; -import cyder.logging.Logger; -import cyder.ui.frame.CyderFrame; -import cyder.utils.ImageUtil; - -import javax.swing.*; -import java.awt.*; -import java.awt.image.BufferedImage; -import java.io.File; - -/** - * A background for the Console. - */ -@Immutable -public final class ConsoleBackground { - /** - * the file referenced by this object. - */ - private final File referenceFile; - - /** - * Constructs a new CyderBackground from the provided file if it can be read as an image. - * - * @param referenceFile the file to use as the background - * @throws IllegalArgumentException if the provided file is invalid, null, or does not exist - */ - public ConsoleBackground(File referenceFile) { - Preconditions.checkNotNull(referenceFile); - Preconditions.checkArgument(referenceFile.exists()); - Preconditions.checkArgument(FileUtil.isSupportedImageExtension(referenceFile)); - Preconditions.checkArgument(ImageUtil.isValidImage(referenceFile)); - - this.referenceFile = referenceFile; - - Logger.log(LogTag.OBJECT_CREATION, this); - } - - /** - * Returns the file referenced by this object. - * - * @return the file referenced by this object - */ - public File getReferenceFile() { - return referenceFile; - } - - /** - * Returns a generated buffered image from the reference file. - * - * @return a generated buffered image from the reference file - */ - public BufferedImage generateBufferedImage() { - BufferedImage image = null; - - try { - image = ImageUtil.read(referenceFile); - } catch (Exception e) { - ExceptionHandler.handle(e); - } - - if (image == null) { - throw new FatalException("Could not general buffered image from reference file: " - + referenceFile.getAbsolutePath()); - } - - CyderFrame console = Console.INSTANCE.getConsoleCyderFrame(); - if (console != null) { - Rectangle monitorDimensions = Console.INSTANCE.getConsoleCyderFrame().getMonitorBounds(); - return ImageUtil.ensureFitsInBounds(image, - new Dimension((int) monitorDimensions.getWidth(), (int) monitorDimensions.getHeight())); - } else { - return image; - } - } - - /** - * Returns a generated image icon from the background file. - * - * @return a generated image icon from the background file - */ - public ImageIcon generateImageIcon() { - return ImageUtil.toImageIcon(generateBufferedImage()); - } - - /** - * Generates a scaled image icon. - * - * @param width the width of the scaled icon - * @param height the height of the scaled icon - * @return the scaled image icon - */ - public ImageIcon generateScaledImageIcon(int width, int height) { - return new ImageIcon(generateImageIcon().getImage().getScaledInstance(width, height, Image.SCALE_DEFAULT)); - } - - /** - * Returns whether the reference file still exists, can be read, and is an image. - * - * @return whether the reference file still exists, can be read, and is an image - */ - public boolean isValid() { - return referenceFile.exists() && generateBufferedImage() != null; - } - - /** - * {@inheritDoc} - */ - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } else if (!(o instanceof ConsoleBackground)) { - return false; - } - - ConsoleBackground other = (ConsoleBackground) o; - return getReferenceFile().equals(other.getReferenceFile()); - } - - /** - * {@inheritDoc} - */ - @Override - public int hashCode() { - return referenceFile.hashCode(); - } - - /** - * {@inheritDoc} - */ - @Override - public String toString() { - return "ConsoleBackground{referenceFile=" + referenceFile.getAbsolutePath() + "}"; - } -} diff --git a/src/main/java/cyder/console/ConsoleConstants.kt b/src/main/java/cyder/console/ConsoleConstants.kt deleted file mode 100644 index 044b044b7..000000000 --- a/src/main/java/cyder/console/ConsoleConstants.kt +++ /dev/null @@ -1,167 +0,0 @@ -package cyder.console - -import com.google.common.collect.ImmutableList -import cyder.enumerations.Direction -import cyder.props.Props -import cyder.strings.CyderStrings -import cyder.utils.StaticUtil -import java.awt.Dimension -import java.awt.Font -import java.io.File - -/** - * Constants used for the [Console]. - */ -internal class ConsoleConstants private constructor() { - companion object { - /** - * The absolute minimum size allowable for the Console. - */ - @JvmField - val MINIMUM_SIZE = Dimension(400, 400) - - /** - * The console snap size. - */ - @JvmField - val SNAP_SIZE = initializeSnapSize() - - /** - * Returns the value for [.SNAP_SIZE] - * - * @return the value for [.SNAP_SIZE] - */ - private fun initializeSnapSize(): Dimension { - val len = Props.consoleSnapSize.value - return Dimension(len, len) - } - - /** - * The possible audio files to play if the starting user background is grayscale. - */ - @JvmField - val GRAYSCALE_AUDIO_PATHS: ImmutableList = ImmutableList.of( - StaticUtil.getStaticResource("badapple.mp3"), - StaticUtil.getStaticResource("beetlejuice.mp3"), - StaticUtil.getStaticResource("blackorwhite.mp3")) - - /** - * The thickness of the border around the input field and output area when enabled. - */ - const val FIELD_BORDER_THICKNESS = 3 - - /** - * The default console direction. - */ - @JvmField - val DEFAULT_CONSOLE_DIRECTION = Direction.TOP - - /** - * The prefix for the input field bash string. - */ - const val BASH_STRING_PREFIX = "@Cyder:~$" + CyderStrings.space - - /** - * The horizontal padding between the input/output fields and the frame bounds. - */ - const val FIELD_X_PADDING = 15 - - /** - * The vertical padding between the input and output fields and the frame bounds. - */ - const val FIELD_Y_PADDING = 15 - - /** - * The height of the input field. - */ - const val INPUT_FIELD_HEIGHT = 100 - - /** - * The input map focus key to allow drag label buttons to be triggered via the enter key. - */ - const val BUTTON_INPUT_FOCUS_MAP_KEY = "Button.focusInputMap" - - /** - * The pressed keyword for input focus mapping. - */ - const val PRESSED = "pressed" - - /** - * The released keyword for input focus mapping. - */ - const val RELEASED = "released" - - /** - * The enter keyword for input focus mapping. - */ - const val ENTER = "ENTER" - - /** - * The released enter keyword for input focus mapping. - */ - const val RELEASED_ENTER = RELEASED + CyderStrings.space + ENTER - - /** - * The width of the taskbar menu label. - */ - const val TASKBAR_MENU_WIDTH = 110 - - /** - * The keycode used to detect the f17 key being pressed and invoke the easter egg. - */ - const val F_17_KEY_CODE = 17 - - /** - * The offset for special function key codes and normal ones. - */ - const val SPECIAL_FUNCTION_KEY_CODE_OFFSET = 13 - - /** - * The fullscreen timeout between background animation increments. - */ - const val fullscreenBackgroundAnimationTimeout = 1 - - /** - * The fullscreen increment for background animations. - */ - const val fullscreenBackgroundAnimationIncrement = 20 - - /** - * The default timeout between background animation increments. - */ - const val defaultBackgroundAnimationTimeout = 5 - - /** - * The default increment for background animations. - */ - const val defaultBackgroundAnimationIncrement = 8 - - /** - * The x,y padding value for title notifications. - */ - const val titleNotificationPadding = 20 - - /** - * The font used for the clock label. - */ - @JvmField - val CONSOLE_CLOCK_FONT = initializeConsoleClockFont() - - /** - * Returns the font for the console clock. - * - * @return the font for the console clock - */ - private fun initializeConsoleClockFont(): Font { - val fontName = Props.consoleClockFontName.value - val fontSize = Props.consoleClockFontSize.value - return Font(fontName, Font.BOLD, fontSize) - } - - /** - * The music file for the f17 easter egg. - */ - @JvmField - val F_17_MUSIC_FILE = StaticUtil.getStaticResource("f17.mp3")!! - } -} \ No newline at end of file diff --git a/src/main/java/cyder/console/TaskbarIcon.java b/src/main/java/cyder/console/TaskbarIcon.java deleted file mode 100644 index 330fb4434..000000000 --- a/src/main/java/cyder/console/TaskbarIcon.java +++ /dev/null @@ -1,515 +0,0 @@ -package cyder.console; - -import com.google.common.base.Preconditions; -import com.google.errorprone.annotations.CanIgnoreReturnValue; -import cyder.constants.CyderColors; -import cyder.constants.CyderFonts; -import cyder.exceptions.IllegalMethodException; -import cyder.logging.LogTag; -import cyder.logging.Logger; -import cyder.strings.CyderStrings; -import cyder.ui.label.CyderLabel; -import cyder.utils.ImageUtil; - -import javax.swing.*; -import java.awt.*; -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; -import java.awt.image.BufferedImage; -import java.awt.image.RescaleOp; -import java.util.Objects; - -/** - * A {@link Console} taskbar icon for the console menu. - */ -public class TaskbarIcon { - /** - * The length of the taskbar icons generated. - */ - private static final int ICON_LEN = 75; - - /** - * The border length of the taskbar icons generated. - */ - private static final int BORDER_LEN = 5; - - /** - * The maximum number of characters allowed for a compact taskbar icon. - */ - private static final int MAX_COMPACT_LABEL_CHARS = 10; - - /** - * The color for custom painted taskbar icon borders. - */ - private static final Color BORDER_COLOR = Color.black; - - /** - * The font used for taskbar icon painted names. - */ - private static final Font labelFont = new Font("Agency FB", Font.BOLD, 28); - - /** - * The factor to darken a buffered image by for hover/focus events. - */ - private static final float MAX_DARK_FACTOR = 0.7f; - - /** - * The rescale operator used to darken buffered images. - */ - private static final RescaleOp rescaleOp = new RescaleOp(MAX_DARK_FACTOR, 0, null); - - /** - * The maximum number of characters allowed for a non-compact taskbar icon. - */ - private static final int MAX_NON_COMPACT_LABEL_CHARS = 4; - - /** - * The actual icon used for the console taskbar. - */ - private JLabel taskbarIcon; - - /** - * The builder last used to construct the encapsulated taskbar icon. - */ - private final Builder builder; - - /** - * Suppress default constructor. - */ - private TaskbarIcon() { - throw new IllegalMethodException(CyderStrings.ILLEGAL_CONSTRUCTOR); - } - - /** - * Constructs and generates a new taskbar icon. - * - * @param builder the builder to construct the taskbar icon from - */ - private TaskbarIcon(Builder builder) { - Preconditions.checkNotNull(builder.getName()); - Preconditions.checkArgument(!builder.getName().isEmpty()); - - Logger.log(LogTag.OBJECT_CREATION, this); - - this.builder = builder; - - generateTaskbarIcon(builder); - } - - /** - * Regenerates the taskbar icon based on the current builder's properties. - */ - public void generateTaskbarIcon() { - generateTaskbarIcon(builder); - } - - /** - * Generates the taskbar icon for a CyderFrame based on the provided properties. - * - * @param builder the TaskbarIcon builder to construct the TaskbarIcon from - */ - private void generateTaskbarIcon(Builder builder) { - Preconditions.checkNotNull(builder.getName()); - Preconditions.checkArgument(!builder.getName().isEmpty()); - - if (builder.isCompact()) { - generateCompactTaskbarIcon(builder); - } else { - generateNonCompactTaskbarIcon(builder); - } - } - - /** - * Generates and returns the compact taskbar icon. - * - * @param builder the builder to construct the compact taskbar icon from - */ - private void generateCompactTaskbarIcon(Builder builder) { - String name = builder.getName().substring(0, Math.min(MAX_COMPACT_LABEL_CHARS, builder.getName().length())); - - JLabel compactTaskbarLabel = new JLabel(name); - compactTaskbarLabel.setForeground(builder.isFocused() ? CyderColors.regularRed : CyderColors.vanilla); - compactTaskbarLabel.setFont(CyderFonts.DEFAULT_FONT_SMALL); - compactTaskbarLabel.setVerticalAlignment(SwingConstants.CENTER); - compactTaskbarLabel.addMouseListener(new MouseAdapter() { - @Override - public void mouseClicked(MouseEvent e) { - builder.getRunnable().run(); - } - - @Override - public void mouseEntered(MouseEvent e) { - compactTaskbarLabel.setForeground( - builder.isFocused() ? CyderColors.vanilla : CyderColors.regularRed); - } - - @Override - public void mouseExited(MouseEvent e) { - compactTaskbarLabel.setForeground( - builder.isFocused() ? CyderColors.regularRed : CyderColors.vanilla); - } - }); - compactTaskbarLabel.setToolTipText(builder.getName()); - - taskbarIcon = compactTaskbarLabel; - } - - /** - * Generates and returns the non-compact taskbar icon. - * - * @param builder the builder to construct the non-compact taskbar icon from - */ - private void generateNonCompactTaskbarIcon(Builder builder) { - BufferedImage paintedImage; - if (builder.getCustomIcon() != null) { - paintedImage = ImageUtil.resizeImage(ICON_LEN, ICON_LEN, builder.getCustomIcon()); - } else { - paintedImage = new BufferedImage(ICON_LEN, ICON_LEN, BufferedImage.TYPE_INT_RGB); - Graphics g = paintedImage.getGraphics(); - g.setColor(BORDER_COLOR); - g.fillRect(0, 0, ICON_LEN, BORDER_LEN); - g.fillRect(0, 0, BORDER_LEN, ICON_LEN); - g.fillRect(ICON_LEN - BORDER_LEN, 0, ICON_LEN, ICON_LEN); - g.fillRect(0, ICON_LEN - BORDER_LEN, ICON_LEN, ICON_LEN); - } - - paintBorderOnImage(paintedImage); - - ImageIcon defaultIcon = new ImageIcon(paintedImage); - ImageIcon focusIcon = new ImageIcon(rescaleOp.filter(paintedImage, null)); - - JLabel nonCompactTaskbarLabel = new JLabel(); - nonCompactTaskbarLabel.setSize(ICON_LEN, ICON_LEN); - nonCompactTaskbarLabel.setIcon(builder.isFocused() ? focusIcon : defaultIcon); - - String name = builder.getName().trim(); - String labelName = name.substring(0, Math.min(MAX_NON_COMPACT_LABEL_CHARS, name.length())).trim(); - - CyderLabel titleLabel = new CyderLabel(builder.getCustomIcon() == null ? labelName : ""); - titleLabel.setFont(labelFont); - titleLabel.setForeground(CyderColors.vanilla); - titleLabel.setBounds(0, 0, ICON_LEN, ICON_LEN); - titleLabel.setFocusable(false); - titleLabel.setToolTipText(builder.getName()); - titleLabel.addMouseListener(new MouseAdapter() { - @Override - public void mouseClicked(MouseEvent e) { - if (builder.getRunnable() != null) { - builder.getRunnable().run(); - } - } - - @Override - public void mouseEntered(MouseEvent e) { - nonCompactTaskbarLabel.setIcon(builder.isFocused() ? defaultIcon : focusIcon); - } - - @Override - public void mouseExited(MouseEvent e) { - nonCompactTaskbarLabel.setIcon(builder.isFocused() ? focusIcon : defaultIcon); - } - }); - nonCompactTaskbarLabel.add(titleLabel); - - taskbarIcon = nonCompactTaskbarLabel; - } - - /** - * Paints the taskbar border on the provided image. - * - * @param image the image to paint the border on - */ - private void paintBorderOnImage(BufferedImage image) { - Graphics g = image.getGraphics(); - g.setColor(builder.getCustomIcon() == null ? builder.getBorderColor() : BORDER_COLOR); - g.fillRect(0, 0, BORDER_LEN, ICON_LEN); - g.fillRect(0, 0, ICON_LEN, BORDER_LEN); - g.fillRect(ICON_LEN - 5, 0, ICON_LEN, ICON_LEN); - g.fillRect(0, ICON_LEN - 5, ICON_LEN, ICON_LEN); - } - - /** - * Returns the generated taskbar icon. - * - * @return the generated taskbar icon - */ - public JLabel getTaskbarIcon() { - return taskbarIcon; - } - - /** - * Sets whether this taskbar icon is focused. - * - * @param focused whether this taskbar icon is focused - */ - public void setFocused(boolean focused) { - this.builder.setFocused(focused); - } - - /** - * Runs the runnable associated with this taskbar icon. - */ - public void runRunnable() { - this.builder.getRunnable().run(); - } - - /** - * {@inheritDoc} - */ - @Override - public String toString() { - return "TaskbarIcon{" - + "taskbarIcon=" + taskbarIcon - + ", builder=" + builder - + "}"; - } - - /** - * {@inheritDoc} - */ - @Override - public boolean equals(Object o) { - if (o == this) { - return true; - } else if (!(o instanceof TaskbarIcon)) { - return false; - } - - TaskbarIcon other = (TaskbarIcon) o; - return Objects.equals(builder, other.builder); - } - - /** - * {@inheritDoc} - */ - @Override - public int hashCode() { - return builder.hashCode(); - } - - /** - * A builder for a {@link TaskbarIcon}. - */ - public static final class Builder { - /** - * Whether this icon should be compact. - */ - private boolean compact; - - /** - * Whether this icon is focused. - */ - private boolean focused; - - /** - * The border color. - */ - private Color borderColor; - - /** - * A possible custom icon. - */ - private ImageIcon customIcon; - - /** - * The runnable to invoke upon a click action. - */ - private Runnable runnable; - - /** - * The name of the icon. - */ - private final String name; - - /** - * Constructs a new builder for a taskbar icon. - * - * @param name the name/tooltip for the taskbar icon - */ - public Builder(String name) { - Preconditions.checkNotNull(name); - Preconditions.checkArgument(!name.isEmpty()); - - this.name = name; - } - - /** - * Sets whether this taskbar icon should be painted in compact mode. - * - * @param compact whether this taskbar icon should be painted in compact mode - * @return this Builder - */ - @CanIgnoreReturnValue - public Builder setCompact(boolean compact) { - this.compact = compact; - return this; - } - - /** - * Sets whether this taskbar icon should be painted as focused. - * - * @param focused whether this taskbar icon should be painted as focused - * @return this Builder - */ - @CanIgnoreReturnValue - public Builder setFocused(boolean focused) { - this.focused = focused; - return this; - } - - /** - * Sets the borderColor for this taskbar icon. - * - * @param borderColor the name for this taskbar icon - * @return this Builder - */ - @CanIgnoreReturnValue - public Builder setBorderColor(Color borderColor) { - Preconditions.checkNotNull(borderColor); - - this.borderColor = borderColor; - return this; - } - - /** - * Sets the customIcon for this taskbar icon. - * - * @param customIcon the name for this taskbar icon - * @return this Builder - */ - @CanIgnoreReturnValue - public Builder setCustomIcon(ImageIcon customIcon) { - Preconditions.checkNotNull(customIcon); - - this.customIcon = customIcon; - return this; - } - - /** - * Sets the runnable for this taskbar icon. - * - * @param runnable the runnable for this taskbar icon - * @return this Builder - */ - @CanIgnoreReturnValue - public Builder setRunnable(Runnable runnable) { - Preconditions.checkNotNull(runnable); - - this.runnable = runnable; - return this; - } - - /** - * Returns whether this icon should be generated as compact. - * - * @return whether this icon should be generated as compact - */ - public boolean isCompact() { - return compact; - } - - /** - * Returns whether this icon should be generated as focused. - * - * @return whether this icon should be generated as focused - */ - public boolean isFocused() { - return focused; - } - - /** - * Returns the border color for this icon. - * - * @return the border color for this icon - */ - public Color getBorderColor() { - return borderColor; - } - - /** - * Returns the custom icon for this icon. - * - * @return the custom icon for this icon - */ - public ImageIcon getCustomIcon() { - return customIcon; - } - - /** - * Returns the runnable for this icon. - * - * @return the runnable for this icon - */ - public Runnable getRunnable() { - return runnable; - } - - /** - * Returns the name and tooltip of this icon. - * - * @return the name and tooltip of this icon - */ - public String getName() { - return name; - } - - /** - * Constructs a new TaskbarIcon instance using this builder's members. - * - * @return a new TaskbarIcon instance using this builder's members - */ - public TaskbarIcon build() { - return new TaskbarIcon(this); - } - - /** - * {@inheritDoc} - */ - @Override - public boolean equals(Object o) { - if (o == this) { - return true; - } else if (!(o instanceof Builder)) { - return false; - } - - Builder other = (Builder) o; - - return other.isCompact() == compact - && other.isFocused() == focused - && Objects.equals(other.getBorderColor(), borderColor) - && Objects.equals(other.getCustomIcon(), customIcon) - && Objects.equals(other.getRunnable(), runnable) - && Objects.equals(other.getName(), name); - } - - /** - * {@inheritDoc} - */ - @Override - public int hashCode() { - int ret = Boolean.hashCode(compact); - ret = 31 * ret + Boolean.hashCode(focused); - ret = 31 * ret + Objects.hashCode(borderColor); - ret = 31 * ret + Objects.hashCode(customIcon); - ret = 31 * ret + Objects.hashCode(runnable); - ret = 31 * ret + Objects.hashCode(name); - return ret; - } - - /** - * {@inheritDoc} - */ - @Override - public String toString() { - return "TaskbarIconBuilder{" - + "compact: " + compact - + ", focused: " + focused - + ", borderColor: " + borderColor - + ", customIcon: " + customIcon - + ", runnable: " + runnable - + ", name: \"" + name + "\"" - + "}"; - } - } -} diff --git a/src/main/java/cyder/console/package-info.java b/src/main/java/cyder/console/package-info.java deleted file mode 100644 index bbbe9876a..000000000 --- a/src/main/java/cyder/console/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -/** - * Classes related to the console and the taskbar. - */ -package cyder.console; diff --git a/src/main/java/cyder/games/HangmanGame.java b/src/main/java/cyder/games/HangmanGame.java deleted file mode 100644 index a59576738..000000000 --- a/src/main/java/cyder/games/HangmanGame.java +++ /dev/null @@ -1,323 +0,0 @@ -package cyder.games; - -import com.google.common.collect.ImmutableList; -import cyder.annotations.*; -import cyder.constants.CyderFonts; -import cyder.constants.CyderRegexPatterns; -import cyder.enumerations.CyderInspection; -import cyder.enumerations.Extension; -import cyder.exceptions.IllegalMethodException; -import cyder.handlers.internal.ExceptionHandler; -import cyder.layouts.CyderPartitionedLayout; -import cyder.math.NumberUtil; -import cyder.strings.CyderStrings; -import cyder.strings.StringUtil; -import cyder.ui.UiUtil; -import cyder.ui.button.CyderButton; -import cyder.ui.field.CyderTextField; -import cyder.ui.frame.CyderFrame; -import cyder.ui.label.CyderLabel; -import cyder.utils.StaticUtil; - -import javax.swing.*; -import java.awt.*; -import java.awt.event.KeyEvent; -import java.awt.event.KeyListener; -import java.io.BufferedReader; -import java.io.FileReader; -import java.util.ArrayList; - -/** - * A java implementation of the classic Hangman game. - */ -@CyderAuthor -@Vanilla -@SuppressCyderInspections(CyderInspection.VanillaInspection) -public final class HangmanGame { - /** - * The frame object. - */ - private static CyderFrame hangmanFrame; - - /** - * The current word. - */ - private static String hangmanWord; - - /** - * The reset button. - */ - private static CyderButton resetButton; - - /** - * The field the user enters a letter in. - */ - private static CyderTextField letterField; - - /** - * The label the current hangman image is appended to - */ - private static JLabel imageLabel; - - /** - * The label displaying the current hangman word. - */ - private static CyderLabel currentWordLabel; - - /** - * The number of wrong guesses. - */ - private static int numWrongGuesses; - - /** - * The letters that have been already guessed. - */ - private static final ArrayList chosenLetters = new ArrayList<>(); - - /** - * The placeholder used for the characters on the current word label. - */ - private static final String wordLabelCharPlaceholder = " _ "; - - /** - * The name of the file containing the hangman words. - */ - private static final String wordsFile = "hangman.txt"; - - /** - * The list of words used for hangman. - */ - private static final ImmutableList words; - - static { - ArrayList ret = new ArrayList<>(); - - try (BufferedReader reader = new BufferedReader(new FileReader(StaticUtil.getStaticPath(wordsFile)))) { - String line; - while ((line = reader.readLine()) != null) { - if (!line.trim().isEmpty()) { - ret.add(line); - } - } - } catch (Exception e) { - ExceptionHandler.handle(e); - } - - words = ImmutableList.copyOf(ret); - } - - /** - * The reset text for the reset button. - */ - private static final String RESET = "Reset"; - - /** - * The play again text for the reset button. - */ - private static final String PLAY_AGAIN = "Play again"; - - /** - * The default hangman icon. - */ - private static final ImageIcon defaultHangmanIcon = new ImageIcon(StaticUtil.getStaticPath("hangman.png")); - - /** - * The frame title. - */ - private static final String HANGMAN = "Hangman"; - - /** - * The width of the hangman frame. - */ - private static final int FRAME_WIDTH = 600; - - /** - * The height of the hangman frame. - */ - private static final int FRAME_HEIGHT = 800; - - /** - * The font for the word label. - */ - private static final Font wordLabelFont = CyderFonts.SEGOE_30.deriveFont(26f); - - /** - * The length of the image label. - */ - private static final int imageLabelLen = 512; - - /** - * The spacing partition between components. - */ - private static final int spacingLen = 3; - - /** - * The height of the primary components, not including the image label. - */ - private static final int componentHeight = 40; - - /** - * The maximum number of wrong guesses a user can make. - */ - private static final int maxNumWrongGuesses = 8; - - /** - * Suppress default constructor. - */ - private HangmanGame() { - throw new IllegalMethodException(CyderStrings.ATTEMPTED_INSTANTIATION); - } - - @Widget(triggers = "hangman", description = "A hangman game") - public static void showGui() { - UiUtil.closeIfOpen(hangmanFrame); - - hangmanFrame = new CyderFrame.Builder() - .setWidth(FRAME_WIDTH) - .setHeight(FRAME_HEIGHT) - .setTitle(HANGMAN) - .build(); - - CyderPartitionedLayout partitionedLayout = new CyderPartitionedLayout(); - - currentWordLabel = new CyderLabel(); - currentWordLabel.setHorizontalAlignment(SwingConstants.CENTER); - currentWordLabel.setFont(wordLabelFont); - currentWordLabel.setSize(imageLabelLen, 2 * componentHeight); - partitionedLayout.addComponentMaintainSize(currentWordLabel); - - partitionedLayout.spacer(spacingLen); - - imageLabel = new JLabel(); - imageLabel.setIcon(defaultHangmanIcon); - imageLabel.setSize(imageLabelLen, imageLabelLen); - partitionedLayout.addComponentMaintainSize(imageLabel); - - partitionedLayout.spacer(spacingLen); - - letterField = new CyderTextField(); - letterField.setHorizontalAlignment(JTextField.CENTER); - letterField.setBackground(Color.white); - letterField.setKeyEventRegexMatcher(CyderRegexPatterns.englishLettersRegex); - letterField.setToolTipText("Enter your letter guess here"); - letterField.addKeyListener(new KeyListener() { - @Override - public void keyTyped(KeyEvent e) { - onLetterFieldKeyAction(e); - } - - @Override - public void keyPressed(KeyEvent e) { - onLetterFieldKeyAction(e); - } - - @Override - public void keyReleased(KeyEvent e) { - onLetterFieldKeyAction(e); - } - }); - letterField.setSize(imageLabelLen, componentHeight); - partitionedLayout.addComponentMaintainSize(letterField); - - partitionedLayout.spacer(spacingLen); - - resetButton = new CyderButton(RESET); - resetButton.addActionListener(e -> setup()); - resetButton.setSize(imageLabelLen, componentHeight); - partitionedLayout.addComponentMaintainSize(resetButton); - - hangmanFrame.setCyderLayout(partitionedLayout); - hangmanFrame.finalizeAndShow(); - letterField.requestFocus(); - - setup(); - } - - /** - * The logic to be invoked on any key event that occurs in the letter field. - * - * @param e the key event - */ - @ForReadability - private static void onLetterFieldKeyAction(KeyEvent e) { - char code = e.getKeyChar(); - if (code == KeyEvent.VK_DELETE || code == KeyEvent.VK_BACK_SPACE) { - e.consume(); - Toolkit.getDefaultToolkit().beep(); - return; - } else if (!Character.isAlphabetic(code)) { - e.consume(); - Toolkit.getDefaultToolkit().beep(); - return; - } - - letterField.setText(""); - letterChosen(code); - } - - /** - * Sets up the hangman game. - */ - private static void setup() { - resetButton.setText(RESET); - letterField.setEnabled(true); - chosenLetters.clear(); - chooseHangmanWord(); - - currentWordLabel.setText(StringUtil.fillString(hangmanWord.length(), wordLabelCharPlaceholder)); - imageLabel.setIcon(defaultHangmanIcon); - - numWrongGuesses = 0; - } - - /** - * Chooses a new hangman word. - */ - private static void chooseHangmanWord() { - hangmanWord = words.get(NumberUtil.generateRandomInt(words.size() - 1)).toLowerCase(); - } - - /** - * Performs the actions necessary when a letter is chosen. - * - * @param chosenLetter the chosen letter - */ - private static void letterChosen(char chosenLetter) { - String letter = String.valueOf(chosenLetter); - if (chosenLetters.contains(letter)) return; - - chosenLetters.add(letter); - - if (hangmanWord.toLowerCase().contains(letter)) { - char[] wordChars = hangmanWord.toCharArray(); - StringBuilder labelTextBuilder = new StringBuilder(); - - for (char currentLetter : wordChars) { - if (chosenLetters.contains(String.valueOf(currentLetter))) { - labelTextBuilder.append(currentLetter); - } else { - labelTextBuilder.append(wordLabelCharPlaceholder); - } - } - - String labelText = labelTextBuilder.toString(); - if (labelText.equalsIgnoreCase(hangmanWord)) { - currentWordLabel.setText("You guessed the word \"" + hangmanWord + "\" Would you like to start again?"); - letterField.setEnabled(false); - resetButton.setText(PLAY_AGAIN); - } else { - currentWordLabel.setText(labelText); - } - } else { - numWrongGuesses++; - imageLabel.setIcon(StaticUtil.getImageIcon( - "hangman" + numWrongGuesses + Extension.PNG.getExtension())); - if (numWrongGuesses == maxNumWrongGuesses) { - currentWordLabel.setText("Game over! You were unable to guess \"" + hangmanWord - + "\" Would you like to start again?"); - resetButton.setText(PLAY_AGAIN); - letterField.setEnabled(false); - } - } - } -} diff --git a/src/main/java/cyder/games/TttGame.java b/src/main/java/cyder/games/TttGame.java deleted file mode 100644 index 47c72d9a0..000000000 --- a/src/main/java/cyder/games/TttGame.java +++ /dev/null @@ -1,329 +0,0 @@ -package cyder.games; - -import com.google.common.collect.Range; -import cyder.annotations.CyderAuthor; -import cyder.annotations.SuppressCyderInspections; -import cyder.annotations.Vanilla; -import cyder.annotations.Widget; -import cyder.constants.CyderColors; -import cyder.constants.CyderFonts; -import cyder.enumerations.CyderInspection; -import cyder.exceptions.IllegalMethodException; -import cyder.getter.GetInputBuilder; -import cyder.getter.GetterUtil; -import cyder.handlers.internal.ExceptionHandler; -import cyder.layouts.CyderGridLayout; -import cyder.strings.CyderStrings; -import cyder.threads.CyderThreadRunner; -import cyder.ui.UiUtil; -import cyder.ui.button.CyderButton; -import cyder.ui.drag.CyderDragLabel; -import cyder.ui.frame.CyderFrame; -import cyder.ui.pane.CyderPanel; - -import javax.swing.*; -import javax.swing.border.LineBorder; -import java.awt.*; -import java.util.Optional; - -/** - * A tic tac toe game widget. - */ -@CyderAuthor -@Vanilla -@SuppressCyderInspections(CyderInspection.VanillaInspection) -public final class TttGame { - /** - * The CyderFrame instance to use to ensure no other games exist with one Cyder instance. - */ - private static CyderFrame tttFrame; - - /** - * The buttons for the board. - */ - private static CyderButton[][] boardButtons; - - /** - * The foreground color used for the buttons. - */ - public static final Color blueForeground = new Color(71, 81, 117); - - /** - * Player enums. - */ - private enum Player { - X, O - } - - /** - * The current player. - */ - private static Player currentPlayer; - - /** - * The information label. - */ - private static JLabel infoLabel; - - /** - * The range of allowable values for tic tac toe. - */ - private static final Range GRID_SIZE_RANGE = Range.closed(3, 11); - - /** - * Suppress default constructor. - */ - private TttGame() { - throw new IllegalMethodException(CyderStrings.ATTEMPTED_INSTANTIATION); - } - - private static final Dimension buttonSize = new Dimension(60, 60); - private static int boardLength = 5; - private static final LineBorder buttonBorder = new LineBorder(CyderColors.navy, 5, false); - private static final int buttonPadding = 10; - - @SuppressCyderInspections(CyderInspection.WidgetInspection) - @Widget(triggers = {"ttt", "tic tac toe"}, description = "A TicTacToe widget") - public static void showGui() { - UiUtil.closeIfOpen(tttFrame); - - int labelOffset = 60; - int frameLen = buttonSize.width * boardLength + buttonPadding * (boardLength + 2) + labelOffset; - - tttFrame = new CyderFrame.Builder() - .setWidth(frameLen) - .setHeight(frameLen) - .setTitle("TicTacToe") - .build(); - tttFrame.addMenuItem("Board Size", () -> CyderThreadRunner.submit(() -> { - try { - Optional optionalSizeString = GetterUtil.getInstance().getInput( - new GetInputBuilder("Board Size", String.valueOf(boardLength)) - .setFieldRegex("[0-7]") - .setRelativeTo(tttFrame)); - if (optionalSizeString.isEmpty()) return; - String sizeString = optionalSizeString.get(); - - try { - int newBoardLength = Integer.parseInt(sizeString); - - if (GRID_SIZE_RANGE.contains(newBoardLength)) { - boardLength = newBoardLength; - showGui(); - } else { - tttFrame.notify("Sorry, but " + newBoardLength - + " is not in the allowable range of [" - + GRID_SIZE_RANGE.lowerEndpoint() + ", " - + GRID_SIZE_RANGE.upperEndpoint() + CyderStrings.closingBracket); - } - } catch (Exception ignored) { - tttFrame.notify("Unable to parse input as an integer"); - } - } catch (Exception e) { - ExceptionHandler.handle(e); - } - }, "Board Size Changer")); - tttFrame.addMenuItem("Reset Board", TttGame::resetBoard); - tttFrame.setMenuButtonShown(true); - - infoLabel = new JLabel(); - infoLabel.setHorizontalAlignment(JLabel.CENTER); - infoLabel.setFont(CyderFonts.DEFAULT_FONT); - infoLabel.setForeground(CyderColors.navy); - infoLabel.setBounds(0, CyderDragLabel.DEFAULT_HEIGHT, frameLen, labelOffset); - tttFrame.getContentPane().add(infoLabel); - - boardButtons = new CyderButton[boardLength][boardLength]; - - CyderGridLayout buttonGridLayout = new CyderGridLayout(boardLength, boardLength); - - for (int y = 0 ; y < boardLength ; y++) { - for (int x = 0 ; x < boardLength ; x++) { - CyderButton button = new CyderButton(""); - button.setPreferredSize(buttonSize); - button.setBackground(CyderColors.vanilla); - button.setFocusPainted(false); - button.setBackground(CyderColors.vanilla); - button.setFont(CyderFonts.SEGOE_30); - button.setBorder(buttonBorder); - button.addActionListener(e -> { - if (button.getText().isEmpty()) { - if (currentPlayer == Player.X) { - button.setText("X"); - button.setFont(CyderFonts.SEGOE_30); - currentPlayer = Player.O; - - } else { - button.setText("O"); - button.setForeground(blueForeground); - button.setFont(CyderFonts.SEGOE_30); - currentPlayer = Player.X; - } - - updateTurnLabel(); - checkForWin(); - } - }); - - button.setSize(buttonSize.width, buttonSize.height); - buttonGridLayout.addComponent(button, x, y); - boardButtons[y][x] = button; - } - } - - CyderPanel panel = new CyderPanel(buttonGridLayout); - panel.setBounds(0, CyderDragLabel.DEFAULT_HEIGHT + labelOffset - 10, - frameLen, frameLen - (CyderDragLabel.DEFAULT_HEIGHT + labelOffset)); - tttFrame.getContentPane().add(panel); - - tttFrame.finalizeAndShow(); - panel.revalidateComponents(); - - currentPlayer = Player.X; - - updateTurnLabel(); - } - - /** - * Updates the player turn label. - */ - private static void updateTurnLabel() { - infoLabel.setText("Player Turn: " + (currentPlayer == Player.X ? "X" : "O")); - } - - /** - * Resets the ttt board - */ - private static void resetBoard() { - currentPlayer = Player.X; - updateTurnLabel(); - - for (CyderButton[] buttonRow : boardButtons) { - for (CyderButton button : buttonRow) { - button.setText(""); - } - } - } - - /** - * Checks for a game win. - */ - private static void checkForWin() { - if (checkPlayerWin("X")) { - tttFrame.notify("X's have won the game! Congratulations!"); - resetBoard(); - } else if (checkPlayerWin("O")) { - tttFrame.notify("O's have won the game! Congratulations!"); - resetBoard(); - } else if (isBoardFull()) { - tttFrame.notify("The game ended with no winners."); - resetBoard(); - } - } - - /** - * Returns whether the provided player has won the game. - * - * @param player the player to check for winning - * @return whether the provided player has won - */ - private static boolean checkPlayerWin(String player) { - return isHorizontalWin(player) || isVerticalWin(player) || isDiagonalWin(player); - } - - /** - * Returns whether the provided player has won via a horizontal win. - * - * @param player the player to test for a horizontal win - * @return whether the provided player has won via a horizontal win - */ - private static boolean isHorizontalWin(String player) { - for (int y = 0 ; y < boardLength ; y++) { - boolean line = true; - - for (int x = 0 ; x < boardLength ; x++) { - line = line && boardButtons[y][x].getText().equals(player); - } - - if (line) { - return true; - } - } - - return false; - } - - /** - * Returns whether the provided player has won via a vertical win. - * - * @param player the player to test for a vertical win - * @return whether the provided player has won via a vertical win - */ - private static boolean isVerticalWin(String player) { - CyderButton[][] rotated = new CyderButton[boardLength][boardLength]; - - for (int i = 0 ; i < boardButtons[0].length ; i++) { - for (int j = boardButtons.length - 1 ; j >= 0 ; j--) { - rotated[i][j] = boardButtons[j][i]; - } - } - - for (int y = 0 ; y < boardLength ; y++) { - boolean line = true; - - for (int x = 0 ; x < boardLength ; x++) { - line = line && rotated[y][x].getText().equals(player); - } - - if (line) { - return true; - } - } - - return false; - } - - /** - * Returns whether the provided player has won via a diagonal win. - * - * @param player the player to test for a diagonal win - * @return whether the provided player has won via a diagonal win - */ - private static boolean isDiagonalWin(String player) { - boolean topLeftBottomRight = true; - - for (int i = 0 ; i < boardLength ; i++) { - topLeftBottomRight = topLeftBottomRight && boardButtons[i][i].getText().equals(player); - } - - if (topLeftBottomRight) { - return true; - } - - boolean topRightBottomLeft = true; - - for (int i = 0 ; i < boardLength ; i++) { - topRightBottomLeft = - topRightBottomLeft && boardButtons[boardLength - i - 1][i].getText().equals(player); - } - - return topRightBottomLeft; - } - - /** - * Returns whether the board is full. - * - * @return whether the board is full - */ - private static boolean isBoardFull() { - for (CyderButton[] buttonRow : boardButtons) { - for (CyderButton button : buttonRow) { - if (button.getText().isEmpty()) { - return false; - } - } - } - - return true; - } -} diff --git a/src/main/java/cyder/games/package-info.java b/src/main/java/cyder/games/package-info.java deleted file mode 100644 index ec5d2ac97..000000000 --- a/src/main/java/cyder/games/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -/** - * Simple games implemented in Cyder. - */ -package cyder.games; diff --git a/src/main/java/cyder/handlers/external/DirectoryViewer.java b/src/main/java/cyder/handlers/external/DirectoryViewer.java deleted file mode 100644 index 3c14e959c..000000000 --- a/src/main/java/cyder/handlers/external/DirectoryViewer.java +++ /dev/null @@ -1,303 +0,0 @@ -package cyder.handlers.external; - -import com.google.common.base.Preconditions; -import cyder.annotations.Widget; -import cyder.constants.CyderColors; -import cyder.constants.CyderFonts; -import cyder.exceptions.IllegalMethodException; -import cyder.files.FileUtil; -import cyder.strings.CyderStrings; -import cyder.strings.StringUtil; -import cyder.ui.UiUtil; -import cyder.ui.button.CyderButton; -import cyder.ui.field.CyderTextField; -import cyder.ui.frame.CyderFrame; -import cyder.ui.list.CyderScrollList; -import cyder.utils.OsUtil; - -import javax.swing.*; -import javax.swing.border.LineBorder; -import java.awt.*; -import java.io.File; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Stack; - -/** - * A directory navigation widget. - */ -public final class DirectoryViewer { - /** - * The frame for the directory widget. - */ - private static CyderFrame directoryFrame; - - /** - * The field to display the current directory and to allow manual paths to be entered. - */ - private static CyderTextField directoryField; - - /** - * The directory scroll label. Needed to allow removal when files are changed. - */ - private static JLabel dirScrollLabel = new JLabel(); - - /** - * The current files. - */ - private static final ArrayList currentFiles = new ArrayList<>(); - - /** - * Stack to traverse backwards through history of viewed directories. - */ - private static final Stack backward = new Stack<>(); - - /** - * Stack to traverse forward through history of viewed directories. - */ - private static final Stack forward = new Stack<>(); - - /** - * The current location of the directory widget. - */ - private static File currentDirectory; - - /** - * The width of the scroll view. - */ - private static final int SCROLL_WIDTH = 600; - - /** - * The height of the scroll view. - */ - private static final int SCROLL_HEIGHT = 400; - - /** - * The scroll list component to display the current files - */ - private static CyderScrollList cyderScrollList = new CyderScrollList(SCROLL_WIDTH, SCROLL_HEIGHT); - - /** - * The x value of the directory scroll label and the loading files label. - */ - private static final int directoryScrollX = 10; - - /** - * The y value of the directory scroll label and the loading files label. - */ - private static final int directoryScrollY = 90; - - /** - * The loading files label. - */ - private static final JLabel loadingFilesLabel = new JLabel(); - - static { - loadingFilesLabel.setText("
Loading files...
"); - loadingFilesLabel.setHorizontalAlignment(JLabel.CENTER); - loadingFilesLabel.setVerticalAlignment(JLabel.CENTER); - loadingFilesLabel.setFont(CyderFonts.DEFAULT_FONT); - loadingFilesLabel.setBorder(new LineBorder(CyderColors.navy, 5, false)); - loadingFilesLabel.setOpaque(false); - loadingFilesLabel.setBounds(directoryScrollX, directoryScrollY, SCROLL_WIDTH, SCROLL_HEIGHT); - } - - /** - * The frame widget. - */ - private static final int frameWidth = 630; - - /** - * The frame height. - */ - private static final int frameHeight = 510; - - /** - * Thee length of the nav buttons. - */ - private static final int navButtonLen = 40; - - /** - * The y values of the nav buttons. - */ - private static final int navButtonYOffset = 40; - - /** - * The x value of the last nav button. - */ - private static final int navButtonLastX = 10; - - /** - * The padding between the nav buttons and the field. - */ - private static final int fieldNavButtonPadding = 25; - - /** - * The x value of the last nav button. - */ - private static final int navButtonNextX = frameWidth - navButtonLastX - 2 * fieldNavButtonPadding; - - /** - * Suppress default constructor. - */ - private DirectoryViewer() { - throw new IllegalMethodException(CyderStrings.ATTEMPTED_INSTANTIATION); - } - - @Widget(triggers = {"dir", "directory"}, description = "A directory navigator widget") - public static void showGui() { - showGui(new File(OsUtil.USER_DIR)); - } - - /** - * Starts the directory viewer in the provided initial directory. - * - * @param initialDirectory the initial directory to start in - * @return whether the gui was shown successfully - */ - public static boolean showGui(File initialDirectory) { - Preconditions.checkNotNull(initialDirectory); - Preconditions.checkArgument(initialDirectory.exists()); - - if (initialDirectory.isFile()) { - initialDirectory = initialDirectory.getParentFile(); - } - - currentDirectory = initialDirectory; - - UiUtil.closeIfOpen(directoryFrame); - - directoryFrame = new CyderFrame.Builder() - .setWidth(frameWidth) - .setHeight(frameHeight) - .setBackgroundIconFromColor(CyderColors.regularBackgroundColor) - .build(); - directoryFrame.setTitle(getTitleForCurrentDirectory()); - - directoryField = new CyderTextField(); - directoryField.setBackground(Color.white); - directoryField.setForeground(CyderColors.navy); - directoryField.setBorder(new LineBorder(CyderColors.navy, 5, false)); - directoryField.setText(currentDirectory.getAbsolutePath()); - directoryField.addActionListener(e -> { - File chosenDir = new File(directoryField.getText()); - - if (chosenDir.isDirectory()) { - forward.clear(); - storeCurrentDirectory(); - currentDirectory = chosenDir; - refreshFiles(); - } else if (chosenDir.isFile()) { - FileUtil.openResource(chosenDir.getAbsolutePath(), true); - } - }); - directoryField.setBounds(60, 40, 500, 40); - directoryFrame.getContentPane().add(directoryField); - - CyderButton last = new CyderButton(" < "); - last.setFocusPainted(false); - last.setForeground(CyderColors.navy); - last.setBackground(CyderColors.regularRed); - last.setFont(CyderFonts.SEGOE_20); - last.setBorder(new LineBorder(CyderColors.navy, 5, false)); - last.addActionListener(e -> { - if (!backward.isEmpty() && !backward.peek().equals(currentDirectory)) { - forward.push(currentDirectory); - currentDirectory = backward.pop(); - refreshFiles(); - } - }); - last.setBounds(navButtonLastX, navButtonYOffset, navButtonLen, navButtonLen); - directoryFrame.getContentPane().add(last); - - CyderButton next = new CyderButton(" > "); - next.setFocusPainted(false); - next.setForeground(CyderColors.navy); - next.setBackground(CyderColors.regularRed); - next.setFont(CyderFonts.SEGOE_20); - next.setBorder(new LineBorder(CyderColors.navy, 5, false)); - next.addActionListener(e -> { - if (!forward.isEmpty() && !forward.peek().equals(currentDirectory)) { - backward.push(currentDirectory); - storeCurrentDirectory(); - currentDirectory = forward.pop(); - refreshFiles(); - } - }); - next.setBounds(navButtonNextX, navButtonYOffset, navButtonLen, navButtonLen); - directoryFrame.getContentPane().add(next); - - loadingFilesLabel.setVisible(true); - directoryFrame.getContentPane().add(loadingFilesLabel); - - directoryFrame.finalizeAndShow(); - directoryField.requestFocus(); - - refreshFiles(); - - return true; - } - - /** - * Stores the current directory as a previous location if necessary. - */ - private static void storeCurrentDirectory() { - if (backward.isEmpty()) { - backward.push(currentDirectory); - } else { - File backwardFile = backward.peek(); - if (backwardFile != null && !backwardFile.getAbsolutePath().equals(currentDirectory.getAbsolutePath())) { - backward.push(currentDirectory); - } - } - } - - /** - * Refreshes the files scroll list based on the current directory. - */ - private static void refreshFiles() { - loadingFilesLabel.setVisible(true); - - cyderScrollList.removeAllElements(); - directoryFrame.remove(dirScrollLabel); - - currentFiles.clear(); - - File[] localDirectoryFiles = currentDirectory.listFiles(); - if (localDirectoryFiles != null && localDirectoryFiles.length > 0) { - Collections.addAll(currentFiles, localDirectoryFiles); - } - - cyderScrollList = new CyderScrollList(SCROLL_WIDTH, SCROLL_HEIGHT, CyderScrollList.SelectionPolicy.SINGLE); - cyderScrollList.setScrollFont(CyderFonts.SEGOE_20.deriveFont(16f)); - - currentFiles.forEach(file -> - cyderScrollList.addElementWithDoubleClickAction(file.getName(), () -> { - if (file.isDirectory()) { - forward.clear(); - storeCurrentDirectory(); - currentDirectory = file; - refreshFiles(); - } else { - FileUtil.openResource(file.getAbsolutePath(), true); - } - })); - - dirScrollLabel = cyderScrollList.generateScrollList(); - dirScrollLabel.setBounds(directoryScrollX, directoryScrollY, SCROLL_WIDTH, SCROLL_HEIGHT); - directoryFrame.getContentPane().add(dirScrollLabel); - - loadingFilesLabel.setVisible(false); - - directoryFrame.revalidate(); - directoryFrame.repaint(); - - directoryFrame.setTitle(getTitleForCurrentDirectory()); - directoryField.setText(currentDirectory.getAbsolutePath()); - } - - private static String getTitleForCurrentDirectory() { - String name = currentDirectory.getName(); - return name.isEmpty() ? currentDirectory.getAbsolutePath() : StringUtil.capsFirstWords(name.toLowerCase()); - } -} diff --git a/src/main/java/cyder/handlers/external/ImageViewer.java b/src/main/java/cyder/handlers/external/ImageViewer.java deleted file mode 100644 index 0462045cb..000000000 --- a/src/main/java/cyder/handlers/external/ImageViewer.java +++ /dev/null @@ -1,395 +0,0 @@ -package cyder.handlers.external; - -import com.google.common.base.Preconditions; -import cyder.console.Console; -import cyder.files.DirectoryWatcher; -import cyder.files.FileUtil; -import cyder.files.WatchDirectoryEvent; -import cyder.files.WatchDirectorySubscriber; -import cyder.getter.GetInputBuilder; -import cyder.getter.GetterUtil; -import cyder.handlers.internal.ExceptionHandler; -import cyder.logging.LogTag; -import cyder.logging.Logger; -import cyder.threads.CyderThreadFactory; -import cyder.threads.CyderThreadRunner; -import cyder.ui.drag.button.LeftButton; -import cyder.ui.drag.button.RightButton; -import cyder.ui.frame.CyderFrame; -import cyder.ui.frame.enumerations.TitlePosition; -import cyder.user.UserDataManager; -import cyder.utils.ArrayUtil; -import cyder.utils.ImageUtil; - -import javax.swing.*; -import java.awt.*; -import java.awt.event.WindowAdapter; -import java.awt.event.WindowEvent; -import java.awt.image.BufferedImage; -import java.io.File; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Optional; -import java.util.concurrent.Executor; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.concurrent.atomic.AtomicReference; -import java.util.stream.IntStream; - -import static cyder.strings.CyderStrings.*; - -/** - * A widget which displays the images supported by Cyder in a provided directory. - */ -public class ImageViewer { - /** - * The next keyword. - */ - private static final String NEXT = "Next"; - - /** - * The last keyword. - */ - private static final String LAST = "Last"; - - /** - * The rename keyword. - */ - private static final String RENAME = "Rename"; - - /** - * The maximum length of the image viewer frame. - */ - private static final int maxFrameLength = 800; - - /** - * The maximum dimension of the image viewer frame. - */ - private static final Dimension maxFrameSize = new Dimension(maxFrameLength, maxFrameLength); - - /** - * The getter util instance used to acquire the new filename from the user during a rename image attempt. - */ - private final GetterUtil getterUtil = GetterUtil.getInstance(); - - /** - * The list of valid image files in the current directory, not recursive. - */ - private final ArrayList validDirectoryImages = new ArrayList<>(); - - /** - * The watcher for the image directory. - */ - private final DirectoryWatcher imageDirectoryWatcher; - - /** - * The starting directory/file. - */ - private final File imageDirectory; - - /** - * The current index of the valid directory images list. - */ - private int currentIndex; - - /** - * The image frame. - */ - private CyderFrame pictureFrame; - - /** - * The next image button. - */ - private RightButton nextButton; - - /** - * The last image button. - */ - private LeftButton lastButton; - - /** - * Creates and returns a new instance. - * - * @param imageDirectoryOrFile the image directory or an image file. - * If a file is provided, the file's parent is used as the directory - * @return a new instance - */ - public static ImageViewer getInstance(File imageDirectoryOrFile) { - return new ImageViewer(imageDirectoryOrFile); - } - - /** - * Creates and returns a new instance. - * - * @param imageDirectoryOrFile the image directory or an image file. - * If a file is provided, the file's parent is used as the directory - * @throws NullPointerException if the provided file is null - * @throws IllegalArgumentException if the provided file does not exist - */ - private ImageViewer(File imageDirectoryOrFile) { - Preconditions.checkNotNull(imageDirectoryOrFile); - Preconditions.checkArgument(imageDirectoryOrFile.exists()); - - this.imageDirectory = imageDirectoryOrFile; - - File watchDirectory = imageDirectoryOrFile.isFile() - ? imageDirectoryOrFile.getParentFile() : imageDirectoryOrFile; - this.imageDirectoryWatcher = new DirectoryWatcher(watchDirectory); - - Logger.log(LogTag.OBJECT_CREATION, this); - } - - /** - * Shows this ImageViewer instance. - * - * @return whether the the image was successfully loaded and opened - */ - public Future showGui() { - return Executors.newSingleThreadExecutor(generateThreadFactory()).submit(() -> { - refreshImageFiles(); - - File currentImage = getCurrentImageFile(); - - ImageIcon newImage = scaleImageIfNeeded(currentImage); - pictureFrame = new CyderFrame.Builder() - .setWidth(newImage.getIconWidth()) - .setHeight(newImage.getIconHeight()) - .setBackgroundIcon(newImage) - .setBackgroundColor(Color.BLACK) - .build(); - pictureFrame.setTitlePosition(TitlePosition.CENTER); - revalidateTitle(FileUtil.getFilename(currentImage.getName())); - pictureFrame.setVisible(true); - pictureFrame.addWindowListener(new WindowAdapter() { - @Override - public void windowClosed(WindowEvent e) { - imageDirectoryWatcher.stopWatching(); - } - }); - - pictureFrame.setMenuButtonShown(true); - pictureFrame.addMenuItem(RENAME, this::onRenameButtonClicked); - - nextButton = new RightButton(); - nextButton.setToolTipText(NEXT); - nextButton.setClickAction(this::transitionForward); - pictureFrame.getTopDragLabel().addRightButton(nextButton, 0); - - lastButton = new LeftButton(); - lastButton.setToolTipText(LAST); - lastButton.setClickAction(this::transitionBackward); - pictureFrame.getTopDragLabel().addRightButton(lastButton, 0); - - revalidateNavigationButtonVisibility(); - startDirectoryWatcher(); - - pictureFrame.finalizeAndShow(); - - return true; - }); - } - - /** - * Returns a reference to the current image file if possible. The first image is returned otherwise. - * - * @return a reference to the current image file. The first image is returned otherwise - */ - private File getCurrentImageFile() { - AtomicReference currentImage = new AtomicReference<>(validDirectoryImages.get(0)); - if (imageDirectory.isFile()) { - validDirectoryImages.stream() - .filter(image -> image.equals(imageDirectory)) - .findFirst().ifPresent(currentImage::set); - } - return currentImage.get(); - } - - /** - * Generates and returns a new {@link CyderThreadFactory} for the loading {@link Executor}. - * - * @return a new {@link CyderThreadFactory} for the loading {@link Executor} - */ - private CyderThreadFactory generateThreadFactory() { - return new CyderThreadFactory("ImageViewer showGui thread, directory" + colon + space + imageDirectory); - } - - /** - * Refreshes the {@link #validDirectoryImages} list based on the currently set {@link #imageDirectory}. - */ - private void refreshImageFiles() { - validDirectoryImages.clear(); - - File[] neighbors = imageDirectory.isDirectory() - ? imageDirectory.listFiles() - : imageDirectory.getParentFile().listFiles(); - if (ArrayUtil.nullOrEmpty(neighbors)) return; - Arrays.stream(neighbors).filter(FileUtil::isSupportedImageExtension).forEach(validDirectoryImages::add); - } - - /** - * Transitions to the next image if possible. - */ - private void transitionForward() { - refreshImageFiles(); - if (validDirectoryImages.size() < 2) return; - currentIndex = currentIndex == validDirectoryImages.size() - 1 ? 0 : currentIndex + 1; - revalidateFromTransition(); - } - - /** - * Transitions to the previous image if possible. - */ - private void transitionBackward() { - refreshImageFiles(); - if (validDirectoryImages.size() < 2) return; - currentIndex = currentIndex == 0 ? validDirectoryImages.size() - 1 : currentIndex - 1; - revalidateFromTransition(); - } - - /** - * The logic to perform following a transition. - */ - private void revalidateFromTransition() { - Point oldCenterPoint = pictureFrame.getCenterPointOnScreen(); - ImageIcon image = scaleImageIfNeeded(validDirectoryImages.get(currentIndex)); - pictureFrame.setSize(image.getIconWidth(), image.getIconHeight()); - pictureFrame.setBackground(image); - pictureFrame.setCenterPoint(oldCenterPoint); - pictureFrame.refreshBackground(); - revalidateTitle(FileUtil.getFilename(validDirectoryImages.get(currentIndex).getName())); - } - - /** - * Returns a scaled image icon for the provided image - * file if the image is bigger than MAX_LEN x MAX_LEN. - * - * @param imageFile the image file to process - * @return the ImageIcon from the image file guaranteed to be no bigger than MAX_LEN x MAX_LEN - */ - private ImageIcon scaleImageIfNeeded(File imageFile) { - Preconditions.checkNotNull(imageFile); - Preconditions.checkArgument(imageFile.exists()); - Preconditions.checkArgument(imageFile.isFile()); - - try { - BufferedImage bufferedImage = ImageUtil.read(imageFile); - bufferedImage = ImageUtil.ensureFitsInBounds(bufferedImage, maxFrameSize); - return ImageUtil.toImageIcon(bufferedImage); - } catch (Exception e) { - throw new IllegalStateException("Could not generate ImageIcon for file" + colon - + space + imageFile.getAbsolutePath() + ", error: " + e.getMessage()); - } - } - - /** - * The actions to invoke when the rename menu item is pressed. - */ - private void onRenameButtonClicked() { - File currentRename = new File(validDirectoryImages.get(currentIndex).getAbsolutePath()); - File currentBackground = Console.INSTANCE - .getCurrentBackground().getReferenceFile().getAbsoluteFile(); - - if (currentRename.getAbsolutePath().equals(currentBackground.getAbsolutePath())) { - pictureFrame.notify("Sorry, " + UserDataManager.INSTANCE.getUsername() - + ", but you're not allowed to rename the background you are currently using"); - return; - } - - getterUtil.closeAllGetInputFrames(); - - String initialFieldText = FileUtil.getFilename(validDirectoryImages.get(currentIndex)); - - CyderThreadRunner.submit(() -> { - try { - GetInputBuilder builder = new GetInputBuilder(RENAME, "New filename for" - + space + quote + validDirectoryImages.get(currentIndex).getName() + quote) - .setRelativeTo(pictureFrame) - .setInitialFieldText(initialFieldText) - .setSubmitButtonText(RENAME); - Optional optionalName = getterUtil.getInput(builder); - if (optionalName.isEmpty() || optionalName.get().equals(initialFieldText)) return; - - String requestedName = optionalName.get(); - - File oldFileReference = new File(validDirectoryImages.get(currentIndex).getAbsolutePath()); - File newFileReference = new File(oldFileReference.getAbsolutePath() - .replace(FileUtil.getFilename(oldFileReference), requestedName)); - - if (oldFileReference.renameTo(newFileReference)) { - pictureFrame.notify("Successfully renamed to" + space + quote + requestedName + quote); - - refreshImageFiles(); - IntStream.range(0, validDirectoryImages.size()) - .forEach(index -> { - if (FileUtil.getFilename(validDirectoryImages.get(index)).equals(requestedName)) { - currentIndex = index; - } - }); - revalidateTitle(requestedName); - } else { - pictureFrame.notify("Could not rename at this time"); - } - - } catch (Exception e) { - ExceptionHandler.handle(e); - } - }, "ImageViewer File Renamer, file" + colon + space + initialFieldText); - } - - /** - * Revalidates the frame title based on the provided name. - * - * @param title the title of the frame - */ - public void revalidateTitle(String title) { - Preconditions.checkNotNull(title); - - title = title.trim(); - - try { - BufferedImage image = ImageUtil.read(validDirectoryImages.get(currentIndex)); - int width = image.getWidth(); - int height = image.getHeight(); - pictureFrame.setTitle(title + space + openingBracket + width + "x" + height + closingBracket); - } catch (Exception e) { - ExceptionHandler.handle(e); - pictureFrame.setTitle(title); - } - } - - /** - * Starts the directory watcher to update the visibilities of the - * next and last buttons based on the contents of the image directory. - */ - private void startDirectoryWatcher() { - WatchDirectorySubscriber subscriber = new WatchDirectorySubscriber() { - @Override - public void onEvent(DirectoryWatcher broker, WatchDirectoryEvent event, File eventFile) { - refreshImageFiles(); - revalidateNavigationButtonVisibility(); - } - }; - subscriber.subscribeTo(WatchDirectoryEvent.FILE_ADDED, - WatchDirectoryEvent.FILE_DELETED, WatchDirectoryEvent.FILE_MODIFIED); - imageDirectoryWatcher.addSubscriber(subscriber); - imageDirectoryWatcher.startWatching(); - } - - /** - * Revalidates the visibility of the navigation buttons. - */ - private void revalidateNavigationButtonVisibility() { - refreshImageFiles(); - setNavigationButtonsVisibility(validDirectoryImages.size() > 1); - } - - /** - * Sets the visibility of the navigation buttons. - * - * @param visible the visibility of the navigation buttons - */ - private void setNavigationButtonsVisibility(boolean visible) { - nextButton.setVisible(visible); - lastButton.setVisible(visible); - } -} diff --git a/src/main/java/cyder/handlers/external/TextViewer.java b/src/main/java/cyder/handlers/external/TextViewer.java deleted file mode 100644 index 030cc0e6d..000000000 --- a/src/main/java/cyder/handlers/external/TextViewer.java +++ /dev/null @@ -1,253 +0,0 @@ -package cyder.handlers.external; - -import com.google.common.base.Preconditions; -import com.google.errorprone.annotations.CanIgnoreReturnValue; -import cyder.console.Console; -import cyder.constants.CyderColors; -import cyder.enumerations.Extension; -import cyder.files.FileUtil; -import cyder.handlers.internal.ExceptionHandler; -import cyder.layouts.CyderPartitionedLayout; -import cyder.strings.CyderStrings; -import cyder.ui.button.CyderButton; -import cyder.ui.field.CyderCaret; -import cyder.ui.field.CyderTextField; -import cyder.ui.frame.CyderFrame; -import cyder.ui.pane.CyderScrollPane; -import cyder.utils.OsUtil; - -import javax.swing.*; -import javax.swing.border.Border; -import javax.swing.border.LineBorder; -import java.awt.*; -import java.io.BufferedWriter; -import java.io.File; -import java.io.FileWriter; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Paths; - -/** - * A handler for viewing text files. - */ -public class TextViewer { - /** - * The file currently being displayed. - */ - private File file; - - /** - * The default frame width. - */ - private static final int defaultFrameWidth = 600; - - /** - * The default frame height. - */ - private static final int defaultFrameHeight = 680; - - /** - * The font for the name field. - */ - private static final Font nameFieldFont = new Font("Agency FB", Font.BOLD, 26); - - /** - * The border for the name field. - */ - private static final Border nameFieldBorder - = BorderFactory.createMatteBorder(0, 0, 4, 0, CyderColors.navy); - - /** - * The padding between the frame and the contents scrolls. - */ - private static final int scrollPadding = 25; - - /** - * The length of the scroll. - */ - private static final int scrollLength = defaultFrameWidth - 2 * scrollPadding; - - /** - * The height of the scroll. - */ - private static final int scrollHeight = scrollLength - 50; - - /** - * The save button text. - */ - private static final String SAVE = "Save"; - - /** - * The file contents area. - */ - private static JTextPane contentsArea; - - /** - * The file name field. - */ - private static CyderTextField nameField; - - /** - * The frame for this text editor. - */ - private CyderFrame textFrame; - - /** - * Returns a new text viewer instance to view the provided file. - * - * @param file the file to view - * @return a text viewer instance - */ - public static TextViewer getInstance(File file) { - return new TextViewer(file); - } - - /** - * Constructs a new text viewer with the provided file. - * - * @param file the file to display for the text file - */ - private TextViewer(File file) { - this.file = Preconditions.checkNotNull(file); - } - - /** - * Opens the text viewer gui. - * - * @return whether the gui opened successfully - */ - @CanIgnoreReturnValue - public boolean showGui() { - textFrame = new CyderFrame.Builder() - .setWidth(defaultFrameWidth) - .setHeight(defaultFrameHeight) - .build(); - - nameField = new CyderTextField(); - nameField.setFont(nameFieldFont); - nameField.setForeground(CyderColors.navy); - nameField.setCaret(new CyderCaret(CyderColors.navy)); - nameField.setBackground(CyderColors.vanilla); - nameField.setSize(300, 40); - nameField.setToolTipText("Filename"); - nameField.setBorder(nameFieldBorder); - nameField.setText(FileUtil.getFilename(file)); - - contentsArea = new JTextPane(); - contentsArea.setText(getCurrentFileContents()); - contentsArea.setSize(scrollLength, scrollHeight); - contentsArea.setBackground(CyderColors.vanilla); - contentsArea.setBorder(new LineBorder(CyderColors.navy, 5)); - contentsArea.setFocusable(true); - contentsArea.setEditable(true); - contentsArea.setSelectionColor(CyderColors.selectionColor); - contentsArea.setFont(Console.INSTANCE.generateUserFont()); - contentsArea.setForeground(CyderColors.navy); - contentsArea.setCaret(new CyderCaret(CyderColors.navy)); - contentsArea.setCaretColor(contentsArea.getForeground()); - - CyderScrollPane contentsScroll = new CyderScrollPane(contentsArea, - ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER, - ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED); - contentsScroll.setThumbColor(CyderColors.regularPink); - contentsScroll.setSize(scrollLength, scrollHeight); - contentsScroll.getViewport().setOpaque(false); - contentsScroll.setOpaque(false); - contentsScroll.setBorder(null); - contentsArea.setAutoscrolls(true); - - CyderButton saveButton = new CyderButton(SAVE); - saveButton.setSize(new Dimension(contentsScroll.getWidth(), 40)); - saveButton.addActionListener(e -> saveButtonAction()); - - CyderPartitionedLayout textLayout = new CyderPartitionedLayout(); - textLayout.spacer(2); - textLayout.addComponentMaintainSize(nameField); - textLayout.spacer(2); - textLayout.addComponentMaintainSize(contentsScroll); - textLayout.spacer(2); - textLayout.addComponentMaintainSize(saveButton); - textLayout.spacer(2); - - revalidateTitle(); - textFrame.setCyderLayout(textLayout); - textFrame.finalizeAndShow(); - - return true; - } - - /** - * Reads and returns the contents of the file. - * - * @return the contents of the file - */ - private String getCurrentFileContents() { - try { - byte[] encoded = Files.readAllBytes(Paths.get(file.getAbsolutePath())); - return new String(encoded, StandardCharsets.UTF_8); - } catch (Exception e) { - ExceptionHandler.handle(e); - } - - throw new IllegalStateException("Could not read contents of current note file"); - } - - /** - * The actions to invoke when the save button is pressed. - */ - private void saveButtonAction() { - String nameContents = nameField.getTrimmedText(); - if (nameContents.toLowerCase().endsWith(Extension.TXT.getExtension())) { - nameContents = nameContents.substring(0, nameContents.length() - Extension.TXT.getExtension().length()); - } - - String requestedName = nameContents + Extension.TXT.getExtension(); - - if (!FileUtil.isValidFilename(requestedName)) { - textFrame.notify("Invalid filename"); - return; - } - - File parent = file.getParentFile(); - File createFile = OsUtil.buildFile(parent.getAbsolutePath(), requestedName); - if (!OsUtil.createFile(createFile, true)) { - textFrame.notify("Could not create file: \"" + requestedName + CyderStrings.quote); - } - if (!OsUtil.deleteFile(file)) { - textFrame.notify("Could not update contents of file"); - } - - file = createFile; - - String contents = contentsArea.getText(); - try (BufferedWriter write = new BufferedWriter(new FileWriter(file))) { - write.write(contents); - } catch (Exception e) { - ExceptionHandler.handle(e); - } - - textFrame.notify("Saved file and contents under: \"" + requestedName + CyderStrings.quote); - } - - /** - * Revalidates the title of the frame. - */ - private void revalidateTitle() { - textFrame.setTitle(FileUtil.getFilename(file)); - } - - /** - * Saves the contents to the file. - * - * @param contents the contents to save. - */ - private void saveContentsToFile(String contents) { - Preconditions.checkNotNull(contents); - - try (BufferedWriter writer = new BufferedWriter(new FileWriter(file))) { - writer.write(contents); - } catch (Exception exception) { - ExceptionHandler.handle(exception); - } - } -} diff --git a/src/main/java/cyder/handlers/external/package-info.java b/src/main/java/cyder/handlers/external/package-info.java deleted file mode 100644 index 092939d38..000000000 --- a/src/main/java/cyder/handlers/external/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -/** - * Handlers related to how files are opened if there is a custom implemented viewer. - */ -package cyder.handlers.external; diff --git a/src/main/java/cyder/handlers/input/BaseInputHandler.java b/src/main/java/cyder/handlers/input/BaseInputHandler.java deleted file mode 100644 index 9c2fbdc33..000000000 --- a/src/main/java/cyder/handlers/input/BaseInputHandler.java +++ /dev/null @@ -1,1407 +0,0 @@ -package cyder.handlers.input; - -import com.google.common.base.Preconditions; -import com.google.common.collect.ImmutableList; -import com.google.common.reflect.ClassPath; -import cyder.annotations.ForReadability; -import cyder.annotations.Handle; -import cyder.annotations.Widget; -import cyder.audio.GeneralAudioPlayer; -import cyder.console.Console; -import cyder.constants.CyderRegexPatterns; -import cyder.enumerations.Dynamic; -import cyder.exceptions.FatalException; -import cyder.exceptions.IllegalMethodException; -import cyder.files.FileUtil; -import cyder.handlers.internal.ExceptionHandler; -import cyder.logging.LogTag; -import cyder.logging.Logger; -import cyder.props.Props; -import cyder.strings.CyderStrings; -import cyder.strings.StringUtil; -import cyder.threads.*; -import cyder.ui.pane.CyderOutputPane; -import cyder.user.UserDataManager; -import cyder.user.UserFile; -import cyder.utils.OsUtil; -import cyder.utils.ReflectionUtil; -import cyder.utils.SecurityUtil; -import cyder.utils.StaticUtil; -import cyder.youtube.YouTubeDownloadManager; -import org.apache.commons.text.similarity.JaroWinklerDistance; - -import javax.swing.*; -import javax.swing.text.ElementIterator; -import javax.swing.text.Style; -import javax.swing.text.StyleConstants; -import javax.swing.text.StyledDocument; -import java.io.*; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Optional; -import java.util.concurrent.Semaphore; -import java.util.concurrent.atomic.AtomicInteger; - -/** - * The base input handler used for linked JTextPane printing - * operations and raw user input sub-handler triggering. - */ -@SuppressWarnings("SpellCheckingInspection") /* Cyder specific words */ -public class BaseInputHandler { - /** - * The linked CyderOutputPane. - */ - private final CyderOutputPane linkedOutputPane; - - /** - * boolean describing whether to quickly append all remaining queued objects to the linked JTextPane. - */ - private boolean shouldFinishPrinting; - - /** - * The file to redirect the outputs of a command to if redirection is enabled. - */ - private File redirectionFile; - - /** - * Boolean describing whether possible command output should be redirected to the redirectionFile. - */ - private boolean redirection; - - /** - * The command that is being handled. - */ - private String command; - - /** - * The arguments of the command. - */ - private final ArrayList args = new ArrayList<>(); - - /** - * The handles which contain specific triggers for pre-determined commands. - */ - public static final ImmutableList> primaryHandlers = ImmutableList.of( - PixelationHandler.class, - GitHandler.class, - ImageHandler.class, - PlayAudioHandler.class, - ColorHandler.class, - NetworkHandler.class, - StatHandler.class, - NumberHandler.class, - ThreadHandler.class, - UiHandler.class, - FileHandler.class, - FrameMovementHandler.class, - PropHandler.class - ); - - /** - * The handles which have do not have specific triggers - * and instead perform checks and operations on the raw command. - */ - public static final ImmutableList> finalHandlers = ImmutableList.of( - GeneralPrintHandler.class, - WidgetHandler.class, - MathHandler.class, - UrlHandler.class, - UserDataHandler.class, - GuiTestHandler.class, - TestHandler.class, - WrappedCommandHandler.class - ); - - /** - * Suppress default constructor. - */ - private BaseInputHandler() { - throw new IllegalMethodException(CyderStrings.ATTEMPTED_INSTANTIATION); - } - - /** - * Constructs a new base input handler liked to the provided {@link JTextPane}. - * - * @param outputArea the JTextPane object to append content to - */ - public BaseInputHandler(JTextPane outputArea) { - Preconditions.checkNotNull(outputArea); - - this.linkedOutputPane = new CyderOutputPane(outputArea); - initializeSpecialThreads(); - clearLists(); - Logger.log(LogTag.OBJECT_CREATION, this); - } - - /** - * Releases resources acquired and initialized via the constructor. - */ - public void deactivate() { - deactivateSpecialThreads(); - killThreads(); - } - - /** - * Sets up the custom thread objects to be managed by this {@link BaseInputHandler}, that of the following: - *
    - *
  • {@link YoutubeUuidCheckerManager}
  • - *
  • {@link BletchyAnimationManager}
  • - *
- */ - private void initializeSpecialThreads() { - YoutubeUuidCheckerManager.INSTANCE.initialize(linkedOutputPane); - BletchyAnimationManager.INSTANCE.initialize(linkedOutputPane); - } - - /** - * Releases the resources initialied by the {@link #initializeSpecialThreads()} method. - */ - private void deactivateSpecialThreads() { - YoutubeUuidCheckerManager.INSTANCE.deactivate(); - BletchyAnimationManager.INSTANCE.deactivate(); - } - - /** - * Clears the console printing lists. - */ - private void clearLists() { - consolePrintingList.clear(); - consolePriorityPrintingList.clear(); - } - - /** - * The input type associated with possible handle strings. - */ - private enum InputType { - /** - * The input was user generated. - */ - USER, - - /** - * The input is the result of a similar command invocation. - */ - SIMILAR_COMMAND - } - - /** - * Handles the input and provides output if necessary to the linked JTextPane. - * - * @param op the operation that is being handled - */ - public void handle(String op) { - handle(op, InputType.USER); - } - - /** - * Handles the input and provides output if necessary to the linked JTextPane. - * - * @param op the operation that is being handled - * @param inputType the input type - */ - public final void handle(String op, InputType inputType) { - if (!handlePreliminaries(op, inputType)) return; - if (attemptRedirection()) return; - if (attemptPrimaryHandlers()) return; - if (attemptFinalHandlers()) return; - - unknownInput(); - } - - /** - * Attempts to pass the current command input to the {@link #redirectionHandler} if not null. - * - * @return whether the {@link #redirectionHandler} handled the current command input - */ - private boolean attemptRedirection() { - if (redirectionHandler != null) { - for (Method method : redirectionHandler.getMethods()) { - if (method.isAnnotationPresent(Handle.class)) { - if (method.getParameterCount() != 0) continue; - - Object invocationResult; - try { - invocationResult = method.invoke(redirectionHandler); - } catch (Exception e) { - throw new FatalException(e.getMessage()); - } - - if (invocationResult instanceof Boolean bool && bool) return true; - } - } - } - - return false; - } - - /** - * Attempts to handle the current command input using the {@link #primaryHandlers}. - * - * @return whether a primary handler handled the current command input - */ - private boolean attemptPrimaryHandlers() { - for (Class handle : primaryHandlers) { - for (Method method : handle.getMethods()) { - if (method.isAnnotationPresent(Handle.class)) { - if (method.getParameterCount() != 0) continue; - - String[] triggers = method.getAnnotation(Handle.class).value(); - for (String trigger : triggers) { - if (commandAndArgsToString().startsWith(trigger)) { - Object invocationResult; - try { - invocationResult = method.invoke(handle); - } catch (Exception e) { - throw new FatalException(e.getMessage()); - } - - if (invocationResult instanceof Boolean bool && bool) return true; - } - } - } - } - } - - return false; - } - - /** - * Attempts to handle the current command input using the {@link #finalHandlers}. - * - * @return whether a final handler handled the current command input - */ - private boolean attemptFinalHandlers() { - for (Class handle : finalHandlers) { - for (Method method : handle.getMethods()) { - if (method.isAnnotationPresent(Handle.class)) { - if (method.getParameterCount() != 0) continue; - - Object invocationResult; - try { - invocationResult = method.invoke(handle); - } catch (Exception e) { - throw new FatalException(e.getMessage()); - } - - if (invocationResult instanceof Boolean bool && bool) return true; - } - } - } - - return false; - } - - /** - * Handles preliminaries such as argument/command parsing and redirection checks - * before passing input data to the handle methods. - * - * @param command the command to handle preliminaries on before behind handled - * @param inputType the input type - * @return whether preliminary checks successfully completed - */ - private boolean handlePreliminaries(String command, InputType inputType) { - Preconditions.checkNotNull(linkedOutputPane); - Preconditions.checkNotNull(command); - this.command = command.trim(); - - resetMembers(); - - if (StringUtil.isNullOrEmpty(this.command)) { - Logger.log(LogTag.HANDLE_METHOD, "Failed preliminaries for empty/null operation"); - return false; - } - - String commandAndArgsToString = commandAndArgsToString(); - Logger.log(LogTag.CLIENT, (inputType == InputType.SIMILAR_COMMAND - ? "" : "[Similar Command]: ") + commandAndArgsToString); - - if (UserDataManager.INSTANCE.shouldFilterchat()) { - StringUtil.BlockedWordResult result = checkFoulLanguage(); - if (result.failed()) { - println("Sorry, " + UserDataManager.INSTANCE.getUsername() + ", but that language" - + " is prohibited, word: " + CyderStrings.quote + result.triggerWord() + CyderStrings.quote); - Logger.log(LogTag.HANDLE_METHOD, "Failed preliminaries due to prohibited language"); - return false; - } - } - - parseArgsFromCommand(); - redirectionCheck(); - - return true; - } - - /** - * Parses the current command into arguments and a command. - */ - private void parseArgsFromCommand() { - String[] parts = this.command.split(CyderRegexPatterns.whiteSpaceRegex); - this.command = parts[0]; - if (parts.length > 1) { - args.clear(); - args.addAll(Arrays.asList(parts).subList(1, parts.length)); - } - } - - /** - * Checks for whether the provided string contains blocked words. - * - * @return the blocked word if found - */ - private StringUtil.BlockedWordResult checkFoulLanguage() { - return StringUtil.containsBlockedWords(command, true); - } - - /** - * Resets redirection, the redirection file, and the arguments array. - */ - private void resetMembers() { - redirection = false; - redirectionFile = null; - args.clear(); - } - - /** - * The char for redirecting input to a file. - */ - private static final String REDIRECTION_CHAR = ">"; - - /** - * Checks for a requested redirection and attempts to create the file if valid. - */ - private void redirectionCheck() { - if (args.size() < 2) return; - String secondToLastArg = args.get(args.size() - 2); - if (!secondToLastArg.equalsIgnoreCase(REDIRECTION_CHAR)) return; - - String requestedFilename = args.get(args.size() - 1); - - if (!FileUtil.isValidFilename(requestedFilename)) { - onFailedRedirect("Provided filename is not valid: \"" + requestedFilename + "\""); - return; - } - - redirection = true; - - try { - redirectionLock.acquire(); - - redirectionFile = Dynamic.buildDynamic(Dynamic.USERS.getFileName(), - Console.INSTANCE.getUuid(), UserFile.FILES.getName(), requestedFilename).getAbsoluteFile(); - - if (redirectionFile.exists()) { - OsUtil.deleteFile(redirectionFile); - } - - if (!OsUtil.createFile(redirectionFile, true)) { - onFailedRedirect("Could not create redirection file: " + redirectionFile.getAbsolutePath()); - } - } catch (Exception e) { - onFailedRedirect(e.getMessage()); - } finally { - redirectionLock.release(); - } - } - - /** - * Handles a failed redirection attempt. - * - * @param errorMessage the error message to show to the user - */ - private void onFailedRedirect(String errorMessage) { - redirection = false; - redirectionFile = null; - - println("Failed to redirect output: " + errorMessage); - } - - /** - * The final handle method for if all other handle methods failed. - */ - private void unknownInput() { - SimilarCommand similarCommandObj = getSimilarCommand(command); - boolean wrapShell = UserDataManager.INSTANCE.shouldWrapShell(); - - if (similarCommandObj.command().isPresent()) { - String similarCommand = similarCommandObj.command().get(); - double tolerance = similarCommandObj.tolerance(); - if (tolerance == 1.0) return; - - if (!StringUtil.isNullOrEmpty(similarCommand)) { - Logger.log(LogTag.DEBUG, "Similar command to " + CyderStrings.quote - + command + CyderStrings.quote + " found with tolerance of " + tolerance - + ", command: " + CyderStrings.quote + similarCommand + CyderStrings.quote); - - if (!wrapShell) { - boolean autoTrigger = Props.autoTriggerSimilarCommands.getValue(); - boolean toleranceMet = tolerance >= Props.autoTriggerSimilarCommandTolerance.getValue(); - - // User may want to change configuration value if they're more error prone - if (tolerance >= Props.similarCommandTolerance.getValue()) { - if (autoTrigger && toleranceMet) { - println(UNKNOWN_COMMAND + "; Invoking similar command: " - + CyderStrings.quote + similarCommand + CyderStrings.quote); - handle(similarCommand, InputType.SIMILAR_COMMAND); - } else { - println(UNKNOWN_COMMAND + "; Most similar command: " - + CyderStrings.quote + similarCommand + CyderStrings.quote); - } - - return; - } - } - } - } - - if (wrapShell) { - CyderThreadRunner.submit(this::performWrapShell, "Unknown Input Shell Wrapper, input: " - + StringUtil.joinParts(args, CyderStrings.space)); - - } else { - println(UNKNOWN_COMMAND); - } - } - - /** - * A record representing a found similar command how close the - * found command is to the original string. - */ - private record SimilarCommand(Optional command, double tolerance) {} - - /** - * Finds the most similar command to the unrecognized one provided. - * - * @param command the user entered command to attempt to find a similar command to - * @return the most similar command to the one provided - */ - private static SimilarCommand getSimilarCommand(String command) { - Preconditions.checkNotNull(command); - Preconditions.checkArgument(!command.isEmpty()); - - String mostSimilarTrigger = ""; - float mostSimilarRatio = 0.0f; - - for (ClassPath.ClassInfo classInfo : ReflectionUtil.getCyderClasses()) { - Class clazz = classInfo.load(); - - for (Method m : clazz.getMethods()) { - ImmutableList triggers = ImmutableList.of(); - - if (m.isAnnotationPresent(Handle.class)) { - triggers = ImmutableList.copyOf(m.getAnnotation(Handle.class).value()); - } else if (m.isAnnotationPresent(Widget.class)) { - triggers = ImmutableList.copyOf(m.getAnnotation(Widget.class).triggers()); - } - - for (String trigger : triggers) { - double ratio = new JaroWinklerDistance().apply(trigger, command); - - if (ratio > mostSimilarRatio) { - mostSimilarRatio = (float) ratio; - mostSimilarTrigger = trigger; - } - } - } - } - - return new SimilarCommand(StringUtil.isNullOrEmpty(mostSimilarTrigger) - ? Optional.empty() - : Optional.of(mostSimilarTrigger), mostSimilarRatio); - } - - /** - * The actions performed when it is known that a wrap shell action should be taken. - */ - private void performWrapShell() { - println(UNKNOWN_COMMAND + ", passing to operating system native shell" + CyderStrings.space - + CyderStrings.openingParenthesis + OsUtil.getShellName() + CyderStrings.closingParenthesis); - - CyderThreadRunner.submit(() -> { - try { - Process process = createAndStartWrapShellProcess(); - - BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); - String line; - process.waitFor(); - - while ((line = reader.readLine()) != null) { - println(line); - - if (escapeWrapShell) { - process.destroy(); - break; - } - } - - escapeWrapShell = false; - } catch (Exception ignored) { - println(UNKNOWN_COMMAND); - } - }, WRAP_SHELL_THREAD_NAME); - } - - /** - * Creates and returns the process that invokes the command and args as an operating system command. - * - * @return the created process after starting - * @throws IOException if any IO errors occur when starting the process - */ - @ForReadability - private Process createAndStartWrapShellProcess() throws IOException { - ArrayList processArgs = new ArrayList<>(args); - processArgs.add(0, command); - ProcessBuilder builder = new ProcessBuilder(processArgs); - builder.redirectErrorStream(true); - return builder.start(); - } - - /** - * Used to escape the terminal wrapper. - */ - private boolean escapeWrapShell; - - /** - * The name of the thread when wrapping the shell - */ - private static final String WRAP_SHELL_THREAD_NAME = "Wrap Shell Thread"; - - /** - * The text to print for an unknown command. - */ - private static final String UNKNOWN_COMMAND = "Unknown command"; - - /** - * Returns the output area's {@link JTextPane}. - * - * @return the output area's {@link JTextPane} - */ - public final JTextPane getJTextPane() { - return linkedOutputPane.getJTextPane(); - } - - /** - * Ends any custom threads such as YouTube or bletchy - * that may have been invoked via this input handler. - */ - public final void killThreads() { - YoutubeUuidCheckerManager.INSTANCE.killAll(); - BletchyAnimationManager.INSTANCE.kill(); - } - - /** - * Semaphore for adding objects to both consolePrintingList and consolePriorityPrintingList. - */ - private final Semaphore printingListAddLock = new Semaphore(1); - - /** - * Acquires the printingListAddLock. - * This method should only be called from inside one of the printing lists overriden add method. - */ - @ForReadability - private void lockAddingToLists() { - try { - printingListAddLock.acquire(); - } catch (Exception exception) { - ExceptionHandler.handle(exception); - } - } - - /** - * Releases the printingListAddLock. - * This method should only be called from inside one of the printing lists overriden add method. - */ - @ForReadability - private void unlockAddingToLists() { - printingListAddLock.release(); - } - - /** - * The printing list for non-important outputs. - * DO NOT ADD DIRECTLY TO THIS LIST UNLESS YOU ARE A PRINT METHOD. - */ - private final ArrayList consolePrintingList = new ArrayList<>() { - @Override - public boolean add(Object e) { - lockAddingToLists(); - boolean ret = super.add(e); - startConsolePrintingAnimationIfNeeded(); - unlockAddingToLists(); - return ret; - } - }; - - /* - Note to maintainers: these lists are anonymously declared to allow for their add methods - to have additional functionality such as thread-safety via printingListAddLock. - */ - - /** - * The priority printing list for important outputs. - * DO NOT ADD DIRECTLY TO THIS LIST UNLESS YOU ARE A PRINT METHOD. - */ - private final ArrayList consolePriorityPrintingList = new ArrayList<>() { - @Override - public boolean add(Object e) { - lockAddingToLists(); - boolean ret = super.add(e); - startConsolePrintingAnimationIfNeeded(); - unlockAddingToLists(); - return ret; - } - }; - - /** - * Whether the printing animation thread is running - */ - private boolean printingAnimationRunning; - - /** - * Begins the typing animation for the Console if it has not already started. - */ - private void startConsolePrintingAnimationIfNeeded() { - if (printingAnimationRunning) return; - printingAnimationRunning = true; - CyderThreadRunner.submit(consolePrintingRunnable, IgnoreThread.ConsolePrintingAnimation.getName()); - } - - /** - * Returns whether both the printing lists are empty. - * - * @return whether both the printing lists are empty - */ - @ForReadability - private boolean listsEmpty() { - return consolePrintingList.isEmpty() && consolePriorityPrintingList.isEmpty(); - } - - /** - * The delay between updating the value of typing animation from the current user's userdata. - */ - private static final int USER_DATA_POLL_FREQUENCY_MS = 3000; - - /** - * Returns whether the typing animation should be performed. - * - * @return whether the typing animation should be performed - */ - @ForReadability - private boolean shouldDoTypingAnimation() { - return UserDataManager.INSTANCE.shouldShowTypingAnimation(); - } - - /** - * Returns whether the typing animation sound should be played. - * - * @return whether the typing animation sound should be played - */ - @ForReadability - private boolean shouldDoTypingSound() { - return UserDataManager.INSTANCE.shouldPlayTypingSound(); - } - - /** - * The console printing animation runnable. - */ - private final Runnable consolePrintingRunnable = () -> { - try { - boolean shouldDoTypingAnimation = shouldDoTypingAnimation(); - boolean shouldDoTypingSound = shouldDoTypingSound(); - long lastPollTime = System.currentTimeMillis(); - int lineTimeout = Props.printingAnimationLineTimeout.getValue(); - - while (!Console.INSTANCE.isClosed() && !listsEmpty()) { - if (System.currentTimeMillis() - lastPollTime > USER_DATA_POLL_FREQUENCY_MS) { - lastPollTime = System.currentTimeMillis(); - shouldDoTypingAnimation = shouldDoTypingAnimation(); - shouldDoTypingSound = shouldDoTypingSound(); - } - - if (!consolePriorityPrintingList.isEmpty()) { - Object line = removeAndLog(consolePriorityPrintingList); - - if (redirection) { - redirectionWrite(line); - } else { - switch (line) { - case JComponent jComponent -> insertJComponent(jComponent); - case ImageIcon imageIcon -> insertImageIcon(imageIcon); - case default -> insertAsString(line); - } - } - } else if (!consolePrintingList.isEmpty()) { - Object line = removeAndLog(consolePrintingList); - - if (redirection) { - redirectionWrite(line); - } else { - switch (line) { - case String string: - if (shouldDoTypingAnimation) { - if (shouldFinishPrinting) { - insertAsString(string); - } else { - innerPrintString(string, shouldDoTypingSound); - } - } else { - insertAsString(line); - } - break; - case JComponent jComponent: - insertJComponent(jComponent); - break; - case ImageIcon imageIcon: - insertImageIcon(imageIcon); - break; - default: - insertAsString(line); - break; - } - } - } else { - shouldFinishPrinting = false; - } - - if (!shouldFinishPrinting && shouldDoTypingAnimation) ThreadUtil.sleep(lineTimeout); - } - - printingAnimationRunning = false; - } catch (Exception e) { - ExceptionHandler.handle(e); - } - }; - - /** - * Removes, logs, and returns the first element from the provided list. - * - * @param list the list to perform the operations on - * @return the object removed from the list - */ - private Object removeAndLog(ArrayList list) { - Preconditions.checkNotNull(list); - Preconditions.checkArgument(!list.isEmpty()); - - Object ret = list.remove(0); - Logger.log(LogTag.CONSOLE_OUT, ret); - return ret; - } - - // ----------------------- - // Document insert methods - // ----------------------- - - /** - * Inserts the provided object into the current outputArea after - * invoking {@link String#valueOf(Object))} on the provided object. - * - * @param object the object to insert - */ - private void insertAsString(Object object) { - Preconditions.checkNotNull(object); - - StyledDocument document = (StyledDocument) getJTextPane().getDocument(); - - try { - document.insertString(document.getLength(), String.valueOf(object), null); - } catch (Exception e) { - ExceptionHandler.handle(e); - } - - getJTextPane().setCaretPosition(getJTextPane().getDocument().getLength()); - } - - /** - * Inserts the provided component into the current outputArea. - * - * @param component the component to insert - */ - private void insertJComponent(JComponent component) { - Preconditions.checkNotNull(component); - - String componentUuid = SecurityUtil.generateUuid(); - Style cs = getJTextPane().getStyledDocument().addStyle(componentUuid, null); - StyleConstants.setComponent(cs, component); - - try { - getJTextPane().getStyledDocument().insertString( - getJTextPane().getStyledDocument().getLength(), componentUuid, cs); - } catch (Exception e) { - ExceptionHandler.handle(e); - } - } - - /** - * Inserts the provided image icon into the current outputArea. - * - * @param imageIcon the iamge icon to insert - */ - private void insertImageIcon(ImageIcon imageIcon) { - Preconditions.checkNotNull(imageIcon); - Preconditions.checkNotNull(getJTextPane()); - - getJTextPane().insertIcon(imageIcon); - } - - // --------------------------- - // End document insert methods - // --------------------------- - - /** - * The frequency at which to play a typing sound effect if enabled. - */ - private static final int TYPING_ANIMATION_SOUND_FREQUENCY = Props.printingAnimationSoundFrequency.getValue(); - - /** - * The number of characters appended for the the current printing animation. - * Used to determine when to play a typing animation sound. - */ - private final AtomicInteger typingAnimationCharsInserted = new AtomicInteger(); - - /** - * The file for the typing sound effect. - */ - private final File typingSoundFile = StaticUtil.getStaticResource("typing.mp3"); - - /** - * Prints the string to the output area checking for - * typing sound, finish printing, and other parameters. - *

- * Note: this method is blocking and SHOULD NOT be used as a - * substitute for the default print/println methods. - * - * @param line the string to append to the output area - * @param typingSound whether the typing sound should be played - */ - private void innerPrintString(String line, boolean typingSound) { - Preconditions.checkNotNull(line); - - try { - if (!linkedOutputPane.acquireLock()) { - throw new FatalException("Failed to acquire output pane lock"); - } - - // todo sometimes this just breaks? - for (String word : line.split("((?=\\s+))")) { - String insertWord = UserDataManager.INSTANCE.isCapsMode() ? word.toUpperCase() : word; - - StyledDocument document = (StyledDocument) getJTextPane().getDocument(); - document.insertString(document.getLength(), insertWord, null); - - getJTextPane().setCaretPosition(getJTextPane().getDocument().getLength()); - - if (typingAnimationCharsInserted.get() == TYPING_ANIMATION_SOUND_FREQUENCY) { - if (!shouldFinishPrinting && typingSound) { - GeneralAudioPlayer.playAudio(typingSoundFile); - typingAnimationCharsInserted.set(0); - } - } else { - typingAnimationCharsInserted.getAndIncrement(); - } - - if (!shouldFinishPrinting) { - ThreadUtil.sleep(Props.printingAnimationWordTimeout.getValue()); - } - } - - typingAnimationCharsInserted.set(0); - } catch (Exception e) { - ExceptionHandler.handle(e); - } finally { - linkedOutputPane.releaseLock(); - } - } - - // ----------------------- - // Document entity removal - // ----------------------- - - /** - * The deafult number of elements in a document. - */ - private static final int defaultDocumentEntities = 3; - - /** - * The number of times to call {@link #removeLastElement()} from within {@link #removeLastEntity()}. - */ - private static final int removeLastElementCalls = 2; - - /** - * Removes the last entity added to the JTextPane by invoking {@link #removeLastElement()} twice due - * to a new line always being printed last. If there are other elements present after the remove, a newline - * is added back to the document. - */ - public final void removeLastEntity() { - try { - ElementIterator iterator = new ElementIterator(getJTextPane().getStyledDocument()); - int count = 0; - while (iterator.next() != null) count++; - - if (!linkedOutputPane.acquireLock()) { - throw new FatalException("Failed to acquire output pane lock"); - } - - removeLastElement(); - removeLastElement(); - if (count > defaultDocumentEntities + removeLastElementCalls) println(""); - - linkedOutputPane.releaseLock(); - } catch (Exception e) { - ExceptionHandler.handle(e); - } - } - - /** - * Returns the last line of text on the linked JTextPane. - * Text is easier to get and return as opposed to general components. - * - * @return the last line of text on the linked JTextPane - */ - public final String getLastTextLine() { - return linkedOutputPane.getStringUtil().getLastTextLine(); - } - - /** - * Removes the last line added to the linked JTextPane such as a component, image icon, string, or newline. - */ - private void removeLastElement() { - linkedOutputPane.getStringUtil().removeLastElement(); - } - - // ----------------- - // Redirection logic - // ----------------- - - /** - * The lock used to ensure output is properly written to the {@link #redirectionFile} - * This also ensures that multiple redirections aren't performed at the same time. - */ - private final Semaphore redirectionLock = new Semaphore(1); - - /** - * The error message to print if redirection fails. - */ - private static final String REDIRECTION_ERROR_MESSAGE = "Could not redirect output"; - - /** - * Writes the provided object to the redirection file instead of the JTextPane. - * - * @param object the object to invoke toString() on and write to the current redirectionFile - */ - private void redirectionWrite(Object object) { - Preconditions.checkNotNull(object); - - if (!redirectionFile.exists()) { - if (!OsUtil.createFile(redirectionFile, true)) { - println(REDIRECTION_ERROR_MESSAGE); - println(object); - return; - } - } - - try (BufferedWriter writer = new BufferedWriter(new FileWriter(redirectionFile, true))) { - redirectionLock.acquire(); - writer.write(String.valueOf(object)); - Logger.log(LogTag.CONSOLE_REDIRECTION, "Console output was redirected to: " - + redirectionFile.getAbsolutePath()); - redirectionLock.release(); - } catch (Exception e) { - ExceptionHandler.handle(e); - } - } - - /** - * The escaped string. - */ - private static final String ESCAPED = "Escaped"; - - /** - * Stops all threads invoked, sets the userInputMode to false, - * stops any audio playing, and finishes printing anything in the printing lists. - */ - public final void escapeThreads() { - killThreads(); - escapeWrapShell = true; - GeneralAudioPlayer.stopGeneralAudio(); - YouTubeDownloadManager.INSTANCE.cancelAllActiveDownloads(); - Console.INSTANCE.stopDancing(); - shouldFinishPrinting = true; - println(ESCAPED); - resetHandlers(); - } - - /** - * Resets the handle iterations and redirection handler. - */ - public void resetHandlers() { - handleIterations = 0; - redirectionHandler = null; - } - - /** - * The iteration the current handler is on. - */ - private int handleIterations = 0; - - /** - * Returns the current handle iteration. - * - * @return the current handle iteration - */ - public int getHandleIterations() { - return handleIterations; - } - - /** - * Sets the current handle iteration. - * - * @param handleIterations the current handle iteration - */ - public void setHandleIterations(int handleIterations) { - this.handleIterations = handleIterations; - } - - /** - * The current handler to send the input to. - */ - private Class redirectionHandler; - - /** - * Returns the current redirection handler. - * - * @return the current redirection handler - */ - public Class getRedirectionHandler() { - return redirectionHandler; - } - - /** - * Sets the current redirection handler. - * - * @param redirectionHandler the current redirection handler - */ - public void setRedirectionHandler(Class redirectionHandler) { - Preconditions.checkNotNull(redirectionHandler); - - this.redirectionHandler = redirectionHandler; - } - - /** - * Returns the current user issued command. - * - * @return the current user issued command - */ - public final String getCommand() { - return this.command; - } - - /** - * Determines if the current command equals the provided text ignoring case. - * - * @param compare the string to check for case-insensitive equality to command - * @return if the current command equals the provided text ignoring case - */ - protected boolean commandIs(String compare) { - Preconditions.checkNotNull(compare); - - return this.command.equalsIgnoreCase(compare); - } - - /** - * Returns whether the arguments array contains the expected number of arguments. - * For example, if the user entered "consolidate windows middle" the command is "consolidate" - * and the args are "windows" and "middle". - * - * @param expectedSize the expected size of the command arguments - * @return whether the arguments array contains the expected number of arguments - */ - protected boolean checkArgsLength(int expectedSize) { - return args.size() == expectedSize; - } - - /** - * Returns the command argument at the provided index. - * Returns null if the index is out of bounds instead of throwing. - * - * @param index the index to retrieve the command argument of - * @return the command argument at the provided index - */ - protected String getArg(int index) { - Preconditions.checkArgument(index >= 0); - Preconditions.checkArgument(index < args.size()); - - return args.get(index); - } - - /** - * Returns the size of the arguments list. - * - * @return the size of the arguments list - */ - protected int getArgsSize() { - return args.size(); - } - - /** - * Returns whether there are no args associated with the most recently issued command. - * - * @return whether there are no args associated with the most recently issued command - */ - protected boolean noArgs() { - return args.isEmpty(); - } - - /** - * Returns the arguments in String form separated by spaces. - * - * @return the arguments in String form separated by spaces - */ - protected String argsToString() { - StringBuilder sb = new StringBuilder(); - - for (int i = 0 ; i < args.size() ; i++) { - sb.append(args.get(i)); - if (i != args.size() - 1) sb.append(CyderStrings.space); - } - - return sb.toString(); - } - - /** - * Returns the original user input, that of the command followed by the arguments. - * - * @return the original user input, that of the command followed by the arguments - */ - protected String commandAndArgsToString() { - return (this.command.trim() + CyderStrings.space + argsToString()).trim(); - } - - /** - * Returns whether the provided string matches the command and - * arguments strung together with whitespace removed. - * - * @param match the string to match to - * @return whether the provided string matched the command args with whitespace removed - */ - protected boolean inputIgnoringSpacesMatches(String match) { - Preconditions.checkNotNull(match); - - return match.replaceAll(CyderRegexPatterns.whiteSpaceRegex, "").equalsIgnoreCase( - commandAndArgsToString().replaceAll(CyderRegexPatterns.whiteSpaceRegex, "")); - } - - /** - * Returns whether the current command and args to string starts with the provided string. - * - * @param startsWith the string - * @return whether the current command and args to string starts with the provided string - */ - protected boolean inputIgnoringSpacesAndCaseStartsWith(String startsWith) { - Preconditions.checkNotNull(startsWith); - - return commandAndArgsToString().replaceAll(CyderRegexPatterns.whiteSpaceRegex, "").toLowerCase() - .startsWith(startsWith.replaceAll(CyderRegexPatterns.whiteSpaceRegex, "").toLowerCase()); - } - - // ------------------------------------------- - // Utils for print methods and synchronization - // ------------------------------------------- - - /** - * Returns whether a YouTube or bletchy thread is running. - * - * @return whether a YouTube or bletchy thread is running - */ - @ForReadability - private boolean threadsActive() { - return YoutubeUuidCheckerManager.INSTANCE.hasActiveCheckers() - || BletchyAnimationManager.INSTANCE.isActive(); - } - - // --------------------- - // Generic print methods - // --------------------- - - /** - * The printing semaphore. - */ - private final Semaphore printingSemaphore = new Semaphore(1); - - /** - * Aqquires the printing lock. - */ - private void acquirePrintingLock() { - try { - printingSemaphore.acquire(); - } catch (Exception e) { - ExceptionHandler.handle(e); - } - } - - /** - * Releases the printing lock. - */ - private void releasePrintingLock() { - printingSemaphore.release(); - } - - /** - * Prints the provided type. - * - * @param type the type to print - */ - public final void print(T type) { - Preconditions.checkNotNull(type); - - if (threadsActive()) { - consolePriorityPrintingList.add(type); - } else { - consolePrintingList.add(type); - } - } - - /** - * Prints the provided type followed by a newline. - * - * @param type the type to print - */ - public final void println(T type) { - Preconditions.checkNotNull(type); - - if (threadsActive()) { - consolePriorityPrintingList.add(type); - consolePriorityPrintingList.add(CyderStrings.newline); - } else { - acquirePrintingLock(); - consolePrintingList.add(type); - consolePrintingList.add(CyderStrings.newline); - releasePrintingLock(); - } - } - - /** - * Adds the provided type to the priority printing list. - * - * @param type the type to add to the priority printing list - */ - public final void printPriority(T type) { - Preconditions.checkNotNull(type); - - consolePriorityPrintingList.add(type); - } - - /** - * Adds the provided type and a newline to the priority printing list. - * - * @param type the type to add to the priority printing list - */ - public final void printlnPriority(T type) { - Preconditions.checkNotNull(type); - - consolePriorityPrintingList.add(type); - consolePriorityPrintingList.add(CyderStrings.newline); - } - - /** - * Prints the provided String lines to the linked JTextPane. - * Note that new lines are automatically added in this so the passed - * array may be strings that do not end with new lines. - * - * @param lines the lines to print to the JTextPane - */ - public final void printlns(String[] lines) { - Arrays.stream(lines).forEach(this::println); - } - - /** - * Prints the provided String lines to the linked JTextPane. - * Note that new lines are automatically added in this so the passed - * array may be strings that do not end with new lines. - * - * @param lines the lines to print to the JTextPane - */ - public final void printlns(List lines) { - lines.forEach(this::println); - } - - // ----------------------------------------------------------------- - // Printed labels which require font, font metric, font size, - // and foreground updating as long as they are contained in the list - // ----------------------------------------------------------------- - - /** - * The list of labels appended to the Console's text pane which require updating whenever - * the following events occur: - * - *

    - *
  • User font changed
  • - *
  • User font metric changed
  • - *
  • User font size changed
  • - *
  • User foreground color changed
  • - *
- */ - private final ArrayList printedLabels = new ArrayList<>(); - - /** - * Adds the providede label to the printed labels list. This label will have its properties updated when - * the following events occurs: - * - *
    - *
  • User font changed
  • - *
  • User font metric changed
  • - *
  • User font size changed
  • - *
  • User foreground color changed
  • - *
- * - * @param label the label to update when the outlined events occur - */ - public void addPrintedLabel(JLabel label) { - Preconditions.checkNotNull(label); - Preconditions.checkArgument(!printedLabels.contains(label)); - - printedLabels.add(label); - } - - /** - * Removes the provided label from the printed labels list. This label will no longer be updated. - * - * @param label the label to remove from the list - */ - public void removePrintedLabel(JLabel label) { - Preconditions.checkNotNull(label); - Preconditions.checkArgument(printedLabels.contains(label)); - - printedLabels.remove(label); - } - - /** - * Clears the printed labels list. These labels will no longer be updated if one of the following events occurs: - * - *
    - *
  • User font changed
  • - *
  • User font metric changed
  • - *
  • User font size changed
  • - *
  • User foreground color changed
  • - *
- */ - public void clearPrintedLabels() { - printedLabels.clear(); - } - - /** - * Refreshes the following properties of all labels current in the printed labels list: - * - *
    - *
  • Foreground
  • - *
  • Font
  • - *
  • Font metric
  • - *
  • Font size
  • - *
- */ - public void refreshPrintedLabels() { - printedLabels.forEach(label -> { - label.setForeground(UserDataManager.INSTANCE.getForegroundColor()); - label.setFont(Console.INSTANCE.generateUserFont()); - }); - } -} \ No newline at end of file diff --git a/src/main/java/cyder/handlers/input/ColorHandler.java b/src/main/java/cyder/handlers/input/ColorHandler.java deleted file mode 100644 index 3ea4c146e..000000000 --- a/src/main/java/cyder/handlers/input/ColorHandler.java +++ /dev/null @@ -1,121 +0,0 @@ -package cyder.handlers.input; - -import cyder.annotations.Handle; -import cyder.console.Console; -import cyder.constants.CyderColors; -import cyder.enumerations.Dynamic; -import cyder.enumerations.Extension; -import cyder.exceptions.IllegalMethodException; -import cyder.handlers.internal.ExceptionHandler; -import cyder.strings.CyderStrings; -import cyder.ui.field.CyderCaret; -import cyder.user.UserData; -import cyder.user.UserDataManager; -import cyder.user.UserFile; -import cyder.utils.ColorUtil; -import cyder.utils.ImageUtil; - -import javax.imageio.ImageIO; -import java.awt.*; -import java.awt.image.BufferedImage; -import java.io.File; - -/** - * A handler for commands which change color/font throughout Cyder. - */ -public class ColorHandler extends InputHandler { - /** - * Suppress default constructor - */ - private ColorHandler() { - throw new IllegalMethodException(CyderStrings.ATTEMPTED_INSTANTIATION); - } - - @Handle({"backgroundcolor", "fix foreground", "foreground", "repaint"}) - public static boolean handle() { - boolean ret = true; - - if (getInputHandler().inputIgnoringSpacesAndCaseStartsWith("backgroundcolor")) { - if (getInputHandler().checkArgsLength(1)) { - try { - int w = Console.INSTANCE.getConsoleCyderFrame().getWidth(); - int h = Console.INSTANCE.getConsoleCyderFrame().getHeight(); - - if (UserDataManager.INSTANCE.isFullscreen()) { - Rectangle monitorBounds = Console.INSTANCE.getConsoleCyderFrame() - .getMonitorBounds().getBounds(); - - w = (int) monitorBounds.getWidth(); - h = (int) monitorBounds.getHeight(); - } - - String colorString = getInputHandler().getArg(0); - Color color = ColorUtil.hexStringToColor(colorString); - BufferedImage saveImage = ImageUtil.bufferedImageFromColor(color, w, h); - - String saveName = "Solid_" + getInputHandler().getArg(0) - + "_Background" + Extension.PNG.getExtension(); - - File saveFile = Dynamic.buildDynamic(Dynamic.USERS.getFileName(), - Console.INSTANCE.getUuid(), UserFile.BACKGROUNDS.getName(), saveName); - - ImageIO.write(saveImage, Extension.PNG.getExtensionWithoutPeriod(), saveFile); - - getInputHandler().println("Background generated, set, and saved as a separate background file."); - - Console.INSTANCE.setBackgroundFile(saveFile); - } catch (Exception ignored) { - getInputHandler().println("Background color command usage: backgroundcolor EC407A"); - } - } else { - getInputHandler().println("Background color command usage: backgroundcolor EC407A"); - } - } else if (getInputHandler().inputIgnoringSpacesMatches("fixforeground")) { - try { - Color backgroundDominantColor = ColorUtil.getDominantColor(ImageUtil.read( - Console.INSTANCE.getCurrentBackground().getReferenceFile())); - - if (shouldUseLightColor(backgroundDominantColor)) { - Console.INSTANCE.getOutputArea().setForeground(CyderColors.defaultLightModeTextColor); - Console.INSTANCE.getInputField().setForeground(CyderColors.defaultLightModeTextColor); - Console.INSTANCE.getInputField().setCaretColor(CyderColors.defaultLightModeTextColor); - Console.INSTANCE.getInputField() - .setCaret(new CyderCaret(CyderColors.defaultLightModeTextColor)); - UserDataManager.INSTANCE.setForegroundColor(CyderColors.defaultLightModeTextColor); - } else { - Console.INSTANCE.getOutputArea().setForeground(CyderColors.defaultDarkModeTextColor); - Console.INSTANCE.getInputField().setForeground(CyderColors.defaultDarkModeTextColor); - Console.INSTANCE.getInputField().setCaretColor(CyderColors.defaultDarkModeTextColor); - Console.INSTANCE.getInputField() - .setCaret(new CyderCaret(CyderColors.defaultDarkModeTextColor)); - UserDataManager.INSTANCE.setForegroundColor(CyderColors.defaultDarkModeTextColor); - } - - UserData.foregroundColor.getOnChangeRunnable().ifPresent(Runnable::run); - getInputHandler().println("Foreground fixed"); - } catch (Exception e) { - ExceptionHandler.handle(e); - ret = false; - } - } else if (getInputHandler().commandIs("repaint")) { - Console.INSTANCE.revalidate(false, false); - getInputHandler().println("Console repainted"); - } else { - ret = false; - } - - return ret; - } - - /** - * Returns whether the text color to be layered over the - * provided background color should be a light mode color. - * - * @param backgroundColor the background color to find a suitable text color for - * @return whether the text color should be a light mode color - */ - private static boolean shouldUseLightColor(Color backgroundColor) { - return (backgroundColor.getRed() * 0.299 + backgroundColor.getGreen() - * 0.587 + backgroundColor.getBlue() * 0.114) > 186; - } -} diff --git a/src/main/java/cyder/handlers/input/FileHandler.java b/src/main/java/cyder/handlers/input/FileHandler.java deleted file mode 100644 index ca19c251e..000000000 --- a/src/main/java/cyder/handlers/input/FileHandler.java +++ /dev/null @@ -1,103 +0,0 @@ -package cyder.handlers.input; - -import cyder.annotations.Handle; -import cyder.console.Console; -import cyder.enumerations.Dynamic; -import cyder.exceptions.IllegalMethodException; -import cyder.files.DosAttribute; -import cyder.files.FileUtil; -import cyder.logging.Logger; -import cyder.strings.CyderStrings; -import cyder.utils.OsUtil; -import cyder.utils.SpotlightUtil; - -import java.io.File; -import java.util.Arrays; - -/** - * A handler related to files and manipulation of them. - */ -public class FileHandler extends InputHandler { - /** - * Suppress default constructor. - */ - private FileHandler() { - throw new IllegalMethodException(CyderStrings.ATTEMPTED_INSTANTIATION); - } - - @Handle({"wipe logs", "open current log", "open last log", "wipe", "cmd", "dos attributes"}) - public static boolean handle() { - boolean ret = true; - - if (getInputHandler().inputIgnoringSpacesMatches("wipe logs")) { - OsUtil.deleteFile(Dynamic.buildDynamic(Dynamic.LOGS.getFileName())); - getInputHandler().println("Logs wiped"); - } else if (getInputHandler().inputIgnoringSpacesMatches("open current log")) { - FileUtil.openResourceUsingNativeProgram(Logger.getCurrentLogFile().getAbsolutePath()); - } else if (getInputHandler().inputIgnoringSpacesMatches("open last log")) { - File[] logs = Logger.getCurrentLogFile().getParentFile().listFiles(); - - if (logs != null) { - if (logs.length == 1) { - getInputHandler().println("No previous logs found"); - } else if (logs.length > 1) { - FileUtil.openResourceUsingNativeProgram(logs[logs.length - 2].getAbsolutePath()); - } - } - } else if (getInputHandler().inputIgnoringSpacesMatches("wipe spot lights")) { - SpotlightUtil.wipeSpotlights(); - } else if (getInputHandler().commandIs("wipe")) { - if (getInputHandler().checkArgsLength(1)) { - File requestedDeleteFile = Dynamic.buildDynamic(Dynamic.USERS.getFileName(), - Console.INSTANCE.getUuid(), getInputHandler().getArg(0)); - if (requestedDeleteFile.exists()) { - if (requestedDeleteFile.isDirectory()) { - if (OsUtil.deleteFile(requestedDeleteFile)) { - getInputHandler().println("Successfully deleted: " - + requestedDeleteFile.getAbsolutePath()); - } else { - getInputHandler().println("Could not delete folder at this time"); - } - } else if (requestedDeleteFile.isFile()) { - if (OsUtil.deleteFile(requestedDeleteFile)) { - getInputHandler().println("Successfully deleted " - + requestedDeleteFile.getAbsolutePath()); - } else { - getInputHandler().println("Unable to delete file at this time"); - } - } else { - throw new IllegalStateException( - "File is not a file nor directory. " + CyderStrings.EUROPEAN_TOY_MAKER); - } - } else { - getInputHandler().println("Requested file does not exist: " - + requestedDeleteFile.getAbsolutePath()); - } - } else { - getInputHandler().print("Wipe command usage: wipe [directory/file within your user directory]"); - } - } else if (getInputHandler().commandIs("cmd")) { - OsUtil.openShell(); - } else if (getInputHandler().inputIgnoringSpacesAndCaseStartsWith("dos attributes")) { - if (getInputHandler().checkArgsLength(2)) { - File file = new File(getInputHandler().getArg(1)); - if (file.exists()) { - getInputHandler().println("DOS attributes for \"" + FileUtil.getFilename(file) + "\""); - getInputHandler().println("------------------------"); - Arrays.stream(DosAttribute.values()).forEach(dosAttribute -> - getInputHandler().println(dosAttribute.getMethodName() + ": " - + DosAttribute.getAttribute(file, dosAttribute))); - } else { - getInputHandler().println("Provided file does not exist, absolute path: " + file.getAbsolutePath()); - getInputHandler().print("Cwd: " + new File(".").getAbsolutePath()); - } - } else { - getInputHandler().println("DOS attributes command usage: dos attributes path/to/my/file.txt"); - } - } else { - ret = false; - } - - return ret; - } -} diff --git a/src/main/java/cyder/handlers/input/FrameMovementHandler.java b/src/main/java/cyder/handlers/input/FrameMovementHandler.java deleted file mode 100644 index 944e5a553..000000000 --- a/src/main/java/cyder/handlers/input/FrameMovementHandler.java +++ /dev/null @@ -1,155 +0,0 @@ -package cyder.handlers.input; - -import cyder.annotations.Handle; -import cyder.console.Console; -import cyder.exceptions.IllegalMethodException; -import cyder.strings.CyderStrings; -import cyder.ui.UiConstants; -import cyder.ui.UiUtil; -import cyder.ui.frame.CyderFrame; -import cyder.ui.frame.enumerations.ScreenPosition; - -import java.awt.*; - -/** - * Handles CyderFrame and Console movement commands. - */ -public class FrameMovementHandler extends InputHandler { - /** - * Suppress default constructor. - */ - private FrameMovementHandler() { - throw new IllegalMethodException(CyderStrings.ATTEMPTED_INSTANTIATION); - } - - /** - * The degrees to rotate the console pane by when making a frame askew. - */ - private static final int ASKEW_DEGREE = 5; - - @Handle({"top left", "top right", "bottom left", "bottom right", - "consolidate windows", "dance", "hide", "askew", "barrel roll", "middle", "center"}) - public static boolean handle() { - boolean ret = true; - - if (getInputHandler().inputIgnoringSpacesMatches("topleft")) { - Console.INSTANCE.setLocationOnScreen(ScreenPosition.TRUE_TOP_LEFT); - } else if (getInputHandler().inputIgnoringSpacesMatches("topright")) { - Console.INSTANCE.setLocationOnScreen(ScreenPosition.TRUE_TOP_RIGHT); - } else if (getInputHandler().inputIgnoringSpacesMatches("bottomleft")) { - Console.INSTANCE.setLocationOnScreen(ScreenPosition.TRUE_BOTTOM_LEFT); - } else if (getInputHandler().inputIgnoringSpacesMatches("bottomright")) { - Console.INSTANCE.setLocationOnScreen(ScreenPosition.TRUE_BOTTOM_RIGHT); - } else if (getInputHandler().inputIgnoringSpacesMatches("middle") - || getInputHandler().inputIgnoringSpacesMatches("center")) { - Console.INSTANCE.setLocationOnScreen(ScreenPosition.TRUE_CENTER); - } else if (getInputHandler().inputIgnoringSpacesMatches("frame titles")) { - for (Frame f : UiUtil.getFrames()) { - getInputHandler().println(f.getTitle()); - } - } else if (getInputHandler().commandIs("consolidate") - && getInputHandler().getArg(0).equalsIgnoreCase("windows")) { - if (getInputHandler().checkArgsLength(3)) { - if (getInputHandler().getArg(1).equalsIgnoreCase("top") - && getInputHandler().getArg(2).equalsIgnoreCase("right")) { - for (CyderFrame f : UiUtil.getCyderFrames()) { - if (f.getState() == UiConstants.FRAME_ICONIFIED) { - f.setState(UiConstants.FRAME_NORMAL); - } - - int anchorX = Console.INSTANCE.getConsoleCyderFrame().getX() - + Console.INSTANCE.getConsoleCyderFrame().getWidth() - - f.getWidth(); - int anchorY = Console.INSTANCE.getConsoleCyderFrame().getY(); - - f.setRestorePoint(new Point(anchorX, anchorY)); - f.setLocation(anchorX, anchorY); - } - } else if (getInputHandler().getArg(1).equalsIgnoreCase("bottom") - && getInputHandler().getArg(2).equalsIgnoreCase("right")) { - for (CyderFrame f : UiUtil.getCyderFrames()) { - if (f.getState() == UiConstants.FRAME_ICONIFIED) { - f.setState(UiConstants.FRAME_NORMAL); - } - - int anchorX = Console.INSTANCE.getConsoleCyderFrame().getX() - + Console.INSTANCE.getConsoleCyderFrame().getWidth() - - f.getWidth(); - int anchorY = Console.INSTANCE.getConsoleCyderFrame().getY() - + Console.INSTANCE.getConsoleCyderFrame().getHeight() - - f.getHeight(); - - f.setRestorePoint(new Point(anchorX, anchorY)); - f.setLocation(anchorX, anchorY); - } - } else if (getInputHandler().getArg(1).equalsIgnoreCase("bottom") - && getInputHandler().getArg(2).equalsIgnoreCase("left")) { - for (CyderFrame f : UiUtil.getCyderFrames()) { - if (f.getState() == UiConstants.FRAME_ICONIFIED) { - f.setState(UiConstants.FRAME_NORMAL); - } - - int anchorX = Console.INSTANCE.getConsoleCyderFrame().getX(); - int anchorY = Console.INSTANCE.getConsoleCyderFrame().getY() - + Console.INSTANCE.getConsoleCyderFrame().getHeight() - - f.getHeight(); - - f.setRestorePoint(new Point(anchorX, anchorY)); - f.setLocation(anchorX, anchorY); - } - } else if (getInputHandler().getArg(1).equalsIgnoreCase("top") - && getInputHandler().getArg(2).equalsIgnoreCase("left")) { - for (CyderFrame f : UiUtil.getCyderFrames()) { - if (f.getState() == UiConstants.FRAME_ICONIFIED) { - f.setState(UiConstants.FRAME_NORMAL); - } - - int anchorX = Console.INSTANCE.getConsoleCyderFrame().getX(); - int anchorY = Console.INSTANCE.getConsoleCyderFrame().getY(); - - f.setRestorePoint(new Point(anchorX, anchorY)); - f.setLocation(anchorX, anchorY); - } - } else { - getInputHandler().println("Command usage: consolidate windows top left"); - } - } else if (getInputHandler().checkArgsLength(2) - && (getInputHandler().getArg(1).equalsIgnoreCase("center") - || getInputHandler().getArg(1).equalsIgnoreCase("middle"))) { - Point consoleCenter = Console.INSTANCE.getConsoleCyderFrame().getCenterPointOnScreen(); - int x = (int) consoleCenter.getX(); - int y = (int) consoleCenter.getY(); - - for (CyderFrame f : UiUtil.getCyderFrames()) { - if (f == Console.INSTANCE.getConsoleCyderFrame()) { - continue; - } - - if (f.getState() == UiConstants.FRAME_ICONIFIED) { - f.setState(UiConstants.FRAME_NORMAL); - } - - int anchorX = x - f.getWidth() / 2; - int anchorY = y - f.getHeight() / 2; - - f.setRestorePoint(new Point(anchorX, anchorY)); - f.setLocation(anchorX, anchorY); - } - } else { - getInputHandler().println("Command usage: consolidate windows top left"); - } - } else if (getInputHandler().commandIs("dance")) { - Console.INSTANCE.dance(); - } else if (getInputHandler().commandIs("hide")) { - Console.INSTANCE.getConsoleCyderFrame().minimizeAndIconify(); - } else if (getInputHandler().inputIgnoringSpacesMatches("barrelroll")) { - Console.INSTANCE.barrelRoll(); - } else if (getInputHandler().commandIs("askew")) { - Console.INSTANCE.getConsoleCyderFrame().rotateBackground(ASKEW_DEGREE); - } else { - ret = false; - } - - return ret; - } -} diff --git a/src/main/java/cyder/handlers/input/GeneralPrintHandler.java b/src/main/java/cyder/handlers/input/GeneralPrintHandler.java deleted file mode 100644 index a59f09055..000000000 --- a/src/main/java/cyder/handlers/input/GeneralPrintHandler.java +++ /dev/null @@ -1,274 +0,0 @@ -package cyder.handlers.input; - -import cyder.annotations.Handle; -import cyder.console.Console; -import cyder.constants.CyderRegexPatterns; -import cyder.enumerations.Suggestion; -import cyder.exceptions.IllegalMethodException; -import cyder.handlers.internal.ExceptionHandler; -import cyder.logging.LogTag; -import cyder.logging.Logger; -import cyder.math.NumberUtil; -import cyder.network.IpDataManager; -import cyder.parsers.ip.IpData; -import cyder.strings.CyderStrings; -import cyder.strings.StringUtil; -import cyder.threads.BletchyAnimationManager; -import cyder.threads.CyderThreadRunner; -import cyder.time.TimeUtil; -import cyder.user.UserDataManager; -import cyder.utils.AstronomyUtil; -import cyder.utils.ImageUtil; -import cyder.utils.OsUtil; - -import javax.swing.*; -import java.io.IOException; -import java.security.SecureRandom; -import java.time.DayOfWeek; -import java.time.LocalDate; -import java.time.temporal.TemporalAdjusters; -import java.util.Calendar; -import java.util.Optional; - -/** - * A handler for printing out general response strings. - */ -public class GeneralPrintHandler extends InputHandler { - /** - * Suppress default constructor. - */ - private GeneralPrintHandler() { - throw new IllegalMethodException(CyderStrings.ATTEMPTED_INSTANTIATION); - } - - @Handle - public static boolean handle() { - boolean ret = true; - - if (getInputHandler().commandIs("shakespeare")) { - if (NumberUtil.generateRandomInt(0, 1) == 1) { - getInputHandler().println("Glamis hath murdered sleep, " - + "and therefore Cawdor shall sleep no more, Macbeth shall sleep no more."); - } else { - getInputHandler().println("To be, or not to be, that is the question: Whether 'tis nobler in " - + "the mind to suffer the slings and arrows of " - + "outrageous fortune, or to take arms against a sea of troubles and by opposing end them."); - } - } else if (getInputHandler().commandIs("asdf")) { - getInputHandler().println("Who is the spiciest meme lord?"); - } else if (getInputHandler().commandIs("thor")) { - getInputHandler().println("Piss off, ghost."); - } else if (getInputHandler().commandIs("alextrebek")) { - getInputHandler().println("Do you mean who is alex trebek?"); - } else if (StringUtil.isPalindrome(getInputHandler() - .getCommand().replaceAll(CyderRegexPatterns.whiteSpaceRegex, "")) - && getInputHandler().getCommand().length() > 3) { - getInputHandler().println("Nice palindrome."); - } else if (getInputHandler().commandIs("coinflip")) { - double randGauss = new SecureRandom().nextGaussian(); - if (randGauss <= 0.0001) { - getInputHandler().println("You're not going to believe this, but it landed on its side."); - } else if (randGauss <= 0.5) { - getInputHandler().println("It's Heads!"); - } else { - getInputHandler().println("It's Tails!"); - } - } else if (getInputHandler().commandIs("hello") - || getInputHandler().commandIs("hi")) { - switch (NumberUtil.generateRandomInt(1, 7)) { - case 1: - getInputHandler().println("Hello, " + UserDataManager.INSTANCE.getUsername() + "."); - break; - case 2: - if (TimeUtil.isEvening()) { - getInputHandler().println("Good evening, " - + UserDataManager.INSTANCE.getUsername() + ". How can I help?"); - } else if (TimeUtil.isMorning()) { - getInputHandler().println("Good morning, " - + UserDataManager.INSTANCE.getUsername() + ". How can I help?"); - } else { - getInputHandler().println("Good afternoon, " - + UserDataManager.INSTANCE.getUsername() + ". How can I help?"); - } - break; - case 3: - getInputHandler().println("What's up, " + UserDataManager.INSTANCE.getUsername() + "?"); - break; - case 4: - getInputHandler().println("How are you doing, " + UserDataManager.INSTANCE.getUsername() + "?"); - break; - case 5: - getInputHandler().println("Greetings, " + UserDataManager.INSTANCE.getUsername() + "."); - break; - case 6: - getInputHandler().println("I'm here...."); - break; - case 7: - getInputHandler().println("Go ahead..."); - break; - } - } else if (getInputHandler().commandIs("bye")) { - getInputHandler().println("Just say you won't let go."); - } else if (getInputHandler().commandIs("time")) { - getInputHandler().println(TimeUtil.weatherTime()); - } else if (getInputHandler().commandIs("lol")) { - getInputHandler().println("My memes are better."); - } else if (getInputHandler().commandIs("thanks")) { - getInputHandler().println("You're welcome."); - } else if (getInputHandler().commandIs("name")) { - getInputHandler().println("My name is Cyder. I am a tool built by" - + " Nathan Cheshire for programmers and advanced users."); - } else if (getInputHandler().commandIs("k")) { - getInputHandler().println("Fun Fact: the letter \"K\" comes from the Greek letter kappa, which was taken " - + "from the Semitic kap, the symbol for an open hand. It is this very hand which " - + "will be slapping you in the face for saying \"k\" to me."); - } else if (getInputHandler().commandIs("no")) { - getInputHandler().println("Yes"); - } else if (getInputHandler().commandIs("nope")) { - getInputHandler().println("yep"); - } else if (getInputHandler().commandIs("yes")) { - getInputHandler().println("no"); - } else if (getInputHandler().commandIs("yep")) { - getInputHandler().println("nope"); - } else if (getInputHandler().commandIs("jarvis")) { - getInputHandler().println("*scoffs in Java* primitive loser AI"); - } else if (getInputHandler().commandIs("thanksgiving")) { - int year = Calendar.getInstance().get(Calendar.YEAR); - LocalDate RealTG = LocalDate.of(year, 11, 1) - .with(TemporalAdjusters.dayOfWeekInMonth(4, DayOfWeek.THURSDAY)); - getInputHandler().println("Thanksgiving this year is on the " + RealTG.getDayOfMonth() + " of November."); - } else if (getInputHandler().commandIs("fibonacci")) { - for (long i : NumberUtil.computeFibonacci(0, 1, 100)) - getInputHandler().println(i); - } else if (getInputHandler().commandIs("break;")) { - getInputHandler().println("Thankfully my pure console based infinite while loop days are over. <3 Nathan"); - } else if (getInputHandler().commandIs("why")) { - getInputHandler().println("Why not?"); - } else if (getInputHandler().commandIs("why not")) { - getInputHandler().println("Why?"); - } else if (getInputHandler().commandIs("groovy")) { - getInputHandler().println("Kotlin is the best JVM lang.... I mean, Java is obviously the best!"); - } else if (getInputHandler().commandIs("&&")) { - getInputHandler().println("||"); - } else if (getInputHandler().commandIs("||")) { - getInputHandler().println("&&"); - } else if (getInputHandler().commandIs("&")) { - getInputHandler().println("|"); - } else if (getInputHandler().commandIs("|")) { - getInputHandler().println("&"); - } else if (getInputHandler().commandIs("espanol")) { - getInputHandler().println("Tu hablas Espanol? Yo estudio Espanol mas-o-menos. Hay tu mi amigo?"); - } else if (getInputHandler().commandIs("look")) { - getInputHandler().println("L()()K ---->> !FREE STUFF! <<---- L()()K"); - } else if (getInputHandler().commandIs("cyder")) { - getInputHandler().println("That's my name, don't wear it out pls"); - } else if (getInputHandler().commandIs("home")) { - getInputHandler().println("There's no place like localhost/127.0.0.1"); - } else if (getInputHandler().commandIs("love")) { - getInputHandler().println("Sorry, " + UserDataManager.INSTANCE.getUsername() + - ", but I don't understand human emotions or affections."); - } else if (getInputHandler().commandIs("loop")) { - getInputHandler().println("InputHandler.handle(\"loop\", true);"); - } else if (getInputHandler().commandIs("story")) { - getInputHandler().println("It was a lazy day. Cyder was enjoying a deep sleep when suddenly " - + UserDataManager.INSTANCE.getUsername() + " started talking to Cyder." - + " It was at this moment that Cyder knew its day had been ruined."); - } else if (getInputHandler().commandIs("i hate you")) { - getInputHandler().println("That's not very nice."); - } else if (getInputHandler().commandIs("easter")) { - getInputHandler().println("Easter Sunday is on " + TimeUtil.getEasterSundayString()); - } else if (getInputHandler().commandIs("age")) { - BletchyAnimationManager.INSTANCE.bletchy("I am somewhere between 69 and 420 years old.", - true, 50, false); - } else if (getInputHandler().commandIs("scrub")) { - BletchyAnimationManager.INSTANCE.bletchy( - "No you!", false, 50, true); - } else if (getInputHandler().commandIs("bletchy")) { - BletchyAnimationManager.INSTANCE.bletchy( - getInputHandler().argsToString(), false, 50, true); - } else if (getInputHandler().commandIs("dst")) { - CyderThreadRunner.submit(() -> { - IpData data = IpDataManager.INSTANCE.getIpData(); - - String location = data.getCity() + ", " + data.getRegion() + ", " + data.getCountry_name(); - if (data.getTime_zone().isIs_dst()) { - getInputHandler().println("DST is underway in " + location + "."); - } else { - getInputHandler().println("DST is not underway in " + location + "."); - } - }, "DST Checker"); - } else if ((getInputHandler().commandAndArgsToString() - .replaceAll(CyderRegexPatterns.whiteSpaceRegex, "").startsWith("longword"))) { - for (int i = 0 ; i < getInputHandler().getArgsSize() ; i++) { - getInputHandler().print("pneumonoultramicroscopicsilicovolcanoconiosis"); - } - - getInputHandler().println(""); - } else if (getInputHandler().commandIs("pwd")) { - getInputHandler().println(OsUtil.USER_DIR); - } else if (getInputHandler().commandIs("whoami")) { - getInputHandler().println(OsUtil.getComputerName() + OsUtil.FILE_SEP - + StringUtil.capsFirstWords(UserDataManager.INSTANCE.getUsername())); - } else if (getInputHandler().commandIs("jarmode")) { - getInputHandler().println(OsUtil.JAR_MODE ? "Cyder is currently running from a JAR" - : "Cyder is currently running from a non-JAR source"); - } else if (getInputHandler().commandIs("clc") || - getInputHandler().commandIs("cls") || - getInputHandler().inputIgnoringSpacesMatches("clear")) { - Console.INSTANCE.getOutputArea().setText(""); - } else if (getInputHandler().commandIs("throw")) { - ExceptionHandler.handle(new Exception("Big boi exceptions; " + - "\"I chase your love around figure 8, I need you more than I can take\"")); - } else if (getInputHandler().commandIs("clearops")) { - Console.INSTANCE.clearCommandHistory(); - Logger.log(LogTag.HANDLE_METHOD, "User cleared command history"); - getInputHandler().println("Command history reset"); - } else if (getInputHandler().commandIs("anagram")) { - if (getInputHandler().checkArgsLength(2)) { - if (StringUtil.areAnagrams(getInputHandler().getArg(0), getInputHandler().getArg(1))) { - getInputHandler().println(getInputHandler().getArg(0) + " and " - + getInputHandler().getArg(1) + " are anagrams of each other"); - } else { - getInputHandler().println(getInputHandler().getArg(0) + " and " - + getInputHandler().getArg(1) + " are not anagrams of each other"); - } - } else { - getInputHandler().println("Anagram usage: anagram word1 word2"); - } - } else if (getInputHandler().commandIs("help")) { - getInputHandler().println("Try typing: "); - - for (Suggestion suggestion : Suggestion.values()) { - getInputHandler().println(CyderStrings.BULLET_POINT + "\t" + suggestion.getCommand() - + "\n\tDescription: " + suggestion.getDescription()); - } - } else if (getInputHandler().commandAndArgsToString().matches(".*tell.*joke.*")) { - getInputHandler().println("Knock Knock\nRace condition\nWho's there?"); - } else if (getInputHandler().commandIs("echo") - || getInputHandler().commandIs("print") - || getInputHandler().commandIs("println")) { - getInputHandler().println(getInputHandler().argsToString()); - } else if (getInputHandler().commandIs("moon")) { - Optional currentMoonPhaseOptional = AstronomyUtil.getCurrentMoonPhase(); - - if (currentMoonPhaseOptional.isPresent()) { - AstronomyUtil.MoonPhase currentMoonPhase = currentMoonPhaseOptional.get(); - - try { - getInputHandler().println(new ImageIcon(ImageUtil.read(currentMoonPhase.imageUrl()))); - } catch (IOException e) { - ExceptionHandler.handle(e); - } - - getInputHandler().println("Moon phase: " + currentMoonPhase.phase()); - getInputHandler().println("Illumination: " + currentMoonPhase.illumination() + "%"); - } else { - getInputHandler().print("Could not find current moon phase"); - } - } else { - ret = false; - } - - return ret; - } -} diff --git a/src/main/java/cyder/handlers/input/GitHandler.java b/src/main/java/cyder/handlers/input/GitHandler.java deleted file mode 100644 index 8eef4706d..000000000 --- a/src/main/java/cyder/handlers/input/GitHandler.java +++ /dev/null @@ -1,189 +0,0 @@ -package cyder.handlers.input; - -import com.google.common.collect.ImmutableList; -import cyder.annotations.ForReadability; -import cyder.annotations.Handle; -import cyder.constants.CyderUrls; -import cyder.exceptions.IllegalMethodException; -import cyder.github.GitHubUtil; -import cyder.github.parsers.Issue; -import cyder.handlers.internal.ExceptionHandler; -import cyder.network.NetworkUtil; -import cyder.process.ProcessUtil; -import cyder.strings.CyderStrings; -import cyder.strings.StringUtil; -import cyder.threads.CyderThreadRunner; -import cyder.user.UserFile; -import cyder.user.UserUtil; -import cyder.utils.OsUtil; - -import java.util.Map; -import java.util.concurrent.Future; - -import static cyder.strings.CyderStrings.quote; -import static cyder.strings.CyderStrings.space; - -/** - * A handler for commands and inputs related to git/github/gitlab. - */ -public class GitHandler extends InputHandler { - /** - * The git command. - */ - private static final String GIT = "git"; - - /** - * The git clone command. - */ - private static final String GIT_CLONE = "git clone"; - - /** - * The issue string separator. - */ - private static final String issueSeparator = "----------------------------------------"; - - /** - * The name of the github issue printer thread. - */ - private static final String GITHUB_ISSUE_PRINTER_THREAD_NAME = "Cyder GitHub Issue Printer"; - - /** - * Suppress default constructor. - */ - private GitHandler() { - throw new IllegalMethodException(CyderStrings.ATTEMPTED_INSTANTIATION); - } - - @Handle({"gitme", "github", "issues", "git clone", "languages"}) - public static boolean handle() { - boolean ret = true; - - if (getInputHandler().commandIs("gitme")) { - gitme(); - } else if (getInputHandler().commandIs("github")) { - NetworkUtil.openUrl(CyderUrls.CYDER_SOURCE); - } else if (getInputHandler().commandIs("issues")) { - printIssues(); - } else if (getInputHandler().inputIgnoringSpacesAndCaseStartsWith(GIT_CLONE)) { - cloneRepo(); - } else if (getInputHandler().commandIs("languages")) { - printLanguagesUsedByCyder(); - } else { - ret = false; - } - - return ret; - } - - @ForReadability - private static void printLanguagesUsedByCyder() { - Map map = GitHubUtil.getLanguages(); - - getInputHandler().println("Cyder uses the following languages:"); - for (Map.Entry entry : map.entrySet()) { - getInputHandler().println(entry.getKey() + " takes up " + OsUtil.formatBytes(entry.getValue())); - } - } - - @ForReadability - private static void cloneRepo() { - String repo = getInputHandler().commandAndArgsToString().substring(GIT_CLONE.length()).trim(); - if (repo.isEmpty()) { - getInputHandler().println("Git clone usage: git clone [repository remote link]"); - return; - } - - String threadName = "Git Cloner, repo: " + repo; - CyderThreadRunner.submit(() -> { - try { - Future futureCloned = GitHubUtil.cloneRepoToDirectory(repo, - UserUtil.getUserFile(UserFile.FILES)); - - while (!futureCloned.isDone()) Thread.onSpinWait(); - boolean cloned = futureCloned.get(); - if (cloned) { - getInputHandler().println("Clone successfully finished"); - } else { - getInputHandler().println("Clone failed"); - } - } catch (Exception e) { - ExceptionHandler.handle(e); - } - }, threadName); - } - - /** - * Generates and returns the commands to send to a process - * builder to perform a git add for all files in the current directory. - * - * @return the commands for a git add - */ - private static String[] generateGitAddCommand() { - return new String[]{GIT, "add", "."}; - } - - /** - * Generates and returns the command to send a process builder to perform - * a git push for all local yet not pushed commits. - * Note this assumes the remote branch currently being tracked is named "main". - * - * @return the commands for a git push - */ - private static String[] generateGitPushCommand() { - return new String[]{GIT, "push", "-u", "origin", "main"}; - } - - /** - * Performs the following git commands at the repo level: - *
    - *
  • git add .
  • - *
  • git commit -m {getArg(0)}
  • - *
  • git push -u origin main
  • - *
- */ - private static void gitme() { - if (getInputHandler().noArgs()) { - getInputHandler().println("gitme usage: gitme [commit message, quotes not needed]"); - return; - } - - String commitMessage = quote + getInputHandler().argsToString() + quote; - String threadName = "Gitme command, commit message: " + commitMessage; - CyderThreadRunner.submit(() -> { - ProcessBuilder gitAddProcessBuilder = new ProcessBuilder(generateGitAddCommand()); - - String[] GIT_COMMIT_COMMAND = {GIT, "commit", "-m", commitMessage}; - ProcessBuilder gitCommitProcessBuilder = new ProcessBuilder(GIT_COMMIT_COMMAND); - - ProcessBuilder gitPushProcessBuilder = new ProcessBuilder(generateGitPushCommand()); - - ImmutableList builders = ImmutableList.of( - gitAddProcessBuilder, gitCommitProcessBuilder, gitPushProcessBuilder); - ProcessUtil.runProcesses(builders).forEach(getInputHandler()::println); - }, threadName); - } - - /** - * Prints all the issues found for the official Cyder github repo. - */ - private static void printIssues() { - CyderThreadRunner.submit(() -> { - ImmutableList issues = GitHubUtil.getCyderIssues(); - - StringBuilder builder = new StringBuilder(); - builder.append(issues.size()).append(space) - .append(StringUtil.getWordFormBasedOnNumber(issues.size(), "issue")) - .append(space).append("found:").append(CyderStrings.newline); - builder.append(issueSeparator).append(CyderStrings.newline); - - issues.forEach(issue -> { - builder.append("Issue #").append(issue.number).append(CyderStrings.newline); - builder.append(issue.title).append(CyderStrings.newline); - builder.append(issue.body).append(CyderStrings.newline); - builder.append(issueSeparator).append(CyderStrings.newline); - }); - - getInputHandler().println(builder); - }, GITHUB_ISSUE_PRINTER_THREAD_NAME); - } -} diff --git a/src/main/java/cyder/handlers/input/GuiTestHandler.java b/src/main/java/cyder/handlers/input/GuiTestHandler.java deleted file mode 100644 index 8d3e692d8..000000000 --- a/src/main/java/cyder/handlers/input/GuiTestHandler.java +++ /dev/null @@ -1,50 +0,0 @@ -package cyder.handlers.input; - -import com.google.common.reflect.ClassPath; -import cyder.annotations.GuiTest; -import cyder.annotations.Handle; -import cyder.exceptions.IllegalMethodException; -import cyder.handlers.internal.ExceptionHandler; -import cyder.strings.CyderStrings; -import cyder.utils.ReflectionUtil; - -import java.lang.reflect.Method; - -/** - * A handler for invoking {@link GuiTest}s. - */ -public class GuiTestHandler extends InputHandler { - /** - * Suppress default constructor. - */ - private GuiTestHandler() { - throw new IllegalMethodException(CyderStrings.ATTEMPTED_INSTANTIATION); - } - - @Handle - public static boolean handle() { - boolean ret = false; - - for (ClassPath.ClassInfo classInfo : ReflectionUtil.getCyderClasses()) { - Class classer = classInfo.load(); - - for (Method method : classer.getMethods()) { - if (method.isAnnotationPresent(GuiTest.class)) { - String trigger = method.getAnnotation(GuiTest.class).value(); - if (trigger.equalsIgnoreCase(getInputHandler().commandAndArgsToString())) { - try { - getInputHandler().println("Invoking gui test " + CyderStrings.quote - + method.getName() + CyderStrings.quote); - method.invoke(classer); - ret = true; - } catch (Exception e) { - ExceptionHandler.handle(e); - } - } - } - } - } - - return ret; - } -} diff --git a/src/main/java/cyder/handlers/input/ImageHandler.java b/src/main/java/cyder/handlers/input/ImageHandler.java deleted file mode 100644 index a6285241d..000000000 --- a/src/main/java/cyder/handlers/input/ImageHandler.java +++ /dev/null @@ -1,145 +0,0 @@ -package cyder.handlers.input; - -import cyder.annotations.Handle; -import cyder.console.Console; -import cyder.enumerations.Dynamic; -import cyder.enumerations.Extension; -import cyder.exceptions.IllegalMethodException; -import cyder.handlers.internal.ExceptionHandler; -import cyder.strings.CyderStrings; -import cyder.threads.CyderThreadRunner; -import cyder.user.UserDataManager; -import cyder.user.UserFile; -import cyder.utils.ImageUtil; -import cyder.utils.SecurityUtil; -import cyder.utils.SpotlightUtil; -import cyder.utils.StaticUtil; - -import javax.swing.*; -import java.io.File; -import java.util.Optional; -import java.util.concurrent.Future; - -/** - * A handler for images and console background manipulation - */ -public class ImageHandler extends InputHandler { - /** - * Suppress default constructor. - */ - private ImageHandler() { - throw new IllegalMethodException(CyderStrings.ATTEMPTED_INSTANTIATION); - } - - @Handle({"java", "msu", "nathan", "nate", "html", "css", "docker", "redis", "blur", "unicorn", "spotlight"}) - public static boolean handle() { - boolean ret = true; - - if (getInputHandler().commandIs("java")) { - getInputHandler().println(new ImageIcon(StaticUtil.getStaticPath("duke.png"))); - } else if (getInputHandler().commandIs("msu")) { - getInputHandler().println(new ImageIcon(StaticUtil.getStaticPath("msu.png"))); - } else if (getInputHandler().commandIs("nathan") || getInputHandler().commandIs("nate")) { - getInputHandler().println(new ImageIcon(StaticUtil.getStaticPath("me.png"))); - } else if (getInputHandler().commandIs("html")) { - getInputHandler().println(new ImageIcon(StaticUtil.getStaticPath("html5.png"))); - } else if (getInputHandler().commandIs("css")) { - getInputHandler().println(new ImageIcon(StaticUtil.getStaticPath("css.png"))); - } else if (getInputHandler().commandIs("docker")) { - getInputHandler().println(new ImageIcon(StaticUtil.getStaticPath("Docker.png"))); - } else if (getInputHandler().commandIs("redis")) { - getInputHandler().println(new ImageIcon(StaticUtil.getStaticPath("Redis.png"))); - } else if (getInputHandler().commandIs("blur")) { - if (getInputHandler().checkArgsLength(1)) { - if (ImageUtil.isSolidColor(Console.INSTANCE.getCurrentBackground().generateBufferedImage())) { - getInputHandler().println("Silly " + UserDataManager.INSTANCE.getUsername() - + ". Your background is a solid color, bluing that won't do anything :P"); - } - - attemptToBlurBackground(); - } else { - getInputHandler().println("Blur command usage: blur [GAUSSIAN BLUR RADIUS]"); - } - } else if (getInputHandler().commandIs("unicorn")) { - getInputHandler().println(new ImageIcon(StaticUtil.getStaticPath("unicorn.png"))); - } else if (getInputHandler().inputIgnoringSpacesMatches("spotlight")) { - CyderThreadRunner.submit(() -> { - getInputHandler().println("Saving backgrounds to your backgrounds directory..."); - SpotlightUtil.saveSpotlights(Dynamic.buildDynamic(Dynamic.USERS.getFileName(), - Console.INSTANCE.getUuid(), - UserFile.BACKGROUNDS.getName())); - getInputHandler().println("Saved images to your backgrounds directory"); - }, spotlightStealerThreadName); - } else { - ret = false; - } - - return ret; - } - - /** - * The name of the thread which saves the spotlights to the current user's backgrounds directory. - */ - private static final String spotlightStealerThreadName = "Spotlight Saver"; - - /** - * The minimum allowable radius when blurring the background. - */ - private static final int MIN_BLUR_SIZE = 3; - - /** - * The name of the thread that attempts to blur the current background. - */ - private static final String BACKGROUND_BLUR_ATTEMPT_THREAD_NAME = "Background Blur Attempt Thread"; - - /** - * Attempts to validate a blur command and if valid, blur the current console background. - */ - private static void attemptToBlurBackground() { - CyderThreadRunner.submit(() -> { - try { - int radius = Integer.parseInt(getInputHandler().getArg(0)); - boolean isEven = radius % 2 == 0; - - if (isEven) { - getInputHandler().println("Blur radius must be an odd number"); - return; - } else if (radius < MIN_BLUR_SIZE) { - getInputHandler().println("Minimum blur radius is " + MIN_BLUR_SIZE); - return; - } - - File currentBackgroundFile = Console.INSTANCE.getCurrentBackground().getReferenceFile(); - - if (currentBackgroundFile == null || !currentBackgroundFile.exists()) { - String name = SecurityUtil.generateUuid(); - boolean saved = ImageUtil.saveImageToTemporaryDirectory(Console.INSTANCE - .getCurrentBackground().generateBufferedImage(), name); - - if (!saved) { - getInputHandler().println("Could not blur background at this time"); - return; - } - - currentBackgroundFile = Dynamic.buildDynamic( - Dynamic.TEMP.getFileName(), name + Extension.PNG.getExtension()); - } - - Future> futureImage = ImageUtil.gaussianBlur(currentBackgroundFile, radius); - while (!futureImage.isDone()) Thread.onSpinWait(); - - if (futureImage.get().isPresent()) { - Console.INSTANCE.setBackgroundFile(futureImage.get().get(), true); - getInputHandler().println("Background blurred, set, and saved as a separate background file."); - } else { - getInputHandler().println("Could not blur background at this time"); - } - } catch (NumberFormatException ignored) { - getInputHandler().println("Invalid input for radius: " + getInputHandler().getArg(0)); - } catch (Exception e) { - getInputHandler().println("Blur command usage: blur [GAUSSIAN BLUR RADIUS]"); - ExceptionHandler.handle(e); - } - }, BACKGROUND_BLUR_ATTEMPT_THREAD_NAME); - } -} diff --git a/src/main/java/cyder/handlers/input/InputHandler.java b/src/main/java/cyder/handlers/input/InputHandler.java deleted file mode 100644 index 128e5fe55..000000000 --- a/src/main/java/cyder/handlers/input/InputHandler.java +++ /dev/null @@ -1,17 +0,0 @@ -package cyder.handlers.input; - -import cyder.console.Console; - -/** - * A base class for InputHandlers to extend in order to enhance readability. - */ -public abstract class InputHandler { - /** - * Returns the Console's input handler. - * - * @return the Console's input handler - */ - protected static BaseInputHandler getInputHandler() { - return Console.INSTANCE.getInputHandler(); - } -} diff --git a/src/main/java/cyder/handlers/input/MathHandler.java b/src/main/java/cyder/handlers/input/MathHandler.java deleted file mode 100644 index 12b3cad63..000000000 --- a/src/main/java/cyder/handlers/input/MathHandler.java +++ /dev/null @@ -1,42 +0,0 @@ -package cyder.handlers.input; - -import com.fathzer.soft.javaluator.DoubleEvaluator; -import cyder.annotations.Handle; -import cyder.exceptions.IllegalMethodException; -import cyder.strings.CyderStrings; -import cyder.strings.StringUtil; - -/** - * A handler for handling mathematical expressions. - */ -public class MathHandler extends InputHandler { - /** - * Suppress default constructor. - */ - private MathHandler() { - throw new IllegalMethodException(CyderStrings.ATTEMPTED_INSTANTIATION); - } - - /** - * The evaluator for evaluating mathematical expressions - */ - private static final DoubleEvaluator evaluator; - - static { - evaluator = new DoubleEvaluator(); - } - - @Handle - public static boolean handle() { - boolean ret = false; - - try { - double result = evaluator.evaluate(StringUtil.firstCharToLowerCase( - getInputHandler().commandAndArgsToString())); - getInputHandler().println(String.valueOf(result)); - ret = true; - } catch (Exception ignored) {} - - return ret; - } -} diff --git a/src/main/java/cyder/handlers/input/NetworkHandler.java b/src/main/java/cyder/handlers/input/NetworkHandler.java deleted file mode 100644 index 7f1812b56..000000000 --- a/src/main/java/cyder/handlers/input/NetworkHandler.java +++ /dev/null @@ -1,225 +0,0 @@ -package cyder.handlers.input; - -import com.google.common.collect.ImmutableList; -import cyder.annotations.Handle; -import cyder.console.Console; -import cyder.constants.CyderRegexPatterns; -import cyder.constants.CyderUrls; -import cyder.enumerations.Dynamic; -import cyder.exceptions.IllegalMethodException; -import cyder.handlers.internal.ExceptionHandler; -import cyder.network.IpDataManager; -import cyder.network.NetworkUtil; -import cyder.network.ScrapingUtil; -import cyder.props.Props; -import cyder.strings.CyderStrings; -import cyder.strings.StringUtil; -import cyder.threads.CyderThreadRunner; -import cyder.usb.UsbDevice; -import cyder.usb.UsbUtil; -import cyder.user.UserFile; -import cyder.utils.MapUtil; -import cyder.utils.OsUtil; -import cyder.utils.SecurityUtil; - -import javax.swing.*; -import java.io.BufferedReader; -import java.io.File; -import java.io.InputStreamReader; -import java.net.HttpURLConnection; -import java.net.URL; -import java.util.Objects; -import java.util.Optional; -import java.util.concurrent.Future; - -/** - * A handler for things which require internet access and may reach out to external domains for data. - */ -public class NetworkHandler extends InputHandler { - /** - * The name of the waiter thread for getting the usb devices. - */ - private static final String USB_DEVICE_WAITER_THREAD_NAME = "Usb Device Waiter"; - - /** - * The name of the thread for performing the whereami command. - */ - private static final String WHEREAMI_THREAD_NAME = "Whereami Information Finder"; - - /** - * The length of the map shown for the where am i command. - */ - private static final int WHERE_AM_I_MAP_LENGTH = 200; - - /** - * Suppress default constructor. - */ - private NetworkHandler() { - throw new IllegalMethodException(CyderStrings.ATTEMPTED_INSTANTIATION); - } - - @Handle({"define", "wikisum", "ip", "pastebin", "download", "usb", "curl", "whereami", "network devices"}) - public static boolean handle() { - boolean ret = true; - - if (getInputHandler().commandIs("define")) { - if (!getInputHandler().checkArgsLength(0)) { - getInputHandler().println(StringUtil.getDefinition(getInputHandler().argsToString())); - } else { - getInputHandler().println("define usage: define YOUR_WORD/expression"); - } - } else if (getInputHandler().commandIs("wikisum")) { - if (!getInputHandler().checkArgsLength(0)) { - String wikiSumSearch = getInputHandler().argsToString(); - Optional wikiSumOptional = StringUtil.getWikipediaSummary(wikiSumSearch); - if (wikiSumOptional.isPresent()) { - String wikiSum = wikiSumOptional.get(); - getInputHandler().println(wikiSum); - } else { - getInputHandler().print("Wikipedia article not found"); - } - } else { - getInputHandler().println("wikisum usage: wikisum YOUR_WORD/expression"); - } - } else if (getInputHandler().commandIs("ip")) { - String ipDataFoundIp = IpDataManager.INSTANCE.getIpData().getIp(); - getInputHandler().println(Objects.requireNonNullElseGet(ipDataFoundIp, - () -> NetworkUtil.getIp().orElse("IP not found"))); - } else if (getInputHandler().commandIs("pastebin")) { - if (getInputHandler().checkArgsLength(1)) { - String urlString; - if (getInputHandler().getArg(0).contains("pastebin.com")) { - urlString = getInputHandler().getArg(0); - } else { - urlString = CyderUrls.PASTEBIN_RAW_BASE + getInputHandler().getArg(1); - } - - try { - URL url = new URL(urlString); - BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream())); - String line; - while ((line = reader.readLine()) != null) { - getInputHandler().println(line); - } - - reader.close(); - } catch (Exception e) { - ExceptionHandler.handle(e); - getInputHandler().println("Unknown pastebin url/UUID"); - } - } else { - getInputHandler().println("pastebin usage: pastebin [URL/UUID]\nExample: pastebin xa7sJvNm"); - } - } else if (getInputHandler().commandIs("usb")) { - CyderThreadRunner.submit(() -> { - getInputHandler().println("Devices connected to " + OsUtil.getComputerName() + " via USB protocol:"); - - Future> futureDevices = UsbUtil.getUsbDevices(); - while (!futureDevices.isDone()) Thread.onSpinWait(); - - try { - futureDevices.get().forEach(device -> { - getInputHandler().println("Status: " + device.getStatus()); - getInputHandler().println("Type: " + device.getType()); - getInputHandler().println("Friendly name: " + device.getFriendlyName()); - getInputHandler().println("Instance ID: " + device.getInstanceId()); - getInputHandler().println("-------------------------"); - }); - } catch (Exception e) { - ExceptionHandler.handle(e); - } - }, USB_DEVICE_WAITER_THREAD_NAME); - } else if (getInputHandler().commandIs("download")) { - if (getInputHandler().checkArgsLength(1)) { - if (NetworkUtil.isValidUrl(getInputHandler().getArg(0))) { - Optional optionalResponseName = NetworkUtil.getUrlTitle(getInputHandler().getArg(0)); - String saveName = SecurityUtil.generateUuid(); - - if (optionalResponseName.isPresent()) { - String responseName = optionalResponseName.get(); - if (!responseName.isEmpty()) { - saveName = responseName; - } - } - - File saveFile = Dynamic.buildDynamic(Dynamic.USERS.getFileName(), Console.INSTANCE.getUuid(), - UserFile.FILES.getName(), saveName); - - getInputHandler().println("Saving file: " + saveName + " to files directory"); - - CyderThreadRunner.submit(() -> { - try { - if (NetworkUtil.downloadResource(getInputHandler().getArg(0), saveFile)) { - getInputHandler().println("Successfully saved"); - } else { - getInputHandler().println("Error: could not download at this time"); - } - } catch (Exception e) { - ExceptionHandler.handle(e); - } - }, "File URL Downloader"); - } else { - getInputHandler().println("Invalid url"); - } - } else { - getInputHandler().println("download usage: download [YOUR LINK]"); - } - } else if (getInputHandler().commandIs("curl")) { - if (getInputHandler().checkArgsLength(1)) { - if (NetworkUtil.isValidUrl(getInputHandler().getArg(0))) { - try { - URL url = new URL(getInputHandler().getArg(0)); - HttpURLConnection http = (HttpURLConnection) url.openConnection(); - - getInputHandler().println(NetworkUtil.readUrl(getInputHandler().getArg(0))); - getInputHandler().println("Response: " + http.getResponseCode() - + CyderStrings.space + http.getResponseMessage()); - - http.disconnect(); - } catch (Exception e) { - ExceptionHandler.handle(e); - } - } else { - getInputHandler().println("Invalid url"); - } - } else { - getInputHandler().println("Curl command usage: curl [URL]"); - } - } else if (getInputHandler().inputIgnoringSpacesMatches("whereami")) { - CyderThreadRunner.submit(() -> { - ScrapingUtil.IspQueryResult result = ScrapingUtil.getIspAndNetworkDetails(); - getInputHandler().println("You live in " + result.city() + ", " + result.state()); - getInputHandler().println("Your country is: " + result.country()); - getInputHandler().println("Your ip is: " + result.ip()); - getInputHandler().println("Your isp is: " + result.isp()); - getInputHandler().println("Your hostname is: " + result.hostname()); - - MapUtil.Builder builder = new MapUtil.Builder(WHERE_AM_I_MAP_LENGTH, WHERE_AM_I_MAP_LENGTH, - Props.mapQuestApiKey.getValue()); - builder.setScaleBar(false); - builder.setLocationString(result.city() - .replaceAll(CyderRegexPatterns.whiteSpaceRegex, NetworkUtil.URL_SPACE) + "," - + result.state().replaceAll(CyderRegexPatterns.whiteSpaceRegex, NetworkUtil.URL_SPACE) + "," - + result.country().replaceAll(CyderRegexPatterns.whiteSpaceRegex, NetworkUtil.URL_SPACE)); - builder.setZoomLevel(8); - builder.setFilterWaterMark(true); - - try { - ImageIcon icon = MapUtil.getMapView(builder); - getInputHandler().println(icon); - } catch (Exception e) { - ExceptionHandler.handle(e); - } - }, WHEREAMI_THREAD_NAME); - } else if (getInputHandler().inputIgnoringSpacesMatches("networkdevices")) { - OsUtil.getNetworkDevices().forEach(networkDevice -> { - getInputHandler().println("Name: " + networkDevice.name()); - getInputHandler().println("Display name: " + networkDevice.displayName()); - }); - } else { - ret = false; - } - - return ret; - } -} diff --git a/src/main/java/cyder/handlers/input/NumberHandler.java b/src/main/java/cyder/handlers/input/NumberHandler.java deleted file mode 100644 index 2dbcf1813..000000000 --- a/src/main/java/cyder/handlers/input/NumberHandler.java +++ /dev/null @@ -1,141 +0,0 @@ -package cyder.handlers.input; - -import cyder.annotations.Handle; -import cyder.constants.CyderRegexPatterns; -import cyder.enumerations.Extension; -import cyder.exceptions.IllegalMethodException; -import cyder.files.FileUtil; -import cyder.handlers.internal.ExceptionHandler; -import cyder.math.NumberToWordUtil; -import cyder.math.NumberUtil; -import cyder.strings.CyderStrings; -import cyder.threads.CyderThreadRunner; - -import java.io.File; -import java.io.FileInputStream; -import java.io.InputStream; - -/** - * A handler to handle things involving numbers. - */ -public class NumberHandler extends InputHandler { - /** - * Suppress default constructor. - */ - private NumberHandler() { - throw new IllegalMethodException(CyderStrings.ATTEMPTED_INSTANTIATION); - } - - @Handle({"binary", "prime", "bindump", "hexdump", "number2string"}) - public static boolean handle() { - boolean ret = true; - - if (getInputHandler().commandIs("binary")) { - if (getInputHandler().checkArgsLength(1) - && CyderRegexPatterns.numberPattern.matcher(getInputHandler().getArg(0)).matches()) { - CyderThreadRunner.submit(() -> { - try { - getInputHandler().println(getInputHandler().getArg(0) + " converted to binary equals: " - + Integer.toBinaryString(Integer.parseInt(getInputHandler().getArg(0)))); - } catch (Exception ignored) { - } - }, "Binary Converter"); - } else { - getInputHandler().println("Your value must only contain numbers."); - } - } else if (getInputHandler().commandIs("prime")) { - if (getInputHandler().checkArgsLength(1)) { - int num = Integer.parseInt(getInputHandler().getArg(0)); - - if (NumberUtil.isPrime(num)) { - getInputHandler().println(num + " is a prime"); - } else { - getInputHandler().println(num + " is not a prime because it is divisible by:\n["); - - for (int factor : NumberUtil.primeFactors(num)) { - getInputHandler().println(factor + ", "); - } - - getInputHandler().println(CyderStrings.closingBracket); - } - } else { - getInputHandler().println("Prime usage: prime NUMBER"); - } - } else if (getInputHandler().commandIs("bindump")) { - if (getInputHandler().checkArgsLength(2)) { - if (!getInputHandler().getArg(0).equals("-f")) { - getInputHandler().println("Bindump usage: bindump -f /path/to/binary/file"); - } else { - File f = new File(getInputHandler().getArg(1)); - - if (f.exists()) { - getInputHandler().printlnPriority("0b" + FileUtil.getBinaryString(f)); - } else { - getInputHandler().println("File: " + getInputHandler().getArg(0) + " does not exist."); - } - } - } else { - getInputHandler().println("Bindump usage: bindump -f /path/to/binary/file"); - } - } else if (getInputHandler().commandIs("hexdump")) { - if (getInputHandler().checkArgsLength(2)) { - if (!getInputHandler().getArg(0).equals("-f")) { - getInputHandler().println("Hexdump usage: hexdump -f /path/to/binary/file"); - } else { - File f = new File(getInputHandler().getArg(1)); - - if (!f.exists()) - throw new IllegalArgumentException("File does not exist"); - - if (FileUtil.getExtension(f).equalsIgnoreCase(Extension.BIN.getExtension())) { - if (f.exists()) { - getInputHandler().printlnPriority("0x" + FileUtil.getHexString(f).toUpperCase()); - } else { - getInputHandler().println("File: " + getInputHandler().getArg(1) + " does not exist."); - } - } else { - try { - InputStream inputStream = new FileInputStream(f); - int numberOfColumns = 10; - - StringBuilder sb = new StringBuilder(); - - long streamPtr = 0; - while (inputStream.available() > 0) { - long col = streamPtr++ % numberOfColumns; - sb.append(String.format("%02x ", inputStream.read())); - if (col == (numberOfColumns - 1)) { - sb.append(CyderStrings.newline); - } - } - - inputStream.close(); - - getInputHandler().printlnPriority(sb.toString()); - } catch (Exception e) { - ExceptionHandler.handle(e); - } - } - } - } else { - getInputHandler().println("Hexdump usage: hexdump -f /path/to/binary/file"); - } - } else if (getInputHandler().commandIs("number2string") - || getInputHandler().commandIs("number2word")) { - if (getInputHandler().checkArgsLength(1)) { - if (CyderRegexPatterns.numberPattern.matcher(getInputHandler().getArg(0)).matches()) { - getInputHandler().println(NumberToWordUtil.toWords(getInputHandler().getArg(0))); - } else { - getInputHandler().println("Could not parse input as number: " - + getInputHandler().getArg(0)); - } - } else { - getInputHandler().println("Command usage: number2string [integer]"); - } - } else { - ret = false; - } - - return ret; - } -} diff --git a/src/main/java/cyder/handlers/input/PixelationHandler.java b/src/main/java/cyder/handlers/input/PixelationHandler.java deleted file mode 100644 index 3ba7e6287..000000000 --- a/src/main/java/cyder/handlers/input/PixelationHandler.java +++ /dev/null @@ -1,121 +0,0 @@ -package cyder.handlers.input; - -import com.google.common.collect.Range; -import cyder.annotations.Handle; -import cyder.console.Console; -import cyder.enumerations.Dynamic; -import cyder.enumerations.Extension; -import cyder.exceptions.IllegalMethodException; -import cyder.files.FileUtil; -import cyder.handlers.internal.ExceptionHandler; -import cyder.strings.CyderStrings; -import cyder.threads.CyderThreadRunner; -import cyder.user.UserDataManager; -import cyder.user.UserFile; -import cyder.utils.ImageUtil; - -import javax.imageio.ImageIO; -import java.awt.image.BufferedImage; -import java.io.File; - -/** - * A handler for handling when images or the console background should be pixelated. - */ -public class PixelationHandler extends InputHandler { - /** - * Suppress default constructor. - */ - private PixelationHandler() { - throw new IllegalMethodException(CyderStrings.ATTEMPTED_INSTANTIATION); - } - - /** - * The range of allowable user-entered pixelation values. - */ - private static final Range pixelRange = Range.closed(2, 500); - - @Handle({"pixelate", "pixelation"}) - public static boolean handle() { - switch (getInputHandler().getHandleIterations()) { - case 0 -> { - boolean isSolidColor = false; - - try { - isSolidColor = ImageUtil.isSolidColor(Console.INSTANCE.getCurrentBackground().getReferenceFile()); - } catch (Exception e) { - ExceptionHandler.handle(e); - } - - if (isSolidColor) { - getInputHandler().println("Silly " + UserDataManager.INSTANCE.getUsername() - + "; your background " + "is a solid color :P"); - } else { - if (getInputHandler().checkArgsLength(1)) { - try { - int size = Integer.parseInt(getInputHandler().getArg(0)); - attemptPixelation(size); - } catch (Exception e) { - return false; - } - } else { - getInputHandler().setRedirectionHandler(PixelationHandler.class); - getInputHandler().setHandleIterations(1); - getInputHandler().println("Enter pixel size"); - } - } - - return true; - } - case 1 -> { - try { - int size = Integer.parseInt(getInputHandler().getCommand()); - attemptPixelation(size); - } catch (Exception ignored) { - getInputHandler().println("Could not parse input as an integer"); - } - - return true; - } - default -> throw new IllegalArgumentException("Illegal handle index for pixelation handler"); - } - } - - /** - * Attempts to pixelate the Console background if the provided size is within the allowable range. - * - * @param size the requested pixel size - */ - private static void attemptPixelation(int size) { - if (pixelRange.contains(size)) { - CyderThreadRunner.submit(() -> { - try { - BufferedImage img = ImageUtil.pixelateImage(ImageUtil.read(Console.INSTANCE. - getCurrentBackground().getReferenceFile().getAbsoluteFile()), size); - - String newName = FileUtil.getFilename(Console.INSTANCE - .getCurrentBackground().getReferenceFile().getName()) - + "_Pixelated_Pixel_Size_" + size + Extension.PNG.getExtension(); - - File saveFile = Dynamic.buildDynamic( - Dynamic.USERS.getFileName(), - Console.INSTANCE.getUuid(), - UserFile.BACKGROUNDS.getName(), newName); - - ImageIO.write(img, Extension.PNG.getExtensionWithoutPeriod(), saveFile); - - getInputHandler().println("Background pixelated and saved as a separate background file."); - - Console.INSTANCE.setBackgroundFile(saveFile); - } catch (Exception e) { - ExceptionHandler.handle(e); - } - }, "Console Background Pixelator"); - } else { - getInputHandler().println("Sorry, " + UserDataManager.INSTANCE.getUsername() - + ", but your pixel value must be in the range [" - + pixelRange.lowerEndpoint() + ", " + pixelRange.upperEndpoint() + CyderStrings.closingBracket); - } - - getInputHandler().resetHandlers(); - } -} diff --git a/src/main/java/cyder/handlers/input/PlayAudioHandler.java b/src/main/java/cyder/handlers/input/PlayAudioHandler.java deleted file mode 100644 index b4deee214..000000000 --- a/src/main/java/cyder/handlers/input/PlayAudioHandler.java +++ /dev/null @@ -1,163 +0,0 @@ -package cyder.handlers.input; - -import cyder.annotations.Handle; -import cyder.audio.CPlayer; -import cyder.audio.GeneralAudioPlayer; -import cyder.console.Console; -import cyder.constants.CyderFonts; -import cyder.constants.CyderIcons; -import cyder.constants.CyderUrls; -import cyder.exceptions.IllegalMethodException; -import cyder.handlers.internal.ExceptionHandler; -import cyder.managers.RobotManager; -import cyder.strings.CyderStrings; -import cyder.strings.StringUtil; -import cyder.threads.BletchyAnimationManager; -import cyder.threads.CyderThreadRunner; -import cyder.threads.ThreadUtil; -import cyder.utils.StaticUtil; -import cyder.youtube.YouTubeUtil; - -import java.awt.*; -import java.awt.event.KeyEvent; -import java.io.File; -import java.util.concurrent.Future; - -/** - * A handler for commands that play audio. - */ -public class PlayAudioHandler extends InputHandler { - /** - * The all the stars music file. - */ - private static final File allTheStars = StaticUtil.getStaticResource("allthestars.mp3"); - - /** - * The Beyno font. - */ - private static final Font beynoFont = new CyderFonts.FontBuilder("BEYNO") - .setSize(getInputHandler().getJTextPane().getFont().getSize()).generate(); - - /** - * The chadwick boseman bletchy text. - */ - private static final String chadwickBosemanBletchyText = "RIP CHADWICK BOSEMAN"; - - /** - * The delay between starting the chadwick boseman easter egg audio/bletchy thread, - * and resetting to the user's font. - */ - private static final int chadwickBosemanResetFontDelay = 4000; - - /** - * Suppress default constructor. - */ - private PlayAudioHandler() { - throw new IllegalMethodException(CyderStrings.ATTEMPTED_INSTANTIATION); - } - - @Handle({"heyya", - "windows", - "lightsaber", - "xbox", - "startrek", - "toystory", - "logic", - "18002738255", - "x", - "black panther", - "chadwick boseman", - "f17", - "play"}) - public static boolean handle() { - boolean ret = true; - - if (getInputHandler().inputIgnoringSpacesMatches("heyya")) { - GeneralAudioPlayer.playAudio(StaticUtil.getStaticResource("hey.mp3")); - } else if (getInputHandler().commandIs("windows")) { - GeneralAudioPlayer.playAudio(StaticUtil.getStaticResource("windows.mp3")); - } else if (getInputHandler().commandIs("lightsaber")) { - GeneralAudioPlayer.playAudio(StaticUtil.getStaticResource("lightsaber.mp3")); - } else if (getInputHandler().commandIs("xbox")) { - GeneralAudioPlayer.playAudio(StaticUtil.getStaticResource("xbox.mp3")); - } else if (getInputHandler().commandIs("startrek")) { - GeneralAudioPlayer.playAudio(StaticUtil.getStaticResource("startrek.mp3")); - } else if (getInputHandler().commandIs("toystory")) { - GeneralAudioPlayer.playAudio(StaticUtil.getStaticResource("theclaw.mp3")); - } else if (getInputHandler().commandIs("logic")) { - GeneralAudioPlayer.playAudio(StaticUtil.getStaticResource("commando.mp3")); - } else if (getInputHandler().getCommand().replace(CyderStrings.dash, "").equals("18002738255")) { - GeneralAudioPlayer.playAudio(StaticUtil.getStaticResource("1800.mp3")); - } else if (getInputHandler().commandIs(CyderStrings.X)) { - Console.INSTANCE.getConsoleCyderFrame().setIconImage(CyderIcons.X_ICON.getImage()); - File audioFile = StaticUtil.getStaticResource("x.mp3"); - GeneralAudioPlayer.playAudio(new CPlayer(audioFile).addOnCompletionCallback( - () -> Console.INSTANCE.getConsoleCyderFrame().setIconImage(CyderIcons.CYDER_ICON.getImage()))); - } else if (getInputHandler().inputIgnoringSpacesMatches("black panther") - || getInputHandler().inputIgnoringSpacesMatches("chadwick boseman")) { - chadwickBosemanEasterEgg(); - } else if (getInputHandler().commandIs("f17")) { - if (RobotManager.INSTANCE.getRobot() != null) { - RobotManager.INSTANCE.getRobot().keyPress(KeyEvent.VK_F17); - } else { - getInputHandler().println("Mr. Robot didn't start :("); - } - } else if (getInputHandler().commandIs("play")) { - onPlayCommand(); - } else { - ret = false; - } - - return ret; - } - - /** - * The actions to invoke when a play command is handled. - */ - private static void onPlayCommand() { - if (StringUtil.isNullOrEmpty(getInputHandler().argsToString())) { - getInputHandler().println("Play command usage: Play [video_url/playlist_url/search query]"); - } - - CyderThreadRunner.submit(() -> { - String url = getInputHandler().argsToString(); - - if (YouTubeUtil.isPlaylistUrl(url)) { - // todo we can either extract all uuids and download individually or simply let youtube-ld - // handle downloading them all - } else if (YouTubeUtil.isVideoUrl(url)) { - YouTubeUtil.downloadYouTubeAudio(url, Console.INSTANCE.getInputHandler()); - } else { - getInputHandler().println("Searching YouTube for: " + url); - - try { - Future futureUuid = YouTubeUtil.getMostLikelyUuid(url); - while (!futureUuid.isDone()) Thread.onSpinWait(); - url = CyderUrls.YOUTUBE_VIDEO_HEADER + futureUuid.get(); - YouTubeUtil.downloadYouTubeAudio(url, Console.INSTANCE.getInputHandler()); - } catch (Exception e) { - ExceptionHandler.handle(e); - } - } - }, "YouTube Download Initializer"); - } - - /** - * Shows the Chadwick Boseman easter egg. - */ - private static void chadwickBosemanEasterEgg() { - String threadName = "Chadwick Boseman Easter Egg Thread"; - CyderThreadRunner.submit(() -> { - getInputHandler().getJTextPane().setText(""); - - GeneralAudioPlayer.playAudio(allTheStars); - getInputHandler().getJTextPane().setFont(beynoFont); - BletchyAnimationManager.INSTANCE.bletchy( - chadwickBosemanBletchyText, false, 15, false); - - ThreadUtil.sleep(chadwickBosemanResetFontDelay); - - getInputHandler().getJTextPane().setFont(Console.INSTANCE.generateUserFont()); - }, threadName); - } -} diff --git a/src/main/java/cyder/handlers/input/PropHandler.java b/src/main/java/cyder/handlers/input/PropHandler.java deleted file mode 100644 index 8fcbab95d..000000000 --- a/src/main/java/cyder/handlers/input/PropHandler.java +++ /dev/null @@ -1,43 +0,0 @@ -package cyder.handlers.input; - -import cyder.annotations.Handle; -import cyder.exceptions.IllegalMethodException; -import cyder.logging.LogTag; -import cyder.logging.Logger; -import cyder.props.PropLoader; -import cyder.props.Props; -import cyder.strings.CyderStrings; -import cyder.strings.StringUtil; - -/** - * A handler for utilities related to props. - */ -public class PropHandler extends InputHandler { - /** - * Suppress default constructor. - */ - private PropHandler() { - throw new IllegalMethodException(CyderStrings.ATTEMPTED_INSTANTIATION); - } - - @Handle("reload props") - public static boolean handle() { - boolean ret = true; - - if (getInputHandler().inputIgnoringSpacesMatches("reloadprops")) { - if (!Props.propsReloadable.getValue()) { - getInputHandler().println("Reloading props is currently disabled" - + " during runtime, check your props file"); - } else { - Logger.log(LogTag.PROPS_ACTION, "Reloading props"); - PropLoader.reloadProps(); - Logger.log(LogTag.PROPS_ACTION, "Props reloaded"); - int size = PropLoader.getPropsSize(); - getInputHandler().println("Reloaded " + size + " " - + StringUtil.getWordFormBasedOnNumber(size, "prop")); - } - } else ret = false; - - return ret; - } -} diff --git a/src/main/java/cyder/handlers/input/StatHandler.java b/src/main/java/cyder/handlers/input/StatHandler.java deleted file mode 100644 index 422f585fe..000000000 --- a/src/main/java/cyder/handlers/input/StatHandler.java +++ /dev/null @@ -1,265 +0,0 @@ -package cyder.handlers.input; - -import com.google.common.collect.ImmutableList; -import com.google.common.reflect.ClassPath; -import cyder.annotations.GuiTest; -import cyder.annotations.Handle; -import cyder.annotations.Widget; -import cyder.enumerations.Dynamic; -import cyder.enumerations.Extension; -import cyder.enumerations.SystemPropertyKey; -import cyder.exceptions.IllegalMethodException; -import cyder.files.FileUtil; -import cyder.handlers.internal.ExceptionHandler; -import cyder.logging.Logger; -import cyder.strings.CyderStrings; -import cyder.threads.CyderThreadRunner; -import cyder.threads.ThreadUtil; -import cyder.utils.OsUtil; -import cyder.utils.ReflectionUtil; -import cyder.utils.StatUtil; - -import java.io.File; -import java.lang.reflect.Method; -import java.net.InetAddress; -import java.net.NetworkInterface; -import java.text.DecimalFormat; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.Enumeration; -import java.util.concurrent.Future; - -/** - * A handler for finding and printing statistics. - */ -public class StatHandler extends InputHandler { - /** - * Suppress default constructor. - */ - private StatHandler() { - throw new IllegalMethodException(CyderStrings.ATTEMPTED_INSTANTIATION); - } - - @Handle({"debug", "count logs", "computer properties", "system properties", "tests", - "network addresses", "filesizes", "badwords", "widgets", "analyze code", "java properties", - "threads", "daemon threads"}) - public static boolean handle() { - boolean ret = true; - - if (getInputHandler().commandIs("debug")) { - CyderThreadRunner.submit(() -> { - try { - getInputHandler().println("Querying computer memory..."); - ImmutableList memory = StatUtil.getComputerMemorySpaces(); - getInputHandler().println("Computer memory:"); - for (String prop : memory) { - getInputHandler().println(prop); - } - - getInputHandler().println("Java properties:"); - Arrays.stream(SystemPropertyKey.values()).forEach(propertyKey - -> getInputHandler().println(propertyKey.getProperty())); - - getInputHandler().println("System properties:"); - for (String prop : StatUtil.getSystemProperties()) { - getInputHandler().println(prop); - } - - Future futureStats = StatUtil.getDebugProps(); - while (!futureStats.isDone()) Thread.onSpinWait(); - StatUtil.DebugStats stats = futureStats.get(); - - getInputHandler().println("Debug stats:"); - for (String line : stats.lines()) { - getInputHandler().println(line); - } - - getInputHandler().println("Country flag:"); - getInputHandler().println(stats.countryFlag()); - } catch (Exception e) { - ExceptionHandler.handle(e); - } - }, "Debug Stat Finder"); - } else if (getInputHandler().inputIgnoringSpacesMatches("computerproperties")) { - getInputHandler().println("This may take a second since this feature counts your PC's free memory"); - - CyderThreadRunner.submit(() -> { - for (String prop : StatUtil.getComputerMemorySpaces()) { - getInputHandler().println(prop); - } - }, "Computer Memory Computer"); - } else if (getInputHandler().inputIgnoringSpacesMatches("systemproperties")) { - for (String prop : StatUtil.getSystemProperties()) { - getInputHandler().println(prop); - } - } else if (getInputHandler().commandIs("countlogs")) { - File[] logDirs = Dynamic.buildDynamic(Dynamic.LOGS.getFileName()).listFiles(); - int count = 0; - int days = 0; - - if (logDirs != null && logDirs.length > 0) { - for (File logDir : logDirs) { - days++; - - File[] logDirFiles = logDir.listFiles(); - - if (logDirFiles != null && logDirFiles.length > 0) { - for (File log : logDirFiles) { - if (FileUtil.getExtension(log).equals(Extension.LOG.getExtension()) - && !logDir.equals(Logger.getCurrentLogFile())) { - count++; - } - } - } - } - } - - getInputHandler().println("Number of log dirs: " + days); - getInputHandler().println("Number of logs: " + count); - } else if (getInputHandler().commandIs("tests")) { - getInputHandler().println("Valid GUI tests to call:"); - getInputHandler().printlns(getGuiTestTriggers()); - } else if (getInputHandler().inputIgnoringSpacesMatches("networkaddresses")) { - try { - Enumeration nets = NetworkInterface.getNetworkInterfaces(); - - for (NetworkInterface netInterface : Collections.list(nets)) { - getInputHandler().println("Display name: " + netInterface.getDisplayName()); - getInputHandler().println("Name: " + netInterface.getName()); - - Enumeration inetAddresses = netInterface.getInetAddresses(); - for (InetAddress inetAddress : Collections.list(inetAddresses)) { - getInputHandler().println("InetAddress: " + inetAddress); - } - } - } catch (Exception e) { - ExceptionHandler.handle(e); - } - } else if (getInputHandler().inputIgnoringSpacesMatches("filesizes")) { - for (StatUtil.FileSize fileSize : StatUtil.fileSizes()) { - getInputHandler().println(fileSize.name() + ": " + OsUtil.formatBytes(fileSize.size())); - } - } else if (getInputHandler().commandIs("widgets")) { - ArrayList descriptions = getWidgetDescriptions(); - - getInputHandler().println("Found " + descriptions.size() + " widgets:"); - getInputHandler().println("-------------------------------------"); - - for (WidgetDescription description : descriptions) { - StringBuilder triggers = new StringBuilder(); - - for (int i = 0 ; i < description.triggers().length ; i++) { - triggers.append(description.triggers()[i]); - - if (i != description.triggers().length - 1) - triggers.append(", "); - } - - getInputHandler().println("Name: " + description.name()); - getInputHandler().println("Description: " + description.description() + "\nTriggers: [" - + triggers.toString().trim() + CyderStrings.closingBracket); - getInputHandler().println("-------------------------------------"); - } - } else if (getInputHandler().inputIgnoringSpacesMatches("analyzecode")) { - if (OsUtil.JAR_MODE) { - getInputHandler().println("Code analyzing is not available when in Jar mode"); - } else { - if (getInputHandler().checkArgsLength(0) - || getInputHandler().checkArgsLength(1)) { - File startDir = new File("src"); - - if (getInputHandler().checkArgsLength(1)) { - startDir = new File(getInputHandler().getArg(0)); - - if (!startDir.exists()) { - getInputHandler().println("Invalid root directory"); - startDir = new File("src/main/java/cyder"); - } - } - - File finalStartDir = startDir; - - CyderThreadRunner.submit(() -> { - int codeLines = StatUtil.totalJavaLines(finalStartDir); - int commentLines = StatUtil.totalComments(finalStartDir); - - getInputHandler().println("Total lines: " + StatUtil.totalLines(finalStartDir)); - getInputHandler().println("Code lines: " + codeLines); - getInputHandler().println("Blank lines: " + StatUtil.totalBlankLines(finalStartDir)); - getInputHandler().println("Comment lines: " + commentLines); - getInputHandler().println("Classes: " + ReflectionUtil.getCyderClasses().size()); - - float ratio = ((float) codeLines / (float) commentLines); - getInputHandler().println("Code to comment ratio: " - + new DecimalFormat("#0.00").format(ratio)); - }, "Code Analyzer"); - } else { - getInputHandler().println("analyzecode usage: analyzecode [path/to/the/root/directory] " + - "(leave path blank to analyze Cyder)"); - } - } - } else if (getInputHandler().inputIgnoringSpacesMatches("javaproperties")) { - Arrays.stream(SystemPropertyKey.values()).forEach(propertyKey - -> getInputHandler().println(propertyKey.getProperty())); - } else if (getInputHandler().commandIs("threads")) { - getInputHandler().printlns(ThreadUtil.getThreadNames()); - } else if (getInputHandler().inputIgnoringSpacesMatches("daemonthreads")) { - getInputHandler().printlns(ThreadUtil.getDaemonThreadNames()); - } else { - ret = false; - } - - return ret; - } - - /** - * A widget and its properties. - */ - private record WidgetDescription(String name, String description, String[] triggers) {} - - /** - * Returns a list of names, descriptions, and triggers of all the widgets found within Cyder. - * - * @return a list of descriptions of all the widgets found within Cyder - */ - private static ArrayList getWidgetDescriptions() { - ArrayList ret = new ArrayList<>(); - - for (ClassPath.ClassInfo classInfo : ReflectionUtil.getCyderClasses()) { - Class clazz = classInfo.load(); - - for (Method method : clazz.getMethods()) { - if (method.isAnnotationPresent(Widget.class)) { - String[] triggers = method.getAnnotation(Widget.class).triggers(); - String description = method.getAnnotation(Widget.class).description(); - ret.add(new WidgetDescription(clazz.getName(), description, triggers)); - } - } - } - - return ret; - } - - /** - * Returns a list of valid gui triggers exposed in Cyder. - * - * @return a list of triggers for gui tests - */ - private static ImmutableList getGuiTestTriggers() { - ArrayList ret = new ArrayList<>(); - - for (ClassPath.ClassInfo classInfo : ReflectionUtil.getCyderClasses()) { - Class clazz = classInfo.load(); - - for (Method m : clazz.getMethods()) { - if (m.isAnnotationPresent(GuiTest.class)) { - String trigger = m.getAnnotation(GuiTest.class).value(); - ret.add(trigger); - } - } - } - - return ImmutableList.copyOf(ret); - } -} diff --git a/src/main/java/cyder/handlers/input/TestHandler.java b/src/main/java/cyder/handlers/input/TestHandler.java deleted file mode 100644 index 2cc4889fb..000000000 --- a/src/main/java/cyder/handlers/input/TestHandler.java +++ /dev/null @@ -1,132 +0,0 @@ -package cyder.handlers.input; - -import com.google.common.base.Preconditions; -import com.google.common.reflect.ClassPath; -import com.google.errorprone.annotations.CanIgnoreReturnValue; -import cyder.annotations.CyderTest; -import cyder.annotations.Handle; -import cyder.exceptions.FatalException; -import cyder.exceptions.IllegalMethodException; -import cyder.handlers.internal.ExceptionHandler; -import cyder.logging.LogTag; -import cyder.logging.Logger; -import cyder.strings.CyderStrings; -import cyder.threads.CyderThreadRunner; -import cyder.utils.ReflectionUtil; - -import java.lang.reflect.Method; - -/** - * A handler for invoking {@link cyder.annotations.CyderTest}s. - */ -public class TestHandler extends InputHandler { - /** - * The name of the default parameter. - */ - private static final String VALUE = "value"; - - /** - * Suppress default constructor. - */ - private TestHandler() { - throw new IllegalMethodException(CyderStrings.ATTEMPTED_INSTANTIATION); - } - - /** - * Invokes all tests with the default trigger of {@link #VALUE}. - */ - public static void invokeDefaultTests() { - Class clazz = CyderTest.class; - - Method method = null; - try { - method = clazz.getDeclaredMethod(VALUE); - } catch (Exception e) { - ExceptionHandler.handle(e); - } - - if (method == null) { - throw new FatalException("Failed reflection when attempting to find CyderTest's default value"); - } - String value = (String) method.getDefaultValue(); - invokeTestsWithTrigger(value); - } - - @Handle - public static boolean handle() { - boolean testTriggered = false; - - for (ClassPath.ClassInfo classInfo : ReflectionUtil.getCyderClasses()) { - Class classer = classInfo.load(); - - for (Method method : classer.getMethods()) { - if (method.isAnnotationPresent(CyderTest.class)) { - String trigger = method.getAnnotation(CyderTest.class).value(); - if (!trigger.equalsIgnoreCase(getInputHandler().commandAndArgsToString())) continue; - testTriggered = invokeTestsWithTrigger(trigger); - if (testTriggered) break; - } - } - } - - return testTriggered; - } - - /** - * Invokes any and all {@link CyderTest}s found with the provided trigger. - * - * @param trigger the trigger - * @return whether a test was invoked - */ - @CanIgnoreReturnValue - private static boolean invokeTestsWithTrigger(String trigger) { - Preconditions.checkNotNull(trigger); - Preconditions.checkArgument(!trigger.isEmpty()); - - boolean ret = false; - - for (ClassPath.ClassInfo classInfo : ReflectionUtil.getCyderClasses()) { - Class classer = classInfo.load(); - - for (Method method : classer.getMethods()) { - if (method.isAnnotationPresent(CyderTest.class)) { - if (!ReflectionUtil.isStatic(method)) { - Logger.log(LogTag.CYDER_TEST_WARNING, "CyderTest method" - + " found not static: " + method.getName()); - continue; - } - if (!ReflectionUtil.isPublic(method)) { - Logger.log(LogTag.CYDER_TEST_WARNING, "CyderTest method" - + " found not public: " + method.getName()); - continue; - } - if (!ReflectionUtil.returnsVoid(method)) { - Logger.log(LogTag.CYDER_TEST_WARNING, "CyderTest method" - + " found not void return: " + method.getName()); - continue; - } - - String testTrigger = method.getAnnotation(CyderTest.class).value(); - if (trigger.equalsIgnoreCase(testTrigger)) { - String threadName = "CyderTest thread runner, method: " + CyderStrings.quote - + method.getName() + CyderStrings.quote; - CyderThreadRunner.submit(() -> { - try { - Logger.log(LogTag.DEBUG, "Invoking CyderTest " - + CyderStrings.quote + method.getName() + CyderStrings.quote - + " in " + ReflectionUtil.getBottomLevelClass(classer)); - method.invoke(classer); - } catch (Exception e) { - ExceptionHandler.handle(e); - } - }, threadName); - - ret = true; - } - } - } - } - - return ret; - } -} diff --git a/src/main/java/cyder/handlers/input/ThreadHandler.java b/src/main/java/cyder/handlers/input/ThreadHandler.java deleted file mode 100644 index a5cbaa8a9..000000000 --- a/src/main/java/cyder/handlers/input/ThreadHandler.java +++ /dev/null @@ -1,37 +0,0 @@ -package cyder.handlers.input; - -import cyder.annotations.Handle; -import cyder.audio.GeneralAudioPlayer; -import cyder.exceptions.IllegalMethodException; -import cyder.strings.CyderStrings; -import cyder.threads.YoutubeUuidCheckerManager; - -/** - * A handler to handle things related to thread ops. - */ -public class ThreadHandler extends InputHandler { - /** - * Suppress default constructor. - */ - private ThreadHandler() { - throw new IllegalMethodException(CyderStrings.ATTEMPTED_INSTANTIATION); - } - - @Handle({"random youtube", "stop script", "stop music"}) - public static boolean handle() { - boolean ret = true; - - if (getInputHandler().inputIgnoringSpacesMatches("randomyoutube")) { - YoutubeUuidCheckerManager.INSTANCE.start(1); - } else if (getInputHandler().inputIgnoringSpacesMatches("stopscript")) { - YoutubeUuidCheckerManager.INSTANCE.killAll(); - getInputHandler().println("YouTube scripts have been killed."); - } else if (getInputHandler().inputIgnoringSpacesMatches("stopmusic")) { - GeneralAudioPlayer.stopGeneralAudio(); - } else { - ret = false; - } - - return ret; - } -} diff --git a/src/main/java/cyder/handlers/input/UiHandler.java b/src/main/java/cyder/handlers/input/UiHandler.java deleted file mode 100644 index 691c005c9..000000000 --- a/src/main/java/cyder/handlers/input/UiHandler.java +++ /dev/null @@ -1,144 +0,0 @@ -package cyder.handlers.input; - -import com.google.common.collect.ImmutableList; -import cyder.annotations.Handle; -import cyder.console.Console; -import cyder.constants.CyderColors; -import cyder.enumerations.ExitCondition; -import cyder.exceptions.IllegalMethodException; -import cyder.strings.CyderStrings; -import cyder.ui.UiUtil; -import cyder.ui.frame.CyderFrame; -import cyder.ui.slider.CyderSliderUi; -import cyder.ui.slider.ThumbShape; -import cyder.user.UserDataManager; -import cyder.user.creation.UserCreator; -import cyder.utils.OsUtil; - -import javax.swing.*; -import java.awt.*; -import java.awt.datatransfer.StringSelection; -import java.util.stream.IntStream; - -/** - * A handler for handling things related to the ui and painting. - */ -public class UiHandler extends InputHandler { - /** - * The slider used to change the opacity of the Console. - */ - private static JSlider opacitySlider; - - /** - * Suppress default constructor. - */ - private UiHandler() { - throw new IllegalMethodException(CyderStrings.ATTEMPTED_INSTANTIATION); - } - - @Handle({"toast", "opacity", "original chams", "screenshot frames", "monitors", - "create user", "panic", "quit", "logout", "clear clipboard", "mouse", "frames", "freeze"}) - public static boolean handle() { - boolean ret = true; - - if (getInputHandler().commandIs("toast")) { - Console.INSTANCE.getConsoleCyderFrame().toast("A toast to you, my liege"); - } else if (getInputHandler().commandIs("freeze")) { - //noinspection StatementWithEmptyBody - while (true) {} - } else if (getInputHandler().commandIs("opacity")) { - if (opacitySlider == null) initializeOpacitySlider(); - getInputHandler().println(opacitySlider); - } else if (getInputHandler().inputIgnoringSpacesMatches("original chams")) { - Console.INSTANCE.originalChams(); - } else if (getInputHandler().inputIgnoringSpacesMatches("screenshot frames")) { - UiUtil.screenshotCyderFrames(); - getInputHandler().println("Successfully saved to your Files directory"); - } else if (getInputHandler().commandIs("monitors")) { - StringBuilder printString = new StringBuilder("Monitor display modes: ").append(CyderStrings.newline); - ImmutableList modes = UiUtil.getMonitorDisplayModes(); - IntStream.range(0, modes.size()).forEach(index -> { - printString.append("Mode ").append(index + 1).append(CyderStrings.newline); - - DisplayMode displayMode = modes.get(index); - printString.append("Width: ").append(displayMode.getWidth()).append(CyderStrings.newline); - printString.append("Height: ").append(displayMode.getHeight()).append(CyderStrings.newline); - printString.append("Bit depth: ").append(displayMode.getBitDepth()).append(CyderStrings.newline); - printString.append("Refresh rate: ").append(displayMode.getRefreshRate()).append(CyderStrings.newline); - }); - - getInputHandler().println(printString.toString().trim()); - } else if (getInputHandler().inputIgnoringSpacesMatches("create user")) { - UserCreator.showGui(); - } else if (getInputHandler().commandIs("panic")) { - if (UserDataManager.INSTANCE.shouldMinimizeOnClose()) { - UiUtil.minimizeAllFrames(); - } else { - OsUtil.exit(ExitCondition.StandardControlledExit); - } - } else if (getInputHandler().commandIs("quit") - || getInputHandler().commandIs("exit") - || getInputHandler().commandIs("leave") - || getInputHandler().commandIs("close")) { - if (UserDataManager.INSTANCE.shouldMinimizeOnClose()) { - UiUtil.minimizeAllFrames(); - } else { - Console.INSTANCE.releaseResourcesAndCloseFrame(true); - } - } else if (getInputHandler().commandIs("logout")) { - Console.INSTANCE.logoutCurrentUserAndShowLoginFrame(); - } else if (getInputHandler().commandIs("mouse")) { - if (getInputHandler().checkArgsLength(2)) { - OsUtil.setMouseLocation(Integer.parseInt(getInputHandler().getArg(0)), - Integer.parseInt(getInputHandler().getArg(1))); - } else { - getInputHandler().println("Mouse command usage: mouse xPixelLocation, yPixelLocation"); - } - } else if (getInputHandler().inputIgnoringSpacesMatches("clear clipboard")) { - StringSelection selection = new StringSelection(null); - java.awt.datatransfer.Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); - clipboard.setContents(selection, selection); - getInputHandler().println("Clipboard has been reset."); - } else if (getInputHandler().commandIs("frames")) { - for (CyderFrame frame : UiUtil.getCyderFrames()) { - getInputHandler().println(frame); - } - } else { - ret = false; - } - - return ret; - } - - /** - * Sets up the opacity slider. - */ - private static void initializeOpacitySlider() { - opacitySlider = new JSlider(SwingConstants.HORIZONTAL, 0, 100, 100); - opacitySlider.setBounds(0, 0, 300, 50); - CyderSliderUi ui = new CyderSliderUi(opacitySlider); - ui.setThumbStroke(new BasicStroke(2.0f)); - ui.setThumbShape(ThumbShape.CIRCLE); - ui.setThumbRadius(35); - ui.setThumbFillColor(CyderColors.navy); - ui.setThumbOutlineColor(CyderColors.vanilla); - ui.setRightThumbColor(CyderColors.vanilla); - ui.setLeftThumbColor(CyderColors.regularPink); - ui.setTrackStroke(new BasicStroke(3.0f)); - opacitySlider.setUI(ui); - opacitySlider.setPaintTicks(false); - opacitySlider.setPaintLabels(false); - opacitySlider.setVisible(true); - opacitySlider.setValue((int) (Console.INSTANCE.getConsoleCyderFrame().getOpacity() - * opacitySlider.getMaximum())); - opacitySlider.addChangeListener(e -> { - float opacity = opacitySlider.getValue() / (float) opacitySlider.getMaximum(); - Console.INSTANCE.getConsoleCyderFrame().setOpacity(opacity); - opacitySlider.repaint(); - }); - opacitySlider.setOpaque(false); - opacitySlider.setToolTipText("Opacity"); - opacitySlider.setFocusable(false); - opacitySlider.repaint(); - } -} diff --git a/src/main/java/cyder/handlers/input/UrlHandler.java b/src/main/java/cyder/handlers/input/UrlHandler.java deleted file mode 100644 index e1e1e9e5d..000000000 --- a/src/main/java/cyder/handlers/input/UrlHandler.java +++ /dev/null @@ -1,116 +0,0 @@ -package cyder.handlers.input; - -import com.google.common.base.Preconditions; -import com.google.common.collect.ImmutableList; -import cyder.annotations.Handle; -import cyder.constants.CyderRegexPatterns; -import cyder.constants.CyderUrls; -import cyder.exceptions.IllegalMethodException; -import cyder.network.NetworkUtil; -import cyder.strings.CyderStrings; - -import java.net.URL; - -/** - * A handler for opening urls. - */ -public class UrlHandler extends InputHandler { - /** - * Suppress default constructor. - */ - private UrlHandler() { - throw new IllegalMethodException(CyderStrings.ATTEMPTED_INSTANTIATION); - } - - /** - * The YouTube base url for searching for specific words within a YouTube url. - */ - public static final String YOUTUBE_WORD_SEARCH_BASE = - "https://www.google.com/search?q=allinurl:REPLACE site:youtube.com"; - - /** - * A record to link a trigger to a url and the printable version name. - */ - private record CyderUrl(String trigger, String url, String printable) {} - - /** - * The list of urls to search trough before attempting to open the raw user input. - */ - private static final ImmutableList urls = ImmutableList.of( - new CyderUrl("desmos", CyderUrls.DESMOS, "Opening Desmos graphing calculator"), - new CyderUrl("404", CyderUrls.GOOGLE_404, "Opening a 404 error"), - new CyderUrl("coffee", CyderUrls.COFFEE_SHOPS, "Finding coffee shops near you"), - new CyderUrl("quake3", CyderUrls.QUAKE_3, - "Opening a video about the Quake 3 fast inverse square root algorithm"), - new CyderUrl("triangle", CyderUrls.TRIANGLE, "Opening triangle calculator"), - new CyderUrl("board", CyderUrls.FLY_SQUIRREL_FLY_HTML, "Opening a slingshot game"), - new CyderUrl("arduino", CyderUrls.ARDUINO, "Raspberry pis are better"), - new CyderUrl("raspberrypi", CyderUrls.RASPBERRY_PI, "Arduinos are better"), - new CyderUrl("vexento", CyderUrls.VEXENTO, "Opening a great artist"), - new CyderUrl("papersplease", CyderUrls.PAPERS_PLEASE, "Opening a great game"), - new CyderUrl("donut", CyderUrls.DUNKIN_DONUTS, "Dunkin' Hoes; the world runs on it"), - new CyderUrl("bai", CyderUrls.BAI, "The best drink"), - new CyderUrl("occamsrazor", CyderUrls.OCCAM_RAZOR, "Opening Occam's razor"), - new CyderUrl("rickandmorty", CyderUrls.PICKLE_RICK, - "Turned myself into a pickle morty! Boom! Big reveal; I'm a pickle!"), - new CyderUrl("about:blank", "about:blank", "Opening about:blank") - ); - - @Handle - public static boolean handle() { - boolean ret = true; - - for (CyderUrl url : urls) { - if (getInputHandler().commandIs(url.trigger())) { - getInputHandler().println(url.printable()); - NetworkUtil.openUrl(url.url()); - return true; - } - } - - if (getInputHandler().commandIs("YoutubeWordSearch")) { - youTubeWordSearch(); - } else { - String possibleUrl = getInputHandler().commandAndArgsToString(); - if (urlValid(possibleUrl)) { - NetworkUtil.openUrl(possibleUrl); - } else { - ret = false; - } - } - - return ret; - } - - /** - * Performs the YouTube word search routine on the user-entered input. - */ - private static void youTubeWordSearch() { - if (getInputHandler().checkArgsLength(1)) { - String input = getInputHandler().getArg(0); - String browse = YOUTUBE_WORD_SEARCH_BASE - .replace("REPLACE", input).replace(CyderRegexPatterns.whiteSpaceRegex, "+"); - NetworkUtil.openUrl(browse); - } else { - getInputHandler().println("YoutubeWordSearch usage: YoutubeWordSearch WORD_TO_FIND"); - } - } - - /** - * Returns whether the provided url is valid. - * - * @param url the url to validate - * @return whether the provided url is valid - */ - private static boolean urlValid(String url) { - Preconditions.checkNotNull(url); - Preconditions.checkArgument(!url.isEmpty()); - - try { - new URL(url).openConnection(); - return true; - } catch (Exception ignored) {} - - return false; - } -} diff --git a/src/main/java/cyder/handlers/input/UserDataHandler.java b/src/main/java/cyder/handlers/input/UserDataHandler.java deleted file mode 100644 index d6dac4c70..000000000 --- a/src/main/java/cyder/handlers/input/UserDataHandler.java +++ /dev/null @@ -1,93 +0,0 @@ -package cyder.handlers.input; - -import cyder.annotations.Handle; -import cyder.annotations.SuppressCyderInspections; -import cyder.constants.CyderRegexPatterns; -import cyder.enumerations.CyderInspection; -import cyder.exceptions.IllegalMethodException; -import cyder.strings.CyderStrings; -import cyder.user.UserData; -import cyder.user.UserDataManager; -import cyder.user.UserEditor; -import cyder.utils.BooleanUtils; - -import java.util.Optional; - -/** - * A handler for switching/toggling user data. - */ -public class UserDataHandler extends InputHandler { - /** - * Suppress default constructor. - */ - private UserDataHandler() { - throw new IllegalMethodException(CyderStrings.ATTEMPTED_INSTANTIATION); - } - - @SuppressCyderInspections(CyderInspection.HandleInspection) - @Handle({"userdata-files", "userdata-fonts", "userdata-colors", "userdata-booleans", "userdata-fields"}) - public static boolean handle() { - if (getInputHandler().inputIgnoringSpacesMatches("userdata-files")) { - UserEditor.showGui(UserEditor.Page.FILES); - return true; - } else if (getInputHandler().inputIgnoringSpacesMatches("userdata-fonts")) { - UserEditor.showGui(UserEditor.Page.FONT_AND_COLOR); - return true; - } else if (getInputHandler().inputIgnoringSpacesMatches("userdata-colors")) { - UserEditor.showGui(UserEditor.Page.FONT_AND_COLOR); - return true; - } else if (getInputHandler().inputIgnoringSpacesMatches("userdata-booleans")) { - UserEditor.showGui(UserEditor.Page.BOOLEANS); - return true; - } else if (getInputHandler().inputIgnoringSpacesMatches("userdata-fields")) { - UserEditor.showGui(UserEditor.Page.FIELDS); - return true; - } - - return attemptUserDataToggle(); - } - - /** - * Attempts to find a user data with the user's input and toggle the parity of it. - * - * @return whether a user data could be found and toggled - */ - private static boolean attemptUserDataToggle() { - String targetedUserData = getInputHandler().getCommand(); - String parsedArgs = getInputHandler().argsToString() - .replaceAll(CyderRegexPatterns.whiteSpaceRegex, ""); - - for (UserData userdata : UserData.getUserDatas()) { - if (targetedUserData.equalsIgnoreCase(userdata.getId())) { - if (userdata.getType().equals(Boolean.class) && !userdata.shouldIgnoreForToggleSwitches()) { - Optional optionalOldValue = - UserDataManager.INSTANCE.getUserDataById(userdata.getId(), Boolean.class); - if (optionalOldValue.isEmpty()) return false; - - boolean oldValue = optionalOldValue.get(); - - boolean newValue; - if (BooleanUtils.isTrue(parsedArgs)) { - newValue = true; - } else if (BooleanUtils.isFalse(parsedArgs)) { - newValue = false; - } else { - newValue = !oldValue; - } - - boolean toggled = UserDataManager.INSTANCE.setUserDataById(userdata.getId(), newValue); - if (toggled) { - getInputHandler().println(userdata.getId() + " set to " + newValue); - UserData.invokeRefresh(userdata.getId()); - } else { - getInputHandler().println("Failed to set " + userdata.getId()); - } - - return true; - } - } - } - - return false; - } -} diff --git a/src/main/java/cyder/handlers/input/WidgetHandler.java b/src/main/java/cyder/handlers/input/WidgetHandler.java deleted file mode 100644 index 4786946f8..000000000 --- a/src/main/java/cyder/handlers/input/WidgetHandler.java +++ /dev/null @@ -1,68 +0,0 @@ -package cyder.handlers.input; - -import com.google.common.reflect.ClassPath; -import cyder.annotations.Handle; -import cyder.annotations.Widget; -import cyder.console.Console; -import cyder.constants.CyderRegexPatterns; -import cyder.exceptions.IllegalMethodException; -import cyder.handlers.internal.ExceptionHandler; -import cyder.logging.LogTag; -import cyder.logging.Logger; -import cyder.strings.CyderStrings; -import cyder.utils.ReflectionUtil; - -import java.lang.reflect.Method; - -/** - * A handler for opening classes tagged as a widget via the @Widget annotation. - */ -public class WidgetHandler extends InputHandler { - /** - * Suppress default constructor. - */ - private WidgetHandler() { - throw new IllegalMethodException(CyderStrings.ATTEMPTED_INSTANTIATION); - } - - @Handle - public static boolean handle() { - for (ClassPath.ClassInfo classInfo : ReflectionUtil.getCyderClasses()) { - Class clazz = classInfo.load(); - - for (Method m : clazz.getMethods()) { - if (m.isAnnotationPresent(Widget.class)) { - String[] widgetTriggers = m.getAnnotation(Widget.class).triggers(); - - for (String widgetTrigger : widgetTriggers) { - widgetTrigger = widgetTrigger.replaceAll(CyderRegexPatterns.whiteSpaceRegex, ""); - String userInput = getInputHandler().commandAndArgsToString() - .replaceAll(CyderRegexPatterns.whiteSpaceRegex, ""); - - if (widgetTrigger.equalsIgnoreCase(userInput)) { - String shortWidgetName = ReflectionUtil.getBottomLevelClass(clazz); - Console.INSTANCE.getInputHandler().println("Opening widget: " + shortWidgetName); - try { - if (m.getParameterCount() == 0) { - m.invoke(clazz); - - Logger.log(LogTag.WIDGET_OPENED, - shortWidgetName + ", trigger = " + - getInputHandler().commandAndArgsToString()); - - return true; - } else - throw new IllegalStateException("Found widget showGui()" + - " annotated method with parameters: " + m.getName() + ", class: " + clazz); - } catch (Exception e) { - ExceptionHandler.handle(e); - } - } - } - } - } - } - - return false; - } -} diff --git a/src/main/java/cyder/handlers/input/WrappedCommandHandler.java b/src/main/java/cyder/handlers/input/WrappedCommandHandler.java deleted file mode 100644 index 2b38f4d46..000000000 --- a/src/main/java/cyder/handlers/input/WrappedCommandHandler.java +++ /dev/null @@ -1,84 +0,0 @@ -package cyder.handlers.input; - -import cyder.annotations.Handle; -import cyder.exceptions.IllegalMethodException; -import cyder.handlers.internal.ExceptionHandler; -import cyder.strings.CyderStrings; - -/** - * A handler for inner commands wrapped with arguments such as size(x), floor(x, y), etc. - */ -public class WrappedCommandHandler extends InputHandler { - /** - * Suppress default constructor. - */ - private WrappedCommandHandler() { - throw new IllegalMethodException(CyderStrings.ATTEMPTED_INSTANTIATION); - } - - @Handle - public static boolean handle() { - boolean ret = true; - - String command = getInputHandler().commandAndArgsToString(); - - try { - int firstParen = command.indexOf(CyderStrings.openingParenthesis); - int comma = command.indexOf(","); - int lastParen = command.indexOf(CyderStrings.closingParenthesis); - - String operation; - String param1 = ""; - String param2 = ""; - - if (firstParen != -1) { - operation = command.substring(0, firstParen); - - if (comma != -1) { - param1 = command.substring(firstParen + 1, comma); - - if (lastParen != -1) { - param2 = command.substring(comma + 1, lastParen); - } - } else if (lastParen != -1) { - param1 = command.substring(firstParen + 1, lastParen); - } - - if (operation.equalsIgnoreCase("abs")) { - getInputHandler().println(Math.abs(Double.parseDouble(param1))); - } else if (operation.equalsIgnoreCase("ceil")) { - getInputHandler().println(Math.ceil(Double.parseDouble(param1))); - } else if (operation.equalsIgnoreCase("floor")) { - getInputHandler().println(Math.floor(Double.parseDouble(param1))); - } else if (operation.equalsIgnoreCase("log")) { - getInputHandler().println(Math.log(Double.parseDouble(param1))); - } else if (operation.equalsIgnoreCase("log10")) { - getInputHandler().println(Math.log10(Double.parseDouble(param1))); - } else if (operation.equalsIgnoreCase("max")) { - getInputHandler().println(Math.max(Double.parseDouble(param1), Double.parseDouble(param2))); - } else if (operation.equalsIgnoreCase("min")) { - getInputHandler().println(Math.min(Double.parseDouble(param1), Double.parseDouble(param2))); - } else if (operation.equalsIgnoreCase("pow")) { - getInputHandler().println(Math.pow(Double.parseDouble(param1), Double.parseDouble(param2))); - } else if (operation.equalsIgnoreCase("round")) { - getInputHandler().println(Math.round(Double.parseDouble(param1))); - } else if (operation.equalsIgnoreCase("sqrt")) { - getInputHandler().println(Math.sqrt(Double.parseDouble(param1))); - } else if (operation.equalsIgnoreCase("tobinary")) { - getInputHandler().println(Integer.toBinaryString((int) (Double.parseDouble(param1)))); - } else if (operation.equalsIgnoreCase("size")) { - getInputHandler().println("Size: " + (lastParen - firstParen - 1)); - } else { - ret = false; - } - } else { - ret = false; - } - } catch (Exception e) { - ExceptionHandler.handle(e); - ret = false; - } - - return ret; - } -} diff --git a/src/main/java/cyder/handlers/input/package-info.java b/src/main/java/cyder/handlers/input/package-info.java deleted file mode 100644 index 2029d315b..000000000 --- a/src/main/java/cyder/handlers/input/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -/** - * Handlers for the {@link cyder.handlers.input.BaseInputHandler} to pass input from the console's input field to. - */ -package cyder.handlers.input; diff --git a/src/main/java/cyder/handlers/internal/ExceptionHandler.java b/src/main/java/cyder/handlers/internal/ExceptionHandler.java deleted file mode 100644 index 89dc95bd2..000000000 --- a/src/main/java/cyder/handlers/internal/ExceptionHandler.java +++ /dev/null @@ -1,359 +0,0 @@ -package cyder.handlers.internal; - -import com.google.common.base.Preconditions; -import com.google.common.collect.ImmutableList; -import cyder.annotations.ForReadability; -import cyder.console.Console; -import cyder.constants.CyderColors; -import cyder.constants.CyderFonts; -import cyder.constants.CyderRegexPatterns; -import cyder.constants.HtmlTags; -import cyder.enumerations.ExitCondition; -import cyder.exceptions.FatalException; -import cyder.exceptions.IllegalMethodException; -import cyder.logging.LogTag; -import cyder.logging.Logger; -import cyder.strings.CyderStrings; -import cyder.strings.StringUtil; -import cyder.threads.CyderThreadRunner; -import cyder.threads.ThreadUtil; -import cyder.ui.frame.CyderFrame; -import cyder.ui.frame.enumerations.FrameType; -import cyder.ui.frame.enumerations.ScreenPosition; -import cyder.user.UserDataManager; -import cyder.utils.OsUtil; - -import javax.swing.*; -import java.awt.*; -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; -import java.io.PrintWriter; -import java.io.StringWriter; -import java.util.Optional; -import java.util.concurrent.atomic.AtomicBoolean; - -/** - * A class to handle and log exceptions thrown throughout Cyder. - */ -public final class ExceptionHandler { - /** - * The red color used for exception panes. - */ - private static final Color exceptionRed = new Color(254, 157, 158); - - /** - * The opacity delta to increment/decrement by. - */ - private static final float opacityShiftDelta = 0.05f; - - /** - * The number of lines to display on an exception preview. - */ - private static final int shownExceptionLines = 10; - - /** - * The insets offset for the exception label on the frame. - */ - private static final int offset = 10; - - /** - * The timeout between opacity increments/decrements. - */ - private static final int opacityTimeout = 20; - - /** - * The prefix for the thread name for exception popup animators. - */ - private static final String exceptionPopupThreadAnimatorNamePrefix = "Exception Popup Opacity Animator: "; - - /** - * The at keyword to split a stack trace at to find the first line number. - */ - private static final String AT = "at"; - - /** - * The minimum opacity for exception popup animations. - */ - private static final float minimumOpacity = 0.0f; - - /** - * The maximum opacity for exception popup animations. - */ - private static final float maximumOpacity = 1.0f; - - /** - * The time the exception popup should be visible between fade-in and fade-out animations. - */ - private static final int exceptionPopupVisibilityTime = 3000; - - /** - * The exception string. - */ - private static final String EXCEPTION = "Exception"; - - /** - * The name of the thread to animate out exception popups. - */ - private static final String exceptionPopupDisposeAnimatorThreadName = "Exception popup dispose animator"; - - /** - * The font to use for exception popups. - */ - private static final Font exceptionPopupFont = CyderFonts.DEFAULT_FONT_SMALL; - - /** - * Suppress default constructor. - */ - private ExceptionHandler() { - throw new IllegalMethodException(CyderStrings.ATTEMPTED_INSTANTIATION); - } - - /** - * Handles an exception by converting it to a string representation and passing it to the {@link Logger}. - * - * @param exception the exception to handle - */ - public static void handle(Exception exception) { - try { - Optional optionalWrite = getPrintableException(exception); - if (optionalWrite.isPresent()) { - String write = optionalWrite.get(); - if (!write.replaceAll(CyderRegexPatterns.whiteSpaceRegex, "").isEmpty()) { - Logger.log(LogTag.EXCEPTION, write); - - // todo this throws a lot - boolean consoleOpen = Console.INSTANCE.getUuid() != null && !Console.INSTANCE.isClosed(); - boolean silenceErrors = UserDataManager.INSTANCE.shouldSilenceErrors(); - if (consoleOpen && !silenceErrors) { - String message = "Exception"; - if (exception != null) { - String exceptionMessage = exception.getMessage(); - if (exceptionMessage != null && !exceptionMessage.isEmpty()) { - message = exceptionMessage; - } - } - showExceptionPane(write, message); - } - } - } - } catch (Exception uhOh) { - if (exception != null) exception.printStackTrace(); - uhOh.printStackTrace(); - } - } - - /** - * Shows a popup pane containing a preview of the exception. - * If the user clicks on the popup, it vanishes immediately and the - * current log is opened externally. - * - * @param printableException the printable exception text - * @param exceptionMessage the result of invoking {@link Exception#getMessage()} - */ - private static void showExceptionPane(String printableException, String exceptionMessage) { - Preconditions.checkNotNull(printableException); - Preconditions.checkArgument(!printableException.isEmpty()); - Preconditions.checkNotNull(exceptionMessage); - Preconditions.checkArgument(!exceptionMessage.isEmpty()); - - AtomicBoolean escapeOpacityThread = new AtomicBoolean(); - ImmutableList exceptionLines = ImmutableList.copyOf(printableException.split(CyderStrings.newline)); - - int labelWidth = 0; - for (int i = 0 ; i < shownExceptionLines ; i++) { - int currentLineWidth = StringUtil.getMinWidth(exceptionLines.get(i), exceptionPopupFont); - labelWidth = Math.max(labelWidth, currentLineWidth); - } - - int lineHeight = StringUtil.getAbsoluteMinHeight(exceptionLines.get(0), exceptionPopupFont); - int labelHeight = lineHeight * shownExceptionLines + 2 * offset; - - StringBuilder builder = new StringBuilder(HtmlTags.openingHtml); - for (int i = 0 ; i < shownExceptionLines ; i++) { - builder.append(exceptionLines.get(i)); - if (i != exceptionLines.size() - 1) builder.append(HtmlTags.breakTag); - } - builder.append(HtmlTags.closingHtml); - - CyderFrame borderlessFrame = new CyderFrame.Builder() - .setWidth(labelWidth + 2 * offset) - .setHeight(labelHeight + 2 * offset) - .setBackgroundIconFromColor(exceptionRed) - .setBackgroundColor(exceptionRed) - .setTitle(exceptionMessage) - .setType(FrameType.POPUP) - .setBorderless(true) - .build(); - - String labelText = builder.toString(); - JLabel label = generatePopupLabel(labelText, escapeOpacityThread, borderlessFrame); - label.setBounds(offset, offset, labelWidth, labelHeight); - borderlessFrame.getContentPane().add(label); - - borderlessFrame.setLocationOnScreen(ScreenPosition.BOTTOM_RIGHT); - borderlessFrame.setOpacity(minimumOpacity); - borderlessFrame.setVisible(true); - - String threadName = exceptionPopupThreadAnimatorNamePrefix + exceptionMessage; - CyderThreadRunner.submit(() -> { - try { - for (float i = minimumOpacity ; i <= maximumOpacity ; i += opacityShiftDelta) { - if (escapeOpacityThread.get()) return; - borderlessFrame.setOpacity(i); - borderlessFrame.repaint(); - ThreadUtil.sleep(opacityTimeout); - } - - borderlessFrame.setOpacity(maximumOpacity); - borderlessFrame.repaint(); - - ThreadUtil.sleep(exceptionPopupVisibilityTime); - if (escapeOpacityThread.get()) return; - - for (float i = maximumOpacity ; i >= minimumOpacity ; i -= opacityShiftDelta) { - if (escapeOpacityThread.get()) return; - borderlessFrame.setOpacity(i); - borderlessFrame.repaint(); - ThreadUtil.sleep(opacityTimeout); - } - - borderlessFrame.setOpacity(minimumOpacity); - borderlessFrame.repaint(); - - if (escapeOpacityThread.get()) return; - borderlessFrame.dispose(true); - } catch (Exception ex) { - ex.printStackTrace(); - } - }, threadName); - } - - /** - * Generates the label for exception popups. - * - * @param labelText the label text - * @param escapeOpacityThread the atomic boolean for escaping the opacity animation. - * @param frame the frame the returned JLabel will be added to - * @return the generated label - */ - @ForReadability - private static JLabel generatePopupLabel(String labelText, AtomicBoolean escapeOpacityThread, CyderFrame frame) { - JLabel label = new JLabel(labelText); - label.addMouseListener(new MouseAdapter() { - @Override - public void mouseClicked(MouseEvent e) { - escapeOpacityThread.set(true); - - CyderThreadRunner.submit(() -> { - for (float i = frame.getOpacity() ; i >= minimumOpacity ; i -= opacityShiftDelta) { - frame.setOpacity(i); - frame.repaint(); - ThreadUtil.sleep(opacityTimeout / 2); - } - - frame.dispose(true); - }, exceptionPopupDisposeAnimatorThreadName); - } - }); - label.setForeground(CyderColors.navy); - label.setFont(CyderFonts.DEFAULT_FONT_SMALL); - label.setHorizontalAlignment(JLabel.CENTER); - label.setVerticalAlignment(JLabel.CENTER); - return label; - } - - /** - * Generates a printable version of the exception. - * - * @param e the exception to return a printable version of - * @return Optional String possibly containing exception details and trace - */ - public static Optional getPrintableException(Exception e) { - if (e == null) { - return Optional.empty(); - } - - StringWriter sw = new StringWriter(); - PrintWriter pw = new PrintWriter(sw); - e.printStackTrace(pw); - String stackTrace = sw.toString(); - - ImmutableList stackTraceElements = ImmutableList.copyOf(e.getStackTrace()); - if (stackTraceElements.isEmpty()) return Optional.empty(); - StackTraceElement stackTraceElement = stackTraceElements.get(0); - - int lineNumber = stackTraceElement.getLineNumber(); - String lineNumberRepresentation = lineNumber == 0 - ? "Throwing line not found in stacktrace" - : "From line: " + lineNumber; - - String declaringClass = stackTraceElement.getClassName(); - String declaringClassRepresentation = "Declaring class: " + declaringClass; - - String filename = stackTraceElement.getFileName(); - String filenameRepresentation = "Filename: " + filename; - - String atRegex = CyderRegexPatterns.whiteSpaceRegex + AT + CyderRegexPatterns.whiteSpaceRegex; - ImmutableList splitStackAt = ImmutableList.copyOf(stackTrace.split(atRegex)); - - StringBuilder retBuilder = new StringBuilder(CyderStrings.newline); - if (!splitStackAt.isEmpty()) { - String origin = splitStackAt.get(0); - retBuilder.append("Exception origin: ").append(origin); - } else { - retBuilder.append("Exception origin not found"); - } - - retBuilder.append(CyderStrings.newline).append(filenameRepresentation); - retBuilder.append(CyderStrings.newline).append(declaringClassRepresentation); - retBuilder.append(CyderStrings.newline).append(lineNumberRepresentation); - retBuilder.append(CyderStrings.newline).append("Trace: ").append(stackTrace); - return Optional.of(retBuilder.toString()); - } - - /** - * Shows a popup with the provided error message. When the opened popup frame is disposed, - * Cyder exits. - * - * @param message the message of the popup - * @param condition the exit condition to log when exiting - * @param title the title of the popup - */ - public static void exceptionExit(String message, ExitCondition condition, String title) { - Preconditions.checkNotNull(message); - Preconditions.checkArgument(!message.isEmpty()); - Preconditions.checkNotNull(condition); - Preconditions.checkNotNull(title); - Preconditions.checkArgument(!title.isEmpty()); - - new InformHandler.Builder(message) - .setTitle(title) - .setPostCloseAction(() -> OsUtil.exit(condition)).inform(); - } - - /** - * Shows a popup with the provided error message. When the opened popup frame is disposed, - * Cyder exits. - * - * @param message the message of the popup - * @param condition the exit condition to log when exiting - */ - public static void exceptionExit(String message, ExitCondition condition) { - exceptionExit(message, condition, EXCEPTION); - } - - /** - * Validates the provided condition, throwing a fatal exception if false. - * - * @param condition the condition to validate - * @param fatalExceptionText the exception text if the condition is false - */ - public static void checkFatalCondition(boolean condition, String fatalExceptionText) { - Preconditions.checkNotNull(fatalExceptionText); - Preconditions.checkArgument(!fatalExceptionText.isEmpty()); - - if (!condition) { - throw new FatalException(fatalExceptionText); - } - } -} \ No newline at end of file diff --git a/src/main/java/cyder/handlers/internal/InformHandler.java b/src/main/java/cyder/handlers/internal/InformHandler.java deleted file mode 100644 index ba7981f12..000000000 --- a/src/main/java/cyder/handlers/internal/InformHandler.java +++ /dev/null @@ -1,360 +0,0 @@ -package cyder.handlers.internal; - -import com.google.common.base.Preconditions; -import com.google.errorprone.annotations.CanIgnoreReturnValue; -import cyder.bounds.BoundsString; -import cyder.bounds.BoundsUtil; -import cyder.constants.CyderColors; -import cyder.exceptions.IllegalMethodException; -import cyder.logging.LogTag; -import cyder.logging.Logger; -import cyder.strings.CyderStrings; -import cyder.strings.StringUtil; -import cyder.ui.drag.CyderDragLabel; -import cyder.ui.frame.CyderFrame; -import cyder.ui.frame.enumerations.FrameType; -import cyder.ui.label.CyderLabel; -import cyder.utils.HtmlUtil; - -import javax.swing.*; -import java.awt.*; -import java.awt.event.WindowAdapter; -import java.awt.event.WindowEvent; - -/** - * Information frames throughout Cyder. - */ -public final class InformHandler { - /** - * The width padding on each side of the information pane. - */ - private static final int frameXPadding = 10; - - /** - * The offset to translate the text label on the information pane down by. - */ - private static final int frameYOffset = CyderDragLabel.DEFAULT_HEIGHT; - - /** - * THe height padding on each side of the information pane. - */ - private static final int frameYPadding = 10; - - /** - * Suppress default constructor. - */ - private InformHandler() { - throw new IllegalMethodException(CyderStrings.ATTEMPTED_INSTANTIATION); - } - - /** - * A quick information pane. - * - * @param text the possibly html styled text to display - * @return a reference to the shown inform frame - * @throws IllegalArgumentException if the provided text is null - */ - @CanIgnoreReturnValue /* Calls don't always need the reference */ - public static CyderFrame inform(String text) { - Preconditions.checkNotNull(text); - - return inform(new Builder(text).setTitle(Builder.DEFAULT_TITLE)); - } - - /** - * Opens an information using the information provided by builder. - * - * @param builder the builder to use for the construction of the information pane - * @return a reference to the shown inform frame - * @throws IllegalArgumentException if the provided builder is null - */ - @CanIgnoreReturnValue /* Calls don't usually need the reference */ - public static CyderFrame inform(Builder builder) { - Preconditions.checkNotNull(builder); - - CyderFrame informFrame; - - // custom container - if (builder.getContainer() != null) { - int containerWidth = builder.getContainer().getWidth(); - int containerHeight = builder.getContainer().getHeight(); - - int xPadding = 1; - int yTopPadding = CyderDragLabel.DEFAULT_HEIGHT - 4; - int yBottomPadding = 2; - - informFrame = new CyderFrame.Builder() - .setWidth(containerWidth + 2 * xPadding) - .setHeight(containerHeight + yTopPadding + yBottomPadding) - .setTitle(builder.getTitle()) - .build(); - - JLabel container = builder.getContainer(); - container.setBounds(xPadding, yTopPadding, - containerWidth, containerHeight); - informFrame.getContentPane().add(container); - } - // intended to generate a text inform pane - else { - CyderLabel textLabel = new CyderLabel(builder.getHtmlText()); - textLabel.setOpaque(false); - textLabel.setForeground(CyderColors.defaultLightModeTextColor); - - BoundsString boundsString = BoundsUtil.widthHeightCalculation(builder.getHtmlText()); - - int containerWidth = boundsString.getWidth(); - int containerHeight = boundsString.getHeight(); - - textLabel.setText(HtmlUtil.addCenteringToHtml(boundsString.getText())); - - builder.setContainer(textLabel); - - informFrame = new CyderFrame.Builder() - .setWidth(containerWidth + frameXPadding * 2) - .setHeight(containerHeight + frameYOffset + 2 * frameYPadding) - .build(); - informFrame.setTitle(builder.getTitle()); - - int containerX = informFrame.getWidth() / 2 - containerWidth / 2; - int containerY = informFrame.getHeight() / 2 - containerHeight / 2 + 5; - - JLabel container = builder.getContainer(); - container.setBounds(containerX, containerY, containerWidth, containerHeight); - informFrame.add(container); - } - - if (builder.getPreCloseAction() != null) { - informFrame.addPreCloseAction(builder.getPreCloseAction()); - } - if (builder.getPostCloseAction() != null) { - informFrame.addPostCloseAction(builder.getPostCloseAction()); - } - - Component relativeTo = builder.getRelativeTo(); - - // if intended to disable relative to - if (relativeTo != null && builder.isDisableRelativeTo()) { - relativeTo.setEnabled(false); - informFrame.addWindowListener(new WindowAdapter() { - @Override - public void windowClosed(WindowEvent e) { - relativeTo.setEnabled(true); - } - }); - } - - informFrame.setFrameType(FrameType.POPUP); - informFrame.setVisible(true); - informFrame.setLocationRelativeTo(relativeTo); - - Logger.log(LogTag.UI_ACTION, "[INFORMATION PANE] text = \"" - + builder.getHtmlText() + "\", relativeTo = " + builder.getRelativeTo()); - - return informFrame; - } - - /** - * A builder for an information pane. - */ - public static class Builder { - /** - * The minimum allowable text length for an information pane. - */ - public static final int MINIMUM_TEXT_LENGTH = 4; - - /** - * The default title for an information pane which are provided no title. - */ - public static final String DEFAULT_TITLE = "Information"; - - /** - * The text, possibly styled with html elements, to display on the information pane. - */ - private final String htmlText; - - /** - * The title of this information pane. - */ - private String title = DEFAULT_TITLE; - - /** - * The component to set this inform frame relative to before the call to setVisible(). - */ - private Component relativeTo; - - /** - * The action to invoke before the frame is closed. - */ - private Runnable preCloseAction; - - /** - * The action to invoke after the frame is closed. - */ - private Runnable postCloseAction; - - /** - * The custom container component to layer on the CyderFrame content pane. - */ - private JLabel container; - - /** - * Whether to disable the relative to component. - */ - private boolean disableRelativeTo; - - /** - * Default constructor for an inform pane with the required parameters. - * - * @param htmlText the html styled text to display on the inform pane - */ - public Builder(String htmlText) { - Preconditions.checkNotNull(htmlText); - Preconditions.checkArgument(StringUtil.getTextLengthIgnoringHtmlTags(htmlText) >= MINIMUM_TEXT_LENGTH); - - this.htmlText = htmlText; - Logger.log(LogTag.OBJECT_CREATION, this); - } - - /** - * Returns the text associated with this builder, possibly containing html style tags. - * - * @return the text associated with this builder, possibly containing html style tags - */ - public String getHtmlText() { - return htmlText; - } - - /** - * Returns the title for the frame. - * - * @return the title for the frame - */ - public String getTitle() { - return title; - } - - /** - * Sets the title for the frame. - * - * @param title the title for the frame - * @return this builder - */ - @CanIgnoreReturnValue - public Builder setTitle(String title) { - this.title = title; - return this; - } - - /** - * Returns the component to set the frame relative to. - * - * @return the component to set the frame relative to - */ - public Component getRelativeTo() { - return relativeTo; - } - - /** - * Sets the component to set the frame relative to. - * - * @param relativeTo the component to set the frame relative to - * @return this builder - */ - @CanIgnoreReturnValue - public Builder setRelativeTo(Component relativeTo) { - this.relativeTo = relativeTo; - return this; - } - - /** - * Returns the pre close action to invoke before disposing the frame. - * - * @return the pre close action to invoke before disposing the frame - */ - public Runnable getPreCloseAction() { - return preCloseAction; - } - - /** - * Sets the pre close action to invoke before disposing the frame. - * - * @param preCloseAction the pre close action to invoke before disposing the frame - * @return this builder - */ - @CanIgnoreReturnValue - public Builder setPreCloseAction(Runnable preCloseAction) { - this.preCloseAction = preCloseAction; - return this; - } - - /** - * Returns the post close action to invoke before disposing the frame. - * - * @return the post close action to invoke before disposing the frame - */ - public Runnable getPostCloseAction() { - return postCloseAction; - } - - /** - * Sets the post close action to invoke before closing the frame. - * - * @param postCloseAction the post close action to invoke before closing the frame - * @return this builder - */ - @CanIgnoreReturnValue - public Builder setPostCloseAction(Runnable postCloseAction) { - this.postCloseAction = postCloseAction; - return this; - } - - /** - * Returns the container to use for the frame's pane. - * - * @return the container to use for the frame's pane - */ - public JLabel getContainer() { - return container; - } - - /** - * Sets the container to use for the frame's pane. - * - * @param container the container to use for the frame's pane - * @return this builder - */ - @CanIgnoreReturnValue - public Builder setContainer(JLabel container) { - this.container = container; - return this; - } - - /** - * Returns whether to disable the relative to component upon showing this dialog. - * - * @return whether to disable the relative to component upon showing this dialog - */ - public boolean isDisableRelativeTo() { - return disableRelativeTo; - } - - /** - * Sets whether to disable the relative to component upon showing this dialog. - * - * @param disableRelativeTo to disable the relative to component upon showing this dialog - * @return this builder - */ - @CanIgnoreReturnValue - public Builder setDisableRelativeTo(boolean disableRelativeTo) { - this.disableRelativeTo = disableRelativeTo; - return this; - } - - /** - * Creates an information pane using the parameters set by this builder - */ - public void inform() { - InformHandler.inform(this); - } - } -} diff --git a/src/main/java/cyder/login/LoginHandler.java b/src/main/java/cyder/login/LoginHandler.java deleted file mode 100644 index ef0a95b2c..000000000 --- a/src/main/java/cyder/login/LoginHandler.java +++ /dev/null @@ -1,674 +0,0 @@ -package cyder.login; - -import com.google.common.base.Preconditions; -import com.google.common.collect.ImmutableList; -import cyder.annotations.ForReadability; -import cyder.annotations.Widget; -import cyder.console.Console; -import cyder.constants.CyderColors; -import cyder.enumerations.ExitCondition; -import cyder.exceptions.FatalException; -import cyder.exceptions.IllegalMethodException; -import cyder.handlers.internal.ExceptionHandler; -import cyder.logging.LogTag; -import cyder.logging.Logger; -import cyder.managers.CyderVersionManager; -import cyder.managers.ProgramModeManager; -import cyder.meta.CyderSplash; -import cyder.props.Props; -import cyder.threads.CyderThreadRunner; -import cyder.threads.ThreadUtil; -import cyder.ui.field.CyderCaret; -import cyder.ui.field.CyderPasswordField; -import cyder.ui.frame.CyderFrame; -import cyder.ui.pane.CyderOutputPane; -import cyder.ui.pane.CyderScrollPane; -import cyder.user.User; -import cyder.user.UserUtil; -import cyder.user.creation.UserCreator; -import cyder.utils.ArrayUtil; -import cyder.utils.OsUtil; -import cyder.utils.SecurityUtil; - -import javax.swing.*; -import java.awt.*; -import java.awt.event.KeyAdapter; -import java.awt.event.KeyEvent; -import java.awt.event.WindowAdapter; -import java.awt.event.WindowEvent; -import java.io.File; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Optional; -import java.util.concurrent.atomic.AtomicBoolean; - -import static cyder.strings.CyderStrings.*; - -/** - * A widget to log into Cyder or any other way that the Console might be invoked. - */ -public final class LoginHandler { - /** - * The width of the login frame. - */ - public static final int LOGIN_FRAME_WIDTH = 600; - - /** - * The height of the login frame. - */ - public static final int LOGIN_FRAME_HEIGHT = 400; - - /** - * The frame used for logging into Cyder - */ - private static CyderFrame loginFrame; - - /** - * The input field for the login frame. - */ - private static JPasswordField loginField; - - /** - * The text area for the login outputs. - */ - private static JTextPane loginArea; - - /** - * Whether to perform the login frame typing animation. - */ - private static boolean doLoginAnimations; - - /** - * The username of the user trying to log in. - */ - private static String username; - - /** - * The string at the beginning of the input field. - */ - private static final String defaultBashString = OsUtil.getOsUsername() + "@" + OsUtil.getComputerName() + ":~$ "; - - /** - * The BashString currently being used. - */ - private static String currentBashString = defaultBashString; - - /** - * Whether the login frame is closed. - */ - private static boolean loginFrameClosed = true; - - /** - * The background color of the login frame. - */ - public static final Color backgroundColor = new Color(21, 23, 24); - - /** - * The color used for the output area text. - */ - public static final Color textColor = new Color(85, 181, 219); - - /** - * The atomic boolean to control whether shift shows the password of the password field. - */ - private static AtomicBoolean shiftShowsPassword; - - /** - * The regular non-priority printing list for the login frame. - */ - private static final ArrayList printingList = new ArrayList<>(); - - /** - * The priority printing list for the login frame. - */ - private static final ArrayList priorityPrintingList = new ArrayList<>(); - - /** - * The font for the login field. - */ - private static final Font loginFieldFont = new Font("Agency FB", Font.BOLD, 26); - - /** - * The current login mode - */ - private static LoginMode loginMode = LoginMode.INPUT; - - /** - * The echo char to use when a password field's text should be visible and not obfuscated. - */ - private static final char DEFAULT_FIELD_ECHO_CHAR = (char) 0; - - /** - * The timeout in ms between char prints. - */ - private static final int charTimeout = 20; - - /** - * The timeout in me between line prints. - */ - private static final int lineTimeout = 400; - - /** - * The name for the login printing animation thread. - */ - private static final String LOGIN_PRINTING_ANIMATION_THREAD_NAME = "Login Printing Animation"; - - /** - * The create command trigger. - */ - private static final String CREATE = "create"; - - /** - * The login command trigger. - */ - private static final String LOGIN = "login"; - - /** - * The quit command trigger. - */ - private static final String QUIT = "quit"; - - /** - * The help command trigger. - */ - private static final String HELP = "help"; - - /** - * The valid commands. - */ - private static final ImmutableList validCommands = ImmutableList.of(CREATE, LOGIN, QUIT, HELP); - - /** - * The prefix for when the login mode is username. - */ - private static final String usernameModePrefix = "Username" + colon + space; - - /** - * The prefix for the login frame title. - */ - private static final String titlePrefix = "Cyder Login" + space; - - /** - * The uuid for the user attempting to log in. - */ - private static String recognizedUuid = ""; - - /** - * Whether Cyder was initially started with a successful AutoCypher - */ - private static boolean startedViaAutoCypher = false; - - /** - * The list of standard prints to print to the login output pane. - */ - private static final ImmutableList standardPrints = ImmutableList.of( - "Cyder version: " + CyderVersionManager.INSTANCE.getVersion() + newline, - "Type " + quote + HELP + quote + " for a list of valid commands" + newline, - "Build: " + CyderVersionManager.INSTANCE.getReleaseDate() + newline, - "Author: Nate Cheshire" + newline, - "Description: A programmer's swiss army knife" + newline - ); - - /** - * Suppress default constructor. - */ - private LoginHandler() { - throw new IllegalMethodException(ATTEMPTED_INSTANTIATION); - } - - /** - * Begins the login typing animation and printing thread. - */ - private static void startTypingAnimation() { - doLoginAnimations = true; - printingList.clear(); - - printingList.addAll(standardPrints); - - CyderOutputPane outputPane = new CyderOutputPane(loginArea); - - CyderThreadRunner.submit(() -> { - try { - while (doLoginAnimations && loginFrame != null) { - if (!priorityPrintingList.isEmpty()) { - if (!outputPane.acquireLock()) { - throw new FatalException("Failed to acquire output pane lock"); - } - - String line = priorityPrintingList.remove(0); - Logger.log(LogTag.LOGIN_OUTPUT, line); - - ArrayUtil.toList(line.toCharArray()).forEach(charizard -> { - outputPane.getStringUtil().print(String.valueOf(charizard)); - ThreadUtil.sleep(charTimeout); - }); - - outputPane.releaseLock(); - } else if (!printingList.isEmpty()) { - if (!outputPane.acquireLock()) { - throw new FatalException("Failed to acquire output pane lock"); - } - - String line = printingList.remove(0); - Logger.log(LogTag.LOGIN_OUTPUT, line); - - ArrayUtil.toList(line.toCharArray()).forEach(charizard -> { - outputPane.getStringUtil().print(String.valueOf(charizard)); - ThreadUtil.sleep(charTimeout); - }); - - outputPane.releaseLock(); - } - - ThreadUtil.sleep(lineTimeout); - } - } catch (Exception e) { - ExceptionHandler.handle(e); - } - }, LOGIN_PRINTING_ANIMATION_THREAD_NAME); - } - - /** - * Shows the login frame - */ - @Widget(triggers = {"login", "pin"}, description = "A widget to switch between Cyder users") - public static void showGui() { - showGui(null); - } - - /** - * Shows the login frame relative to the provided point. - * - * @param centerPoint the point to place the center of the login frame at - */ - public static void showGui(Point centerPoint) { - priorityPrintingList.clear(); - printingList.clear(); - loginMode = LoginMode.INPUT; - - if (loginFrame != null) { - loginFrame.removePostCloseActions(); - loginFrame.dispose(true); - } - - loginFrame = new CyderFrame.Builder() - .setWidth(LOGIN_FRAME_WIDTH) - .setHeight(LOGIN_FRAME_HEIGHT) - .setBackgroundIconFromColor(backgroundColor) - .setTitle(titlePrefix + openingBracket + CyderVersionManager.INSTANCE.getVersion() - + space + "Build" + closingBracket) - .build(); - loginFrame.setBackground(backgroundColor); - loginFrame.addWindowListener(new WindowAdapter() { - @Override - public void windowClosed(WindowEvent e) { - loginFrameClosed = true; - doLoginAnimations = false; - if (Console.INSTANCE.isClosed()) { - OsUtil.exit(ExitCondition.StandardControlledExit); - } - } - - public void windowOpened(WindowEvent e) { - loginField.requestFocus(); - } - }); - loginFrameClosed = false; - - loginArea = new JTextPane(); - loginArea.setBounds(20, 40, 560, 280); - loginArea.setBackground(backgroundColor); - loginArea.setBorder(null); - loginArea.setFocusable(false); - loginArea.setEditable(false); - loginArea.setFont(loginFieldFont); - loginArea.setForeground(textColor); - loginArea.setCaretColor(loginArea.getForeground()); - CyderScrollPane loginScroll = new CyderScrollPane(loginArea, - ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER, - ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED); - loginScroll.setThumbColor(CyderColors.regularPink); - loginScroll.setBounds(20, 40, 560, 280); - loginScroll.getViewport().setOpaque(false); - loginScroll.setOpaque(false); - loginScroll.setBorder(null); - loginArea.setAutoscrolls(true); - loginFrame.getContentPane().add(loginScroll); - - loginField = new JPasswordField(20); - loginField.setEchoChar(DEFAULT_FIELD_ECHO_CHAR); - loginField.setText(currentBashString); - loginField.setBounds(20, 340, 560, 40); - loginField.setBackground(backgroundColor); - loginField.setBorder(null); - loginField.setCaret(new CyderCaret(loginArea.getForeground())); - loginField.setSelectionColor(CyderColors.selectionColor); - loginField.setFont(loginFieldFont); - loginField.setForeground(textColor); - loginField.setCaretColor(textColor); - loginField.addActionListener(e -> loginField.requestFocusInWindow()); - loginField.addKeyListener(new KeyAdapter() { - @Override - public void keyTyped(KeyEvent event) { - loginFieldEnterAction(event); - } - - @Override - public void keyPressed(KeyEvent event) { - correctLoginFieldCaretPosition(); - } - - @Override - public void keyReleased(KeyEvent event) { - correctLoginFieldCaretPosition(); - } - }); - - shiftShowsPassword = CyderPasswordField.addShiftShowsPasswordListener(loginField); - shiftShowsPassword.set(false); - - loginField.setCaretPosition(currentBashString.length()); - loginFrame.getContentPane().add(loginField); - if (centerPoint == null) { - loginFrame.finalizeAndShow(); - } else { - loginFrame.finalizeAndShow(centerPoint); - } - - CyderSplash.INSTANCE.fastDispose(); - if (UserUtil.noCyderUsers()) printlnPriority("No users found; please type " + quote + CREATE + quote); - startTypingAnimation(); - } - - /** - * Checks for the caret position in the login field being before the current bash string - */ - private static void correctLoginFieldCaretPosition() { - if (loginMode != LoginMode.PASSWORD) { - int caretPosition = loginField.getCaretPosition(); - int length = loginField.getPassword().length; - String prefix = switch (loginMode) { - case INPUT -> defaultBashString; - case USERNAME -> usernameModePrefix; - default -> throw new IllegalStateException("Invalid login mode: " + loginMode); - }; - boolean beforeBashString = caretPosition < prefix.length(); - if (beforeBashString) loginField.setCaretPosition(length); - } - } - - /** - * Checks for whether the backspace is being pressed and whether that will - * result in part of the bash string being deleted. - * - * @param keyChar the key char being typed - * @return whether the backspace is being pressed and whether that will - * result in part of the bash string being deleted - */ - @ForReadability - private static boolean checkForBackspaceIntoBashString(char keyChar) { - boolean backSpaceAndBashString = keyChar == KeyEvent.VK_BACK_SPACE && loginMode != LoginMode.PASSWORD; - if (backSpaceAndBashString) { - if (loginField.getPassword().length < currentBashString.length()) { - loginField.setText(currentBashString); - } - - return true; - } - - return false; - } - - /** - * The actions to invoke when a key is pressed in the login field. - * - * @param event the key event - */ - private static void loginFieldEnterAction(KeyEvent event) { - char keyChar = event.getKeyChar(); - if (checkForBackspaceIntoBashString(keyChar)) { - event.consume(); - return; - } - - if (keyChar != newline.charAt(0)) { - return; - } - - String userInput = ""; - char[] fieldInput = loginField.getPassword(); - if (loginMode != LoginMode.PASSWORD) { - userInput = new String(fieldInput).substring(currentBashString.length()); - Logger.log(LogTag.LOGIN_INPUT, userInput); - } - - switch (loginMode) { - case INPUT -> onInputEntered(userInput); - case USERNAME -> onUsernameEntered(userInput); - case PASSWORD -> onPasswordEntered(fieldInput); - } - - Arrays.fill(fieldInput, '\0'); - } - - /** - * Handles the user entering input when the login mode is {@link LoginMode#INPUT}. - * - * @param userInput the current field input with the bash string parsed away - */ - private static void onInputEntered(String userInput) { - if (userInput.equalsIgnoreCase(CREATE)) { - UserCreator.showGui(); - loginField.setText(currentBashString); - } else if (userInput.equalsIgnoreCase(LOGIN)) { - currentBashString = usernameModePrefix; - loginField.setText(currentBashString); - printlnPriority("Awaiting Username"); - loginMode = LoginMode.USERNAME; - } else if (userInput.equalsIgnoreCase(QUIT)) { - loginFrame.dispose(); - } else if (userInput.equalsIgnoreCase(HELP)) { - loginField.setText(currentBashString); - printlnPriority("Valid commands: "); - validCommands.forEach(command -> LoginHandler.printlnPriority( - BULLET_POINT + space + command)); - } else { - loginField.setText(currentBashString); - printlnPriority("Unknown command; See " + quote + HELP + quote + " for " + HELP); - } - } - - /** - * Handles the user entering input when the login mode is {@link LoginMode#USERNAME}. - * - * @param userInput the current field input with the bash string parsed away - */ - private static void onUsernameEntered(String userInput) { - username = userInput; - loginMode = LoginMode.PASSWORD; - loginField.setEchoChar(ECHO_CHAR); - loginField.setText(""); - printlnPriority("Awaiting Password (hold shift to reveal password)"); - currentBashString = defaultBashString; - shiftShowsPassword.set(true); - } - - /** - * Handles the user entering input when the login mode is {@link LoginMode#PASSWORD}. - * - * @param fieldInput the current field input - */ - private static void onPasswordEntered(char[] fieldInput) { - loginField.setEchoChar(DEFAULT_FIELD_ECHO_CHAR); - shiftShowsPassword.set(false); - loginField.setText(""); - printlnPriority("Attempting validation"); - - String hashedPassword = SecurityUtil.toHexString(SecurityUtil.getSha256(fieldInput)); - if (recognize(username, hashedPassword, false)) { - loginFrame.dispose(true); - return; - } - - loginField.setEchoChar(DEFAULT_FIELD_ECHO_CHAR); - loginField.setText(currentBashString); - loginField.setCaretPosition(currentBashString.length()); - - printlnPriority("Login failed"); - loginMode = LoginMode.INPUT; - username = ""; - } - - /** - * Adds the provided line follows by a new line character to the priority printing list - * - * @param line the line to add - */ - private static void printlnPriority(String line) { - priorityPrintingList.add(line + newline); - } - - /** - * Whether the login frame is closed. - * - * @return the login frame is closed - */ - public static boolean isLoginFrameClosed() { - return loginFrameClosed; - } - - /** - * Returns the login frame. - * - * @return the login frame - */ - public static CyderFrame getLoginFrame() { - return loginFrame; - } - - /** - * Returns whether Cyder was initially started with a successful AutoCypher. - * - * @return whether Cyder was initially started with a successful AutoCypher - */ - public static boolean wasStartedViaAutoCypher() { - return startedViaAutoCypher; - } - - /** - * Begins the login sequence to figure out how to enter Cyder and which frame to show, that of - * the console, the login frame, or an exception pane. - */ - public static void showProperStartupFrame() { - boolean autoCypher = Props.autocypher.getValue(); - - if (autoCypher) { - boolean debugHashNamePresent = Props.autocypherName.valuePresent(); - boolean debugHashPasswordPresent = Props.autocypherPassword.valuePresent(); - - if (debugHashNamePresent && debugHashPasswordPresent) { - String name = Props.autocypherName.getValue(); - String password = Props.autocypherPassword.getValue(); - - startedViaAutoCypher = recognize(name, password, true); - ProgramModeManager.INSTANCE.refreshProgramMode(); - if (startedViaAutoCypher) return; - } - - showGui(); - return; - } - - boolean released = CyderVersionManager.INSTANCE.isReleased(); - if (!released) { - ExceptionHandler.exceptionExit("Unreleased build of Cyder", ExitCondition.NotReleased); - return; - } - - Optional optionalUuid = UserUtil.getMostRecentLoggedInUser(); - if (optionalUuid.isPresent()) { - String loggedInUuid = optionalUuid.get(); - Logger.log(LogTag.CONSOLE_LOAD, "Found previously logged in user: " + loggedInUuid); - UserUtil.logoutAllUsers(); - Console.INSTANCE.initializeAndLaunch(loggedInUuid); - return; - } - - showGui(); - } - - /** - * Rests the state variables prior to the logic of {@link #recognize(String, String, boolean)}. - */ - private static void resetRecognitionVars() { - recognizedUuid = ""; - } - - /** - * Attempts to log in a user based on the provided - * name and an already hashed password. - * - * @param name the provided user account name - * @param hashedPass the password already having been hashed - * @return whether the name and pass combo was authenticated and logged in - */ - public static boolean recognize(String name, String hashedPass, boolean autoCypherAttempt) { - resetRecognitionVars(); - - switch (validateUsernamePassword(name, hashedPass)) { - case FAILED -> { - printlnPriority(autoCypherAttempt ? "AutoCypher failed" : "Incorrect password"); - if (!autoCypherAttempt) loginField.requestFocusInWindow(); - return false; - } - case UNKNOWN_USER -> { - printlnPriority("Unknown user"); - return false; - } - case SUCCESS -> { - Preconditions.checkState(!recognizedUuid.isEmpty()); - - Console.INSTANCE.setConsoleLoadStartTime(); - - if (!Console.INSTANCE.isClosed()) { - Console.INSTANCE.releaseResourcesAndCloseFrame(false); - Console.INSTANCE.logoutCurrentUser(); - } - - doLoginAnimations = false; - - Console.INSTANCE.initializeAndLaunch(recognizedUuid); - - return true; - } - default -> throw new IllegalArgumentException("Invalid password status"); - } - } - - /** - * Checks whether the given name/pass combo is valid and returns the result of the check. - * - * @param providedUsername the username given - * @param singlyHashedPassword the singly-hashed password - * @return the result of checking for a user with the provided name and password - */ - private static PasswordCheckResult validateUsernamePassword(String providedUsername, String singlyHashedPassword) { - ArrayList names = new ArrayList<>(); - UserUtil.getUserJsons().forEach(userJson -> names.add(UserUtil.extractUser(userJson).getUsername())); - - if (names.stream().map(String::toLowerCase).noneMatch(name -> name.equalsIgnoreCase(providedUsername))) { - return PasswordCheckResult.UNKNOWN_USER; - } - - for (File userJsonFile : UserUtil.getUserJsons()) { - User user = UserUtil.extractUser(userJsonFile); - if (providedUsername.equalsIgnoreCase(user.getUsername()) - && SecurityUtil.hashAndHex(singlyHashedPassword).equals(user.getPassword())) { - recognizedUuid = userJsonFile.getParentFile().getName(); - return PasswordCheckResult.SUCCESS; - } - } - - return PasswordCheckResult.FAILED; - } -} diff --git a/src/main/java/cyder/login/LoginMode.java b/src/main/java/cyder/login/LoginMode.java deleted file mode 100644 index 10f172ad2..000000000 --- a/src/main/java/cyder/login/LoginMode.java +++ /dev/null @@ -1,21 +0,0 @@ -package cyder.login; - -/** - * The valid login modes. - */ -enum LoginMode { - /** - * Expecting general input. - */ - INPUT, - - /** - * Expecting a username. - */ - USERNAME, - - /** - * Expecting a password. - */ - PASSWORD -} diff --git a/src/main/java/cyder/login/PasswordCheckResult.java b/src/main/java/cyder/login/PasswordCheckResult.java deleted file mode 100644 index 17768b053..000000000 --- a/src/main/java/cyder/login/PasswordCheckResult.java +++ /dev/null @@ -1,21 +0,0 @@ -package cyder.login; - -/** - * The result of a password check. - */ -enum PasswordCheckResult { - /** - * The login failed due to invalid credentials. - */ - FAILED, - - /** - * The login failed due to not being able to locate the username. - */ - UNKNOWN_USER, - - /** - * The login succeeded and the user uuid should be present. - */ - SUCCESS -} diff --git a/src/main/java/cyder/login/package-info.java b/src/main/java/cyder/login/package-info.java deleted file mode 100644 index cdd303ed8..000000000 --- a/src/main/java/cyder/login/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -/** - * Utilities and classes related to the logging in process. - */ -package cyder.login; diff --git a/src/main/java/cyder/managers/CyderVersionManager.java b/src/main/java/cyder/managers/CyderVersionManager.java deleted file mode 100644 index a751ec744..000000000 --- a/src/main/java/cyder/managers/CyderVersionManager.java +++ /dev/null @@ -1,78 +0,0 @@ -package cyder.managers; - -import cyder.logging.LogTag; -import cyder.logging.Logger; - -/** - * A manager for the program's name, version name, release date, released state, and similar states. - */ -@SuppressWarnings("FieldCanBeLocal") /* Readability */ -public enum CyderVersionManager { - /** - * The version manager instance. - */ - INSTANCE; - - /** - * Constructor explicitly defined for logging purposes. - */ - CyderVersionManager() { - Logger.log(LogTag.OBJECT_CREATION, "Version manager constructed"); - } - - /** - * The program name. - */ - private final String programName = "Cyder"; - - /** - * The version name. - */ - private final String version = "Liminal"; - - /** - * The date of release. - */ - private final String releaseDate = "Not Yet Released"; - - /** - * Whether this release is publicly available. - */ - private final boolean released = false; - - /** - * Returns the program name. - * - * @return the program name - */ - public String getProgramName() { - return programName; - } - - /** - * Returns the name for this version. - * - * @return the name for this version - */ - public String getVersion() { - return version; - } - - /** - * Returns the release date for this version. - * - * @return the release date for this version - */ - public String getReleaseDate() { - return releaseDate; - } - - /** - * Returns whether this release is publicly available. - * - * @return whether this release is publicly available - */ - public boolean isReleased() { - return released; - } -} diff --git a/src/main/java/cyder/managers/ProgramMode.java b/src/main/java/cyder/managers/ProgramMode.java deleted file mode 100644 index 2f2ef66d1..000000000 --- a/src/main/java/cyder/managers/ProgramMode.java +++ /dev/null @@ -1,68 +0,0 @@ -package cyder.managers; - -/** - * Possible modes Cyder can exist in for a certain instance. - */ -public enum ProgramMode { - /** - * The normal, user mode for Cyder, started from a JAR file. - */ - NORMAL("Normal", 0), - - /** - * Cyder was started from an IDE and not a JAR file. - */ - IDE_NORMAL("IDE Normal", 1), - - /** - * Cyder was started in a debug mode by an IDE. - */ - IDE_DEBUG("IDE Debug", 2), - - /** - * Cyder was started via an AutoCypher. - */ - DEVELOPER_DEBUG("Developer Debug", 3); - - /** - * The name of the program mode. - */ - private final String name; - - /** - * The level of priority of the program mode. - */ - private final int priorityLevel; - - ProgramMode(String name, int priorityLevel) { - this.name = name; - this.priorityLevel = priorityLevel; - } - - /** - * Returns the name of the program mode. - * - * @return the name of the program mode - */ - public String getName() { - return name; - } - - /** - * Returns the priority level of the program mode. - * - * @return the priority level of the program mode - */ - public int getPriorityLevel() { - return priorityLevel; - } - - /** - * Returns whether this priority level has developer priority. - * - * @return whether this priority level has developer priority - */ - public boolean hasDeveloperPriorityLevel() { - return priorityLevel >= IDE_NORMAL.getPriorityLevel(); - } -} diff --git a/src/main/java/cyder/managers/ProgramModeManager.java b/src/main/java/cyder/managers/ProgramModeManager.java deleted file mode 100644 index 3fba615ff..000000000 --- a/src/main/java/cyder/managers/ProgramModeManager.java +++ /dev/null @@ -1,48 +0,0 @@ -package cyder.managers; - -import cyder.logging.LogTag; -import cyder.logging.Logger; -import cyder.login.LoginHandler; -import cyder.utils.JvmUtil; -import cyder.utils.OsUtil; - -/** - * A manager for the program mode. - */ -public enum ProgramModeManager { - /** - * The program mode manager instance. - */ - INSTANCE; - - /** - * The program mode for this session of Cyder. - */ - private static ProgramMode sessionProgramMode = ProgramMode.NORMAL; - - /** - * Refreshes the current program mode. - */ - public void refreshProgramMode() { - if (LoginHandler.wasStartedViaAutoCypher()) { - sessionProgramMode = ProgramMode.DEVELOPER_DEBUG; - } else if (JvmUtil.currentInstanceLaunchedWithDebug()) { - sessionProgramMode = ProgramMode.IDE_DEBUG; - } else if (!OsUtil.JAR_MODE) { - sessionProgramMode = ProgramMode.IDE_NORMAL; - } else { - sessionProgramMode = ProgramMode.NORMAL; - } - - Logger.log(LogTag.DEBUG, "Refreshed program mode, set as: " + sessionProgramMode.getName()); - } - - /** - * Returns the program mode for this session of Cyder. - * - * @return the program mode for this session of Cyder - */ - public ProgramMode getProgramMode() { - return sessionProgramMode; - } -} diff --git a/src/main/java/cyder/ui/frame/tooltip/TooltipMenuController.java b/src/main/java/cyder/ui/frame/tooltip/TooltipMenuController.java index a516a1bd9..59f09bbe0 100644 --- a/src/main/java/cyder/ui/frame/tooltip/TooltipMenuController.java +++ b/src/main/java/cyder/ui/frame/tooltip/TooltipMenuController.java @@ -10,7 +10,6 @@ import cyder.files.FileUtil; import cyder.getter.GetInputBuilder; import cyder.getter.GetterUtil; -import cyder.managers.ProgramModeManager; import cyder.props.Props; import cyder.strings.StringUtil; import cyder.threads.CyderThreadFactory; diff --git a/src/main/java/cyder/user/UserData.java b/src/main/java/cyder/user/UserData.java index 16bfec4e9..126c12cc2 100644 --- a/src/main/java/cyder/user/UserData.java +++ b/src/main/java/cyder/user/UserData.java @@ -14,7 +14,6 @@ import cyder.ui.list.CyderScrollList; import cyder.user.data.MappedExecutables; import cyder.user.data.ScreenStat; -import cyder.weather.WeatherWidget; import cyder.widgets.ClockWidget; import javax.swing.*; diff --git a/src/main/java/cyder/utils/OsUtil.java b/src/main/java/cyder/utils/OsUtil.java index 68d3ac54e..fb2f7c347 100644 --- a/src/main/java/cyder/utils/OsUtil.java +++ b/src/main/java/cyder/utils/OsUtil.java @@ -10,7 +10,6 @@ import cyder.handlers.internal.ExceptionHandler; import cyder.logging.LogTag; import cyder.logging.Logger; -import cyder.managers.ProgramModeManager; import cyder.managers.RobotManager; import cyder.meta.Cyder; import cyder.strings.CyderStrings; diff --git a/src/main/java/cyder/utils/StatUtil.java b/src/main/java/cyder/utils/StatUtil.java index 298d0abae..bfc3106be 100644 --- a/src/main/java/cyder/utils/StatUtil.java +++ b/src/main/java/cyder/utils/StatUtil.java @@ -9,8 +9,6 @@ import cyder.exceptions.IllegalMethodException; import cyder.files.FileUtil; import cyder.handlers.internal.ExceptionHandler; -import cyder.managers.ProgramMode; -import cyder.managers.ProgramModeManager; import cyder.network.IpDataManager; import cyder.network.LatencyManager; import cyder.network.NetworkUtil; diff --git a/src/main/java/cyder/weather/MeasurementScale.java b/src/main/java/cyder/weather/MeasurementScale.java new file mode 100644 index 000000000..71e78aea0 --- /dev/null +++ b/src/main/java/cyder/weather/MeasurementScale.java @@ -0,0 +1,31 @@ +package cyder.weather; + +/** + * Possible measurement scales, that of imperial or metric. + */ +public enum MeasurementScale { + /** + * The imperial measurement scale. + */ + IMPERIAL("imperial"), + + /** + * The metric measurement scale. + */ + METRIC("metric"); + + private final String weatherDataRepresentation; + + MeasurementScale(String weatherDataRepresentation) { + this.weatherDataRepresentation = weatherDataRepresentation; + } + + /** + * Returns the weather data representation for this measurement scale. + * + * @return the weather data representation for this measurement scale + */ + public String getWeatherDataRepresentation() { + return weatherDataRepresentation; + } +} \ No newline at end of file diff --git a/src/main/java/cyder/weather/WeatherUtil.java b/src/main/java/cyder/weather/WeatherUtil.java index 03ec7a6df..6a3bee669 100644 --- a/src/main/java/cyder/weather/WeatherUtil.java +++ b/src/main/java/cyder/weather/WeatherUtil.java @@ -3,13 +3,13 @@ import com.google.common.base.Preconditions; import cyder.constants.CyderUrls; import cyder.exceptions.IllegalMethodException; -import cyder.handlers.internal.ExceptionHandler; import cyder.props.Props; import cyder.strings.CyderStrings; import cyder.utils.SerializationUtil; import cyder.weather.parsers.WeatherData; import java.io.BufferedReader; +import java.io.IOException; import java.io.InputStreamReader; import java.net.URL; import java.util.Optional; @@ -35,36 +35,6 @@ private WeatherUtil() { throw new IllegalMethodException(CyderStrings.ATTEMPTED_INSTANTIATION); } - /** - * Possible measurement scales, that of imperial or metric. - */ - public enum MeasurementScale { - /** - * The imperial measurement scale. - */ - IMPERIAL("imperial"), - - /** - * The metric measurement scale. - */ - METRIC("metric"); - - private final String weatherDataRepresentation; - - MeasurementScale(String weatherDataRepresentation) { - this.weatherDataRepresentation = weatherDataRepresentation; - } - - /** - * Returns the weather data representation for this measurement scale. - * - * @return the weather data representation for this measurement scale - */ - public String getWeatherDataRepresentation() { - return weatherDataRepresentation; - } - } - /** * Returns the weather data object for the provided location string if available. Empty optional else. * @@ -84,8 +54,8 @@ public static Optional getWeatherData(String locationString) { try (BufferedReader reader = new BufferedReader(new InputStreamReader(new URL(OpenString).openStream()))) { return Optional.of(SerializationUtil.fromJson(reader, WeatherData.class)); - } catch (Exception e) { - ExceptionHandler.handle(e); + } catch (IOException e) { + e.printStackTrace(); } return Optional.empty(); diff --git a/src/main/java/cyder/weather/WeatherWidget.java b/src/main/java/cyder/weather/WeatherWidget.java deleted file mode 100644 index e89f97fcd..000000000 --- a/src/main/java/cyder/weather/WeatherWidget.java +++ /dev/null @@ -1,1204 +0,0 @@ -package cyder.weather; - -import com.google.common.base.Preconditions; -import cyder.annotations.CyderAuthor; -import cyder.annotations.Vanilla; -import cyder.annotations.Widget; -import cyder.console.Console; -import cyder.constants.CyderColors; -import cyder.constants.CyderFonts; -import cyder.constants.CyderRegexPatterns; -import cyder.constants.HtmlTags; -import cyder.enumerations.Extension; -import cyder.enumerations.Units; -import cyder.getter.GetInputBuilder; -import cyder.getter.GetterUtil; -import cyder.handlers.internal.ExceptionHandler; -import cyder.logging.LogTag; -import cyder.logging.Logger; -import cyder.math.AngleUtil; -import cyder.math.InterpolationUtil; -import cyder.network.IpDataManager; -import cyder.network.NetworkUtil; -import cyder.parsers.ip.IpData; -import cyder.props.Props; -import cyder.strings.CyderStrings; -import cyder.strings.StringUtil; -import cyder.threads.CyderThreadRunner; -import cyder.threads.ThreadUtil; -import cyder.time.TimeUtil; -import cyder.ui.UiUtil; -import cyder.ui.drag.button.DragLabelTextButton; -import cyder.ui.frame.CyderFrame; -import cyder.ui.frame.notification.NotificationBuilder; -import cyder.user.UserDataManager; -import cyder.utils.HtmlUtil; -import cyder.utils.ImageUtil; -import cyder.utils.MapUtil; -import cyder.utils.StaticUtil; -import cyder.weather.parsers.WeatherData; - -import javax.swing.*; -import java.awt.*; -import java.awt.image.BufferedImage; -import java.text.DecimalFormat; -import java.text.SimpleDateFormat; -import java.time.Duration; -import java.util.*; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.stream.Collectors; - -import static cyder.strings.CyderStrings.*; - -/** - * A widget for showing the weather for a local area. - */ -@Vanilla -@CyderAuthor -public class WeatherWidget { - /** - * The current location label. - */ - private JLabel locationLabel; - - /** - * The current weather description label. - */ - private JLabel currentWeatherLabel; - - /** - * The custom painted temperature label container to hold the min, current, and max temperatures. - */ - private JLabel customTempLabel; - - /** - * The wind speed label. - */ - private JLabel windSpeedLabel; - - /** - * The custom painted wind direction label. - */ - private JLabel windDirectionLabel; - - /** - * The humidity label. - */ - private JLabel humidityLabel; - - /** - * The pressure label. - */ - private JLabel pressureLabel; - - /** - * The sunset label. - */ - private JLabel sunsetLabel; - - /** - * The sunrise label. - */ - private JLabel sunriseLabel; - - /** - * The timezone label. - */ - private JLabel timezoneLabel; - - /** - * The current time label. - */ - private JLabel currentTimeLabel; - - /** - * The current weather icon label. - */ - private JLabel currentWeatherIconLabel; - - /** - * The current temperature label - */ - private JLabel currentTempLabel; - - /** - * The min temperature label. - */ - private JLabel minTempLabel; - - /** - * The max temperature label. - */ - private JLabel maxTempLabel; - - /** - * The sunrise time in unix time format. - */ - private long sunriseMillis = 0L; - - /** - * The sunset time in unix time format. - */ - private long sunsetMillis = 0L; - - /** - * The sunrise time to display on the label. - */ - private String sunriseFormatted = ""; - - /** - * The sunset time to display on the label. - */ - private String sunsetFormatted = ""; - - /** - * The sunrise hour. - */ - private int sunriseHour; - - /** - * The sunset hour. - */ - private int sunsetHour; - - /** - * The current weather icon resource. - */ - private String weatherIconId = "01d"; - - /** - * The current weather condition. - */ - private String weatherCondition = ""; - - /** - * The current wind speed. - */ - private float windSpeed = 0f; - - /** - * The current temperature. - */ - private float temperature = 0f; - - /** - * The current humidity. - */ - private float humidity = 0f; - - /** - * The current pressure. - */ - private float pressure = 0f; - - /** - * The current wind direction. - */ - private float windBearing = 0f; - - /** - * The current location. - */ - private String currentLocationString = ""; - - /** - * The previous location. - */ - private String previousLocationString = ""; - - /** - * The currently set city. - */ - private String userCity = ""; - - /** - * The currently set state. - */ - private String userState = ""; - - /** - * The currently set country. - */ - private String userCountry = ""; - - /** - * The weather frame. - */ - private CyderFrame weatherFrame; - - /** - * Whether to use the custom location. - */ - private boolean useCustomLoc; - - /** - * Whether to repull weather data every updateFrequency minutes. - */ - private final AtomicBoolean stopUpdating = new AtomicBoolean(false); - - /** - * The maximum temperature - */ - private float minTemp; - - /** - * The minimum temperature. - */ - private float maxTemp; - - /** - * The gmt offset for the current location. - */ - private int parsedGmtOffset; - - /** - * The last gmt offset returned when parsing weather data. - */ - private String weatherDataGmtOffset = "0"; - - /** - * Whether the gmt offset has been set. - */ - private boolean isGmtSet; - - /** - * The width of the frame. - */ - private static final int FRAME_WIDTH = 480; - - /** - * The height of the frame. - */ - private static final int FRAME_HEIGHT = 640; - - /** - * The weather keyword. - */ - private static final String WEATHER = "weather"; - - /** - * The default frame title. - */ - private static final String DEFAULT_TITLE = WEATHER; - - /** - * The shade color for the default background. - */ - private static final Color shadeColor = new Color(89, 85, 161); - - /** - * The primary color for the default background. - */ - private static final Color primaryColorOne = new Color(205, 119, 130); - - /** - * The primary color for the default background. - */ - private static final Color primaryColorTwo = new Color(38, 21, 75); - - /** - * The default frame background. - */ - private static final BufferedImage defaultBackground = ImageUtil.getImageGradient(FRAME_WIDTH, FRAME_HEIGHT, - primaryColorOne, primaryColorTwo, shadeColor); - - /** - * The number of seconds in a singular hour. - */ - private static final int SECONDS_IN_HOUR = 3600; - - /** - * The post meridiem string - */ - private static final String PM = "pm"; - - /** - * The ante meridiem string. - */ - private static final String AM = "am"; - - /** - * The component width for the custom temperature label. - */ - private static final int customTempLabelWidth = 400; - - /** - * The name for the waiting thread for changing the widget's location. - */ - private static final String WEATHER_LOCATION_CHANGER_THREAD_NAME = "Weather Location Changer"; - - /** - * The description for the @Widget annotation. - */ - private static final String widgetDescription = "A widget that displays weather data for the current " + - "city you are in. The location is also changeable"; - - /** - * The getter util instance for changing the weather location. - */ - private final GetterUtil getterUtilInstance = GetterUtil.getInstance(); - - /** - * The instances of weather widget for this Cyder session. - */ - private static final ArrayList instances = new ArrayList<>(); - - /** - * The gmt keyword. - */ - private static final String GMT = "GMT"; - - /** - * The number of comma separated parts for a valid location string. - */ - private static final int cityStateCountryFormatLen = 3; - - /** - * The index of the state abbreviation in a city state country string. - */ - private static final int stateIndex = 1; - - /** - * The length of USA state abbreviations. - */ - private static final int stateAbbrLen = 2; - - /** - * The day time identifier. - */ - private static final String DAY_IMAGE_ID = "d"; - - /** - * The night time identifier. - */ - private static final String NIGHT_IMAGE_ID = "n"; - - /** - * The value to add to the center x value for the temperature label within the custom painted component. - */ - private static final int temperatureLineCenterAdditive = 5; - - /** - * The refreshed keyword. - */ - private static final String REFRESHED = "Refreshed"; - - /** - * The dst active bracketed text. - */ - private static final String DST_ACTIVE = "DST Active"; - - /** - * The date formatter for the sunrise and sunset times. - */ - private static final SimpleDateFormat sunriseSunsetFormat = new SimpleDateFormat("h:mm"); - - /** - * The builder for acquiring the map view. - */ - private final MapUtil.Builder mapViewBuilder = new MapUtil.Builder( - FRAME_WIDTH, FRAME_HEIGHT, Props.mapQuestApiKey.getValue()) - .setFilterWaterMark(true) - .setScaleBarLocation(MapUtil.ScaleBarLocation.BOTTOM); - - /** - * The north cardinal direction abbreviation. - */ - private static final String NORTH = "N"; - - /** - * The south cardinal direction abbreviation. - */ - private static final String SOUTH = "S"; - - /** - * The east cardinal direction abbreviation. - */ - private static final String EAST = "E"; - - /** - * The west cardinal direction abbreviation. - */ - private static final String WEST = "W"; - - /** - * The change location text. - */ - private static final String CHANGE_LOCATION = "Change Location"; - - /** - * The color for the styled text for the example location. - */ - private static final Color exampleColor = new Color(45, 100, 220); - - /** - * The example location. - */ - private static final String exampleChangeLocationText = Props.defaultLocation.getValue(); - - /** - * The styled example change location text. - */ - private static final String styledExampleText = HtmlUtil.generateColoredHtmlText( - exampleChangeLocationText, exampleColor); - - /** - * The complete change location html styled text to show on the string getter's label. - */ - private static final String changeLocationHtmlText = HtmlTags.openingHtml - + "Enter your city, state (if applicable), and country code separated by a comma. Example: " - + HtmlTags.breakTag + styledExampleText + HtmlTags.closingHtml; - - /** - * The thread name for the weather stats updater. - */ - private static final String WEATHER_STATS_UPDATER_THREAD_NAME = "Weather Stats Updater"; - - /** - * The thread name for the weather clock updater. - */ - private static final String WEATHER_CLOCK_UPDATER_THREAD_NAME = "Weather Clock Updater"; - - /** - * The frequency to update the weather stats. - */ - private static final Duration updateStatsFrequency = Duration.ofMinutes(5); - - /** - * The frequency to check the exit condition for the stats updater. - */ - private static final Duration checkUpdateStatsExitConditionFrequency = Duration.ofSeconds(10); - - /** - * The last notification text following a location change attempt. - */ - private String refreshingNotificationText; - - /** - * The decimal formatter for the result. - */ - private static final DecimalFormat floatMeasurementFormatter = new DecimalFormat("#.####"); - - /** - * Creates a new weather widget initialized to the user's current location. - */ - private WeatherWidget() { - Logger.log(LogTag.OBJECT_CREATION, this); - } - - /** - * Shows a new weather widget instance. - */ - @Widget(triggers = "weather", description = widgetDescription) - public static void showGui() { - getInstance().innerShowGui(); - } - - /** - * Returns a new instance of the weather widget. - * - * @return a new instance of the weather widget - */ - private static WeatherWidget getInstance() { - WeatherWidget instance = new WeatherWidget(); - instances.add(instance); - return instance; - } - - /** - * Shows the UI since we need to allow multiple instances of weather widget - * while still having the public static showGui() method with the @Widget annotation. - */ - private void innerShowGui() { - if (NetworkUtil.isHighLatency()) { - Console.INSTANCE.getConsoleCyderFrame().notify("Sorry, " - + UserDataManager.INSTANCE.getUsername() + ", but this feature" - + " is suspended until a stable internet connection can be established"); - return; - } else if (!Props.weatherKey.valuePresent()) { - Console.INSTANCE.getConsoleCyderFrame().inform("Sorry, but the Weather Key has" - + " not been set or is invalid, as a result, many features of Cyder will not work as" - + " intended. Please see the fields panel of the user editor to learn how to acquire" - + " a key and set it.", "Weather Key Not Set"); - return; - } - - repullWeatherStats(); - - UiUtil.closeIfOpen(weatherFrame); - weatherFrame = new CyderFrame.Builder() - .setWidth(FRAME_WIDTH) - .setHeight(FRAME_HEIGHT) - .setBackgroundIconFromColor(CyderColors.regularBlue) - .build(); - weatherFrame.addPreCloseAction(this::onWeatherFrameDisposed); - weatherFrame.setBackground(defaultBackground); - weatherFrame.setTitle(DEFAULT_TITLE); - - currentTimeLabel = new JLabel(getWeatherTimeAccountForGmtOffset(), SwingConstants.CENTER); - currentTimeLabel.setForeground(CyderColors.navy); - currentTimeLabel.setFont(CyderFonts.SEGOE_20); - currentTimeLabel.setBounds(0, 50, 480, 30); - weatherFrame.getContentPane().add(currentTimeLabel); - - locationLabel = new JLabel(currentLocationString, SwingConstants.CENTER); - locationLabel.setForeground(CyderColors.navy); - locationLabel.setFont(CyderFonts.SEGOE_20); - locationLabel.setBounds(0, 85, 480, 30); - weatherFrame.getContentPane().add(locationLabel); - - final int strokeWidth = 3; - - JLabel currentWeatherContainer = new JLabel() { - private static final int arcLen = 25; - private static final int offset = 10; - - @Override - public void paint(Graphics g) { - Graphics2D g2d = (Graphics2D) g; - g.setColor(CyderColors.navy); - g2d.setStroke(new BasicStroke(strokeWidth)); - g2d.fillRoundRect(offset, offset, 100, 160, arcLen, arcLen); - super.paint(g); - } - }; - currentWeatherContainer.setBounds(180, 120, 120, 180); - weatherFrame.getContentPane().add(currentWeatherContainer); - - currentWeatherIconLabel = new JLabel(generateCurrentWeatherIcon()); - currentWeatherIconLabel.setBounds(0, 25, currentWeatherContainer.getWidth(), - currentWeatherContainer.getHeight() / 2); - currentWeatherContainer.add(currentWeatherIconLabel); - - currentWeatherLabel = new JLabel("", SwingConstants.CENTER); - currentWeatherLabel.setForeground(CyderColors.vanilla); - currentWeatherLabel.setFont(CyderFonts.SEGOE_20.deriveFont(18f)); - currentWeatherLabel.setBounds(0, currentWeatherContainer.getHeight() / 2, - currentWeatherContainer.getWidth(), currentWeatherContainer.getHeight() / 2); - currentWeatherContainer.add(currentWeatherLabel); - - ImageIcon sunriseIcon = new ImageIcon(StaticUtil.getStaticPath("sunrise.png")); - JLabel sunriseLabelIcon = new JLabel(sunriseIcon) { - private static final int arcLen = 25; - private static final int offset = 10; - - @Override - public void paint(Graphics g) { - Graphics2D g2d = (Graphics2D) g; - g.setColor(CyderColors.navy); - g2d.setStroke(new BasicStroke(strokeWidth)); - g2d.fillRoundRect(offset, offset, 100, 160, arcLen, arcLen); - super.paint(g); - } - }; - sunriseLabelIcon.setBounds(60, 120, 120, 180); - weatherFrame.getContentPane().add(sunriseLabelIcon); - - sunriseLabel = new JLabel(sunriseFormatted + AM, SwingConstants.CENTER); - sunriseLabel.setForeground(CyderColors.vanilla); - sunriseLabel.setFont(CyderFonts.SEGOE_20); - sunriseLabel.setBounds(0, sunriseLabelIcon.getHeight() / 2, sunriseLabelIcon.getWidth(), - sunriseLabelIcon.getHeight() / 2); - sunriseLabelIcon.add(sunriseLabel); - - ImageIcon sunsetIcon = new ImageIcon(StaticUtil.getStaticPath("sunset.png")); - JLabel sunsetLabelIcon = new JLabel(sunsetIcon) { - private static final int arcLen = 25; - private static final int offset = 10; - - @Override - public void paint(Graphics g) { - Graphics2D g2d = (Graphics2D) g; - g.setColor(CyderColors.navy); - ((Graphics2D) g).setStroke(new BasicStroke(strokeWidth)); - g2d.fillRoundRect(offset, offset, 100, 160, arcLen, arcLen); - super.paint(g); - } - }; - sunsetLabelIcon.setBounds(480 - 60 - 120, 120, 120, 180); - weatherFrame.getContentPane().add(sunsetLabelIcon); - - sunsetLabel = new JLabel(sunsetFormatted + PM, SwingConstants.CENTER); - sunsetLabel.setForeground(CyderColors.vanilla); - sunsetLabel.setFont(CyderFonts.SEGOE_20); - sunsetLabel.setBounds(0, sunsetLabelIcon.getHeight() / 2, sunsetLabelIcon.getWidth(), - sunsetLabelIcon.getHeight() / 2); - sunsetLabelIcon.add(sunsetLabel); - - GetInputBuilder changeLocationBuilder = new GetInputBuilder(CHANGE_LOCATION, changeLocationHtmlText) - .setRelativeTo(weatherFrame) - .setSubmitButtonText(CHANGE_LOCATION) - .setLabelFont(CyderFonts.DEFAULT_FONT_SMALL) - .setInitialFieldText(currentLocationString) - .setSubmitButtonColor(CyderColors.regularPurple); - - DragLabelTextButton locationButton = new DragLabelTextButton.Builder("Change Location") - .setClickAction(() -> CyderThreadRunner.submit(() -> { - getterUtilInstance.closeAllGetFrames(); - Optional optionalNewLocation = getterUtilInstance.getInput(changeLocationBuilder); - - try { - if (optionalNewLocation.isEmpty()) return; - String newLocation = optionalNewLocation.get(); - previousLocationString = currentLocationString; - ArrayList parts = Arrays.stream(newLocation.split(CyderStrings.comma)) - .map(string -> StringUtil.capsFirstWords(string.trim())) - .collect(Collectors.toCollection(ArrayList::new)); - currentLocationString = StringUtil.joinParts(parts, CyderStrings.comma); - useCustomLoc = true; - - refreshingNotificationText = "Attempting to refresh weather stats for location " - + CyderStrings.quote + currentLocationString + CyderStrings.quote; - weatherFrame.notify(refreshingNotificationText); - - repullWeatherStats(); - } catch (Exception ex) { - ExceptionHandler.handle(ex); - } - }, WEATHER_LOCATION_CHANGER_THREAD_NAME)).build(); - - weatherFrame.getTopDragLabel().addLeftButton(locationButton, 0); - - minTempLabel = new JLabel(); - minTempLabel.setForeground(CyderColors.vanilla); - minTempLabel.setFont(CyderFonts.DEFAULT_FONT_SMALL); - weatherFrame.getContentPane().add(minTempLabel); - - maxTempLabel = new JLabel(); - maxTempLabel.setForeground(CyderColors.vanilla); - maxTempLabel.setFont(CyderFonts.DEFAULT_FONT_SMALL); - - currentTempLabel = new JLabel(); - currentTempLabel.setForeground(CyderColors.regularPink); - currentTempLabel.setFont(CyderFonts.DEFAULT_FONT_SMALL); - - customTempLabel = new JLabel() { - private static final int borderLen = 3; - private static final int componentHeight = 40; - - @Override - public void paintComponent(Graphics g) { - int w = customTempLabelWidth - 2 * borderLen; - int h = componentHeight - 2 * borderLen; - g.setColor(CyderColors.navy); - g.fillRect(borderLen, borderLen, w, h); - - int mappedTemperatureValue = (int) Math.round( - InterpolationUtil.rangeMap(temperature, minTemp, maxTemp, 0, customTempLabelWidth)); - int yOffset = 3; - int lineWidth = 6; - g.setColor(CyderColors.regularPink); - g.fillRect(mappedTemperatureValue + yOffset, borderLen, - lineWidth, componentHeight - 2 * yOffset); - - String minTempText = formatFloatMeasurement(minTemp) + "F"; - minTempLabel.setText(minTempText); - minTempLabel.setSize( - StringUtil.getMinWidth(minTempText, minTempLabel.getFont()), - StringUtil.getMinHeight(minTempText, minTempLabel.getFont())); - minTempLabel.setLocation(10, (componentHeight - minTempLabel.getHeight()) / 2); - customTempLabel.add(minTempLabel); - - String currentTempText = formatFloatMeasurement(temperature) + "F"; - currentTempLabel.setText(currentTempText); - currentTempLabel.setSize( - StringUtil.getMinWidth(currentTempText, currentTempLabel.getFont()), - StringUtil.getMinHeight(currentTempText, currentTempLabel.getFont())); - currentTempLabel.setLocation(customTempLabel.getWidth() / 2 - currentTempLabel.getWidth() / 2, - customTempLabel.getHeight() / 2 - currentTempLabel.getHeight() / 2); - customTempLabel.add(currentTempLabel); - - // set max temp label - String maxText = formatFloatMeasurement(maxTemp) + "F"; - maxTempLabel.setText(maxText); - maxTempLabel.setSize( - StringUtil.getMinWidth(maxText, minTempLabel.getFont()), - StringUtil.getMinHeight(maxText, minTempLabel.getFont())); - maxTempLabel.setLocation(customTempLabelWidth - maxTempLabel.getWidth(), - (componentHeight - maxTempLabel.getHeight()) / 2); - customTempLabel.add(maxTempLabel); - - g.setColor(Color.black); - - g.fillRect(0, 0, borderLen, componentHeight); - g.fillRect(customTempLabelWidth - borderLen, 0, borderLen, componentHeight); - g.fillRect(0, 0, customTempLabelWidth, borderLen); - g.fillRect(0, componentHeight - borderLen, customTempLabelWidth, borderLen); - } - }; - customTempLabel.setBounds(40, 320, customTempLabelWidth, 40); - weatherFrame.getContentPane().add(customTempLabel); - - windSpeedLabel = new JLabel("", SwingConstants.CENTER); - windSpeedLabel.setText("Wind: " + windSpeed + Units.MILES_PER_HOUR.getAbbreviation() - + comma + space + windBearing + Units.DEGREES.getAbbreviation() + space + openingParenthesis - + getWindDirection(windBearing) + CyderStrings.closingParenthesis); - windSpeedLabel.setForeground(CyderColors.navy); - windSpeedLabel.setFont(CyderFonts.SEGOE_20); - windSpeedLabel.setBounds(0, 390, 480, 30); - weatherFrame.getContentPane().add(windSpeedLabel); - - windDirectionLabel = new JLabel() { - private static final int length = 50; - private static final int borderLength = 3; - private static final int arrowWidth = 3; - private static final int arrowRadius = 20; - - private static final Color backgroundColor = Color.black; - private static final Color innerColor = CyderColors.navy; - private static final Color arrowColor = CyderColors.regularPink; - - @Override - public void paintComponent(Graphics g) { - g.setColor(backgroundColor); - g.fillRect(0, 0, length, length); - - g.setColor(innerColor); - g.fillRect(borderLength, borderLength, - getWidth() - 2 * borderLength, getHeight() - 2 * borderLength); - - double theta = windBearing * Math.PI / 180.0; - double x = arrowRadius * Math.cos(theta); - double y = arrowRadius * Math.sin(theta); - - int drawToX = (int) Math.round(x); - int drawToY = -(int) Math.round(y); - - Graphics2D g2d = (Graphics2D) g; - g2d.setStroke(new BasicStroke(arrowWidth)); - - int w = getWidth(); - int h = getHeight(); - - g.setColor(arrowColor); - g.drawLine(w / 2, h / 2, w / 2 + drawToX, w / 2 + drawToY); - } - }; - windDirectionLabel.setBounds(weatherFrame.getWidth() / 2 - 50 / 2, 430, 50, 50); - weatherFrame.getContentPane().add(windDirectionLabel); - - humidityLabel = new JLabel("Humidity: " + humidity + "%", SwingConstants.CENTER); - humidityLabel.setForeground(CyderColors.navy); - humidityLabel.setFont(CyderFonts.SEGOE_20); - humidityLabel.setBounds(0, 500, 480, 30); - weatherFrame.getContentPane().add(humidityLabel); - - pressureLabel = new JLabel("Pressure: " + pressure + Units.ATMOSPHERES.getAbbreviation(), - SwingConstants.CENTER); - pressureLabel.setForeground(CyderColors.navy); - pressureLabel.setFont(CyderFonts.SEGOE_20); - pressureLabel.setBounds(0, 540, 480, 30); - weatherFrame.getContentPane().add(pressureLabel); - - timezoneLabel = new JLabel("Timezone: " + getGmtTimezoneLabelText(), SwingConstants.CENTER); - timezoneLabel.setForeground(CyderColors.navy); - timezoneLabel.setFont(CyderFonts.SEGOE_20); - timezoneLabel.setBounds(0, 580, 480, 30); - weatherFrame.getContentPane().add(timezoneLabel); - - weatherFrame.finalizeAndShow(); - - startWeatherStatsUpdater(); - startUpdatingClock(); - } - - /** - * The actions to invoke when this weather frame is disposed. - */ - private void onWeatherFrameDisposed() { - stopUpdating.set(true); - instances.remove(this); - getterUtilInstance.closeAllGetFrames(); - } - - /** - * Starts the thread to update the current time label. - */ - private void startUpdatingClock() { - CyderThreadRunner.submit(() -> { - while (!stopUpdating.get()) { - ThreadUtil.sleep((long) TimeUtil.millisInSecond); - currentTimeLabel.setText(getWeatherTimeAccountForGmtOffset()); - } - }, WEATHER_CLOCK_UPDATER_THREAD_NAME); - } - - /** - * Starts the thread to update the weather stats. - */ - private void startWeatherStatsUpdater() { - CyderThreadRunner.submit(() -> { - try { - while (true) { - ThreadUtil.sleepWithChecks(updateStatsFrequency.toMillis(), - checkUpdateStatsExitConditionFrequency.toMillis(), stopUpdating); - if (stopUpdating.get()) break; - repullWeatherStats(); - } - } catch (Exception e) { - ExceptionHandler.handle(e); - } - }, WEATHER_STATS_UPDATER_THREAD_NAME); - } - - /** - * Returns the current weather time correct based on the current gmt offset. - * - * @return the current weather time correct based on the current gmt offset - */ - private String getWeatherTimeAccountForGmtOffset() { - Calendar calendar = Calendar.getInstance(); - SimpleDateFormat dateFormatter = TimeUtil.weatherFormat; - dateFormatter.setTimeZone(TimeZone.getTimeZone(GMT)); - - try { - int timeOffset = Integer.parseInt(weatherDataGmtOffset) / SECONDS_IN_HOUR; - calendar.add(Calendar.HOUR, timeOffset); - } catch (Exception e) { - ExceptionHandler.handle(e); - } - - return dateFormatter.format(calendar.getTime()); - } - - /** - * Refreshes the weather labels based off of the current vars. - */ - private void refreshWeatherLabels() { - if (currentLocationString.length() > 1) { - String[] parts = currentLocationString.split(CyderStrings.comma); - StringBuilder sb = new StringBuilder(); - - for (int i = 0 ; i < parts.length ; i++) { - String part = parts[i].trim(); - - boolean properLength = parts.length == cityStateCountryFormatLen; - boolean isState = i == stateIndex; - boolean stateAbbreviationLength = part.length() == stateAbbrLen; - if (properLength && isState && stateAbbreviationLength) { - sb.append(part.toUpperCase()); - } else { - sb.append(StringUtil.capsFirstWords(part)); - } - - if (i != parts.length - 1) sb.append(", "); - } - - locationLabel.setText(sb.toString()); - } else { - locationLabel.setText(""); - } - - currentWeatherIconLabel.setIcon(generateCurrentWeatherIcon()); - - currentWeatherLabel.setText(HtmlTags.openingHtml - + HtmlTags.divTextAlignCenterVerticalAlignBottom - + StringUtil.capsFirstWords(weatherCondition) - .replaceAll(CyderRegexPatterns.whiteSpaceRegex, HtmlTags.breakTag) - + HtmlTags.closingHtml); - - windSpeedLabel.setText("Wind" + colon + space + windSpeed + Units.MILES_PER_HOUR.getAbbreviation() - + CyderStrings.comma + space + windBearing + Units.DEGREES.getAbbreviation() + space - + CyderStrings.openingParenthesis + getWindDirection(windBearing) - + CyderStrings.closingParenthesis); - humidityLabel.setText("Humidity: " + humidity + "%"); - pressureLabel.setText("Pressure: " + formatFloatMeasurement(pressure) + Units.ATMOSPHERES.getAbbreviation()); - timezoneLabel.setText("Timezone: " + getGmtTimezoneLabelText()); - - String sunriseMeridiemModifier = sunriseHour < 12 ? AM : PM; - String sunsetMeridiemModifier = sunsetHour >= 12 ? AM : PM; - - sunriseLabel.setText(formatTimeAccountingForGmtOffset(sunriseFormatted) + sunriseMeridiemModifier); - sunsetLabel.setText(formatTimeAccountingForGmtOffset(sunsetFormatted) + sunsetMeridiemModifier); - - customTempLabel.repaint(); - currentTempLabel.setText(temperature + "F"); - - int tempLabelWidth = StringUtil.getMinWidth(currentTempLabel.getText(), currentTempLabel.getFont()); - int tempLabelHeight = StringUtil.getMinHeight(currentTempLabel.getText(), currentTempLabel.getFont()); - - int tempLabelPadding = 3; - currentTempLabel.setBounds(calculateTemperatureLineCenter(temperature, minTemp, maxTemp), - customTempLabel.getY() - tempLabelPadding - tempLabelHeight, tempLabelWidth, tempLabelHeight); - - windDirectionLabel.repaint(); - - String splitCity = currentLocationString.split(CyderStrings.comma)[0]; - refreshFrameTitle(splitCity); - - if (weatherFrame != null) { - weatherFrame.revokeAllNotifications(); - weatherFrame.toast(new NotificationBuilder(REFRESHED).setViewDuration(1000)); - } - } - - /** - * Returns an ImageIcon for the current weather state. - * - * @return an ImageIcon for the current weather state - */ - private ImageIcon generateCurrentWeatherIcon() { - long sunsetTime = new Date(sunsetMillis * 1000).getTime(); - long currentTime = new Date().getTime(); - - boolean isAfterSunset = currentTime > sunsetTime; - String weatherIconIdAndTime = weatherIconId.replaceAll(CyderRegexPatterns.englishLettersRegex, "") - + (isAfterSunset ? NIGHT_IMAGE_ID : DAY_IMAGE_ID); - - return StaticUtil.getImageIcon(weatherIconIdAndTime + Extension.PNG.getExtension()); - } - - /** - * Calculates the x center for the current temperature within the temperature label. - * - * @param temperature the current temperature - * @param minTemp the minimum temperature - * @param maxTemp the maximum temperature - * @return the x center for the current temperature within the temperature label - */ - private int calculateTemperatureLineCenter(float temperature, float minTemp, float maxTemp) { - int tempLabelWidth = StringUtil.getMinWidth(currentTempLabel.getText(), currentTempLabel.getFont()); - - int customTempLabelMinX = customTempLabel.getX(); - int customTempLabelMaxX = customTempLabel.getX() + customTempLabel.getWidth() - tempLabelWidth; - - int temperatureLineCenter = (int) Math.ceil(customTempLabel.getX() + InterpolationUtil.rangeMap( - temperature, minTemp, maxTemp, 0, customTempLabelWidth)) + temperatureLineCenterAdditive; - temperatureLineCenter -= (tempLabelWidth) / 2; - - if (temperatureLineCenter < customTempLabelMinX) { - temperatureLineCenter = customTempLabelMinX; - } - - if (temperatureLineCenter > customTempLabelMaxX) { - temperatureLineCenter = customTempLabelMaxX; - } - - return temperatureLineCenter; - } - - /** - * Refreshes the frame title based on the provided city. - * - * @param city the city to display in the frame title - */ - private void refreshFrameTitle(String city) { - Preconditions.checkNotNull(city); - - city = city.trim(); - - if (!city.isEmpty()) { - String correctedCityName = StringUtil.capsFirstWords(city).trim(); - weatherFrame.setTitle(correctedCityName + StringUtil.getApostropheSuffix(correctedCityName) - + CyderStrings.space + WEATHER); - } else { - weatherFrame.setTitle(DEFAULT_TITLE); - } - } - - /** - * Returns the text for the timezone label. For example, if weatherDataGmtOffset is -18000 - * and DST is active, then the method will return "GMT-5 [DST Active]. - * - * @return the text for the timezone label - */ - private String getGmtTimezoneLabelText() { - IpData data = IpDataManager.INSTANCE.getIpData(); - - String gmtPart = GMT + (Integer.parseInt(weatherDataGmtOffset) / SECONDS_IN_HOUR); - String dstPart = data.getTime_zone().isIs_dst() ? - CyderStrings.space + CyderStrings.openingBracket + DST_ACTIVE + CyderStrings.closingBracket : ""; - - return gmtPart + dstPart; - } - - /** - * Returns the hh:mm time after accounting for the GMT offset. - * - * @param absoluteTime the absolute hh:mm time - * @return the hh:mm time after accounting for the GMT offset - */ - private String formatTimeAccountingForGmtOffset(String absoluteTime) { - Preconditions.checkNotNull(absoluteTime); - Preconditions.checkArgument(absoluteTime.contains(colon)); - - String[] splitTime = absoluteTime.split(colon); - Preconditions.checkState(splitTime.length == 2); - - int hour = Integer.parseInt(splitTime[0]); - int minute = Integer.parseInt(splitTime[1]); - - hour += (Integer.parseInt(weatherDataGmtOffset) / SECONDS_IN_HOUR - (parsedGmtOffset / SECONDS_IN_HOUR)); - - return hour + colon + formatMinutes(minute); - } - - /** - * Formats the provided minutes to always have two digits. - * - * @param minute the minutes value - * @return the formatted minutes string - */ - private String formatMinutes(int minute) { - Preconditions.checkArgument(TimeUtil.minuteRange.contains(minute)); - - return minute < 10 ? "0" + minute : String.valueOf(minute); - } - - /** - * Refreshes the weather stat variables. - */ - private void repullWeatherStats() { - CyderThreadRunner.submit(() -> { - IpData data = IpDataManager.INSTANCE.getIpData(); - - userCity = data.getCity(); - userState = data.getRegion(); - userCountry = data.getCountry_name(); - - if (!useCustomLoc) currentLocationString = userCity + ", " + userState + ", " + userCountry; - - Optional optionalWeatherData = WeatherUtil.getWeatherData(currentLocationString); - if (optionalWeatherData.isEmpty()) { - weatherFrame.revokeNotification(refreshingNotificationText); - weatherFrame.notify("Sorry, but that location is invalid"); - currentLocationString = previousLocationString; - useCustomLoc = false; - return; - } - - WeatherData weatherData = optionalWeatherData.get(); - sunriseMillis = Long.parseLong(String.valueOf(weatherData.getSys().getSunrise())); - sunsetMillis = Long.parseLong(String.valueOf(weatherData.getSys().getSunset())); - weatherIconId = weatherData.getWeather().get(0).getIcon(); - windSpeed = weatherData.getWind().getSpeed(); - windBearing = weatherData.getWind().getDeg(); - weatherCondition = weatherData.getWeather().get(0).getDescription(); - pressure = weatherData.getMain().getPressure(); - humidity = weatherData.getMain().getHumidity(); - temperature = weatherData.getMain().getTemp(); - weatherDataGmtOffset = String.valueOf(weatherData.getTimezone()); - minTemp = weatherData.getMain().getTemp_min(); - maxTemp = weatherData.getMain().getTemp_max(); - - refreshMapBackground(); - - Date sunrise = new Date((long) (sunriseMillis * TimeUtil.millisInSecond)); - sunriseFormatted = sunriseSunsetFormat.format(sunrise); - Calendar sunriseCalendar = GregorianCalendar.getInstance(); - sunriseCalendar.setTimeInMillis(sunriseMillis); - sunriseHour = sunriseCalendar.get(Calendar.HOUR); - - Date sunset = new Date((long) (sunsetMillis * TimeUtil.millisInSecond)); - sunsetFormatted = sunriseSunsetFormat.format(sunset); - Calendar sunsetCalendar = GregorianCalendar.getInstance(); - sunsetCalendar.setTimeInMillis(sunsetMillis); - sunsetHour = sunsetCalendar.get(Calendar.HOUR); - - if (!isGmtSet) { - parsedGmtOffset = Integer.parseInt(weatherDataGmtOffset); - isGmtSet = true; - } - - refreshWeatherLabels(); - - Console.INSTANCE.revalidateConsoleTaskbarMenu(); - }, WEATHER_STATS_UPDATER_THREAD_NAME); - } - - /** - * Refreshes the map background of the weather frame. If not enabled, hides the map. - * If enabled, shows the map. - */ - public void refreshMapBackground() { - try { - if (UserDataManager.INSTANCE.shouldDrawWeatherMap()) { - ImageIcon newMapBackground = mapViewBuilder.setLocationString(currentLocationString).getMapView(); - weatherFrame.setBackground(newMapBackground); - } else { - weatherFrame.setBackground(defaultBackground); - } - - refreshReadableLabels(UserDataManager.INSTANCE.shouldDrawWeatherMap()); - } catch (Exception e) { - ExceptionHandler.handle(e); - weatherFrame.notify("Could not refresh map background"); - } - } - - /** - * Refreshes the labels with raw text on them based on the current visibility of the background map. - * - * @param mapVisible whether the map is visible - */ - private void refreshReadableLabels(boolean mapVisible) { - if (mapVisible) { - currentTimeLabel.setForeground(CyderColors.navy); - locationLabel.setForeground(CyderColors.navy); - windSpeedLabel.setForeground(CyderColors.navy); - humidityLabel.setForeground(CyderColors.navy); - pressureLabel.setForeground(CyderColors.navy); - timezoneLabel.setForeground(CyderColors.navy); - } else { - currentTimeLabel.setForeground(CyderColors.vanilla); - locationLabel.setForeground(CyderColors.vanilla); - windSpeedLabel.setForeground(CyderColors.vanilla); - humidityLabel.setForeground(CyderColors.vanilla); - pressureLabel.setForeground(CyderColors.vanilla); - timezoneLabel.setForeground(CyderColors.vanilla); - } - } - - /** - * Refreshes the map background of all weather instances. - */ - public static void refreshAllMapBackgrounds() { - instances.forEach(WeatherWidget::refreshMapBackground); - } - - /** - * Returns the wind direction string based off of the current wind bearing. - * - * @param bearing the current wind bearing - * @return the wind direction string based off of the current wind bearing - */ - public String getWindDirection(double bearing) { - bearing = AngleUtil.normalizeAngle360(bearing); - - StringBuilder ret = new StringBuilder(); - - if (AngleUtil.angleInNorthernHemisphere(bearing)) { - ret.append(NORTH); - - if (bearing > AngleUtil.NINETY_DEGREES) { - ret.append(WEST); - } else if (bearing < AngleUtil.NINETY_DEGREES) { - ret.append(EAST); - } - } else if (AngleUtil.angleInSouthernHemisphere(bearing)) { - ret.append(SOUTH); - - if (bearing < AngleUtil.TWO_SEVENTY_DEGREES) { - ret.append(WEST); - } else if (bearing > AngleUtil.TWO_SEVENTY_DEGREES) { - ret.append(EAST); - } - } else if (AngleUtil.angleIsEast(bearing)) { - ret.append(EAST); - } else if (AngleUtil.angleIsWest(bearing)) { - ret.append(WEST); - } - - return ret.toString(); - } - - /** - * Returns the float formatted using {@link #floatMeasurementFormatter}. - * - * @param measurement the float measurement to format - * @return the formatted measurement - */ - private static String formatFloatMeasurement(float measurement) { - return floatMeasurementFormatter.format(measurement); - } -} diff --git a/src/main/java/cyder/widgets/CalculatorWidget.java b/src/main/java/cyder/widgets/CalculatorWidget.java deleted file mode 100644 index 8e4dcece5..000000000 --- a/src/main/java/cyder/widgets/CalculatorWidget.java +++ /dev/null @@ -1,314 +0,0 @@ -package cyder.widgets; - -import com.fathzer.soft.javaluator.DoubleEvaluator; -import cyder.annotations.CyderAuthor; -import cyder.annotations.Vanilla; -import cyder.annotations.Widget; -import cyder.constants.CyderColors; -import cyder.constants.CyderFonts; -import cyder.exceptions.IllegalMethodException; -import cyder.strings.CyderStrings; -import cyder.ui.button.CyderModernButton; -import cyder.ui.button.ThemeBuilder; -import cyder.ui.drag.CyderDragLabel; -import cyder.ui.field.CyderTextField; -import cyder.ui.frame.CyderFrame; - -import javax.swing.*; -import javax.swing.border.LineBorder; -import java.awt.*; - -/** - * A calculator widget for parsing mathematical expressions. - */ -@Vanilla -@CyderAuthor -public final class CalculatorWidget { - /** - * The field to display the most recent results in. - */ - private static CyderTextField resultField; - - /** - * The field in which the user may enter an expression - */ - private static CyderTextField calculatorField; - - /** - * The text to display to user if an expression could not be parsed. - */ - private static final String ERROR_TEXT = "Could not parse expression"; - - /** - * The font to use for the results field. - */ - private static final Font fieldFont = new Font("Agency FB", Font.BOLD, 25); - - /** - * The theme for each calculator button. - */ - private static final ThemeBuilder theme = new ThemeBuilder(); - - static { - theme.setFont(CyderFonts.SEGOE_30); - theme.setBorderLength(5); - theme.setBackgroundColor(CyderColors.regularOrange); - theme.setHoverColor(CyderColors.regularOrange.darker()); - theme.setPressedColor(CyderColors.regularOrange.darker().darker()); - } - - /** - * Suppress default constructor. - */ - private CalculatorWidget() { - throw new IllegalMethodException(CyderStrings.ATTEMPTED_INSTANTIATION); - } - - /** - * The width of the calculator frame. - */ - private static final int FRAME_WIDTH = 400; - - /** - * The height of the calculator frame. - */ - private static final int FRAME_HEIGHT = 600; - - /** - * The width and height of each calculator button. - */ - private static final int buttonLength = 75; - - /** - * The description for the widget annotation. - */ - private static final String description = "A calculator widget capable of " - + "performing complex expressions such as e^x, sin(x), cos(x), and so forth."; - - @Widget(triggers = {"calculator", "calc", "math"}, description = description) - public static void showGui() { - CyderFrame calculatorFrame = new CyderFrame.Builder() - .setWidth(FRAME_WIDTH) - .setHeight(FRAME_HEIGHT) - .setTitle("Calculator") - .build(); - calculatorFrame.setTitle("Calculator"); - - resultField = new CyderTextField(); - resultField.setBorder(null); - resultField.setEditable(false); - resultField.setFocusable(true); - resultField.setSelectionColor(CyderColors.selectionColor); - resultField.setHorizontalAlignment(JTextField.RIGHT); - resultField.setFont(fieldFont); - resultField.setBounds(25, CyderDragLabel.DEFAULT_HEIGHT + 10, 350, 30); - calculatorFrame.getContentPane().add(resultField); - - calculatorField = new CyderTextField(); - calculatorField.setBorder(null); - calculatorField.setHorizontalAlignment(JTextField.LEFT); - calculatorField.setSelectionColor(CyderColors.selectionColor); - calculatorField.setToolTipText("Use radians and not degrees for any trig functions"); - calculatorField.setFont(fieldFont); - calculatorField.setBounds(25, - CyderDragLabel.DEFAULT_HEIGHT + 5 + 30 + 5, 350, 25); - calculatorFrame.getContentPane().add(calculatorField); - - JLabel borderLabel = new JLabel(); - borderLabel.setBounds(20, CyderDragLabel.DEFAULT_HEIGHT + 5, 360, 65); - borderLabel.setBorder(new LineBorder(CyderColors.navy, 5)); - borderLabel.setOpaque(false); - calculatorFrame.getContentPane().add(borderLabel); - - CyderModernButton calculatorAdd = new CyderModernButton("+"); - calculatorAdd.addClickRunnable(() -> calculatorField.setText(calculatorField.getText() + "+")); - calculatorAdd.setTheme(theme); - calculatorAdd.setBounds(20, 120, buttonLength, buttonLength); - calculatorFrame.getContentPane().add(calculatorAdd); - - CyderModernButton calculatorSubtract = new CyderModernButton(CyderStrings.dash); - calculatorSubtract.addClickRunnable(() -> calculatorField.setText(calculatorField.getText() - + CyderStrings.dash)); - calculatorSubtract.setTheme(theme); - calculatorSubtract.setBounds(115, 120, buttonLength, buttonLength); - calculatorFrame.getContentPane().add(calculatorSubtract); - - CyderModernButton calculatorMultiply = new CyderModernButton("*"); - calculatorMultiply.addClickRunnable(() -> calculatorField.setText(calculatorField.getText() + "*")); - calculatorMultiply.setTheme(theme); - calculatorMultiply.setBounds(210, 120, buttonLength, buttonLength); - calculatorFrame.getContentPane().add(calculatorMultiply); - - CyderModernButton calculatorDivide = new CyderModernButton(CyderStrings.forwardSlash); - calculatorDivide.addClickRunnable(() -> calculatorField.setText(calculatorField.getText() - + CyderStrings.forwardSlash)); - calculatorDivide.setTheme(theme); - calculatorDivide.setBounds(305, 120, buttonLength, buttonLength); - calculatorFrame.getContentPane().add(calculatorDivide); - - CyderModernButton calculatorSeven = new CyderModernButton("7"); - calculatorSeven.addClickRunnable(() -> calculatorField.setText(calculatorField.getText() + "7")); - calculatorSeven.setTheme(theme); - calculatorSeven.setBounds(20, 215, buttonLength, buttonLength); - calculatorFrame.getContentPane().add(calculatorSeven); - - CyderModernButton calculatorEight = new CyderModernButton("8"); - calculatorEight.addClickRunnable(() -> calculatorField.setText(calculatorField.getText() + "8")); - calculatorEight.setBounds(115, 215, buttonLength, buttonLength); - calculatorEight.setTheme(theme); - calculatorFrame.getContentPane().add(calculatorEight); - - CyderModernButton calculatorNine = new CyderModernButton("9"); - calculatorNine.addClickRunnable(() -> calculatorField.setText(calculatorField.getText() + "9")); - calculatorNine.setTheme(theme); - calculatorNine.setBounds(210, 215, buttonLength, buttonLength); - calculatorFrame.getContentPane().add(calculatorNine); - - CyderModernButton calculatorEquals = new CyderModernButton("="); - calculatorEquals.addClickRunnable(CalculatorWidget::computeExpression); - calculatorEquals.setTheme(theme); - calculatorEquals.setBounds(305, 215, buttonLength, buttonLength); - calculatorFrame.getContentPane().add(calculatorEquals); - - CyderModernButton calculatorFour = new CyderModernButton("4"); - calculatorFour.addClickRunnable(() -> calculatorField.setText(calculatorField.getText() + "4")); - calculatorFour.setTheme(theme); - calculatorFour.setBounds(20, 310, buttonLength, buttonLength); - calculatorFrame.getContentPane().add(calculatorFour); - - CyderModernButton calculatorFive = new CyderModernButton("5"); - calculatorFive.addClickRunnable(() -> calculatorField.setText(calculatorField.getText() + "5")); - calculatorFive.setTheme(theme); - calculatorFive.setBounds(115, 310, buttonLength, buttonLength); - calculatorFrame.getContentPane().add(calculatorFive); - - CyderModernButton calculatorSix = new CyderModernButton("6"); - calculatorSix.addClickRunnable(() -> calculatorField.setText(calculatorField.getText() + "6")); - calculatorSix.setTheme(theme); - calculatorSix.setBounds(210, 310, buttonLength, buttonLength); - calculatorFrame.getContentPane().add(calculatorSix); - - CyderModernButton calculatorClear = new CyderModernButton("CE"); - calculatorClear.addClickRunnable(CalculatorWidget::clearFields); - calculatorClear.setTheme(theme); - calculatorClear.setBounds(305, 310, buttonLength, buttonLength); - calculatorFrame.getContentPane().add(calculatorClear); - - CyderModernButton calculatorOne = new CyderModernButton("1"); - calculatorOne.addClickRunnable(() -> calculatorField.setText(calculatorField.getText() + "1")); - calculatorOne.setTheme(theme); - calculatorOne.setBounds(20, 405, buttonLength, buttonLength); - calculatorFrame.getContentPane().add(calculatorOne); - - CyderModernButton calculatorTwo = new CyderModernButton("2"); - calculatorTwo.addClickRunnable(() -> calculatorField.setText(calculatorField.getText() + "2")); - calculatorTwo.setTheme(theme); - calculatorTwo.setBounds(115, 405, buttonLength, buttonLength); - calculatorFrame.getContentPane().add(calculatorTwo); - - CyderModernButton calculatorThree = new CyderModernButton("3"); - calculatorThree.addClickRunnable(() -> calculatorField.setText(calculatorField.getText() + "3")); - calculatorThree.setTheme(theme); - calculatorThree.setBounds(210, 405, buttonLength, buttonLength); - calculatorFrame.getContentPane().add(calculatorThree); - - CyderModernButton undo = new CyderModernButton("<<"); - undo.addClickRunnable(CalculatorWidget::undoAction); - undo.setTheme(theme); - undo.setBounds(305, 405, buttonLength, buttonLength); - calculatorFrame.getContentPane().add(undo); - - CyderModernButton calculatorZero = new CyderModernButton("0"); - calculatorZero.addClickRunnable(() -> calculatorField.setText(calculatorField.getText() + "0")); - calculatorZero.setTheme(theme); - calculatorZero.setBounds(20, 500, buttonLength, buttonLength); - calculatorFrame.getContentPane().add(calculatorZero); - - CyderModernButton calculatorDecimal = new CyderModernButton("."); - calculatorDecimal.addClickRunnable(() -> calculatorField.setText(calculatorField.getText() + ".")); - calculatorDecimal.setTheme(theme); - calculatorDecimal.setBounds(115, 500, buttonLength, buttonLength); - calculatorFrame.getContentPane().add(calculatorDecimal); - - CyderModernButton calculatorOpenP = new CyderModernButton(CyderStrings.openingParenthesis); - calculatorOpenP.addClickRunnable(() -> calculatorField.setText(calculatorField.getText() - + CyderStrings.openingParenthesis)); - calculatorOpenP.setTheme(theme); - calculatorOpenP.setBounds(210, 500, buttonLength, buttonLength); - calculatorFrame.getContentPane().add(calculatorOpenP); - - CyderModernButton calculatorCloseP = new CyderModernButton(CyderStrings.closingParenthesis); - calculatorCloseP.addClickRunnable(() -> calculatorField.setText(calculatorField.getText() - + CyderStrings.closingParenthesis)); - calculatorCloseP.setTheme(theme); - calculatorCloseP.setBounds(305, 500, buttonLength, buttonLength); - calculatorFrame.getContentPane().add(calculatorCloseP); - - calculatorFrame.finalizeAndShow(); - } - - /** - * Clears the calculator fields. - */ - private static void clearFields() { - calculatorField.setText(""); - resultField.setText(""); - } - - /** - * Removes the last character from the calculator field. - */ - private static void undoAction() { - String text = calculatorField.getText(); - - if (text.length() > 0) { - calculatorField.setText(text.substring(0, text.length() - 1)); - } - } - - /** - * The evaluator for evaluating expressions. - */ - private static final DoubleEvaluator evaluator = new DoubleEvaluator(); - - /** - * The positive infinity string. - */ - private static final String POSITIVE_INFINITY = "+∞"; - - /** - * The negative infinity string. - */ - private static final String NEGATIVE_INFINITY = "-∞"; - - /** - * Attempts to compute the expression from the calculator field. - */ - private static void computeExpression() { - try { - double result = evaluator.evaluate(calculatorField.getText().trim()); - String resultString = String.valueOf(result); - - if (result == Double.POSITIVE_INFINITY) { - resultString = POSITIVE_INFINITY; - } else if (result == Double.NEGATIVE_INFINITY) { - resultString = NEGATIVE_INFINITY; - } - - setResultText(resultString); - } catch (IllegalArgumentException e) { - setResultText(ERROR_TEXT); - } - } - - /** - * Animates in the results text to the results field by fading it from - * {@link CyderColors#regularRed} to {@link CyderColors#navy} in 500ms. - * - * @param resultText the text to show in the results field - */ - private synchronized static void setResultText(String resultText) { - resultField.setText(resultText); - resultField.flashField(); - } -} diff --git a/src/main/java/cyder/widgets/ClickWidget.java b/src/main/java/cyder/widgets/ClickWidget.java deleted file mode 100644 index 91cd69b87..000000000 --- a/src/main/java/cyder/widgets/ClickWidget.java +++ /dev/null @@ -1,126 +0,0 @@ -package cyder.widgets; - -import cyder.annotations.CyderAuthor; -import cyder.annotations.ForReadability; -import cyder.annotations.Vanilla; -import cyder.annotations.Widget; -import cyder.constants.CyderColors; -import cyder.constants.CyderFonts; -import cyder.exceptions.IllegalMethodException; -import cyder.handlers.internal.ExceptionHandler; -import cyder.math.NumberUtil; -import cyder.strings.CyderStrings; -import cyder.ui.UiUtil; -import cyder.ui.frame.CyderFrame; -import cyder.ui.frame.enumerations.FrameType; - -import javax.swing.*; -import java.awt.*; -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; - -/** - * A widget for extreme boredom. - */ -@Vanilla -@CyderAuthor -public final class ClickWidget { - /** - * Suppress default instantiation. - */ - private ClickWidget() { - throw new IllegalMethodException(CyderStrings.ATTEMPTED_INSTANTIATION); - } - - /** - * The frame title and label text. - */ - private static final String CLICK_ME = "Click Me"; - - /** - * The frame width. - */ - private static final int FRAME_WIDTH = 220; - - /** - * The frame height. - */ - private static final int FRAME_HEIGHT = 100; - - /** - * The widget frame. - */ - private static CyderFrame clickMeFrame; - - /** - * The offset between the monitor bounds and the possible locations to place the frame at. - */ - private static final int monitorMinOffset = 200; - - /** - * The font for the click me label. - */ - private static final Font clickMeLabelFont = new Font(CyderFonts.SEGOE_UI_BLACK, Font.BOLD, 22); - - @Widget(triggers = "click me", description = "A troll widget that pops open a new window every time it is clicked") - public static void showGui() { - try { - UiUtil.closeIfOpen(clickMeFrame); - - clickMeFrame = new CyderFrame.Builder() - .setWidth(FRAME_WIDTH) - .setHeight(FRAME_HEIGHT) - .setTitle(CLICK_ME) - .setBackgroundColor(CyderColors.vanilla) - .setType(FrameType.POPUP) - .build(); - clickMeFrame.setAutoFastClose(true); - - JLabel clickMeLabel = new JLabel(CLICK_ME); - clickMeLabel.setHorizontalAlignment(JLabel.CENTER); - clickMeLabel.setVerticalAlignment(JLabel.CENTER); - clickMeLabel.setForeground(CyderColors.navy); - clickMeLabel.setFont(clickMeLabelFont); - clickMeLabel.setBounds(30, 40, 150, 40); - clickMeLabel.addMouseListener(new MouseAdapter() { - @Override - public void mouseReleased(MouseEvent e) { - changeFramePosition(); - } - - @Override - public void mouseEntered(MouseEvent e) { - clickMeLabel.setForeground(CyderColors.regularRed); - } - - @Override - public void mouseExited(MouseEvent e) { - clickMeLabel.setForeground(CyderColors.navy); - } - }); - - clickMeFrame.getContentPane().add(clickMeLabel); - - clickMeFrame.finalizeAndShow(); - } catch (Exception e) { - ExceptionHandler.handle(e); - } - } - - /** - * The logic for when the click me label is pressed. - */ - @ForReadability - private static void changeFramePosition() { - Rectangle bounds = clickMeFrame.getMonitorBounds(); - int minX = (int) bounds.getMinX(); - int maxX = (int) (minX + bounds.getHeight()); - int minY = (int) bounds.getMinY(); - int maxY = (int) (minY + bounds.getHeight()); - - int randomX = NumberUtil.generateRandomInt(minX + monitorMinOffset, maxX - monitorMinOffset); - int randomY = NumberUtil.generateRandomInt(minY + monitorMinOffset, maxY - monitorMinOffset); - - clickMeFrame.setLocation(randomX, randomY); - } -} diff --git a/src/main/java/cyder/widgets/ClockWidget.java b/src/main/java/cyder/widgets/ClockWidget.java deleted file mode 100644 index d344555d7..000000000 --- a/src/main/java/cyder/widgets/ClockWidget.java +++ /dev/null @@ -1,874 +0,0 @@ -package cyder.widgets; - -import com.google.common.base.Joiner; -import com.google.common.base.Preconditions; -import com.google.common.collect.ImmutableList; -import cyder.annotations.CyderAuthor; -import cyder.annotations.ForReadability; -import cyder.annotations.Vanilla; -import cyder.annotations.Widget; -import cyder.console.Console; -import cyder.constants.CyderColors; -import cyder.constants.CyderFonts; -import cyder.constants.HtmlTags; -import cyder.exceptions.IllegalMethodException; -import cyder.getter.GetInputBuilder; -import cyder.getter.GetterUtil; -import cyder.handlers.internal.ExceptionHandler; -import cyder.math.AngleUtil; -import cyder.network.IpDataManager; -import cyder.parsers.ip.IpData; -import cyder.props.Props; -import cyder.strings.CyderStrings; -import cyder.strings.StringUtil; -import cyder.threads.CyderThreadRunner; -import cyder.threads.ThreadUtil; -import cyder.time.TimeUtil; -import cyder.ui.UiUtil; -import cyder.ui.drag.CyderDragLabel; -import cyder.ui.drag.button.DragLabelTextButton; -import cyder.ui.frame.CyderFrame; -import cyder.ui.frame.enumerations.TitlePosition; -import cyder.ui.label.CyderLabel; -import cyder.user.UserDataManager; -import cyder.utils.ColorUtil; -import cyder.weather.WeatherUtil; -import cyder.weather.parsers.Coord; -import cyder.weather.parsers.WeatherData; - -import javax.swing.*; -import javax.swing.border.LineBorder; -import java.awt.*; -import java.text.SimpleDateFormat; -import java.time.Duration; -import java.util.Calendar; -import java.util.Optional; -import java.util.TimeZone; -import java.util.concurrent.atomic.AtomicBoolean; - -import static cyder.strings.CyderStrings.*; - -/** - * A clock widget for displaying the current time in a fancy and minimalistic format. - */ -@Vanilla -@CyderAuthor -public final class ClockWidget { - /** - * Suppress default constructor. - */ - private ClockWidget() { - throw new IllegalMethodException(CyderStrings.ATTEMPTED_INSTANTIATION); - } - - /** - * The clock frame. - */ - private static CyderFrame clockFrame; - - /** - * The custom label to paint. - */ - private static JLabel clockLabel; - - /** - * The digital time label. - */ - private static JLabel digitalTimeAndDateLabel; - - /** - * Whether to show the seconds hand - */ - private static boolean showSecondHand = true; - - /** - * Whether to show the hour numerals. - */ - private static boolean paintHourLabels = true; - - /** - * The default clock color - */ - private static Color clockColor = CyderColors.getGuiThemeColor(); - - /** - * Whether to update the clock. - */ - private static final AtomicBoolean shouldUpdateWidget = new AtomicBoolean(false); - - /** - * The default location for the clock. - */ - private static final String DEFAULT_LOCATION = "Greenwich, London"; - - /** - * The GMT location string. - */ - private static String currentLocation = DEFAULT_LOCATION; - - /** - * The GMT offset for the current timezone. - */ - private static int currentGmtOffset; - - /** - * The taskbar button text to spawn a mini clock frame. - */ - private static final String MINI = "Mini"; - - /** - * The tooltip for the mini button. - */ - private static final String TOOLTIP = "Spawn a mini clock for the current location"; - - /** - * The description of this widget. - */ - private static final String widgetDescription = "A clock widget capable of spawning" - + " mini widgets and changing the time zone"; - - /** - * A joiner for joining strings on commas. - */ - private static final Joiner commaJoiner = Joiner.on(CyderStrings.comma); - - /** - * The list of roman numerals, organized by the angle made between the positive x axis. - */ - private static final ImmutableList romanNumerals = ImmutableList.of( - "III", "IV", "V", "VI", "VII", "VIII", "IX", "X", "XI", "XII", "I", "II" - ); - - /** - * The widget frame title. - */ - private static final String CLOCK = "Clock"; - - /** - * The frame width of the widget. - */ - private static final int FRAME_WIDTH = 500; - - /** - * The frame height of the widget. - */ - private static final int FRAME_HEIGHT = 600; - - /** - * The hour date pattern. - */ - private static final String hourDaterPattern = "HH"; - - /** - * The minute date pattern. - */ - private static final String minuteDatePattern = "mm"; - - /** - * The second date pattern. - */ - private static final String secondDatePattern = "ss"; - - /** - * The font of the clock label. - */ - private static final Font clockFont = new Font(CyderFonts.AGENCY_FB, Font.BOLD, 26); - - /** - * The y padding for the digital time and date label. - */ - private static final int digitalTimeAndDateLabelYPadding = 20; - - /** - * The x padding for the digital time and date label. - */ - private static final int digitalTimeAndDateLabelXPadding = 10; - - /** - * The clock label padding between the label ends and the frame. - */ - private static final int clockLabelPadding = 20; - - /** - * The length of the clock label. - */ - private static final int clockLabelLength = FRAME_WIDTH - 2 * clockLabelPadding; - - /** - * The hour hand ratio for the radius. - */ - private static final float hourHandRatio = 0.65f; - - /** - * The minute hand ratio for the radius. - */ - private static final float minuteHandRatio = 0.75f; - - /** - * The second hand ratio for the radius. - */ - private static final float secondHandRatio = 0.80f; - - /** - * The radius of the center dot. - */ - private static final int centerDotRadius = 20; - - /** - * The additive for the starting y values of numeral labels. - */ - private static final int numeralLabelTopLeftYAdditive = -10; - - /** - * The length of hour box labels. - */ - private static final int hourBoxLabelLength = 20; - - /** - * The padding between the edges of the clock label and painted attributes. - */ - private static final int innerLabelPadding = 20; - - /** - * The maximum radius for clock hands. - */ - private static final int maxHandRadius = (clockLabelLength - innerLabelPadding * 2 - hourBoxLabelLength * 2) / 2; - - /** - * The center of the clock label. - */ - private static final int clockLabelCenter = clockLabelLength / 2; - - /** - * The increment for painting hours. - */ - private static final double hourThetaInc = AngleUtil.THREE_SIXTY_DEGREES / romanNumerals.size(); - - /** - * The radius for the hour hand. - */ - private static final int hourHandRadius = (int) (maxHandRadius * hourHandRatio); - - /** - * The radius for the minute hand. - */ - private static final int minuteHandRadius = (int) (maxHandRadius * minuteHandRatio); - - /** - * The radius for the second hand. - */ - private static final int secondHandRadius = (int) (maxHandRadius * secondHandRatio); - - /** - * The location string. - */ - private static final String LOCATION = "Location"; - - /** - * The current location string. - */ - private static final String CURRENT_LOCATION = "Current Location"; - - /** - * The set location string. - */ - private static final String SET_LOCATION = "Set location"; - - /** - * The label text for the getter util for setting the current location. - */ - private static final String locationLabelText = "Time Location " - + HtmlTags.breakTag + "Enter locations separated by commas"; - - /** - * The color string. - */ - private static final String COLOR = "Color"; - - /** - * The theme color string. - */ - private static final String THEME_COLOR = "Theme color"; - - /** - * The regex for the get string color getter. - */ - private static final String colorThemeFieldRegex = "[A-Fa-f0-9]{0,6}"; - - /** - * The name for the color theme color getter waiting thread. - */ - private static final String CLOCK_COLOR_THEME_GETTER_WAITER = "Clock Color Theme Getter"; - - /** - * The thread name for the clock widget initializer thread. - */ - private static final String CLOCK_WIDGET_INITIALIZER_THREAD_NAME = "Clock Widget Initializer"; - - /** - * The width of mini frames to spawn. - */ - private static final int miniFrameWidth = 600; - - /** - * The height of mini frames to spawn. - */ - private static final int miniFrameHeight = 150; - - /** - * The timezone colon text. - */ - private static final String TIMEZONE = "Timezone:"; - - /** - * The timeout for mini clock updates. - */ - private static final Duration miniClockUpdateTimeout = Duration.ofMillis(500); - - /** - * The mini clock thread prefix. - */ - private static final String minClockUpdaterThreadNamePrefix = "Mini CLock Updater"; - - /** - * The time formatter for getting the current time accounting for the gmt offset. - */ - private static final SimpleDateFormat timeFormatter = TimeUtil.weatherFormat; - - /** - * The GMT timezone string ID. - */ - private static final String GMT = "GMT"; - - /** - * The thread name for the clock time updater. - */ - private static final String CLOCK_UPDATER_THREAD_NAME = "Clock Time Updater"; - - /** - * The delay between clock updates. - */ - private static final Duration CLOCK_UPDATER_TIMEOUT = Duration.ofMillis(250); - - /** - * The builder for getting the theme color. - */ - private static GetInputBuilder themeColorBuilder = null; - - /** - * The GMT timezone object. - */ - private static final TimeZone gmtTimezone = TimeZone.getTimeZone(GMT); - - @Widget(triggers = "clock", description = widgetDescription) - public static void showGui() { - CyderThreadRunner.submit(() -> { - UiUtil.closeIfOpen(clockFrame); - - clockColor = CyderColors.getGuiThemeColor(); - - shouldUpdateWidget.set(true); - setShowSecondHand(UserDataManager.INSTANCE.shouldShowClockWidgetSecondHand()); - setPaintHourLabels(UserDataManager.INSTANCE.shouldPaintClockHourLabels()); - - IpData data = IpDataManager.INSTANCE.getIpData(); - currentLocation = commaJoiner.join(data.getCity(), data.getRegion(), data.getCountry_name()); - currentGmtOffset = getGmtFromUserLocation(); - - CyderFrame.Builder builder = new CyderFrame.Builder() - .setWidth(FRAME_WIDTH) - .setHeight(FRAME_HEIGHT) - .setTitle(CLOCK); - clockFrame = new CyderFrame(builder) { - @Override - public void dispose() { - shouldUpdateWidget.set(false); - super.dispose(); - } - }; - - digitalTimeAndDateLabel = new CyderLabel(getCurrentTimeAccountingForOffset(currentGmtOffset)); - digitalTimeAndDateLabel.setFont(clockFont); - digitalTimeAndDateLabel.setBounds(digitalTimeAndDateLabelXPadding, - CyderDragLabel.DEFAULT_HEIGHT + digitalTimeAndDateLabelYPadding, - FRAME_WIDTH - 2 * digitalTimeAndDateLabelXPadding, 40); - clockFrame.getContentPane().add(digitalTimeAndDateLabel); - - clockLabel = new JLabel() { - @Override - public void paintComponent(Graphics g) { - super.paintComponent(g); - Graphics2D g2d = (Graphics2D) g; - - g2d.setStroke(new BasicStroke(6)); - g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); - g2d.setColor(clockColor); - g2d.setFont(CyderFonts.DEFAULT_FONT); - - if (paintHourLabels) { - paintHourLabels(g2d); - } else { - paintHourBoxes(g2d); - } - - g2d.setStroke(new BasicStroke(6, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND)); - - drawHourHand(g2d); - drawMinuteHand(g2d); - - if (showSecondHand) drawSecondHand(g2d); - - drawCenterDot(g2d); - } - }; - clockLabel.setBounds(clockLabelPadding, 100, clockLabelLength, clockLabelLength); - clockLabel.setBorder(new LineBorder(CyderColors.navy, 5)); - clockFrame.getContentPane().add(clockLabel); - - startClockUpdater(); - installDragLabelButtons(); - - clockFrame.finalizeAndShow(); - }, CLOCK_WIDGET_INITIALIZER_THREAD_NAME); - } - - /** - * Draws the hour hand on the clock. - * - * @param g2d the 2D graphics object - */ - @ForReadability - private static void drawHourHand(Graphics2D g2d) { - int hour = Integer.parseInt(TimeUtil.getTime(hourDaterPattern)) % ((int) TimeUtil.hoursInDay / 2); - - float oneHourAngle = (float) (AngleUtil.THREE_SIXTY_DEGREES / romanNumerals.size()); - double hourTheta = hour * oneHourAngle + AngleUtil.TWO_SEVENTY_DEGREES; - hourTheta = AngleUtil.normalizeAngle360(hourTheta) * Math.PI / AngleUtil.ONE_EIGHTY_DEGREES; - int hourHandDrawToX = (int) Math.round(hourHandRadius * Math.cos(hourTheta)); - int hourHandDrawToY = (int) Math.round(hourHandRadius * Math.sin(hourTheta)); - g2d.drawLine(clockLabelCenter, clockLabelCenter, - clockLabelCenter + hourHandDrawToX, - clockLabelCenter + hourHandDrawToY); - } - - /** - * Draws the minute hand on the clock. - * - * @param g2d the 2D graphics object - */ - @ForReadability - private static void drawMinuteHand(Graphics2D g2d) { - int minute = Integer.parseInt(TimeUtil.getTime(minuteDatePattern)); - - double minuteTheta = (minute / TimeUtil.minutesInHour) * Math.PI * 2.0 + Math.PI * 1.5; - int minuteHandDrawToX = (int) Math.round(minuteHandRadius * Math.cos(minuteTheta)); - int minuteHandDrawToY = (int) Math.round(minuteHandRadius * Math.sin(minuteTheta)); - g2d.drawLine(clockLabelCenter, clockLabelCenter, - clockLabelCenter + minuteHandDrawToX, - clockLabelCenter + minuteHandDrawToY); - } - - /** - * Draws the second hand on the clock. - * - * @param g2d the current 2D graphics object - */ - @ForReadability - private static void drawSecondHand(Graphics2D g2d) { - int second = Integer.parseInt(TimeUtil.getTime(secondDatePattern)); - - double secondTheta = (second / TimeUtil.secondsInMinute) - * Math.PI * 2.0f + Math.PI * 1.5; - int secondHandDrawToX = (int) Math.round(secondHandRadius * Math.cos(secondTheta)); - int secondHandDrawToY = (int) Math.round(secondHandRadius * Math.sin(secondTheta)); - g2d.drawLine(clockLabelCenter, clockLabelCenter, - clockLabelCenter + secondHandDrawToX, - clockLabelCenter + secondHandDrawToY); - } - - /** - * Draws the center dot on the clock. - * - * @param g2d the current 2D graphics object - */ - @ForReadability - private static void drawCenterDot(Graphics2D g2d) { - g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); - g2d.setColor(CyderColors.navy); - g2d.setStroke(new BasicStroke(6, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND)); - g2d.fillOval(clockLabelCenter - centerDotRadius / 2, clockLabelCenter - centerDotRadius / 2, - centerDotRadius, centerDotRadius); - } - - /** - * Draws the hour boxes on the clock. - * - * @param g2d the current 2D graphics object - */ - @ForReadability - private static void paintHourBoxes(Graphics2D g2d) { - double hourTheta = 0.0; - - for (int i = 0 ; i < romanNumerals.size() ; i++) { - double currentRadians = hourTheta * Math.PI / AngleUtil.ONE_EIGHTY_DEGREES; - double x = maxHandRadius * Math.cos(currentRadians); - double y = maxHandRadius * Math.sin(currentRadians); - - int topLeftX = (int) (x - hourBoxLabelLength / 2 + clockLabelCenter); - int topleftY = (int) (y - hourBoxLabelLength / 2 + clockLabelCenter) + numeralLabelTopLeftYAdditive; - - g2d.fillRect(topLeftX, topleftY, hourBoxLabelLength, hourBoxLabelLength); - - hourTheta += hourThetaInc; - } - } - - /** - * Draws the hour labels on the clock. - * - * @param g2d the current 2D graphics object - */ - @ForReadability - private static void paintHourLabels(Graphics2D g2d) { - double hourTheta = 0.0; - - for (int i = 0 ; i < romanNumerals.size() ; i++) { - double radians = hourTheta * Math.PI / AngleUtil.ONE_EIGHTY_DEGREES; - double x = maxHandRadius * Math.cos(radians); - double y = maxHandRadius * Math.sin(radians); - - int topLeftX = (int) (x - hourBoxLabelLength / 2 + clockLabelCenter) + 10; - int topleftY = (int) (y - hourBoxLabelLength / 2 + clockLabelCenter); - - String minText = romanNumerals.get(i); - g2d.drawString(minText, topLeftX - hourBoxLabelLength / 2, - topleftY + hourBoxLabelLength / 2); - - hourTheta += hourThetaInc; - } - } - - /** - * Starts the updating thread for the clock. - */ - private static void startClockUpdater() { - CyderThreadRunner.submit(() -> { - while (shouldUpdateWidget.get()) { - ThreadUtil.sleep(CLOCK_UPDATER_TIMEOUT.toMillis()); - digitalTimeAndDateLabel.setText(getCurrentTimeAccountingForOffset(currentGmtOffset)); - clockLabel.repaint(); - } - }, CLOCK_UPDATER_THREAD_NAME); - } - - /** - * Installs the drag label buttons for this widget. - */ - private static void installDragLabelButtons() { - DragLabelTextButton miniClockButton = new DragLabelTextButton.Builder(MINI) - .setTooltip(TOOLTIP) - .setClickAction(ClockWidget::spawnMiniClock) - .build(); - clockFrame.getTopDragLabel().addRightButton(miniClockButton, 0); - - DragLabelTextButton colorButton = new DragLabelTextButton.Builder(COLOR) - .setTooltip(THEME_COLOR) - .setClickAction(getColorButtonClickRunnable()) - .build(); - clockFrame.getTopDragLabel().addRightButton(colorButton, 0); - - DragLabelTextButton locationButton = new DragLabelTextButton.Builder(LOCATION) - .setTooltip(CURRENT_LOCATION) - .setClickAction(getLocationButtonClickRunnable()) - .build(); - clockFrame.getTopDragLabel().addRightButton(locationButton, 0); - } - - /** - * Returns the runnable for the location drag label button. - * - * @return the runnable for the location drag label button - */ - private static Runnable getLocationButtonClickRunnable() { - return () -> CyderThreadRunner.submit(() -> { - Optional optionalPossibleLocation = GetterUtil.getInstance().getInput( - new GetInputBuilder(LOCATION, locationLabelText) - .setInitialFieldText(currentLocation) - .setSubmitButtonText(SET_LOCATION) - .setRelativeTo(clockFrame) - .setDisableRelativeTo(true)); - if (optionalPossibleLocation.isEmpty()) return; - String possibleLocation = optionalPossibleLocation.get(); - - Optional optionalWeatherData = WeatherUtil.getWeatherData(possibleLocation); - if (optionalWeatherData.isEmpty()) { - Console.INSTANCE.getConsoleCyderFrame().inform("Sorry, " - + "but the Weather Key has not been set or is invalid" - + ", as a result, many features of Cyder will not work as intended. " - + "Please see the fields panel of the user editor to learn how to acquire " - + "a key and set it.", "Weather Key Not Set"); - clockFrame.notify("Failed to update location"); - return; - } - - WeatherData weatherData = optionalWeatherData.get(); - String timezoneString = String.valueOf(weatherData.getTimezone()); - - int timezoneMinutes; - try { - timezoneMinutes = Integer.parseInt(timezoneString); - } catch (Exception ignored) { - clockFrame.notify("Failed to update location"); - return; - } - currentGmtOffset = timezoneMinutes / TimeUtil.secondsInHour; - currentLocation = StringUtil.capsFirstWords(possibleLocation); - - Coord coord = weatherData.getCoord(); - String build = CyderStrings.openingBracket + coord.getLat() + CyderStrings.comma - + coord.getLon() + CyderStrings.closingBracket; - - clockFrame.notify("Successfully updated location to " + weatherData.getName() - + HtmlTags.breakTag + GMT + CyderStrings.colon + space - + currentGmtOffset + HtmlTags.breakTag + build); - }, "tester"); - } - - /** - * Returns the runnable for the drag label color button. - * - * @return the runnable for the drag label color button - */ - private static Runnable getColorButtonClickRunnable() { - if (themeColorBuilder == null) { - themeColorBuilder = new GetInputBuilder(THEME_COLOR, "Theme color") - .setRelativeTo(clockFrame) - .setFieldRegex(colorThemeFieldRegex) - .setInitialFieldText(ColorUtil.toRgbHexString(clockColor)); - } - - return () -> CyderThreadRunner.submit(() -> { - Optional optionalColor = GetterUtil.getInstance().getInput(themeColorBuilder); - if (optionalColor.isEmpty()) return; - String colorText = optionalColor.get().trim(); - - Color newColor; - - try { - newColor = ColorUtil.hexStringToColor(colorText); - } catch (Exception ignored) { - clockFrame.notify("Could not parse input for hex color: " + colorText); - return; - } - - setClockColor(newColor); - }, CLOCK_COLOR_THEME_GETTER_WAITER); - } - - /** - * Sets the color of the clock to the provided color. - * - * @param clockColor the new clock color - */ - @ForReadability - private static void setClockColor(Color clockColor) { - Preconditions.checkNotNull(clockColor); - - ClockWidget.clockColor = clockColor; - clockLabel.repaint(); - } - - /** - * Spawns a mini clock with its own timer based off of the current location. - */ - private static void spawnMiniClock() { - AtomicBoolean updateMiniClock = new AtomicBoolean(true); - - CyderFrame.Builder builder = new CyderFrame.Builder() - .setWidth(miniFrameWidth) - .setHeight(miniFrameHeight) - .setTitle(TIMEZONE + space + openingParenthesis + GMT + currentGmtOffset + closingParenthesis); - CyderFrame miniFrame = new CyderFrame(builder) { - @Override - public void dispose() { - updateMiniClock.set(false); - super.dispose(); - } - }; - miniFrame.setTitlePosition(TitlePosition.CENTER); - - JLabel currentTimeLabel = - new JLabel(getCurrentTimeAccountingForOffset(currentGmtOffset), SwingConstants.CENTER); - currentTimeLabel.setForeground(CyderColors.navy); - currentTimeLabel.setFont(CyderFonts.SEGOE_20); - currentTimeLabel.setBounds(0, 50, miniFrameWidth, 30); - miniFrame.getContentPane().add(currentTimeLabel); - - if (!currentLocation.isEmpty()) { - String locationString = StringUtil.formatCommas(currentLocation); - String labelText = locationString - + space + openingParenthesis + GMT + currentGmtOffset + closingParenthesis; - JLabel locationLabel = new JLabel(labelText, SwingConstants.CENTER); - locationLabel.setForeground(CyderColors.navy); - locationLabel.setFont(CyderFonts.SEGOE_20); - locationLabel.setBounds(0, 80, miniFrameWidth, 30); - miniFrame.getContentPane().add(locationLabel); - } - - String threadName = minClockUpdaterThreadNamePrefix + space + CyderStrings.openingBracket - + GMT + currentGmtOffset + CyderStrings.closingBracket; - CyderThreadRunner.submit(() -> { - // Localize since the global can change - int localGmtOffset = currentGmtOffset; - while (updateMiniClock.get()) { - ThreadUtil.sleep(miniClockUpdateTimeout.toMillis()); - currentTimeLabel.setText(getCurrentTimeAccountingForOffset(localGmtOffset)); - } - }, threadName); - - miniFrame.setVisible(true); - miniFrame.setLocationRelativeTo(clockFrame); - } - - /** - * Returns the current time accounting for the GMT offset by adding - * the number of hours to the returned time. - * - * @param gmtOffsetInHours the GMT offset for the location - * @return the current time accounting for the GMT offset - */ - private static String getCurrentTimeAccountingForOffset(int gmtOffsetInHours) { - Calendar calendar = Calendar.getInstance(); - - timeFormatter.setTimeZone(TimeZone.getTimeZone(GMT)); - - try { - calendar.add(Calendar.HOUR, gmtOffsetInHours); - } catch (Exception e) { - ExceptionHandler.handle(e); - } - - return timeFormatter.format(calendar.getTime()); - } - - /** - * Possible gmt units used for hands of the clock. - */ - private enum GmtUnit { - HOUR("h"), - MINUTE("m"), - SECOND("s"); - - private final String unitString; - private final SimpleDateFormat dateFormatter; - - GmtUnit(String unitString) { - this.unitString = unitString; - this.dateFormatter = new SimpleDateFormat(unitString); - } - - /** - * Returns the unit string for this gmt unit. - * - * @return the unit string for this gmt unit - */ - public String getUnitString() { - return unitString; - } - - /** - * Returns the date formatter for this gmt unit. - * - * @return the date formatter for this gmt unit - */ - public SimpleDateFormat getDateFormatter() { - return dateFormatter; - } - } - - /** - * Returns the h/m/s provided accounting for the GMT offset - * - * @param unit the gmt unit, that of hour, minute, or second - * @return the unit accounting for the GMT offset - */ - private static int getUnitForCurrentGmt(GmtUnit unit) { - Calendar calendar = Calendar.getInstance(); - SimpleDateFormat dateFormatter = unit.getDateFormatter(); - dateFormatter.setTimeZone(gmtTimezone); - - try { - calendar.add(Calendar.HOUR, currentGmtOffset); - } catch (Exception e) { - ExceptionHandler.handle(e); - } - - return Integer.parseInt(dateFormatter.format(calendar.getTime())); - } - - /** - * Returns the GMT based off of the current location. - * - * @return the GMT based off of the current location - */ - private static int getGmtFromUserLocation() { - String key = Props.weatherKey.getValue(); - - if (key.isEmpty()) { - Console.INSTANCE.getConsoleCyderFrame().inform("Sorry, " - + "but the Weather Key has not been set or is invalid" - + ", as a result, many features of Cyder will not work as intended. " - + "Please see the fields panel of the user editor to learn how to acquire a key" - + " and set it.", "Weather Key Not Set"); - - resetGmtOffset(); - return 0; - } - - Optional optionalWeatherData = WeatherUtil.getWeatherData(currentLocation); - if (optionalWeatherData.isEmpty()) { - resetGmtOffset(); - return 0; - } - - WeatherData weatherData = optionalWeatherData.get(); - currentGmtOffset = Integer.parseInt(String.valueOf(weatherData.getTimezone())) / TimeUtil.secondsInHour; - return currentGmtOffset; - } - - /** - * Resets the GMT offset to 0. - */ - private static void resetGmtOffset() { - currentGmtOffset = 0; - currentLocation = DEFAULT_LOCATION; - } - - /** - * Sets whether to show the second hand. - * - * @param showSecondHand whether to show the second hand - */ - @ForReadability - public static void setShowSecondHand(boolean showSecondHand) { - ClockWidget.showSecondHand = showSecondHand; - - if (clockLabel != null && shouldUpdateWidget.get()) { - clockLabel.repaint(); - } - } - - /** - * Sets whether the hour labels should be painted. - * - * @param paintHourLabels whether the hour labels should be painted - */ - @ForReadability - public static void setPaintHourLabels(boolean paintHourLabels) { - ClockWidget.paintHourLabels = paintHourLabels; - - if (clockLabel != null && shouldUpdateWidget.get()) { - clockLabel.repaint(); - } - } -} diff --git a/src/main/java/cyder/widgets/ColorConverterWidget.java b/src/main/java/cyder/widgets/ColorConverterWidget.java deleted file mode 100644 index 74b32265c..000000000 --- a/src/main/java/cyder/widgets/ColorConverterWidget.java +++ /dev/null @@ -1,280 +0,0 @@ -package cyder.widgets; - -import cyder.annotations.CyderAuthor; -import cyder.annotations.SuppressCyderInspections; -import cyder.annotations.Vanilla; -import cyder.annotations.Widget; -import cyder.constants.CyderColors; -import cyder.constants.CyderFonts; -import cyder.constants.CyderRegexPatterns; -import cyder.enumerations.CyderInspection; -import cyder.layouts.CyderPartitionedLayout; -import cyder.logging.LogTag; -import cyder.logging.Logger; -import cyder.strings.CyderStrings; -import cyder.ui.field.CyderTextField; -import cyder.ui.frame.CyderFrame; -import cyder.utils.ColorUtil; - -import javax.swing.*; -import javax.swing.border.LineBorder; -import java.awt.*; -import java.awt.event.FocusAdapter; -import java.awt.event.FocusEvent; -import java.awt.event.KeyAdapter; -import java.awt.event.KeyEvent; - -/** - * A widget for converting between rgb and hex colors. - */ -@Vanilla -@CyderAuthor -public class ColorConverterWidget { - /** - * Returns a new instance of ColorConverterWidget. - * - * @return a new instance of ColorConverterWidget - */ - public static ColorConverterWidget getInstance() { - return new ColorConverterWidget(); - } - - /** - * Creates a new Color Converter Widget. - */ - private ColorConverterWidget() { - Logger.log(LogTag.OBJECT_CREATION, this); - } - - /** - * The widget description. - */ - private static final String description = "A color converter widget to convert from rgb to hex and vice versa"; - - /** - * A widget for converting between rgb and hex colors. - */ - @SuppressCyderInspections(CyderInspection.WidgetInspection) - @Widget(triggers = {"color converter", "color"}, description = description) - public static void showGui() { - getInstance().innerShowGui(); - } - - /** - * Shows the gui of this widget. - */ - public void innerShowGui() { - innerShowGui(CyderFrame.getDominantFrame()); - } - - /** - * The width of the frame. - */ - private static final int FRAME_WIDTH = 300; - - /** - * The height of the frame. - */ - private static final int FRAME_HEIGHT = 400; - - /** - * The color preview text. - */ - private static final String COLOR_PREVIEW = "Color preview"; - - /** - * The hex value text. - */ - private static final String HEX_VALUE = "Hex value"; - - /** - * The rgb value text. - */ - private static final String RGB_VALUE = "Rgb value"; - - /** - * The color converter text. - */ - private static final String TITLE = "Color Converter"; - - /** - * The limit of characters for the rgb field. - */ - private static final int RGB_FIELD_LIMIT = 11; - - /** - * The border for the color preview block. - */ - private static final LineBorder COLOR_BLOCK_BORDER = new LineBorder(CyderColors.navy, 5); - - /** - * The font for the rgb and hex fields. - */ - private static final Font fieldFont = new Font(CyderFonts.SEGOE_UI_BLACK, Font.BOLD, 26); - - /** - * The limit of characters for the hex field. - */ - private static final int hexFieldLimit = 6; - - /** - * The string formatter for the hex field. - */ - private static final String hexFieldStringFormatter = "#%02X%02X%02X"; - - /** - * The partition size for the spacers. - */ - private static final int spacerPartitionLength = 10; - - /** - * The partition space for the color labels and color block. - */ - private static final int colorLabelAndBlockPartitionLength = 15; - - /** - * The partition space for the text fields. - */ - private static final int fieldPartitionLength = 10; - - /** - * The size of the text fields and color block. - */ - private static final Dimension fieldAndBlockSize = new Dimension(220, 50); - - /** - * The starting color for the fields. - */ - private static final Color startingColor = CyderColors.navy; - - /** - * The height of labels. - */ - private static final int labelHeight = 30; - - /** - * Shows the gui of this widget. - * - * @param relativeTo the component to set the widget relative to - */ - public void innerShowGui(Component relativeTo) { - CyderFrame colorFrame = new CyderFrame.Builder() - .setWidth(FRAME_WIDTH) - .setHeight(FRAME_HEIGHT) - .build(); - colorFrame.setTitle(TITLE); - - colorFrame.initializeResizing(); - colorFrame.setResizable(true); - colorFrame.setBackgroundResizing(true); - colorFrame.setMinimumSize(new Dimension(FRAME_WIDTH, FRAME_HEIGHT)); - colorFrame.setMaximumSize(new Dimension(FRAME_WIDTH, 2 * FRAME_HEIGHT)); - - CyderPartitionedLayout layout = new CyderPartitionedLayout(); - - JLabel colorLabel = new JLabel(COLOR_PREVIEW); - colorLabel.setHorizontalAlignment(JLabel.CENTER); - colorLabel.setFont(CyderFonts.SEGOE_20); - colorLabel.setForeground(CyderColors.navy); - colorLabel.setSize(FRAME_WIDTH, labelHeight); - - JLabel hexLabel = new JLabel(HEX_VALUE); - hexLabel.setHorizontalAlignment(JLabel.CENTER); - hexLabel.setFont(CyderFonts.SEGOE_20); - hexLabel.setForeground(CyderColors.navy); - hexLabel.setSize(FRAME_WIDTH, labelHeight); - - JLabel rgbLabel = new JLabel(RGB_VALUE); - rgbLabel.setHorizontalAlignment(JLabel.CENTER); - rgbLabel.setFont(CyderFonts.SEGOE_20); - rgbLabel.setForeground(CyderColors.navy); - rgbLabel.setSize(FRAME_WIDTH, labelHeight); - - JTextField colorBlock = new JTextField(); - colorBlock.setBackground(CyderColors.navy); - colorBlock.setFocusable(false); - colorBlock.setCursor(null); - colorBlock.setToolTipText(COLOR_PREVIEW); - colorBlock.setBorder(COLOR_BLOCK_BORDER); - colorBlock.setSize(fieldAndBlockSize); - - CyderTextField rgbField = new CyderTextField(RGB_FIELD_LIMIT); - rgbField.setHorizontalAlignment(JTextField.CENTER); - rgbField.setText(startingColor.getRed() + CyderStrings.comma + startingColor.getGreen() - + CyderStrings.comma + startingColor.getBlue()); - - CyderTextField hexField = new CyderTextField(hexFieldLimit); - hexField.setKeyEventRegexMatcher(CyderRegexPatterns.hexPattern.pattern()); - hexField.setHorizontalAlignment(JTextField.CENTER); - hexField.setText(String.format(hexFieldStringFormatter, startingColor.getRed(), - startingColor.getGreen(), startingColor.getBlue()) - .replace(CyderStrings.hash, "")); - hexField.setBackground(CyderColors.empty); - hexField.setToolTipText(HEX_VALUE); - hexField.setFont(fieldFont); - hexField.addKeyListener(new KeyAdapter() { - public void keyReleased(KeyEvent e) { - try { - Color hexFieldColor = ColorUtil.hexStringToColor(hexField.getText()); - rgbField.setText(hexFieldColor.getRed() + CyderStrings.comma + hexFieldColor.getGreen() - + CyderStrings.comma + hexFieldColor.getBlue()); - colorBlock.setBackground(hexFieldColor); - } catch (Exception ignored) {} - } - }); - hexField.setSize(fieldAndBlockSize); - hexField.setOpaque(false); - - rgbField.setBackground(CyderColors.empty); - rgbField.setKeyEventRegexMatcher(CyderRegexPatterns.rgbPattern.pattern()); - rgbField.setToolTipText(RGB_VALUE); - rgbField.setFont(fieldFont); - rgbField.addKeyListener(new KeyAdapter() { - public void keyReleased(KeyEvent e) { - try { - String text = rgbField.getText(); - if (!text.contains(CyderStrings.comma)) return; - - String[] parts = text.split(CyderStrings.comma); - if (parts.length != 3) return; - - int r = Integer.parseInt(parts[0]); - int g = Integer.parseInt(parts[1]); - int b = Integer.parseInt(parts[2]); - - Color color = new Color(r, g, b); - hexField.setText(ColorUtil.toRgbHexString(color)); - colorBlock.setBackground(color); - } catch (Exception ignored) {} - } - }); - rgbField.setSize(fieldAndBlockSize); - rgbField.setOpaque(false); - - layout.spacer(spacerPartitionLength); - layout.addComponent(hexLabel, colorLabelAndBlockPartitionLength); - layout.addComponent(hexField, fieldPartitionLength); - layout.addComponent(rgbLabel, colorLabelAndBlockPartitionLength); - layout.addComponent(rgbField, fieldPartitionLength); - layout.addComponent(colorLabel, colorLabelAndBlockPartitionLength); - layout.addComponent(colorBlock, colorLabelAndBlockPartitionLength); - layout.spacer(spacerPartitionLength); - - colorFrame.setCyderLayout(layout); - - rgbField.addFocusListener(new FocusAdapter() { - @Override - public void focusLost(FocusEvent e) { - hexField.requestFocus(); - } - }); - hexField.addFocusListener(new FocusAdapter() { - @Override - public void focusLost(FocusEvent e) { - rgbField.requestFocus(); - } - }); - - colorFrame.finalizeAndShow(relativeTo); - } -} diff --git a/src/main/java/cyder/widgets/ConvexHullWidget.java b/src/main/java/cyder/widgets/ConvexHullWidget.java deleted file mode 100644 index c4c900013..000000000 --- a/src/main/java/cyder/widgets/ConvexHullWidget.java +++ /dev/null @@ -1,430 +0,0 @@ -package cyder.widgets; - -import com.google.common.base.Preconditions; -import com.google.common.collect.ImmutableList; -import cyder.annotations.*; -import cyder.constants.CyderColors; -import cyder.enumerations.CyderInspection; -import cyder.exceptions.IllegalMethodException; -import cyder.layouts.CyderPartitionedLayout; -import cyder.strings.CyderStrings; -import cyder.ui.UiUtil; -import cyder.ui.button.CyderButton; -import cyder.ui.frame.CyderFrame; -import cyder.ui.grid.CyderGrid; -import cyder.ui.grid.GridNode; -import cyder.ui.pane.CyderPanel; - -import javax.swing.*; -import javax.swing.border.LineBorder; -import java.awt.*; -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.Deque; - -/** - * Convex hull widget that solve a convex hull problem using a CyderGrid as the drawing label. - */ -@Vanilla -@CyderAuthor -public final class ConvexHullWidget { - /** - * The CyderFrame to use for the convex hull widget. - */ - private static CyderFrame hullFrame; - - /** - * The grid to use to represent points in space. - */ - private static CyderGrid gridComponent; - - /** - * Suppress default constructor. - */ - private ConvexHullWidget() { - throw new IllegalMethodException(CyderStrings.ATTEMPTED_INSTANTIATION); - } - - /** - * The title of the frame. - */ - private static final String FRAME_TITLE = "Convex Hull"; - - /** - * The solve button text. - */ - private static final String SOLVE = "Solve"; - - /** - * The reset button text. - */ - private static final String RESET = "Reset"; - - /** - * The number of grid nodes. - */ - private static final int GRID_NODES = 175; - - /** - * The length of the grid. - */ - private static final int GRID_LENGTH = 700; - - /** - * The padding around the edges of the grid. - */ - private static final int GRID_PADDING = 3; - - /** - * The size of the parent component for the grid. - */ - private static final int GRID_PARENT_LEN = GRID_LENGTH + 2 * GRID_PADDING; - - /** - * The width of the frame. - */ - private static final int FRAME_WIDTH = 800; - - /** - * The height of the frame. - */ - private static final int FRAME_HEIGHT = 850; - - /** - * The size of the buttons. - */ - private static final Dimension BUTTON_SIZE = new Dimension(300, 40); - - /** - * The top and bottom button padding. - */ - private static final int BUTTON_Y_PADDING = 10; - - /** - * The color of the nodes the user places on the grid. - */ - private static final Color PLACED_NODE_COLOR = CyderColors.regularPink; - - /** - * The color of the nodes placed by the algorithm. - */ - private static final Color WALL_NODE_COLOR = CyderColors.navy; - - /** - * The border for the grid component. - */ - private static final LineBorder GRID_PARENT_BORDER = new LineBorder(CyderColors.navy, GRID_PADDING); - - /** - * The minimum number of points required to form a polygon in 2D space. - */ - private static final int MIN_POLYGON_POINTS = 3; - - /** - * The text to display in a notification if the four corners of the grid are occupied. - */ - private static final String FOUR_CORNERS = "Congratulations, you played yourself"; - - /** - * Shows the convex hull widget. - */ - @SuppressCyderInspections(CyderInspection.WidgetInspection) - @Widget(triggers = {"convex", "convex hull"}, description = "A convex hull algorithm visualizer") - public static void showGui() { - UiUtil.closeIfOpen(hullFrame); - - hullFrame = new CyderFrame.Builder() - .setWidth(FRAME_WIDTH) - .setHeight(FRAME_HEIGHT) - .setTitle(FRAME_TITLE) - .build(); - - JLabel gridComponentParent = new JLabel(); - gridComponentParent.setSize(GRID_PARENT_LEN, GRID_PARENT_LEN); - gridComponentParent.setBorder(GRID_PARENT_BORDER); - - gridComponent = new CyderGrid(GRID_NODES, GRID_LENGTH); - gridComponent.setDrawGridLines(false); - gridComponent.setBounds(GRID_PADDING, GRID_PADDING, GRID_LENGTH, GRID_LENGTH); - gridComponent.setResizable(false); - gridComponent.setNodeColor(PLACED_NODE_COLOR); - gridComponent.setBackground(CyderColors.vanilla); - gridComponent.installClickListener(); - gridComponent.installDragListener(); - gridComponent.setSaveStates(false); - gridComponentParent.add(gridComponent); - - CyderPartitionedLayout buttonPartitionedLayout = new CyderPartitionedLayout(); - buttonPartitionedLayout.setPartitionDirection(CyderPartitionedLayout.PartitionDirection.ROW); - - buttonPartitionedLayout.spacer(10); - CyderButton solveButton = new CyderButton(SOLVE); - solveButton.setSize(BUTTON_SIZE); - solveButton.addActionListener(e -> solveButtonAction()); - buttonPartitionedLayout.addComponent(solveButton, 40); - - CyderButton resetButton = new CyderButton(RESET); - resetButton.addActionListener(e -> reset()); - resetButton.setSize(BUTTON_SIZE); - buttonPartitionedLayout.addComponent(resetButton, 40); - buttonPartitionedLayout.spacer(10); - - CyderPartitionedLayout partitionedLayout = new CyderPartitionedLayout(); - partitionedLayout.spacer(10); - - partitionedLayout.addComponent(gridComponentParent, 70); - - partitionedLayout.spacer(10); - - CyderPanel buttonPanel = new CyderPanel(buttonPartitionedLayout); - buttonPanel.setSize(FRAME_WIDTH, (int) (BUTTON_SIZE.getHeight() + 2 * BUTTON_Y_PADDING)); - partitionedLayout.addComponent(buttonPanel, 10); - - hullFrame.setCyderLayout(partitionedLayout); - hullFrame.finalizeAndShow(); - } - - /** - * The actions to invoke when the solve button is pressed. - */ - private static void solveButtonAction() { - gridComponent.setGridNodes(new ArrayList<>(gridComponent.getNodesOfColor(PLACED_NODE_COLOR))); - - if (gridComponent.getNodeCount() < MIN_POLYGON_POINTS) { - hullFrame.notify(MIN_POLYGON_POINTS + " points are required to create a polygon in 2D space"); - return; - } - - ArrayList points = new ArrayList<>(); - gridComponent.getGridNodes().forEach(node -> points.add(new Point(node.getX(), node.getY()))); - - ImmutableList hull = solveGrahamScan(points); - connectHullPointsWithLines(hull); - checkFourCorners(); - gridComponent.repaint(); - } - - /** - * Connects all points contained within the provided list with lines on the grid. - * - * @param hullPoints the points of the convex hull to connect with lines - */ - private static void connectHullPointsWithLines(ImmutableList hullPoints) { - Preconditions.checkNotNull(hullPoints); - Preconditions.checkArgument(hullPoints.size() >= MIN_POLYGON_POINTS); - - for (int i = 0 ; i < hullPoints.size() ; i++) { - Point firstPoint = hullPoints.get(i); - int secondPointIndex = (i == hullPoints.size() - 1) ? 0 : i + 1; - Point secondPoint = hullPoints.get(secondPointIndex); - - addMidPointsToGrid(firstPoint, secondPoint); - } - } - - /** - * Checks to see if the four corner grid nodes have a user-placed node in them. - */ - @ForReadability - private static void checkFourCorners() { - int min = 0; - int max = gridComponent.getNodeDimensionLength() - 1; - ImmutableList cornerNodes = ImmutableList.of( - /* Color is irrelevant here */ - new GridNode(WALL_NODE_COLOR, min, min), - new GridNode(WALL_NODE_COLOR, min, max), - new GridNode(WALL_NODE_COLOR, max, min), - new GridNode(WALL_NODE_COLOR, max, max) - ); - - if (gridComponent.getGridNodes().containsAll(cornerNodes)) { - hullFrame.notify(FOUR_CORNERS); - } - } - - /** - * Recursively finds the middle point between the provided points and adds - * it to the grid meaning a line between the original two provided points - * is created on the grid. - * - * @param firstPoint the first point - * @param secondPoint the second point - */ - private static void addMidPointsToGrid(Point firstPoint, Point secondPoint) { - Preconditions.checkNotNull(firstPoint); - Preconditions.checkNotNull(secondPoint); - - if (firstPoint.equals(secondPoint)) return; - - int midXPoints = (int) (secondPoint.getX() + firstPoint.getX()) / 2; - int midYPoints = (int) (secondPoint.getY() + firstPoint.getY()) / 2; - Point midPoint = new Point(midXPoints, midYPoints); - - if (midPoint.equals(firstPoint) || midPoint.equals(secondPoint)) return; - - GridNode gridMidPoint = new GridNode(WALL_NODE_COLOR, midXPoints, midYPoints); - if (gridComponent.contains(gridMidPoint)) { - gridComponent.removeNode(gridMidPoint); - } - gridComponent.addNode(gridMidPoint); - - addMidPointsToGrid(firstPoint, midPoint); - addMidPointsToGrid(midPoint, secondPoint); - } - - /** - * Solves the convex hull problem given the list of points. - * - * @param points the list of points - * @return the points in the convex hull - */ - private static ImmutableList solveGrahamScan(ArrayList points) { - Deque stack = new ArrayDeque<>(); - - Point bottomLeftMostPoint = getBottomLeftMostPoint(points); - sortPointsByAngle(points, bottomLeftMostPoint); - - int pointIndex = 0; - stack.push(points.get(pointIndex++)); - stack.push(points.get(pointIndex++)); - - for (int i = pointIndex, size = points.size() ; i < size ; i++) { - Point next = points.get(i); - Point poppedPoint = stack.pop(); - - while (stack.peek() != null) { - PointRotation rotation = determineRotation(stack.peek(), poppedPoint, next); - if (rotation == PointRotation.COUNTER_CLOCK_WISE) break; - poppedPoint = stack.pop(); - } - - stack.push(poppedPoint); - stack.push(points.get(i)); - } - - Point poppedPoint = stack.pop(); - if (stack.peek() == null) { - return ImmutableList.of(); - } - - PointRotation rotation = determineRotation(stack.peek(), poppedPoint, bottomLeftMostPoint); - if (rotation == PointRotation.COUNTER_CLOCK_WISE) { - stack.push(poppedPoint); - } - - return ImmutableList.copyOf(stack); - } - - /** - * Returns the node with the minimum y value from the provided list. - * If two points contain the same y value, the point with the minimum x value is returned. - * - * @param points the list of points - * @return the bottom left most point - */ - private static Point getBottomLeftMostPoint(ArrayList points) { - Preconditions.checkNotNull(points); - Preconditions.checkArgument(!points.isEmpty()); - - Point min = points.get(0); - for (Point point : points) { - if (point.getY() <= min.getY()) { - if (point.getY() < min.getY()) { - min = point; - } - // Choose leftmost point if same y value - else if (point.getX() < min.getX()) { - min = point; - } - } - } - - return min; - } - - /** - * The possible degrees of rotate between two points and a reference point. - */ - private enum PointRotation { - /** - * The points result in a clock wise turn. - */ - CLOCK_WISE, - /** - * The points result in a counter clock wise turn. - */ - COUNTER_CLOCK_WISE, - /** - * The points are co-linear. - */ - CO_LINEAR - } - - /** - * Determines the rotation between the first point and second point relative to the reference point. - * - * @param firstPoint the first point - * @param secondPoint the second point - * @param referencePoint the reference point - * @return the rotation between the first point and second point relative to the reference point - */ - private static PointRotation determineRotation(Point firstPoint, Point secondPoint, Point referencePoint) { - Preconditions.checkNotNull(firstPoint); - Preconditions.checkNotNull(secondPoint); - Preconditions.checkNotNull(referencePoint); - - float area = (secondPoint.x - firstPoint.x) * (referencePoint.y - firstPoint.y) - - (secondPoint.y - firstPoint.y) * (referencePoint.x - firstPoint.x); - - if (area < 0) { - return PointRotation.CLOCK_WISE; - } else if (area > 0) { - return PointRotation.COUNTER_CLOCK_WISE; - } else { - return PointRotation.CO_LINEAR; - } - } - - /** - * Sorts the list of points by angle using the reference point. - * - * @param points the list of points - * @param referencePoint the reference point - */ - private static void sortPointsByAngle(ArrayList points, Point referencePoint) { - Preconditions.checkNotNull(points); - Preconditions.checkNotNull(referencePoint); - - points.sort((firstPoint, secondPoint) -> { - if (firstPoint == referencePoint) { - return -1; - } else if (secondPoint == referencePoint) { - return 1; - } else if (firstPoint == secondPoint) { - return 0; - } - - PointRotation rotation = determineRotation(referencePoint, firstPoint, secondPoint); - if (rotation == PointRotation.CO_LINEAR) { - if (Double.compare(firstPoint.getX(), secondPoint.getX()) == 0) { - return firstPoint.getY() < secondPoint.getY() ? -1 : 1; - } else { - return firstPoint.getX() < secondPoint.getX() ? -1 : 1; - } - } else if (rotation == PointRotation.CLOCK_WISE) { - return 1; - } else if (rotation == PointRotation.COUNTER_CLOCK_WISE) { - return -1; - } - - throw new IllegalStateException("Invalid rotation: " + rotation); - }); - } - - /** - * Resets the convex hull state and grid. - */ - private static void reset() { - gridComponent.clearGrid(); - } -} diff --git a/src/main/java/cyder/widgets/ExampleWidget.java b/src/main/java/cyder/widgets/ExampleWidget.java deleted file mode 100644 index e5f99b834..000000000 --- a/src/main/java/cyder/widgets/ExampleWidget.java +++ /dev/null @@ -1,68 +0,0 @@ -package cyder.widgets; - -import cyder.annotations.CyderAuthor; -import cyder.annotations.SuppressCyderInspections; -import cyder.annotations.Vanilla; -import cyder.annotations.Widget; -import cyder.enumerations.CyderInspection; -import cyder.layouts.CyderGridLayout; -import cyder.logging.LogTag; -import cyder.logging.Logger; -import cyder.ui.button.CyderButton; -import cyder.ui.frame.CyderFrame; -import cyder.user.UserDataManager; - -/** - * An example widget for new Cyder developers to learn the standard widget construction. - */ -@Vanilla -@CyderAuthor -public final class ExampleWidget { - /** - * Suppress default constructor from access outside of this class and log - * when objects are created by the static factory method. - */ - private ExampleWidget() { - Logger.log(LogTag.OBJECT_CREATION, this); - } - - /** - * Returns an instance of ExampleWidget. - * - * @return an instance of ExampleWidget - */ - public static ExampleWidget getInstance() { - return new ExampleWidget(); - } - - @SuppressCyderInspections(CyderInspection.WidgetInspection) - @Widget(triggers = "example widget", description = "An example base widget for new Cyder developers") - public static void showGui() { - getInstance().innerShowGui(); - } - - /** - * Shows the widget. - */ - private void innerShowGui() { - CyderFrame cyderFrame = new CyderFrame.Builder() - .setWidth(600) - .setHeight(600) - .setTitle("Example Widget") - .build(); - - CyderButton cyderButton = new CyderButton("Button"); - cyderButton.setSize(200, 40); - cyderButton.addActionListener(e -> { - // Your logic here or a method reference to a local method (See EJ items 42 and 43) - cyderFrame.notify("Hello " + UserDataManager.INSTANCE.getUsername() + "!"); - }); - - CyderGridLayout gridLayout = new CyderGridLayout(1, 1); - gridLayout.addComponent(cyderButton); - - cyderFrame.setCyderLayout(gridLayout); - - cyderFrame.finalizeAndShow(); - } -} diff --git a/src/main/java/cyder/widgets/FileSignatureWidget.java b/src/main/java/cyder/widgets/FileSignatureWidget.java deleted file mode 100644 index cfc953445..000000000 --- a/src/main/java/cyder/widgets/FileSignatureWidget.java +++ /dev/null @@ -1,324 +0,0 @@ -package cyder.widgets; - -import cyder.annotations.CyderAuthor; -import cyder.annotations.Vanilla; -import cyder.annotations.Widget; -import cyder.constants.CyderColors; -import cyder.constants.CyderFonts; -import cyder.constants.CyderIcons; -import cyder.constants.CyderRegexPatterns; -import cyder.exceptions.IllegalMethodException; -import cyder.getter.GetFileBuilder; -import cyder.getter.GetterUtil; -import cyder.handlers.internal.ExceptionHandler; -import cyder.network.NetworkUtil; -import cyder.strings.CyderStrings; -import cyder.threads.CyderThreadRunner; -import cyder.ui.button.CyderButton; -import cyder.ui.field.CyderTextField; -import cyder.ui.frame.CyderFrame; -import cyder.ui.label.CyderLabel; - -import javax.swing.*; -import java.awt.*; -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; -import java.awt.event.MouseListener; -import java.io.File; -import java.io.FileInputStream; -import java.io.InputStream; -import java.util.Optional; - -@Vanilla -@CyderAuthor -public final class FileSignatureWidget { - /** - * The file to validate. - */ - private static File currentFile; - - /** - * The widget frame. - */ - private static CyderFrame signatureFrame; - - /** - * The expected byte signature input field. - */ - private static CyderTextField signatureField; - - /** - * The label to which the results are displayed on. - */ - private static CyderLabel resultLabel; - - /** - * The label for the files signatures sheet. - */ - private static CyderLabel fileSignaturesSheetLabel; - - /** - * The get file button. - */ - private static CyderButton getFile; - - /** - * The check file button. - */ - private static CyderButton checkFile; - - private static final String FILE_SIGNATURE_FILE_CHOOSER = "File Signature File Chooser"; - - /** - * A link for common file signatures. - */ - public static final String WIKIPEDIA_FILE_SIGNATURES = "https://en.wikipedia.org/wiki/List_of_file_signatures"; - - /** - * The mouse listener for the files signature sheet. - */ - private static final MouseListener fileSignaturesSheetLabelMouseListener = new MouseAdapter() { - @Override - public void mouseClicked(MouseEvent e) { - NetworkUtil.openUrl(WIKIPEDIA_FILE_SIGNATURES); - } - - @Override - public void mouseEntered(MouseEvent e) { - fileSignaturesSheetLabel.setForeground(CyderColors.regularRed); - } - - @Override - public void mouseExited(MouseEvent e) { - fileSignaturesSheetLabel.setForeground(CyderColors.navy); - } - }; - - /** - * The widget description. - */ - private static final String description = "A widget to read the raw file" - + " hex data and determine if the file signature matches the provided signature"; - - /** - * The widget title. - */ - private static final String TITLE = "File Signature Checker"; - - /** - * The font for the files signature sheet label. - */ - private static final Font FILE_SIGNATURES_SHEET_LABEL_FONT = new Font(CyderFonts.AGENCY_FB, Font.BOLD, 28); - - /** - * The select file text. - */ - private static final String SELECT_FILE = "Select File"; - - /** - * The validate file type text. - */ - private static final String VALIDATE_FILE_TYPE = "Validate File Type"; - - /** - * The comparison not yet performed text. - */ - private static final String COMPARISON_NOT_YET_PERFORMED = "Comparison not performed yet"; - - /** - * The file signature sheet label text. - */ - private static final String FILE_SIGNATURE_SHEET = "File Signature Sheet"; - - /** - * The choose file button text. - */ - private static final String CHOOSE_FILE_TO_VALIDATE = "Choose file to validate"; - - /** - * The default label text if a file is not chosen. - */ - private static final String PLEASE_CHOOSE_A_FILE = "Please choose a file"; - - /** - * The label text if a file signature is not entered. - */ - private static final String PLEASE_ENTER_A_FILE_SIGNATURE = "Please enter a file signature"; - - /** - * The width of the frame. - */ - private static final int FRAME_WIDTH = 400; - - /** - * The height of the frame. - */ - private static final int FRAME_HEIGHT = 420; - - /** - * The text for the validate button if the validation is valid. - */ - private static final String VALID = "Valid"; - - /** - * The text for the validate button if the validation is invalid. - */ - private static final String INVALID = "Invalid"; - - /** - * The valid file label text. - */ - private static final String validFileSignatureText = "File signature matches provided signature"; - - /** - * The invalid file label text. - */ - private static final String invalidFileSignatureText = "File signature does NOT match provided signature; " - + "If no more possible signature exist for this file, then it might be " - + "unsafe/corrupted"; - - /** - * The tooltip for the validate button. - */ - private static final String VALIDATE = "Validate"; - - /** - * The tooltip for the choose file button. - */ - private static final String CHOOSE_FILE = "Choose file"; - - /** - * Suppress default constructor. - */ - private FileSignatureWidget() { - throw new IllegalMethodException(CyderStrings.ATTEMPTED_INSTANTIATION); - } - - @Widget(triggers = {"file signature", "signature"}, description = description) - public static void showGui() { - signatureFrame = new CyderFrame.Builder() - .setWidth(FRAME_WIDTH) - .setHeight(FRAME_HEIGHT) - .setBackgroundIcon(CyderIcons.defaultBackground) - .build(); - signatureFrame.setTitle(TITLE); - - signatureField = new CyderTextField(); - signatureField.setHorizontalAlignment(JTextField.CENTER); - signatureField.setBounds(50, 120, 300, 40); - signatureField.setToolTipText("Enter the hex file signature of the file type you presume this file to be"); - signatureField.addActionListener(e -> validate()); - signatureFrame.getContentPane().add(signatureField); - - fileSignaturesSheetLabel = new CyderLabel(FILE_SIGNATURE_SHEET); - fileSignaturesSheetLabel.setBounds(50, 50, 300, 40); - fileSignaturesSheetLabel.setFont(FILE_SIGNATURES_SHEET_LABEL_FONT); - fileSignaturesSheetLabel.setToolTipText("Click here to view a list of common file signatures"); - fileSignaturesSheetLabel.addMouseListener(fileSignaturesSheetLabelMouseListener); - signatureFrame.getContentPane().add(fileSignaturesSheetLabel); - - getFile = new CyderButton(SELECT_FILE); - getFile.setBackground(CyderColors.regularPink); - getFile.setBounds(50, 190, 300, 40); - getFile.addActionListener(e -> getFileAction()); - getFile.setToolTipText(CHOOSE_FILE); - signatureFrame.getContentPane().add(getFile); - - checkFile = new CyderButton(VALIDATE_FILE_TYPE); - checkFile.setBackground(CyderColors.regularBlue); - checkFile.setBounds(50, 260, 300, 40); - checkFile.addActionListener(e -> validate()); - checkFile.setToolTipText(VALIDATE); - signatureFrame.getContentPane().add(checkFile); - - resultLabel = new CyderLabel(COMPARISON_NOT_YET_PERFORMED); - resultLabel.setBounds(50, 300, 300, 120); - signatureFrame.getContentPane().add(resultLabel); - - signatureFrame.finalizeAndShow(); - } - - /** - * The action to perform when the choose file button is clicked. - */ - private static void getFileAction() { - try { - CyderThreadRunner.submit(() -> { - try { - Optional optionalFile = GetterUtil.getInstance().getFile( - new GetFileBuilder(CHOOSE_FILE_TO_VALIDATE).setRelativeTo(signatureFrame)); - if (optionalFile.isEmpty()) return; - currentFile = optionalFile.get(); - resetResults(); - } catch (Exception ex) { - ExceptionHandler.handle(ex); - } - }, FILE_SIGNATURE_FILE_CHOOSER); - } catch (Exception ex) { - ExceptionHandler.handle(ex); - } - } - - /** - * Resets any results from a file validation including: - *
    - *
  • Setting the text of the get file button to the current file's name
  • - *
  • Setting the text of the check file button
  • - *
  • Resetting the results label text
  • - *
- */ - private static void resetResults() { - getFile.setText(currentFile.getName()); - checkFile.setText(VALIDATE_FILE_TYPE); - resultLabel.setText(COMPARISON_NOT_YET_PERFORMED); - } - - /** - * Attempts to validate the chosen file if present with the provided file signature if present. - */ - private static void validate() { - if (currentFile == null) { - signatureFrame.notify(PLEASE_CHOOSE_A_FILE); - return; - } else if (signatureField.getTrimmedText().isEmpty()) { - signatureFrame.notify(PLEASE_ENTER_A_FILE_SIGNATURE); - return; - } - - // Remove any byte identifiers and whitespace - // Text such as "0xFF 0xA0" becomes "FFA0" - String expectedByteSignature = signatureField.getTrimmedText() - .replaceAll("0x", "") - .replaceAll(CyderRegexPatterns.whiteSpaceRegex, ""); - - StringBuilder stringBuilder = new StringBuilder(); - - try { - InputStream inputStream = new FileInputStream(currentFile); - int columns = (int) Math.ceil(expectedByteSignature.length() / 2.0); - - long streamPointer = 0; - while (inputStream.available() > 0) { - long col = streamPointer++ % columns; - stringBuilder.append(String.format("%02x ", inputStream.read())); - - if (col == (columns - 1)) break; - } - } catch (Exception e) { - ExceptionHandler.handle(e); - } - - String byteSignatureString = stringBuilder.toString() - .replaceAll(CyderRegexPatterns.whiteSpaceRegex, ""); - - if (expectedByteSignature.equalsIgnoreCase(byteSignatureString)) { - resultLabel.setText(validFileSignatureText); - checkFile.setText(VALID); - } else { - resultLabel.setText(invalidFileSignatureText); - checkFile.setText(INVALID); - } - - signatureField.flashField(); - } -} diff --git a/src/main/java/cyder/widgets/GameOfLifeWidget.java b/src/main/java/cyder/widgets/GameOfLifeWidget.java deleted file mode 100644 index de8a22318..000000000 --- a/src/main/java/cyder/widgets/GameOfLifeWidget.java +++ /dev/null @@ -1,909 +0,0 @@ -package cyder.widgets; - -import com.google.common.base.Preconditions; -import com.google.errorprone.annotations.Immutable; -import cyder.annotations.*; -import cyder.constants.CyderColors; -import cyder.enumerations.CyderInspection; -import cyder.enumerations.Extension; -import cyder.exceptions.IllegalMethodException; -import cyder.files.FileUtil; -import cyder.getter.GetFileBuilder; -import cyder.getter.GetInputBuilder; -import cyder.getter.GetterUtil; -import cyder.handlers.internal.ExceptionHandler; -import cyder.layouts.CyderGridLayout; -import cyder.layouts.CyderPartitionedLayout; -import cyder.logging.LogTag; -import cyder.logging.Logger; -import cyder.strings.CyderStrings; -import cyder.threads.CyderThreadRunner; -import cyder.threads.ThreadUtil; -import cyder.time.TimeUtil; -import cyder.ui.UiUtil; -import cyder.ui.button.CyderButton; -import cyder.ui.frame.CyderFrame; -import cyder.ui.grid.CyderGrid; -import cyder.ui.grid.GridNode; -import cyder.ui.label.CyderLabel; -import cyder.ui.pane.CyderPanel; -import cyder.ui.selection.CyderComboBox; -import cyder.ui.selection.CyderComboBoxState; -import cyder.ui.selection.CyderSwitch; -import cyder.ui.selection.CyderSwitchState; -import cyder.ui.slider.CyderSliderUi; -import cyder.ui.slider.ThumbShape; -import cyder.user.UserUtil; -import cyder.utils.ArrayUtil; -import cyder.utils.SerializationUtil; -import cyder.utils.StaticUtil; - -import javax.swing.*; -import javax.swing.border.LineBorder; -import java.awt.*; -import java.io.File; -import java.io.FileReader; -import java.io.FileWriter; -import java.io.Reader; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Optional; - -/** - * Conway's game of life visualizer. - */ -@Vanilla -@CyderAuthor -public final class GameOfLifeWidget { - /** - * Suppress default constructor. - */ - private GameOfLifeWidget() { - throw new IllegalMethodException(CyderStrings.ATTEMPTED_INSTANTIATION); - } - - /** - * The game of life frame. - */ - private static CyderFrame conwayFrame; - - /** - * The top-level grid used to display the current generation. - */ - private static CyderGrid conwayGrid; - - /** - * The button to begin/stop (pause) the simulation. - */ - private static CyderButton stopSimulationButton; - - /** - * The combo box to cycle through the built-in presets. - */ - private static CyderComboBox presetComboBox; - - /** - * The slider to speed up/slow down the simulation. - */ - private static JSlider iterationsPerSecondSlider; - - /** - * The switch to toggle between detecting oscillations. - */ - private static CyderSwitch detectOscillationsSwitch; - - /** - * Whether the simulation is running - */ - private static boolean simulationRunning; - - /** - * The minimum allowable iterations per second. - */ - private static final int MIN_ITERATIONS_PER_SECOND = 1; - - /** - * The initial and default iterations per second. - */ - private static final int DEFAULT_ITERATIONS_PER_SECOND = 45; - - /** - * The number of iterations to compute per second. - */ - private static int iterationsPerSecond = DEFAULT_ITERATIONS_PER_SECOND; - - /** - * The maximum number of iterations per second. - */ - private static final int MAX_ITERATIONS_PER_SECOND = 100; - - /** - * The current generation the simulation is on. - */ - private static int generation; - - /** - * The current population of the current state. - */ - private static int population; - - /** - * The maximum population encountered for this simulation. - */ - private static int maxPopulation; - - /** - * The generation corresponding to the maximum population. - */ - private static int correspondingGeneration; - - /** - * The first corresponding generation to achieve the current maximum population. - */ - private static int firstCorrespondingGeneration; - - /** - * The label to display which generation the simulation is on. - */ - private static CyderLabel currentGenerationLabel; - - /** - * The label to display the population for the current generation. - */ - private static CyderLabel currentPopulationLabel; - - /** - * The label to display the maximum population. - */ - private static CyderLabel maxPopulationLabel; - - /** - * The label to display the generation for the maximum population. - */ - private static CyderLabel correspondingGenerationLabel; - - /** - * The state the grid was in before the user last pressed start. - */ - private static ArrayList beforeStartingState; - - /** - * The last state of the grid. - */ - private static ArrayList lastState = new ArrayList<>(); - - /** - * The conway states loaded from static JSON conway directory. - */ - private static ArrayList correspondingConwayStates; - - /** - * The switcher states to cycle between the states loaded from static JSON conway directory. - */ - private static ArrayList comboItems; - - /** - * The minimum dimensional node length for the inner cyder grid. - */ - private static final int MIN_NODES = 50; - - /** - * The width of the widget frame. - */ - private static final int FRAME_WIDTH = 600; - - /** - * The height of the widget frame. - */ - private static final int FRAME_HEIGHT = 920; - - /** - * The reset string. - */ - private static final String RESET = "Reset"; - - /** - * The simulate string. - */ - private static final String SIMULATE = "Simulate"; - - /** - * The widget frame title. - */ - private static final String TITLE = "Conway's Game of Life"; - - /** - * The load string. - */ - private static final String LOAD = "Load"; - - /** - * The save string. - */ - private static final String SAVE = "Save"; - - /** - * The clear string. - */ - private static final String CLEAR = "Clear"; - - /** - * The conway string. - */ - private static final String CONWAY = "conway"; - - /** - * The name of the thread which loads conway states. - */ - private static final String CONWAY_STATE_LOADER_THREAD_NAME = "Conway State Loader"; - - /** - * The stop text. - */ - private static final String STOP = "Stop"; - - /** - * The length of the grid. - */ - private static final int gridLength = 550; - - /** - * The initial node length on the grid. - */ - private static final int gridNodes = 50; - - /** - * The size of the primary widget controls. - */ - private static final Dimension primaryControlSize = new Dimension(160, 40); - - /** - * The size of the labels above the grid. - */ - private static final Dimension topLabelSize = new Dimension(240, 30); - - /** - * The size of the Cyder switches. - */ - private static final Dimension switchSize = new Dimension(200, 55); - - /** - * The delay in ms between preset combo box actions. - */ - private static final int presetComboBoxDelay = 800; - - /** - * The last time a preset combo action was invoked. - */ - private static long lastPresetComboBoxAction = 0; - - /** - * The left and right padding for the slider. - */ - private static final int sliderPadding = 25; - - /** - * The border length for the grid. - */ - private static final int gridBorderLength = 3; - - /** - * The length of the grid parent. - */ - private static final int gridParentLength = gridLength + 2 * gridBorderLength; - - @SuppressCyderInspections(CyderInspection.WidgetInspection) - @Widget(triggers = {CONWAY, "conways", "game of life", "conways game of life"}, - description = "Conway's game of life visualizer") - public static void showGui() { - UiUtil.closeIfOpen(conwayFrame); - - loadConwayStates(); - - conwayFrame = new CyderFrame.Builder() - .setWidth(FRAME_WIDTH) - .setHeight(FRAME_HEIGHT) - .setTitle(TITLE) - .build(); - - CyderPartitionedLayout partitionedLayout = new CyderPartitionedLayout(); - - CyderGridLayout topLabelsGrid = new CyderGridLayout(2, 2); - - currentPopulationLabel = new CyderLabel(); - currentPopulationLabel.setSize(topLabelSize); - topLabelsGrid.addComponent(currentPopulationLabel); - - maxPopulationLabel = new CyderLabel(); - maxPopulationLabel.setSize(topLabelSize); - topLabelsGrid.addComponent(maxPopulationLabel); - - currentGenerationLabel = new CyderLabel(); - currentGenerationLabel.setSize(topLabelSize); - topLabelsGrid.addComponent(currentGenerationLabel); - - correspondingGenerationLabel = new CyderLabel(); - correspondingGenerationLabel.setSize(topLabelSize); - topLabelsGrid.addComponent(correspondingGenerationLabel); - - partitionedLayout.spacer(1); - - CyderPanel topLabelsPanel = new CyderPanel(topLabelsGrid); - topLabelsPanel.setSize((int) (FRAME_WIDTH / 1.5), 50); - partitionedLayout.addComponentMaintainSize(topLabelsPanel, CyderPartitionedLayout.PartitionAlignment.TOP); - - partitionedLayout.spacer(0.5f); - - conwayGrid = new CyderGrid(gridNodes, gridLength); - conwayGrid.setSize(gridLength, gridLength); - conwayGrid.setMinNodes(MIN_NODES); - conwayGrid.setMaxNodes(150); - conwayGrid.setDrawGridLines(false); - conwayGrid.setBackground(CyderColors.vanilla); - conwayGrid.setResizable(true); - conwayGrid.setSmoothScrolling(true); - conwayGrid.installClickAndDragPlacer(); - conwayGrid.setSaveStates(false); - conwayGrid.setLocation(gridBorderLength, gridBorderLength); - - JLabel conwayGridParent = new JLabel(); - conwayGridParent.setBorder(new LineBorder(CyderColors.navy, gridBorderLength)); - conwayGridParent.setSize(gridParentLength, gridParentLength); - conwayGridParent.add(conwayGrid); - partitionedLayout.addComponentMaintainSize(conwayGridParent, CyderPartitionedLayout.PartitionAlignment.TOP); - - partitionedLayout.spacer(1); - - CyderGridLayout primaryControlGrid = new CyderGridLayout(2, 3); - - stopSimulationButton = new CyderButton(SIMULATE); - stopSimulationButton.setSize(primaryControlSize); - stopSimulationButton.addActionListener(e -> stopSimulationButtonAction()); - primaryControlGrid.addComponent(stopSimulationButton); - - CyderButton resetButton = new CyderButton(RESET); - resetButton.setSize(primaryControlSize); - resetButton.addActionListener(e -> resetToPreviousState()); - primaryControlGrid.addComponent(resetButton); - - CyderButton loadButton = new CyderButton(LOAD); - loadButton.setSize(primaryControlSize); - loadButton.addActionListener(e -> loadButtonAction()); - primaryControlGrid.addComponent(loadButton); - - presetComboBox = new CyderComboBox(primaryControlSize.width, primaryControlSize.height, - comboItems, comboItems.get(0)); - presetComboBox.getIterationButton().addActionListener(e -> presetComboBoxAction()); - presetComboBox.setSize(primaryControlSize); - primaryControlGrid.addComponent(presetComboBox); - - CyderButton clearButton = new CyderButton(CLEAR); - clearButton.setSize(primaryControlSize); - clearButton.addActionListener(e -> resetSimulation()); - primaryControlGrid.addComponent(clearButton); - - CyderButton saveButton = new CyderButton(SAVE); - saveButton.setSize(primaryControlSize); - saveButton.addActionListener(e -> toFile()); - primaryControlGrid.addComponent(saveButton); - - CyderPanel primaryControlPanel = new CyderPanel(primaryControlGrid); - primaryControlPanel.setSize(450, 140); - partitionedLayout.addComponentMaintainSize(primaryControlPanel); - - CyderGridLayout switchGrid = new CyderGridLayout(2, 1); - - detectOscillationsSwitch = new CyderSwitch(switchSize, CyderSwitchState.ON); - detectOscillationsSwitch.setSize(switchSize); - detectOscillationsSwitch.setState(CyderSwitchState.ON); - detectOscillationsSwitch.setButtonPercent(50); - detectOscillationsSwitch.setOnText("Oscillations"); - detectOscillationsSwitch.setOffText("Ignore"); - switchGrid.addComponent(detectOscillationsSwitch); - - CyderSwitch drawGridLinesSwitch = new CyderSwitch(switchSize, CyderSwitchState.OFF); - drawGridLinesSwitch.setSize(switchSize); - drawGridLinesSwitch.getSwitchButton().addActionListener(e -> { - CyderSwitchState nextState = drawGridLinesSwitch.getNextState(); - conwayGrid.setDrawGridLines(nextState.equals(CyderSwitchState.ON)); - conwayGrid.repaint(); - }); - drawGridLinesSwitch.setOffText("No Grid"); - drawGridLinesSwitch.setOnText("Grid"); - drawGridLinesSwitch.setButtonPercent(50); - switchGrid.addComponent(drawGridLinesSwitch); - - CyderPanel switchGridPanel = new CyderPanel(switchGrid); - switchGridPanel.setSize(450, 60); - partitionedLayout.addComponentMaintainSize(switchGridPanel); - - iterationsPerSecondSlider = new JSlider(JSlider.HORIZONTAL, MIN_ITERATIONS_PER_SECOND, - MAX_ITERATIONS_PER_SECOND, DEFAULT_ITERATIONS_PER_SECOND); - CyderSliderUi sliderUi = new CyderSliderUi(iterationsPerSecondSlider); - sliderUi.setThumbStroke(new BasicStroke(2.0f)); - sliderUi.setThumbRadius(25); - sliderUi.setThumbShape(ThumbShape.CIRCLE); - sliderUi.setThumbFillColor(Color.black); - sliderUi.setThumbOutlineColor(CyderColors.navy); - sliderUi.setRightThumbColor(CyderColors.regularBlue); - sliderUi.setLeftThumbColor(CyderColors.regularPink); - sliderUi.setTrackStroke(new BasicStroke(3.0f)); - iterationsPerSecondSlider.setUI(sliderUi); - iterationsPerSecondSlider.setSize(350, 40); - iterationsPerSecondSlider.setPaintTicks(false); - iterationsPerSecondSlider.setPaintLabels(false); - iterationsPerSecondSlider.setVisible(true); - iterationsPerSecondSlider.addChangeListener(e -> iterationsSliderChangeAction()); - iterationsPerSecondSlider.setOpaque(false); - iterationsPerSecondSlider.setToolTipText("Iterations per second"); - iterationsPerSecondSlider.setFocusable(false); - iterationsPerSecondSlider.repaint(); - iterationsPerSecondSlider.setSize(FRAME_WIDTH - 2 * sliderPadding, 40); - - partitionedLayout.spacer(0.5f); - partitionedLayout.addComponentMaintainSize(iterationsPerSecondSlider); - partitionedLayout.spacer(0.5f); - - resetSimulation(); - conwayFrame.setCyderLayout(partitionedLayout); - conwayFrame.finalizeAndShow(); - } - - /** - * The actions to invoke when a change of the iterations slider is encountered. - */ - @ForReadability - private static void iterationsSliderChangeAction() { - iterationsPerSecond = iterationsPerSecondSlider.getValue(); - } - - /** - * The actions to invoke when the present combo box button is clicked. - */ - @ForReadability - private static void presetComboBoxAction() { - long now = System.currentTimeMillis(); - if (lastPresetComboBoxAction + presetComboBoxDelay > now) return; - lastPresetComboBoxAction = now; - - CyderComboBoxState nextState = presetComboBox.getNextState(); - - for (int i = 0 ; i < comboItems.size() ; i++) { - if (comboItems.get(i).equals(nextState)) { - beforeStartingState = new ArrayList<>(); - - correspondingConwayStates.get(i).getNodes().forEach(point -> - beforeStartingState.add(new GridNode((int) point.getX(), (int) point.getY()))); - - conwayFrame.revokeAllNotifications(); - conwayFrame.notify("Loaded state: " + correspondingConwayStates.get(i).getName()); - conwayGrid.setNodeDimensionLength(correspondingConwayStates.get(i).getGridSize()); - - break; - } - } - - resetToPreviousState(); - } - - /** - * The actions to invoke when the load button is pressed. - */ - @ForReadability - private static void loadButtonAction() { - CyderThreadRunner.submit(() -> { - Optional optionalFile = GetterUtil.getInstance().getFile(new GetFileBuilder("Load state") - .setRelativeTo(conwayFrame)); - if (optionalFile.isEmpty()) return; - File file = optionalFile.get(); - - if (file.exists() && FileUtil.validateExtension(file, Extension.JSON.getExtension())) { - fromJson(file); - } - }, CONWAY_STATE_LOADER_THREAD_NAME); - } - - /** - * The actions to invoke when the stop simulation button is clicked. - */ - @ForReadability - private static void stopSimulationButtonAction() { - if (simulationRunning) { - stopSimulation(); - } else if (conwayGrid.getNodeCount() > 0) { - simulationRunning = true; - stopSimulationButton.setText(STOP); - conwayGrid.uninstallClickAndDragPlacer(); - conwayGrid.setResizable(false); - start(); - } else { - conwayFrame.notify("Place at least one node"); - } - } - - /** - * Resets the simulation and all values back to their default. - */ - private static void resetSimulation() { - stopSimulation(); - - iterationsPerSecond = DEFAULT_ITERATIONS_PER_SECOND; - - conwayGrid.setNodeDimensionLength(50); - conwayGrid.clearGrid(); - conwayGrid.repaint(); - - detectOscillationsSwitch.setState(CyderSwitchState.ON); - iterationsPerSecondSlider.setValue(DEFAULT_ITERATIONS_PER_SECOND); - iterationsPerSecond = DEFAULT_ITERATIONS_PER_SECOND; - - beforeStartingState = null; - - resetStats(); - } - - /** - * Resets the population/generation statistics and labels. - */ - private static void resetStats() { - generation = 0; - population = 0; - maxPopulation = 0; - correspondingGeneration = 0; - firstCorrespondingGeneration = 0; - - updateLabels(); - } - - /** - * Updates the statistic labels based on the currently set values. - */ - public static void updateLabels() { - currentGenerationLabel.setText("Generation: " + generation); - currentPopulationLabel.setText("Population: " + population); - maxPopulationLabel.setText("Max Population: " + maxPopulation); - - if (firstCorrespondingGeneration == 0 || firstCorrespondingGeneration == generation) { - correspondingGenerationLabel.setText("Corr Gen: " + correspondingGeneration); - } else { - correspondingGenerationLabel.setText("Corr Gen: " + correspondingGeneration - + ", first: " + firstCorrespondingGeneration); - } - } - - /** - * Sets the grid to the state it was in before beginning the simulation. - */ - private static void resetToPreviousState() { - if (beforeStartingState == null) return; - - stopSimulation(); - - conwayGrid.setGridState(beforeStartingState); - conwayGrid.repaint(); - - resetStats(); - population = beforeStartingState.size(); - updateLabels(); - } - - /** - * Performs any stopping actions needed to properly stop the simulation. - */ - private static void stopSimulation() { - simulationRunning = false; - stopSimulationButton.setText(SIMULATE); - conwayGrid.installClickAndDragPlacer(); - conwayGrid.setResizable(true); - } - - /** - * The name of the conway simulation thread. - */ - private static final String CONWAY_SIMULATOR_THREAD_NAME = "Conway Simulator"; - - /** - * Starts the simulation. - */ - private static void start() { - beforeStartingState = new ArrayList<>(conwayGrid.getGridNodes()); - - CyderThreadRunner.submit(() -> { - while (simulationRunning) { - try { - ArrayList nextState = new ArrayList<>(); - - int[][] nextGen = nextGeneration(cyderGridToConwayGrid(conwayGrid.getGridNodes())); - for (int i = 0 ; i < nextGen.length ; i++) { - for (int j = 0 ; j < nextGen[0].length ; j++) { - if (nextGen[i][j] == 1) { - nextState.add(new GridNode(i, j)); - } - } - } - - if (nextState.equals(conwayGrid.getGridNodes())) { - conwayFrame.revokeAllNotifications(); - conwayFrame.notify("Simulation stabilized at generation: " + generation); - stopSimulation(); - return; - } else if (detectOscillationsSwitch.getState().equals(CyderSwitchState.ON) - && nextState.equals(lastState)) { - conwayFrame.revokeAllNotifications(); - conwayFrame.notify("Detected oscillation at generation: " + generation); - stopSimulation(); - return; - } else if (nextState.isEmpty()) { - conwayFrame.revokeAllNotifications(); - conwayFrame.notify("Simulation ended with total elimination at generation: " - + generation); - stopSimulation(); - return; - } - - // advance last state - lastState = new ArrayList<>(conwayGrid.getGridNodes()); - - // set new state - conwayGrid.setGridNodes(nextState); - conwayGrid.repaint(); - - generation++; - population = nextState.size(); - - if (population > maxPopulation) { - firstCorrespondingGeneration = generation; - - maxPopulation = population; - correspondingGeneration = generation; - } else if (population == maxPopulation) { - correspondingGeneration = generation; - } - - updateLabels(); - ThreadUtil.sleep((long) (TimeUtil.millisInSecond / iterationsPerSecond)); - } catch (Exception e) { - ExceptionHandler.handle(e); - } - } - }, CONWAY_SIMULATOR_THREAD_NAME); - } - - /** - * Loads the conway state from the provided json file and sets the current grid state to it. - * - * @param jsonFile the json file to load the state from - */ - private static void fromJson(File jsonFile) { - Preconditions.checkNotNull(jsonFile); - Preconditions.checkArgument(jsonFile.exists()); - - try { - Reader reader = new FileReader(jsonFile); - ConwayState loadState = SerializationUtil.fromJson(reader, ConwayState.class); - reader.close(); - - resetSimulation(); - - conwayGrid.setNodeDimensionLength(loadState.getGridSize()); - loadState.getNodes().forEach(point -> conwayGrid.addNode( - new GridNode((int) point.getX(), (int) point.getY()))); - - conwayFrame.notify("Loaded state: " + loadState.getName()); - beforeStartingState = new ArrayList<>(conwayGrid.getGridNodes()); - - resetStats(); - population = loadState.getNodes().size(); - updateLabels(); - } catch (Exception e) { - ExceptionHandler.handle(e); - conwayFrame.notify("Could not parse json as a valid ConwayState object"); - } - } - - /** - * The name of the thread to save a conway grid state. - */ - private static final String CONWAY_STATE_SAVER_THREAD_NAME = "Conway State Saver"; - - /** - * Saves the current grid state to a json which can be loaded. - */ - private static void toFile() { - CyderThreadRunner.submit(() -> { - if (conwayGrid.getNodeCount() == 0) { - conwayFrame.notify("Place at least one node"); - return; - } - - Optional optionalSaveName = GetterUtil.getInstance().getInput( - new GetInputBuilder("Save name", "Save Conway state file name") - .setRelativeTo(conwayFrame) - .setFieldHintText("Valid filename") - .setSubmitButtonText("Save Conway State")); - if (optionalSaveName.isEmpty()) return; - String saveName = optionalSaveName.get(); - - String filename = saveName + Extension.JSON.getExtension(); - - if (FileUtil.isValidFilename(filename)) { - File saveFile = UserUtil.createFileInUserSpace(filename); - - ArrayList points = new ArrayList<>(); - - conwayGrid.getGridNodes().forEach(node -> points.add(new Point(node.getX(), node.getY()))); - ConwayState state = new ConwayState(saveName, conwayGrid.getNodeDimensionLength(), points); - - try { - FileWriter writer = new FileWriter(saveFile); - SerializationUtil.toJson(state, writer); - writer.close(); - } catch (Exception e) { - ExceptionHandler.handle(e); - conwayFrame.notify("Save state failed"); - } - } else { - conwayFrame.notify("Invalid save name"); - } - }, CONWAY_STATE_SAVER_THREAD_NAME); - } - - /** - * Converts the CyderGrid nodes to a 2D integer array - * needed to compute the next Conway iteration. - * - * @param nodes the list of cyder grid nodes - * @return the 2D array consisting of 1s and 0s - */ - private static int[][] cyderGridToConwayGrid(Collection nodes) { - int len = conwayGrid.getNodeDimensionLength(); - - int[][] ret = new int[len][len]; - nodes.forEach(node -> { - int x = node.getX(); - int y = node.getY(); - - if (x < len && y < len) { - ret[x][y] = 1; - } - }); - return ret; - } - - /** - * Computes the next generation based on the current generation. - * - * @param currentGeneration the current generation - * @return the next generation - */ - private static int[][] nextGeneration(int[][] currentGeneration) { - Preconditions.checkNotNull(currentGeneration); - Preconditions.checkArgument(currentGeneration.length >= MIN_NODES); - Preconditions.checkArgument(currentGeneration[0].length >= MIN_NODES); - - int[][] ret = new int[currentGeneration.length][currentGeneration[0].length]; - - for (int l = 1 ; l < currentGeneration.length - 1 ; l++) { - for (int m = 1 ; m < currentGeneration[0].length - 1 ; m++) { - int aliveNeighbours = 0; - for (int i = -1 ; i <= 1 ; i++) { - for (int j = -1 ; j <= 1 ; j++) { - aliveNeighbours += currentGeneration[l + i][m + j]; - } - } - - aliveNeighbours -= currentGeneration[l][m]; - - if ((currentGeneration[l][m] == 1) && (aliveNeighbours < 2)) { - ret[l][m] = 0; - } else if ((currentGeneration[l][m] == 1) && (aliveNeighbours > 3)) { - ret[l][m] = 0; - } else if ((currentGeneration[l][m] == 0) && (aliveNeighbours == 3)) { - ret[l][m] = 1; - } else { - ret[l][m] = currentGeneration[l][m]; - } - } - } - - return ret; - } - - /** - * Loads the preset conway states from static JSON conway. - */ - private static void loadConwayStates() { - comboItems = new ArrayList<>(); - correspondingConwayStates = new ArrayList<>(); - - File statesDir = StaticUtil.getStaticDirectory(CONWAY); - - if (statesDir.exists()) { - File[] statesDirFiles = statesDir.listFiles(); - - if (ArrayUtil.nullOrEmpty(statesDirFiles)) { - presetComboBox.getIterationButton().setEnabled(false); - return; - } - - Arrays.stream(statesDirFiles).filter(jsonStateFile -> - FileUtil.validateExtension(jsonStateFile, Extension.JSON.getExtension())) - .forEach(jsonStateFile -> { - try { - Reader reader = new FileReader(jsonStateFile); - ConwayState loadState = SerializationUtil.fromJson(reader, ConwayState.class); - reader.close(); - - correspondingConwayStates.add(loadState); - comboItems.add(new CyderComboBoxState(loadState.getName())); - } catch (Exception e) { - Logger.log(LogTag.SYSTEM_IO, "Failed to load conway state: " + jsonStateFile); - ExceptionHandler.handle(e); - } - }); - } - } - - /** - * An object used to store a Conway's game of life grid state. - */ - @SuppressWarnings("ClassCanBeRecord") /* GSON */ - @Immutable - private static class ConwayState { - /** - * The name of the conway state. - */ - private final String name; - - /** - * The grid length for the saved state. - */ - private final int gridSize; - - /** - * The list of nodes for the saves state. - */ - private final ArrayList nodes; - - /** - * Constructs a new conway state. - * - * @param name the name of the state - * @param gridSize the size of the nxn grid - * @param nodes the nodes to place for the state - */ - public ConwayState(String name, int gridSize, ArrayList nodes) { - this.name = Preconditions.checkNotNull(name); - this.gridSize = gridSize; - this.nodes = Preconditions.checkNotNull(nodes); - } - - /** - * Returns the name of this conway state. - * - * @return the name of this conway state - */ - public String getName() { - return name; - } - - /** - * Returns the grid size of this conway state. - * - * @return the grid size of this conway state - */ - public int getGridSize() { - return gridSize; - } - - /** - * Returns the list of points for this conway state. - * - * @return the list of points for this conway state - */ - public ArrayList getNodes() { - return nodes; - } - } -} diff --git a/src/main/java/cyder/widgets/HashingWidget.java b/src/main/java/cyder/widgets/HashingWidget.java deleted file mode 100644 index 1ba4cd070..000000000 --- a/src/main/java/cyder/widgets/HashingWidget.java +++ /dev/null @@ -1,201 +0,0 @@ -package cyder.widgets; - -import com.google.common.collect.ImmutableList; -import cyder.annotations.CyderAuthor; -import cyder.annotations.ForReadability; -import cyder.annotations.Vanilla; -import cyder.annotations.Widget; -import cyder.constants.CyderFonts; -import cyder.constants.HtmlTags; -import cyder.handlers.internal.InformHandler; -import cyder.logging.LogTag; -import cyder.logging.Logger; -import cyder.strings.CyderStrings; -import cyder.ui.button.CyderButton; -import cyder.ui.field.CyderPasswordField; -import cyder.ui.frame.CyderFrame; -import cyder.ui.label.CyderLabel; -import cyder.ui.selection.CyderCheckbox; -import cyder.ui.selection.CyderComboBox; -import cyder.ui.selection.CyderComboBoxState; -import cyder.utils.OsUtil; -import cyder.utils.SecurityUtil; - -/** - * A widget for computing the hash of strings. - */ -@Vanilla -@CyderAuthor -public class HashingWidget { - /** - * The hash field. - */ - private CyderPasswordField hashField; - - /** - * The hash algorithm combo box. - */ - private CyderComboBox comboBox; - - /** - * The widget description. - */ - private static final String description = "A hashing widget to hash any string using" - + " multiple algorithms such as MD5, SHA256, and SHA1"; - - /** - * Constructs and returns a new hashing widget. - * - * @return a new hashing widget - */ - public static HashingWidget getInstance() { - return new HashingWidget(); - } - - /** - * Constructs a new hashing widget. - */ - private HashingWidget() { - Logger.log(LogTag.OBJECT_CREATION, this); - } - - /** - * Shows a new instance of the hashing widget. - */ - @Widget(triggers = {HASH, "hasher"}, description = description) - public static void showGui() { - getInstance().innerShowGui(); - } - - /** - * The hash string. - */ - private static final String HASH = "Hash"; - - /** - * The id of the sha256 algorithm. - */ - private static final String SHA_256 = "SHA-256"; - - /** - * The id of the sha1 algorithm. - */ - private static final String SHA_1 = "SHA-1"; - - /** - * The id of the md5 algorithm. - */ - private static final String MD5 = "MD5"; - - /** - * The valid hashing algorithms for the combo box chooser. - */ - private static final ImmutableList HASH_ALGORITHMS = ImmutableList.of( - new CyderComboBoxState(SHA_256, "SHA256 Algorithm"), - new CyderComboBoxState(SHA_1, "SHA-1 Algorithm"), - new CyderComboBoxState(MD5, "MD5 Algorithm (Do not use for passwords)") - ); - - /** - * The text of the hash result popup. - */ - private static final String HASH_RESULT = HASH + CyderStrings.space + "Result"; - - /** - * The title of the frame. - */ - private static final String TITLE = "Hasher"; - - /** - * The width of the frame. - */ - private static final int FRAME_WIDTH = 500; - - /** - * The height of the frame. - */ - private static final int FRAME_HEIGHT = 260; - - /** - * The checkbox for whether to save the hash result to the clipboard. - */ - private CyderCheckbox saveToClipboardCheckbox; - - /** - * Shows the gui for this instance of the hashing widget. - */ - public void innerShowGui() { - CyderFrame hashFrame = new CyderFrame.Builder() - .setWidth(FRAME_WIDTH) - .setHeight(FRAME_HEIGHT) - .setTitle(TITLE) - .build(); - - CyderLabel Instructions = new CyderLabel("Enter input to be hashed"); - Instructions.setFont(CyderFonts.SEGOE_20); - Instructions.setBounds(65, 40, 400, 30); - hashFrame.getContentPane().add(Instructions); - - hashField = new CyderPasswordField(); - hashField.setToolTipText("Hold shift to reveal contents"); - hashField.addActionListener(e -> hashButtonAction()); - hashField.setBounds(50, 90, 400, 40); - hashFrame.getContentPane().add(hashField); - - CyderButton hashButton = new CyderButton(HASH); - hashButton.addActionListener(e -> hashButtonAction()); - hashButton.setBounds(50, 140, 180, 40); - hashFrame.getContentPane().add(hashButton); - - saveToClipboardCheckbox = new CyderCheckbox(); - saveToClipboardCheckbox.setChecked(); - saveToClipboardCheckbox.setLocation(hashFrame.getWidth() / 2 + 70, 190); - hashFrame.getContentPane().add(saveToClipboardCheckbox); - - CyderLabel saveToClipBoardLabel = new CyderLabel("Copy hash to clipboard:"); - saveToClipBoardLabel.setBounds(120, 200, 200, 30); - hashFrame.getContentPane().add(saveToClipBoardLabel); - - comboBox = new CyderComboBox(210, 40, HASH_ALGORITHMS, HASH_ALGORITHMS.get(0)); - comboBox.setBounds(240, 140, 210, 40); - hashFrame.getContentPane().add(comboBox); - - hashFrame.finalizeAndShow(); - } - - /** - * The action to invoke when the hash button is pressed. - */ - private void hashButtonAction() { - char[] hashFieldContents = hashField.getPassword(); - if (hashFieldContents.length == 0) return; - - String algorithm = comboBox.getCurrentState().getDisplayValue(); - String hashResult = switch (algorithm) { - case SHA_256 -> SecurityUtil.toHexString(SecurityUtil.getSha256(hashFieldContents)); - case SHA_1 -> SecurityUtil.toHexString(SecurityUtil.getSha1(hashFieldContents)); - case MD5 -> SecurityUtil.toHexString(SecurityUtil.getMd5(hashFieldContents)); - default -> throw new IllegalStateException("Unimplemented hash algorithm: " + algorithm); - }; - - String informText = "Your hashed input is:" + HtmlTags.breakTag + hashResult - + HtmlTags.breakTag + "Provided by " + algorithm; - if (saveToClipboardCheckbox.isChecked()) { - OsUtil.setClipboard(hashResult); - } - - new InformHandler.Builder(informText) - .setTitle(algorithm + CyderStrings.space + HASH_RESULT) - .setRelativeTo(hashField).inform(); - - reset(); - } - - /** - * Resets the widget. - */ - @ForReadability - private void reset() { - hashField.setText(""); - } -} diff --git a/src/main/java/cyder/widgets/ImageAveragerWidget.java b/src/main/java/cyder/widgets/ImageAveragerWidget.java deleted file mode 100644 index 781212be9..000000000 --- a/src/main/java/cyder/widgets/ImageAveragerWidget.java +++ /dev/null @@ -1,498 +0,0 @@ -package cyder.widgets; - -import com.google.common.base.Preconditions; -import com.google.common.collect.ImmutableList; -import cyder.annotations.*; -import cyder.console.Console; -import cyder.constants.CyderColors; -import cyder.enumerations.CyderInspection; -import cyder.enumerations.Dynamic; -import cyder.enumerations.Extension; -import cyder.exceptions.IllegalMethodException; -import cyder.files.FileUtil; -import cyder.getter.GetFileBuilder; -import cyder.getter.GetterUtil; -import cyder.handlers.internal.ExceptionHandler; -import cyder.strings.CyderStrings; -import cyder.strings.StringUtil; -import cyder.threads.CyderThreadRunner; -import cyder.ui.UiUtil; -import cyder.ui.button.CyderButton; -import cyder.ui.drag.button.DragLabelTextButton; -import cyder.ui.frame.CyderFrame; -import cyder.ui.list.CyderScrollList; -import cyder.user.UserDataManager; -import cyder.user.UserFile; -import cyder.utils.ImageUtil; - -import javax.imageio.ImageIO; -import javax.swing.*; -import javax.swing.border.LineBorder; -import java.awt.*; -import java.awt.image.BufferedImage; -import java.awt.image.DataBufferByte; -import java.io.File; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Optional; -import java.util.concurrent.atomic.AtomicInteger; - -import static java.awt.image.BufferedImage.TYPE_INT_ARGB; - -/** - * A widget to average images together. - */ -@Vanilla -@CyderAuthor -public final class ImageAveragerWidget { - /** - * The scroll label for the selected images. - */ - private static JLabel imagesScrollLabel; - - /** - * The actual scroll container to hold the scroll label. - */ - private static CyderScrollList imagesScroll; - - /** - * The averaging frame. - */ - private static CyderFrame averagerFrame; - - /** - * The component to hold the scroll on top of it. - */ - private static JLabel imageScrollLabelHolder; - - /** - * Suppress default constructor. - */ - private ImageAveragerWidget() { - throw new IllegalMethodException(CyderStrings.ATTEMPTED_INSTANTIATION); - } - - /** - * The widget description. - */ - private static final String description = "A widget that adds multiple images " - + "together and divides by the total to obtain an average base image"; - - /** - * The title of the widget frame. - */ - private static final String FRAME_TITLE = "Image Averager"; - - /** - * The save text. - */ - private static final String SAVE = "Save"; - - /** - * The save image text. - */ - private static final String SAVE_IMAGE = "Save Image"; - - /** - * The width of the widget frame. - */ - private static final int FRAME_WIDTH = 600; - - /** - * The height of the widget frame. - */ - private static final int FRAME_HEIGHT = 640; - - /** - * The length of the images scroll list. - */ - private static final int imagesScrollLen = 400; - - /** - * The builder for the getter util instance to add a file. - */ - private static final GetFileBuilder builder = new GetFileBuilder("Select any image file") - .setRelativeTo(averagerFrame); - - /** - * The maximum image length that the preview frame can display. - */ - private static final int maxImageLength = 800; - - /** - * The combined files name separator. - */ - private static final String UNDERSCORE = "_"; - - /** - * The add image button text. - */ - private static final String ADD_IMAGE = "Add Image"; - - /** - * The remove images button text. - */ - private static final String REMOVE_IMAGES = "Remove Selected Images"; - - /** - * The average images button text. - */ - private static final String AVERAGE_IMAGES = "Average Images"; - - /** - * The thread name for the waiter thread for the add file getter. - */ - private static final String IMAGE_AVERAGER_ADD_FILE_WAITER_THREAD_NAME = "Image Averager Add File Waiter"; - - /** - * The alpha value for pixels with a non-present alpha value. - */ - private static final int EMPTY_ALPHA = 16777216; - - /** - * The length of data for data elements containing an alpha byte. - */ - private static final int alphaPixelLength = 4; - - /** - * The length of data for data elements without an alpha byte. - */ - private static final int noAlphaPixelLength = 3; - - /** - * Shows the image averaging widget. - */ - @SuppressCyderInspections(CyderInspection.WidgetInspection) - @Widget(triggers = {"average images", "average pictures"}, description = description) - public static void showGui() { - UiUtil.closeIfOpen(averagerFrame); - - averagerFrame = new CyderFrame.Builder() - .setWidth(FRAME_WIDTH) - .setHeight(FRAME_HEIGHT) - .setTitle(FRAME_TITLE) - .build(); - - imagesScroll = new CyderScrollList(imagesScrollLen, imagesScrollLen, CyderScrollList.SelectionPolicy.MULTIPLE); - imagesScroll.setBorder(null); - - imageScrollLabelHolder = new JLabel(); - imageScrollLabelHolder.setBounds(90, 40, 420, 420); - imageScrollLabelHolder.setBorder(new LineBorder(CyderColors.navy, 5)); - averagerFrame.getContentPane().add(imageScrollLabelHolder); - - imagesScrollLabel = imagesScroll.generateScrollList(); - imagesScrollLabel.setBounds(10, 10, imagesScrollLen, imagesScrollLen); - imageScrollLabelHolder.add(imagesScrollLabel); - - imageScrollLabelHolder.setBackground(Color.white); - imagesScrollLabel.setOpaque(true); - imageScrollLabelHolder.setOpaque(true); - imagesScrollLabel.setBackground(Color.white); - - CyderButton addFileButton = new CyderButton(ADD_IMAGE); - addFileButton.setBounds(90, 480, 420, 40); - averagerFrame.getContentPane().add(addFileButton); - addFileButton.addActionListener(e -> addButtonAction()); - - CyderButton removeSelectedImagesButton = new CyderButton(REMOVE_IMAGES); - removeSelectedImagesButton.setBounds(90, 530, 420, 40); - averagerFrame.getContentPane().add(removeSelectedImagesButton); - removeSelectedImagesButton.addActionListener(e -> removeSelectedImagesButtonAction()); - - CyderButton average = new CyderButton(AVERAGE_IMAGES); - average.setBackground(CyderColors.regularPink); - average.setBounds(90, 580, 420, 40); - averagerFrame.getContentPane().add(average); - average.addActionListener(e -> averageButtonAction()); - - averagerFrame.finalizeAndShow(); - } - - /** - * The action to invoke when the remove selected images button is pressed. - */ - @ForReadability - private static void removeSelectedImagesButtonAction() { - ImmutableList selectedElements = imagesScroll.getSelectedElements(); - for (String selectedElement : selectedElements) { - currentFiles.remove(selectedElement); - } - - imagesScroll.removeSelectedElements(); - revalidateImagesScroll(); - } - - private static final HashMap currentFiles = new HashMap<>(); - - /** - * Revalidates the chosen images scroll view. - */ - private static void revalidateImagesScroll() { - imagesScroll.removeAllElements(); - imageScrollLabelHolder.remove(imagesScrollLabel); - - currentFiles.forEach((filename, file) -> { - Runnable openFileRunnable = () -> FileUtil.openResource(file.getAbsolutePath(), true); - imagesScroll.addElementWithDoubleClickAction(filename, openFileRunnable); - }); - - imagesScrollLabel = imagesScroll.generateScrollList(); - imagesScrollLabel.setBounds(10, 10, imagesScrollLen, imagesScrollLen); - imageScrollLabelHolder.setBackground(CyderColors.vanilla); - - imageScrollLabelHolder.add(imagesScrollLabel); - - imageScrollLabelHolder.revalidate(); - imageScrollLabelHolder.repaint(); - - averagerFrame.revalidate(); - averagerFrame.repaint(); - } - - /** - * The action to invoke when the add file button is pressed. - */ - private static void addButtonAction() { - CyderThreadRunner.submit(() -> { - try { - Optional optionalFile = GetterUtil.getInstance().getFile(builder); - if (optionalFile.isEmpty()) return; - File addFile = optionalFile.get(); - if (StringUtil.isNullOrEmpty(FileUtil.getFilename(addFile))) return; - - boolean supportedImage = FileUtil.isSupportedImageExtension(addFile); - if (!supportedImage) { - averagerFrame.notify("Selected file is not a supported image file"); - return; - } - - currentFiles.put(addFile.getName(), addFile); - revalidateImagesScroll(); - } catch (Exception ex) { - ExceptionHandler.handle(ex); - } - }, IMAGE_AVERAGER_ADD_FILE_WAITER_THREAD_NAME); - } - - /** - * Action performed when the user clicks the compute button. - */ - private static void averageButtonAction() { - if (currentFiles.size() < 2) { - averagerFrame.notify("Please add at least two images"); - return; - } - - AtomicInteger width = new AtomicInteger(); - AtomicInteger height = new AtomicInteger(); - - currentFiles.forEach((filename, file) -> { - try { - BufferedImage currentImage = ImageUtil.read(file); - width.set(Math.max(currentImage.getWidth(), width.get())); - height.set(Math.max(currentImage.getHeight(), height.get())); - } catch (Exception e) { - averagerFrame.notify("Failed to read image file: " + file.getAbsolutePath()); - } - }); - - BufferedImage saveImage = computerAverage(width.get(), height.get()); - ImageIcon previewImage = ImageUtil.resizeIfLengthExceeded(new ImageIcon(saveImage), maxImageLength); - - String saveImageName = combineImageNames() + Extension.PNG.getExtension(); - File outputFile = Dynamic.buildDynamic(Dynamic.USERS.getFileName(), - Console.INSTANCE.getUuid(), UserFile.BACKGROUNDS.getName(), saveImageName); - - CyderFrame drawFrame = new CyderFrame.Builder() - .setWidth(previewImage.getIconWidth()) - .setHeight(previewImage.getIconHeight()) - .setBackgroundIcon(previewImage) - .build(); - DragLabelTextButton saveButton = new DragLabelTextButton.Builder(SAVE) - .setTooltip(SAVE_IMAGE) - .setClickAction(() -> { - boolean saved = saveImage(saveImage, outputFile); - if (!saved) { - averagerFrame.notify("Could not save average at this time"); - return; - } - - averagerFrame.notify("Average computed and saved to " - + StringUtil.getApostropheSuffix(UserDataManager.INSTANCE.getUsername()) - + "backgrounds/ directory"); - drawFrame.dispose(true); - }).build(); - drawFrame.getTopDragLabel().addRightButton(saveButton, 0); - - drawFrame.setVisible(true); - drawFrame.setLocationRelativeTo(averagerFrame); - } - - /** - * Saves the provided buffered image to the current user's backgrounds folder using the provided name. - * Png format is used as the image format. - * - * @param saveImage the buffered image to save - * @param outputFile the file to save the buffered image to - * @return whether the saving operation was successful - */ - @ForReadability - private static boolean saveImage(BufferedImage saveImage, File outputFile) { - Preconditions.checkNotNull(saveImage); - Preconditions.checkNotNull(outputFile); - - try { - ImageIO.write(saveImage, Extension.PNG.getExtensionWithoutPeriod(), outputFile); - return true; - } catch (Exception e) { - ExceptionHandler.handle(e); - } - - return false; - } - - /** - * Computes the average of the images inside of the files array list and - * modifies saveImage to have the resulting calculated pixel average. - * - * @param width the width of the resulting image - * @param height the height of the resulting image - */ - private static BufferedImage computerAverage(int width, int height) { - BufferedImage saveImage = new BufferedImage(width, height, TYPE_INT_ARGB); - Graphics2D g2d = saveImage.createGraphics(); - g2d.setPaint(CyderColors.empty); - g2d.fillRect(0, 0, width, height); - - /* - This should be save to hold the sum of all pixel data. - Adding a precondition check for file.size() * 256 < Integer.MAX_VALUE - is even flagged by IntelliJ as always true - */ - int[][] pixelSum = new int[height][width]; - int[][] divideBy = new int[height][width]; - - for (int y = 0 ; y < divideBy.length ; y++) { - for (int x = 0 ; x < divideBy[0].length ; x++) { - divideBy[y][x] = 0; - } - } - - ArrayList averageFiles = new ArrayList<>(); - currentFiles.forEach((filename, file) -> averageFiles.add(file)); - - for (File currentImageFile : averageFiles) { - BufferedImage currentImage; - try { - currentImage = ImageUtil.read(currentImageFile); - } catch (Exception e) { - averagerFrame.inform("IO Failure", "Failed to read image file: " - + currentImageFile.getAbsolutePath()); - ExceptionHandler.handle(e); - continue; - } - - int currentHeight = currentImage.getHeight(); - int currentWidth = currentImage.getWidth(); - - int[][] currentPixels = get2DRgbArray(currentImage); - int currentXOffset = (width - currentWidth) / 2; - int currentYOffset = (height - currentHeight) / 2; - - for (int y = 0 ; y < currentPixels.length ; y++) { - for (int x = 0 ; x < currentPixels[0].length ; x++) { - pixelSum[y + currentYOffset][x + currentXOffset] += currentPixels[y][x]; - divideBy[y + currentYOffset][x + currentXOffset] += 1; - } - } - } - - for (int y = 0 ; y < pixelSum.length ; y++) { - for (int x = 0 ; x < pixelSum[0].length ; x++) { - int dividend = pixelSum[y][x]; - int divisor = divideBy[y][x]; - if (divisor == 0) divisor = 1; - int quotient = dividend / divisor; - saveImage.setRGB(x, y, quotient); - } - } - - return saveImage; - } - - /** - * Returns a two dimensional integer array representing the pixel data of the provided buffered image. - * - * @param image the image to find the pixel data of - * @return an array of pixel data - */ - private static int[][] get2DRgbArray(BufferedImage image) { - byte[] pixels = ((DataBufferByte) image.getRaster().getDataBuffer()).getData(); - - int width = image.getWidth(); - int height = image.getHeight(); - - boolean hasAlphaChannel = image.getAlphaRaster() != null; - int[][] ret = new int[height][width]; - - if (hasAlphaChannel) { - for (int pixel = 0, row = 0, col = 0 ; pixel + 3 < pixels.length ; pixel += alphaPixelLength) { - int argb = 0; - - argb += (((int) pixels[pixel] & 0xff) << 24); - argb += (((int) pixels[pixel + 3] & 0xff) << 16); - argb += (((int) pixels[pixel + 2] & 0xff) << 8); - argb += ((int) pixels[pixel + 1] & 0xff); - - ret[row][col] = argb; - col++; - - if (col == width) { - col = 0; - row++; - } - } - } else { - for (int pixel = 0, row = 0, col = 0 ; pixel + 2 < pixels.length ; pixel += noAlphaPixelLength) { - int argb = 0; - - argb -= EMPTY_ALPHA; - argb += (((int) pixels[pixel + 2] & 0xff) << 16); - argb += (((int) pixels[pixel + 1] & 0xff) << 8); - argb += ((int) pixels[pixel] & 0xff); - - ret[row][col] = argb; - col++; - - if (col == width) { - col = 0; - row++; - } - } - } - - return ret; - } - - /** - * Returns a string of the filenames from the files array - * list combined and separated by an underscore. - * - * @return the combined file names - */ - @ForReadability - private static String combineImageNames() { - StringBuilder ret = new StringBuilder(); - - int finalIndex = currentFiles.size() - 1; - AtomicInteger currentIndex = new AtomicInteger(); - currentFiles.forEach((filename, file) -> { - ret.append(FileUtil.getFilename(file.getName())); - if (currentIndex.get() != finalIndex) ret.append(UNDERSCORE); - currentIndex.getAndIncrement(); - }); - - return ret.toString(); - } -} diff --git a/src/main/java/cyder/widgets/ImagePixelatorWidget.java b/src/main/java/cyder/widgets/ImagePixelatorWidget.java deleted file mode 100644 index b8cbec5ca..000000000 --- a/src/main/java/cyder/widgets/ImagePixelatorWidget.java +++ /dev/null @@ -1,386 +0,0 @@ -package cyder.widgets; - -import cyder.annotations.CyderAuthor; -import cyder.annotations.ForReadability; -import cyder.annotations.Vanilla; -import cyder.annotations.Widget; -import cyder.console.Console; -import cyder.constants.CyderColors; -import cyder.constants.CyderFonts; -import cyder.enumerations.Dynamic; -import cyder.enumerations.Extension; -import cyder.exceptions.IllegalMethodException; -import cyder.files.FileUtil; -import cyder.getter.GetFileBuilder; -import cyder.getter.GetterUtil; -import cyder.handlers.internal.ExceptionHandler; -import cyder.layouts.CyderPartitionedLayout; -import cyder.strings.CyderStrings; -import cyder.strings.StringUtil; -import cyder.threads.CyderThreadRunner; -import cyder.ui.UiUtil; -import cyder.ui.button.CyderButton; -import cyder.ui.field.CyderTextField; -import cyder.ui.frame.CyderFrame; -import cyder.ui.label.CyderLabel; -import cyder.user.UserDataManager; -import cyder.user.UserFile; -import cyder.utils.ImageUtil; - -import javax.imageio.ImageIO; -import javax.swing.*; -import javax.swing.border.LineBorder; -import java.awt.*; -import java.awt.event.KeyAdapter; -import java.awt.event.KeyEvent; -import java.awt.event.KeyListener; -import java.awt.image.BufferedImage; -import java.io.File; -import java.util.Optional; - -/** - * An image pixelator widget. - */ -@Vanilla -@CyderAuthor -public final class ImagePixelatorWidget { - /** - * The current icon that is being displayed. - */ - private static ImageIcon currentDisplayImageIcon; - - /** - * The current raw image file. - */ - private static File currentFile; - - /** - * The image pixelation preview label. - */ - private static JLabel previewLabel; - - /** - * The pixelation size field. - */ - private static CyderTextField pixelSizeField; - - /** - * Suppress default constructor. - */ - private ImagePixelatorWidget() { - throw new IllegalMethodException(CyderStrings.ATTEMPTED_INSTANTIATION); - } - - /** - * The widget description - */ - private static final String description = "A simple image pixelator widget that transforms" - + " the image into an image depicted of the specified number of pixels"; - - /** - * The width of the frame. - */ - private static final int FRAME_WIDTH = 700; - - /** - * The height of the frame. - */ - private static final int FRAME_HEIGHT = 950; - - /** - * The widget frame title. - */ - private static final String IMAGE_PIXELATOR = "Image Pixelator"; - - /** - * The pixel size label text. - */ - private static final String PIXEL_SIZE = "Pixel Size"; - - /** - * The choose image button text. - */ - private static final String CHOOSE_IMAGE = "Choose Image"; - - /** - * The widget frame. - */ - private static CyderFrame pixelFrame; - - /** - * The border for the preview label. - */ - private static final LineBorder previewLabelBorder = new LineBorder(CyderColors.navy, 5, false); - - /** - * The maximum length for the preview label image. - */ - private static final int previewImageMaxLen = 600; - - /** - * The name of the waiter thread for the get file getter util instance. - */ - private static final String CHOOSE_IMAGE_WAITER_THREAD_NAME = "Choose Image Waiter Thread"; - - /** - * The builder for the choose file button. - */ - private static final GetFileBuilder getterUtilBuilder = new GetFileBuilder("Choose file to resize") - .setRelativeTo(pixelFrame); - - /** - * The image files text. - */ - private static final String IMAGE_FILES = "Image files"; - - /** - * The pixel label font. - */ - private static final Font pixelLabelFont = new Font(CyderFonts.AGENCY_FB, Font.BOLD, 28); - - /** - * The approve image label. - */ - private static final String APPROVE_IMAGE = "Approve Image"; - - /** - * The pixelated pixel size part of the name when saving a pixelated image. - */ - private static final String PIXELATED_PIXEL_SIZE = "_Pixelated_Pixel_Size_"; - - /** - * The regex for the pixel size field to restrict the input to numbers. - */ - private static final String pixelSizeFieldRegexMatcher = "[0-9]*"; - - /** - * The length of primary components. - */ - private static final int componentLength = 300; - - /** - * The height of primary components. - */ - private static final int componentHeight = 40; - - /** - * The partition length for primary components. - */ - private static final int componentPartition = 7; - - @Widget(triggers = {"pixelate picture", "pixelate image", "pixelator"}, description = description) - public static void showGui() { - showGui(null); - } - - /** - * Shows the widget ui with the provided image as the preview image. - * - * @param imageFile the image to pixelate - */ - public static void showGui(File imageFile) { - UiUtil.closeIfOpen(pixelFrame); - - pixelFrame = new CyderFrame.Builder() - .setWidth(FRAME_WIDTH) - .setHeight(FRAME_HEIGHT) - .setTitle(IMAGE_PIXELATOR) - .build(); - - float remainingPartition = CyderPartitionedLayout.MAX_PARTITION - 4 * componentPartition; - - CyderPartitionedLayout partitionedLayout = new CyderPartitionedLayout(); - partitionedLayout.setPartitionDirection(CyderPartitionedLayout.PartitionDirection.COLUMN); - - CyderLabel pixelSizeLabel = new CyderLabel(PIXEL_SIZE); - pixelSizeLabel.setFont(pixelLabelFont); - pixelSizeLabel.setSize(componentLength, componentHeight); - partitionedLayout.addComponent(pixelSizeLabel, componentPartition); - - pixelSizeField = new CyderTextField(); - pixelSizeField.setKeyEventRegexMatcher(pixelSizeFieldRegexMatcher); - pixelSizeField.setSize(componentLength, componentHeight); - pixelSizeField.addKeyListener(pixelSizeFieldKeyAdapter); - partitionedLayout.addComponent(pixelSizeField, componentPartition); - - CyderButton chooseImage = new CyderButton(CHOOSE_IMAGE); - chooseImage.setToolTipText(IMAGE_FILES); - chooseImage.setSize(componentLength, componentHeight); - chooseImage.addActionListener(e -> chooseImageButtonAction()); - partitionedLayout.addComponent(chooseImage, componentPartition); - - CyderButton approveImageButton = new CyderButton(APPROVE_IMAGE); - approveImageButton.setSize(componentLength, componentHeight); - approveImageButton.addActionListener(e -> approveImageAction()); - partitionedLayout.addComponent(approveImageButton, componentPartition); - - previewLabel = new JLabel(); - previewLabel.setSize(previewImageMaxLen, previewImageMaxLen); - previewLabel.setBorder(previewLabelBorder); - partitionedLayout.addComponent(previewLabel, (int) remainingPartition); - - attemptToSetFileAsImage(imageFile); - - pixelFrame.setCyderLayout(partitionedLayout); - pixelFrame.finalizeAndShow(); - } - - /** - * The actions to invoke when the approve image button is pressed. - */ - private static void approveImageAction() { - String pixelInput = pixelSizeField.getText(); - if (StringUtil.isNullOrEmpty(pixelInput)) return; - - int pixelSize; - try { - pixelSize = Integer.parseInt(pixelInput); - } catch (Exception e) { - ExceptionHandler.handle(e); - pixelFrame.notify("Could not parse input as an integer:" + pixelInput); - return; - } - - if (pixelSize == 1) { - pixelFrame.notify("Pixel size is already 1"); - return; - } - - BufferedImage currentBufferedImage; - try { - currentBufferedImage = ImageUtil.read(currentFile); - } catch (Exception e) { - ExceptionHandler.handle(e); - pixelFrame.notify("Failed to read image file: " + currentFile.getAbsolutePath()); - return; - } - - BufferedImage saveImage = ImageUtil.pixelateImage(currentBufferedImage, pixelSize); - - String currentFilename = FileUtil.getFilename(currentFile); - String saveName = currentFilename + PIXELATED_PIXEL_SIZE + pixelSize + Extension.PNG.getExtension(); - File saveFile = Dynamic.buildDynamic(Dynamic.USERS.getFileName(), - Console.INSTANCE.getUuid(), UserFile.FILES.getName(), saveName); - - try { - ImageIO.write(saveImage, Extension.PNG.getExtensionWithoutPeriod(), saveFile); - } catch (Exception e) { - ExceptionHandler.handle(e); - pixelFrame.notify("Failed to write pixelated image"); - return; - } - - pixelFrame.notify("Successfully saved pixelated image to " - + StringUtil.getApostropheSuffix(UserDataManager.INSTANCE.getUsername()) - + " files/ directory"); - } - - /** - * The key listener for the pixel size field. - */ - private static final KeyListener pixelSizeFieldKeyAdapter = new KeyAdapter() { - @Override - public void keyReleased(KeyEvent e) { - try { - String input = pixelSizeField.getText(); - if (input.isEmpty()) return; - - int pixelSize = -1; - try { - pixelSize = Integer.parseInt(pixelSizeField.getText()); - } catch (Exception ex) { - ExceptionHandler.handle(ex); - } - - if (pixelSize == -1) { - pixelFrame.notify("Could not parse input as integer: " + input); - return; - } - - if (pixelSize == 0 || pixelSize == 1) return; - - BufferedImage bufferedImage = ImageUtil.pixelateImage(ImageUtil.read(currentFile), pixelSize); - currentDisplayImageIcon = ImageUtil.resizeIfLengthExceeded( - ImageUtil.toImageIcon(bufferedImage), previewImageMaxLen); - previewLabel.setIcon(currentDisplayImageIcon); - - refreshPreviewLabelSize(); - repaintPreviewLabelAndFrame(); - } catch (Exception ex) { - ExceptionHandler.handle(ex); - } - } - }; - - /** - * The actions to invoke when the choose image button is pressed. - */ - @ForReadability - private static void chooseImageButtonAction() { - CyderThreadRunner.submit(() -> { - Optional optionalFile = GetterUtil.getInstance().getFile(getterUtilBuilder); - if (optionalFile.isEmpty()) return; - attemptToSetFileAsImage(optionalFile.get()); - }, CHOOSE_IMAGE_WAITER_THREAD_NAME); - } - - /** - * Attempts to read the provided file and set it as the current image file to be pixelated. - * - * @param imageFile the file to read and set as the current image file to be pixelated - */ - @ForReadability - private static void attemptToSetFileAsImage(File imageFile) { - if (imageFile == null || !imageFile.exists() || !FileUtil.isSupportedImageExtension(imageFile)) { - currentFile = null; - currentDisplayImageIcon = null; - - repaintPreviewLabelAndFrame(); - - return; - } - - currentFile = imageFile; - - BufferedImage newBufferedImage; - try { - newBufferedImage = ImageUtil.read(imageFile); - } catch (Exception e) { - ExceptionHandler.handle(e); - pixelFrame.notify("Could not read chosen file: " + imageFile.getAbsolutePath()); - return; - } - - currentDisplayImageIcon = ImageUtil.resizeIfLengthExceeded( - ImageUtil.toImageIcon(newBufferedImage), previewImageMaxLen); - previewLabel.setIcon(currentDisplayImageIcon); - - refreshPreviewLabelSize(); - repaintPreviewLabelAndFrame(); - } - - /** - * Refreshes the size of the preview label based on the current icon. - */ - @ForReadability - private static void refreshPreviewLabelSize() { - ImageIcon previewIcon = (ImageIcon) previewLabel.getIcon(); - if (previewIcon == null) { - previewLabel.setSize(previewImageMaxLen, previewImageMaxLen); - } else { - previewLabel.setSize(previewIcon.getIconWidth(), previewIcon.getIconHeight()); - } - } - - /** - * Revalidates and repaints the previewLabel and pixelFrame. - */ - @ForReadability - private static void repaintPreviewLabelAndFrame() { - previewLabel.revalidate(); - previewLabel.repaint(); - - pixelFrame.revalidate(); - pixelFrame.repaint(); - } -} diff --git a/src/main/java/cyder/widgets/MinecraftWidget.java b/src/main/java/cyder/widgets/MinecraftWidget.java deleted file mode 100644 index d3a155044..000000000 --- a/src/main/java/cyder/widgets/MinecraftWidget.java +++ /dev/null @@ -1,266 +0,0 @@ -package cyder.widgets; - -import com.google.common.base.Preconditions; -import com.google.common.collect.ImmutableList; -import cyder.annotations.CyderAuthor; -import cyder.annotations.ForReadability; -import cyder.annotations.Vanilla; -import cyder.annotations.Widget; -import cyder.exceptions.IllegalMethodException; -import cyder.files.FileUtil; -import cyder.network.NetworkUtil; -import cyder.strings.CyderStrings; -import cyder.strings.StringUtil; -import cyder.ui.UiUtil; -import cyder.ui.frame.CyderFrame; -import cyder.ui.frame.enumerations.TitlePosition; -import cyder.user.UserDataManager; -import cyder.user.data.MappedExecutable; -import cyder.utils.StaticUtil; - -import javax.swing.*; -import java.awt.*; -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; -import java.awt.event.MouseListener; -import java.io.File; - -/** - * A widget emulating the Minecraft front page. - */ -@Vanilla -@CyderAuthor -public final class MinecraftWidget { - /** - * The minecraft frame. - */ - private static CyderFrame minecraftFrame; - - /** - * The minecraft.net link that redirects to the hamburger icon's result. - */ - public static final String MINECRAFT_HAMBURGER = "https://minecraft.net/en-us/?ref=m"; - - /** - * The minecraft.net link that redirects to the store icon's result. - */ - public static final String MINECRAFT_CHEST = "https://minecraft.net/en-us/store/?ref=m"; - - /** - * The minecraft.net link that redirects to the realm icon's result. - */ - public static final String MINECRAFT_REALMS = "https://minecraft.net/en-us/realms/?ref=m"; - - /** - * The minecraft.net link that redirects to the block icon's result. - */ - public static final String MINECRAFT_BLOCK = "https://my.minecraft.net/en-us/store/minecraft/"; - - /** - * The block image icon. - */ - private static final ImageIcon BLOCK = new ImageIcon(StaticUtil.getStaticPath("Block.png")); - - /** - * The block enter animation. - */ - private static final ImageIcon BLOCK_ENTER = new ImageIcon(StaticUtil.getStaticPath("BlockEnter.gif")); - - /** - * The block exit animation. - */ - private static final ImageIcon BLOCK_EXIT = new ImageIcon(StaticUtil.getStaticPath("BlockExit.gif")); - - /** - * The realms icon. - */ - private static final ImageIcon REALMS = new ImageIcon(StaticUtil.getStaticPath("Realms.png")); - - /** - * The realms enter animation. - */ - private static final ImageIcon REALMS_ENTER = new ImageIcon(StaticUtil.getStaticPath("RealmsEnter.gif")); - - /** - * The realms exit animation. - */ - private static final ImageIcon REALMS_EXIT = new ImageIcon(StaticUtil.getStaticPath("RealmsExit.gif")); - - /** - * The chest icon. - */ - private static final ImageIcon CHEST = new ImageIcon(StaticUtil.getStaticPath("Chest.png")); - - /** - * The chest enter animation. - */ - private static final ImageIcon CHEST_ENTER = new ImageIcon(StaticUtil.getStaticPath("ChestEnter.gif")); - - /** - * The chest exit animation. - */ - private static final ImageIcon CHEST_EXIT = new ImageIcon(StaticUtil.getStaticPath("ChestExit.gif")); - - /** - * The hamburger icon. - */ - private static final ImageIcon HAMBURGER = new ImageIcon(StaticUtil.getStaticPath("Hamburger.png")); - /** - * The hamburger enter animation. - */ - private static final ImageIcon HAMBURGER_ENTER = new ImageIcon( - StaticUtil.getStaticPath("HamburgerEnter.gif")); - - /** - * The hamburger exit animation. - */ - private static final ImageIcon HAMBURGER_EXIT = new ImageIcon( - StaticUtil.getStaticPath("HamburgerExit.gif")); - - /** - * The title of the widget frame. - */ - private static final String FRAME_TITLE = "Minecraft Widget"; - - /** - * The width of the widget frame. - */ - private static final int FRAME_WIDTH = 1263; - - /** - * The height of the image frame. - */ - private static final int FRAME_HEIGHT = 160; - - /** - * Names of mapped exes which may reference a Minecraft executable. - */ - private static final ImmutableList MINECRAFT_NAMES = ImmutableList.of( - "Minecraft", - "Lunar", - "Badlion", - "Optifine", - "ATLauncher", - "Technic", - "Forge" - ); - - /** - * The background of the frame. - */ - private static final ImageIcon background = new ImageIcon(StaticUtil.getStaticPath("Minecraft.png")); - - /** - * Suppress default constructor. - */ - private MinecraftWidget() { - throw new IllegalMethodException(CyderStrings.ATTEMPTED_INSTANTIATION); - } - - @Widget(triggers = "minecraft", description = "A minecraft widget that copies from the Mojang home page") - public static void showGui() { - UiUtil.closeIfOpen(minecraftFrame); - minecraftFrame = new CyderFrame.Builder() - .setWidth(FRAME_WIDTH) - .setHeight(FRAME_HEIGHT) - .setBackgroundIcon(background) - .build(); - minecraftFrame.setTitlePosition(TitlePosition.CENTER); - minecraftFrame.setTitle(FRAME_TITLE); - - JLabel blockLabel = new JLabel(BLOCK); - blockLabel.addMouseListener(generateMouseListener(blockLabel, MINECRAFT_BLOCK, - BLOCK_ENTER, BLOCK_EXIT)); - blockLabel.setBounds(83, 46, 50, 45); - minecraftFrame.getContentPane().add(blockLabel); - - JLabel realmsLabel = new JLabel(REALMS); - realmsLabel.addMouseListener(generateMouseListener(realmsLabel, MINECRAFT_REALMS, - REALMS_ENTER, REALMS_EXIT)); - realmsLabel.setBounds(196, 51, 70, 45); - minecraftFrame.getContentPane().add(realmsLabel); - - JLabel chestLabel = new JLabel(CHEST); - chestLabel.addMouseListener(generateMouseListener(chestLabel, MINECRAFT_CHEST, - CHEST_ENTER, CHEST_EXIT)); - chestLabel.setBounds(1009, 44, 60, 50); - minecraftFrame.getContentPane().add(chestLabel); - - JLabel hamLabel = new JLabel(HAMBURGER); - hamLabel.addMouseListener(generateMouseListener(hamLabel, MINECRAFT_HAMBURGER, - HAMBURGER_ENTER, HAMBURGER_EXIT)); - hamLabel.setBounds(1135, 52, 42, 40); - minecraftFrame.getContentPane().add(hamLabel); - - int x = (UiUtil.getDefaultMonitorWidth() - FRAME_WIDTH) / 2; - int y = UiUtil.getDefaultMonitorHeight() - FRAME_HEIGHT - UiUtil.getWindowsTaskbarHeight() / 2; - minecraftFrame.finalizeAndShow(new Point(x, y)); - minecraftFrame.setIconImage(BLOCK.getImage()); - - checkMappedExes(); - } - - /** - * Generates a mouse adapter for a minecraft gif label. - * - * @param label the label - * @param url the url to open on click - * @param enterGif the enter gif - * @param exitGif the exit gif - * @return the mouse listener - */ - @ForReadability - private static MouseListener generateMouseListener(JLabel label, String url, - ImageIcon enterGif, ImageIcon exitGif) { - Preconditions.checkNotNull(label); - Preconditions.checkNotNull(url); - Preconditions.checkArgument(!url.isEmpty()); - Preconditions.checkNotNull(enterGif); - Preconditions.checkNotNull(exitGif); - - return new MouseAdapter() { - @Override - public void mouseClicked(MouseEvent e) { - NetworkUtil.openUrl(url); - } - - @Override - public void mouseEntered(MouseEvent e) { - enterGif.getImage().flush(); - label.setIcon(enterGif); - } - - @Override - public void mouseExited(MouseEvent e) { - exitGif.getImage().flush(); - label.setIcon(exitGif); - } - }; - } - - /** - * Checks the current user's mapped executables to determine if any might reference a Minecraft launcher. - */ - private static void checkMappedExes() { - UserDataManager.INSTANCE.getMappedExecutables().getExecutables() - .stream().filter(MinecraftWidget::mappedExeReferencesPossibleMinecraftLauncher) - .findFirst().ifPresent(exe -> FileUtil.openResourceUsingNativeProgram(exe.getFilepath())); - } - - /** - * Returns whether the provided mapped executable likely references a Minecraft launcher. - * - * @param mappedExecutable the mapped executable - * @return whether the provided mapped executable likely references a Minecraft launcher - */ - private static boolean mappedExeReferencesPossibleMinecraftLauncher(MappedExecutable mappedExecutable) { - Preconditions.checkNotNull(mappedExecutable); - - File refFile = new File(mappedExecutable.getFilepath()); - if (refFile.exists() && refFile.isFile()) { - return StringUtil.in(FileUtil.getFilename(refFile), true, MINECRAFT_NAMES); - } - - return false; - } -} \ No newline at end of file diff --git a/src/main/java/cyder/widgets/NotesWidget.java b/src/main/java/cyder/widgets/NotesWidget.java deleted file mode 100644 index 5f86be2f6..000000000 --- a/src/main/java/cyder/widgets/NotesWidget.java +++ /dev/null @@ -1,873 +0,0 @@ -package cyder.widgets; - -import com.google.common.base.Preconditions; -import com.google.common.collect.ImmutableList; -import cyder.annotations.CyderAuthor; -import cyder.annotations.ForReadability; -import cyder.annotations.Vanilla; -import cyder.annotations.Widget; -import cyder.console.Console; -import cyder.constants.CyderColors; -import cyder.enumerations.Dynamic; -import cyder.enumerations.Extension; -import cyder.exceptions.IllegalMethodException; -import cyder.files.FileUtil; -import cyder.getter.GetConfirmationBuilder; -import cyder.getter.GetterUtil; -import cyder.handlers.internal.ExceptionHandler; -import cyder.layouts.CyderGridLayout; -import cyder.layouts.CyderPartitionedLayout; -import cyder.strings.CyderStrings; -import cyder.strings.StringUtil; -import cyder.threads.CyderThreadRunner; -import cyder.ui.UiUtil; -import cyder.ui.button.CyderButton; -import cyder.ui.field.CyderCaret; -import cyder.ui.field.CyderTextField; -import cyder.ui.frame.CyderFrame; -import cyder.ui.list.CyderScrollList; -import cyder.ui.pane.CyderPanel; -import cyder.ui.pane.CyderScrollPane; -import cyder.user.UserDataManager; -import cyder.user.UserFile; -import cyder.utils.OsUtil; - -import javax.swing.*; -import javax.swing.border.Border; -import javax.swing.border.LineBorder; -import java.awt.*; -import java.awt.event.KeyAdapter; -import java.awt.event.KeyEvent; -import java.awt.event.KeyListener; -import java.io.BufferedWriter; -import java.io.File; -import java.io.FileWriter; -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Optional; -import java.util.concurrent.atomic.AtomicLong; - -/** - * A note taking widget. - */ -@Vanilla -@CyderAuthor -public final class NotesWidget { - /** - * The notes selection and creation list frame. - */ - private static CyderFrame noteFrame; - - /** - * The default frame width. - */ - private static final int defaultFrameWidth = 600; - - /** - * The default frame height. - */ - private static final int defaultFrameHeight = 680; - - /** - * The padding between the frame and the note files scrolls. - */ - private static final int noteListScrollPadding = 25; - - /** - * The length of the note files list scroll. - */ - private static final int noteListScrollLength = defaultFrameWidth - 2 * noteListScrollPadding; - - /** - * The padding between the frame and the note contents scrolls. - */ - private static final int noteScrollPadding = 50; - - /** - * The notes list scroll. - */ - private static CyderScrollList notesScrollList; - - /** - * The length of the notes JTextArea. - */ - private static final int noteAreaLength = defaultFrameWidth - 2 * noteScrollPadding; - - /** - * The partitioned layout for the notes scroll view. - */ - private static CyderPartitionedLayout framePartitionedLayout; - - /** - * The current frame view. - */ - private static View currentView; - - /** - * The button size of most buttons. - */ - private static final Dimension buttonSize = new Dimension(160, 40); - - /** - * The new note contents area. - */ - private static JTextPane newNoteArea; - - /** - * The new note name field. - */ - private static CyderTextField newNoteNameField; - - /** - * The add button text. - */ - private static final String ADD = "Add"; - - /** - * The open button text. - */ - private static final String OPEN = "Open"; - - /** - * The delete button text. - */ - private static final String DELETE = "Delete"; - - /** - * The possible widget views. - */ - private enum View { - LIST, - ADD, - EDIT, - } - - /** - * The description for this widget. - */ - private static final String description = "A note taking widget that can save and display multiple notes"; - - /** - * The font for the note name fields. - */ - private static final Font noteNameFieldFont = new Font("Agency FB", Font.BOLD, 26); - - /** - * The border for the note name fields. - */ - private static final Border noteNameFieldBorder - = BorderFactory.createMatteBorder(0, 0, 4, 0, CyderColors.navy); - - /** - * The height of the note scrolls. - */ - private static final int noteScrollHeight = noteAreaLength - 2 * noteListScrollPadding; - - /** - * The back button text. - */ - private static final String BACK = "Back"; - - /** - * The create button text. - */ - private static final String CREATE = "Create"; - - /** - * The list of currently read notes from the user's notes directory. - */ - private static final ArrayList notesList = new ArrayList<>(); - - /** - * The exit text. - */ - private static final String EXIT = "Exit"; - - /** - * The stay text (I told you that I never would). - */ - private static final String STAY = "Stay"; - - /** - * The name of the thread which waits for a save confirmation. - */ - private static final String NOTE_EDITOR_EXIT_CONFIRMATION_WAITER_THREAD_NAME = "Note editor exit confirmation"; - - /** - * The add note button text. - */ - private static final String ADD_NOTE = "Add note"; - - /** - * The edit note name field. - */ - private static CyderTextField editNoteNameField; - - /** - * The edit note contents area. - */ - private static JTextPane noteEditArea; - - /** - * The save button text. - */ - private static final String SAVE = "Save"; - - /** - * The currently being edited note file. - */ - private static File currentNoteFile; - - /** - * The confirmation message to display when a user attempts to close the frame when there are pending changes. - */ - private static final String closingConfirmationMessage = "You have unsaved changes, are you sure you wish to exit?"; - - /** - * Whether the current note has unsaved changes. - */ - private static boolean unsavedChanges = false; - - /** - * Whether a new note being added has information that would be lost. - */ - private static boolean newNoteContent = false; - - /** - * The notification text to display when a note is saved. - */ - private static final String SAVED_NOTE = "Saved note"; - - /** - * The last time the current note was saved at. - */ - private static final AtomicLong lastSave = new AtomicLong(); - - /** - * The timeout between allowable note save actions. - */ - private static final int SAVE_BUTTON_TIMEOUT = 500; - - /** - * The border length for the note scroll. - */ - private static final int noteScrollBorderLen = 5; - - /** - * The padding partition for primary view components. - */ - private static final int viewPartitionPadding = 2; - - /** - * The length of the note content scroll view. - */ - private static final int noteContentScrollLength = noteAreaLength - 2 * noteScrollBorderLen; - - /** - * The encoding format for note files. - */ - private static final Charset noteFileEncoding = StandardCharsets.UTF_8; - - /** - * Suppress default constructor. - */ - private NotesWidget() { - throw new IllegalMethodException(CyderStrings.ATTEMPTED_INSTANTIATION); - } - - @Widget(triggers = {"note", "notes"}, description = description) - public static void showGui() { - Preconditions.checkNotNull(Console.INSTANCE.getUuid()); - UiUtil.closeIfOpen(noteFrame); - noteFrame = new CyderFrame.Builder() - .setWidth(defaultFrameWidth) - .setHeight(defaultFrameHeight) - .build(); - setupView(View.LIST); - noteFrame.finalizeAndShow(); - } - - /** - * Sets up the frame for the provided view. - * - * @param view the view - */ - private static void setupView(View view) { - Preconditions.checkNotNull(view); - currentView = view; - - switch (view) { - case LIST -> setupListView(); - case ADD -> setupAddView(); - case EDIT -> setupEditView(); - } - } - - /** - * Sets up and shows the notes list view. - */ - private static void setupListView() { - refreshNotesList(); - resetUnsavedContentVars(); - - currentNoteFile = null; - - framePartitionedLayout = new CyderPartitionedLayout(); - - JLabel notesLabel = regenerateAndGetNotesScrollLabel(); - - CyderGridLayout buttonGridlayout = new CyderGridLayout(3, 1); - - CyderButton addButton = new CyderButton(ADD); - addButton.setSize(buttonSize); - addButton.addActionListener(e -> setupView(View.ADD)); - buttonGridlayout.addComponent(addButton); - - CyderButton openButton = new CyderButton(OPEN); - openButton.setSize(buttonSize); - openButton.addActionListener(e -> openButtonAction()); - buttonGridlayout.addComponent(openButton); - - CyderButton deleteButton = new CyderButton(DELETE); - deleteButton.setSize(buttonSize); - deleteButton.addActionListener(e -> deleteButtonAction()); - buttonGridlayout.addComponent(deleteButton); - - CyderPanel buttonPanel = new CyderPanel(buttonGridlayout); - buttonPanel.setSize(notesLabel.getWidth(), 50); - - framePartitionedLayout.spacer(viewPartitionPadding); - framePartitionedLayout.addComponentMaintainSize(notesLabel, CyderPartitionedLayout.PartitionAlignment.TOP); - framePartitionedLayout.spacer(viewPartitionPadding); - framePartitionedLayout.addComponent(buttonPanel); - framePartitionedLayout.spacer(viewPartitionPadding); - - noteFrame.setCyderLayout(framePartitionedLayout); - noteFrame.repaint(); - revalidateFrameTitle(); - } - - /** - * Sets up and shows the add note view. - */ - private static void setupAddView() { - noteFrame.removeCyderLayoutPanel(); - noteFrame.repaint(); - - newNoteNameField = new CyderTextField(); - newNoteNameField.addKeyListener(getAddNoteKeyListener()); - newNoteNameField.setFont(noteNameFieldFont); - newNoteNameField.setForeground(CyderColors.navy); - newNoteNameField.setCaret(new CyderCaret(CyderColors.navy)); - newNoteNameField.setBackground(CyderColors.vanilla); - newNoteNameField.setSize(300, 40); - newNoteNameField.setToolTipText("Note name"); - newNoteNameField.setBorder(noteNameFieldBorder); - - newNoteArea = new JTextPane(); - newNoteArea.addKeyListener(getAddNoteKeyListener()); - newNoteArea.setSize(noteAreaLength, noteScrollHeight); - newNoteArea.setBackground(CyderColors.vanilla); - newNoteArea.setFocusable(true); - newNoteArea.setEditable(true); - newNoteArea.setSelectionColor(CyderColors.selectionColor); - newNoteArea.setFont(Console.INSTANCE.generateUserFont()); - newNoteArea.setForeground(CyderColors.navy); - newNoteArea.setCaret(new CyderCaret(CyderColors.navy)); - newNoteArea.setCaretColor(newNoteArea.getForeground()); - - CyderScrollPane noteScroll = new CyderScrollPane(newNoteArea, - ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED, - ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED); - noteScroll.setThumbColor(CyderColors.regularPink); - noteScroll.setBorder(new LineBorder(CyderColors.navy, 5)); - noteScroll.setSize(noteContentScrollLength, noteContentScrollLength); - noteScroll.getViewport().setOpaque(false); - noteScroll.setOpaque(false); - noteScroll.setBorder(null); - newNoteArea.setAutoscrolls(true); - - JLabel noteScrollLabel = new JLabel(); - noteScrollLabel.setSize(noteAreaLength, noteAreaLength); - noteScrollLabel.setBorder(new LineBorder(CyderColors.navy, noteScrollBorderLen)); - noteScroll.setLocation(noteScrollBorderLen, noteScrollBorderLen); - noteScrollLabel.add(noteScroll); - - CyderButton backButton = new CyderButton(BACK); - backButton.setSize(buttonSize); - backButton.addActionListener(e -> setupView(View.LIST)); - - CyderButton createButton = new CyderButton(CREATE); - createButton.setSize(buttonSize); - createButton.addActionListener(e -> createNoteAction()); - - CyderGridLayout buttonGridLayout = new CyderGridLayout(2, 1); - buttonGridLayout.addComponent(createButton); - buttonGridLayout.addComponent(backButton); - CyderPanel buttonGridPanel = new CyderPanel(buttonGridLayout); - buttonGridPanel.setSize(400, 50); - - CyderPartitionedLayout addNoteLayout = new CyderPartitionedLayout(); - addNoteLayout.spacer(viewPartitionPadding); - addNoteLayout.addComponentMaintainSize(newNoteNameField); - addNoteLayout.spacer(viewPartitionPadding); - addNoteLayout.addComponentMaintainSize(noteScrollLabel); - addNoteLayout.spacer(viewPartitionPadding); - addNoteLayout.addComponentMaintainSize(buttonGridPanel); - addNoteLayout.spacer(viewPartitionPadding); - - noteFrame.setCyderLayout(addNoteLayout); - revalidateFrameTitle(); - noteFrame.repaint(); - } - - /** - * Returns a key listener for the add note name field and content area. - * - * @return a key listener for the add note name field and content area - */ - private static KeyListener getAddNoteKeyListener() { - return new KeyAdapter() { - @Override - public void keyReleased(KeyEvent e) { - super.keyReleased(e); - refreshNewNoteChanges(); - } - }; - } - - /** - * The actions to invoke when the open button is pressed. - */ - private static void openButtonAction() { - Optional optionalFile = notesScrollList.getSelectedElement(); - if (optionalFile.isEmpty()) return; - String noteName = optionalFile.get(); - - notesList.stream().filter(noteFile -> noteFile.getName().equals(noteName)) - .forEach(noteFile -> currentNoteFile = noteFile); - - setupView(View.EDIT); - } - - /** - * The actions to invoke when the create new note button is clicked in the add note view. - */ - private static void createNoteAction() { - String noteName = newNoteNameField.getTrimmedText(); - if (noteName.isEmpty()) { - noteFrame.notify("Missing name for note"); - return; - } - - if (StringUtil.in(noteName, true, getCurrentNoteFileNames())) { - noteFrame.notify("Note name already in use"); - return; - } - - if (noteName.toLowerCase().endsWith(Extension.TXT.getExtension())) { - noteName = noteName.substring(0, noteName.length() - Extension.TXT.getExtension().length()); - } - - String requestedName = noteName + Extension.TXT.getExtension(); - - if (!FileUtil.isValidFilename(requestedName)) { - noteFrame.notify("Invalid filename"); - return; - } - - File createFile = Dynamic.buildDynamic(Dynamic.USERS.getFileName(), - Console.INSTANCE.getUuid(), UserFile.NOTES.getName(), requestedName); - if (!OsUtil.createFile(createFile, true)) { - noteFrame.notify("Could not create file: " - + CyderStrings.quote + requestedName + CyderStrings.quote); - return; - } - - String contents = newNoteArea.getText(); - try (BufferedWriter write = new BufferedWriter(new FileWriter(createFile))) { - write.write(contents); - } catch (Exception e) { - ExceptionHandler.handle(e); - } - - setupView(View.LIST); - noteFrame.notify("Added note file: " - + CyderStrings.quote + requestedName + CyderStrings.quote); - } - - /** - * Reads and returns the contents of the current note file. - * - * @return the contents of the current note file - */ - private static String getCurrentNoteContents() { - try { - byte[] encoded = Files.readAllBytes(Paths.get(currentNoteFile.getAbsolutePath())); - return new String(encoded, noteFileEncoding); - } catch (Exception e) { - ExceptionHandler.handle(e); - } - - throw new IllegalStateException("Could not read contents of current note file"); - } - - /** - * Sets up and shows the edit note view. - */ - private static void setupEditView() { - Preconditions.checkNotNull(currentNoteFile); - - noteFrame.removeCyderLayoutPanel(); - noteFrame.repaint(); - - editNoteNameField = new CyderTextField(); - editNoteNameField.addKeyListener(getEditNoteKeyListener()); - editNoteNameField.setFont(noteNameFieldFont); - editNoteNameField.setForeground(CyderColors.navy); - editNoteNameField.setCaret(new CyderCaret(CyderColors.navy)); - editNoteNameField.setBackground(CyderColors.vanilla); - editNoteNameField.setSize(300, 40); - editNoteNameField.setToolTipText("Note name"); - editNoteNameField.setBorder(noteNameFieldBorder); - editNoteNameField.setText(FileUtil.getFilename(currentNoteFile)); - - noteEditArea = new JTextPane(); - noteEditArea.addKeyListener(getEditNoteKeyListener()); - noteEditArea.setText(getCurrentNoteContents()); - noteEditArea.setSize(noteAreaLength, noteScrollHeight); - noteEditArea.setBackground(CyderColors.vanilla); - noteEditArea.setFocusable(true); - noteEditArea.setEditable(true); - noteEditArea.setSelectionColor(CyderColors.selectionColor); - noteEditArea.setFont(Console.INSTANCE.generateUserFont()); - noteEditArea.setForeground(CyderColors.navy); - noteEditArea.setCaret(new CyderCaret(CyderColors.navy)); - noteEditArea.setCaretColor(noteEditArea.getForeground()); - noteEditArea.addKeyListener(saveNoteEditAreaKeyListener); - - CyderScrollPane noteScroll = new CyderScrollPane(noteEditArea, - ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED, - ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED); - noteScroll.setThumbColor(CyderColors.regularPink); - noteScroll.setSize(noteContentScrollLength, noteContentScrollLength); - noteScroll.getViewport().setOpaque(false); - noteScroll.setOpaque(false); - noteScroll.setBorder(null); - noteEditArea.setAutoscrolls(true); - - JLabel noteScrollLabel = new JLabel(); - noteScrollLabel.setSize(noteAreaLength, noteAreaLength); - noteScrollLabel.setBorder(new LineBorder(CyderColors.navy, noteScrollBorderLen)); - noteScroll.setLocation(noteScrollBorderLen, noteScrollBorderLen); - noteScrollLabel.add(noteScroll); - - CyderButton saveButton = new CyderButton(SAVE); - saveButton.setSize(buttonSize); - saveButton.addActionListener(e -> editNoteSaveButtonAction()); - lastSave.set(System.currentTimeMillis()); - - CyderButton backButton = new CyderButton(BACK); - backButton.setSize(buttonSize); - backButton.addActionListener(e -> editBackButtonAction()); - - CyderGridLayout buttonGridLayout = new CyderGridLayout(2, 1); - buttonGridLayout.addComponent(saveButton); - buttonGridLayout.addComponent(backButton); - CyderPanel buttonGridPanel = new CyderPanel(buttonGridLayout); - buttonGridPanel.setSize(400, 50); - - CyderPartitionedLayout editNoteLayout = new CyderPartitionedLayout(); - editNoteLayout.spacer(viewPartitionPadding); - editNoteLayout.addComponentMaintainSize(editNoteNameField); - editNoteLayout.spacer(viewPartitionPadding); - editNoteLayout.addComponentMaintainSize(noteScrollLabel); - editNoteLayout.spacer(viewPartitionPadding); - editNoteLayout.addComponentMaintainSize(buttonGridPanel); - editNoteLayout.spacer(viewPartitionPadding); - - refreshUnsavedChanges(); - noteFrame.setCyderLayout(editNoteLayout); - revalidateFrameTitle(); - - noteFrame.repaint(); - } - - /** - * The key listener for saving the edited note contents to the note file when ctrl + s is performed. - */ - private static final KeyListener saveNoteEditAreaKeyListener = new KeyAdapter() { - @Override - public void keyPressed(KeyEvent e) { - if (e.isControlDown() && e.getKeyCode() == KeyEvent.VK_S) { - editNoteSaveButtonAction(); - } - } - }; - - /** - * Returns a key listener for the edit note name field and content area. - * - * @return a key listener for the edit note name field and content area - */ - private static KeyListener getEditNoteKeyListener() { - return new KeyAdapter() { - @Override - public void keyReleased(KeyEvent e) { - super.keyReleased(e); - refreshUnsavedChanges(); - } - }; - } - - /** - * The actions to invoke when the save button on the edit note view is pressed. - */ - private static void editNoteSaveButtonAction() { - if (System.currentTimeMillis() < lastSave.get() + SAVE_BUTTON_TIMEOUT) return; - lastSave.set(System.currentTimeMillis()); - - String noteNameFieldText = editNoteNameField.getTrimmedText(); - if (noteNameFieldText.isEmpty()) { - noteFrame.notify("Missing name for note"); - return; - } - - if (StringUtil.in(noteNameFieldText, true, getCurrentNoteFileNames()) - && !FileUtil.getFilename(currentNoteFile).equals(noteNameFieldText)) { - noteFrame.notify("Note name already in use"); - return; - } - - String newFilename = noteNameFieldText + Extension.TXT.getExtension(); - if (!FileUtil.isValidFilename(newFilename)) { - noteFrame.notify("Invalid filename: " + CyderStrings.quote + newFilename + CyderStrings.quote); - return; - } - - if (!OsUtil.deleteFile(currentNoteFile)) { - noteFrame.notify("Failed to update note contents"); - return; - } - - File newFile = Dynamic.buildDynamic(Dynamic.USERS.getFileName(), - Console.INSTANCE.getUuid(), UserFile.NOTES.getName(), newFilename); - if (!OsUtil.createFile(newFile, true)) { - noteFrame.notify("Failed to update note contents"); - return; - } - - currentNoteFile = newFile; - - String contents = noteEditArea.getText(); - saveToCurrentNote(contents); - } - - /** - * Saves the provided contents to the current note file. - * - * @param contents the contents to save - */ - private static void saveToCurrentNote(String contents) { - Preconditions.checkNotNull(contents); - - try (BufferedWriter writer = new BufferedWriter(new FileWriter(currentNoteFile))) { - writer.write(contents); - noteFrame.notify(SAVED_NOTE); - setUnsavedChanges(false); - } catch (Exception exception) { - ExceptionHandler.handle(exception); - } - } - - /** - * The action to invoke when the back button on the edit note view is pressed. - */ - private static void editBackButtonAction() { - CyderThreadRunner.submit(() -> { - String currentName = editNoteNameField.getTrimmedText(); - String currentContents = noteEditArea.getText(); - - StringBuilder pendingChangesBuilder = new StringBuilder(); - String currentlySavedName = FileUtil.getFilename(currentNoteFile); - if (!currentName.equals(currentlySavedName)) { - pendingChangesBuilder.append("Note name pending changes"); - } - - String currentSavedContents = getCurrentNoteContents(); - if (!currentContents.equals(currentSavedContents)) { - if (!pendingChangesBuilder.isEmpty()) { - pendingChangesBuilder.append(", "); - } - pendingChangesBuilder.append("Note contents pending changes"); - } - - boolean pendingChanges = !pendingChangesBuilder.isEmpty(); - if (pendingChanges) { - GetConfirmationBuilder builder - = new GetConfirmationBuilder("Pending changes", pendingChangesBuilder.toString()) - .setRelativeTo(noteFrame) - .setDisableRelativeTo(true) - .setYesButtonText(EXIT) - .setNoButtonText(STAY); - boolean shouldGoBack = GetterUtil.getInstance().getConfirmation(builder); - if (!shouldGoBack) return; - } - - setupView(View.LIST); - }, NOTE_EDITOR_EXIT_CONFIRMATION_WAITER_THREAD_NAME); - } - - /** - * Revalidates the frame title based on the current view. - */ - private static void revalidateFrameTitle() { - switch (currentView) { - case LIST -> { - String name = UserDataManager.INSTANCE.getUsername(); - noteFrame.setTitle(name + StringUtil.getApostropheSuffix(name) + " notes"); - } - case ADD -> noteFrame.setTitle(ADD_NOTE); - case EDIT -> { - String name = currentNoteFile == null ? "" : FileUtil.getFilename(currentNoteFile); - noteFrame.setTitle("Editing note: " + name); - } - } - } - - /** - * The actions to invoke when the delete button is pressed. - */ - private static void deleteButtonAction() { - Optional selectedElement = notesScrollList.getSelectedElement(); - if (selectedElement.isEmpty()) return; - - notesList.stream().filter(noteFile -> - noteFile.getName().equals(selectedElement.get())).forEach(OsUtil::deleteFile); - refreshNotesList(); - notesScrollList.removeSelectedElement(); - - JLabel notesLabel = regenerateAndGetNotesScrollLabel(); - framePartitionedLayout.setComponent(notesLabel, 1); - noteFrame.repaint(); - } - - /** - * Regenerates the notes scroll list and the JLabel and returns the generated component. - * - * @return the generated component - */ - @ForReadability - private static JLabel regenerateAndGetNotesScrollLabel() { - notesScrollList = new CyderScrollList(noteListScrollLength, noteListScrollLength, - CyderScrollList.SelectionPolicy.SINGLE); - notesList.forEach(noteFile -> notesScrollList.addElementWithDoubleClickAction(noteFile.getName(), () -> { - currentNoteFile = noteFile; - setupView(View.EDIT); - })); - JLabel notesLabel = notesScrollList.generateScrollList(); - notesLabel.setSize(noteListScrollLength, noteListScrollLength); - return notesLabel; - } - - /** - * Refreshes the contents of the notes list. - */ - private static void refreshNotesList() throws IllegalStateException { - File dir = Dynamic.buildDynamic(Dynamic.USERS.getFileName(), - Console.INSTANCE.getUuid(), UserFile.NOTES.getName()); - if (!dir.exists()) return; - - notesList.clear(); - - File[] noteFiles = dir.listFiles(); - if (noteFiles == null) return; - - Arrays.stream(noteFiles).filter(noteFile -> - FileUtil.getExtension(noteFile).equals(Extension.TXT.getExtension())).forEach(notesList::add); - } - - /** - * Refreshes the state of {@link #unsavedChanges}. - */ - private static void refreshUnsavedChanges() { - boolean filenameDifferent = !FileUtil.getFilename(currentNoteFile).equals(editNoteNameField.getText()); - boolean contentsDifferent = !getCurrentNoteContents().equals(noteEditArea.getText()); - setUnsavedChanges(contentsDifferent || filenameDifferent); - } - - /** - * Refreshes the state of {@link #newNoteContent}. - */ - private static void refreshNewNoteChanges() { - boolean filenameContents = !newNoteNameField.getTrimmedText().isEmpty(); - boolean contents = !newNoteArea.getText().isEmpty(); - setNewNoteContent(filenameContents || contents); - } - - /** - * Sets whether there are unsaved changes in the current note or note being created and thus - * whether a closing confirmation should be displayed if the frame is attempted to be disposed. - * - * @param newUnsavedChangesValue whether there are unsaved changes. - */ - private static void setUnsavedChanges(boolean newUnsavedChangesValue) { - if (unsavedChanges == newUnsavedChangesValue) return; - unsavedChanges = newUnsavedChangesValue; - if (newNoteContent) return; - - if (newUnsavedChangesValue) { - noteFrame.setClosingConfirmation(closingConfirmationMessage); - } else { - noteFrame.removeClosingConfirmation(); - } - } - - /** - * Sets whether there is an unsaved note with contents that would be lost if the frame was disposed - * and thus whether a closing confirmation should be displayed if the frame is attempted to be disposed. - * - * @param newNoteContainsContent whether there is a new note present with unsaved changes - */ - private static void setNewNoteContent(boolean newNoteContainsContent) { - if (newNoteContent == newNoteContainsContent) return; - newNoteContent = newNoteContainsContent; - if (unsavedChanges) return; - - if (newNoteContainsContent) { - noteFrame.setClosingConfirmation(closingConfirmationMessage); - } else { - noteFrame.removeClosingConfirmation(); - } - } - - /** - * Resets the states of {@link #unsavedChanges} and {@link #newNoteContent} - * and removes the closing confirmation from the note frame if present. - */ - private static void resetUnsavedContentVars() { - unsavedChanges = false; - newNoteContent = false; - - noteFrame.removeClosingConfirmation(); - } - - private static ImmutableList getCurrentNoteFileNames() { - ArrayList ret = new ArrayList<>(); - - File userNotesDir = Dynamic.buildDynamic(Dynamic.USERS.getFileName(), - Console.INSTANCE.getUuid(), UserFile.NOTES.getName()); - if (userNotesDir.exists()) { - File[] noteFiles = userNotesDir.listFiles(); - if (noteFiles != null && noteFiles.length > 0) { - Arrays.stream(noteFiles).forEach(noteFile -> ret.add(FileUtil.getFilename(noteFile))); - } - } - - return ImmutableList.copyOf(ret); - } -} diff --git a/src/main/java/cyder/widgets/PaintWidget.java b/src/main/java/cyder/widgets/PaintWidget.java deleted file mode 100644 index 9e77ed948..000000000 --- a/src/main/java/cyder/widgets/PaintWidget.java +++ /dev/null @@ -1,825 +0,0 @@ -package cyder.widgets; - -import com.google.common.collect.ImmutableList; -import cyder.annotations.CyderAuthor; -import cyder.annotations.Vanilla; -import cyder.annotations.Widget; -import cyder.console.Console; -import cyder.constants.CyderColors; -import cyder.constants.CyderIcons; -import cyder.constants.CyderRegexPatterns; -import cyder.enumerations.Dynamic; -import cyder.enumerations.Extension; -import cyder.exceptions.IllegalMethodException; -import cyder.files.FileUtil; -import cyder.getter.GetFileBuilder; -import cyder.getter.GetInputBuilder; -import cyder.getter.GetterUtil; -import cyder.handlers.external.ImageViewer; -import cyder.handlers.internal.ExceptionHandler; -import cyder.layouts.CyderGridLayout; -import cyder.strings.CyderStrings; -import cyder.threads.CyderThreadRunner; -import cyder.ui.UiUtil; -import cyder.ui.button.CyderIconButton; -import cyder.ui.drag.CyderDragLabel; -import cyder.ui.field.CyderTextField; -import cyder.ui.frame.CyderFrame; -import cyder.ui.frame.enumerations.FrameType; -import cyder.ui.frame.notification.NotificationBuilder; -import cyder.ui.grid.CyderGrid; -import cyder.ui.grid.GridNode; -import cyder.ui.label.CyderLabel; -import cyder.ui.pane.CyderPanel; -import cyder.ui.selection.CyderCheckbox; -import cyder.ui.selection.CyderCheckboxGroup; -import cyder.ui.slider.CyderSliderUi; -import cyder.ui.slider.ThumbShape; -import cyder.user.UserFile; -import cyder.user.UserUtil; -import cyder.utils.ColorUtil; -import cyder.utils.ImageUtil; -import cyder.utils.OsUtil; -import cyder.utils.StaticUtil; - -import javax.imageio.ImageIO; -import javax.swing.*; -import java.awt.*; -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; -import java.awt.event.WindowAdapter; -import java.awt.event.WindowEvent; -import java.awt.image.BufferedImage; -import java.io.File; -import java.util.ArrayList; -import java.util.Optional; - -/** - * A painting widget, not currently intended to be able to edit/markup images. - */ -@Vanilla -@CyderAuthor -public final class PaintWidget { - /** - * The length of the frame. - */ - public static final int frameLength = 800; - - /** - * The master painting frame. - */ - private static CyderFrame paintFrame; - - /** - * The painting grid. - */ - private static CyderGrid cyderGrid; - - /** - * The button for selecting a region. - */ - private static CyderIconButton selectionTool; - - /** - * The button for selecting a color. - */ - private static CyderIconButton selectColor; - - /** - * Suppress default constructor. - */ - private PaintWidget() { - throw new IllegalMethodException(CyderStrings.ATTEMPTED_INSTANTIATION); - } - - /** - * ShowGUI method standard. - */ - @Widget(triggers = {"paint", "draw"}, description = "A painting widget") - public static void showGui() { - UiUtil.closeIfOpen(paintFrame); - - paintFrame = new CyderFrame.Builder() - .setWidth(frameLength) - .setHeight(frameLength) - .setTitle("Paint") - .build(); - paintFrame.setBackground(CyderIcons.defaultBackgroundLarge); - paintFrame.addPreCloseAction(() -> UiUtil.closeIfOpen(paintControlsFrame)); - paintFrame.addWindowListener(new WindowAdapter() { - @Override - public void windowClosing(WindowEvent e) { - UiUtil.closeIfOpen(paintControlsFrame); - } - - @Override - public void windowClosed(WindowEvent e) { - UiUtil.closeIfOpen(paintControlsFrame); - } - }); - - cyderGrid = new CyderGrid(200, frameLength); - cyderGrid.setBounds(0, CyderDragLabel.DEFAULT_HEIGHT - 5, frameLength, frameLength); - paintFrame.getContentPane().add(cyderGrid); - cyderGrid.setResizable(true); - cyderGrid.setDrawGridLines(false); - cyderGrid.installClickListener(); - cyderGrid.installDragListener(); - cyderGrid.setSmoothScrolling(true); - cyderGrid.setDrawWidth(DEFAULT_BRUSH_WIDTH); - cyderGrid.setNodeColor(currentPaintColor); - - paintFrame.setMenuButtonShown(true); - paintFrame.addMenuItem("Export PNG", () -> CyderThreadRunner.submit(() -> { - if (cyderGrid.getGridNodes().isEmpty()) { - paintFrame.notify("Please place at least one node before saving"); - return; - } - - String filename = FileUtil.constructUniqueName(new File("image" + Extension.PNG.getExtension()), - OsUtil.buildFile(Dynamic.PATH, Dynamic.USERS.getFileName(), - Console.INSTANCE.getUuid(), UserFile.FILES.getName())); - - Optional optionalFilename = GetterUtil.getInstance().getInput( - new GetInputBuilder("Filename", "Enter the filename to save the image as") - .setRelativeTo(paintFrame) - .setInitialFieldText(filename) - .setSubmitButtonText("Save Image")); - if (optionalFilename.isEmpty()) return; - filename = optionalFilename.get(); - - if (!filename.endsWith(Extension.PNG.getExtension())) { - filename += Extension.PNG.getExtension(); - } - - if (FileUtil.isValidFilename(filename)) { - BufferedImage image = new BufferedImage(cyderGrid.getNodeDimensionLength(), - cyderGrid.getNodeDimensionLength(), BufferedImage.TYPE_INT_ARGB); - - Graphics2D g2d = (Graphics2D) image.getGraphics(); - - for (GridNode node : cyderGrid.getGridNodes()) { - g2d.setColor(node.getColor()); - g2d.fillRect(node.getX(), node.getY(), 1, 1); - } - - try { - File referenceFile = UserUtil.createFileInUserSpace(filename); - ImageIO.write(image, Extension.PNG.getExtensionWithoutPeriod(), referenceFile); - - paintFrame.notify(new NotificationBuilder( - "Successfully saved grid as " + CyderStrings.quote + filename - + CyderStrings.quote + " to your Files/ directory. Click me to view it") - .setOnKillAction(() -> ImageViewer.getInstance(referenceFile).showGui())); - } catch (Exception exception) { - ExceptionHandler.handle(exception); - paintFrame.notify("Could not save image at this time"); - } - } else { - paintFrame.notify("Sorry, but " + CyderStrings.quote - + filename + CyderStrings.quote + " is not a valid filename"); - } - }, "Paint Grid Exporter")); - paintFrame.addMenuItem("Layer Image", () -> CyderThreadRunner.submit(() -> { - try { - Optional optionalFile = GetterUtil.getInstance().getFile( - new GetFileBuilder("Layer Image").setRelativeTo(paintFrame)); - if (optionalFile.isEmpty()) return; - File chosenImage = optionalFile.get(); - - if (FileUtil.validateExtension(chosenImage, FileUtil.SUPPORTED_IMAGE_EXTENSIONS)) { - // todo layer image logic - } else { - paintFrame.notify("Image type not supported"); - } - } catch (Exception e) { - ExceptionHandler.handle(e); - paintFrame.notify("Could not add image at this time"); - } - }, "Paint Grid Image Layerer")); - paintFrame.addMenuItem("Pixelate", () -> CyderThreadRunner.submit(() -> { - try { - Optional optionalPixelSizeString = GetterUtil.getInstance().getInput( - new GetInputBuilder("Pixelator", "Enter the pixel size") - .setFieldHintText("Pixel size") - .setRelativeTo(paintFrame) - .setSubmitButtonText("Pixelate Grid") - .setInitialFieldText(String.valueOf(1))); - if (optionalPixelSizeString.isEmpty()) return; - String pixelSizeString = optionalPixelSizeString.get(); - - int pixelSize = Integer.parseInt(pixelSizeString); - if (pixelSize == 1) return; - // todo pixelate image - } catch (Exception e) { - ExceptionHandler.handle(e); - paintFrame.notify("Could not pixelate image at this time"); - } - }, "Paint Grid Pixelator")); - paintFrame.addMenuItem("Scale", () -> CyderThreadRunner.submit(() -> { - try { - Optional optionalDimension = GetterUtil.getInstance().getInput( - new GetInputBuilder("Enter length", "Enter the canvas length") - .setFieldHintText("Grid Length") - .setRelativeTo(paintFrame) - .setSubmitButtonText("Scale grid") - .setInitialFieldText(String.valueOf(cyderGrid.getNodeDimensionLength()))); - if (optionalDimension.isEmpty()) return; - String dimension = optionalDimension.get(); - - int dimensionInt = Integer.parseInt(dimension); - - if (dimensionInt > 0) { - // create reference image to resize - BufferedImage referenceImage = new BufferedImage(cyderGrid.getNodeDimensionLength(), - cyderGrid.getNodeDimensionLength(), BufferedImage.TYPE_INT_ARGB); - - Graphics2D g2d = (Graphics2D) referenceImage.getGraphics(); - - for (GridNode node : cyderGrid.getGridNodes()) { - g2d.setColor(node.getColor()); - g2d.fillRect(node.getX(), node.getY(), 1, 1); - } - - double scaler = (double) dimensionInt / cyderGrid.getNodeDimensionLength(); - int len = (int) (scaler * referenceImage.getWidth()); - - BufferedImage newStateImage = ImageUtil.resizeImage( - referenceImage, BufferedImage.TYPE_INT_ARGB, len, len); - - cyderGrid.setNodeDimensionLength(len); - - ArrayList newState = new ArrayList<>(); - - for (int x = 0 ; x < newStateImage.getWidth() ; x++) { - for (int y = 0 ; y < newStateImage.getHeight() ; y++) { - int color = newStateImage.getRGB(x, y); - - // if alpha is empty, don't copy over - if (((color >> 24) & 0xFF) == 0) - continue; - - newState.add(new GridNode(new Color( - (color >> 16) & 0xFF, - (color >> 8) & 0xFF, - color & 0xFF), x, y)); - } - } - - cyderGrid.setGridState(newState); - } else { - paintFrame.notify("Invalid dimensional length"); - } - } catch (Exception e) { - ExceptionHandler.handle(e); - } - }, "Paint Grid Scaler")); - paintFrame.addMenuItem("Controls", PaintWidget::installControlFrames); - - installControlFrames(); - } - - /** - * Returns the aspect ratio of the provided buffered image. - * - * @param img the image to find the aspect ratio of - * @return the aspect ratio of the provided buffered image - */ - private static double getAspectRatio(BufferedImage img) { - return ((double) img.getWidth() / (double) img.getHeight()); - } - - /** - * The controls frame. - */ - private static CyderFrame paintControlsFrame; - - /** - * The list of recently used colors. - */ - private static ArrayList recentColors; - - /** - * The custom component with an overridden paint component. - */ - private static JLabel recentColorsBlock; - - /** - * The current color. - */ - private static Color currentPaintColor = CyderColors.regularPink; - - /** - * The hex field that displays the current color value. - */ - private static CyderTextField colorHexField; - - /** - * The add nodes checkbox. - */ - private static CyderCheckbox addNodesCheckbox; - - /** - * Opens the paint controls frame. - */ - private static void installControlFrames() { - UiUtil.closeIfOpen(paintControlsFrame); - - recentColors = new ArrayList<>(); - - paintControlsFrame = new CyderFrame.Builder() - .setWidth(frameLength) - .setHeight(230) - .setTitle("Paint Controls") - .build(); - paintControlsFrame.setResizable(true); - paintControlsFrame.setShouldFastClose(true); - - CyderGridLayout parentLayout = new CyderGridLayout(1, 2); - - CyderGridLayout topLayout = new CyderGridLayout(5, 1); - CyderPanel topLayoutPanel = new CyderPanel(topLayout); - parentLayout.addComponent(topLayoutPanel, 0, 0); - - CyderGridLayout bottomLayout = new CyderGridLayout(6, 1); - CyderPanel bottomLayoutPanel = new CyderPanel(bottomLayout); - parentLayout.addComponent(bottomLayoutPanel, 0, 1); - - // vars used for drawing custom component - final int colorRows = 2; - final int colorsPerRow = 6; - final int colorBlockLen = 20; - final int padding = 5; - - recentColorsBlock = new JLabel() { - @Override - protected void paintComponent(Graphics g) { - g.translate(0, 10); - g.setColor(Color.BLACK); - g.fillRect(0, 0, colorsPerRow * colorBlockLen + 2 * padding, 50); - g.setColor(CyderColors.vanilla); - g.fillRect(padding, padding, colorsPerRow * colorBlockLen, 40); - - int numColorsPainted = 0; - int currentX = padding; - int currentY = padding; - - // paint 10 colors at most - while (numColorsPainted < Math.min(colorRows * colorsPerRow, recentColors.size())) { - g.setColor(recentColors.get(recentColors.size() - numColorsPainted - 1)); - g.fillRect(currentX, currentY, colorBlockLen, colorBlockLen); - - currentX += colorBlockLen; - - if (currentX >= padding + colorsPerRow * colorBlockLen) { - currentX = padding; - currentY += colorBlockLen; - } - - numColorsPainted++; - } - - // draw sep lines between colors - g.setColor(Color.BLACK); - // horizontal lines - for (int i = 0 ; i < colorRows ; i++) { - g.drawLine(0, padding + colorBlockLen, - 2 * padding + colorsPerRow * colorBlockLen, padding + colorBlockLen); - } - // vertical lines - for (int i = 0 ; i < colorsPerRow ; i++) { - g.drawLine(padding + i * colorBlockLen, 0, padding + i * colorBlockLen, - 2 * padding + colorRows * colorBlockLen); - } - } - }; - recentColorsBlock.setSize(130, 60); - recentColorsBlock.addMouseListener(new MouseAdapter() { - @Override - public void mouseClicked(MouseEvent e) { - int x = e.getX(); - int y = e.getY(); - - // sub padding from both - x -= padding; - y -= padding; - - // figure out grid points - int xGrid = x / colorBlockLen; - int yGrid = y / colorBlockLen; - int revIndex = xGrid + yGrid * colorsPerRow; - - // make sure in bounds - if (recentColors.size() < 1 + revIndex) - return; - - // get clicked color and set - setNewPaintColor(recentColors.get(recentColors.size() - 1 - revIndex)); - } - }); - topLayout.addComponent(recentColorsBlock, 0, 0); - - colorHexField = new CyderTextField(11); - colorHexField.setHorizontalAlignment(JTextField.CENTER); - colorHexField.setToolTipText("Format: 45FF00 for hex or 255,255,255 for rgb"); - colorHexField.setBounds(0, 40, 110, 40); - colorHexField.setKeyEventRegexMatcher(CyderRegexPatterns.rgbOrHex.pattern()); - colorHexField.addActionListener(e -> { - String text = colorHexField.getText(); - - if (text.contains(CyderStrings.comma)) { - String[] parts = text.split(CyderStrings.comma); - - if (parts.length != 3) { - paintControlsFrame.notify("Could not parse color"); - } else { - try { - int r = Integer.parseInt(parts[0]); - int g = Integer.parseInt(parts[1]); - int b = Integer.parseInt(parts[2]); - - Color newColor = new Color(r, g, b); - setNewPaintColor(newColor); - } catch (Exception ignored) { - paintControlsFrame.notify("Could not parse color"); - } - } - } else { - try { - Color newColor = ColorUtil.hexStringToColor(colorHexField.getText()); - setNewPaintColor(newColor); - } catch (Exception ignored) { - paintControlsFrame.notify("Could not parse color"); - } - } - }); - colorHexField.setText(ColorUtil.toRgbHexString(currentPaintColor)); - - CyderGridLayout innerLayout = new CyderGridLayout(1, 2); - - CyderLabel colorTextLabel = new CyderLabel("New Color"); - colorTextLabel.setBounds(5, 5, 100, 40); - innerLayout.addComponent(colorTextLabel, 0, 0); - - innerLayout.addComponent(colorHexField, 0, 1); - CyderPanel innerPanel = new CyderPanel(innerLayout); - - topLayout.addComponent(innerPanel, 1, 0); - - JLabel historyLabel = new JLabel(); - historyLabel.setSize(120, 100); - - CyderLabel undoLabel = new CyderLabel("Undo"); - undoLabel.setBounds(5, 5, 50, 30); - historyLabel.add(undoLabel); - - CyderLabel redoLabel = new CyderLabel("Redo"); - redoLabel.setBounds(5 + 52 + 10, 5, 50, 30); - historyLabel.add(redoLabel); - - ImageIcon undoDefault = StaticUtil.getImageIcon("undo.png"); - ImageIcon undoHoverAndFocus = StaticUtil.getImageIcon("undo_hover.png"); - CyderIconButton undo = new CyderIconButton.Builder("Undo", undoDefault, undoHoverAndFocus) - .setClickAction(() -> { - cyderGrid.backwardState(); - cyderGrid.revalidate(); - cyderGrid.repaint(); - paintFrame.revalidate(); - paintFrame.repaint(); - }).build(); - undo.setLocation(5, 100 - 60); - historyLabel.add(undo); - - ImageIcon redoDefault = StaticUtil.getImageIcon("redo.png"); - ImageIcon redoFocusHover = StaticUtil.getImageIcon("redo_hover.png"); - CyderIconButton redo = new CyderIconButton.Builder("Redo", redoDefault, redoFocusHover) - .setClickAction(() -> { - cyderGrid.forwardState(); - cyderGrid.revalidate(); - cyderGrid.repaint(); - paintFrame.revalidate(); - paintFrame.repaint(); - }).build(); - redo.setLocation(5 + 52 + 10, 100 - 60); - historyLabel.add(redo); - - topLayout.addComponent(historyLabel, 2, 0); - - JLabel checkBoxLabel = new JLabel(); - checkBoxLabel.setSize(120, 100); - - CyderCheckboxGroup group = new CyderCheckboxGroup(); - - CyderLabel addLabel = new CyderLabel("Add"); - addLabel.setBounds(5, 5, 50, 30); - checkBoxLabel.add(addLabel); - - addNodesCheckbox = new CyderCheckbox(); - addNodesCheckbox.setToolTipText("Paint cells"); - addNodesCheckbox.setBounds(5, 100 - 55, 50, 50); - addNodesCheckbox.addMouseListener(new MouseAdapter() { - @Override - public void mouseClicked(MouseEvent e) { - cyderGrid.setMode(CyderGrid.Mode.ADD); - } - }); - group.addCheckbox(addNodesCheckbox); - addNodesCheckbox.setChecked(); - checkBoxLabel.add(addNodesCheckbox); - - CyderLabel deleteLabel = new CyderLabel("Delete"); - deleteLabel.setBounds(5 + 50 + 10, 5, 50, 30); - checkBoxLabel.add(deleteLabel); - - CyderCheckbox delete = new CyderCheckbox(); - delete.setBounds(5 + 50 + 10, 100 - 55, 50, 50); - delete.setToolTipText("Delete cells"); - delete.addMouseListener(new MouseAdapter() { - @Override - public void mouseClicked(MouseEvent e) { - cyderGrid.setMode(CyderGrid.Mode.DELETE); - } - }); - group.addCheckbox(delete); - checkBoxLabel.add(delete); - - topLayout.addComponent(checkBoxLabel, 3, 0); - - JLabel sliderLabel = new JLabel(); - sliderLabel.setSize(140, 80); - - brushWidth = DEFAULT_BRUSH_WIDTH; - - CyderLabel brushLabel = new CyderLabel("Brush width: " + brushWidth); - brushLabel.setBounds(10, 5, 120, 40); - sliderLabel.add(brushLabel); - - JSlider brushWidthSlider = new JSlider(JSlider.HORIZONTAL, MIN_BRUSH_WIDTH, - MAX_BRUSH_WIDTH, brushWidth); - CyderSliderUi UI = new CyderSliderUi(brushWidthSlider); - UI.setThumbStroke(new BasicStroke(2.0f)); - UI.setThumbShape(ThumbShape.RECTANGLE); - UI.setThumbFillColor(Color.black); - UI.setThumbOutlineColor(CyderColors.navy); - UI.setRightThumbColor(CyderColors.regularBlue); - UI.setLeftThumbColor(CyderColors.regularPink); - UI.setTrackStroke(new BasicStroke(3.0f)); - brushWidthSlider.setUI(UI); - brushWidthSlider.setSize(250, 40); - brushWidthSlider.setPaintTicks(false); - brushWidthSlider.setPaintLabels(false); - brushWidthSlider.setVisible(true); - brushWidthSlider.setValue(brushWidth); - brushWidthSlider.addChangeListener(e -> { - int newWidth = brushWidthSlider.getValue(); - brushWidth = newWidth; - brushLabel.setText("Brush width: " + newWidth); - cyderGrid.setDrawWidth(brushWidth); - }); - brushWidthSlider.setOpaque(false); - brushWidthSlider.setToolTipText("Brush Width"); - brushWidthSlider.setFocusable(false); - brushWidthSlider.repaint(); - brushWidthSlider.setBounds(10, 40, 120, 40); - sliderLabel.add(brushWidthSlider); - - topLayout.addComponent(sliderLabel, 4, 0); - - ImageIcon selectionDefault = StaticUtil.getImageIcon("select.png"); - ImageIcon selectionHoverFocus = StaticUtil.getImageIcon("select_hover.png"); - selectionTool = new CyderIconButton.Builder("Select Region", - selectionDefault, selectionHoverFocus) - .setClickAction(PaintWidget::toggleSelectionMode).setToggleButton(true).build(); - selectionTool.setSize(50, 50); - bottomLayout.addComponent(selectionTool, 0, 0); - - ImageIcon cropDefault = StaticUtil.getImageIcon("crop.png"); - ImageIcon cropHoverFocus = StaticUtil.getImageIcon("crop_hover.png"); - CyderIconButton cropToRegion = new CyderIconButton.Builder("Crop Region", cropDefault, - cropHoverFocus).setClickAction(() -> cyderGrid.cropToSelectedRegion()).build(); - bottomLayout.addComponent(cropToRegion, 1, 0); - - ImageIcon cutDefault = StaticUtil.getImageIcon("cut.png"); - ImageIcon cutHoverFocus = StaticUtil.getImageIcon("cut_hover.png"); - CyderIconButton deleteRegion = new CyderIconButton.Builder("Cut Region", - cutDefault, cutHoverFocus).setClickAction(() -> cyderGrid.deleteSelectedRegion()).build(); - bottomLayout.addComponent(deleteRegion, 2, 0); - - ImageIcon selectColorDefault = StaticUtil.getImageIcon("select_color.png"); - ImageIcon selectColorHoverFocus = StaticUtil.getImageIcon("select_color_hover.png"); - selectColor = new CyderIconButton.Builder("Select Color", selectColorDefault, selectColorHoverFocus) - .setClickAction(PaintWidget::toggleColorSelection).setToggleButton(true).build(); - bottomLayout.addComponent(selectColor, 3, 0); - - ImageIcon rotateDefault = StaticUtil.getImageIcon("rotate.png"); - ImageIcon rotateHoverFocus = StaticUtil.getImageIcon("rotate_hover.png"); - CyderIconButton rotate = new CyderIconButton.Builder("Rotate Region", rotateDefault, - rotateHoverFocus).setClickAction(() -> cyderGrid.rotateRegion()).build(); - bottomLayout.addComponent(rotate, 4, 0); - - // selection region reflecting - ImageIcon reflectDefault = StaticUtil.getImageIcon("reflect.png"); - ImageIcon reflectHoverFocus = StaticUtil.getImageIcon("reflect_hover.png"); - CyderIconButton reflect = new CyderIconButton.Builder("Reflect Region", reflectDefault, - reflectHoverFocus).setClickAction(() -> cyderGrid.reflectRegionHorizontally()).build(); - bottomLayout.addComponent(reflect, 5, 0); - - // use master layout as content pane - CyderPanel panel = new CyderPanel(parentLayout); - paintControlsFrame.setCyderLayoutPanel(panel); - - // init resizing since we can due to the layout - paintControlsFrame.initializeResizing(); - paintControlsFrame.setResizable(true); - paintControlsFrame.setMinimumSize(paintControlsFrame.getSize()); - paintControlsFrame.setMaximumSize(new Dimension( - (int) (paintControlsFrame.getWidth() * 1.5), - paintControlsFrame.getHeight())); - paintControlsFrame.setBackgroundResizing(true); - - installDefaultPaintColors(); - - Rectangle screen = Console.INSTANCE.getConsoleCyderFrame().getMonitorBounds(); - int x = (int) (screen.getX() + (screen.getWidth() - paintControlsFrame.getWidth()) / 2); - int y = (int) (screen.getHeight() - paintControlsFrame.getHeight() - - UiUtil.getWindowsTaskbarHeight()); - - paintControlsFrame.setLocation(x, y); - paintControlsFrame.setVisible(true); - paintControlsFrame.setFrameType(FrameType.POPUP); - - if (paintFrame.isVisible()) return; - - paintFrame.setLocation(x, y - paintFrame.getHeight()); - paintFrame.setVisible(true); - } - - /** - * The default pallet colors. - */ - private static final ImmutableList defaultPallet = ImmutableList.of( - CyderColors.navy, - CyderColors.regularPink, - CyderColors.regularOrange, - CyderColors.regularGreen, - CyderColors.regularBlue, - CyderColors.regularPurple - ); - - /** - * Sets up the default paint colors in the pallet. - */ - private static void installDefaultPaintColors() { - for (Color paintColor : defaultPallet) { - setNewPaintColor(paintColor); - } - } - - /** - * The default brush width. - */ - public static final int DEFAULT_BRUSH_WIDTH = 2; - - /** - * The maximum brush width. - */ - public static final int MAX_BRUSH_WIDTH = 20; - - /** - * The minimum brush width. - */ - public static final int MIN_BRUSH_WIDTH = 1; - - /** - * The default brush width. - */ - private static int brushWidth = DEFAULT_BRUSH_WIDTH; - - /** - * Sets the current color and updates the recent colors block - * - * @param newColor the new color - */ - public static void setNewPaintColor(Color newColor) { - // if no change, ignore - if (newColor.equals(currentPaintColor)) - return; - - // set the current paint - currentPaintColor = newColor; - - // update the hex field with our current color - if (colorHexField != null) - colorHexField.setText(ColorUtil.toRgbHexString(newColor)); - - // ensure if list contains color, it's pulled to the front - // of recent colors and is not duplicated in the list - if (recentColors.contains(newColor)) { - ArrayList newRecentColors = new ArrayList<>(); - - // add all colors that aren't the new one, remove possible duplicates somehow - for (Color recentColor : recentColors) { - if (!recentColor.equals(newColor) && !newRecentColors.contains(recentColor)) - newRecentColors.add(recentColor); - } - - // add the new one to the end - newRecentColors.add(newColor); - - // set recent colors to new object - recentColors = newRecentColors; - } else { - recentColors.add(newColor); - } - - // repaint block to update colors - recentColorsBlock.repaint(); - - // set grid's paint - cyderGrid.setNodeColor(currentPaintColor); - - // ensure everything reflects the mode being adding cells - resetToAdding(); - selectionTool.reset(); - } - - /** - * Handles the button press for selection mode. - */ - private static void toggleSelectionMode() { - CyderGrid.Mode newMode = cyderGrid.getMode() == CyderGrid.Mode.SELECTION - ? CyderGrid.Mode.ADD : CyderGrid.Mode.SELECTION; - - resetToAdding(); - - if (newMode == CyderGrid.Mode.SELECTION) { - paintFrame.setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR)); - cyderGrid.setMode(CyderGrid.Mode.SELECTION); - } else { - paintFrame.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); - - if (addNodesCheckbox.isEnabled()) { - cyderGrid.setMode(CyderGrid.Mode.ADD); - } else { - cyderGrid.setMode(CyderGrid.Mode.DELETE); - } - } - - Toolkit.getDefaultToolkit().sync(); - } - - /** - * The icon used for color selection mode - */ - private static final ImageIcon colorSelectionIcon = StaticUtil.getImageIcon("select_color.png"); - - /** - * The cursor used when color selection is toggled on. - */ - private static final Cursor eyedropperCursor = Toolkit.getDefaultToolkit() - .createCustomCursor(colorSelectionIcon.getImage(), new Point(0, 30), "eyedropper"); - - /** - * Toggles between states for color mode selection. - */ - private static void toggleColorSelection() { - CyderGrid.Mode newMode = cyderGrid.getMode() == CyderGrid.Mode.COLOR_SELECTION - ? CyderGrid.Mode.ADD : CyderGrid.Mode.COLOR_SELECTION; - - resetToAdding(); - selectionTool.reset(); - - if (newMode == CyderGrid.Mode.COLOR_SELECTION) { - cyderGrid.setMode(newMode); - paintFrame.setCursor(eyedropperCursor); - } else { - paintFrame.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); - - if (addNodesCheckbox.isEnabled()) { - cyderGrid.setMode(CyderGrid.Mode.ADD); - } else { - cyderGrid.setMode(CyderGrid.Mode.DELETE); - } - } - - Toolkit.getDefaultToolkit().sync(); - } - - /** - * Resets all icons to their default state, the mode to adding, - * and refreshes the checkboxes. - */ - private static void resetToAdding() { - // refresh add/delete buttons - addNodesCheckbox.setChecked(); - - // de-select toggle-able buttons - selectColor.setIcon(StaticUtil.getImageIcon("select_color.png")); - - // reset grid mode - cyderGrid.setMode(CyderGrid.Mode.ADD); - - // reset cursor - paintFrame.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); - } -} diff --git a/src/main/java/cyder/widgets/PathFinderWidget.java b/src/main/java/cyder/widgets/PathFinderWidget.java deleted file mode 100644 index 707895a05..000000000 --- a/src/main/java/cyder/widgets/PathFinderWidget.java +++ /dev/null @@ -1,1400 +0,0 @@ -package cyder.widgets; - -import cyder.annotations.CyderAuthor; -import cyder.annotations.ForReadability; -import cyder.annotations.Vanilla; -import cyder.annotations.Widget; -import cyder.constants.CyderColors; -import cyder.exceptions.IllegalMethodException; -import cyder.handlers.internal.ExceptionHandler; -import cyder.math.NumberUtil; -import cyder.strings.CyderStrings; -import cyder.threads.CyderThreadRunner; -import cyder.threads.ThreadUtil; -import cyder.ui.UiUtil; -import cyder.ui.button.CyderButton; -import cyder.ui.drag.CyderDragLabel; -import cyder.ui.frame.CyderFrame; -import cyder.ui.grid.CyderGrid; -import cyder.ui.grid.GridNode; -import cyder.ui.label.CyderLabel; -import cyder.ui.selection.CyderCheckbox; -import cyder.ui.selection.CyderCheckboxGroup; -import cyder.ui.selection.CyderSwitch; -import cyder.ui.selection.CyderSwitchState; -import cyder.ui.slider.CyderSliderUi; -import cyder.ui.slider.ThumbShape; - -import javax.swing.*; -import java.awt.*; -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; -import java.awt.event.MouseListener; -import java.util.ArrayList; -import java.util.Comparator; -import java.util.Optional; -import java.util.PriorityQueue; -import java.util.concurrent.Semaphore; -import java.util.concurrent.atomic.AtomicBoolean; - -/** - * A pathfinding widget to visualize Dijkstra's path finding algorithm and the A* algorithm - * with Euclidean distance and Manhattan distance as valid A* heuristics. - */ -@Vanilla -@CyderAuthor -public final class PathFinderWidget { - /** - * The pathfinding frame. - */ - private static CyderFrame pathFindingFrame; - - /** - * The grid component for the visualization. - */ - private static CyderGrid pathfindingGrid; - - /** - * The default number of nodes for the path grid. - */ - private static final int DEFAULT_NODES = 25; - - /** - * The maximum number of nodes for the path grid. - */ - private static final int MAX_NODES = 100; - - /** - * The checkbox dictating whether to perform an animation of the - * A* algorithm or instantly solve the problem if possible. - */ - private static CyderCheckbox showStepsBox; - - /** - * The checkbox dictating whether pathing to a diagonal neighbor is allowable. - */ - private static CyderCheckbox diagonalBox; - - /** - * The checkbox dictating whether the grid mode is ADD or DELETE. - */ - private static CyderCheckbox deleteWallsCheckBox; - - /** - * The checkbox to place the starting node. - */ - private static CyderCheckbox placeStartBox; - - /** - * The checkbox to place the goal node. - */ - private static CyderCheckbox placeGoalBox; - - /** - * The checkbox dictating whether to draw grid lines on the grid. - */ - private static CyderCheckbox drawGridLinesBox; - - /** - * The heuristic switcher to switch between Euclidean and - * Manhattan distances as heuristics for A*. - */ - private static CyderSwitch heuristicSwitch; - - /** - * The off text for the heuristic switch. - */ - private static final String HEURISTIC_OFF = "Manhattan"; - - /** - * The on text for the heuristic switch. - */ - private static final String HEURISTIC_ON = "Euclidean"; - - /** - * The algorithm switcher to switch between A* and Dijkstra's. - */ - private static CyderSwitch algorithmSwitch; - - /** - * The text to use for the algorithm OFF state. - */ - private static final String ALGORITHM_OFF = "A*"; - - /** - * The text to use for the algorithm ON state. - */ - private static final String ALGORITHM_ON = "Dijkstras"; - - /** - * The button to start/pause the animation. - */ - private static CyderButton startPauseButton; - - /** - * The slider to determine the speed of the animation. - */ - private static JSlider speedSlider; - - /** - * The maximum slider value. - */ - private static final int MAX_SLIDER_VALUE = 100; - - /** - * The minimum slider value. - */ - private static final int MIN_SLIDER_VALUE = 1; - - /** - * The default slider value in between the min and max values. - */ - private static final int DEFAULT_SLIDER_VALUE = (MIN_SLIDER_VALUE + MAX_SLIDER_VALUE) / 2; - - /** - * The timeout in ms between the path animation refresh. - */ - private static final int PATH_TRICKLE_TIMEOUT = 30; - - /** - * The current state of the A* algorithm. - */ - private static PathingState currentPathingState = PathingState.NOT_STARTED; - - /** - * The valid states of the A* algorithm. - */ - private enum PathingState { - /** - * The algorithm is finished and found a path. - */ - PATH_FOUND("Path found"), - /** - * The algorithm is finished but no path was found. :( - */ - PATH_NOT_FOUND("No path found"), - /** - * The algorithm is incomplete and may be resumed. - */ - PAUSED("Paused"), - /** - * The algorithm has not yet begun (Widget just opened or reset invoked). - */ - NOT_STARTED("Not Started"), - /** - * The algorithm is currently underway, whether this be the first time it - * has begun, or the 1000th time it has been paused and resumed. - */ - RUNNING("Running..."); - - /** - * The state label text for this pathing state. - */ - private final String stateLabelText; - - PathingState(String stateLabelText) { - this.stateLabelText = stateLabelText; - } - - /** - * Returns the state label text for this pathing state. - * - * @return the state label text for this pathing state - */ - public String getStateLabelText() { - return stateLabelText; - } - } - - /** - * The label to display the current state on. - */ - private static CyderLabel currentStateLabel; - - /** - * The color used for pathable nodes in the open list - */ - private static final Color pathableOpenColor = new Color(254, 104, 88); - - /** - * The color used for pathable nodes that have been removed from the open list. - */ - private static final Color pathableClosedColor = new Color(121, 236, 135); - - /** - * The color used for walls. - */ - private static final Color wallsColor = CyderColors.navy; - - /** - * The color used for the goal node. - */ - private static final Color goalNodeColor = CyderColors.regularOrange; - - /** - * The color used for the start node. - */ - private static final Color startNodeColor = CyderColors.regularPink; - - /** - * The node which the pathfinding starts from. - * By default this is the top left corner (0,0). - */ - private static PathNode startNode; - - /** - * The node which A* attempts to path to. - * By default this is the bottom right corner (DEFAULT_NODES - 1, DEFAULT_NODES - 1). - */ - private static PathNode goalNode; - - /** - * The default point the starting node is placed at. - */ - private static final Point DEFAULT_START_POINT = new Point(0, 0); - - /** - * The default point the goal node is placed at. - */ - private static final Point DEFAULT_GOAL_POINT = new Point(DEFAULT_NODES - 1, DEFAULT_NODES - 1); - - /** - * The font to use for the state label. - */ - private static final Font STATE_LABEL_FONT = new Font("Agency FB", Font.BOLD, 40); - - /** - * The nodes which may be pathed through on the current grid state. - */ - private static final ArrayList pathableNodes = new ArrayList<>(); - - /** - * The nodes which are in the queue to be pathed through. - */ - private static final PriorityQueue openNodes = new PriorityQueue<>(new NodeComparator()); - - /** - * The current path animation object. - * This is always killed before being set to a new object, - * similar to how things are handled in AudioPlayer. - */ - private static PathTrickleAnimator currentPathAnimator; - - /** - * The path solver thread name. - */ - private static final String PATH_SOLVING_THREAD_NAME = "Path Solver"; - - /** - * The semaphore used to achieve thread safety when - * adding/removing to/from the grid and repainting. - */ - private static final Semaphore semaphore = new Semaphore(1); - - /** - * The heuristic value for A* to be logically equivalent to Dijkstra's. - */ - private static final int DIJKSTRA_HEURISTIC = 1; - - /** - * The width of the frame. - */ - private static final int FRAME_WIDTH = 1000; - - /** - * The height of the frame. - */ - private static final int FRAME_HEIGHT = 1070; - - /** - * The widget title. - */ - private static final String TITLE = "Pathfinding Visualizer"; - - /** - * The length of the grid component. - */ - private static final int gridComponentLength = 800; - - /** - * The text for the start button. - */ - private static final String START = "Start"; - - /** - * The text for the reset button. - */ - private static final String RESET = "Reset"; - - /** - * The text for the stop button. - */ - private static final String STOP = "Stop"; - - /** - * The state label string prefix. - */ - private static final String STATE = "State:"; - - /** - * The text for the resume button. - */ - private static final String RESUME = "Resume"; - - /** - * Suppress default constructor. - */ - private PathFinderWidget() { - throw new IllegalMethodException(CyderStrings.ATTEMPTED_INSTANTIATION); - } - - @Widget(triggers = {"path", "pathfinder", "A*"}, - description = "A pathfinding visualizer for A* and Dijkstras algorithms") - public static void showGui() { - UiUtil.closeIfOpen(pathFindingFrame); - - pathFindingFrame = new CyderFrame.Builder() - .setWidth(FRAME_WIDTH) - .setHeight(FRAME_HEIGHT) - .setTitle(TITLE) - .build(); - - pathfindingGrid = new CyderGrid(DEFAULT_NODES, gridComponentLength); - pathfindingGrid.setBounds(100, 80, gridComponentLength, gridComponentLength); - pathfindingGrid.setMinNodes(DEFAULT_NODES); - pathfindingGrid.setMaxNodes(MAX_NODES); - pathfindingGrid.setDrawGridLines(true); - pathfindingGrid.setBackground(CyderColors.vanilla); - pathfindingGrid.setResizable(true); - pathfindingGrid.addOnResizeCallback(() -> { - ArrayList goals = pathfindingGrid.getNodesOfColor(goalNodeColor); - if (goals.size() == 1) { - GridNode localGoal = goals.get(0); - int maxGoalCoordinate = Math.max(localGoal.getX(), localGoal.getY()); - if (maxGoalCoordinate >= pathfindingGrid.getNodeDimensionLength()) { - pathfindingGrid.removeNodesOfColor(goalNodeColor); - } - } - - ArrayList starts = pathfindingGrid.getNodesOfColor(startNodeColor); - if (starts.size() == 1) { - GridNode localStart = starts.get(0); - int maxStartCoordinate = Math.max(localStart.getX(), localStart.getY()); - if (maxStartCoordinate >= pathfindingGrid.getNodeDimensionLength()) { - pathfindingGrid.removeNodesOfColor(startNodeColor); - } - } - }); - pathfindingGrid.setSmoothScrolling(true); - pathFindingFrame.getContentPane().add(pathfindingGrid); - pathfindingGrid.setSaveStates(false); - - currentStateLabel = new CyderLabel(); - currentStateLabel.setFont(STATE_LABEL_FONT); - currentStateLabel.setBounds(40, CyderDragLabel.DEFAULT_HEIGHT, - pathFindingFrame.getWidth() - 80, 50); - pathFindingFrame.getContentPane().add(currentStateLabel); - - int startY = pathfindingGrid.getY() + pathfindingGrid.getHeight(); - int startX = pathfindingGrid.getX(); - - CyderLabel placeStartLabel = new CyderLabel("Start"); - placeStartLabel.setBounds(startX - 50, startY + 5, 150, 40); - pathFindingFrame.getContentPane().add(placeStartLabel); - - placeStartBox = new CyderCheckbox(); - placeStartBox.setToolTipText("Place start node"); - placeStartBox.setBounds(startX, startY + 40, 50, 50); - pathFindingFrame.getContentPane().add(placeStartBox); - placeStartBox.addMouseListener(placeStartBoxMouseListener); - - CyderLabel placeGoalLabel = new CyderLabel("Goal"); - placeGoalLabel.setBounds(startX - 50 + 80, startY + 5, 150, 40); - pathFindingFrame.getContentPane().add(placeGoalLabel); - - placeGoalBox = new CyderCheckbox(); - placeGoalBox.setToolTipText("Place goal node"); - placeGoalBox.setBounds(startX + 80, startY + 40, 50, 50); - pathFindingFrame.getContentPane().add(placeGoalBox); - placeGoalBox.addMouseListener(placeGoalMouseListener); - - CyderCheckboxGroup nodeGroup = new CyderCheckboxGroup(); - nodeGroup.addCheckbox(placeStartBox); - nodeGroup.addCheckbox(placeGoalBox); - - CyderLabel deleteWallsLabel = new CyderLabel("Delete walls"); - deleteWallsLabel.setBounds(startX - 50 + 80 * 2, startY + 5, 150, 40); - pathFindingFrame.getContentPane().add(deleteWallsLabel); - - deleteWallsCheckBox = new CyderCheckbox(); - deleteWallsCheckBox.setToolTipText("Delete Walls"); - deleteWallsCheckBox.setBounds(startX + 80 * 2, startY + 40, 50, 50); - pathFindingFrame.getContentPane().add(deleteWallsCheckBox); - deleteWallsCheckBox.addMouseListener(deleteWallsMouseListener); - - CyderLabel showStepsLabel = new CyderLabel("Steps"); - showStepsLabel.setBounds(startX - 50, startY + 5 + 80, 150, 40); - pathFindingFrame.getContentPane().add(showStepsLabel); - - showStepsBox = new CyderCheckbox(); - showStepsBox.setToolTipText("Show steps"); - showStepsBox.setBounds(startX, startY + 40 + 80, 50, 50); - pathFindingFrame.getContentPane().add(showStepsBox); - - CyderLabel allowDiagonalsLabel = new CyderLabel("Diagonals"); - allowDiagonalsLabel.setBounds(startX - 50 + 80, startY + 5 + 80, 150, 40); - pathFindingFrame.getContentPane().add(allowDiagonalsLabel); - - diagonalBox = new CyderCheckbox(); - diagonalBox.setToolTipText("Allow diagonals"); - diagonalBox.setBounds(startX + 80, startY + 40 + 80, 50, 50); - pathFindingFrame.getContentPane().add(diagonalBox); - - CyderLabel drawGridLinesLabel = new CyderLabel("Grid Lines"); - drawGridLinesLabel.setBounds(startX - 50 + 80 * 2, startY + 5 + 80, 150, 40); - pathFindingFrame.getContentPane().add(drawGridLinesLabel); - - drawGridLinesBox = new CyderCheckbox(); - drawGridLinesBox.setToolTipText("Draw grid lines"); - drawGridLinesBox.setBounds(startX + 80 * 2, startY + 40 + 80, 50, 50); - drawGridLinesBox.addMouseListener(drawGridLinesMouseListener); - pathFindingFrame.getContentPane().add(drawGridLinesBox); - - CyderButton reset = new CyderButton(RESET); - reset.setBounds(350, startY + 40 - 20, 180, 50); - reset.addActionListener(e -> reset()); - pathFindingFrame.getContentPane().add(reset); - - startPauseButton = new CyderButton(START); - startPauseButton.setBounds(350, startY + 40 + 80, 180, 50); - startPauseButton.addActionListener(e -> startPauseButtonAction()); - pathFindingFrame.getContentPane().add(startPauseButton); - - heuristicSwitch = new CyderSwitch(350, 50); - heuristicSwitch.setOffText(HEURISTIC_OFF); - heuristicSwitch.setOnText(HEURISTIC_ON); - heuristicSwitch.setToolTipText("A* Heuristic"); - heuristicSwitch.setBounds(550, startY + 40 - 20, 350, 50); - heuristicSwitch.setButtonPercent(50); - pathFindingFrame.getContentPane().add(heuristicSwitch); - - speedSlider = new JSlider(JSlider.HORIZONTAL, MIN_SLIDER_VALUE, - MAX_SLIDER_VALUE, DEFAULT_SLIDER_VALUE); - CyderSliderUi speedSliderUi = new CyderSliderUi(speedSlider); - speedSliderUi.setThumbStroke(new BasicStroke(2.0f)); - speedSliderUi.setThumbShape(ThumbShape.RECTANGLE); - speedSliderUi.setThumbFillColor(Color.black); - speedSliderUi.setThumbOutlineColor(CyderColors.navy); - speedSliderUi.setRightThumbColor(CyderColors.regularBlue); - speedSliderUi.setLeftThumbColor(CyderColors.regularPink); - speedSliderUi.setTrackStroke(new BasicStroke(3.0f)); - speedSlider.setUI(speedSliderUi); - speedSlider.setBounds(350, startY + 40 + 35, 350 + 180 + 20, 40); - speedSlider.setPaintTicks(false); - speedSlider.setPaintLabels(false); - speedSlider.setVisible(true); - speedSlider.setOpaque(false); - speedSlider.setToolTipText("Pathfinding Speed"); - speedSlider.setFocusable(false); - pathFindingFrame.getContentPane().add(speedSlider); - - algorithmSwitch = new CyderSwitch(350, 50); - algorithmSwitch.setOffText(ALGORITHM_OFF); - algorithmSwitch.setOnText(ALGORITHM_ON); - algorithmSwitch.setToolTipText("Algorithm Switcher"); - algorithmSwitch.setBounds(550, startY + 40 + 80, 350, 50); - algorithmSwitch.setButtonPercent(50); - pathFindingFrame.getContentPane().add(algorithmSwitch); - - reset(); - - pathFindingFrame.finalizeAndShow(); - } - - /** - * The mouse listener for the place start checkbox. - */ - private static final MouseListener placeStartBoxMouseListener = new MouseAdapter() { - @Override - public void mouseClicked(MouseEvent e) { - super.mouseClicked(e); - if (placeStartBox.isChecked()) { - pathfindingGrid.setNodeColor(startNodeColor); - - pathfindingGrid.removeInvokeWhenNodePlacedRunnables(); - pathfindingGrid.invokeWhenNodePlaced(() -> { - pathfindingGrid.removeNodesOfColor(startNodeColor); - - placeStartBox.setNotChecked(); - pathfindingGrid.setNodeColor(wallsColor); - }); - - deleteWallsCheckBox.setNotChecked(); - pathfindingGrid.setMode(CyderGrid.Mode.ADD); - } else { - pathfindingGrid.removeInvokeWhenNodePlacedRunnables(); - pathfindingGrid.setNodeColor(wallsColor); - } - } - }; - - /** - * The mouse listener for the place goal checkbox. - */ - private static final MouseListener placeGoalMouseListener = new MouseAdapter() { - @Override - public void mouseClicked(MouseEvent e) { - super.mouseClicked(e); - if (placeGoalBox.isChecked()) { - pathfindingGrid.setNodeColor(goalNodeColor); - - pathfindingGrid.removeInvokeWhenNodePlacedRunnables(); - pathfindingGrid.invokeWhenNodePlaced(() -> { - pathfindingGrid.removeNodesOfColor(goalNodeColor); - - placeGoalBox.setNotChecked(); - pathfindingGrid.setNodeColor(wallsColor); - }); - - deleteWallsCheckBox.setNotChecked(); - pathfindingGrid.setMode(CyderGrid.Mode.ADD); - } else { - pathfindingGrid.removeInvokeWhenNodePlacedRunnables(); - pathfindingGrid.setNodeColor(wallsColor); - } - } - }; - - /** - * The mouse listener for the delete walls checkbox. - */ - private static final MouseListener deleteWallsMouseListener = new MouseAdapter() { - @Override - public void mouseClicked(MouseEvent e) { - super.mouseClicked(e); - - if (pathfindingGrid.getMode() == CyderGrid.Mode.ADD) { - pathfindingGrid.setMode(CyderGrid.Mode.DELETE); - } else { - pathfindingGrid.setMode(CyderGrid.Mode.ADD); - } - } - }; - - /** - * The mouse listener for the draw grid lines checkbox. - */ - private static final MouseListener drawGridLinesMouseListener = new MouseAdapter() { - @Override - public void mouseClicked(MouseEvent e) { - super.mouseClicked(e); - pathfindingGrid.setDrawGridLines(drawGridLinesBox.isChecked()); - } - }; - - /** - * The actions to invoke when the start/pause button is pressed. - */ - private static void startPauseButtonAction() { - if (pathfindingGrid.getNodesOfColor(startNodeColor).isEmpty()) { - pathFindingFrame.notify("Start node not set"); - return; - } - - if (pathfindingGrid.getNodesOfColor(goalNodeColor).isEmpty()) { - pathFindingFrame.notify("End node not set"); - return; - } - - if (currentPathingState == PathingState.RUNNING) { - currentPathingState = PathingState.PAUSED; - startPauseButton.setText(RESUME); - updateStateLabel(); - enableUiElements(); - return; - } - - disableUiElements(); - startPauseButton.setText(STOP); - - if (currentPathingState == PathingState.PAUSED) { - currentPathingState = PathingState.RUNNING; - startPathStepLoop(); - } else { - searchSetup(); - } - } - - /** - * Performs the setup necessary to start path finding such as - * initializing the lists, finding the start and goal nodes, - * and converting GridNodes to PathNodes. - */ - private static void searchSetup() { - /* - This method is only invoked if start and goal nodes are set. - */ - - endPathAnimator(); - removePathingNodes(); - - ArrayList walls = pathfindingGrid.getNodesOfColor(wallsColor); - - GridNode gridGoal = pathfindingGrid.getNodesOfColor(goalNodeColor).get(0); - goalNode = new PathNode(gridGoal.getX(), gridGoal.getY()); - goalNode.setParent(null); - - GridNode gridStart = pathfindingGrid.getNodesOfColor(startNodeColor).get(0); - startNode = new PathNode(gridStart.getX(), gridStart.getY()); - startNode.setParent(null); - - pathableNodes.clear(); - for (int x = 0 ; x < pathfindingGrid.getNodeDimensionLength() ; x++) { - for (int y = 0 ; y < pathfindingGrid.getNodeDimensionLength() ; y++) { - GridNode node = new GridNode(x, y); - - /* - Ignore walls for pathable nodes. - - Note to maintainers: if future node types are added and should not be pathable, - they should be tested for here. - */ - if (walls.contains(node)) { - continue; - } - - pathableNodes.add(new PathNode(node.getX(), node.getY())); - } - } - - startNode.setG(0); - startNode.setH(heuristic(goalNode)); - - openNodes.clear(); - openNodes.add(startNode); - - currentPathingState = PathingState.RUNNING; - - disableUiElements(); - - pathfindingGrid.setResizable(false); - - startPathStepLoop(); - } - - /** - * Starts the main while loop which takes path steps until a path is - * found or all reachable nodes have been checked. - * All setup of lists must be performed before invoking this method. - */ - private static void startPathStepLoop() { - updateStateLabel(); - - CyderThreadRunner.submit(() -> { - while (currentPathingState == PathingState.RUNNING) { - pathStep(); - - if (showStepsBox.isChecked()) { - lockingRepaintGrid(); - ThreadUtil.sleep(MAX_SLIDER_VALUE - speedSlider.getValue()); - } - } - }, PATH_SOLVING_THREAD_NAME); - } - - /** - * Takes a step towards the goal node according to - * the current heuristic and pathable nodes. - *

- * This is equivalent to what is computed in the primary A* while loop. - * A future feature could be added to allow the algorithm to be - * stepped through via this method. - */ - private static void pathStep() { - if (!openNodes.isEmpty()) { - PathNode min = openNodes.poll(); - - if (min.equals(goalNode)) { - goalNode.setParent(min.getParent()); - - pathFound(); - return; - } - - ArrayList neighbors = new ArrayList<>(); - for (PathNode possibleNeighbor : pathableNodes) { - if (areOrthogonalNeighbors(possibleNeighbor, min) - || (areDiagonalNeighbors(possibleNeighbor, min) && diagonalBox.isChecked())) { - neighbors.add(possibleNeighbor); - } - } - - neighbors.forEach(neighbor -> { - double currentHCost = heuristic(neighbor); - - if (currentHCost < neighbor.getH()) { - neighbor.setH(currentHCost); - neighbor.setParent(min); - neighbor.setG(min.getG() + euclideanDistance(min, neighbor)); - - if (!openNodes.contains(neighbor)) { - openNodes.add(neighbor); - } - } - }); - - // Refresh grid colors based on current state of algorithm and nodes checked. - pathableNodes.stream().filter(pathNode -> !pathNode.equals(startNode) && !pathNode.equals(goalNode)) - .forEach(pathNode -> { - int x = pathNode.getX(); - int y = pathNode.getY(); - - if (openNodes.contains(pathNode)) { - lockingAddNode(new GridNode(pathableOpenColor, x, y)); - } else if (pathNode.getParent() != null) { - lockingAddNode(new GridNode(pathableClosedColor, x, y)); - } - }); - } else { - pathNotFound(); - } - } - - /** - * Performs the actions necessary following a path - * from the start to the goal node being found. - */ - private static void pathFound() { - currentPathingState = PathingState.PATH_FOUND; - - startPauseButton.setText(START); - - enableUiElements(); - updateStateLabel(); - - pathfindingGrid.setResizable(true); - - ArrayList pathForward = new ArrayList<>(); - - PathNode refNode = goalNode.getParent(); - while (refNode != startNode) { - pathForward.add(new Point(refNode.getX(), refNode.getY())); - refNode = refNode.getParent(); - } - - ArrayList pathReversed = new ArrayList<>(); - - for (int i = pathForward.size() - 1 ; i > -1 ; i--) { - pathReversed.add(pathForward.get(i)); - } - - currentPathAnimator = new PathTrickleAnimator(pathReversed); - } - - /** - * A animator class to perform the path found animation. - */ - private static class PathTrickleAnimator { - /** - * The trickle animation thread name. - */ - private static final String PATH_TRICKLE_ANIMATION_THREAD_NAME = "Pathfinding Path Trickle Animator"; - - /** - * The color used for the found path. - */ - private static final Color PATH_COLOR = CyderColors.regularBlue; - - /** - * The color used for the path found animation trickle. - */ - private static final Color PATH_ANIMATION_COLOR = new Color(34, 216, 248); - - /** - * Whether this animation has been killed - */ - private final AtomicBoolean killed = new AtomicBoolean(false); - - /** - * Constructs and starts a new path animator. - * - * @param pathPoints the list of points to animate - */ - public PathTrickleAnimator(ArrayList pathPoints) { - CyderThreadRunner.submit(() -> { - try { - // Draw initial path from start to goal - for (Point pathPoint : pathPoints) { - if (killed.get()) return; - - GridNode updateNode = null; - - for (GridNode node : pathfindingGrid.getGridNodes()) { - if (killed.get()) return; - - if (node.getX() == pathPoint.getX() && node.getY() == pathPoint.getY()) { - updateNode = node; - break; - } - } - - Color color = updateNode != null ? updateNode.getColor() : null; - if (color != null - && (color.equals(PATH_ANIMATION_COLOR) || color.equals(pathableClosedColor))) { - - int x = updateNode.getX(); - int y = updateNode.getY(); - lockingAddNode(new GridNode(PATH_ANIMATION_COLOR, x, y)); - - lockingRepaintGrid(); - ThreadUtil.sleep(PATH_TRICKLE_TIMEOUT); - } - } - - while (true) { - // Trickle from start to goal - for (Point pathPoint : pathPoints) { - if (killed.get()) return; - - Optional overridePoint = pathfindingGrid.getNodeAtPoint(pathPoint); - if (overridePoint.isPresent() - && (overridePoint.get().getColor().equals(PATH_ANIMATION_COLOR) - || overridePoint.get().getColor().equals(PATH_COLOR))) { - int x = (int) pathPoint.getX(); - int y = (int) pathPoint.getY(); - lockingAddNode(new GridNode(PATH_COLOR, x, y)); - - lockingRepaintGrid(); - } - - if (killed.get()) return; - ThreadUtil.sleep(PATH_TRICKLE_TIMEOUT); - if (killed.get()) return; - - overridePoint = pathfindingGrid.getNodeAtPoint(pathPoint); - if (overridePoint.isPresent() - && (overridePoint.get().getColor().equals(PATH_ANIMATION_COLOR) - || overridePoint.get().getColor().equals(PATH_COLOR))) { - - int x = (int) pathPoint.getX(); - int y = (int) pathPoint.getY(); - lockingAddNode(new GridNode(PATH_ANIMATION_COLOR, x, y)); - lockingRepaintGrid(); - } - } - - if (killed.get()) return; - - // Trickle from goal to start - for (int i = pathPoints.size() - 1 ; i >= 0 ; i--) { - Optional overridePoint - = pathfindingGrid.getNodeAtPoint(pathPoints.get(i)); - if (overridePoint.isPresent() - && (overridePoint.get().getColor().equals(PATH_ANIMATION_COLOR) - || overridePoint.get().getColor().equals(PATH_COLOR))) { - lockingAddNode(new GridNode(PATH_COLOR, - (int) pathPoints.get(i).getX(), - (int) pathPoints.get(i).getY())); - lockingRepaintGrid(); - } - - if (killed.get()) return; - ThreadUtil.sleep(PATH_TRICKLE_TIMEOUT); - if (killed.get()) return; - - overridePoint = pathfindingGrid.getNodeAtPoint(pathPoints.get(i)); - if (overridePoint.isPresent() - && (overridePoint.get().getColor().equals(PATH_ANIMATION_COLOR) - || overridePoint.get().getColor().equals(PATH_COLOR))) { - lockingAddNode(new GridNode(PATH_ANIMATION_COLOR, - (int) pathPoints.get(i).getX(), - (int) pathPoints.get(i).getY())); - lockingRepaintGrid(); - } - - if (killed.get()) return; - } - } - } catch (Exception e) { - ExceptionHandler.handle(e); - } - lockingRepaintGrid(); - }, PATH_TRICKLE_ANIMATION_THREAD_NAME); - } - - /** - * Kills this path animator. - */ - public void kill() { - killed.set(true); - } - } - - /** - * Performs the actions necessary following a path - * from the start to the goal node was not found. - */ - private static void pathNotFound() { - currentPathingState = PathingState.PATH_NOT_FOUND; - - updateStateLabel(); - - startPauseButton.setText(START); - - enableUiElements(); - - pathfindingGrid.setResizable(true); - - lockingRepaintGrid(); - } - - /** - * Enables the UI elements during the pathfinding animation. - */ - private static void enableUiElements() { - setUiElementsEnabled(true); - } - - /** - * Disables the UI elements during the pathfinding animation. - */ - private static void disableUiElements() { - setUiElementsEnabled(false); - } - - /** - * Sets whether the ui elements are enabled. - * - * @param enabled whether the ui elements are enabled - */ - @ForReadability - private static void setUiElementsEnabled(boolean enabled) { - deleteWallsCheckBox.setEnabled(enabled); - showStepsBox.setEnabled(enabled); - diagonalBox.setEnabled(enabled); - placeStartBox.setEnabled(enabled); - placeGoalBox.setEnabled(enabled); - drawGridLinesBox.setEnabled(enabled); - - heuristicSwitch.setEnabled(enabled); - algorithmSwitch.setEnabled(enabled); - - if (enabled) { - pathfindingGrid.installClickAndDragPlacer(); - } else { - pathfindingGrid.uninstallClickAndDragPlacer(); - } - } - - /** - * Resets all the checkboxes to their default state. - */ - private static void resetCheckboxStates() { - deleteWallsCheckBox.setChecked(false); - showStepsBox.setChecked(false); - diagonalBox.setChecked(false); - placeStartBox.setChecked(false); - placeGoalBox.setChecked(false); - drawGridLinesBox.setChecked(true); - pathfindingGrid.setDrawGridLines(true); - } - - /** - * Resets the algorithm and heuristic switchers to their default states. - */ - private static void resetSwitcherStates() { - // Corresponds to Manhattan - heuristicSwitch.setState(CyderSwitchState.OFF); - // Corresponds to A* - algorithmSwitch.setState(CyderSwitchState.OFF); - } - - /** - * Updates the state label based. - */ - private static void updateStateLabel() { - currentStateLabel.setText(STATE + CyderStrings.space + currentPathingState.getStateLabelText()); - } - - /** - * Removes all nodes having to do with the pathfinding algorithm such as - * open nodes, closed nodes, and blue path nodes. - * Note this method does not repaint the grid. - */ - private static void removePathingNodes() { - pathfindingGrid.removeNodesOfColor(pathableClosedColor); - pathfindingGrid.removeNodesOfColor(pathableOpenColor); - } - - /** - * Resets the start and goal nodes to their default. - * Note this method does not repaint the grid. - */ - private static void resetStartAndGoalNodes() { - pathfindingGrid.removeNodesOfColor(startNodeColor); - pathfindingGrid.removeNodesOfColor(goalNodeColor); - - startNode = new PathNode(DEFAULT_START_POINT); - goalNode = new PathNode(DEFAULT_GOAL_POINT); - - lockingAddNode(new GridNode(startNodeColor, startNode.getX(), startNode.getY())); - lockingAddNode(new GridNode(goalNodeColor, goalNode.getX(), goalNode.getY())); - } - - /** - * Removes all walls from the grid. - * Note this method does not repaint the grid. - */ - private static void removeWalls() { - pathfindingGrid.removeNodesOfColor(wallsColor); - } - - /** - * Kills the path animator and sets it to null. - */ - private static void endPathAnimator() { - if (currentPathAnimator == null) return; - - currentPathAnimator.kill(); - currentPathAnimator = null; - - pathfindingGrid.removeNodesOfColor(PathTrickleAnimator.PATH_COLOR); - pathfindingGrid.removeNodesOfColor(PathTrickleAnimator.PATH_ANIMATION_COLOR); - - } - - /** - * Resets the visualizer as if the widget was just opened. - */ - public static void reset() { - endPathAnimator(); - - enableUiElements(); - - startPauseButton.setText(START); - - resetCheckboxStates(); - resetSwitcherStates(); - removePathingNodes(); - resetStartAndGoalNodes(); - removeWalls(); - - currentPathingState = PathingState.NOT_STARTED; - updateStateLabel(); - - pathfindingGrid.setNodeDimensionLength(DEFAULT_NODES); - - speedSlider.setValue(DEFAULT_SLIDER_VALUE); - - pathfindingGrid.installClickAndDragPlacer(); - - pathfindingGrid.setResizable(true); - - lockingRepaintGrid(); - } - - /** - * Repaints the pathfinding grid in a thread-safe way. - */ - private static void lockingRepaintGrid() { - try { - semaphore.acquire(); - } catch (Exception e) { - ExceptionHandler.handle(e); - } - - pathfindingGrid.repaint(); - semaphore.release(); - } - - /** - * Adds the node to the pathfinding grid in thread-safe way. - * - * @param node the node to add to the grid - */ - private static void lockingAddNode(GridNode node) { - try { - semaphore.acquire(); - } catch (Exception e) { - ExceptionHandler.handle(e); - } - - pathfindingGrid.addNode(node); - semaphore.release(); - } - - /** - * Returns whether the provided nodes are diagonal neighbors. - * - * @param node1 the first node - * @param node2 the second node - * @return whether the provided nodes are diagonal neighbors - */ - private static boolean areDiagonalNeighbors(PathNode node1, PathNode node2) { - return (node1.getX() == node2.getX() + 1 && node1.getY() == node2.getY() + 1) - || (node1.getX() == node2.getX() + 1 && node1.getY() == node2.getY() - 1) - || (node1.getX() == node2.getX() - 1 && node1.getY() == node2.getY() - 1) - || (node1.getX() == node2.getX() - 1 && node1.getY() == node2.getY() + 1); - } - - /** - * Returns whether the provided nodes are orthogonal neighbors. - * - * @param node1 the first node - * @param node2 the second node - * @return whether the provided nodes are orthogonal neighbors - */ - private static boolean areOrthogonalNeighbors(PathNode node1, PathNode node2) { - return (node1.getX() == node2.getX() && node1.getY() == node2.getY() + 1) - || (node1.getX() == node2.getX() && node1.getY() == node2.getY() - 1) - || (node1.getX() == node2.getX() + 1 && node1.getY() == node2.getY()) - || (node1.getX() == node2.getX() - 1 && node1.getY() == node2.getY()); - } - - /** - * Calculates the heuristic from the provided node to the goal node - * using the currently set heuristic. - * - * @param node the node to calculate the heuristic of - * @return the cost to path from the provided node to the goal - */ - private static double heuristic(PathNode node) { - boolean dijkstrasAlgorithm = algorithmSwitch.getState() == CyderSwitchState.ON; - boolean euclideanDistance = heuristicSwitch.getState() == CyderSwitchState.ON; - if (dijkstrasAlgorithm) { - return DIJKSTRA_HEURISTIC; - } else if (euclideanDistance) { - return euclideanDistance(node, goalNode); - } else { - return manhattanDistance(node, goalNode); - } - } - - /** - * Calculates the g cost from the provided node to the start node. - * This uses Euclidean distance by definition of g cost. - * - * @param node the node to calculate the g cost of - * @return the g cost of the provided node - */ - private static double calcGCost(PathNode node) { - return euclideanDistance(node, startNode); - } - - /** - * Returns the Euclidean distance between the two nodes. - * - * @param node1 the first noDe - * @param node2 the second node - * @return the Euclidean distance between the two nodes - */ - private static double euclideanDistance(PathNode node1, PathNode node2) { - return NumberUtil.calculateMagnitude(node1.getX() - node2.getX(), - node1.getY() - node2.getY()); - } - - /** - * Returns the Manhattan distance between the two nodes. - * - * @param node1 the first node - * @param node2 the second node - * @return the Manhattan distance between the two nodes - */ - private static double manhattanDistance(PathNode node1, PathNode node2) { - return Math.abs(node1.getX() - node2.getX()) + Math.abs(node1.getY() - node2.getY()); - } - - /** - * The node comparator to use for the node queue. - */ - private static class NodeComparator implements Comparator { - @Override - public int compare(PathNode node1, PathNode node2) { - if (node1.getF() > node2.getF()) { - return 1; - } else if (node1.getF() < node2.getF()) { - return -1; - } else { - return Double.compare(node1.getH(), node2.getH()); - } - } - } - - /** - * A node object used for the pathfinding widget. - */ - private static class PathNode { - /** - * The node's x value. - */ - private int x; - - /** - * The node's y value. - */ - private int y; - - /** - * The node's g value. - */ - private double g = Integer.MAX_VALUE; - - /** - * The node's heuristic value. - */ - private double h = Integer.MAX_VALUE; - - /** - * The node's parent. - */ - private PathNode parent; - - /** - * Constructs a new path node. - * - * @param x the initial x value - * @param y the initial y value - */ - public PathNode(int x, int y) { - this.x = x; - this.y = y; - } - - /** - * Suppress default constructor. - */ - private PathNode() { - throw new IllegalMethodException("Cannot create PathNode with default constructor"); - } - - /** - * Constructs a new path node. - * - * @param p the point to use as the initial x,y - */ - public PathNode(Point p) { - this(p.x, p.y); - } - - /** - * Returns the x of the node. - * - * @return the x of the node - */ - public int getX() { - return x; - } - - /** - * Sets the x of the node. - * - * @param x the x of the node - */ - public void setX(int x) { - this.x = x; - } - - /** - * Returns the y of the node. - * - * @return the y of the node - */ - public int getY() { - return y; - } - - /** - * Sets the y of the node. - * - * @param y the y of the node - */ - public void setY(int y) { - this.y = y; - } - - /** - * Returns the g cost of the node. - * - * @return the g cost of the node - */ - public double getG() { - return g; - } - - /** - * Sets the g cost of the node. - * - * @param g the g cost of the node - */ - public void setG(double g) { - this.g = g; - } - - /** - * Returns the h cost of the node. - * - * @return the h cost of the node - */ - public double getH() { - return h; - } - - /** - * Sets the h cost of the node. - * - * @param h the h cost of the node - */ - public void setH(double h) { - this.h = h; - } - - /** - * Returns the f cost of the node. - * - * @return the f cost of the node - */ - public double getF() { - return h + g; - } - - /** - * Returns the parent of the node. - * - * @return the parent of the node - */ - public PathNode getParent() { - return parent; - } - - /** - * Sets the parent of the node. - * - * @param parent the parent of the node - */ - public void setParent(PathNode parent) { - this.parent = parent; - } - - /** - * {@inheritDoc} - */ - @Override - public boolean equals(Object o) { - if (o == null) - return false; - if (!(o instanceof PathNode other)) - return false; - else { - return other.getX() == x && other.getY() == y; - } - } - - /** - * {@inheritDoc} - */ - @Override - public String toString() { - return x + ", " + y; - } - } -} \ No newline at end of file diff --git a/src/main/java/cyder/widgets/PerlinWidget.java b/src/main/java/cyder/widgets/PerlinWidget.java deleted file mode 100644 index 1fe18a3b8..000000000 --- a/src/main/java/cyder/widgets/PerlinWidget.java +++ /dev/null @@ -1,820 +0,0 @@ -package cyder.widgets; - -import cyder.annotations.CyderAuthor; -import cyder.annotations.ForReadability; -import cyder.annotations.Vanilla; -import cyder.annotations.Widget; -import cyder.constants.CyderColors; -import cyder.exceptions.IllegalMethodException; -import cyder.layouts.CyderGridLayout; -import cyder.layouts.CyderPartitionedLayout; -import cyder.math.NumberUtil; -import cyder.strings.CyderStrings; -import cyder.ui.UiUtil; -import cyder.ui.button.CyderButton; -import cyder.ui.drag.CyderDragLabel; -import cyder.ui.frame.CyderFrame; -import cyder.ui.grid.GridNode; -import cyder.ui.label.CyderLabel; -import cyder.ui.pane.CyderPanel; -import cyder.ui.selection.CyderSwitch; -import cyder.ui.selection.CyderSwitchState; -import cyder.ui.slider.CyderSliderUi; -import cyder.ui.slider.ThumbShape; -import cyder.utils.SimplexNoiseUtil; - -import javax.swing.*; -import javax.swing.border.LineBorder; -import java.awt.*; -import java.awt.event.ActionListener; -import java.util.Random; - -/** - * A visualizer for two dimensional perlin-noise and three-dimensional open simplex noise. - */ -@Vanilla -@CyderAuthor -public final class PerlinWidget { - /** - * Suppress default constructor. - */ - private PerlinWidget() { - throw new IllegalMethodException(CyderStrings.ATTEMPTED_INSTANTIATION); - } - - /** - * The button which starts the animation for the noise. - */ - private static CyderButton animateButton; - - /** - * The button which iterates to the next iteration of the noise. - */ - private static CyderButton stepButton; - - /** - * The button to regenerate the noise. - */ - private static CyderButton regenerateButton; - - /** - * The frame used for animation - */ - private static CyderFrame perlinFrame; - - /** - * The overridden label to paint the noise calculations on. - */ - private static JLabel noiseLabel; - - /** - * The minimum feature size for open simplex noise. - */ - public static final double MINIMUM_FEATURE_SIZE = 24.0; - - /** - * The default feature size for open simplex noise. - */ - public static final double DEFAULT_FEATURE_SIZE = 24.0; - - /** - * The maximum feature size for open simplex noise. - */ - public static final double MAXIMUM_FEATURE_SIZE = MINIMUM_FEATURE_SIZE * 2.0; - - /** - * The feature size for open simplex noise. - */ - private static double featureSize = DEFAULT_FEATURE_SIZE; - - /** - * The open simplex noise object. - */ - private static SimplexNoiseUtil noise = new SimplexNoiseUtil(0); - - /** - * The time step current at. - */ - private static double timeStep; - - /** - * The slider used to change the open simplex noise feature size. - */ - private static JSlider featureSlider; - - /** - * The dimension switch. - */ - private static CyderSwitch dimensionSwitch; - - /** - * The slider used to determine the speed of the animation. - */ - private static JSlider speedSlider; - - /** - * The default value for the speed slider. - */ - private static int speedSliderValue = 400; - - /** - * The maximum value for the speed slider. - */ - private static final int speedSliderMaxValue = 500; - - /** - * The minimum value for the speed slider. - */ - private static final int speedSliderMinValue = 0; - - /** - * The resolution of open simplex noise. - */ - private static final int resolution = 600; - - /** - * The node array to store the open simplex noise. - */ - private static GridNode[][] noise3D; - - /** - * The array to store the perlin noise. - */ - private static float[] noise2D; - - /** - * The random object used for randomizing the noise seeds. - */ - private static final Random random = new Random(); - - /** - * The animation timer. - */ - private static Timer timer; - - /** - * The seeding value to use for open simplex noise. - */ - private static float[][] instanceSeed; - - /** - * The number of octaves for open simplex (iterations). - */ - private static int octaves = 1; - - /** - * The maximum number of octaves (iterations). - */ - private static final int maxOctaves = 10; - - /** - * Determines whether the frame has been requested to close or is already closed. - */ - private static boolean closed = true; - - /** - * The frame title string. - */ - private static final String PERLIN = "Perlin Noise"; - - /** - * The stroke for drawing on the noise label. - */ - private static final BasicStroke stroke = new BasicStroke(2); - - /** - * The text for the step button. - */ - private static final String STEP = "Step"; - - /** - * The time step increment. - */ - private static final double timeStepIncrement = 0.1; - - /** - * The stop string. - */ - private static final String STOP = "Stop"; - - /** - * Half of the limit for 8-bit color values. - */ - private static final float halfEightBitColorLimit = 127.5f; - - /** - * The feature slider tooltip text. - */ - private static final String THREE_D_FEATURE_SIZE = "3D Feature Size"; - - /** - * The animate string. - */ - private static final String ANIMATE = "Animate"; - - /** - * The tooltip for the speed slider. - */ - private static final String ANIMATE_TIMEOUT = "Animation Timeout"; - - /** - * The height of the sliders. - */ - private static final int sliderHeight = 40; - - /** - * The maximum feature slider value. - */ - private static final int maxFeatureSliderValue = 1000; - - /** - * The minimum feature slider value. - */ - private static final int minFeatureSliderValue = 0; - - /** - * The default feature slider value. - */ - private static final int defaultFeatureSliderValue = (maxFeatureSliderValue - minFeatureSliderValue) / 2; - - /** - * The grayscale multiplier value. - */ - private static final int grayscaleMultiplier = 0x010101; - - /** - * The length of the top line for 2D noise. - */ - private static final int topLineLength = 2; - - /** - * The length of the grass for 2D noise. - */ - private static final int grassLength = 10; - - /** - * The length of the dirt for 2D noise. - */ - private static final int dirtLength = 20; - - /** - * The text for the off state of the dimension switch button. - */ - private static final String TWO_D = "2D"; - - /** - * The text for the on state of the dimension switch button. - */ - private static final String THREE_D = "3D"; - - /** - * The text for the speed label. - */ - private static final String SPEED = "Speed"; - - /** - * The text for the feature size label. - */ - private static final String FEATURE_SIZE = "Feature size"; - - /** - * The padding between the frame and the start of components. - */ - private static final int framePadding = 25; - - /** - * The width of the frame. - */ - private static final int frameWidth = resolution + 2 * framePadding; - - /** - * The height of the bottom control components. - */ - private static final int interactionComponentsHeight = 240; - - /** - * The frame height. - */ - private static final int frameHeight = resolution + 2 * framePadding - + CyderDragLabel.DEFAULT_HEIGHT + interactionComponentsHeight; - - /** - * The regenerate button text. - */ - private static final String REGENERATE = "Regenerate"; - - /** - * The size of the dimension switch button - */ - private static final Dimension dimensionSwitchSize = new Dimension(180, 55); - - /** - * The size of the slider labels. - */ - private static final Dimension sliderLabelSize = new Dimension(200, 40); - - /** - * The size of the buttons. - */ - private static final Dimension buttonSize = new Dimension(180, 40); - - /** - * The length of the noise label border. - */ - private static final int noiseLabelBorderLength = 5; - - /** - * Shows the perlin noise widget. - */ - @Widget(triggers = {"perlin", "noise"}, description = "Perlin noise visualizer/open simplex noise visualizer") - public static void showGui() { - UiUtil.closeIfOpen(perlinFrame); - - closed = false; - timeStep = 0; - octaves = 1; - - instanceSeed = new float[resolution][resolution]; - - generateNewSeed(); - - initializeNoise(); - - timer = new Timer(speedSliderMaxValue - speedSliderValue, animationAction); - - perlinFrame = new CyderFrame(new CyderFrame.Builder().setWidth(frameWidth).setHeight(frameHeight)) { - @Override - public void repaint() { - if (twoDimensionalMode()) { - super.repaint(); - } - } - }; - perlinFrame.setTitle(PERLIN); - perlinFrame.addPreCloseAction(PerlinWidget::preCloseActions); - - noiseLabel = new JLabel() { - @Override - public void paint(Graphics g) { - if (closed) return; - super.paint(g); - - Graphics2D g2d = (Graphics2D) g; - g2d.setColor(Color.darkGray); - g2d.setStroke(stroke); - - if (twoDimensionalMode()) { - draw2DNoise(g2d); - } else { - draw3DNoise(g2d); - } - } - }; - noiseLabel.setBounds(noiseLabelBorderLength, noiseLabelBorderLength, resolution, resolution); - - JLabel noiseParentLabel = new JLabel(); - noiseParentLabel.setSize(resolution + 2 * noiseLabelBorderLength, - resolution + 2 * noiseLabelBorderLength); - noiseParentLabel.setBorder(new LineBorder(CyderColors.navy, noiseLabelBorderLength)); - noiseParentLabel.add(noiseLabel); - - CyderLabel animateLabel = new CyderLabel(ANIMATE); - animateLabel.setSize(100, 20); - - animateButton = new CyderButton(ANIMATE); - animateButton.addActionListener(e -> generate()); - animateButton.setToolTipText("Animate Perlin Noise"); - animateButton.setSize(buttonSize); - - stepButton = new CyderButton(STEP); - stepButton.addActionListener(e -> nextIteration()); - stepButton.setToolTipText("Increments the octave and displayed the revalidated noise"); - stepButton.setSize(buttonSize); - - regenerateButton = new CyderButton(REGENERATE); - regenerateButton.addActionListener(e -> regenerateButtonAction()); - regenerateButton.setToolTipText("Regenerates the noise"); - regenerateButton.setSize(buttonSize); - - speedSlider = new JSlider(JSlider.HORIZONTAL, speedSliderMinValue, speedSliderMaxValue, speedSliderValue); - - CyderSliderUi speedSliderUi = new CyderSliderUi(speedSlider); - speedSliderUi.setThumbRadius(25); - speedSliderUi.setThumbShape(ThumbShape.CIRCLE); - speedSliderUi.setThumbFillColor(Color.black); - speedSliderUi.setThumbOutlineColor(CyderColors.navy); - speedSliderUi.setRightThumbColor(CyderColors.regularBlue); - speedSliderUi.setLeftThumbColor(CyderColors.regularPink); - speedSliderUi.setTrackStroke(new BasicStroke(3.0f)); - - speedSlider.setUI(speedSliderUi); - speedSlider.setSize(resolution - 2 * framePadding, sliderHeight); - speedSlider.setPaintTicks(false); - speedSlider.setPaintLabels(false); - speedSlider.setVisible(true); - speedSlider.setValue(speedSliderValue); - speedSlider.addChangeListener(e -> speedSliderChangeAction()); - speedSlider.setOpaque(false); - speedSlider.setToolTipText(ANIMATE_TIMEOUT); - speedSlider.setFocusable(false); - speedSlider.repaint(); - - featureSlider = new JSlider(JSlider.HORIZONTAL, minFeatureSliderValue, - maxFeatureSliderValue, defaultFeatureSliderValue); - - CyderSliderUi featureSliderUi = new CyderSliderUi(featureSlider); - featureSliderUi.setThumbRadius(25); - featureSliderUi.setThumbShape(ThumbShape.CIRCLE); - featureSliderUi.setThumbFillColor(Color.black); - featureSliderUi.setThumbOutlineColor(CyderColors.navy); - featureSliderUi.setRightThumbColor(CyderColors.regularBlue); - featureSliderUi.setLeftThumbColor(CyderColors.regularPink); - featureSliderUi.setTrackStroke(new BasicStroke(3.0f)); - - featureSlider.setUI(featureSliderUi); - featureSlider.setSize(resolution - 2 * framePadding, sliderHeight); - featureSlider.setPaintTicks(false); - featureSlider.setPaintLabels(false); - featureSlider.setVisible(true); - featureSlider.setValue(defaultFeatureSliderValue); - featureSlider.addChangeListener(e -> featureSliderChangeAction()); - featureSlider.setOpaque(false); - featureSlider.setToolTipText(THREE_D_FEATURE_SIZE); - featureSlider.setFocusable(false); - featureSlider.repaint(); - - dimensionSwitch = new CyderSwitch(dimensionSwitchSize, CyderSwitchState.OFF); - dimensionSwitch.setButtonPercent(50); - dimensionSwitch.setSize(dimensionSwitchSize); - dimensionSwitch.setOffText(TWO_D); - dimensionSwitch.setOnText(THREE_D); - dimensionSwitch.getSwitchButton().addActionListener(e -> dimensionSwitchButtonAction()); - - CyderPartitionedLayout partitionedLayout = new CyderPartitionedLayout(); - partitionedLayout.spacer(2); - partitionedLayout.addComponent(noiseParentLabel, 70); - - CyderLabel speedSliderLabel = new CyderLabel(SPEED); - speedSliderLabel.setSize(sliderLabelSize); - CyderLabel featureSliderLabel = new CyderLabel(FEATURE_SIZE); - featureSliderLabel.setSize(sliderLabelSize); - - partitionedLayout.addComponent(speedSliderLabel, 4); - partitionedLayout.addComponent(speedSlider, 2); - partitionedLayout.addComponent(featureSliderLabel, 4); - partitionedLayout.addComponent(featureSlider, 2); - - CyderGridLayout gridLayout = new CyderGridLayout(2, 2); - gridLayout.addComponent(animateButton); - gridLayout.addComponent(stepButton); - gridLayout.addComponent(regenerateButton); - gridLayout.addComponent(dimensionSwitch); - CyderPanel gridPanel = new CyderPanel(gridLayout); - gridPanel.setSize(600, 120); - - partitionedLayout.spacer(6); - partitionedLayout.addComponent(gridPanel, 5); - partitionedLayout.spacer(4); - - perlinFrame.setCyderLayout(partitionedLayout); - perlinFrame.finalizeAndShow(); - } - - /** - * The actions to invoke when the dimension switch button is pressed. - */ - @ForReadability - private static void dimensionSwitchButtonAction() { - regenerateButtonAction(); - perlinFrame.repaint(); - } - - /** - * Returns whether the current perlin nose mode is 2D. - * - * @return whether the current perlin nose mode is 2D - */ - @ForReadability - private static boolean twoDimensionalMode() { - return dimensionSwitch.getState().equals(CyderSwitchState.OFF); - } - - /** - * The actions to invoke when the regenerate button is clicked. - */ - private static void regenerateButtonAction() { - if (twoDimensionalMode()) { - if (timer.isRunning()) { - timer.stop(); - unlockUI(); - } - - generateNewSeed(); - octaves = 1; - noise2D = generate2DNoise(instanceSeed[0], octaves); - } else { - timeStep = 0; - - noise = new SimplexNoiseUtil(NumberUtil.generateRandomInt(1000)); - for (int y = 0 ; y < resolution ; y++) { - for (int x = 0 ; x < resolution ; x++) { - double value = noise.eval(x / featureSize, y / featureSize, timeStep); - noise3D[x][y].setColor(generateGrayscaleColor(value)); - noise3D[x][y].setX(x); - noise3D[x][y].setY(y); - } - } - } - - noiseLabel.repaint(); - } - - /** - * Initializes the 2D and 3D noise. - */ - @ForReadability - private static void initializeNoise() { - noise2D = new float[resolution]; - noise2D = generate2DNoise(instanceSeed[0], octaves); - - noise3D = new GridNode[resolution][resolution]; - for (int x = 0 ; x < resolution ; x++) { - for (int y = 0 ; y < resolution ; y++) { - noise3D[x][y] = new GridNode(x, y); - } - } - } - - /** - * The actions to invoke on a feature slider value change. - */ - @ForReadability - private static void featureSliderChangeAction() { - featureSize = (featureSlider.getValue() / (float) maxFeatureSliderValue) - * (MAXIMUM_FEATURE_SIZE - MINIMUM_FEATURE_SIZE) + MINIMUM_FEATURE_SIZE; - - if (!twoDimensionalMode() && !timer.isRunning()) { - for (int y = 0 ; y < resolution ; y++) { - for (int x = 0 ; x < resolution ; x++) { - double value = noise.eval(x / featureSize, y / featureSize, timeStep); - Color color = generateGrayscaleColor(value); - - GridNode ref = noise3D[x][y]; - - ref.setColor(color); - ref.setX(x); - ref.setY(y); - } - } - - noiseLabel.repaint(); - } - } - - /** - * The pre close action to invoke when the frame's dispose function has been called. - */ - @ForReadability - private static void preCloseActions() { - noise2D = null; - noise3D = null; - closed = true; - - stopTimerIfRunning(); - } - - /** - * The actions to invoke on a speed slider value change. - */ - @ForReadability - private static void speedSliderChangeAction() { - speedSliderValue = speedSlider.getValue(); - timer.setDelay(speedSliderMaxValue - speedSliderValue); - } - - /** - * Draws two dimension noise on the noise label. - * - * @param g2d the 2D graphics object - */ - @ForReadability - private static void draw2DNoise(Graphics2D g2d) { - int width = 2; - - for (int x = 0 ; x < resolution - 1 ; x++) { - float y = (float) ((noise2D[x] * resolution / 2.0) + resolution / 2.0); - int minY = (int) y; - int lenDown = 0; - - // Draw top line - g2d.setColor(Color.black); - g2d.fillRect(x, minY, width, topLineLength); - lenDown += topLineLength; - - // Draw grass - g2d.setColor(CyderColors.regularGreen); - g2d.fillRect(x, minY + lenDown, width, grassLength); - lenDown += grassLength; - - // Draw dirt - g2d.setColor(CyderColors.brownDirt); - g2d.fillRect(x, minY + lenDown, width, dirtLength); - lenDown += dirtLength; - - // Draw stone - int stoneLength = resolution - (minY + lenDown); - g2d.setColor(Color.darkGray); - g2d.fillRect(x, minY + lenDown, width, stoneLength); - } - } - - /** - * Draws three dimensional noise on the noise label. - * - * @param g2d the 2D graphics object - */ - @ForReadability - private static void draw3DNoise(Graphics2D g2d) { - int len = 1; - - for (int i = 0 ; i < resolution ; i++) { - for (int j = 0 ; j < resolution ; j++) { - g2d.setColor(noise3D[i][j].getColor()); - g2d.fillRect(i, j, len, len); - } - } - } - - /** - * Stops the timer if running. - */ - @ForReadability - private static void stopTimerIfRunning() { - if (timer != null && timer.isRunning()) { - timer.stop(); - } - } - - /** - * Generates new noise based on the current random seed. - */ - private static void generate() { - if (closed) return; - - if (timer.isRunning()) { - timer.stop(); - animateButton.setText(ANIMATE); - unlockUI(); - } else { - lockUI(); - animateButton.setText(STOP); - timer.start(); - } - } - - /** - * Generates the new iteration of noise from the current noise. - */ - private static void nextIteration() { - if (closed) return; - - if (twoDimensionalMode()) { - if (timer != null && timer.isRunning()) return; - - octaves++; - if (octaves == maxOctaves) { - octaves = 1; - } - - noise2D = generate2DNoise(instanceSeed[0], octaves); - } else { - //serves no purpose during an animation - if (timer != null && timer.isRunning()) return; - - timeStep += timeStepIncrement; - - for (int y = 0 ; y < resolution ; y++) { - for (int x = 0 ; x < resolution ; x++) { - if (closed) return; - - double value = noise.eval(x / featureSize, y / featureSize, timeStep); - noise3D[x][y].setColor(generateGrayscaleColor(value)); - noise3D[x][y].setX(x); - noise3D[x][y].setY(y); - } - } - } - - noiseLabel.repaint(); - } - - /** - * Generates perlin noise based on common algorithm implementation. - * - * @param fSeed the seed value - * @param nOctaves the number of iterations to perform the algorithm on - * @return 2D perlin noise representation (values are between 0 and 1) - */ - private static float[] generate2DNoise(float[] fSeed, int nOctaves) { - float[] ret = new float[resolution]; - - for (int x = 0 ; x < resolution ; x++) { - float fNoise = 0.0f; - float fScale = 1.0f; - float fScaleAcc = 0.0f; - - for (int octave = 0 ; octave < nOctaves ; octave++) { - /* - Assuming octave is a power of two - - assert (octave & (octave - 1)) == 0; - */ - int nPitch = resolution >> octave; - int nSample1 = (x / nPitch) * nPitch; - int nSample2 = (nSample1 + nPitch) % resolution; - - float fBlend = (float) (x - nSample1) / (float) nPitch; - float fSample = (1.0f - fBlend) * fSeed[nSample1] + fBlend * fSeed[nSample2]; - fNoise += fSample * fScale; - fScaleAcc += fScale; - fScale = fScale / 2.0f; - } - - ret[x] = fNoise / fScaleAcc; - } - - return ret; - } - - /** - * The function for the timer to invoke when noise animation is enabled. - */ - private static final ActionListener animationAction = evt -> { - if (closed) return; - - octaves++; - - if (octaves == maxOctaves) { - octaves = 1; - generateNewSeed(); - } - - if (twoDimensionalMode()) { - noise2D = generate2DNoise(instanceSeed[0], octaves); - } else { - timeStep += timeStepIncrement; - - for (int y = 0 ; y < resolution ; y++) { - for (int x = 0 ; x < resolution ; x++) { - if (closed) return; - - double value = noise.eval(x / featureSize, y / featureSize, timeStep); - noise3D[x][y].setColor(generateGrayscaleColor(value)); - noise3D[x][y].setX(x); - noise3D[x][y].setY(y); - } - } - } - - noiseLabel.repaint(); - }; - - @ForReadability - private static void generateNewSeed() { - for (int i = 0 ; i < resolution ; i++) { - for (int j = 0 ; j < resolution ; j++) { - instanceSeed[i][j] = random.nextFloat(); - } - } - } - - /** - * Generates a grayscale color from the double value. - * - * @param value the value to map to a grayscale color - * @return a grayscale color unique to the double provided - */ - private static Color generateGrayscaleColor(double value) { - return new Color(grayscaleMultiplier * (int) ((value + 1) * halfEightBitColorLimit)); - } - - /** - * Locks the perlin UI. - */ - private static void lockUI() { - regenerateButton.setEnabled(false); - stepButton.setEnabled(false); - dimensionSwitch.setEnabled(false); - featureSlider.setEnabled(false); - } - - /** - * Unlocks the perlin UI. - */ - private static void unlockUI() { - regenerateButton.setEnabled(true); - stepButton.setEnabled(true); - dimensionSwitch.setEnabled(true); - featureSlider.setEnabled(true); - } -} \ No newline at end of file diff --git a/src/main/java/cyder/widgets/PhoneWidget.java b/src/main/java/cyder/widgets/PhoneWidget.java deleted file mode 100644 index 5e9c2d1ea..000000000 --- a/src/main/java/cyder/widgets/PhoneWidget.java +++ /dev/null @@ -1,389 +0,0 @@ -package cyder.widgets; - -import com.google.common.base.Preconditions; -import cyder.annotations.CyderAuthor; -import cyder.annotations.ForReadability; -import cyder.annotations.Vanilla; -import cyder.annotations.Widget; -import cyder.audio.GeneralAudioPlayer; -import cyder.constants.CyderColors; -import cyder.constants.CyderFonts; -import cyder.exceptions.IllegalMethodException; -import cyder.strings.CyderStrings; -import cyder.strings.StringUtil; -import cyder.ui.UiUtil; -import cyder.ui.button.CyderModernButton; -import cyder.ui.button.ThemeBuilder; -import cyder.ui.field.CyderTextField; -import cyder.ui.frame.CyderFrame; -import cyder.utils.StaticUtil; - -import java.awt.event.WindowAdapter; -import java.awt.event.WindowEvent; -import java.io.File; -import java.util.Arrays; -import java.util.concurrent.atomic.AtomicBoolean; - -import static cyder.strings.CyderStrings.*; - -/** - * A phone number dialing widget. - */ -@Vanilla -@CyderAuthor -public final class PhoneWidget { - /** - * The dialing string. - */ - private static final String DIALING = "Dialing: "; - - /** - * The width of the widget frame. - */ - private static final int FRAME_WIDTH = 320; - - /** - * The height of the widget frame. - */ - private static final int FRAME_HEIGHT = 500; - - /** - * The string for the back button. - */ - private static final String backText = "<<"; - - /** - * The string used for the call button. - */ - private static final String CALL = "Call"; - - /** - * A regex for targeting anything that is not a digit. - */ - private static final String NON_DIGITS_REGEX = "[^\\d.]"; - - /** - * The widget title. - */ - private static final String TITLE = "Phone"; - - /** - * The button theme. - */ - private static final ThemeBuilder theme = new ThemeBuilder() - .setFont(CyderFonts.SEGOE_30) - .setBackgroundColor(CyderColors.regularOrange) - .setBorderColor(CyderColors.navy) - .setBorderLength(5); - - /** - * The field numbers are stored in. - */ - private static CyderTextField numberField; - - /** - * The current number. - */ - private static String currentPhoneNumber; - - /** - * The widget frame. - */ - private static CyderFrame phoneFrame; - - /** - * Suppress default constructor. - */ - private PhoneWidget() { - throw new IllegalMethodException(CyderStrings.ATTEMPTED_INSTANTIATION); - } - - /** - * Special numbers which when dialed trigger an audio file to play, generally with the same name as the number. - */ - private enum SpecialNumber { - /** - * The 1800 number, plays 1800 by Logic. - */ - SUICIDE_HOTLINE(18002738255L, StaticUtil.getStaticResource("1800.mp3")), - - /** - * The 223 number, plays 223 by YNW Melly - */ - TWO_TWO_THREE(223L, StaticUtil.getStaticResource("223.mp3")); - - /** - * The number dialed to trigger this. - */ - private final long number; - - /** - * The audio file to play. - */ - private final File audioFile; - - SpecialNumber(long number, File audioFile) { - this.number = number; - this.audioFile = audioFile; - } - - /** - * Returns the number dialed to trigger this. - * - * @return the number dialed to trigger this - */ - public long getNumber() { - return number; - } - - /** - * Returns the audio file to play. - * - * @return the audio file to play - */ - public File getAudioFile() { - return audioFile; - } - - /** - * Plays this audio file using the general audio player. - */ - public void play() { - GeneralAudioPlayer.playAudio(audioFile); - } - - /** - * Ends all special audio files if playing. - */ - public static void endAllAudio() { - Arrays.stream(values()).forEach(specialNumber -> - GeneralAudioPlayer.stopAudio(specialNumber.getAudioFile())); - } - } - - @Widget(triggers = "phone", description = "A phone emulating widget") - public static void showGui() { - UiUtil.closeIfOpen(phoneFrame); - - phoneFrame = new CyderFrame.Builder() - .setWidth(FRAME_WIDTH) - .setHeight(FRAME_HEIGHT) - .setTitle(TITLE) - .build(); - phoneFrame.addWindowListener(new WindowAdapter() { - @Override - public void windowClosed(WindowEvent e) { - SpecialNumber.endAllAudio(); - } - }); - - numberField = new CyderTextField(); - numberField.setText(hash); - numberField.setEditable(false); - numberField.setFont(CyderFonts.SEGOE_20); - numberField.setBounds(20, 40, 320 - 40, 40); - phoneFrame.getContentPane().add(numberField); - - currentPhoneNumber = ""; - - CyderModernButton zero = new CyderModernButton("0"); - zero.setBounds(120, 400, 80, 80); - phoneFrame.getContentPane().add(zero); - zero.addClickRunnable(() -> { - currentPhoneNumber = currentPhoneNumber + zero.getText(); - refreshFieldText(); - }); - zero.setTheme(theme); - - CyderModernButton one = new CyderModernButton("1"); - one.setBounds(20, 100, 80, 80); - phoneFrame.getContentPane().add(one); - one.addClickRunnable(() -> { - currentPhoneNumber = currentPhoneNumber + one.getText(); - refreshFieldText(); - }); - one.setTheme(theme); - - CyderModernButton two = new CyderModernButton("2"); - two.setBounds(120, 100, 80, 80); - phoneFrame.getContentPane().add(two); - two.addClickRunnable(() -> { - currentPhoneNumber = currentPhoneNumber + two.getText(); - refreshFieldText(); - }); - two.setTheme(theme); - - CyderModernButton three = new CyderModernButton("3"); - three.setBounds(220, 100, 80, 80); - phoneFrame.getContentPane().add(three); - three.addClickRunnable(() -> { - currentPhoneNumber = currentPhoneNumber + three.getText(); - refreshFieldText(); - }); - three.setTheme(theme); - - CyderModernButton four = new CyderModernButton("4"); - four.setBounds(20, 200, 80, 80); - phoneFrame.getContentPane().add(four); - four.addClickRunnable(() -> { - currentPhoneNumber = currentPhoneNumber + four.getText(); - refreshFieldText(); - }); - four.setTheme(theme); - - CyderModernButton five = new CyderModernButton("5"); - five.setBounds(120, 200, 80, 80); - phoneFrame.getContentPane().add(five); - five.addClickRunnable(() -> { - currentPhoneNumber = currentPhoneNumber + five.getText(); - refreshFieldText(); - }); - five.setTheme(theme); - - CyderModernButton six = new CyderModernButton("6"); - six.setBounds(220, 200, 80, 80); - phoneFrame.getContentPane().add(six); - six.addClickRunnable(() -> { - currentPhoneNumber = currentPhoneNumber + six.getText(); - refreshFieldText(); - }); - six.setTheme(theme); - - CyderModernButton seven = new CyderModernButton("7"); - seven.setBounds(20, 300, 80, 80); - phoneFrame.getContentPane().add(seven); - seven.addClickRunnable(() -> { - currentPhoneNumber = currentPhoneNumber + seven.getText(); - refreshFieldText(); - }); - seven.setTheme(theme); - - CyderModernButton eight = new CyderModernButton("8"); - eight.setBounds(120, 300, 80, 80); - phoneFrame.getContentPane().add(eight); - eight.addClickRunnable(() -> { - currentPhoneNumber = currentPhoneNumber + eight.getText(); - refreshFieldText(); - }); - eight.setTheme(theme); - - CyderModernButton nine = new CyderModernButton("9"); - nine.setBounds(220, 300, 80, 80); - phoneFrame.getContentPane().add(nine); - nine.addClickRunnable(() -> { - currentPhoneNumber = currentPhoneNumber + nine.getText(); - refreshFieldText(); - }); - nine.setTheme(theme); - - CyderModernButton back = new CyderModernButton(backText); - back.setBounds(20, 400, 80, 80); - phoneFrame.getContentPane().add(back); - back.addClickRunnable(() -> { - if (!currentPhoneNumber.isEmpty()) { - currentPhoneNumber = currentPhoneNumber.substring(0, currentPhoneNumber.length() - 1); - refreshFieldText(); - } - }); - back.setTheme(theme); - - CyderModernButton dialNumber = new CyderModernButton(CALL); - dialNumber.setBounds(220, 400, 80, 80); - phoneFrame.getContentPane().add(dialNumber); - dialNumber.addClickRunnable(PhoneWidget::dialNumberAction); - dialNumber.setTheme(theme); - - phoneFrame.finalizeAndShow(); - } - - /** - * The actions to take when the dial number button is pressed. - */ - @ForReadability - private static void dialNumberAction() { - if (currentPhoneNumber.isEmpty()) return; - if (checkForNumbers()) return; - - phoneFrame.toast(DIALING + numberField.getText()); - refreshFieldText(); - } - - /** - * Refreshes the field text based on the current phone number. - */ - private static void refreshFieldText() { - numberField.setText(formatNumber(currentPhoneNumber)); - } - - /** - * Checks the {@link SpecialNumber}s for a special number. - * - * @return whether a special number was found and the runnable invoked - */ - @ForReadability - private static boolean checkForNumbers() { - AtomicBoolean ret = new AtomicBoolean(); - - long dialedNumber = Long.parseLong(StringUtil.getTrimmedText(numberField.getText()) - .replaceAll(NON_DIGITS_REGEX, "")); - Arrays.stream(SpecialNumber.values()).filter(specialNumber -> specialNumber.getNumber() == dialedNumber) - .findFirst().ifPresent(specialNumber -> { - specialNumber.play(); - ret.set(true); - currentPhoneNumber = ""; - refreshFieldText(); - }); - - return ret.get(); - } - - /** - * Returns the number formatted based on the current number - * of digits contained in the phone number. - * - * @param num the current phone number - * @return the phone number formatted - */ - private static String formatNumber(String num) { - Preconditions.checkNotNull(num); - - if (num.isEmpty()) - - num = num.replaceAll(NON_DIGITS_REGEX, ""); - int length = num.length(); - - if (length == 0) { - return hash; - } else if (length < 5) { - return num; - } else if (length == 5) { - return num.charAt(0) + dash + num.substring(1, 5); - } else if (length == 6) { - return num.substring(0, 2) + dash + num.substring(2, 6); - } else if (length == 7) { - return num.substring(0, 3) + dash + num.substring(3, 7); - } else if (length == 8) { - return openingParenthesis + num.charAt(0) + closingParenthesis + space - + num.substring(1, 4) + space + num.substring(4, 8); - } else if (length == 9) { - return openingParenthesis + num.substring(0, 2) + closingParenthesis + space - + num.substring(2, 5) + space + num.substring(5, 9); - } else if (length == 10) { - return openingParenthesis + num.substring(0, 3) + closingParenthesis + space - + num.substring(3, 6) + space + num.substring(6, 10); - } else { - if (length > 15) { - currentPhoneNumber = numberField.getText(); - return numberField.getText(); - } - - String leadingDigits = num.substring(0, length - 10); - int offset = leadingDigits.length(); - - return (leadingDigits + space + openingParenthesis + num.substring(offset, 3 + offset) + - closingParenthesis + - space - + num.substring(3 + offset, 6 + offset) + space + num.substring(6 + offset, length)); - } - } -} diff --git a/src/main/java/cyder/widgets/PizzaWidget.java b/src/main/java/cyder/widgets/PizzaWidget.java deleted file mode 100644 index 47ef033f8..000000000 --- a/src/main/java/cyder/widgets/PizzaWidget.java +++ /dev/null @@ -1,547 +0,0 @@ -package cyder.widgets; - -import com.google.common.collect.ImmutableList; -import cyder.annotations.CyderAuthor; -import cyder.annotations.ForReadability; -import cyder.annotations.Vanilla; -import cyder.annotations.Widget; -import cyder.constants.CyderColors; -import cyder.constants.CyderFonts; -import cyder.constants.HtmlTags; -import cyder.exceptions.IllegalMethodException; -import cyder.strings.CyderStrings; -import cyder.strings.StringUtil; -import cyder.ui.UiUtil; -import cyder.ui.button.CyderButton; -import cyder.ui.field.CyderTextField; -import cyder.ui.frame.CyderFrame; -import cyder.ui.list.CyderScrollList; -import cyder.ui.pane.CyderScrollPane; -import cyder.ui.selection.CyderCheckbox; -import cyder.ui.selection.CyderCheckboxGroup; - -import javax.swing.*; -import javax.swing.border.LineBorder; -import java.awt.*; -import java.util.ArrayList; -import java.util.Optional; - -/** - * A widget for ordering pizza. - */ -@Vanilla -@CyderAuthor -public final class PizzaWidget { - /** - * The widget frame. - */ - private static CyderFrame pizzaFrame; - - /** - * The customer name field. - */ - private static CyderTextField nameField; - - /** - * The checkbox group for the pizza size checkboxes. - */ - private static CyderCheckboxGroup sizeGroup; - - /** - * The small pizza checkbox. - */ - private static CyderCheckbox smallCheckbox; - - /** - * The medium pizza checkbox. - */ - private static CyderCheckbox mediumCheckbox; - - /** - * The large pizza checkbox. - */ - private static CyderCheckbox largeCheckbox; - - /** - * The pizza toppings scroll list. - */ - private static CyderScrollList pizzaToppingsScroll; - - /** - * The crust type scroll list. - */ - private static CyderScrollList crustTypeScroll; - - /** - * The comments area. - */ - private static JTextArea orderComments; - - /** - * The bread sticks checkbox. - */ - private static CyderCheckbox breadSticks; - - /** - * The salad checkbox. - */ - private static CyderCheckbox salad; - - /** - * The soda checkbox. - */ - private static CyderCheckbox soda; - - /** - * The title of the widget frame. - */ - private static final String FRAME_TITLE = "Pizza"; - - /** - * The width of the widget frame. - */ - private static final int FRAME_WIDTH = 600; - - /** - * The height of the widget frame. - */ - private static final int FRAME_HEIGHT = 800; - - /** - * The text of the name label. - */ - private static final String NAME = "Name:"; - - /** - * The text of the size label. - */ - private static final String SIZE = "Size:"; - - /** - * The small text. - */ - private static final String SMALL = "Small"; - - /** - * The medium text. - */ - private static final String MEDIUM = "Medium"; - - /** - * The large text. - */ - private static final String LARGE = "Large"; - - /** - * The crust type label text. - */ - private static final String CRUST_TYPE = "Crust Type"; - - /** - * The toppings label text. - */ - private static final String TOPPINGS = "Toppings"; - - /** - * The rest button text. - */ - private static final String RESET = "Reset"; - - /** - * The place order button text. - */ - private static final String PLACE_ORDER = "Place Order"; - - /** - * The extras label text. - */ - private static final String EXTRAS = "Extras:"; - - /** - * The bread sticks string. - */ - private static final String BREAD_STICKS = "Bread Sticks"; - - /** - * The salad string. - */ - private static final String SALAD = "Salad"; - - /** - * The soda string. - */ - private static final String SODA = "Soda"; - - /** - * The order comments string. - */ - private static final String ORDER_COMMENTS = "Order Comments"; - - /** - * The value for an empty topping list. - */ - private static final String PLAIN = "Plain"; - - /** - * The default crust type. - */ - private static final String THIN = "Thin"; - - /** - * The text for if no order comments are specified. - */ - private static final String NO_COMMENTS = "No comments"; - - /** - * The title of the order confirmation inform pane. - */ - private static final String informTitle = "Order"; - - /** - * The text for if no extras are specified. - */ - private static final String NO_EXTRAS = "No extras"; - - /** - * The possible values for pizza crusts. - */ - private static final ImmutableList crustTypes = ImmutableList.of( - "Thin", - "Thick", - "Deep dish", - "Classic", - "Tavern", - "Seasonal"); - - /** - * The possible values for pizza toppings. - */ - private static final ImmutableList pizzaToppings = ImmutableList.of( - "Pepperoni", - "Sausage", - "Green peppers", - "Onions", - "Tomatoes", - "Anchovies", - "Bacon", - "Chicken", - "Beef", - "Olives", - "Mushrooms"); - - /** - * The length of the pizza topping scroll (width and height). - */ - private static final int pizzaToppingsScrollLength = 200; - - /** - * The width of the crust type scroll. - */ - private static final int crustScrollWidth = 160; - - /** - * The height of the crust type scroll. - */ - private static final int crustScrollHeight = 200; - - /** - * Suppress default constructor. - */ - private PizzaWidget() { - throw new IllegalMethodException(CyderStrings.ATTEMPTED_INSTANTIATION); - } - - @Widget(triggers = "pizza", description = "A fake pizza ordering widget") - public static void showGui() { - UiUtil.closeIfOpen(pizzaFrame); - - pizzaFrame = new CyderFrame.Builder() - .setWidth(FRAME_WIDTH) - .setHeight(FRAME_HEIGHT) - .setTitle(FRAME_TITLE) - .build(); - - JLabel nameLabel = new JLabel(NAME); - nameLabel.setFont(CyderFonts.SEGOE_20); - nameLabel.setForeground(CyderColors.navy); - nameLabel.setBounds(40, 45, 100, 30); - pizzaFrame.getContentPane().add(nameLabel); - - nameField = new CyderTextField(); - nameField.setHorizontalAlignment(JTextField.CENTER); - nameField.setAutoCapitalization(true); - nameField.setBackground(Color.white); - nameField.setBounds(140, 40, 400, 40); - pizzaFrame.getContentPane().add(nameField); - - JLabel sizeLabel = new JLabel(SIZE); - sizeLabel.setFont(CyderFonts.SEGOE_20); - sizeLabel.setForeground(CyderColors.navy); - sizeLabel.setBounds(40, 140, 50, 30); - pizzaFrame.getContentPane().add(sizeLabel); - - JLabel smallLabel = new JLabel(SMALL); - smallLabel.setFont(CyderFonts.SEGOE_20); - smallLabel.setForeground(CyderColors.navy); - smallLabel.setBounds(180, 100, 100, 30); - pizzaFrame.getContentPane().add(smallLabel); - - JLabel mediumLabel = new JLabel(MEDIUM); - mediumLabel.setFont(CyderFonts.SEGOE_20); - mediumLabel.setForeground(CyderColors.navy); - mediumLabel.setBounds(285, 100, 100, 30); - pizzaFrame.getContentPane().add(mediumLabel); - - JLabel largeLabel = new JLabel(LARGE); - largeLabel.setFont(CyderFonts.SEGOE_20); - largeLabel.setForeground(CyderColors.navy); - largeLabel.setBounds(420, 100, 100, 30); - pizzaFrame.getContentPane().add(largeLabel); - - sizeGroup = new CyderCheckboxGroup(); - - smallCheckbox = new CyderCheckbox(); - smallCheckbox.setHorizontalAlignment(SwingConstants.CENTER); - smallCheckbox.setNotChecked(); - smallCheckbox.setBounds(185, 135, 50, 50); - pizzaFrame.getContentPane().add(smallCheckbox); - sizeGroup.addCheckbox(smallCheckbox); - - mediumCheckbox = new CyderCheckbox(); - mediumCheckbox.setHorizontalAlignment(SwingConstants.CENTER); - mediumCheckbox.setBounds(305, 135, 50, 50); - pizzaFrame.getContentPane().add(mediumCheckbox); - sizeGroup.addCheckbox(mediumCheckbox); - - largeCheckbox = new CyderCheckbox(); - largeCheckbox.setHorizontalAlignment(SwingConstants.CENTER); - largeCheckbox.setNotChecked(); - largeCheckbox.setBounds(425, 135, 50, 50); - pizzaFrame.getContentPane().add(largeCheckbox); - sizeGroup.addCheckbox(largeCheckbox); - - JLabel crustLabel = new JLabel(CRUST_TYPE); - crustLabel.setFont(CyderFonts.SEGOE_20); - crustLabel.setForeground(CyderColors.navy); - crustLabel.setBounds(90, 210, 130, 30); - pizzaFrame.getContentPane().add(crustLabel); - - JLabel Toppings = new JLabel(TOPPINGS); - Toppings.setFont(CyderFonts.SEGOE_20); - Toppings.setForeground(CyderColors.navy); - Toppings.setBounds(370, 210, 130, 30); - pizzaFrame.getContentPane().add(Toppings); - - crustTypeScroll = new CyderScrollList(crustScrollWidth, crustScrollHeight, - CyderScrollList.SelectionPolicy.SINGLE); - crustTypes.forEach(crustType -> crustTypeScroll.addElement(crustType)); - - JLabel crustTypeLabel = crustTypeScroll.generateScrollList(); - crustTypeLabel.setBounds(80, 250, 160, 200); - pizzaFrame.getContentPane().add(crustTypeLabel); - - pizzaToppingsScroll = new CyderScrollList(pizzaToppingsScrollLength, pizzaToppingsScrollLength, - CyderScrollList.SelectionPolicy.MULTIPLE); - pizzaToppings.forEach(topping -> pizzaToppingsScroll.addElement(topping)); - - JLabel pizzaToppingsLabel = pizzaToppingsScroll.generateScrollList(); - pizzaToppingsLabel.setBounds(320, 250, 200, 200); - pizzaFrame.getContentPane().add(pizzaToppingsLabel); - - JLabel Extra = new JLabel(EXTRAS); - Extra.setForeground(CyderColors.navy); - Extra.setFont(CyderFonts.SEGOE_20); - Extra.setBounds(40, 510, 130, 30); - pizzaFrame.getContentPane().add(Extra); - - JLabel breadSticksLabel = new JLabel(BREAD_STICKS); - breadSticksLabel.setFont(CyderFonts.SEGOE_20); - breadSticksLabel.setForeground(CyderColors.navy); - breadSticksLabel.setBounds(130, 470, 150, 30); - pizzaFrame.getContentPane().add(breadSticksLabel); - - breadSticks = new CyderCheckbox(); - breadSticks.setHorizontalAlignment(SwingConstants.CENTER); - breadSticks.setNotChecked(); - breadSticks.setBounds(165, 505, 50, 50); - pizzaFrame.getContentPane().add(breadSticks); - - JLabel saladLabel = new JLabel(SALAD); - saladLabel.setFont(CyderFonts.SEGOE_20); - saladLabel.setForeground(CyderColors.navy); - saladLabel.setBounds(310, 470, 150, 30); - pizzaFrame.getContentPane().add(saladLabel); - - salad = new CyderCheckbox(); - salad.setHorizontalAlignment(SwingConstants.CENTER); - salad.setNotChecked(); - salad.setBounds(315, 505, 50, 50); - pizzaFrame.getContentPane().add(salad); - - JLabel sodaLabel = new JLabel(SODA); - sodaLabel.setFont(CyderFonts.SEGOE_20); - sodaLabel.setForeground(CyderColors.navy); - sodaLabel.setBounds(445, 470, 150, 30); - pizzaFrame.getContentPane().add(sodaLabel); - - soda = new CyderCheckbox(); - soda.setHorizontalAlignment(SwingConstants.CENTER); - soda.setNotChecked(); - soda.setBounds(445, 505, 50, 50); - pizzaFrame.getContentPane().add(soda); - - JLabel orderCommentsLabel = new JLabel(ORDER_COMMENTS); - orderCommentsLabel.setFont(CyderFonts.SEGOE_20); - orderCommentsLabel.setForeground(CyderColors.navy); - orderCommentsLabel.setBounds(210, 565, 200, 30); - pizzaFrame.getContentPane().add(orderCommentsLabel); - - orderComments = new JTextArea(5, 20); - orderComments.setFont(CyderFonts.SEGOE_20); - orderComments.setAutoscrolls(true); - orderComments.setLineWrap(true); - orderComments.setWrapStyleWord(true); - orderComments.setSelectionColor(CyderColors.selectionColor); - orderComments.setBorder(new LineBorder(new Color(0, 0, 0))); - - CyderScrollPane orderCommentsScroll = new CyderScrollPane(orderComments, - ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS, - ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); - orderCommentsScroll.setThumbColor(CyderColors.regularRed); - orderCommentsScroll.setBorder(new LineBorder(CyderColors.navy, 5, false)); - orderCommentsScroll.setBounds(80, 600, 600 - 2 * 80, 120); - pizzaFrame.getContentPane().add(orderCommentsScroll); - - CyderButton placeOrder = new CyderButton(PLACE_ORDER); - placeOrder.setFont(CyderFonts.SEGOE_20); - placeOrder.addActionListener(e -> placeOrderAction()); - placeOrder.setBounds(80, 740, 200, 40); - pizzaFrame.getContentPane().add(placeOrder); - - CyderButton resetPizza = new CyderButton(RESET); - resetPizza.setFont(CyderFonts.SEGOE_20); - resetPizza.addActionListener(e -> reset()); - resetPizza.setBounds(180 + 100 + 40, 740, 200, 40); - pizzaFrame.getContentPane().add(resetPizza); - - pizzaFrame.finalizeAndShow(); - } - - /** - * The action to run when the place order button is clicked. - */ - @ForReadability - private static void placeOrderAction() { - String name = nameField.getTrimmedText(); - if (name.isEmpty()) { - pizzaFrame.notify("Please enter a valid name"); - return; - } - name = StringUtil.capsFirstWords(name); - - Optional optionalSize = getSize(); - if (optionalSize.isEmpty()) { - pizzaFrame.notify("Please specify a size"); - return; - } - String size = optionalSize.get() + HtmlTags.breakTag; - - String crust; - ImmutableList selectedElements = crustTypeScroll.getSelectedElements(); - if (selectedElements.isEmpty()) { - crust = THIN; - } else { - crust = selectedElements.get(0); - } - - StringBuilder toppingsChosen = new StringBuilder(); - ImmutableList selectedToppings = pizzaToppingsScroll.getSelectedElements(); - if (selectedToppings.isEmpty()) { - toppingsChosen.append(PLAIN); - } else { - selectedToppings.forEach(topping -> toppingsChosen.append(topping).append(HtmlTags.breakTag)); - } - - ImmutableList extrasList = getExtras(); - String extras; - if (extrasList.isEmpty()) { - extras = NO_EXTRAS; - } else { - StringBuilder extraBuilder = new StringBuilder(); - extrasList.forEach(extra -> extraBuilder.append(extra).append(HtmlTags.breakTag)); - extras = extraBuilder.toString(); - } - - String comments = StringUtil.getTrimmedText(orderComments.getText()); - if (comments.isEmpty()) { - comments = NO_COMMENTS; - } - - pizzaFrame.inform("Name: " + HtmlTags.breakTag + name + HtmlTags.breakTag + HtmlTags.breakTag - + "Size: " + HtmlTags.breakTag + size + HtmlTags.breakTag + HtmlTags.breakTag - + "Crust: " + HtmlTags.breakTag + crust + HtmlTags.breakTag + HtmlTags.breakTag - + "Toppings: " + HtmlTags.breakTag + toppingsChosen + HtmlTags.breakTag - + "Extras: " + HtmlTags.breakTag + extras + HtmlTags.breakTag - + "Comments: " + HtmlTags.breakTag + comments + HtmlTags.breakTag, informTitle); - } - - /** - * Returns a list of extras. - * - * @return a list of extras - */ - private static ImmutableList getExtras() { - ArrayList ret = new ArrayList<>(); - - if (breadSticks.isChecked()) { - ret.add(BREAD_STICKS); - } - if (salad.isChecked()) { - ret.add(SALAD); - } - if (soda.isChecked()) { - ret.add(SODA); - } - - return ImmutableList.copyOf(ret); - } - - /** - * Returns the pizza size if present. Empty optional else. - * - * @return the pizza size if present. Empty optional else - */ - @ForReadability - private static Optional getSize() { - if (smallCheckbox.isChecked()) { - return Optional.of(SMALL); - } else if (mediumCheckbox.isChecked()) { - return Optional.of(MEDIUM); - } else if (largeCheckbox.isChecked()) { - return Optional.of(LARGE); - } else { - return Optional.empty(); - } - } - - /** - * Resets the state of the pizza widget. - */ - @ForReadability - private static void reset() { - nameField.setText(""); - - sizeGroup.clearSelection(); - - crustTypeScroll.deselectAllElements(); - crustTypeScroll.getScrollPane().getHorizontalScrollBar().setValue(0); - pizzaToppingsScroll.deselectAllElements(); - pizzaToppingsScroll.getScrollPane().getHorizontalScrollBar().setValue(0); - - breadSticks.setNotChecked(); - salad.setNotChecked(); - soda.setNotChecked(); - - orderComments.setText(""); - } -} diff --git a/src/main/java/cyder/widgets/package-info.java b/src/main/java/cyder/widgets/package-info.java deleted file mode 100644 index c281b7261..000000000 --- a/src/main/java/cyder/widgets/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -/** - * Cyder widgets for performing conversions, operations, and visualizations. - */ -package cyder.widgets;