├── .gitignore ├── LICENSE ├── README.md ├── build.gradle ├── rundemo.sh └── src ├── main ├── java │ └── com │ │ ├── demo │ │ ├── CustomNotification.java │ │ ├── CustomNotificationDemo.java │ │ ├── QueueManagerDemo.java │ │ ├── SequenceManagerDemo.java │ │ ├── SimpleManagerDemo.java │ │ └── SlideManagerDemo.java │ │ ├── exception │ │ └── NotificationException.java │ │ ├── notification │ │ ├── Notification.java │ │ ├── NotificationBuilder.java │ │ ├── NotificationFactory.java │ │ ├── NotificationListener.java │ │ ├── NotificationManager.java │ │ ├── manager │ │ │ ├── QueueManager.java │ │ │ ├── SequenceManager.java │ │ │ ├── SimpleManager.java │ │ │ └── SlideManager.java │ │ └── types │ │ │ ├── AcceptNotification.java │ │ │ ├── BorderLayoutNotification.java │ │ │ ├── IconNotification.java │ │ │ ├── ProgressBarNotification.java │ │ │ ├── TextNotification.java │ │ │ └── WindowNotification.java │ │ ├── platform │ │ ├── DefaultOperatingSystem.java │ │ ├── Mac.java │ │ ├── OperatingSystem.java │ │ ├── Platform.java │ │ ├── Unix.java │ │ └── Windows.java │ │ ├── theme │ │ ├── TextTheme.java │ │ ├── ThemePackage.java │ │ ├── ThemePackagePresets.java │ │ └── WindowTheme.java │ │ └── utils │ │ ├── IconUtils.java │ │ ├── MathUtils.java │ │ ├── Screen.java │ │ └── Time.java └── resources │ └── com │ └── demo │ └── exclamation.png └── test └── java └── com └── unit ├── MathUtilsTest.java ├── NotificationFactoryTest.java ├── NotificationManagerTest.java ├── NotificationTest.java ├── ProgressBarNotificationTest.java ├── TestUtils.java └── WindowNotificationTest.java /.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | build/ 3 | .gradle/ 4 | github-release/ 5 | bintray-release/ 6 | release.sh 7 | 8 | .project 9 | .classpath 10 | .settings/ 11 | 12 | *~ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Samuel Pfrommer 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JCommunique 2 | JCommunique is a free, open-source library written in pure Java. It uses Swing to show pop-up notifications on the desktop and offers a range of features with a simple-to-use API. Refer to the demo package for examples. 3 | 4 | ### JCommunique is no longer being actively maintained. 5 | 6 | ### Releases 7 | Stable JCommunique releases as jars are available at [2]. As of version 2.0.0, source and javadoc jars are also available. The project is also distributed on JCenter [5]. 8 | 9 | You can add these to your project's build path just as you would include any normal jar. This process can vary across IDEs, but Eclipse instructions are available at [3]. 10 | 11 | ### Building the project 12 | As of version 2.0.0, JCommunique is set up as a Gradle project (previously an Eclipse project). Most of the building can be done using the standard commands provided in the Gradle Java plugin, which are documented extensively online and therefore will not be covered here. To completely test the system, run "gradle build" (will compile, make Javadoc, and run unit tests). To run a demo, use the rundemo.sh script (for Unix machines). Using the terminal, navigate to the directory with rundemo.sh in it and run "sh rundemo.sh NAME_OF_DEMO". For example, to run the SlideManagerDemo.java demo, use the command "sh rundemo.sh SlideManagerDemo". 13 | 14 | ### Contribution 15 | Contributions can be done via a standard pull request, described in [1]. If you have any questions about contributing or any other topics, please feel free to contact me at sam (dot) pfrommer (at) gmail.com. 16 | 17 | See the Github wiki page for more information [4]. 18 | 19 | [1] https://help.github.com/articles/using-pull-requests/ 20 | 21 | [2] https://github.com/spfrommer/JCommunique/releases 22 | 23 | [3] http://www.wikihow.com/Add-JARs-to-Project-Build-Paths-in-Eclipse-%28Java%29 24 | 25 | [4] https://github.com/spfrommer/JCommunique/wiki 26 | 27 | [5] https://bintray.com/spfrommer/maven/jcommunique/view 28 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java' 2 | apply plugin: 'eclipse' 3 | apply plugin: 'maven' 4 | 5 | sourceCompatibility = 1.7 6 | targetCompatibility = 1.7 7 | version = '2.0.0' 8 | 9 | repositories { 10 | jcenter() 11 | } 12 | 13 | dependencies { 14 | testCompile 'junit:junit:4.12' 15 | } 16 | 17 | sourceSets { 18 | main { 19 | java { 20 | srcDir 'src/main/java' 21 | } 22 | resources { 23 | srcDir 'src/main/resources' 24 | } 25 | output.resourcesDir = "build/classes/main" 26 | } 27 | 28 | test { 29 | java { 30 | srcDir 'src/test/java' 31 | } 32 | } 33 | } 34 | 35 | test { 36 | testLogging { 37 | exceptionFormat = 'full' 38 | } 39 | } 40 | 41 | clean { 42 | delete "github-release" 43 | delete "bintray-release" 44 | delete "bin" 45 | } 46 | 47 | task createPom << { 48 | pom { 49 | project { 50 | groupId 'org.jcommunique' 51 | artifactId 'JCommunique' 52 | version '2.0.0' 53 | 54 | inceptionYear '2015' 55 | licenses { 56 | license { 57 | name 'The MIT License' 58 | url 'http://opensource.org/licenses/MIT' 59 | distribution 'repo' 60 | } 61 | } 62 | } 63 | }.writeTo("pom.xml") 64 | } 65 | 66 | jar { 67 | manifest { 68 | attributes 'Implementation-Title': 'Gradle Quickstart', 69 | 'Implementation-Version': version 70 | } 71 | } 72 | 73 | task sourcesJar(type: Jar, dependsOn: classes) { 74 | classifier = 'sources' 75 | from sourceSets.main.allSource 76 | } 77 | 78 | task javadocJar(type: Jar, dependsOn: javadoc) { 79 | classifier = 'javadoc' 80 | from javadoc.destinationDir 81 | } 82 | 83 | artifacts { 84 | archives sourcesJar 85 | archives javadocJar 86 | } 87 | 88 | task rebuild(dependsOn: ['clean', 'build']) 89 | build.mustRunAfter clean 90 | -------------------------------------------------------------------------------- /rundemo.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #Assembles and runs the demo specified by the command line argument. 4 | #The command line argument should be the name of the file with the main in it. 5 | 6 | gradle assemble 7 | cd build/classes/main 8 | java com.demo.$1 9 | -------------------------------------------------------------------------------- /src/main/java/com/demo/CustomNotification.java: -------------------------------------------------------------------------------- 1 | package com.demo; 2 | 3 | import java.awt.BorderLayout; 4 | import java.awt.event.ActionEvent; 5 | import java.awt.event.ActionListener; 6 | 7 | import javax.swing.JButton; 8 | import javax.swing.JLabel; 9 | import javax.swing.JProgressBar; 10 | import javax.swing.Timer; 11 | 12 | import com.notification.NotificationBuilder; 13 | import com.notification.types.BorderLayoutNotification; 14 | import com.theme.TextTheme; 15 | import com.theme.ThemePackage; 16 | import com.theme.WindowTheme; 17 | 18 | /** 19 | * This is a CustomNotification which will only have one line of text and a button that will turn into a progress bar. 20 | */ 21 | public class CustomNotification extends BorderLayoutNotification { 22 | private JLabel m_label; 23 | private JButton m_button; 24 | private JProgressBar m_progress; 25 | 26 | private TextTheme m_theme; 27 | 28 | public CustomNotification() { 29 | m_label = new JLabel(); 30 | m_button = new JButton("Click me!"); 31 | m_progress = new JProgressBar(); 32 | 33 | m_button.addActionListener(new ActionListener() { 34 | @Override 35 | public void actionPerformed(ActionEvent e) { 36 | removeComponent(m_button); 37 | addComponent(m_progress, BorderLayout.SOUTH); 38 | final Timer timer = new Timer(100, null); 39 | 40 | timer.addActionListener(new ActionListener() { 41 | @Override 42 | public void actionPerformed(ActionEvent e) { 43 | m_progress.setValue(m_progress.getValue() + 1); 44 | m_progress.repaint(); 45 | 46 | if (m_progress.getValue() == 100) { 47 | timer.stop(); 48 | hide(); 49 | } 50 | } 51 | }); 52 | 53 | timer.start(); 54 | } 55 | }); 56 | 57 | this.addComponent(m_label, BorderLayout.CENTER); 58 | this.addComponent(m_button, BorderLayout.SOUTH); 59 | } 60 | 61 | /** 62 | * This will set the title and subtitle font and color. 63 | * 64 | * @param theme 65 | * the TextTheme to set 66 | */ 67 | public void setTextTheme(TextTheme theme) { 68 | m_label.setFont(theme.title); 69 | m_label.setForeground(theme.titleColor); 70 | m_button.setFont(theme.subtitle); 71 | m_button.setForeground(theme.subtitleColor); 72 | 73 | m_theme = theme; 74 | } 75 | 76 | public String getText() { 77 | return m_label.getText(); 78 | } 79 | 80 | public void setText(String text) { 81 | m_label.setText(text); 82 | } 83 | 84 | @Override 85 | public void setWindowTheme(WindowTheme theme) { 86 | super.setWindowTheme(theme); 87 | 88 | if (m_theme != null) { 89 | // the WindowNotification is going to automatically give all our labels with the set foreground color, but 90 | // we want to change this to the title color of the font 91 | m_label.setForeground(m_theme.titleColor); 92 | m_button.setForeground(m_theme.subtitleColor); 93 | } 94 | } 95 | 96 | public static class CustomBuilder implements NotificationBuilder { 97 | @Override 98 | public CustomNotification buildNotification(ThemePackage pack, Object... args) { 99 | CustomNotification note = new CustomNotification(); 100 | // handled by the WindowNotification first, then we override the values we want to keep 101 | note.setWindowTheme(pack.getTheme(WindowTheme.class)); 102 | // handled by us 103 | note.setTextTheme(pack.getTheme(TextTheme.class)); 104 | 105 | if (args.length > 0) { 106 | note.setText((String) args[0]); 107 | } else { 108 | note.setText("No text supplied"); 109 | } 110 | return note; 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/main/java/com/demo/CustomNotificationDemo.java: -------------------------------------------------------------------------------- 1 | package com.demo; 2 | 3 | import com.notification.NotificationFactory; 4 | import com.notification.NotificationManager; 5 | import com.notification.NotificationFactory.Location; 6 | import com.notification.manager.SimpleManager; 7 | import com.platform.Platform; 8 | import com.theme.ThemePackagePresets; 9 | import com.utils.Time; 10 | 11 | public class CustomNotificationDemo { 12 | public static void main(String[] args) throws Exception { 13 | Platform.instance().setAdjustForPlatform(true); 14 | // UIManager.setLookAndFeel("com.sun.java.swing.plaf.motif.MotifLookAndFeel"); 15 | // register the custom builder with the factory 16 | NotificationFactory factory = new NotificationFactory(ThemePackagePresets.cleanLight()); 17 | factory.addBuilder(CustomNotification.class, new CustomNotification.CustomBuilder()); 18 | 19 | // add the Notification 20 | NotificationManager manager = new SimpleManager(Location.NORTH); 21 | manager.addNotification(factory.build(CustomNotification.class, "this is test text"), Time.infinite()); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/demo/QueueManagerDemo.java: -------------------------------------------------------------------------------- 1 | package com.demo; 2 | 3 | import com.notification.NotificationFactory; 4 | import com.notification.NotificationFactory.Location; 5 | import com.notification.manager.QueueManager; 6 | import com.notification.types.WindowNotification; 7 | import com.platform.Platform; 8 | import com.theme.ThemePackagePresets; 9 | import com.utils.IconUtils; 10 | import com.utils.Time; 11 | 12 | public class QueueManagerDemo { 13 | public static void main(String[] args) throws Exception { 14 | Platform.instance().setAdjustForPlatform(true); 15 | 16 | NotificationFactory factory = new NotificationFactory(ThemePackagePresets.cleanLight()); 17 | QueueManager manager = new QueueManager(Location.NORTHWEST); 18 | // sets how quickly old notifications should move out of the way for new ones 19 | manager.setSnapFactor(0.23); 20 | 21 | for (int i = 0; i < 10; i++) { 22 | int type = i % 3; 23 | WindowNotification note = null; 24 | switch (type) { 25 | case 0: 26 | note = factory.buildIconNotification("IconNotification", "Subtitle", 27 | IconUtils.createIcon("/com/demo/exclamation.png", 50, 50)); 28 | break; 29 | case 1: 30 | note = factory.buildTextNotification("TextNotification", "Subtitle"); 31 | break; 32 | case 2: 33 | note = factory.buildAcceptNotification("AcceptNotification", "Do you accept?"); 34 | break; 35 | } 36 | // when the notification is clicked, it should hide itself 37 | note.setCloseOnClick(true); 38 | // make it show in the queue for five seconds 39 | manager.addNotification(note, Time.seconds(10)); 40 | // one second delay between creations 41 | Thread.sleep(1000); 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /src/main/java/com/demo/SequenceManagerDemo.java: -------------------------------------------------------------------------------- 1 | package com.demo; 2 | 3 | import com.notification.Notification; 4 | import com.notification.NotificationFactory; 5 | import com.notification.manager.SequenceManager; 6 | import com.notification.types.WindowNotification; 7 | import com.platform.Platform; 8 | import com.theme.ThemePackagePresets; 9 | import com.utils.IconUtils; 10 | import com.utils.Time; 11 | 12 | /** 13 | * This is a simple demo which tests the SequenceManager. 14 | */ 15 | public class SequenceManagerDemo { 16 | public static void main(String[] args) { 17 | // this will make the Notifications match the limits of the platform 18 | // this will mean no fading on unix machines (since it doesn't look too good) 19 | Platform.instance().setAdjustForPlatform(true); 20 | 21 | // makes a factory with the built-in clean light theme 22 | NotificationFactory factory = new NotificationFactory(ThemePackagePresets.aqua()); 23 | SequenceManager sequence = new SequenceManager(); 24 | // makes the notifications fade in and out 25 | sequence.setFadeEnabled(true); 26 | 27 | // builds the various test Notifications 28 | Notification note = factory.buildTextNotification("Test", "test"); 29 | WindowNotification note2 = factory.buildIconNotification("You must click this!", "To make it go away.", 30 | IconUtils.createIcon("/com/demo/exclamation.png", 50, 50)); 31 | note2.setCloseOnClick(true); 32 | Notification note3 = factory.buildAcceptNotification("Test 3", "This will show for three seconds"); 33 | 34 | // adds the Notifications in the order that they should be shown in 35 | // the next Notification will not appear until the previous has become hidden 36 | sequence.addNotification(note, Time.seconds(3)); 37 | sequence.addNotification(note2, Time.infinite()); 38 | sequence.addNotification(note3, Time.seconds(3)); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/com/demo/SimpleManagerDemo.java: -------------------------------------------------------------------------------- 1 | package com.demo; 2 | 3 | import com.notification.NotificationFactory; 4 | import com.notification.NotificationFactory.Location; 5 | import com.notification.NotificationManager; 6 | import com.notification.manager.SimpleManager; 7 | import com.notification.types.AcceptNotification; 8 | import com.notification.types.IconNotification; 9 | import com.notification.types.ProgressBarNotification; 10 | import com.notification.types.TextNotification; 11 | import com.theme.ThemePackagePresets; 12 | import com.utils.IconUtils; 13 | import com.utils.Time; 14 | 15 | /** 16 | * This is a simple demo which uses the SimpleManager to show different types of Notifications. 17 | */ 18 | public class SimpleManagerDemo { 19 | public static void main(String[] args) throws InterruptedException { 20 | // this will make the Notifications match the limits of the platform 21 | // this will mean no fading on unix machines (since it doesn't look too good) 22 | // Platform.instance().setAdjustForPlatform(true); 23 | 24 | // makes a factory with the built-in clean dark theme 25 | NotificationFactory factory = new NotificationFactory(ThemePackagePresets.cleanDark()); 26 | // a normal manager that just pops up the notification 27 | NotificationManager plain = new SimpleManager(Location.NORTHWEST); 28 | // a fade manager that will make the window fade in and out over a two second period 29 | SimpleManager fade = new SimpleManager(Location.SOUTHWEST); 30 | fade.setFadeEnabled(true); 31 | fade.setFadeTime(Time.seconds(2)); 32 | 33 | // adds a text notification to the first manager 34 | TextNotification notification = factory.buildTextNotification("This is the dark theme", 35 | "You can have multiple lines\nOf subtitle text as well\nLine 3"); 36 | notification.setCloseOnClick(true); 37 | // the notification will stay there forever until you click it to exit 38 | plain.addNotification(notification, Time.infinite()); 39 | 40 | Thread.sleep(2000); 41 | // adds an icon notification that should fade in - note that results may vary depending on the platform 42 | IconNotification icon = factory.buildIconNotification("This is a really really really long title", "See the cutoff?", 43 | IconUtils.createIcon("/com/demo/exclamation.png", 50, 50)); 44 | fade.addNotification(icon, Time.seconds(2)); 45 | 46 | Thread.sleep(6000); 47 | AcceptNotification accept = factory.buildAcceptNotification("Do you accept?", "This is a fading notification."); 48 | fade.addNotification(accept, Time.infinite()); 49 | 50 | boolean didAccept = accept.blockUntilReply(); 51 | ProgressBarNotification reply = null; 52 | if (didAccept) { 53 | reply = factory.buildProgressBarNotification("You accepted"); 54 | } else { 55 | reply = factory.buildProgressBarNotification("You did not accept"); 56 | } 57 | reply.setCloseOnClick(true); 58 | fade.addNotification(reply, Time.infinite()); 59 | 60 | for (int i = 0; i < 100; i++) { 61 | reply.setProgress(i); 62 | Thread.sleep(100); 63 | } 64 | fade.removeNotification(reply); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/com/demo/SlideManagerDemo.java: -------------------------------------------------------------------------------- 1 | package com.demo; 2 | 3 | import com.notification.NotificationFactory; 4 | import com.notification.NotificationFactory.Location; 5 | import com.notification.manager.SlideManager; 6 | import com.notification.manager.SlideManager.SlideDirection; 7 | import com.notification.types.TextNotification; 8 | import com.theme.ThemePackagePresets; 9 | import com.utils.Time; 10 | 11 | public class SlideManagerDemo { 12 | public static void main(String[] args) throws Exception { 13 | // makes a factory with the clean theme 14 | NotificationFactory factory = new NotificationFactory(ThemePackagePresets.cleanLight()); 15 | // adds a new manager which displays Notifications at the bottom right of the screen 16 | SlideManager manager = new SlideManager(Location.SOUTHEAST); 17 | 18 | // adds a notification that appears southeast and slides in the default direction, north 19 | manager.addNotification(factory.buildTextNotification("This is sliding north", "Appeared southeast"), Time.seconds(5)); 20 | 21 | manager.setSlideDirection(SlideDirection.WEST); 22 | // adds a notification that appears southeast and slides west 23 | manager.addNotification(factory.buildTextNotification("This is sliding west", "Appeared southeast"), Time.seconds(1)); 24 | // these two notifications should finish in the same end position 25 | 26 | Thread.sleep(1000); 27 | manager.setLocation(Location.NORTHEAST); 28 | manager.setSlideDirection(SlideDirection.SOUTH); 29 | TextNotification northeastNotification = factory.buildTextNotification("This is sliding south", "Appeared northeast"); 30 | northeastNotification.setCloseOnClick(true); 31 | manager.addNotification(northeastNotification, Time.seconds(3)); 32 | } 33 | } -------------------------------------------------------------------------------- /src/main/java/com/exception/NotificationException.java: -------------------------------------------------------------------------------- 1 | package com.exception; 2 | 3 | public class NotificationException extends RuntimeException { 4 | private static final long serialVersionUID = 1L; 5 | 6 | public NotificationException(String exception) { 7 | super(exception); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/notification/Notification.java: -------------------------------------------------------------------------------- 1 | package com.notification; 2 | 3 | import java.util.List; 4 | import java.util.concurrent.CopyOnWriteArrayList; 5 | 6 | /** 7 | * Provides the core methods that a Notification needs. 8 | */ 9 | public abstract class Notification { 10 | private NotificationManager m_manager; 11 | private List m_listeners; 12 | 13 | public Notification() { 14 | m_listeners = new CopyOnWriteArrayList(); 15 | } 16 | 17 | /** 18 | * Listens for events on the Notification (e.g., a click). 19 | * 20 | * @param listener 21 | * the NotificationListener to add 22 | */ 23 | public void addNotificationListener(NotificationListener listener) { 24 | m_listeners.add(listener); 25 | } 26 | 27 | /** 28 | * Removes a listener for events on the Notification (e.g., a click). 29 | * 30 | * @param listener 31 | * the NotificationListener to remove 32 | */ 33 | public void removeNotificationListener(NotificationListener listener) { 34 | m_listeners.remove(listener); 35 | } 36 | 37 | /** 38 | * @return whether or not this Notification has been added to a NotificationManager 39 | */ 40 | public boolean isManaged() { 41 | return m_manager != null; 42 | } 43 | 44 | /** 45 | * @return the NotificationManager managing this Notification 46 | */ 47 | public NotificationManager getNotificationManager() { 48 | return m_manager; 49 | } 50 | 51 | protected void setNotificationManager(NotificationManager manager) { 52 | m_manager = manager; 53 | } 54 | 55 | /** 56 | * Removes the Notification from the Manager. In some cases, this has the same effect as calling hide(); however, 57 | * hide() doesn't invoke Manager-related things like fading, etc. 58 | */ 59 | public void removeFromManager() { 60 | m_manager.removeNotification(this); 61 | } 62 | 63 | protected void fireListeners(String action) { 64 | for (NotificationListener nl : m_listeners) { 65 | nl.actionCompleted(this, action); 66 | } 67 | } 68 | 69 | public abstract int getX(); 70 | 71 | public abstract int getY(); 72 | 73 | public abstract void setLocation(int x, int y); 74 | 75 | public abstract int getWidth(); 76 | 77 | public abstract int getHeight(); 78 | 79 | public abstract void setSize(int width, int height); 80 | 81 | public abstract double getOpacity(); 82 | 83 | public abstract void setOpacity(double opacity); 84 | 85 | /** 86 | * Reveals the Notification on the desktop. 87 | */ 88 | public abstract void show(); 89 | 90 | /** 91 | * Hides the Notification on the desktop. 92 | */ 93 | public abstract void hide(); 94 | 95 | /** 96 | * @return whether the Notification is being shown 97 | */ 98 | public abstract boolean isShown(); 99 | } 100 | -------------------------------------------------------------------------------- /src/main/java/com/notification/NotificationBuilder.java: -------------------------------------------------------------------------------- 1 | package com.notification; 2 | 3 | import com.theme.ThemePackage; 4 | 5 | /** 6 | * This interface is implemented for building custom Notifications. 7 | * 8 | * @param 9 | * the type to build 10 | */ 11 | public interface NotificationBuilder { 12 | /** 13 | * Builds a Notification in accordance with the ThemePackage. 14 | * 15 | * @param pack 16 | * the ThemePackage to apply to the Notification 17 | * @param args 18 | * optional additional arguments passed to the NotificationBuilder 19 | * @return the built Notification 20 | */ 21 | public T buildNotification(ThemePackage pack, Object... args); 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/notification/NotificationFactory.java: -------------------------------------------------------------------------------- 1 | package com.notification; 2 | 3 | import java.util.HashMap; 4 | 5 | import javax.swing.Icon; 6 | import javax.swing.ImageIcon; 7 | 8 | import com.exception.NotificationException; 9 | import com.notification.types.AcceptNotification; 10 | import com.notification.types.IconNotification; 11 | import com.notification.types.ProgressBarNotification; 12 | import com.notification.types.TextNotification; 13 | import com.theme.TextTheme; 14 | import com.theme.ThemePackage; 15 | import com.theme.ThemePackagePresets; 16 | import com.theme.WindowTheme; 17 | 18 | /** 19 | * Creates Notifications using a ThemePackage. It is possible to add custom Notifications by adding 20 | * NotificationBuilders. 21 | */ 22 | public final class NotificationFactory { 23 | private ThemePackage m_pack; 24 | private HashMap, NotificationBuilder> m_builders; 25 | 26 | public enum Location { 27 | NORTH, NORTHEAST, EAST, SOUTHEAST, SOUTH, SOUTHWEST, WEST, NORTHWEST 28 | } 29 | 30 | { 31 | m_builders = new HashMap, NotificationBuilder>(); 32 | m_builders.put(TextNotification.class, new TextNotificationBuilder()); 33 | m_builders.put(AcceptNotification.class, new AcceptNotificationBuilder()); 34 | m_builders.put(IconNotification.class, new IconNotificationBuilder()); 35 | m_builders.put(ProgressBarNotification.class, new ProgressBarNotificationBuilder()); 36 | } 37 | 38 | public NotificationFactory() { 39 | setThemePackage(ThemePackagePresets.cleanLight()); 40 | } 41 | 42 | public NotificationFactory(ThemePackage pack) { 43 | setThemePackage(pack); 44 | } 45 | 46 | /** 47 | * Sets the themes that the factory should use when creating notifications. See ThemePackagePresets for some default 48 | * themes. 49 | * 50 | * @param pack 51 | * the ThemePackage to set 52 | */ 53 | public void setThemePackage(ThemePackage pack) { 54 | m_pack = pack; 55 | } 56 | 57 | /** 58 | * Adds a custom NotificationBuilder. Notifications can then be built using build(Class notificationClass). 59 | * 60 | * @param 61 | * the type of Notification of the NotificationBuilder 62 | * @param notificationClass 63 | * the Class of the Notification that should be built 64 | * @param builder 65 | * the NotificationBuilder that builds the notificationClass 66 | */ 67 | public void addBuilder(Class notificationClass, NotificationBuilder builder) { 68 | m_builders.put(notificationClass, builder); 69 | } 70 | 71 | /** 72 | * Removes the NotificationBuilder associated with this notification class. 73 | * 74 | * @param 75 | * the type of Notification of the NotificationBuilder 76 | * @param notificationClass 77 | * the Class of the Notification built by the NotificationBuilder that should be removed 78 | */ 79 | public void removeBuilder(Class notificationClass) { 80 | m_builders.remove(notificationClass); 81 | } 82 | 83 | /** 84 | * Builds a Notification using the NotificationBuilder associated with the notification class. 85 | * 86 | * @param 87 | * the type of Notification of the NotificationBuilder 88 | * @param notificationClass 89 | * the Class of the Notification to build 90 | * @return the built Notification 91 | */ 92 | public T build(Class notificationClass) { 93 | if (!m_builders.containsKey(notificationClass)) 94 | throw new RuntimeException("No NotificationBuilder for: " + notificationClass); 95 | 96 | @SuppressWarnings("unchecked") 97 | T note = (T) m_builders.get(notificationClass).buildNotification(m_pack, new Object[0]); 98 | return note; 99 | } 100 | 101 | /** 102 | * Builds a Notification using the NotificationBuilder associated with the notification class. 103 | * 104 | * @param 105 | * the type of Notification of the NotificationBuilder 106 | * @param notificationClass 107 | * the class of the Notification to build 108 | * @param args 109 | * the args passed to the NotificationBuilder 110 | * @return the built Notification 111 | */ 112 | public T build(Class notificationClass, Object... args) { 113 | if (!m_builders.containsKey(notificationClass)) 114 | throw new RuntimeException("No NotificationBuilder for: " + notificationClass); 115 | 116 | @SuppressWarnings("unchecked") 117 | T note = (T) m_builders.get(notificationClass).buildNotification(m_pack, args); 118 | return note; 119 | } 120 | 121 | /** 122 | * Builds a TextNotification. 123 | * 124 | * @param title 125 | * the title to display on the TextNotification 126 | * @param subtitle 127 | * the subtitle to display on the TextNotification 128 | * @return the built TextNotification 129 | */ 130 | public TextNotification buildTextNotification(String title, String subtitle) { 131 | return build(TextNotification.class, title, subtitle); 132 | } 133 | 134 | /** 135 | * Builds an AcceptNotification with "Accept" and "Decline" as the button messages. 136 | * 137 | * @param title 138 | * the title to display on the AcceptNotification 139 | * @param subtitle 140 | * the subtitle to display on the AcceptNotification 141 | * @return the built AcceptNotification 142 | */ 143 | public AcceptNotification buildAcceptNotification(String title, String subtitle) { 144 | return build(AcceptNotification.class, title, subtitle); 145 | } 146 | 147 | /** 148 | * Builds an AcceptNotification with the specified Strings. 149 | * 150 | * @param title 151 | * the title to display on the AcceptNotification 152 | * @param subtitle 153 | * the subtitle to display on the AcceptNotification 154 | * @param accept 155 | * the text on the accept button 156 | * @param decline 157 | * the text on the decline button 158 | * @return the built AcceptNotification 159 | */ 160 | public AcceptNotification buildAcceptNotification(String title, String subtitle, String accept, String decline) { 161 | return build(AcceptNotification.class, title, subtitle, accept, decline); 162 | } 163 | 164 | /** 165 | * Builds an IconNotification. 166 | * 167 | * @param title 168 | * the title to display on the IconNotification 169 | * @param subtitle 170 | * the subtitle to display on the IconNotification 171 | * @param icon 172 | * the icon on the IconNotification 173 | * @return the built IconNotification 174 | */ 175 | public IconNotification buildIconNotification(String title, String subtitle, ImageIcon icon) { 176 | return build(IconNotification.class, title, subtitle, icon); 177 | } 178 | 179 | /** 180 | * Builds a ProgressBarNotification. 181 | * 182 | * @param title 183 | * the title to display on the ProgressBarNotification 184 | * @return the built ProgressBarNotification 185 | */ 186 | public ProgressBarNotification buildProgressBarNotification(String title) { 187 | return build(ProgressBarNotification.class, title); 188 | } 189 | 190 | private class TextNotificationBuilder implements NotificationBuilder { 191 | @Override 192 | public TextNotification buildNotification(ThemePackage pack, Object... args) { 193 | if (args.length != 2) 194 | throw new NotificationException("TextNotifications need two arguments: title, subtitle!"); 195 | 196 | TextNotification note = new TextNotification(); 197 | note.setWindowTheme(pack.getTheme(WindowTheme.class)); 198 | note.setTextTheme(pack.getTheme(TextTheme.class)); 199 | note.setTitle((String) args[0]); 200 | note.setSubtitle((String) args[1]); 201 | return note; 202 | } 203 | } 204 | 205 | private class AcceptNotificationBuilder implements NotificationBuilder { 206 | @Override 207 | public AcceptNotification buildNotification(ThemePackage pack, Object... args) { 208 | if (args.length != 2 && args.length != 4) 209 | throw new NotificationException( 210 | "AcceptNotifications need two or four arguments: title, subtitle, accept text, decline text!"); 211 | 212 | AcceptNotification note = new AcceptNotification(); 213 | note.setWindowTheme(pack.getTheme(WindowTheme.class)); 214 | note.setTextTheme(pack.getTheme(TextTheme.class)); 215 | note.setTitle((String) args[0]); 216 | note.setSubtitle((String) args[1]); 217 | if (args.length == 4) { 218 | note.setAcceptText((String) args[2]); 219 | note.setDeclineText((String) args[3]); 220 | } 221 | 222 | return note; 223 | } 224 | } 225 | 226 | private class IconNotificationBuilder implements NotificationBuilder { 227 | @Override 228 | public IconNotification buildNotification(ThemePackage pack, Object... args) { 229 | if (args.length != 3) 230 | throw new NotificationException("IconNotifications need three arguments: title, subtitle, icon!"); 231 | 232 | IconNotification note = new IconNotification(); 233 | note.setWindowTheme(pack.getTheme(WindowTheme.class)); 234 | note.setTextTheme(pack.getTheme(TextTheme.class)); 235 | note.setTitle((String) args[0]); 236 | note.setSubtitle((String) args[1]); 237 | note.setIcon((Icon) args[2]); 238 | return note; 239 | } 240 | } 241 | 242 | private class ProgressBarNotificationBuilder implements NotificationBuilder { 243 | @Override 244 | public ProgressBarNotification buildNotification(ThemePackage pack, Object... args) { 245 | if (args.length != 1) 246 | throw new NotificationException("ProgressBarNotifications need one argument: title!"); 247 | 248 | ProgressBarNotification note = new ProgressBarNotification(); 249 | note.setWindowTheme(pack.getTheme(WindowTheme.class)); 250 | note.setTextTheme(pack.getTheme(TextTheme.class)); 251 | note.setTitle((String) args[0]); 252 | return note; 253 | } 254 | } 255 | } 256 | -------------------------------------------------------------------------------- /src/main/java/com/notification/NotificationListener.java: -------------------------------------------------------------------------------- 1 | package com.notification; 2 | 3 | public interface NotificationListener { 4 | /** 5 | * Called when an action is completed on the Notification (e.g., a click). 6 | * 7 | * @param notification 8 | * the Notification on which the action was completed 9 | * @param action 10 | * a String identifying what the action was 11 | */ 12 | public void actionCompleted(Notification notification, String action); 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/notification/NotificationManager.java: -------------------------------------------------------------------------------- 1 | package com.notification; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | import java.util.TimerTask; 6 | 7 | import com.utils.Time; 8 | 9 | /** 10 | * Manages the creation and movement of Notifications. Once a Notification is added, all aspects of it except for click 11 | * handeling are managed by the NotificationManager. This includes things such as showing and hiding. 12 | */ 13 | public abstract class NotificationManager { 14 | private List m_notifications; 15 | 16 | public NotificationManager() { 17 | m_notifications = new ArrayList(); 18 | } 19 | 20 | /** 21 | * @return all the Notifications being managed by the NotificationManager 22 | */ 23 | public final List getNotifications() { 24 | return m_notifications; 25 | } 26 | 27 | /** 28 | * Adds a Notification and will also make it visible. 29 | * 30 | * @param note 31 | * the Notification to be added 32 | * @param time 33 | * the amount of time the Notification should display (e.g., Time.seconds(1) will make the Notification 34 | * display for one second). 35 | */ 36 | public final void addNotification(Notification note, Time time) { 37 | if (!m_notifications.contains(note)) { 38 | note.setNotificationManager(this); 39 | m_notifications.add(note); 40 | notificationAdded(note, time); 41 | } 42 | } 43 | 44 | /** 45 | * Removes a Notification and will also hide it. 46 | * 47 | * @param note 48 | * the Notification to be removed 49 | */ 50 | public final void removeNotification(Notification note) { 51 | if (m_notifications.contains(note)) { 52 | m_notifications.remove(note); 53 | notificationRemoved(note); 54 | note.setNotificationManager(null); 55 | } 56 | } 57 | 58 | protected abstract void notificationAdded(Notification note, Time time); 59 | 60 | protected abstract void notificationRemoved(Notification note); 61 | 62 | protected void scheduleRemoval(Notification note, Time time) { 63 | if (!time.isInfinite()) { 64 | java.util.Timer removeTimer = new java.util.Timer(); 65 | removeTimer.schedule(new RemoveTask(note), time.getMilliseconds()); 66 | } 67 | } 68 | 69 | private class RemoveTask extends TimerTask { 70 | private Notification m_note; 71 | 72 | public RemoveTask(Notification note) { 73 | m_note = note; 74 | } 75 | 76 | @Override 77 | public void run() { 78 | NotificationManager.this.removeNotification(m_note); 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/main/java/com/notification/manager/QueueManager.java: -------------------------------------------------------------------------------- 1 | package com.notification.manager; 2 | 3 | import java.awt.event.ActionEvent; 4 | import java.awt.event.ActionListener; 5 | import java.util.List; 6 | 7 | import javax.swing.Timer; 8 | 9 | import com.notification.Notification; 10 | import com.notification.NotificationFactory.Location; 11 | import com.utils.MathUtils; 12 | 13 | /** 14 | * A NotificationManager which slides old Notifications above or below new Notifications. 15 | */ 16 | public class QueueManager extends SimpleManager { 17 | private Timer m_timer; 18 | private int m_verticalPadding = 20; 19 | private double m_snapFactor = 0.2; 20 | private ScrollDirection m_scroll = ScrollDirection.SOUTH; 21 | 22 | /** 23 | * Which way the new Notifications scroll. 24 | */ 25 | public enum ScrollDirection { 26 | NORTH, SOUTH 27 | } 28 | 29 | { 30 | m_timer = new Timer(50, new MovementManager()); 31 | m_timer.start(); 32 | } 33 | 34 | public QueueManager() { 35 | super(); 36 | } 37 | 38 | public QueueManager(Location loc) { 39 | super(loc); 40 | } 41 | 42 | /** 43 | * @return the padding in between Notifications in the queue 44 | */ 45 | public int getVerticalPadding() { 46 | return m_verticalPadding; 47 | } 48 | 49 | /** 50 | * Sets the vertical padding between Notifications in the queue. 51 | * 52 | * @param verticalPadding 53 | * the padding that should be maintained between the bottom of the top Notification and the top of the 54 | * bottom Notification, in pixels 55 | */ 56 | public void setVerticalPadding(int verticalPadding) { 57 | m_verticalPadding = verticalPadding; 58 | } 59 | 60 | /** 61 | * @return the proportion of remaining distance that a displaced notification should travel with each position 62 | * update, with a range of 0 (no movement) to 1 (immediately jumps to appropriate location) 63 | */ 64 | public double getSnapFactor() { 65 | return m_snapFactor; 66 | } 67 | 68 | /** 69 | * Sets the speed with which old Notifications move out of the way when new Notifications are added. 70 | * 71 | * @param snapFactor 72 | * the proportion of remaining distance that a displaced notification should travel with each position 73 | * update, with a range of 0 (no movement) to 1 (immediately jumps to appropriate location) 74 | */ 75 | public void setSnapFactor(double snapFactor) { 76 | m_snapFactor = MathUtils.clamp(snapFactor, 0, 1); 77 | } 78 | 79 | /** 80 | * @return the scroll direction 81 | */ 82 | public ScrollDirection getScrollDirection() { 83 | return m_scroll; 84 | } 85 | 86 | /** 87 | * Sets the direction in which old Notifications will scroll after new ones have been added. 88 | * 89 | * @param dir 90 | * the direction to scroll in 91 | */ 92 | public void setScrollDirection(ScrollDirection dir) { 93 | m_scroll = dir; 94 | } 95 | 96 | /** 97 | * Stops controlling the Notifications and leaves them in their current positions. They will still hide after the 98 | * specified time, however. 99 | */ 100 | public void stop() { 101 | m_timer.stop(); 102 | } 103 | 104 | private class MovementManager implements ActionListener { 105 | private int getPreviousShownIndex(List notes, int startIndex) { 106 | for (int i = startIndex; i < notes.size(); i++) { 107 | if (notes.get(i).isShown()) 108 | return i; 109 | } 110 | return -1; 111 | } 112 | 113 | @Override 114 | public void actionPerformed(ActionEvent e) { 115 | List notes = getNotifications(); 116 | if (notes.size() == 0) 117 | return; 118 | 119 | Location loc = getLocation(); 120 | int startx = getScreen().getX(loc, notes.get(0)); 121 | int starty = getScreen().getY(loc, notes.get(0)); 122 | 123 | for (int i = notes.size() - 1; i >= 0; i--) { 124 | Notification note = notes.get(i); 125 | int prevIndex = getPreviousShownIndex(notes, i + 1); 126 | 127 | int dif = 0; 128 | int desdif = 0; 129 | if (prevIndex == -1) { 130 | dif = note.getY() - starty; 131 | desdif = 0; 132 | } else { 133 | Notification prev = notes.get(prevIndex); 134 | dif = note.getY() - prev.getY(); 135 | desdif = prev.getHeight() + m_verticalPadding; 136 | if (m_scroll == ScrollDirection.NORTH) { 137 | desdif *= -1; 138 | } 139 | } 140 | 141 | int delta = desdif - dif; 142 | double moveAmount = delta * m_snapFactor; 143 | if (Math.abs(moveAmount) < 1) { 144 | moveAmount = MathUtils.sign(moveAmount); 145 | } 146 | note.setLocation(startx, note.getY() + (int) moveAmount); 147 | } 148 | } 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /src/main/java/com/notification/manager/SequenceManager.java: -------------------------------------------------------------------------------- 1 | package com.notification.manager; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import com.notification.Notification; 7 | import com.notification.NotificationFactory.Location; 8 | import com.notification.NotificationListener; 9 | import com.notification.types.WindowNotification; 10 | import com.utils.Time; 11 | 12 | /** 13 | * Displays Notifications one after another in a certain location. As soon as the current Notification is hidden, a new 14 | * one will appear in its place. 15 | */ 16 | public class SequenceManager extends SimpleManager { 17 | private List m_sequence; 18 | private Notification m_currentNotification; 19 | 20 | { 21 | m_sequence = new ArrayList(); 22 | } 23 | 24 | public SequenceManager() { 25 | super(); 26 | } 27 | 28 | public SequenceManager(Location loc) { 29 | super(loc); 30 | } 31 | 32 | @Override 33 | public void notificationAdded(Notification notification, Time time) { 34 | notification.addNotificationListener(new CloseListener()); 35 | if (m_currentNotification == null) { 36 | m_currentNotification = notification; 37 | superAdded(notification, time); 38 | } else { 39 | m_sequence.add(new NotificationShowTime(notification, time)); 40 | } 41 | } 42 | 43 | private void superAdded(Notification notification, Time time) { 44 | super.notificationAdded(notification, time); 45 | } 46 | 47 | private class CloseListener implements NotificationListener { 48 | @Override 49 | public void actionCompleted(Notification notification, String action) { 50 | if (action.equals(WindowNotification.HIDDEN)) { 51 | m_currentNotification.removeNotificationListener(this); 52 | m_currentNotification = null; 53 | if (!m_sequence.isEmpty()) { 54 | NotificationShowTime showing = m_sequence.remove(0); 55 | m_currentNotification = showing.notification; 56 | superAdded(showing.notification, showing.time); 57 | } 58 | } 59 | } 60 | } 61 | 62 | private class NotificationShowTime { 63 | public Notification notification; 64 | public Time time; 65 | 66 | public NotificationShowTime(Notification notification, Time time) { 67 | this.notification = notification; 68 | this.time = time; 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/com/notification/manager/SimpleManager.java: -------------------------------------------------------------------------------- 1 | package com.notification.manager; 2 | 3 | import java.util.List; 4 | import java.util.concurrent.CopyOnWriteArrayList; 5 | 6 | import com.notification.Notification; 7 | import com.notification.NotificationFactory.Location; 8 | import com.notification.NotificationManager; 9 | import com.platform.Platform; 10 | import com.utils.Screen; 11 | import com.utils.Time; 12 | 13 | /** 14 | * Simply displays new Notifications in one corner of the screen on top of each other. Has an option for fading (note - 15 | * results will vary across different platforms). 16 | */ 17 | public class SimpleManager extends NotificationManager { 18 | private Location m_loc; 19 | private Screen m_screen; 20 | 21 | private boolean m_fadeEnabled = false; 22 | private Time m_fadeTime; 23 | 24 | private FaderRunnable m_fader; 25 | private Thread m_faderThread; 26 | 27 | private static final int FADE_DELAY = 50; // milliseconds 28 | 29 | { 30 | m_screen = Screen.standard(); 31 | m_fadeEnabled = false; 32 | m_fadeTime = Time.seconds(1); 33 | } 34 | 35 | public SimpleManager() { 36 | m_loc = Location.NORTHEAST; 37 | } 38 | 39 | public SimpleManager(Location loc) { 40 | m_loc = loc; 41 | } 42 | 43 | /** 44 | * @return the time for fading 45 | */ 46 | public Time getFadeTime() { 47 | return m_fadeTime; 48 | } 49 | 50 | /** 51 | * Sets the fade time. To enable, call setFadeEnabled(boolean). 52 | * 53 | * @param fadeTime 54 | * the duration of the fading 55 | */ 56 | public void setFadeTime(Time fadeTime) { 57 | m_fadeTime = fadeTime; 58 | } 59 | 60 | /** 61 | * @return whether or not fading is enabled 62 | */ 63 | public boolean isFadeEnabled() { 64 | syncFadeEnabledWithPlatform(); 65 | 66 | return m_fadeEnabled; 67 | } 68 | 69 | /** 70 | * Sets whether or not fading is enabled. 71 | * 72 | * @param fadeEnabled 73 | * whether or not fading is enabled 74 | */ 75 | public void setFadeEnabled(boolean fadeEnabled) { 76 | m_fadeEnabled = fadeEnabled; 77 | 78 | if (fadeEnabled) { 79 | m_fader = new FaderRunnable(); 80 | m_faderThread = new Thread(m_fader); 81 | m_faderThread.start(); 82 | } else { 83 | m_fader.stop(); 84 | m_fader = null; 85 | m_faderThread = null; 86 | } 87 | 88 | syncFadeEnabledWithPlatform(); 89 | } 90 | 91 | private void syncFadeEnabledWithPlatform() { 92 | if (m_fadeEnabled && Platform.instance().isUsed()) 93 | m_fadeEnabled = Platform.instance().isSupported("fade"); 94 | } 95 | 96 | /** 97 | * @return the location where the Notifications show up 98 | */ 99 | public Location getLocation() { 100 | return m_loc; 101 | } 102 | 103 | /** 104 | * Sets the location where the Notifications show up. 105 | * 106 | * @param loc 107 | * the Location to show at 108 | */ 109 | public void setLocation(Location loc) { 110 | m_loc = loc; 111 | } 112 | 113 | protected Screen getScreen() { 114 | return m_screen; 115 | } 116 | 117 | @Override 118 | protected void notificationAdded(Notification note, Time time) { 119 | note.setLocation(m_screen.getX(m_loc, note), m_screen.getY(m_loc, note)); 120 | 121 | if (isFadeEnabled()) { 122 | double opacity = note.getOpacity(); 123 | note.setOpacity(0); 124 | startFade(note, opacity); 125 | scheduleRemoval(note, time.add(m_fadeTime)); 126 | } else { 127 | scheduleRemoval(note, time); 128 | } 129 | 130 | note.show(); 131 | } 132 | 133 | @Override 134 | protected void notificationRemoved(Notification note) { 135 | if (isFadeEnabled()) { 136 | startFade(note, -note.getOpacity()); 137 | } else { 138 | note.hide(); 139 | } 140 | } 141 | 142 | private void startFade(Notification note, double deltaOpacity) { 143 | m_fader.addFader(new Fader(note, getDeltaFade(deltaOpacity), note.getOpacity() + deltaOpacity)); 144 | } 145 | 146 | private double getDeltaFade(double deltaOpacity) { 147 | return deltaOpacity / m_fadeTime.getMilliseconds(); 148 | } 149 | 150 | private class FaderRunnable implements Runnable { 151 | private List m_faders; 152 | private boolean m_shouldStop; 153 | 154 | public FaderRunnable() { 155 | m_faders = new CopyOnWriteArrayList(); 156 | m_shouldStop = false; 157 | } 158 | 159 | public void addFader(Fader fader) { 160 | m_faders.add(fader); 161 | } 162 | 163 | public void stop() { 164 | m_shouldStop = true; 165 | } 166 | 167 | @Override 168 | public void run() { 169 | while (!m_shouldStop) { 170 | for (Fader fader : m_faders) { 171 | fader.updateFade(); 172 | if (fader.isFinishedFading()) { 173 | m_faders.remove(fader); 174 | } 175 | } 176 | try { 177 | Thread.sleep(FADE_DELAY); 178 | } catch (InterruptedException e) { 179 | e.printStackTrace(); 180 | } 181 | } 182 | } 183 | } 184 | 185 | private class Fader { 186 | private Notification m_note; 187 | private long m_fadeStartTime; 188 | private double m_startFade; 189 | private double m_stopFade; 190 | private double m_deltaFade; // delta fade per millisecond 191 | 192 | public Fader(Notification note, double deltaFade, double stopFade) { 193 | m_note = note; 194 | m_deltaFade = deltaFade; 195 | m_stopFade = stopFade; 196 | m_startFade = note.getOpacity(); 197 | m_fadeStartTime = System.currentTimeMillis(); 198 | } 199 | 200 | public void updateFade() { 201 | long deltaTime = System.currentTimeMillis() - m_fadeStartTime; 202 | if (!isFinishedFading()) { 203 | m_note.setOpacity(m_startFade + m_deltaFade * deltaTime); 204 | } else { 205 | if (m_deltaFade < 0) { 206 | m_note.hide(); 207 | } 208 | } 209 | } 210 | 211 | public boolean isFinishedFading() { 212 | return (m_deltaFade > 0) ? m_note.getOpacity() >= m_stopFade : m_note.getOpacity() <= m_stopFade; 213 | } 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /src/main/java/com/notification/manager/SlideManager.java: -------------------------------------------------------------------------------- 1 | package com.notification.manager; 2 | 3 | import java.awt.event.ActionEvent; 4 | import java.awt.event.ActionListener; 5 | import java.util.HashMap; 6 | 7 | import javax.swing.Timer; 8 | 9 | import com.notification.Notification; 10 | import com.notification.NotificationFactory.Location; 11 | import com.notification.NotificationManager; 12 | import com.utils.Screen; 13 | import com.utils.Time; 14 | 15 | /** 16 | * Slides Notifications into a certain area. This may not work on all machines. 17 | */ 18 | public class SlideManager extends NotificationManager { 19 | private Location m_loc; 20 | 21 | private Screen m_standardScreen; 22 | private Screen m_noPaddingScreen; 23 | 24 | private HashMap m_sliders; 25 | private SlideDirection m_slideIn; 26 | private double m_slideSpeed; 27 | 28 | private HashMap m_slideStates; 29 | // store the values mid slide so that changes to SlideManager do not affect them 30 | 31 | private boolean m_overwrite; 32 | 33 | { 34 | m_sliders = new HashMap(); 35 | m_sliders.put(SlideDirection.NORTH, new NorthSlider()); 36 | m_sliders.put(SlideDirection.SOUTH, new SouthSlider()); 37 | m_sliders.put(SlideDirection.EAST, new EastSlider()); 38 | m_sliders.put(SlideDirection.WEST, new WestSlider()); 39 | m_standardScreen = Screen.standard(); 40 | m_noPaddingScreen = Screen.withPadding(0); 41 | m_slideSpeed = 300; 42 | m_slideStates = new HashMap(); 43 | m_overwrite = false; 44 | } 45 | 46 | public SlideManager() { 47 | setLocation(Location.NORTHEAST); 48 | } 49 | 50 | public SlideManager(Location loc) { 51 | setLocation(loc); 52 | } 53 | 54 | /** 55 | * @return the location where the Notifications show up 56 | */ 57 | public Location getLocation() { 58 | return m_loc; 59 | } 60 | 61 | /** 62 | * Sets the location where the Notifications show up. 63 | * 64 | * @param loc 65 | * the Location to show at 66 | */ 67 | public void setLocation(Location loc) { 68 | m_loc = loc; 69 | if (!m_overwrite) 70 | recalculateSlideDirection(); 71 | } 72 | 73 | /** 74 | * @return the direction that the Notification should slide in from 75 | */ 76 | public SlideDirection getSlideDirection() { 77 | return m_slideIn; 78 | } 79 | 80 | /** 81 | * Sets the direction that the Notification should slide to. 82 | * 83 | * @param slide 84 | * the direction that Notifications should slide to 85 | */ 86 | public void setSlideDirection(SlideDirection slide) { 87 | m_slideIn = slide; 88 | m_overwrite = true; 89 | } 90 | 91 | /** 92 | * @return how fast the Notification should in pixels / second 93 | */ 94 | public double getSlideSpeed() { 95 | return m_slideSpeed; 96 | } 97 | 98 | /** 99 | * Sets how fast the Notification should slide in pixels / second. 100 | * 101 | * @param slideSpeed 102 | * the speed of the slide in pixels / second 103 | */ 104 | public void setSlideSpeed(double slideSpeed) { 105 | m_slideSpeed = slideSpeed; 106 | } 107 | 108 | protected Screen getScreen() { 109 | return m_standardScreen; 110 | } 111 | 112 | private void recalculateSlideDirection() { 113 | switch (m_loc) { 114 | case NORTHWEST: 115 | case NORTH: 116 | case NORTHEAST: 117 | m_slideIn = SlideDirection.SOUTH; 118 | break; 119 | case EAST: 120 | m_slideIn = SlideDirection.WEST; 121 | break; 122 | case SOUTHEAST: 123 | case SOUTH: 124 | case SOUTHWEST: 125 | m_slideIn = SlideDirection.NORTH; 126 | break; 127 | case WEST: 128 | m_slideIn = SlideDirection.EAST; 129 | break; 130 | } 131 | } 132 | 133 | @Override 134 | protected void notificationAdded(Notification note, Time time) { 135 | m_slideStates.put(note, new SlideState(m_loc, m_slideIn.getOpposite())); 136 | 137 | double delay = 50; 138 | double slideDelta = m_slideSpeed / delay; 139 | m_sliders.get(m_slideIn).setBorderPosition(note, m_loc); 140 | m_sliders.get(m_slideIn).animate(note, m_loc, delay, slideDelta, true); 141 | note.show(); 142 | 143 | double slideTime = m_standardScreen.getPadding() / m_slideSpeed; 144 | scheduleRemoval(note, time.add(Time.seconds(slideTime))); 145 | } 146 | 147 | @Override 148 | protected void notificationRemoved(Notification note) { 149 | SlideState state = m_slideStates.get(note); 150 | double delay = 50; 151 | double slideDelta = m_slideSpeed / delay; 152 | m_sliders.get(state.returnDirection).animate(note, state.loc, delay, slideDelta, false); 153 | m_slideStates.remove(note); 154 | } 155 | 156 | private class SlideState { 157 | public Location loc; 158 | public SlideDirection returnDirection; 159 | 160 | public SlideState(Location loc, SlideDirection returnDirection) { 161 | this.loc = loc; 162 | this.returnDirection = returnDirection; 163 | } 164 | } 165 | 166 | public enum SlideDirection { 167 | NORTH, SOUTH(SlideDirection.NORTH), EAST, WEST(SlideDirection.EAST); 168 | 169 | private SlideDirection m_opposite; 170 | 171 | SlideDirection() { 172 | 173 | } 174 | 175 | SlideDirection(SlideDirection opposite) { 176 | m_opposite = opposite; 177 | opposite.m_opposite = this; 178 | } 179 | 180 | public SlideDirection getOpposite() { 181 | return m_opposite; 182 | } 183 | } 184 | 185 | private abstract class Slider implements ActionListener { 186 | protected Notification m_note; 187 | protected double m_delta; 188 | protected double m_stopX; 189 | protected double m_stopY; 190 | 191 | protected double m_x; 192 | protected double m_y; 193 | 194 | protected boolean m_slideIn; 195 | 196 | protected Location m_startLocation; 197 | 198 | public void animate(Notification note, Location loc, double delay, double slideDelta, boolean slideIn) { 199 | m_note = note; 200 | m_x = note.getX(); 201 | m_y = note.getY(); 202 | m_delta = Math.abs(slideDelta); 203 | m_slideIn = slideIn; 204 | m_startLocation = loc; 205 | if (m_slideIn) { 206 | m_stopX = m_standardScreen.getX(m_startLocation, note); 207 | m_stopY = m_standardScreen.getY(m_startLocation, note); 208 | } else { 209 | m_stopX = m_noPaddingScreen.getX(m_startLocation, note); 210 | m_stopY = m_noPaddingScreen.getY(m_startLocation, note); 211 | } 212 | 213 | Timer timer = new Timer((int) delay, this); 214 | timer.start(); 215 | } 216 | 217 | protected void manageStop(ActionEvent e) { 218 | ((Timer) e.getSource()).stop(); 219 | if (!m_slideIn) 220 | m_note.hide(); 221 | } 222 | 223 | public abstract void setBorderPosition(Notification note, Location loc); 224 | } 225 | 226 | private class NorthSlider extends Slider { 227 | @Override 228 | public void actionPerformed(ActionEvent e) { 229 | m_y -= m_delta; 230 | 231 | if (m_y <= m_stopY) { 232 | m_y = m_stopY; 233 | manageStop(e); 234 | } 235 | 236 | m_note.setLocation((int) (m_x), (int) (m_y)); 237 | } 238 | 239 | @Override 240 | public void setBorderPosition(Notification note, Location loc) { 241 | note.setLocation(m_standardScreen.getX(loc, note), m_noPaddingScreen.getY(loc, note)); 242 | } 243 | } 244 | 245 | private class SouthSlider extends Slider { 246 | @Override 247 | public void actionPerformed(ActionEvent e) { 248 | m_y += m_delta; 249 | 250 | if (m_y >= m_stopY) { 251 | m_y = m_stopY; 252 | manageStop(e); 253 | } 254 | 255 | m_note.setLocation((int) (m_x), (int) (m_y)); 256 | } 257 | 258 | @Override 259 | public void setBorderPosition(Notification note, Location loc) { 260 | note.setLocation(m_standardScreen.getX(loc, note), m_noPaddingScreen.getY(loc, note)); 261 | } 262 | } 263 | 264 | private class EastSlider extends Slider { 265 | @Override 266 | public void actionPerformed(ActionEvent e) { 267 | m_x += m_delta; 268 | 269 | if (m_x >= m_stopX) { 270 | m_x = m_stopX; 271 | manageStop(e); 272 | } 273 | 274 | m_note.setLocation((int) (m_x), (int) (m_y)); 275 | } 276 | 277 | @Override 278 | public void setBorderPosition(Notification note, Location loc) { 279 | note.setLocation(m_noPaddingScreen.getX(loc, note), m_standardScreen.getY(loc, note)); 280 | } 281 | } 282 | 283 | private class WestSlider extends Slider { 284 | @Override 285 | public void actionPerformed(ActionEvent e) { 286 | m_x -= m_delta; 287 | 288 | if (m_x <= m_stopX) { 289 | m_x = m_stopX; 290 | manageStop(e); 291 | } 292 | 293 | m_note.setLocation((int) (m_x), (int) (m_y)); 294 | } 295 | 296 | @Override 297 | public void setBorderPosition(Notification note, Location loc) { 298 | note.setLocation(m_noPaddingScreen.getX(loc, note), m_standardScreen.getY(loc, note)); 299 | } 300 | } 301 | } 302 | -------------------------------------------------------------------------------- /src/main/java/com/notification/types/AcceptNotification.java: -------------------------------------------------------------------------------- 1 | package com.notification.types; 2 | 3 | import java.awt.BorderLayout; 4 | import java.awt.Dimension; 5 | import java.awt.event.ActionEvent; 6 | import java.awt.event.ActionListener; 7 | 8 | import javax.swing.JButton; 9 | import javax.swing.JPanel; 10 | 11 | import com.theme.TextTheme; 12 | import com.theme.WindowTheme; 13 | 14 | /** 15 | * This is a Notification that will ask the user to accept of decline a certain action. 16 | */ 17 | public class AcceptNotification extends TextNotification { 18 | private JButton m_accept; 19 | private JButton m_decline; 20 | 21 | private boolean m_accepted; 22 | 23 | public AcceptNotification() { 24 | m_accept = new JButton("Accept"); 25 | m_decline = new JButton("Decline"); 26 | m_accepted = false; 27 | 28 | m_accept.addActionListener(new ActionListener() { 29 | @Override 30 | public void actionPerformed(ActionEvent e) { 31 | m_accepted = true; 32 | removeFromManager(); 33 | } 34 | }); 35 | m_decline.addActionListener(new ActionListener() { 36 | @Override 37 | public void actionPerformed(ActionEvent e) { 38 | m_accepted = false; 39 | removeFromManager(); 40 | } 41 | }); 42 | 43 | setButtonDimensions(new Dimension(100, 22)); 44 | 45 | JPanel buttonPanel = new JPanel(); 46 | buttonPanel.add(m_decline); 47 | buttonPanel.add(m_accept); 48 | this.addComponent(buttonPanel, BorderLayout.SOUTH); 49 | } 50 | 51 | /** 52 | * Will wait for the user to click a button (if the Notification hides, this method will act as if the user clicked 53 | * deny). 54 | * 55 | * @return the user's response 56 | */ 57 | public boolean blockUntilReply() { 58 | synchronized (this) { 59 | try { 60 | wait(); 61 | } catch (InterruptedException e) { 62 | } 63 | } 64 | return m_accepted; 65 | } 66 | 67 | /** 68 | * Sets the preferred size of the buttons. 69 | * 70 | * @param d 71 | * sets the dimensions of the accept and decline buttons 72 | */ 73 | public void setButtonDimensions(Dimension d) { 74 | m_accept.setPreferredSize(d); 75 | m_decline.setPreferredSize(d); 76 | } 77 | 78 | /** 79 | * @return the text on the accept button 80 | */ 81 | public String getAcceptText() { 82 | return m_accept.getText(); 83 | } 84 | 85 | /** 86 | * Sets the text on the accept button. 87 | * 88 | * @param acceptText 89 | * the text on the accept button 90 | */ 91 | public void setAcceptText(String acceptText) { 92 | m_accept.setText(acceptText); 93 | } 94 | 95 | /** 96 | * @return the text on the decline button 97 | */ 98 | public String getDeclineText() { 99 | return m_decline.getText(); 100 | } 101 | 102 | /** 103 | * Sets the text on the decline button. 104 | * 105 | * @param declineText 106 | * the text on the decline button 107 | */ 108 | public void setDeclineText(String declineText) { 109 | m_decline.setText(declineText); 110 | } 111 | 112 | @Override 113 | public void hide() { 114 | super.hide(); 115 | 116 | synchronized (this) { 117 | this.notifyAll(); 118 | } 119 | } 120 | 121 | @Override 122 | public void setTextTheme(TextTheme theme) { 123 | super.setTextTheme(theme); 124 | 125 | m_accept.setForeground(theme.subtitleColor); 126 | m_decline.setForeground(theme.subtitleColor); 127 | } 128 | 129 | @Override 130 | public void setWindowTheme(WindowTheme theme) { 131 | super.setWindowTheme(theme); 132 | 133 | if (getTextTheme() != null) { 134 | m_accept.setForeground(getTextTheme().subtitleColor); 135 | m_decline.setForeground(getTextTheme().subtitleColor); 136 | } 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /src/main/java/com/notification/types/BorderLayoutNotification.java: -------------------------------------------------------------------------------- 1 | package com.notification.types; 2 | 3 | import java.awt.BorderLayout; 4 | import java.awt.Component; 5 | 6 | import javax.swing.JPanel; 7 | import javax.swing.border.EmptyBorder; 8 | 9 | import com.theme.WindowTheme; 10 | 11 | /** 12 | * Lays out Swing Components in a BorderLayout. 13 | */ 14 | public class BorderLayoutNotification extends WindowNotification { 15 | private JPanel m_panel; 16 | 17 | public static final int PANEL_PADDING = 10; 18 | 19 | public BorderLayoutNotification() { 20 | super(); 21 | 22 | m_panel = new JPanel(new BorderLayout()); 23 | m_panel.setBorder(new EmptyBorder(PANEL_PADDING, PANEL_PADDING, PANEL_PADDING, PANEL_PADDING)); 24 | setPanel(m_panel); 25 | } 26 | 27 | /** 28 | * Adds a Component to the Notification. 29 | * 30 | * @param comp 31 | * the Component to add 32 | * @param borderLayout 33 | * the BorderLayout String, e.g. BorderLayout.NORTH 34 | */ 35 | public void addComponent(Component comp, String borderLayout) { 36 | m_panel.add(comp, borderLayout); 37 | 38 | WindowTheme theme = this.getWindowTheme(); 39 | if (theme != null) { 40 | comp.setBackground(theme.background); 41 | comp.setForeground(theme.foreground); 42 | } 43 | getWindow().revalidate(); 44 | getWindow().repaint(); 45 | } 46 | 47 | /** 48 | * Removes a component. 49 | * 50 | * @param comp 51 | * the Component to remove 52 | */ 53 | public void removeComponent(Component comp) { 54 | m_panel.remove(comp); 55 | 56 | getWindow().revalidate(); 57 | getWindow().repaint(); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/com/notification/types/IconNotification.java: -------------------------------------------------------------------------------- 1 | package com.notification.types; 2 | 3 | import java.awt.BorderLayout; 4 | 5 | import javax.swing.Icon; 6 | import javax.swing.JLabel; 7 | import javax.swing.JPanel; 8 | import javax.swing.border.EmptyBorder; 9 | 10 | /** 11 | * An IconNotification displays text, but with an icon. 12 | */ 13 | public class IconNotification extends TextNotification { 14 | private JLabel m_iconLabel; 15 | 16 | public static final int ICON_PADDING = 10; 17 | 18 | public IconNotification() { 19 | super(); 20 | m_iconLabel = new JLabel(); 21 | 22 | this.removeComponent(m_titleLabel); 23 | this.removeComponent(m_subtitleArea); 24 | 25 | JPanel panel = new JPanel(new BorderLayout()); 26 | panel.add(m_titleLabel, BorderLayout.NORTH); 27 | panel.add(m_subtitleArea, BorderLayout.CENTER); 28 | panel.setBorder(new EmptyBorder(0, ICON_PADDING, 0, 0)); 29 | 30 | this.addComponent(m_iconLabel, BorderLayout.WEST); 31 | this.addComponent(panel, BorderLayout.CENTER); 32 | } 33 | 34 | /** 35 | * Sets the icon to use. 36 | * 37 | * @param icon 38 | * the icon to use 39 | */ 40 | public void setIcon(Icon icon) { 41 | m_iconLabel.setIcon(icon); 42 | } 43 | 44 | /** 45 | * @return the icon to use 46 | */ 47 | public Icon getIcon() { 48 | return m_iconLabel.getIcon(); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/com/notification/types/ProgressBarNotification.java: -------------------------------------------------------------------------------- 1 | package com.notification.types; 2 | 3 | import java.awt.BorderLayout; 4 | 5 | import javax.swing.BorderFactory; 6 | import javax.swing.JLabel; 7 | import javax.swing.JPanel; 8 | import javax.swing.JProgressBar; 9 | 10 | import com.theme.TextTheme; 11 | 12 | public class ProgressBarNotification extends BorderLayoutNotification { 13 | private JLabel m_label; 14 | private JProgressBar m_progress; 15 | 16 | public ProgressBarNotification() { 17 | m_label = new JLabel(); 18 | m_progress = new JProgressBar(); 19 | 20 | this.addComponent(m_label, BorderLayout.NORTH); 21 | 22 | JPanel progressPanel = new JPanel(new BorderLayout()); 23 | progressPanel.setBorder(BorderFactory.createEmptyBorder(15, 10, 15, 10)); 24 | progressPanel.add(m_progress, BorderLayout.CENTER); 25 | this.addComponent(progressPanel, BorderLayout.CENTER); 26 | } 27 | 28 | /** 29 | * This will set the text font to that of the title font. 30 | * 31 | * @param theme 32 | * the TextTheme to set 33 | */ 34 | public void setTextTheme(TextTheme theme) { 35 | m_label.setFont(theme.title); 36 | m_label.setForeground(theme.titleColor); 37 | } 38 | 39 | public String getTitle() { 40 | return m_label.getText(); 41 | } 42 | 43 | public void setTitle(String title) { 44 | m_label.setText(title); 45 | } 46 | 47 | /** 48 | * Sest the progress of the progress bar, from 0 to 100. 49 | * 50 | * @param progress 51 | * the progress to set 52 | */ 53 | public void setProgress(int progress) { 54 | m_progress.setValue(progress); 55 | } 56 | 57 | /** 58 | * @return the progress of the progress bar, from 0 to 100 59 | */ 60 | public int getProgress() { 61 | return m_progress.getValue(); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/com/notification/types/TextNotification.java: -------------------------------------------------------------------------------- 1 | package com.notification.types; 2 | 3 | import java.awt.BorderLayout; 4 | 5 | import javax.swing.JLabel; 6 | import javax.swing.JTextArea; 7 | 8 | import com.theme.TextTheme; 9 | import com.theme.WindowTheme; 10 | 11 | /** 12 | * A text notification which will display a title and a subtitle. 13 | */ 14 | public class TextNotification extends BorderLayoutNotification { 15 | protected JLabel m_titleLabel; 16 | protected JTextArea m_subtitleArea; 17 | 18 | private TextTheme m_textTheme; 19 | 20 | public TextNotification() { 21 | m_titleLabel = new JLabel(); 22 | m_subtitleArea = new JTextArea(); 23 | 24 | this.addComponent(m_titleLabel, BorderLayout.NORTH); 25 | this.addComponent(m_subtitleArea, BorderLayout.CENTER); 26 | } 27 | 28 | public String getTitle() { 29 | return m_titleLabel.getText(); 30 | } 31 | 32 | public void setTitle(String title) { 33 | m_titleLabel.setText(title); 34 | } 35 | 36 | public String getSubtitle() { 37 | return m_subtitleArea.getText(); 38 | } 39 | 40 | public void setSubtitle(String subtitle) { 41 | m_subtitleArea.setText(subtitle); 42 | } 43 | 44 | protected TextTheme getTextTheme() { 45 | return m_textTheme; 46 | } 47 | 48 | /** 49 | * @param theme 50 | * the two Fonts that should be used. 51 | */ 52 | public void setTextTheme(TextTheme theme) { 53 | m_textTheme = theme; 54 | m_titleLabel.setFont(theme.title); 55 | m_subtitleArea.setFont(theme.subtitle); 56 | m_titleLabel.setForeground(theme.titleColor); 57 | m_subtitleArea.setForeground(theme.subtitleColor); 58 | } 59 | 60 | @Override 61 | public void setWindowTheme(WindowTheme theme) { 62 | super.setWindowTheme(theme); 63 | 64 | if (m_textTheme != null) { 65 | m_titleLabel.setForeground(m_textTheme.titleColor); 66 | m_subtitleArea.setForeground(m_textTheme.subtitleColor); 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/com/notification/types/WindowNotification.java: -------------------------------------------------------------------------------- 1 | package com.notification.types; 2 | 3 | import java.awt.Component; 4 | import java.awt.Container; 5 | import java.awt.event.MouseAdapter; 6 | import java.awt.event.MouseEvent; 7 | 8 | import javax.swing.JPanel; 9 | import javax.swing.JWindow; 10 | 11 | import com.notification.Notification; 12 | import com.theme.WindowTheme; 13 | import com.utils.MathUtils; 14 | 15 | /** 16 | * A Notification which displays in a JWindow, handles click events, and allows subclasses to supply a JPanel. The 17 | * default Notification dimensions are set; if subclasses want to override this, they can do so in their constructors. 18 | */ 19 | public abstract class WindowNotification extends Notification { 20 | private JWindow m_window; 21 | private JPanel m_panel; 22 | private boolean m_closeOnClick; 23 | private MouseAdapter m_listener; 24 | 25 | private WindowTheme m_theme; 26 | 27 | private static final int DEFAULT_WIDTH = 300; 28 | private static final int DEFAULT_HEIGHT = 100; 29 | public static final String CLICKED = "clicked"; 30 | public static final String SHOWN = "shown"; 31 | public static final String HIDDEN = "hidden"; 32 | 33 | public WindowNotification() { 34 | m_window = new JWindow(); 35 | m_window.setAlwaysOnTop(true); 36 | 37 | m_listener = new MouseAdapter() { 38 | @Override 39 | public void mouseClicked(MouseEvent e) { 40 | fireListeners(CLICKED); 41 | if (m_closeOnClick) 42 | removeFromManager(); 43 | } 44 | }; 45 | 46 | setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); 47 | setPanel(new JPanel()); 48 | } 49 | 50 | protected JWindow getWindow() { 51 | return m_window; 52 | } 53 | 54 | protected void setPanel(JPanel panel) { 55 | if (m_panel != null) { 56 | m_window.remove(m_panel); 57 | m_panel.removeMouseListener(m_listener); 58 | } 59 | 60 | m_panel = panel; 61 | 62 | m_window.add(m_panel); 63 | m_panel.addMouseListener(m_listener); 64 | } 65 | 66 | /** 67 | * @return whether or not the Notification should close when it's clicked 68 | */ 69 | public boolean isCloseOnClick() { 70 | return m_closeOnClick; 71 | } 72 | 73 | /** 74 | * @param close 75 | * 76 | * whether or not the Notification should close when it's clicked 77 | */ 78 | public void setCloseOnClick(boolean close) { 79 | m_closeOnClick = close; 80 | } 81 | 82 | protected WindowTheme getWindowTheme() { 83 | return m_theme; 84 | } 85 | 86 | /** 87 | * Sets the theme of the WindowNotification. It is up to the subclasses how they want to interpret the "image" 88 | * attribute of the theme. 89 | * 90 | * @param theme 91 | * the WindowTheme to set 92 | */ 93 | public void setWindowTheme(WindowTheme theme) { 94 | m_theme = theme; 95 | 96 | m_window.setBackground(theme.background); 97 | m_window.setForeground(theme.foreground); 98 | m_window.setOpacity((float) theme.opacity); 99 | m_window.setSize(theme.width, theme.height); 100 | 101 | m_panel.setBackground(theme.background); 102 | m_panel.setForeground(theme.foreground); 103 | 104 | for (Component comp : m_panel.getComponents()) { 105 | recursiveSetTheme(theme, comp); 106 | } 107 | } 108 | 109 | private void recursiveSetTheme(WindowTheme theme, Component comp) { 110 | comp.setBackground(theme.background); 111 | comp.setForeground(theme.foreground); 112 | 113 | if (comp instanceof Container) { 114 | Container container = (Container) comp; 115 | for (Component component : container.getComponents()) { 116 | recursiveSetTheme(theme, component); 117 | } 118 | } 119 | } 120 | 121 | @Override 122 | public int getX() { 123 | return m_window.getX(); 124 | } 125 | 126 | @Override 127 | public int getY() { 128 | return m_window.getY(); 129 | } 130 | 131 | @Override 132 | public void setLocation(int x, int y) { 133 | m_window.setLocation(x, y); 134 | } 135 | 136 | @Override 137 | public int getWidth() { 138 | return m_window.getWidth(); 139 | } 140 | 141 | @Override 142 | public int getHeight() { 143 | return m_window.getHeight(); 144 | } 145 | 146 | @Override 147 | public void setSize(int width, int height) { 148 | m_window.setSize(width, height); 149 | } 150 | 151 | /** 152 | * Gets the opacity of the window between 0 and 1. 153 | * 154 | * @return the opacity of the window 155 | */ 156 | @Override 157 | public double getOpacity() { 158 | return m_window.getOpacity(); 159 | } 160 | 161 | /** 162 | * Sets the opacity, overriding the value given in the window theme. 163 | * 164 | * @param opacity 165 | * the opacity (clamped to between 0 and 1) 166 | */ 167 | @Override 168 | public void setOpacity(double opacity) { 169 | m_window.setOpacity((float) MathUtils.clamp(opacity, 0, 1)); 170 | } 171 | 172 | @Override 173 | public void show() { 174 | m_window.setVisible(true); 175 | fireListeners(SHOWN); 176 | } 177 | 178 | @Override 179 | public void hide() { 180 | m_window.dispose(); 181 | fireListeners(HIDDEN); 182 | } 183 | 184 | @Override 185 | public boolean isShown() { 186 | return m_window.isVisible(); 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /src/main/java/com/platform/DefaultOperatingSystem.java: -------------------------------------------------------------------------------- 1 | package com.platform; 2 | 3 | public class DefaultOperatingSystem implements OperatingSystem { 4 | @Override 5 | public boolean isSupported(String feature) { 6 | return true; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/com/platform/Mac.java: -------------------------------------------------------------------------------- 1 | package com.platform; 2 | 3 | public class Mac implements OperatingSystem { 4 | @Override 5 | public boolean isSupported(String feature) { 6 | switch (feature) { 7 | case "fade": 8 | return true; 9 | default: 10 | return true; 11 | } 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/platform/OperatingSystem.java: -------------------------------------------------------------------------------- 1 | package com.platform; 2 | 3 | public interface OperatingSystem { 4 | public boolean isSupported(String feature); 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/com/platform/Platform.java: -------------------------------------------------------------------------------- 1 | package com.platform; 2 | 3 | /** 4 | * Detects platform-specific modifications. 5 | */ 6 | public class Platform { 7 | private OperatingSystem m_operatingSystem; 8 | private boolean m_used = false; 9 | 10 | public Platform() { 11 | String os = System.getProperty("os.name").toLowerCase(); 12 | m_operatingSystem = new DefaultOperatingSystem(); 13 | if (os.indexOf("win") >= 0) 14 | m_operatingSystem = new Windows(); 15 | if (os.indexOf("mac") >= 0) 16 | m_operatingSystem = new Mac(); 17 | if (os.indexOf("nix") >= 0 || os.indexOf("nux") >= 0 || os.indexOf("aix") > 0) 18 | m_operatingSystem = new Unix(); 19 | } 20 | 21 | /** 22 | * @return the detected OperatingSystem 23 | */ 24 | public OperatingSystem getOperatingSystem() { 25 | return m_operatingSystem; 26 | } 27 | 28 | /** 29 | * @param feature 30 | * the feature to query 31 | * @return whether or not a certain feature is supported for the platform. 32 | */ 33 | public boolean isSupported(String feature) { 34 | return m_operatingSystem.isSupported(feature); 35 | } 36 | 37 | /** 38 | * @return whether or not the Notifications should be adjusted to best fit the platform. 39 | */ 40 | public boolean isUsed() { 41 | return m_used; 42 | } 43 | 44 | /** 45 | * Sets whether or not the Notifications should be adjusted to best fit the platform. 46 | * 47 | * @param used 48 | * whether or not the Notifications should be adjusted 49 | */ 50 | public void setAdjustForPlatform(boolean used) { 51 | m_used = used; 52 | } 53 | 54 | private static Platform INSTANCE; 55 | 56 | public static Platform instance() { 57 | if (INSTANCE == null) 58 | INSTANCE = new Platform(); 59 | 60 | return INSTANCE; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/com/platform/Unix.java: -------------------------------------------------------------------------------- 1 | package com.platform; 2 | 3 | public class Unix implements OperatingSystem { 4 | @Override 5 | public boolean isSupported(String feature) { 6 | switch (feature) { 7 | case "fade": 8 | return false; 9 | default: 10 | return true; 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/platform/Windows.java: -------------------------------------------------------------------------------- 1 | package com.platform; 2 | 3 | public class Windows implements OperatingSystem { 4 | @Override 5 | public boolean isSupported(String feature) { 6 | switch (feature) { 7 | case "fade": 8 | return false; 9 | default: 10 | return true; 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/theme/TextTheme.java: -------------------------------------------------------------------------------- 1 | package com.theme; 2 | 3 | import java.awt.Color; 4 | import java.awt.Font; 5 | 6 | public class TextTheme { 7 | public Font title; 8 | public Font subtitle; 9 | public Color titleColor; 10 | public Color subtitleColor; 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/theme/ThemePackage.java: -------------------------------------------------------------------------------- 1 | package com.theme; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | /** 7 | * Contains a collection of themes that can be accessed later. 8 | */ 9 | public class ThemePackage { 10 | private Map, Object> m_themes; 11 | 12 | public ThemePackage() { 13 | m_themes = new HashMap, Object>(); 14 | } 15 | 16 | /** 17 | * Sets the theme object related with the theme class. The first parameter should be the class of the second 18 | * parameter. 19 | * 20 | * @param 21 | * the Class of the theme 22 | * @param themeClass 23 | * the Class of the theme to set 24 | * @param theme 25 | * the theme to set 26 | */ 27 | public void setTheme(Class themeClass, T theme) { 28 | m_themes.put(themeClass, theme); 29 | } 30 | 31 | /** 32 | * Gets the theme object related with the theme class. 33 | * 34 | * @param 35 | * the Class of the theme 36 | * @param themeClass 37 | * the Class of the theme to return 38 | * @return the theme corresponding with the given Class 39 | */ 40 | public T getTheme(Class themeClass) { 41 | @SuppressWarnings("unchecked") 42 | T theme = (T) m_themes.get(themeClass); 43 | return theme; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/com/theme/ThemePackagePresets.java: -------------------------------------------------------------------------------- 1 | package com.theme; 2 | 3 | import java.awt.Color; 4 | import java.awt.Font; 5 | 6 | public class ThemePackagePresets { 7 | private ThemePackagePresets() { 8 | } 9 | 10 | public static ThemePackage cleanLight() { 11 | ThemePackage pack = new ThemePackage(); 12 | 13 | WindowTheme window = new WindowTheme(); 14 | window.background = new Color(255, 255, 255); 15 | window.foreground = new Color(160, 205, 250); 16 | window.opacity = 0.8f; 17 | window.width = 300; 18 | window.height = 100; 19 | 20 | TextTheme text = new TextTheme(); 21 | text.title = new Font("Arial", Font.BOLD, 22); 22 | text.subtitle = new Font("Arial", Font.PLAIN, 16); 23 | text.titleColor = new Color(10, 10, 10); 24 | text.subtitleColor = new Color(10, 10, 10); 25 | 26 | pack.setTheme(WindowTheme.class, window); 27 | pack.setTheme(TextTheme.class, text); 28 | 29 | return pack; 30 | } 31 | 32 | public static ThemePackage cleanDark() { 33 | ThemePackage pack = new ThemePackage(); 34 | 35 | WindowTheme window = new WindowTheme(); 36 | window.background = new Color(0, 0, 0); 37 | window.foreground = new Color(16, 124, 162); 38 | window.opacity = 0.8f; 39 | window.width = 300; 40 | window.height = 100; 41 | 42 | TextTheme text = new TextTheme(); 43 | text.title = new Font("Arial", Font.BOLD, 22); 44 | text.subtitle = new Font("Arial", Font.PLAIN, 16); 45 | text.titleColor = new Color(200, 200, 200); 46 | text.subtitleColor = new Color(200, 200, 200); 47 | 48 | pack.setTheme(WindowTheme.class, window); 49 | pack.setTheme(TextTheme.class, text); 50 | 51 | return pack; 52 | } 53 | 54 | public static ThemePackage aqua() { 55 | ThemePackage pack = new ThemePackage(); 56 | 57 | WindowTheme window = new WindowTheme(); 58 | window.background = new Color(0, 191, 255); 59 | window.foreground = new Color(0, 30, 255); 60 | window.opacity = 0.5f; 61 | window.width = 300; 62 | window.height = 100; 63 | 64 | TextTheme text = new TextTheme(); 65 | text.title = new Font("Comic Sans MS", Font.BOLD, 22); 66 | text.subtitle = new Font("Comic Sans MS", Font.PLAIN, 16); 67 | text.titleColor = new Color(10, 10, 10); 68 | text.subtitleColor = new Color(10, 10, 10); 69 | 70 | pack.setTheme(WindowTheme.class, window); 71 | pack.setTheme(TextTheme.class, text); 72 | 73 | return pack; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/com/theme/WindowTheme.java: -------------------------------------------------------------------------------- 1 | package com.theme; 2 | 3 | import java.awt.Color; 4 | 5 | public class WindowTheme { 6 | public Color background; 7 | public Color foreground; 8 | public double opacity; 9 | public int width; 10 | public int height; 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/utils/IconUtils.java: -------------------------------------------------------------------------------- 1 | package com.utils; 2 | 3 | import java.awt.Image; 4 | 5 | import javax.swing.ImageIcon; 6 | 7 | public class IconUtils { 8 | private IconUtils() { 9 | 10 | } 11 | 12 | /** 13 | * Creates an ImageIcon of the specified path with a given width and height. 14 | * 15 | * @param path 16 | * the classpath, starting with the root (e.g., /com/demo/exclamation.png) 17 | * @param width 18 | * the width in pixels 19 | * @param height 20 | * the height in pixels 21 | * @return the created ImageIcon 22 | */ 23 | public static ImageIcon createIcon(String path, int width, int height) { 24 | java.net.URL imgURL = IconUtils.class.getResource(path); 25 | if (imgURL != null) { 26 | ImageIcon icon = new ImageIcon(imgURL); 27 | icon = new ImageIcon(icon.getImage().getScaledInstance(width, height, Image.SCALE_SMOOTH)); 28 | 29 | return icon; 30 | } else { 31 | System.err.println("Couldn't find file: " + path); 32 | return null; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/utils/MathUtils.java: -------------------------------------------------------------------------------- 1 | package com.utils; 2 | 3 | public class MathUtils { 4 | /** 5 | * @param number 6 | * the number to find the sign of 7 | * @return the sign of the number 8 | */ 9 | public static int sign(double number) { 10 | return (int) Math.signum(number); 11 | } 12 | 13 | /** 14 | * Clamps the number to be between the min and the max. 15 | * 16 | * @param num 17 | * the number to clamp 18 | * @param min 19 | * the minimum value 20 | * @param max 21 | * the maximum value 22 | * @return the clamped number between min and max 23 | */ 24 | public static double clamp(double num, double min, double max) { 25 | if (num < min) 26 | return min; 27 | if (num > max) 28 | return max; 29 | return num; 30 | } 31 | } -------------------------------------------------------------------------------- /src/main/java/com/utils/Screen.java: -------------------------------------------------------------------------------- 1 | package com.utils; 2 | 3 | import java.awt.Dimension; 4 | import java.awt.GraphicsDevice; 5 | import java.awt.GraphicsEnvironment; 6 | import java.awt.Toolkit; 7 | 8 | import com.notification.Notification; 9 | import com.notification.NotificationFactory.Location; 10 | 11 | public class Screen { 12 | private int m_width; 13 | private int m_height; 14 | 15 | private int m_leftX; 16 | private int m_centerX; 17 | private int m_rightX; 18 | 19 | private int m_topY; 20 | private int m_centerY; 21 | private int m_bottomY; 22 | 23 | private int m_padding; 24 | 25 | private Screen(boolean spanMultipleMonitors, int padding) { 26 | m_padding = padding; 27 | setupDimensions(spanMultipleMonitors); 28 | calculatePositions(); 29 | } 30 | 31 | public static Screen standard() { 32 | return new Screen(true, 80); 33 | } 34 | 35 | public static Screen withSpan(boolean spanMultipleMonitors) { 36 | return new Screen(spanMultipleMonitors, 80); 37 | } 38 | 39 | public static Screen withPadding(int padding) { 40 | return new Screen(true, padding); 41 | } 42 | 43 | public static Screen withSpanAndPadding(boolean spanMultipleMonitors, int padding) { 44 | return new Screen(spanMultipleMonitors, padding); 45 | } 46 | 47 | private void setupDimensions(boolean spanMultipleMonitors) { 48 | if (spanMultipleMonitors) { 49 | Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); 50 | m_width = screenSize.width; 51 | m_height = screenSize.height; 52 | 53 | } else { 54 | GraphicsDevice gd = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice(); 55 | m_width = gd.getDisplayMode().getWidth(); 56 | m_height = gd.getDisplayMode().getHeight(); 57 | } 58 | } 59 | 60 | private void calculatePositions() { 61 | m_leftX = m_padding; 62 | m_centerX = (int) (m_width / 2d); 63 | m_rightX = m_width - m_padding; 64 | 65 | m_topY = m_padding; 66 | m_centerY = (int) (m_height / 2d); 67 | m_bottomY = m_height - m_padding; 68 | } 69 | 70 | public int getX(Location loc, Notification note) { 71 | switch (loc) { 72 | case SOUTHWEST: 73 | return m_leftX; 74 | case WEST: 75 | return m_leftX; 76 | case NORTHWEST: 77 | return m_leftX; 78 | case NORTH: 79 | return m_centerX - note.getWidth() / 2; 80 | case SOUTH: 81 | return m_centerX - note.getWidth() / 2; 82 | case SOUTHEAST: 83 | return m_rightX - note.getWidth(); 84 | case EAST: 85 | return m_rightX - note.getWidth(); 86 | case NORTHEAST: 87 | return m_rightX - note.getWidth(); 88 | default: 89 | return -1; 90 | } 91 | } 92 | 93 | public int getY(Location loc, Notification note) { 94 | switch (loc) { 95 | case SOUTHWEST: 96 | return m_bottomY - note.getHeight(); 97 | case WEST: 98 | return m_centerY - note.getHeight() / 2; 99 | case NORTHWEST: 100 | return m_topY; 101 | case NORTH: 102 | return m_topY; 103 | case SOUTH: 104 | return m_bottomY - note.getHeight(); 105 | case SOUTHEAST: 106 | return m_bottomY - note.getHeight(); 107 | case EAST: 108 | return m_centerY - note.getHeight() / 2; 109 | case NORTHEAST: 110 | return m_topY; 111 | default: 112 | return -1; 113 | } 114 | } 115 | 116 | public int getPadding() { 117 | return m_padding; 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/main/java/com/utils/Time.java: -------------------------------------------------------------------------------- 1 | package com.utils; 2 | 3 | /** 4 | * Represents a time, which can be used for the duration of a Notification, fade times, etc. 5 | */ 6 | public final class Time { 7 | private int m_milliseconds; 8 | private boolean m_infinite; 9 | 10 | private Time(int milliseconds, boolean infinite) { 11 | m_milliseconds = milliseconds; 12 | m_infinite = infinite; 13 | } 14 | 15 | /** 16 | * @param seconds 17 | * the number of seconds. This is truncated at the millisecond. 18 | * @return a Time of length seconds 19 | */ 20 | public static Time seconds(double seconds) { 21 | return new Time((int) (seconds * 1000), false); 22 | } 23 | 24 | /** 25 | * @param milliseconds 26 | * the number of milliseconds 27 | * @return a Time of length milliseconds 28 | */ 29 | public static Time milliseconds(int milliseconds) { 30 | return new Time(milliseconds, false); 31 | } 32 | 33 | /** 34 | * Specifies an infinite length of Time. 35 | * 36 | * @return a Time representing infinity 37 | */ 38 | public static Time infinite() { 39 | return new Time(-1, true); 40 | } 41 | 42 | /** 43 | * @param time 44 | * the Time to add to 45 | * @return the sum of the two times 46 | */ 47 | public Time add(Time time) { 48 | return new Time(m_milliseconds + time.getMilliseconds(), m_infinite || time.isInfinite()); 49 | } 50 | 51 | /** 52 | * @return the number of seconds, of -1 if it is infinite 53 | */ 54 | public double getSeconds() { 55 | if (m_infinite) 56 | return -1; 57 | return (double) m_milliseconds / 1000; 58 | } 59 | 60 | /** 61 | * @return the number of milliseconds, or -1 if it is infinite 62 | */ 63 | public int getMilliseconds() { 64 | if (m_infinite) 65 | return -1; 66 | return m_milliseconds; 67 | } 68 | 69 | /** 70 | * @return whether or not the Time is infinite 71 | */ 72 | public boolean isInfinite() { 73 | return m_infinite; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/main/resources/com/demo/exclamation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spfrommer/JCommunique/ac5b1895776ad6baead6c7881d9790f051ae3de3/src/main/resources/com/demo/exclamation.png -------------------------------------------------------------------------------- /src/test/java/com/unit/MathUtilsTest.java: -------------------------------------------------------------------------------- 1 | package com.unit; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | 5 | import org.junit.Test; 6 | 7 | import com.utils.MathUtils; 8 | 9 | public class MathUtilsTest { 10 | @Test 11 | public void testSignNegative() { 12 | assertEquals("Sign of negtive number should be negative", -1, MathUtils.sign(-8.5)); 13 | } 14 | 15 | @Test 16 | public void testSignPositive() { 17 | assertEquals("Sign of positive number should be positive", 1, MathUtils.sign(8.5)); 18 | } 19 | 20 | @Test 21 | public void testSignZero() { 22 | assertEquals("Sign of zero should be zero", 0, MathUtils.sign(0)); 23 | } 24 | 25 | @Test 26 | public void testClampMin() { 27 | assertEquals("Clamp should work for min", 5.0, MathUtils.clamp(2.0, 5.0, 7.0), TestUtils.TINY_DELTA); 28 | } 29 | 30 | @Test 31 | public void testClampMax() { 32 | assertEquals("Clamp should work for max", 7.0, MathUtils.clamp(10.0, 5.0, 7.0), TestUtils.TINY_DELTA); 33 | } 34 | 35 | @Test 36 | public void testClampMiddle() { 37 | assertEquals("Clamp should work for in between values", 6.0, MathUtils.clamp(6.0, 5.0, 7.0), TestUtils.TINY_DELTA); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/test/java/com/unit/NotificationFactoryTest.java: -------------------------------------------------------------------------------- 1 | package com.unit; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.Assert.assertNotNull; 5 | 6 | import javax.swing.ImageIcon; 7 | 8 | import org.junit.Test; 9 | 10 | import com.notification.NotificationBuilder; 11 | import com.notification.NotificationFactory; 12 | import com.notification.types.AcceptNotification; 13 | import com.notification.types.IconNotification; 14 | import com.notification.types.ProgressBarNotification; 15 | import com.notification.types.TextNotification; 16 | import com.notification.types.WindowNotification; 17 | import com.theme.ThemePackage; 18 | import com.theme.ThemePackagePresets; 19 | 20 | public class NotificationFactoryTest { 21 | private static final String TITLE = "title"; 22 | private static final String SUBTITLE = "subtitle"; 23 | private static final String ACCEPT = "Accept!"; 24 | private static final String DECLINE = "Decline!"; 25 | private static final ImageIcon ICON = new ImageIcon(); 26 | private static final ThemePackage s_pack = ThemePackagePresets.aqua(); 27 | 28 | @Test 29 | public void testTextNotification() { 30 | NotificationFactory factory = new NotificationFactory(s_pack); 31 | TextNotification note = factory.buildTextNotification(TITLE, SUBTITLE); 32 | assertEquals("TextNotification should have the same title", TITLE, note.getTitle()); 33 | assertEquals("TextNotification should have the same subtitle", SUBTITLE, note.getSubtitle()); 34 | } 35 | 36 | @Test 37 | public void testAcceptNotification() { 38 | NotificationFactory factory = new NotificationFactory(s_pack); 39 | AcceptNotification note = factory.buildAcceptNotification(TITLE, SUBTITLE, ACCEPT, DECLINE); 40 | assertEquals("AcceptNotification should have the same title", TITLE, note.getTitle()); 41 | assertEquals("AcceptNotification should have the same subtitle", SUBTITLE, note.getSubtitle()); 42 | assertEquals("AcceptNotification should have the same accept text", ACCEPT, note.getAcceptText()); 43 | assertEquals("AcceptNotification should have the same decline text", DECLINE, note.getDeclineText()); 44 | } 45 | 46 | @Test 47 | public void testIconNotification() { 48 | NotificationFactory factory = new NotificationFactory(s_pack); 49 | IconNotification note = factory.buildIconNotification(TITLE, SUBTITLE, ICON); 50 | assertEquals("IconNotification should have the same title", TITLE, note.getTitle()); 51 | assertEquals("IconNotification should have the same subtitle", SUBTITLE, note.getSubtitle()); 52 | assertEquals("IconNotification should have the same icon", ICON, note.getIcon()); 53 | } 54 | 55 | @Test 56 | public void testProgressNotification() { 57 | NotificationFactory factory = new NotificationFactory(s_pack); 58 | ProgressBarNotification note = factory.buildProgressBarNotification(TITLE); 59 | assertEquals("ProgressBarNotification should have the same title", TITLE, note.getTitle()); 60 | } 61 | 62 | @Test 63 | public void testCustomBuilder() { 64 | NotificationFactory factory = new NotificationFactory(s_pack); 65 | factory.addBuilder(CustomNotification.class, new CustomBuilder()); 66 | CustomNotification note = factory.build(CustomNotification.class); 67 | assertNotNull("The built notification should not be null", note); 68 | } 69 | 70 | private class CustomBuilder implements NotificationBuilder { 71 | @Override 72 | public CustomNotification buildNotification(ThemePackage pack, Object... args) { 73 | return new CustomNotification(); 74 | } 75 | } 76 | 77 | private class CustomNotification extends WindowNotification { 78 | 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/test/java/com/unit/NotificationManagerTest.java: -------------------------------------------------------------------------------- 1 | package com.unit; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | 5 | import org.junit.Test; 6 | 7 | import com.notification.Notification; 8 | import com.notification.NotificationManager; 9 | import com.notification.types.TextNotification; 10 | import com.utils.Time; 11 | 12 | public class NotificationManagerTest { 13 | @Test 14 | public void addAndRemoveShouldTriggerChildCalls() { 15 | CustomNotificationManager manager = new CustomNotificationManager(); 16 | TextNotification note = new TextNotification(); 17 | 18 | manager.addNotification(note, Time.infinite()); 19 | assertEquals("addNotification() should trigger notificationAdded()", 1, manager.addedCounter); 20 | manager.removeNotification(note); 21 | assertEquals("removeNotification() should trigger notificationRemoved()", 1, manager.removedCounter); 22 | } 23 | 24 | @Test 25 | public void addAndRemoveShouldModifyList() { 26 | CustomNotificationManager manager = new CustomNotificationManager(); 27 | TextNotification note = new TextNotification(); 28 | 29 | manager.addNotification(note, Time.infinite()); 30 | assertEquals("addNotification() should increase Notification list size", 1, manager.getNotifications().size()); 31 | manager.removeNotification(note); 32 | assertEquals("removeNotification() should increase Notification list size", 0, manager.getNotifications().size()); 33 | } 34 | 35 | @Test 36 | public void managerShouldNotDoubleAdd() { 37 | CustomNotificationManager manager = new CustomNotificationManager(); 38 | TextNotification note = new TextNotification(); 39 | 40 | manager.addNotification(note, Time.infinite()); 41 | manager.addNotification(note, Time.infinite()); 42 | assertEquals("Notification should have been added only once", 1, manager.addedCounter); 43 | } 44 | 45 | @Test 46 | public void scheduleRemovalShouldRemove() { 47 | CustomNotificationManager manager = new CustomNotificationManager(); 48 | TextNotification note = new TextNotification(); 49 | 50 | manager.addNotification(note, Time.infinite()); 51 | manager.scheduleRemoval(note, Time.milliseconds(50)); 52 | try { 53 | Thread.sleep(100); 54 | } catch (InterruptedException e) { 55 | e.printStackTrace(); 56 | } 57 | assertEquals("Notification should have been removed once", 1, manager.removedCounter); 58 | } 59 | 60 | @Test 61 | public void managerShouldNotDoubleRemove() { 62 | CustomNotificationManager manager = new CustomNotificationManager(); 63 | TextNotification note = new TextNotification(); 64 | 65 | manager.addNotification(note, Time.infinite()); 66 | manager.scheduleRemoval(note, Time.milliseconds(50)); 67 | manager.removeNotification(note); 68 | manager.removeNotification(note); 69 | assertEquals("Notification should have been removed only once", 1, manager.removedCounter); 70 | try { 71 | Thread.sleep(100); 72 | } catch (InterruptedException e) { 73 | e.printStackTrace(); 74 | } 75 | assertEquals("Notification should have been removed only once, even after scheduling", 1, manager.removedCounter); 76 | } 77 | 78 | private class CustomNotificationManager extends NotificationManager { 79 | private int addedCounter = 0; 80 | private int removedCounter = 0; 81 | 82 | @Override 83 | protected void notificationAdded(Notification note, Time time) { 84 | addedCounter++; 85 | } 86 | 87 | @Override 88 | protected void notificationRemoved(Notification note) { 89 | removedCounter++; 90 | } 91 | 92 | @Override 93 | public void scheduleRemoval(Notification note, Time time) { 94 | super.scheduleRemoval(note, time); 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/test/java/com/unit/NotificationTest.java: -------------------------------------------------------------------------------- 1 | package com.unit; 2 | 3 | import com.notification.Notification; 4 | import com.notification.types.TextNotification; 5 | 6 | import static org.junit.Assert.*; 7 | import org.junit.Test; 8 | 9 | public class NotificationTest { 10 | @Test 11 | public void notificationShouldStartUnmanaged() { 12 | Notification note = new TextNotification(); 13 | assertFalse("Notification should start unmanaged", note.isManaged()); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/test/java/com/unit/ProgressBarNotificationTest.java: -------------------------------------------------------------------------------- 1 | package com.unit; 2 | 3 | import com.notification.types.ProgressBarNotification; 4 | 5 | import static org.junit.Assert.*; 6 | import org.junit.Test; 7 | 8 | public class ProgressBarNotificationTest { 9 | @Test 10 | public void progressBarShouldStartWith0() { 11 | ProgressBarNotification note = new ProgressBarNotification(); 12 | assertEquals("ProgressBarNotification should start with 0 progress", 0, note.getProgress()); 13 | } 14 | 15 | @Test 16 | public void progressBarShouldNotExceed100() { 17 | ProgressBarNotification note = new ProgressBarNotification(); 18 | note.setProgress(123); 19 | assertEquals("ProgressBarNotification should not exceed 100 progress", 100, note.getProgress()); 20 | } 21 | 22 | @Test 23 | public void progressBarShouldNotGoBelow0() { 24 | ProgressBarNotification note = new ProgressBarNotification(); 25 | note.setProgress(-10); 26 | assertEquals("ProgressBarNotification should not go below 0 progress", 0, note.getProgress()); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/test/java/com/unit/TestUtils.java: -------------------------------------------------------------------------------- 1 | package com.unit; 2 | 3 | public class TestUtils { 4 | public static final double TINY_DELTA = 0.000000001; 5 | } 6 | -------------------------------------------------------------------------------- /src/test/java/com/unit/WindowNotificationTest.java: -------------------------------------------------------------------------------- 1 | package com.unit; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.Assert.assertFalse; 5 | import static org.junit.Assert.assertTrue; 6 | 7 | import java.awt.Color; 8 | import java.awt.event.MouseEvent; 9 | 10 | import javax.swing.JPanel; 11 | import javax.swing.JWindow; 12 | 13 | import org.junit.Test; 14 | 15 | import com.notification.Notification; 16 | import com.notification.NotificationListener; 17 | import com.notification.types.TextNotification; 18 | import com.notification.types.WindowNotification; 19 | import com.theme.WindowTheme; 20 | 21 | public class WindowNotificationTest { 22 | @Test 23 | public void opacityShouldNotExceed1() { 24 | WindowNotification note = new TextNotification(); // needs an instance of a concrete class 25 | note.setOpacity(2); 26 | assertEquals("WindowNotification opacity should not exceed 1", 1, note.getOpacity(), TestUtils.TINY_DELTA); 27 | } 28 | 29 | @Test 30 | public void opacityShouldNotGoBelow0() { 31 | WindowNotification note = new TextNotification(); 32 | note.setOpacity(-1); 33 | assertEquals("WindowNotification opacity should not go below 0", 0, note.getOpacity(), TestUtils.TINY_DELTA); 34 | } 35 | 36 | @Test 37 | public void showAndHideShouldMakeWindowVisibleAndInvisible() { 38 | WindowNotification note = new TextNotification(); 39 | note.show(); 40 | assertTrue("Note should be visible after shown", note.isShown()); 41 | note.hide(); 42 | assertFalse("Note should not be visible after shown", note.isShown()); 43 | } 44 | 45 | @Test 46 | public void showShouldFireListeners() { 47 | WindowNotification note = new TextNotification(); 48 | NotificationListener listener = new NotificationListener() { 49 | @Override 50 | public void actionCompleted(Notification note, String action) { 51 | assertEquals("Show should trigger SHOWN message", WindowNotification.SHOWN, action); 52 | } 53 | }; 54 | note.addNotificationListener(listener); 55 | note.show(); 56 | note.removeNotificationListener(listener); 57 | note.hide(); 58 | } 59 | 60 | @Test 61 | public void hideShouldFireListeners() { 62 | WindowNotification note = new TextNotification(); 63 | NotificationListener listener = new NotificationListener() { 64 | @Override 65 | public void actionCompleted(Notification note, String action) { 66 | assertEquals("Hide should trigger HIDDEN message", WindowNotification.HIDDEN, action); 67 | } 68 | }; 69 | note.show(); 70 | note.addNotificationListener(listener); 71 | note.hide(); 72 | note.removeNotificationListener(listener); 73 | } 74 | 75 | @Test 76 | public void clickShouldFireListeners() { 77 | ClickNotification note = new ClickNotification(); 78 | NotificationListener listener = new NotificationListener() { 79 | @Override 80 | public void actionCompleted(Notification note, String action) { 81 | assertEquals("Click should trigger CLICKED message", WindowNotification.CLICKED, action); 82 | } 83 | }; 84 | note.show(); 85 | note.addNotificationListener(listener); 86 | note.simulateClick(); 87 | note.removeNotificationListener(listener); 88 | } 89 | 90 | private class ClickNotification extends WindowNotification { 91 | public void simulateClick() { 92 | JPanel panel = new JPanel(); 93 | setPanel(panel); 94 | MouseEvent me = new MouseEvent(panel, MouseEvent.MOUSE_CLICKED, System.currentTimeMillis(), 0, 10, 10, 1, false); 95 | panel.dispatchEvent(me); 96 | } 97 | } 98 | 99 | @Test 100 | public void setWindowThemeShouldRecurse() { 101 | RecursiveTestNotification note = new RecursiveTestNotification(); 102 | WindowTheme theme = new WindowTheme(); 103 | theme.foreground = Color.RED; 104 | theme.background = Color.BLUE; 105 | note.setWindowTheme(theme); 106 | note.assertMatchesWindowTheme(theme); 107 | } 108 | 109 | private class RecursiveTestNotification extends WindowNotification { 110 | private JPanel m_panel; 111 | private JPanel m_subPanel; 112 | 113 | public RecursiveTestNotification() { 114 | m_panel = new JPanel(); 115 | m_subPanel = new JPanel(); 116 | super.setPanel(m_panel); 117 | m_panel.add(m_subPanel); 118 | } 119 | 120 | public void assertMatchesWindowTheme(WindowTheme theme) { 121 | assertEquals("Main panel background color should match WindowTheme", theme.background, m_panel.getBackground()); 122 | assertEquals("Sub panel background color should match WindowTheme", theme.background, m_subPanel.getBackground()); 123 | assertEquals("Main panel foreground color should match WindowTheme", theme.foreground, m_panel.getForeground()); 124 | assertEquals("Sub panel foreground color should match WindowTheme", theme.foreground, m_subPanel.getForeground()); 125 | } 126 | } 127 | 128 | @Test 129 | public void windowThemeShouldApply() { 130 | WindowAccessNotification note = new WindowAccessNotification(); 131 | WindowTheme theme = new WindowTheme(); 132 | theme.foreground = Color.RED; 133 | theme.background = Color.BLUE; 134 | theme.opacity = 0.5; 135 | theme.width = 100; 136 | theme.height = 400; 137 | note.setWindowTheme(theme); 138 | 139 | assertEquals("Window opacity should equal theme opacity", theme.opacity, note.getOpacity(), TestUtils.TINY_DELTA); 140 | assertEquals("Window width should equal theme width", theme.width, note.getWidth()); 141 | assertEquals("Window height should equal theme height", theme.height, note.getHeight()); 142 | assertEquals("Window foreground should equal theme foreground", theme.foreground, note.getInternalWindow() 143 | .getForeground()); 144 | assertEquals("Window background should equal theme background", theme.background, note.getInternalWindow() 145 | .getBackground()); 146 | } 147 | 148 | private class WindowAccessNotification extends WindowNotification { 149 | public JWindow getInternalWindow() { 150 | return getWindow(); 151 | } 152 | } 153 | } 154 | --------------------------------------------------------------------------------