├── .gitignore ├── docs └── images │ ├── OrganizerOverview.png │ ├── OrganizerOverview1_6.png │ └── SoulsSpeedruns_Banner.png ├── src └── com │ └── soulsspeedruns │ └── organizer │ ├── images │ ├── FrankerZ.png │ ├── DiscordLogo.png │ ├── ImportIcon.png │ ├── ImportIcon24.png │ ├── SettingsIcon.png │ ├── Splash_Screen.bmp │ ├── SoulsSpeedrunsLogo100.png │ ├── SoulsSpeedrunsLogo16.png │ ├── SoulsSpeedrunsLogo32.png │ ├── SoulsSpeedrunsLogo64.ico │ └── readonly │ │ ├── ReadOnlyIcon14.png │ │ ├── ReadOnlyIcon16.png │ │ ├── ReadOnlyIcon22.png │ │ ├── WritableIcon14.png │ │ ├── WritableIcon16.png │ │ ├── WritableIcon22.png │ │ ├── ReadOnlyIconSmall.png │ │ ├── ReadOnlyIconHover22.png │ │ ├── ReadOnlyIconMedium.png │ │ ├── WritableIconHover22.png │ │ ├── ReadOnlyIconDarkMode14.png │ │ ├── ReadOnlyIconDarkMode16.png │ │ ├── ReadOnlyIconDarkMode22.png │ │ ├── WritableIconDarkMode14.png │ │ ├── WritableIconDarkMode16.png │ │ ├── WritableIconDarkMode22.png │ │ ├── ReadOnlyIconDarkModeHover22.png │ │ └── WritableIconDarkModeHover22.png │ ├── theme │ ├── DefaultTheme.java │ ├── GlobalThemeInitTask.java │ ├── GlobalThemeAdjustmentTask.java │ ├── soulsspeedruns_icons.properties │ ├── SoulsSpeedrunsTheme.java │ ├── soulsspeedruns_ui.properties │ └── soulsspeedruns_defaults.properties │ ├── games │ ├── config │ │ ├── GameListListener.java │ │ ├── GameConfigPanel.java │ │ ├── ProfileListRenderer.java │ │ ├── GameListEntryDragListener.java │ │ ├── GamesConfigurationWindow.java │ │ ├── GameListTransferHandler.java │ │ ├── GameListEntry.java │ │ ├── GamesConfigPane.java │ │ ├── GameConfigProfilesPanel.java │ │ ├── GameConfigDirectoryPanel.java │ │ └── GameList.java │ └── Profile.java │ ├── listeners │ ├── SettingsListener.java │ ├── SearchListener.java │ ├── NavigationListener.java │ ├── SortingListener.java │ ├── GameListener.java │ ├── ProfileListener.java │ └── SaveListener.java │ ├── savelist │ ├── SaveListRenderer.java │ ├── SaveListDragListener.java │ ├── RootFolder.java │ ├── Save.java │ ├── SaveListTransferHandler.java │ ├── Folder.java │ └── SaveListEntry.java │ ├── main │ ├── config │ │ ├── SortingComboBoxRenderer.java │ │ ├── GamesComboBoxRenderer.java │ │ ├── ProfilesComboBoxRenderer.java │ │ ├── SortingComboBox.java │ │ ├── SortingCategory.java │ │ ├── GamesComboBox.java │ │ └── ProfilesComboBox.java │ ├── ListPanel.java │ ├── OrganizerWindow.java │ └── TopPanel.java │ ├── messages │ ├── FailedLoadMessage.java │ ├── SuccessfulImportMessage.java │ ├── SuccessfulRefreshMessage.java │ ├── SuccessfulDeleteMessage.java │ ├── SuccessfulLoadMessage.java │ ├── SuccessfulReplaceMessage.java │ ├── UndecoratedMessageDialog.java │ └── AbstractMessage.java │ ├── components │ ├── HyperLink.java │ ├── SearchBar.java │ └── ReadOnlyButton.java │ ├── update │ ├── NewReleasePanel.java │ └── NewReleaseWindow.java │ ├── settings │ ├── HotkeysSettingsPanel.java │ ├── ButtonsSettingsPanel.java │ ├── SettingsWindow.java │ ├── HotkeyTextField.java │ └── GeneralSettingsPanel.java │ ├── about │ ├── AboutPanel.java │ └── AboutWindow.java │ ├── hotkeys │ ├── GlobalHotkey.java │ └── GlobalKeyboardHook.java │ └── managers │ └── VersionManager.java ├── pom.xml └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /bin/ 2 | /.classpath 3 | /.project 4 | /target/ 5 | /.settings 6 | /.vscode -------------------------------------------------------------------------------- /docs/images/OrganizerOverview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kahmul/SoulsSpeedruns-Save-Organizer/HEAD/docs/images/OrganizerOverview.png -------------------------------------------------------------------------------- /docs/images/OrganizerOverview1_6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kahmul/SoulsSpeedruns-Save-Organizer/HEAD/docs/images/OrganizerOverview1_6.png -------------------------------------------------------------------------------- /docs/images/SoulsSpeedruns_Banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kahmul/SoulsSpeedruns-Save-Organizer/HEAD/docs/images/SoulsSpeedruns_Banner.png -------------------------------------------------------------------------------- /src/com/soulsspeedruns/organizer/images/FrankerZ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kahmul/SoulsSpeedruns-Save-Organizer/HEAD/src/com/soulsspeedruns/organizer/images/FrankerZ.png -------------------------------------------------------------------------------- /src/com/soulsspeedruns/organizer/images/DiscordLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kahmul/SoulsSpeedruns-Save-Organizer/HEAD/src/com/soulsspeedruns/organizer/images/DiscordLogo.png -------------------------------------------------------------------------------- /src/com/soulsspeedruns/organizer/images/ImportIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kahmul/SoulsSpeedruns-Save-Organizer/HEAD/src/com/soulsspeedruns/organizer/images/ImportIcon.png -------------------------------------------------------------------------------- /src/com/soulsspeedruns/organizer/images/ImportIcon24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kahmul/SoulsSpeedruns-Save-Organizer/HEAD/src/com/soulsspeedruns/organizer/images/ImportIcon24.png -------------------------------------------------------------------------------- /src/com/soulsspeedruns/organizer/images/SettingsIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kahmul/SoulsSpeedruns-Save-Organizer/HEAD/src/com/soulsspeedruns/organizer/images/SettingsIcon.png -------------------------------------------------------------------------------- /src/com/soulsspeedruns/organizer/images/Splash_Screen.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kahmul/SoulsSpeedruns-Save-Organizer/HEAD/src/com/soulsspeedruns/organizer/images/Splash_Screen.bmp -------------------------------------------------------------------------------- /src/com/soulsspeedruns/organizer/images/SoulsSpeedrunsLogo100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kahmul/SoulsSpeedruns-Save-Organizer/HEAD/src/com/soulsspeedruns/organizer/images/SoulsSpeedrunsLogo100.png -------------------------------------------------------------------------------- /src/com/soulsspeedruns/organizer/images/SoulsSpeedrunsLogo16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kahmul/SoulsSpeedruns-Save-Organizer/HEAD/src/com/soulsspeedruns/organizer/images/SoulsSpeedrunsLogo16.png -------------------------------------------------------------------------------- /src/com/soulsspeedruns/organizer/images/SoulsSpeedrunsLogo32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kahmul/SoulsSpeedruns-Save-Organizer/HEAD/src/com/soulsspeedruns/organizer/images/SoulsSpeedrunsLogo32.png -------------------------------------------------------------------------------- /src/com/soulsspeedruns/organizer/images/SoulsSpeedrunsLogo64.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kahmul/SoulsSpeedruns-Save-Organizer/HEAD/src/com/soulsspeedruns/organizer/images/SoulsSpeedrunsLogo64.ico -------------------------------------------------------------------------------- /src/com/soulsspeedruns/organizer/images/readonly/ReadOnlyIcon14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kahmul/SoulsSpeedruns-Save-Organizer/HEAD/src/com/soulsspeedruns/organizer/images/readonly/ReadOnlyIcon14.png -------------------------------------------------------------------------------- /src/com/soulsspeedruns/organizer/images/readonly/ReadOnlyIcon16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kahmul/SoulsSpeedruns-Save-Organizer/HEAD/src/com/soulsspeedruns/organizer/images/readonly/ReadOnlyIcon16.png -------------------------------------------------------------------------------- /src/com/soulsspeedruns/organizer/images/readonly/ReadOnlyIcon22.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kahmul/SoulsSpeedruns-Save-Organizer/HEAD/src/com/soulsspeedruns/organizer/images/readonly/ReadOnlyIcon22.png -------------------------------------------------------------------------------- /src/com/soulsspeedruns/organizer/images/readonly/WritableIcon14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kahmul/SoulsSpeedruns-Save-Organizer/HEAD/src/com/soulsspeedruns/organizer/images/readonly/WritableIcon14.png -------------------------------------------------------------------------------- /src/com/soulsspeedruns/organizer/images/readonly/WritableIcon16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kahmul/SoulsSpeedruns-Save-Organizer/HEAD/src/com/soulsspeedruns/organizer/images/readonly/WritableIcon16.png -------------------------------------------------------------------------------- /src/com/soulsspeedruns/organizer/images/readonly/WritableIcon22.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kahmul/SoulsSpeedruns-Save-Organizer/HEAD/src/com/soulsspeedruns/organizer/images/readonly/WritableIcon22.png -------------------------------------------------------------------------------- /src/com/soulsspeedruns/organizer/images/readonly/ReadOnlyIconSmall.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kahmul/SoulsSpeedruns-Save-Organizer/HEAD/src/com/soulsspeedruns/organizer/images/readonly/ReadOnlyIconSmall.png -------------------------------------------------------------------------------- /src/com/soulsspeedruns/organizer/images/readonly/ReadOnlyIconHover22.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kahmul/SoulsSpeedruns-Save-Organizer/HEAD/src/com/soulsspeedruns/organizer/images/readonly/ReadOnlyIconHover22.png -------------------------------------------------------------------------------- /src/com/soulsspeedruns/organizer/images/readonly/ReadOnlyIconMedium.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kahmul/SoulsSpeedruns-Save-Organizer/HEAD/src/com/soulsspeedruns/organizer/images/readonly/ReadOnlyIconMedium.png -------------------------------------------------------------------------------- /src/com/soulsspeedruns/organizer/images/readonly/WritableIconHover22.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kahmul/SoulsSpeedruns-Save-Organizer/HEAD/src/com/soulsspeedruns/organizer/images/readonly/WritableIconHover22.png -------------------------------------------------------------------------------- /src/com/soulsspeedruns/organizer/images/readonly/ReadOnlyIconDarkMode14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kahmul/SoulsSpeedruns-Save-Organizer/HEAD/src/com/soulsspeedruns/organizer/images/readonly/ReadOnlyIconDarkMode14.png -------------------------------------------------------------------------------- /src/com/soulsspeedruns/organizer/images/readonly/ReadOnlyIconDarkMode16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kahmul/SoulsSpeedruns-Save-Organizer/HEAD/src/com/soulsspeedruns/organizer/images/readonly/ReadOnlyIconDarkMode16.png -------------------------------------------------------------------------------- /src/com/soulsspeedruns/organizer/images/readonly/ReadOnlyIconDarkMode22.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kahmul/SoulsSpeedruns-Save-Organizer/HEAD/src/com/soulsspeedruns/organizer/images/readonly/ReadOnlyIconDarkMode22.png -------------------------------------------------------------------------------- /src/com/soulsspeedruns/organizer/images/readonly/WritableIconDarkMode14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kahmul/SoulsSpeedruns-Save-Organizer/HEAD/src/com/soulsspeedruns/organizer/images/readonly/WritableIconDarkMode14.png -------------------------------------------------------------------------------- /src/com/soulsspeedruns/organizer/images/readonly/WritableIconDarkMode16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kahmul/SoulsSpeedruns-Save-Organizer/HEAD/src/com/soulsspeedruns/organizer/images/readonly/WritableIconDarkMode16.png -------------------------------------------------------------------------------- /src/com/soulsspeedruns/organizer/images/readonly/WritableIconDarkMode22.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kahmul/SoulsSpeedruns-Save-Organizer/HEAD/src/com/soulsspeedruns/organizer/images/readonly/WritableIconDarkMode22.png -------------------------------------------------------------------------------- /src/com/soulsspeedruns/organizer/images/readonly/ReadOnlyIconDarkModeHover22.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kahmul/SoulsSpeedruns-Save-Organizer/HEAD/src/com/soulsspeedruns/organizer/images/readonly/ReadOnlyIconDarkModeHover22.png -------------------------------------------------------------------------------- /src/com/soulsspeedruns/organizer/images/readonly/WritableIconDarkModeHover22.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kahmul/SoulsSpeedruns-Save-Organizer/HEAD/src/com/soulsspeedruns/organizer/images/readonly/WritableIconDarkModeHover22.png -------------------------------------------------------------------------------- /src/com/soulsspeedruns/organizer/theme/DefaultTheme.java: -------------------------------------------------------------------------------- 1 | package com.soulsspeedruns.organizer.theme; 2 | 3 | import com.github.weisj.darklaf.theme.IntelliJTheme; 4 | 5 | public class DefaultTheme extends IntelliJTheme { 6 | 7 | @Override 8 | public String getName() { 9 | return "Default"; 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/com/soulsspeedruns/organizer/games/config/GameListListener.java: -------------------------------------------------------------------------------- 1 | package com.soulsspeedruns.organizer.games.config; 2 | 3 | 4 | public interface GameListListener 5 | { 6 | 7 | public void entryCreated(GameListEntry entry); 8 | 9 | public void entryUpdated(GameListEntry entry); 10 | 11 | public void entryDeleted(GameListEntry entry); 12 | 13 | public void entrySelected(GameListEntry prevEntry, GameListEntry newEntry); 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/com/soulsspeedruns/organizer/listeners/SettingsListener.java: -------------------------------------------------------------------------------- 1 | package com.soulsspeedruns.organizer.listeners; 2 | 3 | /** 4 | * Settings Listener. 5 | *
6 | * Provides methods to listen for changes to the settings. 7 | * 8 | * @author Kahmul (www.twitch.tv/kahmul78) 9 | * @date 18 Dec 2023 10 | */ 11 | public interface SettingsListener 12 | { 13 | 14 | /** 15 | * Called when the user navigates to next element 16 | */ 17 | public void settingChanged(String prefsKey); 18 | } 19 | -------------------------------------------------------------------------------- /src/com/soulsspeedruns/organizer/listeners/SearchListener.java: -------------------------------------------------------------------------------- 1 | package com.soulsspeedruns.organizer.listeners; 2 | 3 | 4 | /** 5 | * Search Listener. 6 | *
7 | * Provides methods to listen for search requests. 8 | * 9 | * @author Kahmul (www.twitch.tv/kahmul78) 10 | * @date 19 May 2016 11 | */ 12 | public interface SearchListener 13 | { 14 | 15 | /** 16 | * Called when a search request has been sent. 17 | * 18 | * @param input the search input 19 | */ 20 | public void searchRequested(String input); 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/com/soulsspeedruns/organizer/listeners/NavigationListener.java: -------------------------------------------------------------------------------- 1 | package com.soulsspeedruns.organizer.listeners; 2 | 3 | /** 4 | * Navigation Listener. 5 | *
6 | * Provides methods to listen for upwards/downwards navigation. 7 | * 8 | * @author johndisandonato (www.twitch.tv/johndisandonato) 9 | * @date 05 Sep 2018 10 | */ 11 | public interface NavigationListener 12 | { 13 | 14 | /** 15 | * Called when the user navigates to previous element 16 | */ 17 | public void navigatedToPrevious(); 18 | 19 | /** 20 | * Called when the user navigates to next element 21 | */ 22 | public void navigatedToNext(); 23 | } 24 | -------------------------------------------------------------------------------- /src/com/soulsspeedruns/organizer/listeners/SortingListener.java: -------------------------------------------------------------------------------- 1 | package com.soulsspeedruns.organizer.listeners; 2 | 3 | 4 | import com.soulsspeedruns.organizer.main.config.SortingCategory; 5 | 6 | 7 | /** 8 | * Sorting Listener. 9 | *
10 | * Provides methods to listen for sorting category changes.
11 | *
12 | * @author Kahmul (www.twitch.tv/kahmul78)
13 | * @date 20 May 2016
14 | */
15 | public interface SortingListener
16 | {
17 |
18 | /**
19 | * Called when the user chooses a different sorting option.
20 | *
21 | * @param category the category that was chosen
22 | */
23 | public void sortingChanged(SortingCategory category);
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/src/com/soulsspeedruns/organizer/theme/GlobalThemeInitTask.java:
--------------------------------------------------------------------------------
1 | package com.soulsspeedruns.organizer.theme;
2 |
3 | import javax.swing.UIDefaults;
4 | import javax.swing.plaf.InsetsUIResource;
5 |
6 | import com.github.weisj.darklaf.task.DefaultsInitTask;
7 | import com.github.weisj.darklaf.theme.Theme;
8 |
9 | public class GlobalThemeInitTask implements DefaultsInitTask
10 | {
11 |
12 | @Override
13 | public void run(Theme currentTheme, UIDefaults defaults)
14 | {
15 | defaults.put("Button.rollover", true);
16 | defaults.put("ComboBox.squareButton", true);
17 | defaults.put("ComboBox.valueInsets", new InsetsUIResource(4, 4, 4, 4));
18 | defaults.put("MenuItem.insets", new InsetsUIResource(3, 2, 3, 4));
19 | }
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/src/com/soulsspeedruns/organizer/savelist/SaveListRenderer.java:
--------------------------------------------------------------------------------
1 | package com.soulsspeedruns.organizer.savelist;
2 |
3 | import java.awt.Component;
4 |
5 | import javax.swing.DefaultListCellRenderer;
6 | import javax.swing.JLabel;
7 | import javax.swing.JList;
8 | import javax.swing.ListCellRenderer;
9 |
10 | public class SaveListRenderer implements ListCellRenderer
14 | * Panel containing components to configure game settings and their respective
15 | * profiles.
16 | *
17 | * @author Kahmul (www.twitch.tv/kahmul78)
18 | * @date 28 Sep 2015
19 | */
20 | public class GameConfigPanel extends JPanel
21 | {
22 |
23 |
24 | /**
25 | * Creates a game configuration panel.
26 | *
27 | * @param game the game to create the panel for
28 | */
29 | protected GameConfigPanel(Game game)
30 | {
31 | setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS));
32 |
33 | add(new GameConfigDirectoryPanel(game));
34 | add(new JSeparator());
35 | add(new GameConfigProfilesPanel(game));
36 | }
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/src/com/soulsspeedruns/organizer/listeners/GameListener.java:
--------------------------------------------------------------------------------
1 | package com.soulsspeedruns.organizer.listeners;
2 |
3 | import com.soulsspeedruns.organizer.games.Game;
4 |
5 | /**
6 | * Game Listener.
7 | *
8 | * Provides methods to listen for game changes.
9 | *
10 | * @author Kahmul (www.twitch.tv/kahmul78)
11 | * @date 12 Jan 2024
12 | */
13 | public interface GameListener
14 | {
15 |
16 | /**
17 | * Called when a new game has been created.
18 | *
19 | * @param game the game that was created
20 | */
21 | public void gameCreated(Game game);
22 |
23 | /**
24 | * Called when a game has been deleted.
25 | *
26 | * @param game the game that was deleted.
27 | */
28 | public void gameDeleted(Game game);
29 |
30 |
31 | /**
32 | * Called when a game has been edited or changed.
33 | *
34 | * @param game the game that was edited or changed.
35 | */
36 | public void gameEdited(Game game);
37 |
38 |
39 | public void gameMoved(Game game, int newIndex);
40 |
41 | }
42 |
--------------------------------------------------------------------------------
/src/com/soulsspeedruns/organizer/games/config/ProfileListRenderer.java:
--------------------------------------------------------------------------------
1 | package com.soulsspeedruns.organizer.games.config;
2 |
3 | import java.awt.Component;
4 |
5 | import javax.swing.BorderFactory;
6 | import javax.swing.DefaultListCellRenderer;
7 | import javax.swing.JLabel;
8 | import javax.swing.JList;
9 | import javax.swing.ListCellRenderer;
10 |
11 | import com.soulsspeedruns.organizer.games.Profile;
12 |
13 | public class ProfileListRenderer implements ListCellRenderer
14 | * ComboBox displaying the sorting options.
15 | *
16 | * @author Kahmul (www.twitch.tv/kahmul78)
17 | * @date 18 May 2016
18 | */
19 | public class SortingComboBox extends JComboBox
15 | * Long description.
16 | *
17 | * @author Kahmul (www.twitch.tv/kahmul78)
18 | * @date 14 Dec 2023
19 | */
20 | public class FailedLoadMessage extends AbstractMessage
21 | {
22 |
23 | private static final String MESSAGE = "LOAD FAILED";
24 | private static final Color COLOR = Color.decode("0xee6a5c");
25 | private static final int ICON_SIZE = 22;
26 | private static final Icon ICON = IconFontSwing.buildIcon(Iconic.CHECK, ICON_SIZE, COLOR);
27 |
28 |
29 | protected FailedLoadMessage()
30 | {
31 | super();
32 | }
33 |
34 |
35 | @Override
36 | protected String getMessage()
37 | {
38 | return MESSAGE;
39 | }
40 |
41 |
42 | @Override
43 | protected Icon getIcon()
44 | {
45 | return ICON;
46 | }
47 |
48 |
49 | @Override
50 | protected int getIconSize() {
51 | return ICON_SIZE;
52 | }
53 |
54 |
55 | @Override
56 | protected Color getColor()
57 | {
58 | return COLOR;
59 | }
60 |
61 | }
62 |
--------------------------------------------------------------------------------
/src/com/soulsspeedruns/organizer/messages/SuccessfulImportMessage.java:
--------------------------------------------------------------------------------
1 | package com.soulsspeedruns.organizer.messages;
2 |
3 |
4 | import java.awt.Color;
5 |
6 | import javax.swing.Icon;
7 |
8 | import com.soulsspeedruns.organizer.managers.IconsAndFontsManager;
9 |
10 |
11 | /**
12 | * SuccessfulImportMessage.
13 | *
14 | * AbstractMessage class implementation for an Import message.
15 | *
16 | * @author Kahmul (www.twitch.tv/kahmul78)
17 | * @date 15 Jul 2017
18 | */
19 | public class SuccessfulImportMessage extends AbstractMessage
20 | {
21 |
22 | private static final String MESSAGE = "IMPORT SUCCESSFUL";
23 | private static final Icon ICON = IconsAndFontsManager.getImportIcon(IconsAndFontsManager.ICON_SIZE_LARGE);
24 | private static final Color COLOR = Color.decode("0x1d6fbe");
25 |
26 |
27 | protected SuccessfulImportMessage()
28 | {
29 | super();
30 | }
31 |
32 |
33 | @Override
34 | protected String getMessage()
35 | {
36 | return MESSAGE;
37 | }
38 |
39 |
40 | @Override
41 | protected Icon getIcon()
42 | {
43 | return ICON;
44 | }
45 |
46 |
47 | @Override
48 | protected int getIconSize() {
49 | return 22;
50 | }
51 |
52 |
53 | @Override
54 | protected Color getColor()
55 | {
56 | return COLOR;
57 | }
58 |
59 | }
60 |
--------------------------------------------------------------------------------
/src/com/soulsspeedruns/organizer/messages/SuccessfulRefreshMessage.java:
--------------------------------------------------------------------------------
1 | package com.soulsspeedruns.organizer.messages;
2 |
3 |
4 | import java.awt.Color;
5 |
6 | import javax.swing.Icon;
7 |
8 | import jiconfont.icons.Elusive;
9 | import jiconfont.swing.IconFontSwing;
10 |
11 |
12 | /**
13 | * Short description.
14 | *
15 | * Long description.
16 | *
17 | * @author Kahmul (www.twitch.tv/kahmul78)
18 | * @date 13 Apr 2018
19 | */
20 | public class SuccessfulRefreshMessage extends AbstractMessage
21 | {
22 |
23 | private static final String MESSAGE = "REFRESH SUCCESSFUL";
24 | private static final Color COLOR = Color.decode("0x1d6fbe");
25 | private static final int ICON_SIZE = 20;
26 | private static final Icon ICON = IconFontSwing.buildIcon(Elusive.REPEAT, ICON_SIZE, COLOR);
27 |
28 |
29 | protected SuccessfulRefreshMessage()
30 | {
31 | super();
32 | }
33 |
34 |
35 | @Override
36 | protected String getMessage()
37 | {
38 | return MESSAGE;
39 | }
40 |
41 |
42 | @Override
43 | protected Icon getIcon()
44 | {
45 | return ICON;
46 | }
47 |
48 |
49 | @Override
50 | protected int getIconSize() {
51 | return ICON_SIZE;
52 | }
53 |
54 |
55 | @Override
56 | protected Color getColor()
57 | {
58 | return COLOR;
59 | }
60 |
61 | }
62 |
--------------------------------------------------------------------------------
/src/com/soulsspeedruns/organizer/messages/SuccessfulDeleteMessage.java:
--------------------------------------------------------------------------------
1 | package com.soulsspeedruns.organizer.messages;
2 |
3 |
4 | import java.awt.Color;
5 |
6 | import javax.swing.Icon;
7 |
8 | import jiconfont.icons.FontAwesome;
9 | import jiconfont.swing.IconFontSwing;
10 |
11 |
12 | /**
13 | * Short description.
14 | *
15 | * Long description.
16 | *
17 | * @author Kahmul (www.twitch.tv/kahmul78)
18 | * @date 2 Mar 2018
19 | */
20 | public class SuccessfulDeleteMessage extends AbstractMessage
21 | {
22 |
23 | private static final String MESSAGE = "DELETE SUCCESSFUL";
24 | private static final Color COLOR = Color.decode("0xea3622");
25 | private static final int ICON_SIZE = 22;
26 | private static final Icon ICON = IconFontSwing.buildIcon(FontAwesome.CHECK, ICON_SIZE, COLOR);
27 |
28 |
29 | protected SuccessfulDeleteMessage()
30 | {
31 | super();
32 | }
33 |
34 |
35 | @Override
36 | protected String getMessage()
37 | {
38 | return MESSAGE;
39 | }
40 |
41 |
42 | @Override
43 | protected Icon getIcon()
44 | {
45 | return ICON;
46 | }
47 |
48 |
49 | @Override
50 | protected int getIconSize() {
51 | return ICON_SIZE;
52 | }
53 |
54 |
55 | @Override
56 | protected Color getColor()
57 | {
58 | return COLOR;
59 | }
60 |
61 |
62 | }
63 |
--------------------------------------------------------------------------------
/src/com/soulsspeedruns/organizer/messages/SuccessfulLoadMessage.java:
--------------------------------------------------------------------------------
1 | package com.soulsspeedruns.organizer.messages;
2 |
3 |
4 | import java.awt.Color;
5 |
6 | import javax.swing.Icon;
7 |
8 | import jiconfont.icons.Elusive;
9 | import jiconfont.swing.IconFontSwing;
10 |
11 |
12 | /**
13 | * SuccessfulLoadMessage.
14 | *
15 | * AbstractMessage class implementation for a Load message.
16 | *
17 | * @author Kahmul (www.twitch.tv/kahmul78)
18 | * @date 15 Jul 2017
19 | */
20 | public class SuccessfulLoadMessage extends AbstractMessage
21 | {
22 |
23 | private static final String MESSAGE = "LOAD SUCCESSFUL";
24 | private static final Color COLOR = Color.decode("0x2c9558");
25 | private static final int ICON_SIZE = 20;
26 | private static final Icon ICON = IconFontSwing.buildIcon(Elusive.REPEAT, ICON_SIZE, COLOR);
27 |
28 |
29 | protected SuccessfulLoadMessage()
30 | {
31 | super();
32 | }
33 |
34 |
35 | @Override
36 | protected String getMessage()
37 | {
38 | return MESSAGE;
39 | }
40 |
41 |
42 | @Override
43 | protected Icon getIcon()
44 | {
45 | return ICON;
46 | }
47 |
48 |
49 | @Override
50 | protected int getIconSize() {
51 | return ICON_SIZE;
52 | }
53 |
54 |
55 | @Override
56 | protected Color getColor()
57 | {
58 | return COLOR;
59 | }
60 |
61 | }
62 |
--------------------------------------------------------------------------------
/src/com/soulsspeedruns/organizer/messages/SuccessfulReplaceMessage.java:
--------------------------------------------------------------------------------
1 | package com.soulsspeedruns.organizer.messages;
2 |
3 |
4 | import java.awt.Color;
5 |
6 | import javax.swing.Icon;
7 |
8 | import jiconfont.icons.Elusive;
9 | import jiconfont.swing.IconFontSwing;
10 |
11 |
12 | /**
13 | * SuccessfulReplaceMessage.
14 | *
15 | * AbstractMessage class implementation for a Replace message.
16 | *
17 | * @author Kahmul (www.twitch.tv/kahmul78)
18 | * @date 15 Jul 2017
19 | */
20 | public class SuccessfulReplaceMessage extends AbstractMessage
21 | {
22 |
23 | private static final String MESSAGE = "REPLACE SUCCESSFUL";
24 | private static final Color COLOR = Color.decode("0xeb751c");
25 | private static final int ICON_SIZE = 20;
26 | private static final Icon ICON = IconFontSwing.buildIcon(Elusive.REFRESH, ICON_SIZE, COLOR);
27 |
28 |
29 | protected SuccessfulReplaceMessage()
30 | {
31 | super();
32 | }
33 |
34 |
35 | @Override
36 | protected String getMessage()
37 | {
38 | return MESSAGE;
39 | }
40 |
41 |
42 | @Override
43 | protected Icon getIcon()
44 | {
45 | return ICON;
46 | }
47 |
48 |
49 | @Override
50 | protected int getIconSize() {
51 | return ICON_SIZE;
52 | }
53 |
54 |
55 | @Override
56 | protected Color getColor()
57 | {
58 | return COLOR;
59 | }
60 |
61 | }
62 |
--------------------------------------------------------------------------------
/src/com/soulsspeedruns/organizer/listeners/ProfileListener.java:
--------------------------------------------------------------------------------
1 | package com.soulsspeedruns.organizer.listeners;
2 |
3 |
4 | import com.soulsspeedruns.organizer.games.Game;
5 | import com.soulsspeedruns.organizer.games.Profile;
6 |
7 |
8 | /**
9 | * Profile Listener.
10 | *
11 | * Provides methods to listen for profile changes.
12 | *
13 | * @author Kahmul (www.twitch.tv/kahmul78)
14 | * @date 29 Sep 2015
15 | */
16 | public interface ProfileListener
17 | {
18 |
19 | /**
20 | * Called when a profile has been deleted.
21 | *
22 | * @param profile the deleted profile
23 | */
24 | public void profileDeleted(Profile profile);
25 |
26 |
27 | /**
28 | * Called when a profile has been created.
29 | *
30 | * @param profile the created profile
31 | */
32 | public void profileCreated(Profile profile);
33 |
34 |
35 | /**
36 | * Called when the user changes the profile directory in the config window.
37 | */
38 | public void profileDirectoryChanged(Game game);
39 |
40 |
41 | /**
42 | * Called when the user switches to another profile.
43 | *
44 | * @param profile the profile that was switched to
45 | */
46 | public void changedToProfile(Profile profile);
47 |
48 |
49 | /**
50 | * Called when the user switches to another game.
51 | *
52 | * @param game the game that was switched to
53 | */
54 | public void changedToGame(Game game);
55 |
56 | }
57 |
--------------------------------------------------------------------------------
/src/com/soulsspeedruns/organizer/main/ListPanel.java:
--------------------------------------------------------------------------------
1 | package com.soulsspeedruns.organizer.main;
2 |
3 |
4 | import javax.swing.GroupLayout;
5 | import javax.swing.GroupLayout.Alignment;
6 | import javax.swing.JPanel;
7 | import javax.swing.ScrollPaneConstants;
8 |
9 | import com.github.weisj.darklaf.components.OverlayScrollPane;
10 | import com.soulsspeedruns.organizer.savelist.SaveList;
11 |
12 |
13 | /**
14 | * List Panel.
15 | *
16 | * Contains the list with savestates.
17 | *
18 | * @author Kahmul (www.twitch.tv/kahmul78)
19 | * @date 27 Sep 2015
20 | */
21 | public class ListPanel extends JPanel
22 | {
23 |
24 | /**
25 | * Creates a new ListPanel for the main window.
26 | */
27 | protected ListPanel()
28 | {
29 | GroupLayout layout = new GroupLayout(this);
30 |
31 | SaveList saveList = new SaveList();
32 |
33 | OverlayScrollPane savePane = new OverlayScrollPane(saveList, ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
34 | ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
35 |
36 | // Horizontal
37 | GroupLayout.SequentialGroup hGroup = layout.createSequentialGroup();
38 |
39 | hGroup.addGap(12);
40 | hGroup.addGroup(layout.createParallelGroup().addComponent(savePane));
41 | hGroup.addGap(12);
42 |
43 | layout.setHorizontalGroup(hGroup);
44 |
45 | // Vertical
46 | GroupLayout.SequentialGroup vGroup = layout.createSequentialGroup();
47 |
48 | vGroup.addGroup(layout.createParallelGroup(Alignment.BASELINE).addComponent(savePane));
49 |
50 | layout.setVerticalGroup(vGroup);
51 |
52 | setLayout(layout);
53 | }
54 |
55 | }
56 |
--------------------------------------------------------------------------------
/src/com/soulsspeedruns/organizer/theme/soulsspeedruns_icons.properties:
--------------------------------------------------------------------------------
1 | %menuIconOpacity = 80
2 | %navigationIconOpacity = 50
3 | %fileIconOpacity = 80
4 |
5 | %menuIconEnabled = #abb2bf
6 | %menuIconHovered = #F9FCFF
7 | %menuIconSelected = #F9FCFF
8 | %menuIconSelectedSecondary = #F9FCFF
9 | %menuIconDisabled = #2c313a
10 | %menuIconHighlight = #337C97
11 |
12 | %fileIconBackground = #828D96
13 | %fileIconForeground = #404043
14 | %fileIconHighlight = #499C54
15 |
16 | %textIconEnabled = %textForeground
17 | %textIconDisabled = %textForegroundInactive
18 | %textIconSelected = %textSelectionForeground
19 |
20 | %windowButton = #A9A9A9
21 | %windowButtonDisabled = #8C8C8C
22 | %windowCloseHovered = #FFFFFF
23 |
24 | %errorIconColor = #802d43
25 | %informationIconColor = #4269b9
26 | %warningIconColor = #8c812b
27 | %questionIconColor = #4269b9
28 |
29 | %palette.yellow = #E5C07B
30 | %palette.orange = #FF8C00
31 | %palette.red = #EF596F
32 | %palette.pink = #C678DD
33 | %palette.purple = #A03BA8
34 | %palette.indigo = #5E5CE6
35 | %palette.blue = #0A84FF
36 | %palette.teal = #61AFEF
37 | %palette.cyan = #2BBAC5
38 | %palette.green = #89CA78
39 | %palette.lime = #69B320
40 | %palette.forest = #13591E
41 | %palette.brown = #AC8E68
42 | %palette.gray = #ABB2BF
43 |
--------------------------------------------------------------------------------
/src/com/soulsspeedruns/organizer/games/config/GameListEntryDragListener.java:
--------------------------------------------------------------------------------
1 | package com.soulsspeedruns.organizer.games.config;
2 |
3 |
4 | import java.awt.dnd.DnDConstants;
5 | import java.awt.dnd.DragGestureEvent;
6 | import java.awt.dnd.DragGestureListener;
7 | import java.awt.dnd.DragSource;
8 | import java.awt.dnd.DragSourceDragEvent;
9 | import java.awt.dnd.DragSourceDropEvent;
10 | import java.awt.dnd.DragSourceEvent;
11 | import java.awt.dnd.DragSourceListener;
12 |
13 |
14 | public class GameListEntryDragListener implements DragSourceListener, DragGestureListener
15 | {
16 |
17 | private GameListEntry entry;
18 | private DragSource dragSource;
19 | private GameList list;
20 |
21 |
22 | public GameListEntryDragListener(GameListEntry entry, GameList list)
23 | {
24 | this.entry = entry;
25 | this.list = list;
26 |
27 | dragSource = new DragSource();
28 | dragSource.createDefaultDragGestureRecognizer(entry, DnDConstants.ACTION_MOVE, this);
29 | }
30 |
31 |
32 | @Override
33 | public void dragGestureRecognized(DragGestureEvent dge)
34 | {
35 | dragSource.startDrag(dge, null, entry, this);
36 | }
37 |
38 |
39 | @Override
40 | public void dragEnter(DragSourceDragEvent dsde)
41 | {
42 | }
43 |
44 |
45 | @Override
46 | public void dragOver(DragSourceDragEvent dsde)
47 | {
48 | }
49 |
50 |
51 | @Override
52 | public void dropActionChanged(DragSourceDragEvent dsde)
53 | {
54 | }
55 |
56 |
57 | @Override
58 | public void dragExit(DragSourceEvent dse)
59 | {
60 | list.clearDropTarget();
61 | }
62 |
63 |
64 | @Override
65 | public void dragDropEnd(DragSourceDropEvent dsde)
66 | {
67 | list.clearDropTarget();
68 | }
69 |
70 | }
71 |
--------------------------------------------------------------------------------
/src/com/soulsspeedruns/organizer/listeners/SaveListener.java:
--------------------------------------------------------------------------------
1 | package com.soulsspeedruns.organizer.listeners;
2 |
3 |
4 | import com.soulsspeedruns.organizer.savelist.Save;
5 | import com.soulsspeedruns.organizer.savelist.SaveListEntry;
6 |
7 |
8 | /**
9 | * Save Listener.
10 | *
11 | * Provides methods to listen for save changes.
12 | *
13 | * @author Kahmul (www.twitch.tv/kahmul78)
14 | * @date 1 Oct 2015
15 | */
16 | public interface SaveListener
17 | {
18 |
19 | /**
20 | * Called when the selection in the SaveList changed.
21 | *
22 | * @param save the save that was selected
23 | */
24 | public void entrySelected(SaveListEntry entry);
25 |
26 |
27 | /**
28 | * Called when a save is imported or a folder is created.
29 | *
30 | * @param entry the entry that was created
31 | */
32 | public void entryCreated(SaveListEntry entry);
33 |
34 |
35 | /**
36 | * Called when an entry is renamed.
37 | *
38 | * @param entry the entry that was renamed
39 | */
40 | public void entryRenamed(SaveListEntry entry);
41 |
42 |
43 | /**
44 | * Called when the load process of a save has been started.
45 | *
46 | * @param save the save that is loaded
47 | */
48 | public void saveLoadStarted(Save save);
49 |
50 |
51 | /**
52 | * Called when the load process of save was finished.
53 | *
54 | * @param save the save that was loaded
55 | */
56 | public void saveLoadFinished(Save save);
57 |
58 |
59 | /**
60 | * Called when the user switched the current gamefile from writable to read-only and vice versa.
61 | *
62 | * @param writable the writable state switched to
63 | */
64 | public void gameFileWritableStateChanged(boolean writeable);
65 |
66 | }
67 |
--------------------------------------------------------------------------------
/src/com/soulsspeedruns/organizer/savelist/SaveListDragListener.java:
--------------------------------------------------------------------------------
1 | package com.soulsspeedruns.organizer.savelist;
2 |
3 |
4 | import java.awt.dnd.DnDConstants;
5 | import java.awt.dnd.DragGestureEvent;
6 | import java.awt.dnd.DragGestureListener;
7 | import java.awt.dnd.DragSource;
8 | import java.awt.dnd.DragSourceDragEvent;
9 | import java.awt.dnd.DragSourceDropEvent;
10 | import java.awt.dnd.DragSourceEvent;
11 | import java.awt.dnd.DragSourceListener;
12 |
13 |
14 | /**
15 | * SaveListDragListener
16 | *
17 | * DragListener for SaveList.
18 | *
19 | * @author Kahmul (www.twitch.tv/kahmul78)
20 | * @date 27 May 2016
21 | */
22 | public class SaveListDragListener implements DragSourceListener, DragGestureListener
23 | {
24 |
25 | private SaveList saveList;
26 | private DragSource dragSource;
27 |
28 |
29 | /**
30 | * Creates a new drag listener for a SaveList.
31 | */
32 | public SaveListDragListener(SaveList saveList)
33 | {
34 | this.saveList = saveList;
35 | dragSource = new DragSource();
36 | dragSource.createDefaultDragGestureRecognizer(saveList, DnDConstants.ACTION_MOVE, this);
37 | }
38 |
39 |
40 | @Override
41 | public void dragGestureRecognized(DragGestureEvent e)
42 | {
43 | if (saveList.getSelectedValue() != null)
44 | dragSource.startDrag(e, null, saveList.getSelectedValue(), this);
45 | }
46 |
47 |
48 | @Override
49 | public void dragDropEnd(DragSourceDropEvent e)
50 | {
51 | }
52 |
53 |
54 | @Override
55 | public void dragEnter(DragSourceDragEvent e)
56 | {
57 | }
58 |
59 |
60 | @Override
61 | public void dragExit(DragSourceEvent e)
62 | {
63 | }
64 |
65 |
66 | @Override
67 | public void dragOver(DragSourceDragEvent e)
68 | {
69 | }
70 |
71 |
72 | @Override
73 | public void dropActionChanged(DragSourceDragEvent e)
74 | {
75 | }
76 |
77 | }
78 |
--------------------------------------------------------------------------------
/src/com/soulsspeedruns/organizer/savelist/RootFolder.java:
--------------------------------------------------------------------------------
1 | package com.soulsspeedruns.organizer.savelist;
2 |
3 |
4 | import java.io.File;
5 | import java.io.IOException;
6 | import java.nio.file.Files;
7 |
8 | import javax.swing.JOptionPane;
9 |
10 | import com.soulsspeedruns.organizer.managers.GamesManager;
11 | import com.soulsspeedruns.organizer.managers.OrganizerManager;
12 |
13 |
14 | /**
15 | * RootFolder class.
16 | *
17 | * Class representing the root folders of every profile. These folders don't have a parent.
18 | *
19 | * @author Kahmul (www.twitch.tv/kahmul78)
20 | * @date 8 Jul 2017
21 | */
22 | public class RootFolder extends Folder
23 | {
24 |
25 | /**
26 | *
27 | * @param parent
28 | * @param file
29 | */
30 | public RootFolder(File file)
31 | {
32 | super(null, file);
33 | }
34 |
35 |
36 | @Override
37 | public boolean rename(String newName)
38 | {
39 | File newFile = new File(GamesManager.getSelectedGame().getDirectory() + File.separator + newName);
40 | try
41 | {
42 | // if the same name is given, then only the file variable is supposed to be updated for a new parent
43 | if (!getFile().getName().equals((newName)))
44 | Files.move(getFile().toPath(), newFile.toPath());
45 | }
46 | catch (IOException e)
47 | {
48 | JOptionPane.showMessageDialog(null,
49 | "Renaming the entries was not successful. They are possibly being accessed by another program.", "Warning",
50 | JOptionPane.WARNING_MESSAGE);
51 | return false;
52 | }
53 | setFile(newFile);
54 | for (SaveListEntry entry : getChildren())
55 | {
56 | // call rename on all children with the same name to update the path with the new parent
57 | if (!entry.rename(entry.getName()))
58 | return false;
59 | }
60 | return true;
61 | }
62 |
63 | @Override
64 | public Folder getParent() {
65 | return this;
66 | }
67 |
68 |
69 | @Override
70 | public void delete()
71 | {
72 | OrganizerManager.deleteDirectory(getFile());
73 | }
74 |
75 | }
76 |
--------------------------------------------------------------------------------
/src/com/soulsspeedruns/organizer/components/HyperLink.java:
--------------------------------------------------------------------------------
1 | package com.soulsspeedruns.organizer.components;
2 |
3 | import java.awt.Color;
4 | import java.awt.Cursor;
5 | import java.awt.Desktop;
6 | import java.awt.Insets;
7 | import java.awt.event.ActionEvent;
8 | import java.awt.event.ActionListener;
9 | import java.net.URI;
10 |
11 | import javax.swing.JButton;
12 | import javax.swing.UIManager;
13 | import javax.swing.event.ChangeEvent;
14 | import javax.swing.event.ChangeListener;
15 |
16 | import com.github.weisj.darklaf.LafManager;
17 | import com.github.weisj.darklaf.theme.event.ThemeChangeEvent;
18 | import com.github.weisj.darklaf.theme.event.ThemeChangeListener;
19 |
20 | public class HyperLink extends JButton
21 | {
22 |
23 | public HyperLink(String text, String url)
24 | {
25 | super(text);
26 |
27 | setContentAreaFilled(false);
28 | setBorderPainted(false);
29 | setFocusPainted(false);
30 | setMargin(new Insets(0, 0, 0, 0));
31 |
32 | setToolTipText(url);
33 | setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
34 |
35 | setForeground(new Color(UIManager.getColor("hyperlink").getRGB()));
36 |
37 | getModel().addChangeListener(new ChangeListener() {
38 |
39 | @Override
40 | public void stateChanged(ChangeEvent e)
41 | {
42 | setText(getModel().isRollover() ? "" + text + "" : text);
43 | }
44 | });
45 |
46 | addActionListener(new ActionListener() {
47 |
48 | @Override
49 | public void actionPerformed(ActionEvent e)
50 | {
51 | try
52 | {
53 | Desktop.getDesktop().browse(new URI(url));
54 | }
55 | catch (Exception ex)
56 | {
57 | }
58 | }
59 | });
60 |
61 | LafManager.addThemeChangeListener(new ThemeChangeListener() {
62 |
63 | @Override
64 | public void themeInstalled(ThemeChangeEvent e)
65 | {
66 | setForeground(new Color(UIManager.getColor("hyperlink").getRGB()));
67 | }
68 |
69 | @Override
70 | public void themeChanged(ThemeChangeEvent e)
71 | {
72 | }
73 | });
74 | }
75 |
76 | }
77 |
--------------------------------------------------------------------------------
/src/com/soulsspeedruns/organizer/theme/SoulsSpeedrunsTheme.java:
--------------------------------------------------------------------------------
1 | package com.soulsspeedruns.organizer.theme;
2 |
3 | import java.util.Properties;
4 |
5 | import javax.swing.UIDefaults;
6 |
7 | import com.github.weisj.darklaf.properties.icons.IconResolver;
8 | import com.github.weisj.darklaf.theme.Theme;
9 | import com.github.weisj.darklaf.theme.info.PresetIconRule;
10 | import com.github.weisj.darklaf.theme.spec.ColorToneRule;
11 |
12 |
13 | public class SoulsSpeedrunsTheme extends Theme {
14 |
15 | @Override
16 | protected PresetIconRule getPresetIconRule() {
17 | return PresetIconRule.NONE;
18 | }
19 |
20 | @Override
21 | public String getPrefix() {
22 | return "soulsspeedruns";
23 | }
24 |
25 | @Override
26 | public String getName() {
27 | return "SoulsSpeedruns";
28 | }
29 |
30 | @Override
31 | protected String getResourcePath() {
32 | return "";
33 | }
34 |
35 | @Override
36 | protected Class extends Theme> getLoaderClass() {
37 | return SoulsSpeedrunsTheme.class;
38 | }
39 |
40 | @Override
41 | public ColorToneRule getColorToneRule() {
42 | return ColorToneRule.DARK;
43 | }
44 |
45 | @Override
46 | public void customizeUIProperties(final Properties properties, final UIDefaults currentDefaults,
47 | final IconResolver iconResolver) {
48 | super.customizeUIProperties(properties, currentDefaults, iconResolver);
49 | loadCustomProperties("ui", properties, currentDefaults, iconResolver);
50 | }
51 |
52 | @Override
53 | public boolean supportsCustomAccentColor() {
54 | return true;
55 | }
56 |
57 | @Override
58 | public boolean supportsCustomSelectionColor() {
59 | return true;
60 | }
61 |
62 | @Override
63 | public void customizeIconTheme(final Properties properties, final UIDefaults currentDefaults,
64 | final IconResolver iconResolver) {
65 | // loadCustomProperties("icons_adjustments", properties, currentDefaults, iconResolver);
66 | }
67 |
68 | }
69 |
--------------------------------------------------------------------------------
/src/com/soulsspeedruns/organizer/components/SearchBar.java:
--------------------------------------------------------------------------------
1 | package com.soulsspeedruns.organizer.components;
2 |
3 |
4 | import java.util.Timer;
5 | import java.util.TimerTask;
6 |
7 | import javax.swing.JTextField;
8 | import javax.swing.event.DocumentEvent;
9 | import javax.swing.event.DocumentListener;
10 |
11 | import com.github.weisj.darklaf.ui.text.DarkTextFieldUI;
12 | import com.github.weisj.darklaf.ui.text.DarkTextUI;
13 | import com.soulsspeedruns.organizer.managers.SavesManager;
14 |
15 |
16 | /**
17 | * Searchbar.
18 | *
19 | * Allows the user to send search requests to the SaveList.
20 | *
21 | * @author Kahmul (www.twitch.tv/kahmul78)
22 | * @date 18 May 2016
23 | */
24 | public class SearchBar extends JTextField
25 | {
26 |
27 | public static final String DEFAULT_TEXT = "Search...";
28 | private static final int SEARCH_DELAY = 500;
29 |
30 | private Timer searchDelayTimer;
31 | private TimerTask searchTask;
32 |
33 |
34 | /**
35 | * Creates a new searchbar.
36 | */
37 | public SearchBar()
38 | {
39 | super(50);
40 |
41 | searchDelayTimer = new Timer(true);
42 |
43 | putClientProperty(DarkTextFieldUI.KEY_VARIANT, DarkTextFieldUI.VARIANT_SEARCH);
44 | putClientProperty(DarkTextFieldUI.KEY_SHOW_CLEAR, true);
45 | putClientProperty(DarkTextUI.KEY_DEFAULT_TEXT, DEFAULT_TEXT);
46 |
47 | getDocument().addDocumentListener(new DocumentListener() {
48 |
49 | @Override
50 | public void removeUpdate(DocumentEvent e)
51 | {
52 | rescheduleSearch();
53 | }
54 |
55 | @Override
56 | public void insertUpdate(DocumentEvent e)
57 | {
58 | rescheduleSearch();
59 | }
60 |
61 | @Override
62 | public void changedUpdate(DocumentEvent e)
63 | {
64 | rescheduleSearch();
65 | }
66 | });
67 | }
68 |
69 | private void rescheduleSearch()
70 | {
71 | if (searchTask != null)
72 | searchTask.cancel();
73 | searchTask = new TimerTask() {
74 |
75 | @Override
76 | public void run()
77 | {
78 | SavesManager.fireSearchRequestedEvent(getText().trim());
79 | }
80 | };
81 | searchDelayTimer.schedule(searchTask, SEARCH_DELAY);
82 | }
83 |
84 | }
85 |
--------------------------------------------------------------------------------
/src/com/soulsspeedruns/organizer/update/NewReleasePanel.java:
--------------------------------------------------------------------------------
1 | package com.soulsspeedruns.organizer.update;
2 |
3 |
4 | import java.awt.Font;
5 |
6 | import javax.swing.GroupLayout;
7 | import javax.swing.GroupLayout.Alignment;
8 | import javax.swing.JLabel;
9 | import javax.swing.JPanel;
10 |
11 | import com.soulsspeedruns.organizer.managers.VersionManager;
12 |
13 |
14 | /**
15 | * NewReleasePanel.
16 | *
17 | * Panel containing the info about a new release.
18 | *
19 | * @author Kahmul (www.twitch.tv/kahmul78)
20 | * @date 19 Jul 2017
21 | */
22 | public class NewReleasePanel extends JPanel
23 | {
24 |
25 | /**
26 | * Creates a NewReleasePanel.
27 | */
28 | protected NewReleasePanel()
29 | {
30 | GroupLayout layout = new GroupLayout(this);
31 | layout.setAutoCreateGaps(true);
32 | layout.setAutoCreateContainerGaps(true);
33 |
34 | JLabel newReleaseLabel = new JLabel("New Release Version:");
35 | JLabel versionLabel = new JLabel(VersionManager.getLatestReleaseVersion());
36 | JLabel changelogLabel = new JLabel("Changelog:");
37 |
38 | String description = VersionManager.getLatestReleaseDescription().replaceAll("\\R", "
21 | * Allows the configuration of existing and creation of new games and their profiles.
22 | *
23 | * @author Kahmul (www.twitch.tv/kahmul78)
24 | * @date 27 Sep 2015
25 | */
26 | public class GamesConfigurationWindow extends JDialog
27 | {
28 |
29 | private GamesConfigPane gamesPane;
30 |
31 |
32 | /**
33 | * Creates a new configuration window.
34 | */
35 | public GamesConfigurationWindow()
36 | {
37 | super(OrganizerManager.getMainWindow(), "Games Configuration", Dialog.ModalityType.APPLICATION_MODAL);
38 |
39 | initLayout();
40 | initProperties();
41 |
42 | setVisible(true);
43 | }
44 |
45 |
46 | /**
47 | * Sets the properties of the window.
48 | */
49 | private void initProperties()
50 | {
51 | pack();
52 | setResizable(false);
53 | setLocationRelativeTo(OrganizerManager.getMainWindow());
54 | setIconImage(IconsAndFontsManager.getSoulsSpeedrunsImage(IconsAndFontsManager.ICON_SIZE_MEDIUM));
55 | setAlwaysOnTop(SettingsManager.isAlwaysOnTop());
56 |
57 | addWindowListener(new WindowAdapter()
58 | {
59 |
60 | @Override
61 | public void windowOpened(WindowEvent e)
62 | {
63 | requestFocusInWindow();
64 | SwingUtilities.invokeLater(() -> {
65 | SwingUtilities.updateComponentTreeUI(GamesConfigurationWindow.this);
66 | });
67 | gamesPane.loadConfigPane();
68 | }
69 | });
70 | }
71 |
72 |
73 | /**
74 | * Adds all components to the layout.
75 | */
76 | private void initLayout()
77 | {
78 | gamesPane = new GamesConfigPane();
79 |
80 | gamesPane.addMouseListener(new MouseAdapter()
81 | {
82 |
83 | @Override
84 | public void mouseClicked(MouseEvent e)
85 | {
86 | requestFocusInWindow();
87 | }
88 | });
89 |
90 | add(gamesPane);
91 | }
92 |
93 | }
94 |
--------------------------------------------------------------------------------
/src/com/soulsspeedruns/organizer/games/config/GameListTransferHandler.java:
--------------------------------------------------------------------------------
1 | package com.soulsspeedruns.organizer.games.config;
2 |
3 |
4 | import javax.swing.TransferHandler;
5 |
6 |
7 | /**
8 | * Game List Transfer Handler
9 | *
10 | * A TransferHandler for GameList.
11 | *
12 | * @author Kahmul (www.twitch.tv/kahmul78)
13 | * @date 15 Jan 2024
14 | */
15 | public class GameListTransferHandler extends TransferHandler
16 | {
17 |
18 | private GameList list;
19 |
20 |
21 | public GameListTransferHandler(GameList list)
22 | {
23 | this.list = list;
24 | }
25 |
26 |
27 | @Override
28 | public boolean canImport(TransferHandler.TransferSupport support)
29 | {
30 | if (!support.isDataFlavorSupported(GameListEntry.ENTRY_FLAVOR))
31 | return false;
32 | try
33 | {
34 | int draggedIndex = (int) support.getTransferable().getTransferData(GameListEntry.ENTRY_FLAVOR);
35 |
36 | DropLocation dl = support.getDropLocation();
37 | int dropIndex = list.getDropIndexByPoint(dl.getDropPoint());
38 |
39 | if (isValidDropIndex(draggedIndex, dropIndex))
40 | {
41 | list.setDropTargetIndex(dropIndex);
42 | return true;
43 | }
44 |
45 | list.clearDropTarget();
46 | return false;
47 | }
48 | catch (Exception e)
49 | {
50 | e.printStackTrace();
51 | return false;
52 | }
53 | }
54 |
55 |
56 | /**
57 | * Returns whether the given drop index is valid given the index of the dragged entry.
58 | *
59 | * @param draggedIndex the index of the dragged entry
60 | * @param dropIndex the index at which to drop the dragged entry
61 | * @return whether the drop index is valid
62 | */
63 | private boolean isValidDropIndex(int draggedIndex, int dropIndex)
64 | {
65 | if (dropIndex > draggedIndex)
66 | return dropIndex - draggedIndex > 1;
67 |
68 | return draggedIndex - dropIndex > 0;
69 | }
70 |
71 |
72 | @Override
73 | public boolean importData(TransferHandler.TransferSupport support)
74 | {
75 | if (!canImport(support))
76 | return false;
77 | try
78 | {
79 | int draggedIndex = (int) support.getTransferable().getTransferData(GameListEntry.ENTRY_FLAVOR);
80 |
81 | DropLocation dl = support.getDropLocation();
82 | int dropIndex = list.getDropIndexByPoint(dl.getDropPoint());
83 |
84 | list.moveIndexToNewIndex(draggedIndex, dropIndex);
85 | }
86 | catch (Exception e)
87 | {
88 | e.printStackTrace();
89 | return false;
90 | }
91 | return true;
92 | }
93 |
94 | }
95 |
--------------------------------------------------------------------------------
/src/com/soulsspeedruns/organizer/main/config/SortingCategory.java:
--------------------------------------------------------------------------------
1 | package com.soulsspeedruns.organizer.main.config;
2 |
3 |
4 | import java.io.IOException;
5 | import java.nio.file.Files;
6 | import java.nio.file.Path;
7 | import java.nio.file.Paths;
8 | import java.nio.file.attribute.BasicFileAttributes;
9 | import java.nio.file.attribute.FileTime;
10 |
11 | import com.soulsspeedruns.organizer.savelist.SaveListEntry;
12 |
13 |
14 | /**
15 | * Sorting Category Enum.
16 | *
17 | * Enum representing the different sorting categories for the save list.
18 | *
19 | * @author Kahmul (www.twitch.tv/kahmul78)
20 | * @date 19 May 2016
21 | */
22 | public enum SortingCategory
23 | {
24 |
25 | ALPHABET("Alphabet")
26 | {
27 |
28 | @Override
29 | public int compare(SaveListEntry s1, SaveListEntry s2)
30 | {
31 | return s1.getFile().getName().compareToIgnoreCase(s2.getName());
32 | }
33 | },
34 | DATE("Created")
35 | {
36 |
37 | @Override
38 | public int compare(SaveListEntry s1, SaveListEntry s2)
39 | {
40 | try
41 | {
42 | Path path = Paths.get(s1.getFile().getPath());
43 | BasicFileAttributes attributes = Files.readAttributes(path, BasicFileAttributes.class);
44 | FileTime s1CreationTime = attributes.creationTime();
45 |
46 | path = Paths.get(s2.getFile().getPath());
47 | attributes = Files.readAttributes(path, BasicFileAttributes.class);
48 | FileTime s2CreationTime = attributes.creationTime();
49 | return s2CreationTime.compareTo(s1CreationTime);
50 | }
51 | catch (IOException e)
52 | {
53 | e.printStackTrace();
54 | }
55 | return 0;
56 | }
57 | },
58 | READ_ONLY("Read Only")
59 | {
60 |
61 | @Override
62 | public int compare(SaveListEntry s1, SaveListEntry s2)
63 | {
64 | boolean s1ReadOnly = !s1.getFile().canWrite();
65 | boolean s2ReadOnly = !s2.getFile().canWrite();
66 | return s1ReadOnly ? (s2ReadOnly ? 0 : -1) : (s2ReadOnly ? 1 : 0);
67 | }
68 | };
69 |
70 | private String caption;
71 |
72 |
73 | /**
74 | * Creates a new SortingCategory constant.
75 | *
76 | * @param caption the caption of the category
77 | */
78 | private SortingCategory(String caption)
79 | {
80 | this.caption = caption;
81 | }
82 |
83 |
84 | /**
85 | * Returns the string representation of the category.
86 | *
87 | * @return the caption
88 | */
89 | public String getCaption()
90 | {
91 | return caption;
92 | }
93 |
94 |
95 | public abstract int compare(SaveListEntry s1, SaveListEntry s2);
96 |
97 | }
98 |
--------------------------------------------------------------------------------
/src/com/soulsspeedruns/organizer/messages/UndecoratedMessageDialog.java:
--------------------------------------------------------------------------------
1 | package com.soulsspeedruns.organizer.messages;
2 |
3 |
4 | import java.awt.BorderLayout;
5 | import java.awt.Color;
6 | import java.awt.Window;
7 | import java.awt.event.MouseEvent;
8 | import java.awt.event.MouseListener;
9 | import java.awt.event.WindowAdapter;
10 | import java.awt.event.WindowEvent;
11 | import java.awt.event.WindowListener;
12 |
13 | import javax.swing.JDialog;
14 |
15 | import com.soulsspeedruns.organizer.managers.OrganizerManager;
16 |
17 |
18 | /**
19 | * UndecoratedMessageDialog.
20 | *
21 | * The undecorated dialog containing a given message panel, e.g. a successful load message.
22 | *
23 | * @author Kahmul (www.twitch.tv/kahmul78)
24 | * @date 15 Jul 2017
25 | */
26 | public class UndecoratedMessageDialog extends JDialog implements MouseListener
27 | {
28 |
29 | private AbstractMessage message;
30 |
31 |
32 | /**
33 | * @param message the message to be shown
34 | */
35 | protected UndecoratedMessageDialog(AbstractMessage message)
36 | {
37 | super(OrganizerManager.getMainWindow());
38 |
39 | this.message = message;
40 | Window parentWindow = OrganizerManager.getMainWindow();
41 | setSize(Math.min(400, parentWindow.getWidth() / 2), Math.min(100, parentWindow.getHeight() / 10));
42 | setLocationRelativeTo(parentWindow);
43 | setUndecorated(true);
44 | setAutoRequestFocus(false);
45 | setBackground(new Color(0, 0, 0, 0));
46 | setLayout(new BorderLayout());
47 | add(message, BorderLayout.CENTER);
48 |
49 | WindowListener wl = new WindowAdapter() {
50 |
51 | public void windowDeactivated(WindowEvent e)
52 | {
53 | message.fadeOut();
54 | }
55 | };
56 |
57 | addWindowListener(wl);
58 | addMouseListener(this);
59 | }
60 |
61 |
62 | /**
63 | * Starts the fade-in of the message.
64 | */
65 | protected void fadeIn()
66 | {
67 | setVisible(true);
68 | message.fadeIn();
69 | }
70 |
71 |
72 | @Override
73 | public void mouseClicked(MouseEvent e)
74 | {
75 | }
76 |
77 |
78 | @Override
79 | public void mouseEntered(MouseEvent e)
80 | {
81 |
82 | }
83 |
84 |
85 | @Override
86 | public void mouseExited(MouseEvent e)
87 | {
88 | }
89 |
90 |
91 | @Override
92 | public void mousePressed(MouseEvent e)
93 | {
94 | message.fadeOut();
95 | }
96 |
97 |
98 | @Override
99 | public void mouseReleased(MouseEvent e)
100 | {
101 |
102 | }
103 |
104 | }
105 |
--------------------------------------------------------------------------------
/src/com/soulsspeedruns/organizer/theme/soulsspeedruns_ui.properties:
--------------------------------------------------------------------------------
1 |
2 |
3 | border = #FFFFFF
4 |
5 | Button.foreground = %soulsspeedrunsLinkColor
6 | Button.background = %soulsSpeedrunsComponentBackground
7 | Button.defaultBorderColor = %soulsspeedrunsListSelectionForeground
8 | Button.defaultFillColor = %soulsspeedrunsLinkColor
9 | Button.defaultFillColorRollOver = #fca94f
10 | Button.defaultFillColorClick = #fc9c36
11 | Button.disabledText = #495162
12 |
13 | ComboBox.foreground = %soulsspeedrunsLinkColor
14 | #ComboBox.selectionForeground = #212121
15 | ComboBox.selectionForeground = %soulsspeedrunsListSelectionForeground
16 | ComboBox.selectionBackground = %soulsspeedrunsListSelectionBackground
17 |
18 | Hyperlink.linkColor = %soulsspeedrunsLinkColor
19 | link.foreground = %soulsspeedrunsLinkColor
20 |
21 | TextField.background = %soulsSpeedrunsComponentBackground
22 | TextField.inactiveBackground = %soulsSpeedrunsComponentBackground
23 |
24 | CheckBox.activeFillColor = %soulsSpeedrunsComponentBackground
25 | CheckBox.foreground = %soulsspeedrunsLinkColor
26 | CheckBox.selectedFillColor = %soulsSpeedrunsComponentBackground
27 | CheckBox.selectionSelectedColor = %soulsspeedrunsLinkColor
28 | CheckBox.selectionFocusSelectedColor = %soulsspeedrunsLinkColor
29 | CheckBox.activeBorderColor = %soulsspeedrunsLinkColor
30 | CheckBox.selectedBorderColor = %soulsspeedrunsLinkColor
31 |
32 | List.selectionForeground = %soulsspeedrunsListSelectionForeground
33 | List.selectionBackground = %soulsspeedrunsListSelectionBackground
34 | List.foregroundSelected = %soulsspeedrunsListSelectionForeground
35 | List.backgroundSelected = %soulsspeedrunsListSelectionBackground
36 | List.backgroundSelectedNoFocus = %soulsspeedrunsListSelectionBackground
37 | List.foregroundSelectedNoFocus = %soulsspeedrunsListSelectionForeground
38 |
39 | TitledBorder.borderColor = #FFFFFF
40 |
41 | TabbedPane.selectedForeground = %soulsspeedrunsLinkColor
42 | TabbedPane.selectedBackground = %soulsspeedrunsListSelectionBackground
43 | TabbedPane.selectedHoverBackground = %soulsspeedrunsListSelectionBackground
44 | TabbedPane.hoverBackground = %soulsspeedrunsListSelectionBackground
45 |
46 | MenuItem.selectionBackground = %soulsspeedrunsListSelectionBackground
47 | MenuItem.selectionForeground = %soulsspeedrunsListSelectionForeground
--------------------------------------------------------------------------------
/src/com/soulsspeedruns/organizer/main/config/GamesComboBox.java:
--------------------------------------------------------------------------------
1 | package com.soulsspeedruns.organizer.main.config;
2 |
3 |
4 | import java.awt.event.ItemEvent;
5 | import java.util.List;
6 |
7 | import javax.swing.JComboBox;
8 |
9 | import com.soulsspeedruns.organizer.games.Game;
10 | import com.soulsspeedruns.organizer.listeners.GameListener;
11 | import com.soulsspeedruns.organizer.managers.GamesManager;
12 |
13 |
14 | /**
15 | * GamesComboBox.
16 | *
17 | * ComboBox displaying Game objects.
18 | *
19 | * @author Kahmul (www.twitch.tv/kahmul78)
20 | * @date 29 Sep 2015
21 | */
22 | public class GamesComboBox extends JComboBox
21 | * Contains the textfields that allow the user to change the global hotkeys.
22 | *
23 | * @author Kahmul (www.twitch.tv/kahmul78)
24 | * @date 3 Jun 2016
25 | */
26 | public class HotkeysSettingsPanel extends JPanel
27 | {
28 |
29 | private HotkeyTextField[] fields;
30 |
31 |
32 | /**
33 | * Creates a new hotkeys settings panel.
34 | */
35 | protected HotkeysSettingsPanel()
36 | {
37 | GroupLayout layout = new GroupLayout(this);
38 | layout.setAutoCreateGaps(true);
39 | layout.setAutoCreateContainerGaps(true);
40 |
41 | GroupLayout.SequentialGroup hGroup = layout.createSequentialGroup();
42 | GroupLayout.SequentialGroup vGroup = layout.createSequentialGroup();
43 |
44 | Component glue = Box.createHorizontalStrut(50);
45 | vGroup.addGroup(layout.createParallelGroup(Alignment.BASELINE).addComponent(glue));
46 |
47 | GroupLayout.ParallelGroup hLabelGroup = layout.createParallelGroup();
48 | GroupLayout.ParallelGroup hFieldGroup = layout.createParallelGroup();
49 |
50 | GlobalHotkey[] hotkeys = GlobalHotkey.values();
51 | fields = new HotkeyTextField[hotkeys.length];
52 |
53 | for (int i = 0; i < hotkeys.length; i++)
54 | {
55 | JLabel label = new JLabel(hotkeys[i].getCaption());
56 | fields[i] = new HotkeyTextField(hotkeys[i]);
57 |
58 | hLabelGroup.addComponent(label);
59 | hFieldGroup.addComponent(fields[i]);
60 | vGroup.addGroup(layout.createParallelGroup(Alignment.BASELINE).addComponent(label).addComponent(fields[i]));
61 | }
62 |
63 | hGroup.addGroup(hLabelGroup);
64 | hGroup.addGroup(layout.createParallelGroup().addComponent(glue));
65 | hGroup.addGroup(hFieldGroup);
66 |
67 | layout.setHorizontalGroup(hGroup);
68 | layout.setVerticalGroup(vGroup);
69 |
70 | setLayout(layout);
71 | TitledBorder border = BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(EtchedBorder.LOWERED), "Global Hotkeys");
72 | setBorder(border);
73 | }
74 |
75 |
76 | /**
77 | * Applies the changes to the hotkey shortcut.
78 | */
79 | protected void applyChanges()
80 | {
81 | for (HotkeyTextField hotkeyTextField : fields)
82 | {
83 | hotkeyTextField.saveChangesToHotkey();
84 | }
85 | }
86 |
87 | }
--------------------------------------------------------------------------------
/src/com/soulsspeedruns/organizer/games/Profile.java:
--------------------------------------------------------------------------------
1 | package com.soulsspeedruns.organizer.games;
2 |
3 |
4 | import java.io.File;
5 | import java.util.Collections;
6 |
7 | import com.soulsspeedruns.organizer.managers.GamesManager;
8 | import com.soulsspeedruns.organizer.savelist.Folder;
9 | import com.soulsspeedruns.organizer.savelist.RootFolder;
10 |
11 |
12 | /**
13 | * Profile class.
14 | *
15 | * Class representing the profiles for each game.
16 | *
17 | * @author Kahmul (www.twitch.tv/kahmul78)
18 | * @date 27 Sep 2015
19 | */
20 | public class Profile implements Comparable
25 | * Window giving info about a new release, if there is any, as well as a link to the download.
26 | *
27 | * @author Kahmul (www.twitch.tv/kahmul78)
28 | * @date 19 Jul 2017
29 | */
30 | public class NewReleaseWindow extends JDialog
31 | {
32 |
33 | /**
34 | * Creates a new AboutWindow.
35 | */
36 | public NewReleaseWindow()
37 | {
38 | super(null, "New Release Available!", Dialog.ModalityType.APPLICATION_MODAL);
39 |
40 | initLayout();
41 | initProperties();
42 |
43 | setVisible(true);
44 | requestFocus();
45 | }
46 |
47 |
48 | /**
49 | *
50 | */
51 | private void initProperties()
52 | {
53 | setResizable(false);
54 | setIconImage(IconsAndFontsManager.getSoulsSpeedrunsImage(IconsAndFontsManager.ICON_SIZE_MEDIUM));
55 | setLocationRelativeTo(OrganizerManager.getMainWindow());
56 | setAlwaysOnTop(SettingsManager.isAlwaysOnTop());
57 | }
58 |
59 |
60 | /**
61 | *
62 | */
63 | private void initLayout()
64 | {
65 | JPanel guiPanel = new JPanel();
66 | guiPanel.setLayout(new GridBagLayout());
67 | guiPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
68 |
69 | JPanel contentPanel = new JPanel();
70 | contentPanel.setLayout(new FlowLayout(FlowLayout.LEADING, 50, 0));
71 | contentPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 20, 5));
72 |
73 | contentPanel.add(new NewReleasePanel());
74 | contentPanel.add(new JLabel(new ImageIcon(IconsAndFontsManager.getSoulsSpeedrunsImage(IconsAndFontsManager.ICON_SIZE_LARGE))));
75 |
76 | GridBagConstraints constraints = new GridBagConstraints();
77 | guiPanel.add(contentPanel, constraints);
78 | constraints.gridy = 1;
79 | guiPanel.add(createNewDownloadButton(), constraints);
80 |
81 | add(guiPanel);
82 | pack();
83 | }
84 |
85 |
86 | private JButton createNewDownloadButton()
87 | {
88 | JButton downloadButton = new JButton("Go to Releases");
89 | downloadButton.addActionListener(e -> {
90 | OrganizerManager.openLatestReleasePage();
91 | setVisible(false);
92 | });
93 | downloadButton.setToolTipText(VersionManager.getLatestReleaseURL());
94 | return downloadButton;
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/src/com/soulsspeedruns/organizer/savelist/Save.java:
--------------------------------------------------------------------------------
1 | package com.soulsspeedruns.organizer.savelist;
2 |
3 |
4 | import java.awt.Color;
5 | import java.io.File;
6 | import java.io.IOException;
7 | import java.nio.file.Files;
8 |
9 | import javax.swing.BorderFactory;
10 | import javax.swing.Icon;
11 | import javax.swing.JLabel;
12 | import javax.swing.JList;
13 | import javax.swing.JOptionPane;
14 |
15 | import com.soulsspeedruns.organizer.managers.IconsAndFontsManager;
16 | import com.soulsspeedruns.organizer.managers.SavesManager;
17 |
18 | import jiconfont.icons.Iconic;
19 | import jiconfont.swing.IconFontSwing;
20 |
21 |
22 | /**
23 | * Savestate class.
24 | *
25 | * Class representing the savestates and folders.
26 | *
27 | * @author Kahmul (www.twitch.tv/kahmul78)
28 | * @date 26 Sep 2015
29 | */
30 | public class Save extends SaveListEntry
31 | {
32 |
33 | private static final Icon ICON_FILE_DOES_NOT_EXIST = IconFontSwing.buildIcon(Iconic.CHECK, 13, Color.RED);
34 |
35 | /**
36 | * Creates a new Save instance.
37 | *
38 | * @param parent the parent folder
39 | * @param file the associated file
40 | */
41 | public Save(Folder parent, File file)
42 | {
43 | super(parent, file);
44 | }
45 |
46 |
47 | @Override
48 | public int compareTo(SaveListEntry entry)
49 | {
50 | if (entry instanceof Folder)
51 | return 1;
52 | return SavesManager.getSelectedSortingCategory().compare(this, entry);
53 | }
54 |
55 |
56 | @Override
57 | public boolean rename(String newName)
58 | {
59 | File newFile = new File(getParent().getFile() + File.separator + newName);
60 | try
61 | {
62 | // if the same name is given, then only the file variable is supposed to be updated for a new parent
63 | if (!getFile().getName().equals((newName)))
64 | Files.move(getFile().toPath(), newFile.toPath());
65 | }
66 | catch (IOException e)
67 | {
68 | JOptionPane.showMessageDialog(null,
69 | "Renaming the entries was not successful. They are possibly being accessed by another program.", "Warning",
70 | JOptionPane.WARNING_MESSAGE);
71 | return false;
72 | }
73 | setFile(newFile);
74 | return true;
75 | }
76 |
77 |
78 | @Override
79 | public boolean canBeRenamed()
80 | {
81 | return getFile().canWrite();
82 | }
83 |
84 |
85 | @Override
86 | public void render(JList extends SaveListEntry> list, int index, JLabel label)
87 | {
88 | label.setText(getFile().getName());
89 | label.setBorder(BorderFactory.createEmptyBorder(1, 3 + getIndent(), 0, 1));
90 | if(isMarkedForCut())
91 | label.setForeground(Color.GRAY);
92 | if (!getFile().canWrite())
93 | label.setIcon(IconsAndFontsManager.getReadOnlyIcon(IconsAndFontsManager.ICON_SIZE_SMALL, false));
94 | if (!getFile().exists())
95 | {
96 | label.setIcon(ICON_FILE_DOES_NOT_EXIST);
97 | label.setForeground(Color.RED);
98 | label.setToolTipText("File no longer exists!");
99 | }
100 | }
101 |
102 |
103 | @Override
104 | public void delete()
105 | {
106 | getParent().removeChild(this);
107 | getFile().delete();
108 | }
109 |
110 | }
111 |
--------------------------------------------------------------------------------
/src/com/soulsspeedruns/organizer/settings/ButtonsSettingsPanel.java:
--------------------------------------------------------------------------------
1 | package com.soulsspeedruns.organizer.settings;
2 |
3 |
4 | import java.awt.Component;
5 |
6 | import javax.swing.Box;
7 | import javax.swing.GroupLayout;
8 | import javax.swing.GroupLayout.Alignment;
9 | import javax.swing.ImageIcon;
10 | import javax.swing.JButton;
11 | import javax.swing.JPanel;
12 |
13 | import com.soulsspeedruns.organizer.about.AboutWindow;
14 | import com.soulsspeedruns.organizer.managers.IconsAndFontsManager;
15 |
16 |
17 | /**
18 | * ButtonSettingsPanel.
19 | *
20 | * Contains the About button.
21 | *
22 | * @author Kahmul (www.twitch.tv/kahmul78)
23 | * @date 4 Jun 2016
24 | */
25 | public class ButtonsSettingsPanel extends JPanel
26 | {
27 |
28 | /**
29 | * Creates a new buttons settings panel.
30 | *
31 | * @param window the settings window
32 | */
33 | protected ButtonsSettingsPanel(SettingsWindow window)
34 | {
35 | GroupLayout layout = new GroupLayout(this);
36 | layout.setAutoCreateGaps(true);
37 | layout.setAutoCreateContainerGaps(true);
38 |
39 |
40 | JButton aboutButton = createAboutButton();
41 | // JButton saveButton = createSaveButton(window);
42 | // JButton cancelButton = createCancelButton(window);
43 |
44 | Component glue = Box.createHorizontalStrut(238);
45 |
46 | // Horizontal
47 | GroupLayout.SequentialGroup hGroup = layout.createSequentialGroup();
48 |
49 | hGroup.addComponent(glue).addComponent(aboutButton);//.addComponent(saveButton).addComponent(cancelButton);
50 |
51 | layout.setHorizontalGroup(hGroup);
52 |
53 | // Vertical
54 | GroupLayout.SequentialGroup vGroup = layout.createSequentialGroup();
55 |
56 | vGroup.addGroup(
57 | layout.createParallelGroup(Alignment.TRAILING).addComponent(glue).addComponent(aboutButton));//.addComponent(saveButton).addComponent(cancelButton));
58 |
59 | layout.setVerticalGroup(vGroup);
60 |
61 | setLayout(layout);
62 | }
63 |
64 | private JButton createAboutButton()
65 | {
66 | JButton aboutButton = new JButton("About", new ImageIcon(IconsAndFontsManager.getSoulsSpeedrunsImage(IconsAndFontsManager.ICON_SIZE_SMALL)));
67 | aboutButton.addActionListener(event -> new AboutWindow());
68 | return aboutButton;
69 | }
70 |
71 |
72 | /**
73 | * Creates the save button.
74 | *
75 | * @param window the settings window
76 | * @return the save button
77 | */
78 | // private JButton createSaveButton(SettingsWindow window)
79 | // {
80 | // JButton saveButton = new JButton("Save");
81 | // saveButton.addActionListener(event -> {
82 | // window.saveSettings();
83 | // window.dispatchEvent(new WindowEvent(window, WindowEvent.WINDOW_CLOSING));
84 | // });
85 | // return saveButton;
86 | // }
87 |
88 |
89 | /**
90 | * Creates the cancel button.
91 | *
92 | * @param window the settings window
93 | * @return the cancel button
94 | */
95 | // private JButton createCancelButton(SettingsWindow window)
96 | // {
97 | // JButton cancelButton = new JButton("Cancel");
98 | // cancelButton.addActionListener(event -> window.dispatchEvent(new WindowEvent(window, WindowEvent.WINDOW_CLOSING)));
99 | // return cancelButton;
100 | // }
101 |
102 | }
103 |
--------------------------------------------------------------------------------
/src/com/soulsspeedruns/organizer/settings/SettingsWindow.java:
--------------------------------------------------------------------------------
1 | package com.soulsspeedruns.organizer.settings;
2 |
3 |
4 | import java.awt.Dialog;
5 | import java.awt.event.MouseAdapter;
6 | import java.awt.event.MouseEvent;
7 | import java.awt.event.WindowAdapter;
8 | import java.awt.event.WindowEvent;
9 |
10 | import javax.swing.BorderFactory;
11 | import javax.swing.BoxLayout;
12 | import javax.swing.JDialog;
13 | import javax.swing.JPanel;
14 | import javax.swing.SwingUtilities;
15 |
16 | import com.soulsspeedruns.organizer.managers.IconsAndFontsManager;
17 | import com.soulsspeedruns.organizer.managers.OrganizerManager;
18 | import com.soulsspeedruns.organizer.managers.SettingsManager;
19 |
20 |
21 | /**
22 | * SettingsWindow.
23 | *
24 | * Allows the user to configure various settings.
25 | *
26 | * @author Kahmul (www.twitch.tv/kahmul78)
27 | * @date 3 Jun 2016
28 | */
29 | public class SettingsWindow extends JDialog
30 | {
31 |
32 | private GeneralSettingsPanel generalSettingsPanel;
33 | private HotkeysSettingsPanel hotkeysSettingsPanel;
34 |
35 |
36 | /**
37 | * Creates a new SettingsWindow.
38 | */
39 | public SettingsWindow()
40 | {
41 | super(OrganizerManager.getMainWindow(), "Settings", Dialog.ModalityType.APPLICATION_MODAL);
42 |
43 | initLayout();
44 | initProperties();
45 |
46 | setVisible(true);
47 | }
48 |
49 |
50 | /**
51 | * Inits the properties.
52 | */
53 | private void initProperties()
54 | {
55 | pack();
56 | setResizable(false);
57 | SettingsManager.getKeyboardHook().unregisterHook();
58 | setIconImage(IconsAndFontsManager.getSoulsSpeedrunsImage(IconsAndFontsManager.ICON_SIZE_MEDIUM));
59 | setLocationRelativeTo(OrganizerManager.getMainWindow());
60 | setAlwaysOnTop(SettingsManager.isAlwaysOnTop());
61 | addMouseListener(new MouseAdapter() {
62 |
63 | @Override
64 | public void mouseClicked(MouseEvent e)
65 | {
66 | requestFocusInWindow();
67 | }
68 | });
69 | addWindowListener(new WindowAdapter() {
70 |
71 | @Override
72 | public void windowOpened(WindowEvent e)
73 | {
74 | requestFocusInWindow();
75 | SwingUtilities.invokeLater(() -> {
76 | SwingUtilities.updateComponentTreeUI(SettingsWindow.this);
77 | });
78 | }
79 |
80 | @Override
81 | public void windowClosing(WindowEvent e)
82 | {
83 | saveSettings();
84 | SettingsManager.getKeyboardHook().registerHook();
85 | }
86 | });
87 | }
88 |
89 |
90 | /**
91 | * Inits the layout.
92 | */
93 | private void initLayout()
94 | {
95 | JPanel guiPanel = new JPanel();
96 | guiPanel.setLayout(new BoxLayout(guiPanel, BoxLayout.PAGE_AXIS));
97 | guiPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
98 |
99 | generalSettingsPanel = new GeneralSettingsPanel();
100 | hotkeysSettingsPanel = new HotkeysSettingsPanel();
101 | guiPanel.add(generalSettingsPanel);
102 | guiPanel.add(hotkeysSettingsPanel);
103 | guiPanel.add(new ButtonsSettingsPanel(this));
104 |
105 | add(guiPanel);
106 | }
107 |
108 |
109 | /**
110 | * Saves the changes to the settings.
111 | */
112 | protected void saveSettings()
113 | {
114 | generalSettingsPanel.applyChanges();
115 | hotkeysSettingsPanel.applyChanges();
116 | }
117 |
118 | }
119 |
--------------------------------------------------------------------------------
/src/com/soulsspeedruns/organizer/about/AboutPanel.java:
--------------------------------------------------------------------------------
1 | package com.soulsspeedruns.organizer.about;
2 |
3 |
4 | import java.awt.Color;
5 |
6 | import javax.swing.GroupLayout;
7 | import javax.swing.GroupLayout.Alignment;
8 | import javax.swing.JLabel;
9 | import javax.swing.JPanel;
10 | import javax.swing.SwingConstants;
11 |
12 | import com.soulsspeedruns.organizer.components.HyperLink;
13 | import com.soulsspeedruns.organizer.managers.IconsAndFontsManager;
14 | import com.soulsspeedruns.organizer.managers.OrganizerManager;
15 | import com.soulsspeedruns.organizer.managers.VersionManager;
16 |
17 | import jiconfont.icons.Elusive;
18 | import jiconfont.swing.IconFontSwing;
19 |
20 |
21 | /**
22 | * AboutPanel.
23 | *
24 | * Panel containing information about the program.
25 | *
26 | * @author Kahmul (www.twitch.tv/kahmul78)
27 | * @date 5 Jun 2016
28 | */
29 | public class AboutPanel extends JPanel
30 | {
31 |
32 | /**
33 | * Creates a new AboutPanel.
34 | */
35 | protected AboutPanel()
36 | {
37 | GroupLayout layout = new GroupLayout(this);
38 | layout.setAutoCreateGaps(true);
39 | layout.setAutoCreateContainerGaps(true);
40 |
41 | JLabel versionLabel = new JLabel("Version:");
42 | versionLabel.setHorizontalAlignment(SwingConstants.RIGHT);
43 | JLabel versionNumberLabel = new JLabel(VersionManager.getVersion());
44 |
45 | JLabel developerLabel = new JLabel("Developed by:");
46 | developerLabel.setHorizontalAlignment(SwingConstants.RIGHT);
47 |
48 | JLabel discordLabel = createDiscordLabel();
49 | HyperLink devLink = createDevLink();
50 | HyperLink githubLink = createGitHubLink();
51 |
52 | // Horizontal
53 | GroupLayout.SequentialGroup hGroup = layout.createSequentialGroup();
54 |
55 | hGroup.addGroup(layout.createParallelGroup(Alignment.TRAILING).addComponent(versionLabel).addComponent(versionNumberLabel)
56 | .addComponent(developerLabel).addComponent(discordLabel).addComponent(devLink).addComponent(githubLink));
57 |
58 | layout.setHorizontalGroup(hGroup);
59 |
60 | // Vertical
61 | GroupLayout.SequentialGroup vGroup = layout.createSequentialGroup();
62 |
63 | vGroup.addComponent(versionLabel).addComponent(versionNumberLabel).addComponent(developerLabel).addComponent(discordLabel)
64 | .addComponent(devLink).addComponent(githubLink);
65 |
66 | layout.setVerticalGroup(vGroup);
67 |
68 | setLayout(layout);
69 | }
70 |
71 |
72 | private JLabel createDiscordLabel()
73 | {
74 | JLabel discordLabel = new JLabel("Kahmul");
75 | discordLabel.setIcon(IconsAndFontsManager.getDiscordIcon());
76 |
77 | return discordLabel;
78 | }
79 |
80 |
81 | private HyperLink createDevLink()
82 | {
83 | HyperLink devLink = new HyperLink("Kahmul78", OrganizerManager.TWITTER_URL);
84 | devLink.setHorizontalAlignment(SwingConstants.RIGHT);
85 | devLink.setIcon(IconFontSwing.buildIcon(Elusive.TWITTER, 20, new Color(64, 153, 255)));
86 |
87 | return devLink;
88 | }
89 |
90 |
91 | private HyperLink createGitHubLink()
92 | {
93 | HyperLink githubLink = new HyperLink("GitHub Repository", OrganizerManager.GITHUB_REPO_URL);
94 | githubLink.setHorizontalAlignment(SwingConstants.RIGHT);
95 | githubLink.setIcon(IconFontSwing.buildIcon(Elusive.GITHUB, 20));
96 |
97 | return githubLink;
98 | }
99 |
100 | }
101 |
--------------------------------------------------------------------------------
/src/com/soulsspeedruns/organizer/main/config/ProfilesComboBox.java:
--------------------------------------------------------------------------------
1 | package com.soulsspeedruns.organizer.main.config;
2 |
3 |
4 | import java.awt.event.ItemEvent;
5 | import java.util.List;
6 |
7 | import javax.swing.JComboBox;
8 |
9 | import com.soulsspeedruns.organizer.games.Game;
10 | import com.soulsspeedruns.organizer.games.Profile;
11 | import com.soulsspeedruns.organizer.listeners.ProfileListener;
12 | import com.soulsspeedruns.organizer.managers.GamesManager;
13 |
14 |
15 | /**
16 | * ProfilesComboBox.
17 | *
18 | * ComboBox displaying Profile objects.
19 | *
20 | * @author Kahmul (www.twitch.tv/kahmul78)
21 | * @date 29 Sep 2015
22 | */
23 | public class ProfilesComboBox extends JComboBox
17 | * Textfield for changing the global hotkey for an action.
18 | *
19 | * @author Kahmul (www.twitch.tv/kahmul78)
20 | * @date 4 Jun 2016
21 | */
22 | public class HotkeyTextField extends JTextField implements FocusListener, KeyListener
23 | {
24 |
25 | private static final String WAITING_FOR_INPUT_TEXT = "Set Hotkey...";
26 |
27 | private String currentKey;
28 | private GlobalHotkey hotkey;
29 |
30 |
31 | /**
32 | * Creates a textfield for a hotkey.
33 | *
34 | * @param hotkey the hotkey to create this textfield for
35 | */
36 | protected HotkeyTextField(GlobalHotkey hotkey)
37 | {
38 | super(hotkey.getKeyCode());
39 | this.hotkey = hotkey;
40 | currentKey = getText();
41 |
42 | addFocusListener(this);
43 | addKeyListener(this);
44 | setHorizontalAlignment(JTextField.CENTER);
45 | setEditable(false);
46 | }
47 |
48 |
49 | /**
50 | * @return the hotkey
51 | */
52 | public GlobalHotkey getHotkey()
53 | {
54 | return hotkey;
55 | }
56 |
57 |
58 | /**
59 | * Saves the changed hotkey shortcut to the associated hotkey.
60 | */
61 | protected void saveChangesToHotkey()
62 | {
63 | hotkey.setKeyCode(currentKey);
64 | }
65 |
66 |
67 | @Override
68 | public void focusGained(FocusEvent e)
69 | {
70 | setText(WAITING_FOR_INPUT_TEXT);
71 | }
72 |
73 |
74 | @Override
75 | public void focusLost(FocusEvent e)
76 | {
77 | setText(currentKey);
78 | }
79 |
80 |
81 | @Override
82 | public void keyTyped(KeyEvent e)
83 | {
84 | }
85 |
86 |
87 | @Override
88 | public void keyPressed(KeyEvent e)
89 | {
90 | int keyCode = e.getKeyCode();
91 | if (keyCode == KeyEvent.VK_ESCAPE)
92 | {
93 | currentKey = "None";
94 | setText("None");
95 | getParent().requestFocusInWindow();
96 | return;
97 |
98 | }
99 | if (keyCode == KeyEvent.VK_SHIFT || keyCode == KeyEvent.VK_CONTROL || keyCode == KeyEvent.VK_ALT)
100 | return;
101 | updateKey(e);
102 | }
103 |
104 |
105 | @Override
106 | public void keyReleased(KeyEvent e)
107 | {
108 | int keyCode = e.getKeyCode();
109 | if (keyCode == KeyEvent.VK_SHIFT || keyCode == KeyEvent.VK_CONTROL || keyCode == KeyEvent.VK_ALT)
110 | {
111 | // if only modifier keys have been pressed so far and one is released, use them for the keycode
112 | if (WAITING_FOR_INPUT_TEXT.equals(getText()))
113 | updateKey(e);
114 | }
115 | getParent().requestFocusInWindow();
116 | }
117 |
118 |
119 | /**
120 | * Updates the hotkey of this hotkey field with the given KeyEvent.
121 | *
122 | * @param e the KeyEvent
123 | */
124 | private void updateKey(KeyEvent e)
125 | {
126 | String modifiers = KeyEvent.getModifiersExText(e.getModifiersEx());
127 | modifiers = modifiers.replaceAll("\\+", " \\+ ");
128 | modifiers = modifiers.length() > 0 ? modifiers + " + " : modifiers;
129 |
130 | String keyText = KeyEvent.getKeyText(e.getKeyCode());
131 | keyText = keyText.replaceAll("NumPad-", "NumPad ");
132 |
133 | String text = modifiers + keyText;
134 | setText(text);
135 | currentKey = text;
136 | }
137 |
138 | }
139 |
--------------------------------------------------------------------------------
/src/com/soulsspeedruns/organizer/about/AboutWindow.java:
--------------------------------------------------------------------------------
1 | package com.soulsspeedruns.organizer.about;
2 |
3 |
4 | import java.awt.Dialog;
5 | import java.awt.FlowLayout;
6 | import java.awt.event.MouseAdapter;
7 | import java.awt.event.MouseEvent;
8 | import java.awt.event.WindowAdapter;
9 | import java.awt.event.WindowEvent;
10 |
11 | import javax.swing.BorderFactory;
12 | import javax.swing.ImageIcon;
13 | import javax.swing.JDialog;
14 | import javax.swing.JLabel;
15 | import javax.swing.JPanel;
16 | import javax.swing.SwingUtilities;
17 |
18 | import com.soulsspeedruns.organizer.managers.IconsAndFontsManager;
19 | import com.soulsspeedruns.organizer.managers.OrganizerManager;
20 | import com.soulsspeedruns.organizer.managers.SettingsManager;
21 |
22 |
23 | /**
24 | * AboutWindow.
25 | *
26 | * Small window showing links to Twitter and GitHub, as well as the current version.
27 | *
28 | * @author Kahmul (www.twitch.tv/kahmul78)
29 | * @date 4 Jun 2016
30 | */
31 | public class AboutWindow extends JDialog
32 | {
33 |
34 |
35 | /**
36 | * Creates a new AboutWindow.
37 | */
38 | public AboutWindow()
39 | {
40 | super(null, "About SoulsSpeedruns - Save Organizer", Dialog.ModalityType.APPLICATION_MODAL);
41 |
42 | initLayout();
43 | initProperties();
44 |
45 | setVisible(true);
46 | }
47 |
48 |
49 | /**
50 | *
51 | */
52 | private void initProperties()
53 | {
54 | pack();
55 | setResizable(false);
56 | setIconImage(IconsAndFontsManager.getSoulsSpeedrunsImage(IconsAndFontsManager.ICON_SIZE_MEDIUM));
57 | setLocationRelativeTo(OrganizerManager.getMainWindow());
58 | setAlwaysOnTop(SettingsManager.isAlwaysOnTop());
59 |
60 | addWindowListener(new WindowAdapter() {
61 |
62 | @Override
63 | public void windowOpened(WindowEvent e)
64 | {
65 | SwingUtilities.invokeLater(() -> {
66 | SwingUtilities.updateComponentTreeUI(AboutWindow.this);
67 | });
68 | }
69 | });
70 | }
71 |
72 |
73 | /**
74 | *
75 | */
76 | private void initLayout()
77 | {
78 | JPanel guiPanel = new JPanel();
79 | guiPanel.setLayout(new FlowLayout(FlowLayout.LEADING, 50, 0));
80 | guiPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
81 |
82 | JLabel iconLabel = createIconLabel();
83 |
84 | guiPanel.add(new AboutPanel());
85 | guiPanel.add(iconLabel);
86 |
87 | add(guiPanel);
88 | }
89 |
90 | private JLabel createIconLabel()
91 | {
92 | JLabel iconLabel = new JLabel(new ImageIcon(IconsAndFontsManager.getSoulsSpeedrunsImage(IconsAndFontsManager.ICON_SIZE_LARGE)));
93 | iconLabel.addMouseListener(new MouseAdapter() {
94 |
95 | boolean once = false;
96 |
97 | @Override
98 | public void mouseClicked(MouseEvent e) {
99 |
100 | if(e.getClickCount() >= 3 && !once)
101 | {
102 | once = true;
103 | iconLabel.setIcon(IconsAndFontsManager.getFrankerZ());
104 | vibrate();
105 | }
106 | }
107 | });
108 |
109 | return iconLabel;
110 | }
111 |
112 |
113 | private void vibrate()
114 | {
115 | try
116 | {
117 | int originalX = getLocationOnScreen().x;
118 | int originalY = getLocationOnScreen().y;
119 | for(int i = 0; i < 5; i++) {
120 | Thread.sleep(10);
121 | setLocation(originalX, originalY + 5);
122 | Thread.sleep(10);
123 | setLocation(originalX, originalY - 5);
124 | Thread.sleep(10);
125 | setLocation(originalX + 5, originalY);
126 | Thread.sleep(10);
127 | setLocation(originalX, originalY);
128 | }
129 | }
130 | catch (Exception err)
131 | {
132 | err.printStackTrace();
133 | }
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/src/com/soulsspeedruns/organizer/main/OrganizerWindow.java:
--------------------------------------------------------------------------------
1 | package com.soulsspeedruns.organizer.main;
2 |
3 |
4 | import java.awt.Dimension;
5 | import java.awt.event.MouseAdapter;
6 | import java.awt.event.MouseEvent;
7 | import java.awt.event.WindowAdapter;
8 | import java.awt.event.WindowEvent;
9 |
10 | import javax.swing.BoxLayout;
11 | import javax.swing.JFrame;
12 | import javax.swing.JPanel;
13 | import javax.swing.SwingUtilities;
14 |
15 | import com.soulsspeedruns.organizer.listeners.SettingsListener;
16 | import com.soulsspeedruns.organizer.managers.IconsAndFontsManager;
17 | import com.soulsspeedruns.organizer.managers.OrganizerManager;
18 | import com.soulsspeedruns.organizer.managers.SettingsManager;
19 |
20 |
21 | /**
22 | * The Save Organizer window.
23 | *
24 | * Creates and shows the Save Organizer window.
25 | *
26 | * @author Kahmul (www.twitch.tv/kahmul78)
27 | * @date 26 Sep 2015
28 | */
29 | public class OrganizerWindow extends JFrame implements SettingsListener
30 | {
31 |
32 | private static final int MIN_WIDTH = 640;
33 | private static final int MIN_HEIGHT = 550;
34 |
35 | private static final int MIN_WIDTH_COMPACT = 490;
36 | private static final int MIN_HEIGHT_COMPACT = 400;
37 |
38 | private static final boolean IS_RESIZABLE = true;
39 |
40 |
41 | public OrganizerWindow()
42 | {
43 | super("SoulsSpeedruns - Save Organizer");
44 |
45 | initProperties();
46 | initLayout();
47 | initListeners();
48 |
49 | setVisible(true);
50 | setExtendedState(SettingsManager.getStoredMaximizedWindowState());
51 | }
52 |
53 |
54 | /**
55 | * Sets the properties of the window.
56 | */
57 | private void initProperties()
58 | {
59 | setIconImage(IconsAndFontsManager.getSoulsSpeedrunsImage(IconsAndFontsManager.ICON_SIZE_MEDIUM));
60 | setResizable(IS_RESIZABLE);
61 | setAlwaysOnTop(SettingsManager.isAlwaysOnTop());
62 | setMinSize(SettingsManager.isCompactModeEnabled());
63 | Dimension size = SettingsManager.getStoredWindowSize();
64 | setSize(size);
65 | setLocationRelativeTo(null);
66 | OrganizerManager.setMainWindow(this);
67 | SettingsManager.addSettingsListener(this);
68 | }
69 |
70 |
71 | /**
72 | * Adds all components to the layout.
73 | */
74 | private void initLayout()
75 | {
76 | JPanel guiPanel = new JPanel();
77 | guiPanel.setLayout(new BoxLayout(guiPanel, BoxLayout.PAGE_AXIS));
78 |
79 | guiPanel.add(new TopPanel());
80 | guiPanel.add(new ListPanel());
81 | guiPanel.add(new ButtonPanel());
82 |
83 | guiPanel.addMouseListener(new MouseAdapter()
84 | {
85 |
86 | @Override
87 | public void mouseClicked(MouseEvent e)
88 | {
89 | requestFocusInWindow();
90 | }
91 | });
92 |
93 | add(guiPanel);
94 | }
95 |
96 |
97 | /**
98 | * Adds all the listeners to the window.
99 | */
100 | private void initListeners()
101 | {
102 | addWindowListener(new WindowAdapter()
103 | {
104 |
105 | @Override
106 | public void windowOpened(WindowEvent e)
107 | {
108 | requestFocusInWindow();
109 | SwingUtilities.invokeLater(() -> {
110 | SwingUtilities.updateComponentTreeUI(OrganizerWindow.this);
111 | });
112 | }
113 |
114 |
115 | @Override
116 | public void windowClosing(WindowEvent e)
117 | {
118 | int state = getExtendedState();
119 | if (state != JFrame.MAXIMIZED_BOTH)
120 | SettingsManager.setStoredWindowSize(new Dimension(getSize()));
121 | SettingsManager.setStoredMaximizedWindowState(state);
122 | SettingsManager.getKeyboardHook().unregisterHook();
123 | e.getWindow().dispose();
124 | System.exit(0);
125 | }
126 | });
127 | }
128 |
129 |
130 | private void setMinSize(boolean isCompact)
131 | {
132 | if (isCompact)
133 | {
134 | setMinimumSize(new Dimension(MIN_WIDTH_COMPACT, MIN_HEIGHT_COMPACT));
135 | return;
136 | }
137 | setMinimumSize(new Dimension(MIN_WIDTH, MIN_HEIGHT));
138 | }
139 |
140 |
141 | @Override
142 | public void settingChanged(String prefsKey)
143 | {
144 | setMinSize(SettingsManager.isCompactModeEnabled());
145 | }
146 |
147 | }
148 |
--------------------------------------------------------------------------------
/src/com/soulsspeedruns/organizer/savelist/SaveListTransferHandler.java:
--------------------------------------------------------------------------------
1 | package com.soulsspeedruns.organizer.savelist;
2 |
3 |
4 | import java.nio.file.Path;
5 | import java.nio.file.Paths;
6 |
7 | import javax.swing.DefaultListModel;
8 | import javax.swing.DropMode;
9 | import javax.swing.JList;
10 | import javax.swing.JOptionPane;
11 | import javax.swing.TransferHandler;
12 |
13 | import com.soulsspeedruns.organizer.managers.GamesManager;
14 |
15 |
16 | /**
17 | * SaveListTransferHandler
18 | *
19 | * TransferHandler for SaveList.
20 | *
21 | * @author Kahmul (www.twitch.tv/kahmul78)
22 | * @date 27 May 2016
23 | */
24 | public class SaveListTransferHandler extends TransferHandler
25 | {
26 |
27 | private SaveList saveList;
28 |
29 |
30 | /**
31 | * Creates a new TransferHandler for a SaveList.
32 | */
33 | public SaveListTransferHandler(SaveList saveList)
34 | {
35 | this.saveList = saveList;
36 | }
37 |
38 |
39 | @Override
40 | public boolean canImport(TransferHandler.TransferSupport support)
41 | {
42 | if (!support.isDataFlavorSupported(SaveListEntry.ENTRY_FLAVOR))
43 | return false;
44 | JList.DropLocation dl = (JList.DropLocation) support.getDropLocation();
45 |
46 | int index = saveList.locationToIndex(dl.getDropPoint());
47 | if (index == -1)
48 | return false;
49 | try
50 | {
51 | SaveListEntry entry = (SaveListEntry) support.getTransferable().getTransferData(SaveListEntry.ENTRY_FLAVOR);
52 | SaveListEntry newParentFolder = findNewParentFolderFromDropLocation(dl);
53 | if (entry.equals(newParentFolder))
54 | return false;
55 | if (entry.isParentOf(newParentFolder))
56 | return false;
57 | if (entry.getParent().equals(newParentFolder))
58 | return false;
59 | support.setShowDropLocation(true);
60 | }
61 | catch (Exception e)
62 | {
63 | return false;
64 | }
65 | return true;
66 | }
67 |
68 |
69 | @Override
70 | public boolean importData(TransferHandler.TransferSupport support)
71 | {
72 | if (!canImport(support))
73 | return false;
74 | try
75 | {
76 | SaveListEntry entry = (SaveListEntry) support.getTransferable().getTransferData(SaveListEntry.ENTRY_FLAVOR);
77 |
78 | JList.DropLocation dl = (JList.DropLocation) support.getDropLocation();
79 | Folder newParentFolder = findNewParentFolderFromDropLocation(dl);
80 |
81 | Path newPath = Paths.get(newParentFolder.getFile().getPath()).resolve(entry.getName());
82 | if (newPath.toFile().exists())
83 | {
84 | if (JOptionPane.showConfirmDialog(saveList.getParent(),
85 | entry.getName() + " already exists in that directory. Do you want to overwrite?", "Confirmation",
86 | JOptionPane.YES_NO_OPTION) != 0)
87 | return false;
88 | SaveListEntry existingEntry = newParentFolder.getChildByName(entry.getName());
89 | ((DefaultListModel
24 | * An entry in a GameList instance.
25 | *
26 | * @author Kahmul (www.twitch.tv/kahmul78)
27 | * @date 14 Jan 2024
28 | */
29 | public class GameListEntry extends JButton implements Transferable
30 | {
31 |
32 | public static final DataFlavor ENTRY_FLAVOR = new DataFlavor(GameListEntry.class, GameListEntry.class.getSimpleName());
33 |
34 | private final Game game;
35 | private final GameConfigPanel configPanel;
36 |
37 | private final Color backgroundColor = new Color(UIManager.getColor("background").getRGB());
38 | private final Color backgroundHoverColor = new Color(UIManager.getColor("backgroundHover").getRGB());
39 |
40 | private final Color dropTargetColor = new Color(UIManager.getColor("dropForeground").getRGB());
41 |
42 | private boolean isSelected = false;
43 |
44 | private boolean isDropTarget = false;
45 | private boolean drawDropTargetBelow = false;
46 |
47 | private GameList list;
48 |
49 |
50 | public GameListEntry(Game game, GameList list)
51 | {
52 | String caption = game.getCaption();
53 | if (caption.length() > 22)
54 | {
55 | caption = caption.substring(0, 21);
56 | caption += "...";
57 | }
58 |
59 | setText(caption);
60 |
61 | this.game = game;
62 | this.list = list;
63 | configPanel = new GameConfigPanel(game);
64 |
65 | setFocusPainted(false);
66 | setBackground(backgroundColor);
67 | setMargin(new Insets(7, 12, 7, 150));
68 | setBorder(new EmptyBorder(0, 0, 0, 0));
69 |
70 | addMouseListener(new MouseAdapter()
71 | {
72 | @Override
73 | public void mousePressed(MouseEvent e)
74 | {
75 | e.consume();
76 | list.setSelectedEntry(GameListEntry.this);
77 | }
78 | });
79 |
80 | new GameListEntryDragListener(this, list);
81 | }
82 |
83 |
84 | public boolean isSelected()
85 | {
86 | return isSelected;
87 | }
88 |
89 |
90 | public void setSelected(boolean flag)
91 | {
92 | isSelected = flag;
93 | setBackground(flag ? backgroundHoverColor : backgroundColor);
94 | }
95 |
96 |
97 | /**
98 | * Sets whether this entry is highlighted as a potential drop location in the list.
99 | *
100 | * @param isDropTarget whether to highlight this entry as a drop location
101 | * @param drawDropTargetBelow whether to draw the drop line below or above the entry
102 | */
103 | public void setIsDropTarget(boolean isDropTarget, boolean drawDropTargetBelow)
104 | {
105 | if(this.isDropTarget == isDropTarget && this.drawDropTargetBelow == drawDropTargetBelow)
106 | return;
107 | this.isDropTarget = isDropTarget;
108 | this.drawDropTargetBelow = drawDropTargetBelow;
109 | repaint();
110 | }
111 |
112 |
113 | @Override
114 | public void paint(Graphics g)
115 | {
116 | super.paint(g);
117 |
118 | if(isDropTarget)
119 | {
120 | g.setColor(dropTargetColor);
121 | if(drawDropTargetBelow)
122 | {
123 | g.fillRect(0, getHeight() - 2, getWidth() - 1, 2);
124 | return;
125 | }
126 | g.fillRect(0, 0, getWidth() - 1, 2);
127 | }
128 | }
129 |
130 |
131 | public Game getGame()
132 | {
133 | return game;
134 | }
135 |
136 |
137 | public GameConfigPanel getConfigPanel()
138 | {
139 | return configPanel;
140 | }
141 |
142 |
143 | @Override
144 | public DataFlavor[] getTransferDataFlavors()
145 | {
146 | return new DataFlavor[] { ENTRY_FLAVOR };
147 | }
148 |
149 |
150 | @Override
151 | public boolean isDataFlavorSupported(DataFlavor flavor)
152 | {
153 | return flavor.equals(ENTRY_FLAVOR);
154 | }
155 |
156 |
157 | @Override
158 | public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException
159 | {
160 | if (isDataFlavorSupported(flavor))
161 | return list.getIndexOfEntry(this);
162 | throw new UnsupportedFlavorException(flavor);
163 | }
164 | }
165 |
--------------------------------------------------------------------------------
/src/com/soulsspeedruns/organizer/hotkeys/GlobalHotkey.java:
--------------------------------------------------------------------------------
1 | package com.soulsspeedruns.organizer.hotkeys;
2 |
3 |
4 | import javax.swing.JOptionPane;
5 |
6 | import com.soulsspeedruns.organizer.managers.GamesManager;
7 | import com.soulsspeedruns.organizer.managers.SavesManager;
8 | import com.soulsspeedruns.organizer.managers.SettingsManager;
9 | import com.soulsspeedruns.organizer.savelist.Save;
10 |
11 |
12 | /**
13 | * Global Hotkey Enum.
14 | *
15 | * Enum representing the different global hotkeys and their actions.
16 | *
17 | * @author Kahmul (www.twitch.tv/kahmul78)
18 | * @date 4 Jun 2016
19 | */
20 | public enum GlobalHotkey
21 | {
22 |
23 | LOAD_SAVE("Load Savestate:", "None")
24 | {
25 |
26 | @Override
27 | public void action()
28 | {
29 | if (SavesManager.getSelectedEntry() instanceof Save)
30 | SavesManager.loadSave((Save) SavesManager.getSelectedEntry());
31 | }
32 |
33 |
34 | @Override
35 | public String getPrefsKey()
36 | {
37 | return SettingsManager.PREFS_KEY_GLOBAL_HOTKEY_LOAD;
38 | }
39 | },
40 | IMPORT_SAVE("Import Savestate:", "None")
41 | {
42 |
43 | @Override
44 | public void action()
45 | {
46 | if (GamesManager.isAProfileSelected())
47 | {
48 | SavesManager.importSavefile(null);
49 | return;
50 | }
51 | JOptionPane.showMessageDialog(null,
52 | "Create a profile before trying to import a savefile! You can do this in the profile configuration settings.",
53 | "Warning", JOptionPane.WARNING_MESSAGE);
54 | }
55 |
56 |
57 | @Override
58 | public String getPrefsKey()
59 | {
60 | return SettingsManager.PREFS_KEY_GLOBAL_HOTKEY_IMPORT_SAVE;
61 | }
62 | },
63 | READ_ONLY_TOGGLE("Switch Gamefile To Read-Only:", "None")
64 | {
65 |
66 | @Override
67 | public void action()
68 | {
69 | SavesManager.switchCurrentGameFileWritableState();
70 | }
71 |
72 |
73 | @Override
74 | public String getPrefsKey()
75 | {
76 | return SettingsManager.PREFS_KEY_GLOBAL_HOTKEY_READ_ONLY;
77 | }
78 | },
79 | PREV_SAVE_TOGGLE("Highlight Previous Savestate:", "None")
80 | {
81 |
82 | @Override
83 | public void action()
84 | {
85 | SavesManager.fireNavigatedToPreviousEvent();
86 | }
87 |
88 |
89 | @Override
90 | public String getPrefsKey()
91 | {
92 | return SettingsManager.PREFS_KEY_GLOBAL_HOTKEY_PREV_SAVE;
93 | }
94 | },
95 | NEXT_SAVE_TOGGLE("Highlight Next Savestate:", "None")
96 | {
97 |
98 | @Override
99 | public void action()
100 | {
101 | SavesManager.fireNavigatedToNextEvent();
102 | }
103 |
104 |
105 | @Override
106 | public String getPrefsKey()
107 | {
108 | return SettingsManager.PREFS_KEY_GLOBAL_HOTKEY_NEXT_SAVE;
109 | }
110 | },
111 | GLOBAL_HOTKEY_TOGGLE("Toggle Global Hotkeys:", "None")
112 | {
113 |
114 | @Override
115 | public void action()
116 | {
117 | SettingsManager.setGlobalHotkeysEnabled(!SettingsManager.areGlobalHotkeysEnabled());
118 | }
119 |
120 |
121 | @Override
122 | public String getPrefsKey()
123 | {
124 | return SettingsManager.PREFS_KEY_GLOBAL_HOTKEY_TOGGLE;
125 | }
126 | };
127 |
128 | private String caption;
129 | private String keyCode;
130 |
131 |
132 | /**
133 | * Creates a new GlobalHotkey constant.
134 | *
135 | * @param caption the name of the hotkey which is displayed in the settings window
136 | * @param keyCode the associated key code on which the hotkey action will be executed
137 | */
138 | private GlobalHotkey(String caption, String keyCode)
139 | {
140 | setCaption(caption);
141 | this.keyCode = keyCode;
142 | }
143 |
144 |
145 | /**
146 | * The action to perform on pressing the hotkey.
147 | */
148 | public abstract void action();
149 |
150 |
151 | public abstract String getPrefsKey();
152 |
153 |
154 | /**
155 | * @return the caption
156 | */
157 | public String getCaption()
158 | {
159 | return caption;
160 | }
161 |
162 |
163 | /**
164 | * @param caption the caption to set
165 | */
166 | public void setCaption(String caption)
167 | {
168 | this.caption = caption;
169 | }
170 |
171 |
172 | /**
173 | * @return the keyCode
174 | */
175 | public String getKeyCode()
176 | {
177 | String storedCode = SettingsManager.getStoredHotkeyCode(this);
178 | return "".equals(storedCode) ? keyCode : storedCode;
179 |
180 | }
181 |
182 |
183 | /**
184 | * @param keyCode the keyCode to set
185 | */
186 | public void setKeyCode(String keyCode)
187 | {
188 | this.keyCode = keyCode;
189 | SettingsManager.setStoredHotkeyCode(this, keyCode);
190 | }
191 | }
192 |
--------------------------------------------------------------------------------
/src/com/soulsspeedruns/organizer/main/TopPanel.java:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | */
4 | package com.soulsspeedruns.organizer.main;
5 |
6 |
7 | import javax.swing.GroupLayout;
8 | import javax.swing.GroupLayout.Alignment;
9 | import javax.swing.JButton;
10 | import javax.swing.JLabel;
11 | import javax.swing.JPanel;
12 | import javax.swing.SwingConstants;
13 |
14 | import com.soulsspeedruns.organizer.components.SearchBar;
15 | import com.soulsspeedruns.organizer.games.config.GamesConfigurationWindow;
16 | import com.soulsspeedruns.organizer.main.config.GamesComboBox;
17 | import com.soulsspeedruns.organizer.main.config.ProfilesComboBox;
18 | import com.soulsspeedruns.organizer.main.config.SortingComboBox;
19 | import com.soulsspeedruns.organizer.managers.GamesManager;
20 |
21 |
22 | /**
23 | * Top segment of the main window.
24 | *
25 | * Contains the games and profile comboboxes, as well as the Edit Games button and search bar.
26 | *
27 | * @author Kahmul (www.twitch.tv/kahmul78)
28 | * @date 21 Jan 2024
29 | */
30 | public class TopPanel extends JPanel
31 | {
32 |
33 | /**
34 | * Creates the top panel for the main window.
35 | */
36 | protected TopPanel()
37 | {
38 | GroupLayout layout = new GroupLayout(this);
39 | layout.setAutoCreateGaps(true);
40 | layout.setAutoCreateContainerGaps(true);
41 |
42 | JLabel gamesLabel = new JLabel("Game:");
43 | JLabel profilesLabel = new JLabel("Profile:");
44 |
45 | SearchBar searchBar = new SearchBar();
46 |
47 | ProfilesComboBox profilesComboBox = new ProfilesComboBox(GamesManager.getSelectedGame());
48 | GamesComboBox gamesComboBox = new GamesComboBox(profilesComboBox);
49 |
50 | JPanel sortingPanel = createSortingPanel();
51 | JPanel profilesPanel = createProfilesPanel(profilesComboBox);
52 |
53 | layout.linkSize(SwingConstants.HORIZONTAL, gamesComboBox, sortingPanel);
54 |
55 | // Horizontal
56 | GroupLayout.SequentialGroup hGroup = layout.createSequentialGroup();
57 |
58 | hGroup.addGroup(layout.createParallelGroup().addComponent(gamesLabel)
59 | .addComponent(gamesComboBox, GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE)
60 | .addComponent(sortingPanel, GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE));
61 | hGroup.addGroup(
62 | layout.createParallelGroup().addComponent(profilesPanel, GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE, Short.MAX_VALUE)
63 | .addComponent(profilesLabel).addComponent(searchBar));
64 |
65 | layout.setHorizontalGroup(hGroup);
66 |
67 | // Vertical
68 | GroupLayout.SequentialGroup vGroup = layout.createSequentialGroup();
69 |
70 | vGroup.addGroup(layout.createParallelGroup(Alignment.BASELINE).addComponent(gamesLabel).addComponent(profilesLabel));
71 | vGroup.addGroup(layout.createParallelGroup(Alignment.BASELINE)
72 | .addComponent(gamesComboBox, GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE)
73 | .addComponent(profilesPanel, GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE));
74 | vGroup.addGroup(layout.createParallelGroup(Alignment.BASELINE)
75 | .addComponent(sortingPanel, GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE)
76 | .addComponent(searchBar));
77 | vGroup.addGap(10);
78 |
79 | layout.setVerticalGroup(vGroup);
80 |
81 | setLayout(layout);
82 | }
83 |
84 |
85 | /**
86 | * Creates the panel containing the sorting combobox and label.
87 | *
88 | * @return the sorting panel
89 | */
90 | private JPanel createSortingPanel()
91 | {
92 | JLabel sortByLabel = new JLabel("Sort by:");
93 | SortingComboBox sortingComboBox = new SortingComboBox();
94 |
95 | JPanel sortingPanel = new JPanel();
96 | GroupLayout panelLayout = new GroupLayout(sortingPanel);
97 |
98 | panelLayout.setHorizontalGroup(panelLayout.createSequentialGroup().addComponent(sortByLabel).addGap(5).addComponent(sortingComboBox));
99 | panelLayout.setVerticalGroup(panelLayout.createParallelGroup(Alignment.CENTER).addComponent(sortByLabel).addComponent(sortingComboBox));
100 |
101 | sortingPanel.setLayout(panelLayout);
102 |
103 | return sortingPanel;
104 | }
105 |
106 |
107 | /**
108 | * Creates the panel containing the profiles combobox and Edit Games button.
109 | *
110 | * @param profilesComboBox the profiles combobox of this top panel
111 | * @return the profiles panel
112 | */
113 | private JPanel createProfilesPanel(ProfilesComboBox profilesComboBox)
114 | {
115 | JButton editButton = createEditProfilesButton();
116 |
117 | JPanel profilesPanel = new JPanel();
118 | GroupLayout panelLayout = new GroupLayout(profilesPanel);
119 |
120 | panelLayout.setHorizontalGroup(panelLayout.createSequentialGroup()
121 | .addComponent(profilesComboBox, GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE, Short.MAX_VALUE).addGap(5)
122 | .addComponent(editButton));
123 | panelLayout.setVerticalGroup(panelLayout.createParallelGroup()
124 | .addComponent(profilesComboBox, GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE)
125 | .addComponent(editButton));
126 |
127 | profilesPanel.setLayout(panelLayout);
128 |
129 | return profilesPanel;
130 | }
131 |
132 |
133 | /**
134 | * Creates the 'Edit profiles' button.
135 | *
136 | * @return the edit profiles button
137 | */
138 | private JButton createEditProfilesButton()
139 | {
140 | JButton editButton = new JButton("Edit Games");
141 | editButton.addActionListener(event -> {
142 | new GamesConfigurationWindow();
143 | });
144 | return editButton;
145 | }
146 |
147 | }
148 |
--------------------------------------------------------------------------------
/src/com/soulsspeedruns/organizer/savelist/Folder.java:
--------------------------------------------------------------------------------
1 | package com.soulsspeedruns.organizer.savelist;
2 |
3 |
4 | import java.awt.Color;
5 | import java.awt.Font;
6 | import java.io.File;
7 | import java.io.IOException;
8 | import java.nio.file.Files;
9 | import java.util.Collections;
10 | import java.util.LinkedList;
11 | import java.util.List;
12 |
13 | import javax.swing.BorderFactory;
14 | import javax.swing.Icon;
15 | import javax.swing.JLabel;
16 | import javax.swing.JList;
17 | import javax.swing.JOptionPane;
18 | import javax.swing.UIManager;
19 |
20 | import com.soulsspeedruns.organizer.managers.SavesManager;
21 |
22 | import jiconfont.icons.FontAwesome;
23 | import jiconfont.swing.IconFontSwing;
24 |
25 |
26 | /**
27 | * Folder class.
28 | *
29 | * Represents the folders in the SaveList.
30 | *
31 | * @author Kahmul (www.twitch.tv/kahmul78)
32 | * @date 7 Jul 2017
33 | */
34 | public class Folder extends SaveListEntry
35 | {
36 |
37 | private boolean isClosed = true;
38 |
39 | private static final Color ICON_COLOR = new Color(251, 208, 108);
40 | private static final Color ICON_COLOR_ERROR = Color.RED;
41 | private static final int ICON_SIZE = 15;
42 |
43 | private static final Icon ICON = IconFontSwing.buildIcon(FontAwesome.FOLDER, ICON_SIZE, ICON_COLOR);
44 | private static final Icon ICON_OPEN = IconFontSwing.buildIcon(FontAwesome.FOLDER_OPEN, ICON_SIZE, ICON_COLOR);
45 |
46 | private static final Icon ICON_ERROR = IconFontSwing.buildIcon(FontAwesome.FOLDER, ICON_SIZE, ICON_COLOR_ERROR);
47 | private static final Icon ICON_OPEN_ERROR = IconFontSwing.buildIcon(FontAwesome.FOLDER_OPEN, ICON_SIZE, ICON_COLOR_ERROR);
48 |
49 |
50 | /**
51 | * Creates a new Folder instance. Children in the underlying filesystem are automatically added.
52 | *
53 | * @param parent the parent folder
54 | * @param file the associated file
55 | */
56 | public Folder(Folder parent, File file)
57 | {
58 | super(parent, file);
59 | File[] files = file.listFiles();
60 | for (File currentFile : files)
61 | {
62 | if (currentFile.isDirectory())
63 | {
64 | addChild(new Folder(this, currentFile));
65 | continue;
66 | }
67 | addChild(new Save(this, currentFile));
68 | }
69 | Collections.sort(getChildren());
70 | }
71 |
72 | /**
73 | * @return whether the folder is closed or not
74 | */
75 | public boolean isClosed()
76 | {
77 | return isClosed;
78 | }
79 |
80 |
81 | /**
82 | * @param isClosed set the folder state closed or opened
83 | */
84 | public void setClosed(boolean isClosed)
85 | {
86 | this.isClosed = isClosed;
87 | }
88 |
89 |
90 | @Override
91 | public void removeChild(SaveListEntry entry)
92 | {
93 | super.removeChild(entry);
94 | if (getChildren().size() == 0)
95 | setClosed(true);
96 | }
97 |
98 |
99 | @Override
100 | public boolean rename(String newName)
101 | {
102 | File newFile = new File(getParent().getFile() + File.separator + newName);
103 | try
104 | {
105 | // if the same name is given, then only the file variable is supposed to be updated for a new parent
106 | if (!getFile().getName().equals((newName)))
107 | Files.move(getFile().toPath(), newFile.toPath());
108 | }
109 | catch (IOException e)
110 | {
111 | JOptionPane.showMessageDialog(null,
112 | "Renaming the entries was not successful. They are possibly being accessed by another program.", "Warning",
113 | JOptionPane.WARNING_MESSAGE);
114 | return false;
115 | }
116 | setFile(newFile);
117 | for (SaveListEntry entry : getChildren())
118 | {
119 | // call rename on all children with the same name to update the path with the new parent
120 | if (!entry.rename(entry.getName()))
121 | return false;
122 | }
123 | return true;
124 | }
125 |
126 |
127 | @Override
128 | public boolean canBeRenamed()
129 | {
130 | if (getFile().canWrite())
131 | {
132 | for (SaveListEntry entry : getChildren())
133 | {
134 | if (!entry.canBeRenamed())
135 | return false;
136 | }
137 | return true;
138 | }
139 | return false;
140 | }
141 |
142 |
143 | @Override
144 | public void delete()
145 | {
146 | List
18 | * Registers global hotkeys.
19 | *
20 | * @author Kahmul (www.twitch.tv/kahmul78)
21 | * @date 20 May 2016
22 | */
23 | public class GlobalKeyboardHook implements NativeKeyListener
24 | {
25 |
26 | private boolean hotkeysEnabled = false;
27 |
28 | // if a modifier key is released, this is used to decide whether the modifier was tied to a previous key combo or not
29 | private boolean keyComboWasExecuted = false;
30 |
31 |
32 | /**
33 | * Registers a global hotkey hook.
34 | *
35 | * @throws NativeHookException
36 | */
37 | public GlobalKeyboardHook() throws NativeHookException
38 | {
39 | Logger logger = Logger.getLogger(GlobalScreen.class.getPackage().getName());
40 | logger.setLevel(Level.OFF);
41 | GlobalScreen.registerNativeHook();
42 | GlobalScreen.addNativeKeyListener(this);
43 | }
44 |
45 |
46 | /**
47 | * Registers the global hotkey hook.
48 | */
49 | public void registerHook()
50 | {
51 | try
52 | {
53 | GlobalScreen.registerNativeHook();
54 | }
55 | catch (NativeHookException e)
56 | {
57 | e.printStackTrace();
58 | }
59 | }
60 |
61 |
62 | /**
63 | * Unregisters the global hotkey hook.
64 | */
65 | public void unregisterHook()
66 | {
67 | try
68 | {
69 | GlobalScreen.unregisterNativeHook();
70 | }
71 | catch (NativeHookException e)
72 | {
73 | e.printStackTrace();
74 | }
75 | }
76 |
77 |
78 | /**
79 | * Toggles global hotkeys.
80 | *
81 | * @param flag True to enable hotkeys, false to disable them
82 | */
83 | public void setHotkeysEnabled(boolean flag)
84 | {
85 | hotkeysEnabled = flag;
86 | }
87 |
88 |
89 | /**
90 | * Returns whether global hotkeys are enabled.
91 | *
92 | * @return whether global hotkeys are enabled
93 | */
94 | public boolean areHotkeysEnabled()
95 | {
96 | return hotkeysEnabled;
97 | }
98 |
99 |
100 | @Override
101 | public void nativeKeyTyped(NativeKeyEvent e)
102 | {
103 | }
104 |
105 |
106 | @Override
107 | public void nativeKeyPressed(NativeKeyEvent e)
108 | {
109 | keyComboWasExecuted = false;
110 | int keyCode = e.getKeyCode();
111 | if (keyCode == NativeKeyEvent.VC_SHIFT_L || keyCode == NativeKeyEvent.VC_SHIFT_R || keyCode == NativeKeyEvent.VC_CONTROL_L
112 | || keyCode == NativeKeyEvent.VC_CONTROL_R || keyCode == NativeKeyEvent.VC_ALT_L || keyCode == NativeKeyEvent.VC_ALT_R)
113 | return;
114 | executeKeyEvent(e);
115 | }
116 |
117 |
118 | @Override
119 | public void nativeKeyReleased(NativeKeyEvent e)
120 | {
121 | int keyCode = e.getKeyCode();
122 | if (keyCode == NativeKeyEvent.VC_SHIFT_L || keyCode == NativeKeyEvent.VC_SHIFT_R || keyCode == NativeKeyEvent.VC_CONTROL_L
123 | || keyCode == NativeKeyEvent.VC_CONTROL_R || keyCode == NativeKeyEvent.VC_ALT_L || keyCode == NativeKeyEvent.VC_ALT_R)
124 | {
125 | // if only modifiers have been held so far and one is released, then execute the action
126 | if (!keyComboWasExecuted)
127 | executeKeyEvent(e);
128 | }
129 | }
130 |
131 |
132 | /**
133 | * Executes the action of the hotkey with the same key code as the given KeyEvent.
134 | *
135 | * @param e the KeyEvent
136 | */
137 | private void executeKeyEvent(NativeKeyEvent e)
138 | {
139 | String keyText = getKeyText(e);
140 | for (GlobalHotkey hotkey : GlobalHotkey.values())
141 | {
142 | if (!hotkeysEnabled && hotkey != GlobalHotkey.GLOBAL_HOTKEY_TOGGLE)
143 | continue;
144 | if (keyText.equals(hotkey.getKeyCode()))
145 | {
146 | hotkey.action();
147 | keyComboWasExecuted = true;
148 | break;
149 | }
150 | }
151 | }
152 |
153 |
154 | /**
155 | * Gets the key combination text from the KeyEvent.
156 | *
157 | * @param e the KeyEvent
158 | * @return the combined text of all pressed keys
159 | */
160 | private String getKeyText(NativeKeyEvent e)
161 | {
162 | String modifiers = NativeKeyEvent.getModifiersText(e.getModifiers());
163 | if (modifiers.length() > 0)
164 | {
165 | modifiers = modifiers.replaceAll("\\+", " \\+ ");
166 | // replace "Shift + Ctrl/Alt" with "Ctrl/Alt + Shift" for compatibility between global hotkeys and base Java hotkeys
167 | modifiers = modifiers.replaceAll(Toolkit.getProperty("AWT.shift", "Shift") + " \\+ " + Toolkit.getProperty("AWT.control", "Ctrl"),
168 | Toolkit.getProperty("AWT.control", "Ctrl") + " \\+ " + Toolkit.getProperty("AWT.shift", "Shift"));
169 | modifiers = modifiers.replaceAll(Toolkit.getProperty("AWT.shift", "Shift") + " \\+ " + Toolkit.getProperty("AWT.alt", "Alt"),
170 | Toolkit.getProperty("AWT.alt", "alt") + " \\+ " + Toolkit.getProperty("AWT.shift", "Shift"));
171 | modifiers += " + ";
172 | }
173 |
174 | String keyText = "";
175 |
176 | // NativeKeyEvent has different keytexts than Java for modifiers, so need to standardize them
177 | switch (e.getKeyCode())
178 | {
179 | case NativeKeyEvent.VC_CONTROL_L:
180 | case NativeKeyEvent.VC_CONTROL_R:
181 | keyText = KeyEvent.getKeyText(KeyEvent.VK_CONTROL);
182 | break;
183 | case NativeKeyEvent.VC_SHIFT_L:
184 | case NativeKeyEvent.VC_SHIFT_R:
185 | keyText = KeyEvent.getKeyText(KeyEvent.VK_SHIFT);
186 | break;
187 | case NativeKeyEvent.VC_ALT_L:
188 | case NativeKeyEvent.VC_ALT_R:
189 | keyText = KeyEvent.getKeyText(KeyEvent.VK_ALT);
190 | break;
191 | default:
192 | keyText = NativeKeyEvent.getKeyText(e.getKeyCode());
193 | break;
194 | }
195 |
196 | return modifiers + keyText;
197 | }
198 |
199 | }
200 |
--------------------------------------------------------------------------------
/src/com/soulsspeedruns/organizer/games/config/GamesConfigPane.java:
--------------------------------------------------------------------------------
1 | package com.soulsspeedruns.organizer.games.config;
2 |
3 |
4 | import java.awt.Color;
5 | import java.awt.Insets;
6 |
7 | import javax.swing.Box;
8 | import javax.swing.BoxLayout;
9 | import javax.swing.GroupLayout;
10 | import javax.swing.JButton;
11 | import javax.swing.JLabel;
12 | import javax.swing.JOptionPane;
13 | import javax.swing.JPanel;
14 | import javax.swing.SwingUtilities;
15 | import javax.swing.UIManager;
16 | import javax.swing.border.EmptyBorder;
17 | import javax.swing.border.MatteBorder;
18 |
19 | import jiconfont.icons.FontAwesome;
20 | import jiconfont.swing.IconFontSwing;
21 |
22 |
23 | /**
24 | * Games Config Pane
25 | *
26 | * Component pane offering all available games in a scrollable list, together with a configuration panel and buttons to add/delete games.
27 | *
28 | * @author Kahmul (www.twitch.tv/kahmul78)
29 | * @date 11 Jan 2024
30 | */
31 | public class GamesConfigPane extends JPanel implements GameListListener
32 | {
33 |
34 | private JButton editButton;
35 | private JButton deleteButton;
36 | private JLabel settingsLabel;
37 |
38 | private GroupLayout gameSelectionLayout;
39 | private GameList gameList;
40 |
41 |
42 | public GamesConfigPane()
43 | {
44 | super();
45 |
46 | initLayout();
47 | }
48 |
49 |
50 | private void initLayout()
51 | {
52 | setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS));
53 |
54 | add(createButtonsPanel());
55 | add(createGameSelectionPanel());
56 | }
57 |
58 |
59 | private JPanel createButtonsPanel()
60 | {
61 | JPanel buttonPanel = new JPanel();
62 | buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.LINE_AXIS));
63 | buttonPanel.setBorder(new EmptyBorder(0, 0, 0, 0));
64 |
65 | Color buttonColor = new Color(UIManager.getColor("background").getRGB());
66 |
67 | JButton addButton = createButton(FontAwesome.PLUS, buttonColor);
68 | addButton.addActionListener(e -> {
69 | new GameCreationWindow(gameList, null);
70 | });
71 |
72 | editButton = createButton(FontAwesome.PENCIL, buttonColor);
73 | editButton.addActionListener(e -> {
74 | new GameCreationWindow(gameList, gameList.getSelectedEntry().getGame());
75 | });
76 |
77 | deleteButton = createButton(FontAwesome.TRASH, buttonColor);
78 | deleteButton.addActionListener((e) -> {
79 | int confirm = JOptionPane.showConfirmDialog(SwingUtilities.windowForComponent(this),
80 | "Do you really want to delete your custom game '" + gameList.getSelectedEntry().getGame().getCaption() + "'?",
81 | "Delete Custom Game", JOptionPane.YES_NO_OPTION);
82 | if (confirm == 0)
83 | gameList.deleteSelectedEntry();
84 | });
85 |
86 | settingsLabel = new JLabel("Game Settings");
87 |
88 | buttonPanel.add(addButton);
89 | buttonPanel.add(editButton);
90 | buttonPanel.add(deleteButton);
91 |
92 | buttonPanel.add(Box.createHorizontalGlue());
93 | buttonPanel.add(settingsLabel);
94 | buttonPanel.add(Box.createHorizontalGlue());
95 |
96 | buttonPanel.setBorder(new MatteBorder(0, 0, 1, 0, UIManager.getColor("borderSecondary")));
97 |
98 | return buttonPanel;
99 | }
100 |
101 |
102 | private JButton createButton(FontAwesome icon, Color buttonColor)
103 | {
104 | JButton button = new JButton(IconFontSwing.buildIcon(icon, 16, UIManager.getColor("gameConfigButtonColors")));
105 | button.setDisabledIcon(IconFontSwing.buildIcon(icon, 16, UIManager.getColor("disabledIconColor")));
106 | button.setBorderPainted(false);
107 | button.setBackground(buttonColor);
108 | button.setMargin(new Insets(6, 18, 6, 18));
109 | button.setBorder(new EmptyBorder(0, 0, 0, 0));
110 |
111 | return button;
112 | }
113 |
114 |
115 | private JPanel createGameSelectionPanel()
116 | {
117 | JPanel gameSelectionPanel = new JPanel();
118 |
119 | gameSelectionLayout = new GroupLayout(gameSelectionPanel);
120 |
121 | gameList = new GameList();
122 | gameList.addListener(this);
123 | updateButtonState(gameList.getSelectedEntry());
124 |
125 | GameConfigPanel configPanel = gameList.getSelectedEntry().getConfigPanel();
126 |
127 | GroupLayout.SequentialGroup hGroup = gameSelectionLayout.createSequentialGroup()
128 | .addComponent(gameList, GroupLayout.PREFERRED_SIZE, 151, GroupLayout.PREFERRED_SIZE)
129 | .addComponent(configPanel, GroupLayout.PREFERRED_SIZE, 435, GroupLayout.PREFERRED_SIZE);
130 |
131 | GroupLayout.ParallelGroup vGroup = gameSelectionLayout.createParallelGroup()
132 | .addComponent(gameList, GroupLayout.PREFERRED_SIZE, configPanel.getPreferredSize().height, GroupLayout.PREFERRED_SIZE)
133 | .addComponent(configPanel);
134 |
135 | gameSelectionLayout.setHorizontalGroup(hGroup);
136 | gameSelectionLayout.setVerticalGroup(vGroup);
137 |
138 | gameSelectionPanel.setLayout(gameSelectionLayout);
139 |
140 | return gameSelectionPanel;
141 | }
142 |
143 |
144 | /**
145 | * Updates the state of the menu buttons to reflect the given entry.
146 | *
147 | * @param entry
148 | */
149 | private void updateButtonState(GameListEntry entry)
150 | {
151 | editButton.setEnabled(entry.getGame().isCustomGame());
152 | deleteButton.setEnabled(entry.getGame().isCustomGame());
153 | settingsLabel.setText(entry.getGame().getCaption());
154 | }
155 |
156 |
157 | /**
158 | * Called when the games configuration window is opened to init values.
159 | */
160 | protected void loadConfigPane()
161 | {
162 | gameList.updateScrollbar();
163 | }
164 |
165 |
166 | @Override
167 | public void entryCreated(GameListEntry entry)
168 | {
169 |
170 | }
171 |
172 |
173 | @Override
174 | public void entryUpdated(GameListEntry entry)
175 | {
176 | settingsLabel.setText(entry.getGame().getCaption());
177 |
178 | revalidate();
179 | repaint();
180 | }
181 |
182 |
183 | @Override
184 | public void entryDeleted(GameListEntry entry)
185 | {
186 |
187 | }
188 |
189 |
190 | @Override
191 | public void entrySelected(GameListEntry prevEntry, GameListEntry newEntry)
192 | {
193 | if (prevEntry != null)
194 | gameSelectionLayout.replace(prevEntry.getConfigPanel(), newEntry.getConfigPanel());
195 |
196 | updateButtonState(newEntry);
197 |
198 | revalidate();
199 | repaint();
200 | }
201 |
202 | }
203 |
--------------------------------------------------------------------------------
/src/com/soulsspeedruns/organizer/settings/GeneralSettingsPanel.java:
--------------------------------------------------------------------------------
1 | package com.soulsspeedruns.organizer.settings;
2 |
3 |
4 | import java.awt.Component;
5 | import java.awt.event.ItemEvent;
6 |
7 | import javax.swing.BorderFactory;
8 | import javax.swing.Box;
9 | import javax.swing.GroupLayout;
10 | import javax.swing.GroupLayout.Alignment;
11 | import javax.swing.JCheckBox;
12 | import javax.swing.JComboBox;
13 | import javax.swing.JLabel;
14 | import javax.swing.JPanel;
15 | import javax.swing.SwingUtilities;
16 | import javax.swing.border.EtchedBorder;
17 | import javax.swing.border.TitledBorder;
18 |
19 | import com.github.weisj.darklaf.LafManager;
20 | import com.github.weisj.darklaf.theme.Theme;
21 | import com.soulsspeedruns.organizer.managers.SettingsManager;
22 |
23 |
24 | /**
25 | * GeneralSettingsPanel
26 | *
27 | * Contains general settings for the user to change.
28 | *
29 | * @author Kahmul (www.twitch.tv/kahmul78)
30 | * @date 3 Jun 2016
31 | */
32 | public class GeneralSettingsPanel extends JPanel
33 | {
34 |
35 | private JCheckBox alwaysOnTopCheckbox;
36 | private JCheckBox hotkeysCheckbox;
37 | private JCheckBox doubleClickLoadCheckbox;
38 | private JCheckBox checkForUpdatesCheckbox;
39 | private JCheckBox compactModeCheckbox;
40 | private JComboBox
21 | * The abstract superclass for all message implementations.
22 | *
23 | * @author Kahmul (www.twitch.tv/kahmul78)
24 | * @date 15 Jul 2017
25 | */
26 | public abstract class AbstractMessage extends JPanel
27 | {
28 |
29 | private static final Font FONT = new Font("Calibri", Font.BOLD, 17);
30 | private static final int TIME_OUT = 2500;
31 | private static final int iconTextGap = 5;
32 |
33 | public static final AbstractMessage SUCCESSFUL_DELETE = new SuccessfulDeleteMessage();
34 | public static final AbstractMessage SUCCESSFUL_IMPORT = new SuccessfulImportMessage();
35 | public static final AbstractMessage SUCCESSFUL_LOAD = new SuccessfulLoadMessage();
36 | public static final AbstractMessage SUCCESSFUL_REPLACE = new SuccessfulReplaceMessage();
37 | public static final AbstractMessage SUCCESSFUL_REFRESH = new SuccessfulRefreshMessage();
38 |
39 | public static final AbstractMessage FAILED_LOAD = new FailedLoadMessage();
40 |
41 | // used to prevent fadeout when redisplaying a currently displayed message
42 | private static AbstractMessage currentMessage;
43 |
44 | private UndecoratedMessageDialog dialog = null;
45 |
46 | private float alpha = 0.0f;
47 | private float fadeInOutRate = 0.15f;
48 |
49 | private boolean fadingOut = false;
50 |
51 | private Timer fadeOutTimer = new Timer();
52 |
53 |
54 | protected AbstractMessage()
55 | {
56 | setBackground(new Color(0, 0, 0, 0));
57 | }
58 |
59 |
60 | @Override
61 | public void paintComponent(Graphics g)
62 | {
63 | super.paintComponent(g);
64 | Graphics2D g2d = (Graphics2D) g;
65 |
66 | // set the opacity
67 | g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC, alpha));
68 | g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
69 | g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
70 |
71 | g.setColor(getColor());
72 | g.drawRect(0, 0, getWidth() - 1, getHeight() - 1);
73 | g.setFont(FONT);
74 | drawMessageAndIcon(g);
75 | fadeInOut();
76 | }
77 |
78 |
79 | /**
80 | * Adjusts the alpha value depending on whether the message is in fade-in or fade-out mode.
81 | */
82 | private void fadeInOut()
83 | {
84 | if (fadingOut)
85 | {
86 | alpha -= fadeInOutRate;
87 | if (alpha <= 0.0f)
88 | {
89 | alpha = 0.0f;
90 | dialog.setVisible(false);
91 | fadingOut = false;
92 | return;
93 | }
94 | repaint();
95 | return;
96 | }
97 | alpha += fadeInOutRate;
98 | if (alpha >= 1.0f)
99 | {
100 | alpha = 1.0f;
101 | return;
102 | }
103 | repaint();
104 | }
105 |
106 |
107 | /**
108 | * Draws the message and icon associated with this message.
109 | *
110 | * @param g the graphics to draw on
111 | */
112 | private void drawMessageAndIcon(Graphics g)
113 | {
114 | FontMetrics metrics = g.getFontMetrics(g.getFont());
115 | Icon icon = getIcon();
116 |
117 | int iconX = getWidth() / 2 - metrics.stringWidth(getMessage()) / 2 - icon.getIconWidth()/2 - iconTextGap;
118 | int iconY = getHeight() / 2 - icon.getIconHeight() / 2;
119 | icon.paintIcon(null, g, iconX, iconY);
120 |
121 | int messageX = getWidth() / 2 - metrics.stringWidth(getMessage()) / 2 + icon.getIconWidth()/2 + iconTextGap;
122 | int messageY = getHeight() / 2 + metrics.getAscent() / 2;
123 | g.drawString(getMessage(), messageX, messageY);
124 | }
125 |
126 |
127 | /**
128 | * Starts the fade-out. Is the fade-out complete, the parent dialog is set invisible.
129 | */
130 | protected void fadeOut()
131 | {
132 | // don't fadeout the current message
133 | if (this.equals(currentMessage))
134 | return;
135 | fadingOut = true;
136 | repaint();
137 | }
138 |
139 |
140 | /**
141 | * Starts the fade-in.
142 | */
143 | protected void fadeIn()
144 | {
145 | fadingOut = false;
146 | repaint();
147 | }
148 |
149 |
150 | /**
151 | * Starts the fade-in of the message, stops any ongoing fade-out, and resets the fade-out timer.
152 | */
153 | private void display()
154 | {
155 | fadingOut = false;
156 | fadeOutTimer.cancel();
157 | fadeOutTimer = new Timer();
158 | fadeOutTimer.schedule(new TimerTask() {
159 |
160 | @Override
161 | public void run()
162 | {
163 | fadingOut = true;
164 | repaint();
165 | }
166 | }, TIME_OUT);
167 | if (alpha > 0.0f)
168 | return;
169 | dialog = new UndecoratedMessageDialog(this);
170 | dialog.fadeIn();
171 | }
172 |
173 |
174 | /**
175 | * Display the given message on screen.
176 | *
177 | * @param message the message to display
178 | */
179 | public static void display(AbstractMessage message)
180 | {
181 | currentMessage = message;
182 |
183 | SUCCESSFUL_DELETE.fadeOut();
184 | SUCCESSFUL_IMPORT.fadeOut();
185 | SUCCESSFUL_LOAD.fadeOut();
186 | SUCCESSFUL_REPLACE.fadeOut();
187 | SUCCESSFUL_REFRESH.fadeOut();
188 | FAILED_LOAD.fadeOut();
189 |
190 | message.display();
191 | }
192 |
193 |
194 | /**
195 | * Clears all displayed messages on the screen.
196 | */
197 | public static void clearAllMessages()
198 | {
199 | currentMessage = null;
200 |
201 | SUCCESSFUL_DELETE.fadeOut();
202 | SUCCESSFUL_IMPORT.fadeOut();
203 | SUCCESSFUL_LOAD.fadeOut();
204 | SUCCESSFUL_REPLACE.fadeOut();
205 | SUCCESSFUL_REFRESH.fadeOut();
206 | FAILED_LOAD.fadeOut();
207 | }
208 |
209 |
210 | /**
211 | * The text associated with this message.
212 | *
213 | * @return the text
214 | */
215 | protected abstract String getMessage();
216 |
217 |
218 | /**
219 | * The icon associated with this message.
220 | *
221 | * @return the icon
222 | */
223 | protected abstract Icon getIcon();
224 |
225 |
226 | /**
227 | * The size of the icon for this message.
228 | *
229 | * @return
230 | */
231 | protected abstract int getIconSize();
232 |
233 |
234 | /**
235 | * The color associated with this message.
236 | *
237 | * @return the color
238 | */
239 | protected abstract Color getColor();
240 |
241 |
242 |
243 | }
244 |
--------------------------------------------------------------------------------
/src/com/soulsspeedruns/organizer/games/config/GameConfigProfilesPanel.java:
--------------------------------------------------------------------------------
1 | package com.soulsspeedruns.organizer.games.config;
2 |
3 |
4 | import java.awt.GridLayout;
5 |
6 | import javax.swing.GroupLayout;
7 | import javax.swing.GroupLayout.Alignment;
8 | import javax.swing.JButton;
9 | import javax.swing.JLabel;
10 | import javax.swing.JPanel;
11 | import javax.swing.JScrollPane;
12 |
13 | import com.github.weisj.darklaf.components.OverlayScrollPane;
14 | import com.soulsspeedruns.organizer.games.Game;
15 | import com.soulsspeedruns.organizer.games.Profile;
16 | import com.soulsspeedruns.organizer.listeners.ProfileListener;
17 | import com.soulsspeedruns.organizer.managers.GamesManager;
18 |
19 |
20 | /**
21 | * Profile part of the configuration panel.
22 | *
23 | * Contains the profile list as well as the buttons to edit it.
24 | *
25 | * @author Kahmul (www.twitch.tv/kahmul78)
26 | * @date 28 Sep 2015
27 | */
28 | public class GameConfigProfilesPanel extends JPanel
29 | {
30 |
31 | /**
32 | * Creates a new profile panel.
33 | *
34 | * @param game the game of this panel
35 | */
36 | protected GameConfigProfilesPanel(Game game)
37 | {
38 | GroupLayout layout = new GroupLayout(this);
39 | layout.setAutoCreateGaps(true);
40 | layout.setAutoCreateContainerGaps(true);
41 |
42 | JLabel profileLabel = new JLabel("Profiles: ");
43 |
44 | ProfileList profileList = createProfileList(game);
45 | OverlayScrollPane listPane = new OverlayScrollPane(profileList, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
46 | JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
47 |
48 | JPanel optionsPanel = createOptionsPanel(profileList);
49 |
50 | // Horizontal
51 | GroupLayout.SequentialGroup hGroup = layout.createSequentialGroup();
52 |
53 | hGroup.addGroup(layout.createParallelGroup().addComponent(profileLabel).addComponent(listPane));
54 | hGroup.addGroup(layout.createParallelGroup().addComponent(optionsPanel, GroupLayout.PREFERRED_SIZE, new JButton("Browse").getPreferredSize().width,
55 | GroupLayout.PREFERRED_SIZE));
56 |
57 | layout.setHorizontalGroup(hGroup);
58 |
59 | // Vertical
60 | GroupLayout.SequentialGroup vGroup = layout.createSequentialGroup();
61 |
62 | vGroup.addGroup(layout.createParallelGroup(Alignment.BASELINE).addComponent(profileLabel));
63 | vGroup.addGroup(layout.createParallelGroup(Alignment.BASELINE).addComponent(listPane).addComponent(optionsPanel, GroupLayout.PREFERRED_SIZE,
64 | GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE));
65 |
66 | layout.setVerticalGroup(vGroup);
67 |
68 | setLayout(layout);
69 | }
70 |
71 |
72 | /**
73 | * Creates the profile list.
74 | *
75 | * @return the profile list
76 | */
77 | private ProfileList createProfileList(Game game)
78 | {
79 | ProfileList profileList = new ProfileList(game);
80 | return profileList;
81 | }
82 |
83 |
84 | /**
85 | * Creates the button panel.
86 | *
87 | * @return the panel containing the buttons
88 | */
89 | private JPanel createOptionsPanel(ProfileList profileList)
90 | {
91 | JPanel buttonPanel = new JPanel();
92 | buttonPanel.setLayout(new GridLayout(4, 1));
93 |
94 | JButton newButton = createNewButton(profileList);
95 | JButton importButton = createImportButton(profileList);
96 | JButton editButton = createEditButton(profileList);
97 | JButton deleteButton = createDeleteButton(profileList);
98 |
99 | buttonPanel.add(newButton);
100 | buttonPanel.add(editButton);
101 | buttonPanel.add(importButton);
102 | buttonPanel.add(deleteButton);
103 |
104 | addButtonListeners(newButton, editButton, importButton, deleteButton);
105 |
106 | return buttonPanel;
107 | }
108 |
109 |
110 | private void addButtonListeners(JButton newButton, JButton editButton, JButton importButton, JButton deleteButton)
111 | {
112 | // TODO Auto-generated method stub
113 |
114 | GamesManager.addProfileListener(new ProfileListener()
115 | {
116 |
117 | @Override
118 | public void profileDeleted(Profile profile)
119 | {
120 | }
121 |
122 |
123 | @Override
124 | public void profileCreated(Profile profile)
125 | {
126 | }
127 |
128 |
129 | @Override
130 | public void profileDirectoryChanged(Game game)
131 | {
132 | newButton.setEnabled(game.getDirectory() != null);
133 | editButton.setEnabled(game.getDirectory() != null);
134 | importButton.setEnabled(game.getDirectory() != null);
135 | deleteButton.setEnabled(game.getDirectory() != null);
136 | }
137 |
138 |
139 | @Override
140 | public void changedToProfile(Profile profile)
141 | {
142 | }
143 |
144 |
145 | @Override
146 | public void changedToGame(Game game)
147 | {
148 | }
149 |
150 | });
151 | }
152 |
153 |
154 | /**
155 | * Creates the 'Import' button.
156 | *
157 | * @return the import button
158 | */
159 | private JButton createImportButton(ProfileList profileList)
160 | {
161 | JButton importButton = new JButton("Import");
162 | importButton.setToolTipText("Import profile(s).");
163 | importButton.setEnabled(profileList.getGame().getDirectory() != null);
164 | importButton.addActionListener(event -> {
165 | profileList.askToImportProfiles();
166 | });
167 |
168 | return importButton;
169 | }
170 |
171 |
172 | /**
173 | * Creates the 'New' button.
174 | *
175 | * @param game the game of the current panel
176 | * @return the new button
177 | */
178 | private JButton createNewButton(ProfileList profileList)
179 | {
180 | JButton newButton = new JButton("New");
181 | newButton.setToolTipText("Create a new profile.");
182 | newButton.setEnabled(profileList.getGame().getDirectory() != null);
183 | newButton.addActionListener(event -> {
184 | profileList.askToCreateProfile();
185 | });
186 | return newButton;
187 | }
188 |
189 |
190 | /**
191 | * Creates the 'Edit' button.
192 | *
193 | * @param game the game of the current panel
194 | * @return the edit button
195 | */
196 | private JButton createEditButton(ProfileList profileList)
197 | {
198 | JButton editButton = new JButton("Edit");
199 | editButton.setToolTipText("Edit the selected profile.");
200 | editButton.setEnabled(profileList.getGame().getDirectory() != null);
201 | editButton.addActionListener(event -> {
202 | profileList.askToEditProfile(profileList.getSelectedValue());
203 | });
204 | return editButton;
205 | }
206 |
207 |
208 | /**
209 | * Creates the 'Delete' button.
210 | *
211 | * @param game the game of the current panel
212 | * @return the delete button
213 | */
214 | private JButton createDeleteButton(ProfileList profileList)
215 | {
216 | JButton deleteButton = new JButton("Delete");
217 | deleteButton.setToolTipText("Delete the selected profile.");
218 | deleteButton.setEnabled(profileList.getGame().getDirectory() != null);
219 | deleteButton.addActionListener(event -> {
220 | profileList.askToDeleteProfiles(profileList.getSelectedValuesList());
221 | });
222 | return deleteButton;
223 | }
224 |
225 | }
226 |
--------------------------------------------------------------------------------
/src/com/soulsspeedruns/organizer/managers/VersionManager.java:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | */
4 | package com.soulsspeedruns.organizer.managers;
5 |
6 |
7 | import java.io.BufferedReader;
8 | import java.io.IOException;
9 | import java.io.InputStream;
10 | import java.io.InputStreamReader;
11 | import java.io.Reader;
12 | import java.net.URI;
13 | import java.nio.charset.StandardCharsets;
14 |
15 | import org.json.JSONObject;
16 |
17 |
18 | /**
19 | * VersionManager.
20 | *
21 | * Handles methods around the application version and the system environment.
22 | *
23 | * @author Kahmul (www.twitch.tv/kahmul78)
24 | * @date 20 Jan 2024
25 | */
26 | public class VersionManager
27 | {
28 |
29 | private static final String VERSION = "1.6.0";
30 |
31 | private static final String LATEST_RELEASE_JSON_URL = "https://api.github.com/repos/Kahmul/SoulsSpeedruns-Save-Organizer/releases/latest";
32 | private static final String LATEST_RELEASE_URL = "https://github.com/Kahmul/SoulsSpeedruns-Save-Organizer/releases/latest";
33 |
34 | private static String latestReleaseVersion;
35 |
36 | private static String operatingSystem;
37 |
38 |
39 | protected static void initialize()
40 | {
41 | determineOS();
42 | }
43 |
44 |
45 | /**
46 | * Determines the general OS name the application is running on.
47 | */
48 | private static void determineOS()
49 | {
50 | String osName = System.getProperty("os.name").toLowerCase();
51 | if (osName.contains("win"))
52 | {
53 | operatingSystem = "Windows";
54 | }
55 | else if (osName.contains("nix") || osName.contains("nux") || osName.contains("aix"))
56 | {
57 | operatingSystem = "Linux";
58 | }
59 | else if (osName.contains("mac"))
60 | {
61 | operatingSystem = "Mac";
62 | }
63 | }
64 |
65 |
66 | /**
67 | * Returns the save organizer version.
68 | *
69 | * @return the current version
70 | */
71 | public static String getVersion()
72 | {
73 | return VERSION;
74 | }
75 |
76 |
77 | /**
78 | * Returns the URL to the latest release on GitHub.
79 | *
80 | * @return latest github release URl
81 | */
82 | public static String getLatestReleaseURL()
83 | {
84 | return LATEST_RELEASE_URL;
85 | }
86 |
87 |
88 | /**
89 | * Returns whether the OS this application is running on is Windows.
90 | *
91 | * @return whether the OS is Windows
92 | */
93 | public static boolean isRunningOnWindows()
94 | {
95 | return operatingSystem.contains("Windows");
96 | }
97 |
98 |
99 | /**
100 | * Returns whether the OS this application is running on is Linux. It is assumed for the Souls games that they are running on a compatibility
101 | * layer such as Proton.
102 | *
103 | * @return whether the OS is Linux
104 | */
105 | public static boolean isRunningOnLinux()
106 | {
107 | return operatingSystem.contains("Linux");
108 | }
109 |
110 |
111 | /**
112 | * Gets the major Java version of the current runtime.
113 | *
114 | * @return the currently used major Java version
115 | */
116 | public static int getMajorJavaVersion()
117 | {
118 | String[] versionElements = System.getProperty("java.version").split("\\.");
119 | int firstElement = Integer.parseInt(versionElements[0]);
120 | int version = firstElement == 1 ? Integer.parseInt(versionElements[1]) : firstElement;
121 |
122 | return version;
123 | }
124 |
125 |
126 | /**
127 | * Checks whether the local Save Organizer version is outdated compared to the latest GitHub release.
128 | *
129 | * @return whether the local version is outdated
130 | */
131 | public static boolean isVersionOutdated()
132 | {
133 | if (!SettingsManager.isCheckForUpdatesEnabled())
134 | return false;
135 |
136 | if (latestReleaseVersion == null)
137 | latestReleaseVersion = getLatestReleaseVersion();
138 |
139 | String[] vals1 = VERSION.split("\\.");
140 | String[] vals2 = latestReleaseVersion.split("\\.");
141 | int i = 0;
142 | // set index to first non-equal ordinal or length of shortest version string
143 | while (i < vals1.length && i < vals2.length && vals1[i].equals(vals2[i]))
144 | {
145 | i++;
146 | }
147 | // compare first non-equal ordinal number
148 | if (i < vals1.length && i < vals2.length)
149 | {
150 | int diff = Integer.valueOf(vals1[i]).compareTo(Integer.valueOf(vals2[i]));
151 | return Integer.signum(diff) == -1;
152 | }
153 | // the strings are equal or one string is a substring of the other
154 | // e.g. "1.2.3" = "1.2.3" or "1.2.3" < "1.2.3.4"
155 | return Integer.signum(vals1.length - vals2.length) == -1;
156 | }
157 |
158 |
159 | /**
160 | * Checks the latest release version on GitHub and returns it.
161 | *
162 | * @return the latest release version on GitHub
163 | */
164 | public static String getLatestReleaseVersion()
165 | {
166 | JSONObject latestReleaseJSON = getLatestReleaseJSON();
167 | if (latestReleaseJSON != null)
168 | {
169 | String version = latestReleaseJSON.getString("tag_name");
170 | String prefix = version.split("[0-9]")[0];
171 | version = version.substring(prefix.length());
172 |
173 | return version;
174 | }
175 | return "0.0";
176 | }
177 |
178 |
179 | /**
180 | * Retrieves the description of the latest release from GitHub.
181 | *
182 | * @return the latest release description
183 | */
184 | public static String getLatestReleaseDescription()
185 | {
186 | JSONObject latestReleaseJSON = getLatestReleaseJSON();
187 | if (latestReleaseJSON != null)
188 | return latestReleaseJSON.getString("body");
189 | return "";
190 | }
191 |
192 |
193 | /**
194 | * Builds the download URL based on the latest release version.
195 | *
196 | * @return the download URL for the latest release
197 | */
198 | public static String getLatestReleaseDownloadURL()
199 | {
200 | String latestVersion = getLatestReleaseVersion();
201 | return "https://github.com/Kahmul/SoulsSpeedruns-Save-Organizer/releases/download/v." + latestVersion + "SoulsSpeedruns.-.Save.Organizer."
202 | + latestVersion + ".zip";
203 | }
204 |
205 |
206 | /**
207 | * Creates a JSONObject of the latest release on GitHub.
208 | *
209 | * @return the JSONObject of the latest release
210 | */
211 | private static JSONObject getLatestReleaseJSON()
212 | {
213 | try (InputStream is = URI.create(LATEST_RELEASE_JSON_URL).toURL().openStream())
214 | {
215 | BufferedReader rd = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8));
216 | String jsonText = readAll(rd);
217 | JSONObject json = new JSONObject(jsonText);
218 | return json;
219 | }
220 | catch (Exception e)
221 | {
222 | return null;
223 | }
224 | }
225 |
226 |
227 | /**
228 | * Reads all the input from a Reader and returns it in a single String.
229 | *
230 | * @param rd the reader
231 | * @return the input
232 | * @throws IOException
233 | */
234 | private static String readAll(Reader rd) throws IOException
235 | {
236 | StringBuilder sb = new StringBuilder();
237 | int cp;
238 | while ((cp = rd.read()) != -1)
239 | {
240 | sb.append((char) cp);
241 | }
242 | return sb.toString();
243 | }
244 |
245 | }
246 |
--------------------------------------------------------------------------------
/src/com/soulsspeedruns/organizer/components/ReadOnlyButton.java:
--------------------------------------------------------------------------------
1 | package com.soulsspeedruns.organizer.components;
2 |
3 |
4 | import java.awt.event.ComponentAdapter;
5 | import java.awt.event.ComponentEvent;
6 | import java.awt.event.MouseEvent;
7 | import java.awt.event.MouseListener;
8 | import java.io.File;
9 |
10 | import javax.swing.JLabel;
11 |
12 | import com.github.weisj.darklaf.LafManager;
13 | import com.github.weisj.darklaf.theme.event.ThemeChangeEvent;
14 | import com.github.weisj.darklaf.theme.event.ThemeChangeListener;
15 | import com.soulsspeedruns.organizer.games.Game;
16 | import com.soulsspeedruns.organizer.games.Profile;
17 | import com.soulsspeedruns.organizer.listeners.ProfileListener;
18 | import com.soulsspeedruns.organizer.listeners.SaveListener;
19 | import com.soulsspeedruns.organizer.listeners.SettingsListener;
20 | import com.soulsspeedruns.organizer.managers.GamesManager;
21 | import com.soulsspeedruns.organizer.managers.IconsAndFontsManager;
22 | import com.soulsspeedruns.organizer.managers.OrganizerManager;
23 | import com.soulsspeedruns.organizer.managers.SavesManager;
24 | import com.soulsspeedruns.organizer.managers.SettingsManager;
25 | import com.soulsspeedruns.organizer.managers.VersionManager;
26 | import com.soulsspeedruns.organizer.savelist.Save;
27 | import com.soulsspeedruns.organizer.savelist.SaveListEntry;
28 |
29 |
30 | /**
31 | * Read Only Button.
32 | *
33 | * Button to make the given file read-only and vice-versa.
34 | *
35 | * @author Kahmul (www.twitch.tv/kahmul78)
36 | * @date 26 Sep 2015
37 | */
38 | public class ReadOnlyButton extends JLabel implements MouseListener, ProfileListener, SaveListener, SettingsListener
39 | {
40 |
41 | private File file;
42 |
43 |
44 | /**
45 | * Creates a new read only button with the given file and image.
46 | *
47 | * @param file the file to associate with this button.
48 | * @param isCompact whether to show the "writable/read-only" text
49 | */
50 | public ReadOnlyButton(File file)
51 | {
52 | setFile(file);
53 |
54 | addMouseListener(this);
55 | GamesManager.addProfileListener(this);
56 | SavesManager.addSaveListener(this);
57 | SettingsManager.addSettingsListener(this);
58 |
59 | LafManager.addThemeChangeListener(new ThemeChangeListener()
60 | {
61 |
62 | @Override
63 | public void themeInstalled(ThemeChangeEvent e)
64 | {
65 | refreshAppearance(false);
66 | }
67 |
68 |
69 | @Override
70 | public void themeChanged(ThemeChangeEvent e)
71 | {
72 | }
73 | });
74 |
75 | OrganizerManager.getMainWindow().addComponentListener(new ComponentAdapter()
76 | {
77 |
78 | public void componentResized(ComponentEvent e)
79 | {
80 | refreshAppearance(false);
81 | }
82 | });
83 |
84 | setVisible(true);
85 | }
86 |
87 |
88 | /**
89 | * Returns the file that is associated with this button.
90 | *
91 | * @return the file that is associated with this button
92 | */
93 | public File getFile()
94 | {
95 | return file;
96 | }
97 |
98 |
99 | /**
100 | * Sets the file to associate this button with.
101 | *
102 | * @param file the file to associate this button with
103 | */
104 | public void setFile(File file)
105 | {
106 | if (file == null || !file.exists())
107 | {
108 | this.file = null;
109 | setVisible(false);
110 | setToolTipText(null);
111 | return;
112 | }
113 | this.file = file;
114 | setVisible(true);
115 | refreshAppearance(false);
116 | }
117 |
118 |
119 | /**
120 | * Simulates a click.
121 | */
122 | public void doClick()
123 | {
124 | if (file == null || !file.exists() || !GamesManager.getSelectedGame().supportsReadOnly())
125 | return;
126 | file.setWritable(!file.canWrite());
127 | refreshAppearance(true);
128 | }
129 |
130 |
131 | /**
132 | * Changes the image of the read-only button depending on the state of the file and whether the mouse is hovered over it.
133 | *
134 | * @param isHovering
135 | */
136 | private void refreshAppearance(boolean isHovering)
137 | {
138 | boolean isWritable = file != null ? file.canWrite() : false;
139 | boolean showText = !SettingsManager.isCompactModeEnabled()
140 | && (!VersionManager.isVersionOutdated() || OrganizerManager.getMainWindow().getWidth() > 700);
141 | if (isWritable)
142 | {
143 | setText(showText ? "Writable" : null);
144 | setIcon(IconsAndFontsManager.getWritableIcon(IconsAndFontsManager.ICON_SIZE_LARGE, isHovering));
145 | setToolTipText("Click to turn on read-only for the game's savefile.");
146 | }
147 | else
148 | {
149 | setText(showText ? "Read-Only" : null);
150 | setIcon(IconsAndFontsManager.getReadOnlyIcon(IconsAndFontsManager.ICON_SIZE_LARGE, isHovering));
151 | setToolTipText("Click to turn off read-only for the game's savefile.");
152 | }
153 |
154 | }
155 |
156 |
157 | @Override
158 | public void setVisible(boolean flag)
159 | {
160 | super.setVisible(flag);
161 |
162 | if (flag)
163 | {
164 | if (!GamesManager.getSelectedGame().supportsReadOnly())
165 | super.setVisible(false);
166 | if (!GamesManager.isAProfileSelected())
167 | super.setVisible(false);
168 | }
169 |
170 | }
171 |
172 |
173 | @Override
174 | public void mouseClicked(MouseEvent e)
175 | {
176 | doClick();
177 | }
178 |
179 |
180 | @Override
181 | public void mouseEntered(MouseEvent e)
182 | {
183 | refreshAppearance(true);
184 | }
185 |
186 |
187 | @Override
188 | public void mouseExited(MouseEvent e)
189 | {
190 | refreshAppearance(false);
191 | }
192 |
193 |
194 | @Override
195 | public void mousePressed(MouseEvent e)
196 | {
197 | }
198 |
199 |
200 | @Override
201 | public void mouseReleased(MouseEvent e)
202 | {
203 | }
204 |
205 |
206 | @Override
207 | public void profileDeleted(Profile profile)
208 | {
209 | }
210 |
211 |
212 | @Override
213 | public void profileCreated(Profile profile)
214 | {
215 | }
216 |
217 |
218 | @Override
219 | public void profileDirectoryChanged(Game game)
220 | {
221 | setFile(game.getSaveFileLocation());
222 | if (file == null || !file.exists() || !game.supportsReadOnly())
223 | setVisible(false);
224 | else
225 | setVisible(true);
226 | }
227 |
228 |
229 | @Override
230 | public void changedToProfile(Profile profile)
231 | {
232 | }
233 |
234 |
235 | @Override
236 | public void changedToGame(Game game)
237 | {
238 | setFile(game.getSaveFileLocation());
239 | if (file == null || !file.exists() || !game.supportsReadOnly())
240 | setVisible(false);
241 | else
242 | setVisible(true);
243 | }
244 |
245 |
246 | @Override
247 | public void entryCreated(SaveListEntry entry)
248 | {
249 | }
250 |
251 |
252 | @Override
253 | public void entryRenamed(SaveListEntry entry)
254 | {
255 | }
256 |
257 |
258 | @Override
259 | public void entrySelected(SaveListEntry entry)
260 | {
261 | }
262 |
263 |
264 | @Override
265 | public void saveLoadStarted(Save save)
266 | {
267 | }
268 |
269 |
270 | @Override
271 | public void saveLoadFinished(Save save)
272 | {
273 | refreshAppearance(false);
274 | }
275 |
276 |
277 | @Override
278 | public void gameFileWritableStateChanged(boolean writeable)
279 | {
280 | refreshAppearance(false);
281 | }
282 |
283 |
284 | @Override
285 | public void settingChanged(String prefsKey)
286 | {
287 | refreshAppearance(false);
288 | }
289 |
290 | }
291 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
25 | * Contains the directory field as well as the browse button.
26 | *
27 | * @author Kahmul (www.twitch.tv/kahmul78)
28 | * @date 28 Sep 2015
29 | */
30 | public class GameConfigDirectoryPanel extends JPanel
31 | {
32 |
33 | /**
34 | * Creates a new directory panel.
35 | *
36 | * @param game the game of this panel
37 | */
38 | protected GameConfigDirectoryPanel(Game game)
39 | {
40 | GroupLayout layout = new GroupLayout(this);
41 | layout.setAutoCreateGaps(true);
42 | layout.setAutoCreateContainerGaps(true);
43 |
44 | File saveFile = game.getSaveFileLocation();
45 | File gameDir = game.getDirectory();
46 |
47 | JLabel saveFileLabel = new JLabel("Savefile Location:");
48 | JLabel directoryLabel = new JLabel("Profiles Directory:");
49 |
50 | JTextField saveFileField = createSaveFileTextField(saveFile, game);
51 | JTextField directoryField = new JTextField(gameDir != null ? gameDir.getPath() : "");
52 |
53 | JButton directoryBrowseButton = createDirectoryBrowseButton(directoryField, game);
54 | JButton saveFileBrowseButton = createSaveFileBrowseButton(saveFileField, directoryField, directoryBrowseButton, game);
55 |
56 | saveFileField.setEditable(false);
57 | directoryField.setEditable(false);
58 |
59 | // Horizontal
60 | GroupLayout.SequentialGroup hGroup = layout.createSequentialGroup();
61 |
62 | hGroup.addGroup(layout.createParallelGroup().addComponent(saveFileLabel).addComponent(saveFileField).addComponent(directoryLabel)
63 | .addComponent(directoryField));
64 | hGroup.addGroup(layout.createParallelGroup()
65 | .addComponent(saveFileBrowseButton, GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE)
66 | .addComponent(directoryBrowseButton, GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE));
67 |
68 | layout.setHorizontalGroup(hGroup);
69 |
70 | // Vertical
71 | GroupLayout.SequentialGroup vGroup = layout.createSequentialGroup();
72 |
73 | vGroup.addGroup(layout.createParallelGroup(Alignment.CENTER).addComponent(saveFileLabel));
74 |
75 | vGroup.addGroup(layout.createParallelGroup(Alignment.CENTER).addComponent(saveFileField).addComponent(saveFileBrowseButton));
76 |
77 | vGroup.addGroup(layout.createParallelGroup(Alignment.CENTER).addComponent(directoryLabel));
78 |
79 | vGroup.addGroup(layout.createParallelGroup(Alignment.CENTER).addComponent(directoryField).addComponent(directoryBrowseButton));
80 |
81 | layout.setVerticalGroup(vGroup);
82 |
83 | setLayout(layout);
84 | }
85 |
86 |
87 | private JTextField createSaveFileTextField(File saveFile, Game game)
88 | {
89 | JTextField saveFileField = new JTextField(saveFile != null ? saveFile.getPath() : "");
90 | saveFileField.putClientProperty(DarkTextUI.KEY_DEFAULT_TEXT, game.getSuggestedSaveLocation());
91 |
92 | return saveFileField;
93 | }
94 |
95 |
96 | /**
97 | * @param saveFileField
98 | * @param game
99 | * @return
100 | */
101 | private JButton createSaveFileBrowseButton(JTextField saveFileField, JTextField directoryField, JButton directoryBrowseButton, Game game)
102 | {
103 | JButton browseButton = new JButton("Browse");
104 |
105 | browseButton.addActionListener(event -> {
106 | JFileChooser fc = new JFileChooser(game.getSaveFilePathOrSuggested());
107 | fc.setSelectedFile(new File(game.getSaveFilePathOrSuggested() + File.separator + game.getSaveName()));
108 | fc.setFileSelectionMode(JFileChooser.FILES_ONLY);
109 | int val = fc.showOpenDialog(SwingUtilities.windowForComponent(this));
110 | if (val == JFileChooser.APPROVE_OPTION)
111 | {
112 | File selectedSavefile = fc.getSelectedFile();
113 | if (selectedSavefile == null || !selectedSavefile.exists())
114 | {
115 | JOptionPane.showMessageDialog(null, "This file doesn't exist!", "Error occurred", JOptionPane.ERROR_MESSAGE);
116 | return;
117 | }
118 | if (selectedSavefile.getName().equalsIgnoreCase(game.getSaveName()))
119 | {
120 | game.setSaveFileLocation(selectedSavefile);
121 | saveFileField.setText(selectedSavefile.getPath());
122 | directoryBrowseButton.setEnabled(true);
123 | int confirm = JOptionPane
124 | .showConfirmDialog(getParent(),
125 | "Do you wish to use the directory of this savefile to store the profiles for this game?"
126 | + " You can choose an alternative directory if you wish.",
127 | "Choosing Savefile", JOptionPane.YES_NO_OPTION);
128 | if (confirm == 0)
129 | {
130 | directoryField.setText(selectedSavefile.getParentFile().getPath());
131 | game.setDirectory(selectedSavefile.getParentFile());
132 | SettingsManager.storeGameProperties(game);
133 | GamesManager.fireProfileDirectoryChangedEvent(game);
134 | return;
135 | }
136 | SettingsManager.storeGameProperties(game);
137 | return;
138 | }
139 | JOptionPane.showMessageDialog(null, "Filename needs to be '" + game.getSaveName() + "'!", "Error occurred",
140 | JOptionPane.ERROR_MESSAGE);
141 | }
142 | });
143 | return browseButton;
144 | }
145 |
146 |
147 | /**
148 | * Creates the browse button.
149 | *
150 | * @param directoryField the directory field of this panel
151 | * @param game the game associated with this panel
152 | * @return the browse button
153 | */
154 | private JButton createDirectoryBrowseButton(JTextField directoryField, Game game)
155 | {
156 | JButton browseButton = new JButton("Browse");
157 | browseButton.setEnabled(game.getSaveFileLocation() != null);
158 |
159 | browseButton.addActionListener(event -> {
160 | if (game.getSaveFileLocation() == null)
161 | {
162 | JOptionPane.showMessageDialog(null, "Choose a savefile location first before deciding on a profile directory!", "Warning",
163 | JOptionPane.WARNING_MESSAGE);
164 | return;
165 | }
166 | JFileChooser fc = new JFileChooser(directoryField.getText());
167 | fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
168 | int val = fc.showOpenDialog(SwingUtilities.windowForComponent(this));
169 | if (val == JFileChooser.APPROVE_OPTION)
170 | {
171 | File selectedDir = fc.getSelectedFile();
172 | if (selectedDir == null || !selectedDir.exists())
173 | {
174 | JOptionPane.showMessageDialog(null, "This directory doesn't exist!", "Error occurred", JOptionPane.ERROR_MESSAGE);
175 | return;
176 | }
177 | directoryField.setText(selectedDir.getPath());
178 | game.setDirectory(selectedDir);
179 | SettingsManager.storeGameProperties(game);
180 | GamesManager.fireProfileDirectoryChangedEvent(game);
181 | }
182 | });
183 | return browseButton;
184 | }
185 |
186 | }
187 |
--------------------------------------------------------------------------------
/src/com/soulsspeedruns/organizer/games/config/GameList.java:
--------------------------------------------------------------------------------
1 | package com.soulsspeedruns.organizer.games.config;
2 |
3 |
4 | import java.awt.Point;
5 | import java.awt.Rectangle;
6 | import java.util.ArrayList;
7 | import java.util.List;
8 |
9 | import javax.swing.BoxLayout;
10 | import javax.swing.JOptionPane;
11 | import javax.swing.JPanel;
12 | import javax.swing.JScrollPane;
13 | import javax.swing.SwingUtilities;
14 | import javax.swing.TransferHandler;
15 | import javax.swing.UIManager;
16 | import javax.swing.border.MatteBorder;
17 |
18 | import com.soulsspeedruns.organizer.games.Game;
19 | import com.soulsspeedruns.organizer.managers.GamesManager;
20 | import com.soulsspeedruns.organizer.managers.SettingsManager;
21 |
22 |
23 | /**
24 | * Game List
25 | *
26 | * A scrollable list of games.
27 | *
28 | * @author Kahmul (www.twitch.tv/kahmul78)
29 | * @date 15 Jan 2024
30 | */
31 | public class GameList extends JScrollPane
32 | {
33 |
34 | private final List
26 | * Class representing the entries in the Savelist.
27 | *
28 | * @author Kahmul (www.twitch.tv/kahmul78)
29 | * @date 7 Jul 2017
30 | */
31 | public abstract class SaveListEntry implements Comparable
4 | Click the banner below to join the SoulsSpeedruns Discord!
27 |
");
39 |
40 | JLabel newReleaseDescriptionLabel = new JLabel(String.format("
SoulsSpeedruns - Save Organizer
2 |
3 |
5 |
6 |
26 |
28 | java -jar "SoulsSpeedruns - Save Organizer.jar" or similar.
53 |
54 | ## Getting Started
55 |
56 | To get started using the save organizer, follow these steps:
57 |
58 | 1. Start the application, press **Edit Games** in the top right.
59 | 2. In the **Games Configuration** window choose the game you wish to create savefiles for on the side.
60 | 3. Click **Browse** under 'Savefile Location' to choose the file the game uses to store its savedata. If you are choosing the savefile for a Souls game, the organizer will try to automatically find the savefile in its usual default location, so most users will just need to confirm the suggested selection. If you already tinkered with savefiles manually, make sure that you select the correct savefile the game uses. This can usually be verified by checking the date it was last modified. See also [Savefile Locations](#savefile-locations).
61 | 4. The application will ask you if you wish to store your savefiles in the same directory where the game's savefile is stored. You can either agree or choose a different one under 'Profiles Directory'. If you already have existing profiles on your PC, you should point the organizer to that directory in this step.
62 | 5. Press **New** to create a new profile for your game. Name it whatever you'd like, e.g. the name of the category you wish to run.
63 | 6. Once you are done creating your profiles you can close the **Games Configuration** window.
64 | 7. Back in the main window you can now choose the game and your profile(s) at the top.
65 | 8. Start creating savefiles by pressing **Import Savestate**, or **Rightclick > Add Folder** to create folders within your profiles. Savefiles will be imported into the selected folder.
66 | 9. Load your created savefiles by pressing **Load Savestate**. Check the next section [Creating/Loading Savefiles](#creatingloading-savefiles) for more information.
67 | 10. *(Optional)* Click the cog button in the bottom right to access settings. You can enable global hotkeys there to conveniently work with the organizer during practice, as well as change the UI theme of the organizer.
68 | 11. *(Optional)* If you wish to add support for other games besides the Souls games, click the + in the top left of the **Games Configuration** window. There you can create a custom game by giving it a name and telling the organizer what file name to expect for the savefile. Once created, the game functions like any other game in the organizer. You can adjust the order of the games via drag and drop if you wish to have your custom games higher up the list.
69 |
70 | ## Creating/Loading Savefiles
71 |
72 | Due to the how the different FROMSoftware games work, there is a few things to consider when creating and loading savefiles.
73 |
74 | - All characters are stored in the single savefile the game uses.
75 | - Generally you want to quit out to the main menu before creating savefiles. This is not strictly necessary but it makes sure all progress has been saved to the savefile beforehand. Users more familiar with the games may want to force a save by e.g. simply opening/closing the start menu. You can then create a savefile during gameplay, or switch to read-only to repeat the upcoming section multiple times without having to quitout an extra time beforehand.
76 | - Loading savefiles only works in the main menu. Loading a savefile in the middle of gameplay will do nothing as the game will simply overwrite it again the next time it saves.
77 | - When loading a savefile in the main menu and looking at your characters, you may notice that nothing has changed. This is normal, the game only updates that info when the main menu is reloaded. If you choose a character slot, the corresponding character from the loaded savefile will be nonetheless correctly loaded.
78 |
79 | ## Savefile Locations
80 |
81 | The organizer should automatically find your savefile when you open the respective file browser as outlined in [Getting Started](#getting-started), so you should not need to search for it.
82 | If you tinkered with your savefiles manually in advance however, or are otherwise curious where the savefiles can be found, here are their default locations on Windows:
83 |
84 | - Dark Souls: C:\Users\\
85 | - Dark Souls Remastered: C:\Users\\
86 | - Dark Souls II: C:\Users\\
87 | - Dark Souls II: SotFS: C:\Users\\
88 | - Dark Souls III: C:\Users\\
89 | - Sekiro: C:\Users\\
90 | - Elden Ring: C:\Users\\
91 |
92 | ## Planned Features
93 |
94 | - Editing savefiles to adjust stats/items for characters (likely only for DS1)
95 |
96 | ## Troubleshooting
97 |
98 | - You can join the [SoulsSpeedruns Discord](https://discord.soulsspeedruns.com) for troubleshooting help.
99 | - Make sure you use Java JRE 8. You can use the bundled version for this.
100 | - If you're on Steam Deck and there is no visible UI on the organizer, you may need to set the _JAVA_AWT_WM_NONREPARENTING=1 environment variable.
101 | - If you have problems starting up the program after you've already been using it there might be conflicting data between the registry entries and the actual data. In this case it might help to remove the registry entries of the Save Organizer (the following steps are for Windows):
102 |
103 | 1. Press Windows + R.
104 | 2. Enter "regedit".
105 | 3. Navigate to "HKEY_CURRENT_USER\Software\JavaSoft\Prefs\com\soulsspeedruns\organizer\prefs".
106 | 4. Delete all entries.
107 |
108 | ## Credits
109 |
110 | - johndisandonato for adding the 'Highlight Previous/Next Savestate' hotkeys
111 |
--------------------------------------------------------------------------------